Pin AetheriaStores routing with tests; make PlayerSettings readable

Cut 8b. tests/Aetheria.Shared.Tests (xunit, net10.0) builds a temp-directory
catalog through AetheriaStores.Open(catalogWritable: true) per test:
- OpenThenFlushLeavesEveryFileByteIdentical: the catalog, plus run and
  player stores seeded with their globals, a SavedZone and an InputLayout.
- WeaponWrittenThroughGearHandleReloadsAsWeapon
- CatalogRefusesWrites: bytes unchanged too.
- SavedZoneNeverLandsInCatalog: no home without a run store; the snapshot
  schemas show where it landed.
- FactionIsASingletonInstance: through a ref, a ref-keyed map key, and the
  name index.
- MissingCatalogGlobalIsLoud: uses a test-only [CultGlobal] routed to the
  catalog. The game has no catalog globals yet.

The first test found a real defect. PlayerInputSettings.InputActionMap was
keyed by (string, int), and CultLib's untrusted-data MessagePack security
has no hash-resistant comparer for a tuple, so a player store could never
be read back. It is now action name to binding index to path; its two
Unity readers change in the Unity commit.

Each test was checked to fail with the rule it pins removed; see the
report.

Builds: Aetheria.Shared, AetherDb, Aetheria.Shared.Tests (6/6 pass).
Unity does not build yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-14 19:12:54 +02:00
commit c2026142be
39 changed files with 151 additions and 2079 deletions

View file

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 2501605fca062c84d963e811df551698
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,43 +0,0 @@
using System;
using System.IO;
using UnityEditor;
using UnityEngine;
public class AetheriaDatabaseView : DatabaseListView
{
// Singleton to avoid multiple instances of window.
private static DatabaseListView _instance;
public static DatabaseListView Instance => _instance ? _instance : GetWindow<AetheriaDatabaseView>();
[MenuItem("Window/Aetheria/Database Tools")]
static void Init() => Instance.Show();
private void Awake()
{
_instance = this;
}
protected override string FilePath => new DirectoryInfo(Application.dataPath).Parent.CreateSubdirectory("GameData").FullName;
protected override DatabaseEntryGroup[] Groupers => new DatabaseEntryGroup[]
{
new DatabaseEntryGroup<SimpleCommodityData,SimpleCommodityCategory>(
data => data.Category,
category => Enum.GetName(typeof(SimpleCommodityCategory), category),
(data, category) => data.Category = category ),
new DatabaseEntryGroup<CompoundCommodityData, CompoundCommodityCategory>(
data => data.Category,
category => Enum.GetName(typeof(CompoundCommodityCategory), category),
(data, category) => data.Category = category ),
new DatabaseEntryGroup<GearData, HardpointType>(
data => data.HardpointType,
type => Enum.GetName(typeof(HardpointType), type),
(data, type) => data.Hardpoint = type),
new DatabaseEntryGroup<GearData, Guid>(
data => data.Manufacturer,
factionID => factionID == Guid.Empty ? "Default" : cultCache.Get<Faction>(factionID).ShortName,
(data, faction) => data.Manufacturer = faction),
new DatabaseEntryGroup<HullData, HullType>(
data => data.HullType,
type => Enum.GetName(typeof(HullType), type),
(data, type) => data.HullType = type)
};
}

View file

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 4016d25951e246a184441b2aae75eba0
timeCreated: 1617151686

View file

@ -1,31 +0,0 @@
using System;
public abstract class BaseInspector<T>
{
protected const int width = 150;
protected const int labelWidth = 20;
protected const int toggleWidth = 18;
protected const int arrowWidth = 22;
public abstract T Inspect(string label, T value, object parent, DatabaseInspector inspectorWindow);
}
public abstract class BaseInspector<T, A> where A : Attribute
{
protected const int width = 150;
protected const int labelWidth = 20;
protected const int toggleWidth = 18;
protected const int arrowWidth = 22;
public abstract T Inspect(string label, T value, object parent, DatabaseInspector inspectorWindow, A attribute);
}
public abstract class BaseInspector<T, A, B> where A : Attribute where B : Attribute
{
protected const int width = 150;
protected const int labelWidth = 20;
protected const int toggleWidth = 18;
protected const int arrowWidth = 22;
public abstract T Inspect(string label, T value, object parent, DatabaseInspector inspectorWindow, A attributeA, B attributeB);
}

View file

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: f3cde78766994141a3da6ab88d09118f
timeCreated: 1617052142

View file

