More-Work #63

Merged
JonathanMcCaffrey merged 4 commits from More-Work into main 2026-06-03 02:04:21 +00:00
84 changed files with 784 additions and 218 deletions
+4
View File
@@ -137,6 +137,7 @@ DocProject/Help/html
# Click-Once directory # Click-Once directory
publish/ publish/
publish_release/
# Publish Web Output # Publish Web Output
*.[Pp]ublish.xml *.[Pp]ublish.xml
@@ -264,3 +265,6 @@ __pycache__/
**/.vs/ **/.vs/
.DS_Store .DS_Store
publish_release/
+1 -1
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Razor"> <Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup> </PropertyGroup>
+9
View File
@@ -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
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>IGP.Calculator.Cli</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Services\Services.csproj" />
<ProjectReference Include="..\Model\Model.csproj" />
</ItemGroup>
</Project>
+158
View File
@@ -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<string>();
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);
}
@@ -0,0 +1,25 @@
using Services;
namespace IGP.Calculator.Cli.Services;
public class NullStorageService : IStorageService
{
private readonly Dictionary<string, object?> _store = new();
public void Subscribe(Action action) { }
public void Unsubscribe(Action action) { }
public T GetValue<T>(string forKey)
{
if (_store.TryGetValue(forKey, out var value) && value is T typed)
return typed;
return default!;
}
public void SetValue<T>(string key, T value)
{
_store[key] = value;
}
public Task Load() => Task.CompletedTask;
}
+9
View File
@@ -44,6 +44,15 @@
:root { :root {
--faction-aru: #da4e4e;
--immortal-mala: #dc7a29;
--immortal-xol: #87aa87;
--immortal-atzlan: #8B7355;
--faction-qrath: #8EACCD;
--immortal-orzum: #4A6B8A;
--immortal-ajari: #b4e2e3;
--severity-warning-color: #2a2000; --severity-warning-color: #2a2000;
--severity-warning-border-color: #755c13; --severity-warning-border-color: #755c13;
--severity-error-color: #290102; --severity-error-color: #290102;
+50
View File
@@ -11,28 +11,78 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Components", "..\Components
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Services", "..\Services\Services.csproj", "{621178C8-4E8B-478E-80E5-7478F0E7B67E}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Services", "..\Services\Services.csproj", "{621178C8-4E8B-478E-80E5-7478F0E7B67E}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IGP.Calculator.Cli", "..\IGP.Calculator.Cli\IGP.Calculator.Cli.csproj", "{9AA71488-E2F5-43C0-8E40-43E72DB2E3CC}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {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|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.ActiveCfg = Release|Any CPU
{172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{77395F7A-BE93-470C-9F10-F48FFA445B63}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{77395F7A-BE93-470C-9F10-F48FFA445B63}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{0419E7CD-0971-4A56-A61F-C090DF60FAF6}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{0419E7CD-0971-4A56-A61F-C090DF60FAF6}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{621178C8-4E8B-478E-80E5-7478F0E7B67E}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{621178C8-4E8B-478E-80E5-7478F0E7B67E}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -13,6 +13,7 @@
@inject IDataCollectionService DataCollectionService @inject IDataCollectionService DataCollectionService
@page "/build-calculator" @page "/build-calculator"
@using IGP.Pages.BuildCalculator.Parts.Cosmetic
@using Services.Website @using Services.Website
@implements IDisposable @implements IDisposable
@@ -25,7 +26,9 @@
<div class="gridItem" style="grid-area: timing;"> <div class="gridItem" style="grid-area: timing;">
<PanelComponent> <PanelComponent>
<InfoTooltipComponent InfoText="@Locale["Tooltip Filter Info"]"> <InfoTooltipComponent InfoText="@Locale["Tooltip Filter Info"]">
<FilterComponent></FilterComponent> <FactionBorderComponent>
<FilterComponent></FilterComponent>
</FactionBorderComponent>
</InfoTooltipComponent> </InfoTooltipComponent>
</PanelComponent> </PanelComponent>
@@ -56,7 +59,9 @@
<div class="gridItem" style="grid-area: view;"> <div class="gridItem" style="grid-area: view;">
<PanelComponent> <PanelComponent>
<InfoTooltipComponent InfoText="@Locale["Tooltip Entity Info"]"> <InfoTooltipComponent InfoText="@Locale["Tooltip Entity Info"]">
<EntityClickViewComponent/> <ImmortalBorderComponent>
<EntityClickViewComponent/>
</ImmortalBorderComponent>
</InfoTooltipComponent> </InfoTooltipComponent>
</PanelComponent> </PanelComponent>
@@ -0,0 +1,31 @@
@inject IImmortalSelectionService FilterService
@implements IDisposable
<FormLayoutComponent>
<div style="@GetBorderStyle()">
@ChildContent
</div>
</FormLayoutComponent>
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
FilterService.Subscribe(StateHasChanged);
}
void IDisposable.Dispose()
{
FilterService.Unsubscribe(StateHasChanged);
}
string GetBorderStyle()
{
var faction = FilterService.GetFaction();
var color = faction == DataType.FACTION_Aru ? "var(--faction-aru)" : "var(--faction-qrath)";
return $"border-top: 4px solid {color}; padding-top: 14px; margin-top: -12px;";
}
}
@@ -0,0 +1,36 @@
@inject IImmortalSelectionService FilterService
@implements IDisposable
<FormLayoutComponent>
<div style="@GetBorderStyle()">
@ChildContent
</div>
</FormLayoutComponent>
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
FilterService.Subscribe(StateHasChanged);
}
void IDisposable.Dispose()
{
FilterService.Unsubscribe(StateHasChanged);
}
string GetBorderStyle()
{
var immortal = FilterService.GetImmortal();
var color = "#666666";
if (immortal == DataType.IMMORTAL_Orzum) color = "var(--immortal-orzum)";
else if (immortal == DataType.IMMORTAL_Ajari) color = "var(--immortal-ajari)";
else if (immortal == DataType.IMMORTAL_Atzlan) color = "var(--immortal-atzlan)";
else if (immortal == DataType.IMMORTAL_Mala) color = "var(--immortal-mala)";
else if (immortal == DataType.IMMORTAL_Xol) color = "var(--immortal-xol)";
return $"border-top: 4px solid {color}; padding-top: 14px; margin-top: -12px;";
}
}
@@ -2,6 +2,7 @@
@inject IKeyService KeyService @inject IKeyService KeyService
@inject IImmortalSelectionService FilterService @inject IImmortalSelectionService FilterService
@inject IStorageService StorageService @inject IStorageService StorageService
@inject IBuildOrderService BuildOrderService
@using Services.Website @using Services.Website
@implements IDisposable @implements IDisposable
@@ -33,6 +34,7 @@
base.OnInitialized(); base.OnInitialized();
KeyService.Subscribe(HandleClick); KeyService.Subscribe(HandleClick);
StorageService.Subscribe(RefreshDefaults); StorageService.Subscribe(RefreshDefaults);
BuildOrderService.Subscribe(OnBuildOrderServiceChanged);
RefreshDefaults(); RefreshDefaults();
} }
@@ -40,11 +42,20 @@
void IDisposable.Dispose() void IDisposable.Dispose()
{ {
KeyService.Unsubscribe(HandleClick); KeyService.Unsubscribe(HandleClick);
StorageService.Unsubscribe(RefreshDefaults); StorageService.Unsubscribe(RefreshDefaults);
BuildOrderService.Unsubscribe(OnBuildOrderServiceChanged);
} }
void OnBuildOrderServiceChanged()
{
if (BuildOrderService.GetLastRequestInterval() == 0)
{
_entity = null;
StateHasChanged();
}
}
void RefreshDefaults() void RefreshDefaults()
{ {
_viewType = StorageService.GetValue<bool>(StorageKeys.IsPlainView) ? EntityViewType.Plain : EntityViewType.Detailed; _viewType = StorageService.GetValue<bool>(StorageKeys.IsPlainView) ? EntityViewType.Plain : EntityViewType.Detailed;
@@ -0,0 +1,82 @@
@inject IImmortalSelectionService FilterService
@implements IDisposable
<FormLayoutComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
What is this tool?
</InfoQuestionComponent>
<InfoAnswerComponent>
This is a calculator to determine build timings. Mostly so someone can quickly try out a few build
orders to see if they somewhat make sense.
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
How does it work?
</InfoQuestionComponent>
<InfoAnswerComponent>
The tool calculates every second of game time. So if you attempt to build a <b>Legion Hall</b> as
your first action, the tool will scan every second, until you get to one where the request can be
made. In this case, that is interval 58.
<br/>
<br/>
If you then build 2 <b>Apostle of Bindings</b> a <b>Soul Foundry</b> and a 3 <b>Absolvers</b> you
should see yourself roughly floating 500 alloy, with barely having any ether. Which means you could
of gotten an <b>Acropolis</b> and a <b>Zentari</b> without hurting your build.
<br/>
<br/>
Try building <b>Apostle of Bindings</b> before the <b>Legion Hall</b> and see how that changes the
timing of your 3 <b>Absolvers</b>. (Spoiler:
<SpoilerTextComponent> your <b>Absolvers</b> will be built much faster, and you won't be floating so
much alloy.
</SpoilerTextComponent>
)
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
What is CONTROL key for?
</InfoQuestionComponent>
<InfoAnswerComponent>
Economy and tech related upgrades for townhalls.
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
What is SHIFT key for?
</InfoQuestionComponent>
<InfoAnswerComponent>
Misc building related upgrades. (Omnivores)
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
What is 2 key for?
</InfoQuestionComponent>
<InfoAnswerComponent>
It will be for Pyre camps. Currently not implemented.
</InfoAnswerComponent>
</InfoBodyComponent>
</FormLayoutComponent>
@code {
protected override void OnInitialized()
{
base.OnInitialized();
FilterService.Subscribe(StateHasChanged);
}
void IDisposable.Dispose()
{
FilterService.Unsubscribe(StateHasChanged);
}
}
+4 -4
View File
@@ -48,7 +48,7 @@ public class BuildOrderModel
new List<EntityModel> new List<EntityModel>
{ {
EntityModel.Get(DataType.STARTING_Bastion), EntityModel.Get(DataType.STARTING_Bastion),
EntityModel.Get(DataType.STARTING_TownHall_Aru) EntityModel.Get(factionStartingTownHall)
} }
} }
}; };
@@ -59,7 +59,7 @@ public class BuildOrderModel
new List<EntityModel> new List<EntityModel>
{ {
EntityModel.Get(DataType.STARTING_Bastion), 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_Bastion, 0
}, },
{ {
DataType.STARTING_TownHall_Aru, 0 factionStartingTownHall, 0
} }
}; };
UniqueCompletedCount = new Dictionary<string, int> UniqueCompletedCount = new Dictionary<string, int>
@@ -78,7 +78,7 @@ public class BuildOrderModel
DataType.STARTING_Bastion, 1 DataType.STARTING_Bastion, 1
}, },
{ {
DataType.STARTING_TownHall_Aru, 1 factionStartingTownHall, 1
} }
}; };
SupplyCountTimes = new Dictionary<int, int> SupplyCountTimes = new Dictionary<int, int>
+1 -1
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1"/> <PackageReference Include="Newtonsoft.Json" Version="13.0.1"/>
+1 -3
View File
@@ -11,9 +11,7 @@ class Website {
} else { } else {
const hook = process.env.TEST_HOOK || ''; const hook = process.env.TEST_HOOK || '';
this.deploymentType = hook.includes('localhost') ? 'Local' : 'Dev'; this.deploymentType = hook.includes('localhost') ? 'Local' : 'Dev';
this.baseUrl = this.deploymentType === 'Dev' this.baseUrl = 'https://localhost:7234';
? 'https://calm-mud-04916b210.1.azurestaticapps.net'
: 'https://localhost:7234';
} }
const BuildCalculatorPage = require('../pages/buildCalculatorPage'); const BuildCalculatorPage = require('../pages/buildCalculatorPage');
-21
View File
@@ -58,33 +58,12 @@ test.describe('Build Calculator', () => {
const calc = website.buildCalculatorPage; const calc = website.buildCalculatorPage;
await calc.goto(); await calc.goto();
const buttons = page.locator('.keyContainer > div > div');
console.log('Initial Q button text:', await buttons.filter({ hasText: /^Q/ }).first().textContent());
// Wait for Blazor re-render to complete by waiting for the button text to stabilize
// (it goes from QAcropolis → empty during re-render → back to QAcropolis)
let tries = 0;
let text = '';
while (tries < 20) {
await page.waitForTimeout(500);
try {
text = (await buttons.filter({ hasText: /^Q/ }).first().textContent() || '').trim();
if (text && text.length > 1) break;
} catch { }
tries++;
}
console.log(`After Blazor render (${(tries+1)*0.5}s): Q button text: ${JSON.stringify(text)}`);
await calc.filter.selectFaction("Q'Rath"); await calc.filter.selectFaction("Q'Rath");
await calc.filter.selectImmortal('Orzum'); await calc.filter.selectImmortal('Orzum');
await page.waitForTimeout(1000);
console.log('After filter Q button text:', await buttons.filter({ hasText: /^Q/ }).first().textContent());
expect(await calc.timeline.containsEntity('Acropolis')).toBe(false); expect(await calc.timeline.containsEntity('Acropolis')).toBe(false);
await calc.hotkeys.clickKey('Q'); await calc.hotkeys.clickKey('Q');
await page.waitForTimeout(1000);
expect(await calc.entityView.getEntityName()).toBe('Acropolis'); expect(await calc.entityView.getEntityName()).toBe('Acropolis');
expect(await calc.timeline.containsEntity('Acropolis')).toBe(true); expect(await calc.timeline.containsEntity('Acropolis')).toBe(true);
+1
View File
@@ -291,6 +291,7 @@ public interface IBuildOrderService
public void RemoveLast(); public void RemoveLast();
public void Reset(); public void Reset();
public void Reset(string faction);
public int GetLastRequestInterval(); public int GetLastRequestInterval();
public string BuildOrderAsYaml(); public string BuildOrderAsYaml();
+7
View File
@@ -341,6 +341,13 @@ public class BuildOrderService : IBuildOrderService
NotifyDataChanged(); NotifyDataChanged();
} }
public void Reset(string faction)
{
_lastInterval = 0;
_buildOrder.Initialize(faction);
NotifyDataChanged();
}
public int? WillMeetTrainingQueue(EntityModel entity) public int? WillMeetTrainingQueue(EntityModel entity)
{ {
var supply = entity.Supply(); var supply = entity.Supply();
+1 -1
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
+59 -141
View File
@@ -23,105 +23,7 @@
} }
}, },
{ {
"id": "2389ff9bfbc7c06e", "id": "094e8bbc34e4a833",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Untitled.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Untitled"
}
},
{
"id": "516344d92af043d3",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Entity Click View Tests.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Entity Click View Tests"
}
},
{
"id": "e080f009e5533348",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Hotkey Tests.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Hotkey Tests"
}
},
{
"id": "8c62a38928399310",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Army Calc UI.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Army Calc UI"
}
},
{
"id": "63052cace03ac2f7",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Timeline Tests.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Timeline Tests"
}
},
{
"id": "b468ed9d43376232",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Army Display Split.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Army Display Split"
}
},
{
"id": "2c63d9663088f304",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Highest Alloy and Ether Tests.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Highest Alloy and Ether Tests"
}
},
{
"id": "461430f8c29407ab",
"type": "leaf", "type": "leaf",
"state": { "state": {
"type": "markdown", "type": "markdown",
@@ -135,21 +37,35 @@
} }
}, },
{ {
"id": "094e8bbc34e4a833", "id": "68e1ba2b54081b9a",
"type": "leaf", "type": "leaf",
"state": { "state": {
"type": "markdown", "type": "markdown",
"state": { "state": {
"file": "Top Borders in Calculator should change based on Selected Faction and Immortal.md", "file": "Helper Tutorial Info Improvements.md",
"mode": "source", "mode": "source",
"source": false "source": false
}, },
"icon": "lucide-file", "icon": "lucide-file",
"title": "Top Borders in Calculator should change based on Selected Faction and Immortal" "title": "Helper Tutorial Info Improvements"
}
},
{
"id": "b98a69cefb529fc8",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Build Calculator CmdLine.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Build Calculator CmdLine"
} }
} }
], ],
"currentTab": 9 "currentTab": 3
} }
], ],
"direction": "vertical" "direction": "vertical"
@@ -206,8 +122,7 @@
} }
], ],
"direction": "horizontal", "direction": "horizontal",
"width": 200, "width": 508.5
"collapsed": true
}, },
"right": { "right": {
"id": "dd7c1dc4bd54d927", "id": "dd7c1dc4bd54d927",
@@ -322,44 +237,47 @@
"bases:Create new base": false "bases:Create new base": false
} }
}, },
"active": "094e8bbc34e4a833", "active": "b98a69cefb529fc8",
"lastOpenFiles": [ "lastOpenFiles": [
"_Tasks Kanban.base", "_Tasks Kanban.base",
"Helper Tutorial Info Improvements.md", "Build Calculator CmdLine.md",
"Make a Plan to Fully Test the Calculator.md", "AI Help Docs/containerize-and-run.md",
"Highest Alloy and Ether Tests.md", "AI Help Docs/publish-and-serve.md",
"Army Display Split.md", "AI Gen Docs/test-multi-context-entity-comparison.md",
"Timeline Tests.md", "AI Gen Docs/services.md",
"Army Calc UI.md", "AI Gen Docs/recommendations.md",
"Untitled 1.md", "AI Gen Docs/development.md",
"Hotkey Tests.md", "AI Gen Docs/architecture.md",
"Add a Timeline Editor.md", "Images/Pasted image 20260601083005.png",
"Worker Income UI and Tests.md", "Images/Pasted image 20260601082954.png",
"Entity Click View Tests.md", "Tasks/Plan Calculator.md",
"Untitled.md", "Tasks/Remove Items from anywhere in the build calc timeline.md",
"More Wait Tests.md", "Tasks/Top Borders in Calculator should change based on Selected Faction and Immortal.md",
"Build Clear should clear out more stuff.md", "Tasks/Update the Reference Tables with Telerik.md",
"Changing Factions and Immortal should clear out build.md", "Tasks/Worker Income UI and Tests.md",
"Input building delay should have an effect on when a building is built. Tests against 0, 2, 4, 60.md", "Tasks/WebAssembly back to Azure.md",
"Ensure build order gets greyed out past the attack time. Clicking the cancel button will wipe the entire greyed out timeline..md", "Tasks/Spells are currently a production item in data. Make the a ability item so they don't show under production table.md",
"Top Borders in Calculator should change based on Selected Faction and Immortal.md", "Tasks/Nice looking map refrence.md",
"Pasted image 20260601093510.png", "Tasks/Add an Ability to Favourite Data.md",
"Pasted image 20260601093506.png", "Tasks/Add a Timeline Editor.md",
"Pasted image 20260601083333.png", "Tasks/Add Co-op objective reference.md",
"Pasted image 20260601083206.png", "Tasks/Entity Click View Tests.md",
"Pasted image 20260601083147.png", "Tasks/Make a Plan to Fully Test the Calculator.md",
"Pasted image 20260601083127.png", "Tasks/Make page object pattern structure for the Build Calculator and all it's components.md",
"Pasted image 20260601083113.png", "Tasks/More Wait Tests.md",
"Pasted image 20260601083101.png", "Images/Pasted image 20260601093510.png",
"Pasted image 20260601083046.png", "Tasks/Timeline Tests.md",
"Pasted image 20260601083030.png", "Tasks/Make Tests for the Build Calculator.md",
"Jenkins CI.md", "Images",
"AI Gen Docs/test-network-resilience.md", "Images/Pasted image 20260601083019.png",
"Add some cooldown reference.md", "Images/Pasted image 20260601083046.png",
"Add Co-op objective reference.md", "Images/Pasted image 20260601083101.png",
"Add an Ability to Favourite Data.md", "Images/Pasted image 20260601083113.png",
"AI Gen Docs/test-toast-timing-interactions.md", "Tasks",
"AI Gen Docs/test-visual-regression.md", "AI Help Docs",
"Images/Pasted image 20260601093506.png",
"Images/Pasted image 20260601083333.png",
"Images/Pasted image 20260601083206.png",
"AI Gen Docs", "AI Gen Docs",
"AI Gen Tasks" "AI Gen Tasks"
] ]
+62
View File
@@ -0,0 +1,62 @@
# Containerizing the IGP App with Docker
## Steps Performed
### 1. Created a MultiStage 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 Blazorrequired MIME types: `application/wasm` (`.wasm`), `application/octet-stream` (`.dll`, `.blat`, `.dat`, `.webcil`).
- Enables `gzip_static` so precompressed `.gz` variants are served automatically.
- Implements SPA fallback: `try_files $uri $uri/ /index.html` for clientside 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` | Multistage 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
```
+51
View File
@@ -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 <PID>
```
## Files
| File | Purpose |
|---|---|
| `publish_release/wwwroot/` | Published static output |
| `serve_publish.cjs` | Simple Node.js HTTP server with Blazor MIME support |
+7
View File
@@ -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.
-12
View File
@@ -1,12 +0,0 @@
---
type: Task
status: Working On
category:
- QA
- Feature
---
![[Pasted image 20260601083333.png]]
- Ensure referenced data is taken from the database. Unit names and costs are currently hard coded.
- Would be cute to dynamically change the information based on the calculator filter. i.e. when making a build for Aru, we should use the Aru unit names in the help information instead of Q'Rath

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

@@ -1,6 +1,6 @@
--- ---
type: Task type: Task
status: AI Agent Work status: Done
category: category:
--- ---
Consider Playwright features we can use to create obvious tests to our test project in the Playwright folder. Consider Playwright features we can use to create obvious tests to our test project in the Playwright folder.
@@ -0,0 +1,40 @@
---
type: Task
status: Working On
category:
- QA
- Feature
---
![[Pasted image 20260601083333.png]]
- Ensure referenced data is taken from the database. Unit names and costs are currently hard coded.
- Would be cute to dynamically change the information based on the calculator filter. i.e. when making a build for Aru, we should use the Aru unit names in the help information instead of Q'Rath
---
The TutorialHelperComponent should change it's information based on the selected Immortal and Faction.
Currently, it explains a hard coded example of a user playing as Q'Rath and Orzum, and making a Legion Hall, Apostle of Bindings, a Soul Foundry, and 3 Absolvers and a Zentari.
Obviously, if the the filter has Aru selected for the faction, the user would be making a Ether Maw, Altar of the Worthy, God Heart Upgrade and a Amber Womb.
For the units, if they have the Immortal of Mala or Atlzan selected, they would be making Masked Hunters. But if they had Xol selected, they would instead be making Bone Stalkers.
For the units, if they have the Immortal of Atlzan selected, they would be making Resinants. But if they had Mala or Xol selected, they would instead be making Blood Anchors.
If they have Q'Rath selected, things are as they are. But if they have Ajari selected as the Immortal, they build Sipari instead of Zentari.
The calculations should be based on the Alloy and Ether costs from the database.
These variables will be need to be used, and will be taken from hard coded values on the page that matches the associated filter combinations.
Given the steps can be very different between filter combinations, the exact contents will also need to be changed based on the filters. Note the extra God Heart Upgrade step for Aru.
This hard coded content will need to be regenerated a a later date to match information currently in the database. But do not worry about implementing that right now.
---
@@ -1,6 +1,6 @@
--- ---
type: Task type: Task
status: AI Agent Work status: Done
category: category:
--- ---
@@ -0,0 +1,28 @@
---
type: Task
status: AI Agent Work
category:
- Feature
- QA
---
![[Pasted image 20260601083019.png]]
- Top border on the Options component changes based on the selected Faction. Light-gray-blue for Q'Rath, Red for Aru.
- Top border on Entity Click View component changes based on the selected Immortal. Ex. Grey-ish-Green for Xol, Green-red for Mala, Brown for Atzlan. Dark-grey-ish-blue for Orzum, Light-ish-blue for Ajari.
---
Make two new UI component to handle the border display changes based on events given by the FilterService.
One will listen against events on the GetFaction changes, and one based on the GetImmortal changes.
These new UI components will be layered on top of FormLayoutComponent.
The purpose is to change the top border colour based on events, to act as UI feedback.
One component will cover the FilterComponent for changing the Faction, and one component will cover the EntityClickViewComponent for changing the Immortal.
Goal is to accomplish:
- Top border over the FilterComponent component changes based on the selected Faction. Light-gray-blue for Q'Rath, Red for Aru.
- Top border on EntityClickViewComponent changes based on the selected Immortal. Ex. Grey-ish-Green for Xol, Green-red for Mala, Brown for Atzlan. Dark-grey-ish-blue for Orzum, Light-ish-blue for Ajari.
@@ -1,13 +0,0 @@
---
type: Task
status: Working On
category:
- Feature
- QA
---
![[Pasted image 20260601083019.png]]
- Top border on the Options component changes based on the selected Faction. Light-gray-blue for Q'Rath, Red for Aru.
- Top border on Entity Click View component changes based on the selected Immortal. Ex. Grey-ish-Green for Xol, Green-red for Mala, Brown for Atzlan. Dark-grey-ish-blue for Orzum, Light-ish-blue for Ajari.
-5
View File
@@ -1,5 +0,0 @@
---
type: Task
status:
category:
---
-5
View File
@@ -1,5 +0,0 @@
---
type: Task
status:
category:
---
+5 -6
View File
@@ -23,7 +23,6 @@ views:
- Done - Done
- AI Agent Work - AI Agent Work
- AI Gen TODO - AI Gen TODO
- Uncategorized
cardOrders: cardOrders:
file.file: file.file:
Untitled.base: [] Untitled.base: []
@@ -45,11 +44,9 @@ views:
- Jenkins CI.md - Jenkins CI.md
- Improve your SEO.md - Improve your SEO.md
- Create Mobile Calculator UI.md - Create Mobile Calculator UI.md
- Top Borders in Calculator should change based on Selected Faction and Immortal.md
- Army Display Split.md - Army Display Split.md
- Entity Click View Tests.md - Entity Click View Tests.md
- Worker Income UI and Tests.md - Worker Income UI and Tests.md
- Changing Factions and Immortal should clear out build.md
- Ensure build order gets greyed out past the attack time. Clicking the cancel button will wipe the entire greyed out timeline..md - Ensure build order gets greyed out past the attack time. Clicking the cancel button will wipe the entire greyed out timeline..md
- Build Clear should clear out more stuff.md - Build Clear should clear out more stuff.md
- Army Calc UI.md - Army Calc UI.md
@@ -57,7 +54,10 @@ views:
- Input building delay should have an effect on when a building is built. Tests against 0, 2, 4, 60.md - Input building delay should have an effect on when a building is built. Tests against 0, 2, 4, 60.md
- More Wait Tests.md - More Wait Tests.md
- Timeline Tests.md - Timeline Tests.md
Working On: [] Working On:
- Changing Factions and Immortal should clear out build.md
- Helper Tutorial Info Improvements.md
- Highest Alloy and Ether Tests.md
Backlog: Backlog:
- Fully Test the Build Calculator.md - Fully Test the Build Calculator.md
- Add an Ability to Favourite Data.md - Add an Ability to Favourite Data.md
@@ -72,8 +72,7 @@ views:
- Change Ctrl + K Hotkey to something that doesn't conflict with Edge or other browsers.md - Change Ctrl + K Hotkey to something that doesn't conflict with Edge or other browsers.md
- Language Support.md - Language Support.md
AI Agent Work: AI Agent Work:
- Make Tests for the Build Calculator.md - Top Borders in Calculator should change based on Selected Faction and Immortal.md
- Get AI to Add easy Test Tasks.md
Blocked Backlog: Blocked Backlog:
- Nice looking map refrence.md - Nice looking map refrence.md
- Auto Build consideration in Calculator.md - Auto Build consideration in Calculator.md
+27
View File
@@ -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;
}
}
}
+48
View File
@@ -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}`);
});