2026-08-10 14:57:50 +01:00
|
|
|
|
using System;
|
2021-02-26 19:01:47 +00:00
|
|
|
|
using System.Collections;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using UniRx;
|
|
|
|
|
|
using UnityEngine;
|
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-09-14 17:39:12 +02:00
|
|
|
|
using static CultMath.math;
|
|
|
|
|
|
using float2 = CultMath.float2;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
using Random = UnityEngine.Random;
|
2021-02-26 19:01:47 +00:00
|
|
|
|
|
|
|
|
|
|
public class EntityInstance : MonoBehaviour
|
|
|
|
|
|
{
|
2021-05-12 00:15:11 +01:00
|
|
|
|
public MeshRenderer MapIcon;
|
2021-05-02 20:59:45 +01:00
|
|
|
|
public Transform InfluencePrefab;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public Transform PingPrefab;
|
2021-02-26 19:01:47 +00:00
|
|
|
|
public Material InvisibleMaterial;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public static Transform EffectManagerParent;
|
2021-02-26 19:01:47 +00:00
|
|
|
|
public ShieldManager Shield;
|
|
|
|
|
|
public HullCollider[] HullColliders;
|
|
|
|
|
|
|
|
|
|
|
|
public RadiatorHardpoint[] RadiatorHardpoints;
|
|
|
|
|
|
public ThrusterHardpoint[] ThrusterHardpoints;
|
|
|
|
|
|
public WeaponHardpoint[] WeaponHardpoints;
|
|
|
|
|
|
public ArticulationPoint[] ArticulationPoints;
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public GameObject DestroyEffect;
|
2021-02-26 19:01:47 +00:00
|
|
|
|
|
|
|
|
|
|
public event Action OnFadedOut;
|
|
|
|
|
|
public event Action OnFadedIn;
|
|
|
|
|
|
|
|
|
|
|
|
private List<Material> _fadeMaterials = new List<Material>();
|
|
|
|
|
|
private Dictionary<MeshRenderer, Material[]> _meshes = new Dictionary<MeshRenderer, Material[]>();
|
|
|
|
|
|
private List<(MeshRenderer mesh, int submesh, Material material)> _nonFadedSubmeshes = new List<(MeshRenderer mesh, int submesh, Material material)>();
|
|
|
|
|
|
private List<IDisposable> _subscriptions = new List<IDisposable>();
|
|
|
|
|
|
private float _fade = 0;
|
|
|
|
|
|
private bool _fading;
|
|
|
|
|
|
private bool _fadingIn;
|
|
|
|
|
|
private bool _fadedElementsVisible = false;
|
|
|
|
|
|
private bool _unfadedElementsVisible = false;
|
|
|
|
|
|
private float _fadeTime;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
private bool _destroyed;
|
|
|
|
|
|
private (Transform transform, MeshRenderer meshRenderer) _currentPing;
|
|
|
|
|
|
private float _pingBrightness;
|
|
|
|
|
|
private Sensor _sensor;
|
2021-05-02 20:59:45 +01:00
|
|
|
|
private Transform _influenceInstance;
|
2021-04-30 06:39:57 +01:00
|
|
|
|
|
2022-07-10 02:53:09 +01:00
|
|
|
|
//private List<(GameObject source, EquippedItem item)> _audioSources = new List<(GameObject source, EquippedItem item)>();
|
2021-04-30 06:39:57 +01:00
|
|
|
|
// private (Reactor reactor, GameObject sfxSource) _reactor;
|
|
|
|
|
|
// private Dictionary<Radiator, GameObject> _radiatorSfx = new Dictionary<Radiator, GameObject>();
|
2021-03-16 16:35:23 +00:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
private static Dictionary<InstantWeaponData, InstantWeaponEffectManager> _instantWeaponManagers = new Dictionary<InstantWeaponData, InstantWeaponEffectManager>();
|
|
|
|
|
|
private static Dictionary<ConstantWeaponData, ConstantWeaponEffectManager> _constantWeaponManagers = new Dictionary<ConstantWeaponData, ConstantWeaponEffectManager>();
|
2026-06-14 03:37:27 +01:00
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public static void ClearWeaponManagers()
|
|
|
|
|
|
{
|
|
|
|
|
|
_instantWeaponManagers.Clear();
|
|
|
|
|
|
_constantWeaponManagers.Clear();
|
|
|
|
|
|
}
|
2026-06-14 03:37:27 +01:00
|
|
|
|
|
2021-05-12 13:49:04 +01:00
|
|
|
|
public CompassIcon CompassIcon { get; set; }
|
2021-02-26 19:01:47 +00:00
|
|
|
|
public Dictionary<HardpointData, Transform[]> Barrels { get; private set; }
|
|
|
|
|
|
public Dictionary<HardpointData, int> BarrelIndices { get; private set; }
|
2021-03-01 01:53:56 +00:00
|
|
|
|
public Dictionary<Radiator, MeshRenderer> RadiatorMeshes { get; private set; }
|
2021-02-26 19:01:47 +00:00
|
|
|
|
public Transform LookAtPoint { get; private set; }
|
2026-10-08 13:25:53 +02:00
|
|
|
|
private Dictionary<int, Transform> _groupAims;
|
2021-02-26 19:01:47 +00:00
|
|
|
|
public Entity Entity { get; private set; }
|
2021-03-15 11:40:46 +00:00
|
|
|
|
public ZoneRenderer ZoneRenderer { get; private set; }
|
2021-04-15 04:25:22 +01:00
|
|
|
|
public Transform LocalSpace { get; private set; }
|
2021-02-26 19:01:47 +00:00
|
|
|
|
public bool Visible
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _fade > 0.01f;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void Awake()
|
|
|
|
|
|
{
|
2021-04-15 04:25:22 +01:00
|
|
|
|
LocalSpace = new GameObject().transform;
|
2021-02-26 19:01:47 +00:00
|
|
|
|
var meshes = gameObject.GetComponentsInChildren<MeshRenderer>();
|
|
|
|
|
|
var materials = new List<(Material material, List<(MeshRenderer renderer, int index)> submeshes)>();
|
|
|
|
|
|
foreach (var mesh in meshes)
|
|
|
|
|
|
{
|
|
|
|
|
|
_meshes.Add(mesh, mesh.sharedMaterials);
|
|
|
|
|
|
for (var i = 0; i < mesh.sharedMaterials.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var material = mesh.sharedMaterials[i];
|
|
|
|
|
|
if (material.shader.FindPropertyIndex("_Fade") >= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
var match = materials.FirstOrDefault(lm => lm.material == material);
|
|
|
|
|
|
if(match.material==null || material.shader.FindPropertyIndex("_EmissionFresnel") >= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
match = (material, new List<(MeshRenderer renderer, int index)>());
|
|
|
|
|
|
materials.Add(match);
|
|
|
|
|
|
}
|
|
|
|
|
|
match.submeshes.Add((mesh, i));
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
_nonFadedSubmeshes.Add((mesh, i, material));
|
|
|
|
|
|
_meshes[mesh][i] = InvisibleMaterial;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var (material, submeshes) in materials)
|
|
|
|
|
|
{
|
|
|
|
|
|
var materialInstance = Instantiate(material);
|
|
|
|
|
|
_fadeMaterials.Add(materialInstance);
|
|
|
|
|
|
foreach (var (mesh, index) in submeshes)
|
|
|
|
|
|
{
|
|
|
|
|
|
_meshes[mesh][index] = materialInstance;
|
|
|
|
|
|
}
|
|
|
|
|
|
materialInstance.SetFloat("_Fade", 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var mesh in _meshes.Keys) mesh.enabled = false;
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var mesh in _meshes) mesh.Key.sharedMaterials = mesh.Value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual void ShowUnfadedElements()
|
|
|
|
|
|
{
|
|
|
|
|
|
_unfadedElementsVisible = true;
|
|
|
|
|
|
foreach (var (mesh, submesh, material) in _nonFadedSubmeshes) _meshes[mesh][submesh] = material;
|
|
|
|
|
|
foreach (var mesh in _meshes) mesh.Key.sharedMaterials = mesh.Value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual void HideUnfadedElements()
|
|
|
|
|
|
{
|
|
|
|
|
|
_unfadedElementsVisible = false;
|
|
|
|
|
|
foreach (var (mesh, submesh, material) in _nonFadedSubmeshes) _meshes[mesh][submesh] = InvisibleMaterial;
|
|
|
|
|
|
foreach (var mesh in _meshes) mesh.Key.sharedMaterials = mesh.Value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void FadeIn(float time)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_fade > .99f)
|
|
|
|
|
|
{
|
|
|
|
|
|
_fading = false;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
_fadeTime = time;
|
|
|
|
|
|
_fading = true;
|
|
|
|
|
|
_fadingIn = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void FadeOut(float time)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_fade < .01f)
|
|
|
|
|
|
{
|
|
|
|
|
|
_fading = false;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
_fadeTime = time;
|
|
|
|
|
|
_fading = true;
|
|
|
|
|
|
_fadingIn = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-15 11:40:46 +00:00
|
|
|
|
public virtual void SetEntity(ZoneRenderer zoneRenderer, Entity entity)
|
2021-02-26 19:01:47 +00:00
|
|
|
|
{
|
|
|
|
|
|
gameObject.name = entity.Name;
|
2021-04-15 04:25:22 +01:00
|
|
|
|
LocalSpace.gameObject.name = $"{entity.Name} Sim Space";
|
|
|
|
|
|
LocalSpace.SetParent(transform.parent);
|
2021-02-26 19:01:47 +00:00
|
|
|
|
Entity = entity;
|
2021-03-15 11:40:46 +00:00
|
|
|
|
ZoneRenderer = zoneRenderer;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
var hullData = entity.ItemManager.GetData(entity.Hull) as HullData;
|
2026-06-14 03:37:27 +01:00
|
|
|
|
|
2021-02-26 19:01:47 +00:00
|
|
|
|
if(Shield)
|
|
|
|
|
|
Shield.Entity = entity;
|
2026-10-12 21:10:04 +02:00
|
|
|
|
|
|
|
|
|
|
// Landed impacts reach the screen through the entity's one binding: the shield ripple (ShieldManager) and the
|
|
|
|
|
|
// hull reaction and camera kick (ImpactPresenter) subscribe to the Absorb stream the simulation publishes.
|
|
|
|
|
|
var impacts = gameObject.GetComponent<ImpactPresenter>() ?? gameObject.AddComponent<ImpactPresenter>();
|
|
|
|
|
|
impacts.Entity = entity;
|
|
|
|
|
|
(gameObject.GetComponent<CapabilityPresentationBinder>() ?? gameObject.AddComponent<CapabilityPresentationBinder>())
|
|
|
|
|
|
.Bind(entity.Capabilities);
|
2021-02-26 19:01:47 +00:00
|
|
|
|
foreach (var hullCollider in HullColliders) hullCollider.Entity = entity;
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
foreach (var item in entity.Equipment)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var behavior in item.Behaviors)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (behavior is Sensor sensor)
|
|
|
|
|
|
{
|
|
|
|
|
|
_sensor = sensor;
|
|
|
|
|
|
sensor.OnPingStart += () =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var pingInstance = Instantiate(PingPrefab);
|
|
|
|
|
|
var pingMesh = pingInstance.GetComponent<MeshRenderer>();
|
Presenters read the drawn pose: HUD markers, wells, rounds, ping, shield hit, compass, loot view, guided aim
HUD target, lock, incoming-lock, bearing and sweep markers anchor on DrawAhead.Position / AtBearing at Clock.Lead.
GravityLowering's record fill is DrawAhead.Well (pure, pinned at non-zero lead). Rounds take DrawAhead.Time
instead of float Zone.Time + lead. Sensor ping, shield hit, compass icons, wormhole bearing, loot ViewOrigin and
the guided-projectile aim read the drawn pose. Not built in Unity yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 23:41:49 +02:00
|
|
|
|
pingInstance.position = DrawAhead.Position(entity, ActionGameManager.Clock.Lead).ToUnity();
|
2026-08-10 14:57:50 +01:00
|
|
|
|
_pingBrightness = pingMesh.material.GetFloat("_Depth");
|
|
|
|
|
|
_currentPing = (pingInstance, pingMesh);
|
|
|
|
|
|
};
|
|
|
|
|
|
sensor.OnPingEnd += OnSensorPingEnd;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-08 13:49:23 +02:00
|
|
|
|
// A mine layer's presentation is the mine it lays (MineInstance, drawn by ZoneRenderer from Zone.Mines).
|
|
|
|
|
|
if (behavior is InstantWeapon instantWeapon && !FireControl.IsMineLayer(instantWeapon))
|
2026-08-10 14:57:50 +01:00
|
|
|
|
{
|
|
|
|
|
|
var data = (InstantWeaponData) instantWeapon.Data;
|
|
|
|
|
|
if (!_instantWeaponManagers.ContainsKey(data))
|
|
|
|
|
|
{
|
Switch engine asset references from Resources paths to Addressables GUIDs
Cut 2 of docs/addressables-cut.md. The asset move (git mv
Resources/{Prefabs,Schematics,Sprites/Icons} -> Content/...) and the
UnityHelpers.cs deletion already landed in 1fdf8151, picked up by a
concurrent session sharing this working tree; this commit is the rest
of Cut 2 landing on top of that shared state, atomically:
- Bump CultLib to cultlib-unity-v1.0.60 / caching-unity-v1.4.0 (both
45c2f40) in Packages/manifest.json, packages-lock.json and
Directory.Build.props, together with the 8 ServerShared
[CultInspectorAssetPath] usages becoming [CultInspectorAssetGuid] -
the .NET and Unity sides move together or nothing compiles.
- Assets/Scripts/EngineAssets.cs replaces UnityHelpers: loads an
Addressables key (bare GUID or guid[subAsset]) synchronously,
loading the GameObject and taking the component for the four sites
that load a component. ActionBarSlot, EntityInstance, ShipInstance
and ZoneRenderer call it instead of Resources.Load/UnityHelpers; the
inline "Assets/Resources/" prefix strips are gone with them.
CultCacheDrawers' schematic preview goes through
AssetDatabase.GUIDToAssetPath first.
- Assets/Scripts/Editor/EngineAssetCheck.cs validates every stored
[CultInspectorAssetGuid] value: 32 lowercase hex, resolves to a file,
is addressable, and satisfies its member's consumer-type contract
(Texture2D for icons/logos/schematics, a GameObject with the right
component for prefabs) - table-driven, so an attributed member with
no row fails loudly instead of being skipped.
- GameData/Aetheria.cc: the scratch migration (Aetheria.Shared + the
pinned CultLib revision, a throwaway console app, never landed)
walked all 138 attributed field slots across 180 documents,
rewriting the 30 non-empty Resources paths to their .meta GUIDs -
108 stayed empty, 0 unresolved, matching the map's census exactly.
Verification: dotnet test tests/Aetheria.Shared.Tests 61/61 green
against CultLib 45c2f40; Unity 6000.3.24f1 batchmode compiles clean;
EngineAssetCheck.Run passes all 30 stored references; the map's
negative greps are clean; tests/mutation_tests.py's 5 mutations (wrong
type, non-addressable, random guid, an old path, and an untabled
attributed member) are all caught and named, the control case and the
post-revert recompile stay clean, and the catalog/source are restored
byte-identical afterward.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 05:04:50 +02:00
|
|
|
|
var managerPrefab = EngineAssets.Load<InstantWeaponEffectManager>(data.EffectPrefab);
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if(managerPrefab)
|
|
|
|
|
|
{
|
|
|
|
|
|
_instantWeaponManagers.Add(data, Instantiate(managerPrefab, EffectManagerParent));
|
|
|
|
|
|
}
|
Switch engine asset references from Resources paths to Addressables GUIDs
Cut 2 of docs/addressables-cut.md. The asset move (git mv
Resources/{Prefabs,Schematics,Sprites/Icons} -> Content/...) and the
UnityHelpers.cs deletion already landed in 1fdf8151, picked up by a
concurrent session sharing this working tree; this commit is the rest
of Cut 2 landing on top of that shared state, atomically:
- Bump CultLib to cultlib-unity-v1.0.60 / caching-unity-v1.4.0 (both
45c2f40) in Packages/manifest.json, packages-lock.json and
Directory.Build.props, together with the 8 ServerShared
[CultInspectorAssetPath] usages becoming [CultInspectorAssetGuid] -
the .NET and Unity sides move together or nothing compiles.
- Assets/Scripts/EngineAssets.cs replaces UnityHelpers: loads an
Addressables key (bare GUID or guid[subAsset]) synchronously,
loading the GameObject and taking the component for the four sites
that load a component. ActionBarSlot, EntityInstance, ShipInstance
and ZoneRenderer call it instead of Resources.Load/UnityHelpers; the
inline "Assets/Resources/" prefix strips are gone with them.
CultCacheDrawers' schematic preview goes through
AssetDatabase.GUIDToAssetPath first.
- Assets/Scripts/Editor/EngineAssetCheck.cs validates every stored
[CultInspectorAssetGuid] value: 32 lowercase hex, resolves to a file,
is addressable, and satisfies its member's consumer-type contract
(Texture2D for icons/logos/schematics, a GameObject with the right
component for prefabs) - table-driven, so an attributed member with
no row fails loudly instead of being skipped.
- GameData/Aetheria.cc: the scratch migration (Aetheria.Shared + the
pinned CultLib revision, a throwaway console app, never landed)
walked all 138 attributed field slots across 180 documents,
rewriting the 30 non-empty Resources paths to their .meta GUIDs -
108 stayed empty, 0 unresolved, matching the map's census exactly.
Verification: dotnet test tests/Aetheria.Shared.Tests 61/61 green
against CultLib 45c2f40; Unity 6000.3.24f1 batchmode compiles clean;
EngineAssetCheck.Run passes all 30 stored references; the map's
negative greps are clean; tests/mutation_tests.py's 5 mutations (wrong
type, non-addressable, random guid, an old path, and an untabled
attributed member) are all caught and named, the control case and the
post-revert recompile stay clean, and the catalog/source are restored
byte-identical afterward.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 05:04:50 +02:00
|
|
|
|
else Debug.LogError($"No InstantWeaponEffectManager prefab found for GUID {data.EffectPrefab}");
|
2026-08-10 14:57:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
Fire control Cut 3: hit authority moves to FireControl
Deletes the Unity physics hit path (HullCollider.Hit/Splash, and the
raycast/shield/SendHit blocks in Projectile, GuidedProjectile, Laser and
Lightning) and replaces it with a single owner in the same cut: FireControl
now decides whether a shot hits, Zone owns the pending-shot lifecycle, and
Entity.DamageSchematic/ApplyHit (moved verbatim from EntityInstance) apply the
result. Presentation subscribes to Zone.ShotCommitted/ShotResolved and applies
nothing.
FireControl.HitProbability is the pure targeting solution (Accuracy * pSensor
* pSpread, gated by detection/range/lock/arc). FireControl.Fire freezes the
weapon's payload and the shooter's resolved targeting stats at the instant the
trigger is pulled (Q6), computes the predicted intercept through the one
shared PredictedIntercept function (CombatState now calls the same function
instead of duplicating first_order_intercept), and queues a PendingShot.
FireControl.Step commits each shot's outcome at ArrivalTime - CommitHorizon --
deviation from the predicted intercept, measured once, live, at that instant
-- and applies it at ArrivalTime. A shot whose flight is shorter than the
horizon commits and resolves in the same tick it fires. Rolls draw from the
shooter's own ItemManager.Random (Q5, seeded and reproducible); an out-of-arc
or otherwise gated shot draws nothing.
Player fire is arc-gated through the same predicate AI and turret fire use
(Weapon.ArcAllowsFire, checked in InstantWeapon.Trigger -- the one point every
shooter's trigger passes through), closing Q2.
Adds GameplaySettings.CommitHorizon, SchematicCellSize and
AgentMinHitProbability (first guesses; the headless suite is the tuning
harness, operator play smoke is the arbiter).
FORK reported, not worked around silently: HullCollider.SendHit/SendSplash
cannot be deleted yet without breaking ConstantLaser.cs, ConstantLightning.cs,
ConstantParticleWeapon.cs, HitscanEffect.cs and Mine.cs, which still call them
from their own live Physics queries -- that's Cut 4's migration. They fire
into the void today: nothing subscribes to Hit/Splash any more for the four
weapon kinds this cut owns.
tests/Aetheria.Shared.Tests/FireAuthorityTests.cs pins the roll's seeding, the
gate-consumes-no-draw rule, resolve-on-arrival, the commit horizon and its
evasion window, short-flight degradation, the frozen-payload snapshot, aimed
hits, the item-then-hull damage order, the planar penetration march and the
shield-absorbs branch. FireControlTests.cs and InputCapacitorTests.cs are
updated for the new AI gate and OnFire(int) signature.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 14:07:07 +02:00
|
|
|
|
// Cut 3 (docs/fire-control-cut.md): carries the ShotId FireControl.Fire assigned so the
|
|
|
|
|
|
// effect manager (and whatever it spawns) can bind its presentation to Zone.ShotCommitted /
|
|
|
|
|
|
// ShotResolved instead of deciding or applying anything itself.
|
2026-09-29 16:27:54 +02:00
|
|
|
|
// The target handed to the effect is the shot's own, not the shooter's selection: a fused round
|
|
|
|
|
|
// whose selected target was invalid, or out of arc, flies at none (FireControl.Fire) and the
|
|
|
|
|
|
// effect must not chase one.
|
Fire control Cut 3: hit authority moves to FireControl
Deletes the Unity physics hit path (HullCollider.Hit/Splash, and the
raycast/shield/SendHit blocks in Projectile, GuidedProjectile, Laser and
Lightning) and replaces it with a single owner in the same cut: FireControl
now decides whether a shot hits, Zone owns the pending-shot lifecycle, and
Entity.DamageSchematic/ApplyHit (moved verbatim from EntityInstance) apply the
result. Presentation subscribes to Zone.ShotCommitted/ShotResolved and applies
nothing.
FireControl.HitProbability is the pure targeting solution (Accuracy * pSensor
* pSpread, gated by detection/range/lock/arc). FireControl.Fire freezes the
weapon's payload and the shooter's resolved targeting stats at the instant the
trigger is pulled (Q6), computes the predicted intercept through the one
shared PredictedIntercept function (CombatState now calls the same function
instead of duplicating first_order_intercept), and queues a PendingShot.
FireControl.Step commits each shot's outcome at ArrivalTime - CommitHorizon --
deviation from the predicted intercept, measured once, live, at that instant
-- and applies it at ArrivalTime. A shot whose flight is shorter than the
horizon commits and resolves in the same tick it fires. Rolls draw from the
shooter's own ItemManager.Random (Q5, seeded and reproducible); an out-of-arc
or otherwise gated shot draws nothing.
Player fire is arc-gated through the same predicate AI and turret fire use
(Weapon.ArcAllowsFire, checked in InstantWeapon.Trigger -- the one point every
shooter's trigger passes through), closing Q2.
Adds GameplaySettings.CommitHorizon, SchematicCellSize and
AgentMinHitProbability (first guesses; the headless suite is the tuning
harness, operator play smoke is the arbiter).
FORK reported, not worked around silently: HullCollider.SendHit/SendSplash
cannot be deleted yet without breaking ConstantLaser.cs, ConstantLightning.cs,
ConstantParticleWeapon.cs, HitscanEffect.cs and Mine.cs, which still call them
from their own live Physics queries -- that's Cut 4's migration. They fire
into the void today: nothing subscribes to Hit/Splash any more for the four
weapon kinds this cut owns.
tests/Aetheria.Shared.Tests/FireAuthorityTests.cs pins the roll's seeding, the
gate-consumes-no-draw rule, resolve-on-arrival, the commit horizon and its
evasion window, short-flight degradation, the frozen-payload snapshot, aimed
hits, the item-then-hull damage order, the planar penetration march and the
shield-absorbs branch. FireControlTests.cs and InputCapacitorTests.cs are
updated for the new AI gate and OnFire(int) signature.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 14:07:07 +02:00
|
|
|
|
instantWeapon.OnFire += shotId =>
|
2026-09-29 16:27:54 +02:00
|
|
|
|
_instantWeaponManagers[data].Fire(instantWeapon, item, this, ShotTarget(shotId), shotId);
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
if (behavior is ChargedWeapon chargedWeapon)
|
|
|
|
|
|
{
|
|
|
|
|
|
var chargeManager = _instantWeaponManagers[data].GetComponent<ChargeEffectManager>();
|
|
|
|
|
|
if (chargeManager)
|
|
|
|
|
|
{
|
|
|
|
|
|
chargedWeapon.OnStartCharging += () => chargeManager.StartCharging(chargedWeapon, item, this);
|
|
|
|
|
|
chargedWeapon.OnStopCharging += () => chargeManager.StopCharging(chargedWeapon);
|
|
|
|
|
|
chargedWeapon.OnCharged += () => chargeManager.Charged(chargedWeapon);
|
|
|
|
|
|
chargedWeapon.OnFailed += () => chargeManager.Failed(chargedWeapon);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (behavior is ConstantWeapon constantWeapon)
|
|
|
|
|
|
{
|
|
|
|
|
|
var data = (ConstantWeaponData) constantWeapon.Data;
|
|
|
|
|
|
if (!_constantWeaponManagers.ContainsKey(data))
|
|
|
|
|
|
{
|
Switch engine asset references from Resources paths to Addressables GUIDs
Cut 2 of docs/addressables-cut.md. The asset move (git mv
Resources/{Prefabs,Schematics,Sprites/Icons} -> Content/...) and the
UnityHelpers.cs deletion already landed in 1fdf8151, picked up by a
concurrent session sharing this working tree; this commit is the rest
of Cut 2 landing on top of that shared state, atomically:
- Bump CultLib to cultlib-unity-v1.0.60 / caching-unity-v1.4.0 (both
45c2f40) in Packages/manifest.json, packages-lock.json and
Directory.Build.props, together with the 8 ServerShared
[CultInspectorAssetPath] usages becoming [CultInspectorAssetGuid] -
the .NET and Unity sides move together or nothing compiles.
- Assets/Scripts/EngineAssets.cs replaces UnityHelpers: loads an
Addressables key (bare GUID or guid[subAsset]) synchronously,
loading the GameObject and taking the component for the four sites
that load a component. ActionBarSlot, EntityInstance, ShipInstance
and ZoneRenderer call it instead of Resources.Load/UnityHelpers; the
inline "Assets/Resources/" prefix strips are gone with them.
CultCacheDrawers' schematic preview goes through
AssetDatabase.GUIDToAssetPath first.
- Assets/Scripts/Editor/EngineAssetCheck.cs validates every stored
[CultInspectorAssetGuid] value: 32 lowercase hex, resolves to a file,
is addressable, and satisfies its member's consumer-type contract
(Texture2D for icons/logos/schematics, a GameObject with the right
component for prefabs) - table-driven, so an attributed member with
no row fails loudly instead of being skipped.
- GameData/Aetheria.cc: the scratch migration (Aetheria.Shared + the
pinned CultLib revision, a throwaway console app, never landed)
walked all 138 attributed field slots across 180 documents,
rewriting the 30 non-empty Resources paths to their .meta GUIDs -
108 stayed empty, 0 unresolved, matching the map's census exactly.
Verification: dotnet test tests/Aetheria.Shared.Tests 61/61 green
against CultLib 45c2f40; Unity 6000.3.24f1 batchmode compiles clean;
EngineAssetCheck.Run passes all 30 stored references; the map's
negative greps are clean; tests/mutation_tests.py's 5 mutations (wrong
type, non-addressable, random guid, an old path, and an untabled
attributed member) are all caught and named, the control case and the
post-revert recompile stay clean, and the catalog/source are restored
byte-identical afterward.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 05:04:50 +02:00
|
|
|
|
var managerPrefab = EngineAssets.Load<ConstantWeaponEffectManager>(data.EffectPrefab);
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if(managerPrefab)
|
|
|
|
|
|
{
|
|
|
|
|
|
_constantWeaponManagers.Add(data, Instantiate(managerPrefab, EffectManagerParent));
|
|
|
|
|
|
}
|
Switch engine asset references from Resources paths to Addressables GUIDs
Cut 2 of docs/addressables-cut.md. The asset move (git mv
Resources/{Prefabs,Schematics,Sprites/Icons} -> Content/...) and the
UnityHelpers.cs deletion already landed in 1fdf8151, picked up by a
concurrent session sharing this working tree; this commit is the rest
of Cut 2 landing on top of that shared state, atomically:
- Bump CultLib to cultlib-unity-v1.0.60 / caching-unity-v1.4.0 (both
45c2f40) in Packages/manifest.json, packages-lock.json and
Directory.Build.props, together with the 8 ServerShared
[CultInspectorAssetPath] usages becoming [CultInspectorAssetGuid] -
the .NET and Unity sides move together or nothing compiles.
- Assets/Scripts/EngineAssets.cs replaces UnityHelpers: loads an
Addressables key (bare GUID or guid[subAsset]) synchronously,
loading the GameObject and taking the component for the four sites
that load a component. ActionBarSlot, EntityInstance, ShipInstance
and ZoneRenderer call it instead of Resources.Load/UnityHelpers; the
inline "Assets/Resources/" prefix strips are gone with them.
CultCacheDrawers' schematic preview goes through
AssetDatabase.GUIDToAssetPath first.
- Assets/Scripts/Editor/EngineAssetCheck.cs validates every stored
[CultInspectorAssetGuid] value: 32 lowercase hex, resolves to a file,
is addressable, and satisfies its member's consumer-type contract
(Texture2D for icons/logos/schematics, a GameObject with the right
component for prefabs) - table-driven, so an attributed member with
no row fails loudly instead of being skipped.
- GameData/Aetheria.cc: the scratch migration (Aetheria.Shared + the
pinned CultLib revision, a throwaway console app, never landed)
walked all 138 attributed field slots across 180 documents,
rewriting the 30 non-empty Resources paths to their .meta GUIDs -
108 stayed empty, 0 unresolved, matching the map's census exactly.
Verification: dotnet test tests/Aetheria.Shared.Tests 61/61 green
against CultLib 45c2f40; Unity 6000.3.24f1 batchmode compiles clean;
EngineAssetCheck.Run passes all 30 stored references; the map's
negative greps are clean; tests/mutation_tests.py's 5 mutations (wrong
type, non-addressable, random guid, an old path, and an untabled
attributed member) are all caught and named, the control case and the
post-revert recompile stay clean, and the catalog/source are restored
byte-identical afterward.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 05:04:50 +02:00
|
|
|
|
else Debug.LogError($"No ConstantWeaponEffectManager prefab found for GUID {data.EffectPrefab}");
|
2026-08-10 14:57:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
constantWeapon.OnStartFiring += () =>
|
Mining Cut 3: a chunk is a target, detected by reflected light
- Entity.Target is one slot of TargetRef, read-only from outside
(IReadOnlyReactiveProperty); Entity.SetTarget is its one writer. A chunk is
taken only while the entity can see it, so a rock in darkness cannot be
picked. Every handler, agent, turret and test writes through SetTarget.
- Entity.ChunkInfo (Q14 A): the value the per-tick sensor rule settles at,
computed on demand through Sensor.PassiveRate/Sensor.Gain -- sum of the
active sensors' passive rates over (sensors x TargetInfoDecay), capped at 1.
Stored nowhere; no per-tick loop over chunks. ChunkVisible is the detection
threshold through the chunk path.
- The lost-track rule reads through the chunk path: Activate's reconciliation
and a per-tick check of the one slot drop a chunk target that has gone dark.
- Entity.VisibleChunksInReach (Q13 A): visible chunks within the longest range
among active weapons -- the one reach rule for cycling and programmatic picks.
- Entity-only readers take .Entity: TargetedBy/TargetedByCount, target items,
the stance gate (a chunk has no stance), lock (Q12 A: never on a chunk),
turrets, Combat, Minion, FireControl (a chunk fires untargeted until Cut 4).
TargetRange and the arc gate read a chunk's planar position.
- ActionGameManager: the reticle and next/previous offer visible ships and
visible chunks in reach; nearest stays enemies-only; the target panel,
indicator and fire-control debug take TargetRef. EntityInstance reads .Entity.
- MiningCut3Tests pin parity, darkness, monotone brightness, wear and break,
reach, the chunk query, target survival and loss, TargetRef equality, lock,
and field-kind assignment at generation and first load.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 12:56:39 +02:00
|
|
|
|
_constantWeaponManagers[data].StartFiring(data, item, this, entity.Target.Value.Entity != null ? ZoneRenderer.EntityInstances[entity.Target.Value.Entity] : null);
|
2026-08-10 14:57:50 +01:00
|
|
|
|
constantWeapon.OnStopFiring += () =>
|
|
|
|
|
|
_constantWeaponManagers[data].StopFiring(item);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-03-01 01:53:56 +00:00
|
|
|
|
RadiatorMeshes = new Dictionary<Radiator, MeshRenderer>();
|
2021-02-26 19:01:47 +00:00
|
|
|
|
Barrels = new Dictionary<HardpointData, Transform[]>();
|
|
|
|
|
|
BarrelIndices = new Dictionary<HardpointData, int>();
|
2021-03-01 01:53:56 +00:00
|
|
|
|
foreach (var radiator in entity.GetBehaviors<Radiator>())
|
2021-02-26 19:01:47 +00:00
|
|
|
|
{
|
2026-10-03 14:57:27 +02:00
|
|
|
|
var hp = radiator.Item.Hardpoint;
|
2021-03-01 01:53:56 +00:00
|
|
|
|
if (hp != null && hp.Type == HardpointType.Radiator)
|
2021-02-26 19:01:47 +00:00
|
|
|
|
{
|
|
|
|
|
|
var mesh = RadiatorHardpoints.FirstOrDefault(x => x.name == hp.Transform);
|
|
|
|
|
|
if (mesh)
|
|
|
|
|
|
{
|
2021-03-01 01:53:56 +00:00
|
|
|
|
RadiatorMeshes.Add(radiator, mesh.Mesh);
|
2021-02-26 19:01:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-03-01 01:53:56 +00:00
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
foreach (var hp in hullData.Hardpoints)
|
2021-03-01 01:53:56 +00:00
|
|
|
|
{
|
2021-02-26 19:01:47 +00:00
|
|
|
|
if(hp.Type == HardpointType.Ballistic || hp.Type == HardpointType.Energy || hp.Type == HardpointType.Launcher)
|
|
|
|
|
|
{
|
|
|
|
|
|
var whp = WeaponHardpoints.FirstOrDefault(x => x.name == hp.Transform);
|
|
|
|
|
|
if (whp)
|
|
|
|
|
|
{
|
|
|
|
|
|
Barrels.Add(hp, whp.FiringPoint);
|
|
|
|
|
|
BarrelIndices.Add(hp, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-26 01:17:19 +02:00
|
|
|
|
// Cut 3 (docs/fire-control-cut.md): DamageSchematic and both HullCollider subscriptions (blast, Hit)
|
|
|
|
|
|
// are deleted -- FireControl decides hits now and applies them through Entity.ArmorAbsorb/ItemAbsorb/
|
|
|
|
|
|
// DamageHull (ServerShared, Cut 12.3/12.4(b); DamageSchematic, the old per-cell single-lane absorb path
|
|
|
|
|
|
// and ApplyHit are all gone), the single owner in place of what used to exist once per Unity effect.
|
|
|
|
|
|
// HullColliders no longer publish Hit or a blast event at all (R8).
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
2021-02-26 19:01:47 +00:00
|
|
|
|
LookAtPoint = new GameObject($"{entity.Name} Look Point").transform;
|
2026-10-08 13:25:53 +02:00
|
|
|
|
|
|
|
|
|
|
// One aim point per articulation group, placed each LateUpdate where the group's first weapon aims.
|
|
|
|
|
|
_groupAims = new Dictionary<int, Transform>();
|
2021-02-26 19:01:47 +00:00
|
|
|
|
foreach (var articulationPoint in ArticulationPoints)
|
|
|
|
|
|
{
|
2026-10-08 13:25:53 +02:00
|
|
|
|
if (!_groupAims.TryGetValue(articulationPoint.Group, out var aim))
|
|
|
|
|
|
_groupAims[articulationPoint.Group] = aim = new GameObject($"{entity.Name} Aim {articulationPoint.Group}").transform;
|
|
|
|
|
|
articulationPoint.Target = aim;
|
2021-02-26 19:01:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
_subscriptions.Add(Entity.HullDamage.Subscribe(_ =>
|
|
|
|
|
|
{
|
2026-10-12 02:20:46 +02:00
|
|
|
|
if (Entity.Hull.IsDestroyed)
|
2026-08-10 14:57:50 +01:00
|
|
|
|
{
|
|
|
|
|
|
if (!this) return;
|
|
|
|
|
|
_destroyed = true;
|
|
|
|
|
|
if (DestroyEffect != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var t = Instantiate(DestroyEffect).transform;
|
|
|
|
|
|
t.position = transform.position;
|
|
|
|
|
|
}
|
Fire control Cut 5: close the rules that landed on one path and not its twin
Soul's pass over the whole campaign confirmed eleven findings. Seven were code
defects of one shape -- a rule implemented on one path and left off the path
beside it -- and this is all seven.
5.1 Tracking falls back to GameplaySettings.UnaidedTracking (new, 10) instead of
zero, and Commit drops the zero branch that turned a Tracking-less shooter
into a hard wall. Unaided fire was meant to be really, really bad; it was
334 shots and no hits against a target drifting at 5 u/s.
5.2 A shot the shield cannot absorb now breaks it. The decision is frozen into
ShotOutcome.ShieldBroken at commit and performed in Apply, so a shield that
recharges mid-flight cannot retroactively survive. Splash already did this;
the discrete roll never did, which made the whole shield-break mechanic
unreachable for every weapon in the catalog.
5.3 ConstantWeapon gates on ArcAllowsFire alongside StanceAllowsFire. A
side-mounted beam was firing forward.
5.4 The point-blank bearing case moves out of Weapon.ArcAllowsFire and into
InArc, which used to normalize the zero vector into NaN. The trigger said
fire and the probability said impossible.
5.5 A destroyed item drops out of IsRevealed's ranking, so Precision stops
concentrating hits onto a dead subsystem's footprint.
5.6 Zone subscribes Entity.Death and removes the corpse itself. That removal
lived only in Unity's loot-drop subscription, so headless nothing ever left
the zone, shots landed on corpses and no fight could end.
5.7 PendingShot drops PredictedIntercept and FlightTime, written and read
nowhere.
208 tests pass (197 plus eleven new).
Known defect, recorded rather than hidden: the committed mutation harness is
unreliable. Across two runs it failed to restore FireControl.cs and Weapon.cs,
leaving live mutations in the working tree -- including its own 5.1 mutation,
which is how a green suite briefly reported two failures. Its verdicts are
therefore evidence only where re-run from a verified-clean tree. Two anchors
(Zone.cs's ObserveAdd line, Weapon.cs's InArc return) fail to match at all, and
5.3's ConstantWeaponObeysArc survives its own mutation, so that rule is written
but not pinned. Cut 5b owns the repair; nothing here is trusted on the harness's
word, and the suite result above was taken by hand from a hash-verified tree.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 16:56:40 +02:00
|
|
|
|
// Cut 5, 5.6 (docs/fire-control-cut.md, Soul finding 7): Zone now removes the entity itself
|
|
|
|
|
|
// (subscribed to Entity.Death when the entity joined) -- this subscription's own business is
|
|
|
|
|
|
// loot and the destroy effect, presentation both, not removal from the simulation's own
|
|
|
|
|
|
// collection.
|
2026-08-10 14:57:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
2021-05-11 04:45:42 +01:00
|
|
|
|
if (entity is OrbitalEntity orbital && orbital.IsSecureArea)
|
2021-05-02 20:59:45 +01:00
|
|
|
|
{
|
|
|
|
|
|
_influenceInstance = Instantiate(InfluencePrefab);
|
|
|
|
|
|
_influenceInstance.transform.localScale = Vector3.one * orbital.SecurityRadius;
|
|
|
|
|
|
}
|
2021-02-26 19:01:47 +00:00
|
|
|
|
}
|
2022-08-08 22:43:55 +01:00
|
|
|
|
|
2026-09-29 16:27:54 +02:00
|
|
|
|
private EntityInstance ShotTarget(int shotId) =>
|
|
|
|
|
|
Entity.Zone.TryGetShot(shotId, out var shot) && shot.Target != null &&
|
|
|
|
|
|
ZoneRenderer.EntityInstances.TryGetValue(shot.Target, out var instance) ? instance : null;
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
private void OnSensorPingEnd()
|
|
|
|
|
|
{
|
|
|
|
|
|
Destroy(_currentPing.transform.gameObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-26 19:01:47 +00:00
|
|
|
|
public Transform GetBarrel(HardpointData hardpoint)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Barrels.ContainsKey(hardpoint))
|
|
|
|
|
|
{
|
|
|
|
|
|
var barrel = Barrels[hardpoint][BarrelIndices[hardpoint]];
|
|
|
|
|
|
BarrelIndices[hardpoint] = (BarrelIndices[hardpoint] + 1) % Barrels[hardpoint].Length;
|
|
|
|
|
|
return barrel;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return transform;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public virtual void Update()
|
|
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if (_currentPing.transform)
|
|
|
|
|
|
{
|
|
|
|
|
|
_currentPing.transform.localScale = _sensor.PingRadius * Vector3.one;
|
|
|
|
|
|
_currentPing.meshRenderer.material.SetFloat("_Depth", _pingBrightness * _sensor.PingBrightness);
|
|
|
|
|
|
}
|
2021-02-26 19:01:47 +00:00
|
|
|
|
if (_fading)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_fadingIn)
|
|
|
|
|
|
{
|
|
|
|
|
|
_fade += Time.deltaTime / _fadeTime;
|
|
|
|
|
|
if (!_fadedElementsVisible && _fade > .01f)
|
|
|
|
|
|
{
|
|
|
|
|
|
_fadedElementsVisible = true;
|
|
|
|
|
|
foreach (var mesh in _meshes.Keys) mesh.enabled = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_fade > 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
_fade = 1;
|
|
|
|
|
|
_fading = false;
|
|
|
|
|
|
OnFadedIn?.Invoke();
|
|
|
|
|
|
OnFadedIn = null;
|
|
|
|
|
|
ShowUnfadedElements();
|
|
|
|
|
|
}
|
|
|
|
|
|
foreach(var material in _fadeMaterials) material.SetFloat("_Fade", _fade);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
_fade -= Time.deltaTime / _fadeTime;
|
|
|
|
|
|
if(_unfadedElementsVisible && _fade < .99f) HideUnfadedElements();
|
|
|
|
|
|
if (_fade < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
_fadedElementsVisible = false;
|
|
|
|
|
|
foreach (var mesh in _meshes.Keys) mesh.enabled = false;
|
|
|
|
|
|
_fade = 0;
|
|
|
|
|
|
_fading = false;
|
|
|
|
|
|
OnFadedOut?.Invoke();
|
|
|
|
|
|
OnFadedOut = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
foreach(var material in _fadeMaterials) material.SetFloat("_Fade", _fade);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var x in RadiatorMeshes)
|
|
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
x.Value.material.SetFloat("_Emission", Entity.ItemManager.GameplaySettings.TemperatureEmissionCurve.Evaluate(x.Key.RadiatorTemperature));
|
2021-02-26 19:01:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
Draw every moving body at the drawn time T = ExactTime + Lead
Planets, belts, wells, loot, mines, stations, the chunk reticle and the flow
clock are evaluated through DrawAhead (Time, Orbit, Height, Position) at one
drawn time; the rail law lives once in Zone.Offset; OrbitPositionAt, GetHeight,
ChunkPose, EvaluateBelt and RideHeight take a time. Fixes the planet jumping
against a co-moving ship at display rates above the step rate.
Not yet built or run (Unity presenter files are verified by compile only on
Starfire).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:26:36 +02:00
|
|
|
|
// The drawn pose first: the camera's LookAt and the barrels read this frame's pose, not the last frame's.
|
|
|
|
|
|
var drawn = DrawAhead.Position(Entity, ActionGameManager.Clock.Lead);
|
|
|
|
|
|
LocalSpace.localPosition = transform.position = drawn.ToUnity();
|
2026-10-08 13:25:53 +02:00
|
|
|
|
var aimDistance = !Entity.Target.Value.IsNone
|
|
|
|
|
|
? max(Entity.TargetRange, Entity.ItemManager.GameplaySettings.ConvergenceMinimumDistance)
|
|
|
|
|
|
: 10000;
|
|
|
|
|
|
LookAtPoint.position = transform.position + Entity.Aim.ToUnity() * aimDistance;
|
|
|
|
|
|
foreach (var group in _groupAims)
|
|
|
|
|
|
{
|
|
|
|
|
|
var weapon = group.Key >= 0 && group.Key < Entity.WeaponGroups.Length ? Entity.WeaponGroups[group.Key].weapons.FirstOrDefault() : null;
|
|
|
|
|
|
if (weapon == null) { group.Value.position = LookAtPoint.position; continue; }
|
|
|
|
|
|
var barrel = Barrels.TryGetValue(weapon.Item.Hardpoint, out var barrels) ? barrels[0].position : transform.position;
|
|
|
|
|
|
var direction = FireControl.Solution(weapon, Entity, Entity.Target.Value).Direction;
|
|
|
|
|
|
group.Value.position = barrel + new Vector3(direction.x, 0, direction.y) * aimDistance;
|
|
|
|
|
|
}
|
2021-05-02 20:59:45 +01:00
|
|
|
|
if (_influenceInstance)
|
2026-10-08 18:26:02 +02:00
|
|
|
|
_influenceInstance.position = new Vector3(drawn.x, 0, drawn.z);
|
2021-02-26 19:01:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-16 16:35:23 +00:00
|
|
|
|
public virtual void OnDestroy()
|
2021-02-26 19:01:47 +00:00
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
|
if(_sensor!=null)
|
|
|
|
|
|
_sensor.OnPingEnd -= OnSensorPingEnd;
|
2021-05-02 20:59:45 +01:00
|
|
|
|
if (_influenceInstance) Destroy(_influenceInstance.gameObject);
|
2021-04-15 04:25:22 +01:00
|
|
|
|
Destroy(LocalSpace.gameObject);
|
2021-02-26 19:01:47 +00:00
|
|
|
|
foreach(var x in _subscriptions)
|
|
|
|
|
|
x.Dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|