Merge origin/master into sensor-catalog-r2 (code; catalog is master's)
Conflicts: EvasionTermTests keeps the branch's EvLonginusDrive split with master's generic design names; AetherDb Program.cs keeps both the sensor-stat-set and generic-designs cases and lists both in the usage line. GameData/Aetheria.cc is taken from origin/master unchanged; sensor-stat-set apply is rerun on it in the next commit. Tests that read the sensor catalog (TheCatalogCarriesSensorStats and the roles checks) do not pass at this commit: it builds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
005e2fc57b
103 changed files with 5392 additions and 668 deletions
208
Assets/Scripts/Editor/CapabilityPresentationSmoke.cs
Normal file
208
Assets/Scripts/Editor/CapabilityPresentationSmoke.cs
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using GameCult.Caching;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using CultMath;
|
||||
using static CultMath.math;
|
||||
|
||||
// Batch proof of the capability presenters' wiring, without a scene: a zone ship is bound to an EntityInstance through the
|
||||
// game's own SetEntity, and impacts and grabs are published on the ship's Capabilities exactly as the simulation does. A hull absorb spawns
|
||||
// one hull effect under the instance and, for the player's ship at ScreenShake 1, raises one camera impulse; ScreenShake 0
|
||||
// or another ship raises none; a shield absorb spawns no hull effect. The shield ripple itself (ShieldManager.ShowHit
|
||||
// needs a shield material and a rig) is looked at in the editor, not here. Grabs: a Tractor Beam prefab under the instance
|
||||
// is bound by the same SetEntity and presents a grab from events alone (emission at Power and the aim at the held point
|
||||
// from Started to Completed or Cancelled, nothing otherwise, nothing for another entity's grab).
|
||||
//
|
||||
// Unity -batchmode -nographics -projectPath <project> -executeMethod CapabilityPresentationSmoke.Run -logFile <log>
|
||||
//
|
||||
// Do not pass -quit: Run exits the Editor itself, 0 on success and 1 on any failure.
|
||||
public static class CapabilityPresentationSmoke
|
||||
{
|
||||
private const BindingFlags Any = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
|
||||
|
||||
public static void Run()
|
||||
{
|
||||
var work = Path.Combine(Path.GetTempPath(), $"capability-presentation-smoke-{Guid.NewGuid():N}");
|
||||
GameObject[] objects = null;
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(work);
|
||||
objects = Check(work);
|
||||
Clean(objects, work);
|
||||
EditorApplication.Exit(0);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogException(exception);
|
||||
Clean(objects, work);
|
||||
EditorApplication.Exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Edit mode runs no Awake or Start; the smoke runs the ones the game's scene would have.
|
||||
private static void Awaken(Component component) => component.GetType().GetMethod("Awake", Any)?.Invoke(component, null);
|
||||
|
||||
private static GameObject[] Check(string work)
|
||||
{
|
||||
var settings = AssetDatabase.LoadAssetAtPath<GameSettings>("Assets/Resources/Settings.asset")?.GameplaySettings
|
||||
?? throw new InvalidOperationException("No Settings.asset.");
|
||||
|
||||
var shape = new Shape(5, 4);
|
||||
foreach (var cell in shape.AllCoordinates) shape[cell] = true;
|
||||
var hullData = new HullData
|
||||
{
|
||||
Name = "Smoke Hull", HullType = HullType.Ship, Shape = shape, Durability = 1000, Mass = 1000, Armor = 0
|
||||
};
|
||||
var cache = AetheriaStores.Open(Path.Combine(work, "smoke.cc"), catalogWritable: true);
|
||||
cache.Upsert(new VerseGrammar { Revision = 1 });
|
||||
cache.Upsert(hullData);
|
||||
cache.FlushAsync().Wait();
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= 8; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Smoke", Owner = null }, null);
|
||||
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Smoke Hull"));
|
||||
var ship = new Ship(items, zone, new EquippableItem { Data = hullRef, Durability = 1000, Lot = 1 }, new EntitySettings());
|
||||
var other = new Ship(items, zone, new EquippableItem { Data = hullRef, Durability = 1000, Lot = 2 }, new EntitySettings());
|
||||
zone.Admit(ship, piloted: false);
|
||||
zone.Admit(other, piloted: false);
|
||||
|
||||
// The scene's side: the tuning object with a pooled effect, and the game manager that names the player's ship.
|
||||
var feedbackObject = new GameObject("Smoke Impact Feedback");
|
||||
var feedback = feedbackObject.AddComponent<ImpactFeedback>();
|
||||
Awaken(feedback);
|
||||
var effectObject = new GameObject("Smoke Hull Effect");
|
||||
feedback.HullEffect = effectObject.AddComponent<Prototype>();
|
||||
var managerObject = new GameObject("Smoke Game Manager");
|
||||
var manager = managerObject.AddComponent<ActionGameManager>();
|
||||
typeof(ActionGameManager).GetProperty("Instance", Any).SetValue(null, manager);
|
||||
manager.CurrentEntity = ship;
|
||||
|
||||
var beamPrefab = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Content/Prefabs/Tractor Beam.prefab")
|
||||
?? throw new InvalidOperationException("No Tractor Beam prefab.");
|
||||
// An entity's instance as the game builds it: the beam is a child before SetEntity on both ship paths, so the
|
||||
// binder finds it as the game does.
|
||||
(GameObject Object, EntityInstance Instance, TractorBeam Beam) Bound(string name, Entity entity)
|
||||
{
|
||||
var go = new GameObject(name);
|
||||
var bound = go.AddComponent<EntityInstance>();
|
||||
Awaken(bound);
|
||||
bound.HullColliders = new HullCollider[0];
|
||||
bound.RadiatorHardpoints = new RadiatorHardpoint[0];
|
||||
bound.ThrusterHardpoints = new ThrusterHardpoint[0];
|
||||
bound.WeaponHardpoints = new WeaponHardpoint[0];
|
||||
bound.ArticulationPoints = new ArticulationPoint[0];
|
||||
var tractor = ((GameObject)PrefabUtility.InstantiatePrefab(beamPrefab, go.transform)).GetComponent<TractorBeam>();
|
||||
tractor.Power = .8f;
|
||||
tractor.Direction = Vector3.forward;
|
||||
bound.SetEntity(null, entity);
|
||||
return (go, bound, tractor);
|
||||
}
|
||||
var (instanceObject, instance, beam) = Bound("Smoke Entity Instance", ship);
|
||||
var (otherObject, _, otherBeam) = Bound("Smoke Other Instance", other);
|
||||
|
||||
int Effects() => instance.GetComponentsInChildren<Prototype>(true).Count(p => p != null && !p.isOriginalPrototype);
|
||||
void Expect(string what, int effects, int kicks)
|
||||
{
|
||||
if (Effects() != effects || feedback.KicksRaised != kicks)
|
||||
throw new InvalidOperationException($"{what}: {Effects()} hull effects (want {effects}), {feedback.KicksRaised} impulses (want {kicks}).");
|
||||
}
|
||||
AbsorbEvent Hit(AbsorbLayer layer) => new AbsorbEvent(float3(1f, 0f, 2f), float3(0f, 0f, 1f), 100f, DamageType.Kinetic, layer);
|
||||
|
||||
ActionGameManager.PlayerSettings.GraphicsSettings.ScreenShake = 1f;
|
||||
ship.Capabilities.PublishAbsorb(Hit(AbsorbLayer.Hull));
|
||||
Expect("player's hull hit", 1, 1);
|
||||
var effect = instance.GetComponentsInChildren<Prototype>(true).Single(p => !p.isOriginalPrototype);
|
||||
var drawn = DrawAhead.Carried(ship, float3(1f, 0f, 2f), ActionGameManager.Clock.Lead);
|
||||
if (abs(effect.transform.position.x - drawn.x) > 1e-3f || abs(effect.transform.position.z - drawn.z) > 1e-3f)
|
||||
throw new InvalidOperationException("The hull effect is not at the drawn impact point.");
|
||||
|
||||
ActionGameManager.PlayerSettings.GraphicsSettings.ScreenShake = 0f;
|
||||
ship.Capabilities.PublishAbsorb(Hit(AbsorbLayer.Hull));
|
||||
Expect("hull hit at ScreenShake 0", 2, 1);
|
||||
|
||||
ActionGameManager.PlayerSettings.GraphicsSettings.ScreenShake = 1f;
|
||||
manager.CurrentEntity = other;
|
||||
ship.Capabilities.PublishAbsorb(Hit(AbsorbLayer.Hull));
|
||||
// An untracked ship is not drawn at (ActionGameManager.DrawsAt, since contact-hud): no hull effect, no impulse.
|
||||
Expect("hull hit on an untracked ship", 2, 1);
|
||||
|
||||
// Once the player's instruments track it, the same hit is drawn.
|
||||
other.VisibleEntities.Add(ship);
|
||||
ship.Capabilities.PublishAbsorb(Hit(AbsorbLayer.Hull));
|
||||
Expect("hull hit on a tracked ship", 3, 1);
|
||||
other.VisibleEntities.Remove(ship);
|
||||
|
||||
manager.CurrentEntity = ship;
|
||||
ship.Capabilities.PublishAbsorb(Hit(AbsorbLayer.Shield));
|
||||
Expect("player's shield hit", 3, 2);
|
||||
|
||||
// Grabs: the beam presents the simulation's events and nothing else.
|
||||
float Emission() => beam.ParticleSystem.emission.rateOverTimeMultiplier;
|
||||
void ExpectBeam(string what, float emission, bool towardHeld)
|
||||
{
|
||||
beam.Update();
|
||||
var forward = beam.transform.forward;
|
||||
var aimed = towardHeld ? forward.x > .999f : forward.z > .999f;
|
||||
if (abs(Emission() - emission) > 1e-4f || !aimed)
|
||||
throw new InvalidOperationException($"{what}: emission {Emission()} (want {emission}), forward {forward} (want {(towardHeld ? "+x" : "+z")}).");
|
||||
}
|
||||
GrabEvent Grab(GrabPhase phase, float3 at) => new GrabEvent(new FloatingBodyId(7), phase, at, 1f);
|
||||
|
||||
ExpectBeam("idle beam", 0f, false);
|
||||
ship.Capabilities.PublishGrab(Grab(GrabPhase.Started, float3(20f, 0f, 0f)));
|
||||
ExpectBeam("grab started", .8f, true);
|
||||
foreach (var phase in new[] { GrabPhase.Extend, GrabPhase.Envelop, GrabPhase.Pull })
|
||||
{
|
||||
ship.Capabilities.PublishGrab(Grab(phase, float3(20f, 0f, 0f)));
|
||||
ExpectBeam($"grab {phase}", .8f, true);
|
||||
}
|
||||
ship.Capabilities.PublishGrab(Grab(GrabPhase.Completed, float3(20f, 0f, 0f)));
|
||||
ExpectBeam("grab completed", 0f, false);
|
||||
|
||||
// A grab cancelled after its item vanished carries no position: the beam goes dark and never turns toward it.
|
||||
ship.Capabilities.PublishGrab(Grab(GrabPhase.Started, float3(20f, 0f, 0f)));
|
||||
ExpectBeam("second grab started", .8f, true);
|
||||
ship.Capabilities.PublishGrab(Grab(GrabPhase.Cancelled, float3(0f, 0f, 0f)));
|
||||
ExpectBeam("grab cancelled", 0f, false);
|
||||
|
||||
// Aim is on the ground plane: a beam above the held point does not pitch toward it.
|
||||
var beamHome = beam.transform.position;
|
||||
beam.transform.position = beamHome + new Vector3(0f, 10f, 0f);
|
||||
ship.Capabilities.PublishGrab(Grab(GrabPhase.Started, float3(20f, 7f, 0f)));
|
||||
ExpectBeam("grab from above", .8f, true);
|
||||
ship.Capabilities.PublishGrab(Grab(GrabPhase.Completed, float3(20f, 7f, 0f)));
|
||||
beam.transform.position = beamHome;
|
||||
|
||||
// A grab held at the beam's own position has no direction to take: the beam keeps the aim it had.
|
||||
beam.Direction = Vector3.right;
|
||||
var atBeam = float3(beam.transform.position.x, 0f, beam.transform.position.z);
|
||||
ship.Capabilities.PublishGrab(Grab(GrabPhase.Started, atBeam));
|
||||
ExpectBeam("grab at the beam", .8f, true);
|
||||
ship.Capabilities.PublishGrab(Grab(GrabPhase.Completed, atBeam));
|
||||
beam.Direction = Vector3.forward;
|
||||
ExpectBeam("after the grab at the beam", 0f, false);
|
||||
|
||||
// The beam belongs to the ship's entity: another entity's grab, with its own beam bound, moves only that beam.
|
||||
other.Capabilities.PublishGrab(Grab(GrabPhase.Started, float3(20f, 0f, 0f)));
|
||||
ExpectBeam("another entity's grab", 0f, false);
|
||||
otherBeam.Update();
|
||||
if (abs(otherBeam.ParticleSystem.emission.rateOverTimeMultiplier - .8f) > 1e-4f)
|
||||
throw new InvalidOperationException("The other entity's own beam did not present its grab.");
|
||||
|
||||
Debug.Log("CAPABILITY_PRESENTATION_SMOKE ok effects=3 impulses=2 grabs=ok");
|
||||
return new[] { feedbackObject, effectObject, managerObject, instanceObject, otherObject };
|
||||
}
|
||||
|
||||
private static void Clean(GameObject[] objects, string work)
|
||||
{
|
||||
if (objects != null)
|
||||
foreach (var go in objects)
|
||||
if (go != null) UnityEngine.Object.DestroyImmediate(go);
|
||||
try { if (Directory.Exists(work)) Directory.Delete(work, true); }
|
||||
catch (Exception error) { Debug.LogWarning($"Impact presenter smoke left {work}: {error.Message}"); }
|
||||
}
|
||||
}
|
||||
|
|
@ -180,6 +180,32 @@ public static class FloatingItemsDrawnSmoke
|
|||
At(drawn, released, "step");
|
||||
Require(drawn.transform.position != before, "the pickup did not move with the body.");
|
||||
|
||||
// A held item keeps its label lit, so a pull is read as it happens. The label is let go dark first (nothing views
|
||||
// it), then the zone holds the item through its own TryHold and the label must come up.
|
||||
var earlyPickup = Pickups().Single(p => p.Item == early.Item);
|
||||
earlyPickup.LabelPersistDuration = 1;
|
||||
earlyPickup.LabelFadeDuration = .2f;
|
||||
for (var i = 0; i < 1200 && earlyPickup.ScanLabel.color.a > .05f; i++) yield return null;
|
||||
Require(earlyPickup.ScanLabel.color.a <= .05f, "label: an unviewed, unheld label did not go dark.");
|
||||
Require(zone.TryHold(taker, early.Id), "label: the zone refused to hold the item.");
|
||||
for (var i = 0; i < 1200 && earlyPickup.ScanLabel.color.a < .95f; i++) yield return null;
|
||||
Require(earlyPickup.ScanLabel.color.a >= .95f, "label: a held item's label is not lit.");
|
||||
|
||||
// The test scene's stand-in sim and tendril ride the zone's own held-pose curve: its pose at each point of a grab
|
||||
// is where the zone puts a held item at the same progress.
|
||||
var (extend, envelop, pull) = (1.5f, .75f, 1.5f);
|
||||
var holderAt = new CultMath.float3(taker.Position.x, 0f, taker.Position.z);
|
||||
var claimedAt = new CultMath.float3(early.HeldFrom.x, 0f, early.HeldFrom.y);
|
||||
foreach (var elapsed in new[] { 0f, extend + envelop - .01f, extend + envelop + .3f, extend + envelop + .75f, extend + envelop + pull })
|
||||
{
|
||||
zone.SetHeldProgress(early.Id, Mathf.Clamp01((elapsed - extend - envelop) / pull)); // the pull starts after Extend and Envelop
|
||||
zone.Update(.01f);
|
||||
var want = FieldTester.PulledPosition(claimedAt, holderAt, elapsed, extend, envelop, pull);
|
||||
Require(Mathf.Abs(early.Body.Position.x - want.x) < 1e-3f && Mathf.Abs(early.Body.Position.y - want.z) < 1e-3f,
|
||||
$"stand-in pose at {elapsed}s is {want}, the zone holds the item at {early.Body.Position}.");
|
||||
}
|
||||
zone.Free(early.Id);
|
||||
|
||||
// Pickup destroys exactly the picked-up item's pickup.
|
||||
Require(zone.TryPickUp(taker, released.Id), "the station refused a one-cell stack.");
|
||||
yield return null;
|
||||
|
|
|
|||
|
|
@ -1,132 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using GameCult.Caching;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using static CultMath.math;
|
||||
|
||||
// Batch proof of ImpactPresenter's wiring, without a scene: a zone ship is bound to an EntityInstance through the game's
|
||||
// own SetEntity, and impacts are published on the ship's Capabilities exactly as FireControl does. A hull absorb spawns
|
||||
// one hull effect under the instance and, for the player's ship at ScreenShake 1, raises one camera impulse; ScreenShake 0
|
||||
// or another ship raises none; a shield absorb spawns no hull effect. The shield ripple itself (ShieldManager.ShowHit
|
||||
// needs a shield material and a rig) is looked at in the editor, not here.
|
||||
//
|
||||
// Unity -batchmode -nographics -projectPath <project> -executeMethod ImpactPresenterSmoke.Run -logFile <log>
|
||||
//
|
||||
// Do not pass -quit: Run exits the Editor itself, 0 on success and 1 on any failure.
|
||||
public static class ImpactPresenterSmoke
|
||||
{
|
||||
private const BindingFlags Any = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
|
||||
|
||||
public static void Run()
|
||||
{
|
||||
var work = Path.Combine(Path.GetTempPath(), $"impact-presenter-smoke-{Guid.NewGuid():N}");
|
||||
GameObject[] objects = null;
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(work);
|
||||
objects = Check(work);
|
||||
Clean(objects, work);
|
||||
EditorApplication.Exit(0);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogException(exception);
|
||||
Clean(objects, work);
|
||||
EditorApplication.Exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Edit mode runs no Awake or Start; the smoke runs the ones the game's scene would have.
|
||||
private static void Awaken(Component component) => component.GetType().GetMethod("Awake", Any)?.Invoke(component, null);
|
||||
|
||||
private static GameObject[] Check(string work)
|
||||
{
|
||||
var settings = AssetDatabase.LoadAssetAtPath<GameSettings>("Assets/Resources/Settings.asset")?.GameplaySettings
|
||||
?? throw new InvalidOperationException("No Settings.asset.");
|
||||
|
||||
var shape = new Shape(5, 4);
|
||||
foreach (var cell in shape.AllCoordinates) shape[cell] = true;
|
||||
var hullData = new HullData
|
||||
{
|
||||
Name = "Smoke Hull", HullType = HullType.Ship, Shape = shape, Durability = 1000, Mass = 1000, Armor = 0
|
||||
};
|
||||
var cache = AetheriaStores.Open(Path.Combine(work, "smoke.cc"), catalogWritable: true);
|
||||
cache.Upsert(new VerseGrammar { Revision = 1 });
|
||||
cache.Upsert(hullData);
|
||||
cache.FlushAsync().Wait();
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= 8; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Smoke", Owner = null }, null);
|
||||
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Smoke Hull"));
|
||||
var ship = new Ship(items, zone, new EquippableItem { Data = hullRef, Durability = 1000, Lot = 1 }, new EntitySettings());
|
||||
var other = new Ship(items, zone, new EquippableItem { Data = hullRef, Durability = 1000, Lot = 2 }, new EntitySettings());
|
||||
zone.Admit(ship, piloted: false);
|
||||
zone.Admit(other, piloted: false);
|
||||
|
||||
// The scene's side: the tuning object with a pooled effect, and the game manager that names the player's ship.
|
||||
var feedbackObject = new GameObject("Smoke Impact Feedback");
|
||||
var feedback = feedbackObject.AddComponent<ImpactFeedback>();
|
||||
Awaken(feedback);
|
||||
var effectObject = new GameObject("Smoke Hull Effect");
|
||||
feedback.HullEffect = effectObject.AddComponent<Prototype>();
|
||||
var managerObject = new GameObject("Smoke Game Manager");
|
||||
var manager = managerObject.AddComponent<ActionGameManager>();
|
||||
typeof(ActionGameManager).GetProperty("Instance", Any).SetValue(null, manager);
|
||||
manager.CurrentEntity = ship;
|
||||
|
||||
var instanceObject = new GameObject("Smoke Entity Instance");
|
||||
var instance = instanceObject.AddComponent<EntityInstance>();
|
||||
Awaken(instance);
|
||||
instance.HullColliders = new HullCollider[0];
|
||||
instance.RadiatorHardpoints = new RadiatorHardpoint[0];
|
||||
instance.ThrusterHardpoints = new ThrusterHardpoint[0];
|
||||
instance.WeaponHardpoints = new WeaponHardpoint[0];
|
||||
instance.ArticulationPoints = new ArticulationPoint[0];
|
||||
instance.SetEntity(null, ship);
|
||||
|
||||
int Effects() => instance.GetComponentsInChildren<Prototype>(true).Count(p => p != null && !p.isOriginalPrototype);
|
||||
void Expect(string what, int effects, int kicks)
|
||||
{
|
||||
if (Effects() != effects || feedback.KicksRaised != kicks)
|
||||
throw new InvalidOperationException($"{what}: {Effects()} hull effects (want {effects}), {feedback.KicksRaised} impulses (want {kicks}).");
|
||||
}
|
||||
AbsorbEvent Hit(AbsorbLayer layer) => new AbsorbEvent(float3(1f, 0f, 2f), float3(0f, 0f, 1f), 100f, DamageType.Kinetic, layer);
|
||||
|
||||
ActionGameManager.PlayerSettings.GraphicsSettings.ScreenShake = 1f;
|
||||
ship.Capabilities.PublishAbsorb(Hit(AbsorbLayer.Hull));
|
||||
Expect("player's hull hit", 1, 1);
|
||||
var effect = instance.GetComponentsInChildren<Prototype>(true).Single(p => !p.isOriginalPrototype);
|
||||
var drawn = DrawAhead.Carried(ship, float3(1f, 0f, 2f), ActionGameManager.Clock.Lead);
|
||||
if (abs(effect.transform.position.x - drawn.x) > 1e-3f || abs(effect.transform.position.z - drawn.z) > 1e-3f)
|
||||
throw new InvalidOperationException("The hull effect is not at the drawn impact point.");
|
||||
|
||||
ActionGameManager.PlayerSettings.GraphicsSettings.ScreenShake = 0f;
|
||||
ship.Capabilities.PublishAbsorb(Hit(AbsorbLayer.Hull));
|
||||
Expect("hull hit at ScreenShake 0", 2, 1);
|
||||
|
||||
ActionGameManager.PlayerSettings.GraphicsSettings.ScreenShake = 1f;
|
||||
manager.CurrentEntity = other;
|
||||
ship.Capabilities.PublishAbsorb(Hit(AbsorbLayer.Hull));
|
||||
Expect("hull hit on another ship", 3, 1);
|
||||
|
||||
manager.CurrentEntity = ship;
|
||||
ship.Capabilities.PublishAbsorb(Hit(AbsorbLayer.Shield));
|
||||
Expect("player's shield hit", 3, 2);
|
||||
|
||||
Debug.Log("IMPACT_PRESENTER_SMOKE ok effects=3 impulses=2");
|
||||
return new[] { feedbackObject, effectObject, managerObject, instanceObject };
|
||||
}
|
||||
|
||||
private static void Clean(GameObject[] objects, string work)
|
||||
{
|
||||
if (objects != null)
|
||||
foreach (var go in objects)
|
||||
if (go != null) UnityEngine.Object.DestroyImmediate(go);
|
||||
try { if (Directory.Exists(work)) Directory.Delete(work, true); }
|
||||
catch (Exception error) { Debug.LogWarning($"Impact presenter smoke left {work}: {error.Message}"); }
|
||||
}
|
||||
}
|
||||
|
|
@ -8,27 +8,18 @@ using UnityEngine;
|
|||
// playground-cut.md fork L: "presentation is chosen per item, independently of capability... one
|
||||
// presentation... can visualize any subset of an item's capabilities"). This binder has no knowledge
|
||||
// of FieldDriver or any other concrete presentation, and no knowledge of what publishes events --
|
||||
// today the FieldShieldTest scene calls Bind() directly; a real capability owner will do the same
|
||||
// later without this binder changing.
|
||||
// EntityInstance binds every entity's events, and the FieldShieldTest scene binds its stand-in sim's the same way.
|
||||
public class CapabilityPresentationBinder : MonoBehaviour
|
||||
{
|
||||
private readonly List<IDisposable> _subscriptions = new List<IDisposable>();
|
||||
private Func<FloatingBodyId, Transform> _grabTargetResolver;
|
||||
private Func<FloatingBodyId, Vector3> _grabInitialVelocityResolver;
|
||||
|
||||
// Subscribes every presenter interface implemented by components on this object or its children to
|
||||
// the matching stream of `events`. `grabTargetResolver`/`grabInitialVelocityResolver` resolve a
|
||||
// GrabEvent's target id to the live Transform/velocity a grab presenter needs; the owner
|
||||
// of `events` supplies them (the test scene today; the real pickup capability's caller later).
|
||||
public void Bind(CapabilityEvents events, Func<FloatingBodyId, Transform> grabTargetResolver = null,
|
||||
Func<FloatingBodyId, Vector3> grabInitialVelocityResolver = null)
|
||||
// the matching stream of `events`.
|
||||
public void Bind(CapabilityEvents events)
|
||||
{
|
||||
Unbind();
|
||||
if (events == null) return;
|
||||
|
||||
_grabTargetResolver = grabTargetResolver;
|
||||
_grabInitialVelocityResolver = grabInitialVelocityResolver;
|
||||
|
||||
foreach (var presenter in GetComponentsInChildren<IAbsorbPresenter>(true))
|
||||
{
|
||||
var p = presenter;
|
||||
|
|
@ -38,12 +29,7 @@ public class CapabilityPresentationBinder : MonoBehaviour
|
|||
foreach (var presenter in GetComponentsInChildren<IGrabPresenter>(true))
|
||||
{
|
||||
var p = presenter;
|
||||
_subscriptions.Add(events.Grab.Subscribe(e =>
|
||||
{
|
||||
var target = _grabTargetResolver?.Invoke(e.Target);
|
||||
var velocity = _grabInitialVelocityResolver?.Invoke(e.Target) ?? Vector3.zero;
|
||||
p.Grab(e, target, velocity);
|
||||
}));
|
||||
_subscriptions.Add(events.Grab.Subscribe(e => p.Grab(e)));
|
||||
}
|
||||
|
||||
foreach (var presenter in GetComponentsInChildren<IMeleePresenter>(true))
|
||||
|
|
@ -63,8 +49,6 @@ public class CapabilityPresentationBinder : MonoBehaviour
|
|||
{
|
||||
foreach (var s in _subscriptions) s.Dispose();
|
||||
_subscriptions.Clear();
|
||||
_grabTargetResolver = null;
|
||||
_grabInitialVelocityResolver = null;
|
||||
}
|
||||
|
||||
private void OnDisable() => Unbind();
|
||||
|
|
|
|||
|
|
@ -10,13 +10,10 @@ public interface IAbsorbPresenter
|
|||
void Absorb(AbsorbEvent e);
|
||||
}
|
||||
|
||||
// Grab needs a live Transform to animate, which the engine-free GrabEvent cannot carry (its
|
||||
// Target is a plain identity, not a Unity reference). The binder resolves the id through a
|
||||
// delegate supplied by the event source's owner and hands the presenter the result, along with the
|
||||
// initial velocity the target was already moving with (also owner-supplied, for the same reason).
|
||||
// Presenters read the event and the simulated pose, never a Transform the sim does not own.
|
||||
public interface IGrabPresenter
|
||||
{
|
||||
void Grab(GrabEvent e, Transform target, Vector3 initialVelocity);
|
||||
void Grab(GrabEvent e);
|
||||
}
|
||||
|
||||
public interface IMeleePresenter
|
||||
|
|
|
|||
|
|
@ -63,11 +63,7 @@ public class FieldDriver : MonoBehaviour, IAbsorbPresenter, IGrabPresenter, IMel
|
|||
// public float GrabScaleAnimationExponent = .5f;
|
||||
|
||||
[Inspectable]
|
||||
public float GrabExtendTime;
|
||||
[Inspectable]
|
||||
public float GrabEnvelopTime;
|
||||
[Inspectable]
|
||||
public float GrabPullTime;
|
||||
public float TendrilTipScale = 1;
|
||||
|
||||
[Inspectable]
|
||||
public RectTransform.Axis RefractionAxis = RectTransform.Axis.Horizontal;
|
||||
|
|
@ -81,29 +77,17 @@ public class FieldDriver : MonoBehaviour, IAbsorbPresenter, IGrabPresenter, IMel
|
|||
private float _meleeTime;
|
||||
private bool _meleeActive;
|
||||
private MeshCollider _collider;
|
||||
private Transform _grabObject;
|
||||
private Vector3 _grabObjectStartPos;
|
||||
private Vector3 _grabObjectStartTendrilBasePos;
|
||||
private Vector3 _grabObjectEndPos;
|
||||
private Vector3 _grabObjectVelocity;
|
||||
private float _grabObjectScale;
|
||||
private bool _grabbing;
|
||||
private GrabPhase _grabPhase;
|
||||
private float _grabTime;
|
||||
private TendrilPhase _grabPhase;
|
||||
private float _phaseDuration;
|
||||
private Vector3 _grabStart;
|
||||
private Vector3 _grabStartTendrilBase;
|
||||
private Vector3 _tendrilBasePos;
|
||||
private Vector3 _tendrilBendTarget;
|
||||
private Vector3 _tendrilTargetPos;
|
||||
private float _fadePoint;
|
||||
|
||||
// Named distinctly from the global GrabPhase (CapabilityEvents.cs): this is FieldDriver's own
|
||||
// internal tendril-animation timing, still owned here until a real pickup capability exists to
|
||||
// publish per-phase GrabEvent transitions (see Grab(GrabEvent, Transform, Vector3) below).
|
||||
private enum TendrilPhase
|
||||
{
|
||||
Extend,
|
||||
Envelop,
|
||||
Pull
|
||||
}
|
||||
|
||||
private struct FieldHit
|
||||
{
|
||||
public float3 Position;
|
||||
|
|
@ -181,32 +165,41 @@ public class FieldDriver : MonoBehaviour, IAbsorbPresenter, IGrabPresenter, IMel
|
|||
_meleeTime = 0;
|
||||
}
|
||||
|
||||
public bool CanGrab => _grabObject == null;
|
||||
|
||||
public void GrabObject(Transform t, Vector3 v)
|
||||
{
|
||||
_grabPhase = TendrilPhase.Extend;
|
||||
_grabObject = t;
|
||||
_grabObjectStartPos = t.position;
|
||||
_grabObjectVelocity = v;
|
||||
_grabObjectScale = t.localScale.x;
|
||||
_tendrilBasePos = _grabObjectStartTendrilBasePos = _collider.ClosestPoint(_grabObjectStartPos);
|
||||
_fadePoint = .999f;
|
||||
}
|
||||
|
||||
// IAbsorbPresenter: delegates straight to the existing hit-buffer mechanism. e.DamageType is not
|
||||
// read -- the field shield has no per-damage-type visual today; it rides along in the event for
|
||||
// presentations that do vary by type (the mask itself is absorb-capability data, out of scope here).
|
||||
public void Absorb(AbsorbEvent e) => AddHit(e.Position, e.Direction, e.Magnitude);
|
||||
|
||||
// IGrabPresenter: only the Started phase does anything today. FieldDriver still times the
|
||||
// extend/envelop/pull animation itself (TendrilPhase above, driven by GrabExtendTime/
|
||||
// GrabEnvelopTime/GrabPullTime) because no capability yet publishes those phase transitions;
|
||||
// once one does, this is where FieldDriver would react to Envelop/Pull/Completed/Cancelled too.
|
||||
public void Grab(GrabEvent e, Transform target, Vector3 initialVelocity)
|
||||
// IGrabPresenter: the simulation owns the grab; this draws the tendril from the events alone. Started records
|
||||
// where the item is held and where the tendril leaves the field; Extend, Envelop and Pull set the phase and
|
||||
// restart the real-time phase clock over the event's duration (it clamps at 1 until the next event, so a
|
||||
// slowed simulation stretches the wait, not the animation); Completed and Cancelled end it.
|
||||
public void Grab(GrabEvent e)
|
||||
{
|
||||
if (e.Phase == GrabPhase.Started && target != null)
|
||||
GrabObject(target, initialVelocity);
|
||||
switch (e.Phase)
|
||||
{
|
||||
case GrabPhase.Started:
|
||||
_grabbing = true;
|
||||
_grabPhase = GrabPhase.Started;
|
||||
_grabStart = e.TargetPosition.ToUnity();
|
||||
_tendrilBasePos = _grabStartTendrilBase = _collider.ClosestPoint(_grabStart);
|
||||
_tendrilTargetPos = _grabStart;
|
||||
_fadePoint = .999f;
|
||||
break;
|
||||
case GrabPhase.Extend:
|
||||
case GrabPhase.Envelop:
|
||||
case GrabPhase.Pull:
|
||||
if (!_grabbing) break;
|
||||
_grabPhase = e.Phase;
|
||||
_grabTime = 0;
|
||||
_phaseDuration = e.PhaseDuration;
|
||||
break;
|
||||
case GrabPhase.Completed:
|
||||
case GrabPhase.Cancelled:
|
||||
_grabbing = false;
|
||||
_field.SetFloat("_TendrilInfluence", 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// IMeleePresenter: the field shield's swing shape comes from its own authored curves
|
||||
|
|
@ -277,74 +270,47 @@ public class FieldDriver : MonoBehaviour, IAbsorbPresenter, IGrabPresenter, IMel
|
|||
}
|
||||
|
||||
|
||||
if(_grabObject != null)
|
||||
if (_grabbing && _grabPhase != GrabPhase.Started)
|
||||
{
|
||||
_grabTime += Time.deltaTime / _grabPhase switch
|
||||
_grabTime = min(1, _grabTime + Time.deltaTime / max(_phaseDuration, 1e-4f));
|
||||
_tendrilBasePos = damp(_tendrilBasePos.ToCultMath(), _tendrilTargetPos.ToCultMath(), TendrilBaseDamping, Time.deltaTime).ToUnity();
|
||||
switch (_grabPhase)
|
||||
{
|
||||
TendrilPhase.Extend => GrabExtendTime,
|
||||
TendrilPhase.Envelop => GrabEnvelopTime,
|
||||
TendrilPhase.Pull => GrabPullTime,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
if (_grabTime > 1)
|
||||
{
|
||||
_grabTime = 0;
|
||||
if (_grabPhase == TendrilPhase.Envelop)
|
||||
{
|
||||
_grabObjectEndPos = _grabObject.position;
|
||||
}
|
||||
if (_grabPhase == TendrilPhase.Pull)
|
||||
{
|
||||
Destroy(_grabObject.gameObject);
|
||||
_grabObject = null;
|
||||
_field.SetFloat("_TendrilInfluence", 0);
|
||||
}
|
||||
else _grabPhase++;
|
||||
}
|
||||
|
||||
if (_grabObject != null)
|
||||
{
|
||||
_tendrilBasePos = damp(_tendrilBasePos.ToCultMath(), _grabObject.position.ToCultMath(), TendrilBaseDamping, Time.deltaTime).ToUnity();
|
||||
switch (_grabPhase)
|
||||
{
|
||||
case TendrilPhase.Extend:
|
||||
_tendrilBendTarget = _grabObjectStartPos;
|
||||
_grabObject.position += _grabObjectVelocity * Time.deltaTime;
|
||||
_tendrilTargetPos = lerp(
|
||||
lerp(_grabObjectStartTendrilBasePos.ToCultMath(), _grabObjectStartPos.ToCultMath(), _grabTime),
|
||||
lerp(_grabObjectStartPos.ToCultMath(), _grabObject.position.ToCultMath(), _grabTime),
|
||||
pow(_grabTime, TendrilExtensionExponent)).ToUnity();
|
||||
_field.SetFloat("_TendrilInfluence", pow(_grabTime, TendrilExtendBaseAnimationExponent));
|
||||
_field.SetFloat("_TendrilSize", lerp(TendrilBaseRadius/2,TendrilBaseRadius,pow(_grabTime, TendrilExtendBaseAnimationExponent)));
|
||||
_field.SetFloat("_TendrilRadius", _grabObjectScale * pow(_grabTime, TendrilTipRadiusAnimationExponent) * TendrilTipRadius);
|
||||
break;
|
||||
case TendrilPhase.Envelop:
|
||||
_tendrilBendTarget = _grabObjectStartPos;
|
||||
_tendrilTargetPos = _grabObject.position += _grabObjectVelocity * Time.deltaTime * (1-_grabTime);
|
||||
_field.SetFloat("_TendrilInfluence", 1);
|
||||
_field.SetFloat("_TendrilSize", TendrilBaseRadius);
|
||||
_field.SetFloat("_TendrilRadius", _grabObjectScale * TendrilTipRadius);
|
||||
break;
|
||||
case TendrilPhase.Pull:
|
||||
_tendrilTargetPos = _grabObject.position = lerp(_grabObjectEndPos.ToCultMath(), transform.position.ToCultMath(), _grabTime*_grabTime).ToUnity();
|
||||
_tendrilBendTarget = lerp(_grabObjectStartPos.ToCultMath(), _grabObjectEndPos.ToCultMath(), _grabTime*_grabTime).ToUnity();
|
||||
if (_fadePoint > .99 && transform.InverseTransformPoint(_grabObject.position).sqrMagnitude < 1)
|
||||
_fadePoint = _grabTime;
|
||||
_field.SetFloat("_TendrilInfluence", pow(smoothstep(1, _fadePoint, _grabTime), TendrilFadeExponent));
|
||||
_field.SetFloat("_TendrilSize", TendrilBaseRadius);
|
||||
_field.SetFloat("_TendrilRadius", _grabObjectScale * TendrilTipRadius);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
var tendrilRadii = EnvelopeRadiiOrFallback();
|
||||
_field.SetVector("_TendrilBase", Vector3.Scale(transform.InverseTransformPoint(_tendrilBasePos).normalized, tendrilRadii));
|
||||
_field.SetVector("_TendrilBend", Vector3.Scale(transform.InverseTransformPoint(_tendrilBendTarget), tendrilRadii));
|
||||
_field.SetVector("_TendrilTarget", Vector3.Scale(transform.InverseTransformPoint(_tendrilTargetPos), tendrilRadii));
|
||||
case GrabPhase.Extend:
|
||||
_tendrilBendTarget = _grabStart;
|
||||
_tendrilTargetPos = lerp(
|
||||
lerp(_grabStartTendrilBase.ToCultMath(), _grabStart.ToCultMath(), _grabTime),
|
||||
_grabStart.ToCultMath(),
|
||||
pow(_grabTime, TendrilExtensionExponent)).ToUnity();
|
||||
_field.SetFloat("_TendrilInfluence", pow(_grabTime, TendrilExtendBaseAnimationExponent));
|
||||
_field.SetFloat("_TendrilSize", lerp(TendrilBaseRadius/2,TendrilBaseRadius,pow(_grabTime, TendrilExtendBaseAnimationExponent)));
|
||||
_field.SetFloat("_TendrilRadius", TendrilTipScale * pow(_grabTime, TendrilTipRadiusAnimationExponent) * TendrilTipRadius);
|
||||
break;
|
||||
case GrabPhase.Envelop:
|
||||
_tendrilBendTarget = _grabStart;
|
||||
_tendrilTargetPos = _grabStart;
|
||||
_field.SetFloat("_TendrilInfluence", 1);
|
||||
_field.SetFloat("_TendrilSize", TendrilBaseRadius);
|
||||
_field.SetFloat("_TendrilRadius", TendrilTipScale * TendrilTipRadius);
|
||||
break;
|
||||
case GrabPhase.Pull:
|
||||
// The curve of Zone's held pose (Zone.HeldPull): the tip rides the item the simulation pulls in.
|
||||
_tendrilTargetPos = lerp(_grabStart.ToCultMath(), transform.position.ToCultMath(), Zone.HeldPull(_grabTime)).ToUnity();
|
||||
_tendrilBendTarget = _grabStart;
|
||||
if (_fadePoint > .99 && transform.InverseTransformPoint(_tendrilTargetPos).sqrMagnitude < 1)
|
||||
_fadePoint = _grabTime;
|
||||
_field.SetFloat("_TendrilInfluence", pow(smoothstep(1, _fadePoint, _grabTime), TendrilFadeExponent));
|
||||
_field.SetFloat("_TendrilSize", TendrilBaseRadius);
|
||||
_field.SetFloat("_TendrilRadius", TendrilTipScale * TendrilTipRadius);
|
||||
break;
|
||||
}
|
||||
var tendrilRadii = EnvelopeRadiiOrFallback();
|
||||
_field.SetVector("_TendrilBase", Vector3.Scale(transform.InverseTransformPoint(_tendrilBasePos).normalized, tendrilRadii));
|
||||
_field.SetVector("_TendrilBend", Vector3.Scale(transform.InverseTransformPoint(_tendrilBendTarget), tendrilRadii));
|
||||
_field.SetVector("_TendrilTarget", Vector3.Scale(transform.InverseTransformPoint(_tendrilTargetPos), tendrilRadii));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private float almostIdentity( float x )
|
||||
{
|
||||
return x*x*(2.0f-x);
|
||||
|
|
@ -358,7 +324,7 @@ public class FieldDriver : MonoBehaviour, IAbsorbPresenter, IGrabPresenter, IMel
|
|||
private const int GIZMO_STEPS = 16;
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
if (_grabObject != null)
|
||||
if (_grabbing)
|
||||
{
|
||||
Vector3 previous = _tendrilBasePos;
|
||||
for (int i = 1; i <= GIZMO_STEPS; i++)
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ public class ItemPickup : MonoBehaviour
|
|||
var viewAngle = acos(dot(toThis, ViewDirection)) * Mathf.Rad2Deg;
|
||||
if (length(diff) < LabelDisplayMaxDistance && viewAngle < LabelDisplayAngle)
|
||||
_displayTime = Time.time;
|
||||
if (_floating?.Holder != null) _displayTime = Time.time;
|
||||
var targetAlpha = Time.time - _displayTime < LabelPersistDuration ? 1 : 0;
|
||||
var c = ScanLabel.color;
|
||||
c.a = c.a + sign(targetAlpha - c.a) * (Time.deltaTime / LabelFadeDuration);
|
||||
|
|
|
|||
|
|
@ -1,22 +1,51 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
using CultMath.UnityBridge;
|
||||
|
||||
public class TractorBeam : MonoBehaviour
|
||||
// Presents the ship's grab: while the simulation holds an item, the beam looks along the ground-plane vector to the
|
||||
// point the item was held from and emits at Power. Zone pulls the held item along the segment from the ship to that
|
||||
// point (docs/aetheria-release-map.md L11), which is why GrabEvent.TargetPosition on Started is the exact aim and no
|
||||
// pose lookup is needed. Idle, the beam turns with Direction and emits nothing. The simulation decides every grab fact.
|
||||
public class TractorBeam : MonoBehaviour, IGrabPresenter
|
||||
{
|
||||
public ParticleSystem ParticleSystem;
|
||||
public float Radius;
|
||||
public float Traction;
|
||||
public float Distance;
|
||||
|
||||
|
||||
public float Power { get; set; }
|
||||
public Vector3 Direction { get; set; }
|
||||
|
||||
private bool _grabbing;
|
||||
private Vector3 _heldPoint;
|
||||
private Vector3 _heldDirection;
|
||||
|
||||
// Cancelled can carry no position (a vanished item), so only Started is read for one.
|
||||
public void Grab(GrabEvent e)
|
||||
{
|
||||
switch (e.Phase)
|
||||
{
|
||||
case GrabPhase.Started:
|
||||
_grabbing = true;
|
||||
_heldPoint = e.TargetPosition.ToUnity();
|
||||
_heldDirection = Direction;
|
||||
break;
|
||||
case GrabPhase.Completed:
|
||||
case GrabPhase.Cancelled:
|
||||
_grabbing = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
transform.rotation = Quaternion.LookRotation(Direction);
|
||||
// The beam draws nothing until it presents a sim grab (loot-3): no particles, and no pull; the simulation
|
||||
// decides every floating item's motion.
|
||||
var look = Direction;
|
||||
if (_grabbing)
|
||||
{
|
||||
var toHeld = _heldPoint - transform.position;
|
||||
toHeld.y = 0;
|
||||
if (toHeld.sqrMagnitude > 1e-6f) _heldDirection = toHeld;
|
||||
look = _heldDirection;
|
||||
}
|
||||
transform.rotation = Quaternion.LookRotation(look);
|
||||
var emission = ParticleSystem.emission;
|
||||
emission.rateOverTimeMultiplier = 0;
|
||||
emission.rateOverTimeMultiplier = _grabbing ? Power : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,18 +36,20 @@ public class CombatState : BaseState
|
|||
|
||||
// Cut 2 (docs/fire-control-cut.md): aim at whichever revealed weapon on the target hits hardest at
|
||||
// this range, or clear the aim point when nothing qualifies -- the same predicate
|
||||
// (FireControl.IsRevealed) and the same writer (TrySelectTargetItem) the player path uses.
|
||||
// (FireControl.IsRevealed) and the same writer (TrySelectTargetItem) the player path uses. Which weapons it
|
||||
// believes the target has, and how hard each hits, come from its book's claims, not the target's live gear
|
||||
// (aetheria-ew, classify-from-belief; ew-claim-weapon-measure-ruled: the claimed design's rated figures); the
|
||||
// reveal's Item is the aim and stays the truth.
|
||||
EquippedItem bestTargetItem = null;
|
||||
var bestTargetDps = float.MinValue;
|
||||
foreach (var targetItem in target.Equipment)
|
||||
foreach (var reveal in _agent.Ship.RevealedWeapons(target))
|
||||
{
|
||||
if (!(targetItem.Behaviors.FirstOrDefault(b => b is Weapon) is Weapon targetWeapon)) continue;
|
||||
if (!FireControl.IsRevealed(_agent.Ship, targetItem)) continue;
|
||||
var dps = targetWeapon.RangeDamagePerSecond(targetDistance);
|
||||
if (!FireControl.IsRevealed(_agent.Ship, reveal.Item)) continue;
|
||||
var dps = reveal.Claimed.Behaviors.OfType<WeaponData>().First().RatedRangeDamagePerSecond(targetDistance);
|
||||
if (dps > bestTargetDps)
|
||||
{
|
||||
bestTargetDps = dps;
|
||||
bestTargetItem = targetItem;
|
||||
bestTargetItem = reveal.Item;
|
||||
}
|
||||
}
|
||||
_agent.Ship.TrySelectTargetItem(bestTargetItem);
|
||||
|
|
|
|||
|
|
@ -54,6 +54,15 @@ public class ChargedWeaponData : InstantWeaponData
|
|||
{
|
||||
return new ChargedWeapon(this, item);
|
||||
}
|
||||
|
||||
// The one formula of a charged weapon's damage per second at a range (see InstantWeaponData.RangeDamagePerSecond).
|
||||
internal static float RangeDamagePerSecond(BezierCurve damageCurve, float damage, float chargeFiringDamageMultiplier,
|
||||
float minRange, float range, float cooldown, float chargeTime, float atRange) =>
|
||||
damage * chargeFiringDamageMultiplier * damageCurve.Evaluate(saturate(unlerp(minRange, range, atRange))) / (cooldown + chargeTime);
|
||||
|
||||
public override float RatedRangeDamagePerSecond(float range) =>
|
||||
RangeDamagePerSecond(DamageCurve, PerRound(Damage.Max, Count.Max), ChargeFiringDamageMultiplier, MinRange.Max, Range.Max,
|
||||
Cooldown.Max, ChargeTime.Max, range);
|
||||
}
|
||||
|
||||
public class ChargedWeapon : InstantWeapon
|
||||
|
|
@ -75,13 +84,14 @@ public class ChargedWeapon : InstantWeapon
|
|||
// authored charge-up speed IS its sustained rate of fire, not a second, disconnected number.
|
||||
protected override float RateOverride => Evaluate(_data.ChargeEnergy);
|
||||
|
||||
public override float DamagePerSecond => Damage * _data.ChargeFiringDamageMultiplier / (Cooldown + ChargeTime);
|
||||
// The charge multiplier lives in the formula alone: Damage is lerped by charge for the shot itself (UpdateStats), so the
|
||||
// live reads take the uncharged damage, and a nominal weapon reads its design's rated figure at every charge.
|
||||
private float _unchargedDamage;
|
||||
public override float DamagePerSecond => _unchargedDamage * _data.ChargeFiringDamageMultiplier / (Cooldown + ChargeTime);
|
||||
public override float RangeDamagePerSecond(float range)
|
||||
{
|
||||
return Damage *
|
||||
_data.ChargeFiringDamageMultiplier *
|
||||
_data.DamageCurve.Evaluate(saturate(unlerp(MinRange, Range, range))) /
|
||||
(Cooldown + ChargeTime);
|
||||
return ChargedWeaponData.RangeDamagePerSecond(_data.DamageCurve, _unchargedDamage, _data.ChargeFiringDamageMultiplier,
|
||||
MinRange, Range, Cooldown, ChargeTime, range);
|
||||
}
|
||||
|
||||
public event Action OnStartCharging;
|
||||
|
|
@ -121,6 +131,7 @@ public class ChargedWeapon : InstantWeapon
|
|||
ChargeTime = Evaluate(_data.ChargeTime);
|
||||
ChargeEnergy = Evaluate(_data.ChargeEnergy);
|
||||
ChargeHeat = Evaluate(_data.ChargeHeat);
|
||||
_unchargedDamage = Damage;
|
||||
Damage *= lerp(1, _data.ChargeFiringDamageMultiplier, saturate(_charge));
|
||||
Heat *= lerp(1, _data.ChargeFiringHeatMultiplier, saturate(_charge));
|
||||
Spread *= lerp(1, _data.ChargeFiringSpreadMultiplier, saturate(_charge));
|
||||
|
|
|
|||
|
|
@ -23,6 +23,13 @@ public class ConstantWeaponData : WeaponData
|
|||
{
|
||||
return new ConstantWeapon(this, item);
|
||||
}
|
||||
|
||||
// The one formula of a beam's damage per second at a range (see InstantWeaponData.RangeDamagePerSecond).
|
||||
internal static float RangeDamagePerSecond(BezierCurve damageCurve, float damage, float minRange, float range, float atRange) =>
|
||||
damage * damageCurve.Evaluate(saturate(unlerp(minRange, range, atRange)));
|
||||
|
||||
public override float RatedRangeDamagePerSecond(float range) =>
|
||||
RangeDamagePerSecond(DamageCurve, Damage.Max, MinRange.Max, Range.Max, range);
|
||||
}
|
||||
|
||||
public class ConstantWeapon : Weapon, IProgressBehavior, IEventBehavior, IPowerConsumer
|
||||
|
|
@ -49,7 +56,7 @@ public class ConstantWeapon : Weapon, IProgressBehavior, IEventBehavior, IPowerC
|
|||
public override float DamagePerSecond => Damage;
|
||||
public override float RangeDamagePerSecond(float range)
|
||||
{
|
||||
return Damage * _data.DamageCurve.Evaluate(saturate(unlerp(MinRange, Range, range)));
|
||||
return ConstantWeaponData.RangeDamagePerSecond(_data.DamageCurve, Damage, MinRange, Range, range);
|
||||
}
|
||||
|
||||
public event Action OnReloadBegin;
|
||||
|
|
|
|||
|
|
@ -30,6 +30,17 @@ public class InstantWeaponData : WeaponData
|
|||
{
|
||||
return new InstantWeapon(this, item);
|
||||
}
|
||||
|
||||
// The one formula of an instant weapon's damage per second at a range; the live weapon calls it with its evaluated
|
||||
// stats, the design with its rated ones. A burst's round carries its share of the totals: PerRound, the one divisor
|
||||
// UpdateStats and the rated read both use.
|
||||
internal static float PerRound(float total, float burstCount) => total / (int) burstCount;
|
||||
|
||||
internal static float RangeDamagePerSecond(BezierCurve damageCurve, float damage, float minRange, float range, float cooldown, float atRange) =>
|
||||
damage * damageCurve.Evaluate(saturate(unlerp(minRange, range, atRange))) / cooldown;
|
||||
|
||||
public override float RatedRangeDamagePerSecond(float range) =>
|
||||
RangeDamagePerSecond(DamageCurve, PerRound(Damage.Max, Count.Max), MinRange.Max, Range.Max, Cooldown.Max, range);
|
||||
}
|
||||
|
||||
public class InstantWeapon : Weapon, IProgressBehavior, IEventBehavior, IPowerConsumer
|
||||
|
|
@ -61,7 +72,7 @@ public class InstantWeapon : Weapon, IProgressBehavior, IEventBehavior, IPowerCo
|
|||
public override float DamagePerSecond => Damage / Cooldown;
|
||||
public override float RangeDamagePerSecond(float range)
|
||||
{
|
||||
return Damage * _data.DamageCurve.Evaluate(saturate(unlerp(MinRange, Range, range))) / Cooldown;
|
||||
return InstantWeaponData.RangeDamagePerSecond(_data.DamageCurve, Damage, MinRange, Range, Cooldown, range);
|
||||
}
|
||||
|
||||
public override int Ammo
|
||||
|
|
@ -129,9 +140,9 @@ public class InstantWeapon : Weapon, IProgressBehavior, IEventBehavior, IPowerCo
|
|||
BurstTime = Evaluate(_data.BurstTime);
|
||||
Cooldown = Evaluate(_data.Cooldown);
|
||||
|
||||
Damage /= (int) BurstCount;
|
||||
Heat /= (int) BurstCount;
|
||||
Energy /= (int) BurstCount;
|
||||
Damage = InstantWeaponData.PerRound(Damage, BurstCount);
|
||||
Heat = InstantWeaponData.PerRound(Heat, BurstCount);
|
||||
Energy = InstantWeaponData.PerRound(Energy, BurstCount);
|
||||
}
|
||||
|
||||
// Cut 4 (docs/stats-and-power-cut.md §7 Q4): capacity and rate, resolved fresh rather than off the cached
|
||||
|
|
|
|||
|
|
@ -71,6 +71,13 @@ public abstract class WeaponData : BehaviorData
|
|||
// weapon-tracking-authored). Unauthored is a mount that follows anything: FireControl.PMount reads +infinity as 1.
|
||||
[Inspectable, JsonProperty("tracking"), Key(33), RuntimeInspectable]
|
||||
public PerformanceStat Tracking = new PerformanceStat { Min = float.PositiveInfinity, Max = float.PositiveInfinity };
|
||||
|
||||
// What an observer who believes this is the design measures it by (aetheria-ew, ew-claim-weapon-measure-ruled): the
|
||||
// design's rated figures, each stat's Max (what a stat with no terms resolves to), through the same formula the
|
||||
// live weapon's RangeDamagePerSecond runs over its evaluated stats. Never the carrier's quality, heat, wear or power.
|
||||
public abstract float RatedRangeDamagePerSecond(float range);
|
||||
[IgnoreMember]
|
||||
public float RatedRange => Range.Max;
|
||||
}
|
||||
|
||||
public abstract class Weapon : Behavior, IActivatedBehavior
|
||||
|
|
|
|||
|
|
@ -103,7 +103,27 @@ public abstract class Entity
|
|||
// each equipped suppressor folds the strength in equipment order and pays for what it alone removed.
|
||||
foreach (var suppressor in GetBehaviors<LaunchSuppressor>())
|
||||
strength = suppressor.Suppress(channel, strength);
|
||||
_visibilitySources[source] = new Signature(channel, strength, item);
|
||||
_visibilitySources[source] = new Signature(channel, strength, item, ClaimOf(item));
|
||||
}
|
||||
|
||||
// What this ship's acts claim (aetheria-ew, lie-is-own-emission, classify-from-belief): per equipped item, the
|
||||
// equippable class every act of that item is stamped with (SetSignature, Emit, ReceivePaint). The hull is the
|
||||
// item EquippedHull, so its claim lives in the same slot as every other: the hull class its own acts carry and
|
||||
// the profile an observer derives (ClaimedHull) are one value. The truth unless claimed otherwise. Claim is the
|
||||
// one write port; its production writer is the EW behaviour of cut ew-masquerade, tests only until then.
|
||||
// Deactivate clears the slots. Aim, damage and physics keep the truth.
|
||||
private readonly Dictionary<EquippedItem, EquippableItemData> _itemClaims = new Dictionary<EquippedItem, EquippableItemData>();
|
||||
public HullData ClaimedHull => (ClaimOf(EquippedHull) as HullData) ?? HullData;
|
||||
public EquippableItemData ClaimOf(EquippedItem item) =>
|
||||
item == null ? null : _itemClaims.TryGetValue(item, out var claimed) ? claimed : item.Data;
|
||||
internal void Claim(HullData hull) => Claim(EquippedHull, hull);
|
||||
internal void Claim(EquippedItem item, EquippableItemData claimed)
|
||||
{
|
||||
// One hull class in the book: a hull class is claimed by the hull and only by the hull, whichever side asks.
|
||||
if (claimed != null && (item == EquippedHull) != (claimed is HullData))
|
||||
throw new ArgumentException("Only the hull claims a hull class, and the hull claims nothing else.", nameof(claimed));
|
||||
if (claimed == null) _itemClaims.Remove(item);
|
||||
else _itemClaims[item] = claimed;
|
||||
}
|
||||
public readonly ReactiveDictionary<Entity, float> EntityInfoGathered = new ReactiveDictionary<Entity, float>();
|
||||
|
||||
|
|
@ -883,6 +903,7 @@ public abstract class Entity
|
|||
_pingReturns.Clear();
|
||||
_emissions.Clear();
|
||||
_reveals.Clear();
|
||||
_itemClaims.Clear();
|
||||
}
|
||||
|
||||
// Zone.Update runs this on every entity before any entity updates, so a paint made during tick N is published
|
||||
|
|
@ -895,7 +916,7 @@ public abstract class Entity
|
|||
foreach (var paint in _paintInbox)
|
||||
{
|
||||
_incomingPaints.Add(paint.Warning);
|
||||
RecordReveal(paint.Emitter, paint.Item, SignatureChannel.Emission);
|
||||
RecordReveal(paint.Emitter, paint.Item, SignatureChannel.Emission, paint.Warning.EmitterClass);
|
||||
}
|
||||
_paintInbox.Clear();
|
||||
_pingReturns.Clear();
|
||||
|
|
@ -915,16 +936,17 @@ public abstract class Entity
|
|||
internal void Emit(EquippedItem item, float hearRange)
|
||||
{
|
||||
if (!_active) return;
|
||||
_emissions.Add(new ActiveEmission(item, hearRange));
|
||||
_emissions.Add(new ActiveEmission(item, hearRange, ClaimOf(item)));
|
||||
}
|
||||
|
||||
// The callers are Sensor.Execute (a Search paint, Strength 0) and LockWeapon.Execute (a Track paint, Strength
|
||||
// the emitter's own Lock at that paint). The emitter's class is the item's data (null with no item: a
|
||||
// consumable's effect, which has no item and so is never revealed).
|
||||
// the emitter's own Lock at that paint). The emitter's class is the class its ship claims for the item (ClaimOf: the
|
||||
// item's data unless claimed otherwise; null with no item: a consumable's effect, which has no item and so is never
|
||||
// revealed).
|
||||
internal void ReceivePaint(Entity emitter, EquippedItem item, PaintMode mode, float strength)
|
||||
{
|
||||
if (!_active) return;
|
||||
_paintInbox.Add(new PendingPaint(new PaintWarning(normalize((emitter.Position - Position).xz), item?.Data, mode, strength), emitter, item));
|
||||
_paintInbox.Add(new PendingPaint(new PaintWarning(normalize((emitter.Position - Position).xz), emitter.ClaimOf(item), mode, strength), emitter, item));
|
||||
}
|
||||
|
||||
// The reveal book's one way in and its one membership gate (callers: Sense and PublishInbox, both in this file):
|
||||
|
|
@ -932,13 +954,14 @@ public abstract class Entity
|
|||
// radiation, a consumable) reveals nothing, and neither does a subject that is not in the zone: a departed
|
||||
// subject's entries are never created or refreshed, from any caller, and neither does an item this observer
|
||||
// perceives destroyed (PerceivesDestroyed). A later act of the same item replaces the
|
||||
// earlier one.
|
||||
internal void RecordReveal(Entity subject, EquippedItem item, SignatureChannel channel)
|
||||
// earlier one. `claimed` is the class the carrier (signature, emission or paint) stamped: what this observer's
|
||||
// classifying readers will believe of the act; `item` stays the aim and book key.
|
||||
internal void RecordReveal(Entity subject, EquippedItem item, SignatureChannel channel, EquippableItemData claimed)
|
||||
{
|
||||
if (!_active || item == null || !Zone.Entities.Contains(subject) || PerceivesDestroyed(subject, item)) return;
|
||||
if (!_reveals.TryGetValue(subject, out var book))
|
||||
_reveals[subject] = book = new Dictionary<EquippedItem, Reveal>();
|
||||
book[item] = new Reveal(item, channel, normalize((subject.Position - Position).xz), Zone.Time);
|
||||
book[item] = new Reveal(item, channel, normalize((subject.Position - Position).xz), Zone.Time, claimed);
|
||||
}
|
||||
|
||||
// This observer resolves `subject`'s gear (ContactOf's Identified gate) and `item` of it is destroyed: the one rule
|
||||
|
|
@ -970,6 +993,31 @@ public abstract class Entity
|
|||
public IEnumerable<Reveal> RevealsOf(Entity subject) =>
|
||||
_reveals.TryGetValue(subject, out var book) ? book.Values : Enumerable.Empty<Reveal>();
|
||||
|
||||
// The weapons this observer believes `subject` has: each reveal whose claimed class carries a weapon behaviour, in the
|
||||
// subject's equipment order (never the book's slot order, which follows when reveals were made and ended). The one
|
||||
// classifying read of the book for weapons, shared by the enemy schematic and Combat (aetheria-ew, classify-from-belief);
|
||||
// a gun claimed as a non-weapon is not listed, though RevealsOf holds it for aim.
|
||||
public IEnumerable<Reveal> RevealedWeapons(Entity subject) =>
|
||||
RevealsOf(subject).Where(r => r.Claimed != null && r.Claimed.Behaviors.Any(b => b is WeaponData))
|
||||
.OrderBy(r => FittedIndex(subject, r.Item)).ThenBy(r => r.Item.EquippableItem.Lot);
|
||||
|
||||
// A revealed item's place in the subject's equipment; gear the subject no longer carries follows all fitted gear
|
||||
// (ties among it fall to the lot, a stable id), never ahead of it.
|
||||
private static int FittedIndex(Entity subject, EquippedItem item)
|
||||
{
|
||||
var index = subject.Equipment.IndexOf(item);
|
||||
return index < 0 ? int.MaxValue : index;
|
||||
}
|
||||
|
||||
// The weapon rows of `subject`'s schematic as this ship reads it: its own equipment when `subject` is this ship (each
|
||||
// row its own data and live range, nothing to doubt about your own ship), otherwise RevealedWeapons (each row the
|
||||
// claimed class and that design's rated range). The one owner of which rows, which class and which range an
|
||||
// enemy schematic shows; the Unity SchematicDisplay renders it.
|
||||
public IEnumerable<SchematicRow> SchematicRows(Entity subject) =>
|
||||
subject == this
|
||||
? subject.Equipment.Where(i => i.Behaviors.Any(b => b.Data is WeaponData)).Select(i => new SchematicRow(i, i.Data, true))
|
||||
: RevealedWeapons(subject).Select(r => new SchematicRow(r.Item, r.Claimed, false));
|
||||
|
||||
// Whether this observer's book for `subject` holds `item`: the book's membership, no live gear read.
|
||||
internal bool HasReveal(Entity subject, EquippedItem item) =>
|
||||
_reveals.TryGetValue(subject, out var book) && book.ContainsKey(item);
|
||||
|
|
@ -991,7 +1039,9 @@ public abstract class Entity
|
|||
var remembered = any && Zone.Time - freshest.Time < settings.BearingMemory;
|
||||
var detected = tracked || remembered;
|
||||
var bearing = tracked ? normalize((subject.Position - Position).xz) : remembered ? freshest.Bearing : default;
|
||||
return new Contact(detected, tracked, detected && any, info >= settings.TargetGearInfoThreshold, bearing, info);
|
||||
var identified = info >= settings.TargetGearInfoThreshold;
|
||||
// Profile is the one derivation of the hull this observer believes (no stored belief until the judge, ew-masquerade).
|
||||
return new Contact(detected, tracked, detected && any, identified, bearing, info, identified ? subject.ClaimedHull : null);
|
||||
}
|
||||
|
||||
// What this observer's instruments receive of other ships' acts this tick (Zone.Update calls it once, after every
|
||||
|
|
@ -1015,7 +1065,7 @@ public abstract class Entity
|
|||
{
|
||||
if (signature.Item == null) continue;
|
||||
if (perSignature < 0f) perSignature = PassiveInfo(other.Position.xz, 1f);
|
||||
if (signature.Strength * perSignature > threshold) RecordReveal(other, signature.Item, signature.Channel);
|
||||
if (signature.Strength * perSignature > threshold) RecordReveal(other, signature.Item, signature.Channel, signature.Claimed);
|
||||
}
|
||||
// Every emission this ship made this tick that reaches this observer is revealed, bearing only (ruling
|
||||
// sensor-stat-set), through RecordReveal's one gate. It feeds no track.
|
||||
|
|
@ -1026,7 +1076,7 @@ public abstract class Entity
|
|||
for (var j = 0; j < other._emissions.Count; j++)
|
||||
{
|
||||
var emission = other._emissions[j];
|
||||
if (planar <= emission.HearRange * hearing * obscuration) RecordReveal(other, emission.Item, SignatureChannel.Emission);
|
||||
if (planar <= emission.HearRange * hearing * obscuration) RecordReveal(other, emission.Item, SignatureChannel.Emission, emission.Claimed);
|
||||
}
|
||||
}
|
||||
// The observer resolves this ship's gear (PerceivesDestroyed), so it perceives an item of it destroyed and
|
||||
|
|
|
|||
|
|
@ -20,13 +20,13 @@ public sealed class AiFusedDiscipline : Scenario
|
|||
("Cockpit 2x2", int2(2, 6), None),
|
||||
("Large Drive", int2(1, 0), Reversed),
|
||||
("Large Drive", int2(3, 0), Reversed),
|
||||
("Talaria", int2(2, 14), CounterClockwise),
|
||||
("Talaria", int2(3, 14), Clockwise),
|
||||
("Core Power", int2(2, 4), None),
|
||||
("Rugged Drive", int2(2, 14), CounterClockwise),
|
||||
("Rugged Drive", int2(3, 14), Clockwise),
|
||||
("Compact Reactor", int2(2, 4), None),
|
||||
(weapon, int2(1, 8), None),
|
||||
("Iapyx", int2(2, 2), CounterClockwise),
|
||||
("Iapyx", int2(3, 2), Clockwise),
|
||||
("not if i see you first", int2(3, 10), None),
|
||||
("Radiator", int2(2, 2), CounterClockwise),
|
||||
("Radiator", int2(3, 2), Clockwise),
|
||||
("Compact Sensor", int2(3, 10), None),
|
||||
("Fire Control Array", int2(1, 6), Clockwise));
|
||||
|
||||
stage.Player(stage.Generated("Djinni"), float2(0, 0));
|
||||
|
|
|
|||
|
|
@ -27,27 +27,27 @@ public sealed class Arcs : Scenario
|
|||
("Cockpit 2x2", int2(4, 6), None),
|
||||
("Large Drive", int2(3, 0), Reversed),
|
||||
("Large Drive", int2(5, 0), Reversed),
|
||||
("Talaria", int2(4, 14), CounterClockwise),
|
||||
("Talaria", int2(5, 14), Clockwise),
|
||||
("Core Power", int2(4, 4), None),
|
||||
("GT 3K", int2(2, 5), None),
|
||||
("GT 3K", int2(7, 5), None),
|
||||
("FastBlast+-", int2(3, 8), None),
|
||||
("FastBlast+-", int2(6, 8), None),
|
||||
("Iapyx", int2(4, 2), CounterClockwise),
|
||||
("Iapyx", int2(5, 2), Clockwise),
|
||||
("not if i see you first", int2(5, 10), None),
|
||||
("Rugged Drive", int2(4, 14), CounterClockwise),
|
||||
("Rugged Drive", int2(5, 14), Clockwise),
|
||||
("Compact Reactor", int2(4, 4), None),
|
||||
("Guided Missile Launcher", int2(2, 5), None),
|
||||
("Guided Missile Launcher", int2(7, 5), None),
|
||||
("Cell Blaster", int2(3, 8), None),
|
||||
("Cell Blaster", int2(6, 8), None),
|
||||
("Radiator", int2(4, 2), CounterClockwise),
|
||||
("Radiator", int2(5, 2), Clockwise),
|
||||
("Compact Sensor", int2(5, 10), None),
|
||||
("Fire Control Array", int2(3, 6), Clockwise),
|
||||
("Autocannon", int2(8, 4), Clockwise),
|
||||
("ChargeBlast SG", int2(0, 4), CounterClockwise));
|
||||
("Charged Scatter Blaster", int2(0, 4), CounterClockwise));
|
||||
var turret = stage.Fit("Turret",
|
||||
("ClearPath", int2(1, 2), None),
|
||||
("ClearPath", int2(5, 2), None),
|
||||
("Skiron", int2(0, 2), CounterClockwise),
|
||||
("Skiron", int2(7, 2), Clockwise),
|
||||
("Core Power", int2(3, 3), None),
|
||||
("Light Machine Gun", int2(1, 2), None),
|
||||
("Light Machine Gun", int2(5, 2), None),
|
||||
("Long Radiator", int2(0, 2), CounterClockwise),
|
||||
("Long Radiator", int2(7, 2), Clockwise),
|
||||
("Compact Reactor", int2(3, 3), None),
|
||||
("Turret Control Module", int2(3, 5), None),
|
||||
("not if i see you first", int2(4, 5), None),
|
||||
("Compact Sensor", int2(4, 5), None),
|
||||
("Fire Control Array", int2(3, 6), None));
|
||||
|
||||
stage.Player(fighter, float2(0, 0), facing: float2(0, 1));
|
||||
|
|
|
|||
|
|
@ -23,16 +23,16 @@ public sealed class Belt : Scenario
|
|||
("Cockpit 2x2", int2(2, 6), None),
|
||||
("Large Drive", int2(1, 0), Reversed),
|
||||
("Large Drive", int2(3, 0), Reversed),
|
||||
("Talaria", int2(2, 14), CounterClockwise),
|
||||
("Talaria", int2(3, 14), Clockwise),
|
||||
("Core Power", int2(2, 4), None),
|
||||
("GT 3K", int2(0, 5), None),
|
||||
("GT 3K", int2(5, 5), None),
|
||||
("plight", int2(1, 8), None),
|
||||
("FastBlast+-", int2(4, 8), None),
|
||||
("Iapyx", int2(2, 2), CounterClockwise),
|
||||
("Iapyx", int2(3, 2), Clockwise),
|
||||
("not if i see you first", int2(3, 10), None),
|
||||
("Rugged Drive", int2(2, 14), CounterClockwise),
|
||||
("Rugged Drive", int2(3, 14), Clockwise),
|
||||
("Compact Reactor", int2(2, 4), None),
|
||||
("Guided Missile Launcher", int2(0, 5), None),
|
||||
("Guided Missile Launcher", int2(5, 5), None),
|
||||
("Lightning Projector", int2(1, 8), None),
|
||||
("Cell Blaster", int2(4, 8), None),
|
||||
("Radiator", int2(2, 2), CounterClockwise),
|
||||
("Radiator", int2(3, 2), Clockwise),
|
||||
("Compact Sensor", int2(3, 10), None),
|
||||
("Fire Control Array", int2(1, 6), Clockwise));
|
||||
|
||||
// The belt is a ring about its parent's position at the belt orbit's distance. The player stands 100 m outside
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ using static ItemRotation;
|
|||
public sealed class Crossing : Scenario
|
||||
{
|
||||
public override string Name => "Crossing";
|
||||
public override string Brief => "plight at the starboard gun and a FastBlast+- at the port gun, a fast Djinni crossing close ahead and an " +
|
||||
"unpiloted one waiting at range. The Djinni starts to port moving across the bow; the arena's gravity bends its run. Make the pass coasting: give no throttle until the Djinni has crossed the bow, then fly freely (thrusting from the start carries you past it). Verify: as the Djinni crosses close, FastBlast lands some and plight " +
|
||||
public override string Brief => "A Lightning Projector at the starboard gun and a Cell Blaster at the port gun, a fast Djinni crossing close ahead and an " +
|
||||
"unpiloted one waiting at range. The Djinni starts to port moving across the bow; the arena's gravity bends its run. Make the pass coasting: give no throttle until the Djinni has crossed the bow, then fly freely (thrusting from the start carries you past it). Verify: as the Djinni crosses close, FastBlast lands some and projector " +
|
||||
"almost none; against the Djinni at range both land. Fly it again without the Fire Control Array: " +
|
||||
"with it the small gun lands more.";
|
||||
public override uint Seed => 112;
|
||||
|
|
@ -13,11 +13,11 @@ public sealed class Crossing : Scenario
|
|||
|
||||
// Where the Djinni sit: the player is at the origin facing +z. The crosser starts ahead and to port, facing across
|
||||
// the bow and already moving at the Djinni's 50 m/s cap, so its run sweeps across the bow within seconds (the arena
|
||||
// pulls the player to port, which brings the two together). The waiting Djinni sits inside plight's arc at 80% of
|
||||
// pulls the player to port, which brings the two together). The waiting Djinni sits inside the Lightning Projector's arc at 80% of
|
||||
// its Range.
|
||||
public static readonly CultMath.float2 CrosserAt = float2(-150, 200), CrosserFacing = float2(1, 0), CrosserVelocity = float2(50, 0);
|
||||
public const float PlightRange = 1216f; // plight's Range in metres, 1216.4 as the fit stands settled at rest
|
||||
public static readonly CultMath.float2 Waiting = float2(0, PlightRange * .8f);
|
||||
public const float ProjectorRange = 1216f; // the Lightning Projector's Range in metres, 1216.4 as the fit stands settled at rest
|
||||
public static readonly CultMath.float2 Waiting = float2(0, ProjectorRange * .8f);
|
||||
|
||||
public override Galaxy Generate(GalaxyStage stage) => stage.Prelude(DemoTerminus.Cast);
|
||||
|
||||
|
|
@ -27,16 +27,16 @@ public sealed class Crossing : Scenario
|
|||
("Cockpit 2x2", int2(2, 6), None),
|
||||
("Large Drive", int2(1, 0), Reversed),
|
||||
("Large Drive", int2(3, 0), Reversed),
|
||||
("Talaria", int2(2, 14), CounterClockwise),
|
||||
("Talaria", int2(3, 14), Clockwise),
|
||||
("Core Power", int2(2, 4), None),
|
||||
("GT 3K", int2(0, 5), None),
|
||||
("GT 3K", int2(5, 5), None),
|
||||
("plight", int2(1, 8), None),
|
||||
("FastBlast+-", int2(4, 8), None),
|
||||
("Iapyx", int2(2, 2), CounterClockwise),
|
||||
("Iapyx", int2(3, 2), Clockwise),
|
||||
("not if i see you first", int2(3, 10), None),
|
||||
("Rugged Drive", int2(2, 14), CounterClockwise),
|
||||
("Rugged Drive", int2(3, 14), Clockwise),
|
||||
("Compact Reactor", int2(2, 4), None),
|
||||
("Guided Missile Launcher", int2(0, 5), None),
|
||||
("Guided Missile Launcher", int2(5, 5), None),
|
||||
("Lightning Projector", int2(1, 8), None),
|
||||
("Cell Blaster", int2(4, 8), None),
|
||||
("Radiator", int2(2, 2), CounterClockwise),
|
||||
("Radiator", int2(3, 2), Clockwise),
|
||||
("Compact Sensor", int2(3, 10), None),
|
||||
("Fire Control Array", int2(1, 6), Clockwise));
|
||||
|
||||
stage.Player(fighter, float2(0, 0), facing: float2(0, 1));
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ using static ItemRotation;
|
|||
public sealed class Duel : Scenario
|
||||
{
|
||||
public override string Name => "Duel";
|
||||
public override string Brief => "One piloted, hostile Longinus with slow GT 3K missiles and a Spectra in its hold. Verify: rolled " +
|
||||
public override string Brief => "One piloted, hostile Longinus with slow guided missiles and a Light Laser in its hold. Verify: rolled " +
|
||||
"hits and near-misses, subsystem aim, beam reads above bow, armour takes the missile, loot keeps its brand.";
|
||||
public override uint Seed => 104;
|
||||
public override bool Ambient => false;
|
||||
|
|
@ -17,22 +17,22 @@ public sealed class Duel : Scenario
|
|||
("Cockpit 2x2", int2(2, 6), None),
|
||||
("Large Drive", int2(1, 0), Reversed),
|
||||
("Large Drive", int2(3, 0), Reversed),
|
||||
("Talaria", int2(2, 14), CounterClockwise),
|
||||
("Talaria", int2(3, 14), Clockwise),
|
||||
("Core Power", int2(2, 4), None),
|
||||
("GT 3K", int2(0, 5), None),
|
||||
("GT 3K", int2(5, 5), None),
|
||||
("FastBlast+-", int2(1, 8), None),
|
||||
("FastBlast+-", int2(4, 8), None),
|
||||
("Iapyx", int2(2, 2), CounterClockwise),
|
||||
("Iapyx", int2(3, 2), Clockwise),
|
||||
("not if i see you first", int2(3, 10), None),
|
||||
("PotaT+-", int2(1, 5), None),
|
||||
("Rugged Drive", int2(2, 14), CounterClockwise),
|
||||
("Rugged Drive", int2(3, 14), Clockwise),
|
||||
("Compact Reactor", int2(2, 4), None),
|
||||
("Guided Missile Launcher", int2(0, 5), None),
|
||||
("Guided Missile Launcher", int2(5, 5), None),
|
||||
("Cell Blaster", int2(1, 8), None),
|
||||
("Cell Blaster", int2(4, 8), None),
|
||||
("Radiator", int2(2, 2), CounterClockwise),
|
||||
("Radiator", int2(3, 2), Clockwise),
|
||||
("Compact Sensor", int2(3, 10), None),
|
||||
("Capacitor", int2(1, 5), None),
|
||||
("Fire Control Array", int2(1, 6), Clockwise),
|
||||
("Store-All Plus", int2(2, 8), None));
|
||||
("Cargo Bay", int2(2, 8), None));
|
||||
|
||||
stage.Player(fighter, float2(0, 0));
|
||||
var rival = stage.Place(fighter, float2(0, 900), facing: float2(0, -1), stance: ScenarioStance.Hostile, piloted: true);
|
||||
stage.Cargo(rival, "Spectra");
|
||||
stage.Cargo(rival, "Light Laser");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,20 +4,20 @@ using static ItemRotation;
|
|||
public sealed class FusedRounds : Scenario
|
||||
{
|
||||
public override string Name => "Fused Rounds";
|
||||
public override string Brief => "The Smoke Test Hull with Smoke Proximity, Smoke Delayed and a DeathCluster; a hostile bare Longinus at mid range, a bare " +
|
||||
public override string Brief => "The Smoke Test Hull with Smoke Proximity, Smoke Delayed and a Cluster Cannon; a hostile bare Longinus at mid range, a bare " +
|
||||
"hull dead ahead, a fragile one to starboard, a close one inside the proximity blast radius, and one astern. " +
|
||||
"Verify with nothing selected: aim past every hull and the round bursts at max range along the aim; aim at the hull " +
|
||||
"ahead and it stops and bursts on it; fire at the fragile hull and kill it with the DeathCluster before the round " +
|
||||
"ahead and it stops and bursts on it; fire at the fragile hull and kill it with the Cluster Cannon before the round " +
|
||||
"lands: no burst. Select the astern hull: the round fires and bursts at its range along the arc edge. Select the " +
|
||||
"close hull: the burst is pushed out to the blast radius from your centre. A Smoke Proximity burst beside the " +
|
||||
"Longinus damages where the model shows the blast; a Smoke Delayed round into its nose bursts inside. The HUD " +
|
||||
"reads Direct, Burst at N m or Refused for the fused weapons and a percentage for the DeathCluster, and the " +
|
||||
"reads Direct, Burst at N m or Refused for the fused weapons and a percentage for the Cluster Cannon, and the " +
|
||||
"smoke weapons show no Manufacturer row.";
|
||||
public override uint Seed => 108;
|
||||
public override bool Ambient => false;
|
||||
|
||||
// Where everything sits: the player is at the origin facing +z. Ahead lies on the initial aim line, inside every forward
|
||||
// mount's arc. Fragile is a bare Longinus inside the DeathCluster's range; Close is inside Smoke Proximity's blast radius
|
||||
// mount's arc. Fragile is a bare Longinus inside the Cluster Cannon's range; Close is inside Smoke Proximity's blast radius
|
||||
// (8 m from the ship's centre) and beside the aim line, not across it; Stern is outside every mount's arc.
|
||||
public static readonly CultMath.float2 Mid = float2(-200, 600), Ahead = float2(0, 300), Fragile = float2(140, 420),
|
||||
Close = float2(6.4f, 4f), Stern = float2(0, -400);
|
||||
|
|
@ -30,15 +30,15 @@ public sealed class FusedRounds : Scenario
|
|||
("Cockpit 2x2", int2(4, 6), None),
|
||||
("Large Drive", int2(3, 0), Reversed),
|
||||
("Large Drive", int2(5, 0), Reversed),
|
||||
("Talaria", int2(4, 14), CounterClockwise),
|
||||
("Talaria", int2(5, 14), Clockwise),
|
||||
("Core Power", int2(4, 4), None),
|
||||
("Rugged Drive", int2(4, 14), CounterClockwise),
|
||||
("Rugged Drive", int2(5, 14), Clockwise),
|
||||
("Compact Reactor", int2(4, 4), None),
|
||||
("Smoke Proximity", int2(3, 8), None),
|
||||
("Smoke Delayed", int2(6, 8), None),
|
||||
("DeathCluster", int2(5, 17), None),
|
||||
("Iapyx", int2(4, 2), CounterClockwise),
|
||||
("Iapyx", int2(5, 2), Clockwise),
|
||||
("not if i see you first", int2(5, 10), None),
|
||||
("Cluster Cannon", int2(5, 17), None),
|
||||
("Radiator", int2(4, 2), CounterClockwise),
|
||||
("Radiator", int2(5, 2), Clockwise),
|
||||
("Compact Sensor", int2(5, 10), None),
|
||||
("Fire Control Array", int2(3, 6), Clockwise));
|
||||
|
||||
stage.Player(shooter, float2(0, 0), facing: float2(0, 1));
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ using static ItemRotation;
|
|||
public sealed class LauncherAngles : Scenario
|
||||
{
|
||||
public override string Name => "Launcher Angles";
|
||||
public override string Brief => "A GT 3K, a pswarm and two FastBlasts; a hostile bare Longinus broadside on, another beside it. " +
|
||||
"Verify: hits pulse only the facing edge; a pswarm round whose target dies fades unexploded.";
|
||||
public override string Brief => "A Guided Missile Launcher, a Swarm Rocket Pod and two Cell Blasters; a hostile bare Longinus broadside on, another beside it. " +
|
||||
"Verify: hits pulse only the facing edge; a Swarm Rocket Pod round whose target dies fades unexploded.";
|
||||
public override uint Seed => 105;
|
||||
public override bool Ambient => false;
|
||||
|
||||
|
|
@ -17,16 +17,16 @@ public sealed class LauncherAngles : Scenario
|
|||
("Cockpit 2x2", int2(2, 6), None),
|
||||
("Large Drive", int2(1, 0), Reversed),
|
||||
("Large Drive", int2(3, 0), Reversed),
|
||||
("Talaria", int2(2, 14), CounterClockwise),
|
||||
("Talaria", int2(3, 14), Clockwise),
|
||||
("Core Power", int2(2, 4), None),
|
||||
("GT 3K", int2(0, 5), None),
|
||||
("pswarm", int2(5, 5), None),
|
||||
("FastBlast+-", int2(1, 8), None),
|
||||
("FastBlast+-", int2(4, 8), None),
|
||||
("Iapyx", int2(2, 2), CounterClockwise),
|
||||
("Iapyx", int2(3, 2), Clockwise),
|
||||
("not if i see you first", int2(3, 10), None),
|
||||
("Rugged Drive", int2(2, 14), CounterClockwise),
|
||||
("Rugged Drive", int2(3, 14), Clockwise),
|
||||
("Compact Reactor", int2(2, 4), None),
|
||||
("Guided Missile Launcher", int2(0, 5), None),
|
||||
("Swarm Rocket Pod", int2(5, 5), None),
|
||||
("Cell Blaster", int2(1, 8), None),
|
||||
("Cell Blaster", int2(4, 8), None),
|
||||
("Radiator", int2(2, 2), CounterClockwise),
|
||||
("Radiator", int2(3, 2), Clockwise),
|
||||
("Compact Sensor", int2(3, 10), None),
|
||||
("Fire Control Array", int2(1, 6), Clockwise));
|
||||
|
||||
stage.Player(launcher, float2(0, 0));
|
||||
|
|
|
|||
44
Assets/Scripts/ServerShared/Scenarios/LockOn.cs
Normal file
44
Assets/Scripts/ServerShared/Scenarios/LockOn.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
using static CultMath.math;
|
||||
using static ItemRotation;
|
||||
|
||||
public sealed class LockOn : Scenario
|
||||
{
|
||||
public override string Name => "Lock On";
|
||||
public override string Brief => "A hostile closes from out of sight, finds you and locks. Verify: no warning at first; once it locks, one " +
|
||||
"indicator appears toward it and tightens as the lock grows; break line of sight or range and it vanishes; " +
|
||||
"your own ping shows no incoming paint on its side, only the lock does.";
|
||||
public override uint Seed => 113;
|
||||
public override bool Ambient => false;
|
||||
|
||||
// The hostile starts about 4000 m off, to the side of the player's axis, facing the player. At that range its passive
|
||||
// sensors hold no track of the player (the test asks the sim's own PassiveInfo against the detection threshold); the
|
||||
// player sees the hostile a little sooner, at about tick 20. The hostile closes, sees the player some eight seconds in,
|
||||
// and locks two ticks later. At Duel's 900 m (and at 600 m) it sees and locks within a second.
|
||||
public static readonly CultMath.float2 HostileAt = float2(1500, 3700);
|
||||
|
||||
public override Galaxy Generate(GalaxyStage stage) => stage.Prelude(DemoTerminus.Cast);
|
||||
|
||||
public override void Stage(ScenarioStage stage)
|
||||
{
|
||||
var fighter = stage.Fit("Longinus",
|
||||
("Cockpit 2x2", int2(2, 6), None),
|
||||
("Large Drive", int2(1, 0), Reversed),
|
||||
("Large Drive", int2(3, 0), Reversed),
|
||||
("Rugged Drive", int2(2, 14), CounterClockwise),
|
||||
("Rugged Drive", int2(3, 14), Clockwise),
|
||||
("Compact Reactor", int2(2, 4), None),
|
||||
("Guided Missile Launcher", int2(0, 5), None),
|
||||
("Guided Missile Launcher", int2(5, 5), None),
|
||||
("Cell Blaster", int2(1, 8), None),
|
||||
("Cell Blaster", int2(4, 8), None),
|
||||
("Radiator", int2(2, 2), CounterClockwise),
|
||||
("Radiator", int2(3, 2), Clockwise),
|
||||
("Compact Sensor", int2(3, 10), None),
|
||||
("Capacitor", int2(1, 5), None),
|
||||
("Fire Control Array", int2(1, 6), Clockwise),
|
||||
("Cargo Bay", int2(2, 8), None));
|
||||
|
||||
stage.Player(fighter, float2(0, 0), facing: float2(0, 1));
|
||||
stage.Place(fighter, HostileAt, facing: normalize(-HostileAt), stance: ScenarioStance.Hostile, piloted: true);
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/ServerShared/Scenarios/LockOn.cs.meta
Normal file
11
Assets/Scripts/ServerShared/Scenarios/LockOn.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3f0b6d2a9c7e4a1b8d5e2c6f7a9b0c14
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -23,35 +23,35 @@ public sealed class Pursuit : Scenario
|
|||
("Cockpit 2x2", int2(2, 6), None),
|
||||
("Large Drive", int2(1, 0), Reversed),
|
||||
("Large Drive", int2(3, 0), Reversed),
|
||||
("Talaria", int2(2, 14), CounterClockwise),
|
||||
("Talaria", int2(3, 14), Clockwise),
|
||||
("Core Power", int2(2, 4), None),
|
||||
("Rugged Drive", int2(2, 14), CounterClockwise),
|
||||
("Rugged Drive", int2(3, 14), Clockwise),
|
||||
("Compact Reactor", int2(2, 4), None),
|
||||
("Mine Launcher", int2(0, 5), None),
|
||||
("pswarm", int2(5, 5), None),
|
||||
("FastBlast+-", int2(1, 8), None),
|
||||
("FastBlast+-", int2(4, 8), None),
|
||||
("Iapyx", int2(2, 2), CounterClockwise),
|
||||
("Iapyx", int2(3, 2), Clockwise),
|
||||
("not if i see you first", int2(3, 10), None),
|
||||
("Swarm Rocket Pod", int2(5, 5), None),
|
||||
("Cell Blaster", int2(1, 8), None),
|
||||
("Cell Blaster", int2(4, 8), None),
|
||||
("Radiator", int2(2, 2), CounterClockwise),
|
||||
("Radiator", int2(3, 2), Clockwise),
|
||||
("Compact Sensor", int2(3, 10), None),
|
||||
("Fire Control Array", int2(1, 6), Clockwise),
|
||||
("Store-All Plus", int2(2, 8), None));
|
||||
("Cargo Bay", int2(2, 8), None));
|
||||
var pursuer = stage.Fit("Longinus",
|
||||
("Cockpit 2x2", int2(2, 6), None),
|
||||
("Large Drive", int2(1, 0), Reversed),
|
||||
("Large Drive", int2(3, 0), Reversed),
|
||||
("Talaria", int2(2, 14), CounterClockwise),
|
||||
("Talaria", int2(3, 14), Clockwise),
|
||||
("Core Power", int2(2, 4), None),
|
||||
("GT 3K", int2(0, 5), None),
|
||||
("GT 3K", int2(5, 5), None),
|
||||
("FastBlast+-", int2(1, 8), None),
|
||||
("FastBlast+-", int2(4, 8), None),
|
||||
("Iapyx", int2(2, 2), CounterClockwise),
|
||||
("Iapyx", int2(3, 2), Clockwise),
|
||||
("not if i see you first", int2(3, 10), None),
|
||||
("PotaT+-", int2(1, 5), None),
|
||||
("Rugged Drive", int2(2, 14), CounterClockwise),
|
||||
("Rugged Drive", int2(3, 14), Clockwise),
|
||||
("Compact Reactor", int2(2, 4), None),
|
||||
("Guided Missile Launcher", int2(0, 5), None),
|
||||
("Guided Missile Launcher", int2(5, 5), None),
|
||||
("Cell Blaster", int2(1, 8), None),
|
||||
("Cell Blaster", int2(4, 8), None),
|
||||
("Radiator", int2(2, 2), CounterClockwise),
|
||||
("Radiator", int2(3, 2), Clockwise),
|
||||
("Compact Sensor", int2(3, 10), None),
|
||||
("Capacitor", int2(1, 5), None),
|
||||
("Fire Control Array", int2(1, 6), Clockwise),
|
||||
("Store-All Plus", int2(2, 8), None));
|
||||
("Cargo Bay", int2(2, 8), None));
|
||||
|
||||
var player = stage.Player(layer, float2(0, 0), facing: float2(0, 1));
|
||||
stage.Cargo(player, "Coolant Vent", "Coolant Vent", "Coolant Vent", "Thruster Overdrive", "Thruster Overdrive");
|
||||
|
|
|
|||
|
|
@ -22,16 +22,16 @@ public sealed class RefusedRounds : Scenario
|
|||
("Cockpit 2x2", int2(4, 6), None),
|
||||
("Large Drive", int2(3, 0), Reversed),
|
||||
("Large Drive", int2(5, 0), Reversed),
|
||||
("Talaria", int2(4, 14), CounterClockwise),
|
||||
("Talaria", int2(5, 14), Clockwise),
|
||||
("Core Power", int2(4, 4), None),
|
||||
("Rugged Drive", int2(4, 14), CounterClockwise),
|
||||
("Rugged Drive", int2(5, 14), Clockwise),
|
||||
("Compact Reactor", int2(4, 4), None),
|
||||
("Smoke Refused", int2(3, 8), None),
|
||||
("Smoke Refused Beam", int2(6, 8), None),
|
||||
("Smoke Refused Charge", int2(3, 17), None),
|
||||
("Autocannon", int2(5, 17), None),
|
||||
("Iapyx", int2(4, 2), CounterClockwise),
|
||||
("Iapyx", int2(5, 2), Clockwise),
|
||||
("not if i see you first", int2(5, 10), None),
|
||||
("Radiator", int2(4, 2), CounterClockwise),
|
||||
("Radiator", int2(5, 2), Clockwise),
|
||||
("Compact Sensor", int2(5, 10), None),
|
||||
("Fire Control Array", int2(3, 6), Clockwise));
|
||||
|
||||
stage.Player(shooter, float2(0, 0), facing: float2(0, 1));
|
||||
|
|
|
|||
|
|
@ -9,6 +9,6 @@ public static class Scenarios
|
|||
|
||||
public static readonly Scenario[] Tests =
|
||||
{
|
||||
new DjinniShakedown(), new StarvedReactor(), new Arcs(), new Duel(), new LauncherAngles(), new WeaponFeel(), new FusedRounds(), new RefusedRounds(), new AiFusedDiscipline(), new LongHaul(), new Pursuit(), new Crossing(), new Belt()
|
||||
new DjinniShakedown(), new StarvedReactor(), new Arcs(), new Duel(), new LauncherAngles(), new WeaponFeel(), new FusedRounds(), new RefusedRounds(), new AiFusedDiscipline(), new LongHaul(), new Pursuit(), new Crossing(), new Belt(), new LockOn()
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ using static ItemRotation;
|
|||
public sealed class StarvedReactor : Scenario
|
||||
{
|
||||
public override string Name => "Starved Reactor";
|
||||
public override string Brief => "Two Spectras on a MoveOnPro, which can fall to a quarter of Core Power's charge, and a bare hull " +
|
||||
public override string Brief => "Two Light Lasers on a Muon Fusion Reactor, which can fall to a quarter of the Compact Reactor's charge, and a bare hull " +
|
||||
"to shoot. Verify: starvation reads as starvation, brownout degrades, refills stutter.";
|
||||
public override uint Seed => 102;
|
||||
public override bool Ambient => false;
|
||||
|
|
@ -17,14 +17,14 @@ public sealed class StarvedReactor : Scenario
|
|||
("Cockpit 2x2", int2(2, 6), None),
|
||||
("Large Drive", int2(1, 0), Reversed),
|
||||
("Large Drive", int2(3, 0), Reversed),
|
||||
("Talaria", int2(2, 14), CounterClockwise),
|
||||
("Talaria", int2(3, 14), Clockwise),
|
||||
("MoveOnPro", int2(2, 4), None),
|
||||
("Spectra", int2(1, 8), None),
|
||||
("Spectra", int2(4, 8), None),
|
||||
("Iapyx", int2(2, 2), CounterClockwise),
|
||||
("Iapyx", int2(3, 2), Clockwise),
|
||||
("not if i see you first", int2(3, 10), None),
|
||||
("Rugged Drive", int2(2, 14), CounterClockwise),
|
||||
("Rugged Drive", int2(3, 14), Clockwise),
|
||||
("Muon Fusion Reactor", int2(2, 4), None),
|
||||
("Light Laser", int2(1, 8), None),
|
||||
("Light Laser", int2(4, 8), None),
|
||||
("Radiator", int2(2, 2), CounterClockwise),
|
||||
("Radiator", int2(3, 2), Clockwise),
|
||||
("Compact Sensor", int2(3, 10), None),
|
||||
("Fire Control Array", int2(1, 6), Clockwise));
|
||||
stage.Player(starved, float2(0, 0));
|
||||
stage.Place(stage.Bare("Longinus"), float2(0, 400));
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ using static ItemRotation;
|
|||
public sealed class WeaponFeel : Scenario
|
||||
{
|
||||
public override string Name => "Weapon Feel";
|
||||
public override string Brief => "The Smoke Test Hull with a ChargeBlast SG, an Autocannon and a FastBlast+- burst weapon; a bare Longinus dead ahead. " +
|
||||
"Verify: ChargeBlast SG and Autocannon fire with effects, particles and action-bar icons; the burst weapon's " +
|
||||
"cadence is as before; a fast release-and-press on the ChargeBlast SG starts no second charge.";
|
||||
public override string Brief => "The Smoke Test Hull with a Charged Scatter Blaster, an Autocannon and a Cell Blaster burst weapon; a bare Longinus dead ahead. " +
|
||||
"Verify: Charged Scatter Blaster and Autocannon fire with effects, particles and action-bar icons; the burst weapon's " +
|
||||
"cadence is as before; a fast release-and-press on the Charged Scatter Blaster starts no second charge.";
|
||||
public override uint Seed => 107;
|
||||
public override bool Ambient => false;
|
||||
|
||||
|
|
@ -21,15 +21,15 @@ public sealed class WeaponFeel : Scenario
|
|||
("Cockpit 2x2", int2(4, 6), None),
|
||||
("Large Drive", int2(3, 0), Reversed),
|
||||
("Large Drive", int2(5, 0), Reversed),
|
||||
("Talaria", int2(4, 14), CounterClockwise),
|
||||
("Talaria", int2(5, 14), Clockwise),
|
||||
("Core Power", int2(4, 4), None),
|
||||
("ChargeBlast SG", int2(3, 17), None),
|
||||
("Rugged Drive", int2(4, 14), CounterClockwise),
|
||||
("Rugged Drive", int2(5, 14), Clockwise),
|
||||
("Compact Reactor", int2(4, 4), None),
|
||||
("Charged Scatter Blaster", int2(3, 17), None),
|
||||
("Autocannon", int2(5, 17), None),
|
||||
("FastBlast+-", int2(3, 8), None),
|
||||
("Iapyx", int2(4, 2), CounterClockwise),
|
||||
("Iapyx", int2(5, 2), Clockwise),
|
||||
("not if i see you first", int2(5, 10), None),
|
||||
("Cell Blaster", int2(3, 8), None),
|
||||
("Radiator", int2(4, 2), CounterClockwise),
|
||||
("Radiator", int2(5, 2), Clockwise),
|
||||
("Compact Sensor", int2(5, 10), None),
|
||||
("Fire Control Array", int2(3, 6), Clockwise));
|
||||
|
||||
stage.Player(shooter, float2(0, 0), facing: float2(0, 1));
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using System.Linq;
|
||||
using CultMath;
|
||||
|
||||
// Light signatures split by channel (ruling visibility-per-capability-reveal): an observer learns what a ship
|
||||
|
|
@ -20,15 +21,19 @@ public readonly struct Signature
|
|||
public readonly SignatureChannel Channel;
|
||||
public readonly float Strength;
|
||||
public readonly EquippedItem Item;
|
||||
// The class this act claims: the true item's Data unless its ship's EW claims otherwise (aetheria-ew,
|
||||
// classify-from-belief). Stamped by Entity.SetSignature from the ship's claim.
|
||||
public readonly EquippableItemData Claimed;
|
||||
|
||||
public Signature(SignatureChannel channel, float strength, EquippedItem item)
|
||||
public Signature(SignatureChannel channel, float strength, EquippedItem item, EquippableItemData claimed)
|
||||
{
|
||||
Channel = channel;
|
||||
Strength = strength;
|
||||
Item = item;
|
||||
Claimed = claimed;
|
||||
}
|
||||
|
||||
public Signature WithStrength(float strength) => new Signature(Channel, strength, Item);
|
||||
public Signature WithStrength(float strength) => new Signature(Channel, strength, Item, Claimed);
|
||||
}
|
||||
|
||||
// What a painted ship learns of a beam on it (ruling sensor-stat-set item 4): a Search paint is a ping sweeping
|
||||
|
|
@ -64,11 +69,14 @@ public readonly struct ActiveEmission
|
|||
{
|
||||
public readonly EquippedItem Item;
|
||||
public readonly float HearRange;
|
||||
// The class this emission claims (see Signature.Claimed); stamped by Entity.Emit.
|
||||
public readonly EquippableItemData Claimed;
|
||||
|
||||
public ActiveEmission(EquippedItem item, float hearRange)
|
||||
public ActiveEmission(EquippedItem item, float hearRange, EquippableItemData claimed)
|
||||
{
|
||||
Item = item;
|
||||
HearRange = hearRange;
|
||||
Claimed = claimed;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -90,23 +98,53 @@ public readonly struct PaintWarning
|
|||
|
||||
// What an observer has seen `Item` do (ruling visibility-per-capability-reveal): the channel it showed on, the
|
||||
// bearing at that act and the zone clock then. Held per (subject, item) in the observer's Entity; the latest act
|
||||
// replaces the earlier one. Position is never carried: only the track gives it.
|
||||
// replaces the earlier one. Position is never carried: only the track gives it. `Item` stays the true item: it is the
|
||||
// book's key and what aim reads. `Claimed` is the class the act claimed, and is what every classifying reader reads
|
||||
// (aetheria-ew, classify-from-belief).
|
||||
public readonly struct Reveal
|
||||
{
|
||||
public readonly EquippedItem Item;
|
||||
public readonly EquippableItemData Claimed;
|
||||
public readonly SignatureChannel Channel;
|
||||
public readonly float2 Bearing;
|
||||
public readonly float Time;
|
||||
|
||||
public Reveal(EquippedItem item, SignatureChannel channel, float2 bearing, float time)
|
||||
public Reveal(EquippedItem item, SignatureChannel channel, float2 bearing, float time, EquippableItemData claimed)
|
||||
{
|
||||
Item = item;
|
||||
Claimed = claimed;
|
||||
Channel = channel;
|
||||
Bearing = bearing;
|
||||
Time = time;
|
||||
}
|
||||
}
|
||||
|
||||
// One weapon row of a ship's schematic as its viewer is entitled to read it (aetheria-ew, classify-from-belief; ruling
|
||||
// ew-claim-weapon-measure-ruled). Made only by Entity.SchematicRows. `Item` is the true item: the selection mark's key and,
|
||||
// on the viewer's own ship, the live gear. `Claimed` is the class the row shows. The range label is the live range on
|
||||
// the viewer's own ship and the claimed design's rated range on any other; the viewer never sees a target weapon's
|
||||
// quality, wear or power. A renderer shows these and decides nothing.
|
||||
public readonly struct SchematicRow
|
||||
{
|
||||
public readonly EquippedItem Item;
|
||||
public readonly EquippableItemData Claimed;
|
||||
private readonly bool _own;
|
||||
|
||||
internal SchematicRow(EquippedItem item, EquippableItemData claimed, bool own)
|
||||
{
|
||||
Item = item;
|
||||
Claimed = claimed;
|
||||
_own = own;
|
||||
}
|
||||
|
||||
// What a renderer compares to know the rows changed. Range is a reading, not part of the key.
|
||||
public (EquippedItem, EquippableItemData) Key => (Item, Claimed);
|
||||
|
||||
public float Range => _own
|
||||
? Item.Behaviors.OfType<Weapon>().First().Range
|
||||
: Claimed.Behaviors.OfType<WeaponData>().First().RatedRange;
|
||||
}
|
||||
|
||||
// A subject's removal from a zone as the zone recorded it (death, dock, warp, any removal): where it was and how
|
||||
// bright its light was (its Visibility at removal). Judged by each observer's Sense under the
|
||||
// light-act gate; notification only, one tick long, it decides nothing.
|
||||
|
|
@ -127,7 +165,9 @@ public readonly struct Departure
|
|||
// How much an observer holds of one subject, derived on read by Entity.ContactOf from its track, its reveals and
|
||||
// the zone clock. Detected: tracked, or a reveal younger than GameplaySettings.BearingMemory. Tracked: exactly
|
||||
// membership of VisibleEntities. Classified: detected, with any reveal in the book. Identified: the track has reached
|
||||
// TargetGearInfoThreshold. Bearing is the true bearing while tracked, else the freshest reveal's.
|
||||
// TargetGearInfoThreshold, and holds a hull belief: Profile, the hull class this observer believes the subject is (null
|
||||
// below Identified; aetheria-ew, classify-from-belief; seen-through arrives with the judge). Bearing is the true
|
||||
// bearing while tracked, else the freshest reveal's.
|
||||
public readonly struct Contact
|
||||
{
|
||||
public readonly bool Detected;
|
||||
|
|
@ -136,9 +176,11 @@ public readonly struct Contact
|
|||
public readonly bool Identified;
|
||||
public readonly float2 Bearing;
|
||||
public readonly float Info;
|
||||
public readonly HullData Profile;
|
||||
|
||||
public Contact(bool detected, bool tracked, bool classified, bool identified, float2 bearing, float info)
|
||||
public Contact(bool detected, bool tracked, bool classified, bool identified, float2 bearing, float info, HullData profile)
|
||||
{
|
||||
Profile = profile;
|
||||
Detected = detected;
|
||||
Tracked = tracked;
|
||||
Classified = classified;
|
||||
|
|
|
|||
|
|
@ -394,6 +394,10 @@ public class Zone
|
|||
// After StepClouds and before FireControl.Step: each floating item drifts as a KinematicBody on the zone's force
|
||||
// and is removed once its lifetime has run out. A held item is neither integrated nor expired: it is placed
|
||||
// between where it was claimed and its holder, now that the entities have moved.
|
||||
// The curve a held item rides from where it was claimed to its holder: the one owner, which the test scene's
|
||||
// stand-in sim and tendril call rather than spell again.
|
||||
public static float HeldPull(float progress) => smoothstep(0f, 1f, progress);
|
||||
|
||||
private void StepFloatingItems(float deltaTime)
|
||||
{
|
||||
var settings = _itemManager.GameplaySettings;
|
||||
|
|
@ -401,7 +405,7 @@ public class Zone
|
|||
{
|
||||
if (floating.Holder != null)
|
||||
{
|
||||
floating.Body.Position = lerp(floating.HeldFrom, floating.Holder.Position.xz, smoothstep(0f, 1f, floating.HeldProgress));
|
||||
floating.Body.Position = lerp(floating.HeldFrom, floating.Holder.Position.xz, HeldPull(floating.HeldProgress));
|
||||
continue;
|
||||
}
|
||||
floating.Body.Step(deltaTime, GetForce(floating.Body.Position), settings);
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ public class FieldTester : MonoBehaviour
|
|||
public Camera Camera;
|
||||
public FieldDriver TestField;
|
||||
public PropertiesPanel Properties;
|
||||
// The scene's slow viewing timings for a grab, published as the events' phase durations.
|
||||
public float GrabExtendTime;
|
||||
public float GrabEnvelopTime;
|
||||
public float GrabPullTime;
|
||||
|
||||
private AetheriaInput _input;
|
||||
private bool _forceThrust;
|
||||
|
|
@ -49,6 +53,14 @@ public class FieldTester : MonoBehaviour
|
|||
|
||||
private List<(float lerp, float time, Transform transform)> _pickups = new List<(float lerp, float time, Transform transform)>();
|
||||
|
||||
// This scene stands in for the simulation, so it owns its pickup's grab timeline and pose: one grab at a time.
|
||||
private bool _grabbing;
|
||||
private int _grabIndex;
|
||||
private float _grabElapsed;
|
||||
private float3 _grabStart;
|
||||
private bool _envelopPublished;
|
||||
private bool _pullPublished;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_input = new AetheriaInput();
|
||||
|
|
@ -59,10 +71,7 @@ public class FieldTester : MonoBehaviour
|
|||
if (binder == null)
|
||||
Debug.LogError("FieldTester expects a CapabilityPresentationBinder on TestField's GameObject; no capability events will reach the field.");
|
||||
else
|
||||
binder.Bind(_capabilityEvents,
|
||||
grabTargetResolver: id => (int) id.Value >= 0 && (int) id.Value < _pickups.Count ? _pickups[(int) id.Value].transform : null,
|
||||
grabInitialVelocityResolver: id => Vector3.forward *
|
||||
(PickupTravelDistance / lerp(PickupTravelTimeMin, PickupTravelTimeMax, _pickups[(int) id.Value].lerp)));
|
||||
binder.Bind(_capabilityEvents);
|
||||
|
||||
// Impact absorption (fork L): what used to be FieldDriver's own click handler is now this
|
||||
// scene's stand-in for whatever will eventually detect a hit and publish AbsorbEvent.
|
||||
|
|
@ -99,12 +108,16 @@ public class FieldTester : MonoBehaviour
|
|||
var click = pickup.GetComponent<ClickableCollider>();
|
||||
click.OnClick += (collider, data, ray, hit) =>
|
||||
{
|
||||
if (!TestField.CanGrab) return;
|
||||
if (_grabbing) return;
|
||||
click.Clear();
|
||||
var p = _pickups[i1];
|
||||
_capabilityEvents.PublishGrab(new GrabEvent(new FloatingBodyId((uint) i1), GrabPhase.Started, p.transform.position.ToCultMath(), 0));
|
||||
p.transform = null;
|
||||
_pickups[i1] = p;
|
||||
_grabbing = true;
|
||||
_grabIndex = i1;
|
||||
_grabElapsed = 0;
|
||||
_envelopPublished = _pullPublished = false;
|
||||
_grabStart = _pickups[i1].transform.position.ToCultMath();
|
||||
var id = new FloatingBodyId((uint) i1);
|
||||
_capabilityEvents.PublishGrab(new GrabEvent(id, GrabPhase.Started, _grabStart, 0));
|
||||
_capabilityEvents.PublishGrab(new GrabEvent(id, GrabPhase.Extend, _grabStart, GrabExtendTime));
|
||||
};
|
||||
|
||||
pickup.ScanLabelContainer.gameObject.SetActive(false);
|
||||
|
|
@ -117,6 +130,45 @@ public class FieldTester : MonoBehaviour
|
|||
}
|
||||
}
|
||||
|
||||
// Publishes Envelop and Pull once each at their cumulative boundaries, holds the pickup still until the pull and
|
||||
// then moves it along the same smoothstep as Zone's held pose, and ends with Completed and the pickup's removal.
|
||||
private void AdvanceGrab()
|
||||
{
|
||||
_grabElapsed += Time.deltaTime;
|
||||
var id = new FloatingBodyId((uint) _grabIndex);
|
||||
var pullStart = GrabExtendTime + GrabEnvelopTime;
|
||||
if (!_envelopPublished && _grabElapsed >= GrabExtendTime)
|
||||
{
|
||||
_envelopPublished = true;
|
||||
_capabilityEvents.PublishGrab(new GrabEvent(id, GrabPhase.Envelop, _grabStart, GrabEnvelopTime));
|
||||
}
|
||||
if (!_pullPublished && _grabElapsed >= pullStart)
|
||||
{
|
||||
_pullPublished = true;
|
||||
_capabilityEvents.PublishGrab(new GrabEvent(id, GrabPhase.Pull, _grabStart, GrabPullTime));
|
||||
}
|
||||
var pickup = _pickups[_grabIndex].transform;
|
||||
pickup.position = PulledPosition(_grabStart, TestField.transform.position.ToCultMath(), _grabElapsed,
|
||||
GrabExtendTime, GrabEnvelopTime, GrabPullTime).ToUnity();
|
||||
if (_pullPublished && PullProgress(_grabElapsed, GrabExtendTime, GrabEnvelopTime, GrabPullTime) >= 1)
|
||||
{
|
||||
_capabilityEvents.PublishGrab(new GrabEvent(id, GrabPhase.Completed, pickup.position.ToCultMath(), 0));
|
||||
Destroy(pickup.gameObject);
|
||||
var p = _pickups[_grabIndex];
|
||||
p.transform = null;
|
||||
_pickups[_grabIndex] = p;
|
||||
_grabbing = false;
|
||||
}
|
||||
}
|
||||
|
||||
// How far along the pull a grab is `elapsed` seconds in: 0 through Extend and Envelop, then 0..1 over the pull.
|
||||
public static float PullProgress(float elapsed, float extend, float envelop, float pull) =>
|
||||
saturate((elapsed - extend - envelop) / max(pull, 1e-4f));
|
||||
|
||||
// Where the stand-in sim holds its pickup: still through Extend and Envelop, then along Zone's held-pose curve.
|
||||
public static float3 PulledPosition(float3 start, float3 holder, float elapsed, float extend, float envelop, float pull) =>
|
||||
lerp(start, holder, Zone.HeldPull(PullProgress(elapsed, extend, envelop, pull)));
|
||||
|
||||
private void Update()
|
||||
{
|
||||
var move = _forceThrust ? float2(0,1) : _input.Player.Move.ReadValue<Vector2>().ToCultMath();
|
||||
|
|
@ -144,10 +196,12 @@ public class FieldTester : MonoBehaviour
|
|||
|
||||
_capabilityEvents.PublishThrust(new ThrustEvent(_push, min(length(_push), 1), float2(_frontTwist, _rearTwist)));
|
||||
|
||||
if (_grabbing) AdvanceGrab();
|
||||
|
||||
for (var i = 0; i < _pickups.Count; i++)
|
||||
{
|
||||
var (l, time, t) = _pickups[i];
|
||||
if (t == null) continue;
|
||||
if (t == null || (_grabbing && i == _grabIndex)) continue;
|
||||
time = frac(time + Time.deltaTime / lerp(PickupTravelTimeMin, PickupTravelTimeMax, l));
|
||||
t.localScale = Vector3.one * lerp(PickupSizeMin, PickupSizeMax, l) * Zone.PowerPulse(time - .5f, ScaleExponent);
|
||||
t.position = new Vector3(t.position.x, t.position.y, (time - .5f) * PickupTravelDistance);
|
||||
|
|
|
|||
|
|
@ -58,7 +58,8 @@ public class SchematicDisplay : MonoBehaviour
|
|||
|
||||
public class SchematicDisplayItem
|
||||
{
|
||||
public EquippedItem Item;
|
||||
// The row Entity.SchematicRows derived: which item, which class and which range it shows.
|
||||
public SchematicRow Row;
|
||||
public SchematicListElement ListElement;
|
||||
public IProgressBehavior Cooldown;
|
||||
// public ItemUsage ItemUsage;
|
||||
|
|
@ -93,15 +94,12 @@ public class SchematicDisplay : MonoBehaviour
|
|||
CargoTemperatureLabel.text = "N/A";
|
||||
}
|
||||
|
||||
BuildRows(_enemy ? RevealedWeapons() : entity.Equipment.Where(IsWeapon));
|
||||
BuildRows(WeaponRows());
|
||||
}
|
||||
|
||||
private static bool IsWeapon(EquippedItem item) => item.Behaviors.Any(b => b.Data is WeaponData);
|
||||
|
||||
// An enemy schematic lists what `_player` has seen the ship's weapons do: the weapon items of the player's book on it,
|
||||
// in book order (ruling reveal-ends-on-perception), never the ship's live gear. The player's own schematic is its
|
||||
// equipment; there is nothing to reveal about your own ship.
|
||||
private IEnumerable<EquippedItem> RevealedWeapons() => _player.RevealsOf(_entity).Select(r => r.Item).Where(IsWeapon);
|
||||
// Which rows, which class and which range label are Entity.SchematicRows' decision (aetheria-ew, classify-from-belief);
|
||||
// this display only renders them.
|
||||
private IEnumerable<SchematicRow> WeaponRows() => (_player ?? _entity).SchematicRows(_entity);
|
||||
|
||||
private void ReturnRows()
|
||||
{
|
||||
|
|
@ -111,21 +109,21 @@ public class SchematicDisplay : MonoBehaviour
|
|||
_schematicItems = null;
|
||||
}
|
||||
|
||||
private void BuildRows(IEnumerable<EquippedItem> items)
|
||||
private void BuildRows(IEnumerable<SchematicRow> rows)
|
||||
{
|
||||
_schematicItems = items
|
||||
_schematicItems = rows
|
||||
.Select(x => new SchematicDisplayItem
|
||||
{
|
||||
Item = x,
|
||||
Row = x,
|
||||
ListElement = ListElementPrototype.Instantiate<SchematicListElement>(),
|
||||
Cooldown = _enemy ? null : (IProgressBehavior) x.Behaviors.FirstOrDefault(b=> b is IProgressBehavior),
|
||||
Cooldown = _enemy ? null : (IProgressBehavior) x.Item.Behaviors.FirstOrDefault(b=> b is IProgressBehavior),
|
||||
// ItemUsage = _enemy ? null : (ItemUsage) x.Behaviors.FirstOrDefault(b=> b is ItemUsage),
|
||||
Weapon = (Weapon) x.Behaviors.FirstOrDefault(b=>b is Weapon)
|
||||
Weapon = _enemy ? null : (Weapon) x.Item.Behaviors.FirstOrDefault(b=>b is Weapon)
|
||||
})
|
||||
.ToArray();
|
||||
foreach (var x in _schematicItems)
|
||||
{
|
||||
if(x.Item.Data is WeaponItemData weaponItemData)
|
||||
if(x.Row.Claimed is WeaponItemData weaponItemData)
|
||||
x.ListElement.ShowWeapon(weaponItemData);
|
||||
//x.ListElement.Label.text = x.Item.EquippableItem.Name;
|
||||
if (!_enemy)
|
||||
|
|
@ -197,10 +195,10 @@ public class SchematicDisplay : MonoBehaviour
|
|||
|
||||
// A reveal gained or ended after selection changes the rows with no event to hook: rebuild when the
|
||||
// revealed weapon set differs from the rows' items.
|
||||
if (_enemy && !RevealedWeapons().SequenceEqual(_schematicItems.Select(x => x.Item)))
|
||||
if (_enemy && !WeaponRows().Select(r => r.Key).SequenceEqual(_schematicItems.Select(x => x.Row.Key)))
|
||||
{
|
||||
ReturnRows();
|
||||
BuildRows(RevealedWeapons());
|
||||
BuildRows(WeaponRows());
|
||||
}
|
||||
|
||||
// Cut 2 (docs/fire-control-cut.md): marks whichever row is the player's current aim point.
|
||||
|
|
@ -208,7 +206,7 @@ public class SchematicDisplay : MonoBehaviour
|
|||
// as info decays, with no event to hook.
|
||||
if (_enemy && _schematicItems != null)
|
||||
foreach (var x in _schematicItems)
|
||||
x.ListElement.SetSelected(x.Item == _player.ResolvedTargetItem);
|
||||
x.ListElement.SetSelected(x.Row.Item == _player.ResolvedTargetItem);
|
||||
|
||||
if(VisibilityLabel && !_enemy)
|
||||
VisibilityLabel.text = ((int)_entity.Visibility).ToString();
|
||||
|
|
@ -226,11 +224,11 @@ public class SchematicDisplay : MonoBehaviour
|
|||
{
|
||||
if (!_enemy)
|
||||
{
|
||||
var itemData = _entity.ItemManager.GetData(x.Item.EquippableItem);
|
||||
x.ListElement.HeatFill.anchorMax = new Vector2(unlerp(itemData.MinimumTemperature, itemData.MaximumTemperature, x.Item.Temperature), 0);
|
||||
var itemData = _entity.ItemManager.GetData(x.Row.Item.EquippableItem);
|
||||
x.ListElement.HeatFill.anchorMax = new Vector2(unlerp(itemData.MinimumTemperature, itemData.MaximumTemperature, x.Row.Item.Temperature), 0);
|
||||
if (x.Cooldown != null)
|
||||
x.ListElement.CooldownFill.anchorMax = new Vector2(x.Cooldown.Progress, 1);
|
||||
x.ListElement.DurabilityLabel.text = $"{(int)(x.Item.EquippableItem.Durability / itemData.Durability * 100)}%";
|
||||
x.ListElement.DurabilityLabel.text = $"{(int)(x.Row.Item.EquippableItem.Durability / itemData.Durability * 100)}%";
|
||||
if (x.Weapon.WeaponData.AmmoType.IsSet())
|
||||
{
|
||||
if(x.Weapon.WeaponData.MagazineSize > 1)
|
||||
|
|
@ -240,11 +238,8 @@ public class SchematicDisplay : MonoBehaviour
|
|||
}
|
||||
}
|
||||
|
||||
if (x.Weapon.WeaponData != null)
|
||||
{
|
||||
if(x.ListElement.RangeLabel)
|
||||
x.ListElement.RangeLabel.text = ((int) x.Weapon.Range).ToString();
|
||||
}
|
||||
if (x.ListElement.RangeLabel)
|
||||
x.ListElement.RangeLabel.text = ((int) x.Row.Range).ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue