From 3236ccb05fb3f6fb4d409d673ec3aef4fd6cb327 Mon Sep 17 00:00:00 2001 From: Greg Burri Date: Sun, 14 Sep 2025 17:19:16 +0200 Subject: [PATCH] New project to manage La Mimine frigate --- Mimine/AmmoManager.cs | 127 ++++++++++++++++++ Mimine/Components.cs | 37 +++++ Mimine/Constants.cs | 29 ++++ Mimine/Ingots.cs | 144 ++++++++++++++++++++ Mimine/Mimine.csproj | 34 +++++ Mimine/Mimine.mdk.ini | 22 +++ Mimine/Mimine.mdk.local.ini | 7 + Mimine/MissileManager.cs | 209 +++++++++++++++++++++++++++++ Mimine/Program.cs | 260 ++++++++++++++++++++++++++++++++++++ Mimine/packages.config | 4 + 10 files changed, 873 insertions(+) create mode 100644 Mimine/AmmoManager.cs create mode 100644 Mimine/Components.cs create mode 100644 Mimine/Constants.cs create mode 100644 Mimine/Ingots.cs create mode 100644 Mimine/Mimine.csproj create mode 100644 Mimine/Mimine.mdk.ini create mode 100644 Mimine/Mimine.mdk.local.ini create mode 100644 Mimine/MissileManager.cs create mode 100644 Mimine/Program.cs create mode 100644 Mimine/packages.config diff --git a/Mimine/AmmoManager.cs b/Mimine/AmmoManager.cs new file mode 100644 index 0000000..792b5a8 --- /dev/null +++ b/Mimine/AmmoManager.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Sandbox.ModAPI.Ingame; + +using VRage; +using VRage.Game; +using VRage.Game.ModAPI.Ingame; + +namespace IngameScript +{ + internal class AmmoManager + { + readonly IMyGridTerminalSystem gridTerminal; + readonly IMyCubeGrid grid; + readonly Output output; + readonly IList assemblers; + + public AmmoManager(IMyGridTerminalSystem gridTerminal, IMyCubeGrid grid, Output output, IList assemblers) + { + this.gridTerminal = gridTerminal; + this.grid = grid; + this.output = output; + this.assemblers = assemblers; + } + + public class AmmoStatus + { + public MyFixedPoint NbOfCannonShell { get; set; } + public MyFixedPoint NbOfArtilleryShell { get; set; } + public MyFixedPoint NbOfGatlingAmmo { get; set; } + + public AmmoStatus Copy() + { + return this.MemberwiseClone() as AmmoStatus; + } + } + + public void BuildAmmo(AmmoStatus status) + { + if (this.assemblers.Count == 0) + return; + + var mainAssembler = this.assemblers[0]; + + var statusWithAssemblerQueues = status.Copy(); + + // Add the current Queue. + foreach (var assembler in this.gridTerminal.GetBlocks(grid: this.grid)) + { + var queue = new List(); + assembler.GetQueue(queue); + + foreach (var item in queue) + { + if (item.BlueprintId == Constants.CANNON_SHELL_BLUEPRINT_ID) + statusWithAssemblerQueues.NbOfCannonShell += item.Amount; + else if (item.BlueprintId == Constants.ARTILLERY_SHELL_BLUEPRINT_ID) + statusWithAssemblerQueues.NbOfArtilleryShell += item.Amount; + else if (item.BlueprintId == Constants.GATLING_AMMO_BLUEPRINT_ID) + statusWithAssemblerQueues.NbOfGatlingAmmo += item.Amount; + } + } + + if (statusWithAssemblerQueues.NbOfCannonShell < Constants.NUMBER_OF_CANNON_SHELL) + { + var toBuild = Constants.NUMBER_OF_CANNON_SHELL - statusWithAssemblerQueues.NbOfCannonShell; + this.output.Print($"Requesting {toBuild} of {Constants.CANNON_SHELL_BLUEPRINT_ID.SubtypeName}"); + mainAssembler.AddQueueItem(Constants.CANNON_SHELL_BLUEPRINT_ID, toBuild); + } + + if (statusWithAssemblerQueues.NbOfArtilleryShell < Constants.NUMBER_OF_ARTILLERY_SHELL) + { + var toBuild = Constants.NUMBER_OF_ARTILLERY_SHELL - statusWithAssemblerQueues.NbOfArtilleryShell; + this.output.Print($"Requesting {toBuild} of {Constants.ARTILLERY_SHELL_BLUEPRINT_ID.SubtypeName}"); + mainAssembler.AddQueueItem(Constants.ARTILLERY_SHELL_BLUEPRINT_ID, toBuild); + } + + if (statusWithAssemblerQueues.NbOfGatlingAmmo < Constants.NUMBER_OF_GATLING_AMMO) + { + var toBuild = Constants.NUMBER_OF_GATLING_AMMO - statusWithAssemblerQueues.NbOfGatlingAmmo; + this.output.Print($"Requesting {toBuild} of {Constants.GATLING_AMMO_BLUEPRINT_ID.SubtypeName}"); + mainAssembler.AddQueueItem(Constants.GATLING_AMMO_BLUEPRINT_ID, toBuild); + } + } + + public void DisplayAmmoStatus(StringBuilder sb, AmmoStatus status) + { + var assaultConnonShell = "Assault Cannon Shell:"; + sb.Append(assaultConnonShell); + sb.AppendLine($"{status.NbOfCannonShell}".PadLeft(Constants.CONSOLE_LINE_LENGTH - assaultConnonShell.Length)); + + var artilleryShell = "Artillery Shell:"; + sb.Append(artilleryShell); + sb.AppendLine($"{status.NbOfArtilleryShell}".PadLeft(Constants.CONSOLE_LINE_LENGTH - artilleryShell.Length)); + + var gatlingAmmo = "Gatling Ammo Box"; + sb.Append(gatlingAmmo); + sb.AppendLine($"{status.NbOfGatlingAmmo}".PadLeft(Constants.CONSOLE_LINE_LENGTH - gatlingAmmo.Length)); + } + + public AmmoStatus GetAmmoStatus() + { + var status = new AmmoStatus(); + + foreach (var inventory in this.gridTerminal.GetAllInventories(grid)) + { + var items = new List(); + inventory.GetItems(items); + foreach (var item in items) + { + if (item.Type.SubtypeId == "MediumCalibreAmmo") + status.NbOfCannonShell += item.Amount; + else if (item.Type.SubtypeId == "LargeCalibreAmmo") + status.NbOfArtilleryShell += item.Amount; + else if (item.Type.SubtypeId == "NATO_25x184mm") + status.NbOfGatlingAmmo += item.Amount; + } + } + + return status; + } + } +} diff --git a/Mimine/Components.cs b/Mimine/Components.cs new file mode 100644 index 0000000..65a29e4 --- /dev/null +++ b/Mimine/Components.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using VRage; +using VRage.Game; +using VRage.Game.ModAPI.Ingame; + +namespace IngameScript +{ + class ComponentType + { + public readonly MyItemType ItemType; + public readonly MyDefinitionId BlueprintId; + + public ComponentType(MyItemType itemType, MyDefinitionId blueprintId) + { + this.ItemType = itemType; + this.BlueprintId = blueprintId; + } + } + + class ComponentQuantity + { + public readonly ComponentType ComponentType; + public readonly MyFixedPoint Quantity; + + public ComponentQuantity(MyItemType itemType, MyDefinitionId blueprintId, MyFixedPoint quantity) + { + + this.ComponentType = new ComponentType(itemType, blueprintId); + this.Quantity = quantity; + } + } +} diff --git a/Mimine/Constants.cs b/Mimine/Constants.cs new file mode 100644 index 0000000..3848d71 --- /dev/null +++ b/Mimine/Constants.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using VRage.Game; + +namespace IngameScript +{ + internal static class Constants + { + public const int NUMBER_OF_MISSILES = 3; // The quantity of components will be created to build this quantity of missiles. + + public const int NUMBER_OF_CANNON_SHELL = 200; + public const int NUMBER_OF_ARTILLERY_SHELL = 50; + public const int NUMBER_OF_GATLING_AMMO = 500; + + public const int CONSOLE_NB_LINES = 14; + public const int CONSOLE_LINE_LENGTH = 50; + public const string GRID_PREFIX = "[Mimine]"; + + public static readonly MyDefinitionId CANNON_SHELL_BLUEPRINT_ID = MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/Position0110_MediumCalibreAmmo"); + public static readonly MyDefinitionId ARTILLERY_SHELL_BLUEPRINT_ID = MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/Position0120_LargeCalibreAmmo"); + public static readonly MyDefinitionId GATLING_AMMO_BLUEPRINT_ID = MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/Position0080_NATO_25x184mmMagazine"); + + public const float EPSILON = 0.05f; + } +} diff --git a/Mimine/Ingots.cs b/Mimine/Ingots.cs new file mode 100644 index 0000000..879d505 --- /dev/null +++ b/Mimine/Ingots.cs @@ -0,0 +1,144 @@ +using System; +using System.CodeDom; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +using Sandbox.ModAPI.Ingame; + +using VRage; +using VRage.Game; +using VRage.Game.ModAPI.Ingame; + +namespace IngameScript +{ + enum IngotsEnum + { + Iron = 0, + Nickel = 1, + Cobalt = 2, + Magnesium = 3, + Silicon = 4, + Silver = 5, + Gold = 6, + Platinum = 7, + Uranium = 8, + Gravel = 9, // Named 'Stone' in the API. + } + + class IngotException : Exception + { + public IngotException(string mess) : + base(mess) + { + } + } + + class Ingot + { + public static MyItemType FromEnum(IngotsEnum ingot) + { + var name = ""; + switch (ingot) + { + case IngotsEnum.Iron: name = "Iron"; break; + case IngotsEnum.Nickel: name = "Nickel"; break; + case IngotsEnum.Cobalt: name = "Cobalt"; break; + case IngotsEnum.Magnesium: name = "Magnesium"; break; + case IngotsEnum.Silicon: name = "Silicon"; break; + case IngotsEnum.Silver: name = "Silver"; break; + case IngotsEnum.Gold: name = "Gold"; break; + case IngotsEnum.Platinum: name = "Platinum"; break; + case IngotsEnum.Uranium: name = "Uranium"; break; + case IngotsEnum.Gravel: name = "Stone"; break; + } + return MyItemType.MakeIngot(name); + } + + public static IngotsEnum FromSubTypeId(string id) + { + IngotsEnum enumValue = IngotsEnum.Iron; + switch (id) + { + case "Iron": enumValue = IngotsEnum.Iron; break; + case "Nickel": enumValue = IngotsEnum.Nickel; break; + case "Cobalt": enumValue = IngotsEnum.Cobalt; break; + case "Magnesium": enumValue = IngotsEnum.Magnesium; break; + case "Silicon": enumValue = IngotsEnum.Silicon; break; + case "Silver": enumValue = IngotsEnum.Silver; break; + case "Gold": enumValue = IngotsEnum.Gold; break; + case "Platinum": enumValue = IngotsEnum.Platinum; break; + case "Uranium": enumValue = IngotsEnum.Uranium; break; + case "Stone": enumValue = IngotsEnum.Gravel; break; + default: throw new IngotException($"Unknown ingot id: {id}"); + } + return enumValue; + } + } + + internal class Ingots + { + readonly float[] ingots = new float[10]; + + public Ingots(float iron = 0, float nickel = 0, float cobalt = 0, float magnesium = 0, float silicon = 0, float silver = 0, float gold = 0, float platinum = 0, float uranium = 0, float gravel = 0) + { + ingots[(int)IngotsEnum.Iron] = iron; + ingots[(int)IngotsEnum.Nickel] = nickel; + ingots[(int)IngotsEnum.Cobalt] = cobalt; + ingots[(int)IngotsEnum.Magnesium] = magnesium; + ingots[(int)IngotsEnum.Silicon] = silicon; + ingots[(int)IngotsEnum.Silver] = silver; + ingots[(int)IngotsEnum.Gold] = gold; + ingots[(int)IngotsEnum.Platinum] = platinum; + ingots[(int)IngotsEnum.Uranium] = uranium; + ingots[(int)IngotsEnum.Gravel] = gravel; + } + + public float this[IngotsEnum ingot] + { + get { return ingots[(int)ingot]; } + set { ingots[(int)ingot] = value; } + } + + public static Ingots operator +(Ingots a, Ingots b) + { + var result = new Ingots(); + for (int i = 0; i < result.ingots.Length; i++) + result.ingots[i] = a.ingots[i] + b.ingots[i]; + return result; + } + + public override string ToString() + { + var sb = new StringBuilder(); + foreach (var ingot in Enum.GetValues(typeof(IngotsEnum)).Cast()) + { + sb.AppendLine($"{ingot}: {this[ingot]}"); + } + + return sb.ToString(); + } + } + + static class IngotsUtils + { + public static Ingots IngotsFromGridContainers(this IMyGridTerminalSystem gridTerminal, IMyCubeGrid grid) + { + var ingots = new Ingots(); + + foreach (var inventory in gridTerminal.GetAllInventories(grid)) + { + var items = new List(); + inventory.GetItems(items); + + foreach (var item in items) + if (item.Type.TypeId == "MyObjectBuilder_Ingot") + ingots[Ingot.FromSubTypeId(item.Type.SubtypeId)] += (float)item.Amount; + } + + return ingots; + } + } +} diff --git a/Mimine/Mimine.csproj b/Mimine/Mimine.csproj new file mode 100644 index 0000000..7718844 --- /dev/null +++ b/Mimine/Mimine.csproj @@ -0,0 +1,34 @@ + + + + netframework48 + IngameScript + 6 + false + Release;Debug + x64 + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + diff --git a/Mimine/Mimine.mdk.ini b/Mimine/Mimine.mdk.ini new file mode 100644 index 0000000..5add8f4 --- /dev/null +++ b/Mimine/Mimine.mdk.ini @@ -0,0 +1,22 @@ +; This file is project specific and should be checked in to source control. + +[mdk] +; This is a programmable block script project. +; You should not change this. +type=programmableblock + +; Toggle trace (on|off) (verbose output) +trace=off + +; What type of minification to use (none|trim|stripcomments|lite|full) +; none: No minification +; trim: Removes unused types (NOT members). +; stripcomments: trim + removes comments. +; lite: stripcomments + removes leading/trailing whitespace. +; full: lite + renames identifiers to shorter names. +minify=none + +; A list of files and folder to ignore when creating the script. +; This is a comma separated list of glob patterns. +; See https://code.visualstudio.com/docs/editor/glob-patterns +ignores=obj/**/*,MDK/**/*,**/*.debug.cs diff --git a/Mimine/Mimine.mdk.local.ini b/Mimine/Mimine.mdk.local.ini new file mode 100644 index 0000000..4b66820 --- /dev/null +++ b/Mimine/Mimine.mdk.local.ini @@ -0,0 +1,7 @@ +; This file is _local_ to your machine and should not be checked in to source control. + +[mdk] +; Where to output the script to (auto|specific path) +output=auto +; Override the default binary path (auto|specific path) +binarypath=auto \ No newline at end of file diff --git a/Mimine/MissileManager.cs b/Mimine/MissileManager.cs new file mode 100644 index 0000000..c4d7079 --- /dev/null +++ b/Mimine/MissileManager.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Sandbox.ModAPI.Ingame; + +using VRage; +using VRage.Game; +using VRage.Game.ModAPI.Ingame; + +namespace IngameScript +{ + internal class MissileManager + { + readonly IMyGridTerminalSystem gridTerminal; + readonly IMyCubeGrid grid; + readonly Output output; + readonly IList assemblers; + + static readonly List MISSILE_COMPONENTS = new List{ + new ComponentQuantity(MyItemType.MakeComponent("SteelPlate"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/SteelPlate"), 254), + new ComponentQuantity(MyItemType.MakeComponent("Construction"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/ConstructionComponent"), 172), + new ComponentQuantity(MyItemType.MakeComponent("Motor"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/MotorComponent"), 18), + new ComponentQuantity(MyItemType.MakeComponent("InteriorPlate"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/InteriorPlate"), 43), + new ComponentQuantity(MyItemType.MakeComponent("Computer"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/ComputerComponent"), 53), + new ComponentQuantity(MyItemType.MakeComponent("PowerCell"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/PowerCell"), 2), + new ComponentQuantity(MyItemType.MakeComponent("Display"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/Display"), 1), + new ComponentQuantity(MyItemType.MakeComponent("SmallTube"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/SmallTube"), 27), + new ComponentQuantity(MyItemType.MakeComponent("MetalGrid"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/MetalGrid"), 48), + new ComponentQuantity(MyItemType.MakeComponent("LargeTube"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/LargeTube"), 16), + new ComponentQuantity(MyItemType.MakeComponent("Detector"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/DetectorComponent"), 14), + new ComponentQuantity(MyItemType.MakeComponent("RadioCommunication"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/RadioCommunicationComponent"), 22), + new ComponentQuantity(MyItemType.MakeComponent("Girder"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/GirderComponent"), 11), + new ComponentQuantity(MyItemType.MakeComponent("Explosives"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/ExplosivesComponent"), 22), + }; + + // In kg. + static readonly Dictionary INGOTS_PER_COMPONENT = new Dictionary + { + ["SteelPlate"] = new Ingots(iron: 21), + ["Construction"] = new Ingots(iron: 8), + ["Motor"] = new Ingots(iron: 20, nickel: 5), + ["InteriorPlate"] = new Ingots(iron: 3), + ["Computer"] = new Ingots(iron: 0.5f, silicon: 0.2f), + ["PowerCell"] = new Ingots(iron: 10, nickel: 2, silicon: 1), + ["Display"] = new Ingots(iron: 1, silicon: 5), + ["SmallTube"] = new Ingots(iron: 5), + ["MetalGrid"] = new Ingots(iron: 12, nickel: 5, cobalt: 3), + ["LargeTube"] = new Ingots(iron: 30), + ["Detector"] = new Ingots(iron: 5, nickel: 15), + ["RadioCommunication"] = new Ingots(iron: 8, silicon: 1), + ["Girder"] = new Ingots(iron: 6), + ["Explosives"] = new Ingots(silicon: 0.5f, magnesium: 2), + }; + + static readonly Ingots INGOTS_PER_MISSILE; + + static MissileManager() + { + INGOTS_PER_MISSILE = new Ingots(); + foreach (var component in MISSILE_COMPONENTS) + { + INGOTS_PER_MISSILE += INGOTS_PER_COMPONENT[component.ComponentType.ItemType.SubtypeId]; + } + } + + public MissileManager(IMyGridTerminalSystem gridTerminal, IMyCubeGrid grid, Output output, IList assemblers) + { + this.gridTerminal = gridTerminal; + this.grid = grid; + this.output = output; + this.assemblers = assemblers; + } + + public void BuildComponentForMissiles() + { + if (this.assemblers.Count == 0) + return; + + var existingAmounts = this.GetCurrentItemQuantity(MISSILE_COMPONENTS.Select(quantity => quantity.ComponentType)); + + int i = 0; + foreach (var missileComponent in MISSILE_COMPONENTS) + { + var existing = existingAmounts[i]; + //this.output.Print($"{missileComponent.ItemType}: {existing}"); + + var desired = missileComponent.Quantity * Constants.NUMBER_OF_MISSILES; + + if (existing < desired) + { + var toBuild = desired - existing; + this.output.Print($"Requesting {toBuild} of {missileComponent.ComponentType.BlueprintId.SubtypeName}"); + this.assemblers[0].AddQueueItem(missileComponent.ComponentType.BlueprintId, toBuild); + } + i += 1; + } + } + + struct IngotsMissileStatus + { + public string Name { get; set; } + + public float Amount { get; set; } + + public float NbOfMissiles { get; set; } + } + + struct IngotsStatus + { + public string Name { get; set; } + + public float Amount { get; set; } + } + + /// + /// Display how many missile can be build with the current ingot stock. + /// + /// + public void DisplayIngotsStatus(StringBuilder sb) + { + var ingots = this.gridTerminal.IngotsFromGridContainers(this.grid); + + var ingotsMissileStatus = new List(); + var ingotsStatusRest = new List(); // For ingots not needed by missiles. + + foreach (var ingot in Enum.GetValues(typeof(IngotsEnum)).Cast()) + { + var amount = ingots[ingot]; + var amountNeeded = INGOTS_PER_MISSILE[ingot]; + + if (amountNeeded != 0f) + ingotsMissileStatus.Add(new IngotsMissileStatus { Name = ingot.ToString(), Amount = amount, NbOfMissiles = amount / amountNeeded }); + else + ingotsStatusRest.Add(new IngotsStatus { Name = ingot.ToString(), Amount = amount }); + } + + ingotsMissileStatus.Sort((a, b) => b.NbOfMissiles.CompareTo(a.NbOfMissiles)); + + sb.AppendLine(); + sb.AppendLine($"Nb of missiles buildable: {(int)ingotsMissileStatus.Last().NbOfMissiles}"); + sb.AppendLine(new string('-', Constants.CONSOLE_LINE_LENGTH)); + + foreach (var ingotMissileStatus in ingotsMissileStatus) + { + var name = $"{ingotMissileStatus.Name} ({(int)ingotMissileStatus.NbOfMissiles}):"; + sb.Append(name); + var value = $"{ingotMissileStatus.Amount:N2} kg"; + sb.AppendLine(value.PadLeft(Constants.CONSOLE_LINE_LENGTH - name.Length)); + } + + sb.AppendLine(new string('-', Constants.CONSOLE_LINE_LENGTH)); + + foreach (var ingotStatus in ingotsStatusRest) + { + var name = $"{ingotStatus.Name}:"; + sb.Append(name); + var value = $"{ingotStatus.Amount:N2} kg"; + sb.AppendLine(value.PadLeft(Constants.CONSOLE_LINE_LENGTH - name.Length)); + } + } + + + /// + /// It counts elements in queue in assemblers too. + /// + /// + /// + List GetCurrentItemQuantity(IEnumerable componentTypes) + { + var total = new List(new MyFixedPoint[componentTypes.Count()]); + + foreach (var inventory in this.gridTerminal.GetAllInventories(grid)) + { + int i = 0; + foreach (var componentType in componentTypes) + { + var items = inventory.FindItem(componentType.ItemType); + if (items.HasValue) + total[i] += items.Value.Amount; + i += 1; + } + } + + // Special case for assemblers: check the current queue. + foreach (var assembler in this.gridTerminal.GetBlocks(grid: this.grid)) + { + var queue = new List(); + assembler.GetQueue(queue); + + int i = 0; + foreach (var componentType in componentTypes) + { + foreach (var item in queue) + { + if (item.BlueprintId == componentType.BlueprintId) + total[i] += item.Amount; + } + + i += 1; + } + } + + return total; + } + } +} diff --git a/Mimine/Program.cs b/Mimine/Program.cs new file mode 100644 index 0000000..9a8955e --- /dev/null +++ b/Mimine/Program.cs @@ -0,0 +1,260 @@ +using Sandbox.Game; +using Sandbox.Game.EntityComponents; +using Sandbox.ModAPI.Ingame; +using Sandbox.ModAPI.Interfaces; + +using SpaceEngineers.Game.ModAPI.Ingame; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.ComponentModel; +using System.Linq; +using System.Text; + +using VRage; +using VRage.Collections; +using VRage.Game; +using VRage.Game.Components; +using VRage.Game.GUI.TextPanel; +using VRage.Game.ModAPI.Ingame; +using VRage.Game.ModAPI.Ingame.Utilities; +using VRage.Game.ObjectBuilders.Definitions; +using VRage.Game.VisualScripting; +using VRage.GameServices; +using VRage.ObjectBuilders; + +using VRageMath; + +namespace IngameScript +{ + partial class Program : MyGridProgram + { + enum State + { + IDLE, + BUILD_COMPONENTS_MISSILES, + } + + State currentState = State.IDLE; + + readonly IMyCubeGrid grid; + + readonly Output output; + + readonly Output leftPanel; + readonly Output rightPanel; + + readonly List assemblers = new List(); // First one is the main assembler. + + readonly MissileManager missileManager; + readonly AmmoManager ammoManager; + + public Program() + { + this.grid = this.Me.CubeGrid; + + var cockpitPilot = this.GridTerminalSystem.GetBlock("[Mimine] Control Seat Main"); + var cockpitCopilot = this.GridTerminalSystem.GetBlock("[Mimine] Control Seat Copilot"); + + var output = this.Me.GetSurface(0); + this.output = new Output(output, Constants.CONSOLE_NB_LINES); + + this.output.Print("Mimine system starting..."); + + this.leftPanel = new Output(new List { this.GridTerminalSystem.GetBlock("[Mimine] LCD Panel 01", this.grid), cockpitCopilot.GetSurface(1), cockpitPilot.GetSurface(1) }); + this.rightPanel = new Output(new List { this.GridTerminalSystem.GetBlock("[Mimine] LCD Panel 02", this.grid), cockpitCopilot.GetSurface(0), cockpitPilot.GetSurface(0) }); + + foreach (var assembler in this.GridTerminalSystem.GetBlocks(grid: this.grid)) + { + if (assembler.BlockDefinition.SubtypeName == "LargeAssembler") + { + if (assembler.CooperativeMode) + this.assemblers.Add(assembler); + else + this.assemblers.Insert(0, assembler); + } + else + { + // Not very useful to inform about ignored assemblers -> commented out. + //this.output.Print($"Assembler Ignored, not a large assembler:"); + //this.output.Print($" {assembler.CustomName}"); + } + } + + if (this.assemblers.Count == 0) + this.output.PrintError("No assembler found"); + else + this.output.Print($"Main assembler: {this.assemblers[0].CustomName}"); + + this.missileManager = new MissileManager(this.GridTerminalSystem, this.grid, this.output, this.assemblers); + this.ammoManager = new AmmoManager(this.GridTerminalSystem, this.grid, this.output, this.assemblers); + + Runtime.UpdateFrequency = UpdateFrequency.Update100; + } + + public void Save() + { + } + + public void Main(string argument, UpdateType updateSource) + { + if ((updateSource & UpdateType.Update100) != 0) + { + this.UpdateState(); + } + else if ((updateSource & (UpdateType.Terminal | UpdateType.Trigger)) != 0) + { + switch (argument) + { + case "BUILD": + this.currentState = State.BUILD_COMPONENTS_MISSILES; + break; + + case "STOP": + this.currentState = State.IDLE; + break; + + default: + this.output.Print($"Uknown command: {argument}"); + break; + } + } + } + + void UpdateState() + { + var ammoStatus = this.ammoManager.GetAmmoStatus(); + + if (this.currentState == State.BUILD_COMPONENTS_MISSILES) + { + this.missileManager.BuildComponentForMissiles(); + this.ammoManager.BuildAmmo(ammoStatus); + } + + this.DisplayStatus(); + + StringBuilder sb = new StringBuilder(); + this.missileManager.DisplayIngotsStatus(sb); + sb.AppendLine(new string('-', Constants.CONSOLE_LINE_LENGTH)); + this.ammoManager.DisplayAmmoStatus(sb, ammoStatus); + this.rightPanel.Display(sb.ToString()); + } + + void DisplayStatus() + { + var cargoSpace = this.GetCargoSpace(); + var energyState = this.GetEnergyState(); + var hydrogenVolume = this.GetHydrogenVolume(); + var oxygenVolume = this.GetOxygenVolume(); + + this.leftPanel.Display( + $@" +{cargoSpace.ToString("Cargo volume", "kL")} + +{energyState.ToString("Energy", "MWh")} + +{hydrogenVolume.ToString("Hydrogen", "L")} + +{oxygenVolume.ToString("Oxygen", "L")}" + ); + } + + struct Quantity + { + public Quantity(double current, double total) + { + this.Current = current; + this.Total = total; + } + + public readonly double Current; + public readonly double Total; + + public string ToString(string name, string unit) + { + var firstLine = new StringBuilder(); + firstLine.Append(name).Append(": "); + var values = $"{this.Current:N1} {unit} / {this.Total:N1} {unit}"; + firstLine.Append(values.PadLeft(Constants.CONSOLE_LINE_LENGTH - firstLine.Length)); + + var secondLine = new StringBuilder(); + secondLine.Append("["); + var ratio = this.Current / this.Total; + var percentStr = $"{ratio * 100.0,8:N1}%"; + // "-2" because of the starting and ending characters: '[', ']'. + var gaugeSize = Constants.CONSOLE_LINE_LENGTH - percentStr.Length - 2; + var n = (int)((double)gaugeSize * ratio); + secondLine.Append(new string('|', n)); + secondLine.Append(new string(' ', gaugeSize - n)).Append("]"); + secondLine.Append(percentStr); + + return $"{firstLine}\n{secondLine}"; + } + } + + Quantity GetCargoSpace() + { + var containers = this.GridTerminalSystem.GetBlocks(grid: this.grid); + + double currentVolume = 0; + double totalVolume = 0; + + foreach (var container in containers) + { + var inventory = container.GetInventory(); + currentVolume += (double)inventory.CurrentVolume; + totalVolume += (double)inventory.MaxVolume; + } + + return new Quantity(currentVolume, totalVolume); + } + + Quantity GetEnergyState() + { + double currentkWh = 0; + double maxkWh = 0; + var batteries = this.GridTerminalSystem.GetBlocks(grid: this.grid); + + foreach (var battery in batteries) + { + currentkWh += (double)battery.CurrentStoredPower; + maxkWh += (double)battery.MaxStoredPower; + } + + return new Quantity(currentkWh, maxkWh); + } + + Quantity GetHydrogenVolume() + { + double currentVolume = 0; + double totalVolume = 0; + var tanks = this.GridTerminalSystem.GetBlocks(grid: this.grid, filter: tank => tank.CustomName.Contains("Hydrogen")); + + foreach (var tank in tanks) + { + currentVolume += (double)tank.Capacity * (double)tank.FilledRatio; + totalVolume += (double)tank.Capacity; + } + + return new Quantity(currentVolume, totalVolume); + } + + Quantity GetOxygenVolume() + { + double currentVolume = 0; + double totalVolume = 0; + var tanks = this.GridTerminalSystem.GetBlocks(grid: this.grid, filter: tank => tank.CustomName.Contains("Oxygen")); + + foreach (var tank in tanks) + { + currentVolume += (double)tank.Capacity * (double)tank.FilledRatio; + totalVolume += (double)tank.Capacity; + } + + return new Quantity(currentVolume, totalVolume); + } + } +} + diff --git a/Mimine/packages.config b/Mimine/packages.config new file mode 100644 index 0000000..0653ff6 --- /dev/null +++ b/Mimine/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file