Merge eureka/aetheria-release-product-lines-weapons (cut-product-lines-weapons r5, h4) into master
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
6179846249
28 changed files with 1032 additions and 216 deletions
|
|
@ -6,9 +6,10 @@ using GameCult.Caching;
|
|||
using MessagePack;
|
||||
using CultMath;
|
||||
|
||||
// A ship preset (a variant): item designs only, the hull's design, and per hull cell a design and its rotation. A design
|
||||
// names no manufacturer; which product builds a slot is a fact of the galaxy the preset is materialized in, which keeps
|
||||
// a preset portable across galaxies. Authored catalog data: written in CultCache Studio or by the editor capture
|
||||
// A ship preset (a variant): the hull's design, and per hull cell a design and its rotation. A slot names a design and
|
||||
// may name a product of it; a design-only slot is open on purpose, and whoever materializes the preset chooses its
|
||||
// product from the galaxy it is built in (ruling loadout-product-optional-context-chooses), which keeps a preset
|
||||
// portable across galaxies. A preset that needs a brand names it. Authored catalog data: written in CultCache Studio or by the editor capture
|
||||
// command, read at runtime.
|
||||
[CultDocument("aetheria.loadout", "1"), MessagePackObject]
|
||||
public class Loadout
|
||||
|
|
@ -17,6 +18,7 @@ public class Loadout
|
|||
[Key(1)] public CultRecordRef<HullData> Hull;
|
||||
[Key(2)] public List<LoadoutSlot> Slots = new List<LoadoutSlot>();
|
||||
[Key(3)] public int[][] WeaponGroups; // indices into Slots
|
||||
[Key(4)] public CultRecordRef<FactionProductData> HullProduct; // unset: the hull's design alone, chosen at materialization
|
||||
}
|
||||
|
||||
[MessagePackObject]
|
||||
|
|
@ -25,14 +27,15 @@ public class LoadoutSlot
|
|||
[Key(0)] public int2 Position; // hull cell passed to Entity.TryEquip(item, int2)
|
||||
[Key(1)] public ItemRotation Rotation;
|
||||
[Key(2)] public CultRecordRef<EquippableItemData> Design;
|
||||
[Key(3)] public CultRecordRef<FactionProductData> Product; // unset: design-only, chosen at materialization
|
||||
}
|
||||
|
||||
// The owner of loadouts: capture from a live ship, the only writer, and the only builder of an entity from a loadout.
|
||||
public static class Loadouts
|
||||
{
|
||||
// Records the hull design and one slot per equipped item (gear, then cargo bays, then docking bays) with its cell,
|
||||
// rotation and design. Entity.Equipment also holds the hull's own unit; that is Hull, not a slot. Nothing about the
|
||||
// units themselves: no product, quality, ingredients, cargo or state.
|
||||
// rotation, design and product (unset for an unbranded unit). Entity.Equipment also holds the hull's own unit; that is
|
||||
// Hull, not a slot. Nothing else about the units: no quality, ingredients, cargo or state.
|
||||
public static Loadout Capture(ItemManager itemManager, Entity entity, string name)
|
||||
{
|
||||
var cache = itemManager.ItemData;
|
||||
|
|
@ -46,11 +49,13 @@ public static class Loadouts
|
|||
{
|
||||
Name = name,
|
||||
Hull = cache.RefOf((HullData) itemManager.GetData(entity.Hull)),
|
||||
HullProduct = ProductRef(itemManager, entity.Hull),
|
||||
Slots = items.Select(item => new LoadoutSlot
|
||||
{
|
||||
Position = item.Position,
|
||||
Rotation = item.EquippableItem.Rotation,
|
||||
Design = cache.RefOf(itemManager.GetData(item.EquippableItem))
|
||||
Design = cache.RefOf(itemManager.GetData(item.EquippableItem)),
|
||||
Product = ProductRef(itemManager, item.EquippableItem)
|
||||
}).ToList(),
|
||||
WeaponGroups = entity.WeaponGroups
|
||||
.Select(group => group.items.Select(item => items.IndexOf(item)).ToArray())
|
||||
|
|
@ -58,6 +63,9 @@ public static class Loadouts
|
|||
};
|
||||
}
|
||||
|
||||
private static CultRecordRef<FactionProductData> ProductRef(ItemManager itemManager, CraftedItemInstance unit) =>
|
||||
itemManager.Brand(unit).Product is { } product ? itemManager.ItemData.RefOf(product) : default;
|
||||
|
||||
// A preset's key derives from its name, so capturing the same name again addresses the same record.
|
||||
public static CultRecordKey KeyOf(string name) => new CultRecordKey("loadout:" + name);
|
||||
|
||||
|
|
@ -95,24 +103,23 @@ public static class Loadouts
|
|||
// All-or-nothing: returns an entity only when every design resolved and fitted, the loadout has at most
|
||||
// WeaponGroupCount weapon groups, and every group index names a weapon slot. Every failure is listed; on any failure
|
||||
// nothing is returned and the zone and cache are unchanged, but a failed fit has already drawn from
|
||||
// itemManager.Random. How each design is built is Resolve's rule. A game spawner is to pass
|
||||
// LoadoutGenerator.IsAvailable as isAvailable, with no fallback to any manufacturer; RunStart does. The hull's type
|
||||
// itemManager.Random. How each design is built is Resolve's rule. A design-only slot is chosen by choose; a game
|
||||
// spawner is to pass LoadoutGenerator.ChooseProduct, with no fallback to any manufacturer; RunStart does. The hull's type
|
||||
// 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.
|
||||
public static Entity Materialize(ItemManager itemManager, Zone liveZone, Loadout loadout,
|
||||
Predicate<FactionProductData> isAvailable, List<string> failures)
|
||||
Func<CraftedItemData, FactionProductData> choose, List<string> failures)
|
||||
{
|
||||
var cache = itemManager.ItemData;
|
||||
var products = ProductsInKeyOrder(cache);
|
||||
var reported = failures.Count;
|
||||
|
||||
var hullBuild = Resolve(itemManager, products, loadout.Hull, isAvailable, "hull", failures);
|
||||
var hullBuild = Resolve(itemManager, loadout.Hull, loadout.HullProduct, choose, "hull", failures);
|
||||
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, products, slot.Design, isAvailable, Cell(slot), failures)).ToArray();
|
||||
var slotBuilds = loadout.Slots.Select(slot => Resolve(itemManager, slot.Design, slot.Product, choose, Cell(slot), failures)).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}");
|
||||
|
|
@ -124,7 +131,7 @@ public static class Loadouts
|
|||
}
|
||||
if (failures.Count > reported) return null;
|
||||
|
||||
var hull = hullBuild();
|
||||
var hull = (EquippableItem) hullBuild();
|
||||
var settings = itemManager.GameplaySettings.DefaultEntitySettings;
|
||||
Entity entity = hullData.HullType == HullType.Turret
|
||||
? new OrbitalEntity(itemManager, liveZone, hull, default, settings)
|
||||
|
|
@ -133,7 +140,7 @@ public static class Loadouts
|
|||
for (var i = 0; i < loadout.Slots.Count; i++)
|
||||
{
|
||||
var slot = loadout.Slots[i];
|
||||
var unit = slotBuilds[i]();
|
||||
var unit = (EquippableItem) slotBuilds[i]();
|
||||
unit.Rotation = slot.Rotation;
|
||||
if (!entity.TryEquip(unit, slot.Position))
|
||||
{
|
||||
|
|
@ -159,39 +166,51 @@ public static class Loadouts
|
|||
}
|
||||
|
||||
// How one design is built, or null with the failure listed; nothing is minted until the returned builder runs. A
|
||||
// design is built by its first available product in record-key order. 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. A design whose products are all unavailable is not built.
|
||||
public static Func<EquippableItem> Resolve<T>(ItemManager itemManager, CultRecordRef<T> design,
|
||||
Predicate<FactionProductData> isAvailable, string where, List<string> failures) where T : EquippableItemData =>
|
||||
Resolve(itemManager, ProductsInKeyOrder(itemManager.ItemData), design, isAvailable, where, failures);
|
||||
|
||||
private static Func<EquippableItem> Resolve<T>(ItemManager itemManager, FactionProductData[] products,
|
||||
CultRecordRef<T> design, Predicate<FactionProductData> isAvailable, string where, List<string> failures)
|
||||
where T : EquippableItemData
|
||||
// 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
|
||||
// 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
|
||||
{
|
||||
var data = itemManager.ItemData.Get(design);
|
||||
var cache = itemManager.ItemData;
|
||||
var data = cache.Get(design);
|
||||
if (data == null)
|
||||
{
|
||||
failures.Add($"{where}: design {design.Key} is not in the catalog");
|
||||
return null;
|
||||
}
|
||||
|
||||
var made = products.Where(p => p.Design.Key.Equals(design.Key)).ToArray();
|
||||
if (made.Length == 0)
|
||||
return () => (EquippableItem) itemManager.CreateInstance(itemManager.CreateLot(data, default, UnbrandedQuality));
|
||||
|
||||
var product = made.FirstOrDefault(p => isAvailable(p));
|
||||
if (product == null)
|
||||
FactionProductData made;
|
||||
if (product.IsSet())
|
||||
{
|
||||
failures.Add($"{where}: no available product of {data.Name}");
|
||||
made = cache.Get(product);
|
||||
if (made == null)
|
||||
{
|
||||
failures.Add($"{where}: product {product.Key} is not in the catalog");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!cache.GetAll<FactionProductData>().Any(p => p.Design.Key.Equals(design.Key)))
|
||||
return () => itemManager.CreateInstance(itemManager.CreateLot(data, default, UnbrandedQuality));
|
||||
made = choose(data);
|
||||
if (made == null)
|
||||
{
|
||||
failures.Add($"{where}: no available product of {data.Name}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!made.Design.Key.Equals(design.Key))
|
||||
{
|
||||
failures.Add($"{where}: product {made.Name} is not a product of {data.Name}");
|
||||
return null;
|
||||
}
|
||||
return () => (EquippableItem) itemManager.CreateInstance(product);
|
||||
return () => itemManager.CreateInstance(made);
|
||||
}
|
||||
|
||||
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}";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ public class LoadoutGenerator
|
|||
{
|
||||
var (available, preferManufacturers) = AvailableProducts(filter, required);
|
||||
return available.WeightedRandomElements(ref Random, entry =>
|
||||
(preferManufacturers ? ManufacturerPreference(entry.product.Manufacturer) : 1) *
|
||||
(preferManufacturers ? ProductWeight(entry.product) : 1) *
|
||||
pow(entry.design.Shape.Coordinates.Length, sizeExponent) / // Prioritize larger items
|
||||
pow(entry.design.Price, PriceExponent), // Penalize item price to a controllable degree
|
||||
count);
|
||||
|
|
@ -243,8 +243,8 @@ public class LoadoutGenerator
|
|||
|
||||
// No galaxy, or no faction, means no allegiance to filter by: every product is on offer. A fixture generates
|
||||
// loadouts that way, so a test can exercise placement and products without standing up a whole galaxy; a scenario
|
||||
// stage has no faction and reaches every maker. Loadouts.Materialize takes availability as a predicate; RunStart,
|
||||
// the game's one preset spawner, passes this, so presets and generation share one availability rule. A faction
|
||||
// stage has no faction and reaches every maker. Loadouts.Materialize takes a chooser and ChooseProduct is the game's,
|
||||
// so presets and generation share this one availability rule. A faction
|
||||
// always reaches its own manufacturer's gear, and otherwise gear made by a manufacturer its allegiance names
|
||||
// (operator, 2026-09-30: allegiance lists only other factions). Whether that manufacturer is present in the galaxy
|
||||
// does not matter (operator, 2026-10-06: a faction need not be present for its gear to be available).
|
||||
|
|
@ -252,6 +252,25 @@ public class LoadoutGenerator
|
|||
Galaxy == null || Faction == null ||
|
||||
ItemManager.ItemData.Get(product.Manufacturer) == Faction || Faction.Allegiance.ContainsKey(product.Manufacturer);
|
||||
|
||||
// How much this outfitter favors a product: its maker's preference. The one weight of generation and of ChooseProduct.
|
||||
public float ProductWeight(FactionProductData product) => ManufacturerPreference(product.Manufacturer);
|
||||
|
||||
// The product a design named without one is built as (ruling loadout-product-optional-context-chooses): among the
|
||||
// products of the design this outfitter can get, one draw from the run's seeded Random weighted by ProductWeight;
|
||||
// null when none, and uniform over them when every weight is 0. Candidates are ordered by name so the draw's
|
||||
// enumeration, and so its result, never depends on record keys. All of the policy is this body.
|
||||
public FactionProductData ChooseProduct(CraftedItemData design)
|
||||
{
|
||||
var key = ItemManager.ItemData.RefOf(design).Key;
|
||||
var candidates = ItemManager.ItemData.GetAll<FactionProductData>()
|
||||
.Where(product => product.Design.Key.Equals(key) && IsAvailable(product))
|
||||
.OrderBy(product => product.Name, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
if (candidates.Length == 0) return null;
|
||||
var uniform = candidates.All(product => ProductWeight(product) <= 0);
|
||||
return candidates.WeightedRandomElements(ref Random, product => uniform ? 1 : ProductWeight(product), 1)[0];
|
||||
}
|
||||
|
||||
// Prioritize products from the zone faction and its allies, penalizing distance to the manufacturer's headquarters.
|
||||
// A manufacturer with no home zone in this galaxy weighs as if its headquarters lay one jump beyond the farthest
|
||||
// zone this zone reaches: reachable through allegiance, least preferred.
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@ using float3 = CultMath.float3;
|
|||
// The run half of a scenario's vocabulary (docs/scenarios-cut.md, R.4). A scenario's Stage builds its fits and places
|
||||
// them here; nothing joins the arena until Stage returns with no failure, and then RunStart admits it all at once.
|
||||
// Every verb names what it could not do instead of throwing, so one bad name in a script lists every bad name.
|
||||
// Designs and hulls are named as the catalog names them. Only RunStart builds a stage.
|
||||
// Designs, hulls and products are named as the catalog names them. Only RunStart builds a stage.
|
||||
public sealed class ScenarioStage
|
||||
{
|
||||
private readonly ItemManager _items;
|
||||
private readonly Predicate<FactionProductData> _available;
|
||||
private readonly Func<CraftedItemData, FactionProductData> _choose;
|
||||
private readonly List<string> _failures = new List<string>();
|
||||
private readonly List<Placement> _placed = new List<Placement>();
|
||||
private int _placements;
|
||||
|
|
@ -25,8 +25,9 @@ public sealed class ScenarioStage
|
|||
StartingHull = startingHull;
|
||||
TutorialGenerationSettings = tutorialGenerationSettings;
|
||||
Credits = items.GameplaySettings.StartingCredits;
|
||||
// A fit names designs, not manufacturers, so what the galaxy offers decides which product builds each one.
|
||||
_available = new LoadoutGenerator(ref items.Random, items, arena.Galaxy, arena.GalaxyZone, null, 2).IsAvailable;
|
||||
// A design named without a product is chosen by the generator's ChooseProduct. The stage outfits with no faction,
|
||||
// so every maker is reachable and the run's seed draws.
|
||||
_choose = new LoadoutGenerator(ref items.Random, items, arena.Galaxy, arena.GalaxyZone, null, 2).ChooseProduct;
|
||||
}
|
||||
|
||||
public Galaxy Galaxy => Arena.Galaxy;
|
||||
|
|
@ -49,18 +50,24 @@ public sealed class ScenarioStage
|
|||
public bool Piloted;
|
||||
}
|
||||
|
||||
// A hull fitted slot by slot: each slot a design, the hull cell it goes in, and its rotation.
|
||||
// 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)
|
||||
{
|
||||
var where = $"fit {hull}";
|
||||
var hullData = Named<HullData>(hull, where);
|
||||
var (hullData, hullProduct) = NamedOrProduct<HullData>(hull, where);
|
||||
var loadout = new Loadout { Name = hull, WeaponGroups = new int[0][] };
|
||||
if (hullData != null) loadout.Hull = _items.ItemData.RefOf(hullData);
|
||||
if (hullProduct != null) loadout.HullProduct = _items.ItemData.RefOf(hullProduct);
|
||||
foreach (var (design, cell, rotation) in slots)
|
||||
{
|
||||
var data = Named<EquippableItemData>(design, where);
|
||||
var (data, product) = NamedOrProduct<EquippableItemData>(design, where);
|
||||
if (data == null) continue;
|
||||
loadout.Slots.Add(new LoadoutSlot { Design = _items.ItemData.RefOf(data), Position = cell, Rotation = rotation });
|
||||
loadout.Slots.Add(new LoadoutSlot
|
||||
{
|
||||
Design = _items.ItemData.RefOf(data), Position = cell, Rotation = rotation,
|
||||
Product = product == null ? default : _items.ItemData.RefOf(product)
|
||||
});
|
||||
}
|
||||
return hullData != null && loadout.Slots.Count == slots.Length ? new ScenarioFit(loadout) : null;
|
||||
}
|
||||
|
|
@ -119,38 +126,22 @@ public sealed class ScenarioStage
|
|||
return entity;
|
||||
}
|
||||
|
||||
// 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)
|
||||
// Items in the entity's first cargo bay, one per name. A name is an equippable or consumable design (open: the stage
|
||||
// chooses its product) or a product of one (pinned); a name that is neither is a failure.
|
||||
public void Cargo(Entity entity, params string[] names)
|
||||
{
|
||||
if (entity == null) return;
|
||||
var where = $"cargo of {entity.Name}";
|
||||
var bay = entity.CargoBays.FirstOrDefault();
|
||||
foreach (var design in designs)
|
||||
foreach (var name in names)
|
||||
{
|
||||
var data = Named<CraftedItemData>(design, where, found => found is EquippableItemData || found is ConsumableItemData);
|
||||
var (data, product) = NamedOrProduct<CraftedItemData>(name, where, found => found is EquippableItemData || found is ConsumableItemData);
|
||||
if (data == null) continue;
|
||||
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;
|
||||
}
|
||||
var build = Loadouts.Resolve(_items, _items.ItemData.RefOf(data),
|
||||
product == null ? default : _items.ItemData.RefOf(product), _choose, where, _failures);
|
||||
if (build == null) continue;
|
||||
if (bay == null || !bay.TryStore(build()))
|
||||
_failures.Add($"{where}: no room for {design}");
|
||||
_failures.Add($"{where}: no room for {name}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -163,7 +154,7 @@ public sealed class ScenarioStage
|
|||
else
|
||||
{
|
||||
var failures = new List<string>();
|
||||
entity = Loadouts.Materialize(_items, Arena, fit.Loadout, _available, failures);
|
||||
entity = Loadouts.Materialize(_items, Arena, fit.Loadout, _choose, failures);
|
||||
_failures.AddRange(failures.Select(failure => $"{where} ({fit.Loadout.Name}): {failure}"));
|
||||
if (entity == null) return null;
|
||||
}
|
||||
|
|
@ -173,6 +164,17 @@ public sealed class ScenarioStage
|
|||
return entity;
|
||||
}
|
||||
|
||||
// A name is a design, or else exactly one product (names are unique) whose design is a T passing among: the design
|
||||
// and that product. Neither is Named's failure.
|
||||
private (T design, FactionProductData product) NamedOrProduct<T>(string name, string where, Func<T, bool> among = null) where T : CraftedItemData
|
||||
{
|
||||
var cache = _items.ItemData;
|
||||
var products = cache.GetAll<FactionProductData>().Where(p => p.Name == name && cache.Get(p.Design) is T design && (among == null || among(design))).Take(2).ToArray();
|
||||
if (products.Length == 1 && !cache.GetAll<T>().Any(data => data.Name == name && (among == null || among(data))))
|
||||
return ((T) cache.Get(products[0].Design), products[0]);
|
||||
return (Named(name, where, among), null);
|
||||
}
|
||||
|
||||
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 && (among == null || among(data))).Take(2).ToArray();
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ public sealed class AiFusedDiscipline : Scenario
|
|||
(weapon, int2(1, 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));
|
||||
("not if i see you first", int2(3, 10), None), // Compact Sensor
|
||||
("Panopticon Prime", int2(1, 6), Clockwise)); // Fire Control Array
|
||||
|
||||
stage.Player(stage.Generated("Djinni"), float2(0, 0));
|
||||
stage.Place(Armed("Smoke Refused"), Hostile, facing: float2(0, -1), stance: ScenarioStance.Hostile, piloted: true);
|
||||
|
|
|
|||
|
|
@ -30,25 +30,25 @@ public sealed class Arcs : Scenario
|
|||
("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),
|
||||
("GT 3K", int2(2, 5), None), // Guided Missile Launcher
|
||||
("GT 3K", int2(7, 5), None), // Guided Missile Launcher
|
||||
("FastBlast+-", int2(3, 8), None), // Cell Blaster
|
||||
("FastBlast+-", int2(6, 8), None), // Cell Blaster
|
||||
("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),
|
||||
("Charged Scatter Blaster", int2(0, 4), CounterClockwise));
|
||||
("not if i see you first", int2(5, 10), None), // Compact Sensor
|
||||
("Panopticon Prime", int2(3, 6), Clockwise), // Fire Control Array
|
||||
("🫳🔫", int2(8, 4), Clockwise), // Autocannon
|
||||
("ChargeBlast SG", int2(0, 4), CounterClockwise)); // Charged Scatter Blaster
|
||||
var turret = stage.Fit("Turret",
|
||||
("Light Machine Gun", int2(1, 2), None),
|
||||
("Light Machine Gun", int2(5, 2), None),
|
||||
("ClearPath", int2(1, 2), None), // Light Machine Gun
|
||||
("ClearPath", int2(5, 2), None), // Light Machine Gun
|
||||
("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),
|
||||
("Compact Sensor", int2(4, 5), None),
|
||||
("Fire Control Array", int2(3, 6), None));
|
||||
("not if i see you first", int2(4, 5), None), // Compact Sensor
|
||||
("Panopticon Prime", int2(3, 6), None)); // Fire Control Array
|
||||
|
||||
stage.Player(fighter, float2(0, 0), facing: float2(0, 1));
|
||||
stage.Place(stage.Bare("Longinus"), Bow);
|
||||
|
|
|
|||
|
|
@ -26,14 +26,14 @@ public sealed class Belt : Scenario
|
|||
("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),
|
||||
("GT 3K", int2(0, 5), None), // Guided Missile Launcher
|
||||
("GT 3K", int2(5, 5), None), // Guided Missile Launcher
|
||||
("DragOnBreath", int2(1, 8), None), // Lightning Projector
|
||||
("FastBlast+-", int2(4, 8), None), // Cell Blaster
|
||||
("Radiator", int2(2, 2), CounterClockwise),
|
||||
("Radiator", int2(3, 2), Clockwise),
|
||||
("Compact Sensor", int2(3, 10), None),
|
||||
("Fire Control Array", int2(1, 6), Clockwise));
|
||||
("not if i see you first", int2(3, 10), None), // Compact Sensor
|
||||
("Panopticon Prime", int2(1, 6), Clockwise)); // Fire Control Array
|
||||
|
||||
// The belt is a ring about its parent's position at the belt orbit's distance. The player stands 100 m outside
|
||||
// it, at the point nearest the origin, facing along the ring.
|
||||
|
|
|
|||
|
|
@ -36,14 +36,14 @@ public sealed class Crossing : Scenario
|
|||
("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),
|
||||
("GT 3K", int2(0, 5), None), // Guided Missile Launcher
|
||||
("GT 3K", int2(5, 5), None), // Guided Missile Launcher
|
||||
("DragOnBreath", int2(1, 8), None), // Lightning Projector
|
||||
("FastBlast+-", int2(4, 8), None), // Cell Blaster
|
||||
("Radiator", int2(2, 2), CounterClockwise),
|
||||
("Radiator", int2(3, 2), Clockwise),
|
||||
("Compact Sensor", int2(3, 10), None),
|
||||
("Fire Control Array", int2(1, 6), Clockwise));
|
||||
("not if i see you first", int2(3, 10), None), // Compact Sensor
|
||||
("Panopticon Prime", int2(1, 6), Clockwise)); // Fire Control Array
|
||||
|
||||
stage.Player(fighter, Origin, facing: float2(0, 1));
|
||||
stage.Place(stage.Generated("Djinni"), CrosserAt, facing: CrosserFacing, stance: ScenarioStance.Hostile, velocity: CrosserVelocity);
|
||||
|
|
|
|||
|
|
@ -20,19 +20,19 @@ public sealed class Duel : Scenario
|
|||
("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),
|
||||
("GT 3K", int2(0, 5), None), // Guided Missile Launcher
|
||||
("GT 3K", int2(5, 5), None), // Guided Missile Launcher
|
||||
("FastBlast+-", int2(1, 8), None), // Cell Blaster
|
||||
("FastBlast+-", int2(4, 8), None), // Cell Blaster
|
||||
("Radiator", int2(2, 2), CounterClockwise),
|
||||
("Radiator", int2(3, 2), Clockwise),
|
||||
("Compact Sensor", int2(3, 10), None),
|
||||
("not if i see you first", int2(3, 10), None), // Compact Sensor
|
||||
("Capacitor", int2(1, 5), None),
|
||||
("Fire Control Array", int2(1, 6), Clockwise),
|
||||
("Cargo Bay", int2(2, 8), None));
|
||||
("Panopticon Prime", int2(1, 6), Clockwise), // Fire Control Array
|
||||
("Store-All Plus", int2(2, 8), None)); // Cargo Bay
|
||||
|
||||
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, "Light Laser");
|
||||
stage.Cargo(rival, "Spectra");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,11 +35,11 @@ public sealed class FusedRounds : Scenario
|
|||
("Compact Reactor", int2(4, 4), None),
|
||||
("Smoke Proximity", int2(3, 8), None),
|
||||
("Smoke Delayed", int2(6, 8), None),
|
||||
("Cluster Cannon", int2(5, 17), None),
|
||||
("DeathCluster", int2(5, 17), None), // Cluster Cannon
|
||||
("Radiator", int2(4, 2), CounterClockwise),
|
||||
("Radiator", int2(5, 2), Clockwise),
|
||||
("Compact Sensor", int2(5, 10), None),
|
||||
("Fire Control Array", int2(3, 6), Clockwise));
|
||||
("not if i see you first", int2(5, 10), None), // Compact Sensor
|
||||
("Panopticon Prime", int2(3, 6), Clockwise)); // Fire Control Array
|
||||
|
||||
stage.Player(shooter, float2(0, 0), facing: float2(0, 1));
|
||||
stage.Place(stage.Bare("Longinus"), Mid, stance: ScenarioStance.Hostile);
|
||||
|
|
|
|||
|
|
@ -20,14 +20,14 @@ public sealed class LauncherAngles : Scenario
|
|||
("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),
|
||||
("GT 3K", int2(0, 5), None), // Guided Missile Launcher
|
||||
("Leonid", int2(5, 5), None), // Swarm Rocket Pod
|
||||
("FastBlast+-", int2(1, 8), None), // Cell Blaster
|
||||
("FastBlast+-", int2(4, 8), None), // Cell Blaster
|
||||
("Radiator", int2(2, 2), CounterClockwise),
|
||||
("Radiator", int2(3, 2), Clockwise),
|
||||
("Compact Sensor", int2(3, 10), None),
|
||||
("Fire Control Array", int2(1, 6), Clockwise));
|
||||
("not if i see you first", int2(3, 10), None), // Compact Sensor
|
||||
("Panopticon Prime", int2(1, 6), Clockwise)); // Fire Control Array
|
||||
|
||||
stage.Player(launcher, float2(0, 0));
|
||||
stage.Place(stage.Bare("Longinus"), float2(0, 900), facing: float2(1, 0), stance: ScenarioStance.Hostile);
|
||||
|
|
|
|||
|
|
@ -29,16 +29,16 @@ public sealed class LockOn : Scenario
|
|||
("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),
|
||||
("GT 3K", int2(0, 5), None), // Guided Missile Launcher
|
||||
("GT 3K", int2(5, 5), None), // Guided Missile Launcher
|
||||
("FastBlast+-", int2(1, 8), None), // Cell Blaster
|
||||
("FastBlast+-", int2(4, 8), None), // Cell Blaster
|
||||
("Radiator", int2(2, 2), CounterClockwise),
|
||||
("Radiator", int2(3, 2), Clockwise),
|
||||
("Compact Sensor", int2(3, 10), None),
|
||||
("not if i see you first", int2(3, 10), None), // Compact Sensor
|
||||
("Capacitor", int2(1, 5), None),
|
||||
("Fire Control Array", int2(1, 6), Clockwise),
|
||||
("Cargo Bay", int2(2, 8), None));
|
||||
("Panopticon Prime", int2(1, 6), Clockwise), // Fire Control Array
|
||||
("Store-All Plus", int2(2, 8), None)); // Cargo Bay
|
||||
|
||||
stage.Player(fighter, float2(0, 0), facing: float2(0, 1));
|
||||
stage.Place(fighter, HostileAt, facing: normalize(-HostileAt), stance: ScenarioStance.Hostile, piloted: true);
|
||||
|
|
|
|||
|
|
@ -26,15 +26,15 @@ public sealed class Pursuit : Scenario
|
|||
("Rugged Drive", int2(2, 14), CounterClockwise),
|
||||
("Rugged Drive", int2(3, 14), Clockwise),
|
||||
("Compact Reactor", int2(2, 4), None),
|
||||
("Mine Launcher", int2(0, 5), None),
|
||||
("Swarm Rocket Pod", int2(5, 5), None),
|
||||
("Cell Blaster", int2(1, 8), None),
|
||||
("Cell Blaster", int2(4, 8), None),
|
||||
("parting gift", int2(0, 5), None), // Mine Launcher
|
||||
("Leonid", int2(5, 5), None), // Swarm Rocket Pod
|
||||
("FastBlast+-", int2(1, 8), None), // Cell Blaster
|
||||
("FastBlast+-", int2(4, 8), None), // Cell Blaster
|
||||
("Radiator", int2(2, 2), CounterClockwise),
|
||||
("Radiator", int2(3, 2), Clockwise),
|
||||
("Compact Sensor", int2(3, 10), None),
|
||||
("Fire Control Array", int2(1, 6), Clockwise),
|
||||
("Cargo Bay", int2(2, 8), None));
|
||||
("not if i see you first", int2(3, 10), None), // Compact Sensor
|
||||
("Panopticon Prime", int2(1, 6), Clockwise), // Fire Control Array
|
||||
("Store-All Plus", int2(2, 8), None)); // Cargo Bay
|
||||
var pursuer = stage.Fit("Longinus",
|
||||
("Cockpit 2x2", int2(2, 6), None),
|
||||
("Large Drive", int2(1, 0), Reversed),
|
||||
|
|
@ -42,19 +42,19 @@ public sealed class Pursuit : Scenario
|
|||
("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),
|
||||
("GT 3K", int2(0, 5), None), // Guided Missile Launcher
|
||||
("GT 3K", int2(5, 5), None), // Guided Missile Launcher
|
||||
("FastBlast+-", int2(1, 8), None), // Cell Blaster
|
||||
("FastBlast+-", int2(4, 8), None), // Cell Blaster
|
||||
("Radiator", int2(2, 2), CounterClockwise),
|
||||
("Radiator", int2(3, 2), Clockwise),
|
||||
("Compact Sensor", int2(3, 10), None),
|
||||
("not if i see you first", int2(3, 10), None), // Compact Sensor
|
||||
("Capacitor", int2(1, 5), None),
|
||||
("Fire Control Array", int2(1, 6), Clockwise),
|
||||
("Cargo Bay", int2(2, 8), None));
|
||||
("Panopticon Prime", int2(1, 6), Clockwise), // Fire Control Array
|
||||
("Store-All Plus", int2(2, 8), None)); // Cargo Bay
|
||||
|
||||
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.Cargo(player, "Morning Fog", "Morning Fog", "Morning Fog", "send it", "send it");
|
||||
stage.Place(pursuer, float2(0, -Astern), facing: float2(0, 1), stance: ScenarioStance.Hostile, piloted: true);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,11 +28,11 @@ public sealed class RefusedRounds : Scenario
|
|||
("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),
|
||||
("🫳🔫", int2(5, 17), None), // Autocannon
|
||||
("Radiator", int2(4, 2), CounterClockwise),
|
||||
("Radiator", int2(5, 2), Clockwise),
|
||||
("Compact Sensor", int2(5, 10), None),
|
||||
("Fire Control Array", int2(3, 6), Clockwise));
|
||||
("not if i see you first", int2(5, 10), None), // Compact Sensor
|
||||
("Panopticon Prime", int2(3, 6), Clockwise)); // Fire Control Array
|
||||
|
||||
stage.Player(shooter, float2(0, 0), facing: float2(0, 1));
|
||||
stage.Place(stage.Bare("Longinus"), Target);
|
||||
|
|
|
|||
|
|
@ -20,12 +20,12 @@ public sealed class StarvedReactor : Scenario
|
|||
("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),
|
||||
("Spectra", int2(1, 8), None), // Light Laser
|
||||
("Spectra", int2(4, 8), None), // Light Laser
|
||||
("Radiator", int2(2, 2), CounterClockwise),
|
||||
("Radiator", int2(3, 2), Clockwise),
|
||||
("Compact Sensor", int2(3, 10), None),
|
||||
("Fire Control Array", int2(1, 6), Clockwise));
|
||||
("not if i see you first", int2(3, 10), None), // Compact Sensor
|
||||
("Panopticon Prime", int2(1, 6), Clockwise)); // Fire Control Array
|
||||
stage.Player(starved, float2(0, 0));
|
||||
stage.Place(stage.Bare("Longinus"), float2(0, 400));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,13 +24,13 @@ public sealed class WeaponFeel : Scenario
|
|||
("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),
|
||||
("Cell Blaster", int2(3, 8), None),
|
||||
("ChargeBlast SG", int2(3, 17), None), // Charged Scatter Blaster
|
||||
("🫳🔫", int2(5, 17), None), // Autocannon
|
||||
("FastBlast+-", int2(3, 8), None), // Cell Blaster
|
||||
("Radiator", int2(4, 2), CounterClockwise),
|
||||
("Radiator", int2(5, 2), Clockwise),
|
||||
("Compact Sensor", int2(5, 10), None),
|
||||
("Fire Control Array", int2(3, 6), Clockwise));
|
||||
("not if i see you first", int2(5, 10), None), // Compact Sensor
|
||||
("Panopticon Prime", int2(3, 6), Clockwise)); // Fire Control Array
|
||||
|
||||
stage.Player(shooter, float2(0, 0), facing: float2(0, 1));
|
||||
stage.Place(stage.Bare("Longinus"), Target);
|
||||
|
|
|
|||
Loading…
Reference in a new issue