Add hierarchical storage system, card set exclusion, and weight adjustments
**Storage System Improvements:** - Renamed "Card Binder" → "Binder Sheet" (9 slots, weight 0.1) - Renamed "Card Box" → "Card Binder" (12 slots, weight 1.5) - Implemented nested storage: Binder Sheets can be stored in Card Binders - Created new "BinderSheet" tag for hierarchical filtering - Updated StorageHelper to support multi-tag slot filtering **Card Set Management:** - Added folder prefix exclusion: folders starting with `_` are skipped - Skipped sets are logged and counted - ModConfig displays disabled card sets count **Technical Changes:** - TagHelper: Added GetOrCreateBinderSheetTag() method - StorageHelper: Modified CreateCardStorage() to accept List<Tag> - ModBehaviour: Added _skippedSetsCount field and exclusion logic - Updated documentation (CLAUDE.md, README.md) **Why:** - Hierarchical storage provides better organization for large collections - Lighter weights make storage items more practical - Folder exclusion enables easy management of WIP/seasonal content - Multi-tag filtering creates flexible storage systems 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
58b435028e
commit
1050d4f018
21
CLAUDE.md
21
CLAUDE.md
@ -79,7 +79,9 @@ The game loads mods from `Duckov_Data/Mods/`. Each mod requires:
|
||||
|
||||
- **`PackUsageBehavior`** (`src/PackUsageBehavior.cs`): Handles card pack opening mechanics. Implements gacha-style random card distribution based on rarity weights.
|
||||
|
||||
- **`ModConfigApi`** (`src/ModConfigApi.cs`): Optional integration with ModConfig mod. Adds card set information (set name, card number, rarity) to item descriptions in inventory.
|
||||
- **`ModConfigApi`** (`src/ModConfigApi.cs`): Optional integration with ModConfig mod. Adds card set information (set name, card number, rarity) to item descriptions in inventory. Also displays mod statistics including disabled card sets count.
|
||||
|
||||
- **`StorageHelper`** (`src/StorageHelper.cs`): Helper class for creating storage items with multi-tag slot filtering, enabling hierarchical storage systems.
|
||||
|
||||
### Dependencies
|
||||
|
||||
@ -98,6 +100,8 @@ The Description field is optional. If provided, it will be displayed in the item
|
||||
|
||||
Images go in `CardSets/{SetName}/images/`.
|
||||
|
||||
**Disabling Card Sets:** Prefix a card set folder name with `_` (underscore) to exclude it from loading. For example, `_TestSet/` will be skipped. This is useful for work-in-progress sets or seasonal content. The count of disabled sets is displayed in ModConfig.
|
||||
|
||||
## Game API Reference
|
||||
|
||||
Key namespaces and APIs from the game:
|
||||
@ -132,10 +136,16 @@ Key namespaces and APIs from the game:
|
||||
- Cards load from `CardSets/*/cards.txt` files with optional descriptions
|
||||
- Custom PNG images display as item icons
|
||||
- Cards register as game items with proper TypeIDs
|
||||
- Custom "TradingCard" tag for filtering
|
||||
- Custom tags: "TradingCard" and "BinderSheet" for filtering
|
||||
- Card packs with gacha-style mechanics (weighted random distribution)
|
||||
- Storage system with slot-based filtering (9-slot binders, 18-slot boxes)
|
||||
- ModConfig integration for enhanced card info display (set name, number, rarity)
|
||||
- Hierarchical storage system:
|
||||
- **Binder Sheet** (9 slots, weight 0.1): Holds trading cards only
|
||||
- **Card Binder** (12 slots, weight 1.5): Holds trading cards OR binder sheets
|
||||
- Nested storage: Cards → Binder Sheets → Card Binders
|
||||
- Card set exclusion: Prefix folders with `_` to disable loading
|
||||
- ModConfig integration:
|
||||
- Enhanced card info display (set name, number, rarity)
|
||||
- Mod statistics display (total cards, packs, disabled sets)
|
||||
- Debug spawn with F9 key (for testing)
|
||||
- Deploy/remove scripts for quick iteration
|
||||
- Unit tests for parsing logic and pack system
|
||||
@ -153,8 +163,7 @@ Based on analysis of the AdditionalCollectibles mod:
|
||||
### Future Considerations
|
||||
|
||||
- Investigate new ItemBuilder API (added in recent game update) as potential replacement for reflection-based approach
|
||||
- Additional storage variants or customization options
|
||||
- Binder sheets which hold cards are are held by binders
|
||||
- Additional storage variants or customization options (e.g., larger binders, themed storage boxes)
|
||||
|
||||
### Log File Location
|
||||
|
||||
|
||||
32
README.md
32
README.md
@ -6,9 +6,12 @@ A customizable trading card system that lets you add your own card sets to the g
|
||||
|
||||
- **Custom Card Sets** - Create your own trading cards with custom artwork and stats
|
||||
- **Card Packs** - Open randomized card packs with gacha-style rarity distribution
|
||||
- **Storage System** - Organize your collection with 9-slot binders and 18-slot card boxes
|
||||
- **Hierarchical Storage System** - Organize your collection with:
|
||||
- **Binder Sheets** (9 slots, lightweight) - Hold individual cards
|
||||
- **Card Binders** (12 slots) - Hold cards OR binder sheets for nested storage
|
||||
- **Card Set Management** - Disable card sets by prefixing folder names with `_`
|
||||
- **User-Friendly Format** - Define cards using simple pipe-separated text files
|
||||
- **ModConfig Integration** - Enhanced card info display when ModConfig is installed (optional)
|
||||
- **ModConfig Integration** - Enhanced card info display and mod statistics (optional)
|
||||
- **No Programming Required** - Add new card sets without writing any code
|
||||
|
||||
## Requirements
|
||||
@ -21,7 +24,9 @@ This mod requires the HarmonyLoadMod to be installed. It provides the Harmony li
|
||||
**Optional Mod Dependency:**
|
||||
- [ModConfig](https://steamcommunity.com/sharedfiles/filedetails/?id=3592433938) - Subscribe on Steam Workshop
|
||||
|
||||
ModConfig is optional but recommended. When installed, it adds card set information (set name, card number, rarity) to the item description in your inventory, making it easier to identify and organize your cards.
|
||||
ModConfig is optional but recommended. When installed, it:
|
||||
- Adds card set information (set name, card number, rarity) to item descriptions in your inventory
|
||||
- Displays mod statistics including total cards loaded, packs available, and disabled card sets
|
||||
|
||||
## Installation
|
||||
|
||||
@ -81,6 +86,27 @@ Lines starting with `#` are treated as comments and ignored:
|
||||
Blue Dragon | Fantasy Set | 001 | blue_dragon.png | Ultra Rare | 0.01 | 500
|
||||
```
|
||||
|
||||
## Disabling Card Sets
|
||||
|
||||
To temporarily disable a card set without deleting it, prefix the folder name with an underscore `_`:
|
||||
|
||||
```
|
||||
CardSets/
|
||||
├── MyActiveSet/ # This set will load
|
||||
├── _MyTestSet/ # This set will be skipped
|
||||
└── _SeasonalCards/ # This set will be skipped
|
||||
```
|
||||
|
||||
Disabled sets are:
|
||||
- Skipped during mod initialization
|
||||
- Logged in the game console for reference
|
||||
- Counted and displayed in ModConfig (if installed)
|
||||
|
||||
This is useful for:
|
||||
- Work-in-progress card sets
|
||||
- Seasonal or event-specific content
|
||||
- Testing different configurations
|
||||
|
||||
## Folder Structure
|
||||
|
||||
```
|
||||
|
||||
@ -63,14 +63,15 @@ namespace TradingCardMod
|
||||
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 const int BINDER_SHEET_ITEM_ID = 200001;
|
||||
private const int CARD_BINDER_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 Tag? _binderSheetTag;
|
||||
private Item? _binderItem;
|
||||
private Item? _cardBoxItem;
|
||||
|
||||
@ -78,6 +79,7 @@ namespace TradingCardMod
|
||||
private Dictionary<string, List<TradingCard>> _cardsBySet = new Dictionary<string, List<TradingCard>>();
|
||||
private Dictionary<string, int> _cardNameToTypeId = new Dictionary<string, int>();
|
||||
private List<Item> _registeredPacks = new List<Item>();
|
||||
private int _skippedSetsCount = 0;
|
||||
|
||||
// Store pack definitions for runtime lookup (key = "SetName|PackName")
|
||||
private Dictionary<string, CardPack> _packDefinitions = new Dictionary<string, CardPack>();
|
||||
@ -107,8 +109,9 @@ namespace TradingCardMod
|
||||
// Log all available tags for reference
|
||||
TagHelper.LogAvailableTags();
|
||||
|
||||
// Create our custom tag first
|
||||
// Create our custom tags first
|
||||
_tradingCardTag = TagHelper.GetOrCreateTradingCardTag();
|
||||
_binderSheetTag = TagHelper.GetOrCreateBinderSheetTag();
|
||||
|
||||
// Load and register cards - do this early so saves can load them
|
||||
LoadCardSets();
|
||||
@ -220,6 +223,20 @@ namespace TradingCardMod
|
||||
_registeredPacks.Count
|
||||
);
|
||||
|
||||
// Skipped sets display
|
||||
var skippedOption = new System.Collections.Generic.SortedDictionary<string, object>
|
||||
{
|
||||
{ $"{_skippedSetsCount} sets", _skippedSetsCount }
|
||||
};
|
||||
ModConfigAPI.SafeAddDropdownList(
|
||||
MOD_NAME,
|
||||
"SkippedSets",
|
||||
"Disabled Card Sets",
|
||||
skippedOption,
|
||||
typeof(int),
|
||||
_skippedSetsCount
|
||||
);
|
||||
|
||||
// Card sets info - one entry per set for clarity
|
||||
int setIndex = 0;
|
||||
foreach (var setEntry in _cardsBySet)
|
||||
@ -259,11 +276,27 @@ namespace TradingCardMod
|
||||
string[] setDirectories = Directory.GetDirectories(cardSetsPath);
|
||||
Debug.Log($"[TradingCardMod] Found {setDirectories.Length} card set directories");
|
||||
|
||||
_skippedSetsCount = 0;
|
||||
foreach (string setDir in setDirectories)
|
||||
{
|
||||
string setName = Path.GetFileName(setDir);
|
||||
|
||||
// Skip directories that start with underscore (disabled sets)
|
||||
if (setName.StartsWith("_"))
|
||||
{
|
||||
Debug.Log($"[TradingCardMod] Skipping disabled card set: {setName}");
|
||||
_skippedSetsCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
LoadCardSet(setDir);
|
||||
}
|
||||
|
||||
if (_skippedSetsCount > 0)
|
||||
{
|
||||
Debug.Log($"[TradingCardMod] Skipped {_skippedSetsCount} disabled card set(s)");
|
||||
}
|
||||
|
||||
Debug.Log($"[TradingCardMod] Total cards loaded: {_loadedCards.Count}");
|
||||
Debug.Log($"[TradingCardMod] Total items registered: {_registeredItems.Count}");
|
||||
Debug.Log("[TradingCardMod] DEBUG: Press F9 to spawn items (cycles through cards, then binder, then box)");
|
||||
@ -298,36 +331,43 @@ namespace TradingCardMod
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates storage items (binder and card box) for holding trading cards.
|
||||
/// Creates storage items (binder sheet and card binder) for holding trading cards.
|
||||
/// </summary>
|
||||
private void CreateStorageItems()
|
||||
{
|
||||
if (_tradingCardTag == null)
|
||||
if (_tradingCardTag == null || _binderSheetTag == null)
|
||||
{
|
||||
Debug.LogError("[TradingCardMod] Cannot create storage items - TradingCard tag not created!");
|
||||
Debug.LogError("[TradingCardMod] Cannot create storage items - Required tags not created!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create Card Binder (9 slots = 3x3 grid)
|
||||
// Create Binder Sheet (9 slots, stores cards only)
|
||||
_binderItem = StorageHelper.CreateCardStorage(
|
||||
BINDER_ITEM_ID,
|
||||
"Card Binder",
|
||||
"A binder for storing and organizing trading cards. Holds 9 cards.",
|
||||
BINDER_SHEET_ITEM_ID,
|
||||
"Binder Sheet",
|
||||
"A sheet for storing and organizing trading cards. Holds 9 cards.",
|
||||
9,
|
||||
1.5f, // weight
|
||||
0.1f, // weight
|
||||
7500, // value
|
||||
_tradingCardTag
|
||||
new List<Tag> { _tradingCardTag } // Only trading cards allowed
|
||||
);
|
||||
|
||||
// Create Card Box (36 slots = bulk storage)
|
||||
// Add BinderSheet tag to the binder sheet item itself so it can be stored in Card Binders
|
||||
if (_binderItem != null)
|
||||
{
|
||||
_binderItem.Tags.Add(_binderSheetTag);
|
||||
Debug.Log("[TradingCardMod] Added BinderSheet tag to Binder Sheet item");
|
||||
}
|
||||
|
||||
// Create Card Binder (12 slots, stores cards AND binder sheets)
|
||||
_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
|
||||
37500, // value
|
||||
_tradingCardTag
|
||||
CARD_BINDER_ITEM_ID,
|
||||
"Card Binder",
|
||||
"A binder for storing and organizing trading cards. Can hold cards or binder sheets. Holds 12 items.",
|
||||
12,
|
||||
1.5f, // weight
|
||||
12500, // value
|
||||
new List<Tag> { _tradingCardTag, _binderSheetTag } // Cards and binder sheets allowed
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using ItemStatsSystem;
|
||||
@ -22,7 +23,7 @@ namespace TradingCardMod
|
||||
private static readonly List<GameObject> _createdGameObjects = new List<GameObject>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a card binder item with slots that only accept TradingCard tagged items.
|
||||
/// Creates a card binder item with slots that only accept items with specific tags.
|
||||
/// </summary>
|
||||
/// <param name="itemId">Unique ID for this storage item</param>
|
||||
/// <param name="displayName">Display name shown to player</param>
|
||||
@ -30,7 +31,7 @@ namespace TradingCardMod
|
||||
/// <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="allowedTags">List of tags that can be stored in this container</param>
|
||||
/// <param name="icon">Optional custom icon sprite</param>
|
||||
/// <returns>The created Item, or null on failure</returns>
|
||||
public static Item? CreateCardStorage(
|
||||
@ -40,7 +41,7 @@ namespace TradingCardMod
|
||||
int slotCount,
|
||||
float weight,
|
||||
int value,
|
||||
Tag tradingCardTag,
|
||||
List<Tag> allowedTags,
|
||||
Sprite? icon = null)
|
||||
{
|
||||
try
|
||||
@ -87,8 +88,8 @@ namespace TradingCardMod
|
||||
item.Tags.Add(toolTag);
|
||||
}
|
||||
|
||||
// Configure slots to only accept TradingCard tagged items
|
||||
ConfigureCardSlots(item, tradingCardTag, slotCount);
|
||||
// Configure slots to only accept items with allowed tags
|
||||
ConfigureCardSlots(item, allowedTags, slotCount);
|
||||
|
||||
// Set icon if provided
|
||||
if (icon != null)
|
||||
@ -123,9 +124,9 @@ namespace TradingCardMod
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures an item's slots to only accept items with a specific tag.
|
||||
/// Configures an item's slots to only accept items with specific tags.
|
||||
/// </summary>
|
||||
private static void ConfigureCardSlots(Item item, Tag requiredTag, int slotCount)
|
||||
private static void ConfigureCardSlots(Item item, List<Tag> requiredTags, int slotCount)
|
||||
{
|
||||
// Get template slot info if available
|
||||
Slot templateSlot = new Slot();
|
||||
@ -147,13 +148,17 @@ namespace TradingCardMod
|
||||
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);
|
||||
// Add all tag requirements - only items with these tags can go in
|
||||
foreach (var tag in requiredTags)
|
||||
{
|
||||
newSlot.requireTags.Add(tag);
|
||||
}
|
||||
|
||||
item.Slots.Add(newSlot);
|
||||
}
|
||||
|
||||
Debug.Log($"[TradingCardMod] Configured {slotCount} slots with TradingCard filter");
|
||||
string tagNames = string.Join(", ", requiredTags.Select(t => t.name));
|
||||
Debug.Log($"[TradingCardMod] Configured {slotCount} slots with filter: {tagNames}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -82,6 +82,16 @@ namespace TradingCardMod
|
||||
return CreateOrCloneTag("TradingCard", "Trading Card");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the "BinderSheet" tag, creating it if it doesn't exist.
|
||||
/// This tag identifies binder sheets that can be stored in card binders.
|
||||
/// </summary>
|
||||
/// <returns>The BinderSheet tag.</returns>
|
||||
public static Tag GetOrCreateBinderSheetTag()
|
||||
{
|
||||
return CreateOrCloneTag("BinderSheet", "Binder Sheet");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tags created by this mod.
|
||||
/// </summary>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user