Use typed runtime item catalog
This commit is contained in:
parent
0133531d40
commit
949907de65
11 changed files with 415 additions and 245 deletions
Binary file not shown.
|
|
@ -36,8 +36,6 @@ public class ActionGameManager : MonoBehaviour
|
|||
get => _gameDataDirectory ??= new DirectoryInfo(Application.dataPath).Parent.CreateSubdirectory("GameData");
|
||||
}
|
||||
|
||||
private static string LegacyAetherDatabasePath => LegacyItemCatalogBoundary.GetLegacyItemCatalogPath(GameDataDirectory);
|
||||
private static ILegacyItemCatalogReader LegacyCatalog => LegacyItemCatalogBoundary.GetCatalog(GameDataDirectory);
|
||||
|
||||
private static PlayerSettings _playerSettings;
|
||||
public static PlayerSettings PlayerSettings
|
||||
|
|
@ -245,9 +243,15 @@ public class ActionGameManager : MonoBehaviour
|
|||
Debug.Log($"Aetheria typed runtime catalog: {RuntimeCatalog.Items.Count} items, {RuntimeCatalog.Corporations.Count} corporations, {RuntimeCatalog.NameFiles.Count} name files");
|
||||
}
|
||||
|
||||
Debug.Log($"Aetheria legacy item catalog: {LegacyAetherDatabasePath}");
|
||||
if (RuntimeCatalog == null)
|
||||
{
|
||||
throw new InvalidOperationException("Aetheria typed runtime catalog is required before gameplay boot.");
|
||||
}
|
||||
|
||||
var runtimeItemCatalog = new AetheriaRuntimeItemCatalog(RuntimeCatalog);
|
||||
DatabaseLinkBase.BindRuntimeItemCatalog(runtimeItemCatalog);
|
||||
ItemManager = new ItemManager(
|
||||
LegacyCatalog,
|
||||
runtimeItemCatalog,
|
||||
Settings.GameplaySettings,
|
||||
Debug.Log);
|
||||
ZoneRenderer.ItemManager = ItemManager;
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
using System.IO;
|
||||
|
||||
public static class LegacyItemCatalogBoundary
|
||||
{
|
||||
private static LegacyItemCatalogCache _catalogCache;
|
||||
|
||||
public static string GetLegacyItemCatalogPath(DirectoryInfo gameDataDirectory)
|
||||
{
|
||||
return Path.Combine(gameDataDirectory.FullName, "AetherDB.msgpack");
|
||||
}
|
||||
|
||||
public static ILegacyItemCatalogReader GetCatalog(DirectoryInfo gameDataDirectory)
|
||||
{
|
||||
if (_catalogCache != null) return _catalogCache;
|
||||
|
||||
_catalogCache = new LegacyItemCatalogCache();
|
||||
_catalogCache.AddBackingStore(new SingleFileMessagePackBackingStore(GetLegacyItemCatalogPath(gameDataDirectory)));
|
||||
_catalogCache.PullAllBackingStores();
|
||||
DatabaseLinkBase.BindLegacyItemCatalog(_catalogCache);
|
||||
return _catalogCache;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f32bcbdbd7db4a3595f11f4540b90a3b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
338
Assets/Scripts/ServerShared/AetheriaRuntimeItemCatalog.cs
Normal file
338
Assets/Scripts/ServerShared/AetheriaRuntimeItemCatalog.cs
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using GameCult.Aetheria.State.Unity;
|
||||
using MessagePack;
|
||||
using Unity.Mathematics;
|
||||
|
||||
public interface IRuntimeItemCatalogReader
|
||||
{
|
||||
ItemData Get(Guid guid);
|
||||
T Get<T>(Guid guid) where T : ItemData;
|
||||
}
|
||||
|
||||
public sealed class AetheriaRuntimeItemCatalog : IRuntimeItemCatalogReader
|
||||
{
|
||||
private readonly Dictionary<Guid, ItemData> _items;
|
||||
private static readonly IReadOnlyDictionary<int, Type> BehaviorTypesByUnionKey = typeof(BehaviorData)
|
||||
.GetCustomAttributes<UnionAttribute>()
|
||||
.ToDictionary(attribute => attribute.Key, attribute => attribute.SubType);
|
||||
|
||||
public AetheriaRuntimeItemCatalog(AetheriaRuntimeCatalogSnapshot catalog)
|
||||
{
|
||||
if (catalog == null) throw new ArgumentNullException(nameof(catalog));
|
||||
|
||||
_items = catalog.Items
|
||||
.Select(ProjectItem)
|
||||
.Where(item => item != null)
|
||||
.ToDictionary(item => item.ID, item => item);
|
||||
}
|
||||
|
||||
public ItemData Get(Guid guid)
|
||||
{
|
||||
ItemData item;
|
||||
_items.TryGetValue(guid, out item);
|
||||
return item;
|
||||
}
|
||||
|
||||
public T Get<T>(Guid guid) where T : ItemData
|
||||
{
|
||||
return Get(guid) as T;
|
||||
}
|
||||
|
||||
private static ItemData ProjectItem(AetheriaRuntimeCatalogItem item)
|
||||
{
|
||||
if (!Guid.TryParse(item.LegacyId, out var id))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var projected = CreateItemData(item);
|
||||
if (projected == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
projected.ID = id;
|
||||
projected.Name = item.Name;
|
||||
projected.Description = item.Description;
|
||||
projected.Manufacturer = ParseGuid(item.ManufacturerLegacyId);
|
||||
projected.Mass = (float)item.Mass;
|
||||
projected.Shape = ProjectShape(item.ShapeWidth, item.ShapeHeight, item.ShapeCells);
|
||||
projected.Price = item.Price;
|
||||
|
||||
if (projected is SimpleCommodityData simple)
|
||||
{
|
||||
simple.MaxStack = item.MaxStack;
|
||||
}
|
||||
|
||||
if (projected is EquippableItemData equippable)
|
||||
{
|
||||
equippable.Durability = (float)item.Durability;
|
||||
equippable.Behaviors = item.BehaviorPayloads
|
||||
.Select(ProjectBehavior)
|
||||
.Where(behavior => behavior != null)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
if (projected is GearData gear)
|
||||
{
|
||||
gear.Hardpoint = ParseEnum(item.HardpointType, HardpointType.Tool);
|
||||
}
|
||||
|
||||
if (projected is CargoBayData cargoBay)
|
||||
{
|
||||
cargoBay.InteriorShape = ProjectShape(item.InteriorShapeWidth, item.InteriorShapeHeight, item.InteriorShapeCells);
|
||||
}
|
||||
|
||||
if (projected is WeaponItemData weapon)
|
||||
{
|
||||
weapon.WeaponRange = ParseEnum(item.WeaponRange, WeaponRange.Short);
|
||||
weapon.WeaponCaliber = ParseEnum(item.WeaponCaliber, WeaponCaliber.Small);
|
||||
weapon.WeaponType = ParseEnum(item.WeaponType, WeaponType.Laser);
|
||||
weapon.WeaponFireTypes = ParseFlags(item.WeaponFireTypes, WeaponFireType.None);
|
||||
weapon.WeaponModifiers = ParseFlags(item.WeaponModifiers, WeaponModifiers.None);
|
||||
}
|
||||
|
||||
if (projected is HullData hull)
|
||||
{
|
||||
hull.HullType = ParseEnum(item.HullType, HullType.Ship);
|
||||
hull.Hardpoints = item.Hardpoints.Select(ProjectHardpoint).ToList();
|
||||
}
|
||||
|
||||
return projected;
|
||||
}
|
||||
|
||||
private static ItemData CreateItemData(AetheriaRuntimeCatalogItem item)
|
||||
{
|
||||
switch (item.Category)
|
||||
{
|
||||
case "SimpleCommodityData":
|
||||
return new SimpleCommodityData();
|
||||
case "CompoundCommodityData":
|
||||
return new CompoundCommodityData();
|
||||
case "ConsumableItemData":
|
||||
return new ConsumableItemData();
|
||||
case "GearData":
|
||||
return new GearData();
|
||||
case "CargoBayData":
|
||||
return new CargoBayData();
|
||||
case "DockingBayData":
|
||||
return new DockingBayData();
|
||||
case "WeaponItemData":
|
||||
return new WeaponItemData();
|
||||
case "HullData":
|
||||
return new HullData();
|
||||
default:
|
||||
return !string.IsNullOrWhiteSpace(item.HardpointType) ? new GearData() : null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Shape ProjectShape(int width, int height, IReadOnlyList<AetheriaRuntimeShapeCell> cells)
|
||||
{
|
||||
var shape = new Shape(Math.Max(width, 1), Math.Max(height, 1));
|
||||
foreach (var coordinate in shape.AllCoordinates)
|
||||
{
|
||||
shape[coordinate] = false;
|
||||
}
|
||||
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
shape[new int2(cell.X, cell.Y)] = true;
|
||||
}
|
||||
|
||||
return shape;
|
||||
}
|
||||
|
||||
private static HardpointData ProjectHardpoint(AetheriaRuntimeHardpoint hardpoint)
|
||||
{
|
||||
return new HardpointData
|
||||
{
|
||||
Type = ParseEnum(hardpoint.Type, HardpointType.Hull),
|
||||
Position = new int2(hardpoint.PositionX, hardpoint.PositionY),
|
||||
Shape = ProjectShape(hardpoint.ShapeWidth, hardpoint.ShapeHeight, hardpoint.ShapeCells),
|
||||
Transform = hardpoint.Transform,
|
||||
Rotation = ParseEnum(hardpoint.Rotation, ItemRotation.None),
|
||||
Armor = (float)hardpoint.Armor
|
||||
};
|
||||
}
|
||||
|
||||
private static BehaviorData ProjectBehavior(AetheriaRuntimeBehaviorPayload payload)
|
||||
{
|
||||
if (!BehaviorTypesByUnionKey.TryGetValue(payload.UnionKey, out var behaviorType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var behavior = Activator.CreateInstance(behaviorType) as BehaviorData;
|
||||
if (behavior == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
behavior.Group = payload.Group;
|
||||
|
||||
foreach (var field in GetKeyedFields(behaviorType))
|
||||
{
|
||||
var payloadField = payload.Fields.FirstOrDefault(candidate => candidate.Key == field.key);
|
||||
if (payloadField == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
field.field.SetValue(behavior, ConvertValue(payloadField.Value, field.field.FieldType));
|
||||
}
|
||||
|
||||
return behavior;
|
||||
}
|
||||
|
||||
private static IEnumerable<(int key, FieldInfo field)> GetKeyedFields(Type type)
|
||||
{
|
||||
return type
|
||||
.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
.Select(field => (attribute: field.GetCustomAttribute<KeyAttribute>(), field))
|
||||
.Where(entry => entry.attribute?.IntKey != null)
|
||||
.Select(entry => (entry.attribute.IntKey.GetValueOrDefault(), entry.field));
|
||||
}
|
||||
|
||||
private static object ConvertValue(AetheriaRuntimeBehaviorValue value, Type targetType)
|
||||
{
|
||||
if (targetType == typeof(string)) return value.StringValue ?? "";
|
||||
if (targetType == typeof(bool)) return value.BoolValue;
|
||||
if (targetType == typeof(float)) return (float)value.NumberValue;
|
||||
if (targetType == typeof(double)) return value.NumberValue;
|
||||
if (targetType == typeof(int)) return checked((int)value.NumberValue);
|
||||
if (targetType == typeof(uint)) return checked((uint)value.NumberValue);
|
||||
if (targetType == typeof(Guid)) return ParseGuid(value.LegacyIdValue);
|
||||
if (targetType == typeof(float2)) return ConvertFloat2(value);
|
||||
if (targetType == typeof(float3)) return ConvertFloat3(value);
|
||||
if (targetType == typeof(float4)) return ConvertFloat4(value);
|
||||
if (targetType == typeof(int2)) return ConvertInt2(value);
|
||||
if (targetType.IsEnum) return ParseEnumValue(value, targetType);
|
||||
|
||||
if (targetType.IsArray)
|
||||
{
|
||||
return ConvertArray(value, targetType.GetElementType());
|
||||
}
|
||||
|
||||
if (typeof(IList).IsAssignableFrom(targetType) && targetType.IsGenericType)
|
||||
{
|
||||
return ConvertList(value, targetType);
|
||||
}
|
||||
|
||||
if (targetType.GetCustomAttribute<MessagePackObjectAttribute>() != null)
|
||||
{
|
||||
return ConvertKeyedObject(value, targetType);
|
||||
}
|
||||
|
||||
return targetType.IsValueType ? Activator.CreateInstance(targetType) : null;
|
||||
}
|
||||
|
||||
private static object ConvertArray(AetheriaRuntimeBehaviorValue value, Type elementType)
|
||||
{
|
||||
var array = Array.CreateInstance(elementType, value.Children.Count);
|
||||
for (var index = 0; index < value.Children.Count; index++)
|
||||
{
|
||||
array.SetValue(ConvertValue(value.Children[index], elementType), index);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
private static object ConvertList(AetheriaRuntimeBehaviorValue value, Type targetType)
|
||||
{
|
||||
var elementType = targetType.GetGenericArguments()[0];
|
||||
var list = Activator.CreateInstance(targetType) as IList;
|
||||
foreach (var child in value.Children)
|
||||
{
|
||||
list.Add(ConvertValue(child, elementType));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private static object ConvertKeyedObject(AetheriaRuntimeBehaviorValue value, Type targetType)
|
||||
{
|
||||
var instance = Activator.CreateInstance(targetType);
|
||||
var children = value.Children;
|
||||
foreach (var field in GetKeyedFields(targetType))
|
||||
{
|
||||
if (field.key >= 0 && field.key < children.Count)
|
||||
{
|
||||
field.field.SetValue(instance, ConvertValue(children[field.key], field.field.FieldType));
|
||||
}
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static object ParseEnumValue(AetheriaRuntimeBehaviorValue value, Type targetType)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(value.StringValue))
|
||||
{
|
||||
return Enum.Parse(targetType, value.StringValue, true);
|
||||
}
|
||||
|
||||
return Enum.ToObject(targetType, checked((int)value.NumberValue));
|
||||
}
|
||||
|
||||
private static Guid ParseGuid(string value)
|
||||
{
|
||||
return Guid.TryParse(value, out var result) ? result : Guid.Empty;
|
||||
}
|
||||
|
||||
private static T ParseEnum<T>(string value, T fallback) where T : struct
|
||||
{
|
||||
return Enum.TryParse(value, true, out T parsed) ? parsed : fallback;
|
||||
}
|
||||
|
||||
private static T ParseFlags<T>(string value, T fallback) where T : struct
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return value
|
||||
.Split('|')
|
||||
.Select(part => part.Trim())
|
||||
.Where(part => part.Length > 0)
|
||||
.Aggregate(fallback, (current, part) =>
|
||||
Enum.TryParse(part, true, out T parsed)
|
||||
? (T)Enum.ToObject(typeof(T), Convert.ToInt32(current) | Convert.ToInt32(parsed))
|
||||
: current);
|
||||
}
|
||||
|
||||
private static float2 ConvertFloat2(AetheriaRuntimeBehaviorValue value)
|
||||
{
|
||||
return new float2(
|
||||
value.Children.Count > 0 ? (float)value.Children[0].NumberValue : 0,
|
||||
value.Children.Count > 1 ? (float)value.Children[1].NumberValue : 0);
|
||||
}
|
||||
|
||||
private static float3 ConvertFloat3(AetheriaRuntimeBehaviorValue value)
|
||||
{
|
||||
return new float3(
|
||||
value.Children.Count > 0 ? (float)value.Children[0].NumberValue : 0,
|
||||
value.Children.Count > 1 ? (float)value.Children[1].NumberValue : 0,
|
||||
value.Children.Count > 2 ? (float)value.Children[2].NumberValue : 0);
|
||||
}
|
||||
|
||||
private static float4 ConvertFloat4(AetheriaRuntimeBehaviorValue value)
|
||||
{
|
||||
return new float4(
|
||||
value.Children.Count > 0 ? (float)value.Children[0].NumberValue : 0,
|
||||
value.Children.Count > 1 ? (float)value.Children[1].NumberValue : 0,
|
||||
value.Children.Count > 2 ? (float)value.Children[2].NumberValue : 0,
|
||||
value.Children.Count > 3 ? (float)value.Children[3].NumberValue : 0);
|
||||
}
|
||||
|
||||
private static int2 ConvertInt2(AetheriaRuntimeBehaviorValue value)
|
||||
{
|
||||
return new int2(
|
||||
value.Children.Count > 0 ? checked((int)value.Children[0].NumberValue) : 0,
|
||||
value.Children.Count > 1 ? checked((int)value.Children[1].NumberValue) : 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 897dd3ee37f64ef987d554516646c5cf
|
||||
timeCreated: 1781359840
|
||||
|
|
@ -68,7 +68,7 @@ public abstract class DatabaseEntry
|
|||
public class DatabaseLink<T> : DatabaseLinkBase where T : ItemData
|
||||
{
|
||||
[IgnoreMember]
|
||||
public T Value => ResolveLegacyItemCatalog<T>(LinkID);
|
||||
public T Value => ResolveRuntimeItemCatalog<T>(LinkID);
|
||||
}
|
||||
|
||||
[MessagePackObject]
|
||||
|
|
@ -78,17 +78,17 @@ public class DatabaseLinkBase
|
|||
public Guid LinkID;
|
||||
|
||||
[IgnoreMember]
|
||||
private static ILegacyItemCatalogReader Catalog { get; set; }
|
||||
private static IRuntimeItemCatalogReader Catalog { get; set; }
|
||||
|
||||
protected static T ResolveLegacyItemCatalog<T>(Guid linkId) where T : ItemData
|
||||
protected static T ResolveRuntimeItemCatalog<T>(Guid linkId) where T : ItemData
|
||||
{
|
||||
if (Catalog == null)
|
||||
throw new InvalidOperationException("Legacy item link resolution is not bound. Open LegacyItemCatalogBoundary before reading legacy item object graphs.");
|
||||
throw new InvalidOperationException("Runtime item catalog resolution is not bound. Open typed Aetheria runtime state before reading item object graphs.");
|
||||
|
||||
return Catalog.Get<T>(linkId);
|
||||
}
|
||||
|
||||
public static void BindLegacyItemCatalog(ILegacyItemCatalogReader catalog)
|
||||
public static void BindRuntimeItemCatalog(IRuntimeItemCatalogReader catalog)
|
||||
{
|
||||
if (catalog == null) throw new ArgumentNullException(nameof(catalog));
|
||||
|
||||
|
|
|
|||
|
|
@ -1,114 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using MessagePack;
|
||||
using UniRx;
|
||||
|
||||
public interface ILegacyItemCatalogReader
|
||||
{
|
||||
ItemData Get(Guid guid);
|
||||
T Get<T>(Guid guid) where T : ItemData;
|
||||
}
|
||||
|
||||
public class LegacyItemCatalogCache : ILegacyItemCatalogReader
|
||||
{
|
||||
|
||||
private readonly object addLock = new object();
|
||||
|
||||
private List<CacheBackingStore> _backingStores = new List<CacheBackingStore>();
|
||||
|
||||
private readonly Dictionary<Guid, ItemData> _entries = new Dictionary<Guid, ItemData>();
|
||||
|
||||
public void AddBackingStore(CacheBackingStore store)
|
||||
{
|
||||
_backingStores.Add(store);
|
||||
store.EntryAdded.Subscribe(entry =>
|
||||
{
|
||||
AddInternal(entry, store);
|
||||
});
|
||||
}
|
||||
|
||||
public void PullAllBackingStores()
|
||||
{
|
||||
foreach(var store in _backingStores) store.PullAll();
|
||||
}
|
||||
|
||||
private void AddInternal(DatabaseEntry entry, CacheBackingStore source = null)
|
||||
{
|
||||
lock(addLock)
|
||||
{
|
||||
if (entry is ItemData item)
|
||||
{
|
||||
_entries[item.ID] = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ItemData Get(Guid guid)
|
||||
{
|
||||
ItemData entry;
|
||||
_entries.TryGetValue(guid, out entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
public T Get<T>(Guid guid) where T : ItemData
|
||||
{
|
||||
return Get(guid) as T;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class CacheBackingStore
|
||||
{
|
||||
protected CacheBackingStore()
|
||||
{
|
||||
EntryAdded = new Subject<DatabaseEntry>();
|
||||
}
|
||||
|
||||
public abstract void PullAll();
|
||||
|
||||
public Subject<DatabaseEntry> EntryAdded { get; }
|
||||
|
||||
protected Dictionary<Guid, DatabaseEntry> Entries = new Dictionary<Guid, DatabaseEntry>();
|
||||
}
|
||||
|
||||
public abstract class SingleFileBackingStore : CacheBackingStore
|
||||
{
|
||||
public FileInfo FileInfo { get; }
|
||||
|
||||
public abstract DatabaseEntry[] Deserialize(byte[] data);
|
||||
|
||||
public SingleFileBackingStore(string filePath)
|
||||
{
|
||||
FileInfo = new FileInfo(filePath);
|
||||
}
|
||||
|
||||
public override void PullAll()
|
||||
{
|
||||
if (!FileInfo.Exists) return;
|
||||
|
||||
foreach (var entry in Deserialize(File.ReadAllBytes(FileInfo.FullName)))
|
||||
{
|
||||
Entries[entry.ID] = entry;
|
||||
EntryAdded.OnNext(entry);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class SingleFileMessagePackBackingStore : SingleFileBackingStore
|
||||
{
|
||||
public SingleFileMessagePackBackingStore(string filePath) : base(filePath)
|
||||
{
|
||||
RegisterResolver.Register();
|
||||
}
|
||||
|
||||
public override DatabaseEntry[] Deserialize(byte[] data)
|
||||
{
|
||||
return MessagePackSerializer.Deserialize<DatabaseEntry[]>(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a43b39c6991618b43aed4a24ffcdc0ff
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -30,7 +30,7 @@ public class ItemManager
|
|||
|
||||
// private Guid _forceLoadZone;
|
||||
|
||||
private readonly ILegacyItemCatalogReader _itemData;
|
||||
private readonly IRuntimeItemCatalogReader _itemData;
|
||||
public GameplaySettings GameplaySettings { get; }
|
||||
|
||||
// public double Time
|
||||
|
|
@ -46,7 +46,7 @@ public class ItemManager
|
|||
|
||||
// private readonly Dictionary<CraftedItemData, int> Tier = new Dictionary<CraftedItemData, int>();
|
||||
|
||||
public ItemManager(ILegacyItemCatalogReader itemData, GameplaySettings settings, Action<string> logger)
|
||||
public ItemManager(IRuntimeItemCatalogReader itemData, GameplaySettings settings, Action<string> logger)
|
||||
{
|
||||
_itemData = itemData;
|
||||
GameplaySettings = settings;
|
||||
|
|
|
|||
|
|
@ -17,21 +17,20 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
|
|||
|
||||
## Current Mechanism
|
||||
|
||||
- Unity client/runtime lives under `Assets/`. Gameplay still reads the legacy
|
||||
item/name catalog through a private `ActionGameManager` boot helper, which is
|
||||
now the only runtime entrypoint that calls `LegacyItemCatalogBoundary.GetCatalog`.
|
||||
`ActionGameManager` no longer owns a global legacy cache.
|
||||
- Unity client/runtime lives under `Assets/`. Gameplay now opens the typed
|
||||
runtime catalog from `GameData/aetheria-world.cc` and projects current
|
||||
`ItemData` DTOs through `AetheriaRuntimeItemCatalog`. `ActionGameManager` no
|
||||
longer owns or opens a legacy catalog cache.
|
||||
`GameCult.Aetheria.State.Unity`, the embedded Unity package under
|
||||
`Packages/org.gamecult.aetheria.state`, owns the Unity runtime path to
|
||||
`GameData/aetheria-world.cc` through `AetheriaRuntimeStateBoundary` and owns
|
||||
the Unity boot-time state-file existence probe through
|
||||
`AetheriaRuntimeStateBoot`. `ActionGameManager` consumes that package-owned
|
||||
boot report and warns when `aetheria-world.cc` is missing before treating
|
||||
legacy catalog data as runtime state. When the typed state file exists,
|
||||
boot report and throws if `aetheria-world.cc` is missing before gameplay
|
||||
boot. When the typed state file exists,
|
||||
`ActionGameManager` opens a package-owned read-only runtime catalog snapshot
|
||||
from the `.cc` directory store before constructing the temporary legacy
|
||||
`ItemManager`. `LegacyItemCatalogBoundary` no longer names or owns typed state
|
||||
paths. Local run saves, loadouts, player settings files, zone files, and
|
||||
`ItemManager`. Local run saves, loadouts, player settings files, zone files, and
|
||||
generated keyboard layout caches no longer write bespoke durable files. The
|
||||
legacy
|
||||
`PlayerSettings` runtime object is no longer decorated as a MessagePack
|
||||
|
|
@ -42,12 +41,10 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
|
|||
`Economy.Shared` wrapper have been removed from live source. The stale
|
||||
`StrategyGameManager.csbak` backup file and unused Unity asset MessagePack
|
||||
formatter classes have also been deleted, so the remaining direct
|
||||
`MessagePackSerializer` calls are the two legacy catalog deserializers and
|
||||
their resolver setup.
|
||||
`MessagePackSerializer` calls are no longer present in live runtime source.
|
||||
- Local legacy catalog data remains in `GameData/AetherDB.msgpack` and
|
||||
`GameData/NameFile/*.msgpack` as migration inputs. The Unity runtime legacy
|
||||
catalog bridge opens only `GameData/AetherDB.msgpack`; `NameFile/*.msgpack`
|
||||
is no longer a runtime backing store. The old
|
||||
`GameData/NameFile/*.msgpack` as migration inputs only. Unity gameplay no
|
||||
longer opens those MessagePack files at runtime. The old
|
||||
`PlayerSettings.msgpack`, `.loadout`, `.zone`, and
|
||||
`GameData/KeyboardLayouts/*.msgpack` authority paths are disabled or deleted.
|
||||
The old `SavedGame`/`SavedZone` DTOs and `Galaxy` save-loader constructor are
|
||||
|
|
@ -75,26 +72,17 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
|
|||
`EntityPack` serialization. Unity has not wired its save/loadout UI to this
|
||||
document yet, so the old in-memory `EntityPack` path is still a runtime
|
||||
construction helper, not durable authority.
|
||||
- The private `ActionGameManager` catalog boot helper delegates to
|
||||
`LegacyItemCatalogBoundary`, which opens `LegacyItemCatalogCache` as the only
|
||||
concrete pull-only catalog cache. Runtime consumers receive
|
||||
`ILegacyItemCatalogReader`, so the old `AetherDB.msgpack` backing store may hydrate
|
||||
in-memory item domain objects for the current Unity runtime, but the cache now
|
||||
admits only `ItemData` entries and cannot resolve generic `DatabaseEntry`
|
||||
object graphs. This path cannot
|
||||
push or delete legacy files. The legacy backing-store
|
||||
write/realtime APIs, public cache mutation methods, enumeration methods, and
|
||||
global-setting lookup methods have been deleted; only backing-store pull
|
||||
hydration can populate it, and only single-ID reads can consume it. The
|
||||
backing-store serializer methods are also deleted, so the legacy cache
|
||||
implementation exposes deserialization only. UI code no longer reaches into
|
||||
`LegacyItemCatalogBoundary.GetCatalog` directly.
|
||||
- `ItemManager` no longer exposes the raw `ILegacyItemCatalogReader` as public
|
||||
- `ActionGameManager` opens `AetheriaRuntimeCatalogStore` over
|
||||
`aetheria-world.cc`, projects it through `AetheriaRuntimeItemCatalog`, and
|
||||
binds `DatabaseLinkBase` to that typed runtime item reader before gameplay
|
||||
objects read `ItemInstance.Data.Value`. The old `LegacyItemCatalogBoundary`,
|
||||
`LegacyItemCatalogCache`, and runtime MessagePack deserializer path have been
|
||||
deleted.
|
||||
- `ItemManager` no longer exposes the raw runtime item catalog reader as public
|
||||
gameplay/UI API. Temporary item instantiation bridges now go through the
|
||||
narrow `ItemManager.GetCatalogEntry<T>` method. `ItemManager` no longer
|
||||
exposes a legacy catalog enumeration API. This does not remove the legacy reader under
|
||||
the hood; it prevents old catalog authority from leaking into every caller
|
||||
that only needs a domain lookup. The item properties UI no longer uses
|
||||
exposes a legacy catalog enumeration API. This prevents catalog authority from
|
||||
leaking into every caller that only needs a domain lookup. The item properties UI no longer uses
|
||||
`ItemManager` for manufacturer display; it resolves the manufacturer through
|
||||
the package-owned `ActionGameManager.RuntimeCatalog` typed snapshot. Entity
|
||||
restore and loadout manufacturer-distance weighting no longer use `ItemManager`
|
||||
|
|
@ -106,20 +94,18 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
|
|||
item candidate selection before hydrating selected legacy item DTOs by ID for
|
||||
exact fitting checks and behavior construction. The unused `TradeMenuDebug`
|
||||
script has been deleted instead of preserving an old uGUI debug path that
|
||||
turned typed trade rows back into legacy `ItemData` objects.
|
||||
- `Galaxy` generation no longer accepts `ILegacyItemCatalogReader` or `ItemManager`.
|
||||
turned typed trade rows back into legacy `ItemData` objects. That hydration
|
||||
now comes from typed state, not `AetherDB.msgpack`.
|
||||
- `Galaxy` generation no longer accepts a runtime item catalog reader or `ItemManager`.
|
||||
Sector and tutorial generation receive the package-owned typed runtime
|
||||
catalog. `Galaxy` projects typed corporation v2 records into temporary legacy
|
||||
`Faction` DTOs, including allegiance edges, for the existing simulation shape
|
||||
and resolves full name arrays from `aetheria.name_file.v2` records. The
|
||||
legacy catalog runtime no longer opens the old `GameData/NameFile/*.msgpack`
|
||||
directory.
|
||||
`ActionGameManager` still has a
|
||||
private legacy catalog boot helper that feeds `ItemManager` for item/entity
|
||||
systems; menu/generation paths no longer receive the legacy reader directly.
|
||||
runtime no longer opens the old `GameData/NameFile/*.msgpack` directory.
|
||||
- `DatabaseLink<T>.Value` can resolve legacy links only after
|
||||
`LegacyItemCatalogBoundary` binds the pull-only item catalog cache. Legacy catalog
|
||||
construction no longer grabs global `DatabaseLinkBase` authority.
|
||||
`ActionGameManager` binds the typed runtime item catalog projected from
|
||||
`aetheria-world.cc`. MessagePack catalog construction no longer grabs global
|
||||
`DatabaseLinkBase` authority.
|
||||
- `Economy.Server` now starts the modern `Aetheria.State` CultMesh node and no
|
||||
longer owns RethinkDB/LiteNetLib state.
|
||||
- `Aetheria.State.Import` writes a typed quarantine manifest and migration
|
||||
|
|
@ -154,8 +140,8 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
|
|||
typed item shape masks, typed interior masks, hardpoints, and behavior
|
||||
payloads. It also owns a read-only Unity-compatible CultCache directory-store
|
||||
opener for the known Aetheria catalog schemas, so Unity runtime code can read
|
||||
`aetheria-world.cc` catalog records without `DatabaseEntry` or
|
||||
`LegacyItemCatalogCache`. The SDK-style `Aetheria.State.Unity` facade includes
|
||||
`aetheria-world.cc` catalog records without `DatabaseEntry` or a MessagePack
|
||||
catalog cache. The SDK-style `Aetheria.State.Unity` facade includes
|
||||
that package source and owns the full `Aetheria.State` mapper plus Eve surface
|
||||
read path for .NET smokes. Neither is a simulation owner and neither writes
|
||||
state.
|
||||
|
|
@ -258,33 +244,28 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
|
|||
The embedded `GameCult.Aetheria.State.Unity` package owns Unity's runtime
|
||||
typed state file path plus the boot-time state-file probe, and is the landing
|
||||
zone for future typed catalog boot.
|
||||
`LegacyItemCatalogBoundary` is the only named owner for opening the old item catalog
|
||||
and binding legacy `DatabaseLink<T>` resolution until catalog migration lands.
|
||||
`ActionGameManager` has a private boot helper that calls it only to construct
|
||||
`ItemManager`. Runtime code receives narrow `ItemManager` catalog methods
|
||||
where gameplay only needs lookups. `ItemManager` still receives
|
||||
`ILegacyItemCatalogReader` until typed runtime catalog boot replaces it; `Galaxy`,
|
||||
entity restore, and faction-distance loadout weighting have been moved off
|
||||
legacy faction catalog reads.
|
||||
`ActionGameManager` opens the package-owned typed runtime catalog snapshot
|
||||
and binds `DatabaseLink<T>` resolution through `AetheriaRuntimeItemCatalog`.
|
||||
Runtime code receives narrow `ItemManager` catalog methods where gameplay
|
||||
only needs item lookups. `Galaxy`, entity restore, and faction-distance
|
||||
loadout weighting have been moved off legacy faction catalog reads.
|
||||
The embedded Unity state package owns the read-only runtime catalog model
|
||||
contract and the read-only known-schema `.cc` catalog opener. `ActionGameManager`
|
||||
now exposes that typed package snapshot at boot. The SDK-style
|
||||
`Aetheria.State.Unity` facade maps typed `.cc` documents into the same
|
||||
contract for full .NET smokes and Eve surface reads. Neither writes state or
|
||||
owns simulation. The legacy cache no longer has a public mutation surface, but
|
||||
it still materializes `ItemManager`. The legacy cache is item-only; old
|
||||
`Faction`, `NameFile`, global, zone, player, task, and body entries are no
|
||||
longer accepted as runtime catalog entries. Item properties manufacturer display is
|
||||
a typed snapshot consumer; loadout generation is the remaining runtime
|
||||
single-ID item hydration bridge. The old trade debug UI and console `give`
|
||||
command have been deleted. No live
|
||||
caller can enumerate legacy catalog entries through `ItemManager` or
|
||||
`ILegacyItemCatalogReader`.
|
||||
owns simulation. The runtime no longer has a MessagePack catalog cache.
|
||||
`AetheriaRuntimeItemCatalog` materializes temporary `ItemData` DTOs from typed
|
||||
item records for the old simulation object model. Item properties
|
||||
manufacturer display is a typed snapshot consumer; loadout generation is the
|
||||
remaining runtime single-ID item lookup bridge. The old trade debug UI and
|
||||
console `give` command have been deleted. No live caller can enumerate legacy
|
||||
catalog entries through `ItemManager`.
|
||||
- Inputs: Unity gameplay code, legacy catalog files, typed state documents, and
|
||||
CultMesh server state.
|
||||
- Outputs: `Aetheria.State` emits `.cc` state and CultMesh documents. Legacy
|
||||
catalog reads emit in-memory domain objects only; the old local save and
|
||||
editor catalog write paths have been cut. Migrated catalog documents are
|
||||
- Outputs: `Aetheria.State` emits `.cc` state and CultMesh documents. Runtime
|
||||
typed item catalog projection emits in-memory domain objects only; the old
|
||||
local save and editor catalog write paths have been cut. Migrated catalog documents are
|
||||
addressed through `AetheriaCatalogKeys` and `AetheriaStateNode` legacy-ID
|
||||
methods, not importer-local string concatenation. Player settings and loadout
|
||||
templates have typed state documents and node put/get paths; Unity's menu,
|
||||
|
|
@ -303,9 +284,9 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
|
|||
generated keyboard layout caches, and server-side `DatabaseCache` paths.
|
||||
- Shared paths: manual gameplay edits, editor edits, server updates, file load,
|
||||
file save, and migration all need to converge on one typed commit primitive.
|
||||
- Deletion line: no new behavior should be added to `LegacyItemCatalogBoundary`, the
|
||||
old `DatabaseEntry`, `LegacyItemCatalogCache`, or MessagePack catalog paths
|
||||
except bounded migration readers that emit typed `Aetheria.State` documents.
|
||||
- Deletion line: no new behavior should be added to old `DatabaseEntry` or
|
||||
MessagePack catalog paths except bounded migration readers that emit typed
|
||||
`Aetheria.State` documents.
|
||||
|
||||
## Target Authority Map
|
||||
|
||||
|
|
@ -321,11 +302,10 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
|
|||
`DatabaseEntry.ID`, global cache statics, and any compatibility reader.
|
||||
- Shared paths: gameplay input, editor edits, import/deep-load, replication,
|
||||
simulation ticks, and tests all call the same typed state service.
|
||||
- Deletion line: after catalog migration smokes pass, replace
|
||||
`ActionGameManager`'s private legacy catalog boot helper and `ItemManager`
|
||||
boot with the typed runtime catalog package, then delete or quarantine the
|
||||
remaining `LegacyItemCatalogCache`/`DatabaseEntry` runtime catalog dependency and
|
||||
remove old MessagePack backing store classes from live Unity source.
|
||||
- Deletion line: replace the remaining `DatabaseEntry`/`ItemData` runtime DTO
|
||||
projection with native typed item instances, then remove old MessagePack
|
||||
catalog metadata from live Unity source once import-only migration no longer
|
||||
needs it.
|
||||
|
||||
## Intended Change
|
||||
|
||||
|
|
@ -451,11 +431,14 @@ First Aetheria surfaces to publish:
|
|||
remain migration inputs only.
|
||||
- Done: narrow `ILegacyItemCatalogReader` and `DatabaseLink<T>` to `ItemData`;
|
||||
`LegacyItemCatalogCache` ignores non-item entries from `AetherDB.msgpack`.
|
||||
- Replace `ActionGameManager` cache bootstrap with the new state runtime.
|
||||
- Done: replace `ActionGameManager` cache bootstrap with
|
||||
`AetheriaRuntimeItemCatalog`, a typed-state-backed projection from
|
||||
`AetheriaRuntimeCatalogSnapshot`; delete `LegacyItemCatalogBoundary`,
|
||||
`LegacyItemCatalogCache`, and the runtime MessagePack deserializer path.
|
||||
- Convert domain references from GUID/base-class patterns to typed record
|
||||
refs.
|
||||
- Remove runtime dependency on `LegacyItemCatalogCache` and `DatabaseEntry` as
|
||||
owners.
|
||||
- Remove runtime dependency on `DatabaseEntry`/`ItemData` DTOs as item
|
||||
instance owners.
|
||||
|
||||
5. Mesh host
|
||||
- Replace `Economy.Server` RethinkDB and LiteNetLib database authority with a
|
||||
|
|
@ -518,9 +501,9 @@ First Aetheria surfaces to publish:
|
|||
- Unity runtime catalog smoke proves read-only catalog consumers can open the
|
||||
typed `.cc` store through both the SDK-style `Aetheria.State.Unity` opener and
|
||||
the embedded package-owned read-only catalog opener, receiving the same
|
||||
package-owned runtime catalog read models without `DatabaseEntry` or
|
||||
`LegacyItemCatalogCache`, including typed item masks, interior masks, hardpoint
|
||||
definitions, corporation/name-file links, and typed behavior payloads.
|
||||
package-owned runtime catalog read models without a MessagePack catalog cache,
|
||||
including typed item masks, interior masks, hardpoint definitions,
|
||||
corporation/name-file links, and typed behavior payloads.
|
||||
- Unity play smoke proves runtime UI reads from Eve surfaces and sends commands
|
||||
through the shared state service.
|
||||
- UI Toolkit lowering parity compares Aetheria surfaces against the Eve browser
|
||||
|
|
|
|||
Loading…
Reference in a new issue