diff --git a/Assets/Scripts/ServerShared/Entity.cs b/Assets/Scripts/ServerShared/Entity.cs index 60b84ce87..17f0af7c2 100644 --- a/Assets/Scripts/ServerShared/Entity.cs +++ b/Assets/Scripts/ServerShared/Entity.cs @@ -14,6 +14,9 @@ using int2 = CultMath.int2; public abstract class Entity { + // Every hull cell's temperature (kelvin) when the entity is built. + public const float StartingCellTemperature = 280; + public Zone Zone; public Faction Faction; public EquippableItem Hull; @@ -911,7 +914,7 @@ public abstract class Entity Armor[v.x, v.y] += Hardpoints[v.x, v.y].Armor; MaxArmor[v.x, v.y] += Hardpoints[v.x, v.y].Armor; } - Temperature[v.x, v.y] = 280; + Temperature[v.x, v.y] = StartingCellTemperature; ThermalMass[v.x, v.y] = hullData.Mass * hullData.SpecificHeat / cellCount; } GearOccupancy = new EquippedItem[hullData.Shape.Width, hullData.Shape.Height]; diff --git a/tests/Aetheria.Shared.Tests/ShippedWeaponOperabilityTests.cs b/tests/Aetheria.Shared.Tests/ShippedWeaponOperabilityTests.cs new file mode 100644 index 000000000..7a9d494a2 --- /dev/null +++ b/tests/Aetheria.Shared.Tests/ShippedWeaponOperabilityTests.cs @@ -0,0 +1,22 @@ +using System.Linq; +using Xunit; + +// A weapon is thermally dead at 0 performance: ThermalOnline is false and wear runs at full rate. Hull cells +// start at Entity.StartingCellTemperature, so every shipped weapon must perform there, or a ship fitted with it +// can never fire it. The catalog comes from the shipped GameData/Aetheria.cc and the temperature from the +// constant Entity builds its cells with, so a unit slip in authoring (celsius for kelvin) fails here. +public sealed class ShippedWeaponOperabilityTests +{ + [Fact] + public void EveryShippedWeaponPerformsAtAHullsStartingTemperature() + { + using var cache = RestoredHullsTests.OpenCatalog(); + var weapons = cache.GetAll().OfType().ToArray(); + Assert.NotEmpty(weapons); + + var dead = weapons.Where(weapon => weapon.Performance(Entity.StartingCellTemperature) <= 0f) + .Select(weapon => $"{weapon.Name} ({weapon.MinimumTemperature}..{weapon.MaximumTemperature} K)").ToArray(); + + Assert.Empty(dead); + } +} diff --git a/tools/AetherDb/Program.cs b/tools/AetherDb/Program.cs index 967449419..fec67ee99 100644 --- a/tools/AetherDb/Program.cs +++ b/tools/AetherDb/Program.cs @@ -1510,15 +1510,39 @@ public static class Program // 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 - // 50, cooldown 2, thermal limits 173.15..473.15 K read here as -100..200); BlastRadius 25, the delays 2 and 2 - // and the 30 s lifetime are the retired prefab's. Dry run unless passed "apply". + // 50, cooldown 2, thermal limits 173.15..473.15 K, kelvin like every shipped item); BlastRadius 25, the delays + // 2 and 2 and the 30 s lifetime are the retired prefab's. The optimum and plateau are the 2021 record's + // sibling weapon's (it carries 173.15..473.15 K too), because that record sets neither. An already authored + // Mine Launcher has its thermal envelope brought to these values. Dry run unless passed "apply". private static int MineLauncherCatalog(bool apply) { const string name = "Mine Launcher"; var db = AetherDb.Open(catalogWritable: apply); - if (db.Cache.GetAll().Any(gear => gear.Name == name)) + const float minimumTemperature = 173.15f, maximumTemperature = 473.15f, optimalTemperature = 249.65f, plateauWidth = 36f; + if (db.Cache.GetAll().SingleOrDefault(gear => gear.Name == name) is { } authored) { - Console.WriteLine($"\"{name}\" is already authored; nothing to do."); + if (authored is not WeaponItemData existing) throw new InvalidOperationException($"\"{name}\" exists and is not a weapon."); + if (existing.MinimumTemperature == minimumTemperature && existing.MaximumTemperature == maximumTemperature && + existing.OptimalTemperature == optimalTemperature && existing.PlateauWidth == plateauWidth) + { + Console.WriteLine($"\"{name}\" is already authored; nothing to do."); + return 0; + } + Console.WriteLine($"\"{name}\" thermal envelope {existing.MinimumTemperature}..{existing.MaximumTemperature} (optimum {existing.OptimalTemperature}, plateau {existing.PlateauWidth}) -> {minimumTemperature}..{maximumTemperature} K (optimum {optimalTemperature}, plateau {plateauWidth})."); + if (!apply) + { + Console.WriteLine(" +Dry run. Pass \"apply\" to correct it."); + return 0; + } + existing.MinimumTemperature = minimumTemperature; + existing.MaximumTemperature = maximumTemperature; + existing.OptimalTemperature = optimalTemperature; + existing.PlateauWidth = plateauWidth; + CultRecordRefs.Validate(existing); + db.Cache.Commit(batch => batch.Upsert(typeof(GearData), existing, db.Cache.RefOf(existing).Key)); + Console.WriteLine($" +Corrected \"{name}\" in Aetheria.cc"); return 0; } @@ -1536,8 +1560,10 @@ public static class Program Mass = 75f, Price = 125000, Durability = 100f, - MinimumTemperature = -100f, - MaximumTemperature = 200f, + MinimumTemperature = minimumTemperature, + MaximumTemperature = maximumTemperature, + OptimalTemperature = optimalTemperature, + PlateauWidth = plateauWidth, Behaviors = { new MineLayerData