Staged fits: a scenario states the quality of a fit its checks rest on
ScenarioStage.Fit(hull, quality, slots) mints every lot of the fit at the stated quality through Loadouts.Materialize/Resolve, so Crossing's fighter and EvasionTermTests' hand-fitted Longinus and Djinni do not ride the item stream. No seed changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
30597b0e45
commit
7b2a6d064d
5 changed files with 65 additions and 16 deletions
|
|
@ -108,18 +108,19 @@ public static class Loadouts
|
|||
// picks the entity: a ship hull builds a Ship, a turret hull an OrbitalEntity with no orbit, which stays where it is
|
||||
// put; a station hull is refused. The entity always gets exactly WeaponGroupCount groups; a loadout with fewer is
|
||||
// padded with empty groups. A failed materialization leaves any minted lots in the live ledger; they are not roots,
|
||||
// so the next commit drops them.
|
||||
// so the next commit drops them. A stated quality mints every lot of the fit at it, drawing nothing (a scenario states
|
||||
// one for a fit its checks rest on).
|
||||
public static Entity Materialize(ItemManager itemManager, Zone liveZone, Loadout loadout,
|
||||
Func<CraftedItemData, FactionProductData> choose, List<string> failures)
|
||||
Func<CraftedItemData, FactionProductData> choose, List<string> failures, float? quality = null)
|
||||
{
|
||||
var cache = itemManager.ItemData;
|
||||
var reported = failures.Count;
|
||||
|
||||
var hullBuild = Resolve(itemManager, loadout.Hull, loadout.HullProduct, choose, "hull", failures);
|
||||
var hullBuild = Resolve(itemManager, loadout.Hull, loadout.HullProduct, choose, "hull", failures, quality);
|
||||
var hullData = cache.Get(loadout.Hull);
|
||||
if (hullData is { HullType: HullType.Station })
|
||||
failures.Add($"hull: {hullData.Name} is a station hull; a preset builds a ship or a turret");
|
||||
var slotBuilds = loadout.Slots.Select(slot => Resolve(itemManager, slot.Design, slot.Product, choose, Cell(slot), failures)).ToArray();
|
||||
var slotBuilds = loadout.Slots.Select(slot => Resolve(itemManager, slot.Design, slot.Product, choose, Cell(slot), failures, quality)).ToArray();
|
||||
var groups = loadout.WeaponGroups ?? new int[0][];
|
||||
var groupCount = itemManager.GameplaySettings.WeaponGroupCount;
|
||||
if (groups.Length > groupCount) failures.Add($"weapon groups: {groups.Length} groups, at most {groupCount}");
|
||||
|
|
@ -168,11 +169,11 @@ public static class Loadouts
|
|||
// How one design is built, or null with the failure listed; nothing is minted until the returned builder runs. A
|
||||
// pinned product is built as named, whatever choose says; it must be a product of the design. A design with no
|
||||
// product at all is outside the economy (docs/scenarios-cut.md, Q3: generation, stock and sale all go through
|
||||
// products) and is built unbranded, with no maker, at UnbrandedQuality. Otherwise choose names the product, and
|
||||
// products) and is built unbranded, with no maker, at UnbrandedQuality (a stated quality replaces it). Otherwise choose names the product, and
|
||||
// null (nothing available) or a product of another design is a failure.
|
||||
public static Func<CraftedItemInstance> Resolve<T>(ItemManager itemManager, CultRecordRef<T> design,
|
||||
CultRecordRef<FactionProductData> product, Func<CraftedItemData, FactionProductData> choose, string where,
|
||||
List<string> failures) where T : CraftedItemData
|
||||
List<string> failures, float? quality = null) where T : CraftedItemData
|
||||
{
|
||||
var cache = itemManager.ItemData;
|
||||
var data = cache.Get(design);
|
||||
|
|
@ -195,7 +196,7 @@ public static class Loadouts
|
|||
else
|
||||
{
|
||||
if (!cache.GetAll<FactionProductData>().Any(p => p.Design.Key.Equals(design.Key)))
|
||||
return () => itemManager.CreateInstance(itemManager.CreateLot(data, default, UnbrandedQuality));
|
||||
return () => itemManager.CreateInstance(itemManager.CreateLot(data, default, quality ?? UnbrandedQuality));
|
||||
made = choose(data);
|
||||
if (made == null)
|
||||
{
|
||||
|
|
@ -209,7 +210,7 @@ public static class Loadouts
|
|||
failures.Add($"{where}: product {made.Name} is not a product of {data.Name}");
|
||||
return null;
|
||||
}
|
||||
return () => itemManager.CreateInstance(made);
|
||||
return () => itemManager.CreateInstance(quality is { } q ? itemManager.CreateLot(made, q) : itemManager.CreateLot(made));
|
||||
}
|
||||
|
||||
private static string Cell(LoadoutSlot slot) => $"slot {slot.Position.x},{slot.Position.y}";
|
||||
|
|
|
|||
|
|
@ -52,7 +52,15 @@ public sealed class ScenarioStage
|
|||
|
||||
// A hull fitted slot by slot: each slot a design or a product, the hull cell it goes in, and its rotation. A name is
|
||||
// a design (open: the stage chooses its product) or a product (pinned: that brand).
|
||||
public ScenarioFit Fit(string hull, params (string design, int2 cell, ItemRotation rotation)[] slots)
|
||||
public ScenarioFit Fit(string hull, params (string design, int2 cell, ItemRotation rotation)[] slots) =>
|
||||
FitAt(hull, null, slots);
|
||||
|
||||
// The same fit with every lot it mints (hull, slots, unbranded) at the stated quality, drawing nothing. A fit a scenario's
|
||||
// checks rest on states its quality, so the check does not ride the item stream.
|
||||
public ScenarioFit Fit(string hull, float quality, params (string design, int2 cell, ItemRotation rotation)[] slots) =>
|
||||
FitAt(hull, quality, slots);
|
||||
|
||||
private ScenarioFit FitAt(string hull, float? quality, (string design, int2 cell, ItemRotation rotation)[] slots)
|
||||
{
|
||||
var where = $"fit {hull}";
|
||||
var (hullData, hullProduct) = NamedOrProduct<HullData>(hull, where);
|
||||
|
|
@ -69,7 +77,7 @@ public sealed class ScenarioStage
|
|||
Product = product == null ? default : _items.ItemData.RefOf(product)
|
||||
});
|
||||
}
|
||||
return hullData != null && loadout.Slots.Count == slots.Length ? new ScenarioFit(loadout) : null;
|
||||
return hullData != null && loadout.Slots.Count == slots.Length ? new ScenarioFit(loadout, quality) : null;
|
||||
}
|
||||
|
||||
// A hull with nothing fitted.
|
||||
|
|
@ -154,7 +162,7 @@ public sealed class ScenarioStage
|
|||
else
|
||||
{
|
||||
var failures = new List<string>();
|
||||
entity = Loadouts.Materialize(_items, Arena, fit.Loadout, _choose, failures);
|
||||
entity = Loadouts.Materialize(_items, Arena, fit.Loadout, _choose, failures, fit.Quality);
|
||||
_failures.AddRange(failures.Select(failure => $"{where} ({fit.Loadout.Name}): {failure}"));
|
||||
if (entity == null) return null;
|
||||
}
|
||||
|
|
@ -190,7 +198,13 @@ public sealed class ScenarioFit
|
|||
{
|
||||
internal readonly Loadout Loadout;
|
||||
internal readonly EntityPack Pack;
|
||||
internal readonly float? Quality;
|
||||
|
||||
internal ScenarioFit(Loadout loadout, float? quality = null)
|
||||
{
|
||||
Loadout = loadout;
|
||||
Quality = quality;
|
||||
}
|
||||
|
||||
internal ScenarioFit(Loadout loadout) => Loadout = loadout;
|
||||
internal ScenarioFit(EntityPack pack) => Pack = pack;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ public sealed class Crossing : Scenario
|
|||
// The waiting Djinni sits inside the Lightning Projector's arc at 80% of its Range.
|
||||
public static readonly CultMath.float2 Origin = float2(724, 124);
|
||||
public static readonly CultMath.float2 CrosserAt = Origin + float2(-128, 45), CrosserFacing = float2(1, 0), CrosserVelocity = float2(50, 0);
|
||||
// The fighter is staged at this quality, so its gear and the range below do not ride the item stream.
|
||||
public const float StagedQuality = .6f;
|
||||
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 = Origin + float2(0, ProjectorRange * .8f);
|
||||
|
||||
|
|
@ -29,7 +31,7 @@ public sealed class Crossing : Scenario
|
|||
|
||||
public override void Stage(ScenarioStage stage)
|
||||
{
|
||||
var fighter = stage.Fit("Longinus",
|
||||
var fighter = stage.Fit("Longinus", StagedQuality,
|
||||
("Cockpit 2x2", int2(2, 6), None),
|
||||
("Large Drive", int2(1, 0), Reversed),
|
||||
("Large Drive", int2(3, 0), Reversed),
|
||||
|
|
|
|||
Loading…
Reference in a new issue