diff --git a/.gitignore b/.gitignore
index 39bdb4e..6303abf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -137,6 +137,7 @@ DocProject/Help/html
# Click-Once directory
publish/
+publish_release/
# Publish Web Output
*.[Pp]ublish.xml
@@ -264,3 +265,6 @@ __pycache__/
**/.vs/
.DS_Store
+
+
+publish_release/
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..e6f79af
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,9 @@
+FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
+WORKDIR /src
+COPY . .
+RUN dotnet publish IGP/IGP.csproj -c Release -o /app/publish
+
+FROM nginx:alpine AS final
+WORKDIR /usr/share/nginx/html
+COPY --from=build /app/publish/wwwroot ./
+COPY nginx.conf /etc/nginx/nginx.conf
diff --git a/IGP.Calculator.Cli/IGP.Calculator.Cli.csproj b/IGP.Calculator.Cli/IGP.Calculator.Cli.csproj
new file mode 100644
index 0000000..9d19ccd
--- /dev/null
+++ b/IGP.Calculator.Cli/IGP.Calculator.Cli.csproj
@@ -0,0 +1,16 @@
+
+
+
+ Exe
+ net10.0
+ enable
+ enable
+ IGP.Calculator.Cli
+
+
+
+
+
+
+
+
diff --git a/IGP.Calculator.Cli/Program.cs b/IGP.Calculator.Cli/Program.cs
new file mode 100644
index 0000000..6f0d493
--- /dev/null
+++ b/IGP.Calculator.Cli/Program.cs
@@ -0,0 +1,158 @@
+using Model.Entity;
+using Model.Entity.Data;
+using Services;
+using Services.Immortal;
+using Services.Website;
+using IGP.Calculator.Cli.Services;
+
+var faction = DataType.FACTION_QRath;
+var immortal = DataType.IMMORTAL_Orzum;
+var attackTime = 1500;
+var entityNames = new List();
+
+for (var i = 0; i < args.Length; i++)
+{
+ switch (args[i].ToLower())
+ {
+ case "--faction" when i + 1 < args.Length:
+ var f = args[++i].ToLower();
+ faction = f switch
+ {
+ "qrath" => DataType.FACTION_QRath,
+ "aru" => DataType.FACTION_Aru,
+ _ => throw new Exception($"Unknown faction '{args[i]}'. Use QRath or Aru.")
+ };
+ immortal = f switch
+ {
+ "qrath" => DataType.IMMORTAL_Orzum,
+ "aru" => DataType.IMMORTAL_Mala,
+ _ => immortal
+ };
+ break;
+ case "--immortal" when i + 1 < args.Length:
+ var im = args[++i];
+ immortal = im switch
+ {
+ nameof(DataType.IMMORTAL_Orzum) => DataType.IMMORTAL_Orzum,
+ nameof(DataType.IMMORTAL_Ajari) => DataType.IMMORTAL_Ajari,
+ nameof(DataType.IMMORTAL_Atzlan) => DataType.IMMORTAL_Atzlan,
+ nameof(DataType.IMMORTAL_Mala) => DataType.IMMORTAL_Mala,
+ nameof(DataType.IMMORTAL_Xol) => DataType.IMMORTAL_Xol,
+ _ => throw new Exception($"Unknown immortal '{im}'.")
+ };
+ break;
+ case "--attack-time" or "-a" when i + 1 < args.Length:
+ attackTime = int.Parse(args[++i]);
+ break;
+ default:
+ entityNames.Add(args[i]);
+ break;
+ }
+}
+
+var toastService = new ToastService();
+var storageService = new NullStorageService();
+var timingService = new TimingService(storageService);
+timingService.SetAttackTime(attackTime);
+var buildOrderService = new BuildOrderService(toastService, timingService);
+var economyService = new EconomyService();
+
+buildOrderService.Reset(faction);
+economyService.Calculate(buildOrderService, timingService, 0);
+
+Console.WriteLine($"Faction: {(faction == DataType.FACTION_QRath ? "Q'Rath" : "Aru")}");
+Console.WriteLine($"Immortal: {immortal.Replace("IMMORTAL_", "")}");
+Console.WriteLine($"Attack Time: {attackTime}s");
+Console.WriteLine(new string('-', 50));
+
+foreach (var name in entityNames)
+{
+ if (name.StartsWith("wait ", StringComparison.OrdinalIgnoreCase))
+ {
+ var seconds = int.Parse(name[5..]);
+ buildOrderService.AddWait(seconds);
+ economyService.Calculate(buildOrderService, timingService, buildOrderService.GetLastRequestInterval());
+ Console.WriteLine($" Wait {seconds}s -> now at interval {buildOrderService.GetLastRequestInterval()}");
+ continue;
+ }
+
+ if (name.StartsWith("waitto ", StringComparison.OrdinalIgnoreCase))
+ {
+ var interval = int.Parse(name[7..]);
+ buildOrderService.AddWaitTo(interval);
+ economyService.Calculate(buildOrderService, timingService, buildOrderService.GetLastRequestInterval());
+ Console.WriteLine($" Wait to {interval}s -> now at interval {buildOrderService.GetLastRequestInterval()}");
+ continue;
+ }
+
+ var entity = FindEntity(name, faction, immortal);
+ if (entity == null)
+ {
+ Console.WriteLine($" ERROR: '{name}' not found for this faction/immortal.");
+ continue;
+ }
+
+ var beforeInterval = buildOrderService.GetLastRequestInterval();
+ var added = buildOrderService.Add(entity, economyService);
+ if (added)
+ {
+ economyService.Calculate(buildOrderService, timingService, buildOrderService.GetLastRequestInterval());
+ var startedAt = buildOrderService.GetLastRequestInterval();
+ var production = entity.Production();
+ var completedAt = production != null ? startedAt + production.BuildTime : startedAt;
+ var cost = production != null
+ ? $" [{production.Alloy}a/{production.Ether}e/{production.Pyre}p, {production.BuildTime}s]"
+ : "";
+ Console.WriteLine($" {entity.GetName(),-25} start={startedAt,4}s done={completedAt,4}s{cost}");
+ }
+ else
+ {
+ Console.WriteLine($" ERROR: Could not add '{name}'.");
+ var toasts = toastService.GetToasts();
+ if (toasts.Count > 0)
+ {
+ var lastToast = toasts[0];
+ Console.WriteLine($" Reason: {lastToast.Title} - {lastToast.Message}");
+ }
+ }
+}
+
+Console.WriteLine(new string('-', 50));
+
+var lastInterval = buildOrderService.GetLastRequestInterval();
+var finalEconomy = economyService.GetEconomy(timingService.GetAttackTime());
+var lastEconomy = economyService.GetEconomy(lastInterval);
+
+Console.WriteLine($"Army Attacking At: {timingService.GetAttackTime()}s");
+Console.WriteLine($"");
+Console.WriteLine($"At attack time ({timingService.GetAttackTime()}s):");
+Console.WriteLine($" Alloy: {finalEconomy.Alloy,10:F1}");
+Console.WriteLine($" Ether: {finalEconomy.Ether,10:F1}");
+Console.WriteLine($" Pyre: {finalEconomy.Pyre,10:F1}");
+Console.WriteLine($"");
+Console.WriteLine($"At last build action ({lastInterval}s):");
+Console.WriteLine($" Alloy: {lastEconomy.Alloy,10:F1}");
+Console.WriteLine($" Ether: {lastEconomy.Ether,10:F1}");
+Console.WriteLine($" Pyre: {lastEconomy.Pyre,10:F1}");
+
+static EntityModel? FindEntity(string name, string faction, string immortal)
+{
+ var candidates = EntityModel.GetList()
+ .Where(e => e.Info()?.Name?.Equals(name, StringComparison.OrdinalIgnoreCase) == true)
+ .Where(e => e.Faction()?.Faction == faction)
+ .ToList();
+
+ if (candidates.Count == 0)
+ candidates = EntityModel.GetList()
+ .Where(e => e.Info()?.Name?.Equals(name, StringComparison.OrdinalIgnoreCase) == true)
+ .Where(e => e.Faction() == null || e.Faction()!.Faction == DataType.FACTION_Neutral)
+ .ToList();
+
+ if (candidates.Count == 0) return null;
+ if (candidates.Count == 1) return candidates[0];
+
+ var vanguardMatch = candidates.FirstOrDefault(e => e.VanguardAdded()?.ImmortalId == immortal);
+ if (vanguardMatch != null) return vanguardMatch;
+
+ return candidates.FirstOrDefault(e => e.VanguardAdded() == null);
+}
diff --git a/IGP.Calculator.Cli/Services/NullStorageService.cs b/IGP.Calculator.Cli/Services/NullStorageService.cs
new file mode 100644
index 0000000..76a4115
--- /dev/null
+++ b/IGP.Calculator.Cli/Services/NullStorageService.cs
@@ -0,0 +1,25 @@
+using Services;
+
+namespace IGP.Calculator.Cli.Services;
+
+public class NullStorageService : IStorageService
+{
+ private readonly Dictionary _store = new();
+
+ public void Subscribe(Action action) { }
+ public void Unsubscribe(Action action) { }
+
+ public T GetValue(string forKey)
+ {
+ if (_store.TryGetValue(forKey, out var value) && value is T typed)
+ return typed;
+ return default!;
+ }
+
+ public void SetValue(string key, T value)
+ {
+ _store[key] = value;
+ }
+
+ public Task Load() => Task.CompletedTask;
+}
diff --git a/IGP/IGP.sln b/IGP/IGP.sln
index b2b93e6..8694309 100644
--- a/IGP/IGP.sln
+++ b/IGP/IGP.sln
@@ -11,28 +11,78 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Components", "..\Components
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Services", "..\Services\Services.csproj", "{621178C8-4E8B-478E-80E5-7478F0E7B67E}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IGP.Calculator.Cli", "..\IGP.Calculator.Cli\IGP.Calculator.Cli.csproj", "{9AA71488-E2F5-43C0-8E40-43E72DB2E3CC}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}.Debug|x64.Build.0 = Debug|Any CPU
+ {172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}.Debug|x86.Build.0 = Debug|Any CPU
{172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}.Release|x64.ActiveCfg = Release|Any CPU
+ {172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}.Release|x64.Build.0 = Release|Any CPU
+ {172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}.Release|x86.ActiveCfg = Release|Any CPU
+ {172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}.Release|x86.Build.0 = Release|Any CPU
{77395F7A-BE93-470C-9F10-F48FFA445B63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{77395F7A-BE93-470C-9F10-F48FFA445B63}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {77395F7A-BE93-470C-9F10-F48FFA445B63}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {77395F7A-BE93-470C-9F10-F48FFA445B63}.Debug|x64.Build.0 = Debug|Any CPU
+ {77395F7A-BE93-470C-9F10-F48FFA445B63}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {77395F7A-BE93-470C-9F10-F48FFA445B63}.Debug|x86.Build.0 = Debug|Any CPU
{77395F7A-BE93-470C-9F10-F48FFA445B63}.Release|Any CPU.ActiveCfg = Release|Any CPU
{77395F7A-BE93-470C-9F10-F48FFA445B63}.Release|Any CPU.Build.0 = Release|Any CPU
+ {77395F7A-BE93-470C-9F10-F48FFA445B63}.Release|x64.ActiveCfg = Release|Any CPU
+ {77395F7A-BE93-470C-9F10-F48FFA445B63}.Release|x64.Build.0 = Release|Any CPU
+ {77395F7A-BE93-470C-9F10-F48FFA445B63}.Release|x86.ActiveCfg = Release|Any CPU
+ {77395F7A-BE93-470C-9F10-F48FFA445B63}.Release|x86.Build.0 = Release|Any CPU
{0419E7CD-0971-4A56-A61F-C090DF60FAF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0419E7CD-0971-4A56-A61F-C090DF60FAF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0419E7CD-0971-4A56-A61F-C090DF60FAF6}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0419E7CD-0971-4A56-A61F-C090DF60FAF6}.Debug|x64.Build.0 = Debug|Any CPU
+ {0419E7CD-0971-4A56-A61F-C090DF60FAF6}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0419E7CD-0971-4A56-A61F-C090DF60FAF6}.Debug|x86.Build.0 = Debug|Any CPU
{0419E7CD-0971-4A56-A61F-C090DF60FAF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0419E7CD-0971-4A56-A61F-C090DF60FAF6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0419E7CD-0971-4A56-A61F-C090DF60FAF6}.Release|x64.ActiveCfg = Release|Any CPU
+ {0419E7CD-0971-4A56-A61F-C090DF60FAF6}.Release|x64.Build.0 = Release|Any CPU
+ {0419E7CD-0971-4A56-A61F-C090DF60FAF6}.Release|x86.ActiveCfg = Release|Any CPU
+ {0419E7CD-0971-4A56-A61F-C090DF60FAF6}.Release|x86.Build.0 = Release|Any CPU
{621178C8-4E8B-478E-80E5-7478F0E7B67E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{621178C8-4E8B-478E-80E5-7478F0E7B67E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {621178C8-4E8B-478E-80E5-7478F0E7B67E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {621178C8-4E8B-478E-80E5-7478F0E7B67E}.Debug|x64.Build.0 = Debug|Any CPU
+ {621178C8-4E8B-478E-80E5-7478F0E7B67E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {621178C8-4E8B-478E-80E5-7478F0E7B67E}.Debug|x86.Build.0 = Debug|Any CPU
{621178C8-4E8B-478E-80E5-7478F0E7B67E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{621178C8-4E8B-478E-80E5-7478F0E7B67E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {621178C8-4E8B-478E-80E5-7478F0E7B67E}.Release|x64.ActiveCfg = Release|Any CPU
+ {621178C8-4E8B-478E-80E5-7478F0E7B67E}.Release|x64.Build.0 = Release|Any CPU
+ {621178C8-4E8B-478E-80E5-7478F0E7B67E}.Release|x86.ActiveCfg = Release|Any CPU
+ {621178C8-4E8B-478E-80E5-7478F0E7B67E}.Release|x86.Build.0 = Release|Any CPU
+ {9AA71488-E2F5-43C0-8E40-43E72DB2E3CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9AA71488-E2F5-43C0-8E40-43E72DB2E3CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9AA71488-E2F5-43C0-8E40-43E72DB2E3CC}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9AA71488-E2F5-43C0-8E40-43E72DB2E3CC}.Debug|x64.Build.0 = Debug|Any CPU
+ {9AA71488-E2F5-43C0-8E40-43E72DB2E3CC}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9AA71488-E2F5-43C0-8E40-43E72DB2E3CC}.Debug|x86.Build.0 = Debug|Any CPU
+ {9AA71488-E2F5-43C0-8E40-43E72DB2E3CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9AA71488-E2F5-43C0-8E40-43E72DB2E3CC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9AA71488-E2F5-43C0-8E40-43E72DB2E3CC}.Release|x64.ActiveCfg = Release|Any CPU
+ {9AA71488-E2F5-43C0-8E40-43E72DB2E3CC}.Release|x64.Build.0 = Release|Any CPU
+ {9AA71488-E2F5-43C0-8E40-43E72DB2E3CC}.Release|x86.ActiveCfg = Release|Any CPU
+ {9AA71488-E2F5-43C0-8E40-43E72DB2E3CC}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Model/BuildOrders/BuildOrderModel.cs b/Model/BuildOrders/BuildOrderModel.cs
index f147f15..d76838d 100644
--- a/Model/BuildOrders/BuildOrderModel.cs
+++ b/Model/BuildOrders/BuildOrderModel.cs
@@ -48,7 +48,7 @@ public class BuildOrderModel
new List
{
EntityModel.Get(DataType.STARTING_Bastion),
- EntityModel.Get(DataType.STARTING_TownHall_Aru)
+ EntityModel.Get(factionStartingTownHall)
}
}
};
@@ -59,7 +59,7 @@ public class BuildOrderModel
new List
{
EntityModel.Get(DataType.STARTING_Bastion),
- EntityModel.Get(DataType.STARTING_TownHall_Aru)
+ EntityModel.Get(factionStartingTownHall)
}
}
};
@@ -69,7 +69,7 @@ public class BuildOrderModel
DataType.STARTING_Bastion, 0
},
{
- DataType.STARTING_TownHall_Aru, 0
+ factionStartingTownHall, 0
}
};
UniqueCompletedCount = new Dictionary
@@ -78,7 +78,7 @@ public class BuildOrderModel
DataType.STARTING_Bastion, 1
},
{
- DataType.STARTING_TownHall_Aru, 1
+ factionStartingTownHall, 1
}
};
SupplyCountTimes = new Dictionary
diff --git a/Services/IServices.cs b/Services/IServices.cs
index 2e679a1..72c9a40 100644
--- a/Services/IServices.cs
+++ b/Services/IServices.cs
@@ -291,6 +291,7 @@ public interface IBuildOrderService
public void RemoveLast();
public void Reset();
+ public void Reset(string faction);
public int GetLastRequestInterval();
public string BuildOrderAsYaml();
diff --git a/Services/Immortal/BuildOrderService.cs b/Services/Immortal/BuildOrderService.cs
index e884ae8..4be1a93 100644
--- a/Services/Immortal/BuildOrderService.cs
+++ b/Services/Immortal/BuildOrderService.cs
@@ -341,6 +341,13 @@ public class BuildOrderService : IBuildOrderService
NotifyDataChanged();
}
+ public void Reset(string faction)
+ {
+ _lastInterval = 0;
+ _buildOrder.Initialize(faction);
+ NotifyDataChanged();
+ }
+
public int? WillMeetTrainingQueue(EntityModel entity)
{
var supply = entity.Supply();
diff --git a/docs/.obsidian/workspace.json b/docs/.obsidian/workspace.json
index f4e8def..14d6326 100644
--- a/docs/.obsidian/workspace.json
+++ b/docs/.obsidian/workspace.json
@@ -56,12 +56,12 @@
"state": {
"type": "markdown",
"state": {
- "file": "Changing Factions and Immortal should clear out build.md",
+ "file": "Build Calculator CmdLine.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
- "title": "Changing Factions and Immortal should clear out build"
+ "title": "Build Calculator CmdLine"
}
}
],
@@ -122,7 +122,7 @@
}
],
"direction": "horizontal",
- "width": 200
+ "width": 508.5
},
"right": {
"id": "dd7c1dc4bd54d927",
@@ -240,41 +240,44 @@
"active": "b98a69cefb529fc8",
"lastOpenFiles": [
"_Tasks Kanban.base",
- "Helper Tutorial Info Improvements.md",
- "Highest Alloy and Ether Tests.md",
- "Army Display Split.md",
- "Timeline Tests.md",
- "Army Calc UI.md",
- "Hotkey Tests.md",
- "Entity Click View Tests.md",
- "Untitled.md",
- "Top Borders in Calculator should change based on Selected Faction and Immortal.md",
- "Make a Plan to Fully Test the Calculator.md",
- "Untitled 1.md",
- "Add a Timeline Editor.md",
- "Worker Income UI and Tests.md",
- "More Wait Tests.md",
- "Build Clear should clear out more stuff.md",
- "Changing Factions and Immortal should clear out build.md",
- "Input building delay should have an effect on when a building is built. Tests against 0, 2, 4, 60.md",
- "Ensure build order gets greyed out past the attack time. Clicking the cancel button will wipe the entire greyed out timeline..md",
- "Pasted image 20260601093510.png",
- "Pasted image 20260601093506.png",
- "Pasted image 20260601083333.png",
- "Pasted image 20260601083206.png",
- "Pasted image 20260601083147.png",
- "Pasted image 20260601083127.png",
- "Pasted image 20260601083113.png",
- "Pasted image 20260601083101.png",
- "Pasted image 20260601083046.png",
- "Pasted image 20260601083030.png",
- "Jenkins CI.md",
- "AI Gen Docs/test-network-resilience.md",
- "Add some cooldown reference.md",
- "Add Co-op objective reference.md",
- "Add an Ability to Favourite Data.md",
- "AI Gen Docs/test-toast-timing-interactions.md",
- "AI Gen Docs/test-visual-regression.md",
+ "Build Calculator CmdLine.md",
+ "AI Help Docs/containerize-and-run.md",
+ "AI Help Docs/publish-and-serve.md",
+ "AI Gen Docs/test-multi-context-entity-comparison.md",
+ "AI Gen Docs/services.md",
+ "AI Gen Docs/recommendations.md",
+ "AI Gen Docs/development.md",
+ "AI Gen Docs/architecture.md",
+ "Images/Pasted image 20260601083005.png",
+ "Images/Pasted image 20260601082954.png",
+ "Tasks/Plan Calculator.md",
+ "Tasks/Remove Items from anywhere in the build calc timeline.md",
+ "Tasks/Top Borders in Calculator should change based on Selected Faction and Immortal.md",
+ "Tasks/Update the Reference Tables with Telerik.md",
+ "Tasks/Worker Income UI and Tests.md",
+ "Tasks/WebAssembly back to Azure.md",
+ "Tasks/Spells are currently a production item in data. Make the a ability item so they don't show under production table.md",
+ "Tasks/Nice looking map refrence.md",
+ "Tasks/Add an Ability to Favourite Data.md",
+ "Tasks/Add a Timeline Editor.md",
+ "Tasks/Add Co-op objective reference.md",
+ "Tasks/Entity Click View Tests.md",
+ "Tasks/Make a Plan to Fully Test the Calculator.md",
+ "Tasks/Make page object pattern structure for the Build Calculator and all it's components.md",
+ "Tasks/More Wait Tests.md",
+ "Images/Pasted image 20260601093510.png",
+ "Tasks/Timeline Tests.md",
+ "Tasks/Make Tests for the Build Calculator.md",
+ "Images",
+ "Images/Pasted image 20260601083019.png",
+ "Images/Pasted image 20260601083046.png",
+ "Images/Pasted image 20260601083101.png",
+ "Images/Pasted image 20260601083113.png",
+ "Tasks",
+ "AI Help Docs",
+ "Images/Pasted image 20260601093506.png",
+ "Images/Pasted image 20260601083333.png",
+ "Images/Pasted image 20260601083206.png",
"AI Gen Docs",
"AI Gen Tasks"
]
diff --git a/docs/AI Help Docs/containerize-and-run.md b/docs/AI Help Docs/containerize-and-run.md
new file mode 100644
index 0000000..7ba1a38
--- /dev/null
+++ b/docs/AI Help Docs/containerize-and-run.md
@@ -0,0 +1,62 @@
+# Containerizing the IGP App with Docker
+
+## Steps Performed
+
+### 1. Created a Multi‑Stage Dockerfile (`Dockerfile`)
+
+Two stages:
+
+| Stage | Image | Purpose |
+|---|---|---|
+| `build` | `mcr.microsoft.com/dotnet/sdk:10.0` | Restores dependencies, builds, and publishes the Blazor WASM app |
+| `final` | `nginx:alpine` | Copies the published `wwwroot/` output and a custom `nginx.conf` that serves it |
+
+### 2. Created a Custom nginx Config (`nginx.conf`)
+
+- Listens on port **8887** (not the default 80) so it doesn't conflict with other containers.
+- Adds Blazor‑required MIME types: `application/wasm` (`.wasm`), `application/octet-stream` (`.dll`, `.blat`, `.dat`, `.webcil`).
+- Enables `gzip_static` so pre‑compressed `.gz` variants are served automatically.
+- Implements SPA fallback: `try_files $uri $uri/ /index.html` for client‑side routing.
+
+### 3. Built the Image
+
+```
+docker build -t igp-app:latest -f Dockerfile .
+```
+
+Result: `docker.io/library/igp-app:latest`
+
+### 4. Ran the Container
+
+```
+docker run -d --name igp-app -p 8887:8887 igp-app:latest
+```
+
+The container is now serving at **http://localhost:8887**.
+
+### 5. Verified
+
+- `docker ps` shows the container `Up` and port mapping `0.0.0.0:8887->8887/tcp`.
+- `curl http://localhost:8887/` returns HTTP `200`.
+
+## Files
+
+| File | Purpose |
+|---|---|
+| `Dockerfile` | Multi‑stage build: .NET SDK → publish → nginx |
+| `nginx.conf` | nginx config with Blazor MIME types, gzip, SPA fallback, port 8887 |
+
+## How to Stop
+
+```
+docker stop igp-app
+docker rm igp-app
+```
+
+## How to Rebuild
+
+```
+docker build -t igp-app:latest -f Dockerfile .
+docker rm -f igp-app
+docker run -d --name igp-app -p 8887:8887 igp-app:latest
+```
diff --git a/docs/AI Help Docs/publish-and-serve.md b/docs/AI Help Docs/publish-and-serve.md
new file mode 100644
index 0000000..ff35ad1
--- /dev/null
+++ b/docs/AI Help Docs/publish-and-serve.md
@@ -0,0 +1,51 @@
+# Publishing and Serving the IGP App Locally
+
+## Steps Performed
+
+### 1. Publish the Blazor WebAssembly App
+
+Ran `dotnet publish` targeting Release configuration, outputting to `publish_release/`:
+
+```
+dotnet publish .\IGP\IGP.csproj -c Release -o .\publish_release
+```
+
+This produced a standard Blazor WASM publish layout:
+- `publish_release/wwwroot/` — static assets (HTML, CSS, JS, WASM, DLLs)
+- `publish_release/dotnet.js` — .NET runtime loader
+- `publish_release/web.config` — IIS configuration
+
+### 2. Serve the Published Files on Port 8777
+
+Wrote a small Node.js static file server (`serve_publish.cjs`) that:
+
+- Serves files from `publish_release/wwwroot/`
+- Maps correct MIME types for `.wasm` ( `application/wasm` ), `.dll` ( `application/octet-stream` ), `.js` ( `application/javascript` ), `.br`, `.gz`
+- Implements SPA fallback: non-file routes serve `index.html` so Blazor's client-side routing works on refresh
+
+```
+node serve_publish.cjs
+```
+
+Server is now running at **http://localhost:8777**.
+
+### 3. Verify
+
+- `netstat -ano | findstr ":8777"` confirms the process is LISTENING
+- `curl -s -o NUL -w "%{http_code}" http://localhost:8777/` returns `200`
+
+## How to Stop
+
+Find the process and kill it:
+
+```
+netstat -ano | findstr ":8777.*LISTENING"
+Stop-Process -Id
+```
+
+## Files
+
+| File | Purpose |
+|---|---|
+| `publish_release/wwwroot/` | Published static output |
+| `serve_publish.cjs` | Simple Node.js HTTP server with Blazor MIME support |
diff --git a/docs/Build Calculator CmdLine.md b/docs/Build Calculator CmdLine.md
new file mode 100644
index 0000000..6ae92ba
--- /dev/null
+++ b/docs/Build Calculator CmdLine.md
@@ -0,0 +1,7 @@
+I want you to analyze the BuildCalculator and the services it uses to function.
+
+Make a cmdline console app with C#, that will allow you to hand in a build order list and have the tool return the Army Attacking At and the displayed Alloy and Ether at the current time interval.
+
+Try to share the service project. Add new logic where you have to, to make this console specification work.
+
+Give me example text you would pass in to the console to make a build order that let's say, builds a Legion Hall with two Zentari.
\ No newline at end of file
diff --git a/docs/Pasted image 20260601082954.png b/docs/Images/Pasted image 20260601082954.png
similarity index 100%
rename from docs/Pasted image 20260601082954.png
rename to docs/Images/Pasted image 20260601082954.png
diff --git a/docs/Pasted image 20260601083005.png b/docs/Images/Pasted image 20260601083005.png
similarity index 100%
rename from docs/Pasted image 20260601083005.png
rename to docs/Images/Pasted image 20260601083005.png
diff --git a/docs/Pasted image 20260601083019.png b/docs/Images/Pasted image 20260601083019.png
similarity index 100%
rename from docs/Pasted image 20260601083019.png
rename to docs/Images/Pasted image 20260601083019.png
diff --git a/docs/Pasted image 20260601083030.png b/docs/Images/Pasted image 20260601083030.png
similarity index 100%
rename from docs/Pasted image 20260601083030.png
rename to docs/Images/Pasted image 20260601083030.png
diff --git a/docs/Pasted image 20260601083046.png b/docs/Images/Pasted image 20260601083046.png
similarity index 100%
rename from docs/Pasted image 20260601083046.png
rename to docs/Images/Pasted image 20260601083046.png
diff --git a/docs/Pasted image 20260601083101.png b/docs/Images/Pasted image 20260601083101.png
similarity index 100%
rename from docs/Pasted image 20260601083101.png
rename to docs/Images/Pasted image 20260601083101.png
diff --git a/docs/Pasted image 20260601083113.png b/docs/Images/Pasted image 20260601083113.png
similarity index 100%
rename from docs/Pasted image 20260601083113.png
rename to docs/Images/Pasted image 20260601083113.png
diff --git a/docs/Pasted image 20260601083127.png b/docs/Images/Pasted image 20260601083127.png
similarity index 100%
rename from docs/Pasted image 20260601083127.png
rename to docs/Images/Pasted image 20260601083127.png
diff --git a/docs/Pasted image 20260601083147.png b/docs/Images/Pasted image 20260601083147.png
similarity index 100%
rename from docs/Pasted image 20260601083147.png
rename to docs/Images/Pasted image 20260601083147.png
diff --git a/docs/Pasted image 20260601083206.png b/docs/Images/Pasted image 20260601083206.png
similarity index 100%
rename from docs/Pasted image 20260601083206.png
rename to docs/Images/Pasted image 20260601083206.png
diff --git a/docs/Pasted image 20260601083333.png b/docs/Images/Pasted image 20260601083333.png
similarity index 100%
rename from docs/Pasted image 20260601083333.png
rename to docs/Images/Pasted image 20260601083333.png
diff --git a/docs/Pasted image 20260601093506.png b/docs/Images/Pasted image 20260601093506.png
similarity index 100%
rename from docs/Pasted image 20260601093506.png
rename to docs/Images/Pasted image 20260601093506.png
diff --git a/docs/Pasted image 20260601093510.png b/docs/Images/Pasted image 20260601093510.png
similarity index 100%
rename from docs/Pasted image 20260601093510.png
rename to docs/Images/Pasted image 20260601093510.png
diff --git a/docs/Add Co-op objective reference.md b/docs/Tasks/Add Co-op objective reference.md
similarity index 100%
rename from docs/Add Co-op objective reference.md
rename to docs/Tasks/Add Co-op objective reference.md
diff --git a/docs/Add a Timeline Editor.md b/docs/Tasks/Add a Timeline Editor.md
similarity index 100%
rename from docs/Add a Timeline Editor.md
rename to docs/Tasks/Add a Timeline Editor.md
diff --git a/docs/Add an Ability to Favourite Data.md b/docs/Tasks/Add an Ability to Favourite Data.md
similarity index 100%
rename from docs/Add an Ability to Favourite Data.md
rename to docs/Tasks/Add an Ability to Favourite Data.md
diff --git a/docs/Add some cooldown reference.md b/docs/Tasks/Add some cooldown reference.md
similarity index 100%
rename from docs/Add some cooldown reference.md
rename to docs/Tasks/Add some cooldown reference.md
diff --git a/docs/Allow to Always see Advanced Options in Calculator without pressing Space.md b/docs/Tasks/Allow to Always see Advanced Options in Calculator without pressing Space.md
similarity index 100%
rename from docs/Allow to Always see Advanced Options in Calculator without pressing Space.md
rename to docs/Tasks/Allow to Always see Advanced Options in Calculator without pressing Space.md
diff --git a/docs/Army Calc UI.md b/docs/Tasks/Army Calc UI.md
similarity index 100%
rename from docs/Army Calc UI.md
rename to docs/Tasks/Army Calc UI.md
diff --git a/docs/Army Display Split.md b/docs/Tasks/Army Display Split.md
similarity index 100%
rename from docs/Army Display Split.md
rename to docs/Tasks/Army Display Split.md
diff --git a/docs/Auto Build consideration in Calculator.md b/docs/Tasks/Auto Build consideration in Calculator.md
similarity index 100%
rename from docs/Auto Build consideration in Calculator.md
rename to docs/Tasks/Auto Build consideration in Calculator.md
diff --git a/docs/Basic Build Order Sheet.md b/docs/Tasks/Basic Build Order Sheet.md
similarity index 100%
rename from docs/Basic Build Order Sheet.md
rename to docs/Tasks/Basic Build Order Sheet.md
diff --git a/docs/Build Clear should clear out more stuff.md b/docs/Tasks/Build Clear should clear out more stuff.md
similarity index 100%
rename from docs/Build Clear should clear out more stuff.md
rename to docs/Tasks/Build Clear should clear out more stuff.md
diff --git a/docs/Change Ctrl + K Hotkey to something that doesn't conflict with Edge or other browsers.md b/docs/Tasks/Change Ctrl + K Hotkey to something that doesn't conflict with Edge or other browsers.md
similarity index 100%
rename from docs/Change Ctrl + K Hotkey to something that doesn't conflict with Edge or other browsers.md
rename to docs/Tasks/Change Ctrl + K Hotkey to something that doesn't conflict with Edge or other browsers.md
diff --git a/docs/Changing Factions and Immortal should clear out build.md b/docs/Tasks/Changing Factions and Immortal should clear out build.md
similarity index 100%
rename from docs/Changing Factions and Immortal should clear out build.md
rename to docs/Tasks/Changing Factions and Immortal should clear out build.md
diff --git a/docs/Create Automated Tests.md b/docs/Tasks/Create Automated Tests.md
similarity index 100%
rename from docs/Create Automated Tests.md
rename to docs/Tasks/Create Automated Tests.md
diff --git a/docs/Create Mobile Calculator UI.md b/docs/Tasks/Create Mobile Calculator UI.md
similarity index 100%
rename from docs/Create Mobile Calculator UI.md
rename to docs/Tasks/Create Mobile Calculator UI.md
diff --git a/docs/Ensure build order gets greyed out past the attack time. Clicking the cancel button will wipe the entire greyed out timeline..md b/docs/Tasks/Ensure build order gets greyed out past the attack time. Clicking the cancel button will wipe the entire greyed out timeline..md
similarity index 100%
rename from docs/Ensure build order gets greyed out past the attack time. Clicking the cancel button will wipe the entire greyed out timeline..md
rename to docs/Tasks/Ensure build order gets greyed out past the attack time. Clicking the cancel button will wipe the entire greyed out timeline..md
diff --git a/docs/Entity Click View Tests.md b/docs/Tasks/Entity Click View Tests.md
similarity index 100%
rename from docs/Entity Click View Tests.md
rename to docs/Tasks/Entity Click View Tests.md
diff --git a/docs/Fix Entity Recursion Error - Parent.md b/docs/Tasks/Fix Entity Recursion Error - Parent.md
similarity index 100%
rename from docs/Fix Entity Recursion Error - Parent.md
rename to docs/Tasks/Fix Entity Recursion Error - Parent.md
diff --git a/docs/Fully Test the Build Calculator.md b/docs/Tasks/Fully Test the Build Calculator.md
similarity index 100%
rename from docs/Fully Test the Build Calculator.md
rename to docs/Tasks/Fully Test the Build Calculator.md
diff --git a/docs/Get AI to Add easy Test Tasks.md b/docs/Tasks/Get AI to Add easy Test Tasks.md
similarity index 100%
rename from docs/Get AI to Add easy Test Tasks.md
rename to docs/Tasks/Get AI to Add easy Test Tasks.md
diff --git a/docs/Helper Tutorial Info Improvements.md b/docs/Tasks/Helper Tutorial Info Improvements.md
similarity index 100%
rename from docs/Helper Tutorial Info Improvements.md
rename to docs/Tasks/Helper Tutorial Info Improvements.md
diff --git a/docs/Highest Alloy and Ether Tests.md b/docs/Tasks/Highest Alloy and Ether Tests.md
similarity index 100%
rename from docs/Highest Alloy and Ether Tests.md
rename to docs/Tasks/Highest Alloy and Ether Tests.md
diff --git a/docs/Hotkey Tests.md b/docs/Tasks/Hotkey Tests.md
similarity index 100%
rename from docs/Hotkey Tests.md
rename to docs/Tasks/Hotkey Tests.md
diff --git a/docs/Improve your SEO.md b/docs/Tasks/Improve your SEO.md
similarity index 100%
rename from docs/Improve your SEO.md
rename to docs/Tasks/Improve your SEO.md
diff --git a/docs/Input building delay should have an effect on when a building is built. Tests against 0, 2, 4, 60.md b/docs/Tasks/Input building delay should have an effect on when a building is built. Tests against 0, 2, 4, 60.md
similarity index 100%
rename from docs/Input building delay should have an effect on when a building is built. Tests against 0, 2, 4, 60.md
rename to docs/Tasks/Input building delay should have an effect on when a building is built. Tests against 0, 2, 4, 60.md
diff --git a/docs/Jenkins CI.md b/docs/Tasks/Jenkins CI.md
similarity index 100%
rename from docs/Jenkins CI.md
rename to docs/Tasks/Jenkins CI.md
diff --git a/docs/Language Support.md b/docs/Tasks/Language Support.md
similarity index 100%
rename from docs/Language Support.md
rename to docs/Tasks/Language Support.md
diff --git a/docs/Make Examples be based on Database Information.md b/docs/Tasks/Make Examples be based on Database Information.md
similarity index 100%
rename from docs/Make Examples be based on Database Information.md
rename to docs/Tasks/Make Examples be based on Database Information.md
diff --git a/docs/Make Tests for the Build Calculator.md b/docs/Tasks/Make Tests for the Build Calculator.md
similarity index 100%
rename from docs/Make Tests for the Build Calculator.md
rename to docs/Tasks/Make Tests for the Build Calculator.md
diff --git a/docs/Make a Plan to Fully Test the Calculator.md b/docs/Tasks/Make a Plan to Fully Test the Calculator.md
similarity index 100%
rename from docs/Make a Plan to Fully Test the Calculator.md
rename to docs/Tasks/Make a Plan to Fully Test the Calculator.md
diff --git a/docs/Make page object pattern structure for the Build Calculator and all it's components.md b/docs/Tasks/Make page object pattern structure for the Build Calculator and all it's components.md
similarity index 100%
rename from docs/Make page object pattern structure for the Build Calculator and all it's components.md
rename to docs/Tasks/Make page object pattern structure for the Build Calculator and all it's components.md
diff --git a/docs/More Wait Tests.md b/docs/Tasks/More Wait Tests.md
similarity index 100%
rename from docs/More Wait Tests.md
rename to docs/Tasks/More Wait Tests.md
diff --git a/docs/Nice looking map refrence.md b/docs/Tasks/Nice looking map refrence.md
similarity index 100%
rename from docs/Nice looking map refrence.md
rename to docs/Tasks/Nice looking map refrence.md
diff --git a/docs/Plan Calculator.md b/docs/Tasks/Plan Calculator.md
similarity index 100%
rename from docs/Plan Calculator.md
rename to docs/Tasks/Plan Calculator.md
diff --git a/docs/Remove Items from anywhere in the build calc timeline.md b/docs/Tasks/Remove Items from anywhere in the build calc timeline.md
similarity index 100%
rename from docs/Remove Items from anywhere in the build calc timeline.md
rename to docs/Tasks/Remove Items from anywhere in the build calc timeline.md
diff --git a/docs/Spells are currently a production item in data. Make the a ability item so they don't show under production table.md b/docs/Tasks/Spells are currently a production item in data. Make the a ability item so they don't show under production table.md
similarity index 100%
rename from docs/Spells are currently a production item in data. Make the a ability item so they don't show under production table.md
rename to docs/Tasks/Spells are currently a production item in data. Make the a ability item so they don't show under production table.md
diff --git a/docs/Timeline Tests.md b/docs/Tasks/Timeline Tests.md
similarity index 100%
rename from docs/Timeline Tests.md
rename to docs/Tasks/Timeline Tests.md
diff --git a/docs/Top Borders in Calculator should change based on Selected Faction and Immortal.md b/docs/Tasks/Top Borders in Calculator should change based on Selected Faction and Immortal.md
similarity index 100%
rename from docs/Top Borders in Calculator should change based on Selected Faction and Immortal.md
rename to docs/Tasks/Top Borders in Calculator should change based on Selected Faction and Immortal.md
diff --git a/docs/Update the Reference Tables with Telerik.md b/docs/Tasks/Update the Reference Tables with Telerik.md
similarity index 100%
rename from docs/Update the Reference Tables with Telerik.md
rename to docs/Tasks/Update the Reference Tables with Telerik.md
diff --git a/docs/WebAssembly back to Azure.md b/docs/Tasks/WebAssembly back to Azure.md
similarity index 100%
rename from docs/WebAssembly back to Azure.md
rename to docs/Tasks/WebAssembly back to Azure.md
diff --git a/docs/Worker Income UI and Tests.md b/docs/Tasks/Worker Income UI and Tests.md
similarity index 100%
rename from docs/Worker Income UI and Tests.md
rename to docs/Tasks/Worker Income UI and Tests.md
diff --git a/docs/Untitled 1.md b/docs/Untitled 1.md
deleted file mode 100644
index cac4e10..0000000
--- a/docs/Untitled 1.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-type: Task
-status:
-category:
----
diff --git a/docs/Untitled.md b/docs/Untitled.md
deleted file mode 100644
index cac4e10..0000000
--- a/docs/Untitled.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-type: Task
-status:
-category:
----
diff --git a/docs/_Tasks Kanban.base b/docs/_Tasks Kanban.base
index 411341f..3dfaf9c 100644
--- a/docs/_Tasks Kanban.base
+++ b/docs/_Tasks Kanban.base
@@ -23,7 +23,6 @@ views:
- Done
- AI Agent Work
- AI Gen TODO
- - Uncategorized
cardOrders:
file.file:
Untitled.base: []
@@ -56,8 +55,8 @@ views:
- More Wait Tests.md
- Timeline Tests.md
Working On:
- - Helper Tutorial Info Improvements.md
- Changing Factions and Immortal should clear out build.md
+ - Helper Tutorial Info Improvements.md
- Highest Alloy and Ether Tests.md
Backlog:
- Fully Test the Build Calculator.md
diff --git a/nginx.conf b/nginx.conf
new file mode 100644
index 0000000..b2abc29
--- /dev/null
+++ b/nginx.conf
@@ -0,0 +1,27 @@
+events { }
+
+http {
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+
+ types {
+ application/wasm wasm;
+ application/octet-stream dll blat dat webcil;
+ application/gzip gz;
+ }
+
+ server {
+ listen 8887;
+ server_name localhost;
+
+ root /usr/share/nginx/html;
+ index index.html;
+
+ gzip_static on;
+ gzip_types application/wasm application/octet-stream application/json text/html text/css application/javascript;
+
+ location / {
+ try_files $uri $uri/ /index.html;
+ }
+ }
+}
diff --git a/serve_publish.cjs b/serve_publish.cjs
new file mode 100644
index 0000000..c1c1c53
--- /dev/null
+++ b/serve_publish.cjs
@@ -0,0 +1,48 @@
+const http = require('http');
+const fs = require('fs');
+const path = require('path');
+
+const PORT = 8777;
+const ROOT = path.join(__dirname, 'publish_release', 'wwwroot');
+
+const MIME = {
+ '.html': 'text/html',
+ '.js': 'application/javascript',
+ '.wasm': 'application/wasm',
+ '.dll': 'application/octet-stream',
+ '.css': 'text/css',
+ '.png': 'image/png',
+ '.jpg': 'image/jpeg',
+ '.svg': 'image/svg+xml',
+ '.ico': 'image/x-icon',
+ '.json': 'application/json',
+ '.br': 'application/octet-stream',
+ '.gz': 'application/gzip',
+};
+
+const server = http.createServer((req, res) => {
+ let filePath = path.join(ROOT, req.url === '/' ? 'index.html' : req.url);
+ const ext = path.extname(filePath);
+
+ fs.readFile(filePath, (err, data) => {
+ if (err) {
+ // SPA fallback: serve index.html for non-file routes
+ fs.readFile(path.join(ROOT, 'index.html'), (err2, data2) => {
+ if (err2) {
+ res.writeHead(404);
+ res.end('Not found');
+ return;
+ }
+ res.writeHead(200, { 'Content-Type': 'text/html' });
+ res.end(data2);
+ });
+ return;
+ }
+ res.writeHead(200, { 'Content-Type': MIME[ext] || 'application/octet-stream' });
+ res.end(data);
+ });
+});
+
+server.listen(PORT, () => {
+ console.log(`Serving IGP publish on http://localhost:${PORT}`);
+});