CLI and Publish Tests
@@ -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/
|
||||||
@@ -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>
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -56,12 +56,12 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"type": "markdown",
|
"type": "markdown",
|
||||||
"state": {
|
"state": {
|
||||||
"file": "Changing Factions and Immortal should clear out build.md",
|
"file": "Build Calculator CmdLine.md",
|
||||||
"mode": "source",
|
"mode": "source",
|
||||||
"source": false
|
"source": false
|
||||||
},
|
},
|
||||||
"icon": "lucide-file",
|
"icon": "lucide-file",
|
||||||
"title": "Changing Factions and Immortal should clear out build"
|
"title": "Build Calculator CmdLine"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -122,7 +122,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"direction": "horizontal",
|
"direction": "horizontal",
|
||||||
"width": 200
|
"width": 508.5
|
||||||
},
|
},
|
||||||
"right": {
|
"right": {
|
||||||
"id": "dd7c1dc4bd54d927",
|
"id": "dd7c1dc4bd54d927",
|
||||||
@@ -240,41 +240,44 @@
|
|||||||
"active": "b98a69cefb529fc8",
|
"active": "b98a69cefb529fc8",
|
||||||
"lastOpenFiles": [
|
"lastOpenFiles": [
|
||||||
"_Tasks Kanban.base",
|
"_Tasks Kanban.base",
|
||||||
"Helper Tutorial Info Improvements.md",
|
"Build Calculator CmdLine.md",
|
||||||
"Highest Alloy and Ether Tests.md",
|
"AI Help Docs/containerize-and-run.md",
|
||||||
"Army Display Split.md",
|
"AI Help Docs/publish-and-serve.md",
|
||||||
"Timeline Tests.md",
|
"AI Gen Docs/test-multi-context-entity-comparison.md",
|
||||||
"Army Calc UI.md",
|
"AI Gen Docs/services.md",
|
||||||
"Hotkey Tests.md",
|
"AI Gen Docs/recommendations.md",
|
||||||
"Entity Click View Tests.md",
|
"AI Gen Docs/development.md",
|
||||||
"Untitled.md",
|
"AI Gen Docs/architecture.md",
|
||||||
"Top Borders in Calculator should change based on Selected Faction and Immortal.md",
|
"Images/Pasted image 20260601083005.png",
|
||||||
"Make a Plan to Fully Test the Calculator.md",
|
"Images/Pasted image 20260601082954.png",
|
||||||
"Untitled 1.md",
|
"Tasks/Plan Calculator.md",
|
||||||
"Add a Timeline Editor.md",
|
"Tasks/Remove Items from anywhere in the build calc timeline.md",
|
||||||
"Worker Income UI and Tests.md",
|
"Tasks/Top Borders in Calculator should change based on Selected Faction and Immortal.md",
|
||||||
"More Wait Tests.md",
|
"Tasks/Update the Reference Tables with Telerik.md",
|
||||||
"Build Clear should clear out more stuff.md",
|
"Tasks/Worker Income UI and Tests.md",
|
||||||
"Changing Factions and Immortal should clear out build.md",
|
"Tasks/WebAssembly back to Azure.md",
|
||||||
"Input building delay should have an effect on when a building is built. Tests against 0, 2, 4, 60.md",
|
"Tasks/Spells are currently a production item in data. Make the a ability item so they don't show under production table.md",
|
||||||
"Ensure build order gets greyed out past the attack time. Clicking the cancel button will wipe the entire greyed out timeline..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"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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 |
|
||||||
@@ -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.
|
||||||
|
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,5 +0,0 @@
|
|||||||
---
|
|
||||||
type: Task
|
|
||||||
status:
|
|
||||||
category:
|
|
||||||
---
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
type: Task
|
|
||||||
status:
|
|
||||||
category:
|
|
||||||
---
|
|
||||||
@@ -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: []
|
||||||
@@ -56,8 +55,8 @@ views:
|
|||||||
- More Wait Tests.md
|
- More Wait Tests.md
|
||||||
- Timeline Tests.md
|
- Timeline Tests.md
|
||||||
Working On:
|
Working On:
|
||||||
- Helper Tutorial Info Improvements.md
|
|
||||||
- Changing Factions and Immortal should clear out build.md
|
- Changing Factions and Immortal should clear out build.md
|
||||||
|
- Helper Tutorial Info Improvements.md
|
||||||
- Highest Alloy and Ether Tests.md
|
- Highest Alloy and Ether Tests.md
|
||||||
Backlog:
|
Backlog:
|
||||||
- Fully Test the Build Calculator.md
|
- Fully Test the Build Calculator.md
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}`);
|
||||||
|
});
|
||||||