Smoke Test Hull command, Weapon Feel and Arcs on the test hull (catalog not yet applied)
AetherDb smoke-hull [apply] adds a product-less Longinus copy with a 2x2 Energy and a 2x2 Ballistic hardpoint past the bow and a 1x1 side mount on each flank. Weapon Feel (seed 107) stages it with ChargeBlast SG, Autocannon and FastBlast+-; Arcs flies it with a mount on each flank and a Port and Starboard hull. This commit does not build a passing suite: the scenarios name the hull and GameData/Aetheria.cc does not hold it until the next commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
34ec64356e
commit
e8442328df
8 changed files with 297 additions and 12 deletions
|
|
@ -4,20 +4,26 @@ using static ItemRotation;
|
|||
public sealed class Arcs : Scenario
|
||||
{
|
||||
public override string Name => "Arcs";
|
||||
public override string Brief => "Forward mounts, a bare hull off the bow, one off the stern, a hostile turret. Verify: the hull keeps its " +
|
||||
public override string Brief => "The Smoke Test Hull with forward mounts and a side mount on each flank; a bare hull off the bow, one off " +
|
||||
"the stern, one abeam to port, one abeam to starboard, and a hostile turret. Verify: the hull keeps its " +
|
||||
"heading while you aim at the stern hull, and the forward guns hold. Designate the bow hull and fire with the " +
|
||||
"aim anywhere: the forward guns fire on it and hit. The turret tracks you all the way round.";
|
||||
"aim anywhere: the forward guns fire on it and hit. The side guns fire at their beam hull and never through " +
|
||||
"the hull. Designate the starboard hull, aim ahead and fire its group: it fires on the hull without the aim " +
|
||||
"and the port group holds; undesignate and aim to port: the port group fires free. The turret tracks you " +
|
||||
"all the way round.";
|
||||
public override uint Seed => 103;
|
||||
public override bool Ambient => false;
|
||||
|
||||
// Where the targets sit: the player is at the origin facing +z, and every mount fires forward.
|
||||
public static readonly CultMath.float2 Bow = float2(0, 400), Stern = float2(0, -400), Turret = float2(600, 0);
|
||||
// Where the targets sit: the player is at the origin facing +z. The forward mounts fire forward, the starboard mount
|
||||
// (Clockwise) along +x and the port mount (CounterClockwise) along -x.
|
||||
public static readonly CultMath.float2 Bow = float2(0, 400), Stern = float2(0, -400), Turret = float2(600, 0),
|
||||
Port = float2(-400, 0), Starboard = float2(400, 0);
|
||||
|
||||
public override Galaxy Generate(GalaxyStage stage) => stage.Prelude();
|
||||
|
||||
public override void Stage(ScenarioStage stage)
|
||||
{
|
||||
var fighter = stage.Fit("Longinus",
|
||||
var fighter = stage.Fit("Smoke Test Hull",
|
||||
("Cockpit 2x2", int2(2, 6), None),
|
||||
("Large Drive", int2(1, 0), Reversed),
|
||||
("Large Drive", int2(3, 0), Reversed),
|
||||
|
|
@ -31,7 +37,9 @@ public sealed class Arcs : Scenario
|
|||
("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));
|
||||
("Fire Control Array", int2(1, 6), Clockwise),
|
||||
("Point Defense Gun", int2(5, 4), Clockwise),
|
||||
("Point Defense Laser", int2(0, 4), CounterClockwise));
|
||||
var turret = stage.Fit("Turret",
|
||||
("ClearPath", int2(1, 2), None),
|
||||
("ClearPath", int2(5, 2), None),
|
||||
|
|
@ -46,5 +54,7 @@ public sealed class Arcs : Scenario
|
|||
stage.Place(stage.Bare("Longinus"), Bow);
|
||||
stage.Place(stage.Bare("Longinus"), Stern);
|
||||
stage.Place(turret, Turret, stance: ScenarioStance.Hostile);
|
||||
stage.Place(stage.Bare("Longinus"), Port);
|
||||
stage.Place(stage.Bare("Longinus"), Starboard);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 WeaponFeel(), new LongHaul()
|
||||
};
|
||||
}
|
||||
|
|
|
|||
38
Assets/Scripts/ServerShared/Scenarios/WeaponFeel.cs
Normal file
38
Assets/Scripts/ServerShared/Scenarios/WeaponFeel.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
using static CultMath.math;
|
||||
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 uint Seed => 107;
|
||||
public override bool Ambient => false;
|
||||
|
||||
// The one target, ahead of the nose: inside every forward mount's arc.
|
||||
public static readonly CultMath.float2 Target = float2(0, 400);
|
||||
|
||||
public override Galaxy Generate(GalaxyStage stage) => stage.Prelude(DemoTerminus.Cast);
|
||||
|
||||
public override void Stage(ScenarioStage stage)
|
||||
{
|
||||
var shooter = stage.Fit("Smoke Test Hull",
|
||||
("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),
|
||||
("ChargeBlast SG", int2(1, 17), None),
|
||||
("Autocannon", int2(3, 17), None),
|
||||
("FastBlast+-", int2(1, 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));
|
||||
|
||||
stage.Player(shooter, float2(0, 0), facing: float2(0, 1));
|
||||
stage.Place(stage.Bare("Longinus"), Target);
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/ServerShared/Scenarios/WeaponFeel.cs.meta
Normal file
11
Assets/Scripts/ServerShared/Scenarios/WeaponFeel.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 74c554ab5a6a4ac5ad0334a29135dc7f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -477,8 +477,8 @@ public sealed partial class RunStartTests
|
|||
}
|
||||
}
|
||||
|
||||
// Arcs' geometry: the bow hull sits inside every one of the player's mount arcs, the stern hull outside all of them,
|
||||
// and the turret is hostile.
|
||||
// Arcs' geometry: the bow hull sits inside every one of the player's forward mount arcs, the stern hull outside all of
|
||||
// them (the side mounts are ArcsFlanks'), the turret is hostile and the bare hulls are neutral.
|
||||
[Fact]
|
||||
public void ArcsGeometry()
|
||||
{
|
||||
|
|
@ -489,7 +489,7 @@ public sealed partial class RunStartTests
|
|||
var turret = Assert.IsType<OrbitalEntity>(staged.Entities[2]);
|
||||
Assert.Equal((Arcs.Bow, Arcs.Stern, Arcs.Turret), (bow.Position.xz, stern.Position.xz, turret.Position.xz));
|
||||
|
||||
var mounts = player.Equipment.Where(item => item.Behaviors.OfType<Weapon>().Any()).ToList();
|
||||
var mounts = player.Equipment.Where(item => item.Behaviors.OfType<Weapon>().Any() && item.Hardpoint.Rotation == ItemRotation.None).ToList();
|
||||
Assert.NotEmpty(mounts);
|
||||
foreach (var mount in mounts)
|
||||
{
|
||||
|
|
@ -497,7 +497,7 @@ public sealed partial class RunStartTests
|
|||
Assert.False(FireControl.InArc(mount, stern.Position - player.Position), $"{mount.Data.Name} bears on the stern hull");
|
||||
}
|
||||
Assert.True(player.IsHostileTo(turret) && turret.IsHostileTo(player), "the turret is hostile");
|
||||
Assert.False(player.IsHostileTo(bow) || player.IsHostileTo(stern), "the bare hulls are neutral");
|
||||
Assert.False(player.IsHostileTo(bow) || player.IsHostileTo(stern) || player.IsHostileTo(staged.Entities[3]) || player.IsHostileTo(staged.Entities[4]), "the bare hulls are neutral");
|
||||
}
|
||||
|
||||
// Q3 (operator, 2026-09-30): a design no product sells is a scenario test design and nothing else. A test design
|
||||
|
|
|
|||
83
tests/Aetheria.Shared.Tests/SmokeHullCommandTests.cs
Normal file
83
tests/Aetheria.Shared.Tests/SmokeHullCommandTests.cs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using GameCult.Caching;
|
||||
using GameCult.Caching.MessagePack;
|
||||
using Xunit;
|
||||
|
||||
// AetherDb's smoke-hull command (aetheria-release, cut scenarios-smoke), run on a scratch copy of the shipped catalog
|
||||
// with the Smoke Test Hull removed from it.
|
||||
public sealed class SmokeHullCommandTests : IDisposable
|
||||
{
|
||||
private const string HullName = "Smoke Test Hull";
|
||||
|
||||
private readonly string _root = Path.Combine(Path.GetTempPath(), "smoke-hull-command-" + Guid.NewGuid().ToString("N"));
|
||||
private string CatalogPath => Path.Combine(_root, "GameData", "Aetheria.cc");
|
||||
|
||||
public SmokeHullCommandTests()
|
||||
{
|
||||
Directory.CreateDirectory(Path.Combine(_root, "GameData"));
|
||||
Directory.CreateDirectory(Path.Combine(_root, "Assets", "Resources"));
|
||||
var shipped = AetherDb.FindRoot();
|
||||
File.Copy(Path.Combine(shipped, "GameData", "Aetheria.cc"), CatalogPath);
|
||||
File.Copy(Path.Combine(shipped, "Assets", "Resources", "Settings.asset"), Path.Combine(_root, "Assets", "Resources", "Settings.asset"));
|
||||
using var cache = new CultCache(TestCatalog.Registry());
|
||||
cache.AddBackingStore(new SingleFileMessagePackBackingStore(CatalogPath), AetheriaStores.CatalogTypes);
|
||||
foreach (var hull in cache.GetAll<HullData>().Where(h => h.Name == HullName).ToArray()) Assert.True(cache.Remove(cache.RefOf(hull).Key));
|
||||
cache.FlushAsync().Wait();
|
||||
}
|
||||
|
||||
public void Dispose() => Directory.Delete(_root, true);
|
||||
|
||||
private (HullData[] Hulls, HullData Longinus, EquippableItemData[] Fitted, int Products) Read()
|
||||
{
|
||||
var db = AetherDb.Open(root: _root);
|
||||
try
|
||||
{
|
||||
var hulls = db.Cache.GetAll<HullData>().Where(h => h.Name == HullName).ToArray();
|
||||
var keys = hulls.Select(h => db.Cache.RefOf(h).Key).ToArray();
|
||||
var products = db.Cache.GetAll<FactionProductData>().Count(p => keys.Any(k => p.Design.Key.Equals(k)));
|
||||
var fitted = new[] { "ChargeBlast SG", "Autocannon", "Point Defense Gun", "Point Defense Laser" }
|
||||
.Select(name => db.Cache.GetAll<EquippableItemData>().Single(d => d.Name == name)).ToArray();
|
||||
return (hulls, db.Cache.GetAll<HullData>().Single(h => h.Name == "Longinus"), fitted, products);
|
||||
}
|
||||
finally { db.Cache.Dispose(); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_dry_run_writes_nothing_and_apply_lands_one_productless_hull_once()
|
||||
{
|
||||
var before = File.ReadAllBytes(CatalogPath);
|
||||
Assert.Empty(Read().Hulls);
|
||||
|
||||
Assert.Equal(0, Program.SmokeHullCatalog(apply: false, root: _root));
|
||||
Assert.Equal(before, File.ReadAllBytes(CatalogPath));
|
||||
Assert.Empty(Read().Hulls);
|
||||
|
||||
Assert.Equal(0, Program.SmokeHullCatalog(apply: true, root: _root));
|
||||
var landed = File.ReadAllBytes(CatalogPath);
|
||||
Assert.NotEqual(before, landed);
|
||||
var (hulls, longinus, fitted, products) = Read();
|
||||
var hull = Assert.Single(hulls);
|
||||
Assert.Equal(0, products);
|
||||
|
||||
// The Longinus's own hardpoints survive, and four are added: each takes the gear the scenarios fit to it.
|
||||
Assert.Equal(longinus.Hardpoints.Count + 4, hull.Hardpoints.Count);
|
||||
var added = hull.Hardpoints.Skip(longinus.Hardpoints.Count).ToArray();
|
||||
var (chargeBlast, autocannon, pdGun, pdLaser) = (fitted[0], fitted[1], fitted[2], fitted[3]);
|
||||
Assert.Single(added, h => h.Type == HardpointType.Energy && h.Rotation == ItemRotation.None && h.Takes(chargeBlast));
|
||||
Assert.Single(added, h => h.Type == HardpointType.Ballistic && h.Rotation == ItemRotation.None && h.Takes(autocannon));
|
||||
Assert.Single(added, h => h.Type == HardpointType.Ballistic && h.Rotation == ItemRotation.Clockwise && h.Takes(pdGun));
|
||||
Assert.Single(added, h => h.Type == HardpointType.Energy && h.Rotation == ItemRotation.CounterClockwise && h.Takes(pdLaser));
|
||||
foreach (var hardpoint in added)
|
||||
foreach (var cell in hardpoint.Shape.Coordinates)
|
||||
Assert.True(hull.Shape[hardpoint.Position + cell], "a new hardpoint lies outside the hull's cells");
|
||||
|
||||
// A second apply finds the hull authored and writes nothing; so does a dry run.
|
||||
Assert.Equal(0, Program.SmokeHullCatalog(apply: true, root: _root));
|
||||
Assert.Equal(landed, File.ReadAllBytes(CatalogPath));
|
||||
Assert.Equal(0, Program.SmokeHullCatalog(apply: false, root: _root));
|
||||
Assert.Equal(landed, File.ReadAllBytes(CatalogPath));
|
||||
Assert.Single(Read().Hulls);
|
||||
}
|
||||
}
|
||||
80
tests/Aetheria.Shared.Tests/SmokeHullScenarioTests.cs
Normal file
80
tests/Aetheria.Shared.Tests/SmokeHullScenarioTests.cs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
// aetheria-release, cut scenarios-smoke: what the Smoke Test Hull scenarios stage, read from the staged run and the
|
||||
// shipped catalog's hull.
|
||||
public sealed partial class RunStartTests
|
||||
{
|
||||
private static bool Bears(EquippedItem mount, Entity player, Entity target) => FireControl.InArc(mount, target.Position - player.Position);
|
||||
|
||||
private static List<EquippedItem> WeaponMounts(Entity ship) =>
|
||||
ship.Equipment.Where(item => item.Behaviors.OfType<Weapon>().Any()).ToList();
|
||||
|
||||
// Weapon Feel: the player flies the Smoke Test Hull with ChargeBlast SG on the 2x2 Energy hardpoint, Autocannon
|
||||
// on the 2x2 Ballistic hardpoint and a burst weapon; the bare hull ahead lies in every mount's arc.
|
||||
[Fact]
|
||||
public void WeaponFeelConditions()
|
||||
{
|
||||
var staged = Stage(Arena(false), new WeaponFeel());
|
||||
var player = staged.Player;
|
||||
player.GenerateWeaponGroups();
|
||||
Assert.Equal("Smoke Test Hull", _items.GetData(player.EquippedHull.EquippableItem).Name);
|
||||
|
||||
EquippedItem Mount(string design) => Assert.Single(WeaponMounts(player), item => _items.GetData(item.EquippableItem).Name == design);
|
||||
var charge = Mount("ChargeBlast SG");
|
||||
var autocannon = Mount("Autocannon");
|
||||
Assert.Equal((HardpointType.Energy, 2, 2), (charge.Hardpoint.Type, charge.Hardpoint.Shape.Width, charge.Hardpoint.Shape.Height));
|
||||
Assert.Equal((HardpointType.Ballistic, 2, 2), (autocannon.Hardpoint.Type, autocannon.Hardpoint.Shape.Width, autocannon.Hardpoint.Shape.Height));
|
||||
|
||||
var mounts = WeaponMounts(player);
|
||||
Assert.Contains(mounts, item => ((WeaponItemData)_items.GetData(item.EquippableItem)).WeaponModifiers.HasFlag(WeaponModifiers.RapidFire));
|
||||
foreach (var mount in mounts)
|
||||
{
|
||||
Assert.Contains(player.WeaponGroups, group => group.items.Contains(mount));
|
||||
Assert.True(Bears(mount, player, Assert.Single(staged.Entities)), $"{_items.GetData(mount.EquippableItem).Name} cannot bear on the hull ahead");
|
||||
}
|
||||
}
|
||||
|
||||
// Arcs' flanks: each side mount bears on its own flank's hull and on nothing else, the forward mounts bear on the
|
||||
// bow and on neither flank, and the two side mounts sit in different weapon groups.
|
||||
[Fact]
|
||||
public void ArcsFlanks()
|
||||
{
|
||||
var staged = Stage(Arena(false), new Arcs());
|
||||
var player = staged.Player;
|
||||
player.GenerateWeaponGroups();
|
||||
var (bow, stern, port, starboard) = (staged.Entities[0], staged.Entities[1], staged.Entities[3], staged.Entities[4]);
|
||||
Assert.Equal((Arcs.Port, Arcs.Starboard), (port.Position.xz, starboard.Position.xz));
|
||||
|
||||
var mounts = WeaponMounts(player);
|
||||
var forward = mounts.Where(item => item.Hardpoint.Rotation == ItemRotation.None).ToList();
|
||||
var starboardMounts = mounts.Where(item => item.Hardpoint.Rotation == ItemRotation.Clockwise).ToList();
|
||||
var portMounts = mounts.Where(item => item.Hardpoint.Rotation == ItemRotation.CounterClockwise).ToList();
|
||||
Assert.NotEmpty(forward);
|
||||
Assert.NotEmpty(starboardMounts);
|
||||
Assert.NotEmpty(portMounts);
|
||||
foreach (var mount in starboardMounts)
|
||||
{
|
||||
Assert.True(Bears(mount, player, starboard), "a starboard mount cannot bear on the starboard hull");
|
||||
Assert.False(Bears(mount, player, port) || Bears(mount, player, bow) || Bears(mount, player, stern), "a starboard mount bears off its flank");
|
||||
}
|
||||
foreach (var mount in portMounts)
|
||||
{
|
||||
Assert.True(Bears(mount, player, port), "a port mount cannot bear on the port hull");
|
||||
Assert.False(Bears(mount, player, starboard) || Bears(mount, player, bow) || Bears(mount, player, stern), "a port mount bears off its flank");
|
||||
}
|
||||
foreach (var mount in forward)
|
||||
{
|
||||
Assert.True(Bears(mount, player, bow), "a forward mount cannot bear on the bow hull");
|
||||
Assert.False(Bears(mount, player, port) || Bears(mount, player, starboard), "a forward mount bears on a flank");
|
||||
}
|
||||
|
||||
int GroupOf(EquippedItem mount) => Array.FindIndex(player.WeaponGroups, group => group.items.Contains(mount));
|
||||
var starboardGroups = starboardMounts.Select(GroupOf).ToList();
|
||||
var portGroups = portMounts.Select(GroupOf).ToList();
|
||||
Assert.DoesNotContain(-1, starboardGroups.Concat(portGroups));
|
||||
Assert.Empty(starboardGroups.Intersect(portGroups));
|
||||
}
|
||||
}
|
||||
|
|
@ -42,6 +42,7 @@ public static class Program
|
|||
case "field-kinds": return FieldKindsCatalog(args.Contains("apply"));
|
||||
case "pd-gear": return PdGearCatalog(args.Contains("apply"));
|
||||
case "mine-launcher": return MineLauncherCatalog(args.Contains("apply"));
|
||||
case "smoke-hull": return SmokeHullCatalog(args.Contains("apply"));
|
||||
case "roles-backfill": return RolesBackfill(args.Contains("apply"));
|
||||
case "consumables-first-set": return ConsumablesFirstSetCatalog(args.Contains("apply"));
|
||||
case "pirates-faction":
|
||||
|
|
@ -49,7 +50,7 @@ public static class Program
|
|||
return PiratesFaction(args.Contains("apply"));
|
||||
case "verse-grammar": return VerseGrammarCommand.Run(args.Contains("apply"));
|
||||
default:
|
||||
Console.WriteLine("commands: census, factions, station-fit, hardpoint-fit, loadout [seed], save, settings, settings-dump, dangling [clear <Type.Member>]... [apply], shield-migrate [apply], brownout-migrate [apply], roles-migrate [apply], firing-arc-migrate [apply], targeting-catalog [apply], targeting-catalog-6c [apply], targeting-catalog-6d [apply], ship-authoring create|inspect|validate|compose, field-kinds [apply], mine-launcher [apply], roles-backfill [apply], pd-gear [apply], consumables-first-set [apply], pirates-faction [apply], verse-grammar [apply]");
|
||||
Console.WriteLine("commands: census, factions, station-fit, hardpoint-fit, loadout [seed], save, settings, settings-dump, dangling [clear <Type.Member>]... [apply], shield-migrate [apply], brownout-migrate [apply], roles-migrate [apply], firing-arc-migrate [apply], targeting-catalog [apply], targeting-catalog-6c [apply], targeting-catalog-6d [apply], ship-authoring create|inspect|validate|compose, field-kinds [apply], mine-launcher [apply], smoke-hull [apply], roles-backfill [apply], pd-gear [apply], consumables-first-set [apply], pirates-faction [apply], verse-grammar [apply]");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -1601,6 +1602,68 @@ public static class Program
|
|||
return 0;
|
||||
}
|
||||
|
||||
// aetheria-release cut scenarios-smoke (rulings weapon-feel-to-smoke-cut-test-hull, test-hull-both-flanks): the
|
||||
// Smoke Test Hull, a copy of the Longinus hull that no product sells, so no generated ship, station or shop offers
|
||||
// it and only a scenario fits it. It adds what no shipped ship hull has, where the Longinus runs out of cells: two
|
||||
// rows of cells past the bow carrying a 2x2 Energy hardpoint (ChargeBlast SG) and a 2x2 Ballistic hardpoint
|
||||
// (Autocannon), and a 1x1 side mount on each flank, a Clockwise Ballistic one on the starboard and a
|
||||
// CounterClockwise Energy one on the port. The Longinus's own hardpoints are untouched. Its prefab is the
|
||||
// Longinus's, which has no barrel for the new hardpoints: they fire from the hull centre. Dry run unless passed
|
||||
// "apply".
|
||||
public static int SmokeHullCatalog(bool apply, string root = null)
|
||||
{
|
||||
const string name = "Smoke Test Hull";
|
||||
var db = AetherDb.Open(catalogWritable: apply, root: root);
|
||||
if (db.Cache.GetAll<HullData>().Any(hull => hull.Name == name))
|
||||
{
|
||||
Console.WriteLine($"\"{name}\" is already authored; nothing to do.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
var longinus = db.Cache.GetAll<HullData>().Single(hull => hull.Name == "Longinus");
|
||||
var options = CultDocumentMessagePackSerialization.OptionsFor(typeof(HullData).Assembly);
|
||||
var hull = MessagePackSerializer.Deserialize<HullData>(MessagePackSerializer.Serialize(longinus, options), options);
|
||||
hull.Name = name;
|
||||
hull.Description = "The Longinus with two forward 2x2 hardpoints and a side mount on each flank, for scenarios. No faction builds it.";
|
||||
|
||||
// Two rows of cells past the bow, over the width of the tail's neighbours (x 1..4).
|
||||
var grown = new Shape(longinus.Shape.Width, longinus.Shape.Height + 2);
|
||||
for (var x = 0; x < longinus.Shape.Width; x++)
|
||||
for (var y = 0; y < longinus.Shape.Height; y++) grown.Cells[x, y] = longinus.Shape.Cells[x, y];
|
||||
for (var x = 1; x <= 4; x++)
|
||||
for (var y = longinus.Shape.Height; y < grown.Height; y++) grown.Cells[x, y] = true;
|
||||
hull.Shape = grown;
|
||||
|
||||
static Shape Cells(int width, int height)
|
||||
{
|
||||
var shape = new Shape(width, height);
|
||||
for (var x = 0; x < width; x++)
|
||||
for (var y = 0; y < height; y++) shape.Cells[x, y] = true;
|
||||
return shape;
|
||||
}
|
||||
var bow = longinus.Shape.Height;
|
||||
hull.Hardpoints.Add(new HardpointData { Type = HardpointType.Energy, Position = CultMath.math.int2(1, bow), Shape = Cells(2, 2), Rotation = ItemRotation.None, Armor = 20f });
|
||||
hull.Hardpoints.Add(new HardpointData { Type = HardpointType.Ballistic, Position = CultMath.math.int2(3, bow), Shape = Cells(2, 2), Rotation = ItemRotation.None, Armor = 20f });
|
||||
hull.Hardpoints.Add(new HardpointData { Type = HardpointType.Ballistic, Position = CultMath.math.int2(longinus.Shape.Width - 1, 4), Shape = Cells(1, 1), Rotation = ItemRotation.Clockwise, Armor = 10f });
|
||||
hull.Hardpoints.Add(new HardpointData { Type = HardpointType.Energy, Position = CultMath.math.int2(0, 4), Shape = Cells(1, 1), Rotation = ItemRotation.CounterClockwise, Armor = 10f });
|
||||
|
||||
Console.WriteLine($"Adds \"{name}\": a Longinus copy grown from {longinus.Shape.Width}x{longinus.Shape.Height} to {grown.Width}x{grown.Height}, no product.");
|
||||
foreach (var hardpoint in hull.Hardpoints.Skip(longinus.Hardpoints.Count))
|
||||
Console.WriteLine($" {hardpoint.Type,-9} {hardpoint.Shape.Width}x{hardpoint.Shape.Height} at {hardpoint.Position.x},{hardpoint.Position.y} rotation {hardpoint.Rotation}");
|
||||
if (!apply)
|
||||
{
|
||||
Console.WriteLine("
|
||||
Dry run. Pass \"apply\" to author it.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
CultRecordRefs.Validate(hull);
|
||||
db.Cache.Commit(batch => batch.Upsert(typeof(HullData), hull));
|
||||
Console.WriteLine($"
|
||||
Authored \"{name}\" in Aetheria.cc");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// sim-mines-presenter: the Mine Launcher item, so a ship can be fitted with the sim's mine layer. Its numbers
|
||||
// are the 2021 record's (GameData/Legacy/AetherDB.2021-03-05.msgpack, recovered in 8c71a637: mass 75, price
|
||||
// 125000, damage 50, range 2000, energy 15, heat 500, visibility 10, magazine 6, reload 5, spread 5, velocity
|
||||
|
|
|
|||
Loading…
Reference in a new issue