@ -1,293 +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;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using MessagePack;
using Newtonsoft.Json;
using CultMath;
using UnityEditor;
using UnityEngine;
using static CultMath.math;
using static UnityEditor.EditorGUILayout;
using int2 = CultMath.int2;
using Object = UnityEngine.Object;
public class DatabaseInspector : EditorWindow
{
public static CultCache CultCache;
public DatabaseEntry entry;
// Singleton to avoid multiple instances of window.
private static DatabaseInspector _instance;
public static DatabaseInspector Instance => _instance ? _instance : GetWindow<DatabaseInspector>();
private static bool _listItemStyle;
public static GUIStyle ListItemStyle => (_listItemStyle = !_listItemStyle) ? DatabaseListView.ListStyleEven : DatabaseListView.ListStyleOdd;
public const int Width = 150;
private Vector2 _view;
private GUIStyle _warning;
private List<(Type targetType, HashSet<Type> attributes, Type inspectorType, object instance)> _inspectors =
new List<(Type targetType, HashSet<Type> attributes, Type inspectorType, object instance)>();
private List<(Type targetType, HashSet<Type> attributes, Type inspectorType, Dictionary<Type, object> instances)> _genericInspectors =
new List<(Type targetType, HashSet<Type> attributes, Type inspectorType, Dictionary<Type, object> instances)>();
private HashSet<object> _foldouts = new HashSet<object>();
public Color LabelColor => EditorGUIUtility.isProSkin ? Color.white : Color.black;
void OnEnable()
{
_instance = this;
CultCache.OnUpdate += (_,__) => EditorDispatcher.Dispatch(Repaint);
wantsMouseMove = true;
_warning = new GUIStyle(EditorStyles.boldLabel);
_warning.normal.textColor = Color.red;
var inspectorTypes = typeof(BaseInspector<>).GetAllGenericChildClasses()
.Concat(typeof(BaseInspector<,>).GetAllGenericChildClasses())
.Concat(typeof(BaseInspector<,,>).GetAllGenericChildClasses());
foreach (var type in inspectorTypes)
{
var genericArguments = type.BaseType.GetGenericArguments();
if(genericArguments.Any())
{
if (genericArguments[0].ContainsGenericParameters)
{
_genericInspectors.Add((
genericArguments[0].GetGenericTypeDefinition(),
new HashSet<Type>(genericArguments.Skip(1)),
type,
new Dictionary<Type, object>()
));
}
else
{
_inspectors.Add((
genericArguments[0],
new HashSet<Type>(genericArguments.Skip(1)),
type,
Activator.CreateInstance(type)
));
}
}
}
}
public void Inspect(object obj, object parent = null)
{
foreach (var field in obj.GetType().GetFields().OrderBy(f=>f.GetCustomAttribute<KeyAttribute>()?.IntKey ?? 0))
{
Inspect(obj, field, parent);
}
}
public void Inspect(object obj, FieldInfo field, object parent = null)
{
var inspectable = field.GetCustomAttribute<InspectableAttribute>();
if (inspectable == null) return;
var attributes = field.GetCustomAttributes(false);
var type = field.FieldType;
var value = field.GetValue(obj);
field.SetValue(obj, InspectMember(field.Name.SplitCamelCase(), value, parent ?? obj, type, false, attributes));
}
public object InspectMember(string label, object value, object parent, Type type, bool suppressContainer = false, params object[] attributes)
{
var style = _listItemStyle;
object GetAttribute(Type attributeType)
{
return attributes.FirstOrDefault(attributeType.IsInstanceOfType);
}
if (value == null && type != typeof(string) && !(type.IsInterface || type.IsAbstract)) value = Activator.CreateInstance(type);
var potentialInspectors = _inspectors
.Where(pi =>
pi.targetType == type &&
pi.attributes.All(attributeType => GetAttribute(attributeType) != null));
if (potentialInspectors.Any())
{
var preferredInspector = potentialInspectors.MaxBy(i => i.attributes.Count);
var parameters = new[] {label, value, parent, this}
.Concat(preferredInspector.attributes.Select(GetAttribute))
.ToArray();
value = preferredInspector.inspectorType.GetMethod("Inspect").Invoke(preferredInspector.instance, parameters);
_listItemStyle = style;
return value;
}
if (type.IsEnum)
{
var isflags = type.GetCustomAttributes<FlagsAttribute>().Any();
var names = Enum.GetNames(type);
value = InspectEnum(label, (int) value, isflags ? names.Skip(1).ToArray() : names, isflags);
_listItemStyle = style;
return value;
}
if (type.GetCustomAttribute<InspectableAttribute>() != null)
{
if(!suppressContainer)
GUILayout.BeginVertical(GUI.skin.box);
using (new HorizontalScope())
{
if(!suppressContainer)
{
if (Foldout(_foldouts.Contains(value), label, true, EditorStyles.boldLabel))
_foldouts.Add(value);
else _foldouts.Remove(value);
}
if (type.IsInterface || type.IsAbstract)
{
var subTypes = type.GetAllChildClasses().Where(t=>!(t.IsInterface || t.IsAbstract)).ToArray();
var selected = value != null ? Array.IndexOf(subTypes, value.GetType()) + 1 : 0;
var newSelection = Popup(selected,
subTypes.Select(t => t.Name.SplitCamelCase()).Prepend("None").ToArray());
if (newSelection != selected)
value = newSelection == 0 ? null : Activator.CreateInstance(subTypes[newSelection - 1]);
}
else if (value == null) value = Activator.CreateInstance(type);
}
if (value != null && (_foldouts.Contains(value) || suppressContainer)) Inspect(value, parent);
if(!suppressContainer)
GUILayout.EndVertical();
_listItemStyle = style;
return value;
}
if (type.IsGenericType)
{
var genericTypeDefinition = type.GetGenericTypeDefinition();
var potentialGenericInspectors = _genericInspectors
.Where(pi =>
pi.targetType == genericTypeDefinition &&
pi.attributes.All(attributeType => GetAttribute(attributeType) != null));
if (potentialGenericInspectors.Any())
{
var genericTypeArguments = type.GenericTypeArguments;
var preferredInspector = potentialGenericInspectors.MaxBy(i => i.attributes.Count);
var genericInspectorType = preferredInspector.inspectorType.MakeGenericType(genericTypeArguments);
if (!preferredInspector.instances.ContainsKey(genericInspectorType))
preferredInspector.instances[genericInspectorType] =
Activator.CreateInstance(genericInspectorType);
var parameters = new[] {label, value, parent, this};
value = genericInspectorType.GetMethod("Inspect").Invoke(preferredInspector.instances[genericInspectorType], parameters);
_listItemStyle = style;
return value;
}
}
Debug.Log($"Field \"{label}\" has unknown type {type.Name}. No inspector was generated.");
_listItemStyle = style;
return value;
}
public int InspectEnum(string label, int value, string[] enumOptions, bool flags)
{
if (flags)
{
Space();
LabelField(label, EditorStyles.boldLabel);
using (new VerticalScope(GUI.skin.box))
{
for (var i = 0; i < enumOptions.Length; i++)
{
using (var h = new HorizontalScope())
{
GUILayout.Label(enumOptions[i], GUILayout.Width(Width));
var set = Toggle(1 << i == (1 << i & value));
if (set)
value |= 1 << i;
else value &= ~(1 << i);
}
}
}
return value;
}
using (var h = new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(Width));
return Popup(value, enumOptions);
}
}
public void OnGUI()
{
if (entry==null)
{
LabelField("No Entry Selected!");
return;
}
if(entry is INamedEntry namedEntry)
GUILayout.Label(namedEntry.EntryName, EditorStyles.boldLabel);
_view = BeginScrollView(
_view,
false,
false,
GUIStyle.none,
GUI.skin.verticalScrollbar,
GUI.skin.scrollView,
GUILayout.Width(EditorGUIUtility.currentViewWidth),
GUILayout.ExpandHeight(true));
// Serialize inspected object so we can check whether it has actually changed
RegisterResolver.Register();
var previousBytes = MessagePackSerializer.Serialize<DatabaseEntry>(entry);
var previousHash = previousBytes.GetHashSHA1();
EditorGUI.BeginChangeCheck();
var dataEntry = entry;
using (new HorizontalScope())
{
// if(GUILayout.Button("Print JSON"))
// Debug.Log(entry.ToJson());
if (GUILayout.Button("Delete Entry"))
{
CultCache.Remove(entry);
entry = null;
return;
}
if (GUILayout.Button("Clone Entry"))
{
var copy = MessagePackSerializer.Deserialize<DatabaseEntry>(previousBytes);
copy.ID = Guid.NewGuid();
CultCache.Add(copy);
}
}
using (var h = new HorizontalScope())
{
GUILayout.Label("ID");
LabelField(dataEntry.ID.ToString());
}
_listItemStyle = false;
Inspect(entry);
if (EditorGUI.EndChangeCheck())
{
if(previousHash != MessagePackSerializer.Serialize(entry).GetHashSHA1())
CultCache.Add(entry);
}
EndScrollView();
}
}

