StorageHub Walkthrough¶
Difficulty: Advanced Concepts: Multi-tab UI, crafting system, item snapshots, data persistence, relay network, reflection
StorageHub is a unified storage interface that lets you browse, search, craft, and decraft items across all your registered chests from a single panel.
What It Does¶
Press F5 to open a 6-tab panel: - Items - Browse all registered chest contents with search, sort, and category filters - Crafting - One-click crafting with recursive intermediate crafting support - Recipes - Full recipe browser showing what creates and uses each item - Shimmer - Decraft items back into ingredients using shimmer - Unlocks - Tier progression, station memory, relay management, and mysterious chest upgrades - Network - View registered chests and crafting station availability
Key Concepts¶
1. Chest Registration (No Harmony Patches)¶
StorageHub detects chest opens by polling player.chest each frame rather than patching:
private int _lastChest = -1;
void OnPostUpdate()
{
var player = Main.player[Main.myPlayer];
int currentChest = player.chest;
if (currentChest >= 0 && _lastChest < 0)
{
// Player just opened a chest - register it
OnChestOpened(currentChest);
}
_lastChest = currentChest;
}
Chests are stored per-character per-world in the config. Locked chests, trapped chests, and mimics are excluded.
2. Snapshot-Based Item Access¶
All item access uses immutable snapshots rather than direct references. This prevents accidental mutation of game state:
public class ItemSnapshot
{
public int Type;
public int Stack;
public string Name;
public int Rarity;
// ... other fields
// Source tracking for operations
public int SourceChestIndex;
public int SourceSlotIndex;
}
// Get all items from registered chests
List<ItemSnapshot> items = _storageProvider.GetAllItems();
// Take an item (moves from chest to cursor)
_storageProvider.TakeItemToCursor(snapshot);
3. Tiered Progression System¶
Storage access range expands through 4 tiers, each unlocked by consuming items:
// Tier requirements (item types consumed to upgrade)
Tier 0 → 1: Shadow Scale (86) or Tissue Sample (1329)
Tier 1 → 2: Hellstone Bar (175)
Tier 2 → 3: Hallowed Bar (1225)
Tier 3 → 4: Luminite Bar (3467)
// Range per tier (in tiles)
Tier 0: 50 tiles (800 pixels)
Tier 1: 100 tiles (1600 pixels)
Tier 2: 500 tiles (8000 pixels)
Tier 3: 1000 tiles (16000 pixels) + station memory unlock
Tier 4: Unlimited range
Tier 3+ unlocks station memory: the mod remembers crafting stations you've visited, so you don't need to be near them every time.
4. Relay Network (BFS Range Extension)¶
Players can place up to 10 relays to extend their storage range via a breadth-first search. Each relay extends range by 200 tiles from its position:
// Range calculation: BFS from player through relay network
HashSet<(int, int)> reachable = new HashSet<(int, int)>();
Queue<(int x, int y, int range)> queue = new Queue<>();
// Start from player position
queue.Enqueue((playerTileX, playerTileY, baseRange));
while (queue.Count > 0)
{
var (x, y, remaining) = queue.Dequeue();
// Check for relays within remaining range
foreach (var relay in relays)
{
int dist = ManhattanDistance(x, y, relay.X, relay.Y);
if (dist <= remaining)
{
// Relay extends range from its position
queue.Enqueue((relay.X, relay.Y, relayRange));
}
}
}
5. Recipe Indexing for O(1) Lookup¶
The crafting system pre-indexes all recipes on first use for fast lookup:
// Pre-built indexes
Dictionary<int, List<Recipe>> _recipesByResult; // item type → recipes that make it
Dictionary<int, List<Recipe>> _recipesByIngredient; // item type → recipes that use it
Dictionary<int, List<Recipe>> _recipesByStation; // tile type → recipes at that station
// Craftability check
bool CanCraft(Recipe recipe)
{
// Check all ingredients available in storage + inventory
foreach (var ingredient in recipe.requiredItem)
{
int available = CountInStorage(ingredient.type) + CountInInventory(ingredient.type);
if (available < ingredient.stack) return false;
}
// Check crafting station (or station memory)
return HasStation(recipe) || _stationMemory.Contains(recipe.requiredTile);
}
6. Recursive Crafting¶
When partial materials are available, the system can auto-craft intermediates:
// Example: Crafting a Nights Edge when you have ore instead of bars
// 1. Detect missing: need 10 Hellstone Bars, have 0
// 2. Find recipe: Hellstone Bar = 3 Hellstone + 1 Obsidian (at Hellforge)
// 3. Check: have 30 Hellstone + 10 Obsidian? Yes
// 4. Auto-craft: 10 Hellstone Bars first, then craft Nights Edge
The craftability UI shows three states: - Green: All materials available, can craft now - Yellow: Partial materials, recursive crafting can fill the gap - Gray: Not enough materials even with recursive crafting
7. Shimmer Decrafting¶
Reverses crafting recipes, converting items back to ingredients:
// Shimmer decraft: reverse a recipe
void DecraftItem(ItemSnapshot item, Recipe recipe)
{
// Remove the item from storage
ConsumeItem(item, recipe.createItem.stack);
// Spawn each ingredient
foreach (var ingredient in recipe.requiredItem)
{
SpawnItem(ingredient.type, ingredient.stack);
}
}
The shimmer system respects: - Boss progression locks: Some items only decraft after defeating specific bosses - Biome variants: Corruption vs Crimson recipes differ - Special unlock: Requires 10 Aether Blocks to activate
8. Station Detection¶
Crafting stations are detected by scanning nearby tiles:
// 34 tile-based stations + 5 environment conditions
int[] StationTileIDs = {
TileID.WorkBenches, TileID.Anvils, TileID.Furnaces,
TileID.Loom, TileID.Kegs, /* ... */
};
bool[] EnvironmentConditions = {
IsNearWater(), // player.adjWater
IsNearHoney(), // player.adjHoney
IsNearLava(), // player.adjLava
IsInSnowBiome(), // player.ZoneSnow
IsInGraveyard(), // player.ZoneGraveyard
};
At Tier 3+, visited stations are remembered in station memory, persisted per-world so you don't lose progress.
9. Multi-Tab UI Architecture¶
The UI uses a coordinator pattern with independent tabs:
class StorageHubUI
{
private DraggablePanel _panel;
private ITab[] _tabs;
private int _activeTab;
void DrawPanel()
{
if (!_panel.BeginDraw()) return;
// Tab bar at top
_activeTab = TabBar.Draw(x, y, width, tabNames, _activeTab);
// Active tab draws its content
_tabs[_activeTab].Draw(contentX, contentY, contentWidth, contentHeight);
_panel.EndDraw();
}
}
interface ITab
{
void Draw(int x, int y, int width, int height);
void MarkDirty(); // Signal that data needs refresh
}
Each tab maintains its own scroll state, filter state, and refresh timing. Tabs are marked dirty when storage changes occur (chest opened, item crafted, etc.).
10. Per-World Data Persistence¶
StorageHub saves progression data per-character per-world:
// File: mods/storage-hub/worlds/{world-name}/{character-name}.json
{
"tier": 2,
"registeredChests": [[100, 200], [150, 200]],
"relays": [[120, 195]],
"stationMemory": [18, 16, 77],
"stationMemoryEnabled": true,
"favorites": [3506, 757, 1326],
"sortMode": "name",
"categoryFilter": "all"
}
// Character-level unlocks survive across worlds
// File: mods/storage-hub/characters/{character-name}.json
{
"shimmerUnlocked": true
}
UI Components¶
StorageHub uses its own custom UI components (it predates the Widget Library), plus TextUtil from the Widget Library:
| Component | Library | Purpose |
|---|---|---|
| Panel (custom) | Custom | Manual drag, z-order via RegisterPanelBounds/RegisterPanelDraw |
TabBar |
Custom | Tab navigation with active indicators |
SearchBar |
Custom | Text input with real-time filtering |
ScrollPanel |
Custom | Virtual scrolling for item lists |
ItemSlotGrid |
Custom | Grid layout with 44px item slots |
TextUtil |
Widget Library | Text measurement and truncation |
StorageHub implements its own draggable panel with manual drag tracking rather than using the Widget Library's DraggablePanel. New mods should use the Widget Library instead; see Core API Reference.
Configuration¶
StorageHub has several config options beyond the standard enabled:
| Config Key | Type | Default | Description |
|---|---|---|---|
blockHotbarFromCrafting |
bool | false | Protect hotbar slots 1-10 from crafting consumption |
recursiveCrafting |
bool | true | Enable auto-crafting of intermediate ingredients |
recursiveCraftingDepth |
int | 0 | Maximum recursive depth (0 = unlimited) |
paintingChest |
bool | true | Enable the Mysterious Chest feature |
Lessons Learned¶
- Polling beats patching: Checking
player.chesteach frame is simpler and more robust than Harmony-patching chest open methods - Snapshots prevent bugs: Never hand out references to real game items; return copies
- Pre-index for performance: Building recipe lookups once is far better than searching every frame
- Per-world persistence: Storage registrations and progression must be scoped to character+world pairs
- Lazy refresh: Only recalculate when data changes (dirty flag), not every frame
- Tiered progression: Gates encourage exploration while still being useful early
- Station memory at high tiers: Rewards invested players without making early game trivial
- Relay BFS: Elegant way to extend range without unlimited access
For more on building mod UIs, see Core API Reference - Widget Library. For crafting station tile IDs, always verify against the decomp, never guess.