2026-06-13 18:06:59 +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;
|
2020-12-19 02:37:13 +00:00
|
|
|
using System.Collections;
|
2020-12-12 03:42:58 +00:00
|
|
|
using System.Collections.Generic;
|
2026-06-25 18:09:33 +01:00
|
|
|
using System.Globalization;
|
2020-12-12 03:42:58 +00:00
|
|
|
using System.Linq;
|
2026-06-21 09:37:54 +01:00
|
|
|
using GameCult.Aetheria.EveRuntime;
|
2026-06-21 09:54:15 +01:00
|
|
|
using GameCult.Aetheria.State.Verse;
|
2026-06-16 22:08:16 +01:00
|
|
|
using GameCult.Eve.Surface;
|
2026-06-28 22:28:54 +01:00
|
|
|
using GameCult.Mesh;
|
2020-12-12 03:42:58 +00:00
|
|
|
using TMPro;
|
2020-12-20 03:04:52 +00:00
|
|
|
using UniRx;
|
|
|
|
|
using UniRx.Triggers;
|
2020-12-12 03:42:58 +00:00
|
|
|
using UnityEngine;
|
|
|
|
|
using UnityEngine.UI;
|
2020-12-20 03:04:52 +00:00
|
|
|
using UnityEngine.EventSystems;
|
2020-12-27 22:18:22 +00:00
|
|
|
using UnityEngine.Experimental.Rendering;
|
2021-01-19 01:22:56 +00:00
|
|
|
using Unity.Mathematics;
|
2021-03-15 11:40:46 +00:00
|
|
|
using UnityEngine.Serialization;
|
2026-06-16 22:08:16 +01:00
|
|
|
using UIE = UnityEngine.UIElements;
|
2020-12-12 03:42:58 +00:00
|
|
|
using static Unity.Mathematics.math;
|
2020-12-12 20:43:09 +00:00
|
|
|
using int2 = Unity.Mathematics.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;
|
2020-12-12 03:42:58 +00:00
|
|
|
public TextMeshProUGUI Title;
|
2021-01-27 00:05:30 +00:00
|
|
|
public TextMeshProUGUI MinTempLabel;
|
|
|
|
|
public TextMeshProUGUI MaxTempLabel;
|
2021-02-01 08:13:14 +00:00
|
|
|
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;
|
2020-12-12 03:42:58 +00:00
|
|
|
public GridLayoutGroup Grid;
|
2020-12-27 22:18:22 +00:00
|
|
|
public Sprite[] NodeBackgroundTextures;
|
2020-12-12 03:42:58 +00:00
|
|
|
public Sprite[] NodeTextures;
|
2021-01-17 02:45:52 +00:00
|
|
|
public Sprite[] ThermalTextures;
|
|
|
|
|
public float ThermalToggleRegionSize = .2f;
|
2020-12-12 03:42:58 +00:00
|
|
|
public Prototype NodePrototype;
|
2020-12-27 22:18:22 +00:00
|
|
|
public RawImage TemperatureDisplay;
|
|
|
|
|
public Texture2D TemperatureColor;
|
|
|
|
|
public ExponentialLerp TemperatureColorCurve;
|
|
|
|
|
public ExponentialLerp TemperatureAlphaCurve;
|
|
|
|
|
public bool FitToContent;
|
2021-01-10 03:59:47 +00:00
|
|
|
public float CellHitPulseTime;
|
2021-01-17 02:45:52 +00:00
|
|
|
public Color ToggleEnabledColor;
|
|
|
|
|
public Color ToggleDisabledColor;
|
2021-01-10 03:59:47 +00:00
|
|
|
public Color CellBackgroundColor = new Color(0, 0, 0, .75f);
|
2021-04-10 01:47:16 +01:00
|
|
|
public float MinTempRange = 1;
|
|
|
|
|
public float DoubleClickTime = .5f;
|
2021-04-13 01:50:42 +01:00
|
|
|
public float HitDamageThreshold = 1;
|
2020-12-20 03:04:52 +00:00
|
|
|
|
2021-04-21 01:49:07 +01:00
|
|
|
// Subject<InventoryEventData> _onBeginDrag;
|
|
|
|
|
// Subject<InventoryEventData> _onDrag;
|
|
|
|
|
// Subject<InventoryEventData> _onEndDrag;
|
2021-04-10 01:47:16 +01:00
|
|
|
Subject<(InventoryEventData data, int clickCount)> _onClick;
|
2021-04-21 01:49:07 +01:00
|
|
|
// 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-06-25 18:09:33 +01:00
|
|
|
private string _displayedEntityKey = "";
|
|
|
|
|
private string _displayedCargoEntityKey = "";
|
|
|
|
|
private int _displayedCargoIndex = -1;
|
2026-06-14 03:43:37 +01:00
|
|
|
private Shape _displayedHullShape;
|
|
|
|
|
private Shape _displayedHullInterior;
|
2020-12-27 22:18:22 +00:00
|
|
|
private Texture2D _temperatureTexture;
|
|
|
|
|
private RectTransform _firstRect;
|
2026-06-25 18:09:33 +01:00
|
|
|
private AetheriaUnityDragSession _dragSession;
|
2026-06-28 03:34:08 +01:00
|
|
|
private AetheriaUnityObservedEntityIndex _observedEntityIndex;
|
2026-06-28 10:07:05 +01:00
|
|
|
private AetheriaUnityObservedDockingIndex _observedDockingIndex;
|
2020-12-20 03:04:52 +00:00
|
|
|
|
|
|
|
|
public ItemInstance FakeItem;
|
|
|
|
|
public Shape FakeOccupancy;
|
|
|
|
|
public Shape IgnoreOccupancy;
|
|
|
|
|
public List<GameObject> EmptyCells = new List<GameObject>();
|
2026-06-25 18:09:33 +01:00
|
|
|
|
|
|
|
|
public void SetDragSession(AetheriaUnityDragSession dragSession)
|
|
|
|
|
{
|
|
|
|
|
_dragSession = dragSession;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 03:34:08 +01:00
|
|
|
public void SetObservedEntityIndex(AetheriaUnityObservedEntityIndex observedEntityIndex)
|
2026-06-25 18:09:33 +01:00
|
|
|
{
|
2026-06-28 15:12:19 +01:00
|
|
|
if (!ReferenceEquals(_observedEntityIndex, observedEntityIndex))
|
|
|
|
|
{
|
|
|
|
|
_observedDockingIndex?.Dispose();
|
|
|
|
|
_observedDockingIndex = null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 03:34:08 +01:00
|
|
|
_observedEntityIndex = observedEntityIndex;
|
2026-06-25 18:09:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private AetheriaUnityDragSession DragSession => _dragSession ??= new AetheriaUnityDragSession();
|
2020-12-20 03:04:52 +00:00
|
|
|
public Dictionary<int2, InventoryCell> CellInstances = new Dictionary<int2, InventoryCell>();
|
|
|
|
|
|
2021-01-08 12:44:10 +00:00
|
|
|
private List<IDisposable> _subscriptions = new List<IDisposable>();
|
2021-01-10 03:59:47 +00:00
|
|
|
private Dictionary<int2, int> _cellAnimationSequence = new Dictionary<int2, int>();
|
|
|
|
|
private int _hitSequence = 0;
|
2021-01-17 02:45:52 +00:00
|
|
|
private bool _thermal = false;
|
2021-04-10 01:47:16 +01:00
|
|
|
private int _clickCount;
|
|
|
|
|
private InventoryCell _clickCell;
|
|
|
|
|
private float _clickTime;
|
2026-06-25 18:09:33 +01:00
|
|
|
private string _clientStatePath = "";
|
2026-06-28 22:28:54 +01:00
|
|
|
private CultMeshReactiveDocument<AetheriaRuntimeCatalogSnapshot> _catalog;
|
|
|
|
|
private CultMeshReactiveDocument<AetheriaRuntimePlayerSettingsDocument> _playerSettings;
|
2026-06-28 23:00:02 +01:00
|
|
|
private CultMeshReactiveDocument<AetheriaRuntimeCurrentEntityDocument> _currentEntity;
|
|
|
|
|
private CultMeshReactiveDocument<AetheriaRuntimeStationRefitDocument> _stationRefit;
|
|
|
|
|
private CultMeshReactiveDocument<AetheriaRuntimeDaemonFrameDocument> _loadoutFrame;
|
2026-06-28 09:48:19 +01:00
|
|
|
private int _inventoryEntityIndex = -1;
|
2026-06-28 23:00:02 +01:00
|
|
|
private CultMeshReactiveDocument<AetheriaRuntimeInventoryDocument> _inventory;
|
2026-06-25 18:09:33 +01:00
|
|
|
private AetheriaRuntimeStationRefitEntityOption[] _dropdownStationRefitEntities =
|
|
|
|
|
Array.Empty<AetheriaRuntimeStationRefitEntityOption>();
|
|
|
|
|
private AetheriaRuntimeStationLoadoutRestoreOption[] _dropdownStationRefitLoadouts =
|
|
|
|
|
Array.Empty<AetheriaRuntimeStationLoadoutRestoreOption>();
|
2026-06-28 15:31:19 +01:00
|
|
|
private AetheriaRuntimeInventoryDropdownSurfaceModel _dropdownSurfaceModel;
|
2026-06-21 09:37:54 +01:00
|
|
|
private readonly AetheriaEveUnitySurfaceChrome _dropdownSurfaceChrome = new AetheriaEveUnitySurfaceChrome
|
|
|
|
|
{
|
|
|
|
|
RootAlignItems = UIE.Align.FlexStart,
|
|
|
|
|
RootJustifyContent = UIE.Justify.FlexStart,
|
|
|
|
|
RootPaddingTop = 0f,
|
|
|
|
|
Width = 420f,
|
|
|
|
|
MinWidth = 0f,
|
|
|
|
|
MaxWidth = 520f,
|
|
|
|
|
PaddingLeft = 18f,
|
|
|
|
|
PaddingRight = 18f,
|
|
|
|
|
PaddingTop = 18f,
|
|
|
|
|
PaddingBottom = 18f
|
|
|
|
|
};
|
2026-06-16 22:08:16 +01:00
|
|
|
private UIE.UIDocument _dropdownSurfaceDocument;
|
2021-01-10 03:59:47 +00:00
|
|
|
|
|
|
|
|
private bool _hud;
|
2021-01-08 12:44:10 +00:00
|
|
|
|
2020-12-20 03:04:52 +00:00
|
|
|
private int2[] _offsets = {
|
2020-12-12 20:43:09 +00:00
|
|
|
int2(0, 1),
|
|
|
|
|
int2(1, 0),
|
|
|
|
|
int2(0, -1),
|
|
|
|
|
int2(-1, 0),
|
|
|
|
|
int2(1, 1),
|
|
|
|
|
int2(1, -1),
|
|
|
|
|
int2(-1, -1),
|
|
|
|
|
int2(-1, 1)
|
|
|
|
|
};
|
2020-12-12 03:42:58 +00:00
|
|
|
|
2021-02-27 20:53:36 +00:00
|
|
|
public InventoryPanelTarget Target =>
|
|
|
|
|
_displayedEntity != null ? InventoryPanelTarget.Equipment :
|
|
|
|
|
_displayedCargo != null ? InventoryPanelTarget.Cargo : InventoryPanelTarget.None;
|
2026-06-16 01:59:18 +01:00
|
|
|
public Entity DisplayedEntity => _displayedEntity;
|
|
|
|
|
public EquippedCargoBay DisplayedCargo => _displayedCargo;
|
2021-02-27 20:53:36 +00:00
|
|
|
|
2020-12-19 02:37:13 +00:00
|
|
|
private void Start()
|
|
|
|
|
{
|
2021-01-17 02:45:52 +00:00
|
|
|
if (Thermal)
|
|
|
|
|
{
|
|
|
|
|
Thermal.onClick.AddListener(() =>
|
|
|
|
|
{
|
2026-06-16 22:08:16 +01:00
|
|
|
HideDropdownSurface();
|
2021-01-17 02:45:52 +00:00
|
|
|
Display(_displayedEntity, false, !_thermal);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Current)
|
|
|
|
|
{
|
|
|
|
|
Current.onClick.AddListener(() =>
|
|
|
|
|
{
|
2026-06-16 22:08:16 +01:00
|
|
|
HideDropdownSurface();
|
2026-06-25 18:09:33 +01:00
|
|
|
if (!IsCurrentEntity(_displayedEntity))
|
2021-01-17 02:45:52 +00:00
|
|
|
{
|
2021-02-11 03:14:20 +00:00
|
|
|
if(_displayedEntity is Ship ship)
|
|
|
|
|
{
|
2026-06-25 18:09:33 +01:00
|
|
|
RequestDockedCurrentShip(ship);
|
2021-02-11 03:14:20 +00:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2021-03-15 11:40:46 +00:00
|
|
|
Dialog.Clear();
|
|
|
|
|
Dialog.Title.text = "Can't select entity, you can only pilot a ship!";
|
|
|
|
|
Dialog.Show();
|
|
|
|
|
Dialog.MoveToCursor();
|
2021-02-11 03:14:20 +00:00
|
|
|
}
|
2021-01-17 02:45:52 +00:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2021-04-10 01:47:16 +01:00
|
|
|
|
|
|
|
|
if (EditName)
|
|
|
|
|
{
|
|
|
|
|
EditName.onClick.AddListener(() =>
|
|
|
|
|
{
|
2026-06-16 22:08:16 +01:00
|
|
|
HideDropdownSurface();
|
2021-04-10 01:47:16 +01:00
|
|
|
Dialog.Clear();
|
|
|
|
|
var entityName = _displayedEntity.Name;
|
|
|
|
|
Dialog.AddField("Name", () => entityName, s => entityName = s);
|
|
|
|
|
Dialog.Show(() =>
|
|
|
|
|
{
|
2026-06-25 18:09:33 +01:00
|
|
|
RequestEntityName(_displayedEntity, entityName);
|
2021-04-10 01:47:16 +01:00
|
|
|
});
|
|
|
|
|
Dialog.MoveToCursor();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-27 22:18:22 +00:00
|
|
|
if(Dropdown)
|
2020-12-19 02:37:13 +00:00
|
|
|
{
|
2020-12-27 22:18:22 +00:00
|
|
|
Dropdown.onClick.AddListener(() =>
|
2020-12-19 02:37:13 +00:00
|
|
|
{
|
2026-06-16 22:08:16 +01:00
|
|
|
RenderDropdownSurface();
|
2020-12-27 22:18:22 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-16 22:08:16 +01:00
|
|
|
private void RenderDropdownSurface()
|
|
|
|
|
{
|
2026-06-28 15:31:19 +01:00
|
|
|
_dropdownSurfaceModel = ComposeDropdownSurface();
|
2026-06-16 22:08:16 +01:00
|
|
|
|
2026-06-21 09:37:54 +01:00
|
|
|
_dropdownSurfaceDocument = AetheriaEveUnitySurfaceHost.RenderRuntime(
|
|
|
|
|
transform,
|
|
|
|
|
_dropdownSurfaceDocument,
|
|
|
|
|
"Aetheria Inventory Dropdown Surface",
|
2026-06-28 15:31:19 +01:00
|
|
|
AetheriaRuntimeInventoryDropdownSurfaceBuilder.Build(_dropdownSurfaceModel.State),
|
2026-06-21 09:37:54 +01:00
|
|
|
HandleDropdownSurfaceCommand,
|
|
|
|
|
_dropdownSurfaceChrome);
|
2026-06-16 22:08:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void HandleDropdownSurfaceCommand(EveSurfaceCommandRequest request)
|
|
|
|
|
{
|
2026-06-21 09:37:54 +01:00
|
|
|
if (!AetheriaRuntimeInventoryDropdownSurfaceCommands.TryRead(request, out var command))
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning($"Unknown inventory dropdown command: {request?.Command}");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (command.Kind == AetheriaRuntimeInventoryDropdownCommandKind.Close)
|
2026-06-16 22:08:16 +01:00
|
|
|
{
|
|
|
|
|
HideDropdownSurface();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-21 09:37:54 +01:00
|
|
|
if (command.Kind == AetheriaRuntimeInventoryDropdownCommandKind.Select &&
|
2026-06-28 15:31:19 +01:00
|
|
|
_dropdownSurfaceModel?.TryResolve(command.Command, out var selection) == true)
|
2026-06-16 22:08:16 +01:00
|
|
|
{
|
2026-06-21 21:25:13 +01:00
|
|
|
ExecuteDropdownSelection(selection);
|
2026-06-16 22:08:16 +01:00
|
|
|
HideDropdownSurface();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-21 09:37:54 +01:00
|
|
|
Debug.LogWarning($"Unknown inventory dropdown command: {request?.Command}");
|
2026-06-16 22:08:16 +01:00
|
|
|
}
|
|
|
|
|
|
2026-06-21 21:25:13 +01:00
|
|
|
private void ExecuteDropdownSelection(AetheriaRuntimeInventoryDropdownSelection selection)
|
2026-06-16 22:08:16 +01:00
|
|
|
{
|
2026-06-21 21:25:13 +01:00
|
|
|
switch (selection.Kind)
|
2026-06-16 22:08:16 +01:00
|
|
|
{
|
2026-06-21 21:25:13 +01:00
|
|
|
case AetheriaRuntimeInventoryDropdownSelectionKind.EntityEquipment:
|
|
|
|
|
case AetheriaRuntimeInventoryDropdownSelectionKind.Entity:
|
2026-06-25 18:09:33 +01:00
|
|
|
if (TryResolveStationRefitEntity(selection.EntityKey, selection.EntityIndex, out var entity))
|
2026-06-16 22:08:16 +01:00
|
|
|
{
|
2026-06-25 18:09:33 +01:00
|
|
|
Display(entity);
|
2026-06-16 22:08:16 +01:00
|
|
|
}
|
2026-06-21 21:25:13 +01:00
|
|
|
return;
|
|
|
|
|
case AetheriaRuntimeInventoryDropdownSelectionKind.EntityBay:
|
2026-06-25 18:09:33 +01:00
|
|
|
if (TryResolveStationRefitEntity(selection.EntityKey, selection.EntityIndex, out entity) &&
|
2026-06-21 21:25:13 +01:00
|
|
|
selection.BayIndex >= 0 &&
|
2026-06-25 18:09:33 +01:00
|
|
|
selection.BayIndex < entity.CargoBays.Count)
|
2026-06-21 09:37:54 +01:00
|
|
|
{
|
2026-06-25 18:09:33 +01:00
|
|
|
Display(entity.CargoBays[selection.BayIndex]);
|
2026-06-21 21:25:13 +01:00
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
case AetheriaRuntimeInventoryDropdownSelectionKind.DockingBay:
|
2026-06-28 03:24:07 +01:00
|
|
|
if (TryResolveCurrentDockingBay(out var dockingBay))
|
2026-06-21 21:25:13 +01:00
|
|
|
{
|
|
|
|
|
Display(dockingBay);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
case AetheriaRuntimeInventoryDropdownSelectionKind.SaveLoadout:
|
|
|
|
|
if (_displayedEntity != null)
|
|
|
|
|
{
|
2026-06-25 18:09:33 +01:00
|
|
|
RequestLoadoutTemplateSave(_displayedEntity);
|
2026-06-21 21:25:13 +01:00
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
case AetheriaRuntimeInventoryDropdownSelectionKind.Loadout:
|
2026-06-25 18:09:33 +01:00
|
|
|
if (selection.TemplateIndex >= 0 && selection.TemplateIndex < _dropdownStationRefitLoadouts.Length)
|
2026-06-21 21:25:13 +01:00
|
|
|
{
|
2026-06-25 18:09:33 +01:00
|
|
|
RequestRuntimeLoadoutRestore(_dropdownStationRefitLoadouts[selection.TemplateIndex]);
|
2026-06-21 21:25:13 +01:00
|
|
|
}
|
|
|
|
|
return;
|
2026-06-16 22:08:16 +01:00
|
|
|
}
|
2026-06-21 21:25:13 +01:00
|
|
|
}
|
2026-06-16 22:08:16 +01:00
|
|
|
|
2026-06-21 21:25:13 +01:00
|
|
|
private void HideDropdownSurface()
|
|
|
|
|
{
|
|
|
|
|
if (_dropdownSurfaceDocument == null)
|
|
|
|
|
return;
|
2026-06-16 22:08:16 +01:00
|
|
|
|
2026-06-21 21:25:13 +01:00
|
|
|
AetheriaEveUnitySurfaceHost.Hide(_dropdownSurfaceDocument);
|
|
|
|
|
}
|
2026-06-16 22:08:16 +01:00
|
|
|
|
2026-06-28 15:31:19 +01:00
|
|
|
private AetheriaRuntimeInventoryDropdownSurfaceModel ComposeDropdownSurface()
|
2026-06-21 21:25:13 +01:00
|
|
|
{
|
2026-06-25 18:09:33 +01:00
|
|
|
var stationRefit = ResolveStationRefit();
|
|
|
|
|
_dropdownStationRefitEntities = (stationRefit?.AvailableEntities ?? Array.Empty<AetheriaRuntimeStationRefitEntityOption>())
|
|
|
|
|
.ToArray();
|
|
|
|
|
_dropdownStationRefitLoadouts = (stationRefit?.LoadoutRestoreOptions ?? Array.Empty<AetheriaRuntimeStationLoadoutRestoreOption>())
|
|
|
|
|
.ToArray();
|
|
|
|
|
var entityOptions = _dropdownStationRefitEntities
|
2026-06-21 21:25:13 +01:00
|
|
|
.Select((entity, entityIndex) => new AetheriaRuntimeInventoryDropdownEntityOption(
|
|
|
|
|
entityIndex,
|
2026-06-25 18:09:33 +01:00
|
|
|
entity.EntityKey,
|
|
|
|
|
string.IsNullOrWhiteSpace(entity.DisplayName) ? $"Entity {entity.EntityIndex}" : entity.DisplayName,
|
|
|
|
|
IsDisplayedEntityKey(entity.EntityKey),
|
|
|
|
|
Enumerable.Range(0, Math.Max(entity.CargoBayCount, 0))
|
|
|
|
|
.Select(bayIndex => new AetheriaRuntimeInventoryDropdownBayOption(
|
2026-06-21 21:25:13 +01:00
|
|
|
bayIndex,
|
|
|
|
|
$"Bay {bayIndex + 1}",
|
2026-06-25 18:09:33 +01:00
|
|
|
IsDisplayedCargoBayKey(entity.EntityKey, bayIndex)))
|
2026-06-21 21:25:13 +01:00
|
|
|
.ToArray()))
|
|
|
|
|
.ToArray();
|
2026-06-25 18:09:33 +01:00
|
|
|
var loadoutOptions = _dropdownStationRefitLoadouts
|
|
|
|
|
.Select((loadout, optionIndex) => new AetheriaRuntimeInventoryDropdownLoadoutOption(
|
|
|
|
|
optionIndex,
|
|
|
|
|
loadout.TemplateName,
|
|
|
|
|
loadout.CanRestore ? $"{loadout.Price:n0}" : "",
|
|
|
|
|
loadout.CanRestore))
|
2026-06-21 21:25:13 +01:00
|
|
|
.ToArray();
|
2026-06-25 18:09:33 +01:00
|
|
|
var hasDockingBay = TryResolveCurrentDockingBayRow(out var currentDockingBay);
|
2026-06-21 21:25:13 +01:00
|
|
|
|
2026-06-28 15:31:19 +01:00
|
|
|
return AetheriaRuntimeInventoryDropdownSurfaceBuilder.Compose(
|
2026-06-21 09:37:54 +01:00
|
|
|
Title?.text ?? "None",
|
2026-06-21 21:25:13 +01:00
|
|
|
entityOptions,
|
|
|
|
|
hasDockingBay,
|
2026-06-25 18:09:33 +01:00
|
|
|
hasDockingBay
|
|
|
|
|
? $"Docking Bay {currentDockingBay.DockingBayIndex + 1}"
|
|
|
|
|
: "Docking Bay",
|
|
|
|
|
hasDockingBay &&
|
|
|
|
|
IsDisplayedCargoBayKey(
|
|
|
|
|
ResolveStationRefit()?.DockParentEntityKey ?? "",
|
|
|
|
|
currentDockingBay.DockingBayIndex),
|
2026-06-21 21:25:13 +01:00
|
|
|
_displayedEntity != null,
|
|
|
|
|
loadoutOptions,
|
2026-06-21 09:37:54 +01:00
|
|
|
DateTime.UtcNow.ToString("O"));
|
2026-06-16 22:08:16 +01:00
|
|
|
}
|
|
|
|
|
|
2026-06-21 09:37:54 +01:00
|
|
|
private void Update()
|
2020-12-27 22:18:22 +00:00
|
|
|
{
|
2021-01-27 00:05:30 +00:00
|
|
|
if (_displayedEntity != null && TemperatureDisplay)
|
2020-12-27 22:18:22 +00:00
|
|
|
{
|
2021-04-10 01:47:16 +01:00
|
|
|
var tempRange = _displayedEntity.MaxTemp - _displayedEntity.MinTemp;
|
|
|
|
|
var opacity = smoothstep(0, MinTempRange, tempRange);
|
2021-01-27 00:05:30 +00:00
|
|
|
if(MinTempLabel)
|
|
|
|
|
{
|
2026-06-25 18:09:33 +01:00
|
|
|
MinTempLabel.text = FormatTemperature(_displayedEntity.MinTemp);
|
|
|
|
|
MaxTempLabel.text = FormatTemperature(_displayedEntity.MaxTemp);
|
2021-01-27 00:05:30 +00:00
|
|
|
}
|
2020-12-27 22:18:22 +00:00
|
|
|
for(var x = 0; x < _temperatureTexture.width; x++)
|
2020-12-19 02:37:13 +00:00
|
|
|
{
|
2020-12-27 22:18:22 +00:00
|
|
|
for (var y = 0; y < _temperatureTexture.height; y++)
|
|
|
|
|
{
|
2026-06-14 03:43:37 +01:00
|
|
|
if (_displayedHullShape[int2(x-1, y-1)])
|
2020-12-27 22:18:22 +00:00
|
|
|
{
|
2021-01-27 00:05:30 +00:00
|
|
|
var temp = (_displayedEntity.Temperature[x - 1, y - 1] - _displayedEntity.MinTemp) / (_displayedEntity.MaxTemp-_displayedEntity.MinTemp);
|
2020-12-27 22:18:22 +00:00
|
|
|
var color = TemperatureColor.GetPixelBilinear(TemperatureColorCurve.Evaluate(temp), 0);
|
2021-04-10 01:47:16 +01:00
|
|
|
color.a = TemperatureAlphaCurve.Evaluate(temp) * opacity;
|
2020-12-27 22:18:22 +00:00
|
|
|
_temperatureTexture.SetPixel(x,y,color);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
_temperatureTexture.SetPixel(x,y,new Color(0,0,0,0));
|
|
|
|
|
}
|
2020-12-19 02:37:13 +00:00
|
|
|
}
|
2020-12-27 22:18:22 +00:00
|
|
|
_temperatureTexture.Apply();
|
|
|
|
|
TemperatureDisplay.rectTransform.anchoredPosition = _firstRect.anchoredPosition - Vector2.one * Grid.cellSize * 1.5f;
|
|
|
|
|
}
|
2020-12-19 02:37:13 +00:00
|
|
|
}
|
|
|
|
|
|
2021-01-08 12:44:10 +00:00
|
|
|
private void OnDisable()
|
|
|
|
|
{
|
2026-06-16 22:08:16 +01:00
|
|
|
HideDropdownSurface();
|
2021-01-08 12:44:10 +00:00
|
|
|
Clear();
|
|
|
|
|
}
|
2021-01-17 02:45:52 +00:00
|
|
|
|
2021-01-08 12:44:10 +00:00
|
|
|
|
2020-12-27 22:18:22 +00:00
|
|
|
public void Clear()
|
2020-12-12 03:42:58 +00:00
|
|
|
{
|
2026-06-16 22:08:16 +01:00
|
|
|
HideDropdownSurface();
|
2020-12-27 22:18:22 +00:00
|
|
|
_displayedCargo = null;
|
|
|
|
|
_displayedEntity = null;
|
2026-06-25 18:09:33 +01:00
|
|
|
_displayedEntityKey = "";
|
|
|
|
|
_displayedCargoEntityKey = "";
|
|
|
|
|
_displayedCargoIndex = -1;
|
2026-06-14 03:43:37 +01:00
|
|
|
_displayedHullShape = null;
|
|
|
|
|
_displayedHullInterior = null;
|
2020-12-27 22:18:22 +00:00
|
|
|
|
2020-12-20 03:04:52 +00:00
|
|
|
foreach(var empty in EmptyCells)
|
|
|
|
|
Destroy(empty);
|
|
|
|
|
EmptyCells.Clear();
|
|
|
|
|
|
|
|
|
|
foreach(var node in CellInstances.Values)
|
|
|
|
|
node.GetComponent<Prototype>().ReturnToPool();
|
|
|
|
|
CellInstances.Clear();
|
2020-12-27 22:18:22 +00:00
|
|
|
|
2021-01-08 12:44:10 +00:00
|
|
|
foreach(var s in _subscriptions)
|
|
|
|
|
s.Dispose();
|
|
|
|
|
|
|
|
|
|
_subscriptions.Clear();
|
|
|
|
|
|
2021-04-10 01:47:16 +01:00
|
|
|
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);
|
2021-02-01 08:13:14 +00:00
|
|
|
|
2020-12-27 22:18:22 +00:00
|
|
|
if(Title)
|
|
|
|
|
Title.text = "None";
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-17 02:45:52 +00:00
|
|
|
public void Display(Entity entity, bool hud = false, bool thermal = false)
|
2020-12-27 22:18:22 +00:00
|
|
|
{
|
|
|
|
|
Clear();
|
2021-04-21 01:49:07 +01:00
|
|
|
|
|
|
|
|
_subscriptions.Add(entity.Equipment.ObserveAdd().Subscribe(_ => RefreshCells()));
|
|
|
|
|
_subscriptions.Add(entity.Equipment.ObserveRemove().Subscribe(_ => RefreshCells()));
|
|
|
|
|
|
2021-01-17 02:45:52 +00:00
|
|
|
_thermal = thermal;
|
2021-01-10 03:59:47 +00:00
|
|
|
_hud = hud;
|
2020-12-20 03:04:52 +00:00
|
|
|
|
|
|
|
|
_displayedEntity = entity;
|
2021-01-17 02:45:52 +00:00
|
|
|
_displayedCargo = null;
|
2026-06-25 18:09:33 +01:00
|
|
|
_displayedCargoEntityKey = "";
|
|
|
|
|
_displayedCargoIndex = -1;
|
|
|
|
|
_displayedEntityKey = TryResolveEntityRecordKey(entity, out var displayedEntityKey)
|
|
|
|
|
? displayedEntityKey
|
|
|
|
|
: "";
|
2020-12-27 22:18:22 +00:00
|
|
|
_firstRect = null;
|
2021-01-27 00:05:30 +00:00
|
|
|
|
2021-02-01 08:13:14 +00:00
|
|
|
if (TemperatureRange)
|
|
|
|
|
TemperatureRange.SetActive(true);
|
2020-12-27 22:18:22 +00:00
|
|
|
|
|
|
|
|
if(Title)
|
|
|
|
|
Title.text = entity.Name;
|
2020-12-19 02:37:13 +00:00
|
|
|
|
2021-04-10 01:47:16 +01:00
|
|
|
if (EditName)
|
|
|
|
|
{
|
|
|
|
|
EditName.gameObject.SetActive(true);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-14 03:43:37 +01:00
|
|
|
if (!TryResolveTypedHullGeometry(entity.Hull, out _displayedHullShape, out _displayedHullInterior))
|
|
|
|
|
{
|
|
|
|
|
Debug.LogError($"Cannot display inventory for {entity.Name}: missing typed hull geometry.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-01-17 02:45:52 +00:00
|
|
|
|
2020-12-27 22:18:22 +00:00
|
|
|
if (FitToContent)
|
|
|
|
|
{
|
|
|
|
|
var gridRect = Grid.GetComponent<RectTransform>();
|
|
|
|
|
var rect = gridRect.rect;
|
2026-06-14 03:43:37 +01:00
|
|
|
Grid.cellSize = Vector2.one * (int) min(rect.width / (_displayedHullShape.Width + 1), rect.height / (_displayedHullShape.Height + 1));
|
2020-12-27 22:18:22 +00:00
|
|
|
}
|
2021-01-17 02:45:52 +00:00
|
|
|
|
2021-01-27 00:05:30 +00:00
|
|
|
if(TemperatureDisplay)
|
|
|
|
|
{
|
|
|
|
|
_temperatureTexture = new Texture2D(
|
2026-06-14 03:43:37 +01:00
|
|
|
_displayedHullShape.Width + 2,
|
|
|
|
|
_displayedHullShape.Height + 2,
|
2021-01-27 00:05:30 +00:00
|
|
|
TextureFormat.RGBA32,
|
|
|
|
|
false,
|
|
|
|
|
false);
|
|
|
|
|
TemperatureDisplay.gameObject.SetActive(true);
|
|
|
|
|
TemperatureDisplay.texture = _temperatureTexture;
|
|
|
|
|
var tempRect = TemperatureDisplay.rectTransform;
|
2026-06-14 03:43:37 +01:00
|
|
|
tempRect.sizeDelta = Grid.cellSize * new Vector2(_displayedHullShape.Width + 2, _displayedHullShape.Height + 2);
|
2021-01-27 00:05:30 +00:00
|
|
|
}
|
2021-01-17 02:45:52 +00:00
|
|
|
|
|
|
|
|
if (Current)
|
|
|
|
|
{
|
|
|
|
|
Current.gameObject.SetActive(true);
|
2026-06-21 19:23:54 +01:00
|
|
|
Current.targetGraphic.color =
|
2026-06-25 18:09:33 +01:00
|
|
|
IsCurrentEntity(entity)
|
2026-06-21 19:23:54 +01:00
|
|
|
? ToggleEnabledColor
|
|
|
|
|
: ToggleDisabledColor;
|
2021-01-17 02:45:52 +00:00
|
|
|
}
|
|
|
|
|
if (Thermal)
|
|
|
|
|
{
|
|
|
|
|
Thermal.gameObject.SetActive(true);
|
|
|
|
|
Thermal.targetGraphic.color = thermal ? ToggleEnabledColor : ToggleDisabledColor;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-14 03:43:37 +01:00
|
|
|
Grid.constraintCount = _displayedHullShape.Width;
|
|
|
|
|
foreach (var v in _displayedHullShape.AllCoordinates)
|
2020-12-20 03:04:52 +00:00
|
|
|
{
|
2026-06-14 03:43:37 +01:00
|
|
|
if (!_displayedHullShape[v])
|
2020-12-20 03:04:52 +00:00
|
|
|
{
|
|
|
|
|
var empty = new GameObject("Empty Node", typeof(RectTransform));
|
|
|
|
|
empty.transform.SetParent(Grid.transform);
|
2020-12-27 22:18:22 +00:00
|
|
|
if (!_firstRect)
|
|
|
|
|
_firstRect = empty.GetComponent<RectTransform>();
|
2020-12-20 03:04:52 +00:00
|
|
|
EmptyCells.Add(empty);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var cell = NodePrototype.Instantiate<InventoryCell>();
|
2021-01-12 00:54:29 +00:00
|
|
|
cell.Background.color = CellBackgroundColor;
|
2020-12-27 22:18:22 +00:00
|
|
|
if (!_firstRect)
|
|
|
|
|
_firstRect = cell.GetComponent<RectTransform>();
|
2021-01-17 02:45:52 +00:00
|
|
|
if (!thermal)
|
|
|
|
|
{
|
2021-01-28 02:42:39 +00:00
|
|
|
if(cell.PointerClickTrigger)
|
|
|
|
|
{
|
|
|
|
|
cell.PointerClickTrigger.OnPointerClickAsObservable()
|
2021-04-10 01:47:16 +01:00
|
|
|
.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));
|
|
|
|
|
});
|
2021-01-28 02:42:39 +00:00
|
|
|
cell.BeginDragTrigger.OnBeginDragAsObservable()
|
2021-04-21 01:49:07 +01:00
|
|
|
.Subscribe(data =>
|
|
|
|
|
{
|
|
|
|
|
//Debug.Log("Entity Drag Start");
|
|
|
|
|
var item = entity.GearOccupancy[v.x, v.y];
|
|
|
|
|
if (item != null)
|
|
|
|
|
{
|
2026-06-14 03:43:37 +01:00
|
|
|
var originalOccupancy = _displayedHullShape.Inset(GetItemShape(item.EquippableItem), item.Position, item.EquippableItem.Rotation);
|
2021-04-21 01:49:07 +01:00
|
|
|
_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;
|
2026-06-25 18:09:33 +01:00
|
|
|
DragSession.Begin(new EquippedItemDragObject(item, entity, item.Position - v));
|
2022-07-10 02:53:09 +01:00
|
|
|
//AkSoundEngine.PostEvent("Pickup", gameObject);
|
|
|
|
|
// TODO: SFX: Pickup Item
|
2021-04-21 01:49:07 +01:00
|
|
|
}
|
|
|
|
|
});
|
2021-01-28 02:42:39 +00:00
|
|
|
cell.DragTrigger.OnDragAsObservable()
|
2021-04-21 01:49:07 +01:00
|
|
|
.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);
|
|
|
|
|
});
|
2021-01-28 02:42:39 +00:00
|
|
|
cell.EndDragTrigger.OnEndDragAsObservable()
|
2021-04-21 01:49:07 +01:00
|
|
|
.Subscribe(data =>
|
|
|
|
|
{
|
|
|
|
|
//Debug.Log("Entity Drag End");
|
|
|
|
|
IgnoreOccupancy = null;
|
2026-06-25 18:09:33 +01:00
|
|
|
var hadDragTarget = DragSession.End();
|
2026-06-21 14:12:06 +01:00
|
|
|
if (!hadDragTarget)
|
2021-04-21 01:49:07 +01:00
|
|
|
entity.GearOccupancy[v.x, v.y].EquippableItem.Rotation = _originalRotation;
|
|
|
|
|
foreach(var dragObject in _dragCells)
|
|
|
|
|
Destroy(dragObject.gameObject);
|
|
|
|
|
_dragCells = null;
|
|
|
|
|
RefreshCells();
|
|
|
|
|
});
|
2021-01-28 02:42:39 +00:00
|
|
|
cell.PointerEnterTrigger.OnPointerEnterAsObservable()
|
2021-04-21 01:49:07 +01:00
|
|
|
.Subscribe(data =>
|
|
|
|
|
{
|
|
|
|
|
//Debug.Log("Entity Pointer Enter");
|
2026-06-25 18:09:33 +01:00
|
|
|
if (!DragSession.TryGetDraggedItem(out var itemDragObject)) return;
|
2021-04-21 01:49:07 +01:00
|
|
|
var item = itemDragObject.Item;
|
|
|
|
|
if (!(item is EquippableItem equippableItem)) return;
|
|
|
|
|
var placementPosition = v + itemDragObject.OriginCellOffset;
|
2026-06-21 09:37:54 +01:00
|
|
|
//foreach (var cell in _dragCells) cell.gameObject.SetActive(false);
|
|
|
|
|
FakeItem = item;
|
|
|
|
|
FakeOccupancy = _displayedHullShape.Inset(GetItemShape(equippableItem), placementPosition, item.Rotation);
|
|
|
|
|
RefreshCells();
|
2026-06-25 18:09:33 +01:00
|
|
|
DragSession.RegisterTarget(drag =>
|
2021-04-21 01:49:07 +01:00
|
|
|
{
|
2026-06-21 09:37:54 +01:00
|
|
|
//Debug.Log("Entity Drag Callback");
|
|
|
|
|
FakeOccupancy = null;
|
2026-06-21 14:12:06 +01:00
|
|
|
RequestDraggedItemToEntity(drag, entity, placementPosition);
|
2026-06-21 09:37:54 +01:00
|
|
|
// TODO: SFX: Equip
|
|
|
|
|
});
|
2021-04-21 01:49:07 +01:00
|
|
|
});
|
2021-01-28 02:42:39 +00:00
|
|
|
cell.PointerExitTrigger.OnPointerExitAsObservable()
|
2021-04-21 01:49:07 +01:00
|
|
|
.Subscribe(data =>
|
|
|
|
|
{
|
2026-06-25 18:09:33 +01:00
|
|
|
if (!DragSession.TryGetDraggedItem(out _)) return;
|
2021-04-21 01:49:07 +01:00
|
|
|
FakeItem = null;
|
|
|
|
|
FakeOccupancy = null;
|
|
|
|
|
RefreshCells();
|
2026-06-25 18:09:33 +01:00
|
|
|
DragSession.UnregisterTarget();
|
2021-04-21 01:49:07 +01:00
|
|
|
});
|
2021-01-28 02:42:39 +00:00
|
|
|
}
|
2021-01-17 02:45:52 +00:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
cell.PointerClickTrigger.OnPointerClickAsObservable().Subscribe(data =>
|
|
|
|
|
{
|
|
|
|
|
var rect = cell.GetComponent<RectTransform>();
|
|
|
|
|
var point = Rect.PointToNormalized(rect.rect, rect.InverseTransformPoint(data.position));
|
2021-02-26 19:01:47 +00:00
|
|
|
// Debug.Log($"Clicked at pos {data.position}, normalized {point}");
|
2026-06-14 03:43:37 +01:00
|
|
|
if (_displayedHullShape[int2(v.x - 1, v.y)] && point.x < ThermalToggleRegionSize)
|
2021-01-17 02:45:52 +00:00
|
|
|
{
|
2026-06-25 18:09:33 +01:00
|
|
|
RequestHullConductivityToggle(entity, int2(v.x - 1, v.y), 0);
|
2021-01-17 02:45:52 +00:00
|
|
|
}
|
2026-06-14 03:43:37 +01:00
|
|
|
if (_displayedHullShape[int2(v.x + 1, v.y)] && point.x > 1 - ThermalToggleRegionSize)
|
2021-01-17 02:45:52 +00:00
|
|
|
{
|
2026-06-25 18:09:33 +01:00
|
|
|
RequestHullConductivityToggle(entity, int2(v.x, v.y), 0);
|
2021-01-17 02:45:52 +00:00
|
|
|
}
|
2026-06-14 03:43:37 +01:00
|
|
|
if (_displayedHullShape[int2(v.x, v.y - 1)] && point.y < ThermalToggleRegionSize)
|
2021-01-17 02:45:52 +00:00
|
|
|
{
|
2026-06-25 18:09:33 +01:00
|
|
|
RequestHullConductivityToggle(entity, int2(v.x, v.y - 1), 1);
|
2021-01-17 02:45:52 +00:00
|
|
|
}
|
2026-06-14 03:43:37 +01:00
|
|
|
if (_displayedHullShape[int2(v.x, v.y + 1)] && point.y > 1 - ThermalToggleRegionSize)
|
2021-01-17 02:45:52 +00:00
|
|
|
{
|
2026-06-25 18:09:33 +01:00
|
|
|
RequestHullConductivityToggle(entity, int2(v.x, v.y), 1);
|
2021-01-17 02:45:52 +00:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-12-20 03:04:52 +00:00
|
|
|
CellInstances.Add(v, cell);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
RefreshCells(CellInstances.Keys);
|
2021-01-08 12:44:10 +00:00
|
|
|
|
|
|
|
|
_subscriptions.Add(entity.ArmorDamage.Subscribe(hit =>
|
|
|
|
|
{
|
2021-01-10 03:59:47 +00:00
|
|
|
var hitCells = new[] { hit.pos };
|
|
|
|
|
StartCoroutine(Pulse(hitCells, HitType.Armor, _hitSequence++));
|
|
|
|
|
RefreshCells(hitCells);
|
2021-01-08 12:44:10 +00:00
|
|
|
}));
|
|
|
|
|
|
2021-04-13 01:50:42 +01:00
|
|
|
_subscriptions.Add(entity.ItemDamage.Where(hit=>hit.damage > HitDamageThreshold).Subscribe(hit =>
|
2021-01-08 12:44:10 +00:00
|
|
|
{
|
2021-04-13 01:50:42 +01:00
|
|
|
var hitCells = hit.item.InsetShape.Coordinates;
|
|
|
|
|
if(gameObject.activeInHierarchy)
|
|
|
|
|
StartCoroutine(Pulse(hitCells, HitType.Armor, _hitSequence++));
|
2021-01-10 03:59:47 +00:00
|
|
|
RefreshCells(hitCells);
|
2021-01-08 12:44:10 +00:00
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-10 03:59:47 +00:00
|
|
|
private IEnumerator Pulse(int2[] cells, HitType hitType, int sequence)
|
2021-01-08 12:44:10 +00:00
|
|
|
{
|
2021-01-10 03:59:47 +00:00
|
|
|
foreach (var cell in cells)
|
2021-01-08 12:44:10 +00:00
|
|
|
{
|
2021-01-10 03:59:47 +00:00
|
|
|
if(_cellAnimationSequence.ContainsKey(cell))
|
|
|
|
|
_cellAnimationSequence[cell] = max(_cellAnimationSequence[cell], sequence);
|
|
|
|
|
else
|
|
|
|
|
_cellAnimationSequence[cell] = sequence;
|
2021-01-08 12:44:10 +00:00
|
|
|
}
|
2021-01-10 03:59:47 +00:00
|
|
|
var hitColor = hitType switch
|
|
|
|
|
{
|
|
|
|
|
HitType.Armor => Settings.ArmorHitColor,
|
|
|
|
|
HitType.Hardpoint => Settings.HardpointHitColor,
|
|
|
|
|
HitType.Gear => Settings.GearHitColor,
|
|
|
|
|
_ => Color.white
|
|
|
|
|
};
|
2021-01-08 12:44:10 +00:00
|
|
|
float startTime = Time.time;
|
2021-01-10 03:59:47 +00:00
|
|
|
while (Time.time - startTime < CellHitPulseTime)
|
2021-01-08 12:44:10 +00:00
|
|
|
{
|
2021-01-10 03:59:47 +00:00
|
|
|
var lerp = (Time.time - startTime) / CellHitPulseTime;
|
|
|
|
|
foreach(var cell in cells)
|
|
|
|
|
if(_cellAnimationSequence[cell] == sequence)
|
2021-02-01 08:13:14 +00:00
|
|
|
if(CellInstances.ContainsKey(cell))
|
|
|
|
|
CellInstances[cell].Background.color = Color.Lerp(hitColor, CellBackgroundColor, lerp);
|
2021-01-08 12:44:10 +00:00
|
|
|
yield return null;
|
|
|
|
|
}
|
2021-01-10 03:59:47 +00:00
|
|
|
foreach (var cell in cells)
|
2021-02-01 08:13:14 +00:00
|
|
|
if(CellInstances.ContainsKey(cell))
|
|
|
|
|
CellInstances[cell].Background.color = CellBackgroundColor;
|
2020-12-14 02:56:35 +00:00
|
|
|
}
|
|
|
|
|
|
2020-12-20 03:04:52 +00:00
|
|
|
public void Display(EquippedCargoBay cargo)
|
2020-12-14 02:56:35 +00:00
|
|
|
{
|
2020-12-27 22:18:22 +00:00
|
|
|
Clear();
|
2020-12-20 03:04:52 +00:00
|
|
|
|
2021-04-21 01:49:07 +01:00
|
|
|
_subscriptions.Add(cargo.Cargo.ObserveAdd().Subscribe(_ => RefreshCells()));
|
|
|
|
|
_subscriptions.Add(cargo.Cargo.ObserveRemove().Subscribe(_ => RefreshCells()));
|
|
|
|
|
|
2020-12-20 03:04:52 +00:00
|
|
|
_displayedCargo = cargo;
|
2021-01-17 02:45:52 +00:00
|
|
|
_displayedEntity = null;
|
2026-06-25 18:09:33 +01:00
|
|
|
_displayedEntityKey = "";
|
|
|
|
|
if (TryResolveCargoBay(cargo, out var displayedCargoEntityKey, out var displayedCargoIndex))
|
|
|
|
|
{
|
|
|
|
|
_displayedCargoEntityKey = displayedCargoEntityKey;
|
|
|
|
|
_displayedCargoIndex = displayedCargoIndex;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_displayedCargoEntityKey = "";
|
|
|
|
|
_displayedCargoIndex = -1;
|
|
|
|
|
}
|
2020-12-19 02:37:13 +00:00
|
|
|
|
2020-12-27 22:18:22 +00:00
|
|
|
if(Title)
|
|
|
|
|
Title.text = cargo.Name;
|
2020-12-19 02:37:13 +00:00
|
|
|
|
2026-06-14 04:47:02 +01:00
|
|
|
// FakeOccupancy = new Shape(cargo.InteriorShape.Width, cargo.InteriorShape.Height);
|
|
|
|
|
// IgnoreOccupancy = new Shape(cargo.InteriorShape.Width, cargo.InteriorShape.Height);
|
|
|
|
|
Grid.constraintCount = cargo.InteriorShape.Width;
|
|
|
|
|
foreach (var v in cargo.InteriorShape.AllCoordinates)
|
2020-12-20 03:04:52 +00:00
|
|
|
{
|
2026-06-14 04:47:02 +01:00
|
|
|
if (!cargo.InteriorShape[v])
|
2020-12-20 03:04:52 +00:00
|
|
|
{
|
|
|
|
|
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()
|
2021-04-10 01:47:16 +01:00
|
|
|
.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));
|
|
|
|
|
});
|
2020-12-20 03:04:52 +00:00
|
|
|
cell.BeginDragTrigger.OnBeginDragAsObservable()
|
2021-04-21 01:49:07 +01:00
|
|
|
.Subscribe(data =>
|
|
|
|
|
{
|
|
|
|
|
//Debug.Log("Inventory Drag Start");
|
|
|
|
|
var item = cargo.Occupancy[v.x, v.y];
|
|
|
|
|
if (item != null)
|
|
|
|
|
{
|
|
|
|
|
var itemPosition = cargo.Cargo[item];
|
2026-06-14 04:47:02 +01:00
|
|
|
var originalOccupancy = cargo.InteriorShape.Inset(GetItemShape(item), itemPosition, item.Rotation);
|
2021-04-21 01:49:07 +01:00
|
|
|
_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;
|
2026-06-25 18:09:33 +01:00
|
|
|
DragSession.Begin(new ItemInstanceDragObject(item, cargo, cargo.Cargo[item] - v));
|
2022-07-10 02:53:09 +01:00
|
|
|
// TODO: SFX: Pickup
|
2021-04-21 01:49:07 +01:00
|
|
|
}
|
|
|
|
|
});
|
2020-12-20 03:04:52 +00:00
|
|
|
cell.DragTrigger.OnDragAsObservable()
|
2021-04-21 01:49:07 +01:00
|
|
|
.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);
|
|
|
|
|
});
|
2020-12-20 03:04:52 +00:00
|
|
|
cell.EndDragTrigger.OnEndDragAsObservable()
|
2021-04-21 01:49:07 +01:00
|
|
|
.Subscribe(data =>
|
|
|
|
|
{
|
|
|
|
|
//Debug.Log("Inventory Drag End");
|
|
|
|
|
IgnoreOccupancy = null;
|
2026-06-25 18:09:33 +01:00
|
|
|
DragSession.End();
|
2021-04-21 01:49:07 +01:00
|
|
|
foreach(var dragObject in _dragCells)
|
|
|
|
|
Destroy(dragObject.gameObject);
|
|
|
|
|
_dragCells = null;
|
2025-07-18 00:21:33 +01:00
|
|
|
RefreshCells();
|
2021-04-21 01:49:07 +01:00
|
|
|
});
|
2020-12-20 03:04:52 +00:00
|
|
|
cell.PointerEnterTrigger.OnPointerEnterAsObservable()
|
2021-04-21 01:49:07 +01:00
|
|
|
.Subscribe(data =>
|
|
|
|
|
{
|
|
|
|
|
//Debug.Log("Inventory Pointer Enter");
|
2026-06-25 18:09:33 +01:00
|
|
|
if (!DragSession.TryGetDraggedItem(out var itemDragObject)) return;
|
2021-04-21 01:49:07 +01:00
|
|
|
var item = itemDragObject.Item;
|
|
|
|
|
var placementPosition = v + itemDragObject.OriginCellOffset;
|
2026-06-21 09:37:54 +01:00
|
|
|
//foreach (var cell in _dragCells) cell.gameObject.SetActive(false);
|
|
|
|
|
FakeItem = item;
|
|
|
|
|
FakeOccupancy = cargo.InteriorShape.Inset(GetItemShape(item), placementPosition, item.Rotation);
|
|
|
|
|
RefreshCells();
|
2026-06-25 18:09:33 +01:00
|
|
|
DragSession.RegisterTarget(drag =>
|
2021-04-21 01:49:07 +01:00
|
|
|
{
|
2026-06-21 09:37:54 +01:00
|
|
|
//Debug.Log("Inventory Drag Callback");
|
|
|
|
|
FakeOccupancy = null;
|
2026-06-21 14:12:06 +01:00
|
|
|
RequestDraggedItemToCargo(drag, cargo, placementPosition);
|
2026-06-21 09:37:54 +01:00
|
|
|
// TODO: SFX: Drop
|
|
|
|
|
});
|
2021-04-21 01:49:07 +01:00
|
|
|
});
|
2020-12-20 03:04:52 +00:00
|
|
|
cell.PointerExitTrigger.OnPointerExitAsObservable()
|
2021-04-21 01:49:07 +01:00
|
|
|
.Subscribe(data =>
|
|
|
|
|
{
|
2026-06-25 18:09:33 +01:00
|
|
|
if (!DragSession.TryGetDraggedItem(out _)) return;
|
2021-04-21 01:49:07 +01:00
|
|
|
FakeItem = null;
|
|
|
|
|
FakeOccupancy = null;
|
|
|
|
|
RefreshCells();
|
2026-06-25 18:09:33 +01:00
|
|
|
DragSession.UnregisterTarget();
|
2021-04-21 01:49:07 +01:00
|
|
|
});
|
|
|
|
|
|
2020-12-20 03:04:52 +00:00
|
|
|
CellInstances.Add(v, cell);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
RefreshCells();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void RefreshCells()
|
|
|
|
|
{
|
|
|
|
|
RefreshCells(CellInstances.Keys);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void RefreshCells(IEnumerable<int2> cells)
|
|
|
|
|
{
|
|
|
|
|
if (_displayedEntity != null)
|
|
|
|
|
{
|
|
|
|
|
foreach (var v in cells)
|
|
|
|
|
{
|
|
|
|
|
if(!CellInstances.ContainsKey(v)) continue;
|
|
|
|
|
|
|
|
|
|
var spriteIndex = 0;
|
2026-06-14 03:43:37 +01:00
|
|
|
var interior = _displayedHullInterior[v];
|
2021-04-21 01:49:07 +01:00
|
|
|
var item = FakeOccupancy?[v]??false ? FakeItem : IgnoreOccupancy?[v]??false ? null : _displayedEntity.GearOccupancy[v.x, v.y]?.EquippableItem;
|
2020-12-20 03:04:52 +00:00
|
|
|
var hardpoint = _displayedEntity.Hardpoints[v.x, v.y];
|
|
|
|
|
|
|
|
|
|
bool HardpointMatch(int2 offset)
|
|
|
|
|
{
|
2021-01-11 02:56:37 +00:00
|
|
|
var v2 = v + offset * (Flip ? -1 : 1);
|
2020-12-20 03:04:52 +00:00
|
|
|
return !(
|
2026-06-14 03:43:37 +01:00
|
|
|
_displayedHullShape[v2] &&
|
2020-12-20 03:04:52 +00:00
|
|
|
_displayedEntity.Hardpoints[v2.x, v2.y] == hardpoint &&
|
2021-04-21 01:49:07 +01:00
|
|
|
(FakeOccupancy?[v2]??false ? FakeItem : IgnoreOccupancy?[v2]??false ? null : _displayedEntity.GearOccupancy[v2.x, v2.y]?.EquippableItem) == item
|
2020-12-20 03:04:52 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool NoHardpointMatch(int2 offset)
|
|
|
|
|
{
|
2021-01-11 02:56:37 +00:00
|
|
|
var v2 = v + offset * (Flip ? -1 : 1);
|
2020-12-20 03:04:52 +00:00
|
|
|
return !(
|
2026-06-14 03:43:37 +01:00
|
|
|
_displayedHullShape[v2] && (
|
|
|
|
|
!interior && !_displayedHullInterior[v2] && _displayedEntity.Hardpoints[v2.x, v2.y] == null ||
|
2020-12-20 03:04:52 +00:00
|
|
|
interior && item != null &&
|
2021-04-21 01:49:07 +01:00
|
|
|
(FakeOccupancy?[v2]??false ? FakeItem : IgnoreOccupancy?[v2]??false ? null : _displayedEntity.GearOccupancy[v2.x, v2.y]?.EquippableItem) == item
|
2020-12-20 03:04:52 +00:00
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (hardpoint != null)
|
2020-12-14 02:56:35 +00:00
|
|
|
{
|
|
|
|
|
for(int i = 0; i < 8; i++)
|
2020-12-20 03:04:52 +00:00
|
|
|
if (HardpointMatch(_offsets[i]))
|
2020-12-14 02:56:35 +00:00
|
|
|
spriteIndex += 1 << i;
|
2020-12-20 03:04:52 +00:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
for(int i = 0; i < 8; i++)
|
|
|
|
|
if (NoHardpointMatch(_offsets[i]))
|
|
|
|
|
spriteIndex += 1 << i;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-27 22:18:22 +00:00
|
|
|
var bgSpriteIndex = spriteIndex;
|
|
|
|
|
|
2020-12-20 03:04:52 +00:00
|
|
|
if (item != null)
|
|
|
|
|
spriteIndex += 1 << 8;
|
2021-01-17 02:45:52 +00:00
|
|
|
|
|
|
|
|
if (_thermal)
|
|
|
|
|
{
|
|
|
|
|
bool ThermalMatch(int2 offset)
|
|
|
|
|
{
|
2026-06-14 03:43:37 +01:00
|
|
|
if (!_displayedHullShape[v + offset]) return false;
|
2021-01-17 02:45:52 +00:00
|
|
|
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;
|
|
|
|
|
}
|
2020-12-20 03:04:52 +00:00
|
|
|
|
2020-12-27 22:18:22 +00:00
|
|
|
CellInstances[v].Background.sprite = NodeBackgroundTextures[bgSpriteIndex];
|
2021-01-17 02:45:52 +00:00
|
|
|
CellInstances[v].Icon.sprite = _thermal ? ThermalTextures[spriteIndex] : NodeTextures[spriteIndex];
|
2020-12-27 22:18:22 +00:00
|
|
|
CellInstances[v].Icon.color = GetColor(v);
|
2020-12-20 03:04:52 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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];
|
|
|
|
|
|
|
|
|
|
bool ItemMatch(int2 offset)
|
|
|
|
|
{
|
|
|
|
|
var v2 = v + offset;
|
2026-06-14 04:47:02 +01:00
|
|
|
return !_displayedCargo.InteriorShape[v2] || item == null ||
|
2020-12-20 03:04:52 +00:00
|
|
|
(FakeOccupancy?[v2]??false ? FakeItem : IgnoreOccupancy?[v2]??false ? null : _displayedCargo.Occupancy[v2.x, v2.y]) != item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < 8; i++)
|
|
|
|
|
if (ItemMatch(_offsets[i]))
|
|
|
|
|
spriteIndex += 1 << i;
|
2020-12-27 22:18:22 +00:00
|
|
|
|
|
|
|
|
var bgSpriteIndex = spriteIndex;
|
2020-12-20 03:04:52 +00:00
|
|
|
|
|
|
|
|
if (item != null)
|
|
|
|
|
spriteIndex += 1 << 8;
|
|
|
|
|
|
2020-12-27 22:18:22 +00:00
|
|
|
CellInstances[v].Background.sprite = NodeBackgroundTextures[bgSpriteIndex];
|
|
|
|
|
CellInstances[v].Icon.sprite = NodeTextures[spriteIndex];
|
|
|
|
|
CellInstances[v].Icon.color = GetColor(v);
|
2020-12-20 03:04:52 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Color GetColor(int2 position, bool highlight = false)
|
|
|
|
|
{
|
|
|
|
|
if (_displayedEntity != null)
|
|
|
|
|
{
|
2026-06-14 03:43:37 +01:00
|
|
|
var interior = _displayedHullInterior[position];
|
2020-12-20 03:04:52 +00:00
|
|
|
var hardpoint = _displayedEntity.Hardpoints[position.x, position.y];
|
2021-04-21 01:49:07 +01:00
|
|
|
var item = (FakeOccupancy?[position] ?? false ? FakeItem :
|
|
|
|
|
IgnoreOccupancy?[position] ?? false ? null : _displayedEntity.GearOccupancy[position.x, position.y]?.EquippableItem) as EquippableItem;
|
2021-01-10 03:59:47 +00:00
|
|
|
|
|
|
|
|
if (_hud)
|
|
|
|
|
{
|
2021-02-01 08:13:14 +00:00
|
|
|
if (_displayedEntity.Armor[position.x, position.y] > .01f)
|
|
|
|
|
return Settings.ArmorGradient.Evaluate(_displayedEntity.Armor[position.x, position.y] / _displayedEntity.MaxArmor[position.x, position.y]);
|
2021-01-10 03:59:47 +00:00
|
|
|
|
2026-06-14 00:58:13 +01:00
|
|
|
if (item != null) return Settings.DurabilityGradient.Evaluate(item.Durability / GetMaxDurability(item));
|
2021-02-01 08:13:14 +00:00
|
|
|
return float3(.25f).ToColor();
|
2021-01-10 03:59:47 +00:00
|
|
|
}
|
2020-12-20 03:04:52 +00:00
|
|
|
|
|
|
|
|
if (hardpoint == null)
|
|
|
|
|
{
|
|
|
|
|
if(!interior)
|
|
|
|
|
return Color.white;
|
|
|
|
|
|
|
|
|
|
if (item == null)
|
|
|
|
|
{
|
2021-02-01 08:13:14 +00:00
|
|
|
return float3(.25f).ToColor();
|
2020-12-20 03:04:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(highlight)
|
|
|
|
|
return Color.white;
|
|
|
|
|
|
2021-02-01 08:13:14 +00:00
|
|
|
return float3(.5f).ToColor();
|
2020-12-20 03:04:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var tint = hardpoint.TintColor;
|
|
|
|
|
|
|
|
|
|
if (!highlight || item == null)
|
|
|
|
|
tint *= .7071f;
|
|
|
|
|
|
|
|
|
|
return tint.ToColor();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
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);
|
2026-06-28 12:16:19 +01:00
|
|
|
if (FindTypedInventoryItem(item)?.TryGetHardpointType(out HardpointType typedHardpoint) == true)
|
2026-06-21 09:37:54 +01:00
|
|
|
c = HardpointData.GetColor(typedHardpoint).ToUnityFloat3();
|
2020-12-20 03:04:52 +00:00
|
|
|
|
|
|
|
|
if(!highlight)
|
|
|
|
|
c *= .7071f;
|
|
|
|
|
|
|
|
|
|
return c.ToColor();
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-12-14 02:56:35 +00:00
|
|
|
|
2026-06-14 00:58:13 +01:00
|
|
|
private float GetMaxDurability(ItemInstance item)
|
|
|
|
|
{
|
|
|
|
|
var typedItem = FindTypedInventoryItem(item);
|
|
|
|
|
if (typedItem != null && typedItem.Durability > 0)
|
|
|
|
|
return (float)typedItem.Durability;
|
|
|
|
|
|
2026-06-14 20:40:13 +01:00
|
|
|
return item is EquippableItem equippable ? Math.Max(equippable.Durability, 1f) : 1f;
|
2026-06-14 00:58:13 +01:00
|
|
|
}
|
|
|
|
|
|
2026-06-14 01:03:52 +01:00
|
|
|
private Shape GetItemShape(ItemInstance item)
|
|
|
|
|
{
|
|
|
|
|
var typedItem = FindTypedInventoryItem(item);
|
|
|
|
|
if (typedItem != null && typedItem.ShapeCells.Count > 0)
|
2026-06-14 03:43:37 +01:00
|
|
|
return ToShape(typedItem.ShapeWidth, typedItem.ShapeHeight, typedItem.ShapeCells);
|
2026-06-14 01:03:52 +01:00
|
|
|
|
2026-06-14 01:54:28 +01:00
|
|
|
return new Shape(1, 1);
|
2026-06-14 01:03:52 +01:00
|
|
|
}
|
|
|
|
|
|
2026-06-25 18:09:33 +01:00
|
|
|
private bool TryResolveTypedHullGeometry(ItemInstance hull, out Shape hullShape, out Shape interiorShape)
|
2026-06-14 03:43:37 +01:00
|
|
|
{
|
|
|
|
|
hullShape = null;
|
|
|
|
|
interiorShape = null;
|
|
|
|
|
var typedHull = FindTypedInventoryItem(hull);
|
|
|
|
|
if (typedHull == null ||
|
|
|
|
|
typedHull.ShapeCells.Count == 0 ||
|
|
|
|
|
typedHull.InteriorShapeCells.Count == 0 ||
|
|
|
|
|
typedHull.ShapeWidth <= 0 ||
|
|
|
|
|
typedHull.ShapeHeight <= 0 ||
|
|
|
|
|
typedHull.InteriorShapeWidth <= 0 ||
|
|
|
|
|
typedHull.InteriorShapeHeight <= 0)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hullShape = ToShape(typedHull.ShapeWidth, typedHull.ShapeHeight, typedHull.ShapeCells);
|
|
|
|
|
interiorShape = ToShape(typedHull.InteriorShapeWidth, typedHull.InteriorShapeHeight, typedHull.InteriorShapeCells);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Shape ToShape(int width, int height, IReadOnlyList<AetheriaRuntimeShapeCell> cells)
|
2026-06-14 01:03:52 +01:00
|
|
|
{
|
2026-06-14 03:43:37 +01:00
|
|
|
var shape = new Shape(Math.Max(width, 1), Math.Max(height, 1));
|
|
|
|
|
foreach (var cell in cells)
|
|
|
|
|
{
|
|
|
|
|
if (cell.X >= 0 && cell.Y >= 0 && cell.X < shape.Width && cell.Y < shape.Height)
|
|
|
|
|
shape[int2(cell.X, cell.Y)] = true;
|
|
|
|
|
}
|
2026-06-14 01:03:52 +01:00
|
|
|
|
|
|
|
|
return shape;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-25 18:09:33 +01:00
|
|
|
private AetheriaRuntimeCatalogItem FindTypedInventoryItem(ItemInstance item)
|
|
|
|
|
{
|
2026-06-28 12:21:38 +01:00
|
|
|
return ResolveCatalog()?.FindItem(item, x => x.ItemKey);
|
2026-06-25 18:09:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RequestDockedCurrentShip(Ship ship)
|
|
|
|
|
{
|
|
|
|
|
if (ship == null || !TryResolveEntityRecordKey(ship, out var targetEntityKey))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
TrySubmitOperation(
|
|
|
|
|
operations => operations.SetDockedCurrentShip(targetEntityKey),
|
|
|
|
|
"docked current ship");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RequestEntityName(Entity entity, string name)
|
|
|
|
|
{
|
|
|
|
|
if (entity == null || !TryResolveEntityRecordKey(entity, out var targetEntityKey))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
TrySubmitOperation(
|
|
|
|
|
operations => operations.SetEntityName(targetEntityKey, name ?? ""),
|
|
|
|
|
"entity name");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RequestRuntimeLoadoutRestore(AetheriaRuntimeStationLoadoutRestoreOption loadout)
|
|
|
|
|
{
|
|
|
|
|
if (loadout == null ||
|
|
|
|
|
!loadout.CanRestore ||
|
|
|
|
|
string.IsNullOrWhiteSpace(loadout.TargetEntityKey) ||
|
|
|
|
|
string.IsNullOrWhiteSpace(loadout.TemplateName))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TrySubmitOperation(
|
|
|
|
|
operations => operations.RestoreLoadout(loadout.TargetEntityKey, loadout.TemplateName, loadout.Price),
|
|
|
|
|
"loadout restore");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RequestLoadoutTemplateSave(Entity entity)
|
|
|
|
|
{
|
|
|
|
|
if (entity == null || !TryResolveEntityRecordKey(entity, out var targetEntityKey))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2026-06-28 05:32:06 +01:00
|
|
|
var client = ResolveClient();
|
2026-06-29 04:02:45 +01:00
|
|
|
var loadout = CreateLoadoutTemplate(targetEntityKey);
|
2026-06-25 18:09:33 +01:00
|
|
|
if (loadout?.RootEntity == null || string.IsNullOrWhiteSpace(loadout.RootEntity.Hull?.ItemKey ?? ""))
|
|
|
|
|
return;
|
|
|
|
|
|
2026-06-28 05:32:06 +01:00
|
|
|
var submitted = client
|
2026-06-25 18:09:33 +01:00
|
|
|
.Ui.SaveLoadoutTemplateAsync(loadout, "unity-inventory")
|
|
|
|
|
.GetAwaiter()
|
|
|
|
|
.GetResult();
|
|
|
|
|
|
|
|
|
|
Debug.Log($"Submitted Aetheria loadout template save Eve operation: {submitted.OperationId}");
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogError($"Failed to send Aetheria loadout template save Eve command: {ex}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RequestHullConductivityToggle(Entity entity, int2 position, int axis)
|
|
|
|
|
{
|
|
|
|
|
if (entity == null || !TryResolveEntityRecordKey(entity, out var targetEntityKey))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
TrySubmitOperation(
|
|
|
|
|
operations => operations.ToggleHullConductivity(targetEntityKey, position.x, position.y, axis),
|
|
|
|
|
"hull conductivity");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool TryResolveEntityRecordKey(Entity entity, out string recordKey)
|
|
|
|
|
{
|
|
|
|
|
recordKey = "";
|
2026-06-28 03:34:08 +01:00
|
|
|
return _observedEntityIndex != null &&
|
|
|
|
|
_observedEntityIndex.TryResolveEntityRecordKey(entity, out recordKey);
|
2026-06-25 18:09:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool IsCurrentEntity(Entity entity)
|
|
|
|
|
{
|
|
|
|
|
if (entity == null ||
|
|
|
|
|
!TryResolveEntityRecordKey(entity, out var entityKey) ||
|
|
|
|
|
string.IsNullOrWhiteSpace(entityKey) ||
|
|
|
|
|
!TryResolveCurrentEntityKey(out var currentEntityKey))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return string.Equals(entityKey, currentEntityKey, StringComparison.Ordinal);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool TryResolveCurrentEntityKey(out string currentEntityKey)
|
|
|
|
|
{
|
2026-06-28 15:12:19 +01:00
|
|
|
currentEntityKey = ResolveCurrentEntity()?.EntityKey ?? "";
|
|
|
|
|
return !string.IsNullOrWhiteSpace(currentEntityKey);
|
2026-06-25 18:09:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool TryResolveCurrentDockingBayRow(out AetheriaRuntimeStationDockingBayRow dockingBay)
|
|
|
|
|
{
|
|
|
|
|
dockingBay = null;
|
2026-06-28 15:12:19 +01:00
|
|
|
var refit = ResolveStationRefitDocument();
|
|
|
|
|
if (refit?.IsDocked != true || refit.DockingBayIndex < 0)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
dockingBay = (refit.DockingBays ?? Array.Empty<AetheriaRuntimeStationDockingBayRow>())
|
|
|
|
|
.FirstOrDefault(row => row != null && row.DockingBayIndex == refit.DockingBayIndex);
|
|
|
|
|
return dockingBay != null;
|
2026-06-25 18:09:33 +01:00
|
|
|
}
|
|
|
|
|
|
2026-06-28 03:24:07 +01:00
|
|
|
private bool TryResolveCurrentDockingBay(out EquippedCargoBay dockingBay)
|
2026-06-25 18:09:33 +01:00
|
|
|
{
|
2026-06-28 01:18:21 +01:00
|
|
|
dockingBay = null;
|
2026-06-28 02:04:56 +01:00
|
|
|
if (!TryResolveObservedDockingIndex(out var dockingIndex) ||
|
|
|
|
|
!dockingIndex.TryResolveCurrentDockingBay(out var resolvedDockingBay))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dockingBay = resolvedDockingBay;
|
|
|
|
|
return dockingBay != null;
|
2026-06-25 18:09:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private AetheriaRuntimeStationRefitDocument ResolveStationRefit()
|
|
|
|
|
{
|
2026-06-28 15:12:19 +01:00
|
|
|
return ResolveStationRefitDocument();
|
2026-06-27 16:07:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool TryResolveObservedDockingIndex(out AetheriaUnityObservedDockingIndex dockingIndex)
|
|
|
|
|
{
|
|
|
|
|
dockingIndex = null;
|
2026-06-28 03:34:08 +01:00
|
|
|
if (_observedEntityIndex == null)
|
2026-06-27 16:07:46 +01:00
|
|
|
return false;
|
|
|
|
|
|
2026-06-28 10:07:05 +01:00
|
|
|
dockingIndex = _observedDockingIndex ??= new AetheriaUnityObservedDockingIndex(ResolveClient, _observedEntityIndex);
|
2026-06-27 16:07:46 +01:00
|
|
|
return true;
|
2026-06-25 18:09:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool TryResolveStationRefitEntity(string entityKey, int optionIndex, out Entity entity)
|
|
|
|
|
{
|
|
|
|
|
entity = null;
|
|
|
|
|
if (string.IsNullOrWhiteSpace(entityKey) &&
|
|
|
|
|
optionIndex >= 0 &&
|
|
|
|
|
optionIndex < (_dropdownStationRefitEntities?.Length ?? 0))
|
|
|
|
|
{
|
|
|
|
|
entityKey = _dropdownStationRefitEntities[optionIndex]?.EntityKey ?? "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return TryResolveObservedAvailableEntityByKey(entityKey, out entity);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool TryResolveObservedAvailableEntityByKey(string entityKey, out Entity entity)
|
|
|
|
|
{
|
|
|
|
|
entity = null;
|
2026-06-28 03:34:08 +01:00
|
|
|
if (_observedEntityIndex == null || string.IsNullOrWhiteSpace(entityKey))
|
2026-06-25 18:09:33 +01:00
|
|
|
return false;
|
|
|
|
|
|
2026-06-28 03:34:08 +01:00
|
|
|
return _observedEntityIndex.TryResolveEntityByRecordKey(entityKey, out entity);
|
2026-06-25 18:09:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool IsDisplayedEntityKey(string entityKey)
|
|
|
|
|
{
|
|
|
|
|
return _displayedEntity != null &&
|
|
|
|
|
!string.IsNullOrWhiteSpace(_displayedEntityKey) &&
|
|
|
|
|
string.Equals(_displayedEntityKey, entityKey, StringComparison.Ordinal);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool IsDisplayedCargoBayKey(string entityKey, int cargoBayIndex)
|
|
|
|
|
{
|
|
|
|
|
return _displayedCargo != null &&
|
|
|
|
|
!string.IsNullOrWhiteSpace(_displayedCargoEntityKey) &&
|
|
|
|
|
string.Equals(_displayedCargoEntityKey, entityKey, StringComparison.Ordinal) &&
|
|
|
|
|
_displayedCargoIndex == cargoBayIndex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool TryResolveCargoBay(EquippedCargoBay cargoBay, out string entityKey, out int cargoIndex)
|
|
|
|
|
{
|
|
|
|
|
return TryResolveEquippedItem(cargoBay, out entityKey, out cargoIndex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool TryResolveEquippedItem(EquippedItem item, out string entityKey, out int equipmentIndex)
|
2026-06-14 00:54:19 +01:00
|
|
|
{
|
2026-06-25 18:09:33 +01:00
|
|
|
entityKey = "";
|
|
|
|
|
equipmentIndex = -1;
|
|
|
|
|
var entity = item?.Entity;
|
|
|
|
|
if (entity?.Equipment == null ||
|
|
|
|
|
!TryResolveEntityRecordKey(entity, out entityKey))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
equipmentIndex = entity.Equipment.IndexOf(item);
|
|
|
|
|
return equipmentIndex >= 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool TryResolveTypedInventoryRows(
|
|
|
|
|
string entityKey,
|
|
|
|
|
out IReadOnlyList<AetheriaRuntimeRtsInventoryItem> equipment,
|
|
|
|
|
out IReadOnlyList<AetheriaRuntimeRtsInventoryItem> cargo)
|
|
|
|
|
{
|
|
|
|
|
equipment = Array.Empty<AetheriaRuntimeRtsInventoryItem>();
|
|
|
|
|
cargo = Array.Empty<AetheriaRuntimeRtsInventoryItem>();
|
|
|
|
|
if (string.IsNullOrWhiteSpace(entityKey))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2026-06-28 09:48:19 +01:00
|
|
|
var current = ResolveCurrentEntity();
|
2026-06-25 18:09:33 +01:00
|
|
|
if (current != null && string.Equals(current.EntityKey, entityKey, StringComparison.Ordinal))
|
|
|
|
|
{
|
|
|
|
|
equipment = current.Equipment ?? Array.Empty<AetheriaRuntimeRtsInventoryItem>();
|
|
|
|
|
cargo = current.Cargo ?? Array.Empty<AetheriaRuntimeRtsInventoryItem>();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 14:25:08 +01:00
|
|
|
var refit = ResolveStationRefitDocument();
|
2026-06-25 18:09:33 +01:00
|
|
|
var entityIndex = -1;
|
|
|
|
|
if (refit != null)
|
|
|
|
|
{
|
|
|
|
|
if (string.Equals(refit.DockParentEntityKey, entityKey, StringComparison.Ordinal))
|
|
|
|
|
entityIndex = refit.DockParentEntityIndex;
|
|
|
|
|
else
|
|
|
|
|
entityIndex = (refit.AvailableEntities ?? Array.Empty<AetheriaRuntimeStationRefitEntityOption>())
|
|
|
|
|
.FirstOrDefault(option => string.Equals(option.EntityKey, entityKey, StringComparison.Ordinal))
|
|
|
|
|
?.EntityIndex ?? -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (entityIndex < 0)
|
|
|
|
|
return false;
|
|
|
|
|
|
2026-06-28 09:48:19 +01:00
|
|
|
var inventory = ResolveInventory(entityIndex);
|
2026-06-25 18:09:33 +01:00
|
|
|
if (inventory == null || !string.Equals(inventory.EntityKey, entityKey, StringComparison.Ordinal))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
equipment = inventory.Equipment ?? Array.Empty<AetheriaRuntimeRtsInventoryItem>();
|
|
|
|
|
cargo = inventory.Cargo ?? Array.Empty<AetheriaRuntimeRtsInventoryItem>();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning($"Failed to validate Aetheria inventory projection for {entityKey}: {ex.Message}");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool TryValidateTypedCargoSlot(
|
|
|
|
|
string entityKey,
|
|
|
|
|
int cargoIndex,
|
|
|
|
|
ItemInstance item,
|
|
|
|
|
int2 originPosition,
|
|
|
|
|
bool hasOriginPosition)
|
|
|
|
|
{
|
|
|
|
|
if (item == null ||
|
|
|
|
|
string.IsNullOrWhiteSpace(item.ItemKey) ||
|
|
|
|
|
!TryResolveTypedInventoryRows(entityKey, out _, out var cargo))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cargo.Any(row =>
|
|
|
|
|
string.Equals(row.Source, "cargo", StringComparison.Ordinal) &&
|
|
|
|
|
row.SourceIndex == cargoIndex &&
|
|
|
|
|
string.Equals(row.ItemKey, item.ItemKey, StringComparison.Ordinal) &&
|
|
|
|
|
(!hasOriginPosition || (row.X == originPosition.x && row.Y == originPosition.y)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool TryValidateTypedEquipmentSlot(
|
|
|
|
|
string entityKey,
|
|
|
|
|
int equipmentIndex,
|
|
|
|
|
EquippedItem item)
|
|
|
|
|
{
|
|
|
|
|
if (item?.EquippableItem == null ||
|
|
|
|
|
string.IsNullOrWhiteSpace(item.EquippableItem.ItemKey) ||
|
|
|
|
|
!TryResolveTypedInventoryRows(entityKey, out var equipment, out _))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return equipment.Any(row =>
|
|
|
|
|
string.Equals(row.Source, "equipment", StringComparison.Ordinal) &&
|
|
|
|
|
row.SourceIndex == equipmentIndex &&
|
|
|
|
|
row.X == item.Position.x &&
|
|
|
|
|
row.Y == item.Position.y &&
|
|
|
|
|
string.Equals(row.ItemKey, item.EquippableItem.ItemKey, StringComparison.Ordinal));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RequestCargoItemTransfer(
|
|
|
|
|
EquippedCargoBay origin,
|
|
|
|
|
EquippedCargoBay destination,
|
|
|
|
|
ItemInstance item,
|
|
|
|
|
int2 destinationPosition,
|
|
|
|
|
bool hasDestinationPosition)
|
|
|
|
|
{
|
|
|
|
|
if (!TryResolveCargoBay(origin, out var originEntityKey, out var originCargoIndex) ||
|
|
|
|
|
!TryResolveCargoBay(destination, out var destinationEntityKey, out var destinationCargoIndex) ||
|
|
|
|
|
item == null ||
|
|
|
|
|
string.IsNullOrWhiteSpace(item.ItemKey) ||
|
|
|
|
|
!origin.Cargo.TryGetValue(item, out var originPosition) ||
|
|
|
|
|
!TryValidateTypedCargoSlot(originEntityKey, originCargoIndex, item, originPosition, true))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var quantity = item is SimpleCommodity commodity ? commodity.Quantity : 1;
|
|
|
|
|
if (quantity <= 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
TrySubmitOperation(
|
|
|
|
|
operations => operations.TransferCargoItem(
|
|
|
|
|
originEntityKey,
|
|
|
|
|
originCargoIndex,
|
|
|
|
|
destinationEntityKey,
|
|
|
|
|
destinationCargoIndex,
|
|
|
|
|
item.ItemKey,
|
|
|
|
|
quantity,
|
|
|
|
|
originPosition.x,
|
|
|
|
|
originPosition.y,
|
|
|
|
|
destinationPosition.x,
|
|
|
|
|
destinationPosition.y,
|
|
|
|
|
hasDestinationPosition),
|
|
|
|
|
"cargo transfer");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RequestCargoItemEquip(
|
|
|
|
|
EquippedCargoBay origin,
|
|
|
|
|
Entity destination,
|
|
|
|
|
EquippableItem item,
|
|
|
|
|
int2 destinationPosition,
|
|
|
|
|
bool hasDestinationPosition)
|
|
|
|
|
{
|
|
|
|
|
if (!TryResolveCargoBay(origin, out var originEntityKey, out var originCargoIndex) ||
|
|
|
|
|
!TryResolveEntityRecordKey(destination, out var destinationEntityKey) ||
|
|
|
|
|
item == null ||
|
|
|
|
|
string.IsNullOrWhiteSpace(item.ItemKey) ||
|
|
|
|
|
!origin.Cargo.TryGetValue(item, out var originPosition) ||
|
|
|
|
|
!TryValidateTypedCargoSlot(originEntityKey, originCargoIndex, item, originPosition, true))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TrySubmitOperation(
|
|
|
|
|
operations => operations.EquipItem(
|
|
|
|
|
"cargo",
|
|
|
|
|
originEntityKey,
|
|
|
|
|
originCargoIndex,
|
|
|
|
|
destinationEntityKey,
|
|
|
|
|
item.ItemKey,
|
|
|
|
|
originPosition.x,
|
|
|
|
|
originPosition.y,
|
|
|
|
|
destinationPosition.x,
|
|
|
|
|
destinationPosition.y,
|
|
|
|
|
hasDestinationPosition),
|
|
|
|
|
"cargo equip");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RequestEquippedItemStore(
|
|
|
|
|
EquippedItem item,
|
|
|
|
|
EquippedCargoBay destination,
|
|
|
|
|
int2 destinationPosition,
|
|
|
|
|
bool hasDestinationPosition)
|
|
|
|
|
{
|
|
|
|
|
if (!TryResolveEquippedItem(item, out var originEntityKey, out var equipmentIndex) ||
|
|
|
|
|
!TryResolveCargoBay(destination, out var destinationEntityKey, out var destinationCargoIndex) ||
|
|
|
|
|
item?.EquippableItem == null ||
|
|
|
|
|
!TryValidateTypedEquipmentSlot(originEntityKey, equipmentIndex, item))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TrySubmitOperation(
|
|
|
|
|
operations => operations.StoreItem(
|
|
|
|
|
originEntityKey,
|
|
|
|
|
equipmentIndex,
|
|
|
|
|
destinationEntityKey,
|
|
|
|
|
destinationCargoIndex,
|
|
|
|
|
item.EquippableItem.ItemKey,
|
|
|
|
|
destinationPosition.x,
|
|
|
|
|
destinationPosition.y,
|
|
|
|
|
hasDestinationPosition),
|
|
|
|
|
"equipment store");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RequestEquippedItemEquip(
|
|
|
|
|
EquippedItem item,
|
|
|
|
|
Entity destination,
|
|
|
|
|
int2 destinationPosition)
|
|
|
|
|
{
|
|
|
|
|
if (!TryResolveEquippedItem(item, out var originEntityKey, out var equipmentIndex) ||
|
|
|
|
|
!TryResolveEntityRecordKey(destination, out var destinationEntityKey) ||
|
|
|
|
|
item?.EquippableItem == null ||
|
|
|
|
|
!TryValidateTypedEquipmentSlot(originEntityKey, equipmentIndex, item))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TrySubmitOperation(
|
|
|
|
|
operations => operations.EquipItem(
|
|
|
|
|
"equipment",
|
|
|
|
|
originEntityKey,
|
|
|
|
|
equipmentIndex,
|
|
|
|
|
destinationEntityKey,
|
|
|
|
|
item.EquippableItem.ItemKey,
|
|
|
|
|
item.Position.x,
|
|
|
|
|
item.Position.y,
|
|
|
|
|
destinationPosition.x,
|
|
|
|
|
destinationPosition.y,
|
|
|
|
|
true),
|
|
|
|
|
"equipment equip");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool TrySubmitOperation(
|
|
|
|
|
Action<AetheriaControl> submit,
|
|
|
|
|
string label)
|
|
|
|
|
{
|
|
|
|
|
if (submit == null)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
submit(ResolveClient().Control);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning($"Failed to send Aetheria daemon inventory {label} operation; operation not submitted: {ex.Message}");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private AetheriaClient ResolveClient()
|
|
|
|
|
{
|
2026-06-28 02:46:59 +01:00
|
|
|
var stateBoot = AetheriaRuntimeStateBoot.Inspect(AetheriaUnityRuntimePaths.GameDataDirectory);
|
|
|
|
|
if (!string.Equals(_clientStatePath, stateBoot.StateFilePath, StringComparison.Ordinal))
|
|
|
|
|
{
|
|
|
|
|
_clientStatePath = stateBoot.StateFilePath;
|
|
|
|
|
ClearClientCaches();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return AetheriaUnityRuntimeClientProvider.ResolveClient(stateBoot, "unity-inventory");
|
2026-06-25 18:09:33 +01:00
|
|
|
}
|
|
|
|
|
|
2026-06-28 02:46:59 +01:00
|
|
|
private void ClearClientCaches()
|
2026-06-25 18:09:33 +01:00
|
|
|
{
|
2026-06-28 09:04:28 +01:00
|
|
|
_catalog?.Dispose();
|
|
|
|
|
_playerSettings?.Dispose();
|
2026-06-28 09:48:19 +01:00
|
|
|
_currentEntity?.Dispose();
|
|
|
|
|
_stationRefit?.Dispose();
|
2026-06-28 12:45:49 +01:00
|
|
|
_loadoutFrame?.Dispose();
|
2026-06-28 10:07:05 +01:00
|
|
|
_observedDockingIndex?.Dispose();
|
2026-06-28 09:48:19 +01:00
|
|
|
_inventory?.Dispose();
|
2026-06-25 18:09:33 +01:00
|
|
|
_catalog = null;
|
|
|
|
|
_playerSettings = null;
|
2026-06-28 09:48:19 +01:00
|
|
|
_currentEntity = null;
|
|
|
|
|
_stationRefit = null;
|
2026-06-28 12:45:49 +01:00
|
|
|
_loadoutFrame = null;
|
2026-06-28 10:07:05 +01:00
|
|
|
_observedDockingIndex = null;
|
2026-06-28 09:48:19 +01:00
|
|
|
_inventory = null;
|
|
|
|
|
_inventoryEntityIndex = -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private AetheriaRuntimeCurrentEntityDocument ResolveCurrentEntity()
|
|
|
|
|
{
|
|
|
|
|
if (_currentEntity != null)
|
|
|
|
|
return _currentEntity.Current;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_currentEntity = ResolveClient()
|
2026-06-29 04:18:22 +01:00
|
|
|
.State.Reactive<AetheriaRuntimeCurrentEntityDocument>();
|
2026-06-28 09:48:19 +01:00
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning($"Failed to bind Aetheria current entity for inventory panel: {ex.Message}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return _currentEntity?.Current;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 14:25:08 +01:00
|
|
|
private AetheriaRuntimeStationRefitDocument ResolveStationRefitDocument()
|
2026-06-28 09:48:19 +01:00
|
|
|
{
|
|
|
|
|
if (_stationRefit != null)
|
|
|
|
|
return _stationRefit.Current;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_stationRefit = ResolveClient()
|
2026-06-29 04:18:22 +01:00
|
|
|
.State.Reactive<AetheriaRuntimeStationRefitDocument>();
|
2026-06-28 09:48:19 +01:00
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning($"Failed to bind Aetheria station refit for inventory panel: {ex.Message}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return _stationRefit?.Current;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-29 04:02:45 +01:00
|
|
|
private AetheriaRuntimeLoadoutTemplateCommit CreateLoadoutTemplate(string targetEntityKey)
|
2026-06-28 10:23:14 +01:00
|
|
|
{
|
2026-06-28 12:45:49 +01:00
|
|
|
var frame = ResolveLoadoutFrame();
|
2026-06-29 04:02:45 +01:00
|
|
|
return (frame?.Run ?? new AetheriaRuntimeRunCheckpointCommit())
|
|
|
|
|
.CreateLoadoutTemplate(targetEntityKey ?? "");
|
2026-06-28 12:45:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private AetheriaRuntimeDaemonFrameDocument ResolveLoadoutFrame()
|
|
|
|
|
{
|
|
|
|
|
if (_loadoutFrame != null)
|
|
|
|
|
return _loadoutFrame.Current;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_loadoutFrame = ResolveClient()
|
2026-06-29 04:18:22 +01:00
|
|
|
.State.Reactive<AetheriaRuntimeDaemonFrameDocument>();
|
2026-06-28 12:45:49 +01:00
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning($"Failed to bind Aetheria daemon frame for loadout template save: {ex.Message}");
|
|
|
|
|
}
|
2026-06-28 10:23:14 +01:00
|
|
|
|
2026-06-28 12:45:49 +01:00
|
|
|
return _loadoutFrame?.Current;
|
2026-06-28 10:23:14 +01:00
|
|
|
}
|
|
|
|
|
|
2026-06-28 09:48:19 +01:00
|
|
|
private AetheriaRuntimeInventoryDocument ResolveInventory(int entityIndex)
|
|
|
|
|
{
|
|
|
|
|
if (_inventory != null && _inventoryEntityIndex == entityIndex)
|
|
|
|
|
return _inventory.Current;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var nextInventory = ResolveClient()
|
2026-06-29 04:18:22 +01:00
|
|
|
.State.Reactive<AetheriaRuntimeInventoryDocument>(entityIndex);
|
2026-06-28 09:48:19 +01:00
|
|
|
_inventory?.Dispose();
|
|
|
|
|
_inventoryEntityIndex = entityIndex;
|
|
|
|
|
_inventory = nextInventory;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning($"Failed to bind Aetheria inventory projection for entity {entityIndex}: {ex.Message}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return _inventory?.Current;
|
2026-06-25 18:09:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private AetheriaRuntimeCatalogSnapshot ResolveCatalog()
|
|
|
|
|
{
|
|
|
|
|
if (_catalog != null)
|
2026-06-28 09:04:28 +01:00
|
|
|
return _catalog.Current;
|
2026-06-25 18:09:33 +01:00
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2026-06-29 04:18:22 +01:00
|
|
|
_catalog = ResolveClient().State.Reactive<AetheriaRuntimeCatalogSnapshot>();
|
2026-06-25 18:09:33 +01:00
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2026-06-28 09:04:28 +01:00
|
|
|
Debug.LogWarning($"Failed to bind Aetheria runtime catalog for inventory panel: {ex.Message}");
|
2026-06-25 18:09:33 +01:00
|
|
|
}
|
|
|
|
|
|
2026-06-28 09:04:28 +01:00
|
|
|
return _catalog?.Current;
|
2026-06-25 18:09:33 +01:00
|
|
|
}
|
|
|
|
|
|
2026-06-28 01:52:11 +01:00
|
|
|
private AetheriaRuntimePlayerSettingsDocument ResolvePlayerSettings()
|
2026-06-25 18:09:33 +01:00
|
|
|
{
|
|
|
|
|
if (_playerSettings != null)
|
2026-06-28 09:04:28 +01:00
|
|
|
return _playerSettings.Current;
|
2026-06-25 18:09:33 +01:00
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_playerSettings = ResolveClient()
|
2026-06-29 04:18:22 +01:00
|
|
|
.State.Reactive<AetheriaRuntimePlayerSettingsDocument>();
|
2026-06-25 18:09:33 +01:00
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2026-06-28 09:04:28 +01:00
|
|
|
Debug.LogWarning($"Failed to bind Aetheria player settings for inventory panel: {ex.Message}");
|
2026-06-25 18:09:33 +01:00
|
|
|
}
|
|
|
|
|
|
2026-06-28 09:04:28 +01:00
|
|
|
return _playerSettings?.Current;
|
2026-06-25 18:09:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string FormatValue(float value)
|
|
|
|
|
{
|
|
|
|
|
var settings = ResolvePlayerSettings();
|
|
|
|
|
var significantDigits = settings?.SignificantDigits ?? 3;
|
|
|
|
|
var magnitude = value == 0.0f ? 0 : (int)Math.Floor(Math.Log10(Math.Abs(value))) + 1;
|
|
|
|
|
var digits = significantDigits - magnitude;
|
|
|
|
|
if (digits < 0)
|
|
|
|
|
digits = 0;
|
|
|
|
|
|
|
|
|
|
var formatted = value.ToString($"N{digits}", CultureInfo.CurrentCulture);
|
|
|
|
|
var separator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
|
|
|
|
|
var decimalSeparator = Convert.ToChar(separator);
|
|
|
|
|
return formatted.Contains(separator)
|
|
|
|
|
? formatted.TrimEnd('0').TrimEnd(decimalSeparator)
|
|
|
|
|
: formatted;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string FormatTemperature(float value)
|
|
|
|
|
{
|
|
|
|
|
var unit = ResolvePlayerSettings()?.TemperatureUnit ?? nameof(TemperatureUnit.Celsius);
|
|
|
|
|
if (string.Equals(unit, nameof(TemperatureUnit.Kelvin), StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
return $"{FormatValue(value)} K";
|
|
|
|
|
if (string.Equals(unit, nameof(TemperatureUnit.Fahrenheit), StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
return $"{FormatValue(value * (9f / 5) - 459.67f)} F";
|
|
|
|
|
|
|
|
|
|
return $"{FormatValue(value - 273.15f)} C";
|
2026-06-14 00:54:19 +01:00
|
|
|
}
|
|
|
|
|
|
2026-06-21 14:12:06 +01:00
|
|
|
private void RequestDraggedItemToEntity(DragObject drag, Entity destination, int2 destinationPosition)
|
2021-03-04 03:58:46 +00:00
|
|
|
{
|
2026-06-16 02:35:13 +01:00
|
|
|
switch (drag)
|
2021-03-04 03:58:46 +00:00
|
|
|
{
|
2026-06-16 02:35:13 +01:00
|
|
|
case EquippedItemDragObject equippedItemDragObject:
|
2026-06-25 18:09:33 +01:00
|
|
|
RequestEquippedItemEquip(
|
2026-06-16 02:35:13 +01:00
|
|
|
equippedItemDragObject.EquippedItem,
|
|
|
|
|
destination,
|
|
|
|
|
destinationPosition);
|
2026-06-21 14:12:06 +01:00
|
|
|
return;
|
2026-06-16 02:35:13 +01:00
|
|
|
case ItemInstanceDragObject itemInstanceDragObject when itemInstanceDragObject.Item is EquippableItem equippableItem:
|
2026-06-25 18:09:33 +01:00
|
|
|
RequestCargoItemEquip(
|
2026-06-16 02:35:13 +01:00
|
|
|
itemInstanceDragObject.OriginInventory,
|
|
|
|
|
destination,
|
|
|
|
|
equippableItem,
|
2026-06-25 18:09:33 +01:00
|
|
|
destinationPosition,
|
|
|
|
|
true);
|
2026-06-21 14:12:06 +01:00
|
|
|
return;
|
2021-03-04 03:58:46 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-21 14:12:06 +01:00
|
|
|
private void RequestDraggedItemToCargo(DragObject drag, EquippedCargoBay destination, int2 destinationPosition)
|
2021-01-27 16:14:41 +00:00
|
|
|
{
|
2026-06-16 02:35:13 +01:00
|
|
|
switch (drag)
|
2021-01-27 16:14:41 +00:00
|
|
|
{
|
2026-06-16 02:35:13 +01:00
|
|
|
case EquippedItemDragObject equippedItemDragObject:
|
2026-06-25 18:09:33 +01:00
|
|
|
RequestEquippedItemStore(
|
2026-06-16 02:35:13 +01:00
|
|
|
equippedItemDragObject.EquippedItem,
|
|
|
|
|
destination,
|
2026-06-25 18:09:33 +01:00
|
|
|
destinationPosition,
|
|
|
|
|
true);
|
2026-06-21 14:12:06 +01:00
|
|
|
return;
|
2026-06-16 02:35:13 +01:00
|
|
|
case ItemInstanceDragObject itemInstanceDragObject:
|
2026-06-25 18:09:33 +01:00
|
|
|
RequestCargoItemTransfer(
|
2026-06-16 02:35:13 +01:00
|
|
|
itemInstanceDragObject.OriginInventory,
|
|
|
|
|
destination,
|
|
|
|
|
itemInstanceDragObject.Item,
|
2026-06-25 18:09:33 +01:00
|
|
|
destinationPosition,
|
|
|
|
|
true);
|
2026-06-21 14:12:06 +01:00
|
|
|
return;
|
2021-01-27 16:14:41 +00:00
|
|
|
}
|
2026-06-16 02:35:13 +01:00
|
|
|
}
|
2021-01-27 16:14:41 +00:00
|
|
|
|
2026-06-16 22:08:16 +01:00
|
|
|
private void OnDestroy()
|
|
|
|
|
{
|
2026-06-28 09:04:28 +01:00
|
|
|
ClearClientCaches();
|
|
|
|
|
|
2026-06-16 22:08:16 +01:00
|
|
|
if (_dropdownSurfaceDocument != null)
|
|
|
|
|
{
|
2026-06-21 09:37:54 +01:00
|
|
|
AetheriaEveUnitySurfaceHost.DestroyDocument(_dropdownSurfaceDocument);
|
2026-06-16 22:08:16 +01:00
|
|
|
_dropdownSurfaceDocument = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-07 01:10:31 +00:00
|
|
|
public Subject<PointerEventData> OnBackgroundClick = new Subject<PointerEventData>();
|
2021-04-21 01:49:07 +01:00
|
|
|
private Transform[] _dragCells;
|
|
|
|
|
private Vector2[] _dragOffsets;
|
|
|
|
|
private ItemRotation _originalRotation;
|
|
|
|
|
|
2021-04-10 01:47:16 +01:00
|
|
|
public Subject<(InventoryEventData data, int clickCount)> OnClickAsObservable() => _onClick ?? (_onClick = new Subject<(InventoryEventData data, int clickCount)>());
|
2021-04-21 01:49:07 +01:00
|
|
|
// 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>());
|
2021-03-07 01:10:31 +00:00
|
|
|
|
|
|
|
|
public void OnPointerClick(PointerEventData eventData)
|
|
|
|
|
{
|
|
|
|
|
OnBackgroundClick.OnNext(eventData);
|
|
|
|
|
}
|
2020-12-20 03:04:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public abstract class InventoryEventData
|
|
|
|
|
{
|
|
|
|
|
protected InventoryEventData(PointerEventData pointerEventData, int2 position)
|
|
|
|
|
{
|
|
|
|
|
PointerEventData = pointerEventData;
|
|
|
|
|
Position = position;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public PointerEventData PointerEventData { get; }
|
|
|
|
|
public int2 Position { get; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class InventoryEntityEventData : InventoryEventData
|
|
|
|
|
{
|
|
|
|
|
public InventoryEntityEventData(PointerEventData pointerEventData, int2 position, Entity entity) :
|
|
|
|
|
base(pointerEventData, position)
|
|
|
|
|
{
|
|
|
|
|
Entity = entity;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Entity Entity { get; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class InventoryCargoEventData : InventoryEventData
|
|
|
|
|
{
|
|
|
|
|
public InventoryCargoEventData(PointerEventData pointerEventData, int2 position, EquippedCargoBay cargoBay) :
|
|
|
|
|
base(pointerEventData, position)
|
|
|
|
|
{
|
|
|
|
|
CargoBay = cargoBay;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public EquippedCargoBay CargoBay { get; }
|
2020-12-12 03:42:58 +00:00
|
|
|
}
|
2021-02-27 20:53:36 +00:00
|
|
|
|
|
|
|
|
public enum InventoryPanelTarget
|
|
|
|
|
{
|
|
|
|
|
None,
|
|
|
|
|
Cargo,
|
|
|
|
|
Equipment
|
|
|
|
|
}
|