View file

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 30b612dc279f724418b8a7a5426287c2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,319 +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 System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using static UnityEditor.EditorGUILayout;
public abstract class DatabaseListView : EditorWindow
{
public static GUIStyle ListStyleOdd;
public static GUIStyle ListStyleEven;
public Guid SelectedItem;
public GUIStyle SelectedStyle;
protected CultCache cultCache;
private static DatabaseInspector _inspector;
private Dictionary<string, (bool foldout, (Type entryType, bool foldout)[] types)> _tables;
private Type[] _globalTypes;
private Vector2 _view;
private HashSet<int> _groupFoldouts = new HashSet<int>();
protected abstract string FilePath { get; }
protected virtual DatabaseEntryGroup[] Groupers => new DatabaseEntryGroup[] { };
protected class DatabaseEntryGroup<T, K> : DatabaseEntryGroup where T : DatabaseEntry
{
public Func<T,K> GroupSelector { get; }
public Func<K,string> GroupLabel { get; }
public Action<T, K> Activator { get; }
public DatabaseEntryGroup(Func<T, K> groupSelector, Func<K, string> groupLabel, Action<T, K> activator)
{
GroupSelector = groupSelector;
GroupLabel = groupLabel;
Activator = activator;
}
public override bool CanGroup(Type type)
{
return typeof(T) == type;
}
public override object SelectGroup(DatabaseEntry o)
{
if (o is T t) return GroupSelector(t);
return null;
}
public override string GetGroupName(object o)
{
if (o is K k) return GroupLabel(k);
return "ERROR";
}
public override int HashGroup(object o)
{
return o.GetHashCode();
if (o is K k) return k.GetHashCode();
return 0;
}
public override void Activate(DatabaseEntry o, object g)
{
if(o is T t && g is K k) Activator(t, k);
throw new ArgumentException(
$"Entry group activation failed! Expected: object type {typeof(T)}, group type {typeof(K)}; received: object type {o.GetType()}, group type {g.GetType()}");
}
}
protected abstract class DatabaseEntryGroup
{
public abstract bool CanGroup(Type type);
public abstract object SelectGroup(DatabaseEntry o);
public abstract string GetGroupName(object o);
public abstract int HashGroup(object o);
public abstract void Activate(DatabaseEntry o, object g);
}
public Color LabelColor => EditorGUIUtility.isProSkin ? Color.white : Color.black;
private bool _listItemStyle;
private SingleFileMessagePackBackingStore _msgPackStore;
public GUIStyle ListItemStyle => (_listItemStyle = !_listItemStyle) ? ListStyleEven : ListStyleOdd;
void OnEnable()
{
ListStyleOdd = new GUIStyle
{
normal = {background = EditorGUIUtility.isProSkin ? XKCDColors.DarkGrey.ToTexture() : XKCDColors.LightGrey.ToTexture()},
margin = new RectOffset(0, 0, 0, 0)
};
ListStyleEven = new GUIStyle
{
normal = {background = EditorGUIUtility.isProSkin ? Color.Lerp(XKCDColors.DarkGrey,Color.black,.5f).ToTexture() : Color.Lerp(XKCDColors.LightGrey,Color.white,.5f).ToTexture()},
margin = new RectOffset(0, 0, 0, 0)
};
_msgPackStore = new SingleFileMessagePackBackingStore(Path.Combine(FilePath, "AetherDB.msgpack"));
cultCache = new CultCache();
cultCache.AddBackingStore(_msgPackStore);
cultCache.AddBackingStore(new MultiFileMessagePackBackingStore(FilePath), typeof(NameFile));
cultCache.PullAllBackingStores();
cultCache.OnUpdate += (_,__) => EditorDispatcher.Dispatch(Repaint);
DatabaseInspector.CultCache = cultCache;
_inspector = DatabaseInspector.Instance;
_inspector.Show();
_globalTypes = typeof(DatabaseEntry).GetAllChildClasses()
.Where(t => t.GetCustomAttribute<InspectableAttribute>() != null && t.GetCustomAttribute<GlobalSettingsAttribute>() != null).ToArray();
_tables = typeof(DatabaseEntry).GetAllChildClasses()
.Where(t => t.GetCustomAttribute<InspectableAttribute>() != null && t.GetCustomAttribute<GlobalSettingsAttribute>() == null)
.GroupBy(t=>t.GetCustomAttribute<DatabaseCategoryAttribute>()?.Category ?? "Default")
.ToDictionary(group=>group.Key, group=> (false, group.Select(t=>(t, false)).ToArray()));
InitStyles();
}
private void InitStyles()
{
SelectedStyle = new GUIStyle
{
normal = {background = EditorGUIUtility.isProSkin ? XKCDColors.DarkGreyBlue.ToTexture() : XKCDColors.Greyblue.ToTexture()},
margin = new RectOffset(0, 0, 0, 0)
};
}
public void OnGUI()
{
Event currentEvent = Event.current;
EventType currentEventType = currentEvent.type;
if (currentEventType == EventType.DragUpdated)
// Indicate that we don't accept drags ourselves
DragAndDrop.visualMode = DragAndDropVisualMode.Rejected;
if (GUILayout.Button("Save"))
{
_msgPackStore.PushAll();
Debug.Log("Saved database to AetherDB.msgpack");
}
GUILayout.Space(5);
_view = BeginScrollView(
_view,
false,
false,
GUIStyle.none,
GUI.skin.verticalScrollbar,
GUI.skin.scrollView,
GUILayout.Width(EditorGUIUtility.currentViewWidth),
GUILayout.ExpandHeight(true));
foreach (var globalType in _globalTypes)
{
var globalEntry = cultCache.GetGlobal(globalType);
var style = ListItemStyle;
var selected = SelectedItem == globalEntry.ID;
using (new HorizontalScope(selected?SelectedStyle:style))
{
using (var h = new HorizontalScope())
{
if (h.rect.Contains(currentEvent.mousePosition))
{
if (currentEventType == EventType.MouseUp)
Select(globalEntry);
}
GUILayout.Label(globalType.Name.SplitCamelCase(), selected ? EditorStyles.whiteLabel : GUI.skin.label);
}
}
}
void DisplayGroup(Type entryType, IEnumerable<DatabaseEntry> entries, IEnumerable<DatabaseEntryGroup> nestedGroups, int indent = 2, Action<DatabaseEntry> activatePreviousGroups = null, string previousGroupNames = "")
{
var grouper = nestedGroups.FirstOrDefault();
if (grouper != null)
{
var groups = entries.GroupBy(o => grouper.SelectGroup(o));
foreach (var group in groups)
{
//var groupHash = grouper.HashGroup(group.Key);
var groupName = grouper.GetGroupName(group.Key);
var groupHash = groupName.GetHashCode();
using (new HorizontalScope(ListItemStyle))
{
GUILayout.Space(indent*10);
if (Foldout(_groupFoldouts.Contains(groupHash), FormatTypeName(groupName), true))
_groupFoldouts.Add(groupHash);
else _groupFoldouts.Remove(groupHash);
}
if (_groupFoldouts.Contains(groupHash))
{
Action<DatabaseEntry> activate = entry =>
{
activatePreviousGroups?.Invoke(entry);
grouper.Activate(entry, group.Key);
};
DisplayGroup(entryType, group, nestedGroups.Skip(1), indent + 1, activate, $"{groupName} {previousGroupNames}");
using (var h = new HorizontalScope(ListItemStyle))
{
if (GUI.Button(h.rect, GUIContent.none, GUIStyle.none))
CreateItem(entryType, activate);
GUILayout.Space((indent+1)*10);
GUILayout.Label($"Create {groupName} {previousGroupNames}");
}
}
}
}
else
{
int index = 0;
foreach (var entry in entries)
{
index++;
var style = ListItemStyle;
var selected = SelectedItem == entry.ID;
using (var h = new HorizontalScope(selected ? SelectedStyle : style))
{
if (h.rect.Contains(currentEvent.mousePosition))
{
if (currentEventType == EventType.MouseDrag)
{
DragAndDrop.PrepareStartDrag();
DragAndDrop.SetGenericData("Item", entry.ID);
DragAndDrop.StartDrag("Database Item");
}
else if (currentEventType == EventType.MouseUp)
Select(entry);
}
GUILayout.Space(indent*10);
GUILayout.Label((entry as INamedEntry)?.EntryName ?? index.ToString(),
selected ? EditorStyles.whiteLabel : GUI.skin.label);
}
}
}
}
foreach (var table in _tables.Keys.ToArray())
{
var (tableFoldout, _) = _tables[table];
using (new HorizontalScope(ListItemStyle))
{
_tables[table] = (Foldout(tableFoldout = _tables[table].foldout, table, true), _tables[table].types);
}
if (tableFoldout)
{
for (var i = 0; i < _tables[table].types.Length; i++)
{
var (entryType, typeFoldout) = _tables[table].types[i];
using (new HorizontalScope(ListItemStyle))
{
GUILayout.Space(10);
_tables[table].types[i] = (entryType, typeFoldout = Foldout(typeFoldout,
((NameAttribute) entryType.GetCustomAttribute(typeof(NameAttribute)))?.Name ?? FormatTypeName(entryType.Name),
true));
}
if (typeFoldout)
{
var entries = cultCache.GetAll(entryType)
.OrderBy(entry => entry is INamedEntry namedEntry ? namedEntry.EntryName : entry.ID.ToString());
var groupers = Groupers.Where(g => g.CanGroup(entryType));
DisplayGroup(entryType, entries, groupers);
using (var h = new HorizontalScope(ListItemStyle))
{
if (GUI.Button(h.rect, GUIContent.none, GUIStyle.none))
CreateItem(entryType);
GUILayout.Space(20);
GUILayout.Label($"Create {FormatTypeName(entryType.Name)}");
}
}
}
}
}
EndScrollView();
}
public static string FormatTypeName(string typeName)
{
return (typeName.EndsWith("Data")
? typeName.Substring(0, typeName.Length - 4)
: typeName).SplitCamelCase();
}
void Select(DatabaseEntry item)
{
SelectedItem = item.ID;
Repaint();
_inspector.entry = cultCache.Get(SelectedItem);
_inspector.Repaint();
}
private DatabaseEntry CreateItem(Type type, Action<DatabaseEntry> onCreate = null)
{
var newEntry = (DatabaseEntry) Activator.CreateInstance(type);
if(newEntry is INamedEntry entry)
entry.EntryName = $"New {FormatTypeName(type.Name)}";
onCreate?.Invoke(newEntry);
cultCache.Add(newEntry);
Select(newEntry);
return newEntry;
}
}

View file

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: b514fcab25f3b914b89824efbe244591
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,81 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEditor;
using UnityEngine;
public static class EditorDispatcher
{
private static readonly Queue<Action> dispatchQueue = new Queue<Action>();
private static double timeSliceLimit = 10.0; // in miliseconds
private static Stopwatch timer;
static EditorDispatcher()
{
EditorApplication.update += Update;
timer = new Stopwatch();
}
private static void Update ()
{
lock (dispatchQueue)
{
int dispatchCount = 0;
timer.Reset();
timer.Start();
while (dispatchQueue.Count > 0 && (timer.Elapsed.TotalMilliseconds <= timeSliceLimit))
{
dispatchQueue.Dequeue().Invoke();
dispatchCount++;
}
timer.Stop();
// if (dispatchCount > 0)
// UnityEngine.Debug.Log(string.Format("[EditorDispatcher] Dispatched {0} calls in {1}ms", dispatchCount, timer.Elapsed.TotalMilliseconds));
// todo some logic for disconnecting update when the queue is empty
}
}
/// <summary>
/// Send an Action Delegate to be run on the main thread. See EditorDispatchActions for some common usecases.
/// </summary>
/// <param name="task">An action delegate to run on the main thread</param>
/// <returns>An AsyncDispatch that can be used to track if the dispatch has completed.</returns>
public static AsyncDispatch Dispatch(Action task)
{
lock (dispatchQueue)
{
AsyncDispatch dispatch = new AsyncDispatch();
// enqueue a new task that runs the supplied task and completes the dispatcher
dispatchQueue.Enqueue(() => { task(); dispatch.FinishedDispatch(); });
return dispatch;
}
}
}
/// <summary>
/// Represents the progress of the dispatched action. Can be yielded to in a coroutine.
/// If not using coroutines, look at the IsDone property to find out when its okay to proceed.
/// </summary>
public class AsyncDispatch : CustomYieldInstruction
{
public bool IsDone { get; private set; }
public override bool keepWaiting { get { return !IsDone; } }
/// <summary>
/// Flags this dispatch as completed.
/// </summary>
internal void FinishedDispatch()
{
IsDone = true;
}
}

