Guidebook CSharp 05 Saves Shop
Persist state, address devices, and sell custom items. Theory: Core Save Engine, Developer Hardware IDs Inventory, Developer Shop Items.
Sidecars: hook your state into SaveGuard
Section titled “Sidecars: hook your state into SaveGuard”GregSaveGuard.RegisterSidecar("shift_helper_cs", save: () => SerializeMyState(), // -> string load: data => RestoreMyState(data)); // <- stringGregSaveGuard.RegisterVanillaModuleMap("shift_helper_cs", toVanilla: id => MapToVanillaOrNull(id), fallbackId: 0);Writes land in <saveDir>/greg_shift_helper_cs.<save>.tsv (atomic .tmp + .bak); backups in Documents/DatacenterBackups/ (pre-greg/ permanent + 3 rolling) — and only after a fresh backup succeeded (guarantee, see Core Save Engine). Sanitize (SanitizeSaveData, e.g. SFP prefabID clamp) keeps vanilla loads valid.
Typed options: GregConfigService.LoadConfig<T> / SaveConfig<T>; GregPersistenceService (%AppData%/gregCore/Saves/<key>.json, traversal-guarded); GregModSave (Create / Fill / Read / ReadAll / Upsert / Remove on SaveData.modItemData, keyed by modFolderName); GregSaveEngine LiteDB (gregSave_<guid>.greg.db, greg_meta + grid_state, writes gated by GregFeatureGuard.SaveEngine.Write).
Devices: inventory UIDs, never labels
Section titled “Devices: inventory UIDs, never labels”GregEntityInventory (gregCore.Infrastructure.Persistence) rebuilds on every load; entries are Entry{Kind, NativeKey, Uid, Hint} over InventoryKind{Server, Switch, Router, Firewall, PatchPanel, Cable, SfpModule, LacpGroup}:
if (!GregEntityInventory.IsReady) return; // rebuilt in the LoadNetworkState postfixvar servers = GregEntityInventory.GetAll(GregEntityInventory.InventoryKind.Server);int n = GregEntityInventory.Count(GregEntityInventory.InventoryKind.Server);bool ok = GregEntityInventory.TryFindLive(uid, out var go); // uid -> live GameObjectbool known = GregEntityInventory.TryGetUid(GregEntityInventory.InventoryKind.Router, nativeKey, out var routerUid);GregEntityInventory.Rebuilt += () => { /* re-resolve your cached handles here */ };string report = GregEntityInventory.Verify(); // Dump() prints the whole tableServers/switches/patch panels reuse gregID:<Type>:<12HEX> (ServerPrefix, SwitchPrefix, PatchPanelPrefix); cables/LACP use deterministic vanilla-derived UIDs (UidPrefix = "gregUID:"); routers/firewalls/SFP persist via greg_inventory.<save>.tsv. Tune via MelonPreferences gregCore.EntityInventory (Enabled, VerboseLogging, DumpOnRebuild). Keep gameObject.name vanilla; never invent a second ID scheme.
Shop items: IDs, prefab, section, cart
Section titled “Shop items: IDs, prefab, section, cart”ID ranges (never reuse): 100, 1000/2000/3000 series, 9001+.
GregShopItems.RegisterPrefab(itemId, baseItemId, resolver);GregShopItems.UnregisterPrefab(itemId);GregShopItems.TryGetBaseId(itemId, out var baseId);GregShopItems.TryResolvePrefab(itemId, out var prefab);var section = GregShopItems.FindSection(shop, "HL Mods"); // created if missingGregShopItems.AddButton(template, section, itemId, label, price, xp, guid, isCustomColor, sprite);GregShop.CartAddOne(...); GregShop.CartRemoveOne(...); // stay on vanilla cart behavior// persist via GregModPack DTOs (ShopItem/StaticItem/DllRef/Snapshot) + GregModSaveCheckout emits greg.SYSTEM.ButtonCheckOut; compat CommonShopPatch routes ButtonShopScreen → InjectAll, ButtonCheckOut → OnCheckout(quantity). Cap pending quantity (12 → 200), per-unit spec, GUID-deduped buttons.
Exercises
Section titled “Exercises”- Register a sidecar round-trip; confirm save → load → mod-removed vanilla load.
- Resolve three devices via inventory; subscribe
Rebuiltand re-resolve. - Add one shop item end-to-end (mesh → prefab →
HL Mods→ cart → checkout → reload).
Checkpoint
Section titled “Checkpoint”- State survives reload; devices addressed by UID; one custom item buyable.
Next: Guidebook CSharp Complete Project — ShiftHelper in C#.