Aetheria/Assets/Scripts/ServerShared/AetheriaStores.cs
Meta Vens db83cd290d Make loadouts authored catalog presets with an editor capture command
Loadouts are ship presets (variants) for spawners, not a player feature.

- Route Loadout to the catalog; the player store no longer holds it.
- Materialize takes no credits: affordability, the charge and Loadouts.Price
  are deleted, since only the player menu priced loadouts. Availability,
  key-order product choice, all-or-nothing failures and the weapon-group
  rules stay.
- Delete the InventoryPanel Save Loadout and Restore Loadout entries and
  RestoreLoadout, which also removes its docking-bay overwrite.
- The editor opens the catalog writable in the one process cache, and the
  editor-only console command capturepreset "<name>" [replace] captures the
  piloted ship. Loadouts.Commit keys a preset by name, refuses a same-name
  replace unless asked, and commits conditionally on that key. The single-file
  store then lands only that record onto the file as it is on disk. An
  unconditional commit would re-serialize every live catalog instance,
  including any that play has mutated.
- Tests: capture -> commit -> fresh reopen round-trip, explicit replace with
  no duplicate, and a preset commit that does not persist a mutated live
  Faction. The money-only tests are removed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:09:43 +02:00

57 lines
3.2 KiB
C#

using System;
using System.Linq;
using GameCult.Caching;
using GameCult.Caching.MessagePack;
// Aetheria's stores: one home store per type, composed here and nowhere else.
public static class AetheriaStores
{
public static readonly Type[] CatalogTypes = { typeof(ItemData), typeof(Faction), typeof(FactionProductData), typeof(PersonalityAttribute), typeof(NameFile), typeof(InputLayout), typeof(Loadout) };
public static readonly Type[] RunTypes = { typeof(OrbitData), typeof(BodyData), typeof(SavedZone), typeof(SavedGame) };
public static readonly Type[] PlayerTypes = { typeof(PlayerSettings) };
// Attaches (hydrates) the catalog read-only unless catalogWritable, then the run and player stores when given.
// A read-only catalog throws when a [CultGlobal] type routed to it has no record. A writable catalog is being
// authored (the importer starts from an empty file), so it is not held to that.
public static CultCache Open(string catalogPath, string runPath = null, string playerPath = null, bool catalogWritable = false)
{
var cache = new CultCache();
try
{
cache.AddBackingStore(new SingleFileMessagePackBackingStore(catalogPath, !catalogWritable), CatalogTypes);
if (runPath != null) cache.AddBackingStore(new SingleFileMessagePackBackingStore(runPath), RunTypes);
if (playerPath != null) cache.AddBackingStore(new SingleFileMessagePackBackingStore(playerPath), PlayerTypes);
var missing = catalogWritable ? null : cache.Registry.AllDescriptors.FirstOrDefault(descriptor =>
descriptor.IsGlobal &&
CatalogTypes.Any(home => home.IsAssignableFrom(descriptor.DocumentType)) &&
cache.AllStoredDocuments.All(stored => stored.Descriptor != descriptor));
if (missing != null)
throw new InvalidOperationException($"Catalog {catalogPath} has no {missing.SchemaName} record; catalog globals are authored, never invented.");
return cache;
}
catch
{
cache.Dispose();
throw;
}
}
}
public static class CultRecordRefs
{
public static bool IsSet(this CultRecordKey key) => !string.IsNullOrEmpty(key.Value);
public static bool IsSet<T>(this CultRecordRef<T> reference) => reference.Key.IsSet();
// The document a reference names, or null when it is unset or names nothing this cache holds.
public static T Get<T>(this CultCache cache, CultRecordRef<T> reference) where T : class =>
reference.IsSet() ? cache.Get<T>(reference.Key) : null;
// A reference to a document this cache holds. The key comes from the cache, never from the document.
public static CultRecordRef<T> RefOf<T>(this CultCache cache, T document) where T : class =>
new CultRecordRef<T>(cache.TryGetHandle(document)?.Key ??
throw new InvalidOperationException($"This cache holds no such {document.GetType().Name}."));
// Writes a document to its home store; the cache mints its key the first time.
public static CultRecordRef<T> Upsert<T>(this CultCache cache, T document) where T : class =>
new CultRecordRef<T>(cache.UpsertAsync(document).Result.Key);
}