View file

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: b7269ba738c469a4ea5b50811ef07f11
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: b62c0242f5704a96829889e1d4c44900
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,234 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using CultMath;
using UnityEditor;
using UnityEngine;
using static UnityEditor.EditorGUILayout;
using static CultMath.math;
public class StatReferenceInspector : BaseInspector<StatReference>
{
public override StatReference Inspect(string label, StatReference value, object parent, DatabaseInspector inspectorWindow)
{
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
using (new VerticalScope())
{
var targetObjects = typeof(BehaviorData).GetAllChildClasses()
.Concat(typeof(EquippableItemData).GetAllChildClasses()).ToArray();
var objectNames = targetObjects.Select(b => b.Name).ToArray();
var selectedIndex = Array.IndexOf(objectNames, value.Target);
if (selectedIndex == -1)
{
selectedIndex = 0;
GUI.changed = true;
}
using (new HorizontalScope())
{
GUILayout.Label("Target", GUILayout.Width(width));
var newSelection = Popup(selectedIndex, objectNames);
if (newSelection != selectedIndex)
GUI.changed = true;
value.Target = objectNames[newSelection];
}
using (new HorizontalScope())
{
GUILayout.Label("Stat", GUILayout.Width(width));
var stats = targetObjects[selectedIndex].GetFields()
.Where(f => f.FieldType == typeof(PerformanceStat)).ToArray();
if(stats.Length==0)
GUILayout.Label("No Stats!");
else
{
var statNames = stats.Select(s => s.Name).ToArray();
var selectedStatIndex = Array.IndexOf(statNames, value.Stat);
if (selectedStatIndex == -1)
{
selectedStatIndex = 0;
GUI.changed = true;
}
var newSelection = Popup(selectedStatIndex, statNames);
if (newSelection != selectedStatIndex)
GUI.changed = true;
value.Stat = statNames[newSelection];
}
}
}
}
return value;
}
}
public class PerformanceStatInspector : BaseInspector<PerformanceStat>
{
public override PerformanceStat Inspect(string label, PerformanceStat value, object parent, DatabaseInspector inspectorWindow)
{
if(value == null)
value = new PerformanceStat();
using (new VerticalScope())
{
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
GUILayout.Label("Min", EditorStyles.miniLabel, GUILayout.Width(labelWidth));
value.Min = DelayedFloatField(value.Min);
GUILayout.Label("Max", EditorStyles.miniLabel, GUILayout.Width(labelWidth + 5));
value.Max = DelayedFloatField(value.Max);
}
// Only display heat, durability and quality effects if the stat actually varies
if (Math.Abs(value.Min - value.Max) > .0001f)
{
using (new HorizontalScope())
{
GUILayout.Label("", GUILayout.Width(width));
GUILayout.Label("H", EditorStyles.miniLabel, GUILayout.Width(labelWidth));
value.HeatExponentMultiplier = DelayedFloatField(value.HeatExponentMultiplier);
GUILayout.Label("D", EditorStyles.miniLabel, GUILayout.Width(labelWidth));
value.DurabilityExponentMultiplier = DelayedFloatField(value.DurabilityExponentMultiplier);
GUILayout.Label("Q", EditorStyles.miniLabel, GUILayout.Width(labelWidth));
value.QualityExponent = DelayedFloatField(value.QualityExponent);
}
}
Space();
}
return value;
}
}
public class ShapeInspector : BaseInspector<Shape>
{
private Material _schematicMat = new Material(Shader.Find("Legacy Shaders/Particles/Additive"));
public override Shape Inspect(string label, Shape value, object parent, DatabaseInspector inspectorWindow)
{
if(value == null)
{
value = new Shape(1, 1);
value[int2(0)] = true;
}
using (new VerticalScope())
{
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
GUILayout.Label("Size", GUILayout.ExpandWidth(false));
GUILayout.Label("X", GUILayout.Width(labelWidth));
value.Width = DelayedIntField(value.Width);
GUILayout.Label("Y", GUILayout.Width(labelWidth));
value.Height = DelayedIntField(value.Height);
}
using (new HorizontalScope())
{
GUILayout.Label("", GUILayout.Width(width));
using (new VerticalScope())
{
for (var y = value.Height - 1; y >= 0; y--)
{
using (new HorizontalScope())
{
for (var x = 0; x < value.Width; x++)
{
var oldColor = GUI.backgroundColor;
value[int2(x, y)] = Toggle(value[int2(x, y)], GUILayout.Width(toggleWidth));
GUI.backgroundColor = oldColor;
}
}
}
}
GUILayout.FlexibleSpace();
}
}
return value;
}
}
public class ShapeSchematicInspector : BaseInspector<Shape, InspectableSchematicShapeAttribute>
{
private Material _schematicMat = new Material(Shader.Find("Legacy Shaders/Particles/Additive"));
public override Shape Inspect(string label, Shape value, object parent, DatabaseInspector inspectorWindow, InspectableSchematicShapeAttribute attribute)
{
var item = parent as EquippableItemData;
if(value == null)
{
value = new Shape(1, 1);
value[int2(0)] = true;
}
using (new VerticalScope())
{
Texture2D schematic = null;
if(item != null && !string.IsNullOrEmpty(item.Schematic))
schematic = AssetDatabase.LoadAssetAtPath<Texture2D>(item.Schematic);
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
GUILayout.Label("Size", GUILayout.ExpandWidth(false));
if(schematic == null)
{
GUILayout.Label("X", GUILayout.Width(labelWidth));
value.Width = DelayedIntField(value.Width);
GUILayout.Label("Y", GUILayout.Width(labelWidth));
value.Height = DelayedIntField(value.Height);
}
else
{
GUILayout.Label("X", GUILayout.Width(labelWidth));
value.Width = DelayedIntField(value.Width);
value.Height = Mathf.RoundToInt(value.Width * ((float)schematic.height / schematic.width));
GUILayout.Label("Y", GUILayout.Width(labelWidth));
LabelField($"{value.Height}");
}
}
using (new HorizontalScope())
{
GUILayout.Label("", GUILayout.Width(width));
using (var a = new VerticalScope())
{
if(schematic != null)
{
EditorGUI.DrawPreviewTexture(a.rect, schematic, _schematicMat);
}
for (var y = value.Height - 1; y >= 0; y--)
{
using (new HorizontalScope())
{
for (var x = 0; x < value.Width; x++)
{
var oldColor = GUI.backgroundColor;
if(item is HullData hull)
{
var hardpoint = hull.Hardpoints
.FirstOrDefault(hp =>
hp.Shape.Coordinates
.Select(v => v + hp.Position)
.Any(v => v.x == x && v.y == y));
if (hardpoint != null)
GUI.backgroundColor = hardpoint.TintColor.ToColor();
}
value[int2(x, y)] = Toggle(value[int2(x, y)], GUILayout.Width(toggleWidth));
GUI.backgroundColor = oldColor;
}
}
}
}
GUILayout.FlexibleSpace();
}
}
return value;
}
}

View file

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 57d0bc090768458b9428efd7a8a33741
timeCreated: 1617069994

View file

@ -1,58 +0,0 @@
using System.Linq;
using CultMath;
using UnityEditor;
using UnityEngine;
using static UnityEditor.EditorGUILayout;
using static CultMath.math;
public class AnimationCurveFloat4Inspector : BaseInspector<float4[]>
{
public override float4[] Inspect(string label, float4[] value, object parent, DatabaseInspector inspectorWindow)
{
var val = value != null && value.Length > 0
? value.ToCurve()
: new AnimationCurve();
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
val = CurveField(val, Color.yellow, new Rect(0, 0, 1, 1));
}
return val.keys.Select(k => float4(k.time, k.value, k.inTangent, k.outTangent)).ToArray();
}
}
public class BezierCurveInspector : BaseInspector<BezierCurve>
{
public override BezierCurve Inspect(string label, BezierCurve value, object parent, DatabaseInspector inspectorWindow)
{
var val = value != null && value.Keys != null && value.Keys.Length > 0
? value.ToCurve()
: new AnimationCurve();
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
val = CurveField(val, Color.yellow, new Rect(0, 0, 1, 1));
}
value.Keys = val.keys.Select(k => float4(k.time, k.value, k.inTangent, k.outTangent)).ToArray();
return value;
}
}
public class AnimationCurveInspect : BaseInspector<AnimationCurve>
{
public override AnimationCurve Inspect(string label, AnimationCurve value, object parent, DatabaseInspector inspectorWindow)
{
using (var h = new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
if (value == null)
value = new AnimationCurve();
value = CurveField(value, Color.yellow, new Rect(0, 0, 1, 1));
}
return value;
}
}

View file

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: c89e0e7eb9f243cbbce28df59f6fb1c9
timeCreated: 1617066399

View file

