QuickKeys Walkthrough¶
Difficulty: Advanced Concepts: Complex input handling, inventory manipulation, reflection, multiple features
Attribution: QuickKeys is inspired by the Helpful Hotkeys mod for tModLoader by direwolf420. This implementation provides similar quality-of-life features for vanilla Terraria 1.4.5.
QuickKeys is the most complex bundled mod. It provides auto-torch placement, recall hotkey, quick-stack, ruler overlay, and an extended hotbar (slots 11-20 via NumPad).
What It Does¶
- Auto-Torch (Tilde): Places torches at cursor position
- Auto-Recall (Home): Uses recall potions/magic mirror
- Quick-Stack (End): Quick-stacks to nearby chests
- Ruler (K): Toggles a ruler distance overlay (sets
player.rulerLineeach frame, overriding vanilla's per-frame reset) - Extended Hotbar (NumPad 1-9, 0): Quick switch to hotbar slots 11-20 (disabled by default, enable via F6 mod menu)
Key Concepts¶
1. Direct Field and Method Access¶
QuickKeys accesses game internals directly via the Terraria.exe reference:
using Terraria;
using Terraria.ID;
using Microsoft.Xna.Framework;
// Inventory: direct field access
Item[] inventory = player.inventory;
int selectedSlot = player.selectedItem;
// Item check: direct method call
player.ItemCheck();
// selectedItemState.Select() to change active item
player.selectedItemState.Select(slotIndex);
// Tile set (for torch detection): direct
bool[] torchSet = TileID.Sets.Torches;
bool isTorch = torchSet[item.createTile];
No reflection cache needed — all these are public members.
2. Item Slot Swapping¶
Since selectedItem is often read-only, swap inventory slots:
private int _savedSlot = -1;
private int _targetSlot = -1;
private bool _needsRestore = false;
private void UseItemInSlot(Player player, int slot)
{
_savedSlot = player.selectedItem;
_targetSlot = slot;
// Physically swap the items
Item temp = player.inventory[_savedSlot];
player.inventory[_savedSlot] = player.inventory[slot];
player.inventory[slot] = temp;
// Trigger item use
player.controlUseItem = true;
_needsRestore = true;
}
// Call every frame
private void RestoreIfNeeded(Player player)
{
if (!_needsRestore) return;
if (player.itemAnimation > 0) return; // Still using item
// Swap back
Item temp = player.inventory[_savedSlot];
player.inventory[_savedSlot] = player.inventory[_targetSlot];
player.inventory[_targetSlot] = temp;
_needsRestore = false;
}
3. Finding Items by Type¶
Search inventory slots (indices 0-58, 59 slots total):
private int FindItemOfTypes(Player player, int[] types)
{
for (int i = 0; i < player.inventory.Length; i++)
{
Item item = player.inventory[i];
if (item != null && item.stack > 0)
{
if (Array.IndexOf(types, item.type) >= 0)
return i;
}
}
return -1;
}
// Recall items - priority order (infinite-use first, then consumables)
int[] recallItems = {
3124, // Cell Phone (highest priority - infinite use, most features)
5437, // Shellphone (base)
5358, // Shellphone (spawn)
5359, // Shellphone (ocean)
5360, // Shellphone (underworld)
5361, // Shellphone (home)
50, // Magic Mirror
3199, // Ice Mirror
2350 // Recall Potion (lowest priority - consumable)
};
4. Torch Placement¶
Find a torch in inventory and place it at the cursor position. Key points:
- Use item.createTile for the tile type (supports all torch variants)
- Use item.placeStyle for colored torches (blue, green, etc.)
- Verify stack > 0 before placing
- Call TurnToAir() when stack reaches 0
private void PlaceTorch()
{
// Find first torch with stack > 0
Item torchItem = null;
for (int i = 0; i < player.inventory.Length; i++)
{
Item item = player.inventory[i];
if (item.stack > 0 && TileID.Sets.Torches[item.createTile])
{
torchItem = item;
break;
}
}
if (torchItem == null) return; // No torches
// Get torch properties
int tileType = torchItem.createTile; // Tile type (4 for torches)
int placeStyle = torchItem.placeStyle; // Style for colored variants
// Convert mouse to tile coordinates
int tileX = (int)(Main.MouseWorld.X / 16f);
int tileY = (int)(Main.MouseWorld.Y / 16f);
// Place using correct type and style
bool placed = WorldGen.PlaceTile(tileX, tileY, tileType,
mute: false, forced: false, plr: Main.myPlayer, style: placeStyle);
if (placed)
{
// Consume torch properly
torchItem.stack--;
if (torchItem.stack <= 0)
torchItem.TurnToAir(); // Remove empty item from inventory
}
}
5. Multiple Keybinds¶
Register multiple actions:
public void Initialize(ModContext context)
{
context.RegisterKeybind("auto-torch", "Auto Torch",
"Place a torch near cursor", "OemTilde", OnAutoTorch);
context.RegisterKeybind("auto-recall", "Auto Recall",
"Use recall item", "Home", UseRecall);
context.RegisterKeybind("quick-stack", "Quick Stack",
"Quick stack to chests", "End", QuickStack);
// Extended hotbar: NumPad1-9 for slots 11-19, NumPad0 for slot 20
for (int i = 1; i <= 9; i++)
{
int slot = i + 10; // NumPad1 = slot 11, etc.
context.RegisterKeybind($"hotbar-{slot}", $"Hotbar Slot {slot}",
$"Switch to hotbar slot {slot}", $"NumPad{i}", () => SwitchToSlot(slot));
}
context.RegisterKeybind("hotbar-20", "Hotbar Slot 20",
"Switch to hotbar slot 20", "NumPad0", () => SwitchToSlot(20));
}
Code Structure Overview¶
public class Mod : IMod
{
// State for item use restoration
private int _savedSlot = -1;
private bool _needsRestore = false;
// Ruler state
private static bool _rulerActive = false;
public void Initialize(ModContext context)
{
RegisterKeybinds(context);
FrameEvents.OnPostUpdate += OnPostUpdate;
}
// No Harmony patches needed. QuickKeys uses keybind callbacks
// and FrameEvents.OnPostUpdate for all its features.
// Keybind handlers
private void PlaceTorch() { /* ... */ }
private void UseRecall() { /* ... */ }
private void QuickStack() { /* ... */ }
private void OnRulerToggle() { _rulerActive = !_rulerActive; }
private void SwitchToSlot(int slot) { /* ... */ }
// Frame event to restore inventory and maintain ruler state
// QuickKeys uses FrameEvents.OnPostUpdate, not Player.Update patching
private void OnPostUpdate()
{
// Ruler: re-apply rulerLine each frame (vanilla ResetEffects clears it)
if (_rulerActive)
{
player.rulerLine = true;
player.builderAccStatus[0] = 0;
}
// Item restoration after quick-use
if (_needsRestore && player.itemAnimation <= 0)
{
RestoreInventory(player);
}
}
}
Lessons Learned¶
- Access public members directly -
player.inventory,player.ItemCheck(),TileID.Sets.Torches— no reflection needed - Use
selectedItemState.Select()- Cleaner than swapping; falls back to swap if Select fails - Search full inventory - Not just hotbar (10 slots)
- Bounds check everything - Tile access, array access
- Multiplayer sync - Send tile updates in multiplayer
- Restore state - Always clean up after temporary changes
- Multiple approaches - Have fallbacks when the primary method fails
For more on Harmony patching patterns, see Harmony Basics.