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>
This commit is contained in:
parent
6536a7126a
commit
0d9ab65422
@ -20,29 +20,36 @@ namespace TradingCardMod
|
||||
// Base game item ID to clone (135 is commonly used for collectibles)
|
||||
private const int BASE_ITEM_ID = 135;
|
||||
|
||||
// Storage item IDs (high range to avoid conflicts)
|
||||
private const int BINDER_ITEM_ID = 200001;
|
||||
private const int CARD_BOX_ITEM_ID = 200002;
|
||||
|
||||
private string _modPath = string.Empty;
|
||||
private List<TradingCard> _loadedCards = new List<TradingCard>();
|
||||
private List<Item> _registeredItems = new List<Item>();
|
||||
private List<GameObject> _createdGameObjects = new List<GameObject>();
|
||||
private Tag? _tradingCardTag;
|
||||
private Item? _binderItem;
|
||||
private Item? _cardBoxItem;
|
||||
|
||||
// Debug: track if we've spawned test items
|
||||
// Debug: track spawn cycling
|
||||
private int _debugSpawnIndex = 0;
|
||||
private List<Item> _allSpawnableItems = new List<Item>();
|
||||
|
||||
/// <summary>
|
||||
/// Called when the mod is loaded. Initialize the card system here.
|
||||
/// Called when the GameObject is created. Initialize early to register items before saves load.
|
||||
/// </summary>
|
||||
void Start()
|
||||
void Awake()
|
||||
{
|
||||
_instance = this;
|
||||
|
||||
// Get the mod's directory path
|
||||
_modPath = Path.GetDirectoryName(GetType().Assembly.Location) ?? string.Empty;
|
||||
|
||||
Debug.Log("[TradingCardMod] Mod initialized!");
|
||||
Debug.Log("[TradingCardMod] Mod awakening (early init)...");
|
||||
Debug.Log($"[TradingCardMod] Mod path: {_modPath}");
|
||||
|
||||
// Apply Harmony patches
|
||||
// Apply Harmony patches FIRST - before anything else
|
||||
Patches.ApplyPatches();
|
||||
|
||||
try
|
||||
@ -50,8 +57,18 @@ namespace TradingCardMod
|
||||
// Create our custom tag first
|
||||
_tradingCardTag = TagHelper.GetOrCreateTradingCardTag();
|
||||
|
||||
// Load and register cards
|
||||
// Load and register cards - do this early so saves can load them
|
||||
LoadCardSets();
|
||||
|
||||
// Create storage items
|
||||
CreateStorageItems();
|
||||
|
||||
// Build spawnable items list (cards + storage)
|
||||
_allSpawnableItems.AddRange(_registeredItems);
|
||||
if (_binderItem != null) _allSpawnableItems.Add(_binderItem);
|
||||
if (_cardBoxItem != null) _allSpawnableItems.Add(_cardBoxItem);
|
||||
|
||||
Debug.Log("[TradingCardMod] Mod initialized successfully!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -84,7 +101,41 @@ namespace TradingCardMod
|
||||
|
||||
Debug.Log($"[TradingCardMod] Total cards loaded: {_loadedCards.Count}");
|
||||
Debug.Log($"[TradingCardMod] Total items registered: {_registeredItems.Count}");
|
||||
Debug.Log("[TradingCardMod] DEBUG: Press F9 to spawn a trading card!");
|
||||
Debug.Log("[TradingCardMod] DEBUG: Press F9 to spawn items (cycles through cards, then binder, then box)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates storage items (binder and card box) for holding trading cards.
|
||||
/// </summary>
|
||||
private void CreateStorageItems()
|
||||
{
|
||||
if (_tradingCardTag == null)
|
||||
{
|
||||
Debug.LogError("[TradingCardMod] Cannot create storage items - TradingCard tag not created!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create Card Binder (9 slots = 3x3 grid)
|
||||
_binderItem = StorageHelper.CreateCardStorage(
|
||||
BINDER_ITEM_ID,
|
||||
"Card Binder",
|
||||
"A binder for storing and organizing trading cards. Holds 9 cards.",
|
||||
9,
|
||||
0.5f, // weight
|
||||
500, // value
|
||||
_tradingCardTag
|
||||
);
|
||||
|
||||
// Create Card Box (36 slots = bulk storage)
|
||||
_cardBoxItem = StorageHelper.CreateCardStorage(
|
||||
CARD_BOX_ITEM_ID,
|
||||
"Card Box",
|
||||
"A large box for bulk storage of trading cards. Holds 36 cards.",
|
||||
36,
|
||||
2.0f, // weight
|
||||
1500, // value
|
||||
_tradingCardTag
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -92,37 +143,37 @@ namespace TradingCardMod
|
||||
/// </summary>
|
||||
void Update()
|
||||
{
|
||||
// Debug: Press F9 to spawn a card
|
||||
// Debug: Press F9 to spawn an item
|
||||
if (Input.GetKeyDown(KeyCode.F9))
|
||||
{
|
||||
SpawnDebugCard();
|
||||
SpawnDebugItem();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns a trading card for testing purposes.
|
||||
/// Spawns items for testing - cycles through cards, then storage items.
|
||||
/// </summary>
|
||||
private void SpawnDebugCard()
|
||||
private void SpawnDebugItem()
|
||||
{
|
||||
if (_registeredItems.Count == 0)
|
||||
if (_allSpawnableItems.Count == 0)
|
||||
{
|
||||
Debug.LogWarning("[TradingCardMod] No cards registered to spawn!");
|
||||
Debug.LogWarning("[TradingCardMod] No items registered to spawn!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Cycle through registered cards
|
||||
Item cardToSpawn = _registeredItems[_debugSpawnIndex % _registeredItems.Count];
|
||||
// Cycle through all spawnable items
|
||||
Item itemToSpawn = _allSpawnableItems[_debugSpawnIndex % _allSpawnableItems.Count];
|
||||
_debugSpawnIndex++;
|
||||
|
||||
try
|
||||
{
|
||||
// Use game's utility to give item to player
|
||||
ItemUtilities.SendToPlayer(cardToSpawn);
|
||||
Debug.Log($"[TradingCardMod] Spawned card: {cardToSpawn.DisplayName} (ID: {cardToSpawn.TypeID})");
|
||||
ItemUtilities.SendToPlayer(itemToSpawn);
|
||||
Debug.Log($"[TradingCardMod] Spawned: {itemToSpawn.DisplayName} (ID: {itemToSpawn.TypeID})");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"[TradingCardMod] Failed to spawn card: {ex.Message}");
|
||||
Debug.LogError($"[TradingCardMod] Failed to spawn item: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -386,10 +437,14 @@ namespace TradingCardMod
|
||||
}
|
||||
_createdGameObjects.Clear();
|
||||
|
||||
// Clean up storage items
|
||||
StorageHelper.Cleanup();
|
||||
|
||||
// Clean up tags
|
||||
TagHelper.Cleanup();
|
||||
|
||||
_loadedCards.Clear();
|
||||
_allSpawnableItems.Clear();
|
||||
|
||||
Debug.Log("[TradingCardMod] Cleanup complete.");
|
||||
}
|
||||
|
||||
@ -54,65 +54,24 @@ namespace TradingCardMod
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Example Harmony Patches
|
||||
// ==========================================================================
|
||||
//
|
||||
// Below are example patches showing common patterns. Uncomment and modify
|
||||
// as needed once you've identified the game methods to patch.
|
||||
//
|
||||
// To find methods to patch, use a decompiler (ILSpy) on the game DLLs in:
|
||||
// Duckov_Data/Managed/
|
||||
// Safety Patches - Prevent crashes from missing mod items
|
||||
// ==========================================================================
|
||||
|
||||
/*
|
||||
/// <summary>
|
||||
/// Example: Postfix patch that runs after a method completes.
|
||||
/// Use case: Log when items are added to inventory, modify return values.
|
||||
/// Patch to prevent crashes when loading saves with mod items that aren't registered yet.
|
||||
/// Logs a warning for missing mod items instead of letting the game crash.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(ItemStatsSystem.ItemUtilities), "SendToPlayer")]
|
||||
public static class SendToPlayer_Patch
|
||||
[HarmonyPatch(typeof(ItemStatsSystem.ItemAssetsCollection), "GetPrefab", new Type[] { typeof(int) })]
|
||||
public static class GetPrefab_SafetyPatch
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
public static void Postfix(ItemStatsSystem.Item item)
|
||||
public static void Postfix(int typeID, ItemStatsSystem.Item __result)
|
||||
{
|
||||
// Check if the item is one of our trading cards
|
||||
// This runs after the original method completes
|
||||
Debug.Log($"[TradingCardMod] Item sent to player: {item.name}");
|
||||
// Check if this TypeID is in our mod's range and wasn't found
|
||||
if (typeID >= 100000 && __result == null)
|
||||
{
|
||||
Debug.LogWarning($"[TradingCardMod] Item TypeID {typeID} not found. Item was likely saved before mod loaded. It will be lost.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Example: Prefix patch that runs before a method.
|
||||
/// Use case: Modify parameters, skip original method, validate inputs.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(SomeClass), "SomeMethod")]
|
||||
public static class SomeMethod_Patch
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
public static bool Prefix(ref int someParameter)
|
||||
{
|
||||
// Modify parameter before original method runs
|
||||
someParameter = someParameter * 2;
|
||||
|
||||
// Return true to run original method, false to skip it
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Example: Transpiler patch that modifies IL instructions.
|
||||
/// Use case: Complex modifications, inserting code mid-method.
|
||||
/// Note: Advanced technique - prefer Prefix/Postfix when possible.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(SomeClass), "SomeMethod")]
|
||||
public static class SomeMethod_Transpiler
|
||||
{
|
||||
[HarmonyTranspiler]
|
||||
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
// Modify and return the IL instructions
|
||||
return instructions;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
193
src/StorageHelper.cs
Normal file
193
src/StorageHelper.cs
Normal file
@ -0,0 +1,193 @@
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user