@ -1,169 +0,0 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using static UnityEditor.EditorGUILayout;
public class DatabaseLinkInspector : BaseInspector<Guid, InspectableDatabaseLinkAttribute>
{
public override Guid Inspect(string label, Guid value, object parent, DatabaseInspector inspectorWindow, InspectableDatabaseLinkAttribute link)
{
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
var valueEntry = DatabaseInspector.CultCache.Get(value);
using (var h = new HorizontalScope(GUI.skin.box))
{
GUILayout.Label((valueEntry as INamedEntry)?.EntryName ?? valueEntry?.ID.ToString() ?? $"Drag and drop: {DatabaseListView.FormatTypeName(link.EntryType.Name)}");
if (h.rect.Contains(Event.current.mousePosition) && (Event.current.type == EventType.DragUpdated || Event.current.type == EventType.DragPerform))
{
var guid = (Guid) DragAndDrop.GetGenericData("Item");
var dragEntry = DatabaseInspector.CultCache.Get(guid);
var dragValid = dragEntry != null && link.EntryType.IsInstanceOfType(dragEntry) && dragEntry != valueEntry;
if(Event.current.type == EventType.DragUpdated)
DragAndDrop.visualMode = dragValid ? DragAndDropVisualMode.Copy : DragAndDropVisualMode.Rejected;
else if(Event.current.type == EventType.DragPerform)
{
if (dragValid)
{
DragAndDrop.AcceptDrag();
GUI.changed = true;
return guid;
}
}
}
if (GUILayout.Button("-", GUILayout.Width((EditorGUIUtility.singleLineHeight - 3)*2), GUILayout.Height(EditorGUIUtility.singleLineHeight-3)))
{
return Guid.Empty;
}
}
}
return value;
}
}
public class DatabaseLinkObjectInspector<T> : BaseInspector<DatabaseLink<T>> where T : DatabaseEntry
{
public override DatabaseLink<T> Inspect(string label, DatabaseLink<T> value, object parent, DatabaseInspector inspectorWindow)
{
if (value == null)
value = new DatabaseLink<T>();
if(!string.IsNullOrEmpty(label))
{
GUILayout.BeginHorizontal();
GUILayout.Label(label, GUILayout.Width(width));
}
var valueEntry = value.Value;
using (var h = string.IsNullOrEmpty(label) ? new HorizontalScope() : new HorizontalScope(GUI.skin.box))
{
GUILayout.Label((valueEntry as INamedEntry)?.EntryName ?? valueEntry?.ID.ToString() ?? $"Drag and drop: {DatabaseListView.FormatTypeName(typeof(T).Name)}");
if (h.rect.Contains(Event.current.mousePosition) && (Event.current.type == EventType.DragUpdated || Event.current.type == EventType.DragPerform))
{
var guid = (Guid) DragAndDrop.GetGenericData("Item");
var dragEntry = DatabaseInspector.CultCache.Get(guid);
var dragValid = dragEntry is T && dragEntry != valueEntry;
if(Event.current.type == EventType.DragUpdated)
DragAndDrop.visualMode = dragValid ? DragAndDropVisualMode.Copy : DragAndDropVisualMode.Rejected;
else if(Event.current.type == EventType.DragPerform)
{
if (dragValid)
{
DragAndDrop.AcceptDrag();
value.LinkID = guid;
return value;
}
}
}
if(value.LinkID != Guid.Empty)
{
if (GUILayout.Button("-",
GUILayout.Width((EditorGUIUtility.singleLineHeight - 3) * 2),
GUILayout.Height(EditorGUIUtility.singleLineHeight - 3)))
{
value.LinkID = Guid.Empty;
}
}
}
if(!string.IsNullOrEmpty(label))
{
GUILayout.EndHorizontal();
}
return value;
}
}
public class DatabaseLinkListInspector : BaseInspector<List<Guid>, InspectableDatabaseLinkAttribute>
{
private bool _listItemStyle;
public GUIStyle ListItemStyle =>
(_listItemStyle = !_listItemStyle) ? DatabaseListView.ListStyleEven : DatabaseListView.ListStyleOdd;
public override List<Guid> Inspect(string label,
List<Guid> value,
object parent,
DatabaseInspector inspectorWindow,
InspectableDatabaseLinkAttribute link)
{
using (var v = new VerticalScope(GUI.skin.box))
{
GUILayout.Label(label, EditorStyles.boldLabel);
if (value.Count == 0)
{
using (new HorizontalScope(ListItemStyle))
{
GUILayout.Label($"Drag from list to add {link.EntryType.Name}");
}
}
foreach (var guid in value.ToArray())
{
using (new HorizontalScope(ListItemStyle))
{
var entry = DatabaseInspector.CultCache.Get(guid);
if (entry == null)
{
value.Remove(guid);
GUI.changed = true;
}
else
{
GUILayout.Label((entry as INamedEntry)?.EntryName ?? entry.ID.ToString());
// var rect = GetControlRect(false, GUILayout.Width(EditorGUIUtility.singleLineHeight));
// GUI.DrawTexture(rect, Icons.Instance.minus, ScaleMode.StretchToFill, true, 1, LabelColor, 0, 0);
if (GUILayout.Button("-", GUILayout.Width((EditorGUIUtility.singleLineHeight - 3)*2), GUILayout.Height(EditorGUIUtility.singleLineHeight-3)))
{
value.Remove(guid);
}
}
}
}
if (v.rect.Contains(Event.current.mousePosition))
{
var guid = DragAndDrop.GetGenericData("Item");
var dragObj = DragAndDrop.GetGenericData("Item") is Guid ? DatabaseInspector.CultCache.Get((Guid) guid) : null;
var dragValid = dragObj != null && link.EntryType.IsInstanceOfType(dragObj) && !value.Contains(dragObj.ID);
if(Event.current.type == EventType.DragUpdated)
DragAndDrop.visualMode = dragValid ? DragAndDropVisualMode.Copy : DragAndDropVisualMode.Rejected;
else if(Event.current.type == EventType.DragPerform)
{
if (dragValid)
{
DragAndDrop.AcceptDrag();
value.Add((Guid) guid);
GUI.changed = true;
}
}
}
}
return value;
}
}

View file

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 5e08712ea4854536b130f82ad61db7a8
timeCreated: 1617069551

View file

