efd-trading-card-base/src/StorageHelper.cs
Cal Corum 0d9ab65422 Implement Phase 3: Storage system with slot filtering
- Add StorageHelper.cs for creating filtered storage items
- Create Card Binder (9 slots) and Card Box (36 slots)
- Slots use requireTags to only accept TradingCard tagged items
- Update debug spawn (F9) to cycle through cards and storage items
- Move initialization from Start() to Awake() to fix save/load crash
- Add safety patch for missing item warnings
- Items now persist correctly across game saves

Storage items clone base item 1255 which has slot support, then
configure slots with TradingCard tag requirement.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 17:12:03 -06:00

194 lines
7.0 KiB
C#

using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using ItemStatsSystem;
using ItemStatsSystem.Items;
using Duckov.Utilities;
using SodaCraft.Localizations;
namespace TradingCardMod
{
/// <summary>
/// Helper class for creating storage items (binders, card boxes) with slot filtering.
/// </summary>
public static class StorageHelper
{
// Base item ID that has slots (used as template for storage items)
private const int SLOTTED_ITEM_BASE_ID = 1255;
// Track created storage items for cleanup
private static readonly List<Item> _createdStorageItems = new List<Item>();
private static readonly List<GameObject> _createdGameObjects = new List<GameObject>();
/// <summary>
/// Creates a card binder item with slots that only accept TradingCard tagged items.
/// </summary>
/// <param name="itemId">Unique ID for this storage item</param>
/// <param name="displayName">Display name shown to player</param>
/// <param name="description">Item description</param>
/// <param name="slotCount">Number of card slots</param>
/// <param name="weight">Item weight</param>
/// <param name="value">Item value</param>
/// <param name="tradingCardTag">The TradingCard tag for filtering</param>
/// <param name="icon">Optional custom icon sprite</param>
/// <returns>The created Item, or null on failure</returns>
public static Item? CreateCardStorage(
int itemId,
string displayName,
string description,
int slotCount,
float weight,
int value,
Tag tradingCardTag,
Sprite? icon = null)
{
try
{
// Get base item with slots
Item original = ItemAssetsCollection.GetPrefab(SLOTTED_ITEM_BASE_ID);
if (original == null)
{
Debug.LogError($"[TradingCardMod] Base slotted item ID {SLOTTED_ITEM_BASE_ID} not found!");
return null;
}
// Clone the item
GameObject clone = UnityEngine.Object.Instantiate(original.gameObject);
clone.name = $"CardStorage_{itemId}";
UnityEngine.Object.DontDestroyOnLoad(clone);
_createdGameObjects.Add(clone);
Item item = clone.GetComponent<Item>();
if (item == null)
{
Debug.LogError("[TradingCardMod] Cloned storage object has no Item component!");
return null;
}
// Set basic properties
string locKey = $"TC_Storage_{itemId}";
item.SetPrivateField("typeID", itemId);
item.SetPrivateField("weight", weight);
item.SetPrivateField("value", value);
item.SetPrivateField("displayName", locKey);
item.SetPrivateField("quality", 3); // Uncommon quality
item.SetPrivateField("order", 0);
item.SetPrivateField("maxStackCount", 1);
// Set display quality
item.DisplayQuality = (DisplayQuality)3;
// Set tags - storage items should be tools
item.Tags.Clear();
Tag? toolTag = TagHelper.GetTargetTag("Tool");
if (toolTag != null)
{
item.Tags.Add(toolTag);
}
// Configure slots to only accept TradingCard tagged items
ConfigureCardSlots(item, tradingCardTag, slotCount);
// Set icon if provided
if (icon != null)
{
item.SetPrivateField("icon", icon);
}
// Set localization
LocalizationManager.SetOverrideText(locKey, displayName);
LocalizationManager.SetOverrideText($"{locKey}_Desc", description);
// Register with game
if (ItemAssetsCollection.AddDynamicEntry(item))
{
_createdStorageItems.Add(item);
Debug.Log($"[TradingCardMod] Registered storage: {displayName} (ID: {itemId}, Slots: {slotCount})");
return item;
}
else
{
Debug.LogError($"[TradingCardMod] Failed to register storage {displayName}!");
UnityEngine.Object.Destroy(clone);
_createdGameObjects.Remove(clone);
return null;
}
}
catch (Exception ex)
{
Debug.LogError($"[TradingCardMod] Error creating storage {displayName}: {ex.Message}");
return null;
}
}
/// <summary>
/// Configures an item's slots to only accept items with a specific tag.
/// </summary>
private static void ConfigureCardSlots(Item item, Tag requiredTag, int slotCount)
{
// Get template slot info if available
Slot templateSlot = new Slot();
if (item.Slots.Count > 0)
{
templateSlot = item.Slots[0];
}
// Clear existing slots
item.Slots.Clear();
// Create new slots with tag filtering
for (int i = 0; i < slotCount; i++)
{
Slot newSlot = new Slot(templateSlot.Key);
newSlot.SlotIcon = templateSlot.SlotIcon;
// Set unique key for each slot
typeof(Slot).GetField("key", BindingFlags.Instance | BindingFlags.NonPublic)
?.SetValue(newSlot, $"CardSlot{i}");
// Add tag requirement - only TradingCard items can go in
newSlot.requireTags.Add(requiredTag);
item.Slots.Add(newSlot);
}
Debug.Log($"[TradingCardMod] Configured {slotCount} slots with TradingCard filter");
}
/// <summary>
/// Gets all storage items created by this helper.
/// </summary>
public static IReadOnlyList<Item> GetCreatedStorageItems()
{
return _createdStorageItems.AsReadOnly();
}
/// <summary>
/// Cleans up all storage items created by this helper.
/// </summary>
public static void Cleanup()
{
foreach (var item in _createdStorageItems)
{
if (item != null)
{
ItemAssetsCollection.RemoveDynamicEntry(item);
}
}
_createdStorageItems.Clear();
foreach (var go in _createdGameObjects)
{
if (go != null)
{
UnityEngine.Object.Destroy(go);
}
}
_createdGameObjects.Clear();
Debug.Log("[TradingCardMod] StorageHelper cleaned up.");
}
}
}