Skip to content

Core API Reference

This documents the validated APIs in TerrariaModder.Core that are proven to work.

IMod Interface

Every mod must implement IMod:

public interface IMod
{
    string Id { get; }       // Must match manifest.json
    string Name { get; }
    string Version { get; }

    void Initialize(ModContext context);
    void Unload();
}

For world lifecycle hooks, optionally implement IModLifecycle:

public interface IModLifecycle
{
    void OnContentReady(ModContext context);  // All mods initialized, runtime type IDs assigned
    void OnWorldLoad();                       // Entering a world
    void OnWorldUnload();                     // Leaving a world
}

Why two interfaces? IModLifecycle is separate for backward compatibility. Old mods compiled against earlier Core versions that only implement IMod will still load correctly — the framework checks for IModLifecycle via type casting and only calls the hooks if the mod implements it.

Recompilation required for Core 0.4.0+: While old DLLs that only use IMod + Initialize + Unload will load, mods that reference changed APIs (like the old IModConfig interface) need to be recompiled against the new Core. The injector handles this gracefully — incompatible mods log a clear error and are skipped, without crashing the game or affecting other mods.

IMod Lifecycle

  1. Initialize() - Called when mod loads. Set up config, keybinds, UI, events.
  2. OnContentReady() (IModLifecycle) - Called after all mods initialized and runtime type IDs assigned. Use for cross-mod item lookups (context.GetItemType, context.TryGetItemType).
  3. OnWorldLoad() (IModLifecycle) - Called when entering a world.
  4. OnWorldUnload() (IModLifecycle) - Called when leaving a world.
  5. Unload() - Called when game closes. Clean up resources.

Injector Lifecycle Hooks

The injector discovers public static void methods on any type in your mod assembly and calls them at specific points during game startup. These are separate from the IMod interface; they fire for the assembly, not per-mod-instance.

public class Mod : IMod
{
    // IMod methods (Initialize, OnWorldLoad, etc.) ...

    /// Called after Main.Initialize() completes.
    /// GraphicsDevice, Window.Handle, Main.instance all ready.
    /// Use for manual Harmony patches that need game types resolved.
    public static void OnGameReady()
    {
        // Safe to patch, access GraphicsDevice, etc.
    }

    /// Called after Main.LoadContent() completes.
    /// NOTE: Fires BEFORE OnGameReady (XNA calls LoadContent from within Initialize).
    public static void OnContentLoaded()
    {
        // Textures and fonts loaded, but patches may not be applied yet.
    }

    /// Called on the first Main.Update() frame.
    /// Full game loop is running.
    public static void OnFirstUpdate()
    {
        // One-time setup needing the game loop active.
    }

    /// Called when game is exiting (before Terraria disposes systems).
    public static void OnShutdown()
    {
        // Save state, final cleanup.
    }
}
Hook When Use For
OnGameReady After Main.Initialize() Manual Harmony patches, GraphicsDevice access
OnContentLoaded After Main.LoadContent() Custom texture loading (but see timing note)
OnFirstUpdate First Main.Update() Setup requiring full game loop
OnShutdown Game exiting Save state, cleanup

Timing: OnContentLoaded fires BEFORE OnGameReady because XNA calls LoadContent() from within Initialize(). If you need both content and patches ready, use OnGameReady since LoadContent has already completed by then.

See Harmony Basics for details on when to use lifecycle hooks vs attribute patches.

ModContext

Provided to Initialize(), gives access to framework services:

public void Initialize(ModContext context)
{
    // Logging
    ILogger log = context.Logger;

    // Typed configuration (returns your ModConfig subclass, or null if none registered)
    var config = context.GetConfig<MyModConfig>();

    // Mod folder path
    string path = context.ModFolder;

    // Manifest data
    ModManifest manifest = context.Manifest;

    // Register keybinds
    context.RegisterKeybind("id", "Name", "Description", "Key", callback);
}

ModContext Members

Member Type Description
Logger ILogger Per-mod logger instance
Config ModConfig Mod configuration base (use GetConfig<T>() for typed access)
ModFolder string Path to the mod's folder
Manifest ModManifest Parsed manifest.json data

Custom Item Registration

// Register a custom item
bool success = context.RegisterItem("fire-sword", new ItemDefinition
{
    DisplayName = "Flame Blade",
    Tooltip = new[] { "Shoots fireballs", "Burns enemies on contact" },
    Damage = 50,
    Melee = true,
    UseStyle = 1,
    Rarity = 5,
    Value = 50000
});

// Get all items registered by this mod
IEnumerable<string> items = context.GetItems();

See Custom Assets for full ItemDefinition, RecipeDefinition, ShopDefinition, and DropDefinition documentation.

Debug Command Registration

// Register a debug command (namespaced as "modid.name")
context.RegisterCommand("status", "Show mod status", args =>
{
    CommandRegistry.Write("Status: OK");
});

// Get all commands registered by this mod
IReadOnlyList<CommandInfo> cmds = context.GetCommands();