@ -1,211 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using static UnityEditor.EditorGUILayout;
public class FloatDictionaryInspector : BaseInspector<Dictionary<Guid, float>, InspectableDatabaseLinkAttribute>
{
public override Dictionary<Guid, float> Inspect(string label,
Dictionary<Guid, float> value,
object parent,
DatabaseInspector inspectorWindow,
InspectableDatabaseLinkAttribute attribute)
{
Space();
LabelField(label, EditorStyles.boldLabel);
using (var v = new VerticalScope(GUI.skin.box))
{
if (value.Count == 0)
{
using (new HorizontalScope(DatabaseInspector.ListItemStyle))
{
GUILayout.Label("Drag from list to add item");
}
}
foreach (var ingredient in value.ToArray())
{
using (new HorizontalScope(DatabaseInspector.ListItemStyle))
{
var entry = DatabaseInspector.CultCache.Get(ingredient.Key);
if (entry == null)
{
value.Remove(ingredient.Key);
GUI.changed = true;
return value;
}
if(entry is INamedEntry named)
GUILayout.Label(named.EntryName);
else GUILayout.Label(entry.ID.ToString());
value[ingredient.Key] = DelayedFloatField(value[ingredient.Key], GUILayout.Width(50));
// var rect = GetControlRect(false, GUILayout.Width(EditorGUIUtility.singleLineHeight));
// GUI.DrawTexture(rect, Icons.Instance.minus, ScaleMode.StretchToFill, true, 1, LabelColor, 0, 0);
if (GUILayout.Button("-", GUILayout.Width((EditorGUIUtility.singleLineHeight - 3)*2), GUILayout.Height(EditorGUIUtility.singleLineHeight-3)))
value.Remove(ingredient.Key);
}
}
if (v.rect.Contains(Event.current.mousePosition))
{
var dragData = DragAndDrop.GetGenericData("Item");
var isId = dragData is Guid;
var dragEntry = isId ? DatabaseInspector.CultCache.Get((Guid)dragData) : null;
var correctType = attribute.EntryType.IsInstanceOfType(dragEntry);
if(Event.current.type == EventType.DragUpdated)
DragAndDrop.visualMode = correctType ? DragAndDropVisualMode.Copy : DragAndDropVisualMode.Rejected;
else if(Event.current.type == EventType.DragPerform)
{
if (isId && correctType)
{
DragAndDrop.AcceptDrag();
value[(Guid)dragData] = 1;
GUI.changed = true;
}
}
}
}
return value;
}
}
public class RangedFloatDictionaryInspector : BaseInspector<Dictionary<Guid, float>, InspectableDatabaseLinkAttribute, RangedFloatAttribute>
{
public override Dictionary<Guid, float> Inspect(string label,
Dictionary<Guid, float> value,
object parent,
DatabaseInspector inspectorWindow,
InspectableDatabaseLinkAttribute link,
RangedFloatAttribute range)
{
Space();
LabelField(label, EditorStyles.boldLabel);
using (var v = new VerticalScope(GUI.skin.box))
{
if (value.Count == 0)
{
using (new HorizontalScope(DatabaseInspector.ListItemStyle))
{
GUILayout.Label("Drag from list to add item");
}
}
foreach (var ingredient in value.ToArray())
{
using (new HorizontalScope(DatabaseInspector.ListItemStyle))
{
var entry = DatabaseInspector.CultCache.Get(ingredient.Key);
if (entry == null)
{
value.Remove(ingredient.Key);
GUI.changed = true;
return value;
}
if(entry is INamedEntry named)
GUILayout.Label(named.EntryName);
else GUILayout.Label(entry.ID.ToString());
value[ingredient.Key] = Slider(value[ingredient.Key], range.Min, range.Max);
// var rect = GetControlRect(false, GUILayout.Width(EditorGUIUtility.singleLineHeight));
// GUI.DrawTexture(rect, Icons.Instance.minus, ScaleMode.StretchToFill, true, 1, LabelColor, 0, 0);
if (GUILayout.Button("-", GUILayout.Width((EditorGUIUtility.singleLineHeight - 3)*2), GUILayout.Height(EditorGUIUtility.singleLineHeight-3)))
value.Remove(ingredient.Key);
}
}
if (v.rect.Contains(Event.current.mousePosition))
{
var dragData = DragAndDrop.GetGenericData("Item");
var isId = dragData is Guid;
var dragEntry = isId ? DatabaseInspector.CultCache.Get((Guid)dragData) : null;
var correctType = link.EntryType.IsInstanceOfType(dragEntry);
if(Event.current.type == EventType.DragUpdated)
DragAndDrop.visualMode = correctType ? DragAndDropVisualMode.Copy : DragAndDropVisualMode.Rejected;
else if(Event.current.type == EventType.DragPerform)
{
if (isId && correctType)
{
DragAndDrop.AcceptDrag();
value[(Guid)dragData] = range.Max;
GUI.changed = true;
}
}
}
}
return value;
}
}
public class IntDictionaryInspector : BaseInspector<Dictionary<Guid, int>, InspectableDatabaseLinkAttribute>
{
public override Dictionary<Guid, int> Inspect(string label,
Dictionary<Guid, int> value,
object parent,
DatabaseInspector inspectorWindow,
InspectableDatabaseLinkAttribute attribute)
{
Space();
LabelField(label, EditorStyles.boldLabel);
using (var v = new VerticalScope(GUI.skin.box))
{
if (value.Count == 0)
{
using (var h = new HorizontalScope(DatabaseInspector.ListItemStyle))
{
GUILayout.Label("Drag from list to add item");
}
}
foreach (var ingredient in value.ToArray())
{
using (var h = new HorizontalScope(DatabaseInspector.ListItemStyle))
{
var entry = DatabaseInspector.CultCache.Get(ingredient.Key);
if (entry == null)
{
value.Remove(ingredient.Key);
GUI.changed = true;
return value;
}
if(entry is INamedEntry named)
GUILayout.Label(named.EntryName);
else GUILayout.Label(entry.ID.ToString());
value[ingredient.Key] = DelayedIntField(value[ingredient.Key], GUILayout.Width(50));
// var rect = GetControlRect(false, GUILayout.Width(EditorGUIUtility.singleLineHeight));
// GUI.DrawTexture(rect, Icons.Instance.minus, ScaleMode.StretchToFill, true, 1, LabelColor, 0, 0);
if (GUILayout.Button("-", GUILayout.Width((EditorGUIUtility.singleLineHeight - 3)*2), GUILayout.Height(EditorGUIUtility.singleLineHeight-3)) || value[ingredient.Key]==0)
value.Remove(ingredient.Key);
}
}
if (v.rect.Contains(Event.current.mousePosition))
{
var dragData = DragAndDrop.GetGenericData("Item");
var isId = dragData is Guid;
var dragEntry = isId ? DatabaseInspector.CultCache.Get((Guid)dragData) : null;
var correctType = attribute.EntryType.IsInstanceOfType(dragEntry);
if(Event.current.type == EventType.DragUpdated)
DragAndDrop.visualMode = correctType ? DragAndDropVisualMode.Copy : DragAndDropVisualMode.Rejected;
else if(Event.current.type == EventType.DragPerform)
{
if (isId && correctType)
{
DragAndDrop.AcceptDrag();
value[(Guid)dragData] = 1;
GUI.changed = true;
}
}
}
}
return value;
}
}

View file

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: a42620fd406045889578e2ad8d5fd8de
timeCreated: 1617067689

View file

@ -1,59 +0,0 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using static CultMath.math;
using static UnityEditor.EditorGUILayout;
public class EnumInspector<T> : BaseInspector<List<T>, InspectableEnumValuesAttribute>
{
public override List<T> Inspect(string label, List<T> list, object parent, DatabaseInspector inspectorWindow, InspectableEnumValuesAttribute attribute)
{
var names = Enum.GetNames(attribute.EnumType);
list ??= new List<T>();
while(list.Count < names.Length) list.Add(Activator.CreateInstance<T>());
while(list.Count > names.Length) list.RemoveAt(list.Count-1);
for (var i = 0; i < names.Length; i++)
inspectorWindow.InspectMember(names[i], list[i], parent, typeof(T));
return list;
}
}
public class EnumDictionaryInspector<E, T> : BaseInspector<EnumDictionary<E, T>> where E : Enum
{
private static HashSet<object> _foldouts = new HashSet<object>();
public override EnumDictionary<E, T> Inspect(string label, EnumDictionary<E, T> dict, object parent, DatabaseInspector inspectorWindow)
{
var names = Enum.GetNames(typeof(E));
if (dict == null || dict.Values.Length != names.Length)
{
var newValue = new T[names.Length];
if(dict != null && dict.Values.Length > 0)
Array.Copy(dict.Values, newValue, min(dict.Values.Length, newValue.Length));
dict.Values = newValue;
}
using (new VerticalScope(GUI.skin.box))
{
if (Foldout(_foldouts.Contains(dict), label, true, EditorStyles.boldLabel))
_foldouts.Add(dict);
else _foldouts.Remove(dict);
if(_foldouts.Contains(dict))
{
for (var i = 0; i < names.Length; i++)
{
using (new VerticalScope(DatabaseInspector.ListItemStyle))
{
dict.Values[i] = (T) inspectorWindow.InspectMember(names[i], dict.Values[i], parent, typeof(T));
}
}
}
}
return dict;
}
}

View file

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: da1b15e5564747958639c8f959226c74
timeCreated: 1617335525

View file

@ -1,54 +0,0 @@
using UnityEditor;
using UnityEngine;
using static UnityEditor.EditorGUILayout;
public class FloatInspector : BaseInspector<float>
{
public override float Inspect(string label, float value, object parent, DatabaseInspector inspectorWindow)
{
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
return DelayedFloatField(value);
}
}
}
public class RangedFloatInspector : BaseInspector<float, InspectableRangedFloatAttribute>
{
public override float Inspect(string label,
float value,
object parent,
DatabaseInspector inspectorWindow,
InspectableRangedFloatAttribute attribute)
{
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
return Slider(value, attribute.Min, attribute.Max);
}
}
}
public class TemperatureFloatInspector : BaseInspector<float, InspectableTemperatureAttribute>
{
public override float Inspect(string label,
float value,
object parent,
DatabaseInspector inspectorWindow,
InspectableTemperatureAttribute attribute)
{
using (var h = new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
GUILayout.Label("°K", EditorStyles.miniLabel, GUILayout.Width(labelWidth));
value = DelayedFloatField(value);
GUILayout.Label("°C", EditorStyles.miniLabel, GUILayout.Width(labelWidth));
value = DelayedFloatField(value - 273.15f) + 273.15f;
GUILayout.Label("°F", EditorStyles.miniLabel, GUILayout.Width(labelWidth));
value = (DelayedFloatField((value - 273.15f) * 1.8f + 32) - 32) / 1.8f + 273.15f;
return value;
}
}
}

View file

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 47b8e5bc90714055881eb31baf2ab879
timeCreated: 1617065074

View file

@ -1,40 +0,0 @@
using static CultMath.math;
using UnityEditor;
using UnityEngine;
using static UnityEditor.EditorGUILayout;
public class IntInspector : BaseInspector<int>
{
public override int Inspect(string label, int value, object parent, DatabaseInspector inspectorWindow)
{
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
return DelayedIntField(value);
}
}
}
public class UintInspector : BaseInspector<uint>
{
public override uint Inspect(string label, uint value, object parent, DatabaseInspector inspectorWindow)
{
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
return (uint) max(DelayedIntField((int)value), 0);
}
}
}
public class RangedIntInspector : BaseInspector<int, InspectableRangedIntAttribute>
{
public override int Inspect(string label, int value, object parent, DatabaseInspector inspectorWindow, InspectableRangedIntAttribute attribute)
{
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
return IntSlider(value, attribute.Min, attribute.Max);
}
}
}

View file

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: ac7d3b8651964ce1a732a002447d6f69
timeCreated: 1617065365

View file

