Code cleanup

This commit is contained in:
2026-05-27 10:51:19 -04:00
parent 5e486b0edb
commit 2423d232cf
48 changed files with 53731 additions and 46250 deletions
+2 -1
View File
@@ -10,10 +10,11 @@
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code { @code {
private int currentCount = 0; private int currentCount;
private void IncrementCount() private void IncrementCount()
{ {
currentCount++; currentCount++;
} }
} }
+2 -4
View File
@@ -1,4 +1,3 @@
using System;
using OpenQA.Selenium; using OpenQA.Selenium;
using OpenQA.Selenium.Chrome; using OpenQA.Selenium.Chrome;
@@ -10,10 +9,9 @@ public static class DriverFactory
{ {
var options = new ChromeOptions(); var options = new ChromeOptions();
var headless = Environment.GetEnvironmentVariable("HEADLESS"); var headless = Environment.GetEnvironmentVariable("HEADLESS");
if (!string.IsNullOrEmpty(headless) && (headless == "1" || headless.Equals("true", StringComparison.OrdinalIgnoreCase))) if (!string.IsNullOrEmpty(headless) &&
{ (headless == "1" || headless.Equals("true", StringComparison.OrdinalIgnoreCase)))
options.AddArgument("--headless=new"); options.AddArgument("--headless=new");
}
options.AddArgument("--disable-gpu"); options.AddArgument("--disable-gpu");
options.AddArgument("--no-sandbox"); options.AddArgument("--no-sandbox");
+6 -2
View File
@@ -5,11 +5,15 @@ namespace AOW4.SeleniumTests.Pages;
public class CounterPage public class CounterPage
{ {
private readonly IWebDriver _driver; private readonly IWebDriver _driver;
public CounterPage(IWebDriver driver) => _driver = driver;
public CounterPage(IWebDriver driver)
{
_driver = driver;
}
public bool IsAt() public bool IsAt()
{ {
var url = _driver.Url ?? string.Empty; var url = _driver.Url ?? string.Empty;
return url.Contains("counter", System.StringComparison.OrdinalIgnoreCase) || _driver.PageSource.Contains("Counter"); return url.Contains("counter", StringComparison.OrdinalIgnoreCase) || _driver.PageSource.Contains("Counter");
} }
} }
+7 -2
View File
@@ -5,11 +5,16 @@ namespace AOW4.SeleniumTests.Pages;
public class HomePage public class HomePage
{ {
private readonly IWebDriver _driver; private readonly IWebDriver _driver;
public HomePage(IWebDriver driver) => _driver = driver;
public HomePage(IWebDriver driver)
{
_driver = driver;
}
public bool IsAt() public bool IsAt()
{ {
var url = _driver.Url ?? string.Empty; var url = _driver.Url ?? string.Empty;
return url.EndsWith("/") || url.Contains("/index", System.StringComparison.OrdinalIgnoreCase) || _driver.PageSource.Contains("Home"); return url.EndsWith("/") || url.Contains("/index", StringComparison.OrdinalIgnoreCase) ||
_driver.PageSource.Contains("Home");
} }
} }
+4 -6
View File
@@ -1,4 +1,3 @@
using System.Linq;
using OpenQA.Selenium; using OpenQA.Selenium;
namespace AOW4.SeleniumTests.Pages; namespace AOW4.SeleniumTests.Pages;
@@ -15,12 +14,11 @@ public class NavMenuPage
public void ClickLinkByText(string linkText) public void ClickLinkByText(string linkText)
{ {
var link = _driver.FindElements(By.CssSelector("a[href]")) var link = _driver.FindElements(By.CssSelector("a[href]"))
.FirstOrDefault(e => !string.IsNullOrWhiteSpace(e.Text) && e.Text.Trim().Equals(linkText, System.StringComparison.OrdinalIgnoreCase)); .FirstOrDefault(e =>
!string.IsNullOrWhiteSpace(e.Text) &&
e.Text.Trim().Equals(linkText, StringComparison.OrdinalIgnoreCase));
if (link == null) if (link == null) throw new NoSuchElementException($"Link with text '{linkText}' not found in the page.");
{
throw new NoSuchElementException($"Link with text '{linkText}' not found in the page.");
}
link.Click(); link.Click();
} }
-15
View File
@@ -1,15 +0,0 @@
using OpenQA.Selenium;
namespace AOW4.SeleniumTests.Pages;
public class WeatherPage
{
private readonly IWebDriver _driver;
public WeatherPage(IWebDriver driver) => _driver = driver;
public bool IsAt()
{
var url = _driver.Url ?? string.Empty;
return url.Contains("weather", System.StringComparison.OrdinalIgnoreCase) || _driver.PageSource.Contains("Weather");
}
}
+2
View File
@@ -1,6 +1,7 @@
# AOW4 Selenium Tests # AOW4 Selenium Tests
Requirements: Requirements:
- .NET 10 SDK - .NET 10 SDK
- Google Chrome installed (compatible with ChromeDriver package) - Google Chrome installed (compatible with ChromeDriver package)
- The AOW4 web app running locally (by default at `http://localhost:5000`) or set `BASE_URL` env var. - The AOW4 web app running locally (by default at `http://localhost:5000`) or set `BASE_URL` env var.
@@ -16,5 +17,6 @@ dotnet test AOW4.SeleniumTests\AOW4.SeleniumTests.csproj
``` ```
Notes: Notes:
- Navigation tests use the UI nav links — ensure the app is running before executing tests. - Navigation tests use the UI nav links — ensure the app is running before executing tests.
- Broken links scanner sends HTTP HEAD requests and falls back to GET if needed. - Broken links scanner sends HTTP HEAD requests and falls back to GET if needed.
+4 -4
View File
@@ -1,15 +1,12 @@
using AOW4.SeleniumTests.Driver;
using NUnit.Framework; using NUnit.Framework;
using OpenQA.Selenium; using OpenQA.Selenium;
using AOW4.SeleniumTests.Driver;
namespace AOW4.SeleniumTests.Tests; namespace AOW4.SeleniumTests.Tests;
[TestFixture] [TestFixture]
public abstract class BaseTest public abstract class BaseTest
{ {
protected IWebDriver Driver = null!;
protected string BaseUrl => "http://localhost:5212/";
[OneTimeSetUp] [OneTimeSetUp]
public void GlobalSetup() public void GlobalSetup()
{ {
@@ -29,6 +26,9 @@ public abstract class BaseTest
} }
} }
protected IWebDriver Driver = null!;
protected string BaseUrl => "http://localhost:5212/";
protected void GoHome() protected void GoHome()
{ {
Driver.Navigate().GoToUrl(BaseUrl); Driver.Navigate().GoToUrl(BaseUrl);
+8 -20
View File
@@ -1,7 +1,4 @@
using NUnit.Framework; using NUnit.Framework;
using System.Net.Http;
using System.Collections.Generic;
using System.Linq;
using OpenQA.Selenium; using OpenQA.Selenium;
namespace AOW4.SeleniumTests.Tests; namespace AOW4.SeleniumTests.Tests;
@@ -25,19 +22,16 @@ public class BrokenLinksTest : BaseTest
foreach (var raw in anchors) foreach (var raw in anchors)
{ {
if (raw.StartsWith("javascript:", System.StringComparison.OrdinalIgnoreCase)) if (raw.StartsWith("javascript:", StringComparison.OrdinalIgnoreCase))
continue; continue;
if (raw.StartsWith("mailto:", System.StringComparison.OrdinalIgnoreCase)) if (raw.StartsWith("mailto:", StringComparison.OrdinalIgnoreCase))
continue; continue;
System.Uri uri; Uri uri;
try try
{ {
uri = new System.Uri(raw, System.UriKind.RelativeOrAbsolute); uri = new Uri(raw, UriKind.RelativeOrAbsolute);
if (!uri.IsAbsoluteUri) if (!uri.IsAbsoluteUri) uri = new Uri(new Uri(BaseUrl), raw);
{
uri = new System.Uri(new System.Uri(BaseUrl), raw);
}
} }
catch catch
{ {
@@ -54,21 +48,15 @@ public class BrokenLinksTest : BaseTest
// try GET as fallback // try GET as fallback
using var greq = new HttpRequestMessage(HttpMethod.Get, uri); using var greq = new HttpRequestMessage(HttpMethod.Get, uri);
var gresp = client.Send(greq); var gresp = client.Send(greq);
if (!gresp.IsSuccessStatusCode) if (!gresp.IsSuccessStatusCode) failures.Add($"{(int)gresp.StatusCode} {uri}");
{
failures.Add($"{(int)gresp.StatusCode} {uri}");
} }
} }
} catch (Exception ex)
catch (System.Exception ex)
{ {
failures.Add($"Error checking {uri}: {ex.Message}"); failures.Add($"Error checking {uri}: {ex.Message}");
} }
} }
if (failures.Any()) if (failures.Any()) Assert.Fail("Broken links found:\n" + string.Join("\n", failures));
{
Assert.Fail("Broken links found:\n" + string.Join("\n", failures));
}
} }
} }
+3 -2
View File
@@ -1,5 +1,4 @@
using NUnit.Framework; using NUnit.Framework;
using AOW4.SeleniumTests.Pages;
namespace AOW4.SeleniumTests.Tests; namespace AOW4.SeleniumTests.Tests;
@@ -11,7 +10,9 @@ public class NavigationTests : BaseTest
{ {
GoHome(); GoHome();
Assert.IsTrue(Driver.Url.Contains(expectedPath, System.StringComparison.OrdinalIgnoreCase) || Driver.PageSource.Contains(linkText), Assert.IsTrue(
Driver.Url.Contains(expectedPath, StringComparison.OrdinalIgnoreCase) ||
Driver.PageSource.Contains(linkText),
$"Expected to be on route containing '{expectedPath}' after clicking '{linkText}', but was '{Driver.Url}'"); $"Expected to be on route containing '{expectedPath}' after clicking '{linkText}', but was '{Driver.Url}'");
} }
} }
-42
View File
@@ -1,42 +0,0 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">AOW4</a>
</div>
</div>
<input type="checkbox" title="Navigation menu" class="navbar-toggler" />
<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
<nav class="nav flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Counter
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="weather">
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Weather
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="/references/magic-materials">
<span class="bi bi-book-fill-nav-menu" aria-hidden="true"></span> Magic Materials
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="/references/province-improvements">
<span class="bi bi-buildings-nav-menu" aria-hidden="true"></span> Province Improvements
</NavLink>
</div>
</nav>
</div>
-105
View File
@@ -1,105 +0,0 @@
.navbar-toggler {
appearance: none;
cursor: pointer;
width: 3.5rem;
height: 2.5rem;
color: white;
position: absolute;
top: 0.5rem;
right: 1rem;
border: 1px solid rgba(255, 255, 255, 0.1);
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1);
}
.navbar-toggler:checked {
background-color: rgba(255, 255, 255, 0.5);
}
.top-row {
min-height: 3.5rem;
background-color: rgba(0,0,0,0.4);
}
.navbar-brand {
font-size: 1.1rem;
}
.bi {
display: inline-block;
position: relative;
width: 1.25rem;
height: 1.25rem;
margin-right: 0.75rem;
top: -1px;
background-size: cover;
}
.bi-house-door-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
}
.bi-plus-square-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
}
.bi-list-nested-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
}
.nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.nav-item:first-of-type {
padding-top: 1rem;
}
.nav-item:last-of-type {
padding-bottom: 1rem;
}
.nav-item ::deep .nav-link {
color: #d7d7d7;
background: none;
border: none;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
width: 100%;
}
.nav-item ::deep a.active {
background-color: rgba(255,255,255,0.37);
color: white;
}
.nav-item ::deep .nav-link:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
.nav-scrollable {
display: none;
}
.navbar-toggler:checked ~ .nav-scrollable {
display: block;
}
@media (min-width: 641px) {
.navbar-toggler {
display: none;
}
.nav-scrollable {
/* Never collapse the sidebar for wide screens */
display: block;
/* Allow sidebar to scroll for tall menus */
height: calc(100vh - 3.5rem);
overflow-y: auto;
}
}
@@ -68,12 +68,13 @@
protected override void OnInitialized() protected override void OnInitialized()
{ {
Result = BuildingPlanCalculator.CreateSampleBuildPlan(60); Result = BuildingPlanCalculator.CreateSampleBuildPlan();
Json = JsonSerializer.Serialize(Result, new JsonSerializerOptions Json = JsonSerializer.Serialize(Result, new JsonSerializerOptions
{ {
WriteIndented = true WriteIndented = true
}); });
} }
} }
<style> <style>
+9 -5
View File
@@ -15,22 +15,26 @@
<h3>Development Mode</h3> <h3>Development Mode</h3>
<p> <p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred. Swapping to <strong>Development</strong> environment will display more detailed information about the error that
occurred.
</p> </p>
<p> <p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong> <strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users. It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong> For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong>
environment variable to <strong>Development</strong>
and restarting the app. and restarting the app.
</p> </p>
@code{ @code{
[CascadingParameter] [CascadingParameter] private HttpContext? HttpContext { get; set; }
private HttpContext? HttpContext { get; set; }
private string? RequestId { get; set; } private string? RequestId { get; set; }
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId); private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
protected override void OnInitialized() => protected override void OnInitialized()
{
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
} }
}
+1
View File
@@ -52,6 +52,7 @@
{ {
sections = SectionsData.GetAllSections(); sections = SectionsData.GetAllSections();
} }
} }
<style> <style>
@@ -26,9 +26,15 @@
<td>@material.Category</td> <td>@material.Category</td>
<td>@FormatAnnexResources(material)</td> <td>@FormatAnnexResources(material)</td>
<td>@material.GlobalBonus</td> <td>@material.GlobalBonus</td>
<td><div class="preformatted">@material.InfusionEffects1</div></td> <td>
<td><div class="preformatted">@material.InfusionEffects2</div></td> <div class="preformatted">@material.InfusionEffects1</div>
<td><div class="preformatted">@material.InfusionEffects3</div></td> </td>
<td>
<div class="preformatted">@material.InfusionEffects2</div>
</td>
<td>
<div class="preformatted">@material.InfusionEffects3</div>
</td>
</tr> </tr>
} }
</tbody> </tbody>
@@ -37,6 +43,7 @@
</div> </div>
@code { @code {
private static string FormatAnnexResources(MagicMaterial material) private static string FormatAnnexResources(MagicMaterial material)
{ {
var parts = new List<string>(); var parts = new List<string>();
@@ -72,6 +79,7 @@
return parts.Count > 0 ? string.Join("; ", parts) : "—"; return parts.Count > 0 ? string.Join("; ", parts) : "—";
} }
} }
<style> <style>
@@ -3,7 +3,8 @@
<div class="page-container"> <div class="page-container">
<h1>Province Improvements Reference</h1> <h1>Province Improvements Reference</h1>
<p class="subtitle">A reference view of the `ProvinceImprovement` data loaded from `ProvinceImprovementsData.RawData`.</p> <p class="subtitle">A reference view of the `ProvinceImprovement` data loaded from
`ProvinceImprovementsData.RawData`.</p>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped table-hover"> <table class="table table-striped table-hover">
@@ -25,8 +26,12 @@
<td>@improvement.Category</td> <td>@improvement.Category</td>
<td>@improvement.Source</td> <td>@improvement.Source</td>
<td>@improvement.CostProduction / @improvement.CostGold</td> <td>@improvement.CostProduction / @improvement.CostGold</td>
<td><div class="preformatted">@improvement.Effects</div></td> <td>
<td><div class="preformatted">@improvement.Requirements</div></td> <div class="preformatted">@improvement.Effects</div>
</td>
<td>
<div class="preformatted">@improvement.Requirements</div>
</td>
</tr> </tr>
} }
</tbody> </tbody>
-64
View File
@@ -1,64 +0,0 @@
@page "/weather"
@attribute [StreamRendering]
<PageTitle>Weather</PageTitle>
<h1>Weather</h1>
<p>This component demonstrates showing data.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th aria-label="Temperature in Celsius">Temp. (C)</th>
<th aria-label="Temperature in Fahrenheit">Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
// Simulate asynchronous loading to demonstrate streaming rendering
await Task.Delay(500);
var startDate = DateOnly.FromDateTime(DateTime.Now);
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
}
private class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}
+4 -2
View File
@@ -1,6 +1,8 @@
<Router AppAssembly="typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(Client._Imports).Assembly }" NotFoundPage="typeof(Pages.NotFound)"> @using AOW4.Components.Pages
<Router AppAssembly="typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(Client._Imports).Assembly }"
NotFoundPage="typeof(NotFound)">
<Found Context="routeData"> <Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" /> <RouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)"/>
<FocusOnNavigate RouteData="routeData" Selector="h1"/> <FocusOnNavigate RouteData="routeData" Selector="h1"/>
</Found> </Found>
</Router> </Router>
+16 -21
View File
@@ -14,7 +14,8 @@ public sealed class ResourceAmounts
public int Stability { get; set; } public int Stability { get; set; }
[JsonIgnore] [JsonIgnore]
public bool IsZero => Draft == 0 && Food == 0 && Knowledge == 0 && Industry == 0 && Magic == 0 && Gold == 0 && Imperial == 0 && Stability == 0; public bool IsZero => Draft == 0 && Food == 0 && Knowledge == 0 && Industry == 0 && Magic == 0 && Gold == 0 &&
Imperial == 0 && Stability == 0;
public void Add(ResourceAmounts other) public void Add(ResourceAmounts other)
{ {
@@ -41,7 +42,8 @@ public sealed class ResourceAmounts
} }
public static ResourceAmounts operator +(ResourceAmounts a, ResourceAmounts b) public static ResourceAmounts operator +(ResourceAmounts a, ResourceAmounts b)
=> new ResourceAmounts {
return new ResourceAmounts
{ {
Draft = a.Draft + b.Draft, Draft = a.Draft + b.Draft,
Food = a.Food + b.Food, Food = a.Food + b.Food,
@@ -52,9 +54,11 @@ public sealed class ResourceAmounts
Imperial = a.Imperial + b.Imperial, Imperial = a.Imperial + b.Imperial,
Stability = a.Stability + b.Stability Stability = a.Stability + b.Stability
}; };
}
public static ResourceAmounts operator -(ResourceAmounts a, ResourceAmounts b) public static ResourceAmounts operator -(ResourceAmounts a, ResourceAmounts b)
=> new ResourceAmounts {
return new ResourceAmounts
{ {
Draft = a.Draft - b.Draft, Draft = a.Draft - b.Draft,
Food = a.Food - b.Food, Food = a.Food - b.Food,
@@ -66,6 +70,7 @@ public sealed class ResourceAmounts
Stability = a.Stability - b.Stability Stability = a.Stability - b.Stability
}; };
} }
}
public sealed class BuildingDefinition public sealed class BuildingDefinition
{ {
@@ -137,7 +142,6 @@ public static class BuildingPlanCalculator
var stored = new ResourceAmounts(); var stored = new ResourceAmounts();
if (startingResources is not null) if (startingResources is not null)
{
stored = new ResourceAmounts stored = new ResourceAmounts
{ {
Draft = startingResources.Draft, Draft = startingResources.Draft,
@@ -149,10 +153,8 @@ public static class BuildingPlanCalculator
Imperial = startingResources.Imperial, Imperial = startingResources.Imperial,
Stability = startingResources.Stability Stability = startingResources.Stability
}; };
}
if (startingBuildings is not null) if (startingBuildings is not null)
{
foreach (var startingBuilding in startingBuildings) foreach (var startingBuilding in startingBuildings)
{ {
var entry = new BuildOrderEntry var entry = new BuildOrderEntry
@@ -168,7 +170,6 @@ public static class BuildingPlanCalculator
activeBuildings.Add(entry); activeBuildings.Add(entry);
result.BuildOrder.Add(entry); result.BuildOrder.Add(entry);
} }
}
var requestsByTurn = buildRequests var requestsByTurn = buildRequests
.OrderBy(r => r.RequestedTurn) .OrderBy(r => r.RequestedTurn)
@@ -183,19 +184,16 @@ public static class BuildingPlanCalculator
var upkeepThisTurn = new ResourceAmounts(); var upkeepThisTurn = new ResourceAmounts();
foreach (var building in activeBuildings) foreach (var building in activeBuildings)
{
if (building.BuiltFinishTurn == 0 || turn > building.BuiltFinishTurn) if (building.BuiltFinishTurn == 0 || turn > building.BuiltFinishTurn)
{ {
incomeThisTurn.Add(building.Definition.Income); incomeThisTurn.Add(building.Definition.Income);
upkeepThisTurn.Add(building.Definition.Upkeep); upkeepThisTurn.Add(building.Definition.Upkeep);
} }
}
stored.Add(incomeThisTurn); stored.Add(incomeThisTurn);
stored.Subtract(upkeepThisTurn); stored.Subtract(upkeepThisTurn);
if (requestsByTurn.TryGetValue(turn, out var requests)) if (requestsByTurn.TryGetValue(turn, out var requests))
{
foreach (var request in requests) foreach (var request in requests)
{ {
var nextProject = new BuildOrderEntry var nextProject = new BuildOrderEntry
@@ -223,15 +221,11 @@ public static class BuildingPlanCalculator
projects.Add(nextProject); projects.Add(nextProject);
result.BuildOrder.Add(nextProject); result.BuildOrder.Add(nextProject);
} }
}
var availableIndustry = incomeThisTurn.Industry; var availableIndustry = incomeThisTurn.Industry;
foreach (var project in projects.Where(p => p.BuiltFinishTurn == 0).OrderBy(p => p.RequestedTurn)) foreach (var project in projects.Where(p => p.BuiltFinishTurn == 0).OrderBy(p => p.RequestedTurn))
{ {
if (availableIndustry <= 0 || project.IndustryCostRemaining <= 0) if (availableIndustry <= 0 || project.IndustryCostRemaining <= 0) continue;
{
continue;
}
var applied = Math.Min(availableIndustry, project.IndustryCostRemaining); var applied = Math.Min(availableIndustry, project.IndustryCostRemaining);
project.IndustryCostRemaining -= applied; project.IndustryCostRemaining -= applied;
@@ -271,7 +265,7 @@ public static class BuildingPlanCalculator
{ {
return new List<BuildingDefinition> return new List<BuildingDefinition>
{ {
new BuildingDefinition new()
{ {
Id = "town-hall-1", Id = "town-hall-1",
Name = "Town Hall I", Name = "Town Hall I",
@@ -288,7 +282,7 @@ public static class BuildingPlanCalculator
Upkeep = new ResourceAmounts(), Upkeep = new ResourceAmounts(),
Cost = new ResourceAmounts() Cost = new ResourceAmounts()
}, },
new BuildingDefinition new()
{ {
Id = "throne", Id = "throne",
Name = "Throne", Name = "Throne",
@@ -308,7 +302,7 @@ public static class BuildingPlanCalculator
{ {
var sampleRequests = new List<BuildOrderRequest> var sampleRequests = new List<BuildOrderRequest>
{ {
new BuildOrderRequest new()
{ {
ItemId = "farm-1", ItemId = "farm-1",
Definition = new BuildingDefinition Definition = new BuildingDefinition
@@ -323,7 +317,7 @@ public static class BuildingPlanCalculator
}, },
RequestedTurn = 2 RequestedTurn = 2
}, },
new BuildOrderRequest new()
{ {
ItemId = "workshop-1", ItemId = "workshop-1",
Definition = new BuildingDefinition Definition = new BuildingDefinition
@@ -338,7 +332,7 @@ public static class BuildingPlanCalculator
}, },
RequestedTurn = 5 RequestedTurn = 5
}, },
new BuildOrderRequest new()
{ {
ItemId = "market-1", ItemId = "market-1",
Definition = new BuildingDefinition Definition = new BuildingDefinition
@@ -355,6 +349,7 @@ public static class BuildingPlanCalculator
} }
}; };
return CalculateBuildPlan(totalTurns, sampleRequests, GetDefaultStartingBuildings(), new ResourceAmounts { Gold = 0 }); return CalculateBuildPlan(totalTurns, sampleRequests, GetDefaultStartingBuildings(),
new ResourceAmounts { Gold = 0 });
} }
} }
+2 -1
View File
@@ -17,7 +17,8 @@ public class ProvinceImprovement
public required string Category { get; set; } public required string Category { get; set; }
/// <summary> /// <summary>
/// A description of the effects this improvement provides (resource bonuses, adjacency bonuses, special mechanics, etc.). /// A description of the effects this improvement provides (resource bonuses, adjacency bonuses, special mechanics,
/// etc.).
/// </summary> /// </summary>
public required string Effects { get; set; } public required string Effects { get; set; }
+2 -1
View File
@@ -225,7 +225,8 @@ public static class ProvinceImprovementsData
+10 Mana income. +10 Mana income.
+5 Mana per adjacent Conduit or Research Post. +5 Mana per adjacent Conduit or Research Post.
""", """,
Requirements = "Must be built on an acquired Province.\nRequires City Tier 2.\nRequires Town Hall II: Mage's Plaza", Requirements =
"Must be built on an acquired Province.\nRequires City Tier 2.\nRequires Town Hall II: Mage's Plaza",
Source = "Mystic - School of Summoning", Source = "Mystic - School of Summoning",
CostProduction = 130, CostProduction = 130,
CostGold = 60 CostGold = 60
+10 -12
View File
@@ -1,25 +1,23 @@
using System.Collections.Generic;
namespace AOW4.Data; namespace AOW4.Data;
public static class ResourceNodesData public static class ResourceNodesData
{ {
public static readonly IReadOnlyList<ResourceNode> RawData = new List<ResourceNode> public static readonly IReadOnlyList<ResourceNode> RawData = new List<ResourceNode>
{ {
new ResourceNode new()
{ {
Name = "Pastures", Name = "Pastures",
Description = "Roaming herds on lush fields.", Description = "Roaming herds on lush fields.",
IncreaseFood = 10, IncreaseFood = 10,
ForceEnableFarm = true ForceEnableFarm = true
}, },
new ResourceNode new()
{ {
Name = "Oasis", Name = "Oasis",
Description = "A lush oasis full of nutritious food.", Description = "A lush oasis full of nutritious food.",
IncreaseFood = 10 IncreaseFood = 10
}, },
new ResourceNode new()
{ {
Name = "Iron Deposit", Name = "Iron Deposit",
Description = "A rich vein full of ore.", Description = "A rich vein full of ore.",
@@ -27,14 +25,14 @@ public static class ResourceNodesData
ForceEnableMine = true, ForceEnableMine = true,
ForceEnableQuarry = true ForceEnableQuarry = true
}, },
new ResourceNode new()
{ {
Name = "Gold Vein", Name = "Gold Vein",
Description = "A large vein of valuable gold.", Description = "A large vein of valuable gold.",
IncreaseGold = 10, IncreaseGold = 10,
ForceEnableMine = true ForceEnableMine = true
}, },
new ResourceNode new()
{ {
Name = "Mana Node", Name = "Mana Node",
Description = "Magical currents converge at this location.", Description = "Magical currents converge at this location.",
@@ -42,13 +40,13 @@ public static class ResourceNodesData
ForceEnableConduit = true, ForceEnableConduit = true,
ForceEnableResearchPost = true ForceEnableResearchPost = true
}, },
new ResourceNode new()
{ {
Name = "Fishing Ground", Name = "Fishing Ground",
Description = "A plentiful source of fish.", Description = "A plentiful source of fish.",
IncreaseFood = 15 IncreaseFood = 15
}, },
new ResourceNode new()
{ {
Name = "Pearl Reef", Name = "Pearl Reef",
Description = "A bloom of valuable pearls.", Description = "A bloom of valuable pearls.",
@@ -57,19 +55,19 @@ public static class ResourceNodesData
ForceEnableMine = true, ForceEnableMine = true,
ForceEnableConduit = true ForceEnableConduit = true
}, },
new ResourceNode new()
{ {
Name = "Chitinous Growths", Name = "Chitinous Growths",
Description = "These grotesque growths deposit valuable liquid and ore at unnatural speed.", Description = "These grotesque growths deposit valuable liquid and ore at unnatural speed.",
IncreaseGold = 30 IncreaseGold = 30
}, },
new ResourceNode new()
{ {
Name = "Monoliths", Name = "Monoliths",
Description = "There is writing in an unknown language on these obsidian giants.", Description = "There is writing in an unknown language on these obsidian giants.",
IncreaseKnowledge = 30 IncreaseKnowledge = 30
}, },
new ResourceNode new()
{ {
Name = "Blossom Orchard", Name = "Blossom Orchard",
Description = "Eternally blooming trees offering bounty of fruit and wood.", Description = "Eternally blooming trees offering bounty of fruit and wood.",
+8 -7
View File
@@ -6,13 +6,13 @@ public static class SectionsData
{ {
return new List<Section> return new List<Section>
{ {
new Section new()
{ {
Name = "Calculators", Name = "Calculators",
Description = "Useful calculator tools for various computations", Description = "Useful calculator tools for various computations",
Links = new List<SectionLink> Links = new List<SectionLink>
{ {
new SectionLink new()
{ {
Title = "Building Plan Calculator", Title = "Building Plan Calculator",
Url = "/building-calculator", Url = "/building-calculator",
@@ -20,27 +20,28 @@ public static class SectionsData
} }
} }
}, },
new Section new()
{ {
Name = "References", Name = "References",
Description = "Reference materials and documentation", Description = "Reference materials and documentation",
Links = new List<SectionLink> Links = new List<SectionLink>
{ {
new SectionLink new()
{ {
Title = "Magic Materials Reference", Title = "Magic Materials Reference",
Url = "/references/magic-materials", Url = "/references/magic-materials",
Description = "View the magic material dataset and bonuses in a reference table." Description = "View the magic material dataset and bonuses in a reference table."
}, },
new SectionLink new()
{ {
Title = "Province Improvements Reference", Title = "Province Improvements Reference",
Url = "/references/province-improvements", Url = "/references/province-improvements",
Description = "View the province improvements dataset including costs, effects, and requirements." Description =
"View the province improvements dataset including costs, effects, and requirements."
} }
} }
}, },
new Section new()
{ {
Name = "Learning", Name = "Learning",
Description = "Educational resources and learning materials", Description = "Educational resources and learning materials",
+6 -4
View File
@@ -1,5 +1,5 @@
using AOW4.Client.Pages;
using AOW4.Components; using AOW4.Components;
using _Imports = AOW4.Client._Imports;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@@ -13,12 +13,14 @@ var app = builder.Build();
if (app.Environment.IsDevelopment()) if (app.Environment.IsDevelopment())
{ {
app.UseWebAssemblyDebugging(); app.UseWebAssemblyDebugging();
} else }
else
{ {
app.UseExceptionHandler("/Error", createScopeForErrors: true); app.UseExceptionHandler("/Error", true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts(); app.UseHsts();
} }
app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true); app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
app.UseHttpsRedirection(); app.UseHttpsRedirection();
@@ -27,6 +29,6 @@ app.UseAntiforgery();
app.MapStaticAssets(); app.MapStaticAssets();
app.MapRazorComponents<App>() app.MapRazorComponents<App>()
.AddInteractiveWebAssemblyRenderMode() .AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(AOW4.Client._Imports).Assembly); .AddAdditionalAssemblies(typeof(_Imports).Assembly);
app.Run(); app.Run();
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -224,6 +224,7 @@ h6, h5, h4, h3, h2, h1 {
h1 { h1 {
font-size: calc(1.375rem + 1.5vw); font-size: calc(1.375rem + 1.5vw);
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
h1 { h1 {
font-size: 2.5rem; font-size: 2.5rem;
@@ -233,6 +234,7 @@ h1 {
h2 { h2 {
font-size: calc(1.325rem + 0.9vw); font-size: calc(1.325rem + 0.9vw);
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
h2 { h2 {
font-size: 2rem; font-size: 2rem;
@@ -242,6 +244,7 @@ h2 {
h3 { h3 {
font-size: calc(1.3rem + 0.6vw); font-size: calc(1.3rem + 0.6vw);
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
h3 { h3 {
font-size: 1.75rem; font-size: 1.75rem;
@@ -251,6 +254,7 @@ h3 {
h4 { h4 {
font-size: calc(1.275rem + 0.3vw); font-size: calc(1.275rem + 0.3vw);
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
h4 { h4 {
font-size: 1.5rem; font-size: 1.5rem;
@@ -351,6 +355,7 @@ a {
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1)); color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
text-decoration: underline; text-decoration: underline;
} }
a:hover { a:hover {
--bs-link-color-rgb: var(--bs-link-hover-color-rgb); --bs-link-color-rgb: var(--bs-link-hover-color-rgb);
} }
@@ -375,6 +380,7 @@ pre {
overflow: auto; overflow: auto;
font-size: 0.875em; font-size: 0.875em;
} }
pre code { pre code {
font-size: inherit; font-size: inherit;
color: inherit; color: inherit;
@@ -386,6 +392,7 @@ code {
color: var(--bs-code-color); color: var(--bs-code-color);
word-wrap: break-word; word-wrap: break-word;
} }
a > code { a > code {
color: inherit; color: inherit;
} }
@@ -397,6 +404,7 @@ kbd {
background-color: var(--bs-body-color); background-color: var(--bs-body-color);
border-radius: 0.25rem; border-radius: 0.25rem;
} }
kbd kbd { kbd kbd {
padding: 0; padding: 0;
font-size: 1em; font-size: 1em;
@@ -474,6 +482,7 @@ select {
select { select {
word-wrap: normal; word-wrap: normal;
} }
select:disabled { select:disabled {
opacity: 1; opacity: 1;
} }
@@ -488,6 +497,7 @@ button,
[type=submit] { [type=submit] {
-webkit-appearance: button; -webkit-appearance: button;
} }
button:not(:disabled), button:not(:disabled),
[type=button]:not(:disabled), [type=button]:not(:disabled),
[type=reset]:not(:disabled), [type=reset]:not(:disabled),
@@ -519,11 +529,13 @@ legend {
font-size: calc(1.275rem + 0.3vw); font-size: calc(1.275rem + 0.3vw);
line-height: inherit; line-height: inherit;
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
legend { legend {
font-size: 1.5rem; font-size: 1.5rem;
} }
} }
legend + * { legend + * {
clear: left; clear: left;
} }
@@ -224,6 +224,7 @@ h6, h5, h4, h3, h2, h1 {
h1 { h1 {
font-size: calc(1.375rem + 1.5vw); font-size: calc(1.375rem + 1.5vw);
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
h1 { h1 {
font-size: 2.5rem; font-size: 2.5rem;
@@ -233,6 +234,7 @@ h1 {
h2 { h2 {
font-size: calc(1.325rem + 0.9vw); font-size: calc(1.325rem + 0.9vw);
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
h2 { h2 {
font-size: 2rem; font-size: 2rem;
@@ -242,6 +244,7 @@ h2 {
h3 { h3 {
font-size: calc(1.3rem + 0.6vw); font-size: calc(1.3rem + 0.6vw);
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
h3 { h3 {
font-size: 1.75rem; font-size: 1.75rem;
@@ -251,6 +254,7 @@ h3 {
h4 { h4 {
font-size: calc(1.275rem + 0.3vw); font-size: calc(1.275rem + 0.3vw);
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
h4 { h4 {
font-size: 1.5rem; font-size: 1.5rem;
@@ -351,6 +355,7 @@ a {
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1)); color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
text-decoration: underline; text-decoration: underline;
} }
a:hover { a:hover {
--bs-link-color-rgb: var(--bs-link-hover-color-rgb); --bs-link-color-rgb: var(--bs-link-hover-color-rgb);
} }
@@ -375,6 +380,7 @@ pre {
overflow: auto; overflow: auto;
font-size: 0.875em; font-size: 0.875em;
} }
pre code { pre code {
font-size: inherit; font-size: inherit;
color: inherit; color: inherit;
@@ -386,6 +392,7 @@ code {
color: var(--bs-code-color); color: var(--bs-code-color);
word-wrap: break-word; word-wrap: break-word;
} }
a > code { a > code {
color: inherit; color: inherit;
} }
@@ -397,6 +404,7 @@ kbd {
background-color: var(--bs-body-color); background-color: var(--bs-body-color);
border-radius: 0.25rem; border-radius: 0.25rem;
} }
kbd kbd { kbd kbd {
padding: 0; padding: 0;
font-size: 1em; font-size: 1em;
@@ -474,6 +482,7 @@ select {
select { select {
word-wrap: normal; word-wrap: normal;
} }
select:disabled { select:disabled {
opacity: 1; opacity: 1;
} }
@@ -488,6 +497,7 @@ button,
[type=submit] { [type=submit] {
-webkit-appearance: button; -webkit-appearance: button;
} }
button:not(:disabled), button:not(:disabled),
[type=button]:not(:disabled), [type=button]:not(:disabled),
[type=reset]:not(:disabled), [type=reset]:not(:disabled),
@@ -519,11 +529,13 @@ legend {
font-size: calc(1.275rem + 0.3vw); font-size: calc(1.275rem + 0.3vw);
line-height: inherit; line-height: inherit;
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
legend { legend {
font-size: 1.5rem; font-size: 1.5rem;
} }
} }
legend + * { legend + * {
clear: right; clear: right;
} }
@@ -553,6 +565,7 @@ legend + * {
[type="number"] { [type="number"] {
direction: ltr; direction: ltr;
} }
::-webkit-search-decoration { ::-webkit-search-decoration {
-webkit-appearance: none; -webkit-appearance: none;
} }
@@ -591,4 +604,5 @@ progress {
[hidden] { [hidden] {
display: none !important; display: none !important;
} }
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */ /*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff