Pursuit scenario: Cargo stages consumables; a hostile on your tail, a Mine Launcher fitted, vents and overdrives in the hold
ScenarioStage.Cargo resolves one name across equippable and consumable designs; a consumable is built from its first available product in key order. Pursuit (seed 111, demo cast) lists last in Scenarios.Tests. All commits build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b888b20c33
commit
9ccc846c1a
6 changed files with 192 additions and 8 deletions
|
|
@ -190,7 +190,7 @@ public static class Loadouts
|
|||
return () => (EquippableItem) itemManager.CreateInstance(product);
|
||||
}
|
||||
|
||||
private static FactionProductData[] ProductsInKeyOrder(CultCache cache) =>
|
||||
internal static FactionProductData[] ProductsInKeyOrder(CultCache cache) =>
|
||||
cache.GetAll<FactionProductData>().OrderBy(p => cache.RefOf(p).Key.Value, StringComparer.Ordinal).ToArray();
|
||||
|
||||
private static string Cell(LoadoutSlot slot) => $"slot {slot.Position.x},{slot.Position.y}";
|
||||
|
|
|
|||
|
|
@ -116,7 +116,9 @@ public sealed class ScenarioStage
|
|||
return entity;
|
||||
}
|
||||
|
||||
// Items in the entity's first cargo bay, one per design named.
|
||||
// Items in the entity's first cargo bay, one per design named. A name is an equippable design (built as a fit
|
||||
// builds it) or a consumable design (built from its first available product in key order, as a generated hold is
|
||||
// stocked); a name that is neither is a failure.
|
||||
public void Cargo(Entity entity, params string[] designs)
|
||||
{
|
||||
if (entity == null) return;
|
||||
|
|
@ -124,10 +126,27 @@ public sealed class ScenarioStage
|
|||
var bay = entity.CargoBays.FirstOrDefault();
|
||||
foreach (var design in designs)
|
||||
{
|
||||
var data = Named<EquippableItemData>(design, where);
|
||||
var data = Named<CraftedItemData>(design, where, found => found is EquippableItemData || found is ConsumableItemData);
|
||||
if (data == null) continue;
|
||||
var build = Loadouts.Resolve(_items, _items.ItemData.RefOf(data), _available, where, _failures);
|
||||
if (build != null && (bay == null || !bay.TryStore(build())))
|
||||
Func<CraftedItemInstance> build;
|
||||
if (data is ConsumableItemData)
|
||||
{
|
||||
var key = _items.ItemData.RefOf(data).Key;
|
||||
var product = Loadouts.ProductsInKeyOrder(_items.ItemData).Where(p => p.Design.Key.Equals(key)).FirstOrDefault(p => _available(p));
|
||||
if (product == null)
|
||||
{
|
||||
_failures.Add($"{where}: no product offers {design}");
|
||||
continue;
|
||||
}
|
||||
build = () => _items.CreateInstance(product);
|
||||
}
|
||||
else
|
||||
{
|
||||
var equippable = Loadouts.Resolve(_items, _items.ItemData.RefOf((EquippableItemData) data), _available, where, _failures);
|
||||
if (equippable == null) continue;
|
||||
build = equippable;
|
||||
}
|
||||
if (bay == null || !bay.TryStore(build()))
|
||||
_failures.Add($"{where}: no room for {design}");
|
||||
}
|
||||
}
|
||||
|
|
@ -151,9 +170,9 @@ public sealed class ScenarioStage
|
|||
return entity;
|
||||
}
|
||||
|
||||
private T Named<T>(string name, string where) where T : EquippableItemData
|
||||
private T Named<T>(string name, string where, Func<T, bool> among = null) where T : CraftedItemData
|
||||
{
|
||||
var named = _items.ItemData.GetAll<T>().Where(data => data.Name == name).Take(2).ToArray();
|
||||
var named = _items.ItemData.GetAll<T>().Where(data => data.Name == name && (among == null || among(data))).Take(2).ToArray();
|
||||
if (named.Length == 1) return named[0];
|
||||
_failures.Add(named.Length == 0 ? $"{where}: no design named {name}" : $"{where}: more than one design is named {name}");
|
||||
return null;
|
||||
|
|
|
|||
60
Assets/Scripts/ServerShared/Scenarios/Pursuit.cs
Normal file
60
Assets/Scripts/ServerShared/Scenarios/Pursuit.cs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
using static CultMath.math;
|
||||
using static ItemRotation;
|
||||
|
||||
public sealed class Pursuit : Scenario
|
||||
{
|
||||
// The pursuer starts astern, inside the 400 at which VapourCloudTests.ACloudBreaksALockAtRange shows a fresh vent
|
||||
// cloud breaking a sensor's lock, and well outside the 40 at which AStrongCloseSensorBurnsThrough shows a close
|
||||
// sensor burning through.
|
||||
private const float Astern = 300;
|
||||
|
||||
public override string Name => "Pursuit";
|
||||
public override string Brief => "A hostile Longinus on your tail; a Mine Launcher fitted, three Coolant Vents and two Thruster " +
|
||||
"Overdrives aboard. Verify: lay mines in its path, the mine pulses slowly after 2 s, fast when " +
|
||||
"the hostile is near, explodes 2 s later; vent and coast to lose it; overdrive away.";
|
||||
public override uint Seed => 111;
|
||||
public override bool Ambient => false;
|
||||
|
||||
public override Galaxy Generate(GalaxyStage stage) => stage.Prelude(DemoTerminus.Cast);
|
||||
|
||||
public override void Stage(ScenarioStage stage)
|
||||
{
|
||||
var layer = 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),
|
||||
("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),
|
||||
("Fire Control Array", int2(1, 6), Clockwise),
|
||||
("Store-All Plus", 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),
|
||||
("Fire Control Array", int2(1, 6), Clockwise),
|
||||
("Store-All Plus", 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");
|
||||
stage.Place(pursuer, float2(0, -Astern), facing: float2(0, 1), stance: ScenarioStance.Hostile, piloted: true);
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/ServerShared/Scenarios/Pursuit.cs.meta
Normal file
11
Assets/Scripts/ServerShared/Scenarios/Pursuit.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4b0587057e6a4618bf63944364298870
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -9,6 +9,6 @@ public static class Scenarios
|
|||
|
||||
public static readonly Scenario[] Tests =
|
||||
{
|
||||
new DjinniShakedown(), new StarvedReactor(), new Arcs(), new Duel(), new LauncherAngles(), new LongHaul()
|
||||
new DjinniShakedown(), new StarvedReactor(), new Arcs(), new Duel(), new LauncherAngles(), new LongHaul(), new Pursuit()
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue