2026-08-10 14:57:50 +01:00
|
|
|
|
/* 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/. */
|
|
|
|
|
|
|
Replace the Database Tools with the Studio and cut Unity over
Cut 8b. The Unity project compiles with 0 errors in 6000.3.24f1 batchmode.
Packages: manifest.json pins org.gamecult.cultlib at cultlib-unity-v1.0.57
and org.gamecult.caching.unity at caching-unity-v1.2.0, next to the 8a
CultMath tag. Aetheria.Shared.Unity references GameCult.CultLib and keeps
noEngineReferences.
The Database Tools (Assets/Scripts/CultCache/Editor) are deleted in this
commit, alongside their five ported drawers in
Assets/Scripts/Editor/CultCacheDrawers.cs, each an
[CultInspectorDrawer] ICultInspectorDrawer:
- InspectableType: popup of the attribute type's [Union] member names,
writing the string.
- InspectableColor: float3 and float4 through ColorField.
- InspectableAnimationCurve: float4[] and BezierCurve through CurveField.
- InspectableTemperature: Kelvin, Celsius and Fahrenheit fields over one
value.
- InspectableSchematicShape: size and cell toggles. The old drawer also
drew the item's schematic texture and tinted hull hardpoints, which need
the parent document. The tagged Studio hands a drawer only the value, so
those wait on the Studio exposing the open record.
Unity call sites:
- ActionGameManager opens the catalog through AetheriaStores; Cut 10 adds
the run and player stores.
- Refs resolve through ItemManager.GetData or Get(ref); orbit, planet and
belt maps key by CultRecordKey; identity is compared by reference or key.
- The PlayerSettings.msgpack reader and writer, which Cut 10 deletes, pass
CultDocumentMessagePackSerialization.OptionsFor, since the embedded run
reaches CultMath values and refs. Keyboard layouts need no resolver and
keep MessagePack's defaults until Cut 10.
- The input map uses the nested action to binding shape.
- PropertiesPanel reads CultInspectorRangeAttribute.
- NameTools loses its legacy name-file writer.
- Slime compares serialized settings instead of the deleted SHA1 helper.
- FieldDriver drops InspectorHeader.
- .gitignore covers the test project's build output.
Builds: Aetheria.Shared, AetherDb, Aetheria.Shared.Tests (6/6), Unity
batchmode (0 errors).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 19:15:20 +02:00
|
|
|
|
using GameCult.Caching;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Globalization;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
|
using TMPro;
|
|
|
|
|
|
using UniRx;
|
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using UnityEngine.EventSystems;
|
|
|
|
|
|
using UnityEngine.UI;
|
2026-09-14 17:39:12 +02:00
|
|
|
|
using CultMath;
|
|
|
|
|
|
using static CultMath.math;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
public class PropertiesPanel : MonoBehaviour
|
|
|
|
|
|
{
|
|
|
|
|
|
public RectTransform Spacer;
|
|
|
|
|
|
public DropdownMenu Dropdown;
|
|
|
|
|
|
public TextMeshProUGUI Title;
|
|
|
|
|
|
public RectTransform Section;
|
|
|
|
|
|
public PropertiesList List;
|
|
|
|
|
|
public Property Property;
|
|
|
|
|
|
public PropertyLabel PropertyLabel;
|
|
|
|
|
|
public AttributeProperty Attribute;
|
|
|
|
|
|
public InputField InputField;
|
|
|
|
|
|
public RangedFloatField RangedFloatField;
|
|
|
|
|
|
public RangedFloatField ProgressField;
|
|
|
|
|
|
public EnumField EnumField;
|
|
|
|
|
|
public BoolField BoolField;
|
|
|
|
|
|
public PropertyButton PropertyButton;
|
|
|
|
|
|
public ButtonField ButtonField;
|
|
|
|
|
|
public IncrementField IncrementField;
|
|
|
|
|
|
public StatSheet StatSheet;
|
|
|
|
|
|
public CurveField CurveField;
|
|
|
|
|
|
public WeaponGroupAssignment WeaponGroupAssignment;
|
|
|
|
|
|
public RectTransform Content;
|
|
|
|
|
|
public RectTransform DragParent;
|
|
|
|
|
|
|
|
|
|
|
|
[HideInInspector] public ActionGameManager GameManager;
|
|
|
|
|
|
|
|
|
|
|
|
protected List<GameObject> Properties = new List<GameObject>();
|
|
|
|
|
|
protected event Action RefreshPropertyValues;
|
|
|
|
|
|
protected bool RadioSelection = false;
|
|
|
|
|
|
|
|
|
|
|
|
private RectTransform _dragObject;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected event Action<GameObject> OnPropertyAdded;
|
|
|
|
|
|
protected Action OnPropertiesChanged;
|
|
|
|
|
|
|
|
|
|
|
|
public int Children => Properties.Count;
|
|
|
|
|
|
|
|
|
|
|
|
public void Awake()
|
|
|
|
|
|
{
|
|
|
|
|
|
OnPropertyAdded += go => go.SetActive(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public virtual void Update()
|
|
|
|
|
|
{
|
|
|
|
|
|
RefreshValues();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnDestroy()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// private void OnDisable()
|
|
|
|
|
|
// {
|
|
|
|
|
|
// RemoveListener();
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
public void Clear()
|
|
|
|
|
|
{
|
|
|
|
|
|
//RemoveListener();
|
|
|
|
|
|
foreach(var property in Properties)
|
|
|
|
|
|
Destroy(property);
|
|
|
|
|
|
Title.text = "Properties";
|
|
|
|
|
|
Properties.Clear();
|
|
|
|
|
|
RefreshPropertyValues = null;
|
|
|
|
|
|
RadioSelection = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public RectTransform AddSpacer()
|
|
|
|
|
|
{
|
|
|
|
|
|
var spacer = Instantiate(Spacer, Content ?? transform);
|
|
|
|
|
|
Properties.Add(spacer.gameObject);
|
|
|
|
|
|
OnPropertyAdded?.Invoke(spacer.gameObject);
|
|
|
|
|
|
return spacer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public RectTransform AddSection(string name)
|
|
|
|
|
|
{
|
|
|
|
|
|
var section = Instantiate(Section, Content ?? transform);
|
|
|
|
|
|
section.GetComponentInChildren<TextMeshProUGUI>().text = name;
|
|
|
|
|
|
Properties.Add(section.gameObject);
|
|
|
|
|
|
OnPropertyAdded?.Invoke(section.gameObject);
|
|
|
|
|
|
return section;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Property AddProperty(Func<string> read)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (read == null) throw new ArgumentException("Attempted to add property with null read function!");
|
|
|
|
|
|
|
|
|
|
|
|
var property = Instantiate(Property, Content ?? transform);
|
|
|
|
|
|
property.Label.text = read();
|
|
|
|
|
|
|
|
|
|
|
|
RefreshPropertyValues += () => property.Label.text = read();
|
|
|
|
|
|
Properties.Add(property.gameObject);
|
|
|
|
|
|
OnPropertyAdded?.Invoke(property.gameObject);
|
|
|
|
|
|
return property;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Property AddProperty(string name, Func<string> read = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Property property;
|
|
|
|
|
|
if(read != null)
|
|
|
|
|
|
property = Instantiate(PropertyLabel, Content ?? transform);
|
|
|
|
|
|
else
|
|
|
|
|
|
property = Instantiate(Property, Content ?? transform);
|
|
|
|
|
|
property.Label.text = name;
|
|
|
|
|
|
|
|
|
|
|
|
if (read != null)
|
|
|
|
|
|
RefreshPropertyValues += () => ((PropertyLabel) property).Value.text = read.Invoke();
|
|
|
|
|
|
Properties.Add(property.gameObject);
|
|
|
|
|
|
OnPropertyAdded?.Invoke(property.gameObject);
|
|
|
|
|
|
return property;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public StatSheet AddStatSheet()
|
|
|
|
|
|
{
|
|
|
|
|
|
var sheet = Instantiate(StatSheet, Content ?? transform);
|
|
|
|
|
|
Properties.Add(sheet.gameObject);
|
|
|
|
|
|
OnPropertyAdded?.Invoke(sheet.gameObject);
|
|
|
|
|
|
RefreshPropertyValues += () => sheet.RefreshValues();
|
|
|
|
|
|
return sheet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public PropertiesList AddList(string name) //, IEnumerable<(string, Func<string>)> elements)
|
|
|
|
|
|
{
|
|
|
|
|
|
var list = Instantiate(List, Content ?? transform);
|
|
|
|
|
|
list.GameManager = GameManager;
|
|
|
|
|
|
list.Dropdown = Dropdown;
|
|
|
|
|
|
list.Title.text = name;
|
|
|
|
|
|
// foreach (var element in elements)
|
|
|
|
|
|
// {
|
|
|
|
|
|
// var item = Instantiate(PropertyPrefab, list);
|
|
|
|
|
|
// item.Name.text = element.Item1;
|
|
|
|
|
|
// item.Value.text = element.Item2();
|
|
|
|
|
|
// item.ValueFunction = element.Item2;
|
|
|
|
|
|
// Properties.Add(item.gameObject);
|
|
|
|
|
|
// }
|
|
|
|
|
|
//RefreshPropertyValues += () => list.RefreshValues();
|
|
|
|
|
|
Properties.Add(list.gameObject);
|
|
|
|
|
|
OnPropertyAdded?.Invoke(list.gameObject);
|
|
|
|
|
|
return list;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public AttributeProperty AddPersonalityProperty(PersonalityAttribute attribute, Func<float> read)
|
|
|
|
|
|
{
|
|
|
|
|
|
var attributeInstance = Instantiate(Attribute, Content ?? transform);
|
|
|
|
|
|
attributeInstance.Title.text = attribute.Name;
|
|
|
|
|
|
attributeInstance.HighLabel.text = attribute.HighName;
|
|
|
|
|
|
attributeInstance.LowLabel.text = attribute.LowName;
|
|
|
|
|
|
RefreshPropertyValues += () => attributeInstance.Slider.value = read();
|
|
|
|
|
|
Properties.Add(attributeInstance.gameObject);
|
|
|
|
|
|
OnPropertyAdded?.Invoke(attributeInstance.gameObject);
|
|
|
|
|
|
return attributeInstance;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public CurveField AddCurveField()
|
|
|
|
|
|
{
|
|
|
|
|
|
var curveInstance = Instantiate(CurveField, Content ?? transform);
|
|
|
|
|
|
Properties.Add(curveInstance.gameObject);
|
|
|
|
|
|
return curveInstance;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public virtual PropertyButton AddButton(string name, Action onClick)
|
|
|
|
|
|
{
|
|
|
|
|
|
var button = Instantiate(PropertyButton, Content ?? transform);
|
|
|
|
|
|
button.Label.text = name;
|
|
|
|
|
|
button.Button.interactable = onClick != null;
|
|
|
|
|
|
button.Button.onClick.AddListener(() => onClick());
|
|
|
|
|
|
Properties.Add(button.gameObject);
|
|
|
|
|
|
OnPropertyAdded?.Invoke(button.gameObject);
|
|
|
|
|
|
return button;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void AddButton(string name, string label, Action onClick)
|
|
|
|
|
|
{
|
|
|
|
|
|
var button = Instantiate(ButtonField, Content ?? transform);
|
|
|
|
|
|
button.Label.text = name;
|
|
|
|
|
|
button.ButtonLabel.text = label;
|
|
|
|
|
|
button.Button.interactable = onClick != null;
|
|
|
|
|
|
button.Button.onClick.AddListener(() => onClick());
|
|
|
|
|
|
Properties.Add(button.gameObject);
|
|
|
|
|
|
OnPropertyAdded?.Invoke(button.gameObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void AddField(string name, Func<string> read, Action<string> write)
|
|
|
|
|
|
{
|
|
|
|
|
|
var field = Instantiate(InputField, Content ?? transform);
|
|
|
|
|
|
field.Label.text = name;
|
|
|
|
|
|
field.Field.contentType = TMP_InputField.ContentType.Standard;
|
|
|
|
|
|
field.Field.onValueChanged.AddListener(val => write(val));
|
|
|
|
|
|
// field.Field.text = read();
|
|
|
|
|
|
RefreshPropertyValues += () =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var s = read();
|
|
|
|
|
|
if (field.Field.text != s)
|
|
|
|
|
|
{
|
|
|
|
|
|
field.Field.text = s;
|
|
|
|
|
|
field.gameObject.SetActive(false);
|
|
|
|
|
|
Observable.NextFrame().Subscribe(_ =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if(field != null)
|
|
|
|
|
|
OnPropertyAdded?.Invoke(field.gameObject);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
Properties.Add(field.gameObject);
|
|
|
|
|
|
OnPropertyAdded?.Invoke(field.gameObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void AddField(string name, Func<float> read, Action<float> write)
|
|
|
|
|
|
{
|
|
|
|
|
|
var field = Instantiate(InputField, Content ?? transform);
|
|
|
|
|
|
field.Label.text = name;
|
|
|
|
|
|
field.Field.contentType = TMP_InputField.ContentType.DecimalNumber;
|
|
|
|
|
|
field.Field.onValueChanged.AddListener(val => write(float.Parse(val)));
|
|
|
|
|
|
RefreshPropertyValues += () =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var s = read().ToString(CultureInfo.InvariantCulture);
|
|
|
|
|
|
if (field.Field.text != s)
|
|
|
|
|
|
{
|
|
|
|
|
|
field.Field.text = s;
|
|
|
|
|
|
field.gameObject.SetActive(false);
|
|
|
|
|
|
Observable.NextFrame().Subscribe(_ => OnPropertyAdded?.Invoke(field.gameObject));
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
Properties.Add(field.gameObject);
|
|
|
|
|
|
OnPropertyAdded?.Invoke(field.gameObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void AddField(string name, Func<int> read, Action<int> write)
|
|
|
|
|
|
{
|
|
|
|
|
|
var field = Instantiate(InputField, Content ?? transform);
|
|
|
|
|
|
field.Label.text = name;
|
|
|
|
|
|
field.Field.contentType = TMP_InputField.ContentType.IntegerNumber;
|
|
|
|
|
|
field.Field.onValueChanged.AddListener(val => write(int.Parse(val)));
|
|
|
|
|
|
RefreshPropertyValues += () =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var s = read().ToString(CultureInfo.InvariantCulture);
|
|
|
|
|
|
if (field.Field.text != s)
|
|
|
|
|
|
{
|
|
|
|
|
|
field.Field.text = s;
|
|
|
|
|
|
field.gameObject.SetActive(false);
|
|
|
|
|
|
Observable.NextFrame().Subscribe(_ => OnPropertyAdded?.Invoke(field.gameObject));
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
Properties.Add(field.gameObject);
|
|
|
|
|
|
OnPropertyAdded?.Invoke(field.gameObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void AddIncrementField(string name, Func<int> read, Action<int> write, Func<int> min, Func<int> max)
|
|
|
|
|
|
{
|
|
|
|
|
|
var field = Instantiate(IncrementField, Content ?? transform);
|
|
|
|
|
|
field.Label.text = name;
|
|
|
|
|
|
field.Increment.onClick.AddListener(() => write(read() + 1));
|
|
|
|
|
|
field.Decrement.onClick.AddListener(() => write(read() - 1));
|
|
|
|
|
|
RefreshPropertyValues += () =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var val = read();
|
|
|
|
|
|
var minval = min();
|
|
|
|
|
|
var maxval = max();
|
|
|
|
|
|
if (val < minval || val > maxval)
|
|
|
|
|
|
write(val = clamp(val, minval, maxval));
|
|
|
|
|
|
field.Value.text = val.ToString(CultureInfo.InvariantCulture);
|
|
|
|
|
|
field.Increment.interactable = val == maxval;
|
|
|
|
|
|
field.Decrement.interactable = val == minval;
|
|
|
|
|
|
};
|
|
|
|
|
|
Properties.Add(field.gameObject);
|
|
|
|
|
|
OnPropertyAdded?.Invoke(field.gameObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void AddField(string name, Func<float> read, Action<float> write, float min, float max)
|
|
|
|
|
|
{
|
|
|
|
|
|
var field = Instantiate(RangedFloatField, Content ?? transform);
|
|
|
|
|
|
field.Label.text = name;
|
|
|
|
|
|
field.Slider.wholeNumbers = false;
|
|
|
|
|
|
field.Slider.minValue = min;
|
|
|
|
|
|
field.Slider.maxValue = max;
|
|
|
|
|
|
field.Slider.onValueChanged.AddListener(val => write(val));
|
|
|
|
|
|
RefreshPropertyValues += () => field.Slider.value = read();
|
|
|
|
|
|
Properties.Add(field.gameObject);
|
|
|
|
|
|
OnPropertyAdded?.Invoke(field.gameObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void AddProgressField(string name, Func<float> read)
|
|
|
|
|
|
{
|
|
|
|
|
|
var field = Instantiate(ProgressField, Content ?? transform);
|
|
|
|
|
|
field.Label.text = name;
|
|
|
|
|
|
field.Slider.wholeNumbers = false;
|
|
|
|
|
|
field.Slider.minValue = 0;
|
|
|
|
|
|
field.Slider.maxValue = 1;
|
|
|
|
|
|
//field.Slider.onValueChanged.AddListener(val => write(val));
|
|
|
|
|
|
RefreshPropertyValues += () => field.Slider.value = read();
|
|
|
|
|
|
Properties.Add(field.gameObject);
|
|
|
|
|
|
OnPropertyAdded?.Invoke(field.gameObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void AddField(string name, Func<int> read, Action<int> write, int min, int max)
|
|
|
|
|
|
{
|
|
|
|
|
|
var field = Instantiate(RangedFloatField, Content ?? transform);
|
|
|
|
|
|
field.Label.text = name;
|
|
|
|
|
|
field.Slider.wholeNumbers = true;
|
|
|
|
|
|
field.Slider.minValue = min;
|
|
|
|
|
|
field.Slider.maxValue = max;
|
|
|
|
|
|
field.Slider.onValueChanged.AddListener(val => write(Mathf.RoundToInt(val)));
|
|
|
|
|
|
RefreshPropertyValues += () => field.Slider.value = read();
|
|
|
|
|
|
Properties.Add(field.gameObject);
|
|
|
|
|
|
OnPropertyAdded?.Invoke(field.gameObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void AddField(string name, Func<bool> read, Action<bool> write)
|
|
|
|
|
|
{
|
|
|
|
|
|
var field = Instantiate(BoolField, Content ?? transform);
|
|
|
|
|
|
field.Label.text = name;
|
|
|
|
|
|
field.Toggle.onValueChanged.AddListener(val => write(val));
|
|
|
|
|
|
RefreshPropertyValues += () => field.Toggle.isOn = read();
|
|
|
|
|
|
Properties.Add(field.gameObject);
|
|
|
|
|
|
OnPropertyAdded?.Invoke(field.gameObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void AddField(string name, Func<int> read, Action<int> write, string[] enumOptions)
|
|
|
|
|
|
{
|
|
|
|
|
|
var field = Instantiate(EnumField, Content ?? transform);
|
|
|
|
|
|
field.Label.text = name;
|
|
|
|
|
|
field.Dropdown.onClick.AddListener(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var selected = read();
|
|
|
|
|
|
Dropdown.gameObject.SetActive(true);
|
|
|
|
|
|
Dropdown.Clear();
|
|
|
|
|
|
for (int i = 0; i < enumOptions.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var index = i;
|
|
|
|
|
|
Dropdown.AddOption(enumOptions[i], () => write(index), index == selected);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Dropdown.Show((RectTransform) field.Dropdown.transform);
|
|
|
|
|
|
});
|
|
|
|
|
|
RefreshPropertyValues += () => field.DropdownLabel.text = enumOptions[read()];
|
|
|
|
|
|
Properties.Add(field.gameObject);
|
|
|
|
|
|
OnPropertyAdded?.Invoke(field.gameObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void AddItemProperties(ItemInstance item)
|
|
|
|
|
|
{
|
Replace the Database Tools with the Studio and cut Unity over
Cut 8b. The Unity project compiles with 0 errors in 6000.3.24f1 batchmode.
Packages: manifest.json pins org.gamecult.cultlib at cultlib-unity-v1.0.57
and org.gamecult.caching.unity at caching-unity-v1.2.0, next to the 8a
CultMath tag. Aetheria.Shared.Unity references GameCult.CultLib and keeps
noEngineReferences.
The Database Tools (Assets/Scripts/CultCache/Editor) are deleted in this
commit, alongside their five ported drawers in
Assets/Scripts/Editor/CultCacheDrawers.cs, each an
[CultInspectorDrawer] ICultInspectorDrawer:
- InspectableType: popup of the attribute type's [Union] member names,
writing the string.
- InspectableColor: float3 and float4 through ColorField.
- InspectableAnimationCurve: float4[] and BezierCurve through CurveField.
- InspectableTemperature: Kelvin, Celsius and Fahrenheit fields over one
value.
- InspectableSchematicShape: size and cell toggles. The old drawer also
drew the item's schematic texture and tinted hull hardpoints, which need
the parent document. The tagged Studio hands a drawer only the value, so
those wait on the Studio exposing the open record.
Unity call sites:
- ActionGameManager opens the catalog through AetheriaStores; Cut 10 adds
the run and player stores.
- Refs resolve through ItemManager.GetData or Get(ref); orbit, planet and
belt maps key by CultRecordKey; identity is compared by reference or key.
- The PlayerSettings.msgpack reader and writer, which Cut 10 deletes, pass
CultDocumentMessagePackSerialization.OptionsFor, since the embedded run
reaches CultMath values and refs. Keyboard layouts need no resolver and
keep MessagePack's defaults until Cut 10.
- The input map uses the nested action to binding shape.
- PropertiesPanel reads CultInspectorRangeAttribute.
- NameTools loses its legacy name-file writer.
- Slime compares serialized settings instead of the deleted SHA1 helper.
- FieldDriver drops InspectorHeader.
- .gitignore covers the test project's build output.
Builds: Aetheria.Shared, AetherDb, Aetheria.Shared.Tests (6/6), Unity
batchmode (0 errors).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 19:15:20 +02:00
|
|
|
|
var data = GameManager.ItemManager.GetData(item);
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
AddProperty(data.Description);
|
|
|
|
|
|
|
|
|
|
|
|
if (item is SimpleCommodity simpleCommodity)
|
|
|
|
|
|
AddProperty("Quantity", () => simpleCommodity.Quantity.ToString());
|
|
|
|
|
|
|
|
|
|
|
|
var sheet = AddStatSheet();
|
2026-09-17 04:16:23 +02:00
|
|
|
|
// Branding is derived from provenance, not stored on the design; a SimpleCommodity carries no lot and shows none.
|
|
|
|
|
|
if (item is CraftedItemInstance crafted)
|
|
|
|
|
|
{
|
|
|
|
|
|
var (maker, product) = GameManager.ItemManager.Brand(crafted);
|
|
|
|
|
|
if (maker != null)
|
|
|
|
|
|
sheet.AddStat("Manufacturer", () => maker.Name);
|
|
|
|
|
|
if (product != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
AddProperty(product.Description);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
sheet.AddStat("Mass", () => ActionGameManager.PlayerSettings.Format(GameManager.ItemManager.GetMass(item)));
|
|
|
|
|
|
|
|
|
|
|
|
//AddProperty("Thermal Mass", () => Context.GetThermalMass(item).SignificantDigits(Context.GameplaySettings.SignificantDigits));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void AddEquippableItemProperties(EquippableItem item, Func<PerformanceStat, float> statValueFunction)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (item.Durability < .01f)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var gearData = GameManager.ItemManager.GetData(item);
|
|
|
|
|
|
|
|
|
|
|
|
var sheet = AddStatSheet();
|
|
|
|
|
|
foreach (var behavior in gearData.Behaviors)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (behavior is StatModifierData statMod)
|
|
|
|
|
|
{
|
|
|
|
|
|
sheet.AddStat($"{statMod.Stat.Target.SplitCamelCase()}:{statMod.Stat.Stat.SplitCamelCase()}", () => $"{(statMod.Type == StatModifierType.Constant ? "+" : "x")}{ActionGameManager.PlayerSettings.Format(statValueFunction(statMod.Modifier))}");
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
var type = behavior.GetType();
|
|
|
|
|
|
if (type.GetCustomAttribute(typeof(RuntimeInspectable)) == null) continue;
|
|
|
|
|
|
foreach (var field in type.GetFields().Where(f => f.GetCustomAttribute<RuntimeInspectable>() != null))
|
|
|
|
|
|
{
|
|
|
|
|
|
var fieldType = field.FieldType;
|
|
|
|
|
|
if (fieldType == typeof(float))
|
|
|
|
|
|
sheet.AddStat(field.Name.SplitCamelCase(), () => $"{ActionGameManager.PlayerSettings.Format((float) field.GetValue(behavior))}");
|
|
|
|
|
|
else if (fieldType == typeof(int))
|
|
|
|
|
|
sheet.AddStat(field.Name.SplitCamelCase(), () => $"{(int) field.GetValue(behavior)}");
|
|
|
|
|
|
else if (fieldType == typeof(PerformanceStat))
|
|
|
|
|
|
{
|
|
|
|
|
|
var stat = (PerformanceStat) field.GetValue(behavior);
|
|
|
|
|
|
sheet.AddStat(field.Name.SplitCamelCase(), () => $"{ActionGameManager.PlayerSettings.Format(statValueFunction(stat))}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (gearData.Behaviors.FirstOrDefault(b => b is WeaponData) is WeaponData weapon)
|
|
|
|
|
|
{
|
|
|
|
|
|
var range = AddCurveField();
|
|
|
|
|
|
range.Show("Damage Range", weapon.DamageCurve, t => ActionGameManager.PlayerSettings.Format(lerp(statValueFunction(weapon.MinRange), statValueFunction(weapon.Range), t)));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private string GetTitle(EquippableItem item)
|
|
|
|
|
|
{
|
|
|
|
|
|
var (tier, upgrades) = GameManager.ItemManager.GetTier(item);
|
|
|
|
|
|
return
|
2026-10-08 14:34:47 +02:00
|
|
|
|
$"<color=#{ColorUtility.ToHtmlStringRGB(tier.Color.ToColor())}>{GameManager.ItemManager.Title(item)}</color><smallcaps><size=60%> ({tier.Name}{new string('+', upgrades)})";
|
2026-08-10 14:57:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Inspect(EquippedItem item)
|
|
|
|
|
|
{
|
|
|
|
|
|
Clear();
|
|
|
|
|
|
if (item?.EquippableItem == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
Title.text = GetTitle(item.EquippableItem);
|
|
|
|
|
|
|
|
|
|
|
|
AddItemProperties(item.EquippableItem);
|
|
|
|
|
|
AddSpacer();
|
|
|
|
|
|
|
|
|
|
|
|
if (item.GetBehavior<Weapon>() != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var weaponGroups = Instantiate(WeaponGroupAssignment, Content ?? transform);
|
|
|
|
|
|
weaponGroups.Inspect(item);
|
|
|
|
|
|
var dragOffset = Vector2.zero;
|
|
|
|
|
|
Transform dragObject = null;
|
|
|
|
|
|
weaponGroups.OnBeginDragAsObservable().Subscribe(x =>
|
|
|
|
|
|
{
|
|
|
|
|
|
//Debug.Log($"Began dragging weapon group {x.group}");
|
|
|
|
|
|
GameManager.BeginDrag(new WeaponGroupDragObject(x.group));
|
|
|
|
|
|
dragObject = Instantiate(weaponGroups.Groups[x.group], DragParent, true).transform;
|
|
|
|
|
|
dragOffset = (Vector2)dragObject.position - x.pointerEventData.position;
|
|
|
|
|
|
});
|
|
|
|
|
|
weaponGroups.OnDragAsObservable().Subscribe(x =>
|
|
|
|
|
|
{
|
|
|
|
|
|
dragObject.position = x.pointerEventData.position + dragOffset;
|
|
|
|
|
|
});
|
|
|
|
|
|
weaponGroups.OnEndDragAsObservable().Subscribe(x =>
|
|
|
|
|
|
{
|
|
|
|
|
|
//Debug.Log($"Ended dragging weapon group {x.group}");
|
|
|
|
|
|
GameManager.EndDrag();
|
|
|
|
|
|
Destroy(dragObject.gameObject);
|
|
|
|
|
|
});
|
|
|
|
|
|
Properties.Add(weaponGroups.gameObject);
|
|
|
|
|
|
OnPropertyAdded?.Invoke(weaponGroups.gameObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var gearData = GameManager.ItemManager.GetData(item.EquippableItem);
|
|
|
|
|
|
var statusSheet = AddStatSheet();
|
|
|
|
|
|
if (item.EquippableItem.Durability < .01f)
|
|
|
|
|
|
statusSheet.AddStat("Durability", () => "Item Destroyed!");
|
|
|
|
|
|
else statusSheet.AddStat("Durability", () => $"{(int)(item.EquippableItem.Durability / gearData.Durability * 100)}%");
|
|
|
|
|
|
statusSheet.AddStat("Temperature", () => ActionGameManager.PlayerSettings.FormatTemperature(item.Temperature));
|
Cut 1: terms replace the four fixed exponents; R-heat
PerformanceStat's four fixed fields (HeatExponentMultiplier,
DurabilityExponentMultiplier, QualityExponent, FromRole) tombstone into a
declared List<StatTerm> (source + exponent + role), walked by the one
evaluation path Cut 0 unified. A stat with no terms resolves to Max (the
same identity a stat with all-zero exponents already produced), so the
migration is exact: a zero multiplier becomes no term, not a term with
exponent 0.
R-heat (operator, docs/stats-and-power-cut.md): EquippableItemData's
HeatPerformanceCurve (a BezierCurve, plus the 100-sample scan that inferred
OptimalTemperature and its cache) is retired in favor of an authored
minimum, maximum, optimum and plateau width, with linear falloff outside
the plateau. StatValidation refuses an optimum outside its bounds or a
negative plateau width, loudly, at catalog load. The plateau's consequence
rides along for free: Wear's thermal term is pow(ThermalPerformance, ...),
and Performance() now returns exactly 1 across the plateau, so a
well-managed item takes wear only from deltaTemp there -- the
operational-lifespan lever the operator asked for.
ConsumableItemEffect's progress-through-duration substitution for "heat"
becomes its own StatSource (ConsumableProgress) rather than an unconditional
override applied regardless of what a stat declares.
Tests: LoadoutTests' four PerformanceStat-shaped fixtures rebuilt against
Terms/authored heat fields; HeatResponseTests.cs (new) pins load
validation, the plateau shape, and the three wear-lever claims (held
inside takes no thermal wear, held outside does, a fast swing across still
wears) each under its own mutation in tests/mutation_tests_stats_power_cut1.py
(new, 10 rules including a no-op control; all pass).
64 pre-Cut-1 tests still pass unmodified; 71 total with the 7 new ones.
Unity batchmode: 0 error CS. ShieldPanelCut1Verify and Cut2Verify pass;
Cut3Verify fails with an unrelated pre-existing "Kernel 'KInit' not found"
compute-shader error, reproduced against Cut 0's own landing state with no
shader/compute file in this diff -- flagged for Soul, not fixed here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 17:18:06 +02:00
|
|
|
|
// R-heat (docs/stats-and-power-cut.md): the authored shape is min/max/optimum/plateau width, not a
|
|
|
|
|
|
// curve, so the thermal performance panel reads those directly rather than sampling a BezierCurve.
|
|
|
|
|
|
// Cut 8 owns the real UI reconciliation pass; this keeps the panel legible and compiling in the interim.
|
|
|
|
|
|
statusSheet.AddStat("Optimal Temp", () => ActionGameManager.PlayerSettings.FormatTemperature(gearData.OptimalTemperature));
|
|
|
|
|
|
statusSheet.AddStat("Thermal Performance", () => $"{(int)(gearData.Performance(item.Temperature) * 100)}%");
|
2026-08-10 14:57:50 +01:00
|
|
|
|
AddEquippableItemProperties(item.EquippableItem, item.Evaluate);
|
|
|
|
|
|
AddSpacer();
|
|
|
|
|
|
|
|
|
|
|
|
AddField("Override Shutdown", () => item.EquippableItem.OverrideShutdown, b => item.EquippableItem.OverrideShutdown = b);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var behavior in item.Behaviors)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (behavior)
|
|
|
|
|
|
{
|
|
|
|
|
|
case Thermotoggle thermotoggle when thermotoggle.ThermotoggleData.Adjustable:
|
|
|
|
|
|
AddField("Target Temperature",
|
|
|
|
|
|
() => thermotoggle.TargetTemperature,
|
|
|
|
|
|
temp => thermotoggle.TargetTemperature = temp);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
RefreshValues();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Inspect(ItemInstance item)
|
|
|
|
|
|
{
|
|
|
|
|
|
Clear();
|
|
|
|
|
|
|
|
|
|
|
|
AddItemProperties(item);
|
|
|
|
|
|
|
|
|
|
|
|
if (item is EquippableItem gear)
|
|
|
|
|
|
{
|
|
|
|
|
|
Title.text = GetTitle(gear);
|
|
|
|
|
|
AddSpacer();
|
|
|
|
|
|
var gearData = GameManager.ItemManager.GetData(gear);
|
|
|
|
|
|
var statusSheet = AddStatSheet();
|
|
|
|
|
|
statusSheet.AddStat("Durability", () => $"{(int)(gear.Durability / gearData.Durability * 100)}%");
|
Cut 1: terms replace the four fixed exponents; R-heat
PerformanceStat's four fixed fields (HeatExponentMultiplier,
DurabilityExponentMultiplier, QualityExponent, FromRole) tombstone into a
declared List<StatTerm> (source + exponent + role), walked by the one
evaluation path Cut 0 unified. A stat with no terms resolves to Max (the
same identity a stat with all-zero exponents already produced), so the
migration is exact: a zero multiplier becomes no term, not a term with
exponent 0.
R-heat (operator, docs/stats-and-power-cut.md): EquippableItemData's
HeatPerformanceCurve (a BezierCurve, plus the 100-sample scan that inferred
OptimalTemperature and its cache) is retired in favor of an authored
minimum, maximum, optimum and plateau width, with linear falloff outside
the plateau. StatValidation refuses an optimum outside its bounds or a
negative plateau width, loudly, at catalog load. The plateau's consequence
rides along for free: Wear's thermal term is pow(ThermalPerformance, ...),
and Performance() now returns exactly 1 across the plateau, so a
well-managed item takes wear only from deltaTemp there -- the
operational-lifespan lever the operator asked for.
ConsumableItemEffect's progress-through-duration substitution for "heat"
becomes its own StatSource (ConsumableProgress) rather than an unconditional
override applied regardless of what a stat declares.
Tests: LoadoutTests' four PerformanceStat-shaped fixtures rebuilt against
Terms/authored heat fields; HeatResponseTests.cs (new) pins load
validation, the plateau shape, and the three wear-lever claims (held
inside takes no thermal wear, held outside does, a fast swing across still
wears) each under its own mutation in tests/mutation_tests_stats_power_cut1.py
(new, 10 rules including a no-op control; all pass).
64 pre-Cut-1 tests still pass unmodified; 71 total with the 7 new ones.
Unity batchmode: 0 error CS. ShieldPanelCut1Verify and Cut2Verify pass;
Cut3Verify fails with an unrelated pre-existing "Kernel 'KInit' not found"
compute-shader error, reproduced against Cut 0's own landing state with no
shader/compute file in this diff -- flagged for Soul, not fixed here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 17:18:06 +02:00
|
|
|
|
// R-heat: see the equipped Inspect() overload above for why this is stats, not a curve field.
|
|
|
|
|
|
statusSheet.AddStat("Optimal Temp", () => ActionGameManager.PlayerSettings.FormatTemperature(gearData.OptimalTemperature));
|
2026-08-10 14:57:50 +01:00
|
|
|
|
AddEquippableItemProperties(gear, stat => GameManager.ItemManager.Evaluate(stat, gear));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
RefreshValues();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Inspect(ItemData data)
|
|
|
|
|
|
{
|
|
|
|
|
|
AddProperty("Type", () => data.Name);
|
|
|
|
|
|
AddProperty(data.Description).Label.fontStyle = FontStyles.Normal;
|
|
|
|
|
|
if (data is EquippableItemData gearData)
|
|
|
|
|
|
{
|
|
|
|
|
|
AddProperty("Durability", () => ActionGameManager.PlayerSettings.Format(gearData.Durability));
|
|
|
|
|
|
var sheet = AddStatSheet();
|
|
|
|
|
|
foreach (var behavior in gearData.Behaviors)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (behavior is StatModifierData statMod)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(Math.Abs(statMod.Modifier.Min - statMod.Modifier.Max) < .001f)
|
|
|
|
|
|
sheet.AddStat($"{statMod.Stat.Target}:{statMod.Stat.Stat}", () => $"{(statMod.Type == StatModifierType.Constant ? "+" : "x")}{ActionGameManager.PlayerSettings.Format(statMod.Modifier.Min)}");
|
|
|
|
|
|
else
|
|
|
|
|
|
sheet.AddStat($"{statMod.Stat.Target}:{statMod.Stat.Stat}", () => $"{(statMod.Type == StatModifierType.Constant ? "+" : "x")}{ActionGameManager.PlayerSettings.Format(statMod.Modifier.Min)}-{ActionGameManager.PlayerSettings.Format(statMod.Modifier.Max)}");
|
|
|
|
|
|
}
|
|
|
|
|
|
var type = behavior.GetType();
|
|
|
|
|
|
if (type.GetCustomAttribute(typeof(RuntimeInspectable)) != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var field in type.GetFields().Where(f => f.GetCustomAttribute<RuntimeInspectable>() != null))
|
|
|
|
|
|
{
|
|
|
|
|
|
var fieldType = field.FieldType;
|
|
|
|
|
|
if (fieldType == typeof(float))
|
|
|
|
|
|
sheet.AddStat(field.Name, () => $"{ActionGameManager.PlayerSettings.Format((float) field.GetValue(behavior))}");
|
|
|
|
|
|
else if (fieldType == typeof(int))
|
|
|
|
|
|
sheet.AddStat(field.Name, () => $"{(int) field.GetValue(behavior)}");
|
|
|
|
|
|
else if (fieldType == typeof(PerformanceStat))
|
|
|
|
|
|
{
|
|
|
|
|
|
var stat = (PerformanceStat) field.GetValue(behavior);
|
|
|
|
|
|
if(Math.Abs(stat.Min - stat.Max) < .001f)
|
|
|
|
|
|
sheet.AddStat(field.Name, () => $"{ActionGameManager.PlayerSettings.Format(stat.Min)}");
|
|
|
|
|
|
else
|
|
|
|
|
|
sheet.AddStat(field.Name, () => $"{ActionGameManager.PlayerSettings.Format(stat.Min)}-{ActionGameManager.PlayerSettings.Format(stat.Max)}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Inspect(object obj, bool inspectablesOnly = false, bool readWrite = false, bool topLevel = true)
|
|
|
|
|
|
{
|
|
|
|
|
|
var fields = obj.GetType().GetFields();
|
|
|
|
|
|
foreach (var field in fields)
|
|
|
|
|
|
Inspect(obj, field, inspectablesOnly, readWrite);
|
|
|
|
|
|
|
|
|
|
|
|
// if(topLevel)
|
|
|
|
|
|
// foreach (var field in _propertyFields)
|
|
|
|
|
|
// field.transform.SetSiblingIndex(_propertyFields.IndexOf(field));
|
|
|
|
|
|
|
|
|
|
|
|
RefreshValues();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Inspect(object obj, FieldInfo field, bool inspectablesOnly = false, bool readWrite = false)
|
|
|
|
|
|
{
|
|
|
|
|
|
var inspectable = field.GetCustomAttribute<InspectableAttribute>();
|
|
|
|
|
|
if (inspectable == null && inspectablesOnly) return;
|
|
|
|
|
|
var type = field.FieldType;
|
|
|
|
|
|
|
|
|
|
|
|
if (type == typeof(float))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (readWrite)
|
|
|
|
|
|
{
|
Replace the Database Tools with the Studio and cut Unity over
Cut 8b. The Unity project compiles with 0 errors in 6000.3.24f1 batchmode.
Packages: manifest.json pins org.gamecult.cultlib at cultlib-unity-v1.0.57
and org.gamecult.caching.unity at caching-unity-v1.2.0, next to the 8a
CultMath tag. Aetheria.Shared.Unity references GameCult.CultLib and keeps
noEngineReferences.
The Database Tools (Assets/Scripts/CultCache/Editor) are deleted in this
commit, alongside their five ported drawers in
Assets/Scripts/Editor/CultCacheDrawers.cs, each an
[CultInspectorDrawer] ICultInspectorDrawer:
- InspectableType: popup of the attribute type's [Union] member names,
writing the string.
- InspectableColor: float3 and float4 through ColorField.
- InspectableAnimationCurve: float4[] and BezierCurve through CurveField.
- InspectableTemperature: Kelvin, Celsius and Fahrenheit fields over one
value.
- InspectableSchematicShape: size and cell toggles. The old drawer also
drew the item's schematic texture and tinted hull hardpoints, which need
the parent document. The tagged Studio hands a drawer only the value, so
those wait on the Studio exposing the open record.
Unity call sites:
- ActionGameManager opens the catalog through AetheriaStores; Cut 10 adds
the run and player stores.
- Refs resolve through ItemManager.GetData or Get(ref); orbit, planet and
belt maps key by CultRecordKey; identity is compared by reference or key.
- The PlayerSettings.msgpack reader and writer, which Cut 10 deletes, pass
CultDocumentMessagePackSerialization.OptionsFor, since the embedded run
reaches CultMath values and refs. Keyboard layouts need no resolver and
keep MessagePack's defaults until Cut 10.
- The input map uses the nested action to binding shape.
- PropertiesPanel reads CultInspectorRangeAttribute.
- NameTools loses its legacy name-file writer.
- Slime compares serialized settings instead of the deleted SHA1 helper.
- FieldDriver drops InspectorHeader.
- .gitignore covers the test project's build output.
Builds: Aetheria.Shared, AetherDb, Aetheria.Shared.Tests (6/6), Unity
batchmode (0 errors).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 19:15:20 +02:00
|
|
|
|
if (field.GetCustomAttribute<CultInspectorRangeAttribute>() is { } ranged)
|
2026-08-10 14:57:50 +01:00
|
|
|
|
AddField(field.Name.SplitCamelCase(), () => (float) field.GetValue(obj), f => field.SetValue(obj, f),
|
|
|
|
|
|
ranged.Min, ranged.Max);
|
|
|
|
|
|
else
|
|
|
|
|
|
AddField(field.Name.SplitCamelCase(), () => (float) field.GetValue(obj), f => field.SetValue(obj, f));
|
|
|
|
|
|
} else AddProperty(field.Name.SplitCamelCase(), () => ((float) field.GetValue(obj)).ToString("0.##"));
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (type == typeof(int))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (readWrite)
|
|
|
|
|
|
{
|
Replace the Database Tools with the Studio and cut Unity over
Cut 8b. The Unity project compiles with 0 errors in 6000.3.24f1 batchmode.
Packages: manifest.json pins org.gamecult.cultlib at cultlib-unity-v1.0.57
and org.gamecult.caching.unity at caching-unity-v1.2.0, next to the 8a
CultMath tag. Aetheria.Shared.Unity references GameCult.CultLib and keeps
noEngineReferences.
The Database Tools (Assets/Scripts/CultCache/Editor) are deleted in this
commit, alongside their five ported drawers in
Assets/Scripts/Editor/CultCacheDrawers.cs, each an
[CultInspectorDrawer] ICultInspectorDrawer:
- InspectableType: popup of the attribute type's [Union] member names,
writing the string.
- InspectableColor: float3 and float4 through ColorField.
- InspectableAnimationCurve: float4[] and BezierCurve through CurveField.
- InspectableTemperature: Kelvin, Celsius and Fahrenheit fields over one
value.
- InspectableSchematicShape: size and cell toggles. The old drawer also
drew the item's schematic texture and tinted hull hardpoints, which need
the parent document. The tagged Studio hands a drawer only the value, so
those wait on the Studio exposing the open record.
Unity call sites:
- ActionGameManager opens the catalog through AetheriaStores; Cut 10 adds
the run and player stores.
- Refs resolve through ItemManager.GetData or Get(ref); orbit, planet and
belt maps key by CultRecordKey; identity is compared by reference or key.
- The PlayerSettings.msgpack reader and writer, which Cut 10 deletes, pass
CultDocumentMessagePackSerialization.OptionsFor, since the embedded run
reaches CultMath values and refs. Keyboard layouts need no resolver and
keep MessagePack's defaults until Cut 10.
- The input map uses the nested action to binding shape.
- PropertiesPanel reads CultInspectorRangeAttribute.
- NameTools loses its legacy name-file writer.
- Slime compares serialized settings instead of the deleted SHA1 helper.
- FieldDriver drops InspectorHeader.
- .gitignore covers the test project's build output.
Builds: Aetheria.Shared, AetherDb, Aetheria.Shared.Tests (6/6), Unity
batchmode (0 errors).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 19:15:20 +02:00
|
|
|
|
if (field.GetCustomAttribute<CultInspectorRangeAttribute>() is { } ranged)
|
2026-08-10 14:57:50 +01:00
|
|
|
|
AddField(field.Name.SplitCamelCase(), () => (int) field.GetValue(obj), f => field.SetValue(obj, f),
|
Replace the Database Tools with the Studio and cut Unity over
Cut 8b. The Unity project compiles with 0 errors in 6000.3.24f1 batchmode.
Packages: manifest.json pins org.gamecult.cultlib at cultlib-unity-v1.0.57
and org.gamecult.caching.unity at caching-unity-v1.2.0, next to the 8a
CultMath tag. Aetheria.Shared.Unity references GameCult.CultLib and keeps
noEngineReferences.
The Database Tools (Assets/Scripts/CultCache/Editor) are deleted in this
commit, alongside their five ported drawers in
Assets/Scripts/Editor/CultCacheDrawers.cs, each an
[CultInspectorDrawer] ICultInspectorDrawer:
- InspectableType: popup of the attribute type's [Union] member names,
writing the string.
- InspectableColor: float3 and float4 through ColorField.
- InspectableAnimationCurve: float4[] and BezierCurve through CurveField.
- InspectableTemperature: Kelvin, Celsius and Fahrenheit fields over one
value.
- InspectableSchematicShape: size and cell toggles. The old drawer also
drew the item's schematic texture and tinted hull hardpoints, which need
the parent document. The tagged Studio hands a drawer only the value, so
those wait on the Studio exposing the open record.
Unity call sites:
- ActionGameManager opens the catalog through AetheriaStores; Cut 10 adds
the run and player stores.
- Refs resolve through ItemManager.GetData or Get(ref); orbit, planet and
belt maps key by CultRecordKey; identity is compared by reference or key.
- The PlayerSettings.msgpack reader and writer, which Cut 10 deletes, pass
CultDocumentMessagePackSerialization.OptionsFor, since the embedded run
reaches CultMath values and refs. Keyboard layouts need no resolver and
keep MessagePack's defaults until Cut 10.
- The input map uses the nested action to binding shape.
- PropertiesPanel reads CultInspectorRangeAttribute.
- NameTools loses its legacy name-file writer.
- Slime compares serialized settings instead of the deleted SHA1 helper.
- FieldDriver drops InspectorHeader.
- .gitignore covers the test project's build output.
Builds: Aetheria.Shared, AetherDb, Aetheria.Shared.Tests (6/6), Unity
batchmode (0 errors).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 19:15:20 +02:00
|
|
|
|
(int) ranged.Min, (int) ranged.Max);
|
2026-08-10 14:57:50 +01:00
|
|
|
|
else
|
|
|
|
|
|
AddField(field.Name.SplitCamelCase(), () => (int) field.GetValue(obj), f => field.SetValue(obj, f));
|
|
|
|
|
|
} else AddProperty(field.Name.SplitCamelCase(), () => ((int) field.GetValue(obj)).ToString());
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (type.IsEnum)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (readWrite)
|
|
|
|
|
|
AddField(field.Name.SplitCamelCase(), () => (int) field.GetValue(obj), i => field.SetValue(obj, i),
|
|
|
|
|
|
Enum.GetNames(field.FieldType));
|
|
|
|
|
|
else
|
|
|
|
|
|
AddProperty(field.Name.SplitCamelCase(), () => Enum.GetName(type, field.GetValue(obj)).SplitCamelCase());
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (type == typeof(string))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (readWrite)
|
|
|
|
|
|
AddField(field.Name.SplitCamelCase(), () => (string) field.GetValue(obj), i => field.SetValue(obj, i));
|
|
|
|
|
|
else
|
|
|
|
|
|
AddProperty(field.Name.SplitCamelCase(), () => (string) field.GetValue(obj));
|
|
|
|
|
|
}
|
|
|
|
|
|
//else if (field.FieldType == typeof(Color)) Inspect(field.Name, () => (Color) field.GetValue(obj), c => field.SetValue(obj, c));
|
|
|
|
|
|
else if (field.FieldType == typeof(bool)) AddField(field.Name, () => (bool) field.GetValue(obj), b => field.SetValue(obj, b));
|
|
|
|
|
|
else if (type.GetCustomAttribute<InspectableAttribute>() != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// if(!_propertyFields.Any() || !_propertyFields.Last().gameObject.name.ToUpper().Contains("DIVIDER"))
|
|
|
|
|
|
// _propertyFields.Add(Divider.Instantiate<Prototype>());
|
|
|
|
|
|
var list = AddList(field.Name);
|
|
|
|
|
|
list.Inspect(field.GetValue(obj), inspectablesOnly, readWrite, false);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
Debug.Log($"Field \"{field.Name}\" has unknown type {field.FieldType.Name}. No inspector was generated.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void RefreshValues()
|
|
|
|
|
|
{
|
|
|
|
|
|
RefreshPropertyValues?.Invoke();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|