This skill should be used when the user asks to "validate data structures", "check save format", "update config options", or discusses data persistence...
Validate data structures and prevent regressions in save formats, config, and modData conventions.
Models/FavoritesModel.cs (FavoritesData, FavoriteOutfit)ModConfig.cspublic class FavoritesData
{
public List<FavoriteOutfit> Favorites { get; set; }
}
public class FavoriteOutfit
{
// Metadata
public bool isFavorite { get; set; } = false;
public string Name { get; set; } = "";
public string Category { get; set; } = ""; // Spring/Summer/Fall/Winter/Special
public int LastWorn { get; set; } = 0;
// Item references (modData tags)
public Dictionary<string, string> Items { get; set; }
// Keys: "Hat", "Shirt", "Pants", "Shoes", "LeftRing", "RightRing"
// Values: modData tag like "Hat_abc123"
// Item IDs (for recreation)
public Dictionary<string, string> ItemIds { get; set; }
// Keys: "Hat", "Shirt", "Pants", "Shoes", "LeftRing", "RightRing"
// Values: QualifiedItemId like "(H)6", "(S)1001"
// Dye colors
public Dictionary<string, string> ItemColors { get; set; }
// Keys: "Shirt", "Pants" (only dyeable items)
// Values: "R,G,B,A" format
// Appearance
public string HairIndex { get; set; } = "";
public int Hair { get; set; } = 0;
public string AccessoryIndex { get; set; } = "";
public int Accessory { get; set; } = 0;
}
Required Fields:
Name is non-null (can be empty)Category is valid: "Spring", "Summer", "Fall", "Winter", or "Special"Items dictionary is non-nullItemIds dictionary is non-nullItemColors dictionary is non-nullItem References:
Data Integrity:
public sealed class ModConfig
{
public bool StartingDresser { get; set; } = true;
public bool RobinSellsDressers { get; set; } = true;
public bool TravelingMerchantSellsDressers { get; set; } = true;
public bool IncludeRingsInOutfits { get; set; } = true;
public bool IncludeFacialHair { get; set; } = false;
public bool IncludeModdedHairstyles { get; set; } = true;
public bool IncludeModdedAccessories { get; set; } = true;
public DresserSharingMode DresserInventorySharing { get; set; } = DresserSharingMode.Touching;
}
public enum DresserSharingMode { Individual, Touching, SameBuilding }
When adding new config options:
ModConfig.cs with proper default valueStardewOutfitManager.csGMCM Registration Pattern:
api.AddBoolOption(
mod: ModManifest,
name: () => "Option Name",
tooltip: () => "Description of what this does",
getValue: () => Config.OptionName,
setValue: v => Config.OptionName = v
);
// Prefix with mod's UniqueID
item.modData[$"{ModId}/FavoriteItem"] = "value";
// This mod's convention (from manifest.json: LiminalWarmth.StardewOutfitManager)
item.modData["LiminalWarmth.StardewOutfitManager/FavoriteItem"] = $"{item.Name}_{Guid.NewGuid()}";
LiminalWarmth.StardewOutfitManager/If old keys exist without prefix:
// Check for legacy key and migrate
if (item.modData.TryGetValue("StardewOutfitManagerFavoriteItem", out string legacyValue))
{
// Migrate to new key
item.modData[$"{ModId}/FavoriteItem"] = legacyValue;
item.modData.Remove("StardewOutfitManagerFavoriteItem");
}
// Unique per player, per save, per multiplayer session
string path = $"data/favoritesData/{Game1.player.Name}_{Constants.SaveFolderName}_{Game1.player.UniqueMultiplayerID}.json";
// Use Helper.Data API
Helper.Data.WriteJsonFile<FavoritesData>(path, data);
var loaded = Helper.Data.ReadJsonFile<FavoritesData>(path);
Helper.Data API (not raw file I/O)When making breaking changes to data structures:
Examples of breaking changes:
private FavoritesData MigrateData(FavoritesData data, int fromVersion)
{
// Version 1 -> 2: Renamed "Outfit" to "Items"
if (fromVersion < 2)
{
foreach (var outfit in data.Favorites)
{
// Migration logic
}
}
// Version 2 -> 3: Added ItemColors
if (fromVersion < 3)
{
foreach (var outfit in data.Favorites)
{
outfit.ItemColors ??= new Dictionary<string, string>();
}
}
return data;
}
Always use null-safe access:
// WRONG: Assumes Items exists
var hatTag = outfit.Items["Hat"];
// CORRECT: Null-safe
if (outfit.Items?.TryGetValue("Hat", out string hatTag) == true)
{
// Use hatTag
}
// Also initialize missing dictionaries on load
outfit.Items ??= new Dictionary<string, string>();
outfit.ItemIds ??= new Dictionary<string, string>();
outfit.ItemColors ??= new Dictionary<string, string>();
## Data Validation Results
### Schema Issues
- [ ] FavoriteOutfit.Category allows invalid values (no validation)
- [ ] Missing null check for Items dictionary in LoadFavorites
### Config Issues
- [ ] New option `DebugMode` not registered in GMCM
### ModData Key Issues
- [ ] Legacy key "StardewOutfitManagerFavoriteItem" found in FavoritesMethods.cs:45
- Fix: Migrate to "LiminalWarmth.StardewOutfitManager/FavoriteItem"
### Breaking Changes
- [ ] Property renamed from `Outfit` to `Items` - migration required
### Passed Checks
- [x] All config options have defaults
- [x] Save paths include all required components
- [x] ItemIds use valid QualifiedItemId format
(O) Object
(BC) BigCraftable
(F) Furniture
(W) Weapon
(B) Boots
(H) Hat
(P) Pants
(S) Shirt
(T) Tool
(TR) Trinket
(R) Ring
"Spring"
"Summer"
"Fall"
"Winter"
"Special"
"Hat"
"Shirt"
"Pants"
"Shoes"
"LeftRing"
"RightRing"