CLI and Publish Tests
@@ -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/
|
||||
@@ -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
|
||||
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
|
||||
|
||||
@@ -48,7 +48,7 @@ public class BuildOrderModel
|
||||
new List<EntityModel>
|
||||
{
|
||||
EntityModel.Get(DataType.STARTING_Bastion),
|
||||
EntityModel.Get(DataType.STARTING_TownHall_Aru)
|
||||
EntityModel.Get(factionStartingTownHall)
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -59,7 +59,7 @@ public class BuildOrderModel
|
||||
new List<EntityModel>
|
||||
{
|
||||
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<string, int>
|
||||
@@ -78,7 +78,7 @@ public class BuildOrderModel
|
||||
DataType.STARTING_Bastion, 1
|
||||
},
|
||||
{
|
||||
DataType.STARTING_TownHall_Aru, 1
|
||||
factionStartingTownHall, 1
|
||||
}
|
||||
};
|
||||
SupplyCountTimes = new Dictionary<int, int>
|
||||
|
||||
@@ -291,6 +291,7 @@ public interface IBuildOrderService
|
||||
|
||||
public void RemoveLast();
|
||||
public void Reset();
|
||||
public void Reset(string faction);
|
||||
|
||||
public int GetLastRequestInterval();
|
||||
public string BuildOrderAsYaml();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
|
||||
@@ -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
|
||||
- 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
|
||||
|
||||
@@ -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}`);
|
||||
});
|
||||