2026-08-10 14:57:50 +01:00
|
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
2020-12-22 03:13:43 +00:00
|
|
|
|
* 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;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
using System.Collections;
|
2020-12-12 03:42:58 +00:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using TMPro;
|
2020-12-20 03:04:52 +00:00
|
|
|
|
using UniRx;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
using UniRx.Triggers;
|
2020-12-12 03:42:58 +00:00
|
|
|
|
using UnityEngine;
|
2026-06-30 19:14:49 +01:00
|
|
|
|
using UnityEngine.UI;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
using UnityEngine.EventSystems;
|
|
|
|
|
|
using UnityEngine.Experimental.Rendering;
|
2026-09-14 17:39:12 +02:00
|
|
|
|
using CultMath;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
using UnityEngine.Serialization;
|
2026-09-14 17:39:12 +02:00
|
|
|
|
using static CultMath.math;
|
|
|
|
|
|
using int2 = CultMath.int2;
|
2020-12-12 03:42:58 +00:00
|
|
|
|
|
2021-03-07 01:10:31 +00:00
|
|
|
|
public class InventoryPanel : MonoBehaviour, IPointerClickHandler
|
2020-12-12 03:42:58 +00:00
|
|
|
|
{
|
2021-04-10 01:47:16 +01:00
|
|
|
|
public ConfirmationDialog Dialog;
|
2021-04-21 01:49:07 +01:00
|
|
|
|
public RectTransform DragParent;
|
2021-01-11 02:56:37 +00:00
|
|
|
|
public bool Flip;
|
2021-01-08 12:44:10 +00:00
|
|
|
|
public GameSettings Settings;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public ActionGameManager GameManager;
|
|
|
|
|
|
public ContextMenu ContextMenu;
|
2020-12-12 03:42:58 +00:00
|
|
|
|
public TextMeshProUGUI Title;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public TextMeshProUGUI MinTempLabel;
|
|
|
|
|
|
public TextMeshProUGUI MaxTempLabel;
|
|
|
|
|
|
public GameObject TemperatureRange;
|
2020-12-19 02:37:13 +00:00
|
|
|
|
public Button Dropdown;
|
2021-04-10 01:47:16 +01:00
|
|
|
|
public Button EditName;
|
2021-01-17 02:45:52 +00:00
|
|
|
|
public Button Current;
|
|
|
|
|
|
public Button Thermal;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public GridLayoutGroup Grid;
|
|
|
|
|
|
public Sprite[] NodeBackgroundTextures;
|
|
|
|
|
|
public Sprite[] NodeTextures;
|
|
|
|
|
|
public Sprite[] ThermalTextures;
|
|
|
|
|
|
public float ThermalToggleRegionSize = .2f;
|
|
|
|
|
|
public Prototype NodePrototype;
|
|
|
|
|
|
public RawImage TemperatureDisplay;
|
|
|
|
|
|
public Texture2D TemperatureColor;
|
|
|
|
|
|
public ExponentialLerp TemperatureColorCurve;
|
|
|
|
|
|
public ExponentialLerp TemperatureAlphaCurve;
|
|
|
|
|
|
public bool FitToContent;
|
|
|
|
|
|
public float CellHitPulseTime;
|
2021-01-17 02:45:52 +00:00
|
|
|
|
public Color ToggleEnabledColor;
|
|
|
|
|
|
public Color ToggleDisabledColor;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public Color CellBackgroundColor = new Color(0, 0, 0, .75f);
|
|
|
|
|
|
public float MinTempRange = 1;
|
|
|
|
|
|
public float DoubleClickTime = .5f;
|
|
|
|
|
|
public float HitDamageThreshold = 1;
|
|
|
|
|
|
|
|
|
|
|
|
// Subject<InventoryEventData> _onBeginDrag;
|
|
|
|
|
|
// Subject<InventoryEventData> _onDrag;
|
|
|
|
|
|
// Subject<InventoryEventData> _onEndDrag;
|
|
|
|
|
|
Subject<(InventoryEventData data, int clickCount)> _onClick;
|
|
|
|
|
|
// Subject<InventoryEventData> _onPointerEnter;
|
|
|
|
|
|
// Subject<InventoryEventData> _onPointerExit;
|
2020-12-12 03:42:58 +00:00
|
|
|
|
|
2020-12-20 03:04:52 +00:00
|
|
|
|
private Entity _displayedEntity;
|
|
|
|
|
|
private EquippedCargoBay _displayedCargo;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
private Texture2D _temperatureTexture;
|
|
|
|
|
|
private RectTransform _firstRect;
|
|
|
|
|
|
|
|
|
|
|
|
public ItemInstance FakeItem;
|
|
|
|
|
|
public Shape FakeOccupancy;
|
|
|
|
|
|
public Shape IgnoreOccupancy;
|
|
|
|
|
|
public List<GameObject> EmptyCells = new List<GameObject>();
|
|
|
|
|
|
public Dictionary<int2, InventoryCell> CellInstances = new Dictionary<int2, InventoryCell>();
|
|
|
|
|
|
|
|
|
|
|
|
private List<IDisposable> _subscriptions = new List<IDisposable>();
|
|
|
|
|
|
private Dictionary<int2, int> _cellAnimationSequence = new Dictionary<int2, int>();
|
|
|
|
|
|
private int _hitSequence = 0;
|
|
|
|
|
|
private bool _thermal = false;
|
|
|
|
|
|
private int _clickCount;
|
|
|
|
|
|
private InventoryCell _clickCell;
|
|
|
|
|
|
private float _clickTime;
|
2026-06-25 18:09:33 +01:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
private bool _hud;
|
|
|
|
|
|
|
|
|
|
|
|
private int2[] _offsets = {
|
|
|
|
|
|
int2(0, 1),
|
|
|
|
|
|
int2(1, 0),
|
|
|
|
|
|
int2(0, -1),
|
|
|
|
|
|
int2(-1, 0),
|
|
|
|
|
|
int2(1, 1),
|
|
|
|
|
|
int2(1, -1),
|
|
|
|
|
|
int2(-1, -1),
|
|
|
|
|
|
int2(-1, 1)
|
2026-06-21 09:37:54 +01:00
|
|
|
|
};
|
2021-01-10 03:59:47 +00:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public InventoryPanelTarget Target =>
|
2021-02-27 20:53:36 +00:00
|
|
|
|
_displayedEntity != null ? InventoryPanelTarget.Equipment :
|
|
|
|
|
|
_displayedCargo != null ? InventoryPanelTarget.Cargo : InventoryPanelTarget.None;
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
private void Start()
|
2026-06-30 19:14:49 +01:00
|
|
|
|
{
|
2021-01-17 02:45:52 +00:00
|
|
|
|
if (Thermal)
|
2026-08-10 14:57:50 +01:00
|
|
|
|
{
|
2021-01-17 02:45:52 +00:00
|
|
|
|
Thermal.onClick.AddListener(() =>
|
|
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
Display(_displayedEntity, false, !_thermal);
|
2021-01-17 02:45:52 +00:00
|
|
|
|
});
|
2026-08-10 14:57:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-17 02:45:52 +00:00
|
|
|
|
if (Current)
|
2026-08-10 14:57:50 +01:00
|
|
|
|
{
|
2021-01-17 02:45:52 +00:00
|
|
|
|
Current.onClick.AddListener(() =>
|
|
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (_displayedEntity != GameManager.CurrentEntity)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(_displayedEntity is Ship ship)
|
|
|
|
|
|
{
|
|
|
|
|
|
GameManager.CurrentEntity = ship;
|
|
|
|
|
|
GameManager.DockingBay.DockedShip = ship;
|
|
|
|
|
|
Current.targetGraphic.color = ToggleEnabledColor;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
Dialog.Clear();
|
|
|
|
|
|
Dialog.Title.text = "Can't select entity, you can only pilot a ship!";
|
|
|
|
|
|
Dialog.Show();
|
|
|
|
|
|
Dialog.MoveToCursor();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-01-17 02:45:52 +00:00
|
|
|
|
});
|
2026-08-10 14:57:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-04-10 01:47:16 +01:00
|
|
|
|
if (EditName)
|
2026-08-10 14:57:50 +01:00
|
|
|
|
{
|
|
|
|
|
|
EditName.onClick.AddListener(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
Dialog.Clear();
|
|
|
|
|
|
var entityName = _displayedEntity.Name;
|
|
|
|
|
|
Dialog.AddField("Name", () => entityName, s => entityName = s);
|
|
|
|
|
|
Dialog.Show(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
_displayedEntity.Name = entityName;
|
|
|
|
|
|
Title.text = _displayedEntity.Name;
|
|
|
|
|
|
});
|
|
|
|
|
|
Dialog.MoveToCursor();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(Dropdown)
|
|
|
|
|
|
{
|
|
|
|
|
|
Dropdown.onClick.AddListener(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
ContextMenu.Clear();
|
|
|
|
|
|
foreach (var entity in GameManager.AvailableEntities())
|
|
|
|
|
|
{
|
|
|
|
|
|
if (entity.CargoBays.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
var options = entity.CargoBays
|
|
|
|
|
|
.Where(bay => bay != _displayedCargo)
|
|
|
|
|
|
.Select<EquippedCargoBay, (string text, Action action, bool enabled)>((bay, index) =>
|
|
|
|
|
|
($"Bay {index+1}", () => Display(bay), true));
|
|
|
|
|
|
if(entity != _displayedEntity) options = options.Prepend(("Equipment", () => Display(entity), true));
|
|
|
|
|
|
ContextMenu.AddDropdown(entity.Name, options);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if(entity != _displayedEntity) ContextMenu.AddOption(entity.Name, () => Display(entity));
|
|
|
|
|
|
}
|
2026-06-30 19:14:49 +01:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if(GameManager.DockingBay!=null && _displayedCargo!=GameManager.DockingBay)
|
|
|
|
|
|
ContextMenu.AddOption(GameManager.DockingBay.Name, () => Display(GameManager.DockingBay));
|
2026-06-30 19:14:49 +01:00
|
|
|
|
|
Make loadouts player-store design documents built all-or-nothing (Cut 10)
Loadout.cs owns loadouts: the document, capture, save and materialization.
- Loadout ("aetheria.loadout", player store) holds a hull design ref, and
per slot a hull cell, a rotation and an EquippableItemData design ref,
plus weapon groups as slot indices. It names no product, manufacturer,
unit roll, cargo, faction, position or behaviour state.
- Loadouts.Capture records the hull design and one slot per equipped item
(gear, cargo bays, docking bays). Entity.Equipment also holds the hull's
own unit (MapEntity adds EquippedHull); that is Loadout.Hull, not a slot.
- Loadouts.Save is the only writer: one Commit to the player store, reusing
the existing key for the name.
- Loadouts.Materialize is the only builder of a ship from a loadout. For
the hull and each slot in order it resolves the design and takes the
first product in catalog order that LoadoutGenerator.IsAvailable (now
public) accepts, with no any-manufacturer fallback. Every failing slot is
listed; units are built only after everything resolved, and any resolve
or fit failure returns null with no ship, no charge and no write.
- Loadouts.Price is the hull design's price plus each slot design's price.
- InventoryPanel: Save Loadout asks for a name (default: the ship's name)
and saves the capture. Restore Loadout, shown when docked with saved
loadouts, lists each with its price, debits credits only after
Materialize succeeds, then docks the ship as before; a failure opens
"Loadout cannot be built here" with one line per failing slot.
Tests: new LoadoutTests (3): LoadoutRoundTripsThroughAFreshCache,
LoadoutHoldsNoRunOrGalaxyRefs, MissingDesignReports. Each fails with its
rule mutated out. The fixture hull is 3x3, not the spec's 2x2:
HullData.InteriorCells is Shape.Shrink(), so a 2x2 hull has no interior
cell a cargo bay can occupy.
Builds: headless, 14 tests pass, Unity batchmode compile reports no error CS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:06:06 +02:00
|
|
|
|
ContextMenu.AddOption("Save Loadout",
|
|
|
|
|
|
() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var entity = _displayedEntity;
|
|
|
|
|
|
var loadoutName = entity.Name;
|
|
|
|
|
|
Dialog.Clear();
|
|
|
|
|
|
Dialog.Title.text = "Save Loadout";
|
|
|
|
|
|
Dialog.AddField("Name", () => loadoutName, s => loadoutName = s);
|
|
|
|
|
|
Dialog.Show(() => Loadouts.Save(ActionGameManager.CultCache,
|
|
|
|
|
|
Loadouts.Capture(GameManager.ItemManager, entity, loadoutName)));
|
|
|
|
|
|
Dialog.MoveToCursor();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
var loadouts = ActionGameManager.CultCache.GetAll<Loadout>().ToArray();
|
|
|
|
|
|
if (GameManager.DockedEntity != null && loadouts.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
ContextMenu.AddDropdown("Restore Loadout",
|
|
|
|
|
|
loadouts.Select<Loadout, (string text, Action action, bool enabled)>(loadout =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var price = Loadouts.Price(GameManager.ItemManager, loadout);
|
|
|
|
|
|
return ($"{loadout.Name} - {price:n0}", () => RestoreLoadout(loadout, price), price < GameManager.Credits);
|
|
|
|
|
|
}));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
ContextMenu.Show();
|
|
|
|
|
|
});
|
2021-04-10 01:47:16 +01:00
|
|
|
|
}
|
2026-06-30 19:14:49 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
Make loadouts player-store design documents built all-or-nothing (Cut 10)
Loadout.cs owns loadouts: the document, capture, save and materialization.
- Loadout ("aetheria.loadout", player store) holds a hull design ref, and
per slot a hull cell, a rotation and an EquippableItemData design ref,
plus weapon groups as slot indices. It names no product, manufacturer,
unit roll, cargo, faction, position or behaviour state.
- Loadouts.Capture records the hull design and one slot per equipped item
(gear, cargo bays, docking bays). Entity.Equipment also holds the hull's
own unit (MapEntity adds EquippedHull); that is Loadout.Hull, not a slot.
- Loadouts.Save is the only writer: one Commit to the player store, reusing
the existing key for the name.
- Loadouts.Materialize is the only builder of a ship from a loadout. For
the hull and each slot in order it resolves the design and takes the
first product in catalog order that LoadoutGenerator.IsAvailable (now
public) accepts, with no any-manufacturer fallback. Every failing slot is
listed; units are built only after everything resolved, and any resolve
or fit failure returns null with no ship, no charge and no write.
- Loadouts.Price is the hull design's price plus each slot design's price.
- InventoryPanel: Save Loadout asks for a name (default: the ship's name)
and saves the capture. Restore Loadout, shown when docked with saved
loadouts, lists each with its price, debits credits only after
Materialize succeeds, then docks the ship as before; a failure opens
"Loadout cannot be built here" with one line per failing slot.
Tests: new LoadoutTests (3): LoadoutRoundTripsThroughAFreshCache,
LoadoutHoldsNoRunOrGalaxyRefs, MissingDesignReports. Each fails with its
rule mutated out. The fixture hull is 3x3, not the spec's 2x2:
HullData.InteriorCells is Shape.Shrink(), so a 2x2 hull has no interior
cell a cargo bay can occupy.
Builds: headless, 14 tests pass, Unity batchmode compile reports no error CS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:06:06 +02:00
|
|
|
|
// Builds the loadout against the current galaxy at the docked station. On failure nothing changes: no ship, no
|
|
|
|
|
|
// charge, and a dialog lists every slot that could not be built here.
|
|
|
|
|
|
private void RestoreLoadout(Loadout loadout, int price)
|
|
|
|
|
|
{
|
|
|
|
|
|
var station = GameManager.DockedEntity;
|
|
|
|
|
|
var failures = new List<string>();
|
|
|
|
|
|
var ship = Loadouts.Materialize(GameManager.ItemManager, ActionGameManager.CurrentGalaxy, GameManager.Zone.GalaxyZone,
|
|
|
|
|
|
station.Faction, GameManager.Zone, loadout, failures);
|
|
|
|
|
|
if (ship == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Dialog.Clear();
|
|
|
|
|
|
Dialog.Title.text = "Loadout cannot be built here";
|
|
|
|
|
|
foreach (var failure in failures) Dialog.AddProperty(failure);
|
|
|
|
|
|
Dialog.Show();
|
|
|
|
|
|
Dialog.MoveToCursor();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
GameManager.Credits -= price;
|
|
|
|
|
|
ship.SetParent(station);
|
|
|
|
|
|
ship.IsPlayerShip = true;
|
|
|
|
|
|
GameManager.DockingBay.DockedShip = ship;
|
|
|
|
|
|
GameManager.CurrentEntity = ship;
|
|
|
|
|
|
Display(ship);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
private void Update()
|
2026-06-30 19:14:49 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (_displayedEntity != null && TemperatureDisplay)
|
|
|
|
|
|
{
|
|
|
|
|
|
var tempRange = _displayedEntity.MaxTemp - _displayedEntity.MinTemp;
|
|
|
|
|
|
var opacity = smoothstep(0, MinTempRange, tempRange);
|
|
|
|
|
|
if(MinTempLabel)
|
|
|
|
|
|
{
|
|
|
|
|
|
MinTempLabel.text = ActionGameManager.PlayerSettings.FormatTemperature(_displayedEntity.MinTemp);
|
|
|
|
|
|
MaxTempLabel.text = ActionGameManager.PlayerSettings.FormatTemperature(_displayedEntity.MaxTemp);
|
|
|
|
|
|
}
|
|
|
|
|
|
var hullData = GameManager.ItemManager.GetData(_displayedEntity.Hull) as HullData;
|
|
|
|
|
|
for(var x = 0; x < _temperatureTexture.width; x++)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (var y = 0; y < _temperatureTexture.height; y++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (hullData.Shape[int2(x-1, y-1)])
|
|
|
|
|
|
{
|
|
|
|
|
|
var temp = (_displayedEntity.Temperature[x - 1, y - 1] - _displayedEntity.MinTemp) / (_displayedEntity.MaxTemp-_displayedEntity.MinTemp);
|
|
|
|
|
|
var color = TemperatureColor.GetPixelBilinear(TemperatureColorCurve.Evaluate(temp), 0);
|
|
|
|
|
|
color.a = TemperatureAlphaCurve.Evaluate(temp) * opacity;
|
|
|
|
|
|
_temperatureTexture.SetPixel(x,y,color);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
_temperatureTexture.SetPixel(x,y,new Color(0,0,0,0));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
_temperatureTexture.Apply();
|
|
|
|
|
|
TemperatureDisplay.rectTransform.anchoredPosition = _firstRect.anchoredPosition - Vector2.one * Grid.cellSize * 1.5f;
|
|
|
|
|
|
}
|
2026-06-30 19:14:49 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
private void OnDisable()
|
2026-06-30 19:14:49 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
Clear();
|
2026-06-30 19:14:49 +01:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
2026-06-30 19:14:49 +01:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public void Clear()
|
2026-06-30 19:14:49 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
_displayedCargo = null;
|
|
|
|
|
|
_displayedEntity = null;
|
|
|
|
|
|
|
|
|
|
|
|
foreach(var empty in EmptyCells)
|
|
|
|
|
|
Destroy(empty);
|
|
|
|
|
|
EmptyCells.Clear();
|
|
|
|
|
|
|
|
|
|
|
|
foreach(var node in CellInstances.Values)
|
|
|
|
|
|
node.GetComponent<Prototype>().ReturnToPool();
|
|
|
|
|
|
CellInstances.Clear();
|
|
|
|
|
|
|
|
|
|
|
|
foreach(var s in _subscriptions)
|
|
|
|
|
|
s.Dispose();
|
|
|
|
|
|
|
|
|
|
|
|
_subscriptions.Clear();
|
|
|
|
|
|
|
|
|
|
|
|
if (Current) Current.gameObject.SetActive(false);
|
|
|
|
|
|
if (Thermal) Thermal.gameObject.SetActive(false);
|
|
|
|
|
|
if (TemperatureRange) TemperatureRange.SetActive(false);
|
|
|
|
|
|
if(TemperatureDisplay) TemperatureDisplay.gameObject.SetActive(false);
|
|
|
|
|
|
if (EditName) EditName.gameObject.SetActive(false);
|
|
|
|
|
|
|
|
|
|
|
|
if(Title)
|
|
|
|
|
|
Title.text = "None";
|
2026-06-30 19:14:49 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public void Display(Entity entity, bool hud = false, bool thermal = false)
|
2026-06-30 19:14:49 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
Clear();
|
|
|
|
|
|
|
|
|
|
|
|
_subscriptions.Add(entity.Equipment.ObserveAdd().Subscribe(_ => RefreshCells()));
|
|
|
|
|
|
_subscriptions.Add(entity.Equipment.ObserveRemove().Subscribe(_ => RefreshCells()));
|
|
|
|
|
|
|
|
|
|
|
|
_thermal = thermal;
|
|
|
|
|
|
_hud = hud;
|
2026-06-30 19:14:49 +01:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
_displayedEntity = entity;
|
|
|
|
|
|
_displayedCargo = null;
|
|
|
|
|
|
_firstRect = null;
|
|
|
|
|
|
|
|
|
|
|
|
if (TemperatureRange)
|
|
|
|
|
|
TemperatureRange.SetActive(true);
|
2020-12-27 22:18:22 +00:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if(Title)
|
|
|
|
|
|
Title.text = entity.Name;
|
2026-06-16 22:08:16 +01:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (EditName)
|
2026-06-30 19:14:49 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
EditName.gameObject.SetActive(true);
|
2026-06-30 19:14:49 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
var hullData = GameManager.ItemManager.GetData(entity.Hull) as HullData;
|
|
|
|
|
|
|
|
|
|
|
|
if (FitToContent)
|
2026-06-30 19:14:49 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
var gridRect = Grid.GetComponent<RectTransform>();
|
|
|
|
|
|
var rect = gridRect.rect;
|
|
|
|
|
|
Grid.cellSize = Vector2.one * (int) min(rect.width / (hullData.Shape.Width + 1), rect.height / (hullData.Shape.Height + 1));
|
2026-06-30 19:14:49 +01:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
if(TemperatureDisplay)
|
2026-06-21 09:37:54 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
_temperatureTexture = new Texture2D(
|
|
|
|
|
|
hullData.Shape.Width + 2,
|
|
|
|
|
|
hullData.Shape.Height + 2,
|
|
|
|
|
|
TextureFormat.RGBA32,
|
|
|
|
|
|
false,
|
|
|
|
|
|
false);
|
|
|
|
|
|
TemperatureDisplay.gameObject.SetActive(true);
|
|
|
|
|
|
TemperatureDisplay.texture = _temperatureTexture;
|
|
|
|
|
|
var tempRect = TemperatureDisplay.rectTransform;
|
|
|
|
|
|
tempRect.sizeDelta = Grid.cellSize * new Vector2(hullData.Shape.Width + 2, hullData.Shape.Height + 2);
|
2026-06-21 09:37:54 +01:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
if (Current)
|
2026-06-16 22:08:16 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
Current.gameObject.SetActive(true);
|
|
|
|
|
|
Current.targetGraphic.color = entity == GameManager.CurrentEntity ? ToggleEnabledColor : ToggleDisabledColor;
|
2026-06-16 22:08:16 +01:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (Thermal)
|
2026-06-16 22:08:16 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
Thermal.gameObject.SetActive(true);
|
|
|
|
|
|
Thermal.targetGraphic.color = thermal ? ToggleEnabledColor : ToggleDisabledColor;
|
2026-06-16 22:08:16 +01:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
Grid.constraintCount = hullData.Shape.Width;
|
|
|
|
|
|
foreach (var v in hullData.Shape.AllCoordinates)
|
2026-06-16 22:08:16 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (!hullData.Shape[v])
|
|
|
|
|
|
{
|
|
|
|
|
|
var empty = new GameObject("Empty Node", typeof(RectTransform));
|
|
|
|
|
|
empty.transform.SetParent(Grid.transform);
|
|
|
|
|
|
if (!_firstRect)
|
|
|
|
|
|
_firstRect = empty.GetComponent<RectTransform>();
|
|
|
|
|
|
EmptyCells.Add(empty);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
var cell = NodePrototype.Instantiate<InventoryCell>();
|
|
|
|
|
|
cell.Background.color = CellBackgroundColor;
|
|
|
|
|
|
if (!_firstRect)
|
|
|
|
|
|
_firstRect = cell.GetComponent<RectTransform>();
|
|
|
|
|
|
if (!thermal)
|
2026-06-21 09:37:54 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if(cell.PointerClickTrigger)
|
|
|
|
|
|
{
|
|
|
|
|
|
cell.PointerClickTrigger.OnPointerClickAsObservable()
|
|
|
|
|
|
.Subscribe(data =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (cell != _clickCell || Time.time - _clickTime > DoubleClickTime)
|
|
|
|
|
|
_clickCount = 0;
|
|
|
|
|
|
_clickCell = cell;
|
|
|
|
|
|
_clickTime = Time.time;
|
|
|
|
|
|
_clickCount++;
|
|
|
|
|
|
_onClick?.OnNext((new InventoryEntityEventData(data, v, entity), _clickCount));
|
|
|
|
|
|
});
|
|
|
|
|
|
cell.BeginDragTrigger.OnBeginDragAsObservable()
|
|
|
|
|
|
.Subscribe(data =>
|
|
|
|
|
|
{
|
|
|
|
|
|
//Debug.Log("Entity Drag Start");
|
|
|
|
|
|
var item = entity.GearOccupancy[v.x, v.y];
|
|
|
|
|
|
if (item != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var originalOccupancy = hullData.Shape.Inset(item.Data.Shape, item.Position, item.EquippableItem.Rotation);
|
|
|
|
|
|
_dragCells = originalOccupancy.Coordinates
|
|
|
|
|
|
.Select(v1 => Instantiate(CellInstances[v1], DragParent, true).transform).ToArray();
|
|
|
|
|
|
foreach(var dragCell in _dragCells)
|
|
|
|
|
|
{
|
|
|
|
|
|
DestroyImmediate(dragCell.GetComponent<Prototype>());
|
|
|
|
|
|
foreach (var component in dragCell.GetComponentsInChildren<ObservableTriggerBase>())
|
|
|
|
|
|
component.enabled = false;
|
|
|
|
|
|
foreach (var img in dragCell.GetComponentsInChildren<Image>())
|
|
|
|
|
|
img.color = new Color(img.color.r, img.color.g, img.color.b, img.color.a * .5f);
|
|
|
|
|
|
dragCell.GetComponentInChildren<Image>().raycastTarget = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
_dragOffsets = _dragCells.Select(x => (Vector2) x.position - data.position).ToArray();
|
|
|
|
|
|
IgnoreOccupancy = originalOccupancy;
|
|
|
|
|
|
RefreshCells();
|
|
|
|
|
|
_originalRotation = item.EquippableItem.Rotation;
|
|
|
|
|
|
GameManager.BeginDrag(new EquippedItemDragObject(item, entity, item.Position - v));
|
|
|
|
|
|
//AkSoundEngine.PostEvent("Pickup", gameObject);
|
|
|
|
|
|
// TODO: SFX: Pickup Item
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
cell.DragTrigger.OnDragAsObservable()
|
|
|
|
|
|
.Subscribe(data =>
|
|
|
|
|
|
{
|
|
|
|
|
|
for (var i = 0; i < _dragCells.Length; i++)
|
|
|
|
|
|
_dragCells[i].position = new Vector3(
|
|
|
|
|
|
data.position.x + _dragOffsets[i].x,
|
|
|
|
|
|
data.position.y + _dragOffsets[i].y,
|
|
|
|
|
|
_dragCells[i].position.z);
|
|
|
|
|
|
});
|
|
|
|
|
|
cell.EndDragTrigger.OnEndDragAsObservable()
|
|
|
|
|
|
.Subscribe(data =>
|
|
|
|
|
|
{
|
|
|
|
|
|
//Debug.Log("Entity Drag End");
|
|
|
|
|
|
IgnoreOccupancy = null;
|
|
|
|
|
|
if (!GameManager.EndDrag())
|
|
|
|
|
|
entity.GearOccupancy[v.x, v.y].EquippableItem.Rotation = _originalRotation;
|
|
|
|
|
|
foreach(var dragObject in _dragCells)
|
|
|
|
|
|
Destroy(dragObject.gameObject);
|
|
|
|
|
|
_dragCells = null;
|
|
|
|
|
|
RefreshCells();
|
|
|
|
|
|
});
|
|
|
|
|
|
cell.PointerEnterTrigger.OnPointerEnterAsObservable()
|
|
|
|
|
|
.Subscribe(data =>
|
|
|
|
|
|
{
|
|
|
|
|
|
//Debug.Log("Entity Pointer Enter");
|
|
|
|
|
|
if (!(GameManager.DragObject is ItemDragObject itemDragObject)) return;
|
|
|
|
|
|
var item = itemDragObject.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 itemData = GameManager.ItemManager.GetData(item);
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (!(item is EquippableItem equippableItem)) return;
|
|
|
|
|
|
var placementPosition = v + itemDragObject.OriginCellOffset;
|
|
|
|
|
|
if (entity.ItemFits(equippableItem, placementPosition))
|
|
|
|
|
|
{
|
|
|
|
|
|
//foreach (var cell in _dragCells) cell.gameObject.SetActive(false);
|
|
|
|
|
|
FakeItem = item;
|
|
|
|
|
|
FakeOccupancy = hullData.Shape.Inset(itemData.Shape, placementPosition, item.Rotation);
|
|
|
|
|
|
RefreshCells();
|
|
|
|
|
|
GameManager.RegisterDragTarget(drag =>
|
|
|
|
|
|
{
|
|
|
|
|
|
//Debug.Log("Entity Drag Callback");
|
|
|
|
|
|
if (GameManager.DragObject is EquippedItemDragObject equippedItemDragObject)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(equippedItemDragObject.OriginEntity.TryUnequip(equippedItemDragObject.EquippedItem) == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// AkSoundEngine.PostEvent("UI_Fail", gameObject);
|
|
|
|
|
|
// TODO: SFX: Fail
|
|
|
|
|
|
Dialog.Clear();
|
|
|
|
|
|
Dialog.Title.text = "Unable to move item!";
|
|
|
|
|
|
Dialog.AddProperty("Verify that cargo bays are empty before un-equipping them.");
|
|
|
|
|
|
Dialog.Show();
|
|
|
|
|
|
Dialog.MoveToCursor();
|
|
|
|
|
|
}
|
|
|
|
|
|
// else
|
|
|
|
|
|
// AkSoundEngine.PostEvent("Unequip", gameObject);
|
|
|
|
|
|
// TODO: SFX: Unequip
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (GameManager.DragObject is ItemInstanceDragObject itemInstanceDragObject)
|
|
|
|
|
|
itemInstanceDragObject.OriginInventory.Remove(itemInstanceDragObject.Item);
|
|
|
|
|
|
|
|
|
|
|
|
FakeOccupancy = null;
|
|
|
|
|
|
var success = entity.TryEquip(equippableItem, placementPosition);
|
|
|
|
|
|
RefreshCells();
|
|
|
|
|
|
// TODO: SFX: Equip
|
|
|
|
|
|
return success;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
cell.PointerExitTrigger.OnPointerExitAsObservable()
|
|
|
|
|
|
.Subscribe(data =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!(GameManager.DragObject is ItemDragObject)) return;
|
|
|
|
|
|
FakeItem = null;
|
|
|
|
|
|
FakeOccupancy = null;
|
|
|
|
|
|
RefreshCells();
|
|
|
|
|
|
GameManager.UnregisterDragTarget();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-06-21 21:25:13 +01:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
cell.PointerClickTrigger.OnPointerClickAsObservable().Subscribe(data =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var rect = cell.GetComponent<RectTransform>();
|
|
|
|
|
|
var point = Rect.PointToNormalized(rect.rect, rect.InverseTransformPoint(data.position));
|
|
|
|
|
|
// Debug.Log($"Clicked at pos {data.position}, normalized {point}");
|
|
|
|
|
|
if (hullData.Shape[int2(v.x - 1, v.y)] && point.x < ThermalToggleRegionSize)
|
|
|
|
|
|
{
|
|
|
|
|
|
entity.HullConductivity[v.x - 1, v.y].x = !entity.HullConductivity[v.x - 1, v.y].x;
|
|
|
|
|
|
RefreshCells(new []{v,int2(v.x - 1, v.y)});
|
|
|
|
|
|
}
|
|
|
|
|
|
if (hullData.Shape[int2(v.x + 1, v.y)] && point.x > 1 - ThermalToggleRegionSize)
|
|
|
|
|
|
{
|
|
|
|
|
|
entity.HullConductivity[v.x, v.y].x = !entity.HullConductivity[v.x, v.y].x;
|
|
|
|
|
|
RefreshCells(new []{v,int2(v.x + 1, v.y)});
|
|
|
|
|
|
}
|
|
|
|
|
|
if (hullData.Shape[int2(v.x, v.y - 1)] && point.y < ThermalToggleRegionSize)
|
|
|
|
|
|
{
|
|
|
|
|
|
entity.HullConductivity[v.x, v.y - 1].y = !entity.HullConductivity[v.x, v.y - 1].y;
|
|
|
|
|
|
RefreshCells(new []{v,int2(v.x, v.y - 1)});
|
|
|
|
|
|
}
|
|
|
|
|
|
if (hullData.Shape[int2(v.x, v.y + 1)] && point.y > 1 - ThermalToggleRegionSize)
|
|
|
|
|
|
{
|
|
|
|
|
|
entity.HullConductivity[v.x, v.y].y = !entity.HullConductivity[v.x, v.y].y;
|
|
|
|
|
|
RefreshCells(new []{v,int2(v.x, v.y + 1)});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
CellInstances.Add(v, cell);
|
|
|
|
|
|
}
|
2026-06-16 22:08:16 +01:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
RefreshCells(CellInstances.Keys);
|
|
|
|
|
|
|
|
|
|
|
|
_subscriptions.Add(entity.ArmorDamage.Subscribe(hit =>
|
2026-06-30 19:14:49 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
var hitCells = new[] { hit.pos };
|
|
|
|
|
|
StartCoroutine(Pulse(hitCells, HitType.Armor, _hitSequence++));
|
|
|
|
|
|
RefreshCells(hitCells);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
_subscriptions.Add(entity.ItemDamage.Where(hit=>hit.damage > HitDamageThreshold).Subscribe(hit =>
|
2021-01-08 12:44:10 +00:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
var hitCells = hit.item.InsetShape.Coordinates;
|
|
|
|
|
|
if(gameObject.activeInHierarchy)
|
|
|
|
|
|
StartCoroutine(Pulse(hitCells, HitType.Armor, _hitSequence++));
|
|
|
|
|
|
RefreshCells(hitCells);
|
|
|
|
|
|
}));
|
2020-12-14 02:56:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
private IEnumerator Pulse(int2[] cells, HitType hitType, int sequence)
|
2020-12-14 02:56:35 +00:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
foreach (var cell in cells)
|
2026-06-25 18:09:33 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if(_cellAnimationSequence.ContainsKey(cell))
|
|
|
|
|
|
_cellAnimationSequence[cell] = max(_cellAnimationSequence[cell], sequence);
|
|
|
|
|
|
else
|
|
|
|
|
|
_cellAnimationSequence[cell] = sequence;
|
2026-06-25 18:09:33 +01:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
var hitColor = hitType switch
|
2020-12-20 03:04:52 +00:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
HitType.Armor => Settings.ArmorHitColor,
|
|
|
|
|
|
HitType.Hardpoint => Settings.HardpointHitColor,
|
|
|
|
|
|
HitType.Gear => Settings.GearHitColor,
|
|
|
|
|
|
_ => Color.white
|
|
|
|
|
|
};
|
|
|
|
|
|
float startTime = Time.time;
|
|
|
|
|
|
while (Time.time - startTime < CellHitPulseTime)
|
|
|
|
|
|
{
|
|
|
|
|
|
var lerp = (Time.time - startTime) / CellHitPulseTime;
|
|
|
|
|
|
foreach(var cell in cells)
|
|
|
|
|
|
if(_cellAnimationSequence[cell] == sequence)
|
|
|
|
|
|
if(CellInstances.ContainsKey(cell))
|
|
|
|
|
|
CellInstances[cell].Background.color = Color.Lerp(hitColor, CellBackgroundColor, lerp);
|
|
|
|
|
|
yield return null;
|
2020-12-20 03:04:52 +00:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
foreach (var cell in cells)
|
|
|
|
|
|
if(CellInstances.ContainsKey(cell))
|
|
|
|
|
|
CellInstances[cell].Background.color = CellBackgroundColor;
|
2020-12-20 03:04:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public void Display(EquippedCargoBay cargo)
|
2020-12-20 03:04:52 +00:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
Clear();
|
|
|
|
|
|
|
|
|
|
|
|
_subscriptions.Add(cargo.Cargo.ObserveAdd().Subscribe(_ => RefreshCells()));
|
|
|
|
|
|
_subscriptions.Add(cargo.Cargo.ObserveRemove().Subscribe(_ => RefreshCells()));
|
|
|
|
|
|
|
|
|
|
|
|
_displayedCargo = cargo;
|
|
|
|
|
|
_displayedEntity = null;
|
2021-01-17 02:45:52 +00:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if(Title)
|
|
|
|
|
|
Title.text = cargo.Name;
|
|
|
|
|
|
|
|
|
|
|
|
// FakeOccupancy = new Shape(cargo.Data.InteriorShape.Width, cargo.Data.InteriorShape.Height);
|
|
|
|
|
|
// IgnoreOccupancy = new Shape(cargo.Data.InteriorShape.Width, cargo.Data.InteriorShape.Height);
|
|
|
|
|
|
Grid.constraintCount = cargo.Data.InteriorShape.Width;
|
|
|
|
|
|
foreach (var v in cargo.Data.InteriorShape.AllCoordinates)
|
2020-12-20 03:04:52 +00:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (!cargo.Data.InteriorShape[v])
|
|
|
|
|
|
{
|
|
|
|
|
|
var empty = new GameObject("Empty Node", typeof(RectTransform));
|
|
|
|
|
|
empty.transform.SetParent(Grid.transform);
|
|
|
|
|
|
EmptyCells.Add(empty);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
var cell = NodePrototype.Instantiate<InventoryCell>();
|
|
|
|
|
|
cell.PointerClickTrigger.OnPointerClickAsObservable()
|
|
|
|
|
|
.Subscribe(data =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (cell != _clickCell || Time.time - _clickTime > DoubleClickTime)
|
|
|
|
|
|
_clickCount = 0;
|
|
|
|
|
|
_clickCell = cell;
|
|
|
|
|
|
_clickTime = Time.time;
|
|
|
|
|
|
_clickCount++;
|
|
|
|
|
|
_onClick?.OnNext((new InventoryCargoEventData(data, v, cargo), _clickCount));
|
|
|
|
|
|
});
|
|
|
|
|
|
cell.BeginDragTrigger.OnBeginDragAsObservable()
|
|
|
|
|
|
.Subscribe(data =>
|
|
|
|
|
|
{
|
|
|
|
|
|
//Debug.Log("Inventory Drag Start");
|
|
|
|
|
|
var item = cargo.Occupancy[v.x, v.y];
|
|
|
|
|
|
if (item != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var itemPosition = cargo.Cargo[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 itemData = GameManager.ItemManager.GetData(item);
|
2026-08-10 14:57:50 +01:00
|
|
|
|
var originalOccupancy = cargo.Data.InteriorShape.Inset(itemData.Shape, itemPosition, item.Rotation);
|
|
|
|
|
|
_dragCells = originalOccupancy.Coordinates
|
|
|
|
|
|
.Select(v1 => Instantiate(CellInstances[v1], DragParent, true).transform).ToArray();
|
|
|
|
|
|
foreach(var dragCell in _dragCells)
|
|
|
|
|
|
{
|
|
|
|
|
|
DestroyImmediate(dragCell.GetComponent<Prototype>());
|
|
|
|
|
|
foreach (var component in dragCell.GetComponentsInChildren<ObservableTriggerBase>())
|
|
|
|
|
|
component.enabled = false;
|
|
|
|
|
|
foreach (var img in dragCell.GetComponentsInChildren<Image>())
|
|
|
|
|
|
img.color = new Color(img.color.r, img.color.g, img.color.b, img.color.a * .5f);
|
|
|
|
|
|
dragCell.GetComponentInChildren<Image>().raycastTarget = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
_dragOffsets = _dragCells.Select(x => (Vector2) x.position - data.position).ToArray();
|
|
|
|
|
|
IgnoreOccupancy = originalOccupancy;
|
|
|
|
|
|
RefreshCells();
|
|
|
|
|
|
_originalRotation = item.Rotation;
|
|
|
|
|
|
GameManager.BeginDrag(new ItemInstanceDragObject(item, cargo, cargo.Cargo[item] - v));
|
|
|
|
|
|
// TODO: SFX: Pickup
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
cell.DragTrigger.OnDragAsObservable()
|
|
|
|
|
|
.Subscribe(data =>
|
|
|
|
|
|
{
|
|
|
|
|
|
for (var i = 0; i < _dragCells.Length; i++)
|
|
|
|
|
|
_dragCells[i].position = new Vector3(
|
|
|
|
|
|
data.position.x + _dragOffsets[i].x,
|
|
|
|
|
|
data.position.y + _dragOffsets[i].y,
|
|
|
|
|
|
_dragCells[i].position.z);
|
|
|
|
|
|
});
|
|
|
|
|
|
cell.EndDragTrigger.OnEndDragAsObservable()
|
|
|
|
|
|
.Subscribe(data =>
|
|
|
|
|
|
{
|
|
|
|
|
|
//Debug.Log("Inventory Drag End");
|
|
|
|
|
|
IgnoreOccupancy = null;
|
|
|
|
|
|
GameManager.EndDrag();
|
|
|
|
|
|
foreach(var dragObject in _dragCells)
|
|
|
|
|
|
Destroy(dragObject.gameObject);
|
|
|
|
|
|
_dragCells = null;
|
|
|
|
|
|
RefreshCells();
|
|
|
|
|
|
});
|
|
|
|
|
|
cell.PointerEnterTrigger.OnPointerEnterAsObservable()
|
|
|
|
|
|
.Subscribe(data =>
|
|
|
|
|
|
{
|
|
|
|
|
|
//Debug.Log("Inventory Pointer Enter");
|
|
|
|
|
|
if (!(GameManager.DragObject is ItemDragObject itemDragObject)) return;
|
|
|
|
|
|
var item = itemDragObject.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 itemData = GameManager.ItemManager.GetData(item);
|
2026-08-10 14:57:50 +01:00
|
|
|
|
var placementPosition = v + itemDragObject.OriginCellOffset;
|
|
|
|
|
|
if (cargo.ItemFits(item, placementPosition))
|
|
|
|
|
|
{
|
|
|
|
|
|
//foreach (var cell in _dragCells) cell.gameObject.SetActive(false);
|
|
|
|
|
|
FakeItem = item;
|
|
|
|
|
|
FakeOccupancy = cargo.Data.InteriorShape.Inset(itemData.Shape, placementPosition, item.Rotation);
|
|
|
|
|
|
RefreshCells();
|
|
|
|
|
|
GameManager.RegisterDragTarget(drag =>
|
|
|
|
|
|
{
|
|
|
|
|
|
//Debug.Log("Inventory Drag Callback");
|
|
|
|
|
|
if (GameManager.DragObject is EquippedItemDragObject equippedItemDragObject)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(equippedItemDragObject.OriginEntity.TryUnequip(equippedItemDragObject.EquippedItem) == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// TODO: SFX: Fail
|
|
|
|
|
|
Dialog.Clear();
|
|
|
|
|
|
Dialog.Title.text = "Unable to move item!";
|
|
|
|
|
|
Dialog.AddProperty("Verify that cargo bays are empty before un-equipping them.");
|
|
|
|
|
|
Dialog.Show();
|
|
|
|
|
|
Dialog.MoveToCursor();
|
|
|
|
|
|
}
|
|
|
|
|
|
//else
|
|
|
|
|
|
// TODO: SFX: Unequip
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (GameManager.DragObject is ItemInstanceDragObject itemInstanceDragObject)
|
|
|
|
|
|
itemInstanceDragObject.OriginInventory.Remove(itemInstanceDragObject.Item);
|
|
|
|
|
|
cargo.TryStore(item, placementPosition);
|
|
|
|
|
|
FakeOccupancy = null;
|
|
|
|
|
|
// TODO: SFX: Drop
|
|
|
|
|
|
return true;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
cell.PointerExitTrigger.OnPointerExitAsObservable()
|
|
|
|
|
|
.Subscribe(data =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!(GameManager.DragObject is ItemDragObject)) return;
|
|
|
|
|
|
FakeItem = null;
|
|
|
|
|
|
FakeOccupancy = null;
|
|
|
|
|
|
RefreshCells();
|
|
|
|
|
|
GameManager.UnregisterDragTarget();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
CellInstances.Add(v, cell);
|
|
|
|
|
|
}
|
2020-12-20 03:04:52 +00:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
RefreshCells();
|
2020-12-20 03:04:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public void RefreshCells()
|
2020-12-20 03:04:52 +00:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
RefreshCells(CellInstances.Keys);
|
2026-06-30 19:14:49 +01:00
|
|
|
|
}
|
2021-01-10 03:59:47 +00:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public void RefreshCells(IEnumerable<int2> cells)
|
2026-06-30 19:14:49 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (_displayedEntity != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var v in cells)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!CellInstances.ContainsKey(v)) continue;
|
|
|
|
|
|
|
|
|
|
|
|
var hullData = GameManager.ItemManager.GetData(_displayedEntity.Hull) as HullData;
|
|
|
|
|
|
|
|
|
|
|
|
var spriteIndex = 0;
|
|
|
|
|
|
var interior = hullData.InteriorCells[v];
|
|
|
|
|
|
var item = FakeOccupancy?[v]??false ? FakeItem : IgnoreOccupancy?[v]??false ? null : _displayedEntity.GearOccupancy[v.x, v.y]?.EquippableItem;
|
|
|
|
|
|
var hardpoint = _displayedEntity.Hardpoints[v.x, v.y];
|
|
|
|
|
|
|
|
|
|
|
|
bool HardpointMatch(int2 offset)
|
|
|
|
|
|
{
|
|
|
|
|
|
var v2 = v + offset * (Flip ? -1 : 1);
|
|
|
|
|
|
return !(
|
|
|
|
|
|
hullData.Shape[v2] &&
|
|
|
|
|
|
_displayedEntity.Hardpoints[v2.x, v2.y] == hardpoint &&
|
|
|
|
|
|
(FakeOccupancy?[v2]??false ? FakeItem : IgnoreOccupancy?[v2]??false ? null : _displayedEntity.GearOccupancy[v2.x, v2.y]?.EquippableItem) == item
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2020-12-14 02:56:35 +00:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
bool NoHardpointMatch(int2 offset)
|
|
|
|
|
|
{
|
|
|
|
|
|
var v2 = v + offset * (Flip ? -1 : 1);
|
|
|
|
|
|
return !(
|
|
|
|
|
|
hullData.Shape[v2] && (
|
|
|
|
|
|
!interior && !hullData.InteriorCells[v2] && _displayedEntity.Hardpoints[v2.x, v2.y] == null ||
|
|
|
|
|
|
interior && item != null &&
|
|
|
|
|
|
(FakeOccupancy?[v2]??false ? FakeItem : IgnoreOccupancy?[v2]??false ? null : _displayedEntity.GearOccupancy[v2.x, v2.y]?.EquippableItem) == item
|
|
|
|
|
|
)
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2026-06-14 00:58:13 +01:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (hardpoint != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
for(int i = 0; i < 8; i++)
|
|
|
|
|
|
if (HardpointMatch(_offsets[i]))
|
|
|
|
|
|
spriteIndex += 1 << i;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
for(int i = 0; i < 8; i++)
|
|
|
|
|
|
if (NoHardpointMatch(_offsets[i]))
|
|
|
|
|
|
spriteIndex += 1 << i;
|
|
|
|
|
|
}
|
2026-06-14 01:03:52 +01:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
var bgSpriteIndex = spriteIndex;
|
2026-06-14 01:03:52 +01:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (item != null)
|
|
|
|
|
|
spriteIndex += 1 << 8;
|
2026-06-30 19:14:49 +01:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (_thermal)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool ThermalMatch(int2 offset)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!hullData.Shape[v + offset]) return false;
|
|
|
|
|
|
var i = (offset.x, offset.y);
|
|
|
|
|
|
return i switch
|
|
|
|
|
|
{
|
|
|
|
|
|
(1, 0) => _displayedEntity.HullConductivity[v.x, v.y].x,
|
|
|
|
|
|
(-1, 0) => _displayedEntity.HullConductivity[v.x-1, v.y].x,
|
|
|
|
|
|
(0, 1) => _displayedEntity.HullConductivity[v.x, v.y].y,
|
|
|
|
|
|
(0, -1) => _displayedEntity.HullConductivity[v.x, v.y-1].y,
|
|
|
|
|
|
_ => false
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
spriteIndex = 0;
|
|
|
|
|
|
for(int i = 0; i < 4; i++)
|
|
|
|
|
|
if (ThermalMatch(_offsets[i]))
|
|
|
|
|
|
spriteIndex += 1 << i;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CellInstances[v].Background.sprite = NodeBackgroundTextures[bgSpriteIndex];
|
|
|
|
|
|
CellInstances[v].Icon.sprite = _thermal ? ThermalTextures[spriteIndex] : NodeTextures[spriteIndex];
|
|
|
|
|
|
CellInstances[v].Icon.color = GetColor(v);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if(_displayedCargo != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var v in cells)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!CellInstances.ContainsKey(v)) continue;
|
|
|
|
|
|
|
|
|
|
|
|
var spriteIndex = 0;
|
|
|
|
|
|
var item = FakeOccupancy?[v]??false ? FakeItem : IgnoreOccupancy?[v]??false ? null : _displayedCargo.Occupancy[v.x, v.y];
|
2026-06-14 03:43:37 +01:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
bool ItemMatch(int2 offset)
|
|
|
|
|
|
{
|
|
|
|
|
|
var v2 = v + offset;
|
|
|
|
|
|
return !_displayedCargo.Data.InteriorShape[v2] || item == null ||
|
|
|
|
|
|
(FakeOccupancy?[v2]??false ? FakeItem : IgnoreOccupancy?[v2]??false ? null : _displayedCargo.Occupancy[v2.x, v2.y]) != item;
|
|
|
|
|
|
}
|
2026-06-14 03:43:37 +01:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
for(int i = 0; i < 8; i++)
|
|
|
|
|
|
if (ItemMatch(_offsets[i]))
|
|
|
|
|
|
spriteIndex += 1 << i;
|
|
|
|
|
|
|
|
|
|
|
|
var bgSpriteIndex = spriteIndex;
|
|
|
|
|
|
|
|
|
|
|
|
if (item != null)
|
|
|
|
|
|
spriteIndex += 1 << 8;
|
|
|
|
|
|
|
|
|
|
|
|
CellInstances[v].Background.sprite = NodeBackgroundTextures[bgSpriteIndex];
|
|
|
|
|
|
CellInstances[v].Icon.sprite = NodeTextures[spriteIndex];
|
|
|
|
|
|
CellInstances[v].Icon.color = GetColor(v);
|
|
|
|
|
|
}
|
2026-06-14 03:43:37 +01:00
|
|
|
|
}
|
2026-06-25 18:09:33 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public Color GetColor(int2 position, bool highlight = false)
|
2026-06-25 18:09:33 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (_displayedEntity != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var hullData = GameManager.ItemManager.GetData(_displayedEntity.Hull) as HullData;
|
|
|
|
|
|
var interior = hullData.InteriorCells[position];
|
|
|
|
|
|
var hardpoint = _displayedEntity.Hardpoints[position.x, position.y];
|
|
|
|
|
|
var item = (FakeOccupancy?[position] ?? false ? FakeItem :
|
|
|
|
|
|
IgnoreOccupancy?[position] ?? false ? null : _displayedEntity.GearOccupancy[position.x, position.y]?.EquippableItem) as EquippableItem;
|
2026-06-25 18:09:33 +01:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (_hud)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_displayedEntity.Armor[position.x, position.y] > .01f)
|
|
|
|
|
|
return Settings.ArmorGradient.Evaluate(_displayedEntity.Armor[position.x, position.y] / _displayedEntity.MaxArmor[position.x, position.y]);
|
2026-06-25 18:09:33 +01:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (item != null) return Settings.DurabilityGradient.Evaluate(item.Durability / _displayedEntity.ItemManager.GetData(item).Durability);
|
|
|
|
|
|
return float3(.25f).ToColor();
|
|
|
|
|
|
}
|
2026-06-25 18:09:33 +01:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (hardpoint == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!interior)
|
|
|
|
|
|
return Color.white;
|
|
|
|
|
|
|
|
|
|
|
|
if (item == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return float3(.25f).ToColor();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(highlight)
|
|
|
|
|
|
return Color.white;
|
|
|
|
|
|
|
|
|
|
|
|
return float3(.5f).ToColor();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var tint = hardpoint.TintColor;
|
|
|
|
|
|
|
|
|
|
|
|
if (!highlight || item == null)
|
|
|
|
|
|
tint *= .7071f;
|
|
|
|
|
|
|
|
|
|
|
|
return tint.ToColor();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
2026-06-25 18:09:33 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
var item = FakeOccupancy?[position] ?? false ? FakeItem : IgnoreOccupancy?[position] ?? false ? null : _displayedCargo.Occupancy[position.x, position.y];
|
|
|
|
|
|
|
|
|
|
|
|
if (item == null)
|
|
|
|
|
|
return Color.white * .25f;
|
|
|
|
|
|
|
|
|
|
|
|
var c = float3(1);
|
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 (GameManager.ItemManager.GetData(item) is EquippableItemData equippable)
|
2026-08-10 14:57:50 +01:00
|
|
|
|
c = HardpointData.GetColor(equippable.HardpointType);
|
|
|
|
|
|
|
|
|
|
|
|
if(!highlight)
|
|
|
|
|
|
c *= .7071f;
|
|
|
|
|
|
|
|
|
|
|
|
return c.ToColor();
|
2026-06-25 18:09:33 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public bool CanDropItem(ItemInstance item)
|
2026-06-25 18:09:33 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (_displayedEntity != null && item is EquippableItem equippableItem)
|
2026-06-25 18:09:33 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
return _displayedEntity.TryFindSpace(equippableItem, out _);
|
2026-06-25 18:09:33 +01:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
if (_displayedCargo != null)
|
2026-06-25 18:09:33 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
return _displayedCargo.TryFindSpace(item);
|
2026-06-25 18:09:33 +01:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
return false;
|
2026-06-25 18:09:33 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public bool DropItem(ItemInstance item)
|
2026-06-25 18:09:33 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (_displayedEntity != null && item is EquippableItem equippableItem)
|
2026-06-25 18:09:33 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
return _displayedEntity.TryEquip(equippableItem);
|
2026-06-25 18:09:33 +01:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
if(_displayedCargo != null)
|
2026-06-25 18:09:33 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
return _displayedCargo.TryStore(item);
|
2026-06-25 18:09:33 +01:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
return false;
|
2026-06-25 18:09:33 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public Subject<PointerEventData> OnBackgroundClick = new Subject<PointerEventData>();
|
|
|
|
|
|
private Transform[] _dragCells;
|
|
|
|
|
|
private Vector2[] _dragOffsets;
|
|
|
|
|
|
private ItemRotation _originalRotation;
|
|
|
|
|
|
|
|
|
|
|
|
public Subject<(InventoryEventData data, int clickCount)> OnClickAsObservable() => _onClick ?? (_onClick = new Subject<(InventoryEventData data, int clickCount)>());
|
|
|
|
|
|
// public UniRx.IObservable<InventoryEventData> OnBeginDragAsObservable() => _onBeginDrag ?? (_onBeginDrag = new Subject<InventoryEventData>());
|
|
|
|
|
|
// public UniRx.IObservable<InventoryEventData> OnDragAsObservable() => _onDrag ?? (_onDrag = new Subject<InventoryEventData>());
|
|
|
|
|
|
// public UniRx.IObservable<InventoryEventData> OnEndDragAsObservable() => _onEndDrag ?? (_onEndDrag = new Subject<InventoryEventData>());
|
|
|
|
|
|
// public UniRx.IObservable<InventoryEventData> OnPointerEnterAsObservable() => _onPointerEnter ?? (_onPointerEnter = new Subject<InventoryEventData>());
|
|
|
|
|
|
// public UniRx.IObservable<InventoryEventData> OnPointerExitAsObservable() => _onPointerExit ?? (_onPointerExit = new Subject<InventoryEventData>());
|
|
|
|
|
|
|
|
|
|
|
|
public void OnPointerClick(PointerEventData eventData)
|
2026-06-28 09:48:19 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
OnBackgroundClick.OnNext(eventData);
|
2026-06-28 09:48:19 +01:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
}
|
2026-06-28 09:48:19 +01:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public abstract class InventoryEventData
|
|
|
|
|
|
{
|
|
|
|
|
|
protected InventoryEventData(PointerEventData pointerEventData, int2 position)
|
2026-06-28 09:48:19 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
PointerEventData = pointerEventData;
|
|
|
|
|
|
Position = position;
|
2026-06-28 09:48:19 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public PointerEventData PointerEventData { get; }
|
|
|
|
|
|
public int2 Position { get; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public class InventoryEntityEventData : InventoryEventData
|
|
|
|
|
|
{
|
|
|
|
|
|
public InventoryEntityEventData(PointerEventData pointerEventData, int2 position, Entity entity) :
|
|
|
|
|
|
base(pointerEventData, position)
|
2021-01-27 16:14:41 +00:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
Entity = entity;
|
|
|
|
|
|
}
|
2021-01-27 16:14:41 +00:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public Entity Entity { get; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public class InventoryCargoEventData : InventoryEventData
|
|
|
|
|
|
{
|
|
|
|
|
|
public InventoryCargoEventData(PointerEventData pointerEventData, int2 position, EquippedCargoBay cargoBay) :
|
|
|
|
|
|
base(pointerEventData, position)
|
|
|
|
|
|
{
|
|
|
|
|
|
CargoBay = cargoBay;
|
2026-06-16 22:08:16 +01:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
public EquippedCargoBay CargoBay { get; }
|
2020-12-12 03:42:58 +00:00
|
|
|
|
}
|
2021-02-27 20:53:36 +00:00
|
|
|
|
|
|
|
|
|
|
public enum InventoryPanelTarget
|
|
|
|
|
|
{
|
|
|
|
|
|
None,
|
2026-08-10 14:57:50 +01:00
|
|
|
|
Cargo,
|
|
|
|
|
|
Equipment
|
2021-02-27 20:53:36 +00:00
|
|
|
|
}
|