Asset Experience Mod Walkthrough¶
Difficulty: Intermediate Concepts: Custom items, textures, behavior hooks, recipes, shops, NPC drops
The Asset Experience Mod is a hands-on tutorial mod demonstrating the Custom Assets system. It registers 13 custom items covering every major item category: weapons, armor, accessories, consumables, and materials.
Early Access: This mod is available in beta on Discord. It serves as both a playable example and a reference implementation for modders building custom items.
What It Does¶
Install the mod and press NumPad keys to spawn groups of test items:
- NumPad1 - Melee weapons (fire sword with +100 bonus damage, ice sword that's intentionally broken)
- NumPad2 - Ranged weapons (shadow bow that fires Jester Arrows, plasma gun with dynamic tooltips)
- NumPad3 - Consumables (eternal potion that never runs out, buff potions)
- NumPad4 - Armor set (160 total defense) + emerald accessory
- NumPad5 - All 13 items at once
- NumPad0 - Log all modded items in your inventory
Every item has a custom texture, and most demonstrate one or more behavior hooks (damage bonuses, projectile overrides, use prevention, tooltip injection, etc.).
Key Concepts¶
1. Registering Custom Items¶
Custom items are registered through context.RegisterItem() with an ItemDefinition that sets all properties:
context.RegisterItem("power-sword", new ItemDefinition
{
DisplayName = "Power Test Sword",
Tooltip = new[] { "Extreme damage test sword", "+100 bonus damage" },
Texture = "assets/textures/tx_sword_fire.png",
Damage = 500,
KnockBack = 15f,
UseTime = 20,
UseAnimation = 20,
UseStyle = 1, // Swing
Melee = true,
AutoReuse = true,
Width = 40,
Height = 40,
Rarity = 11,
Value = 1000000
});
Each item gets a real runtime type ID (starting at 6145). This means custom items work exactly like vanilla items: they stack, sort, save/load, and display in the UI normally.
2. Custom Textures¶
Place PNG files in your mod's assets/textures/ folder and reference them in the ItemDefinition:
my-mod/
├── assets/
│ └── textures/
│ ├── tx_sword_fire.png
│ ├── tx_potion_red.png
│ └── tx_helm_dragon.png
├── manifest.json
└── MyMod.dll
context.RegisterItem("fire-sword", new ItemDefinition
{
Texture = "assets/textures/tx_sword_fire.png",
// ... other properties
});
Textures are loaded once and cached. The path is relative to your mod's folder.
3. Behavior Hooks¶
Hooks let custom items run code at specific points. Here are the hooks demonstrated by this mod:
ModifyWeaponDamage - Add bonus damage at calculation time:
context.RegisterItem("power-sword", new ItemDefinition
{
Damage = 500,
ModifyWeaponDamage = (object player, ref int damage) =>
{
damage += 100; // +100 bonus on top of base damage
}
});
CanUseItem - Prevent an item from being used:
context.RegisterItem("ice-sword", new ItemDefinition
{
Damage = 300,
CanUseItem = (player) => false // Can never swing this sword
});
OnShoot - Override the projectile a ranged weapon fires:
context.RegisterItem("shadow-bow", new ItemDefinition
{
UseAmmo = 40, // Arrow ammo type
Shoot = 1, // Default arrow projectile
OnShoot = (player, origProj, speed) => 5 // Always fire Jester Arrows
});
OnConsume - Control whether a consumable is used up:
context.RegisterItem("red-potion", new ItemDefinition
{
Consumable = true,
BuffType = 5, // Ironskin
BuffTime = 3600, // 60 seconds
OnConsume = (player) => false // Never consumed - infinite uses
});
ModifyTooltips - Add dynamic lines to an item's tooltip:
context.RegisterItem("plasma-gun", new ItemDefinition
{
ModifyTooltips = (lines) =>
{
double time = Main.time;
bool day = Main.dayTime;
lines.Add($"Game time: {(day ? "Day" : "Night")} {time:F0}");
}
});
OnHitNPC - React when a weapon hits an enemy:
OnHitNPC = (player, npc, damage, kb, crit) =>
{
_log.Info($"Hit for {damage} damage! Crit: {crit}");
}
UpdateEquip - Run logic each frame while an accessory is equipped:
context.RegisterItem("emerald-amulet", new ItemDefinition
{
Accessory = true,
UpdateEquip = (player) =>
{
// Runs every frame while equipped
// Add stat bonuses, trigger effects, etc.
}
});
Tip: Per-frame hooks like
UpdateEquipandOnHoldItemfire 60 times per second. Use a counter to throttle logging or expensive operations.
4. Armor Slots¶
Armor items use slot IDs to borrow vanilla armor visuals:
context.RegisterItem("dragon-helmet", new ItemDefinition
{
DisplayName = "Dragon Helmet",
Defense = 50,
HeadSlot = 1, // Borrows Iron Helmet visuals
Width = 20,
Height = 20,
Rarity = 5
});
context.RegisterItem("dragon-chestplate", new ItemDefinition
{
Defense = 60,
BodySlot = 1, // Borrows Iron Chestplate visuals
});
context.RegisterItem("dragon-leggings", new ItemDefinition
{
Defense = 50,
LegSlot = 1, // Borrows Iron Greaves visuals
});
Set HeadSlot, BodySlot, or LegSlot to make items equippable in armor slots. The slot ID determines which vanilla armor sprites are used for the player's appearance.
5. Recipes¶
Register crafting recipes that use your custom items:
context.RegisterRecipe(new RecipeDefinition
{
Result = "asset-experience-mod:power-sword", // modId:itemName
ResultStack = 1,
Ingredients = { { "IronBar", 10 }, { "Gel", 5 } },
Station = "Anvils"
});
Ingredients can reference vanilla items by name or ID. The result uses modId:itemName format for custom items. Station names match Terraria's crafting station groups (Anvils, Furnaces, Bottles, WorkBenches, etc.).
6. Shop Entries and NPC Drops¶
Add custom items to NPC shops:
context.AddShopItem(new ShopDefinition
{
NpcType = 17, // Merchant
ItemId = "asset-experience-mod:emerald-amulet",
Price = 100000 // 10 gold (in copper coins)
});
Register items as NPC drops:
context.RegisterDrop(new DropDefinition
{
NpcType = 1, // Blue Slime
ItemId = "asset-experience-mod:mythril-ore",
Chance = 0.25f,
MinStack = 1,
MaxStack = 3
});
7. Spawning Items via Code¶
Items have real type IDs, so you can spawn them with SetDefaults:
private void SpawnItem(string itemName)
{
var player = Main.LocalPlayer;
string fullId = $"{Id}:{itemName}";
int runtimeType = ItemRegistry.GetRuntimeType(fullId);
if (runtimeType < 0) return;
for (int i = 0; i < 50; i++)
{
if (player.inventory[i] == null || player.inventory[i].IsAir)
{
player.inventory[i] = new Item();
player.inventory[i].SetDefaults(runtimeType);
return;
}
}
}
ItemRegistry.GetRuntimeType() resolves the modId:itemName string to the integer type ID that Terraria's item system uses internally.
Complete Item Reference¶
| Item | Category | Key Hook | Effect |
|---|---|---|---|
| Power Test Sword | Melee | ModifyWeaponDamage, OnHitNPC | +100 bonus damage, hit logging |
| Ice Test Sword | Melee | CanUseItem | Intentionally broken (can't swing) |
| Shadow Bow | Ranged | OnShoot | Converts any arrow to Jester Arrow |
| Plasma Gun | Ranged | ModifyTooltips, OnHoldItem | Dynamic tooltip with game time |
| Red Test Potion | Consumable | OnConsume | Gives Ironskin, never consumed |
| Blue Test Potion | Consumable | OnUse | Gives Wrath, logs on use |
| Green Test Potion | Consumable | (none) | Gives Regeneration (vanilla behavior) |
| Dragon Helmet | Armor | (none) | 50 defense, head slot |
| Dragon Chestplate | Armor | (none) | 60 defense, body slot |
| Dragon Leggings | Armor | (none) | 50 defense, legs slot |
| Emerald Amulet | Accessory | UpdateEquip | Periodic tick logging |
| Custom Mythril Ore | Material | (none) | Stackable, used in recipes, drops from Blue Slimes |
| Shadow Ore | Material | (none) | Stackable material |
Configuration¶
| Config Key | Type | Default | Description |
|---|---|---|---|
enabled |
bool | true | Load custom items and enable keybinds |
Lessons Learned¶
- Items get real type IDs - Custom items are indistinguishable from vanilla at runtime. Sorting, stacking, saving all work automatically.
- One registration call does everything -
RegisterItemhandles type allocation, texture loading, tooltip building, and hook wiring in a singleItemDefinition. - Hooks are optional - Items without hooks (Green Test Potion, armor pieces, ores) work with pure vanilla behavior. Only add hooks when you need custom logic.
- Throttle per-frame hooks -
UpdateEquipandOnHoldItemfire every frame. Use a counter (if (counter++ % 300 == 0)) to avoid log spam or expensive work. - CanUseItem returning false is powerful - It completely prevents item use while keeping the item visible and equippable. Useful for conditional items.
- OnConsume returning false creates infinite consumables - The buff still applies but the item is never consumed.
- Textures are simple - Drop PNGs in
assets/textures/, reference the path in the definition, done. - Recipes, shops, and drops are separate registrations - Keep item definitions focused on the item itself, register integration points separately.
Getting the Mod¶
Asset Experience Mod is currently in early access. Join the Discord to download it and share feedback.
For the full Custom Assets API reference, see Core API Reference - Custom Assets System.