@ -1,132 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using static UnityEditor.EditorGUILayout;
public class ListInspector<T> : BaseInspector<List<T>>
{
private static HashSet<object> _foldouts = new HashSet<object>();
public override List<T> Inspect(string label, List<T> list, object parent, DatabaseInspector inspectorWindow)
{
var listType = typeof(T);
var inspectable = listType.GetCustomAttribute<InspectableAttribute>();
// if (listType.GetCustomAttribute<InspectableAttribute>() != null)
// {
//Space();
using (new VerticalScope(GUI.skin.box))
{
if (list == null)
{
list = new List<T>();
}
else
{
var sorted = true;
var order = int.MinValue;
foreach(var element in list)
{
if (element == null)
{
list.Remove(element);
return list;
}
var elementType = element.GetType();
var elementOrder = elementType.GetCustomAttribute<OrderAttribute>()?.Order ?? 0;
if (elementOrder < order)
sorted = false;
order = elementOrder;
}
if (!sorted)
{
list.Sort((x, y) => (x.GetType().GetCustomAttribute<OrderAttribute>()?.Order ?? 0).CompareTo(y.GetType().GetCustomAttribute<OrderAttribute>()?.Order ?? 0));
}
}
if (Foldout(_foldouts.Contains(list), label, true, EditorStyles.boldLabel))
_foldouts.Add(list);
else _foldouts.Remove(list);
if (_foldouts.Contains(list))
{
foreach (var o in list)
{
if (o == null)
{
list.Remove(o);
break;
}
bool _tinted = false;
var originalColor = GUI.backgroundColor;
if (o is ITintInspector tint)
{
_tinted = true;
GUI.backgroundColor = tint.TintColor.ToColor();
}
using (new VerticalScope(DatabaseInspector.ListItemStyle))
{
using (new HorizontalScope())
{
if (inspectable != null)
{
if (Foldout(_foldouts.Contains(o), o.ToString(), true, EditorStyles.boldLabel))
_foldouts.Add(o);
else
_foldouts.Remove(o);
}
else inspectorWindow.InspectMember("", o, parent, listType, true);
if (GUILayout.Button("-", GUILayout.Width((EditorGUIUtility.singleLineHeight - 3)*2), GUILayout.Height(EditorGUIUtility.singleLineHeight-3)))
{
list.Remove(o);
break;
}
}
if(inspectable != null && _foldouts.Contains(o))
inspectorWindow.InspectMember("", o, parent, o.GetType(), true);
}
if(_tinted)
GUI.backgroundColor = originalColor;
}
using (new HorizontalScope(DatabaseInspector.ListItemStyle))
{
GUILayout.Label($"Add new {listType.GetFullName()}", GUILayout.ExpandWidth(true));
// var rect = GetControlRect(false, GUILayout.Width(EditorGUIUtility.singleLineHeight));
// GUI.DrawTexture(rect, Icons.Instance.plus, ScaleMode.StretchToFill, true, 1, LabelColor, 0, 0);
if (GUILayout.Button("+", GUILayout.Width((EditorGUIUtility.singleLineHeight - 3)*2), GUILayout.Height(EditorGUIUtility.singleLineHeight-3)))
{
if (listType.IsAbstract || listType.IsInterface)
{
var menu = new GenericMenu();
foreach (var childType in listType.GetAllChildClasses().Where(t=>!t.IsAbstract && !t.IsInterface))
{
menu.AddItem(new GUIContent(childType.Name), false, () =>
{
var o = (T) Activator.CreateInstance(childType);
_foldouts.Add(o);
list.Add(o);
});
}
menu.ShowAsContext();
}
else
{
var o = Activator.CreateInstance<T>();
_foldouts.Add(o);
list.Add(o);
}
}
}
}
}
// }
// else Debug.Log($"Field \"{label}\" is a list of non-Inspectable type {listType.Name}. No inspector was generated.");
return list;
}
}

View file

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 551335721057411d942b3317ae028546
timeCreated: 1617145746

View file

@ -1,145 +0,0 @@
using CultMath;
using CultMath.UnityBridge;
using UnityEditor;
using UnityEngine;
using static UnityEditor.EditorGUILayout;
public class Float2Inspector : BaseInspector<float2>
{
public override float2 Inspect(string label, float2 value, object parent, DatabaseInspector inspectorWindow)
{
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
GUILayout.Label("X", GUILayout.Width(labelWidth));
value.x = DelayedFloatField(value.x);
GUILayout.Label("Y", GUILayout.Width(labelWidth));
value.y = DelayedFloatField(value.y);
return value;
}
}
}
public class Float3Inspector : BaseInspector<float3>
{
public override float3 Inspect(string label, float3 value, object parent, DatabaseInspector inspectorWindow)
{
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
GUILayout.Label("X", GUILayout.Width(labelWidth));
value.x = DelayedFloatField(value.x);
GUILayout.Label("Y", GUILayout.Width(labelWidth));
value.y = DelayedFloatField(value.y);
GUILayout.Label("Z", GUILayout.Width(labelWidth));
value.z = DelayedFloatField(value.z);
return value;
}
}
}
public class Float3ColorInspector : BaseInspector<float3, InspectableColorAttribute>
{
public override float3 Inspect(string label, float3 value, object parent, DatabaseInspector inspectorWindow, InspectableColorAttribute attribute)
{
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
return ColorField(value.ToColor()).ToFloat3();
}
}
}
public class Float4Inspector : BaseInspector<float4>
{
public override float4 Inspect(string label, float4 value, object parent, DatabaseInspector inspectorWindow)
{
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
GUILayout.Label("X", GUILayout.Width(labelWidth));
value.x = DelayedFloatField(value.x);
GUILayout.Label("Y", GUILayout.Width(labelWidth));
value.y = DelayedFloatField(value.y);
GUILayout.Label("Z", GUILayout.Width(labelWidth));
value.z = DelayedFloatField(value.z);
GUILayout.Label("W", GUILayout.Width(labelWidth));
value.w = DelayedFloatField(value.w);
return value;
}
}
}
public class Float4ColorInspector : BaseInspector<float4, InspectableColorAttribute>
{
public override float4 Inspect(string label, float4 value, object parent, DatabaseInspector inspectorWindow, InspectableColorAttribute attribute)
{
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
return ColorField(value.ToColor()).ToCultMath();
}
}
}
public class Int2Inspector : BaseInspector<int2>
{
public override int2 Inspect(string label, int2 value, object parent, DatabaseInspector inspectorWindow)
{
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
GUILayout.Label("X", GUILayout.Width(labelWidth));
value.x = DelayedIntField(value.x);
GUILayout.Label("Y", GUILayout.Width(labelWidth));
value.y = DelayedIntField(value.y);
if (GUILayout.Button("\u2190", GUILayout.Width(arrowWidth)))
value.x--;
if (GUILayout.Button("\u2192", GUILayout.Width(arrowWidth)))
value.x++;
if (GUILayout.Button("\u2191", GUILayout.Width(arrowWidth)))
value.y++;
if (GUILayout.Button("\u2193", GUILayout.Width(arrowWidth)))
value.y--;
return value;
}
}
}
public class Int3Inspector : BaseInspector<int3>
{
public override int3 Inspect(string label, int3 value, object parent, DatabaseInspector inspectorWindow)
{
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
GUILayout.Label("X", GUILayout.Width(labelWidth));
value.x = DelayedIntField(value.x);
GUILayout.Label("Y", GUILayout.Width(labelWidth));
value.y = DelayedIntField(value.y);
GUILayout.Label("Z", GUILayout.Width(labelWidth));
value.z = DelayedIntField(value.z);
return value;
}
}
}
public class Int4Inspector : BaseInspector<int4>
{
public override int4 Inspect(string label, int4 value, object parent, DatabaseInspector inspectorWindow)
{
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
GUILayout.Label("X", GUILayout.Width(labelWidth));
value.x = DelayedIntField(value.x);
GUILayout.Label("Y", GUILayout.Width(labelWidth));
value.y = DelayedIntField(value.y);
GUILayout.Label("Z", GUILayout.Width(labelWidth));
value.z = DelayedIntField(value.z);
GUILayout.Label("W", GUILayout.Width(labelWidth));
value.w = DelayedIntField(value.w);
return value;
}
}
}

View file

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: ae5bba94d1f1a7144944aebf833497b3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,40 +0,0 @@
using UnityEditor;
using UnityEngine;
using static UnityEditor.EditorGUILayout;
public class BoolInspector : BaseInspector<bool>
{
public override bool Inspect(string label, bool value, object parent, DatabaseInspector inspectorWindow)
{
using (var h = new EditorGUILayout.HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
return Toggle(value);
}
}
}
public class StringInspector : BaseInspector<string>
{
public override string Inspect(string label, string value, object parent, DatabaseInspector inspectorWindow)
{
using (var h = new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
return DelayedTextField(value);
}
}
}
public class StringAreaInspector : BaseInspector<string, InspectableTextAttribute>
{
public override string Inspect(string label, string value, object parent, DatabaseInspector inspectorWindow, InspectableTextAttribute attribute)
{
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
EditorStyles.textArea.wordWrap = true;
return DelayedTextField(value, EditorStyles.textArea, GUILayout.Height(100));
}
}
}

