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
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue