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/. */
|
|
|
|
|
|
|
Replace the Database Tools with the Studio and cut Unity over
Cut 8b. The Unity project compiles with 0 errors in 6000.3.24f1 batchmode.
Packages: manifest.json pins org.gamecult.cultlib at cultlib-unity-v1.0.57
and org.gamecult.caching.unity at caching-unity-v1.2.0, next to the 8a
CultMath tag. Aetheria.Shared.Unity references GameCult.CultLib and keeps
noEngineReferences.
The Database Tools (Assets/Scripts/CultCache/Editor) are deleted in this
commit, alongside their five ported drawers in
Assets/Scripts/Editor/CultCacheDrawers.cs, each an
[CultInspectorDrawer] ICultInspectorDrawer:
- InspectableType: popup of the attribute type's [Union] member names,
writing the string.
- InspectableColor: float3 and float4 through ColorField.
- InspectableAnimationCurve: float4[] and BezierCurve through CurveField.
- InspectableTemperature: Kelvin, Celsius and Fahrenheit fields over one
value.
- InspectableSchematicShape: size and cell toggles. The old drawer also
drew the item's schematic texture and tinted hull hardpoints, which need
the parent document. The tagged Studio hands a drawer only the value, so
those wait on the Studio exposing the open record.
Unity call sites:
- ActionGameManager opens the catalog through AetheriaStores; Cut 10 adds
the run and player stores.
- Refs resolve through ItemManager.GetData or Get(ref); orbit, planet and
belt maps key by CultRecordKey; identity is compared by reference or key.
- The PlayerSettings.msgpack reader and writer, which Cut 10 deletes, pass
CultDocumentMessagePackSerialization.OptionsFor, since the embedded run
reaches CultMath values and refs. Keyboard layouts need no resolver and
keep MessagePack's defaults until Cut 10.
- The input map uses the nested action to binding shape.
- PropertiesPanel reads CultInspectorRangeAttribute.
- NameTools loses its legacy name-file writer.
- Slime compares serialized settings instead of the deleted SHA1 helper.
- FieldDriver drops InspectorHeader.
- .gitignore covers the test project's build output.
Builds: Aetheria.Shared, AetherDb, Aetheria.Shared.Tests (6/6), Unity
batchmode (0 errors).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 19:15:20 +02:00
|
|
|
|
using GameCult.Caching;
|
2020-12-22 03:13:43 +00:00
|
|
|
|
using System;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
using System.Collections;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
using System.Linq;
|
2020-11-29 22:50:12 +00:00
|
|
|
|
using Cinemachine;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
using Ink;
|
|
|
|
|
|
using Ink.Runtime;
|
|
|
|
|
|
using MessagePack;
|
|
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
|
using TMPro;
|
|
|
|
|
|
using UniRx;
|
2020-11-27 22:47:33 +00:00
|
|
|
|
using UnityEngine;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
using UnityEngine.InputSystem;
|
|
|
|
|
|
using UnityEngine.Rendering.PostProcessing;
|
2021-03-15 11:40:46 +00:00
|
|
|
|
using UnityEngine.Serialization;
|
2020-12-14 02:56:35 +00:00
|
|
|
|
using UnityEngine.UI;
|
2026-09-14 17:39:12 +02:00
|
|
|
|
using CultMath;
|
Convert engine types through CultMath.UnityBridge
CultMath has no implicit conversions to or from Vector*, Quaternion or
Color, so Assembly-CSharp converts explicitly with ToCultMath/ToUnity at
each engine boundary: FieldDriver, GuidedProjectile(Manager),
EntityInstance, ActionGameManager, HullCollider, GridObject, ShipInstance,
ShieldManager, ItemPickup, FieldTester, PlaceUIElementWorldspace,
MapMenuInput, MapRenderer, SectorRenderer and ZoneRenderer. CultMath math
calls that were handed Unity vectors now take converted values;
Vector3.Scale in FieldDriver becomes a float3 product, and ItemPickup's
Vector3.Dot on float3s becomes dot. packages-lock.json records the
cultmath git package at f0605367 and demotes com.unity.mathematics to a
transitive dependency.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 17:47:33 +02:00
|
|
|
|
using CultMath.UnityBridge;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
using UnityEngine.EventSystems;
|
2026-09-14 17:39:12 +02:00
|
|
|
|
using static CultMath.math;
|
|
|
|
|
|
using float2 = CultMath.float2;
|
|
|
|
|
|
using float3 = CultMath.float3;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
using Path = System.IO.Path;
|
2026-09-14 17:39:12 +02:00
|
|
|
|
using quaternion = CultMath.quaternion;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
using Random = UnityEngine.Random;
|
2020-11-27 22:47:33 +00:00
|
|
|
|
|
|
|
|
|
|
public class ActionGameManager : MonoBehaviour
|
|
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
// Always check for null if accessing from anywhere that might not be in-game (e.g. menu UI)
|
|
|
|
|
|
public static ActionGameManager Instance { get; private set; }
|
|
|
|
|
|
private static DirectoryInfo _gameDataDirectory;
|
|
|
|
|
|
public static DirectoryInfo GameDataDirectory
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _gameDataDirectory ??= new DirectoryInfo(Application.dataPath).Parent.CreateSubdirectory("GameData");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static CultCache _cultCache;
|
|
|
|
|
|
|
Capture presets through their own cache; keep the catalog read-only
Opening the process cache's catalog writable in the editor was the wrong cut.
It made catalog read-only only by convention, so a stray catalog write in play
succeeded and a later flush could land play-mutated instances in Aetheria.cc.
- The process cache opens the catalog read-only in every build again, so the
missing-global check runs for it as it did before db83cd29. The writable
empty-seed exemption from ab4ced0f stays for AetherDb and fixtures.
- Loadouts.Commit(catalogPath, loadout, replace) is the only preset writer. It
opens its own short-lived cache over the catalog, writable, commits the one
preset conditionally on its key and disposes it. No other cache or instance
is touched. The running game sees the preset only after the catalog reloads.
CultLib at a0813c6 allows this: a read-only single-file store takes no lock
and holds no handle, and the writer's commit takes the exclusive sidecar
<path>.lock and replaces the file atomically.
- Capture refuses, writing nothing, when Aetheria.cc is missing (it never
creates the catalog), when a preset of that name exists under another key
(CultCache does not reject duplicate names on write), or when the key holds a
preset and replace is not given.
- The console refuses a command with any character outside letters, digits,
spaces and hyphens instead of stripping it, so "Mk.II" can no longer become
"MkII". capturepreset refuses a second argument other than replace, so an
unquoted capturepreset My Ship refuses instead of saving "My".
- The capture log line says the session sees the preset after a reload, and
that a Studio session opened earlier overwrites captured presets on save.
- Tests: capture while the process cache holds the catalog read-only leaves it
unchanged; a missing catalog refuses and creates nothing; a same name under a
Studio-minted key refuses, with or without replace; a game-shaped read-only
open missing a global throws. The existing preset tests now commit through
the catalog path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:32:52 +02:00
|
|
|
|
private static string CatalogPath => Path.Combine(GameDataDirectory.FullName, "Aetheria.cc");
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public static CultCache CultCache
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
2026-06-25 18:09:33 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (_cultCache != null) return _cultCache;
|
|
|
|
|
|
|
Land RunSave and the one-cache run and player lifecycle (Cut 10)
Owner: ActionGameManager composes the process's one cache. Its CultCache
getter attaches the catalog, GameData/run.cc and GameData/player.cc on
first access, in either scene, and nothing reopens it.
- RunSave (SavedGame.cs) replaces the SavedGame constructor:
- Capture builds the live run as plain documents and writes nothing. Zone
contents are `zone.Contents?.PackZone() ?? zone.PackedContents`, so a
zone generated earlier but not loaded this session keeps its contents
and its orbit and body records instead of being erased by the next save.
- Commit is the only writer of SavedGame and SavedZone: one Commit with
zone i at `savedzone-{i}`, SavedGame.Zones set to those refs, and every
stored SavedZone outside that set removed. Orbits and bodies staged by
zone generation land in the same single-file write.
- Clear removes every run record in one Commit.
- Run begins: New Game's first statement is RunSave.Clear. Run saves:
ActionGameManager.SaveRun at wormhole arrival and quit. Run ends: Die
clears the run, then saves settings.
- Run exists only as the run store's SavedGame global: MainMenu enables
Continue from it (and logs when the run store holds records but no
SavedGame), and StartGame reads it once to pick the new-run or resume
branch.
- PlayerSettings: the getter returns the player store's global and, on
first launch, commits GetDefaultPlayerSettings; it is the only creator.
SavePlayerSettings commits the instance.
- InputDisplayLayout reads its InputLayout from the catalog by the layout
asset's name and throws when the catalog has none.
Tests: new RunSaveTests (5): RepeatedSavesKeepRecordCountConstant,
SaveRemovesZonesNoLongerInTheRun, SaveWritesOnlyTheRunStore,
ResumeThenSaveKeepsUnloadedZoneContents, ClearRemovesEveryRunRecord. Each
fails with its rule mutated out.
Builds: headless, 11 tests pass, Unity batchmode compile reports no error CS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:00:27 +02:00
|
|
|
|
// All three stores attach once and stay attached until the process exits. The run lifecycle is record-level
|
|
|
|
|
|
// inside this cache; reopening would replace the catalog instances the galaxy and live entities hold.
|
Capture presets through their own cache; keep the catalog read-only
Opening the process cache's catalog writable in the editor was the wrong cut.
It made catalog read-only only by convention, so a stray catalog write in play
succeeded and a later flush could land play-mutated instances in Aetheria.cc.
- The process cache opens the catalog read-only in every build again, so the
missing-global check runs for it as it did before db83cd29. The writable
empty-seed exemption from ab4ced0f stays for AetherDb and fixtures.
- Loadouts.Commit(catalogPath, loadout, replace) is the only preset writer. It
opens its own short-lived cache over the catalog, writable, commits the one
preset conditionally on its key and disposes it. No other cache or instance
is touched. The running game sees the preset only after the catalog reloads.
CultLib at a0813c6 allows this: a read-only single-file store takes no lock
and holds no handle, and the writer's commit takes the exclusive sidecar
<path>.lock and replaces the file atomically.
- Capture refuses, writing nothing, when Aetheria.cc is missing (it never
creates the catalog), when a preset of that name exists under another key
(CultCache does not reject duplicate names on write), or when the key holds a
preset and replace is not given.
- The console refuses a command with any character outside letters, digits,
spaces and hyphens instead of stripping it, so "Mk.II" can no longer become
"MkII". capturepreset refuses a second argument other than replace, so an
unquoted capturepreset My Ship refuses instead of saving "My".
- The capture log line says the session sees the preset after a reload, and
that a Studio session opened earlier overwrites captured presets on save.
- Tests: capture while the process cache holds the catalog read-only leaves it
unchanged; a missing catalog refuses and creates nothing; a same name under a
Studio-minted key refuses, with or without replace; a game-shaped read-only
open missing a global throws. The existing preset tests now commit through
the catalog path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:32:52 +02:00
|
|
|
|
// The catalog is read-only in every build; capturepreset writes through its own cache (Loadouts.Commit).
|
Land RunSave and the one-cache run and player lifecycle (Cut 10)
Owner: ActionGameManager composes the process's one cache. Its CultCache
getter attaches the catalog, GameData/run.cc and GameData/player.cc on
first access, in either scene, and nothing reopens it.
- RunSave (SavedGame.cs) replaces the SavedGame constructor:
- Capture builds the live run as plain documents and writes nothing. Zone
contents are `zone.Contents?.PackZone() ?? zone.PackedContents`, so a
zone generated earlier but not loaded this session keeps its contents
and its orbit and body records instead of being erased by the next save.
- Commit is the only writer of SavedGame and SavedZone: one Commit with
zone i at `savedzone-{i}`, SavedGame.Zones set to those refs, and every
stored SavedZone outside that set removed. Orbits and bodies staged by
zone generation land in the same single-file write.
- Clear removes every run record in one Commit.
- Run begins: New Game's first statement is RunSave.Clear. Run saves:
ActionGameManager.SaveRun at wormhole arrival and quit. Run ends: Die
clears the run, then saves settings.
- Run exists only as the run store's SavedGame global: MainMenu enables
Continue from it (and logs when the run store holds records but no
SavedGame), and StartGame reads it once to pick the new-run or resume
branch.
- PlayerSettings: the getter returns the player store's global and, on
first launch, commits GetDefaultPlayerSettings; it is the only creator.
SavePlayerSettings commits the instance.
- InputDisplayLayout reads its InputLayout from the catalog by the layout
asset's name and throws when the catalog has none.
Tests: new RunSaveTests (5): RepeatedSavesKeepRecordCountConstant,
SaveRemovesZonesNoLongerInTheRun, SaveWritesOnlyTheRunStore,
ResumeThenSaveKeepsUnloadedZoneContents, ClearRemovesEveryRunRecord. Each
fails with its rule mutated out.
Builds: headless, 11 tests pass, Unity batchmode compile reports no error CS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:00:27 +02:00
|
|
|
|
_cultCache = AetheriaStores.Open(
|
Capture presets through their own cache; keep the catalog read-only
Opening the process cache's catalog writable in the editor was the wrong cut.
It made catalog read-only only by convention, so a stray catalog write in play
succeeded and a later flush could land play-mutated instances in Aetheria.cc.
- The process cache opens the catalog read-only in every build again, so the
missing-global check runs for it as it did before db83cd29. The writable
empty-seed exemption from ab4ced0f stays for AetherDb and fixtures.
- Loadouts.Commit(catalogPath, loadout, replace) is the only preset writer. It
opens its own short-lived cache over the catalog, writable, commits the one
preset conditionally on its key and disposes it. No other cache or instance
is touched. The running game sees the preset only after the catalog reloads.
CultLib at a0813c6 allows this: a read-only single-file store takes no lock
and holds no handle, and the writer's commit takes the exclusive sidecar
<path>.lock and replaces the file atomically.
- Capture refuses, writing nothing, when Aetheria.cc is missing (it never
creates the catalog), when a preset of that name exists under another key
(CultCache does not reject duplicate names on write), or when the key holds a
preset and replace is not given.
- The console refuses a command with any character outside letters, digits,
spaces and hyphens instead of stripping it, so "Mk.II" can no longer become
"MkII". capturepreset refuses a second argument other than replace, so an
unquoted capturepreset My Ship refuses instead of saving "My".
- The capture log line says the session sees the preset after a reload, and
that a Studio session opened earlier overwrites captured presets on save.
- Tests: capture while the process cache holds the catalog read-only leaves it
unchanged; a missing catalog refuses and creates nothing; a same name under a
Studio-minted key refuses, with or without replace; a game-shaped read-only
open missing a global throws. The existing preset tests now commit through
the catalog path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:32:52 +02:00
|
|
|
|
CatalogPath,
|
Land RunSave and the one-cache run and player lifecycle (Cut 10)
Owner: ActionGameManager composes the process's one cache. Its CultCache
getter attaches the catalog, GameData/run.cc and GameData/player.cc on
first access, in either scene, and nothing reopens it.
- RunSave (SavedGame.cs) replaces the SavedGame constructor:
- Capture builds the live run as plain documents and writes nothing. Zone
contents are `zone.Contents?.PackZone() ?? zone.PackedContents`, so a
zone generated earlier but not loaded this session keeps its contents
and its orbit and body records instead of being erased by the next save.
- Commit is the only writer of SavedGame and SavedZone: one Commit with
zone i at `savedzone-{i}`, SavedGame.Zones set to those refs, and every
stored SavedZone outside that set removed. Orbits and bodies staged by
zone generation land in the same single-file write.
- Clear removes every run record in one Commit.
- Run begins: New Game's first statement is RunSave.Clear. Run saves:
ActionGameManager.SaveRun at wormhole arrival and quit. Run ends: Die
clears the run, then saves settings.
- Run exists only as the run store's SavedGame global: MainMenu enables
Continue from it (and logs when the run store holds records but no
SavedGame), and StartGame reads it once to pick the new-run or resume
branch.
- PlayerSettings: the getter returns the player store's global and, on
first launch, commits GetDefaultPlayerSettings; it is the only creator.
SavePlayerSettings commits the instance.
- InputDisplayLayout reads its InputLayout from the catalog by the layout
asset's name and throws when the catalog has none.
Tests: new RunSaveTests (5): RepeatedSavesKeepRecordCountConstant,
SaveRemovesZonesNoLongerInTheRun, SaveWritesOnlyTheRunStore,
ResumeThenSaveKeepsUnloadedZoneContents, ClearRemovesEveryRunRecord. Each
fails with its rule mutated out.
Builds: headless, 11 tests pass, Unity batchmode compile reports no error CS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:00:27 +02:00
|
|
|
|
runPath: Path.Combine(GameDataDirectory.FullName, "run.cc"),
|
Capture presets through their own cache; keep the catalog read-only
Opening the process cache's catalog writable in the editor was the wrong cut.
It made catalog read-only only by convention, so a stray catalog write in play
succeeded and a later flush could land play-mutated instances in Aetheria.cc.
- The process cache opens the catalog read-only in every build again, so the
missing-global check runs for it as it did before db83cd29. The writable
empty-seed exemption from ab4ced0f stays for AetherDb and fixtures.
- Loadouts.Commit(catalogPath, loadout, replace) is the only preset writer. It
opens its own short-lived cache over the catalog, writable, commits the one
preset conditionally on its key and disposes it. No other cache or instance
is touched. The running game sees the preset only after the catalog reloads.
CultLib at a0813c6 allows this: a read-only single-file store takes no lock
and holds no handle, and the writer's commit takes the exclusive sidecar
<path>.lock and replaces the file atomically.
- Capture refuses, writing nothing, when Aetheria.cc is missing (it never
creates the catalog), when a preset of that name exists under another key
(CultCache does not reject duplicate names on write), or when the key holds a
preset and replace is not given.
- The console refuses a command with any character outside letters, digits,
spaces and hyphens instead of stripping it, so "Mk.II" can no longer become
"MkII". capturepreset refuses a second argument other than replace, so an
unquoted capturepreset My Ship refuses instead of saving "My".
- The capture log line says the session sees the preset after a reload, and
that a Studio session opened earlier overwrites captured presets on save.
- Tests: capture while the process cache holds the catalog read-only leaves it
unchanged; a missing catalog refuses and creates nothing; a same name under a
Studio-minted key refuses, with or without replace; a game-shaped read-only
open missing a global throws. The existing preset tests now commit through
the catalog path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:32:52 +02:00
|
|
|
|
playerPath: Path.Combine(GameDataDirectory.FullName, "player.cc"));
|
Land RunSave and the one-cache run and player lifecycle (Cut 10)
Owner: ActionGameManager composes the process's one cache. Its CultCache
getter attaches the catalog, GameData/run.cc and GameData/player.cc on
first access, in either scene, and nothing reopens it.
- RunSave (SavedGame.cs) replaces the SavedGame constructor:
- Capture builds the live run as plain documents and writes nothing. Zone
contents are `zone.Contents?.PackZone() ?? zone.PackedContents`, so a
zone generated earlier but not loaded this session keeps its contents
and its orbit and body records instead of being erased by the next save.
- Commit is the only writer of SavedGame and SavedZone: one Commit with
zone i at `savedzone-{i}`, SavedGame.Zones set to those refs, and every
stored SavedZone outside that set removed. Orbits and bodies staged by
zone generation land in the same single-file write.
- Clear removes every run record in one Commit.
- Run begins: New Game's first statement is RunSave.Clear. Run saves:
ActionGameManager.SaveRun at wormhole arrival and quit. Run ends: Die
clears the run, then saves settings.
- Run exists only as the run store's SavedGame global: MainMenu enables
Continue from it (and logs when the run store holds records but no
SavedGame), and StartGame reads it once to pick the new-run or resume
branch.
- PlayerSettings: the getter returns the player store's global and, on
first launch, commits GetDefaultPlayerSettings; it is the only creator.
SavePlayerSettings commits the instance.
- InputDisplayLayout reads its InputLayout from the catalog by the layout
asset's name and throws when the catalog has none.
Tests: new RunSaveTests (5): RepeatedSavesKeepRecordCountConstant,
SaveRemovesZonesNoLongerInTheRun, SaveWritesOnlyTheRunStore,
ResumeThenSaveKeepsUnloadedZoneContents, ClearRemovesEveryRunRecord. Each
fails with its rule mutated out.
Builds: headless, 11 tests pass, Unity batchmode compile reports no error CS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:00:27 +02:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
return _cultCache;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Land RunSave and the one-cache run and player lifecycle (Cut 10)
Owner: ActionGameManager composes the process's one cache. Its CultCache
getter attaches the catalog, GameData/run.cc and GameData/player.cc on
first access, in either scene, and nothing reopens it.
- RunSave (SavedGame.cs) replaces the SavedGame constructor:
- Capture builds the live run as plain documents and writes nothing. Zone
contents are `zone.Contents?.PackZone() ?? zone.PackedContents`, so a
zone generated earlier but not loaded this session keeps its contents
and its orbit and body records instead of being erased by the next save.
- Commit is the only writer of SavedGame and SavedZone: one Commit with
zone i at `savedzone-{i}`, SavedGame.Zones set to those refs, and every
stored SavedZone outside that set removed. Orbits and bodies staged by
zone generation land in the same single-file write.
- Clear removes every run record in one Commit.
- Run begins: New Game's first statement is RunSave.Clear. Run saves:
ActionGameManager.SaveRun at wormhole arrival and quit. Run ends: Die
clears the run, then saves settings.
- Run exists only as the run store's SavedGame global: MainMenu enables
Continue from it (and logs when the run store holds records but no
SavedGame), and StartGame reads it once to pick the new-run or resume
branch.
- PlayerSettings: the getter returns the player store's global and, on
first launch, commits GetDefaultPlayerSettings; it is the only creator.
SavePlayerSettings commits the instance.
- InputDisplayLayout reads its InputLayout from the catalog by the layout
asset's name and throws when the catalog has none.
Tests: new RunSaveTests (5): RepeatedSavesKeepRecordCountConstant,
SaveRemovesZonesNoLongerInTheRun, SaveWritesOnlyTheRunStore,
ResumeThenSaveKeepsUnloadedZoneContents, ClearRemovesEveryRunRecord. Each
fails with its rule mutated out.
Builds: headless, 11 tests pass, Unity batchmode compile reports no error CS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:00:27 +02:00
|
|
|
|
// The only creator of PlayerSettings: an absent global means first launch, and the defaults are committed.
|
|
|
|
|
|
public static PlayerSettings PlayerSettings
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
var settings = CultCache.GetGlobal<PlayerSettings>();
|
|
|
|
|
|
if (settings != null) return settings;
|
|
|
|
|
|
settings = GetDefaultPlayerSettings();
|
|
|
|
|
|
CultCache.Commit(batch => batch.Upsert(settings));
|
|
|
|
|
|
return settings;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static void SavePlayerSettings()
|
|
|
|
|
|
{
|
|
|
|
|
|
var settings = PlayerSettings;
|
|
|
|
|
|
CultCache.Commit(batch => batch.Upsert(settings));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
private static PlayerSettings GetDefaultPlayerSettings()
|
|
|
|
|
|
{
|
|
|
|
|
|
var settings = new PlayerSettings();
|
|
|
|
|
|
settings.Name = Environment.UserName;
|
|
|
|
|
|
settings.InputSettings.ActionBarInputs.Add("<Keyboard>/leftShift");
|
|
|
|
|
|
settings.InputSettings.ActionBarInputs.Add("<Mouse>/leftButton");
|
|
|
|
|
|
settings.InputSettings.ActionBarInputs.Add("<Mouse>/rightButton");
|
|
|
|
|
|
settings.InputSettings.ActionBarInputs.Add("<Mouse>/middleButton");
|
|
|
|
|
|
for (int i = 1; i < 6; i++) settings.InputSettings.ActionBarInputs.Add($"<Keyboard>/{i}");
|
|
|
|
|
|
return settings;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static Galaxy CurrentGalaxy;
|
|
|
|
|
|
public static bool IsTutorial;
|
2021-04-18 05:13:20 +01:00
|
|
|
|
|
2021-03-10 09:18:07 +00:00
|
|
|
|
public GameSettings Settings;
|
2021-03-18 22:48:33 +00:00
|
|
|
|
//public string StarterShipTemplate = "Longinus";
|
2021-03-10 09:18:07 +00:00
|
|
|
|
public float2 Sensitivity;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public int Credits = 15000000;
|
|
|
|
|
|
public float TargetSpottedBlinkFrequency = 20;
|
|
|
|
|
|
public float TargetSpottedBlinkOffset = -.25f;
|
|
|
|
|
|
|
2021-03-10 09:18:07 +00:00
|
|
|
|
[Header("Postprocessing")]
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public float DeathPostTransitionTime;
|
|
|
|
|
|
public PostProcessVolume DeathPost;
|
|
|
|
|
|
public PostProcessVolume HeatstrokePost;
|
|
|
|
|
|
public PostProcessVolume HypothermiaPost;
|
|
|
|
|
|
public PostProcessVolume SevereHeatstrokePost;
|
|
|
|
|
|
public PostProcessVolume SevereHypothermiaPost;
|
2021-03-20 04:20:17 +00:00
|
|
|
|
|
2021-03-10 09:18:07 +00:00
|
|
|
|
[Header("Scene Links")]
|
2021-05-08 07:35:16 +01:00
|
|
|
|
public GameObject UiRoot;
|
|
|
|
|
|
public GameObject HelpScreen;
|
|
|
|
|
|
public InputDisplayLayout InputDisplayLayout;
|
2021-04-18 05:13:20 +01:00
|
|
|
|
public Transform ActionBar;
|
|
|
|
|
|
public ActionBarSlot ActionBarSlot;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public Transform EffectManagerParent;
|
2021-04-09 14:01:34 +01:00
|
|
|
|
public ZoneRenderer ZoneRenderer;
|
2020-12-22 03:13:43 +00:00
|
|
|
|
public CinemachineVirtualCamera DockCamera;
|
2020-11-29 22:50:12 +00:00
|
|
|
|
public CinemachineVirtualCamera FollowCamera;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public CinemachineVirtualCamera WormholeCamera;
|
|
|
|
|
|
//public SectorRenderer SectorRenderer;
|
|
|
|
|
|
public SectorMap SectorMap;
|
|
|
|
|
|
|
2021-04-29 00:21:33 +01:00
|
|
|
|
[Header("Menu UI")]
|
|
|
|
|
|
public MainMenu MainMenu;
|
|
|
|
|
|
public MenuPanel Menu;
|
|
|
|
|
|
public MapRenderer MenuMap;
|
|
|
|
|
|
public TradeMenu TradeMenu;
|
2020-12-14 02:56:35 +00:00
|
|
|
|
public InventoryMenu Inventory;
|
2020-12-27 22:18:22 +00:00
|
|
|
|
public InventoryPanel ShipPanel;
|
2021-01-11 02:56:37 +00:00
|
|
|
|
public InventoryPanel TargetShipPanel;
|
2021-03-27 10:03:24 +00:00
|
|
|
|
public ConfirmationDialog Dialog;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public ContextMenu Context;
|
|
|
|
|
|
public DropdownMenu Dropdown;
|
|
|
|
|
|
|
2021-04-29 00:21:33 +01:00
|
|
|
|
[Header("Gameplay UI")]
|
|
|
|
|
|
public CanvasGroup GameplayUI;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public EventLog EventLog;
|
2021-04-29 00:21:33 +01:00
|
|
|
|
public Prototype HostileTargetIndicator;
|
2021-05-11 04:45:42 +01:00
|
|
|
|
public Prototype FriendlyTargetIndicator;
|
2021-04-29 00:21:33 +01:00
|
|
|
|
public PlaceUIElementWorldspace ViewDot;
|
|
|
|
|
|
public Prototype LockIndicator;
|
|
|
|
|
|
public PlaceUIElementWorldspace[] Crosshairs;
|
|
|
|
|
|
public GameObject HitMarker;
|
|
|
|
|
|
public float HitMarkerDuration;
|
|
|
|
|
|
public SchematicDisplay SchematicDisplay;
|
|
|
|
|
|
public SchematicDisplay TargetSchematicDisplay;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
2021-04-29 00:21:33 +01:00
|
|
|
|
[Header("Target Indicator")]
|
|
|
|
|
|
public PlaceUIElementWorldspace TargetIndicator;
|
|
|
|
|
|
public Image TargetHitpointsFill;
|
|
|
|
|
|
public Image TargetVisibilityFill;
|
|
|
|
|
|
public Image VisibilityToTargetFill;
|
|
|
|
|
|
public Image TargetShieldsBackground;
|
|
|
|
|
|
public Image TargetShieldsFill;
|
|
|
|
|
|
public Image TargetShieldsIcon;
|
|
|
|
|
|
public Color ShieldColor;
|
|
|
|
|
|
public Color NoShieldColor;
|
|
|
|
|
|
public Sprite ShieldIcon;
|
|
|
|
|
|
public Sprite NoShieldIcon;
|
2021-03-27 10:03:24 +00:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public float IntroDuration;
|
|
|
|
|
|
|
2020-12-02 16:42:09 +00:00
|
|
|
|
//public PlayerInput Input;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
2020-12-22 03:13:43 +00:00
|
|
|
|
// private CinemachineFramingTransposer _transposer;
|
|
|
|
|
|
// private CinemachineComposer _composer;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
private bool _paused;
|
|
|
|
|
|
private float _time;
|
|
|
|
|
|
private int _zoomLevelIndex;
|
2021-03-10 09:18:07 +00:00
|
|
|
|
private Entity _currentEntity;
|
2021-01-14 23:47:08 +00:00
|
|
|
|
|
2021-01-05 02:56:17 +00:00
|
|
|
|
// private ShipInput _shipInput;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
private float2 _entityYawPitch;
|
2020-12-22 03:13:43 +00:00
|
|
|
|
private float3 _viewDirection;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
private (HardpointData[] hardpoints, Transform[] barrels, PlaceUIElementWorldspace crosshair)[] _articulationGroups;
|
|
|
|
|
|
private (LockWeapon targetLock, PlaceUIElementWorldspace indicator, Rotate spin)[] _lockingIndicators;
|
|
|
|
|
|
private Dictionary<Entity, VisibleTargetIndicator> _visibleHostileIndicators = new Dictionary<Entity, VisibleTargetIndicator>();
|
|
|
|
|
|
private Dictionary<Entity, VisibleTargetIndicator> _visibleFriendlyIndicators = new Dictionary<Entity, VisibleTargetIndicator>();
|
|
|
|
|
|
private List<IDisposable> _shipSubscriptions = new List<IDisposable>();
|
|
|
|
|
|
private List<IDisposable> _targetSubscriptions = new List<IDisposable>();
|
|
|
|
|
|
private float _severeHeatstrokePhase;
|
|
|
|
|
|
private bool _uiHidden;
|
|
|
|
|
|
private bool _menuShown;
|
|
|
|
|
|
private List<ActionBarSlot> _actionBarSlots = new List<ActionBarSlot>();
|
|
|
|
|
|
private List<InputAction> _actionBarActions = new List<InputAction>();
|
|
|
|
|
|
private float _hitMarkerTime;
|
|
|
|
|
|
|
2021-03-27 10:03:24 +00:00
|
|
|
|
public AetheriaInput Input { get; private set; }
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public EquippedDockingBay DockingBay { get; private set; }
|
|
|
|
|
|
public Entity DockedEntity { get; private set; }
|
|
|
|
|
|
//if better place, please move
|
|
|
|
|
|
public Entity TowingStation { get; private set; }
|
|
|
|
|
|
|
|
|
|
|
|
public ZoneEnvironment CurrentEnvironment
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
return Settings.DefaultEnvironment;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-07-21 21:40:44 +01:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public Entity CurrentEntity
|
2021-03-06 02:42:19 +00:00
|
|
|
|
{
|
2021-03-10 09:18:07 +00:00
|
|
|
|
get => _currentEntity;
|
|
|
|
|
|
set => _currentEntity = value;
|
2021-03-06 02:42:19 +00:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
public ItemManager ItemManager { get; private set; }
|
|
|
|
|
|
public Zone Zone { get; private set; }
|
|
|
|
|
|
|
|
|
|
|
|
private readonly (float2 direction, string name)[] _directions = {
|
|
|
|
|
|
(float2(0, 1), "Front"),
|
|
|
|
|
|
(float2(1, 0), "Right"),
|
|
|
|
|
|
(float2(-1, 0), "Left"),
|
|
|
|
|
|
(float2(0, -1), "Rear")
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
public DragObject DragObject { get; private set; }
|
|
|
|
|
|
private Func<DragObject, bool> _endDragCallback;
|
|
|
|
|
|
|
|
|
|
|
|
private List<Story> _stories = new List<Story>();
|
|
|
|
|
|
|
|
|
|
|
|
public EntitySettings NewEntitySettings
|
|
|
|
|
|
{
|
|
|
|
|
|
get => MessagePackSerializer.Deserialize<EntitySettings>(MessagePackSerializer.Serialize(Settings.GameplaySettings.DefaultEntitySettings));
|
|
|
|
|
|
}
|
2026-06-21 08:56:07 +01:00
|
|
|
|
|
Land RunSave and the one-cache run and player lifecycle (Cut 10)
Owner: ActionGameManager composes the process's one cache. Its CultCache
getter attaches the catalog, GameData/run.cc and GameData/player.cc on
first access, in either scene, and nothing reopens it.
- RunSave (SavedGame.cs) replaces the SavedGame constructor:
- Capture builds the live run as plain documents and writes nothing. Zone
contents are `zone.Contents?.PackZone() ?? zone.PackedContents`, so a
zone generated earlier but not loaded this session keeps its contents
and its orbit and body records instead of being erased by the next save.
- Commit is the only writer of SavedGame and SavedZone: one Commit with
zone i at `savedzone-{i}`, SavedGame.Zones set to those refs, and every
stored SavedZone outside that set removed. Orbits and bodies staged by
zone generation land in the same single-file write.
- Clear removes every run record in one Commit.
- Run begins: New Game's first statement is RunSave.Clear. Run saves:
ActionGameManager.SaveRun at wormhole arrival and quit. Run ends: Die
clears the run, then saves settings.
- Run exists only as the run store's SavedGame global: MainMenu enables
Continue from it (and logs when the run store holds records but no
SavedGame), and StartGame reads it once to pick the new-run or resume
branch.
- PlayerSettings: the getter returns the player store's global and, on
first launch, commits GetDefaultPlayerSettings; it is the only creator.
SavePlayerSettings commits the instance.
- InputDisplayLayout reads its InputLayout from the catalog by the layout
asset's name and throws when the catalog has none.
Tests: new RunSaveTests (5): RepeatedSavesKeepRecordCountConstant,
SaveRemovesZonesNoLongerInTheRun, SaveWritesOnlyTheRunStore,
ResumeThenSaveKeepsUnloadedZoneContents, ClearRemovesEveryRunRecord. Each
fails with its rule mutated out.
Builds: headless, 11 tests pass, Unity batchmode compile reports no error CS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:00:27 +02:00
|
|
|
|
private void OnApplicationQuit()
|
|
|
|
|
|
{
|
|
|
|
|
|
SaveRun();
|
|
|
|
|
|
SavePlayerSettings();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Wormhole arrival and quit save the run; a dead run has no galaxy, so it is not saved.
|
|
|
|
|
|
public void SaveRun()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (CurrentGalaxy != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var (game, zones) = RunSave.Capture(CultCache, CurrentGalaxy, Zone, DockedEntity ?? CurrentEntity, IsTutorial,
|
|
|
|
|
|
_actionBarSlots.Select(s => s.Save()).ToArray());
|
2026-09-17 04:16:23 +02:00
|
|
|
|
RunSave.Commit(CultCache, game, zones, ItemManager.Lots);
|
Land RunSave and the one-cache run and player lifecycle (Cut 10)
Owner: ActionGameManager composes the process's one cache. Its CultCache
getter attaches the catalog, GameData/run.cc and GameData/player.cc on
first access, in either scene, and nothing reopens it.
- RunSave (SavedGame.cs) replaces the SavedGame constructor:
- Capture builds the live run as plain documents and writes nothing. Zone
contents are `zone.Contents?.PackZone() ?? zone.PackedContents`, so a
zone generated earlier but not loaded this session keeps its contents
and its orbit and body records instead of being erased by the next save.
- Commit is the only writer of SavedGame and SavedZone: one Commit with
zone i at `savedzone-{i}`, SavedGame.Zones set to those refs, and every
stored SavedZone outside that set removed. Orbits and bodies staged by
zone generation land in the same single-file write.
- Clear removes every run record in one Commit.
- Run begins: New Game's first statement is RunSave.Clear. Run saves:
ActionGameManager.SaveRun at wormhole arrival and quit. Run ends: Die
clears the run, then saves settings.
- Run exists only as the run store's SavedGame global: MainMenu enables
Continue from it (and logs when the run store holds records but no
SavedGame), and StartGame reads it once to pick the new-run or resume
branch.
- PlayerSettings: the getter returns the player store's global and, on
first launch, commits GetDefaultPlayerSettings; it is the only creator.
SavePlayerSettings commits the instance.
- InputDisplayLayout reads its InputLayout from the catalog by the layout
asset's name and throws when the catalog has none.
Tests: new RunSaveTests (5): RepeatedSavesKeepRecordCountConstant,
SaveRemovesZonesNoLongerInTheRun, SaveWritesOnlyTheRunStore,
ResumeThenSaveKeepsUnloadedZoneContents, ClearRemovesEveryRunRecord. Each
fails with its rule mutated out.
Builds: headless, 11 tests pass, Unity batchmode compile reports no error CS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:00:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-25 18:09:33 +01:00
|
|
|
|
private void OnDisable()
|
2026-06-21 08:56:07 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
Input.Dispose();
|
|
|
|
|
|
ConsoleController.ClearCommands();
|
|
|
|
|
|
EntityInstance.ClearWeaponManagers();
|
2026-06-21 08:56:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-25 18:09:33 +01:00
|
|
|
|
void Start()
|
2026-06-16 20:42:42 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
Instance = this;
|
|
|
|
|
|
EntityInstance.EffectManagerParent = EffectManagerParent;
|
|
|
|
|
|
ConsoleController.MessageReceiver = this;
|
|
|
|
|
|
|
2026-09-17 04:16:23 +02:00
|
|
|
|
ItemManager = new ItemManager(CultCache, RunSave.Lots(CultCache), Settings.GameplaySettings, Debug.Log);
|
2026-08-10 14:57:50 +01:00
|
|
|
|
ZoneRenderer.ItemManager = ItemManager;
|
|
|
|
|
|
|
|
|
|
|
|
// If hiding minimap asteroids, turn them off to start with
|
|
|
|
|
|
if (!PlayerSettings.GraphicsSettings.ShowAsteroidsInMinimap)
|
|
|
|
|
|
ZoneRenderer.ShowAsteroidUI = false;
|
|
|
|
|
|
|
2026-06-25 18:09:33 +01:00
|
|
|
|
// TODO: Process Stories
|
2026-06-16 20:42:42 +01:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
#region Input Handling
|
|
|
|
|
|
|
|
|
|
|
|
Input = new AetheriaInput();
|
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
|
|
|
|
foreach (var action in PlayerSettings.InputSettings.InputActionMap) foreach (var binding in action.Value) Input.asset[action.Key].ApplyBindingOverride(binding.Key, binding.Value);
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
InputDisplayLayout.Input = Input.asset;
|
|
|
|
|
|
Input.Global.Enable();
|
|
|
|
|
|
|
|
|
|
|
|
_zoomLevelIndex = Settings.DefaultMinimapZoom;
|
|
|
|
|
|
Input.Player.MinimapZoom.performed += context =>
|
|
|
|
|
|
{
|
|
|
|
|
|
_zoomLevelIndex = (_zoomLevelIndex + 1) % Settings.MinimapZoomLevels.Length;
|
|
|
|
|
|
ZoneRenderer.MinimapDistance = Settings.MinimapZoomLevels[_zoomLevelIndex];
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Input.Global.ZoneMap.performed += context =>
|
|
|
|
|
|
{
|
|
|
|
|
|
ToggleMenuTab(MenuTab.Map);
|
|
|
|
|
|
MenuMap.Position = CurrentEntity.Position.xz;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Input.Global.Inventory.performed += context => ToggleMenuTab(MenuTab.Inventory);
|
|
|
|
|
|
|
|
|
|
|
|
Input.Global.GalaxyMap.performed += context => ToggleMenuTab(MenuTab.Galaxy);
|
|
|
|
|
|
|
|
|
|
|
|
Input.Global.Interact.performed += context =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (EventSystem.current.currentSelectedGameObject != null && EventSystem.current.currentSelectedGameObject.GetComponent<TMP_InputField>() != null) return;
|
|
|
|
|
|
if (MainMenu.gameObject.activeSelf) return;
|
|
|
|
|
|
if (CurrentEntity == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// TODO: SFX: Fail
|
|
|
|
|
|
Dialog.Clear();
|
|
|
|
|
|
Dialog.Title.text = "Can't undock. You dont have a ship!";
|
|
|
|
|
|
Dialog.Show();
|
|
|
|
|
|
Dialog.MoveToCursor();
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (CurrentEntity.Parent == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var wormhole in ZoneRenderer.WormholeInstances.Keys)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!(length(wormhole.Position - CurrentEntity.Position.xz) < Settings.GameplaySettings.WormholeExitRadius)) continue;
|
|
|
|
|
|
EnterWormhole(wormhole);
|
|
|
|
|
|
}
|
|
|
|
|
|
Dock();
|
|
|
|
|
|
}
|
|
|
|
|
|
else Undock();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Input.Global.MainMenu.performed += context =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if(Menu.gameObject.activeSelf)
|
|
|
|
|
|
ToggleMenuTab(Menu.CurrentTab);
|
|
|
|
|
|
else
|
|
|
|
|
|
ToggleFullscreenMenu(MainMenu.gameObject);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Input.Global.InputScreen.performed += context => ToggleFullscreenMenu(HelpScreen);
|
|
|
|
|
|
|
|
|
|
|
|
Input.Player.HideUI.performed += context =>
|
|
|
|
|
|
{
|
|
|
|
|
|
_uiHidden = !_uiHidden;
|
|
|
|
|
|
GameplayUI.alpha = _uiHidden ? 0 : 1;
|
|
|
|
|
|
ActionBar.gameObject.SetActive(!_uiHidden);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Input.Player.OverrideShutdown.performed += context =>
|
|
|
|
|
|
{
|
|
|
|
|
|
CurrentEntity.OverrideShutdown = !CurrentEntity.OverrideShutdown;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Input.Player.Ping.performed += context =>
|
|
|
|
|
|
{
|
|
|
|
|
|
CurrentEntity.Sensor?.Ping();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Input.Player.ToggleHeatsinks.performed += context =>
|
|
|
|
|
|
{
|
|
|
|
|
|
CurrentEntity.HeatsinksEnabled = !CurrentEntity.HeatsinksEnabled;
|
|
|
|
|
|
// TODO: SFX: Success/Fail
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Input.Player.ToggleShield.performed += context =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (CurrentEntity.Shield != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
CurrentEntity.Shield.Item.Enabled.Value = !CurrentEntity.Shield.Item.Enabled.Value;
|
|
|
|
|
|
// TODO: SFX: Success/Fail
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
#region Targeting
|
2026-06-17 01:50:15 +01:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
Input.Player.TargetReticle.performed += context =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!CurrentEntity.VisibleEnemies.Any()) return;
|
|
|
|
|
|
var underReticle = CurrentEntity.VisibleEnemies.Where(x => x != CurrentEntity)
|
|
|
|
|
|
.MaxBy(x => dot(normalize(x.Position - CurrentEntity.Position), CurrentEntity.LookDirection));
|
|
|
|
|
|
CurrentEntity.Target.Value = CurrentEntity.Target.Value == underReticle ? null : underReticle;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Input.Player.TargetNearest.performed += context =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if(CurrentEntity.VisibleEnemies.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
CurrentEntity.Target.Value = CurrentEntity.VisibleEnemies.Where(x => x != CurrentEntity)
|
|
|
|
|
|
.MaxBy(x => length(x.Position - CurrentEntity.Position));
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Input.Player.TargetNext.performed += context =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!CurrentEntity.VisibleEnemies.Any()) return;
|
|
|
|
|
|
var targets = CurrentEntity.VisibleEnemies.Where(x => x != CurrentEntity).OrderBy(x => length(x.Position - CurrentEntity.Position)).ToArray();
|
|
|
|
|
|
var currentTargetIndex = Array.IndexOf(targets, CurrentEntity.Target.Value);
|
|
|
|
|
|
CurrentEntity.Target.Value = targets[(currentTargetIndex + 1) % targets.Length];
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Input.Player.TargetPrevious.performed += context =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!CurrentEntity.VisibleEnemies.Any()) return;
|
|
|
|
|
|
var targets = CurrentEntity.VisibleEnemies.Where(x => x != CurrentEntity).OrderBy(x => length(x.Position - CurrentEntity.Position)).ToArray();
|
|
|
|
|
|
var currentTargetIndex = Array.IndexOf(targets, CurrentEntity.Target.Value);
|
|
|
|
|
|
CurrentEntity.Target.Value = targets[(currentTargetIndex + targets.Length - 1) % targets.Length];
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#region Action Bar
|
|
|
|
|
|
|
|
|
|
|
|
ActionBarSlot createBinding(string controlPath)
|
|
|
|
|
|
{
|
|
|
|
|
|
var action = new InputAction(binding: controlPath);
|
|
|
|
|
|
_actionBarActions.Add(action);
|
|
|
|
|
|
var slot = Instantiate(ActionBarSlot, ActionBar);
|
|
|
|
|
|
slot.Binding = null;
|
|
|
|
|
|
_actionBarSlots.Add(slot);
|
|
|
|
|
|
action.started += context => slot.Binding?.Activate();
|
|
|
|
|
|
action.canceled += context => slot.Binding?.Deactivate();
|
|
|
|
|
|
|
|
|
|
|
|
var shortName = controlPath.Substring(controlPath.LastIndexOf('/') + 1);
|
|
|
|
|
|
var sprite = Resources.Load<Sprite>($"Sprites/Input/{shortName}");
|
|
|
|
|
|
if (sprite != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
slot.InputIcon.sprite = sprite;
|
|
|
|
|
|
slot.InputLabel.gameObject.SetActive(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
slot.InputLabel.text = shortName;
|
|
|
|
|
|
slot.InputIcon.gameObject.SetActive(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
slot.PointerEnterTrigger.OnPointerEnterAsObservable().Subscribe(_ =>
|
|
|
|
|
|
{
|
|
|
|
|
|
//Debug.Log($"Pointer entered action bar slot {controlPath}");
|
|
|
|
|
|
RegisterDragTarget(dragAction =>
|
|
|
|
|
|
{
|
|
|
|
|
|
//Debug.Log("Registering binding!");
|
|
|
|
|
|
switch (dragAction)
|
|
|
|
|
|
{
|
|
|
|
|
|
case EquippedItemDragObject equippedItemDragAction:
|
|
|
|
|
|
var trigger = equippedItemDragAction.EquippedItem.GetBehavior<IActivatedBehavior>();
|
|
|
|
|
|
if (trigger == null) return false;
|
|
|
|
|
|
slot.Binding = new ActionBarGearBinding(CurrentEntity, slot, equippedItemDragAction.EquippedItem, trigger);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
case ItemInstanceDragObject itemInstanceDragAction:
|
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 (!(ItemManager.GetData(itemInstanceDragAction.Item) is ConsumableItemData consumable)) return false;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
slot.Binding = new ActionBarConsumableBinding(CurrentEntity, slot, consumable);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
case WeaponGroupDragObject weaponGroupDragAction:
|
|
|
|
|
|
slot.Binding = new ActionBarWeaponGroupBinding(CurrentEntity, slot, weaponGroupDragAction.Group);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw new ArgumentOutOfRangeException(nameof(dragAction));
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
slot.PointerExitTrigger.OnPointerExitAsObservable().Subscribe(_ =>
|
|
|
|
|
|
{
|
|
|
|
|
|
//Debug.Log($"Pointer exited action bar slot {controlPath}");
|
|
|
|
|
|
UnregisterDragTarget();
|
|
|
|
|
|
});
|
|
|
|
|
|
return slot;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var bindings = PlayerSettings.InputSettings.ActionBarInputs.OrderBy(i => i)
|
|
|
|
|
|
.Select(createBinding).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
2026-06-25 18:09:33 +01:00
|
|
|
|
StartGame();
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
//if (!PlayerSettings.InputSettings.ActionBarInputs.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
var newbinds = Enumerable.Range(0, 64)//CurrentEntity.WeaponGroups
|
|
|
|
|
|
.Zip(
|
|
|
|
|
|
bindings,
|
|
|
|
|
|
(i, slot) =>
|
|
|
|
|
|
slot.Binding = new ActionBarWeaponGroupBinding(CurrentEntity, slot, i)
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ConsoleController.AddCommand("revealzones",
|
|
|
|
|
|
_ =>
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var zones in CurrentGalaxy.Zones
|
|
|
|
|
|
.Where(z=>!CurrentGalaxy.DiscoveredZones.Contains(z))
|
|
|
|
|
|
.GroupBy(z=>z.Distance[CurrentGalaxy.Entrance])
|
|
|
|
|
|
.OrderBy(g=>g.Key))
|
|
|
|
|
|
{
|
|
|
|
|
|
SectorMap.QueueZoneReveal(zones);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
ConsoleController.AddCommand("give",
|
|
|
|
|
|
args =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var itemName = string.Join(" ", args);
|
|
|
|
|
|
var item = ItemManager.ItemData.GetAll<EquippableItemData>()
|
|
|
|
|
|
.FirstOrDefault(itemData => string.Equals(itemData.Name, itemName, StringComparison.InvariantCultureIgnoreCase));
|
|
|
|
|
|
if (item != null)
|
|
|
|
|
|
{
|
2026-09-17 04:16:23 +02:00
|
|
|
|
_currentEntity.CargoBays.First().TryStore(ItemManager.CreateInstance(ItemManager.CreateLot(item, default, .95f)));
|
2026-08-10 14:57:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
ConsoleController.AddCommand("trackmissile",
|
|
|
|
|
|
_ =>
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var missileManager in FindObjectsByType<GuidedProjectileManager>(FindObjectsSortMode.None))
|
|
|
|
|
|
{
|
|
|
|
|
|
missileManager.OnFireGuided.Where(x => x.source == _currentEntity).Take(1).Subscribe(x =>
|
|
|
|
|
|
{
|
|
|
|
|
|
FollowCamera.Follow = x.missile.transform;
|
|
|
|
|
|
FollowCamera.LookAt = x.target;
|
|
|
|
|
|
x.missile.OnKill += () =>
|
|
|
|
|
|
{
|
|
|
|
|
|
FollowCamera.LookAt = ZoneRenderer.EntityInstances[CurrentEntity].LookAtPoint;
|
|
|
|
|
|
FollowCamera.Follow = ZoneRenderer.EntityInstances[CurrentEntity].transform;
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
ConsoleController.AddCommand("spawnturret",
|
|
|
|
|
|
_ =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var nearestFaction = CurrentGalaxy.Factions.MinBy(f => CurrentGalaxy.HomeZones[f].Distance[Zone.GalaxyZone]);
|
|
|
|
|
|
|
|
|
|
|
|
var loadoutGenerator = IsTutorial ? new LoadoutGenerator(
|
|
|
|
|
|
ref ItemManager.Random,
|
|
|
|
|
|
ItemManager,
|
|
|
|
|
|
CurrentGalaxy,
|
|
|
|
|
|
Zone.GalaxyZone,
|
|
|
|
|
|
nearestFaction,
|
|
|
|
|
|
.5f) : new LoadoutGenerator(
|
|
|
|
|
|
ref ItemManager.Random,
|
|
|
|
|
|
ItemManager,
|
|
|
|
|
|
CurrentGalaxy,
|
|
|
|
|
|
Zone.GalaxyZone,
|
|
|
|
|
|
nearestFaction,
|
|
|
|
|
|
.5f);
|
|
|
|
|
|
|
2026-09-17 03:42:12 +02:00
|
|
|
|
var turret = EntitySerializer.Unpack(ItemManager, Zone, loadoutGenerator.GenerateTurretLoadout());
|
2026-08-10 14:57:50 +01:00
|
|
|
|
turret.Position.xz = _currentEntity.Position.xz +
|
|
|
|
|
|
ItemManager.Random.NextFloat2Direction() * ItemManager.Random.NextFloat(50, 500);
|
|
|
|
|
|
turret.Zone = Zone;
|
|
|
|
|
|
Zone.Entities.Add(turret);
|
|
|
|
|
|
turret.Activate();
|
|
|
|
|
|
});
|
|
|
|
|
|
//Temporary, or not
|
|
|
|
|
|
ConsoleController.AddCommand("tow", _ => TowShip());
|
Make loadouts authored catalog presets with an editor capture command
Loadouts are ship presets (variants) for spawners, not a player feature.
- Route Loadout to the catalog; the player store no longer holds it.
- Materialize takes no credits: affordability, the charge and Loadouts.Price
are deleted, since only the player menu priced loadouts. Availability,
key-order product choice, all-or-nothing failures and the weapon-group
rules stay.
- Delete the InventoryPanel Save Loadout and Restore Loadout entries and
RestoreLoadout, which also removes its docking-bay overwrite.
- The editor opens the catalog writable in the one process cache, and the
editor-only console command capturepreset "<name>" [replace] captures the
piloted ship. Loadouts.Commit keys a preset by name, refuses a same-name
replace unless asked, and commits conditionally on that key. The single-file
store then lands only that record onto the file as it is on disk. An
unconditional commit would re-serialize every live catalog instance,
including any that play has mutated.
- Tests: capture -> commit -> fresh reopen round-trip, explicit replace with
no duplicate, and a preset commit that does not persist a mutated live
Faction. The money-only tests are removed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:09:43 +02:00
|
|
|
|
|
Capture presets through their own cache; keep the catalog read-only
Opening the process cache's catalog writable in the editor was the wrong cut.
It made catalog read-only only by convention, so a stray catalog write in play
succeeded and a later flush could land play-mutated instances in Aetheria.cc.
- The process cache opens the catalog read-only in every build again, so the
missing-global check runs for it as it did before db83cd29. The writable
empty-seed exemption from ab4ced0f stays for AetherDb and fixtures.
- Loadouts.Commit(catalogPath, loadout, replace) is the only preset writer. It
opens its own short-lived cache over the catalog, writable, commits the one
preset conditionally on its key and disposes it. No other cache or instance
is touched. The running game sees the preset only after the catalog reloads.
CultLib at a0813c6 allows this: a read-only single-file store takes no lock
and holds no handle, and the writer's commit takes the exclusive sidecar
<path>.lock and replaces the file atomically.
- Capture refuses, writing nothing, when Aetheria.cc is missing (it never
creates the catalog), when a preset of that name exists under another key
(CultCache does not reject duplicate names on write), or when the key holds a
preset and replace is not given.
- The console refuses a command with any character outside letters, digits,
spaces and hyphens instead of stripping it, so "Mk.II" can no longer become
"MkII". capturepreset refuses a second argument other than replace, so an
unquoted capturepreset My Ship refuses instead of saving "My".
- The capture log line says the session sees the preset after a reload, and
that a Studio session opened earlier overwrites captured presets on save.
- Tests: capture while the process cache holds the catalog read-only leaves it
unchanged; a missing catalog refuses and creates nothing; a same name under a
Studio-minted key refuses, with or without replace; a game-shaped read-only
open missing a global throws. The existing preset tests now commit through
the catalog path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:32:52 +02:00
|
|
|
|
// Editor only: capturepreset "<name>" [replace] writes the piloted ship as a catalog preset. A multi-word name must
|
|
|
|
|
|
// be quoted; any other second argument than replace is refused rather than dropped.
|
Make loadouts authored catalog presets with an editor capture command
Loadouts are ship presets (variants) for spawners, not a player feature.
- Route Loadout to the catalog; the player store no longer holds it.
- Materialize takes no credits: affordability, the charge and Loadouts.Price
are deleted, since only the player menu priced loadouts. Availability,
key-order product choice, all-or-nothing failures and the weapon-group
rules stay.
- Delete the InventoryPanel Save Loadout and Restore Loadout entries and
RestoreLoadout, which also removes its docking-bay overwrite.
- The editor opens the catalog writable in the one process cache, and the
editor-only console command capturepreset "<name>" [replace] captures the
piloted ship. Loadouts.Commit keys a preset by name, refuses a same-name
replace unless asked, and commits conditionally on that key. The single-file
store then lands only that record onto the file as it is on disk. An
unconditional commit would re-serialize every live catalog instance,
including any that play has mutated.
- Tests: capture -> commit -> fresh reopen round-trip, explicit replace with
no duplicate, and a preset commit that does not persist a mutated live
Faction. The money-only tests are removed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:09:43 +02:00
|
|
|
|
if (Application.isEditor)
|
|
|
|
|
|
ConsoleController.AddCommand("capturepreset", args =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var console = ConsoleController.Instance;
|
|
|
|
|
|
var name = args.Length > 0 ? args[0] : "";
|
Capture presets through their own cache; keep the catalog read-only
Opening the process cache's catalog writable in the editor was the wrong cut.
It made catalog read-only only by convention, so a stray catalog write in play
succeeded and a later flush could land play-mutated instances in Aetheria.cc.
- The process cache opens the catalog read-only in every build again, so the
missing-global check runs for it as it did before db83cd29. The writable
empty-seed exemption from ab4ced0f stays for AetherDb and fixtures.
- Loadouts.Commit(catalogPath, loadout, replace) is the only preset writer. It
opens its own short-lived cache over the catalog, writable, commits the one
preset conditionally on its key and disposes it. No other cache or instance
is touched. The running game sees the preset only after the catalog reloads.
CultLib at a0813c6 allows this: a read-only single-file store takes no lock
and holds no handle, and the writer's commit takes the exclusive sidecar
<path>.lock and replaces the file atomically.
- Capture refuses, writing nothing, when Aetheria.cc is missing (it never
creates the catalog), when a preset of that name exists under another key
(CultCache does not reject duplicate names on write), or when the key holds a
preset and replace is not given.
- The console refuses a command with any character outside letters, digits,
spaces and hyphens instead of stripping it, so "Mk.II" can no longer become
"MkII". capturepreset refuses a second argument other than replace, so an
unquoted capturepreset My Ship refuses instead of saving "My".
- The capture log line says the session sees the preset after a reload, and
that a Studio session opened earlier overwrites captured presets on save.
- Tests: capture while the process cache holds the catalog read-only leaves it
unchanged; a missing catalog refuses and creates nothing; a same name under a
Studio-minted key refuses, with or without replace; a game-shaped read-only
open missing a global throws. The existing preset tests now commit through
the catalog path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:32:52 +02:00
|
|
|
|
var replace = args.Length == 2 && args[1] == "replace";
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(name) || args.Length > 2 || args.Length == 2 && !replace)
|
|
|
|
|
|
{
|
|
|
|
|
|
console.AppendLogLine("usage: capturepreset \"<name>\" [replace] (quote a name with spaces)");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
Make loadouts authored catalog presets with an editor capture command
Loadouts are ship presets (variants) for spawners, not a player feature.
- Route Loadout to the catalog; the player store no longer holds it.
- Materialize takes no credits: affordability, the charge and Loadouts.Price
are deleted, since only the player menu priced loadouts. Availability,
key-order product choice, all-or-nothing failures and the weapon-group
rules stay.
- Delete the InventoryPanel Save Loadout and Restore Loadout entries and
RestoreLoadout, which also removes its docking-bay overwrite.
- The editor opens the catalog writable in the one process cache, and the
editor-only console command capturepreset "<name>" [replace] captures the
piloted ship. Loadouts.Commit keys a preset by name, refuses a same-name
replace unless asked, and commits conditionally on that key. The single-file
store then lands only that record onto the file as it is on disk. An
unconditional commit would re-serialize every live catalog instance,
including any that play has mutated.
- Tests: capture -> commit -> fresh reopen round-trip, explicit replace with
no duplicate, and a preset commit that does not persist a mutated live
Faction. The money-only tests are removed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:09:43 +02:00
|
|
|
|
if (!(_currentEntity is Ship ship)) { console.AppendLogLine("capturepreset: pilot a ship first"); return; }
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
Capture presets through their own cache; keep the catalog read-only
Opening the process cache's catalog writable in the editor was the wrong cut.
It made catalog read-only only by convention, so a stray catalog write in play
succeeded and a later flush could land play-mutated instances in Aetheria.cc.
- The process cache opens the catalog read-only in every build again, so the
missing-global check runs for it as it did before db83cd29. The writable
empty-seed exemption from ab4ced0f stays for AetherDb and fixtures.
- Loadouts.Commit(catalogPath, loadout, replace) is the only preset writer. It
opens its own short-lived cache over the catalog, writable, commits the one
preset conditionally on its key and disposes it. No other cache or instance
is touched. The running game sees the preset only after the catalog reloads.
CultLib at a0813c6 allows this: a read-only single-file store takes no lock
and holds no handle, and the writer's commit takes the exclusive sidecar
<path>.lock and replaces the file atomically.
- Capture refuses, writing nothing, when Aetheria.cc is missing (it never
creates the catalog), when a preset of that name exists under another key
(CultCache does not reject duplicate names on write), or when the key holds a
preset and replace is not given.
- The console refuses a command with any character outside letters, digits,
spaces and hyphens instead of stripping it, so "Mk.II" can no longer become
"MkII". capturepreset refuses a second argument other than replace, so an
unquoted capturepreset My Ship refuses instead of saving "My".
- The capture log line says the session sees the preset after a reload, and
that a Studio session opened earlier overwrites captured presets on save.
- Tests: capture while the process cache holds the catalog read-only leaves it
unchanged; a missing catalog refuses and creates nothing; a same name under a
Studio-minted key refuses, with or without replace; a game-shaped read-only
open missing a global throws. The existing preset tests now commit through
the catalog path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:32:52 +02:00
|
|
|
|
var written = Loadouts.Commit(CatalogPath, Loadouts.Capture(ItemManager, ship, name), replace);
|
|
|
|
|
|
console.AppendLogLine(written
|
|
|
|
|
|
? $"Preset '{name}' written to {CatalogPath}. This session sees it after the catalog reloads (next launch). " +
|
|
|
|
|
|
"Reopen CultCache Studio before saving there: a Studio session opened earlier overwrites captured presets."
|
|
|
|
|
|
: $"Preset '{name}' changed on disk since load; nothing written");
|
Make loadouts authored catalog presets with an editor capture command
Loadouts are ship presets (variants) for spawners, not a player feature.
- Route Loadout to the catalog; the player store no longer holds it.
- Materialize takes no credits: affordability, the charge and Loadouts.Price
are deleted, since only the player menu priced loadouts. Availability,
key-order product choice, all-or-nothing failures and the weapon-group
rules stay.
- Delete the InventoryPanel Save Loadout and Restore Loadout entries and
RestoreLoadout, which also removes its docking-bay overwrite.
- The editor opens the catalog writable in the one process cache, and the
editor-only console command capturepreset "<name>" [replace] captures the
piloted ship. Loadouts.Commit keys a preset by name, refuses a same-name
replace unless asked, and commits conditionally on that key. The single-file
store then lands only that record onto the file as it is on disk. An
unconditional commit would re-serialize every live catalog instance,
including any that play has mutated.
- Tests: capture -> commit -> fresh reopen round-trip, explicit replace with
no duplicate, and a preset commit that does not persist a mutated live
Faction. The money-only tests are removed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:09:43 +02:00
|
|
|
|
}
|
|
|
|
|
|
catch (InvalidOperationException e)
|
|
|
|
|
|
{
|
|
|
|
|
|
console.AppendLogLine(e.Message);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2026-08-10 14:57:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void BeginDrag(DragObject dragObject)
|
|
|
|
|
|
{
|
|
|
|
|
|
this.DragObject = dragObject;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void RegisterDragTarget(Func<DragObject, bool> onEndDrag)
|
|
|
|
|
|
{
|
|
|
|
|
|
_endDragCallback = onEndDrag;
|
2026-06-17 01:50:15 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public void UnregisterDragTarget()
|
2026-06-17 01:50:15 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
_endDragCallback = null;
|
2026-06-17 01:50:15 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public bool EndDrag()
|
2026-06-17 01:50:15 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
var success = _endDragCallback?.Invoke(DragObject);
|
|
|
|
|
|
DragObject = null;
|
|
|
|
|
|
return success ?? false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void EnablePlayerInput()
|
|
|
|
|
|
{
|
|
|
|
|
|
Cursor.lockState = CursorLockMode.Locked;
|
|
|
|
|
|
Input.Player.Enable();
|
|
|
|
|
|
foreach (var a in _actionBarActions) a.Enable();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void DisablePlayerInput()
|
|
|
|
|
|
{
|
|
|
|
|
|
Cursor.lockState = CursorLockMode.None;
|
|
|
|
|
|
Input.Player.Disable();
|
|
|
|
|
|
foreach (var a in _actionBarActions) a.Disable();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void EnterWormhole(Wormhole wormhole)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!(CurrentEntity is Ship ship) || ship.WormholeAnimationInProgress) return;
|
|
|
|
|
|
// var wormholeCameraFollow = new GameObject("Wormhole Camera Follow").transform;
|
|
|
|
|
|
// wormholeCameraFollow.position = new Vector3(wormhole.Position.x, -50, wormhole.Position.y);
|
|
|
|
|
|
// wormholeCameraFollow.rotation = Quaternion.LookRotation(Vector3.down, ship.LookDirection);
|
|
|
|
|
|
// WormholeCamera.enabled = true;
|
|
|
|
|
|
// WormholeCamera.Follow = wormholeCameraFollow;
|
|
|
|
|
|
// FollowCamera.enabled = false;
|
|
|
|
|
|
ship.EnterWormhole(wormhole.Position);
|
|
|
|
|
|
ship.OnEnteredWormhole += () =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var oldZone = Zone;
|
|
|
|
|
|
PopulateLevel(wormhole.Target);
|
|
|
|
|
|
foreach (var zone in wormhole.Target.AdjacentZones)
|
|
|
|
|
|
CurrentGalaxy.DiscoveredZones.Add(zone);
|
|
|
|
|
|
SectorMap.QueueZoneReveal(wormhole.Target.AdjacentZones);
|
|
|
|
|
|
ship.ExitWormhole(ZoneRenderer.WormholeInstances.Keys.First(w => w.Target == oldZone.GalaxyZone).Position,
|
|
|
|
|
|
Settings.GameplaySettings.WormholeExitVelocity * ItemManager.Random.NextFloat2Direction());
|
|
|
|
|
|
CurrentEntity.Zone = Zone;
|
Land RunSave and the one-cache run and player lifecycle (Cut 10)
Owner: ActionGameManager composes the process's one cache. Its CultCache
getter attaches the catalog, GameData/run.cc and GameData/player.cc on
first access, in either scene, and nothing reopens it.
- RunSave (SavedGame.cs) replaces the SavedGame constructor:
- Capture builds the live run as plain documents and writes nothing. Zone
contents are `zone.Contents?.PackZone() ?? zone.PackedContents`, so a
zone generated earlier but not loaded this session keeps its contents
and its orbit and body records instead of being erased by the next save.
- Commit is the only writer of SavedGame and SavedZone: one Commit with
zone i at `savedzone-{i}`, SavedGame.Zones set to those refs, and every
stored SavedZone outside that set removed. Orbits and bodies staged by
zone generation land in the same single-file write.
- Clear removes every run record in one Commit.
- Run begins: New Game's first statement is RunSave.Clear. Run saves:
ActionGameManager.SaveRun at wormhole arrival and quit. Run ends: Die
clears the run, then saves settings.
- Run exists only as the run store's SavedGame global: MainMenu enables
Continue from it (and logs when the run store holds records but no
SavedGame), and StartGame reads it once to pick the new-run or resume
branch.
- PlayerSettings: the getter returns the player store's global and, on
first launch, commits GetDefaultPlayerSettings; it is the only creator.
SavePlayerSettings commits the instance.
- InputDisplayLayout reads its InputLayout from the catalog by the layout
asset's name and throws when the catalog has none.
Tests: new RunSaveTests (5): RepeatedSavesKeepRecordCountConstant,
SaveRemovesZonesNoLongerInTheRun, SaveWritesOnlyTheRunStore,
ResumeThenSaveKeepsUnloadedZoneContents, ClearRemovesEveryRunRecord. Each
fails with its rule mutated out.
Builds: headless, 11 tests pass, Unity batchmode compile reports no error CS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:00:27 +02:00
|
|
|
|
SaveRun();
|
2026-08-10 14:57:50 +01:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void PopulateLevel(GalaxyZone galaxyZone)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (galaxyZone == null) throw new ArgumentNullException(nameof(galaxyZone));
|
|
|
|
|
|
|
|
|
|
|
|
if (galaxyZone.Contents == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
galaxyZone.PackedContents ??= ZoneGenerator.GenerateZone(
|
|
|
|
|
|
ItemManager,
|
|
|
|
|
|
Settings.ZoneSettings,
|
|
|
|
|
|
CurrentGalaxy,
|
|
|
|
|
|
galaxyZone,
|
|
|
|
|
|
IsTutorial
|
|
|
|
|
|
);
|
|
|
|
|
|
galaxyZone.Contents = new Zone(ItemManager, Settings.PlanetSettings, galaxyZone.PackedContents, galaxyZone, CurrentGalaxy);
|
|
|
|
|
|
}
|
|
|
|
|
|
Zone = galaxyZone.Contents;
|
|
|
|
|
|
PlayMusic(MusicType.Overworld);
|
|
|
|
|
|
|
|
|
|
|
|
Zone.Log = s => Debug.Log($"Zone: {s}");
|
|
|
|
|
|
|
|
|
|
|
|
if (CurrentEntity != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
CurrentEntity.Deactivate();
|
|
|
|
|
|
CurrentEntity.Zone.Entities.Remove(CurrentEntity);
|
|
|
|
|
|
CurrentEntity.Zone = Zone;
|
|
|
|
|
|
Zone.Entities.Add(CurrentEntity);
|
|
|
|
|
|
CurrentEntity.Activate();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ZoneRenderer.LoadZone(Zone);
|
|
|
|
|
|
|
|
|
|
|
|
if (CurrentEntity != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
UnbindEntity();
|
|
|
|
|
|
BindToEntity(CurrentEntity);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ToggleFullscreenMenu(GameObject menu)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (EventSystem.current != null && EventSystem.current.currentSelectedGameObject != null && EventSystem.current.currentSelectedGameObject.GetComponent<TMP_InputField>() != null) return;
|
|
|
|
|
|
if (CurrentEntity == null) return;
|
|
|
|
|
|
if (menu.activeSelf)
|
|
|
|
|
|
{
|
|
|
|
|
|
_paused = false;
|
|
|
|
|
|
menu.SetActive(false);
|
|
|
|
|
|
UiRoot.SetActive(true);
|
|
|
|
|
|
if (!_menuShown)
|
|
|
|
|
|
{
|
|
|
|
|
|
EnablePlayerInput();
|
|
|
|
|
|
UpdatePlayerPanel();
|
|
|
|
|
|
UpdateTargetPanel(CurrentEntity.Target.Value);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
_paused = true;
|
|
|
|
|
|
menu.SetActive(true);
|
|
|
|
|
|
UiRoot.SetActive(false);
|
|
|
|
|
|
_menuShown = Menu.gameObject.activeSelf;
|
|
|
|
|
|
if (!_menuShown) DisablePlayerInput();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ToggleMenuTab(MenuTab tab)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (EventSystem.current.currentSelectedGameObject != null && EventSystem.current.currentSelectedGameObject.GetComponent<TMP_InputField>() != null) return;
|
|
|
|
|
|
if (MainMenu.gameObject.activeSelf) return;
|
|
|
|
|
|
if (Menu.gameObject.activeSelf && Menu.CurrentTab == tab)
|
|
|
|
|
|
{
|
|
|
|
|
|
Menu.gameObject.SetActive(false);
|
|
|
|
|
|
if (CurrentEntity != null && CurrentEntity.Parent == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
EnablePlayerInput();
|
|
|
|
|
|
UpdatePlayerPanel();
|
|
|
|
|
|
UpdateTargetPanel(CurrentEntity.Target.Value);
|
|
|
|
|
|
GameplayUI.gameObject.SetActive(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DisablePlayerInput();
|
|
|
|
|
|
Menu.ShowTab(tab);
|
|
|
|
|
|
GameplayUI.gameObject.SetActive(false);
|
2026-06-17 01:50:15 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-25 18:09:33 +01:00
|
|
|
|
private void StartGame()
|
2026-06-17 01:50:15 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (CurrentGalaxy != null)
|
|
|
|
|
|
{
|
Land RunSave and the one-cache run and player lifecycle (Cut 10)
Owner: ActionGameManager composes the process's one cache. Its CultCache
getter attaches the catalog, GameData/run.cc and GameData/player.cc on
first access, in either scene, and nothing reopens it.
- RunSave (SavedGame.cs) replaces the SavedGame constructor:
- Capture builds the live run as plain documents and writes nothing. Zone
contents are `zone.Contents?.PackZone() ?? zone.PackedContents`, so a
zone generated earlier but not loaded this session keeps its contents
and its orbit and body records instead of being erased by the next save.
- Commit is the only writer of SavedGame and SavedZone: one Commit with
zone i at `savedzone-{i}`, SavedGame.Zones set to those refs, and every
stored SavedZone outside that set removed. Orbits and bodies staged by
zone generation land in the same single-file write.
- Clear removes every run record in one Commit.
- Run begins: New Game's first statement is RunSave.Clear. Run saves:
ActionGameManager.SaveRun at wormhole arrival and quit. Run ends: Die
clears the run, then saves settings.
- Run exists only as the run store's SavedGame global: MainMenu enables
Continue from it (and logs when the run store holds records but no
SavedGame), and StartGame reads it once to pick the new-run or resume
branch.
- PlayerSettings: the getter returns the player store's global and, on
first launch, commits GetDefaultPlayerSettings; it is the only creator.
SavePlayerSettings commits the instance.
- InputDisplayLayout reads its InputLayout from the catalog by the layout
asset's name and throws when the catalog has none.
Tests: new RunSaveTests (5): RepeatedSavesKeepRecordCountConstant,
SaveRemovesZonesNoLongerInTheRun, SaveWritesOnlyTheRunStore,
ResumeThenSaveKeepsUnloadedZoneContents, ClearRemovesEveryRunRecord. Each
fails with its rule mutated out.
Builds: headless, 11 tests pass, Unity batchmode compile reports no error CS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:00:27 +02:00
|
|
|
|
var saved = CultCache.GetGlobal<SavedGame>();
|
|
|
|
|
|
if (saved == null)
|
2026-08-10 14:57:50 +01:00
|
|
|
|
{
|
|
|
|
|
|
SectorMap.QueueZoneReveal(CurrentGalaxy.Entrance.AdjacentZones.Prepend(CurrentGalaxy.Entrance));
|
|
|
|
|
|
PopulateLevel(CurrentGalaxy.Entrance);
|
|
|
|
|
|
var loadoutGenerator = new LoadoutGenerator(ref ItemManager.Random, ItemManager, CurrentGalaxy, Zone.GalaxyZone, IsTutorial ? CurrentGalaxy.ResolveFaction(Settings.TutorialGenerationSettings.ProtagonistFaction) : null, 2);
|
|
|
|
|
|
var ship = EntitySerializer.Unpack(
|
2026-09-17 03:42:12 +02:00
|
|
|
|
ItemManager,
|
|
|
|
|
|
Zone,
|
|
|
|
|
|
loadoutGenerator.GenerateShipLoadout(data => string.IsNullOrEmpty(Settings.StartingHullName) || data.Name==Settings.StartingHullName ));
|
2026-08-10 14:57:50 +01:00
|
|
|
|
((Ship) ship).IsPlayerShip = true;
|
|
|
|
|
|
ship.Position = float3.zero;
|
|
|
|
|
|
ship.Zone = Zone;
|
|
|
|
|
|
Zone.Entities.Add(ship);
|
|
|
|
|
|
ship.Activate();
|
|
|
|
|
|
BindToEntity(ship);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach(var group in CurrentGalaxy.DiscoveredZones
|
|
|
|
|
|
.GroupBy(dz=>dz.Distance[CurrentGalaxy.Entrance]))
|
|
|
|
|
|
SectorMap.QueueZoneReveal(group);
|
Land RunSave and the one-cache run and player lifecycle (Cut 10)
Owner: ActionGameManager composes the process's one cache. Its CultCache
getter attaches the catalog, GameData/run.cc and GameData/player.cc on
first access, in either scene, and nothing reopens it.
- RunSave (SavedGame.cs) replaces the SavedGame constructor:
- Capture builds the live run as plain documents and writes nothing. Zone
contents are `zone.Contents?.PackZone() ?? zone.PackedContents`, so a
zone generated earlier but not loaded this session keeps its contents
and its orbit and body records instead of being erased by the next save.
- Commit is the only writer of SavedGame and SavedZone: one Commit with
zone i at `savedzone-{i}`, SavedGame.Zones set to those refs, and every
stored SavedZone outside that set removed. Orbits and bodies staged by
zone generation land in the same single-file write.
- Clear removes every run record in one Commit.
- Run begins: New Game's first statement is RunSave.Clear. Run saves:
ActionGameManager.SaveRun at wormhole arrival and quit. Run ends: Die
clears the run, then saves settings.
- Run exists only as the run store's SavedGame global: MainMenu enables
Continue from it (and logs when the run store holds records but no
SavedGame), and StartGame reads it once to pick the new-run or resume
branch.
- PlayerSettings: the getter returns the player store's global and, on
first launch, commits GetDefaultPlayerSettings; it is the only creator.
SavePlayerSettings commits the instance.
- InputDisplayLayout reads its InputLayout from the catalog by the layout
asset's name and throws when the catalog has none.
Tests: new RunSaveTests (5): RepeatedSavesKeepRecordCountConstant,
SaveRemovesZonesNoLongerInTheRun, SaveWritesOnlyTheRunStore,
ResumeThenSaveKeepsUnloadedZoneContents, ClearRemovesEveryRunRecord. Each
fails with its rule mutated out.
Builds: headless, 11 tests pass, Unity batchmode compile reports no error CS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:00:27 +02:00
|
|
|
|
PopulateLevel(CurrentGalaxy.Zones[saved.CurrentZone]);
|
|
|
|
|
|
var targetEntity = Zone.Entities[saved.CurrentZoneEntity];
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (targetEntity is OrbitalEntity orbitalEntity)
|
|
|
|
|
|
{
|
|
|
|
|
|
CurrentEntity = targetEntity.Children.First(c => c is Ship {IsPlayerShip: true});
|
|
|
|
|
|
DoDock(orbitalEntity, orbitalEntity.DockingBays.First());
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
//StartCoroutine(IntroCutscene(targetEntity as Ship));
|
|
|
|
|
|
BindToEntity(targetEntity);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < _actionBarSlots.Count; i++)
|
|
|
|
|
|
{
|
Land RunSave and the one-cache run and player lifecycle (Cut 10)
Owner: ActionGameManager composes the process's one cache. Its CultCache
getter attaches the catalog, GameData/run.cc and GameData/player.cc on
first access, in either scene, and nothing reopens it.
- RunSave (SavedGame.cs) replaces the SavedGame constructor:
- Capture builds the live run as plain documents and writes nothing. Zone
contents are `zone.Contents?.PackZone() ?? zone.PackedContents`, so a
zone generated earlier but not loaded this session keeps its contents
and its orbit and body records instead of being erased by the next save.
- Commit is the only writer of SavedGame and SavedZone: one Commit with
zone i at `savedzone-{i}`, SavedGame.Zones set to those refs, and every
stored SavedZone outside that set removed. Orbits and bodies staged by
zone generation land in the same single-file write.
- Clear removes every run record in one Commit.
- Run begins: New Game's first statement is RunSave.Clear. Run saves:
ActionGameManager.SaveRun at wormhole arrival and quit. Run ends: Die
clears the run, then saves settings.
- Run exists only as the run store's SavedGame global: MainMenu enables
Continue from it (and logs when the run store holds records but no
SavedGame), and StartGame reads it once to pick the new-run or resume
branch.
- PlayerSettings: the getter returns the player store's global and, on
first launch, commits GetDefaultPlayerSettings; it is the only creator.
SavePlayerSettings commits the instance.
- InputDisplayLayout reads its InputLayout from the catalog by the layout
asset's name and throws when the catalog has none.
Tests: new RunSaveTests (5): RepeatedSavesKeepRecordCountConstant,
SaveRemovesZonesNoLongerInTheRun, SaveWritesOnlyTheRunStore,
ResumeThenSaveKeepsUnloadedZoneContents, ClearRemovesEveryRunRecord. Each
fails with its rule mutated out.
Builds: headless, 11 tests pass, Unity batchmode compile reports no error CS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:00:27 +02:00
|
|
|
|
_actionBarSlots[i].Restore(saved.ActionBarBindings[i], CurrentEntity);
|
2026-08-10 14:57:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-17 01:50:15 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
private IEnumerator IntroCutscene(Ship ship)
|
2026-06-16 20:42:42 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
ZoneRenderer.PerspectiveEntity = ship;
|
|
|
|
|
|
var entityPosition = ship.Position.xz;
|
|
|
|
|
|
var followOrbit = Zone.Orbits.Keys.MinBy(o => lengthsq(Zone.GetOrbitPosition(o) - entityPosition));
|
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 followPlanet = ZoneRenderer.Planets[Zone.Planets.FirstOrDefault(p => p.Value.Orbit.Key.Equals(followOrbit)).Key];
|
2026-08-10 14:57:50 +01:00
|
|
|
|
DockCamera.Follow = followPlanet.Body.transform;
|
|
|
|
|
|
var rootOrbit = followOrbit;
|
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
|
|
|
|
while (Zone.Orbits[rootOrbit].Data.Parent.IsSet())
|
|
|
|
|
|
rootOrbit = Zone.Orbits[rootOrbit].Data.Parent.Key;
|
|
|
|
|
|
var rootPlanet = ZoneRenderer.Planets[Zone.Planets.FirstOrDefault(p => p.Value.Orbit.Key.Equals(rootOrbit)).Key];
|
2026-08-10 14:57:50 +01:00
|
|
|
|
DockCamera.LookAt = rootPlanet.Body.transform;
|
|
|
|
|
|
|
|
|
|
|
|
var shipVelocity = ship.GetBehavior<VelocityLimit>().Limit;
|
|
|
|
|
|
var followOrbitPosition = Zone.GetOrbitPosition(followOrbit);
|
|
|
|
|
|
var shipDirection = normalize(Zone.GetOrbitPosition(rootOrbit) - followOrbitPosition);
|
|
|
|
|
|
ship.Position.xz = followOrbitPosition - shipDirection * shipVelocity * IntroDuration;
|
|
|
|
|
|
|
|
|
|
|
|
var startTime = Time.time;
|
|
|
|
|
|
while (Time.time - startTime < IntroDuration)
|
2026-06-29 11:48:00 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
ship.Direction = shipDirection;
|
|
|
|
|
|
ship.Velocity = shipDirection * shipVelocity;
|
|
|
|
|
|
yield return null;
|
2026-06-29 11:48:00 +01:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
BindToEntity(ship);
|
|
|
|
|
|
}
|
2026-06-29 11:48:00 +01:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public void Dock()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (CurrentEntity.Parent != null) return;
|
|
|
|
|
|
if (CurrentEntity is Ship ship)
|
2026-06-29 11:48:00 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
foreach (var entity in Zone.Entities.ToArray())
|
|
|
|
|
|
{
|
|
|
|
|
|
if (entity != CurrentEntity && lengthsq(entity.Position.xz - CurrentEntity.Position.xz) <
|
|
|
|
|
|
Settings.GameplaySettings.DockingDistance * Settings.GameplaySettings.DockingDistance)
|
|
|
|
|
|
{
|
|
|
|
|
|
var bay = entity.TryDock(ship);
|
|
|
|
|
|
if (bay != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
UnbindEntity();
|
|
|
|
|
|
DoDock(entity, bay);
|
|
|
|
|
|
// TODO: SFX: Docking
|
|
|
|
|
|
//AkSoundEngine.PostEvent("Dock", gameObject);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-29 11:48:00 +01:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
}
|
2026-06-29 11:48:00 +01:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
private void DoDock(Entity entity, EquippedDockingBay dockingBay)
|
|
|
|
|
|
{
|
|
|
|
|
|
TradeMenu.Inventory = entity.CargoBays.First();
|
|
|
|
|
|
DockedEntity = entity;
|
|
|
|
|
|
ZoneRenderer.PerspectiveEntity = DockedEntity;
|
|
|
|
|
|
DockingBay = dockingBay;
|
|
|
|
|
|
DockCamera.enabled = true;
|
|
|
|
|
|
FollowCamera.enabled = false;
|
|
|
|
|
|
var orbital = (OrbitalEntity) entity;
|
|
|
|
|
|
DockCamera.Follow = ZoneRenderer.EntityInstances[orbital].transform;
|
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 parentOrbit = Zone.Orbits[orbital.OrbitData].Data.Parent.Key;
|
|
|
|
|
|
var parentOrbitPlanet = Zone.Planets.FirstOrDefault(p => p.Value.Orbit.Key.Equals(parentOrbit)).Key;
|
|
|
|
|
|
if (parentOrbitPlanet.IsSet() && ZoneRenderer.Planets.ContainsKey(parentOrbitPlanet))
|
2026-08-10 14:57:50 +01:00
|
|
|
|
DockCamera.LookAt = ZoneRenderer.Planets[parentOrbitPlanet].Body.transform;
|
|
|
|
|
|
else DockCamera.LookAt = ZoneRenderer.ZoneRoot;
|
|
|
|
|
|
if (entity is OrbitalEntity {CanTow: true})
|
|
|
|
|
|
TowingStation = entity;
|
|
|
|
|
|
Menu.ShowTab(MenuTab.Inventory);
|
2026-06-29 11:48:00 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public void Undock()
|
2026-06-29 11:48:00 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (CurrentEntity.Parent == null) return;
|
|
|
|
|
|
if (CurrentEntity is Ship ship)
|
2026-06-29 11:48:00 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (CurrentEntity.GetBehavior<Cockpit>() == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Dialog.Clear();
|
|
|
|
|
|
Dialog.Title.text = "Can't undock. Missing cockpit component!";
|
|
|
|
|
|
Dialog.Show();
|
|
|
|
|
|
Dialog.MoveToCursor();
|
|
|
|
|
|
// TODO: SFX: Fail
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (CurrentEntity.GetBehavior<Thruster>() == null && CurrentEntity.GetBehavior<AetherDrive>() == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Dialog.Clear();
|
|
|
|
|
|
Dialog.Title.text = "Can't undock. Missing thruster component!";
|
|
|
|
|
|
Dialog.Show();
|
|
|
|
|
|
Dialog.MoveToCursor();
|
|
|
|
|
|
// TODO: SFX: Fail
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (CurrentEntity.GetBehavior<Reactor>() == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Dialog.Clear();
|
|
|
|
|
|
Dialog.Title.text = "Can't undock. Missing reactor component!";
|
|
|
|
|
|
Dialog.Show();
|
|
|
|
|
|
Dialog.MoveToCursor();
|
|
|
|
|
|
// TODO: SFX: Fail
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (CurrentEntity.Parent.TryUndock(ship))
|
|
|
|
|
|
{
|
|
|
|
|
|
BindToEntity(ship);
|
|
|
|
|
|
// TODO: SFX: Undock
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
Dialog.Title.text = "Can't undock. Must empty docking bay!";
|
|
|
|
|
|
Dialog.Show();
|
|
|
|
|
|
Dialog.MoveToCursor();
|
|
|
|
|
|
// TODO: SFX: Fail
|
|
|
|
|
|
}
|
2026-06-29 11:48:00 +01:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
}
|
2026-06-29 11:48:00 +01:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public void TowShip()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (CurrentEntity.Parent != null) return;
|
|
|
|
|
|
if (CurrentEntity is Ship ship)
|
2026-06-29 11:48:00 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (Zone.Equals(TowingStation.Zone))
|
|
|
|
|
|
{
|
|
|
|
|
|
var distance = (int)length(TowingStation.Position.xz - CurrentEntity.Position.xz);
|
|
|
|
|
|
CurrentEntity.Position.xz = TowingStation.Position.xz;
|
|
|
|
|
|
Dock();
|
|
|
|
|
|
//Debug.Log($"${distance}");
|
|
|
|
|
|
//payment = distance * TowingZoneRate;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
var distance = Zone.GalaxyZone.Distance[TowingStation.Zone.GalaxyZone];
|
|
|
|
|
|
PopulateLevel(TowingStation.Zone.GalaxyZone);
|
|
|
|
|
|
CurrentEntity.Zone = Zone;
|
|
|
|
|
|
CurrentEntity.Position.xz = TowingStation.Position.xz;
|
|
|
|
|
|
Dock();
|
|
|
|
|
|
//Debug.Log($"${distance * 1000}");
|
|
|
|
|
|
//payment = distance * TowingGalaxyRate;
|
|
|
|
|
|
}
|
2026-06-29 11:48:00 +01:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
}
|
2026-06-29 11:48:00 +01:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
private void UnbindEntity()
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var indicator in _visibleHostileIndicators) Destroy(indicator.Value.gameObject);
|
|
|
|
|
|
_visibleHostileIndicators.Clear();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var indicator in _visibleFriendlyIndicators) Destroy(indicator.Value.gameObject);
|
|
|
|
|
|
_visibleFriendlyIndicators.Clear();
|
|
|
|
|
|
|
|
|
|
|
|
if(_lockingIndicators!=null) foreach(var (_, indicator, _) in _lockingIndicators)
|
|
|
|
|
|
indicator.GetComponent<Prototype>().ReturnToPool();
|
|
|
|
|
|
DisablePlayerInput();
|
|
|
|
|
|
Cursor.lockState = CursorLockMode.None;
|
|
|
|
|
|
GameplayUI.gameObject.SetActive(false);
|
|
|
|
|
|
|
|
|
|
|
|
foreach(var subscription in _shipSubscriptions) subscription.Dispose();
|
|
|
|
|
|
_shipSubscriptions.Clear();
|
2026-06-29 11:48:00 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
private void BindToEntity(Entity entity)
|
2026-06-29 11:48:00 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (!ZoneRenderer.EntityInstances.ContainsKey(entity))
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogError($"Attempted to bind to entity {entity.Name}, but SectorRenderer has no such instance!");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CurrentEntity = entity;
|
|
|
|
|
|
DeathPost.weight = 0;
|
|
|
|
|
|
ZoneRenderer.PerspectiveEntity = CurrentEntity;
|
|
|
|
|
|
|
|
|
|
|
|
Menu.gameObject.SetActive(false);
|
|
|
|
|
|
DockedEntity = null;
|
|
|
|
|
|
DockingBay = null;
|
|
|
|
|
|
DockCamera.enabled = false;
|
|
|
|
|
|
FollowCamera.enabled = true;
|
|
|
|
|
|
|
|
|
|
|
|
if (length(CurrentEntity.Direction) > .1f)
|
|
|
|
|
|
_viewDirection = float3(CurrentEntity.Direction.x,0,CurrentEntity.Direction.y);
|
|
|
|
|
|
|
|
|
|
|
|
Cursor.lockState = CursorLockMode.Locked;
|
|
|
|
|
|
EnablePlayerInput();
|
|
|
|
|
|
GameplayUI.gameObject.SetActive(true);
|
|
|
|
|
|
ShipPanel.Display(CurrentEntity, true);
|
|
|
|
|
|
SchematicDisplay.ShowShip(CurrentEntity);
|
|
|
|
|
|
|
|
|
|
|
|
FollowCamera.LookAt = ZoneRenderer.EntityInstances[CurrentEntity].LookAtPoint;
|
|
|
|
|
|
FollowCamera.Follow = ZoneRenderer.EntityInstances[CurrentEntity].transform;
|
|
|
|
|
|
_articulationGroups = CurrentEntity.Equipment
|
|
|
|
|
|
.Where(item => item.Behaviors.Any(x => x.Data is WeaponData && !(x.Data is LauncherData)))
|
|
|
|
|
|
.GroupBy(item => ZoneRenderer.EntityInstances[CurrentEntity]
|
|
|
|
|
|
.GetBarrel(CurrentEntity.Hardpoints[item.Position.x, item.Position.y])
|
|
|
|
|
|
.GetComponentInParent<ArticulationPoint>()?.Group ?? -1)
|
|
|
|
|
|
.Select((group, index) => {
|
|
|
|
|
|
return (
|
|
|
|
|
|
group.Select(item => CurrentEntity.Hardpoints[item.Position.x, item.Position.y]).ToArray(),
|
|
|
|
|
|
group.Select(item => ZoneRenderer.EntityInstances[CurrentEntity].GetBarrel(CurrentEntity.Hardpoints[item.Position.x, item.Position.y])).ToArray(),
|
|
|
|
|
|
Crosshairs[index]
|
|
|
|
|
|
);
|
|
|
|
|
|
}).ToArray();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var crosshair in Crosshairs)
|
|
|
|
|
|
crosshair.gameObject.SetActive(false);
|
|
|
|
|
|
foreach (var group in _articulationGroups)
|
|
|
|
|
|
group.crosshair.gameObject.SetActive(true);
|
|
|
|
|
|
|
|
|
|
|
|
_shipSubscriptions.Add(CurrentEntity.TargetedByCount.Subscribe(count =>
|
|
|
|
|
|
{
|
|
|
|
|
|
PlayMusic(count > 0 ? MusicType.Combat : MusicType.Overworld);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
_shipSubscriptions.Add(CurrentEntity.Target.Subscribe(target =>
|
|
|
|
|
|
{
|
|
|
|
|
|
// Clear previous subscriptions related to currently targeted enemy
|
|
|
|
|
|
foreach(var subscription in _targetSubscriptions)
|
|
|
|
|
|
subscription.Dispose();
|
|
|
|
|
|
_targetSubscriptions.Clear();
|
|
|
|
|
|
|
|
|
|
|
|
UpdateTargetPanel(target);
|
|
|
|
|
|
if (target != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (target.Shield != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
TargetShieldsBackground.color = new Color(ShieldColor.r, ShieldColor.g, ShieldColor.b, .4f);
|
|
|
|
|
|
TargetShieldsIcon.color = ShieldColor;
|
|
|
|
|
|
TargetShieldsIcon.sprite = ShieldIcon;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
TargetShieldsBackground.color = new Color(NoShieldColor.r, NoShieldColor.g, NoShieldColor.b, .4f);
|
|
|
|
|
|
TargetShieldsIcon.color = NoShieldColor;
|
|
|
|
|
|
TargetShieldsIcon.sprite = NoShieldIcon;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Subscribe to incoming hits from the player ship to display the hit marker
|
|
|
|
|
|
_targetSubscriptions.Add(target.IncomingHit.Where(e => e == CurrentEntity).Subscribe(_ =>
|
|
|
|
|
|
{
|
|
|
|
|
|
HitMarker.SetActive(true);
|
|
|
|
|
|
_hitMarkerTime = HitMarkerDuration;
|
|
|
|
|
|
}));
|
|
|
|
|
|
}
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var hostile in CurrentEntity.VisibleEnemies)
|
2026-06-29 11:48:00 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
var indicator = HostileTargetIndicator.Instantiate<VisibleTargetIndicator>();
|
|
|
|
|
|
_visibleHostileIndicators.Add(hostile, indicator);
|
2026-06-29 11:48:00 +01:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
_shipSubscriptions.Add(CurrentEntity.VisibleEnemies.ObserveAdd().Subscribe(addEvent =>
|
2026-06-29 11:48:00 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
var indicator = HostileTargetIndicator.Instantiate<VisibleTargetIndicator>();
|
|
|
|
|
|
_visibleHostileIndicators.Add(addEvent.Value, indicator);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
_shipSubscriptions.Add(CurrentEntity.VisibleEnemies.ObserveRemove().Subscribe(removeEvent =>
|
|
|
|
|
|
{
|
|
|
|
|
|
_visibleHostileIndicators[removeEvent.Value].GetComponent<Prototype>().ReturnToPool();
|
|
|
|
|
|
_visibleHostileIndicators.Remove(removeEvent.Value);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var friendly in CurrentEntity.VisibleFriendlies)
|
|
|
|
|
|
{
|
|
|
|
|
|
var indicator = FriendlyTargetIndicator.Instantiate<VisibleTargetIndicator>();
|
|
|
|
|
|
_visibleFriendlyIndicators.Add(friendly, indicator);
|
2026-06-29 11:48:00 +01:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
_shipSubscriptions.Add(CurrentEntity.VisibleFriendlies.ObserveAdd().Subscribe(addEvent =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var indicator = FriendlyTargetIndicator.Instantiate<VisibleTargetIndicator>();
|
|
|
|
|
|
_visibleFriendlyIndicators.Add(addEvent.Value, indicator);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
_shipSubscriptions.Add(CurrentEntity.VisibleFriendlies.ObserveRemove().Subscribe(removeEvent =>
|
|
|
|
|
|
{
|
|
|
|
|
|
_visibleFriendlyIndicators[removeEvent.Value].GetComponent<Prototype>().ReturnToPool();
|
|
|
|
|
|
_visibleFriendlyIndicators.Remove(removeEvent.Value);
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
_shipSubscriptions.Add(CurrentEntity.Death.Subscribe(Die));
|
|
|
|
|
|
|
|
|
|
|
|
_lockingIndicators = CurrentEntity.GetBehaviors<LockWeapon>()
|
|
|
|
|
|
.Select(x =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var i = LockIndicator.Instantiate<PlaceUIElementWorldspace>();
|
|
|
|
|
|
return (x, i, i.GetComponent<Rotate>());
|
|
|
|
|
|
}).ToArray();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void UpdatePlayerPanel()
|
|
|
|
|
|
{
|
|
|
|
|
|
ShipPanel.Display(CurrentEntity, true);
|
|
|
|
|
|
SchematicDisplay.ShowShip(CurrentEntity);
|
2026-06-16 20:42:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
private void UpdateTargetPanel(Entity target)
|
|
|
|
|
|
{
|
|
|
|
|
|
TargetIndicator.gameObject.SetActive(target != null);
|
|
|
|
|
|
TargetShipPanel.gameObject.SetActive(target != null);
|
|
|
|
|
|
if (target != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
TargetShipPanel.Display(target, true);
|
|
|
|
|
|
TargetSchematicDisplay.ShowShip(target, CurrentEntity);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void Die(CauseOfDeath cause)
|
|
|
|
|
|
{
|
|
|
|
|
|
var deathTime = Time.time;
|
|
|
|
|
|
UnbindEntity();
|
|
|
|
|
|
CurrentEntity = null;
|
|
|
|
|
|
MainMenu.gameObject.SetActive(true);
|
|
|
|
|
|
Menu.gameObject.SetActive(false);
|
|
|
|
|
|
CurrentGalaxy = null;
|
Land RunSave and the one-cache run and player lifecycle (Cut 10)
Owner: ActionGameManager composes the process's one cache. Its CultCache
getter attaches the catalog, GameData/run.cc and GameData/player.cc on
first access, in either scene, and nothing reopens it.
- RunSave (SavedGame.cs) replaces the SavedGame constructor:
- Capture builds the live run as plain documents and writes nothing. Zone
contents are `zone.Contents?.PackZone() ?? zone.PackedContents`, so a
zone generated earlier but not loaded this session keeps its contents
and its orbit and body records instead of being erased by the next save.
- Commit is the only writer of SavedGame and SavedZone: one Commit with
zone i at `savedzone-{i}`, SavedGame.Zones set to those refs, and every
stored SavedZone outside that set removed. Orbits and bodies staged by
zone generation land in the same single-file write.
- Clear removes every run record in one Commit.
- Run begins: New Game's first statement is RunSave.Clear. Run saves:
ActionGameManager.SaveRun at wormhole arrival and quit. Run ends: Die
clears the run, then saves settings.
- Run exists only as the run store's SavedGame global: MainMenu enables
Continue from it (and logs when the run store holds records but no
SavedGame), and StartGame reads it once to pick the new-run or resume
branch.
- PlayerSettings: the getter returns the player store's global and, on
first launch, commits GetDefaultPlayerSettings; it is the only creator.
SavePlayerSettings commits the instance.
- InputDisplayLayout reads its InputLayout from the catalog by the layout
asset's name and throws when the catalog has none.
Tests: new RunSaveTests (5): RepeatedSavesKeepRecordCountConstant,
SaveRemovesZonesNoLongerInTheRun, SaveWritesOnlyTheRunStore,
ResumeThenSaveKeepsUnloadedZoneContents, ClearRemovesEveryRunRecord. Each
fails with its rule mutated out.
Builds: headless, 11 tests pass, Unity batchmode compile reports no error CS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:00:27 +02:00
|
|
|
|
RunSave.Clear(CultCache);
|
2026-08-10 14:57:50 +01:00
|
|
|
|
SavePlayerSettings();
|
|
|
|
|
|
Observable.EveryUpdate()
|
|
|
|
|
|
.Where(_ => Time.time - deathTime < DeathPostTransitionTime)
|
|
|
|
|
|
.Subscribe(_ =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var t = (Time.time - deathTime) / DeathPostTransitionTime;
|
|
|
|
|
|
if(cause==CauseOfDeath.Heatstroke)
|
|
|
|
|
|
{
|
|
|
|
|
|
HeatstrokePost.weight = 1 - t;
|
|
|
|
|
|
SevereHeatstrokePost.weight = 1 - t;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (cause == CauseOfDeath.Hypothermia)
|
|
|
|
|
|
{
|
|
|
|
|
|
HypothermiaPost.weight = 1 - t;
|
|
|
|
|
|
SevereHypothermiaPost.weight = 1 - t;
|
|
|
|
|
|
}
|
|
|
|
|
|
DeathPost.weight = t;
|
|
|
|
|
|
},
|
|
|
|
|
|
() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
HeatstrokePost.weight = 0;
|
|
|
|
|
|
SevereHeatstrokePost.weight = 0;
|
|
|
|
|
|
HypothermiaPost.weight = 0;
|
|
|
|
|
|
SevereHypothermiaPost.weight = 0;
|
|
|
|
|
|
DeathPost.weight = 1;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-25 18:09:33 +01:00
|
|
|
|
// public void ToggleEditMode()
|
|
|
|
|
|
// {
|
|
|
|
|
|
// _editMode = !_editMode;
|
|
|
|
|
|
// FollowCamera.gameObject.SetActive(!_editMode);
|
|
|
|
|
|
// TopDownCamera.gameObject.SetActive(_editMode);
|
|
|
|
|
|
// }
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
public IEnumerable<EquippedCargoBay> AvailableCargoBays()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (CurrentEntity.Parent != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var bay in CurrentEntity.Parent.DockingBays)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (bay.DockedShip.IsPlayerShip) yield return bay;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public IEnumerable<Entity> AvailableEntities()
|
|
|
|
|
|
{
|
|
|
|
|
|
if(DockedEntity != null)
|
|
|
|
|
|
foreach (var entity in DockedEntity.Children)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (entity is Ship { IsPlayerShip: true }) yield return entity;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (CurrentEntity != null)
|
|
|
|
|
|
yield return CurrentEntity;
|
|
|
|
|
|
}
|
2026-06-16 20:42:42 +01:00
|
|
|
|
|
2026-06-25 18:09:33 +01:00
|
|
|
|
public void PlayMusic(MusicType type)
|
2026-06-16 20:42:42 +01:00
|
|
|
|
{
|
2026-06-25 18:09:33 +01:00
|
|
|
|
// TODO: SFX: Music
|
2026-06-21 08:56:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-25 18:09:33 +01:00
|
|
|
|
void Update()
|
2026-06-21 08:56:07 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if(!_paused)
|
|
|
|
|
|
{
|
|
|
|
|
|
_time += Time.deltaTime;
|
|
|
|
|
|
_hitMarkerTime -= Time.deltaTime;
|
|
|
|
|
|
if(HitMarker.activeSelf && _hitMarkerTime < 0) HitMarker.SetActive(false);
|
|
|
|
|
|
// ItemManager.Time = _time;
|
|
|
|
|
|
if(CurrentEntity !=null && CurrentEntity.Parent==null)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var indicator in _visibleHostileIndicators)
|
|
|
|
|
|
{
|
|
|
|
|
|
indicator.Value.gameObject.SetActive(indicator.Key!=CurrentEntity.Target.Value);
|
Convert engine types through CultMath.UnityBridge
CultMath has no implicit conversions to or from Vector*, Quaternion or
Color, so Assembly-CSharp converts explicitly with ToCultMath/ToUnity at
each engine boundary: FieldDriver, GuidedProjectile(Manager),
EntityInstance, ActionGameManager, HullCollider, GridObject, ShipInstance,
ShieldManager, ItemPickup, FieldTester, PlaceUIElementWorldspace,
MapMenuInput, MapRenderer, SectorRenderer and ZoneRenderer. CultMath math
calls that were handed Unity vectors now take converted values;
Vector3.Scale in FieldDriver becomes a float3 product, and ItemPickup's
Vector3.Dot on float3s becomes dot. packages-lock.json records the
cultmath git package at f0605367 and demotes com.unity.mathematics to a
transitive dependency.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 17:47:33 +02:00
|
|
|
|
indicator.Value.Place.Target = indicator.Key.Position.ToUnity();
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (!indicator.Key.Active)
|
|
|
|
|
|
indicator.Value.Fill.enabled = false;
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
indicator.Value.Fill.fillAmount =
|
|
|
|
|
|
saturate(indicator.Key.EntityInfoGathered[CurrentEntity] / Settings.GameplaySettings.TargetDetectionInfoThreshold);
|
|
|
|
|
|
indicator.Value.Fill.enabled =
|
|
|
|
|
|
!(indicator.Key.EntityInfoGathered[CurrentEntity] > Settings.GameplaySettings.TargetDetectionInfoThreshold) ||
|
|
|
|
|
|
sin(TargetSpottedBlinkFrequency * Time.time) + TargetSpottedBlinkOffset > 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
foreach (var indicator in _visibleFriendlyIndicators)
|
|
|
|
|
|
{
|
|
|
|
|
|
indicator.Value.gameObject.SetActive(indicator.Key!=CurrentEntity.Target.Value);
|
Convert engine types through CultMath.UnityBridge
CultMath has no implicit conversions to or from Vector*, Quaternion or
Color, so Assembly-CSharp converts explicitly with ToCultMath/ToUnity at
each engine boundary: FieldDriver, GuidedProjectile(Manager),
EntityInstance, ActionGameManager, HullCollider, GridObject, ShipInstance,
ShieldManager, ItemPickup, FieldTester, PlaceUIElementWorldspace,
MapMenuInput, MapRenderer, SectorRenderer and ZoneRenderer. CultMath math
calls that were handed Unity vectors now take converted values;
Vector3.Scale in FieldDriver becomes a float3 product, and ItemPickup's
Vector3.Dot on float3s becomes dot. packages-lock.json records the
cultmath git package at f0605367 and demotes com.unity.mathematics to a
transitive dependency.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 17:47:33 +02:00
|
|
|
|
indicator.Value.Place.Target = indicator.Key.Position.ToUnity();
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (!indicator.Key.Active)
|
|
|
|
|
|
indicator.Value.Fill.enabled = false;
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
indicator.Value.Fill.enabled = true;
|
|
|
|
|
|
indicator.Value.Fill.fillAmount =
|
|
|
|
|
|
saturate(indicator.Key.EntityInfoGathered[CurrentEntity] / Settings.GameplaySettings.TargetDetectionInfoThreshold);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
var look = Input.Player.Look.ReadValue<Vector2>();
|
|
|
|
|
|
_entityYawPitch = float2(_entityYawPitch.x + look.x * Sensitivity.x, clamp(_entityYawPitch.y + look.y * Sensitivity.y, -.45f * PI, .45f * PI));
|
2026-09-14 17:39:12 +02:00
|
|
|
|
_viewDirection = mul(float3(0, 0, 1), CultMath.float3x3.Euler(float3(_entityYawPitch.yx, 0), RotationOrder.YXZ));
|
2026-08-10 14:57:50 +01:00
|
|
|
|
CurrentEntity.LookDirection = _viewDirection;
|
|
|
|
|
|
HeatstrokePost.weight = saturate(unlerp(0, Settings.GameplaySettings.SevereHeatstrokeRiskThreshold, CurrentEntity.Heatstroke));
|
|
|
|
|
|
var severeHeatstrokeLerp = saturate(unlerp(Settings.GameplaySettings.SevereHeatstrokeRiskThreshold, 1, CurrentEntity.Heatstroke));
|
|
|
|
|
|
SevereHeatstrokePost.weight =
|
|
|
|
|
|
severeHeatstrokeLerp + severeHeatstrokeLerp * (1 - severeHeatstrokeLerp) *
|
|
|
|
|
|
max(Settings.HeatstrokePhasingFloor, sin(Time.time * Settings.HeatstrokePhasingFrequency));
|
|
|
|
|
|
|
|
|
|
|
|
if(CurrentEntity is Ship ship)
|
|
|
|
|
|
{
|
Convert engine types through CultMath.UnityBridge
CultMath has no implicit conversions to or from Vector*, Quaternion or
Color, so Assembly-CSharp converts explicitly with ToCultMath/ToUnity at
each engine boundary: FieldDriver, GuidedProjectile(Manager),
EntityInstance, ActionGameManager, HullCollider, GridObject, ShipInstance,
ShieldManager, ItemPickup, FieldTester, PlaceUIElementWorldspace,
MapMenuInput, MapRenderer, SectorRenderer and ZoneRenderer. CultMath math
calls that were handed Unity vectors now take converted values;
Vector3.Scale in FieldDriver becomes a float3 product, and ItemPickup's
Vector3.Dot on float3s becomes dot. packages-lock.json records the
cultmath git package at f0605367 and demotes com.unity.mathematics to a
transitive dependency.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 17:47:33 +02:00
|
|
|
|
ship.MovementDirection = Input.Player.Move.ReadValue<Vector2>().ToCultMath();
|
2026-08-10 14:57:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var target = CurrentEntity.Target.Value;
|
|
|
|
|
|
if (target != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var threshold = Settings.GameplaySettings.TargetDetectionInfoThreshold;
|
|
|
|
|
|
TargetVisibilityFill.fillAmount = lerp(.25f, .75f, (CurrentEntity.EntityInfoGathered[target] - threshold)/(1-threshold));
|
|
|
|
|
|
VisibilityToTargetFill.fillAmount = lerp(.25f, .75f, target.EntityInfoGathered[CurrentEntity] / threshold);
|
|
|
|
|
|
TargetHitpointsFill.fillAmount = lerp(.25f, .75f, target.Hull.Durability / target.EquippedHull.Data.Durability);
|
|
|
|
|
|
TargetShieldsFill.fillAmount = target.Shield == null ? 0 : lerp(.25f, .75f, target.Shield.Progress);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var tractorPower = Input.Player.TractorBeam.ReadValue<float>();
|
|
|
|
|
|
CurrentEntity.TractorPower =
|
|
|
|
|
|
saturate(CurrentEntity.TractorPower + sign(tractorPower - CurrentEntity.TractorPower) * Time.deltaTime * 2);
|
|
|
|
|
|
}
|
|
|
|
|
|
Zone.Update(Time.deltaTime);
|
|
|
|
|
|
}
|
2026-06-16 20:42:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
private void LateUpdate()
|
2026-06-16 20:42:42 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
UpdateTargetIndicators();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void UpdateTargetIndicators()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (CurrentEntity == null || CurrentEntity.Parent != null) return;
|
|
|
|
|
|
|
|
|
|
|
|
ViewDot.Target = ZoneRenderer.EntityInstances[CurrentEntity].LookAtPoint.position;
|
|
|
|
|
|
if (CurrentEntity.Target.Value != null)
|
Convert engine types through CultMath.UnityBridge
CultMath has no implicit conversions to or from Vector*, Quaternion or
Color, so Assembly-CSharp converts explicitly with ToCultMath/ToUnity at
each engine boundary: FieldDriver, GuidedProjectile(Manager),
EntityInstance, ActionGameManager, HullCollider, GridObject, ShipInstance,
ShieldManager, ItemPickup, FieldTester, PlaceUIElementWorldspace,
MapMenuInput, MapRenderer, SectorRenderer and ZoneRenderer. CultMath math
calls that were handed Unity vectors now take converted values;
Vector3.Scale in FieldDriver becomes a float3 product, and ItemPickup's
Vector3.Dot on float3s becomes dot. packages-lock.json records the
cultmath git package at f0605367 and demotes com.unity.mathematics to a
transitive dependency.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 17:47:33 +02:00
|
|
|
|
TargetIndicator.Target = CurrentEntity.Target.Value.Position.ToUnity();
|
|
|
|
|
|
var distance = length(ViewDot.Target.ToCultMath() - CurrentEntity.Position);
|
2026-08-10 14:57:50 +01:00
|
|
|
|
foreach (var (_, barrels, crosshair) in _articulationGroups)
|
2026-06-16 20:42:42 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
var averagePosition = Vector3.zero;
|
|
|
|
|
|
foreach (var barrel in barrels)
|
|
|
|
|
|
averagePosition += barrel.position + barrel.forward * distance;
|
|
|
|
|
|
averagePosition /= barrels.Length;
|
|
|
|
|
|
crosshair.Target = averagePosition;
|
2026-06-16 20:42:42 +01:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
foreach (var (targetLock, indicator, spin) in _lockingIndicators)
|
|
|
|
|
|
{
|
|
|
|
|
|
var showLockingIndicator = targetLock.Lock > .01f && CurrentEntity.Target.Value != null && CurrentEntity.Target.Value.IsHostileTo(CurrentEntity);
|
|
|
|
|
|
indicator.gameObject.SetActive(showLockingIndicator);
|
|
|
|
|
|
if(showLockingIndicator)
|
|
|
|
|
|
{
|
Convert engine types through CultMath.UnityBridge
CultMath has no implicit conversions to or from Vector*, Quaternion or
Color, so Assembly-CSharp converts explicitly with ToCultMath/ToUnity at
each engine boundary: FieldDriver, GuidedProjectile(Manager),
EntityInstance, ActionGameManager, HullCollider, GridObject, ShipInstance,
ShieldManager, ItemPickup, FieldTester, PlaceUIElementWorldspace,
MapMenuInput, MapRenderer, SectorRenderer and ZoneRenderer. CultMath math
calls that were handed Unity vectors now take converted values;
Vector3.Scale in FieldDriver becomes a float3 product, and ItemPickup's
Vector3.Dot on float3s becomes dot. packages-lock.json records the
cultmath git package at f0605367 and demotes com.unity.mathematics to a
transitive dependency.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 17:47:33 +02:00
|
|
|
|
indicator.Target = CurrentEntity.Target.Value.Position.ToUnity();
|
2026-08-10 14:57:50 +01:00
|
|
|
|
indicator.NoiseAmplitude = Settings.GameplaySettings.LockIndicatorNoiseAmplitude * (1 - targetLock.Lock);
|
|
|
|
|
|
indicator.NoiseFrequency = Settings.GameplaySettings.LockIndicatorFrequency.Evaluate(targetLock.Lock);
|
|
|
|
|
|
spin.Speed = Settings.GameplaySettings.LockSpinSpeed.Evaluate(targetLock.Lock);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public abstract class DragObject{}
|
2026-06-21 16:53:33 +01:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public class WeaponGroupDragObject : DragObject
|
|
|
|
|
|
{
|
|
|
|
|
|
public WeaponGroupDragObject(int group)
|
|
|
|
|
|
{
|
|
|
|
|
|
Group = group;
|
2026-06-21 16:53:33 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public int Group { get; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public abstract class ItemDragObject : DragObject
|
|
|
|
|
|
{
|
|
|
|
|
|
protected ItemDragObject(int2 originCellOffset, ItemInstance item)
|
|
|
|
|
|
{
|
|
|
|
|
|
OriginCellOffset = originCellOffset;
|
|
|
|
|
|
Item = item;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ItemInstance Item { get; }
|
|
|
|
|
|
public int2 OriginCellOffset { get; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public class ItemInstanceDragObject : ItemDragObject
|
|
|
|
|
|
{
|
|
|
|
|
|
public ItemInstanceDragObject(ItemInstance item, EquippedCargoBay originInventory, int2 originCellOffset) : base(originCellOffset, item)
|
2026-06-21 17:10:27 +01:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
OriginInventory = originInventory;
|
2026-06-21 17:10:27 +01:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
public EquippedCargoBay OriginInventory { get; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public class EquippedItemDragObject : ItemDragObject
|
|
|
|
|
|
{
|
|
|
|
|
|
public EquippedItemDragObject(EquippedItem item, Entity originEntity, int2 originCellOffset) : base(originCellOffset, item.EquippableItem)
|
|
|
|
|
|
{
|
|
|
|
|
|
EquippedItem = item;
|
|
|
|
|
|
OriginEntity = originEntity;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public EquippedItem EquippedItem { get; }
|
|
|
|
|
|
public Entity OriginEntity { get; }
|
2025-01-30 01:58:05 +00:00
|
|
|
|
}
|