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}`); +});