Commands are executed via the Debug Console (Ctrl+) or programmatically viaCommandRegistry.Execute()`.

Keybind Registration

// Register with string key
Keybind kb = context.RegisterKeybind(
    keybindId: "toggle",
    label: "Toggle Feature",
    description: "Turn feature on/off",
    defaultKey: "F5",
    callback: OnToggle
);

// Register with modifier keys using string format
Keybind kb2 = context.RegisterKeybind(
    keybindId: "action",
    label: "Action",
    description: "Do something",
    defaultKey: "Ctrl+G",  // String format with modifiers
    callback: OnAction
);

// Retrieve registered keybinds
Keybind myKeybind = context.GetKeybind("toggle");
IEnumerable<Keybind> allMyKeybinds = context.GetKeybinds();

ILogger

Per-mod logger that writes to the shared log file:

private ILogger _log;

_log.Debug("Verbose info");     // For troubleshooting
_log.Info("Normal message");    // Standard logging
_log.Warn("Potential issue");   // Warnings
_log.Error("Something failed"); // Errors
_log.Error("Failed", exception); // Error with exception details

Properties:

Property Type Description
MinLevel LogLevel Minimum level to output (messages below are ignored)
ModId string The mod ID this logger is for

Log output format:

[2024-01-30 12:34:56] [INFO ] [my-mod] Normal message

All logs go to TerrariaModder/core/logs/terrariamodder.log.

Config System

Configuration is defined by subclassing ModConfig with typed properties and attributes. The framework reflects on the class to generate settings UI automatically and serializes to JSON. No config_schema in manifest.json is needed.

Defining a Config Class

using TerrariaModder.Core.Config;

public class MyModConfig : ModConfig
{
    public override int Version => 1;

    [Client, Label("Enabled"), Description("Enable or disable the mod.")]
    public bool Enabled { get; set; } = true;

    [Client, Label("Count"), Description("Maximum item count."), Range(1, 100)]
    public int Count { get; set; } = 10;

    [Client, Label("Speed"), Description("Movement speed multiplier."), Range(0.1f, 10.0f)]
    public float Speed { get; set; } = 1.5f;

    [Client, Label("Player Name"), Description("Display name.")]
    public string PlayerName { get; set; } = "Player";
}

Using Config in Your Mod

// In Initialize() or OnContentReady():
var config = context.GetConfig<MyModConfig>();
bool enabled = config.Enabled;  // Direct typed property access
int count = config.Count;

// Modify and save
config.Count = 25;
config.Save();

// Other operations
config.Reload();          // Reload from disk
config.ResetToDefaults(); // Reset all properties to declared defaults

Config Attributes

Attribute Description
[Client] Client-scoped property (default if untagged)
[Server] Server-scoped property (synced to clients in multiplayer)
[Label("...")] Display label in settings UI
[Description("...")] Tooltip description
[Range(min, max)] Numeric range constraint (shows slider in UI)
[Options("a", "b", "c")] Dropdown options for string properties
[RestartRequired] Marks property as requiring restart to take effect
[FormerlySerializedAs("oldName")] Migration support for renamed properties

ModConfig Members

Member Description
Version Abstract property — config schema version for migration
Save() Persist current values to disk
Reload() Reload from disk
ResetToDefaults() Reset all properties to declared default values
HasChangesFromBaseline() True if any property differs from startup value
HasRestartRequiredChanges() True if any [RestartRequired] property changed
GetPropertyMetadata() Get metadata for all config properties
FilePath Path to the config JSON file

Config files are stored in TerrariaModder/core/configs/{mod-id}.client.json (and .server.json for server-scoped properties).

Migration from Legacy Config

If your mod previously used the old config_schema system (per-mod config.json files in mods/{id}/), user settings are automatically migrated to the new system on first load:

  1. Legacy path (mods/{id}/config.json) → migrated to core/configs/{id}.client.json
  2. Key format — camelCase keys are matched case-insensitively to PascalCase properties
  3. FormerlySerializedAs — use [FormerlySerializedAs("oldName")] on properties you've renamed

The automatic migration ensures existing users keep their settings when you update your mod. However, we encourage all mod authors to adopt the new class-based config system — it provides: - Auto-generated F6 settings UI (no manual UI code needed) - [Server]/[Client] scoping for multiplayer - Type-safe properties with validation attributes ([Range], [Options]) - Hot reload via OnConfigChanged()

The legacy migration layer will eventually be removed in a future Core version. Plan to migrate your config when convenient.

Hot Reload Support

Implement OnConfigChanged() in your mod class to handle live config updates from the mod menu.

Note: This method is discovered via reflection - it's not part of the IMod interface. Just add it as a public void method on your mod class.

public class Mod : IMod
{
    private MyModConfig _config;
    private ModContext _context;

    public void Initialize(ModContext context)
    {
        _context = context;
        _config = context.GetConfig<MyModConfig>();
    }

    // Called by mod menu automatically when a config value changes
    public void OnConfigChanged()
    {
        _context.Logger.Info($"Config reloaded: Enabled={_config.Enabled}, Count={_config.Count}");
    }
}

All config changes are saved to disk immediately. If you implement OnConfigChanged(), the mod is also notified in real-time. Without it, the mod menu shows "Game Restart Required" since changes only take effect after restart.

Version Migration

Override Migrate() to handle schema changes between versions:

public class MyModConfig : ModConfig
{
    public override int Version => 2;

    [Client, Label("Scan Radius")]
    public int ScanRadius { get; set; } = 30;

    protected override void Migrate(Dictionary<string, object> raw, int fromVersion)
    {
        if (fromVersion < 2 && raw.ContainsKey("oldRadius"))
        {
            raw["ScanRadius"] = raw["oldRadius"];
            raw.Remove("oldRadius");
        }
    }
}

Keybinds

Registering in manifest.json

{
  "keybinds": [
    {
      "id": "toggle",
      "label": "Toggle Feature",
      "description": "Turn feature on/off",
      "default": "F5"
    }
  ]
}

Note: Use "default" not "default_key" for the default key binding.

Registering in Code

context.RegisterKeybind(
    id: "toggle",
    label: "Toggle Feature",
    description: "Turn feature on/off",
    defaultKey: "F5",
    callback: OnToggle
);

private void OnToggle()
{
    _enabled = !_enabled;
    _log.Info($"Feature is now {(_enabled ? "ON" : "OFF")}");
}

Keybind Persistence

User keybinds are automatically saved to TerrariaModder/core/keybinds.json and persist across game restarts:

{
  "quick-keys.auto-torch": "NumPad1",
  "item-spawner.toggle": "F7"
}

When a mod loads, any saved bindings are automatically restored.

Restart Detection

// Check if keybinds changed since startup
bool keybindsChanged = KeybindManager.HasKeybindChangesFromBaseline(modId);

// Check if config changed since startup
bool configChanged = config.HasChangesFromBaseline();

Used by ModMenu to show "Game Restart Required" for mods without OnConfigChanged().

KeybindManager (Static)

Central registry for all keybinds.

Properties:

Property Type Description
Keybinds IReadOnlyList<Keybind> All registered keybinds
Enabled bool Enable/disable all keybind processing

Methods:

// Get all keybinds
IReadOnlyList<Keybind> all = KeybindManager.GetAllKeybinds();

// Get keybind by full ID (modId.keybindId)
Keybind kb = KeybindManager.GetKeybind("quick-keys.auto-torch");

// Get all keybinds for a mod
IEnumerable<Keybind> modKeybinds = KeybindManager.GetKeybindsForMod("quick-keys");

// Change a keybind
KeybindManager.SetBinding("quick-keys.auto-torch", KeyCombo.Parse("NumPad1"));

// Reset to default
KeybindManager.ResetToDefault("quick-keys.auto-torch");

// Get conflicts between mods
List<KeybindConflict> conflicts = KeybindManager.GetConflicts();

Keybind Class

Represents a single registered keybind.

Properties:

Property Type Description
Id string Full ID (modId.keybindId format)
ModId string Mod that owns this keybind
Label string Display label
Description string Tooltip text
DefaultKey KeyCombo Default key combination
CurrentKey KeyCombo Current key combination (settable)
Callback Action Callback invoked when pressed
Enabled bool Whether this keybind is enabled

Methods:

keybind.ResetToDefault();  // Reset to default key

KeyCombo Class

Represents a key combination (key + modifiers).

Properties:

Property Type Description
Key int Key code (from KeyCode constants)
Ctrl bool Ctrl modifier
Shift bool Shift modifier
Alt bool Alt modifier

Methods:

// Parse from string
KeyCombo combo = KeyCombo.Parse("Ctrl+Shift+F5");

// Check state
bool pressed = combo.IsPressed();       // Currently held
bool just = combo.JustPressed();        // Just pressed this frame

// Other
string str = combo.ToString();          // "Ctrl+Shift+F5"
KeyCombo copy = combo.Clone();

InputState (Static)

Low-level keyboard and mouse state tracking.

Key State Methods:

bool down = InputState.IsKeyDown(keyCode);           // Key currently held
bool justPressed = InputState.IsKeyJustPressed(keyCode);  // Just pressed this frame
bool justReleased = InputState.IsKeyJustReleased(keyCode); // Just released this frame

Modifier Methods:

bool ctrl = InputState.IsCtrlDown();
bool shift = InputState.IsShiftDown();
bool alt = InputState.IsAltDown();

Properties:

Property Type Description
ScrollWheelDelta int Scroll wheel change since last frame

Utility:

// Check if input should be blocked (chat open, menu, etc.)
if (InputState.ShouldBlockInput()) return;

Note: InputState.Update() is called automatically by the framework each frame.

Key Names

Key strings are parsed by KeyCombo.Parse(). Modifier combos use + separator (e.g., "Ctrl+Shift+F1").

Common key names: - Letters: A through Z - Numbers: D0 through D9 - Function keys: F1 through F12 - Numpad: NumPad0 through NumPad9 - Numpad operators: Multiply, Add, Subtract, Decimal, Divide - Special: Space, Enter, Tab, Escape - Modifiers: LeftControl, RightControl, LeftShift, RightShift, LeftAlt, RightAlt - Mouse: MouseRight or RMB, MouseMiddle or MMB - Others: OemTilde (tilde key), Home, End, Insert, Delete, PageUp, PageDown

Note: MouseLeft cannot be used for keybinds - it's reserved for UI interaction (clicking the rebind button itself).

FrameEvents

Subscribe to per-frame game events:

using TerrariaModder.Core.Events;

public void Initialize(ModContext context)
{
    FrameEvents.OnPreUpdate += OnPreUpdate;
    FrameEvents.OnPostUpdate += OnPostUpdate;
    FrameEvents.OnPreDraw += OnPreDraw;
    FrameEvents.OnPostDraw += OnPostDraw;
    FrameEvents.OnUIOverlay += OnDraw;
}

public void Unload()
{
    FrameEvents.OnPreUpdate -= OnPreUpdate;
    FrameEvents.OnPostUpdate -= OnPostUpdate;
    FrameEvents.OnPreDraw -= OnPreDraw;
    FrameEvents.OnPostDraw -= OnPostDraw;
    FrameEvents.OnUIOverlay -= OnDraw;
}

FrameEvents Members

Event Description
OnPreUpdate Before game updates each frame. Use for input processing.
OnPostUpdate After game updates each frame. Use for state reactions.
OnPreDraw Before game draws each frame. Use for preparing draw data.
OnPostDraw After game draws each frame. Use for custom rendering.
OnUIOverlay Just before cursor is drawn. Use this for custom UI panels.

Important: Always unsubscribe from events in Unload() to prevent memory leaks.

GameEvents

World and state events:

GameEvents.OnWorldLoad += () => { };
GameEvents.OnWorldUnload += () => { };
GameEvents.OnDayStart += () => { };
GameEvents.OnNightStart += () => { };
GameEvents.OnWorldSave += () => { };      // Before save
GameEvents.OnReturnToMenu += () => { };   // Exiting to menu

PlayerEvents

PlayerEvents.OnPlayerSpawn += (args) => { };   // PlayerSpawnEventArgs
PlayerEvents.OnPlayerDeath += (args) => { };   // PlayerDeathEventArgs
PlayerEvents.OnPlayerHurt += (args) => { };    // PlayerHurtEventArgs (Cancellable)
PlayerEvents.OnBuffApplied += (args) => { };   // BuffEventArgs
PlayerEvents.OnPlayerUpdate += (args) => { };  // Per-frame

Event Args Properties:

Event Args Extends Properties
PlayerSpawnEventArgs PlayerEventArgs PlayerIndex, Player, SpawnX, SpawnY
PlayerDeathEventArgs PlayerEventArgs PlayerIndex, Player, Damage, DeathReason
PlayerHurtEventArgs CancellableEventArgs PlayerIndex, Player, Damage, HitDirection, PvP, Cancelled
BuffEventArgs PlayerEventArgs PlayerIndex, Player, BuffType, Duration

Note: PlayerHurtEventArgs extends CancellableEventArgs (not PlayerEventArgs) and defines its own PlayerIndex/Player properties.

ItemEvents

ItemEvents.OnItemPickup += (args) => { };  // ItemPickupEventArgs (Cancellable)
ItemEvents.OnItemUse += (args) => { };     // ItemUseEventArgs (Cancellable)
ItemEvents.OnItemDrop += (args) => { };    // ItemDropEventArgs (Cancellable)

Event Args Properties:

Event Args Properties
ItemPickupEventArgs PlayerIndex, Player, ItemType, Item, Stack, Cancelled
ItemUseEventArgs PlayerIndex, Player, ItemType, Item, Cancelled
ItemDropEventArgs ItemType, Stack, X, Y, Cancelled

NPCEvents

NPCEvents.OnNPCSpawn += (args) => { };   // NPCSpawnEventArgs
NPCEvents.OnBossSpawn += (args) => { };  // BossSpawnEventArgs
NPCEvents.OnNPCDeath += (args) => { };   // NPCDeathEventArgs
NPCEvents.OnNPCHit += (args) => { };     // NPCHitEventArgs (Cancellable)

Event Args Properties:

Event Args Extends Properties
NPCSpawnEventArgs NPCEventArgs NPCIndex, NPC, NPCType, SpawnX (float), SpawnY (float)
BossSpawnEventArgs NPCEventArgs NPCIndex, NPC, NPCType, BossName
NPCDeathEventArgs NPCEventArgs NPCIndex, NPC, NPCType, LastDamage, KillerPlayerIndex
NPCHitEventArgs CancellableEventArgs NPCIndex, NPC, Damage, Knockback, HitDirection, Crit, Cancelled

Note: NPCHitEventArgs extends CancellableEventArgs (not NPCEventArgs) and defines its own NPCIndex/NPC properties.

WorldEvents

Currently untested. These event args are defined but no events currently fire them. Reserved for future use.

// WorldEventArgs: WorldName, WorldId, IsNewWorld
// TimeEventArgs: Time, IsDay

Event Args Base Classes

ModEventArgs - Base class for all mod events:

Property Type Description
Timestamp DateTime When the event was created (set to DateTime.Now)

CancellableEventArgs - Extends ModEventArgs for cancellable events:

Member Type Description
Cancelled bool Set to true to cancel the event
Cancel() void Convenience method - sets Cancelled = true

Cancellable Events

Set args.Cancelled = true or call args.Cancel() to prevent the action:

PlayerEvents.OnPlayerHurt += (args) => {
    if (ShouldBlockDamage(args))
        args.Cancelled = true;  // Or: args.Cancel();
};

UIRenderer

Static class for drawing custom UI. Use in FrameEvents.OnUIOverlay handler.

Drawing Methods

using TerrariaModder.Core.UI;

void OnDraw()
{
    // Filled rectangle (RGBA or Color4)
    UIRenderer.DrawRect(x, y, width, height, r, g, b, alpha);
    UIRenderer.DrawRect(x, y, width, height, UIColors.PanelBg);

    // Rectangle outline
    UIRenderer.DrawRectOutline(x, y, width, height, r, g, b, alpha, thickness);
    UIRenderer.DrawRectOutline(x, y, width, height, UIColors.Border, thickness);

    // Panel (background + border)
    UIRenderer.DrawPanel(x, y, width, height, bgR, bgG, bgB, bgA);
    UIRenderer.DrawPanel(x, y, width, height, UIColors.PanelBg);

    // Text (with border for readability)
    UIRenderer.DrawText("text", x, y, r, g, b, alpha);
    UIRenderer.DrawText("text", x, y, UIColors.Text);

    // Text with shadow (same as DrawText, uses Utils.DrawBorderString)
    UIRenderer.DrawTextShadow("text", x, y, r, g, b, alpha);
    UIRenderer.DrawTextShadow("text", x, y, UIColors.Text);

    // Text at smaller scale (0.75x)
    UIRenderer.DrawTextSmall("small text", x, y, r, g, b, alpha);
    UIRenderer.DrawTextSmall("small text", x, y, UIColors.TextDim);

    // Text with custom scale
    UIRenderer.DrawTextScaled("scaled", x, y, r, g, b, alpha, 1.5f);

    // Draw item icon (for item spawners, inventories, etc.)
    UIRenderer.DrawItem(itemType, x, y, width, height, alpha);

    // Load and draw custom PNG textures (cached, aspect-ratio preserved)
    object tex = UIRenderer.LoadTexture("path/to/image.png");
    UIRenderer.DrawTexture(tex, x, y, width, height, alpha);
}

Most drawing methods support both (r, g, b, a) byte parameters and Color4 struct overloads. The Color4 overloads work with the UIColors palette for consistent theming. Note: DrawTextScaled only has the RGBA overload; use DrawText with Color4 for themed text.

Text Measurement

// Measure text width using the game font (returns pixel width)
int width = UIRenderer.MeasureText("Hello world");

// For truncation and layout, prefer TextUtil from the Widget Library:
int width2 = TextUtil.MeasureWidth("Hello world");
string truncated = TextUtil.Truncate("Very long text", 200);

Scissor Clipping (for scrollable regions)

// Clip drawing to a rectangular region
UIRenderer.BeginClip(x, y, width, height);
// ... draw content that may extend outside bounds ...
UIRenderer.EndClip();

// Check if currently clipping
bool clipping = UIRenderer.IsClipping;

Mouse Input

// Mouse position
int mx = UIRenderer.MouseX;
int my = UIRenderer.MouseY;

// Mouse buttons
bool leftDown = UIRenderer.MouseLeft;        // Left button held
bool leftClick = UIRenderer.MouseLeftClick;  // Left button just clicked (one frame)
bool rightDown = UIRenderer.MouseRight;      // Right button held
bool rightClick = UIRenderer.MouseRightClick; // Right button just clicked

// Scroll wheel
int scroll = UIRenderer.ScrollWheel;         // Scroll delta

// Hit testing
bool hovering = UIRenderer.IsMouseOver(x, y, width, height);

Screen Dimensions

int screenW = UIRenderer.ScreenWidth;
int screenH = UIRenderer.ScreenHeight;

Input Blocking

// Block mouse input from reaching the game (for custom UI)
UIRenderer.BlockInput(true);

// Clear click state (prevent click from propagating)
UIRenderer.ConsumeClick();

// Consume scroll wheel (prevent hotbar scrolling)
UIRenderer.ConsumeScroll();

// Block keyboard when waiting for key input (e.g., keybind rebinding)
// This prevents pressed keys from activating hotbar slots
UIRenderer.IsWaitingForKeyInput = true;

Input Blocking Details: - BlockInput(true) sets Main.blockMouse, Player.mouseInterface, PlayerInput.WritingText, and clears player control flags - ConsumeScroll() clears both ScrollWheelDelta (hotbar) and ScrollWheelDeltaForUI (UI) - IsWaitingForKeyInput is specifically for scenarios like keybind rebinding where you need to capture a key press without it activating game actions. Set this immediately when entering key capture mode.

Panel Registration

There are two approaches to panel management:

Recommended: Use the Widget Library's DraggablePanel (see Widget Library). It handles panel bounds, z-order, input blocking, and draw registration automatically. This is the preferred approach for new mods.

Manual approach: Register panel bounds directly for custom UI without the widget library:

// Register panel bounds (call every frame while UI is visible)
UIRenderer.RegisterPanelBounds("my-panel-id", x, y, width, height);

// When UI closes
UIRenderer.UnregisterPanelBounds("my-panel-id");

// Check if mouse is over any registered panel
bool overPanel = UIRenderer.IsMouseOverAnyPanel();
bool overMyPanel = UIRenderer.IsMouseOverPanel("my-panel-id");

// Check if blocking is active (any panels registered)
bool blocking = UIRenderer.IsBlocking;

Panel draw registration - Register a draw callback for z-order management:

// Register (panel draws in correct z-order, click-to-focus works)
UIRenderer.RegisterPanelDraw("my-panel-id", MyDrawCallback);

// Bring to front (drawn last, gets clicks first)
UIRenderer.BringToFront("my-panel-id");

// Unregister when closing
UIRenderer.UnregisterPanelDraw("my-panel-id");

Panel z-order - When multiple panels are open, use z-order checking:

// Returns true if a higher-z-order panel should handle input instead
if (UIRenderer.ShouldBlockForHigherPriorityPanel("my-panel-id")) return;

Panels use dynamic z-order with click-to-focus. Clicking on a panel brings it to the front. The most recently focused panel has the highest priority. ModMenu always stays on top when open.

Click-through prevention: When panels are registered and the mouse is over them, the framework automatically prevents clicks from passing through to: - Inventory slots - via ItemSlot.Handle patch - HUD buttons (Quick Stack, Bestiary, Sort, Smart Stack) - via PlayerInput.IgnoreMouseInterface patch

Best practice for modal UIs: 1. Use DraggablePanel from the Widget Library (handles everything automatically) 2. Or manually call RegisterPanelBounds() every frame while visible 3. Call UnregisterPanelBounds() when closing 4. Set IsWaitingForKeyInput = true only when actively capturing keyboard (search fields, keybind rebinding)

Text Input (Advanced)

Recommended: Use the TextInput widget from the Widget Library instead of these low-level methods.

For mods that need direct keyboard text input (like search boxes):

// Enable text input mode (call in both Update and Draw)
UIRenderer.EnableTextInput();

// Handle IME input
UIRenderer.HandleIME();

// Clear input state when starting
UIRenderer.ClearInput();

// Get input text (call in Draw phase)
string newText = UIRenderer.GetInputText(currentText);

// Check if Escape was pressed
if (UIRenderer.CheckInputEscape())
{
    // User pressed Escape, close text input
}

// Disable when done
UIRenderer.DisableTextInput();

// Block keyboard from reaching the game while typing
UIRenderer.RegisterKeyInputBlock("my-search-bar");
// ... when done:
UIRenderer.UnregisterKeyInputBlock("my-search-bar");

Inventory Control

UIRenderer.OpenInventory();    // Open player inventory
UIRenderer.CloseInventory();   // Close player inventory
bool open = UIRenderer.IsInventoryOpen;  // Check if inventory is open

UIRenderer Members

Member Description
DrawRect() Draw filled rectangle
DrawRectOutline() Draw rectangle outline
DrawPanel() Draw panel with background and border
DrawText() Draw text with border
DrawTextShadow() Draw text (same as DrawText)
DrawTextSmall() Draw smaller text (0.75 scale)
DrawTextScaled() Draw text with custom scale
DrawItem() Draw item icon
LoadTexture() Load a PNG file as a texture (cached)
DrawTexture() Draw a loaded texture (aspect-ratio preserved)
BeginClip(), EndClip() Scissor clipping for scroll regions
IsClipping True if clipping is active
MouseX, MouseY Mouse position
MouseLeft, MouseRight Mouse button held state
MouseMiddle Middle mouse button held state
MouseLeftClick, MouseRightClick Mouse button just clicked
MouseMiddleClick Middle mouse button just clicked
ScreenWidth, ScreenHeight Screen dimensions
ScrollWheel Scroll wheel delta
IsMouseOver() Hit test rectangle
BlockInput() Block mouse/keyboard from game
BlockMouseOnly() Block only mouse input
BlockKeyboardInput() Block only keyboard input
ConsumeClick() Clear left click state
ConsumeRightClick() Clear right click state
ConsumeMiddleClick() Clear middle click state
ConsumeScroll() Clear scroll wheel state
IsBlocking True if input blocking is active
IsWaitingForKeyInput Set true when capturing key input
RegisterPanelBounds() Register UI panel for click-through prevention
UnregisterPanelBounds() Unregister UI panel
ClearAllPanelBounds() Clear all registered panels
IsMouseOverAnyPanel() Check if mouse over any registered panel
IsMouseOverPanel() Check if mouse over specific panel
ShouldBlockForHigherPriorityPanel() Check panel priority
RegisterPanelDraw() Register draw callback for z-order management
UnregisterPanelDraw() Unregister draw callback
BringToFront() Bring panel to top of z-order
DrawAllPanels() Draw all registered panels (called internally)
OpenInventory(), CloseInventory() Inventory control
IsInventoryOpen Check if inventory is open
RegisterKeyInputBlock() Block keyboard input to world (for text fields)
UnregisterKeyInputBlock() Unblock keyboard input
SetMouseBlocking() Set mouse blocking state directly
MeasureText() Measure text width in pixels (uses game font)
EnableTextInput(), DisableTextInput() Text input mode
HandleIME() Handle IME input
ClearInput() Clear input state
GetInputText() Get keyboard input
CheckInputEscape() Check Escape pressed

Widget Library

The Widget Library (TerrariaModder.Core.UI.Widgets) provides a complete set of reusable UI widgets for building mod interfaces. Widgets handle input blocking, z-ordering, and focus management automatically.

using TerrariaModder.Core.UI.Widgets;

DraggablePanel

Top-level window container with title bar, close button, dragging, and automatic z-order management.

// Create a panel
var panel = new DraggablePanel("my-panel", "My UI", 400, 300);

// Configuration
panel.HeaderHeight = 35;     // Title bar height (default 35)
panel.Padding = 8;           // Content padding (default 8)
panel.ShowCloseButton = true;
panel.Draggable = true;
panel.CloseOnEscape = true;
panel.ClipContent = true;    // Clip content to panel bounds
panel.OnClose = () => { };   // Optional close callback
panel.IconTexture = UIRenderer.LoadTexture("path/to/icon.png"); // Optional: override icon

// Icon auto-resolution: if panel ID matches a mod ID, the mod's icon.png
// is used automatically. Falls back to the default framework icon.

// Open/close
panel.Open();                // Open and auto-center
panel.Open(100, 200);        // Open at specific position
panel.Close();
panel.Toggle();

// Register draw callback (called automatically by z-order system)
panel.RegisterDrawCallback(DrawMyPanel);
panel.UnregisterDrawCallback();

// In your draw callback:
void DrawMyPanel()
{
    if (!panel.BeginDraw()) return;  // Returns false if panel is closed

    // Draw content using panel.ContentX, ContentY, ContentWidth, ContentHeight
    // Input blocking is handled automatically

    panel.EndDraw();
}

Properties:

Property Type Description
PanelId string Unique panel identifier
Title string Title bar text
Width, Height int Panel dimensions
X, Y int Panel position
IsOpen bool Whether panel is visible
BlockInput bool Whether a higher-z panel is blocking this one
ContentX, ContentY int Top-left of content area
ContentWidth, ContentHeight int Available content dimensions
Padding int Content padding from panel edge (default: 8)

StackLayout

Vertical layout helper that eliminates manual Y-coordinate math. Stack-allocated struct with zero GC pressure. Default spacing is 4 pixels.

var layout = new StackLayout(panel.ContentX, panel.ContentY, panel.ContentWidth, spacing: 8);

// Integrated widget helpers (draw + auto-advance):
if (layout.Button("Click Me")) { /* handle click */ }
layout.SectionHeader("Settings");
if (layout.Toggle("Feature", isEnabled)) { isEnabled = !isEnabled; }
if (layout.Checkbox("Option", isChecked)) { isChecked = !isChecked; }
layout.Label("Some text");
layout.Divider();

// Manual advance for custom widgets:
int y = layout.Advance(30);  // Reserve 30px, returns the Y to draw at
layout.Space(16);             // Add blank space

// Position-override variants (draw at specific X, custom width):
if (layout.ButtonAt(x + 100, 120, "OK")) { /* handle */ }
if (layout.ToggleAt(x + 100, 120, "Mode", active)) { active = !active; }

Properties:

Property Type Description
X int Left edge of layout area
Y int Starting Y position
Width int Available width
CurrentY int Where the next widget draws
TotalHeight int Total height consumed

Methods:

Method Returns Description
Button(label) bool Full-width button, returns true on click
ButtonAt(x, width, label) bool Button at specific X and width
Toggle(label, active) bool Full-width toggle, returns true on click
ToggleAt(x, width, label, active) bool Toggle at specific X and width
Checkbox(label, checked) bool Checkbox with label, returns true on click
SectionHeader(text) void Divider line with label
Label(text) void Text label (default color)
Label(text, color, height) void Text label with Color4 and custom height
Divider() void Horizontal divider line
Advance(height) int Reserve space, returns Y to draw at
Space(height) void Add blank vertical space

Button

Clickable button with hover state.

// Basic button
if (Button.Draw(x, y, width, height, "Click Me"))
{
    // Handle click
}

// Custom colors
if (Button.Draw(x, y, width, height, "Custom", normalBg, hoverBg, textColor))
{
    // Handle click
}

Toggle

ON/OFF toggle button. Caller manages state.

if (Toggle.Draw(x, y, width, height, "Feature Name", isActive))
{
    isActive = !isActive;  // Flip state on click
}

Checkbox

Checkbox with optional label and partial state support.

// Checkbox only
if (Checkbox.Draw(x, y, size, isChecked))
{
    isChecked = !isChecked;
}

// Checkbox with label (full-row click area)
if (Checkbox.DrawWithLabel(x, y, width, height, "Enable feature", isChecked))
{
    isChecked = !isChecked;
}

// Partial state (yellow indicator)
Checkbox.Draw(x, y, size, isChecked, partial: true);

SectionHeader

Divider line with a label for grouping content.

int height = SectionHeader.Draw(x, y, width, "Section Title");

TextInput

Single-line text field with IME support. Create once and reuse.

var textInput = new TextInput(placeholder: "Search...", maxLength: 100);

// In Update phase (required for IME):
textInput.Update();

// In Draw phase:
string value = textInput.Draw(x, y, width, height: 28);
if (textInput.HasChanged) { /* use value */ }

// Properties
textInput.KeyBlockId     // Keyboard input block ID (for RegisterKeyInputBlock)

// Control
textInput.Focus();
textInput.Unfocus();
textInput.Clear();

Properties:

Property Type Description
Text string Current text value
IsFocused bool Whether the input has focus
HasChanged bool Whether text changed this frame

Slider

Horizontal value slider with drag and click-to-seek.

var slider = new Slider();

// Integer slider
int newValue = slider.Draw(x, y, width, height, currentValue, min: 0, max: 100);

// Float slider
float newFloat = slider.Draw(x, y, width, height, currentFloat, min: 0f, max: 1f);

ScrollView

Virtual scrolling container. Uses culling instead of GPU clipping to avoid nesting limitations.

var scroll = new ScrollView();

scroll.Begin(x, y, width, height, totalContentHeight);

for (int i = 0; i < items.Count; i++)
{
    int itemY = i * 30;
    if (!scroll.IsVisible(itemY, 30)) continue;  // Cull off-screen items

    int drawY = scroll.ContentY + itemY;
    // Draw item at drawY...
}

scroll.End();  // Draws scrollbar and handles scroll input

Properties:

Property Type Description
ScrollOffset int Current scroll position
ContentHeight int Total content height
ViewHeight int Visible area height
ContentX, ContentY int Content area origin
ContentWidth int Content area width
ViewTop, ViewBottom int Visible range in content space
MaxScroll int Maximum scroll offset
NeedsScrolling bool True if content exceeds view height

Methods: ResetScroll(), ScrollToY(y)

TabBar

Tab strip for switching between views.

string[] tabs = { "General", "Advanced", "About" };
int activeTab = TabBar.Draw(x, y, width, tabs, activeTab, height: 28);

Centered overlay dialog with dimming background. Blocks all background interaction.

var modal = new Modal(width: 480);

// Open from a button click, etc.
if (someCondition) modal.Open();

// Draw (anywhere in your draw loop)
if (modal.BeginDraw("Confirm Action", contentHeight: 100))
{
    UIRenderer.DrawText("Are you sure?", modal.ContentX, modal.ContentY, UIColors.Text);

    if (Button.Draw(modal.ContentX, modal.ContentY + 40, 80, 26, "Yes"))
    {
        // Handle confirm
        modal.Close();
    }
    if (Button.Draw(modal.ContentX + 90, modal.ContentY + 40, 80, 26, "No"))
    {
        modal.Close();
    }

    modal.EndDraw();
}

Tooltip

Deferred tooltip rendering. Last-writer-wins per frame.

// Set tooltip when hovering over something
if (WidgetInput.IsMouseOver(x, y, width, height))
{
    Tooltip.Set("Simple tooltip text");
    // Or with title:
    Tooltip.Set("Item Name", "Detailed description here");
}

// Draw at end of panel (called automatically by DraggablePanel.EndDraw)
Tooltip.DrawDeferred();

TextUtil

Text measurement and truncation utilities.

int pixelWidth = TextUtil.MeasureWidth("Hello world");
string truncated = TextUtil.Truncate("Very long text here", maxPixelWidth: 200);
string tail = TextUtil.VisibleTail("Long input text", maxPixelWidth: 150);

WidgetInput

Unified input access with z-order blocking. Used internally by all widgets.

// Mouse state (respects z-order blocking)
int mx = WidgetInput.MouseX;
int my = WidgetInput.MouseY;
bool clicked = WidgetInput.MouseLeftClick;
bool hover = WidgetInput.IsMouseOver(x, y, width, height);

// Consume input to prevent propagation
WidgetInput.ConsumeClick();
WidgetInput.ConsumeRightClick();
WidgetInput.ConsumeScroll();

// Modifier keys
bool shift = WidgetInput.IsShiftHeld;
bool ctrl = WidgetInput.IsCtrlHeld;
bool alt = WidgetInput.IsAltHeld;

UIColors and Color4

Centralized color palette with colorblind theme support.

using TerrariaModder.Core.UI;

// Use semantic colors (all Color4 structs)
UIRenderer.DrawRect(x, y, w, h, UIColors.PanelBg);
UIRenderer.DrawText("Hello", x, y, UIColors.Text);

// Color4 struct
Color4 color = new Color4(255, 100, 100, 230);
Color4 transparent = color.WithAlpha(128);

Color Categories:

Category Colors
Panel PanelBg, HeaderBg, SectionBg, TooltipBg
Items ItemBg, ItemHoverBg, ItemActiveBg
Text Text, TextDim, TextHint, TextTitle
Status Success, Error, Warning, Info
Interactive Button, ButtonHover, Accent, AccentText
Chrome Border, Divider, InputBg, InputFocusBg
Scroll ScrollTrack, ScrollThumb
Slider SliderTrack, SliderThumb, SliderThumbHover
Console ConsoleCommand, ConsolePrompt
Close CloseBtn, CloseBtnHover

Utility Methods:

// Get color for item rarity (-1 to 11+)
Color4 rarityColor = UIColors.GetRarityColor(item.Rarity);

Themes: "normal", "red-green", "blue-yellow", "high-contrast"

UIColors.SetTheme("high-contrast");
string current = UIColors.CurrentTheme;
string[] available = UIColors.ThemeNames;

Complete Example: Panel with Widgets

public class Mod : IMod
{
    private DraggablePanel _panel;
    private ScrollView _scroll = new ScrollView();
    private Slider _slider = new Slider();
    private bool _godMode;
    private int _speed = 1;
    private List<string> _items = new List<string> { "Sword", "Shield", "Potion", "Arrow", "Torch" };

    public void Initialize(ModContext context)
    {
        _panel = new DraggablePanel("my-ui", "My Settings", 350, 400);
        _panel.RegisterDrawCallback(DrawUI);

        context.RegisterKeybind("toggle", "Toggle UI", "Open settings", "F7", () => _panel.Toggle());
    }

    private void DrawUI()
    {
        if (!_panel.BeginDraw()) return;

        var layout = new StackLayout(_panel.ContentX, _panel.ContentY, _panel.ContentWidth, spacing: 6);

        layout.SectionHeader("Gameplay");
        if (layout.Toggle("God Mode", _godMode)) _godMode = !_godMode;

        layout.SectionHeader("Speed");
        int y = layout.Advance(20);
        _speed = _slider.Draw(layout.X, y, layout.Width, 20, _speed, 1, 10);

        layout.SectionHeader("Items");
        // Scrollable list
        int listHeight = _panel.ContentHeight - layout.TotalHeight - 8;
        _scroll.Begin(layout.X, layout.CurrentY, layout.Width, listHeight, _items.Count * 24);
        for (int i = 0; i < _items.Count; i++)
        {
            int itemY = i * 24;
            if (!_scroll.IsVisible(itemY, 24)) continue;
            UIRenderer.DrawText(_items[i], layout.X, _scroll.ContentY + itemY, UIColors.Text);
        }
        _scroll.End();

        _panel.EndDraw();
    }

    public void Unload()
    {
        _panel.UnregisterDrawCallback();
    }
}

Game Class

Convenience accessors for common game state. Uses direct Terraria and XNA references.

using TerrariaModder.Core.Reflection;

Game State

bool inMenu = Game.InMenu;           // True if in main menu
bool paused = Game.IsPaused;         // True if game is paused
bool loading = Game.IsLoading;       // True if loading
bool inWorld = Game.InWorld;         // True if in a world (not menu)

bool multiplayer = Game.IsMultiplayer;  // True if multiplayer
bool server = Game.IsServer;            // True if this is the server
bool client = Game.IsClient;            // True if this is a client
bool singleplayer = Game.IsSingleplayer; // True if singleplayer

Screen and Mouse

int screenW = Game.ScreenWidth;
int screenH = Game.ScreenHeight;
float uiScale = Game.UIScale;

int mouseX = Game.MouseX;
int mouseY = Game.MouseY;
Vector2 mouseScreen = Game.MouseScreen;  // Mouse in screen coordinates
Vector2 mouseWorld = Game.MouseWorld;    // Mouse in world coordinates
bool mouseL = Game.MouseLeft;
bool mouseR = Game.MouseRight;

Vector2 screenPos = Game.ScreenPosition;  // Top-left corner in world coords

World

int maxX = Game.MaxTilesX;        // World width in tiles
int maxY = Game.MaxTilesY;        // World height in tiles
int surface = Game.WorldSurface;  // Surface level Y
int rock = Game.RockLayer;        // Rock layer Y

double time = Game.Time;          // Current time of day
bool day = Game.IsDayTime;        // True if daytime
bool blood = Game.BloodMoon;      // True if blood moon
bool eclipse = Game.Eclipse;      // True if solar eclipse
bool rain = Game.Raining;         // True if raining

Local Player

int myIndex = Game.MyPlayerIndex;
Player player = Game.LocalPlayer;
Vector2 pos = Game.PlayerPosition;
Vector2 center = Game.PlayerCenter;

int health = Game.PlayerHealth;
int maxHealth = Game.PlayerMaxHealth;
int mana = Game.PlayerMana;
int maxMana = Game.PlayerMaxMana;
bool dead = Game.PlayerDead;

int slot = Game.SelectedItem;
Item held = Game.HeldItem;

Input Helpers

Game.BlockMouse();              // Block mouse input
bool chatOpen = Game.ChatOpen;  // True if chat is open
bool invOpen = Game.InventoryOpen;  // True if inventory open
bool uiBlocking = Game.UIBlocking;  // True if any UI blocking input

Arrays

Player[] players = Game.Players;
NPC[] npcs = Game.NPCs;
Projectile[] projectiles = Game.Projectiles;

Utility Methods

// Coordinate conversion
Vector2 worldPos = Game.TileToWorld(tileX, tileY);
(int x, int y) = Game.WorldToTile(worldPos);

// Get tile at position
Tile tile = Game.GetTile(tileX, tileY);

// Check tile solidity
bool solid = Game.IsTileSolid(tileType);

Actions

// Show chat message
Game.ShowMessage("Hello!", r, g, b);

// Place a tile
bool success = Game.PlaceTile(tileX, tileY, tileType, style);

ConfigPropertyMeta

Metadata for a configuration property (built from ModConfig subclass attributes).

Property Type Description
Property PropertyInfo The reflected property
Key string Property name (JSON key)
Label string Display label from [Label] attribute
Description string Tooltip from [Description] attribute
Scope ConfigScope Client or Server
RestartRequired bool True if [RestartRequired] attribute present
Min float? Minimum from [Range] attribute
Max float? Maximum from [Range] attribute
Options string[] Valid options from [Options] attribute
FormerNames string[] Old property names from [FormerlySerializedAs]

Methods: - object GetValue(ModConfig instance) - Get current property value - void SetValue(ModConfig instance, object value) - Set property value

CommandRegistry

Central registry for debug commands. Thread-safe. Core commands have no namespace prefix; mod commands are prefixed with modid..

using TerrariaModder.Core.Debug;

Registering Commands

Mods register commands via ModContext:

// In your mod's Initialize():
context.RegisterCommand("status", "Show mod status", args =>
{
    CommandRegistry.Write("Status: OK");
});
// Command is registered as "my-mod.status"

Command Output

// Write output from a command (fires OnOutput event and logs)
CommandRegistry.Write("Hello from my command");

Executing Commands

bool found = CommandRegistry.Execute("help");           // Execute by name
bool found = CommandRegistry.Execute("my-mod.status");  // Execute mod command

Querying Commands

IReadOnlyList<CommandInfo> all = CommandRegistry.GetCommands();
IReadOnlyList<CommandInfo> modCmds = CommandRegistry.GetCommandsForMod("my-mod");
IReadOnlyList<CommandInfo> coreCmds = CommandRegistry.GetCoreCommands();
string desc = CommandRegistry.GetHelp("help");
bool exists = CommandRegistry.HasCommand("help");

Built-in Core Commands

Command Description
help List all registered commands
mods List loaded mods with status
config Show config values for a mod
clear Clear console output

CommandInfo Properties

Property Type Description
Name string Full command name (e.g., "help" or "my-mod.status")
Description string Human-readable description
ModId string Mod that registered this command (null for core)

Events

// Subscribe to command output (used by DebugTools mod)
CommandRegistry.OnOutput += (message) => { };

// Subscribe to clear events
CommandRegistry.OnClearOutput += () => { };

// Programmatically clear output (fires OnClearOutput)
CommandRegistry.Clear();

Harmony

The framework includes Harmony for runtime patching. See Harmony Basics for detailed usage.

All mod patches must be applied manually using _harmony.Patch(). Attribute-based [HarmonyPatch] attributes are only used internally by the Core framework and are not auto-applied for mods.

Manual patches should be applied in the OnGameReady lifecycle hook:

private static Harmony _harmony;

public static void OnGameReady()
{
    _harmony = new Harmony("com.yourname.yourmod");
    // Apply manual patches here -- game types are ready
    _harmony.Patch(method, postfix: new HarmonyMethod(typeof(Mod), "MyPostfix"));
}

public void Unload()
{
    _harmony?.UnpatchAll("com.yourname.yourmod");
}

See also: Tested Patterns for practical examples.

Custom Assets System

The Custom Assets system lets mods register new items with real Terraria item type IDs. Custom items work like vanilla items: they stack, equip, save/load, and display tooltips normally.

Registering Items

public void Initialize(ModContext context)
{
    // Register a custom weapon
    context.RegisterItem("fire-sword", new ItemDefinition
    {
        DisplayName = "Flame Blade",
        Tooltip = new[] { "Shoots fireballs", "Burns enemies on contact" },
        Damage = 50,
        Melee = true,
        UseStyle = 1,
        UseTime = 20,
        UseAnimation = 20,
        KnockBack = 5f,
        Shoot = 85,        // Vanilla projectile ID
        ShootSpeed = 10f,
        Rarity = 5,
        Value = 50000,
        Width = 40,
        Height = 40
    });

    // Register a custom accessory
    context.RegisterItem("speed-ring", new ItemDefinition
    {
        DisplayName = "Ring of Speed",
        Tooltip = new[] { "+10% movement speed" },
        Accessory = true,
        Rarity = 4,
        Value = 30000,
        Width = 20,
        Height = 20,
        UpdateEquip = (player) =>
        {
            // Called every frame while equipped.
            // player is passed as object — cast to Player for direct access.
            var p = (Player)player;
            p.moveSpeed += 0.1f;
        }
    });
}

ItemDefinition Properties

Identity:

Property Type Default Description
DisplayName string (required) Item display name
Tooltip string[] null Tooltip lines
Texture string null Relative path to texture in mod folder

Combat:

Property Type Default Description
Damage int 0 Base damage
KnockBack float 0 Knockback value
UseTime int 20 Use speed
UseAnimation int 20 Animation duration
UseStyle int 0 How item is used (1=swing, 2=eat, 3=stab, 4=hold up, 5=shoot)
Crit int 4 Crit chance %
Mana int 0 Mana cost
AutoReuse bool false Auto-swing/auto-use on hold

Damage Types (set one to true):

Property Type Default
Melee bool false
Ranged bool false
Magic bool false
Summon bool false

Projectile:

Property Type Default Description
Shoot int 0 Projectile type ID to fire
ShootSpeed float 0 Projectile velocity

Equipment:

Property Type Default Description
Defense int 0 Defense (armor)
Accessory bool false Equips as accessory
Vanity bool false Vanity slot (no stats)
HeadSlot int -1 Head equipment slot
BodySlot int -1 Body equipment slot
LegSlot int -1 Leg equipment slot

Consumable / Potion:

Property Type Default Description
MaxStack int 1 Max stack size
Consumable bool false Consumed on use
Potion bool false Potion (causes potion sickness)
HealLife int 0 HP restored
HealMana int 0 Mana restored
BuffType int 0 Buff applied on use
BuffTime int 0 Buff duration in frames

Ammo:

Property Type Default Description
Ammo int 0 Ammo category this item is
UseAmmo int 0 Ammo category this weapon uses

Placement:

Property Type Default Description
CreateTile int -1 Tile type to place
CreateWall int -1 Wall type to place
PlaceStyle int 0 Tile/wall style variant

Visual:

Property Type Default Description
Width int 20 Sprite width
Height int 20 Sprite height
Scale float 1f Scale multiplier
HoldStyle int 0 Hold animation style
NoUseGraphic bool false Hide use animation
NoMelee bool false No melee hitbox
Channel bool false Hold to use continuously

Economy:

Property Type Default Description
Rarity int 0 Item rarity tier
Value int 0 Sell value in copper
Material bool false Can be used in recipes

Behavior Hooks

ItemDefinition supports runtime behavior hooks. Terraria types are passed as object because Core.dll does not reference Terraria.exe directly. In your mod (which does reference Terraria.exe), cast to Player, NPC, etc. for direct field access.

context.RegisterItem("eternal-potion", new ItemDefinition
{
    DisplayName = "Eternal Healing Potion",
    Consumable = true,
    Potion = true,
    HealLife = 150,
    UseStyle = 2,
    MaxStack = 30,

    // Prevent consumption (infinite potion)
    OnConsume = (player) => false,

    // Custom use logic
    OnUse = (player) =>
    {
        // Additional effects when used
        return true; // Allow use
    },

    // Modify tooltips dynamically
    ModifyTooltips = (lines) =>
    {
        lines.Add("Never consumed on use!");
    }
});

Available Hooks:

Hook Signature Description
CanUseItem Func<object, bool> Return false to prevent use. (player)
OnUse Func<object, bool> Called on use start. Return false to cancel. (player)
OnHitNPC Action<object, object, int, float, bool> After hitting NPC. (player, npc, damage, knockback, crit)
ModifyWeaponDamage ModifyDamageDelegate Modify damage by ref. (player, ref damage)
OnShoot Func<object, int, float, int?> Override projectile. (player, projType, speed) → type or null
OnConsume Func<object, bool> Return false to prevent consuming. (player)
UpdateEquip Action<object> Per-frame while equipped as accessory. (player)
OnHoldItem Action<object> Per-frame while held/selected. (player)
ModifyTooltips Action<List<string>> Modify tooltip lines dynamically. (lines)

Recipes

Register crafting recipes for custom or vanilla items:

context.RegisterRecipe(new RecipeDefinition
{
    Result = "my-mod:fire-sword",     // Custom item
    ResultStack = 1,
    Ingredients = new Dictionary<string, int>
    {
        { "IronBroadsword", 1 },      // Vanilla item by name
        { "HellstoneBar", 10 },        // Vanilla item by name
        { "my-mod:fire-gem", 5 }       // Custom item from this mod
    },
    Station = "Anvil"                  // Crafting station
});

RecipeDefinition Properties:

Property Type Default Description
Result string (required) Result item: "modid:name" or vanilla name/ID
ResultStack int 1 Stack size of result
Ingredients Dictionary<string, int> (required) Key: item reference, Value: count
Station string null Crafting station name (null = hand-crafted)

Item references can be: - Custom item: "modid:itemname" (e.g., "my-mod:fire-sword") - Vanilla item name: "IronBroadsword", "HellstoneBar" (matches ItemID field names) - Vanilla item ID: "1", "175" (numeric string)

NPC Shops

Add custom items to NPC shop inventories:

context.AddShopItem(new ShopDefinition
{
    NpcType = 17,                      // Merchant NPC type ID
    ItemId = "my-mod:speed-ring",      // Custom item
    Price = 100000                     // 10 gold (in copper, 0 = use item value)
});

ShopDefinition Properties:

Property Type Default Description
NpcType int (required) NPC type ID
ItemId string (required) Item reference
Price int 0 Price in copper (0 = item's Value)

NPC Drops

Register items as NPC drops:

context.RegisterDrop(new DropDefinition
{
    NpcType = 4,                       // Eye of Cthulhu
    ItemId = "my-mod:fire-gem",
    Chance = 0.33f,                    // 33% drop chance
    MinStack = 1,
    MaxStack = 3
});

DropDefinition Properties:

Property Type Default Description
NpcType int (required) NPC type ID
ItemId string (required) Item reference
Chance float 1.0 Drop chance (0.0 to 1.0)
MinStack int 1 Minimum drop stack
MaxStack int 1 Maximum drop stack

Custom Textures

Place texture files in your mod folder and reference them in the ItemDefinition:

context.RegisterItem("fire-sword", new ItemDefinition
{
    DisplayName = "Flame Blade",
    Texture = "assets/textures/fire-sword.png",  // Relative to mod folder
    // ...
});

If no texture is specified, the item uses a default placeholder texture.

Save/Load

Custom items are automatically saved and loaded. The framework intercepts the player save process: - Before save: custom items are extracted from inventory (replaced with air) - Custom item data is stored in a sidecar .moddata file next to the player save - After save: custom items are restored to their original slots - On load: custom items are restored from the sidecar file

This means custom items persist across game sessions without modifying vanilla save files.