View file

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 91dcbfa1679e4be1944d6856ca63bf13
timeCreated: 1617065181

View file

@ -1,23 +0,0 @@
using System;
using System.Linq;
using UnityEditor;
using UnityEngine;
using static UnityEditor.EditorGUILayout;
public class TypeInspector : BaseInspector<Type, InspectableTypeAttribute>
{
public override Type Inspect(string label, Type value, object parent, DatabaseInspector inspectorWindow, InspectableTypeAttribute attribute)
{
var types = attribute.Type.GetAllChildClasses();
var enumOptions = new[] {"None"}.Concat(types.Select(t => t.Name)).ToArray();
var index = Array.IndexOf(types, value) + 1;
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
index = Popup(index, enumOptions);
}
return index == 0 ? null : types[index - 1];
}
}

View file

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 4762384a61dc4d08a1ba4e7564a20081
timeCreated: 1617066802

View file

@ -1,47 +0,0 @@
using UnityEditor;
using UnityEngine;
using static UnityEditor.EditorGUILayout;
public class TextureInspector : BaseInspector<string, InspectableTextureAttribute>
{
public override string Inspect(string label,
string value,
object parent,
DatabaseInspector inspectorWindow,
InspectableTextureAttribute attribute)
{
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
return AssetDatabase.GetAssetPath(ObjectField(AssetDatabase.LoadAssetAtPath<Texture2D>(value), typeof(Texture2D), false));
}
}
}
public class GameObjectInspector : BaseInspector<string, InspectablePrefabAttribute>
{
public override string Inspect(string label, string value, object parent, DatabaseInspector inspectorWindow, InspectablePrefabAttribute attribute)
{
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
return AssetDatabase.GetAssetPath(ObjectField(AssetDatabase.LoadAssetAtPath<GameObject>(value), typeof(GameObject), false));
}
}
}
public class TextAssetInspector : BaseInspector<string, InspectableTextAssetAttribute>
{
public override string Inspect(string label,
string value,
object parent,
DatabaseInspector inspectorWindow,
InspectableTextAssetAttribute attribute)
{
using (new HorizontalScope())
{
GUILayout.Label(label, GUILayout.Width(width));
return AssetDatabase.GetAssetPath(ObjectField(AssetDatabase.LoadAssetAtPath<TextAsset>(value), typeof(TextAsset), false));
}
}
}

View file

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 8519fa7d4f06468bb9be151f053d1fcb
timeCreated: 1617067005

View file

@ -68,7 +68,9 @@ public class PlayerGraphicsSettings
[MessagePackObject]
public class PlayerInputSettings
{
[Key(0)] public Dictionary<(string action, int binding), string> InputActionMap = new Dictionary<(string action, int binding), string>();
// Binding override paths by action name, then binding index. A tuple key has no hash-resistant comparer, so
// MessagePack's untrusted-data security refuses to read it back.
[Key(0)] public Dictionary<string, Dictionary<int, string>> InputActionMap = new Dictionary<string, Dictionary<int, string>>();
[Key(1)] public List<string> ActionBarInputs = new List<string>();
}

View file

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- Pins AetheriaStores' routing and the document model against CultLib's CultCache, headless. -->
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<IsPackable>false</IsPackable>
<Nullable>disable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Aetheria.Shared\Aetheria.Shared.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.4.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,133 @@
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using CultMath;
using GameCult.Caching;
using GameCult.Caching.MessagePack;
using MessagePack;
using Xunit;
// A [CultGlobal] routed to the catalog (PersonalityAttribute is a catalog home). The game has none yet, so this is
// the only way to prove a read-only catalog refuses to open without one.
[CultDocument("aetheria.tests.catalogglobal", "1"), CultGlobal, MessagePackObject]
public class TestCatalogGlobal : PersonalityAttribute { }
// Each test gets its own temp directory holding a catalog built through Open(catalogWritable: true).
public sealed class AetheriaStoresTests : IDisposable
{
private readonly string _root = Path.Combine(Path.GetTempPath(), "aetheria-stores-" + Guid.NewGuid().ToString("N"));
private string Catalog => Path.Combine(_root, "Aetheria.cc");
private string Run => Path.Combine(_root, "run.cc");
private string Player => Path.Combine(_root, "player.cc");
public AetheriaStoresTests()
{
Directory.CreateDirectory(_root);
using var cache = AetheriaStores.Open(Catalog, catalogWritable: true);
cache.Upsert(new TestCatalogGlobal { Name = "Temperament" });
var faction = new Faction { Name = "Adrasteia", ShortName = "ADR", PrimaryColor = new float3(1, .5f, .25f) };
faction.Allegiance[cache.Upsert(faction)] = 1;
cache.Upsert(faction);
cache.Upsert(new WeaponItemData { Name = "Lance", Manufacturer = cache.RefOf(faction) });
cache.FlushAsync().Wait();
}
public void Dispose() => Directory.Delete(_root, true);
[Fact]
public void OpenThenFlushLeavesEveryFileByteIdentical()
{
using (var cache = AetheriaStores.Open(Catalog, Run, Player))
{
var zone = cache.Upsert(new SavedZone { Name = "Entrance", Position = new float2(1, 2), Contents = new ZonePack() });
cache.Upsert(new SavedGame { Zones = new[] { zone }, Factions = new[] { cache.RefOf(cache.GetAll<Faction>().Single()) } });
cache.Upsert(new PlayerSettings());
cache.Upsert(new InputLayout { Rows = new InputLayoutRow[] { new InputLayoutRowSpacer { Height = 1 } } });
cache.FlushAsync().Wait();
}
var before = new[] { Catalog, Run, Player }.Select(Hash).ToArray();
using (var cache = AetheriaStores.Open(Catalog, Run, Player))
{
Assert.NotNull(cache.GetGlobal<SavedGame>());
Assert.NotNull(cache.GetGlobal<PlayerSettings>());
cache.FlushAsync().Wait();
}
Assert.Equal(before, new[] { Catalog, Run, Player }.Select(Hash).ToArray());
}
[Fact]
public void WeaponWrittenThroughGearHandleReloadsAsWeapon()
{
CultRecordHandle<GearData> handle;
using (var cache = AetheriaStores.Open(Catalog, catalogWritable: true))
{
handle = cache.UpsertAsync<GearData>(new WeaponItemData { Name = "Pike" }).Result;
cache.FlushAsync().Wait();
}
using (var cache = AetheriaStores.Open(Catalog))
Assert.Equal("Pike", Assert.IsType<WeaponItemData>(cache.Get(handle.Key)).Name);
}
[Fact]
public void CatalogRefusesWrites()
{
var before = Hash(Catalog);
using (var cache = AetheriaStores.Open(Catalog))
{
Assert.Throws<InvalidOperationException>(() => cache.Upsert(new Faction { Name = "Usurper" }));
cache.FlushAsync().Wait();
}
Assert.Equal(before, Hash(Catalog));
}
[Fact]
public void SavedZoneNeverLandsInCatalog()
{
using (var cache = AetheriaStores.Open(Catalog, catalogWritable: true))
Assert.Throws<InvalidOperationException>(() => cache.Upsert(new SavedZone { Name = "Stray" }));
using (var cache = AetheriaStores.Open(Catalog, Run, catalogWritable: true))
{
cache.Upsert(new SavedZone { Name = "Home" });
cache.FlushAsync().Wait();
}
Assert.DoesNotContain(SchemaNames(Catalog), name => name == "aetheria.savedzone");
Assert.Contains("aetheria.savedzone", SchemaNames(Run));
}
[Fact]
public void FactionIsASingletonInstance()
{
using var cache = AetheriaStores.Open(Catalog);
var faction = cache.GetAll<Faction>().Single();
Assert.Same(faction, cache.Get(cache.GetAll<WeaponItemData>().Single().Manufacturer));
Assert.Same(faction, cache.Get(faction.Allegiance.Keys.Single()));
Assert.Same(faction, cache.GetByName<Faction>("Adrasteia"));
}
[Fact]
public void MissingCatalogGlobalIsLoud()
{
var empty = Path.Combine(_root, "empty.cc");
using (AetheriaStores.Open(empty, catalogWritable: true)) { }
var error = Assert.Throws<InvalidOperationException>(() => AetheriaStores.Open(empty));
Assert.Contains("aetheria.tests.catalogglobal", error.Message);
using (AetheriaStores.Open(Catalog)) { }
}
private static string Hash(string path) => Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(path)));
private static string[] SchemaNames(string path)
{
var snapshot = CultDocumentMessagePackSerialization.DeserializeSnapshot(File.ReadAllBytes(path));
return snapshot.Records
.Select(record => snapshot.SchemaCatalog.Single(entry => entry.SchemaId == record.SchemaId).SchemaName)
.ToArray();
}
}