Initial Commit

This commit is contained in:
2026-05-29 14:17:46 -04:00
commit b7d0676d5b
498 changed files with 30308 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
@inject IStorageService StorageService
@inject IPermissionService PermissionService
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
@if (isLoaded)
{
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
}
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
<EntityDialogPortal/>
<ToastPortal/>
<SearchPortal/>
<ConfirmationDialogPortal/>
@if (PermissionService.GetIsDataCollectionEnabled())
{
<NavigationTracker/>
}
<style>
a {
color: white;
font-weight: 700;
}
a:hover {
color: white;
text-decoration: underline;
text-decoration-color: #8fc5ff;
text-decoration-thickness: 3px;
}
:root {
--severity-warning-color: #2a2000;
--severity-warning-border-color: #755c13;
--severity-error-color: #290102;
--severity-error-border-color: #4C2C33;
--severity-information-color: #030129;
--severity-information-border-color: #2c3a4c;
--severity-success-color: #042901;
--severity-success-border-color: #2E4C2C;
--accent: #432462;
--primary: #4308a3;
--primary-border: #2c0b62;
--primary-hover: #5e00f7;
--primary-border-hover: #a168ff;
--background: #161618;
--secondary: #23133e;
--secondary-hover: #2a0070;
--secondary-border-hover: #a168ff;
--paper: #252526;
--paper-border: #151516;
--paper-hover: #52366f;
--paper-border-hover: #653497;
--info: #451376;
--info-border: #210b36;
--dialog-border-color: black;
--dialog-border-width: 2px;
--dialog-radius: 6px;
}
</style>
@code {
private bool isLoaded;
protected override async Task OnInitializedAsync()
{
await StorageService.Load();
isLoaded = true;
StateHasChanged();
}
}
@@ -0,0 +1,116 @@
@implements IDisposable;
@inject IMyDialogService MyDialogService
@inject IJSRuntime JsRuntime
@inject NavigationManager NavigationManager
@if (MyDialogService.IsVisible)
{
<div class="confirmDialogBackground" onclick="@CloseDialog">
<div class="confirmDialogContainer"
@onclick:preventDefault="true"
@onclick:stopPropagation="true">
<div class="confirmDialogHeader">
@MyDialogService.GetDialogContents().Title
</div>
<div class="confirmDialogBody">
@MyDialogService.GetDialogContents().Message
</div>
<div class="confirmDialogFooter">
<ButtonComponent MyButtonType="MyButtonType.Secondary"
OnClick="MyDialogService.GetDialogContents().OnCancel">
Cancel
</ButtonComponent>
<ButtonComponent MyButtonType="MyButtonType.Primary"
OnClick="MyDialogService.GetDialogContents().OnConfirm">
@MyDialogService.GetDialogContents().ConfirmButtonLabel
</ButtonComponent>
</div>
</div>
</div>
<style>
.pageContents * {
filter: blur(2px);
}
.confirmDialogBackground {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
}
.confirmDialogContainer {
margin-left: auto;
margin-right: auto;
margin-top: 64px;
width: 800px;
height: 600px;
background-color: var(--background);
border-width: var(--dialog-border-width);
border-style: solid;
border-color: var(--dialog-border-color);
border-radius: var(--dialog-radius);
padding: 8px;
box-shadow: 1px 2px 2px black;
display: flex;
flex-direction: column;
}
.confirmDialogHeader {
font-size: 1.4em;
padding: 12px;
}
.confirmDialogBody {
padding: 12px;
flex-grow: 1;
}
.confirmDialogFooter {
display: flex;
gap: 12px;
justify-content: flex-end;
padding: 12px;
}
</style>
}
@code {
protected override void OnInitialized()
{
base.OnInitialized();
MyDialogService.Subscribe(StateHasChanged);
}
void IDisposable.Dispose()
{
MyDialogService.Unsubscribe(StateHasChanged);
}
public void CloseDialog()
{
MyDialogService.Hide();
}
}
@@ -0,0 +1,171 @@
@implements IDisposable;
@inject IEntityDialogService entityDialogService
<div class="dialogBackground" onclick="@CloseDialog">
<div class="dialogContainer"
@onclick:preventDefault="true"
@onclick:stopPropagation="true">
@if (entity == null)
{
<div>Entity is null</div>
}
else
{
<div class="dialogHeader">
@if (entityDialogService.HasHistory())
{
<button class="backButton" @onclick="entityDialogService.BackDialog">
<div class="backButtonIcon"></div>
</button>
}
<div class="dialogTitle">
@entity.Info().Name
</div>
</div>
<div class="dialogContent">
<CascadingValue Value="@entity">
<EntityVanguardAddedComponent/>
<EntityInfoComponent/>
<EntityVanguardsComponent/>
<EntityProductionComponent/>
<EntityStatsComponent/>
<EntityMechanicsComponent/>
<EntityPassivesComponent/>
<EntityPyreSpellsComponent/>
<EntityUpgradesComponent/>
<EntityWeaponsComponent/>
<EntityAbilitiesComponent/>
</CascadingValue>
</div>
<div class="dialogFooter"></div>
}
</div>
</div>
<style>
.pageContents * {
filter: blur(2px);
}
.dialogBackground {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
}
.dialogContainer {
margin-left: auto;
margin-right: auto;
margin-top: 64px;
width: 800px;
height: 600px;
background-color: var(--background);
border-width: var(--dialog-border-width);
border-style: solid;
border-color: var(--dialog-border-color);
border-radius: var(--dialog-radius);
box-shadow: 1px 2px 2px black;
}
.dialogHeader {
width: 100%;
background-color: var(--accent);
border-top-left-radius: var(--dialog-radius);
border-top-right-radius: var(--dialog-radius);
border-bottom: 4px solid black;
display: flex;
align-items: center;
justify-content: flex-start;
}
.backButton {
margin-left: 16px;
padding: 12px;
border: 1px solid var(--accent);
}
.backButton:hover {
background-color: var(--primary-hover);
border: 1px solid var(--primary-border-hover);
}
.backButtonIcon {
height: 32px;
width: 32px;
border: solid white;
border-width: 0 9px 9px 0;
transform: rotate(135deg);
}
.dialogTitle {
padding: 16px;
font-size: 2rem;
font-weight: bold;
}
.dialogContent {
flex-grow: 1;
padding: 6px;
overflow-y: auto;
overflow-x: hidden;
height: 800px;
}
.dialogFooter {
width: 100%;
height: 6px;
background-color: var(--paper);
}
</style>
@code {
EntityModel entity = default!;
private int refresh;
protected override void OnInitialized()
{
base.OnInitialized();
entity = EntityData.Get()[entityDialogService.GetEntityId() ?? string.Empty];
entityDialogService.Subscribe(OnUpdate);
}
void IDisposable.Dispose()
{
entityDialogService.Unsubscribe(OnUpdate);
}
void OnUpdate()
{
entity = EntityData.Get()[entityDialogService.GetEntityId()];
refresh++;
StateHasChanged();
}
public void CloseDialog()
{
entityDialogService.CloseDialog();
}
}
@@ -0,0 +1 @@

@@ -0,0 +1,211 @@
@implements IDisposable;
@inject ISearchService searchService
@inject IJSRuntime jsRuntime
@inject NavigationManager navigationManager
@if (searchService.IsLoaded() && searchService.IsVisible)
{
<div id="searchBackground" class="searchBackground" onclick="@CloseDialog">
<div class="searchContainer"
@onclick:preventDefault="true"
@onclick:stopPropagation="true">
<FormLayoutComponent>
<FormTextComponent OnFocus="OnFocus" Id="searchInput" Placeholder="Search..."
OnInput="SearchChanged"></FormTextComponent>
</FormLayoutComponent>
<div class="searchBox">
@if (SearchText.Length > 0)
{
foreach (var searchSection in searchService.Searches)
{
var searchPoints = searchSection.Value.FindAll(x => x.Title.ToLower().Contains(SearchText.ToLower()));
@if (searchPoints.Count > 0)
{
<div>
<div class="searchSectionTitle">
@searchSection.Key
</div>
<div class="searchContents">
@foreach (var searchPoint in searchPoints)
{
<div>
<button class="searchLink @searchPoint.PointType.ToLower()"
label="@searchPoint.Title"
@onclick="() => OnSearch(searchPoint)">
@searchPoint.Title
</button>
@if (!searchPoint.Summary.Trim().Equals(""))
{
<i> - @searchPoint.Summary</i>
}
</div>
}
</div>
</div>
}
}
}
</div>
</div>
</div>
<style>
.pageContents * {
filter: blur(2px);
}
.searchBackground {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
}
.searchBox {
padding: 12px;
overflow-y: scroll;
overflow-x: hidden;
height: 530px;
border: 1px solid black;
border-radius: 2px;
}
.searchContents {
display: flex;
flex-direction: column;
gap: 6px;
align-items: flex-start;
padding: 12px;
}
.searchSectionTitle {
font-weight: bolder;
}
.searchContainer {
margin-left: auto;
margin-right: auto;
margin-top: 64px;
width: 800px;
height: 600px;
background-color: var(--background);
border-width: var(--dialog-border-width);
border-style: solid;
border-color: var(--dialog-border-color);
border-radius: var(--dialog-radius);
padding: 8px;
box-shadow: 1px 2px 2px black;
}
.searchLink {
text-decoration: underline;
}
@@media only screen and (max-width: 1025px) {
.searchContainer {
height: 300px;
}
.searchBox {
height: 230px;
}
}
</style>
}
@code {
private ElementReference searchBox;
private string SearchText { get; set; } = "";
protected override void OnInitialized()
{
searchService.Subscribe(OnSearchChanged);
timer = new Timer(200);
timer.Elapsed += FocusTimer;
timer.Enabled = false;
}
private void FocusTimer(object? sender, ElapsedEventArgs e)
{
jsRuntime.InvokeVoidAsync("SetFocusToElement", "searchInput");
StateHasChanged();
}
private Timer timer = null!;
private void OnSearchChanged()
{
if (timer.Enabled != searchService.IsVisible)
{
timer.Enabled = searchService.IsVisible;
}
StateHasChanged();
}
public void Dispose()
{
searchService.Unsubscribe(OnSearchChanged);
timer.Elapsed -= FocusTimer;
}
public void CloseDialog()
{
searchService.Hide();
}
public void NavigateTo(string url)
{
if (url.Contains("#"))
{
navigationManager.NavigateTo(url,
navigationManager.Uri.Split("#").First().Contains(url.Split("#").First()));
}
else
{
navigationManager.NavigateTo(url);
}
}
private void SearchChanged(ChangeEventArgs obj)
{
SearchText = obj.Value!.ToString()!;
}
private void OnSearch(SearchPointModel searchPoint)
{
NavigateTo(searchPoint.Href);
searchService.Hide();
}
private void OnFocus(object obj)
{
timer.Enabled = false;
}
}
+60
View File
@@ -0,0 +1,60 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
<LangVersion>12</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DefineConstants>TRACE;NO_SQL</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DefineConstants>TRACE;NO_SQL</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Blazor-Analytics" Version="4.0.0" />
<PackageReference Include="Markdig" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.5" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.5" />
<PackageReference Include="MudBlazor" Version="9.2.0" />
</ItemGroup>
<ItemGroup>
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Components\Components.csproj" />
<ProjectReference Include="..\Model\Model.csproj" />
<ProjectReference Include="..\Services\Services.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="wwwroot\generated" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Localizations.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Localizations.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Compile Update="Localizations.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Example.resx</DependentUpon>
</Compile>
</ItemGroup>
</Project>
+49
View File
@@ -0,0 +1,49 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.32112.339
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IGP", "IGP.csproj", "{172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Model", "..\Model\Model.csproj", "{77395F7A-BE93-470C-9F10-F48FFA445B63}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Components", "..\Components\Components.csproj", "{0419E7CD-0971-4A56-A61F-C090DF60FAF6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Services", "..\Services\Services.csproj", "{621178C8-4E8B-478E-80E5-7478F0E7B67E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestAutomation", "..\TestAutomation\TestAutomation.csproj", "{8B49D038-D013-460D-9C4F-817CAFFEB06F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
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}.Release|Any CPU.ActiveCfg = Release|Any CPU
{172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}.Release|Any CPU.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}.Release|Any CPU.ActiveCfg = Release|Any CPU
{77395F7A-BE93-470C-9F10-F48FFA445B63}.Release|Any CPU.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}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0419E7CD-0971-4A56-A61F-C090DF60FAF6}.Release|Any CPU.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}.Release|Any CPU.ActiveCfg = Release|Any CPU
{621178C8-4E8B-478E-80E5-7478F0E7B67E}.Release|Any CPU.Build.0 = Release|Any CPU
{8B49D038-D013-460D-9C4F-817CAFFEB06F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8B49D038-D013-460D-9C4F-817CAFFEB06F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8B49D038-D013-460D-9C4F-817CAFFEB06F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8B49D038-D013-460D-9C4F-817CAFFEB06F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {19100811-B909-4D20-9AE0-2EB579A55B3A}
EndGlobalSection
EndGlobal
+5
View File
@@ -0,0 +1,5 @@
@page "/"
@layout PageLayout
<HomePage/>
+114
View File
@@ -0,0 +1,114 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace IGP {
using System;
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Localizations {
private static System.Resources.ResourceManager resourceMan;
private static System.Globalization.CultureInfo resourceCulture;
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Localizations() {
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
internal static System.Resources.ResourceManager ResourceManager {
get {
if (object.Equals(null, resourceMan)) {
System.Resources.ResourceManager temp = new System.Resources.ResourceManager("IGP.Localizations", typeof(Localizations).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
internal static System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
internal static string Greeting {
get {
return ResourceManager.GetString("Greeting", resourceCulture);
}
}
internal static string Tooltip_Chart_Info {
get {
return ResourceManager.GetString("Tooltip Chart Info", resourceCulture);
}
}
internal static string Tooltip_Filter_Info {
get {
return ResourceManager.GetString("Tooltip Filter Info", resourceCulture);
}
}
internal static string Tooltip_Entity_Info {
get {
return ResourceManager.GetString("Tooltip Entity Info", resourceCulture);
}
}
internal static string Tooltip_Bank_Info {
get {
return ResourceManager.GetString("Tooltip Bank Info", resourceCulture);
}
}
internal static string Tooltip_Army_Info {
get {
return ResourceManager.GetString("Tooltip Army Info", resourceCulture);
}
}
internal static string Tooltip_Highlights_Info {
get {
return ResourceManager.GetString("Tooltip Highlights Info", resourceCulture);
}
}
internal static string Tooltip_BuildOrder_Info {
get {
return ResourceManager.GetString("Tooltip BuildOrder Info", resourceCulture);
}
}
internal static string Tooltip_Timing_Info {
get {
return ResourceManager.GetString("Tooltip Timing Info", resourceCulture);
}
}
internal static string Tooltip_Hotkey_Info {
get {
return ResourceManager.GetString("Tooltip Hotkey Info", resourceCulture);
}
}
internal static string Tooltip_Options_Info {
get {
return ResourceManager.GetString("Tooltip Options Info", resourceCulture);
}
}
}
}
View File
+68
View File
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
id="root"
xmlns="">
<xsd:element name="root" msdata:IsDataSet="true">
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<data name="Greeting" xml:space="preserve">
<value>Hello</value>
</data>
<data name="Tooltip Chart Info" xml:space="preserve">
<value>Shows economy at each game interval. Use to determine if spending additional resources on harvesters will help or hinder overall timing attack.</value>
</data>
<data name="Tooltip Filter Info" xml:space="preserve">
<value>Select build details, such as Faction and Immortal.
Affects entities you can build.</value>
</data>
<data name="Tooltip Entity Info" xml:space="preserve">
<value>Summary of the entity you just selected.</value>
</data>
<data name="Tooltip Bank Info" xml:space="preserve">
<value>Bank at time of last requested action. Use this section to determine if your build is floating too much alloy or ether.</value>
</data>
<data name="Tooltip Army Info" xml:space="preserve">
<value>Overview of current army, and when it will be ready to begin an attack.</value>
</data>
<data name="Tooltip Highlights Info" xml:space="preserve">
<value>Timeline highlights of your build order. Shows when you start a new action and when the action is done.</value>
</data>
<data name="Tooltip BuildOrder Info" xml:space="preserve">
<value>Some raw JSON data to represent your build order.</value>
</data>
<data name="Tooltip Timing Info" xml:space="preserve">
<value>Enter build details.</value>
</data>
<data name="Tooltip Hotkey Info" xml:space="preserve">
<value>Click on the desired entity to build it. &lt;i&gt;You cannot build entities you cannot afford, construct an ether extractor before spending ether.&lt;/i&gt;
You can also use the default Immortal hotkeys, but the above hotkey UI must have focus for this to work. &lt;i&gt;I.e. click on it if hotkeys aren't working, and a white border should appear after key input to indicate focus.&lt;/i&gt;
Additionally, more entities will appear as you build the required technology. You can click or press ` to remove the last made entity. &lt;i&gt;But you cannot remove the starting entities at interval 0.&lt;/i&gt;</value>
</data>
<data name="Tooltip Options Info" xml:space="preserve">
<value>Misc calculator controls.</value>
</data>
</root>
+143
View File
@@ -0,0 +1,143 @@
@inherits LayoutComponentBase
@inject ISearchService SearchService
@inject IDataCollectionService DataCollectionService
@inject NavigationManager NavigationManager
@using Model.Website.Data
@using Services.Website
@implements IDisposable
<MudThemeProvider @ref="@_mudThemeProvider" @bind-IsDarkMode="@_isDarkMode"/>
<MudPopoverProvider/>
<MudDialogProvider/>
<MudSnackbarProvider/>
<MudLayout>
<MudAppBar Elevation="1">
<MudHidden Breakpoint="Breakpoint.SmAndDown" Invert="true">
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start"
OnClick="@(e => DrawerToggle())"/>
</MudHidden>
<MudButton Class="ml-3 mr-8"
Href="/"
Target="_self"
Variant="Variant.Text"
Color="Color.Default">
<MudText Typo="Typo.h5"> IGP Fan Reference</MudText>
</MudButton>
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true">
@foreach (var page in WebsiteData.GetPages())
{
<MudButton Href="@(page.Href)"
Variant="Variant.Text"
Color="Color.Default"
Class="mr-4">
<MudIcon Icon="@(page.Icon)" Class="mr-2"/>
@(page.Name)
</MudButton>
}
</MudHidden>
<MudSpacer/>
<SearchButtonComponent Id="desktop-searchButton"/>
</MudAppBar>
<MudHidden Breakpoint="Breakpoint.SmAndDown" Invert="true">
<MudDrawer @bind-Open="_drawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2">
<MudPaper Width="250px" Class="d-inline-flex py-3" Elevation="0">
<MudNavMenu Class="mud-width-full flex-grow-1">
@foreach (var page in WebsiteData.GetPages())
{
<MudNavLink Href="@(page.Href)" Icon="@(page.Icon)">@(page.Name)</MudNavLink>
}
</MudNavMenu>
</MudPaper>
</MudDrawer>
</MudHidden>
<MudMainContent>
<MudContainer Class="px-8" MaxWidth="MaxWidth.False">
<div id="content" class="content">
@Body
</div>
</MudContainer>
</MudMainContent>
</MudLayout>
<FooterComponent></FooterComponent>
@code {
private bool _isDarkMode = true;
private MudThemeProvider _mudThemeProvider;
bool _drawerOpen = true;
void DrawerToggle()
{
_drawerOpen = !_drawerOpen;
}
protected override void OnInitialized()
{
base.OnInitialized();
CollectFirstPageLoaded();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
//TODO: Support light mode
//_isDarkMode = await _mudThemeProvider.GetSystemPreference();
//StateHasChanged();
}
}
private void CollectFirstPageLoaded()
{
var skipBaseUri = NavigationManager.Uri.Substring(NavigationManager.BaseUri.Length,
NavigationManager.Uri.Length - NavigationManager.BaseUri.Length);
var rootUrl = skipBaseUri.Split("/").First();
if (rootUrl.Trim().Equals(""))
{
rootUrl = "home";
}
DataCollectionService.SendEvent(DataCollectionKeys.FirstPage,
new Dictionary<string, string> { { "page", rootUrl } });
}
protected override async Task OnInitializedAsync()
{
await Focus();
}
private async Task Focus()
{
// await jsRuntime.InvokeVoidAsync("SetFocusToElement", pageContents);
}
protected override async void OnAfterRender(bool firstRender)
{
// await jsRuntime.InvokeVoidAsync("SetFocusToElement", pageContents);
}
void IDisposable.Dispose()
{
}
void HasChanged()
{
StateHasChanged();
}
private void HandleKeyDown(KeyboardEventArgs keyboardEventArgs)
{
if ((keyboardEventArgs.CtrlKey || keyboardEventArgs.MetaKey) && keyboardEventArgs.Key.ToLower() == "k")
{
SearchService.Show();
}
}
}
+13
View File
@@ -0,0 +1,13 @@
.layoutContainer {
height: 100vh;
overflow-y: scroll;
overflow-x: hidden;
}
.content {
margin-bottom: 64px;
display: flex;
min-width: 100%;
min-height: 100%;
flex-direction: column;
}
+52
View File
@@ -0,0 +1,52 @@
@layout PageLayout
@inherits BasePage
@inject IDataCollectionService DataCollectionService
@page "/about"
<LayoutMediumContentComponent>
<WebsiteTitleComponent>About</WebsiteTitleComponent>
<PaperComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
What is this website for?
</InfoQuestionComponent>
<InfoAnswerComponent>
This is just a "yet another third-party tool" website for a video game. If you played a game like Path
of Exile, you are probably already used to seeing a bunch of said tools.
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
So what is <i>this</i> specific tool for?
</InfoQuestionComponent>
<InfoAnswerComponent>
Ideally, this website will be a casual reference, for getting started with understanding the themes and
game patterns of IMMORTAL: Gates of Pyre. That said, this tool is currently not near to achieving said
goal. In the meantime, you can check out the simple calculator and database tools on this website.
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
The game hasn't even been released. Isn't it a bit early for publishing community tools?
</InfoQuestionComponent>
<InfoAnswerComponent>
Maybe 🙂
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
Any disclaimers?
</InfoQuestionComponent>
<InfoAnswerComponent>
This website has no association with "SunSpear Games." Beyond that, any game data displayed on this
website for "IMMORTAL: Gates of Pyre" may be inaccurate due to my own human error and time limitations.
Use with caution.
</InfoAnswerComponent>
</InfoBodyComponent>
</PaperComponent>
</LayoutMediumContentComponent>
+37
View File
@@ -0,0 +1,37 @@
@using Services.Website
@inject IDataCollectionService DataCollectionService
@inject NavigationManager NavigationManager
@code {
protected override void OnInitialized()
{
base.OnInitialized();
CollectLoadedPage();
}
private void CollectLoadedPage()
{
var skipBaseUri = NavigationManager.Uri.Substring(NavigationManager.BaseUri.Length,
NavigationManager.Uri.Length - NavigationManager.BaseUri.Length);
var splitData = skipBaseUri.Split("/");
var rootUrl = splitData.First();
if (rootUrl.Trim().Equals(""))
{
rootUrl = "home";
}
var eventData = new Dictionary<string, string> { { "page", rootUrl } };
if (splitData.Length > 1)
{
eventData["inner-page"] = splitData.Last();
}
DataCollectionService.SendEvent(DataCollectionKeys.PageInitialized, eventData);
}
}
@@ -0,0 +1,305 @@
@layout PageLayout
@inherits BasePage
@inject IStringLocalizer<Localizations> Locale
@inject IKeyService KeyService
@inject IImmortalSelectionService FilterService
@inject IBuildOrderService BuildOrderService
@inject IEconomyService EconomyService
@inject IToastService ToastService
@inject ITimingService TimingService
@inject IDataCollectionService DataCollectionService
@page "/build-calculator"
@using Services.Website
@implements IDisposable
<LayoutLargeContentComponent>
<WebsiteTitleComponent>Build Calculator</WebsiteTitleComponent>
<AlertComponent Type="@SeverityType.Warning">
<Title>Work In Progress and Not Fully Tested</Title>
<Message>
Build Calculator hasn't been thoroughly tested. Bugs and inaccurate results assumed.
<br/>
Currently not considering running out of alloy and ether to harvest.
<br/>
<br/>
Build Calculator was built based on a much older version of the game and was only quickly modified for the
June 2025 Playtest version, so the above disclaimer is only more true.
<br/>
Expect even more oddities and invalid data then the above warning implies.
</Message>
</AlertComponent>
<ContentDividerComponent></ContentDividerComponent>
<div class="calculatorGrid">
<div class="gridItem" style="grid-area: timing;">
<ButtonComponent MyButtonType="MyButtonType.Secondary" OnClick="OnResetClicked">Clear Build Order
</ButtonComponent>
<PanelComponent>
<InfoTooltipComponent InfoText="@Locale["Tooltip Timing Info"]">
<TimingComponent></TimingComponent>
</InfoTooltipComponent>
</PanelComponent>
<PanelComponent>
<InfoTooltipComponent InfoText="@Locale["Tooltip Filter Info"]">
<FilterComponent></FilterComponent>
</InfoTooltipComponent>
</PanelComponent>
<PanelComponent>
<InfoTooltipComponent InfoText="@Locale["Tooltip Options Info"]">
<OptionsComponent></OptionsComponent>
</InfoTooltipComponent>
</PanelComponent>
</div>
<div class="gridItem" style="grid-area: chart;">
<PanelComponent>
<InfoTooltipComponent InfoText="@Locale["Tooltip Chart Info"]">
<BuildChartComponent></BuildChartComponent>
</InfoTooltipComponent>
</PanelComponent>
</div>
<div class="gridItem" style="grid-area: view;">
<PanelComponent>
<InfoTooltipComponent InfoText="@Locale["Tooltip Entity Info"]">
<EntityClickViewComponent/>
</InfoTooltipComponent>
</PanelComponent>
</div>
<div class="gridItem" style="grid-area: bank;">
<PanelComponent>
<InfoTooltipComponent InfoText="@Locale["Tooltip Bank Info"]">
<BankComponent></BankComponent>
</InfoTooltipComponent>
</PanelComponent>
</div>
<div class="gridItem" style="grid-area: army;">
<PanelComponent>
<InfoTooltipComponent InfoText="@Locale["Tooltip Army Info"]">
<ArmyComponent></ArmyComponent>
</InfoTooltipComponent>
</PanelComponent>
</div>
<div class="gridItem gridKeys">
<PanelComponent>
<InfoTooltipComponent InfoText="@Locale["Tooltip Hotkey Info"]">
<HotkeyViewerComponent Size="80"></HotkeyViewerComponent>
</InfoTooltipComponent>
</PanelComponent>
</div>
<div class="gridItem" style="grid-area: highlights;">
<PanelComponent>
<InfoTooltipComponent InfoText="@Locale["Tooltip Highlights Info"]">
<HighlightsComponent></HighlightsComponent>
</InfoTooltipComponent>
</PanelComponent>
</div>
<div class="gridItem" style="grid-area: buildorder;">
<PanelComponent>
<InfoTooltipComponent InfoText="@Locale["Tooltip BuildOrder Info"]">
<BuildOrderComponent></BuildOrderComponent>
</InfoTooltipComponent>
</PanelComponent>
</div>
</div>
<ContentDividerComponent></ContentDividerComponent>
<PaperComponent>
<FormLayoutComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
What is this tool?
</InfoQuestionComponent>
<InfoAnswerComponent>
This is a calculator to determine build timings. Mostly so someone can quickly try out a few build
orders to see if they somewhat make sense.
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
How does it work?
</InfoQuestionComponent>
<InfoAnswerComponent>
The tool calculates every second of game time. So if you attempt to build a <b>Legion Hall</b> as
your first action, the tool will scan every second, until you get to one where the request can be
made. In this case, that is interval 58.
<br/>
<br/>
If you then build 2 <b>Apostle of Bindings</b> a <b>Soul Foundry</b> and a 3 <b>Absolvers</b> you
should see yourself roughly floating 500 alloy, with barely having any ether. Which means you could
of gotten an <b>Acropolis</b> and a <b>Zentari</b> without hurting your build.
<br/>
<br/>
Try building <b>Apostle of Bindings</b> before the <b>Legion Hall</b> and see how that changes the
timing of your 3 <b>Absolvers</b>. (Spoiler:
<SpoilerTextComponent> your <b>Absolvers</b> will be built much faster, and you won't be floating so
much alloy.
</SpoilerTextComponent>
)
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
What is CONTROL key for?
</InfoQuestionComponent>
<InfoAnswerComponent>
Economy and tech related upgrades for townhalls.
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
What is SHIFT key for?
</InfoQuestionComponent>
<InfoAnswerComponent>
Misc building related upgrades. (Omnivores)
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
What is 2 key for?
</InfoQuestionComponent>
<InfoAnswerComponent>
It will be for Pyre camps. Currently not implemented.
</InfoAnswerComponent>
</InfoBodyComponent>
</FormLayoutComponent>
</PaperComponent>
</LayoutLargeContentComponent>
<style>
.gridItem {
display: flex;
flex-direction: column;
gap: 8px;
}
.calculatorGrid {
display: grid;
gap: 8px;
max-width: 90vw;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-template-rows: 600px 400px 450px;
grid-template-areas:
'timing view view view'
'timing bank army army'
'keys keys highlights buildorder'
'chart chart chart chart';
}
.gridKeys {
grid-area: keys;
}
@@media only screen and (max-width: 1025px) {
.gridKeys {
background-color: #282A30;
}
.calculatorGrid {
grid-template-columns: 1fr;
grid-template-rows: auto;
grid-template-areas:
'timing'
'view'
'keys'
'bank'
'army'
'highlights'
'buildorder'
'chart';
padding-left: 2px;
padding-right: 2px;
}
}
</style>
@code {
protected override void OnInitialized()
{
base.OnInitialized();
EconomyService.Calculate(BuildOrderService, TimingService, 0);
KeyService.Subscribe(HandleClick);
DataCollectionService.SendEvent(
DataCollectionKeys.PageInitialized,
new Dictionary<string, string> { { "page", "build-calculator" } }
);
}
void IDisposable.Dispose()
{
KeyService.Unsubscribe(HandleClick);
}
private void OnResetClicked()
{
ToastService.AddToast(new ToastModel
{
SeverityType = SeverityType.Success,
Message = "Build order has been cleared.",
Title = "Reset"
});
BuildOrderService.Reset();
}
private void HandleClick()
{
var hotkey = KeyService.GetHotkey();
if (hotkey == "")
{
return;
}
if (hotkey == "`")
{
BuildOrderService.RemoveLast();
EconomyService.Calculate(BuildOrderService, TimingService, BuildOrderService.GetLastRequestInterval());
return;
}
var hotkeyGroup = KeyService.GetHotkeyGroup();
var isHoldSpace = KeyService.IsHoldingSpace();
var faction = FilterService.GetFaction();
var immortal = FilterService.GetImmortal();
var entity = EntityModel.GetFrom(hotkey!, hotkeyGroup, isHoldSpace, faction, immortal);
if (entity == null)
{
return;
}
if (BuildOrderService.Add(entity, EconomyService))
{
EconomyService.Calculate(BuildOrderService, TimingService, BuildOrderService.GetLastRequestInterval());
}
}
}
@@ -0,0 +1,156 @@
@inject IJSRuntime jsRuntime
@inject IBuildOrderService buildOrder
@inject ITimingService timingService
@implements IDisposable
<div class="armyView">
<FormLayoutComponent>
<div style="display: flex; gap: 24px;">
<FormDisplayComponent Label="Army Completed At">
<Display>@lastInterval | T @Interval.ToTime(lastInterval)</Display>
</FormDisplayComponent>
<FormDisplayComponent Label="Army Attacking At">
<Display>@(lastInterval + timingService.GetTravelTime()) |
T @Interval.ToTime(lastInterval + timingService.GetTravelTime())</Display>
</FormDisplayComponent>
</div>
<FormDisplayComponent Label="Army units built">
<Display>
<div class="armyCardsContainer">
@foreach (var unit in armyCount)
{
<div class="armyCard">
<div class="armyCountPosition">
<div class="armyCount">@unit.Value.ToString()x</div>
</div>
<div>@unit.Key</div>
</div>
}
</div>
</Display>
</FormDisplayComponent>
</FormLayoutComponent>
</div>
<style>
.armyView {
overflow-y: scroll;
width: 100%;
overflow-x: hidden;
height: 350px;
}
.armyCardsContainer {
display: flex;
width: 100%;
gap: 16px;
flex-wrap: wrap;
}
.armyCard {
width: 100px;
height: 80px;
padding: 16px;
}
.armyCountPosition {
height: 0;
top: -20px;
left: -16px;
position: relative;
}
.armyCount {
font-weight: bolder;
}
</style>
@code {
private int lastInterval;
readonly Dictionary<string, int> armyCount = new();
List<EntityModel> army = new();
protected override void OnInitialized()
{
base.OnInitialized();
buildOrder.Subscribe(OnBuildOrderChanged);
timingService.Subscribe(StateHasChanged);
}
void IDisposable.Dispose()
{
buildOrder.Unsubscribe(OnBuildOrderChanged);
timingService.Unsubscribe(StateHasChanged);
}
protected override bool ShouldRender()
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.time", "ArmyComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.timeEnd", "ArmyComponent");
#endif
}
void OnBuildOrderChanged()
{
var armyCountWas = 0;
foreach (var army in armyCount)
{
armyCountWas += army.Value;
}
armyCount.Clear();
lastInterval = 0;
var entitiesOverTime = buildOrder.GetOrders();
foreach (var entitiesAtTime in entitiesOverTime)
{
foreach (var entity in entitiesAtTime.Value)
{
if (entity.EntityType == EntityType.Army)
{
if (!armyCount.TryAdd(entity.Info().Name, 1))
{
armyCount[entity.Info().Name]++;
}
if (entity.Production() != null && entity.Production().BuildTime + entitiesAtTime.Key > lastInterval)
{
lastInterval = entity.Production().BuildTime + entitiesAtTime.Key;
}
}
}
}
//TODO Better
var armyCountIs = 0;
foreach (var army in armyCount)
{
armyCountIs += army.Value;
}
if (armyCountWas != armyCountIs)
{
StateHasChanged();
}
}
}
@@ -0,0 +1,134 @@
@inject IJSRuntime jsRuntime;
@inject IEconomyService economyService
@implements IDisposable
<div class="bankContainer">
<FormDisplayComponent Label="Time">
<Display>@(BuildOrderService.GetLastRequestInterval() + 1) |
T @Interval.ToTime(BuildOrderService.GetLastRequestInterval() + 1)</Display>
</FormDisplayComponent>
<div class="bankRow">
<FormDisplayComponent Label="Alloy">
<Display>@_economy.Alloy +@_economy.AlloyIncome</Display>
</FormDisplayComponent>
<FormDisplayComponent Label="Ether">
<Display>@Math.Round(_economy.Ether) +@Math.Round(_economy.EtherIncome)</Display>
</FormDisplayComponent>
</div>
<div class="bankRow">
<FormDisplayComponent Label="Pyre">
<Display>@_economy.Pyre</Display>
</FormDisplayComponent>
<FormDisplayComponent Label="Supply">
<Display>@_supplyTaken / @_supplyGranted (@(_supplyGranted / 16)@(_extraBuildings > 0 ? "+" + _extraBuildings : ""))</Display>
</FormDisplayComponent>
</div>
<div>
<div class="workerText">Workers</div>
<div class="bankRow">
<FormDisplayComponent Label="Current">
<Display>@_economy.WorkerCount</Display>
</FormDisplayComponent>
<FormDisplayComponent Label="Busy">
<Display>@_economy.BusyWorkerCount</Display>
</FormDisplayComponent>
<FormDisplayComponent Label="Creating">
<Display>@_economy.CreatingWorkerCount</Display>
</FormDisplayComponent>
</div>
</div>
</div>
<style>
.bankContainer {
display: flex;
flex-direction: column;
gap: 5px;
}
.workerText {
margin-bottom: -2px;
font-size: 0.8em;
}
.bankRow {
display: flex;
gap: 8px;
}
</style>
@code {
[Inject] IBuildOrderService BuildOrderService { get; set; } = default!;
[Inject] IEconomyService EconomyService { get; set; } = default!;
EconomyModel _economy = new();
int _supplyGranted;
int _supplyTaken;
int _extraBuildings;
protected override void OnInitialized()
{
base.OnInitialized();
BuildOrderService.Subscribe(OnBuildOrderChanged);
_economy = EconomyService.GetEconomy(BuildOrderService.GetLastRequestInterval() + 1);
}
protected override bool ShouldRender()
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.time", "BankComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.timeEnd", "BankComponent");
#endif
}
void IDisposable.Dispose()
{
BuildOrderService.Unsubscribe(OnBuildOrderChanged);
}
void OnBuildOrderChanged()
{
_economy = EconomyService.GetEconomy(BuildOrderService.GetLastRequestInterval() + 1);
var ordersOverTime = BuildOrderService.GetOrders();
_supplyTaken = (from ordersAtInterval in ordersOverTime
from order in ordersAtInterval.Value
where order.Supply() != null
where order.Supply().Takes > 0
select order.Supply().Takes).Sum();
_supplyGranted = (from ordersAtInterval in ordersOverTime
from order in ordersAtInterval.Value
where order.Supply() != null
where order.Supply().Grants > 0
select order.Supply().Grants).Sum();
_extraBuildings = 0;
if (_supplyGranted > 160)
{
_extraBuildings = (_supplyGranted - 160) / 16;
_supplyGranted = 160;
}
StateHasChanged();
}
}
@@ -0,0 +1,331 @@
@inject IEconomyService EconomyService
@inject IBuildOrderService BuildOrderService
@inject ITimingService TimingService
@inject IJSRuntime JsRuntime;
@implements IDisposable
@if (lastRequestedRefreshIndex != requestedRefreshIndex)
{
<LoadingComponent/>
}
else
{
<div class="chartsContainer">
@foreach (var chart in charts)
{
var takenPixels = new Dictionary<int, bool>();
<div style="width: @chart.IntervalDisplayMax.ToString()px; height: @chart.ValueDisplayMax.ToString()px">
<div
style="position: relative; border: 2px solid gray; border-radius:2px; width: @chart.IntervalDisplayMax.ToString()px; height: @chart.ValueDisplayMax.ToString()px">
@foreach (var point in chart.Points)
{
var x = int.Parse(point.GetInterval(chart.HighestIntervalPoint, chart.IntervalDisplayMax));
if (takenPixels.ContainsKey(x)) continue;
takenPixels.Add(x, true);
<div style="position: absolute;
bottom:@point.GetValue(chart.HighestValuePoint, chart.ValueDisplayMax)px;
left:@point.GetInterval(chart.HighestIntervalPoint, chart.IntervalDisplayMax)px;
width: 0px;
height: 0px;">
<div
style="width:1px; height: 1px; border-top-right-radius:10px; border-top-left-radius:10px; border: 2px solid @chart.ChartColor; background-color:@chart.ChartColor">
</div>
</div>
}
</div>
</div>
}
</div>
<style>
.chartsContainer {
position: relative;
display: flex;
gap: 20px;
flex-wrap: wrap;
justify-content: center;
margin-bottom: 20px;
}
</style>
<FormLayoutComponent>
<FormDisplayComponent Label="Highest Alloy">
<Display>@highestAlloyPoint</Display>
</FormDisplayComponent>
<FormDisplayComponent Label="Highest Ether">
<Display>@highestEtherPoint</Display>
</FormDisplayComponent>
<DevOnlyComponent>
<FormDisplayComponent Label="Highest Pyre">
<Display>@highestEtherPoint</Display>
</FormDisplayComponent>
</DevOnlyComponent>
<FormDisplayComponent Label="Highest Army">
<Display>@highestArmyPoint</Display>
</FormDisplayComponent>
</FormLayoutComponent>
}
@code {
private readonly int width = 250;
List<int> valueList = new();
readonly List<ChartModel> charts = new();
float highestAlloyPoint;
float highestEtherPoint;
float highestPyrePoint;
float highestArmyPoint;
private Timer ageTimer = null!;
protected override void OnInitialized()
{
base.OnInitialized();
BuildOrderService.Subscribe(OnBuilderOrderChanged);
TimingService.Subscribe(OnBuilderOrderChanged);
ageTimer = new Timer(1000);
ageTimer.Elapsed += OnAge!;
ageTimer.Enabled = true;
GenerateChart();
}
int lastRequestedRefreshIndex;
void OnAge(object? sender, ElapsedEventArgs elapsedEventArgs)
{
if (requestedRefreshIndex > 0)
{
if (requestedRefreshIndex == lastRequestedRefreshIndex)
{
GenerateChart();
requestedRefreshIndex = 0;
lastRequestedRefreshIndex = 0;
}
lastRequestedRefreshIndex = requestedRefreshIndex;
}
ageTimer.Enabled = true;
}
void IDisposable.Dispose()
{
BuildOrderService.Unsubscribe(OnBuilderOrderChanged);
TimingService.Unsubscribe(OnBuilderOrderChanged);
}
int requestedRefreshIndex;
void OnBuilderOrderChanged()
{
requestedRefreshIndex++;
StateHasChanged();
}
protected override bool ShouldRender()
{
#if DEBUG
JsRuntime.InvokeVoidAsync("console.time", "ChartComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
JsRuntime.InvokeVoidAsync("console.timeEnd", "ChartComponent");
#endif
}
void GenerateChart()
{
var economyOverTime = EconomyService.GetOverTime();
charts.Clear();
var alloyChart = new ChartModel
{
IntervalDisplayMax = width,
ValueDisplayMax = 100,
ChartColor = "Cyan"
};
var etherChart = new ChartModel
{
Offset = width,
IntervalDisplayMax = width,
ValueDisplayMax = 100,
ChartColor = "LightGreen"
};
var pyreChart = new ChartModel
{
Offset = width * 2,
IntervalDisplayMax = width,
ValueDisplayMax = 100,
ChartColor = "Red"
};
var armyChart = new ChartModel
{
Offset = width * 3,
IntervalDisplayMax = width,
ValueDisplayMax = 100,
ChartColor = "White"
};
highestAlloyPoint = 0;
highestEtherPoint = 0;
highestPyrePoint = 0;
highestArmyPoint = 0;
for (var interval = 0; interval < economyOverTime.Count(); interval++)
{
var army = from unit in BuildOrderService.GetCompletedBefore(interval)
where unit.EntityType == EntityType.Army
select unit;
var armyValue = 0;
foreach (var unit in army)
{
armyValue += unit.Production().Alloy + unit.Production().Ether;
}
highestArmyPoint = Math.Max(highestArmyPoint, armyValue);
armyChart.Points.Add(new PointModel { Interval = interval, Value = armyValue });
}
for (var interval = 0; interval < economyOverTime.Count(); interval++)
{
var alloyPoint = new PointModel { Interval = interval };
var etherPoint = new PointModel { Interval = interval };
var pyrePoint = new PointModel { Interval = interval };
var economyAtSecond = economyOverTime[interval];
var alloyWorkerHarvesters = from harvester in economyAtSecond.HarvestPoints
where harvester.Harvest() != null
where harvester.Harvest().RequiresWorker
where harvester.Harvest().Resource == ResourceType.Alloy
select harvester;
var alloyAutomaticHarvesters = from harvester in economyAtSecond.HarvestPoints
where harvester.Harvest() != null
where harvester.Harvest().RequiresWorker == false
where harvester.Harvest().Resource == ResourceType.Alloy
select harvester;
var etherAutomaticHarvesters = from harvester in economyAtSecond.HarvestPoints
where harvester.Harvest() != null
where harvester.Harvest().RequiresWorker == false
where harvester.Harvest().Resource == ResourceType.Ether
select harvester;
float autoAlloy = 0;
float workerSlots = 0;
float workerAlloy = 0;
float autoEther = 0;
float economySpending = 0;
foreach (var alloyAutoHarvester in alloyAutomaticHarvesters)
{
autoAlloy += alloyAutoHarvester.Harvest().Slots * alloyAutoHarvester.Harvest().HarvestedPerInterval;
var production = alloyAutoHarvester.Production();
if (production != null)
{
economySpending += production.Alloy;
}
}
foreach (var alloyWorkerHarvester in alloyWorkerHarvesters)
{
workerSlots += alloyWorkerHarvester.Harvest().Slots;
var production = alloyWorkerHarvester.Production();
if (production != null)
{
economySpending += production.Alloy;
}
}
foreach (var etherWorkerHarvester in etherAutomaticHarvesters)
{
autoEther += etherWorkerHarvester.Harvest().Slots * etherWorkerHarvester.Harvest().HarvestedPerInterval;
var production = etherWorkerHarvester.Production();
if (production != null)
{
economySpending += production.Alloy;
}
}
economySpending += (economyAtSecond.WorkerCount - 6) * 50;
workerAlloy = Math.Min(economyAtSecond.WorkerCount - economyAtSecond.BusyWorkerCount, workerSlots);
alloyPoint.TempValue = workerAlloy + autoAlloy;
etherPoint.Value = autoEther;
if (interval > 0)
{
alloyPoint.TempValue += alloyChart.Points.Last().TempValue;
etherPoint.Value += etherChart.Points.Last().Value;
pyrePoint.Value = pyreChart.Points.Last().Value + 1;
}
alloyPoint.Value = alloyPoint.TempValue - economySpending;
highestAlloyPoint = Math.Max(highestAlloyPoint, alloyPoint.Value);
highestEtherPoint = Math.Max(highestEtherPoint, etherPoint.Value);
alloyChart.Points.Add(alloyPoint);
etherChart.Points.Add(etherPoint);
pyreChart.Points.Add(pyrePoint);
}
alloyChart.HighestValuePoint = (int)Math.Max(highestAlloyPoint, 5000.0f);
etherChart.HighestValuePoint = (int)Math.Max(highestEtherPoint, 2000.0f);
pyreChart.HighestValuePoint = (int)Math.Max(highestPyrePoint, 2000.0f);
alloyChart.HighestIntervalPoint = economyOverTime.Count();
etherChart.HighestIntervalPoint = economyOverTime.Count();
pyreChart.HighestIntervalPoint = economyOverTime.Count();
armyChart.HighestValuePoint = (int)Math.Max(highestArmyPoint, 2000.0f);
armyChart.HighestIntervalPoint = economyOverTime.Count();
charts.Add(alloyChart);
charts.Add(etherChart);
//TODO WIP
//charts.Add(pyreChart);
charts.Add(armyChart);
StateHasChanged();
}
}
@@ -0,0 +1,43 @@
@inject IJSRuntime jsRuntime;
@inject IBuildOrderService buildOrderService
@implements IDisposable
<FormLayoutComponent>
<FormTextAreaComponent Label="JSON Data"
Rows="14"
Value="@buildOrderService.AsJson()">
</FormTextAreaComponent>
</FormLayoutComponent>
@code {
protected override void OnInitialized()
{
base.OnInitialized();
buildOrderService.Subscribe(StateHasChanged);
}
void IDisposable.Dispose()
{
buildOrderService.Unsubscribe(StateHasChanged);
}
protected override bool ShouldRender()
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.time", "BuildOrderComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.timeEnd", "BuildOrderComponent");
#endif
}
}
@@ -0,0 +1,86 @@
@inject IJSRuntime JsRuntime
@inject IKeyService KeyService
@inject IImmortalSelectionService FilterService
@inject IBuildOrderService BuildOrderService
@inject IStorageService StorageService
@using Services.Website
@implements IDisposable
@if (_entity != null)
{
<div class="entityClickView">
<CascadingValue Value="_entity">
<CascadingValue Value="@_viewType">
<EntityViewComponent></EntityViewComponent>
</CascadingValue>
</CascadingValue>
</div>
}
<style>
.entityClickView {
overflow-y: scroll;
width: 100%;
overflow-x: hidden;
height: 550px;
}
</style>
@code {
private EntityModel? _entity;
private string _viewType = EntityViewType.Detailed;
protected override void OnInitialized()
{
base.OnInitialized();
KeyService.Subscribe(HandleClick);
StorageService.Subscribe(RefreshDefaults);
RefreshDefaults();
}
void IDisposable.Dispose()
{
KeyService.Unsubscribe(HandleClick);
StorageService.Unsubscribe(RefreshDefaults);
}
void RefreshDefaults()
{
_viewType = StorageService.GetValue<bool>(StorageKeys.IsPlainView) ? EntityViewType.Plain : EntityViewType.Detailed;
}
protected override bool ShouldRender()
{
#if DEBUG
JsRuntime.InvokeVoidAsync("console.time", "EntityClickViewComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
JsRuntime.InvokeVoidAsync("console.timeEnd", "EntityClickViewComponent");
#endif
}
private void HandleClick()
{
var hotkey = KeyService.GetHotkey();
var hotkeyGroup = KeyService.GetHotkeyGroup();
var isHoldSpace = KeyService.IsHoldingSpace();
var faction = FilterService.GetFaction();
var immortal = FilterService.GetImmortal();
var foundEntity = EntityModel.GetFrom(hotkey!, hotkeyGroup, isHoldSpace, faction, immortal);
if (foundEntity != null && _entity != foundEntity)
{
_entity = foundEntity;
StateHasChanged();
}
}
}
@@ -0,0 +1,80 @@
@inject IJSRuntime JsRuntime;
@inject IImmortalSelectionService FilterService
<FormLayoutComponent>
<FormSelectComponent OnChange="@OnFactionChanged">
<FormLabelComponent>Faction</FormLabelComponent>
<ChildContent>
<option value="@DataType.FACTION_Aru"
selected="@(FilterService.GetFaction().Equals(DataType.FACTION_Aru))">
Aru
</option>
<option value="@DataType.FACTION_QRath"
selected="@(FilterService.GetFaction().Equals(DataType.FACTION_QRath))">
Q'Rath
</option>
</ChildContent>
</FormSelectComponent>
<FormSelectComponent OnChange="@OnImmortalChanged">
<FormLabelComponent>Immortal</FormLabelComponent>
<ChildContent>
@if (FilterService.GetFaction() == DataType.FACTION_QRath)
{
<option value="@DataType.IMMORTAL_Orzum"
selected="@(FilterService.GetImmortal().Equals(DataType.IMMORTAL_Orzum))">
Orzum
</option>
<option value="@DataType.IMMORTAL_Ajari"
selected="@(FilterService.GetImmortal().Equals(DataType.IMMORTAL_Ajari))">
Ajari
</option>
}
@if (FilterService.GetFaction() == DataType.FACTION_Aru)
{
<option value="@DataType.IMMORTAL_Atzlan"
selected="@(FilterService.GetImmortal().Equals(DataType.IMMORTAL_Atzlan))">
Atzlan
</option>
<option value="@DataType.IMMORTAL_Mala"
selected="@(FilterService.GetImmortal().Equals(DataType.IMMORTAL_Mala))">
Mala
</option>
<option value="@DataType.IMMORTAL_Xol"
selected="@(FilterService.GetImmortal().Equals(DataType.IMMORTAL_Xol))">
Xol
</option>
}
</ChildContent>
</FormSelectComponent>
</FormLayoutComponent>
@code {
void OnFactionChanged(ChangeEventArgs e)
{
FilterService.SelectFaction(e.Value!.ToString()!);
}
void OnImmortalChanged(ChangeEventArgs e)
{
FilterService.SelectImmortal(e.Value!.ToString()!);
}
protected override bool ShouldRender()
{
#if DEBUG
JsRuntime.InvokeVoidAsync("console.time", "FilterComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
JsRuntime.InvokeVoidAsync("console.timeEnd", "FilterComponent");
#endif
}
}
@@ -0,0 +1,89 @@
@inject IJSRuntime jsRuntime;
@inject IEconomyService economyService
@inject IBuildOrderService buildOrderService
@inject ITimingService timingService
@implements IDisposable
<div class="highlightsContainer">
<div>
<div>Requested</div>
@foreach (var ordersAtTime in buildOrderService.StartedOrders.Reverse())
{
foreach (var order in ordersAtTime.Value)
{
<div>
@ordersAtTime.Key | T @Interval.ToTime(ordersAtTime.Key)
</div>
<div>
@order.Info().Name
</div>
<br/>
}
}
</div>
<div>
<div>Finished</div>
@foreach (var ordersAtTime in buildOrderService.CompletedOrders.Reverse())
{
foreach (var order in ordersAtTime.Value)
{
<div>
@ordersAtTime.Key | T @Interval.ToTime(ordersAtTime.Key)
</div>
<div>
@order.Info().Name
</div>
<br/>
}
}
</div>
</div>
<style>
.highlightsContainer {
overflow-y: scroll;
overflow-x: hidden;
height: 400px;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4px;
}
</style>
@code {
protected override void OnInitialized()
{
base.OnInitialized();
economyService.Subscribe(StateHasChanged);
buildOrderService.Subscribe(StateHasChanged);
}
void IDisposable.Dispose()
{
economyService.Unsubscribe(StateHasChanged);
buildOrderService.Unsubscribe(StateHasChanged);
}
protected override bool ShouldRender()
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.time", "HighlightsComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.timeEnd", "HighlightsComponent");
#endif
}
}
@@ -0,0 +1,400 @@
@inject IJSRuntime JsRuntime;
@using Services.Website
@implements IDisposable
@inject IKeyService KeyService
@inject IBuildOrderService BuildOrderService
@inject IImmortalSelectionService FilterService
@inject IEconomyService EconomyService
@inject ITimingService TimingService
@inject IToastService ToastService
@inject IDataCollectionService DataCollectionService
<InputPanelComponent>
<div class="keyContainer">
@foreach (var hotkey in hotkeys)
{
if (hotkey.IsHidden)
{
continue;
}
var color = (hotkey.KeyText.Equals("SPACE") && KeyService.IsHoldingSpace()) || KeyService.GetAllPressedKeys().Contains(hotkey.KeyText)
? hotkey.GetColor()
: hotkey.GetColor();
var x = hotkey.PositionX * Size;
var y = hotkey.PositionY * Size + (hotkey.PositionY == 0 ? 5 : -50);
var width = Size * hotkey.Width;
var height = hotkey.PositionY == 0 ? 50 : Size;
var borderRadius = hotkey.PositionY == 0 ? 12 : 0;
var border = "1px solid black";
if (hotkey.KeyText.Equals(key))
{
border = "5px solid black";
}
if (hotkey.KeyText.Equals(controlGroup))
{
color = "#257525";
}
if (hotkey.KeyText.Equals("SPACE") && KeyService.IsHoldingSpace())
{
border = "5px solid green";
}
var keyText = hotkey.KeyText.Equals("CAPSLOCK") ? "Caps"
: hotkey.KeyText.Equals("CONTROL") ? "Ctrl"
: hotkey.KeyText.Equals("SHIFT") ? "Shift"
: hotkey.KeyText.Equals("X") ? "X"
: hotkey.KeyText.Equals("SPACE") ? "Space" : hotkey.KeyText;
var controlStyle = $"background-color:{color}; " +
$"width: {width}px; " +
"border-top: 1px solid black; " +
"border-left: 1px solid black; " +
"border-right: 1px solid black; " +
$"border-top-left-radius: {borderRadius}px; " +
$"border-top-right-radius: {borderRadius}px; " +
"overflow: hidden; " +
"text-align: center;";
var keyStyle = $"background-color:{color}; " +
$"border: {border}; " +
$"width: {width}px; " +
$"height: {height}px; " +
"overflow: hidden; " +
"padding: 4px;";
var usedStyle = hotkey.PositionY == 0 ? controlStyle : keyStyle;
<div style="position:relative;
cursor:pointer;
top:@y.ToString()px;
left:@x.ToString()px;
width: 0px;
height: 0px;">
<div @onclick="e => ButtonClicked(e, hotkey)" style="@usedStyle">
@keyText
@foreach (var entity in data.Values)
{
if (InvalidKey(entity, hotkey) || InvalidKeyGroup(entity, hotkey) || InvalidHoldSpace(entity))
{
continue;
}
if (InvalidFaction(entity))
{
continue;
}
if (InvalidVanguard(entity) || InvalidNonVanguard(entity))
{
continue;
}
var isVanguard = entity.VanguardAdded() != null;
var style = isVanguard ? "font-weight: bold;" : "";
if (BuildOrderService.WillMeetRequirements(entity) == null)
{
style += "color:gray; font-style: italic;";
}
<div style="@style">@entity.Info()?.Name</div>
}
</div>
</div>
}
</div>
</InputPanelComponent>
<style>
.keyContainer {
width: 400px;
max-width: 95vw;
height: 350px;
outline: 3px solid black;
border-radius: 8px;
background-color: #282A30;
margin: auto;
}
@@media only screen and (max-width: 1025px) {
.keyContainer {
transform: scale(0.85) translateX(-20px);
background-color: transparent;
outline: none;
}
}
</style>
@code {
[Parameter] public int Size { get; set; } = 100;
readonly Dictionary<string, EntityModel> data = EntityModel.GetDictionary();
readonly List<HotkeyModel> hotkeys = HotkeyModel.GetAll();
private string controlGroup = "C";
private string key = "";
protected override void OnInitialized()
{
base.OnInitialized();
KeyService.Subscribe(OnKeyPressed);
FilterService.Subscribe(StateHasChanged);
BuildOrderService.Subscribe(OnBuilderOrderChanged);
}
void IDisposable.Dispose()
{
KeyService.Unsubscribe(OnKeyPressed);
FilterService.Unsubscribe(StateHasChanged);
BuildOrderService.Unsubscribe(OnBuilderOrderChanged);
}
int completedTimeCount;
void OnBuilderOrderChanged()
{
if (BuildOrderService.UniqueCompletedTimes.Count != completedTimeCount)
{
completedTimeCount = BuildOrderService.UniqueCompletedTimes.Count;
StateHasChanged();
}
}
protected override bool ShouldRender()
{
#if DEBUG
JsRuntime.InvokeVoidAsync("console.time", "HotKeyViewerComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
JsRuntime.InvokeVoidAsync("console.timeEnd", "HotKeyViewerComponent");
#endif
}
// Move to Filter Service
bool InvalidFaction(EntityModel entity)
{
if (entity.Faction() != null && entity.Faction()?.Faction != FilterService.GetFaction() && FilterService.GetFaction() != DataType.Any)
{
return true;
}
return false;
}
// Move to Filter Service
bool InvalidVanguard(EntityModel entity)
{
if (entity.VanguardAdded() != null
&& entity.VanguardAdded()?.ImmortalId != FilterService.GetImmortal()
&& FilterService.GetImmortal() != DataType.Any)
{
return true;
}
return false;
}
// Move to Filter Service
bool InvalidNonVanguard(EntityModel entity)
{
if (entity.Replaceds().Count > 0)
{
foreach (var replaced in entity.Replaceds())
{
if (FilterService.GetImmortal() == replaced.ImmortalId)
{
return true;
}
}
}
return false;
}
bool InvalidKey(EntityModel entity, HotkeyModel key)
{
if (entity.Hotkey()?.Hotkey == key.KeyText)
{
return false;
}
return true;
}
bool InvalidKeyGroup(EntityModel entity, HotkeyModel key)
{
if (entity.Hotkey()?.HotkeyGroup == controlGroup)
{
return false;
}
return true;
}
bool InvalidKey(EntityModel entity)
{
if (entity.Hotkey()?.Hotkey == key)
{
return false;
}
return true;
}
bool InvalidKeyGroup(EntityModel entity)
{
if (entity.Hotkey()?.HotkeyGroup == controlGroup)
{
return false;
}
return true;
}
bool InvalidHoldSpace(EntityModel entity)
{
if (entity.Hotkey()?.HoldSpace == KeyService.IsHoldingSpace())
{
return false;
}
return true;
}
void OnKeyPressed()
{
var controlGroupWas = controlGroup;
var keyWas = key;
if (KeyService.GetAllPressedKeys().Contains("Z"))
{
controlGroup = "Z";
}
if (KeyService.GetAllPressedKeys().Contains("X"))
{
controlGroup = "X";
}
if (KeyService.GetAllPressedKeys().Contains("C"))
{
controlGroup = "C";
}
if (KeyService.GetAllPressedKeys().Contains("D"))
{
controlGroup = "D";
}
if (KeyService.GetAllPressedKeys().Contains("V"))
{
controlGroup = "V";
}
if (KeyService.GetAllPressedKeys().Contains("ALT"))
{
controlGroup = "ALT";
}
if (KeyService.GetAllPressedKeys().Contains("SHIFT"))
{
controlGroup = "SHIFT";
}
if (KeyService.GetAllPressedKeys().Contains("CONTROL"))
{
controlGroup = "CONTROL";
}
if (KeyService.GetAllPressedKeys().Count > 0)
{
key = KeyService.GetAllPressedKeys().First();
}
if (controlGroupWas != controlGroup || keyWas != key)
{
StateHasChanged();
}
}
private void HandleClick()
{
var hotkey = KeyService.GetHotkey();
if (hotkey is "`")
HandleCancelEntity();
if (EntityFromKey(hotkey, out var entity))
return;
if (BuildOrderService.Add(entity!, EconomyService))
EconomyService.Calculate(BuildOrderService, TimingService, BuildOrderService.GetLastRequestInterval());
}
private void HandleCancelEntity()
{
BuildOrderService.RemoveLast();
EconomyService.Calculate(BuildOrderService, TimingService, BuildOrderService.GetLastRequestInterval());
}
private bool EntityFromKey(string? hotkey, out EntityModel? entity)
{
var hotkeyGroup = KeyService.GetHotkeyGroup();
var isHoldSpace = KeyService.IsHoldingSpace();
var faction = FilterService.GetFaction();
var immortal = FilterService.GetImmortal();
entity = EntityModel.GetFrom(hotkey!, hotkeyGroup, isHoldSpace, faction, immortal);
return entity == null;
}
private void ButtonClicked(MouseEventArgs mouseEventArgs, HotkeyModel hotkey)
{
DataCollectionService.SendEvent(
DataCollectionKeys.BuildCalcInput,
new Dictionary<string, string> { { "key", hotkey.KeyText.ToLower() }, { "input-source", "mouse" } }
);
if (hotkey.KeyText.Equals(HotKeyType.SPACE.ToString()))
{
if (KeyService.IsHoldingSpace())
{
KeyService.RemovePressedKey(hotkey.KeyText);
}
else
{
KeyService.AddPressedKey(hotkey.KeyText);
}
}
else
{
KeyService.AddPressedKey(hotkey.KeyText);
KeyService.RemovePressedKey(hotkey.KeyText);
}
}
}
@@ -0,0 +1,52 @@
@using Services.Website
@inject IKeyService KeyService
@inject IDataCollectionService DataCollectionService
@inject IJSRuntime JsRuntime
<div tabindex="0"
style="margin: auto;"
@onkeydown="HandleKeyDown"
@onkeyup="HandleKeyUp"
@onkeydown:preventDefault="true"
@onkeydown:stopPropagation="true">
@ChildContent
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
private void HandleKeyDown(KeyboardEventArgs e)
{
DataCollectionService.SendEvent(
DataCollectionKeys.BuildCalcInput,
new Dictionary<string, string> { { "key", e.Key.ToLower() }, { "input-source", "keyboard" } }
);
KeyService.AddPressedKey(e.Key);
}
private void HandleKeyUp(KeyboardEventArgs e)
{
KeyService.RemovePressedKey(e.Key);
}
protected override bool ShouldRender()
{
#if DEBUG
JsRuntime.InvokeVoidAsync("console.time", "InputPanelComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
JsRuntime.InvokeVoidAsync("console.timeEnd", "InputPanelComponent");
#endif
}
}
@@ -0,0 +1,124 @@
@inject IJSRuntime JsRuntime;
@inject IBuildOrderService BuildOrderService
@inject IEconomyService EconomyService
@inject IToastService ToastService
@inject ITimingService TimingService
@implements IDisposable
<FormLayoutComponent>
<FormNumberComponent Max="600"
Min="0"
Value="BuildDelay"
OnChange="@OnBuildingInputDelayChanged">
<FormLabelComponent>Building Input Delay</FormLabelComponent>
<FormInfoComponent>Add a input delay to constructing buildings for simulating worker movement and player
micro.
</FormInfoComponent>
</FormNumberComponent>
<div class="optionRow">
<FormLayoutComponent>
<FormNumberComponent Max="600"
Min="1"
Value="WaitTime"
OnChange="@OnWaitTimeChanged">
<FormLabelComponent>Wait Time</FormLabelComponent>
</FormNumberComponent>
<ButtonComponent OnClick="OnWaitClicked">Add Wait</ButtonComponent>
</FormLayoutComponent>
<FormLayoutComponent>
<FormNumberComponent Max="2048"
Min="1"
Value="WaitTo"
OnChange="@OnWaitToChanged">
<FormLabelComponent>Wait To</FormLabelComponent>
</FormNumberComponent>
<ButtonComponent OnClick="OnWaitToClicked">Add Wait</ButtonComponent>
</FormLayoutComponent>
</div>
</FormLayoutComponent>
<style>
.optionRow {
display: flex;
gap: 12px;
}
</style>
@code {
private int BuildDelay { get; set; } = 2;
private int WaitTime { get; set; } = 30;
private int WaitTo { get; set; } = 30;
protected override void OnInitialized()
{
base.OnInitialized();
TimingService.Subscribe(RefreshDefaults);
RefreshDefaults();
}
void IDisposable.Dispose()
{
TimingService.Unsubscribe(RefreshDefaults);
}
void RefreshDefaults()
{
BuildDelay = TimingService.BuildingInputDelay;
WaitTime = TimingService.WaitTime;
WaitTo = TimingService.WaitTo;
StateHasChanged();
}
void OnBuildingInputDelayChanged(ChangeEventArgs changeEventArgs)
{
TimingService.BuildingInputDelay = int.Parse(changeEventArgs.Value!.ToString()!);
}
void OnWaitTimeChanged(ChangeEventArgs changeEventArgs)
{
TimingService.WaitTime = (int)changeEventArgs.Value!;
WaitTime = (int)changeEventArgs.Value!;
}
void OnWaitToChanged(ChangeEventArgs changeEventArgs)
{
TimingService.WaitTo = (int)changeEventArgs.Value!;
WaitTo = (int)changeEventArgs.Value!;
}
private void OnWaitClicked()
{
if (BuildOrderService.AddWait(WaitTime))
{
EconomyService.Calculate(BuildOrderService, TimingService, BuildOrderService.GetLastRequestInterval());
}
}
private void OnWaitToClicked()
{
if (BuildOrderService.AddWaitTo(WaitTo))
{
EconomyService.Calculate(BuildOrderService, TimingService, BuildOrderService.GetLastRequestInterval());
}
}
protected override bool ShouldRender()
{
#if DEBUG
JsRuntime.InvokeVoidAsync("console.time", "TimingComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
JsRuntime.InvokeVoidAsync("console.timeEnd", "TimingComponent");
#endif
}
}
@@ -0,0 +1,88 @@
@inject IJSRuntime jsRuntime
@inject IEconomyService economyService
@inject IBuildOrderService buildOrderService
@implements IDisposable
<Virtualize Items="@economyService.GetOverTime()" Context="economyAtSecond" ItemSize="400" OverscanCount="4">
<div style="display: grid; gap: 8px; grid-template-columns: 1fr 1fr;">
<div>
<div>
@economyAtSecond.Interval
</div>
<div>
T @Interval.ToTime(economyAtSecond.Interval) | A @economyAtSecond.Alloy | E @economyAtSecond.Ether
</div>
<div>
Worker Count: @(economyAtSecond.WorkerCount)
</div>
<div>
Free Worker Count: @(economyAtSecond.WorkerCount - economyAtSecond.BusyWorkerCount)
</div>
<div>
Busy Worker Count: @economyAtSecond.BusyWorkerCount
</div>
<div>
Creating Worker Count: @economyAtSecond.CreatingWorkerCount
</div>
<br/>
</div>
<div>
@if (buildOrderService.StartedOrders.TryGetValue(economyAtSecond.Interval, out var ordersAtTime))
{
@foreach (var order in ordersAtTime)
{
<div>
Requested: @order.Info().Name
</div>
}
}
@if (buildOrderService.CompletedOrders.TryGetValue(economyAtSecond.Interval, out var ordersCompletedAtTime))
{
@foreach (var order in ordersCompletedAtTime)
{
<div>
New: @order.Info().Name
</div>
}
}
</div>
</div>
</Virtualize>
@code {
protected override void OnInitialized()
{
base.OnInitialized();
economyService.Subscribe(StateHasChanged);
buildOrderService.Subscribe(StateHasChanged);
}
void IDisposable.Dispose()
{
economyService.Unsubscribe(StateHasChanged);
buildOrderService.Unsubscribe(StateHasChanged);
}
protected override bool ShouldRender()
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.time", "TimelineComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.timeEnd", "TimelineComponent");
#endif
}
}
@@ -0,0 +1,105 @@
@inject IJSRuntime jsRuntime;
@inject IBuildOrderService buildOrderService
@inject IEconomyService economyService
@inject IToastService toastService
@inject ITimingService timingService
@implements IDisposable
<FormLayoutComponent>
<FormNumberComponent Max="2048"
Min="0"
Value="@timingService.GetAttackTime()"
OnChange="@OnAttackTimeChanged">
<FormLabelComponent>Attack Time</FormLabelComponent>
<FormInfoComponent>
<i>&emsp; T @Interval.ToTime(timingService.GetAttackTime())</i>
</FormInfoComponent>
</FormNumberComponent>
<FormNumberComponent Max="2048"
Min="0"
Value="@timingService.GetTravelTime()"
OnChange="@OnTravelTimeChanged">
<FormLabelComponent>Travel Time</FormLabelComponent>
<FormInfoComponent>
<i>&emsp; T @Interval.ToTime(timingService.GetTravelTime())</i>
</FormInfoComponent>
</FormNumberComponent>
</FormLayoutComponent>
@code {
protected override void OnInitialized()
{
base.OnInitialized();
timingService.Subscribe(StateHasChanged);
}
void IDisposable.Dispose()
{
timingService.Unsubscribe(StateHasChanged);
}
void OnAttackTimeChanged(ChangeEventArgs changeEventArgs)
{
timingService.SetAttackTime(int.Parse(changeEventArgs.Value!.ToString()!));
economyService.Calculate(buildOrderService, timingService, buildOrderService.GetLastRequestInterval());
toastService.AddToast(new ToastModel
{
Title = "Attack Time",
Message = "Attack Time has changed.",
SeverityType = SeverityType.Success
});
StateHasChanged();
}
void OnTravelTimeChanged(ChangeEventArgs changeEventArgs)
{
timingService.SetTravelTime(int.Parse(changeEventArgs.Value!.ToString()!));
economyService.Calculate(buildOrderService, timingService, buildOrderService.GetLastRequestInterval());
toastService.AddToast(new ToastModel
{
Title = "Travel Time",
Message = "Travel Time has changed.",
SeverityType = SeverityType.Success
});
StateHasChanged();
}
void OnNameChanged(ChangeEventArgs changeEventArgs)
{
buildOrderService.SetName(changeEventArgs.Value!.ToString()!);
}
void OnColorChanged(ChangeEventArgs changeEventArgs)
{
buildOrderService.DeprecatedSetColor(changeEventArgs.Value!.ToString()!);
}
void OnNotesChanged(ChangeEventArgs changeEventArgs)
{
buildOrderService.SetNotes(changeEventArgs.Value!.ToString()!);
}
protected override bool ShouldRender()
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.time", "TimingComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.timeEnd", "TimingComponent");
#endif
}
}
+22
View File
@@ -0,0 +1,22 @@
@layout PageLayout
@inject IDataCollectionService DataCollectionService
@inherits BasePage
@page "/contact"
<LayoutMediumContentComponent>
<WebsiteTitleComponent>Contact</WebsiteTitleComponent>
<PaperComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
How do I contact you for feature requests and bug reports?
</InfoQuestionComponent>
<InfoAnswerComponent>
You can message me at <b>JonathanMcCaffrey#3544</b> on my <a href="https://discord.gg/uMq8bMGeeN"
target="_blank">Discord</a> channel.
</InfoAnswerComponent>
</InfoBodyComponent>
</PaperComponent>
</LayoutMediumContentComponent>
+24
View File
@@ -0,0 +1,24 @@
@page "/data-collection"
@inject IDataCollectionService DataCollectionService
@inherits BasePage
@layout PageLayout
<LayoutMediumContentComponent>
<AlertComponent>
<Title>Not Implemented</Title>
<Message></Message>
</AlertComponent>
<PaperComponent>
TODO
</PaperComponent>
<ContentDividerComponent/>
<PaperComponent>
</PaperComponent>
</LayoutMediumContentComponent>
@@ -0,0 +1,59 @@
@layout PageLayout
@using IGP.Pages.DataTables.Parts
@inherits BasePage
@page "/data-tables"
<LayoutLargeContentComponent>
<WebsiteTitleComponent>Data Tables</WebsiteTitleComponent>
<AlertComponent Type="@SeverityType.Warning">
<Title>Errors Present</Title>
<Message>
Incomplete feature for easily comparing unit stats.
</Message>
</AlertComponent>
<MudTabs Elevation="2">
<MudTabPanel Text="Attacks">
<WeaponTable/>
</MudTabPanel>
<MudTabPanel Text="Production">
<ProductionTable/>
</MudTabPanel>
<MudTabPanel Text="Health">
<VitalityTable/>
</MudTabPanel>
<MudTabPanel Text="Movement">
<MovementTable/>
</MudTabPanel>
</MudTabs>
<ContentDividerComponent></ContentDividerComponent>
<PaperComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
What is this tool?
</InfoQuestionComponent>
<InfoAnswerComponent>
This tool is a data table of all information belonging to a type of data. Such as viewing all weapons,
so one can easily sort and see what weapon attack has the highest range, and what faction and unit that
attack belongs to.
</InfoAnswerComponent>
</InfoBodyComponent>
</PaperComponent>
</LayoutLargeContentComponent>
<style>
</style>
@code {
}
@@ -0,0 +1,21 @@
<MudDataGrid T="EntityMovementModel" Items="@_data"
SortMode="SortMode.Multiple"
Filterable="true"
Hideable="true">
<Columns>
<PropertyColumn Property="x => x.Parent.GetName()" Title="Entity"/>
<PropertyColumn Property="x => x.Movement"/>
<PropertyColumn Property="x => x.Speed"/>
<PropertyColumn Property="x => x.Parent.GetFaction()" Title="Faction"/>
<PropertyColumn Property="x => x.Parent.GetImmortal()" Title="Immortal"/>
</Columns>
</MudDataGrid>
@code {
readonly IEnumerable<EntityMovementModel> _data = EntityData.Get()
.SelectMany(e => e.Value.EntityParts)
.OfType<EntityMovementModel>()
.ToList();
}
@@ -0,0 +1,22 @@
<MudDataGrid T="EntityProductionModel" Items="@data"
SortMode="SortMode.Multiple"
Filterable="true"
Hideable="true">
<Columns>
<PropertyColumn Property="x => x.Parent.GetName()" Title="Entity"/>
<PropertyColumn Property="x => x.Alloy"/>
<PropertyColumn Property="x => x.BuildTime"/>
<PropertyColumn Property="x => x.Ether"/>
<PropertyColumn Property="x => x.Parent.GetFaction()" Title="Faction"/>
<PropertyColumn Property="x => x.Parent.GetImmortal()" Title="Immortal"/>
</Columns>
</MudDataGrid>
@code {
readonly IEnumerable<EntityProductionModel> data = EntityData.Get()
.SelectMany(e => e.Value.EntityParts)
.OfType<EntityProductionModel>()
.ToList();
}
@@ -0,0 +1,25 @@
<MudDataGrid T="EntityVitalityModel" Items="@_data"
SortMode="SortMode.Multiple"
Filterable="true"
Hideable="true">
<Columns>
<PropertyColumn Property="x => x.Parent.GetName()" Title="Entity"/>
<PropertyColumn Property="x => x.Health"/>
<PropertyColumn Property="x => x.Armor"/>
<PropertyColumn Property="x => x.Defense"/>
<PropertyColumn Property="x => x.DefenseLayer"/>
<PropertyColumn Property="x => x.IsStructure"/>
<PropertyColumn Property="x => x.IsEtheric"/>
<PropertyColumn Property="x => x.Parent.GetFaction()" Title="Faction"/>
<PropertyColumn Property="x => x.Parent.GetImmortal()" Title="Immortal"/>
</Columns>
</MudDataGrid>
@code {
readonly IEnumerable<EntityVitalityModel> _data = EntityData.Get()
.SelectMany(e => e.Value.EntityParts)
.OfType<EntityVitalityModel>()
.ToList();
}
@@ -0,0 +1,31 @@
<MudDataGrid T="EntityWeaponModel" Items="@_data"
SortMode="SortMode.Multiple"
Filterable="true"
Hideable="true">
<Columns>
<PropertyColumn Property="x => x.Parent.GetName()" Title="Entity"/>
<PropertyColumn Property="x => x.Range" Title="Range"/>
<PropertyColumn Property="x => x.Targets" Title="Targets"/>
<PropertyColumn Property="x => x.Damage" Title="Damage"/>
<PropertyColumn Property="x => x.AttacksPerSecond" Title="Attacks Per Second"/>
<PropertyColumn Property="x => x.DamagePerSecond()" Title="DPS"/>
<PropertyColumn Property="x => x.HasSplash" Title="Has Splash"/>
<PropertyColumn Property="x => x.DamagePerSecondLight()" Title="DPS (Light)"/>
<PropertyColumn Property="x => x.DamagePerSecondMedium()" Title="DPS (Medium)"/>
<PropertyColumn Property="x => x.DamagePerSecondHeavy()" Title="DPS (Heavy)"/>
<PropertyColumn Property="x => x.Parent.GetFaction()" Title="Faction"/>
<PropertyColumn Property="x => x.Parent.GetImmortal()" Title="Immortal"/>
</Columns>
<PagerContent>
<MudDataGridPager T="EntityWeaponModel"/>
</PagerContent>
</MudDataGrid>
@code {
readonly IEnumerable<EntityWeaponModel> _data = EntityData.Get()
.SelectMany(e => e.Value.EntityParts)
.OfType<EntityWeaponModel>()
.ToList();
}
@@ -0,0 +1,261 @@
@layout PageLayout
@inherits BasePage
@page "/database"
@using Model
@implements IDisposable
@inject IEntityDisplayService EntityDisplayService
<LayoutLargeContentComponent>
<WebsiteTitleComponent>Database</WebsiteTitleComponent>
<PaperComponent>
<FormDisplayComponent Label="Patch">
<Display>
Game Patch: @Variables.GamePatch
</Display>
</FormDisplayComponent>
</PaperComponent>
<div style="margin-left: 8px">
<ButtonGroupComponent OnClick="choice => { EntityDisplayService.SetDisplayType(choice); }"
Choice="@EntityDisplayService.GetDisplayType()"
Choices="@EntityDisplayService.DefaultChoices()"></ButtonGroupComponent>
</div>
<PaperComponent>
<EntityFilterComponent></EntityFilterComponent>
@if (searches != null)
{
<div class="databaseItems">
@foreach (var entity in searches)
{
<CascadingValue Value="entity">
<CascadingValue Value="@EntityDisplayService.GetDisplayType()">
<EntityViewComponent></EntityViewComponent>
</CascadingValue>
</CascadingValue>
}
</div>
}
</PaperComponent>
<ContentDividerComponent></ContentDividerComponent>
<PaperComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
What is this tool?
</InfoQuestionComponent>
<InfoAnswerComponent>
This is a reference database. Mostly so unit stats can be reviewed outside of the game, IMMORTAL: Gates
of Pyre.
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
Is this database complete?
</InfoQuestionComponent>
<InfoAnswerComponent>
No. A lot of content is missing, that needs to be manually transfered from screenshots of IMMORTAL:
Gates of Pyre. This will happen slowly over-time.
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
Is this database updated to the latest version?
</InfoQuestionComponent>
<InfoAnswerComponent>
Maybe. Check this <b>@Variables.GamePatch</b> version number, and compare it to the
number on discord, in the <b>#game-updates</b> channel. That should give a general sense of how out of
date the data is.
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
This database has some errors in it.
</InfoQuestionComponent>
<InfoAnswerComponent>
Yup. The content is being transferred by hand, so some mistakes are expected.
</InfoAnswerComponent>
</InfoBodyComponent>
</PaperComponent>
</LayoutLargeContentComponent>
<style>
.databaseItems {
height: 900px;
overflow-x: hidden;
overflow-y: auto;
background-color: var(--background);
gap: 4px;
border-top: 4px solid var(--accent);
border-bottom: 4px solid var(--accent);
padding: 4px;
}
.databaseInfoContainer {
display: flex;
gap: 24px;
}
</style>
@code {
[Inject] public IEntityFilterService EntityFilterService { get; set; } = default!;
readonly List<EntityModel> defaults = (from entity in EntityModel.GetList()
where entity.IsSpeculative == false
select entity).ToList();
List<EntityModel> factions = default!;
List<EntityModel> immortals = default!;
List<EntityModel> entities = default!;
List<EntityModel> searches = default!;
string selectedFactionType = DataType.Any;
string selectedImmortalType = DataType.Any;
string selectedEntityType = EntityType.Army;
string searchText = "";
protected override void OnInitialized()
{
base.OnInitialized();
RefreshFactionSearch();
EntityFilterService.Subscribe(OnChange);
EntityDisplayService.Subscribe(StateHasChanged);
}
void IDisposable.Dispose()
{
EntityFilterService.Unsubscribe(OnChange);
EntityDisplayService.Unsubscribe(StateHasChanged);
}
void OnChange(EntityFilterEvent filterEntityEvent)
{
if (filterEntityEvent == EntityFilterEvent.OnRefreshFaction)
{
RefreshFactionSearch();
}
if (filterEntityEvent == EntityFilterEvent.OnRefreshImmortal)
{
RefreshImmortalSearch();
}
if (filterEntityEvent == EntityFilterEvent.OnRefreshEntity)
{
RefreshEntitySearch();
}
if (filterEntityEvent == EntityFilterEvent.OnRefreshSearch)
{
RefreshTextSearch();
}
}
void RefreshFactionSearch()
{
selectedFactionType = EntityFilterService.GetFactionType();
if (selectedFactionType == DataType.Any)
{
factions = defaults.ToList();
}
else
{
factions = (from entity in defaults
where entity.Faction() != null && entity.Faction().Faction == selectedFactionType
select entity).ToList();
}
RefreshImmortalSearch();
}
void OnImmortalChanged(ChangeEventArgs e)
{
selectedImmortalType = e.Value!.ToString()!;
RefreshImmortalSearch();
}
void RefreshImmortalSearch()
{
selectedImmortalType = EntityFilterService.GetImmortalType();
if (selectedImmortalType == DataType.Any)
{
immortals = factions.ToList();
}
else
{
immortals = (from entity in factions
where entity.VanguardAdded() == null || entity.VanguardAdded().ImmortalId == selectedImmortalType
select entity).ToList();
}
RefreshEntitySearch();
}
void OnEntityChanged(ChangeEventArgs e)
{
selectedEntityType = e.Value!.ToString()!;
RefreshEntitySearch();
}
void RefreshEntitySearch()
{
selectedEntityType = EntityFilterService.GetEntityType();
if (selectedEntityType == EntityType.Any)
{
entities = immortals.ToList();
}
else
{
entities = (from entity in immortals
where entity.EntityType == selectedEntityType
select entity).ToList();
}
RefreshTextSearch();
}
void OnSearchTextChanged(ChangeEventArgs e)
{
searchText = e.Value!.ToString()!;
RefreshTextSearch();
}
void RefreshTextSearch()
{
searchText = EntityFilterService.GetSearchText();
if (searchText.Trim() == "")
{
searches = entities.ToList();
}
else
{
searches = (from entity in entities
where entity.Info().Name.ToLower().Contains(searchText.ToLower())
select entity).ToList();
}
StateHasChanged();
}
}
@@ -0,0 +1,100 @@
@layout PageLayout
@inherits BasePage
@page "/database/{text}"
@inject IEntityDisplayService EntityDisplayService
@using Model
@implements IDisposable
<LayoutLargeContentComponent>
<PaperComponent>
<FormDisplayComponent Label="Patch">
<Display>
Game Patch: @Variables.GamePatch
</Display>
</FormDisplayComponent>
</PaperComponent>
<div style="margin-left: 8px">
<ButtonGroupComponent OnClick="choice => { EntityDisplayService.SetDisplayType(choice); }"
Choice="@EntityDisplayService.GetDisplayType()"
Choices="@EntityDisplayService.DefaultChoices()"></ButtonGroupComponent>
</div>
@if (Text!.Trim().ToLower().Equals("walter"))
{
<PaperComponent>
<CodeComponent>
Unhandled Exception: EXCEPTION_MEMORY_SIZE_VIOLATION
UNIT_WALTER too powerful to be displayed.
This SHOULD NEVER HAPPEN!
</CodeComponent>
</PaperComponent>
}
else if (_entity == null)
{
<PaperComponent>
<div>Invalid entity name entered: <span id="invalidSearch">@Text</span></div>
<div>No such entity. Did you mean <b>"<span id="validSearch">Throne</span>"</b>?</div>
</PaperComponent>
}
else
{
<PaperComponent>
<CascadingValue Value="_entity">
<CascadingValue Value="@EntityDisplayService.GetDisplayType()">
<EntityViewComponent></EntityViewComponent>
</CascadingValue>
</CascadingValue>
</PaperComponent>
}
</LayoutLargeContentComponent>
@code {
[Parameter] public string? Text { get; set; }
private EntityModel? _entity;
protected override void OnInitialized()
{
EntityDisplayService.Subscribe(StateHasChanged);
}
protected override void OnParametersSet()
{
base.OnParametersSet();
base.OnInitialized();
FocusEntity();
}
private void FocusEntity()
{
foreach (var e in EntityData.Get().Values)
{
if (e.Info().Name.ToLower().Equals(Text!.ToLower()))
{
_entity = e;
return;
}
}
}
void IDisposable.Dispose()
{
EntityDisplayService.Unsubscribe(StateHasChanged);
}
}
@@ -0,0 +1,56 @@
@if (Entity != null)
{
var isVanguard = Entity.VanguardAdded() != null ? " vanguard" : "";
<div id="@Entity.EntityType.ToLower()-@Entity.Info().Name.ToLower()" class="entitiesContainer @isVanguard">
<EntityHeaderComponent></EntityHeaderComponent>
<div class="entityPartsContainer">
<EntityVanguardAddedComponent></EntityVanguardAddedComponent>
<EntityInfoComponent></EntityInfoComponent>
<EntityVanguardsComponent></EntityVanguardsComponent>
<EntityProductionComponent></EntityProductionComponent>
<EntityStatsComponent></EntityStatsComponent>
<EntityMechanicsComponent></EntityMechanicsComponent>
<EntityPassivesComponent></EntityPassivesComponent>
<EntityPyreSpellsComponent></EntityPyreSpellsComponent>
<EntityUpgradesComponent></EntityUpgradesComponent>
<EntityWeaponsComponent></EntityWeaponsComponent>
<EntityAbilitiesComponent></EntityAbilitiesComponent>
</div>
</div>
}
<style>
.entitiesContainer {
margin-bottom: 12px;
width: 100%;
overflow-y: auto;
overflow-x: hidden;
padding: 30px;
display: flex;
flex-wrap: wrap;
flex-direction: column;
}
.entityPartsContainer {
display: flex;
flex-direction: column;
flex-wrap: wrap;
width: 100%;
}
@@media only screen and (max-width: 1025px) {
.entitiesContainer {
padding: 0px;
}
}
</style>
@code {
[CascadingParameter] public EntityModel? Entity { get; set; }
[CascadingParameter] public string? StyleType { get; set; }
}
@@ -0,0 +1,134 @@
@if (Entity!.IdAbilities().Count > 0)
{
@if (StyleType.Equals("Plain"))
{
@foreach (var idAbility in Entity.IdAbilities())
{
var spell = EntityModel.Get(idAbility.Id);
var info = spell.Info();
var production = spell.Production();
<div>
<div>
<b>Ability Name:</b> @spell.Info().Name
</div>
<div>
<b>- Description:</b> @((MarkupString)info.Description)
</div>
@if (!info.Notes.Trim().Equals(""))
{
<div>
<b>- Notes:</b> @((MarkupString)info.Notes)
</div>
}
<div>
@if (production != null)
{
if (production.Energy != 0)
{
<div>
<b>- Energy: </b> @production.Energy
</div>
}
@if (!production.DefensiveLayer.Equals(0))
{
<div>
<b>- Shields:</b> @production.DefensiveLayer
</div>
}
if (production.BuildTime != 0)
{
<div>
<b>- BuildTime: </b> @production.BuildTime
</div>
}
if (production.Cooldown != 0)
{
<div>
<b>- Cooldown: </b> @production.Cooldown
</div>
}
}
</div>
</div>
}
}
else
{
<EntityDisplayComponent Title="Abilities">
@foreach (var idAbility in Entity.IdAbilities())
{
var spell = EntityModel.Get(idAbility.Id);
var info = spell.Info();
var production = spell.Production();
<div>
<div>
<b>Name:</b>
<EntityLabelComponent EntityId="@spell.DataType"/>
</div>
<div>
<b>Description:</b> @((MarkupString)info.Description)
</div>
@if (!info.Notes.Trim().Equals(""))
{
<div>
<b>Notes:</b> @((MarkupString)info.Notes)
</div>
}
<div>
@if (production != null)
{
if (production.Energy != 0)
{
<div>
<b> Energy: </b> @production.Energy
</div>
}
@if (!production.DefensiveLayer.Equals(0))
{
<div>
<b>Shields:</b> @production.DefensiveLayer
</div>
}
if (production.BuildTime != 0)
{
<div>
<b> BuildTime: </b> @production.BuildTime
</div>
}
if (production.Cooldown != 0)
{
<div>
<b> Cooldown: </b> @production.Cooldown
</div>
}
}
</div>
</div>
}
</EntityDisplayComponent>
}
}
@code {
[CascadingParameter] public EntityModel? Entity { get; set; }
[CascadingParameter] public string StyleType { get; set; } = "Detailed";
}
@@ -0,0 +1,72 @@
@if (StyleType.Equals("Plain"))
{
<div>
<b id="entityName">@Entity?.Info().Name</b>
@if (Entity?.Info().Descriptive != DescriptiveType.None)
{
<span>, @Entity?.Info().Descriptive.Replace("_", " ")</span>
}
</div>
}
else
{
<div class="entityHeader">
<div id="entityName" class="entityHeaderText">
@Entity?.Info().Name
</div>
<div style="font-size:1.4rem;">
<b>@Entity?.EntityType.Replace("_", " ")</b>
@if (Entity?.Info().Descriptive != DescriptiveType.None)
{
<span>
<b>:</b> @Entity!.Info().Descriptive.Replace("_", " ")
</span>
}
</div>
<div>
@if (Entity.Info().FlavorText != "")
{
<div>
<i> @((MarkupString)Entity.Info().FlavorText)</i>
</div>
}
</div>
</div>
<style>
.entityHeader {
display: flex;
flex-direction: row;
gap: 4px;
justify-content: space-between;
margin-bottom: 22px;
width: 100%;
margin-left: -6px;
}
.entityHeaderText {
font-size: 2rem;
font-weight: 900;
}
@@media only screen and (max-width: 1025px) {
.entityHeader {
flex-direction: column;
justify-content: normal;
margin-left: 4px;
}
}
</style>
}
@code {
[CascadingParameter] public EntityModel? Entity { get; set; }
[CascadingParameter] public string StyleType { get; set; } = "Detailed";
}
@@ -0,0 +1,132 @@
@if (StyleType.Equals("Plain"))
{
@if (Entity!.Info().Description != "")
{
<div>
<b>Description:</b> @((MarkupString)Entity.Info().Description)
</div>
}
@if (Entity.Info().Notes != "")
{
<div>
<b>Notes:</b> @((MarkupString)Entity.Info().Notes)
</div>
}
@if (Entity.Info().FlavorText != "")
{
<div>
<i>@((MarkupString)Entity.Info().FlavorText)</i>
</div>
}
<div class="infoDisplayContainer">
<div>
@if (Entity.Faction() != null)
{
<div>
<b>Faction:</b> @EntityData.Get()[Entity.Faction().Faction].Info().Name
</div>
}
@if (Entity.Tier() != null)
{
<div>
<b>Tier:</b> @Entity.Tier().Tier
</div>
}
</div>
@if (Entity.Hotkey() != null)
{
<div>
<div>
<b>Hotkey Group:</b> @Entity.Hotkey().HotkeyGroup
</div>
<div>
<b>Hotkey:</b> @Entity.Hotkey().Hotkey
</div>
<div>
<b>Hold Space:</b> @(Entity.Hotkey().HoldSpace ? "Yes" : "No")
</div>
</div>
}
</div>
}
else
{
<EntityDisplayComponent Title="Info">
@if (Entity!.Info().Description != "")
{
<div>
<b>Description:</b> @((MarkupString)Entity.Info().Description)
</div>
}
@if (Entity.Info().Notes != "")
{
<div>
<b>Notes:</b> @((MarkupString)Entity.Info().Notes)
</div>
}
<div class="infoDisplayContainer">
<div>
@if (Entity.Faction() != null)
{
<div>
<b>Faction:</b> @EntityData.Get()[Entity.Faction().Faction].Info().Name
</div>
}
@if (Entity.Tier() != null)
{
<div>
<b>Tier:</b> @Entity.Tier().Tier
</div>
}
</div>
@if (Entity.Hotkey() != null)
{
<div>
<div>
<b>Hotkey Group:</b> @Entity.Hotkey().HotkeyGroup
</div>
<div>
<b>Hotkey:</b> @Entity.Hotkey().Hotkey
</div>
<div>
<b>Hold Space:</b> @(Entity.Hotkey().HoldSpace ? "Yes" : "No")
</div>
</div>
}
</div>
</EntityDisplayComponent>
<style>
.infoDisplayContainer {
display: flex;
gap: 32px;
}
@@media only screen and (max-width: 1025px) {
.infoDisplayContainer {
flex-direction: column;
gap: 4px;
}
}
</style>
}
@code {
[CascadingParameter] public EntityModel? Entity { get; set; }
[CascadingParameter] public string StyleType { get; set; } = "Detailed";
}
@@ -0,0 +1,30 @@
@if (Entity!.Mechanics().Count > 0)
{
<EntityDisplayComponent Title="Mechanics">
<div>
@foreach (var data in Entity.Mechanics())
{
<div>
<div>
<span>
<b>Name:</b> @data.Name
</span>
</div>
<div>
<b>Description:</b> @data.Description
</div>
<br/>
</div>
}
</div>
</EntityDisplayComponent>
}
@code {
[CascadingParameter] public EntityModel? Entity { get; set; }
[CascadingParameter] public string StyleType { get; set; } = "Detailed";
}
@@ -0,0 +1,139 @@
@if (Entity!.IdPassives().Count > 0)
{
@if (StyleType.Equals("Plain"))
{
@foreach (var idPassive in Entity.IdPassives())
{
var passive = EntityModel.Get(idPassive.Id);
var info = passive.Info();
var production = passive.Production();
var requirements = passive.Requirements();
<div>
<div>
<b>Passive Name:</b> @info.Name
</div>
<div>
<b>- Description:</b> @((MarkupString)info.Description)
</div>
@if (!info.Notes.Trim().Equals(""))
{
<div>
<b>- Notes:</b> @((MarkupString)info.Notes)
</div>
}
</div>
@if (production != null)
{
<div>
@if (production.Pyre != 0)
{
<div>
<b>- Pyre:</b> @production.Pyre
</div>
}
@if (production.Cooldown != 0)
{
<div>
<b>- Cooldown:</b> @production.Cooldown.ToString()s
</div>
}
</div>
}
@if (requirements.Count > 0)
{
@foreach (var requirement in requirements)
{
var requirementModel = EntityData.Get()[requirement.Id];
<div>
<span>
<b>- @requirement.Requirement.Replace("_", " "):</b> @requirementModel.Info().Name
</span>
</div>
}
}
}
}
else
{
<EntityDisplayComponent Title="Passives">
@foreach (var idPassive in Entity.IdPassives())
{
var passive = EntityModel.Get(idPassive.Id);
var info = passive.Info();
var production = passive.Production();
var requirements = passive.Requirements();
<div>
<div>
<b>Name:</b>
<EntityLabelComponent EntityId="@passive.DataType"/>
</div>
<div>
<b>Description:</b> @((MarkupString)info.Description)
</div>
@if (!info.Notes.Trim().Equals(""))
{
<div>
<b>Notes:</b> @((MarkupString)info.Notes)
</div>
}
@if (requirements.Count > 0)
{
@foreach (var requirement in requirements)
{
var requirementModel = EntityData.Get()[requirement.Id];
<div>
<span>
<b>@requirement.Requirement.Replace("_", " "):</b> @requirementModel.Info().Name
</span>
</div>
}
}
</div>
@if (production != null)
{
<div>
@if (production.Pyre != 0)
{
<div>
<b>Pyre:</b> @production.Pyre
</div>
}
@if (production.Cooldown != 0)
{
<div>
<b>Cooldown:</b> @production.Cooldown.ToString()s
</div>
}
</div>
}
}
</EntityDisplayComponent>
}
}
@code {
[CascadingParameter] public EntityModel? Entity { get; set; }
[CascadingParameter] public string StyleType { get; set; } = "Detailed";
}
@@ -0,0 +1,225 @@
@if (Production != null || Supply != null || Requirements.Count > 0)
{
@if (StyleType.Equals("Plain"))
{
@if (Requirements.Count() > 0)
{
<div>
@foreach (var requirement in Requirements)
{
var requirementModel = EntityData.Get()[requirement.Id];
<div>
<span>
<b>@requirement.Requirement.Replace("_", " "):</b> @requirementModel.Info().Name
</span>
</div>
}
</div>
}
@if (Production != null && (!Production.Alloy.Equals(0)
|| !Production.Ether.Equals(0)
|| !Production.BuildTime.Equals(0)
|| !Production.Cooldown.Equals(0)))
{
<div>
@if (!Production.Alloy.Equals(0))
{
<div>
<b>Alloy:</b> @Production.Alloy
</div>
}
@if (!Production.Ether.Equals(0))
{
<div>
<b>Ether:</b> @Production.Ether
</div>
}
@if (!Production.Pyre.Equals(0))
{
<div>
<b>Pyre:</b> @Production.Pyre
</div>
}
@if (!Production.Pyre.Equals(0))
{
<div>
<b>Shields:</b> @Production.DefensiveLayer
</div>
}
@if (!Production.BuildTime.Equals(0))
{
<div>
<b>Build Time:</b> @Production.BuildTime.ToString()s
</div>
}
@if (!Production.Energy.Equals(0))
{
<div>
<b>Energy:</b> @Production.Energy
</div>
}
@if (!Production.Cooldown.Equals(0))
{
<div>
<b>Cooldown:</b> @Production.Cooldown.ToString()s
</div>
}
</div>
}
@if (Supply != null)
{
<div>
@if (!Supply.Grants.Equals(0))
{
<div>
<b>Grants:</b> @Supply.Grants
</div>
}
@if (!Supply.Takes.Equals(0))
{
<div>
<b>Takes:</b> @Supply.Takes Supply
</div>
}
</div>
}
}
else
{
<EntityDisplayComponent Title="Production">
<div class="ProductionContainer">
@if (Requirements.Count() > 0)
{
<div>
@foreach (var requirement in Requirements)
{
<div>
<span>
<b>@requirement.Requirement.Replace("_", " "):</b> <EntityLabelComponent
EntityId="@requirement.Id"/>
</span>
</div>
}
</div>
}
@if (Production != null && (!Production.Alloy.Equals(0)
|| !Production.Ether.Equals(0)
|| !Production.BuildTime.Equals(0)
|| !Production.Cooldown.Equals(0)))
{
<div>
@if (!Production.Alloy.Equals(0))
{
<div>
<b>Alloy:</b> @Production.Alloy
</div>
}
@if (!Production.Ether.Equals(0))
{
<div>
<b>Ether:</b> @Production.Ether
</div>
}
@if (!Production.Pyre.Equals(0))
{
<div>
<b>Pyre:</b> @Production.Pyre
</div>
}
@if (!Production.DefensiveLayer.Equals(0))
{
<div>
<b>Shields:</b> @Production.DefensiveLayer
</div>
}
@if (!Production.BuildTime.Equals(0))
{
<div>
<b>Build Time:</b> @Production.BuildTime.ToString()s
</div>
}
@if (!Production.Energy.Equals(0))
{
<div>
<b>Energy:</b> @Production.Energy
</div>
}
@if (!Production.Cooldown.Equals(0))
{
<div>
<b>Cooldown:</b> @Production.Cooldown.ToString()s
</div>
}
</div>
}
@if (Supply != null)
{
<div>
@if (!Supply.Grants.Equals(0))
{
<div>
<b>Grants:</b> @Supply.Grants
</div>
}
@if (!Supply.Takes.Equals(0))
{
<div>
<b>Takes:</b> @Supply.Takes Supply
</div>
}
</div>
}
</div>
</EntityDisplayComponent>
<style>
.ProductionContainer {
display: flex;
gap: 32px;
}
@@media only screen and (max-width: 1025px) {
.ProductionContainer {
flex-direction: column;
gap: 4px;
}
}
</style>
}
}
@code {
[CascadingParameter] public EntityModel? Entity { get; set; }
[CascadingParameter] public string StyleType { get; set; } = "Detailed";
private EntityProductionModel Production => Entity!.Production();
private List<EntityRequirementModel> Requirements => Entity!.Requirements();
private EntitySupplyModel Supply => Entity!.Supply();
protected override void OnParametersSet()
{
StateHasChanged();
}
}
@@ -0,0 +1,98 @@
@if (Entity!.IdPyreSpells().Count > 0)
{
@if (StyleType.Equals("Plain"))
{
@foreach (var pyreSpell in Entity.IdPyreSpells())
{
var spell = EntityModel.Get(pyreSpell.Id);
var info = spell.Info();
var production = spell.Production();
<div>
<div>
<b>Spell Name:</b> @spell.Info().Name
</div>
<div>
<b>- Description:</b> @((MarkupString)info.Description)
</div>
<div>
@if (production != null)
{
if (production.Pyre != 0)
{
<b>- Pyre: </b>
@production.Pyre
}
if (production.BuildTime != 0)
{
<b>- BuildTime: </b>
@production.BuildTime
}
if (production.Cooldown != 0)
{
<b>- Cooldown: </b>
@production.Cooldown
}
}
</div>
</div>
}
}
else
{
<EntityDisplayComponent Title="Pyre Spells">
@foreach (var pyreSpell in Entity.IdPyreSpells())
{
var spell = EntityModel.Get(pyreSpell.Id);
var info = spell.Info();
var production = spell.Production();
<div>
<div>
<b>Name:</b>
<EntityLabelComponent EntityId="@spell.DataType"/>
</div>
<div>
<b>Description:</b> @((MarkupString)info.Description)
</div>
<div>
@if (production != null)
{
if (production.Pyre != 0)
{
<b> Pyre: </b>
@production.Pyre
}
if (production.BuildTime != 0)
{
<b> BuildTime: </b>
@production.BuildTime
}
if (production.Cooldown != 0)
{
<b> Cooldown: </b>
@production.Cooldown
}
}
</div>
</div>
}
</EntityDisplayComponent>
}
}
@code {
[CascadingParameter] public EntityModel? Entity { get; set; }
[CascadingParameter] public string StyleType { get; set; } = "Detailed";
}
@@ -0,0 +1,180 @@
@if (Vitality != null || Movement != null)
{
@if (StyleType.Equals("Plain"))
{
@if (Vitality != null)
{
<div>
@if (!Vitality.DefenseLayer.Equals(0))
{
<div>
<b>Shield:</b> @Vitality.DefenseLayer
</div>
}
@if (!Vitality.Health.Equals(0))
{
<div>
<b>Health:</b> <span id="entityHealth">@Vitality.Health</span>
</div>
}
@if (!Vitality.Energy.Equals(0))
{
<div>
<b>Energy:</b> @Vitality.Energy
</div>
}
@if (!Vitality.Lasts.Equals(0))
{
<div>
<b>Lasts:</b> @Vitality.Lasts.ToString()s
</div>
}
@if (Vitality.Armor != "")
{
<div>
<b>Armor:</b> @Vitality.Armor
</div>
}
@if (Vitality.IsEtheric)
{
<div>
<b> + Etheric</b>
</div>
}
@if (Vitality.IsStructure)
{
<div>
<b> + Structure</b>
</div>
}
</div>
}
@if (Movement != null)
{
<div>
@if (!Movement.Speed.Equals(0))
{
<div>
<b>Speed:</b> @Movement.Speed
</div>
}
else
{
<div>
<b>Speed:</b> Immobile
</div>
}
<div>
<b>Move Type:</b> @Movement.Movement
</div>
</div>
}
}
else
{
<EntityDisplayComponent Title="Stats">
<div class="statContainer">
@if (Vitality != null)
{
<div>
@if (!Vitality.DefenseLayer.Equals(0))
{
<div>
<b>Shield:</b> @Vitality.DefenseLayer
</div>
}
@if (!Vitality.Health.Equals(0))
{
<div>
<b>Health:</b> <span id="entityHealth">@Vitality.Health</span>
</div>
}
@if (!Vitality.Energy.Equals(0))
{
<div>
<b>Energy:</b> @Vitality.Energy
</div>
}
@if (!Vitality.Lasts.Equals(0))
{
<div>
<b>Lasts:</b> @Vitality.Lasts.ToString()s
</div>
}
@if (Vitality.Armor != "")
{
<div>
<b>Armor:</b> @Vitality.Armor
</div>
}
@if (Vitality.IsEtheric)
{
<div>
<b> + Etheric</b>
</div>
}
@if (Vitality.IsStructure)
{
<div>
<b> + Structure</b>
</div>
}
</div>
}
@if (Movement != null)
{
<div>
@if (!Movement.Speed.Equals(0))
{
<div>
<b>Speed:</b> @Movement.Speed
</div>
}
else
{
<div>
<b>Speed:</b> Immobile
</div>
}
<div>
<b>Move Type:</b> @Movement.Movement
</div>
</div>
}
</div>
</EntityDisplayComponent>
<style>
.statContainer {
display: flex;
gap: 32px;
}
@@media only screen and (max-width: 1025px) {
.statContainer {
flex-direction: column;
gap: 4px;
}
}
</style>
}
}
@code {
[CascadingParameter] public EntityModel? Entity { get; set; }
[CascadingParameter] public string StyleType { get; set; } = "Detailed";
private EntityVitalityModel Vitality => Entity!.Vitality();
private EntityMovementModel Movement => Entity!.Movement();
}
@@ -0,0 +1,63 @@
@if (Entity!.IdUpgrades().Count > 0)
{
@if (StyleType.Equals("Plain"))
{
@foreach (var upgradeId in Entity.IdUpgrades())
{
var entity = EntityModel.Get(upgradeId.Id);
<div>
<div>
<b>Upgrade Name:</b> @entity.Info().Name
</div>
<div>
<b>- Description:</b> @entity.Info().Description
</div>
</div>
}
}
else
{
<EntityDisplayComponent Title="Upgrades">
<div class="upgradesContainer">
@foreach (var upgradeId in Entity.IdUpgrades())
{
var entity = EntityModel.Get(upgradeId.Id);
<div>
<div>
<b>Name:</b>
<EntityLabelComponent EntityId="@entity.DataType"/>
</div>
<div>
<b>Description:</b> @entity.Info().Description
</div>
</div>
}
</div>
</EntityDisplayComponent>
<style>
.upgradesContainer {
display: flex;
gap: 32px;
}
@@media only screen and (max-width: 1025px) {
.upgradesContainer {
flex-direction: column;
gap: 4px;
}
}
</style>
}
}
@code {
[CascadingParameter] public EntityModel? Entity { get; set; }
[CascadingParameter] public string StyleType { get; set; } = "Detailed";
}
@@ -0,0 +1,49 @@
@if (Vanguard != null)
{
var immortalId = Vanguard.ImmortalId;
var immortal = EntityData.Get()[immortalId];
var replaced = EntityData.Get()[Vanguard.ReplaceId];
@if (StyleType.Equals("Plain"))
{
<div>
<b>Immortal:</b> @immortal.Info().Name
</div>
@if (!Vanguard.ReplaceId.Equals(""))
{
<div>
<b>Replaces:</b> @replaced.Info().Name
</div>
}
}
else
{
<EntityDisplayComponent Title="Vanguard">
<div>
<div>
<b>Immortal:</b>
<EntityLabelComponent EntityId="@immortal.DataType"/>
</div>
@if (!Vanguard.ReplaceId.Equals(""))
{
<div>
<b>Replaces:</b>
<EntityLabelComponent EntityId="@Vanguard.ReplaceId"></EntityLabelComponent>
</div>
}
</div>
</EntityDisplayComponent>
}
}
@code {
[CascadingParameter] public EntityModel? Entity { get; set; }
private EntityVanguardAddedModel? Vanguard => Entity?.VanguardAdded();
[CascadingParameter] public string StyleType { get; set; } = "Detailed";
}
@@ -0,0 +1,71 @@
@if (Entity!.IdVanguards().Count > 0)
{
@if (StyleType.Equals("Plain"))
{
@foreach (var data in Entity.IdVanguards())
{
var entity = EntityModel.Get(data.Id);
var requirements = entity.Requirements();
var vanguardAdded = entity.VanguardAdded();
var replaced = EntityData.Get()[vanguardAdded.ReplaceId];
var immortal = EntityData.Get()[vanguardAdded.ImmortalId];
var productionBuilding = (from building in requirements
where building.Requirement == RequirementType.Production_Building
select building).First().Id;
<div>
<div>
<b>Name:</b> @entity.Info().Name
</div>
<div>
<b>- Replaces:</b> @replaced.Info().Name
</div>
<div>
<b>- Built From:</b> @immortal.Info().Name
</div>
</div>
}
}
else
{
<EntityDisplayComponent Title="Vanguards">
@foreach (var data in Entity.IdVanguards())
{
var entity = EntityModel.Get(data.Id);
var requirements = entity.Requirements();
var vanguard = entity.VanguardAdded();
var productionBuilding = (from building in requirements
where building.Requirement == RequirementType.Production_Building
select building).First().Id;
<div>
<div>
<b>Name:</b>
<EntityLabelComponent EntityId="@entity.DataType"/>
</div>
<div>
<b>Replaces:</b>
<EntityLabelComponent EntityId="@vanguard.ReplaceId"/>
</div>
<div>
<b>Built From:</b>
<EntityLabelComponent EntityId="@productionBuilding"/>
</div>
</div>
}
</EntityDisplayComponent>
}
}
@code {
[CascadingParameter] public EntityModel? Entity { get; set; }
[CascadingParameter] public string StyleType { get; set; } = "Detailed";
}
@@ -0,0 +1,263 @@
@inject IStorageService StorageService
@using Services.Website
@implements IDisposable
@if (Entity!.Weapons().Count > 0)
{
@if (StyleType.Equals("Plain"))
{
var index = 0;
foreach (var data in Entity.Weapons())
{
index++;
<div>
<div>
<div class="damageContainer">
<div>
<b>Weapon @index</b>
</div>
<div>
<b>- Damage:</b> @data.Damage
</div>
@if (data.LightDamage != 0)
{
<div class="alternateDamage">
<i>- vs Light: @data.LightDamage</i>&nbsp;
</div>
}
@if (data.MediumDamage != 0)
{
<div class="alternateDamage">
<i>- vs Medium: @data.MediumDamage</i>&nbsp;
</div>
}
@if (data.HeavyDamage != 0)
{
<div class="alternateDamage">
<i>- vs Heavy: @data.HeavyDamage</i>&nbsp;
</div>
}
@if (data.EthericDamageBonus != 0)
{
<div class="alternateDamage">
<i>- vs Etheric +@data.EthericDamageBonus</i>&nbsp;
</div>
}
@if (data.StructureDamageBonus != 0)
{
<div class="alternateDamage">
<i>- vs Structure: +@data.StructureDamageBonus</i>&nbsp;
</div>
}
</div>
</div>
<div>
<b>- Range:</b> @data.Range
</div>
@if (data.SecondsBetweenAttacks > 0)
{
<div>
<b>- AttacksPerSecond:</b> @(1 / data.SecondsBetweenAttacks)
</div>
<div>
- (or <b>SecondsBetweenAttacks:</b> @data.SecondsBetweenAttacks)
</div>
}
else if (data.AttacksPerSecond > 0)
{
<div>
<b>- AttacksPerSecond:</b> @data.AttacksPerSecond
</div>
<div>
- (or <b>SecondsBetweenAttacks:</b> @(1 / data.AttacksPerSecond))
</div>
}
<div>
<b>- Targets:</b> @data.Targets
</div>
@if (data.AttacksPerSecond != 0)
{
<span>
<b>- DPS:</b> @(Math.Round(data.Damage * data.AttacksPerSecond))&nbsp;
</span>
@if (data.LightDamage != 0)
{
<span>
<i>- Light DPS: @(Math.Round(data.LightDamage * data.AttacksPerSecond))</i>&nbsp;
</span>
}
@if (data.MediumDamage != 0)
{
<span>
<i>- Medium DPS: @(Math.Round(data.MediumDamage * data.AttacksPerSecond))</i>&nbsp;
</span>
}
@if (data.HeavyDamage != 0)
{
<span>
<i>- Heavy DPS: @(Math.Round(data.HeavyDamage * data.AttacksPerSecond))</i>&nbsp;
</span>
}
}
</div>
}
}
else
{
<EntityDisplayComponent Title="Weapons">
<div class="weaponsContainer">
@foreach (var data in Entity.Weapons())
{
<div>
<div>
<div class="damageContainer">
<div>
<b>Damage:</b> @data.Damage
</div>
@if (data.LightDamage != 0)
{
<div class="alternateDamage">
<i>vs Light: @data.LightDamage</i>&nbsp;
</div>
}
@if (data.MediumDamage != 0)
{
<div class="alternateDamage">
<i>vs Medium: @data.MediumDamage</i>&nbsp;
</div>
}
@if (data.HeavyDamage != 0)
{
<div class="alternateDamage">
<i>vs Heavy: @data.HeavyDamage</i>&nbsp;
</div>
}
@if (data.EthericDamageBonus != 0)
{
<div class="alternateDamage">
<i>vs Etheric +@data.EthericDamageBonus</i>&nbsp;
</div>
}
@if (data.StructureDamageBonus != 0)
{
<div class="alternateDamage">
<i>vs Structure: +@data.StructureDamageBonus</i>&nbsp;
</div>
}
</div>
</div>
<div>
<b>Range:</b> @data.Range
</div>
@if (data.SecondsBetweenAttacks > 0)
{
<div>
<b>AttacksPerSecond:</b> @(1 / data.SecondsBetweenAttacks)
</div>
<div>
(or <b>SecondsBetweenAttacks:</b> @data.SecondsBetweenAttacks)
</div>
}
else if (data.AttacksPerSecond > 0)
{
<div>
<b>AttacksPerSecond:</b> @data.AttacksPerSecond
</div>
<div>
(or <b>SecondsBetweenAttacks:</b> @(1 / data.AttacksPerSecond))
</div>
}
<div>
<b>Targets:</b> @data.Targets
</div>
@if (data.AttacksPerSecond != 0)
{
<div>
<b>DPS:</b> @(Math.Round(data.Damage * data.AttacksPerSecond))&nbsp;
</div>
@if (data.LightDamage != 0)
{
<div>
<i>Light DPS: @(Math.Round(data.LightDamage * data.AttacksPerSecond))</i>&nbsp;
</div>
}
@if (data.MediumDamage != 0)
{
<div>
<i>Medium DPS: @(Math.Round(data.MediumDamage * data.AttacksPerSecond))</i>&nbsp;
</div>
}
@if (data.HeavyDamage != 0)
{
<div>
<i>Heavy DPS: @(Math.Round(data.HeavyDamage * data.AttacksPerSecond))</i>&nbsp;
</div>
}
}
</div>
}
</div>
</EntityDisplayComponent>
<style>
.weaponsContainer {
display: flex;
gap: 32px;
}
@@media only screen and (max-width: 1025px) {
.weaponsContainer {
flex-direction: column;
gap: 4px;
}
}
.alternateDamage {
margin-left: 18px;
}
.damageContainer {
margin-bottom: 6px;
}
</style>
}
}
@code {
[CascadingParameter] public EntityModel? Entity { get; set; }
[CascadingParameter] public string StyleType { get; set; } = "Detailed";
private bool _isDynamicFormatting;
protected override void OnInitialized()
{
base.OnInitialized();
StorageService.Subscribe(RefreshDefaults);
}
void RefreshDefaults()
{
_isDynamicFormatting = StorageService.GetValue<bool>(StorageKeys.IsDynamicFormatting);
}
void IDisposable.Dispose()
{
StorageService.Unsubscribe(RefreshDefaults);
}
}
@@ -0,0 +1,264 @@
<div class="desktopFilters">
<div class="desktopFiltersContainer">
<div class="filtersContainer">
<div class="filterContainer">
@foreach (var choice in EntityFilterService.GetFactionChoices())
{
var styleClass = "";
if (choice.Equals(EntityFilterService.GetFactionType()))
{
styleClass = "selected";
}
<button @onclick="@(e => OnChangeFaction(choice))"
class="choiceButton @styleClass">
@(choice == DataType.Any
? DataType.Any
: EntityData.Get()[choice].Info().Name)
</button>
}
</div>
@if (EntityFilterService.GetFactionType() != "Any" && EntityFilterService.GetFactionType() != "None")
{
<div class="filterContainer">
@foreach (var choice in EntityFilterService.GetImmortalChoices())
{
var name = EntityData.Get()[choice].Info().Name;
var styleClass = "";
if (choice.Equals(EntityFilterService.GetImmortalType()))
{
styleClass = "selected";
}
<button class="choiceButton @styleClass"
@onclick="@(e => OnChangeImmortal(choice))">@name</button>
}
</div>
}
</div>
<div class="filterContainer">
@foreach (var choice in EntityFilterService.GetEntityChoices())
{
var styleClass = "";
if (choice.Equals(EntityFilterService.GetEntityType()))
{
styleClass = "selected";
}
<button class="choiceButton @styleClass"
@onclick="@(e => OnChangeEntity(choice))">@choice.Replace("_", " ")</button>
}
</div>
<FormTextComponent Id="filterName" Label="Filter Name" Placeholder="Throne..."
OnChange="@(e => EntityFilterService.EnterSearchText(e.Value!.ToString()!))"/>
</div>
</div>
<div class="mobileFilters">
<FormLayoutComponent>
<FormSelectComponent OnChange="@OnFactionChanged">
<FormLabelComponent>Faction</FormLabelComponent>
<ChildContent>
<option value="@DataType.Any" selected>Any</option>
<option value="@DataType.FACTION_Aru">Aru</option>
<option value="@DataType.FACTION_QRath">Q'Rath</option>
</ChildContent>
</FormSelectComponent>
<FormSelectComponent OnChange="@OnImmortalChanged">
<FormLabelComponent>Immortal</FormLabelComponent>
<ChildContent>
<option value="@DataType.Any" selected>Any</option>
<option value="@DataType.IMMORTAL_Atzlan">Atzlan</option>
<option value="@DataType.IMMORTAL_Mala">Mala</option>
<option value="@DataType.IMMORTAL_Xol">Xol</option>
<option value="@DataType.IMMORTAL_Orzum">Orzum</option>
<option value="@DataType.IMMORTAL_Ajari">Ajari</option>
</ChildContent>
</FormSelectComponent>
<FormSelectComponent OnChange="@OnEntityChanged">
<FormLabelComponent>Entity</FormLabelComponent>
<ChildContent>
<option value="@EntityType.Any">Any</option>
<option value="@EntityType.Ability">Ability</option>
<option value="@EntityType.Army" selected>Army</option>
<option value="@EntityType.Building">Building</option>
<option value="@EntityType.Building_Upgrade">Building Upgrade</option>
<option value="@EntityType.Command">Command</option>
<option value="@EntityType.Faction">Faction</option>
<option value="@EntityType.Immortal">Immortal</option>
<option value="@EntityType.Pyre_Spell">Spell</option>
<option value="@EntityType.Passive">Passive</option>
<option value="@EntityType.Tech">Tech</option>
<option value="@EntityType.Worker">Worker</option>
</ChildContent>
</FormSelectComponent>
<FormTextComponent Id="filterName" Label="Filter Name" Placeholder="Throne..." OnChange="OnSearchTextChanged"/>
</FormLayoutComponent>
</div>
<style>
.desktopFilters {
display: flex;
gap: 12px;
flex-direction: column;
justify-content: flex-start;
justify-items: flex-start;
top: 50px;
padding: 12px;
width: 100%;
left: 0px;
}
.desktopFiltersContainer {
width: 75%;
min-width: 1000px;
margin: auto;
display: flex;
gap: 16px;
flex-direction: column;
justify-content: flex-start;
justify-items: flex-start;
}
.filtersContainer {
display: flex;
gap: 16px;
}
.filterContainer {
display: flex;
background-color: var(--background);
gap: 2px;
margin-right: auto;
border-radius: 8px;
}
.choiceButton {
background-color: var(--primary);
color: white;
padding: 12px;
border: 1px solid var(--primary);
}
.choiceButton:hover {
background-color: var(--primary-hover);
border-color: var(--primary-border-hover);
}
.selected {
background-color: var(--secondary);
color: white;
font-style: normal;
font-weight: bold;
}
.selected:hover {
background-color: var(--secondary-hover);
border-color: var(--secondary-border-hover);
}
.filterContainer .choiceButton:first-child {
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
}
.filterContainer .choiceButton:last-child {
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
}
@@media only screen and (max-width: 1025px) {
.desktopNavContainer {
display: none;
}
}
@@media only screen and (max-width: 480px) {
.filtersContainer {
flex-direction: column;
}
.filterContainer {
flex-direction: column;
}
}
.mobileFilters {
display: none;
}
@@media only screen and (max-width: 1024px) {
.mobileFilters {
display: block;
}
.desktopFilters {
display: none;
}
.desktopSpacer {
display: none;
}
}
</style>
@code {
[Inject] public IEntityFilterService EntityFilterService { get; set; } = default!;
protected override void OnInitialized()
{
base.OnInitialized();
}
void OnChangeFaction(string clickedFaction)
{
EntityFilterService.SelectFactionType(clickedFaction);
StateHasChanged();
}
void OnChangeImmortal(string clickedImmortal)
{
EntityFilterService.SelectImmortalType(clickedImmortal);
}
void OnChangeEntity(string clickedEntity)
{
EntityFilterService.SelectEntityType(clickedEntity);
}
void OnFactionChanged(ChangeEventArgs e)
{
EntityFilterService.SelectFactionType(e.Value!.ToString()!);
}
void OnImmortalChanged(ChangeEventArgs e)
{
EntityFilterService.SelectImmortalType(e.Value!.ToString()!);
}
void OnEntityChanged(ChangeEventArgs e)
{
EntityFilterService.SelectEntityType(e.Value!.ToString()!);
}
void OnSearchTextChanged(ChangeEventArgs e)
{
EntityFilterService.EnterSearchText(e.Value!.ToString()!);
}
}
@@ -0,0 +1,106 @@
.desktopFilters {
display: flex;
gap: 12px;
flex-direction: column;
justify-content: flex-start;
justify-items: flex-start;
top: 50px;
padding: 12px;
width: 100%;
left: 0px;
}
.desktopFiltersContainer {
width: 75%;
min-width: 1000px;
margin: auto;
display: flex;
gap: 16px;
flex-direction: column;
justify-content: flex-start;
justify-items: flex-start;
}
.filtersContainer {
display: flex;
gap: 16px;
}
.filterContainer {
display: flex;
background-color: var(--background);
gap: 2px;
margin-right: auto;
border-radius: 8px;
}
.choiceButton {
background-color: var(--primary);
color: white;
padding: 12px;
border: 1px solid var(--primary);
}
.choiceButton:hover {
background-color: var(--primary-hover);
border-color: var(--primary-border-hover);
}
.selected {
background-color: var(--secondary);
color: white;
font-style: normal;
font-weight: bold;
}
.selected:hover {
background-color: var(--secondary-hover);
border-color: var(--secondary-border-hover);
}
.filterContainer .choiceButton:first-child {
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
}
.filterContainer .choiceButton:last-child {
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
}
@media only screen and (max-width: 1025px) {
.desktopNavContainer {
display: none;
}
}
@media only screen and (max-width: 480px) {
.filtersContainer {
flex-direction: column;
}
.filterContainer {
flex-direction: column;
}
}
.mobileFilters {
display: none;
}
@media only screen and (max-width: 1024px) {
.mobileFilters {
display: block;
}
.desktopFilters {
display: none;
}
.desktopSpacer {
display: none;
}
}
@@ -0,0 +1,57 @@
@page "/economy-comparison"
@inherits BasePage
@implements IDisposable
@inject IEconomyComparisonService EconomyComparisonService
@layout PageLayout
<LayoutMediumContentComponent>
<WebsiteTitleComponent>Economy Comparision</WebsiteTitleComponent>
<PaperComponent>
<div>You</div>
<EconomyInputComponent ForPlayer="0"/>
</PaperComponent>
<PaperComponent>
<div>Them</div>
<EconomyInputComponent ForPlayer="1"/>
</PaperComponent>
<PaperComponent>
<EconomyDifferenceComponent/>
</PaperComponent>
<PaperComponent>
<ChartComponent/>
</PaperComponent>
<ContentDividerComponent/>
<PaperComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
What is this tool for?
</InfoQuestionComponent>
<InfoAnswerComponent>
Compare two economies together to determine best attack timing windows.
</InfoAnswerComponent>
</InfoBodyComponent>
</PaperComponent>
</LayoutMediumContentComponent>
@code {
protected override void OnInitialized()
{
base.OnInitialized();
EconomyComparisonService.Subscribe(StateHasChanged);
}
void IDisposable.Dispose()
{
EconomyComparisonService.Unsubscribe(StateHasChanged);
}
}
@@ -0,0 +1,200 @@
@inject IEconomyComparisonService economyComparisonService
@inject IJSRuntime jsRuntime;
@using Model.BuildOrders
@implements IDisposable
<div class="chartsContainer">
@{
var index = 0;
}
@foreach (var chart in charts)
{
index++;
<div style="width: 0; height: @chart.ValueDisplayMax.ToString()px">
<div
style="left: calc(-@width.ToString()px / 2); position: relative; border: 2px solid gray; border-radius:2px; width: @chart.IntervalDisplayMax.ToString()px; height: @chart.ValueDisplayMax.ToString()px">
@foreach (var point in chart.Points)
{
var xCoord = point.GetInterval(chart.HighestIntervalPoint, chart.IntervalDisplayMax);
var show = int.Parse(xCoord) / 6 % 2;
var player = index - 1;
if (show == player)
{
<div style="position: absolute;
bottom:@point.GetValue(highestAlloyPoint, chart.ValueDisplayMax)px;
left:@point.GetInterval(chart.HighestIntervalPoint, chart.IntervalDisplayMax)px;
width: 0px;
height: 0px;">
<div
style="width:1px; height: 1px; border-top-right-radius:10px; border-top-left-radius:10px; border: 2px solid @chart.ChartColor; background-color:@chart.ChartColor">
</div>
</div>
}
}
</div>
</div>
}
</div>
<style>
.chartsContainer {
position: relative;
display: flex;
flex-wrap: wrap;
justify-content: center;
margin-bottom: 20px;
}
</style>
@code {
private readonly int width = 800;
private readonly int height = 700;
private List<ChartModel> charts = new();
float highestAlloyPoint;
protected override void OnInitialized()
{
base.OnInitialized();
economyComparisonService.Subscribe(OnBuilderOrderChanged);
OnBuilderOrderChanged();
}
int lastRequestedRefreshIndex;
void IDisposable.Dispose()
{
economyComparisonService.Unsubscribe(OnBuilderOrderChanged);
}
void OnBuilderOrderChanged()
{
charts = new List<ChartModel>();
var index = 0;
highestAlloyPoint = 0;
foreach (var buildToCompare in economyComparisonService.BuildsToCompare)
{
GenerateChart(index++, buildToCompare);
}
StateHasChanged();
}
protected override bool ShouldRender()
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.time", "ChartComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.timeEnd", "ChartComponent");
#endif
}
void GenerateChart(int index, BuildToCompareModel buildToCompareModel)
{
var economyOverTime = buildToCompareModel.EconomyOverTimeModel;
var alloyChart = new ChartModel
{
IntervalDisplayMax = width,
ValueDisplayMax = height,
ChartColor = buildToCompareModel.ChartColor
};
for (var interval = 0; interval < economyOverTime.Count(); interval++)
{
var alloyPoint = new PointModel { Interval = interval };
var economyAtSecond = economyOverTime[interval];
var alloyWorkerHarvesters = from harvester in economyAtSecond.HarvestPoints
where harvester.Harvest() != null
where harvester.Harvest().RequiresWorker
where harvester.Harvest().Resource == ResourceType.Alloy
select harvester;
var alloyAutomaticHarvesters = from harvester in economyAtSecond.HarvestPoints
where harvester.Harvest() != null
where harvester.Harvest().RequiresWorker == false
where harvester.Harvest().Resource == ResourceType.Alloy
select harvester;
float autoAlloy = 0;
float workerSlots = 0;
float workerAlloy = 0;
float economySpending = 0;
foreach (var alloyAutoHarvester in alloyAutomaticHarvesters)
{
autoAlloy += alloyAutoHarvester.Harvest().Slots * alloyAutoHarvester.Harvest().HarvestedPerInterval;
var production = alloyAutoHarvester.Production();
if (production != null)
{
economySpending += production.Alloy;
}
}
foreach (var alloyWorkerHarvester in alloyWorkerHarvesters)
{
workerSlots += alloyWorkerHarvester.Harvest().Slots;
var production = alloyWorkerHarvester.Production();
if (production != null)
{
economySpending += production.Alloy;
}
}
economySpending += (economyAtSecond.WorkerCount - 6) * 50;
workerAlloy = Math.Min(economyAtSecond.WorkerCount - economyAtSecond.BusyWorkerCount, workerSlots);
alloyPoint.TempValue = workerAlloy + autoAlloy;
if (interval > 0)
{
alloyPoint.TempValue += alloyChart.Points.Last().TempValue;
}
alloyPoint.Value = alloyPoint.TempValue - economySpending;
highestAlloyPoint = Math.Max(highestAlloyPoint, alloyPoint.Value);
alloyChart.Points.Add(alloyPoint);
}
alloyChart.HighestValuePoint = highestAlloyPoint;
alloyChart.HighestIntervalPoint = economyOverTime.Count();
charts.Add(alloyChart);
}
}
@@ -0,0 +1,127 @@
@inject IEconomyComparisonService economyComparisonService
@implements IDisposable
<div class="differences">
<div class="differenceContainer">
<div class="differenceTitle">
Starting Advantage
</div>
<div>
At Time: @StartingAdvantageAtTime | T @Interval.ToTime(StartingAdvantageAtTime)
</div>
</div>
<div class="differenceContainer">
<div class="differenceTitle">
Peak Advantage
</div>
<div>
By Alloy: @PeakAdvantageByAlloy
</div>
<div>
At Time: @PeakAdvantageAtTime | T @Interval.ToTime(PeakAdvantageAtTime)
</div>
</div>
<div class="differenceContainer">
<div class="differenceTitle">
Worsening Time
</div>
<div>
At Time: @WorseningTime | T @Interval.ToTime(WorseningTime)
</div>
</div>
<div class="differenceContainer">
<div class="differenceTitle">
Miracle Time
</div>
<div>
At Time: @MiracleTime | T @Interval.ToTime(MiracleTime)
</div>
</div>
</div>
<style>
.differences {
display: flex;
flex-direction: column;
gap: 12px;
padding: 12px;
}
.differenceTitle {
font-size: 1.2em;
font-weight: 800;
}
.differenceContainer {
}
</style>
@code {
private int StartingAdvantageAtTime;
private int PeakAdvantageByAlloy;
private int PeakAdvantageAtTime;
private int WorseningTime;
private int MiracleTime;
protected override void OnInitialized()
{
base.OnInitialized();
economyComparisonService.Subscribe(CalculateDifferences);
}
void IDisposable.Dispose()
{
economyComparisonService.Unsubscribe(CalculateDifferences);
}
void CalculateDifferences()
{
PeakAdvantageByAlloy = 0;
StartingAdvantageAtTime = 0;
WorseningTime = 0;
MiracleTime = 0;
for (var interval = 0; interval < economyComparisonService.BuildsToCompare[0].EconomyOverTimeModel.Count; interval++)
{
var yourEconomy = economyComparisonService.BuildsToCompare[0].EconomyOverTimeModel[interval];
var theirEconomy = economyComparisonService.BuildsToCompare[1].EconomyOverTimeModel[interval];
var deltaEconomy = yourEconomy.Alloy - theirEconomy.Alloy;
if (deltaEconomy >= 0)
{
if (deltaEconomy > PeakAdvantageByAlloy)
{
if (StartingAdvantageAtTime == 0)
{
StartingAdvantageAtTime = interval;
}
PeakAdvantageByAlloy = (int)deltaEconomy;
PeakAdvantageAtTime = interval;
}
}
else
{
if (PeakAdvantageByAlloy > 0 && WorseningTime == 0)
{
WorseningTime = interval;
}
if (deltaEconomy < -1000 && MiracleTime == 0)
{
MiracleTime = interval;
}
}
}
StateHasChanged();
}
}
@@ -0,0 +1,85 @@
@inject IEconomyComparisonService economyComparisonService
@implements IDisposable
<FormLayoutComponent>
<FormSelectComponent OnChange="@OnFactionChanged">
<FormLabelComponent>Faction</FormLabelComponent>
<ChildContent>
<option value="@DataType.FACTION_Aru" selected="@IsSelected(DataType.FACTION_Aru)">Aru</option>
<option value="@DataType.FACTION_QRath" selected="@IsSelected(DataType.FACTION_QRath)">Q'Rath</option>
</ChildContent>
</FormSelectComponent>
<ContentDividerComponent/>
<FormNumberComponent Value="@TownHallCount" OnChange="ChangeTownHallNumber">
<FormLabelComponent>Number of TownHall Expansions</FormLabelComponent>
</FormNumberComponent>
<ContentDividerComponent/>
@{
var index = 0;
}
@foreach (var timing in TownHallTimings)
{
index++;
<FormNumberComponent Value="@timing" OnChange="e => ChangeBuildTime(e, index - 1)">
<FormLabelComponent>
TownHall build time
</FormLabelComponent>
</FormNumberComponent>
}
<ContentDividerComponent/>
<FormTextComponent Label="Chart Color" Value="@ChartColor" OnChange="ChangeColor"/>
</FormLayoutComponent>
<style>
</style>
@code {
[Parameter] public int ForPlayer { get; set; }
private int TownHallCount => economyComparisonService.GetTownHallCount(ForPlayer);
private string ChartColor => economyComparisonService.GetColor(ForPlayer);
private string Faction => economyComparisonService.GetFaction(ForPlayer);
private List<int> TownHallTimings => economyComparisonService.GetTownHallBuildTimes(ForPlayer);
protected override void OnInitialized()
{
base.OnInitialized();
economyComparisonService.Subscribe(StateHasChanged);
}
void IDisposable.Dispose()
{
economyComparisonService.Unsubscribe(StateHasChanged);
}
private void OnFactionChanged(ChangeEventArgs obj)
{
throw new NotImplementedException();
}
private bool IsSelected(string factionType)
{
return Faction.Equals(factionType);
}
private void ChangeColor(ChangeEventArgs obj)
{
economyComparisonService.ChangeColor(ForPlayer, obj.Value!.ToString()!);
}
private void ChangeTownHallNumber(ChangeEventArgs obj)
{
economyComparisonService.ChangeNumberOfTownHalls(ForPlayer, (int)obj.Value!);
}
private void ChangeBuildTime(ChangeEventArgs obj, int index)
{
economyComparisonService.ChangeTownHallTiming(ForPlayer, index, (int)obj.Value!);
}
}
+400
View File
@@ -0,0 +1,400 @@
@layout PageLayout
@inject IDataCollectionService DataCollectionService
@using Model
@inherits BasePage
@page "/harass-calculator"
<LayoutMediumContentComponent>
<WebsiteTitleComponent>Harass Calculator</WebsiteTitleComponent>
<AlertComponent Type="@SeverityType.Warning">
<Title>Might be out of date</Title>
<Message>
This calculation is from several years ago and might not reflect the current state of the game.
</Message>
</AlertComponent>
<PaperComponent>
Credit to Zard for deriving the formula.
</PaperComponent>
<PaperComponent>
<LayoutRowComponent>
<LayoutColumnComponent>
<FormLayoutComponent>
<FormDisplayComponent Label="Cost of worker">
<Display>@CostOfWorker</Display>
</FormDisplayComponent>
<FormDisplayComponent Label="Alloy mined per second by worker">
<Display>@AlloyMinedPerSecondByWorker</Display>
</FormDisplayComponent>
<FormDisplayComponent Label="Time to produce worker">
<Display>@TimeToProduceWorker</Display>
</FormDisplayComponent>
</FormLayoutComponent>
</LayoutColumnComponent>
<LayoutColumnComponent>
<FormLayoutComponent>
<FormNumberComponent Min="1"
Id="numberOfWorkersLostToHarass"
Value="@((int)NumberOfWorkersLostToHarass)"
OnChange="@(e =>
{
NumberOfWorkersLostToHarass = int.Parse(e.Value!.ToString()!);
Calculate();
})">
<FormLabelComponent>Number of workers lost to harass</FormLabelComponent>
</FormNumberComponent>
<FormNumberComponent Min="1"
Id="numberOfTownHallsExisting"
Value="@((int)NumberOfTownHallsExisting)"
OnChange="OnTownHallsChanged">
<FormLabelComponent>Number of townhalls you have</FormLabelComponent>
</FormNumberComponent>
<div id="numberOfTownHallTravelTimes">
@{
var index = 0;
}
@foreach (var travelTime in TravelTimes)
{
index++;
if (index == 1)
{
continue;
}
var id = $"numberOfTownHallsExisting_{index}";
<FormNumberComponent Min="0"
Id="@id"
Value="@((int)travelTime.Value)"
OnChange="e => { OnTownHallTravelTimeChanged(e, travelTime); }">
<FormLabelComponent>Worker travel time from other
base @(travelTime.Index + 1)</FormLabelComponent>
</FormNumberComponent>
}
</div>
<FormDisplayComponent Label="Total alloy lost">
<Display>
<div style="font-size: 1.5rem; font-weight: 800;">
<span id="totalAlloyHarassment">
@TotalAlloyHarassment
</span>
</div>
</Display>
</FormDisplayComponent>
</FormLayoutComponent>
<br/>
<div>
(<b>Worker replacement costs:</b> <span id="workerReplacementCost">@WorkerReplacementCost()</span>)
</div>
<div>
(<b>Delayed mining time:</b> <span id="delayedMiningCost">@DelayedMiningCost()</span>)
</div>
<div>
(<b>Average travel time:</b> <span id="getAverageTravelTime">@GetAverageTravelTime()</span>)
</div>
</LayoutColumnComponent>
</LayoutRowComponent>
</PaperComponent>
<ContentDividerComponent></ContentDividerComponent>
<PaperComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
What is this tool?
</InfoQuestionComponent>
<InfoAnswerComponent>
The Harass Calculator allows you to calculate damage done to an enemy alloy line. For example, if you
were to attack with Ichors, and kill 6 enemy workers, you can set the
<b>
Number of workers lost to
harass
</b> to 6. This would determine a loss of <span id="exampleTotalAlloyLoss">@ExampleTotalAlloyLoss</span>
alloy. Quite
the large number.
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
What can I learn from this?
</InfoQuestionComponent>
<InfoAnswerComponent>
Well, let's assume you lost a full alloy line of workers, and have to take that
@ExampleTotalAlloyLoss alloy cost (<span id="exampleWorkerCost">@ExampleWorkerCost</span>
to rebuy the workers, and <span id="exampleMiningTimeCost">@ExampleMiningTimeCost</span> in lost mining
time.)
<br/><br/>
If you were to set the <b>Number of townhalls you have</b> to 2, the calculator will consider worker
transfer micro. Allowing you to cut the total cost by roughly
<span id="exampleTotalAlloyLossDifference">@ExampleTotalAlloyLossDifference</span> alloy. However, that
number isn't
entirely accurate, you are also going to have to bump up the <b>Worker travel time to alloy</b> to
account for the time it takes the transferred workers to arrive at the decimated alloy line.
<br/><br/>
Let's say it takes 10 seconds for workers to transfer from your second base. Let's enter that for the
second base travel time for the more accurate loss of
<span
id="exampleTotalAlloyLossAccurate">
@ExampleTotalAlloyLossAccurate
</span> alloy
(saving you <span
id="exampleTotalAlloyLossAccurateDifference">@ExampleTotalAlloyLossAccurateDifference</span> alloy.)
<i>
Which is
much better than not transferring workers!
</i>
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
Can I see the formula for the calculation?
</InfoQuestionComponent>
<InfoAnswerComponent>
The Harass Calculator is based on the following calculation.
<br/>
<div class="mathContainer">
<div> =c*m+r*g*(t+l) +</div>
<MathLoopSumComponent>
<LoopStart><i>x</i> =1</LoopStart>
<LoopEnd>
<MathDivisionComponent>
<Dividee>m</Dividee>
<Divider>a</Divider>
</MathDivisionComponent>
</LoopEnd>
<IndexSymbol>
<i>x</i>
</IndexSymbol>
</MathLoopSumComponent>
<div style="width: 132px">g*<i>x</i>*(t+l)</div>
</div>
<br/>
<div style="font-family:monospace;">
<div>c is CostOfWorker</div>
<div>m is NumberOfWorkersLostToHarass, <i>m for [M]otes</i></div>
<div>a is NumberOfTownHallsExisting, <i>a for [A]cropolis</i></div>
<div>r is m mod a is LeftOverWorkersToProduceCount()</div>
<div>g is AlloyMinedPerSecondByWorker</div>
<div>t is TimeToProduceWorker</div>
<div>l is TravelTime</div>
<div><i>x</i> is workerProductionIndex</div>
</div>
<br/>
This logic has since been changed slightly to allow client to enter different travel times per base.
<br/>
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
Can I see the code for the calculation?
</InfoQuestionComponent>
<InfoAnswerComponent>
<br/>
<LinkButtonComponent
Href="https://git.jonathanmccaffrey.ca/JonathanMcCaffrey/IGP-Fan-Reference/src/branch/main/IGP/Pages/HarassCalculatorPage.razor#L226">
View Here
</LinkButtonComponent>
</InfoAnswerComponent>
</InfoBodyComponent>
</PaperComponent>
</LayoutMediumContentComponent>
<style>
.mathContainer {
font-family: monospace;
display: flex;
flex-direction: row;
align-items: flex-start;
border: 1px black solid;
padding: 32px;
background-color: #1A1B1E
}
@@media only screen and (max-width: 1025px) {
.mathContainer {
padding-left: 2px;
padding-right: 2px;
}
}
</style>
@code {
// Example calcs
float ExampleTotalAlloyLoss => Calculate(
WorkerReplacementCost(6),
SimultaneousProductionFloor(1, 6),
6,
new List<float> { 0 });
float ExampleWorkerCost => WorkerReplacementCost(6);
float ExampleMiningTimeCost => ExampleTotalAlloyLoss - ExampleWorkerCost;
float ExampleTotalAlloyLossDifference => ExampleTotalAlloyLoss - Calculate(
WorkerReplacementCost(6),
SimultaneousProductionFloor(2, 6),
6,
new List<float> { 0, 0 });
float ExampleTotalAlloyLossAccurate => Calculate(
WorkerReplacementCost(6),
SimultaneousProductionFloor(2, 6),
6,
new List<float> { 0, 10 });
float ExampleTotalAlloyLossAccurateDifference => ExampleTotalAlloyLoss - ExampleTotalAlloyLossAccurate;
float TotalAlloyHarassment;
readonly float CostOfWorker = 50;
readonly float AlloyMinedPerSecondByWorker = 1;
readonly float TimeToProduceWorker = 20;
float NumberOfWorkersLostToHarass = 1;
float NumberOfTownHallsExisting = 1;
float GetAverageTravelTime()
{
if (TravelTimes.Count == 0)
{
return 0;
}
float sum = 0;
foreach (var travelTime in TravelTimes)
{
sum += travelTime.Value;
}
return sum / NumberOfTownHallsExisting;
}
float SimultaneousProductionFloor()
{
if (NumberOfTownHallsExisting <= 0 || NumberOfWorkersLostToHarass <= 0)
{
return 0;
}
return NumberOfWorkersLostToHarass / Math.Min(NumberOfTownHallsExisting, NumberOfWorkersLostToHarass);
}
float SimultaneousProductionFloor(float existingTownHalls, float numberOfWorkersLost)
{
if (existingTownHalls <= 0 || numberOfWorkersLost <= 0)
{
return 0;
}
return numberOfWorkersLost / Math.Min(existingTownHalls, numberOfWorkersLost);
}
float WorkerReplacementCost()
{
return CostOfWorker * NumberOfWorkersLostToHarass;
}
float WorkerReplacementCost(int numberOfWorkersLostToHarass)
{
return CostOfWorker * numberOfWorkersLostToHarass;
}
float DelayedMiningCost()
{
return TotalAlloyHarassment - WorkerReplacementCost();
}
void Calculate()
{
TotalAlloyHarassment = Calculate(WorkerReplacementCost(),
SimultaneousProductionFloor(),
NumberOfWorkersLostToHarass,
TravelTimes.Select(x => x.Value).ToList(),
TimeToProduceWorker,
AlloyMinedPerSecondByWorker);
}
float Calculate(float workerReplacementCost,
float simultaneousProductionFloor,
float numberOfWorkersLostToHarass,
IList<float> travelTimes,
float timeToProduceWorker = 20,
float alloyMinedPerSecondByWorker = 1)
{
var totalAlloyHarassment = workerReplacementCost;
for (var workerProductionIndex = 0; workerProductionIndex < simultaneousProductionFloor; workerProductionIndex++)
{
totalAlloyHarassment += alloyMinedPerSecondByWorker * timeToProduceWorker * (workerProductionIndex + 1);
}
var remainder = (int)(numberOfWorkersLostToHarass % simultaneousProductionFloor);
for (var remainderIndex = 0; remainderIndex < remainder; remainderIndex++)
{
totalAlloyHarassment += alloyMinedPerSecondByWorker * timeToProduceWorker * (simultaneousProductionFloor + 1);
}
for (var travelTimeIndex = 0; travelTimeIndex < numberOfWorkersLostToHarass; travelTimeIndex++)
{
var townHallIndex = travelTimeIndex % travelTimes.Count;
totalAlloyHarassment += alloyMinedPerSecondByWorker * travelTimes[townHallIndex];
}
return totalAlloyHarassment;
}
protected override void OnInitialized()
{
base.OnInitialized();
Calculate();
}
public List<TravelTime> TravelTimes { get; set; } = new() { new TravelTime(0, 0) };
private void OnTownHallsChanged(ChangeEventArgs obj)
{
NumberOfTownHallsExisting = int.Parse(obj.Value!.ToString()!);
while (TravelTimes.Count > NumberOfTownHallsExisting)
TravelTimes.Remove(TravelTimes.Last());
while (TravelTimes.Count < NumberOfTownHallsExisting)
TravelTimes.Add(new TravelTime(TravelTimes.Count, 10 * TravelTimes.Count));
Calculate();
}
private void OnTownHallTravelTimeChanged(ChangeEventArgs obj, TravelTime travelTime)
{
travelTime.Value = (int)obj.Value!;
Calculate();
StateHasChanged();
}
}
+65
View File
@@ -0,0 +1,65 @@
@layout PageLayout;
@inherits BasePage
@page "/immortal-home"
<LayoutMediumContentComponent>
<PaperComponent>
<div class="mainContainer">
<div class="mainTitle">
Fan Reference
</div>
<div>
Refer to various aspects of "IMMORTAL: Gates of Pyre" from this external reference!
</div>
</div>
</PaperComponent>
<ContentDividerComponent></ContentDividerComponent>
<PaperComponent>
<div class="heroesContainer">
<ContentHighlightComponent Title="Build Calculator"
Description="Make a build!"
Href="/build-calculator"
ImageHref="image/hero/Build.png"/>
<ContentHighlightComponent Title="Database"
Description="Review the units!"
Href="/database"
ImageHref="image/hero/Database.png"/>
</div>
</PaperComponent>
</LayoutMediumContentComponent>
<style>
.mainContainer {
padding-bottom: 32px;
}
.mainTitle {
font-size: 2.2rem;
font-weight: bold;
}
.heroesContainer {
display: grid;
gap: 64px;
justify-content: center;
margin: auto;
grid-template-columns: 1fr 1fr;
}
@@media only screen and (max-width: 1025px) {
.heroesContainer {
grid-template-columns: 1fr;
}
}
</style>
@@ -0,0 +1,74 @@
<NavLink Href="@Href" class="contentHighlight">
<div class="contentHighlightTitle">
@Title
</div>
<img width="268px" height="161px" src="@ImageHref" class="contentHighlightImage" alt="@Title"/>
<div class="contentHighlightCallToAction">
@Description
</div>
</NavLink>
<style>
.contentHighlight {
background-color: var(--paper);
border: 1px solid var(--paper-border);
border-radius: 2px;
padding-left: 12px;
padding-right: 12px;
padding-top: 24px;
padding-bottom: 24px;
color: white;
display: flex;
flex-direction: column;
gap: 12px;
text-align: center;
margin-left: 12px;
margin-right: 12px;
}
.contentHighlight:hover {
background-color: var(--paper-hover);
border-color: var(--paper-border-hover);
text-decoration: none;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.6);
transform: translateY(-2px) scale(1.01);
}
.contentHighlightTitle {
font-weight: 800;
font-size: 1.3rem;
margin: auto;
}
.contentHighlightImage {
border: 1px solid rgba(0, 0, 0, 0.5);
box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.5);
width: calc(100% - 32px);
margin-left: auto;
margin-right: auto;
margin-top: 12px;
margin-bottom: 12px;
}
.contentHighlightCallToAction {
font-weight: 700;
font-size: 1.1rem;
margin: auto;
padding: 16px;
}
</style>
@code {
[Parameter] public string Href { get; set; } = default!;
[Parameter] public string Title { get; set; } = default!;
[Parameter] public string Description { get; set; } = default!;
[Parameter] public string ImageHref { get; set; } = default!;
}
@@ -0,0 +1,48 @@
@layout PageLayout
@inherits BasePage
@page "/memory-tester"
<LayoutMediumContentComponent>
<WebsiteTitleComponent>Memory Tester</WebsiteTitleComponent>
<PaperComponent>
<UnitMemoryManager></UnitMemoryManager>
</PaperComponent>
<ContentDividerComponent></ContentDividerComponent>
<PaperComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
What is this tool?
</InfoQuestionComponent>
<InfoAnswerComponent>
A tool to test your memory of unit stats. Look at the first unit given, and fill in the remaining stats
based on how they should compare.
<br/><br/>
For example, if the first unit you see is the Masked Hunter of range 400, do you remember the range of
the Scepter? Are they the same? Does the Scepter have double the range of the Masked Hunter? Less range
than it? Well, enter your guess and submit!
<SpoilerTextComponent>The range is 200 longer, so if you remember that, you know you are going to need
more than Masked Hunters to deal with hard to reach enemy Scepters.
</SpoilerTextComponent>
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
Why is this tool here?
</InfoQuestionComponent>
<InfoAnswerComponent>
It was just a tool to quickly develop for fun when I didn't want to cover something larger on the
02/27/2022 live coding stream.
<br/><br/>
It may get expanded upon later.
</InfoAnswerComponent>
</InfoBodyComponent>
</PaperComponent>
</LayoutMediumContentComponent>
@@ -0,0 +1,129 @@
@implements IDisposable;
@inject IMemoryTesterService MemoryTesterService;
<div class="unitMemoryContainer@(isCorrect ? " correct" : isWrong ? "wrong" : "")">
<FormLayoutComponent>
<FormDisplayComponent Label="Name">
<Display>@EntityMemory.Name</Display>
</FormDisplayComponent>
@foreach (var question in questions)
{
var questionWrong = hasBeenSubmitted && !question.IsRevealed && question.Guess != question.Answer;
<FormGuessComponent IsSubmitted="hasBeenSubmitted"
OnChange="answerEventArgs => OnAnswerEntered(answerEventArgs, question)"
MemoryQuestion="question"/>
@if (questionWrong)
{
<div class="wrongAnswer">The correct answer was @question.Answer</div>
}
}
</FormLayoutComponent>
</div>
<style>
.unitMemoryContainer {
}
.unitMemoryContainer.correct {
border-color: green;
}
.unitMemoryContainer.wrong {
border-color: red;
}
.wrongAnswer {
padding: 12px;
color: #ff2525;
font-weight: 700;
background-color: rgba(0, 0, 0, 0.4);
padding: 8px;
border-radius: 2px;
}
</style>
@code {
[Parameter] public MemoryEntityModel EntityMemory { get; set; } = default!;
private List<MemoryQuestionModel> questions { get; set; } = default!;
private bool hasBeenSubmitted;
private bool isCorrect;
private bool isWrong;
public int Guess { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
MemoryTesterService.Subscribe(OnMemoryEvent);
OnRefresh();
}
void IDisposable.Dispose()
{
MemoryTesterService.Unsubscribe(OnMemoryEvent);
}
void OnMemoryEvent(MemoryTesterEvent memoryTesterEvent)
{
if (memoryTesterEvent == MemoryTesterEvent.OnVerify)
{
OnVerify();
}
if (memoryTesterEvent == MemoryTesterEvent.OnRefresh)
{
OnRefresh();
}
}
public void OnAnswerEntered(AnswerEventArgs answerEventArgs, MemoryQuestionModel question)
{
question.Guess = answerEventArgs.Guess;
MemoryTesterService.Update(question);
}
void OnVerify()
{
hasBeenSubmitted = true;
isCorrect = true;
foreach (var question in questions)
{
if (question.Answer != question.Guess)
{
isCorrect = false;
isWrong = true;
return;
}
}
StateHasChanged();
}
void OnRefresh()
{
hasBeenSubmitted = false;
isCorrect = false;
isWrong = false;
questions = (from question in MemoryTesterService.GetQuestions()
where question.MemoryEntityModelId == EntityMemory.Id
select question).ToList();
StateHasChanged();
}
}
@@ -0,0 +1,104 @@
@implements IDisposable;
@inject IMemoryTesterService MemoryTesterService;
<div class="quizContainer">
<div class="quizListContainer">
@if (entities != null && questions != null)
{
@foreach (var entityMemory in entities)
{
<UnitMemory EntityMemory="entityMemory"></UnitMemory>
}
}
</div>
<div class="quizButtons">
<ButtonComponent MyButtonType="MyButtonType.Secondary" OnClick="OnRefreshQuiz">Refresh</ButtonComponent>
<ButtonComponent MyButtonType="MyButtonType.Primary" OnClick="OnSubmitQuiz">Submit</ButtonComponent>
</div>
</div>
<style>
.quizContainer {
display: flex;
flex-direction: column;
gap: 16px;
padding: 16px;
}
.quizListContainer {
display: flex;
flex-direction: column;
gap: 16px;
}
@@media (min-width: @SupportedWebSizes.Tablet) {
.quizContainer {
}
.quizButtons {
display: flex;
flex-direction: row;
gap: 16px;
justify-content: flex-end;
width: 100%;
}
.quizListContainer {
}
}
@@media (min-width: @SupportedWebSizes.Desktop) {
.quizListContainer {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
</style>
@code {
private List<MemoryEntityModel> entities = null!;
private List<MemoryQuestionModel> questions = null!;
protected override void OnInitialized()
{
base.OnInitialized();
MemoryTesterService.Subscribe(OnMemoryEvent);
MemoryTesterService.GenerateQuiz();
}
void IDisposable.Dispose()
{
MemoryTesterService.Unsubscribe(OnMemoryEvent);
}
void OnMemoryEvent(MemoryTesterEvent memoryTesterEvent)
{
if (memoryTesterEvent == MemoryTesterEvent.OnVerify)
{
StateHasChanged();
}
if (memoryTesterEvent == MemoryTesterEvent.OnRefresh)
{
entities = MemoryTesterService.GetEntities();
questions = MemoryTesterService.GetQuestions();
StateHasChanged();
}
}
void OnSubmitQuiz(EventArgs eventArgs)
{
MemoryTesterService.Verify();
}
void OnRefreshQuiz(EventArgs eventArgs)
{
MemoryTesterService.GenerateQuiz();
}
}
+145
View File
@@ -0,0 +1,145 @@
@layout PageLayout
@inherits BasePage
@inject INoteService NoteService
@implements IDisposable
@inject IDataCollectionService DataCollectionService
@page "/notes"
@if (!NoteService.IsLoaded())
{
<LoadingComponent/>
}
else
{
<LayoutMediumContentComponent>
<WebsiteTitleComponent>Notes</WebsiteTitleComponent>
<PaperComponent>
@foreach (var noteSection in NoteService.NoteSectionModels)
{
<div class="noteSectionContainer">
<div class="noteSectionTitle">@noteSection.Name</div>
<div class="noteContentContainer">
@foreach (var noteContent in noteSection.NoteContentModels)
{
<NavLink class="noteContentLink" href="@noteContent.GetNoteLink()">
<div class="noteContentName">@noteContent.Name</div>
<div class="noteContentDescription">@noteContent.Description</div>
</NavLink>
}
</div>
</div>
}
</PaperComponent>
</LayoutMediumContentComponent>
}
<style>
.noteSectionContainer {
width: 100%;
padding-left: 8px;
padding-right: 8px;
padding-top: 8px;
padding-bottom: 64px;
}
.noteSectionTitle {
font-size: 3rem;
font-weight: bold;
text-align: center;
margin-bottom: 32px;
}
.noteContentContainer {
display: grid;
gap: 12px;
grid-template-columns: 1fr 1fr;
}
@@media only screen and (max-width: 1025px) {
.noteContentContainer {
grid-template-columns: 1fr;
}
}
.noteContentName {
font-weight: bold;
font-size: 1.6rem;
}
.noteContentDescription {
font-weight: normal;
}
.noteContentLink {
background-color: var(--paper);
border: 1px solid var(--paper-border);
border-radius: 2px;
padding-left: 12px;
padding-right: 12px;
padding-top: 24px;
padding-bottom: 24px;
color: white;
display: flex;
flex-direction: column;
gap: 12px;
text-align: center;
margin-left: 12px;
margin-right: 12px;
}
.noteContentLink:hover {
background-color: var(--paper-hover);
border-color: var(--paper-border-hover);
text-decoration: none;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.6);
transform: translateY(-2px) scale(1.01);
}
</style>
@code {
[Parameter] public string? Href1 { get; set; }
[Parameter] public string? Href2 { get; set; }
[Parameter] public string? Href3 { get; set; }
[Parameter] public string? Href4 { get; set; }
[Parameter] public string? Href5 { get; set; }
private string Href => Href5 ?? Href4 ?? Href3 ?? Href2 ?? Href1 ?? "";
string selectedSection = "All";
protected override void OnInitialized()
{
base.OnInitialized();
NoteService.Subscribe(StateHasChanged);
NoteService.Load();
}
void IDisposable.Dispose()
{
NoteService.Unsubscribe(StateHasChanged);
}
void OnSectionChanged(ChangeEventArgs e)
{
selectedSection = e.Value!.ToString()!;
StateHasChanged();
}
}
+121
View File
@@ -0,0 +1,121 @@
@layout PageLayout
@inherits BasePage
@inject INoteService NoteService
@implements IDisposable
@inject IDataCollectionService DataCollectionService
@page "/notes/{href1}/{href2?}/{href3?}/{href4?}/{href5?}"
@if (!NoteService.IsLoaded())
{
<LoadingComponent/>
}
else
{
<LayoutWithSidebarComponent>
<Sidebar>
<NoteNavComponent
Connections="NoteService.NoteConnectionModels"
Notes="NoteService.NoteContentModels"/>
</Sidebar>
<Content>
<PaperComponent>
@foreach (var note in NoteService.NoteContentModels)
{
if (!note.Href.Equals(Href))
{
continue;
}
<NoteComponent NoteContentModel="note"/>
}
</PaperComponent>
</Content>
</LayoutWithSidebarComponent>
}
<style>
pre code {
color: white;
}
h1 {
display: block;
font-size: 2em;
margin-top: 0.67em;
margin-bottom: 0.67em;
margin-left: 0;
margin-right: 0;
font-weight: bold;
}
h2 {
display: block;
font-size: 1.5em;
margin-top: 0.83em;
margin-bottom: 0.83em;
margin-left: 0;
margin-right: 0;
font-weight: bold;
}
li {
display: list-item;
}
p {
display: block;
margin-top: 1em;
margin-bottom: 1em;
margin-left: 0;
margin-right: 0;
}
ul {
display: block;
list-style-type: disc;
margin-top: 1em;
margin-bottom: 1em;
margin-left: 0;
margin-right: 0;
padding-left: 40px;
}
pre {
background: black;
padding: 2px;
}
</style>
@code {
[Parameter] public string? Href1 { get; set; }
[Parameter] public string? Href2 { get; set; }
[Parameter] public string? Href3 { get; set; }
[Parameter] public string? Href4 { get; set; }
[Parameter] public string? Href5 { get; set; }
private string Href => Href5 ?? Href4 ?? Href3 ?? Href2 ?? Href1 ?? "";
protected override void OnInitialized()
{
base.OnInitialized();
NoteService.Subscribe(StateHasChanged);
NoteService.Load();
}
void IDisposable.Dispose()
{
NoteService.Unsubscribe(StateHasChanged);
}
}
@@ -0,0 +1,78 @@
@inject HttpClient httpClient
@if (content == null)
{
<LoadingComponent/>
}
else
{
<div class="note">
<div class="noteHeader">
<h1 class="noteTitle">@noteFrontMatter.Title</h1>
<div class="noteDates">
<div class="noteDateUpdated"><b>Updated</b>: @noteFrontMatter.UpdatedDate.ToString("MM/dd/yyyy")</div>
<div class="noteDateCreated"><b>Created</b>: @noteFrontMatter.CreatedDate.ToString("MM/dd/yyyy")</div>
</div>
</div>
<div class="noteContent">@((MarkupString)Markdown.ToHtml(content, Pipeline))</div>
<div class="noteFooter">
<LinkButtonComponent Href="@GitUrl">
Edit on GitHub <i class="fa-brands fa-github" style="font-size: 24px; margin-left: 5px;"></i>
</LinkButtonComponent>
</div>
</div>
}
<style>
.noteTitle {
font-weight: bold;
}
.noteHeader {
display: flex;
justify-content: space-between;
}
.noteDates {
display: flex;
flex-direction: column;
}
.noteFooter {
display: flex;
justify-content: flex-end;
}
</style>
@code {
[Parameter] public NoteContentModel NoteContentModel { get; set; } = default!;
NoteFrontMatterModel noteFrontMatter = null!;
private string? content;
private string Filepath => $"content/notes/{NoteContentModel.Content}.md";
private string GitUrl => $"{Project.GitResourcesUrl}/{Filepath}";
private MarkdownPipeline Pipeline => MarkdownFiles.Pipeline;
private async Task<NoteFrontMatterModel> LoadContent()
{
content = await MarkdownFiles.LoadMarkdown(httpClient, Filepath);
return noteFrontMatter =
await MarkdownFiles.LoadFrontMatter<NoteFrontMatterModel>(httpClient, Filepath);
}
protected override async Task OnParametersSetAsync()
{
await LoadContent();
}
protected override async Task OnInitializedAsync()
{
await LoadContent();
}
}
@@ -0,0 +1,71 @@
@if (Note!.NoteContentModels.Count > 0)
{
<div class="noteInnerNavContainer">
@foreach (var innerNote in Note.NoteContentModels)
{
var linkStyle = $"noteInnerNavButton inner_{Layers}";
<NavLink class="@linkStyle" href="@innerNote.GetNoteLink()">@innerNote.Name</NavLink>
<NoteInnerNavComponent Note="@innerNote" Layers="@IncrementLayers()"/>
}
</div>
}
<style>
.noteInnerNavContainer {
display: flex;
flex-direction: column;
}
.noteInnerNavButton a {
color: white;
}
.noteInnerNavButton a:hover {
color: white;
}
.noteInnerNavButton {
padding: 8px;
margin-left: 8px;
color: white;
}
.noteInnerNavButton:hover {
}
.inner_1 {
margin-left: 8px;
}
.inner_2 {
margin-left: 16px;
}
.inner_3 {
margin-left: 24px;
}
</style>
@code {
[Parameter] public NoteContentModel? Note { get; set; }
[Parameter] public int Layers { get; set; } = 1;
public int IncrementLayers()
{
return Layers + 1;
}
private string GetLink(NoteContentModel note)
{
return $"notes/{note.Href}";
}
}
@@ -0,0 +1,42 @@
<div class="noteNavContainer">
@foreach (var note in Notes)
{
if (note.Parent == null)
{
<NavLink class="noteNavButton" href="@note.GetNoteLink()">@note.Name</NavLink>
<NoteInnerNavComponent Note="@note"/>
}
}
</div>
<style>
.noteNavContainer {
display: flex;
flex-direction: column;
gap: 8px;
}
.noteNavButton a {
color: white;
}
.noteNavButton a:hover {
color: white;
background-color: var(--primary-hover);
}
.noteNavButton {
padding: 8px;
color: white;
}
</style>
@code {
[Parameter] public List<NoteContentModel> Notes { get; set; } = default!;
[Parameter] public List<NoteConnectionModel> Connections { get; set; } = default!;
}
+132
View File
@@ -0,0 +1,132 @@
@page "/permissions"
@inject IPermissionService PermissionService
@layout PageLayout
@inject IMyDialogService MyDialogService
@inherits BasePage
@using Services.Website
@implements IDisposable
<LayoutMediumContentComponent>
<PaperComponent>
<FormLayoutComponent>
<FormToggleComponent
Label="Storage Enabled"
Info="Is storage enabled?"
Value="_storageEnabled"
OnChange="StoragePermissionChanged"/>
<FormToggleComponent
Label="Data Collection Enabled"
Info="Is data collection enabled?"
Value="_dataCollectionEnabled"
OnChange="DataCollectionPermissionChanged"/>
</FormLayoutComponent>
</PaperComponent>
<ContentDividerComponent/>
<PaperComponent>
<InfoBodyComponent>
<InfoQuestionComponent>What's this page?</InfoQuestionComponent>
<InfoAnswerComponent>This page has options to enable and disable certain permissions settings. Such as
Storage and Data Collection.
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>What does this website store?</InfoQuestionComponent>
<InfoAnswerComponent>This website usages storage to track user defined default on the Storage page.
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>Why would I enable storage?</InfoQuestionComponent>
<InfoAnswerComponent>Enable storage if you want to website to remeber past settings whenever you visit the
website on the same web browser.
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>What data does this website collect?</InfoQuestionComponent>
<InfoAnswerComponent>
This website usages Google Analytics to collect data on usage of this website.
<br/><br/>
Items include: if people use keyboard or mouse in build calculator, what pages people visit, and other
usages.
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>Why would I enable data collection?</InfoQuestionComponent>
<InfoAnswerComponent>Enable data tracking if you want the website maintainer to know how your using the
website.
</InfoAnswerComponent>
</InfoBodyComponent>
</PaperComponent>
</LayoutMediumContentComponent>
@code {
private bool _storageEnabled;
private bool _dataCollectionEnabled;
protected override void OnInitialized()
{
PermissionService.Subscribe(Update);
Update();
}
void Update()
{
_storageEnabled = PermissionService.GetIsStorageEnabled();
_dataCollectionEnabled = PermissionService.GetIsDataCollectionEnabled();
StateHasChanged();
}
void IDisposable.Dispose()
{
PermissionService.Unsubscribe(Update);
}
private void StoragePermissionChanged(ChangeEventArgs obj)
{
PermissionService.SetIsStorageEnabled(!PermissionService.GetIsStorageEnabled());
}
private void DataCollectionPermissionChanged(ChangeEventArgs obj)
{
void OnDataCollectionConfirmClicked(MouseEventArgs mouseEventArgs)
{
PermissionService.SetIsDataCollectionEnabled(!PermissionService.GetIsDataCollectionEnabled());
MyDialogService.Hide();
}
void OnDataCollectionCancelClicked(MouseEventArgs mouseEventArgs)
{
MyDialogService.Hide();
}
if (_storageEnabled && !PermissionService.GetIsDataCollectionEnabled())
{
MyDialogService.Show(new DialogContents
{
Title = "Permission Request",
Message = "Are you sure you want to enable data collection? This feature is implemented with Google Analytics, and your data will be used to gauge interests, find bugs, and optimize updates in IGP Fan Reference.",
OnConfirm = new EventCallback<EventArgs>(this, OnDataCollectionConfirmClicked),
OnCancel = new EventCallback<EventArgs>(this, OnDataCollectionCancelClicked),
ConfirmButtonLabel = "Enable Data Collection"
});
}
else
{
PermissionService.SetIsDataCollectionEnabled(!PermissionService.GetIsDataCollectionEnabled());
}
}
}
+15
View File
@@ -0,0 +1,15 @@
@inherits BasePage
@page "/raw-database"
<div class="page">
<AlertComponent>
<Title>Placeholders and Speculative</Title>
<Message>The data I am using contains placeholders and speculation on future mechanics. Ignore said data when
using this JSON.
</Message>
</AlertComponent>
<CodeComponent>
@EntityData.AsJson()
</CodeComponent>
</div>
+15
View File
@@ -0,0 +1,15 @@
.page {
display: flex;
flex-direction: column;
padding: 16px;
gap: 16px;
}
@media only screen and (max-width: 1025px) {
.page {
display: flex;
flex-direction: column;
padding: 2px;
gap: 2px;
}
}
+248
View File
@@ -0,0 +1,248 @@
@page "/storage"
@inherits BasePage
@inject IStorageService StorageService
@using Services.Website
@implements IDisposable
@layout PageLayout
<LayoutMediumContentComponent>
@if (!_enabledPermissions)
{
<AlertComponent Type="@SeverityType.Error">
<Title>Storage Disabled</Title>
<Message>Enable Storage on the Permissions Page.</Message>
</AlertComponent>
}
else
{
<PaperComponent>
<FormLayoutComponent>
<FormToggleComponent
Label="Is Plain View"
Info="Should Entity view be in plain text?"
Value="@_isEntityPlainView"
OnChange="EntityViewChanged"/>
</FormLayoutComponent>
<DevOnlyComponent>
<FormLayoutComponent>
<FormToggleComponent
Label="Is Dynamic Formatting"
Info="Should [Attacks Per Second/Seconds Between Attack] match in-game values?"
Value="@_isDynamicFormatting"
OnChange="DynamicFormattingChanged"/>
</FormLayoutComponent>
</DevOnlyComponent>
</PaperComponent>
<PaperComponent>
<FormLayoutComponent>
<FormNumberComponent Max="2048"
Min="0"
Value="@(_attackTime == null ? 0 : (int)_attackTime)"
OnChange="AttackTimeChanged">
<FormLabelComponent>Attack Time</FormLabelComponent>
<FormInfoComponent>
@if (_attackTime != null)
{
<i>&emsp; T @Interval.ToTime((int)_attackTime)</i>
}
</FormInfoComponent>
</FormNumberComponent>
<FormNumberComponent Max="2048"
Min="0"
Value="@(_travelTime == null ? 0 : (int)_travelTime)"
OnChange="TravelTimeChanged">
<FormLabelComponent>Travel Time</FormLabelComponent>
<FormInfoComponent>
@if (_travelTime != null)
{
<i>&emsp; T @Interval.ToTime((int)_travelTime)</i>
}
</FormInfoComponent>
</FormNumberComponent>
<FormSelectComponent OnChange="@OnFactionChanged">
<FormLabelComponent>Faction</FormLabelComponent>
<ChildContent>
<option value="@DataType.FACTION_Aru"
selected="@(Faction.Equals(DataType.FACTION_Aru))">
Aru
</option>
<option value="@DataType.FACTION_QRath"
selected="@(Faction.Equals(DataType.FACTION_QRath))">
Q'Rath
</option>
</ChildContent>
</FormSelectComponent>
<FormSelectComponent OnChange="@OnImmortalChanged">
<FormLabelComponent>Immortal</FormLabelComponent>
<ChildContent>
@if (Faction == DataType.FACTION_QRath)
{
<option value="@DataType.IMMORTAL_Orzum"
selected="@(Immortal.Equals(DataType.IMMORTAL_Orzum))">
Orzum
</option>
<option value="@DataType.IMMORTAL_Ajari"
selected="@(Immortal.Equals(DataType.IMMORTAL_Ajari))">
Ajari
</option>
}
@if (Faction == DataType.FACTION_Aru)
{
<option value="@DataType.IMMORTAL_Mala"
selected="@(Immortal.Equals(DataType.IMMORTAL_Mala))">
Mala
</option>
<option value="@DataType.IMMORTAL_Xol"
selected="@(Immortal.Equals(DataType.IMMORTAL_Xol))">
Xol
</option>
}
</ChildContent>
</FormSelectComponent>
<FormNumberComponent Max="600"
Min="0"
Value="@(_buildingInputDelay == null ? 0 : (int)_buildingInputDelay)"
OnChange="OnBuildingInputDelayChanged">
<FormLabelComponent>Building Input Delay</FormLabelComponent>
<FormInfoComponent>Add a input delay to constructing buildings for simulating worker movement and
player micro.
</FormInfoComponent>
</FormNumberComponent>
<FormNumberComponent Max="600"
Min="1"
Value="@(_waitTime == null ? 0 : (int)_waitTime)"
OnChange="@OnWaitTimeChanged">
<FormLabelComponent>Wait Time</FormLabelComponent>
</FormNumberComponent>
</FormLayoutComponent>
<FormLayoutComponent>
<FormNumberComponent Max="2048"
Min="1"
Value="@(_waitTo == null ? 0 : (int)_waitTo)"
OnChange="@OnWaitToChanged">
<FormLabelComponent>Wait To</FormLabelComponent>
</FormNumberComponent>
</FormLayoutComponent>
</PaperComponent>
}
<ContentDividerComponent/>
<PaperComponent>
</PaperComponent>
</LayoutMediumContentComponent>
@code {
bool _enabledPermissions;
protected override void OnInitialized()
{
base.OnInitialized();
_enabledPermissions = StorageService.GetValue<bool>(StorageKeys.EnabledStorage);
RefreshDefaults();
StorageService.Subscribe(RefreshDefaults);
}
void IDisposable.Dispose()
{
StorageService.Unsubscribe(RefreshDefaults);
}
private int? _attackTime;
private int? _travelTime;
private string? _faction;
private string? _immortal;
private string? Faction => _faction == null ? DataType.FACTION_QRath : _faction;
private string? Immortal => _immortal == null ? DataType.IMMORTAL_Orzum : _immortal;
private int? _buildingInputDelay;
private int? _waitTime;
private int? _waitTo;
void RefreshDefaults()
{
_isEntityPlainView = StorageService.GetValue<bool>(StorageKeys.IsPlainView);
_isDynamicFormatting = StorageService.GetValue<bool>(StorageKeys.IsDynamicFormatting);
_attackTime = StorageService.GetValue<int?>(StorageKeys.AttackTime);
_travelTime = StorageService.GetValue<int?>(StorageKeys.TravelTime);
_faction = StorageService.GetValue<string?>(StorageKeys.SelectedFaction);
_immortal = StorageService.GetValue<string?>(StorageKeys.SelectedImmortal);
_buildingInputDelay = StorageService.GetValue<int?>(StorageKeys.BuildInputDelay);
_waitTime = StorageService.GetValue<int?>(StorageKeys.WaitTime);
_waitTo = StorageService.GetValue<int?>(StorageKeys.WaitTo);
StateHasChanged();
}
private bool _isEntityPlainView;
private bool _isDynamicFormatting;
private void EntityViewChanged(ChangeEventArgs obj)
{
StorageService.SetValue(StorageKeys.IsPlainView, obj.Value);
}
private void DynamicFormattingChanged(ChangeEventArgs obj)
{
StorageService.SetValue(StorageKeys.IsDynamicFormatting, obj.Value);
}
private void AttackTimeChanged(ChangeEventArgs obj)
{
StorageService.SetValue(StorageKeys.AttackTime, obj.Value);
}
private void TravelTimeChanged(ChangeEventArgs obj)
{
StorageService.SetValue(StorageKeys.TravelTime, obj.Value);
}
private void OnFactionChanged(ChangeEventArgs obj)
{
StorageService.SetValue(StorageKeys.SelectedFaction, obj.Value);
}
private void OnImmortalChanged(ChangeEventArgs obj)
{
StorageService.SetValue(StorageKeys.SelectedImmortal, obj.Value);
}
private void OnBuildingInputDelayChanged(ChangeEventArgs obj)
{
StorageService.SetValue(StorageKeys.BuildInputDelay, obj.Value);
}
private void OnWaitTimeChanged(ChangeEventArgs obj)
{
StorageService.SetValue(StorageKeys.WaitTime, obj.Value);
}
private void OnWaitToChanged(ChangeEventArgs obj)
{
StorageService.SetValue(StorageKeys.WaitTo, obj.Value);
}
}
+50
View File
@@ -0,0 +1,50 @@
@page "/streams"
@inherits BasePage
@layout PageLayout
<LayoutMediumContentComponent>
<WebsiteTitleComponent>Streams</WebsiteTitleComponent>
<PaperComponent>
<InfoBodyComponent>
<InfoQuestionComponent>When and where are you streaming?</InfoQuestionComponent>
<InfoAnswerComponent>I stream Sunday updates on <a href="https://www.twitch.tv/jonathanmccaffrey"
target="_blank">Twitch</a>.
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>What exactly are you streaming?</InfoQuestionComponent>
<InfoAnswerComponent>
The plan will be sprint planning and general development of this website.
<br/><br/>
Feel free to jump into the stream to ask questions or make feature requests for the website.
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>Why should you watch these streams?</InfoQuestionComponent>
<InfoAnswerComponent>
You shouldn't. By nature of being live coding streams, I think they will have little entertainment and
educational value.
<br/><br/>
The most reason (that comes to mind) is to see a coding day of one software developer. Although please
note that I stream content that I think is easy (to look smart), so it's not a "truly random" coding
day. For example, you won't find any vods of the five-week sprint where I (figuratively) bash my head
against the table trying to get SQL working in Blazor WASM before giving up and moving on.
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>Anything else I should know?</InfoQuestionComponent>
<InfoAnswerComponent>I'll be streaming under the "<a
href="https://www.twitch.tv/directory/game/Software%20and%20Game%20Development" target="_blank">Twitch,
Software and Game Development</a>" category. If you are looking to see some actual IGP gameplay,
there are better and more focused streamers to provide said content. Check out the "<a
href="https://www.twitch.tv/directory/game/IMMORTAL%3A%20Gates%20of%20Pyre/videos/all"
target="_blank">Twitch, IMMORTAL: Gates of Pyre</a>" category for some examples.
</InfoAnswerComponent>
</InfoBodyComponent>
</PaperComponent>
</LayoutMediumContentComponent>
@@ -0,0 +1,25 @@
@implements IDisposable;
@inject IMyDialogService MyDialogService
<ConfirmationDialogComponent></ConfirmationDialogComponent>
@code {
protected override void OnInitialized()
{
base.OnInitialized();
MyDialogService.Subscribe(OnUpdate);
}
void IDisposable.Dispose()
{
MyDialogService.Unsubscribe(OnUpdate);
}
void OnUpdate()
{
StateHasChanged();
}
}
@@ -0,0 +1,29 @@
@implements IDisposable;
@inject IEntityDialogService entityDialogService
@if (entityDialogService.HasDialog())
{
<EntityDialogComponent></EntityDialogComponent>
}
@code {
protected override void OnInitialized()
{
base.OnInitialized();
entityDialogService.Subscribe(OnUpdate);
}
void IDisposable.Dispose()
{
entityDialogService.Unsubscribe(OnUpdate);
}
void OnUpdate()
{
StateHasChanged();
}
}
+49
View File
@@ -0,0 +1,49 @@
@implements IDisposable;
@inject ISearchService searchService
@inject IJSRuntime jsRuntime
<SearchDialogComponent></SearchDialogComponent>
@code {
private string test = "Q";
protected override void OnInitialized()
{
searchService.Subscribe(OnUpdate);
}
protected override async Task OnInitializedAsync()
{
await searchService.Load();
await jsRuntime.InvokeVoidAsync("SetDotnetReference", DotNetObjectReference.Create(this));
}
public void Dispose()
{
searchService.Unsubscribe(OnUpdate);
}
void OnUpdate()
{
StateHasChanged();
}
[JSInvokable("OnKeyPress")]
public async Task OnKeyPress(string code, bool ctrlKey, bool shiftKey, bool altKey, bool metaKey)
{
if (code.ToLower().Equals("k") && (ctrlKey || shiftKey || altKey || metaKey))
{
if (searchService.IsVisible)
{
searchService.Hide();
}
else
{
searchService.Show();
}
}
}
}
+60
View File
@@ -0,0 +1,60 @@
@implements IDisposable;
@inject IToastService toastService
@if (toastService.HasToasts())
{
<div class="toastsContainer">
@foreach (var toast in Toasts)
{
<ToastComponent Toast="toast"/>
}
</div>
}
<style>
.toastsContainer {
position: fixed;
top: 64px;
right: 64px;
display: flex;
flex-direction: column;
gap: 5px;
}
</style>
@code {
private List<ToastModel> Toasts => toastService.GetToasts();
private Timer ageTimer = null!;
protected override void OnInitialized()
{
base.OnInitialized();
toastService.Subscribe(OnUpdate);
ageTimer = new Timer(10);
ageTimer.Elapsed += OnAge!;
ageTimer.Enabled = true;
}
void IDisposable.Dispose()
{
toastService.Unsubscribe(OnUpdate);
}
void OnAge(object? sender, ElapsedEventArgs elapsedEventArgs)
{
toastService.AgeToasts();
ageTimer.Enabled = true;
}
void OnUpdate()
{
StateHasChanged();
}
}
+78
View File
@@ -0,0 +1,78 @@
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
using Blazor.Analytics;
using Blazored.LocalStorage;
using IGP;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Services;
using MudBlazor.Services;
using Services;
using Services.Development;
using Services.Immortal;
using Services.Website;
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US");
CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en-US");
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.Logging.SetMinimumLevel(LogLevel.Warning);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddScoped<LazyAssemblyLoader>();
builder.Services.AddLocalization();
builder.Services.AddBlazoredLocalStorageAsSingleton(config =>
{
config.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
config.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
config.JsonSerializerOptions.IgnoreReadOnlyProperties = true;
config.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
config.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
config.JsonSerializerOptions.ReadCommentHandling = JsonCommentHandling.Skip;
config.JsonSerializerOptions.WriteIndented = false;
});
#if DEBUG
builder.Services.AddGoogleAnalytics("G-S96LW7TVFY");
#else
builder.Services.AddGoogleAnalytics(builder.Configuration["GATag"]);
#endif
builder.Services.AddScoped<INavigationService, NavigationService>();
builder.Services.AddScoped<IKeyService, KeyService>();
builder.Services.AddScoped<IImmortalSelectionService, ImmortalSelectionService>();
builder.Services.AddScoped<IBuildComparisonService, DeprecatedBuildComparisionService>();
builder.Services.AddScoped<IBuildOrderService, BuildOrderService>();
builder.Services.AddScoped<IEconomyService, EconomyService>();
builder.Services.AddScoped<ITimingService, TimingService>();
builder.Services.AddScoped<IMemoryTesterService, MemoryTesterService>();
builder.Services.AddScoped<IEntityFilterService, EntityFilterService>();
builder.Services.AddScoped<IEntityDisplayService, EntityDisplayService>();
builder.Services.AddScoped<IEntityDialogService, EntityDialogService>();
builder.Services.AddScoped<IToastService, ToastService>();
builder.Services.AddScoped<INoteService, NoteService>();
builder.Services.AddScoped<ISearchService, SearchService>();
builder.Services.AddScoped<IStorageService, StorageService>();
builder.Services.AddScoped<IPermissionService, PermissionService>();
builder.Services.AddScoped<IEconomyComparisonService, EconomyComparisionService>();
builder.Services.AddScoped<IDataCollectionService, DataCollectionService>();
builder.Services.AddScoped<IMyDialogService, MyDialogService>();
builder.Services.AddScoped(sp => new HttpClient
{
BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
});
builder.Services.AddMudServices();
await builder.Build().RunAsync();
+30
View File
@@ -0,0 +1,30 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:51403",
"sslPort": 44377
}
},
"profiles": {
"IGP": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://localhost:7234;http://localhost:5234",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
+9
View File
@@ -0,0 +1,9 @@
namespace IGP.Utils;
public static class Interval
{
public static string ToTime(int interval)
{
return TimeSpan.FromSeconds(interval).ToString(@"mm\:ss");
}
}
+52
View File
@@ -0,0 +1,52 @@
using Markdig;
using Markdig.Extensions.Yaml;
using Markdig.Syntax;
using YamlDotNet.Serialization;
namespace IGP.Utils;
public static class MarkdownFiles
{
private static readonly IDeserializer YamlDeserializer =
new DeserializerBuilder()
.IgnoreUnmatchedProperties()
.Build();
public static readonly MarkdownPipeline Pipeline
= new MarkdownPipelineBuilder()
.UseYamlFrontMatter()
.UseAdvancedExtensions()
.Build();
public static async Task<string> LoadMarkdown(HttpClient httpClient, string filepath)
{
return await httpClient.GetStringAsync(filepath);
}
public static async Task<T> LoadFrontMatter<T>(HttpClient httpClient, string filepath)
{
var markdown = await LoadMarkdown(httpClient, filepath);
var document = Markdown.Parse(markdown, Pipeline);
var block = document
.Descendants<YamlFrontMatterBlock>()
.FirstOrDefault();
if (block == null)
return default!;
var yaml =
block
.Lines
.Lines
.OrderByDescending(x => x.Line)
.Select(x => $"{x}\n")
.ToList()
.Select(x => x.Replace("---", string.Empty))
.Where(x => !string.IsNullOrWhiteSpace(x))
.Aggregate((s, agg) => agg + s);
return YamlDeserializer.Deserialize<T>(yaml);
}
}
+7
View File
@@ -0,0 +1,7 @@
namespace IGP.Utils;
public static class Project
{
public static string GitResourcesUrl =>
"https://github.com/JonathanMcCaffrey/IGP-Fan-Reference/blob/develop/IGP/wwwroot/";
}
+4
View File
@@ -0,0 +1,4 @@
global using System.Net.Http;
global using System;
global using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
global using Microsoft.Extensions.DependencyInjection;
+55
View File
@@ -0,0 +1,55 @@
@using Components.Display
@using Components.Feedback
@using Components.Form
@using Components.Info
@using Components.Inputs
@using Components.Layout
@using Components.Navigation
@using Components.Shared
@using IGP.Dialog
@using IGP.Pages
@using IGP.Pages.BuildCalculator
@using IGP.Pages.BuildCalculator.Parts
@using IGP.Pages.Database.Entity
@using IGP.Pages.Database.Entity.Parts
@using IGP.Pages.Database.Parts
@using IGP.Pages.EconomyComparison
@using IGP.Pages.EconomyComparison.Parts
@using IGP.Pages.Home
@using IGP.Pages.Home.Parts
@using IGP.Pages.MemoryTester.Parts
@using IGP.Pages.Notes
@using IGP.Pages.Notes.Parts
@using IGP.Portals
@using IGP.Utils
@using Markdig
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.Extensions.Localization
@using Microsoft.JSInterop
@using Model.Chart
@using Model.Economy
@using Model.Entity
@using Model.Entity.Data
@using Model.Entity.Parts
@using Model.Feedback
@using Model.Hotkeys
@using Model.MemoryTester
@using Model.Notes
@using Model.RoadMap
@using Model.RoadMap.Enums
@using Model.Types
@using Model.Website
@using Services
@using Services.Immortal
@using System.Globalization
@using System.Reflection
@using System.Timers
@using Blazor.Analytics
@using Blazor.Analytics.Components
@using Blazor.Analytics.Abstractions
@using MudBlazor
@using MudBlazor.Services
+7
View File
@@ -0,0 +1,7 @@
{
"sdk": {
"version": "7.0.0",
"rollForward": "latestMajor",
"allowPrerelease": true
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"navigationFallback": {
"rewrite": "/index.html"
}
}
+6
View File
@@ -0,0 +1,6 @@
/about / 200
/resume / 200
/tools / 200
/tutorials / 200
/twitch / 200
/work / 200
+17
View File
@@ -0,0 +1,17 @@
{
"ConnectionStrings": {
"DefaultConnection": "Data Source=./Database.db"
},
"AzureAdB2C": {
"Authority": "https://aadB2CInstance.b2clogin.com/qualified.domain.name/b2c_1_susi",
"ClientId": "33333333-3333-3333-33333333333333333",
"ValidateAuthority": false
},
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Warning"
}
}
}
@@ -0,0 +1,57 @@
---
title: Holdout
summary: First coop test map in pre-alpha.
created_date: 2/18/2022
updated_date: 2/18/2022
---
Information contained in this note is based on this <a href="https://www.youtube.com/watch?v=XkAgOCIz3DE">YouTube,
Reference Video</a>.
![Open Bases](./image/notes/coop-holdout/OpenBases.png)
**Open Bases**
On this map, you start with around 500 alloy and 100 ether. You are probably going to want to expand to the bases in the
marked order, given the density of defending enemies shown on the minimap.
You should know that these are all standard bases that will mine out in 10 minutes. Giving a total of 18,000 alloy and
7,200 ether. Plus an additional 6,000 alloy from the starting Bastion. In the late game, you will have zero income,
aside from pyre.
![Enemy Spawns](./image/notes/coop-holdout/EnemySpawns.png)
**Enemy Spawn Areas**
The first enemy wave will spawn at 1 minute, and every 2 minutes after will spawn a new wave. These waves are small, and
won't be a threat until the 15-minute mark.
![Defend Points](./image/notes/coop-holdout/DefendPoints.png)
**Pyre Towers**
You have till then to take all 5 of your bases, and set a defensive line at the outer Pyre towers.
The spawn size post the 15-minute mark does become rather large. You may be tempted to fall back and abandon forward
bases, but the waves will stack if not dealt with. Eventually, more units than the game can handle, so ensure outer pyre
towers are held. Try to take them back if you lose them.
![Pyre](./image/notes/coop-holdout/Pyre.png)
**Pyre Camps**
When you have the time you are also going to need to take the 4 pyre camps spread around the map. It will probably be
ideal to split your army in half, to protect your two outer towers, and just have a small force of Ichors or Dervishes
to clear the camps quickly.
![Multipliers](./image/notes/coop-holdout/Multipliers.png)
**Multipliers**
If you have additional free time, you can take out the Altar of the Worthys on the edges of the map to double your
current more multiplier: 2, 4, 8, to the max of 16. Amber Wombs will also spawn, with a pack of enemies to defend them.
Killing an Amber Womb will increase your score, but also spawn random friendly and enemy units. With this spawning, it's
possible to go past the supply cap.
But really, these optional objectives can be completely ignored, so you can just focus on surviving for as long as
possible.
Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 KiB

@@ -0,0 +1,268 @@
---
title: Custom HotKey Setup
summary: Customize your hotkeys in the pre-alpha.
created_date: 4/13/2022
updated_date: 4/13/2022
---
In the pre-alpha, IGP comes with some Unreal default hotkey setup.
This document will explain how to set up, modify, and use a new hotkey setup.
# Save the "Input.ini" file
***Copy the below content and save the file as `Input.ini`.***
```ini
[/Script/Engine.InputSettings]
ActionMappings = (ActionName="Ability1",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Q)
ActionMappings = (ActionName="Ability2",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=W)
ActionMappings = (ActionName="Ability3",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=E)
ActionMappings = (ActionName="Ability4",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=R)
ActionMappings = (ActionName="Ability5",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=F)
ActionMappings = (ActionName="Ability6",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=V)
ActionMappings = (ActionName="Ability7",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=A)
ActionMappings = (ActionName="Ability8",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=S)
ActionMappings = (ActionName="AttackMove",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=A)
ActionMappings = (ActionName="CancelOrders",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Tilde)
ActionMappings = (ActionName="CompleteAllTraining",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=I)
ActionMappings = (ActionName="CompleteConstructions",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=K)
ActionMappings = (ActionName="ConstructionTab",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=C)
ActionMappings = (ActionName="ControlGroup1",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=D)
ActionMappings = (ActionName="ControlGroup10",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Zero)
ActionMappings = (ActionName="ControlGroup2",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Two)
ActionMappings = (ActionName="ControlGroup3",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Three)
ActionMappings = (ActionName="ControlGroup4",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Four)
ActionMappings = (ActionName="ControlGroup5",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Five)
ActionMappings = (ActionName="ControlGroup6",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Six)
ActionMappings = (ActionName="ControlGroup7",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Seven)
ActionMappings = (ActionName="ControlGroup8",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Eight)
ActionMappings = (ActionName="ControlGroup9",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Nine)
ActionMappings = (ActionName="ControlGroupAddStealKey",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=SpaceBar)
ActionMappings = (ActionName="Delete",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Delete)
ActionMappings = (ActionName="DisableWeapons",bShift=True,bCtrl=False,bAlt=True,bCmd=False,Key=J)
ActionMappings = (ActionName="DownArrow",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Down)
ActionMappings = (ActionName="DragPan",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=MiddleMouseButton)
ActionMappings = (ActionName="Enter Cinematic Camera",bShift=True,bCtrl=False,bAlt=True,bCmd=False,Key=L)
ActionMappings = (ActionName="FullMana",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=J)
ActionMappings = (ActionName="FullyDamageSelectedUnits",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=P)
ActionMappings = (ActionName="GetAlloyEther",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=M)
ActionMappings = (ActionName="GetPyre",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=L)
ActionMappings = (ActionName="Hide HUD",bShift=True,bCtrl=False,bAlt=True,bCmd=False,Key=O)
ActionMappings = (ActionName="InclusiveSelect/QueueCommand",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=LeftShift)
ActionMappings = (ActionName="JumpToCameraLocation1",bShift=True,bCtrl=False,bAlt=False,bCmd=False,Key=Z)
ActionMappings = (ActionName="JumpToCameraLocation2",bShift=True,bCtrl=False,bAlt=False,bCmd=False,Key=X)
ActionMappings = (ActionName="JumpToCameraLocation3",bShift=True,bCtrl=False,bAlt=False,bCmd=False,Key=C)
ActionMappings = (ActionName="JumpToCameraLocation4",bShift=True,bCtrl=False,bAlt=False,bCmd=False,Key=V)
ActionMappings = (ActionName="JumpToCameraLocation5",bShift=True,bCtrl=False,bAlt=False,bCmd=False,Key=Two)
ActionMappings = (ActionName="JumpToCameraLocation6",bShift=True,bCtrl=False,bAlt=False,bCmd=False,Key=Three)
ActionMappings = (ActionName="JumpToCameraLocation7",bShift=True,bCtrl=False,bAlt=False,bCmd=False,Key=Four)
ActionMappings = (ActionName="LeftArrow",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Left)
ActionMappings = (ActionName="LeftMouseClick",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=LeftMouseButton)
ActionMappings = (ActionName="MarcoPolo",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Comma)
ActionMappings = (ActionName="Menu",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Escape)
ActionMappings = (ActionName="Menu",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=F10)
ActionMappings = (ActionName="MinimapJumpTo",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=LeftMouseButton)
ActionMappings = (ActionName="Move/ContextCommand",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=RightMouseButton)
ActionMappings = (ActionName="PyreTab",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=One)
ActionMappings = (ActionName="ResearchTab",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Tab)
ActionMappings = (ActionName="RespawnNeutrals",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=N)
ActionMappings = (ActionName="RightArrow",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Right)
ActionMappings = (ActionName="RightMouseClick",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=RightMouseButton)
ActionMappings = (ActionName="Select/ConfirmAbility",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=LeftMouseButton)
ActionMappings = (ActionName="SelectUnitProductionBuildings",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Z)
ActionMappings = (ActionName="SetCameraLocation1",bShift=False,bCtrl=True,bAlt=False,bCmd=False,Key=Z)
ActionMappings = (ActionName="SetCameraLocation2",bShift=False,bCtrl=True,bAlt=False,bCmd=False,Key=X)
ActionMappings = (ActionName="SetCameraLocation3",bShift=False,bCtrl=True,bAlt=False,bCmd=False,Key=C)
ActionMappings = (ActionName="SetCameraLocation4",bShift=False,bCtrl=True,bAlt=False,bCmd=False,Key=V)
ActionMappings = (ActionName="SetCameraLocation5",bShift=False,bCtrl=True,bAlt=False,bCmd=False,Key=Two)
ActionMappings = (ActionName="SetCameraLocation6",bShift=False,bCtrl=True,bAlt=False,bCmd=False,Key=Three)
ActionMappings = (ActionName="SetCameraLocation7",bShift=False,bCtrl=True,bAlt=False,bCmd=False,Key=Four)
ActionMappings = (ActionName="StandGround",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=S)
ActionMappings = (ActionName="SwitchAbilityCommandLayer",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=SpaceBar)
ActionMappings = (ActionName="TheCheatToRuleThemAll",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=None)
ActionMappings = (ActionName="Toggle Gamepad",bShift=False,bCtrl=True,bAlt=True,bCmd=False,Key=Slash)
ActionMappings = (ActionName="UpArrow",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Up)
ActionMappings = (ActionName="ZoomIn",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=MouseScrollUp)
ActionMappings = (ActionName="ZoomOut",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=MouseScrollDown)
ActionMappings = (ActionName="UnitTypeSelectionModifier",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=LeftControl)
```
***Copy the above content and save the file as `Input.ini`.***
# Understand the Input.ini
You can notice a single line of this file can be broken down like this.
`ActionMappings=(ActionName="AttackMove",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=A)`
- `ActionMappings=(***)`: Indicates content is an action mapping. i.e. a hotkey
- `ActionName="AttackMove"`: Indicates the name of the selected action. Here, we are using the `AttackMove` move action,
that forces your selected army to attack.
- `Key=A`: Indicates key being mapped to the action. Set to `Key=Tab` to require the Tab key to be pressed instead, to
perform the `AttackMove` action.
- `bShift=False`: Indicates that the Shift key is not held. Set to `bShift=True` to require a shift key held to
perform the mapped action.
- `bCtrl=False`: Indicates that the Ctrl key is not held. Set to `bCtrl=True` to require a ctrl key held to perform
the mapped action.
- `bAlt=False`: Indicates that the Alt key is not held. Set to `bAlt=True` to require a alt key held to perform the
mapped action.
- `bCmd=False`: Indicates that the Cmd key is not held. Set to `bCmd=True` to require a cmd key held to perform the
mapped action. (Macs not supported by IGP)
# Modify the Input.ini file
You are now going to want to modify the file with your own hotkey setup.
To do this, replace any of the Key=`VALUE` mapped to the desired actions with any value from the list below.
*For example, you can swap the Z Key value in SelectUnitProductionBuildings to C. Which would look like
this:
`ActionMappings=(ActionName="SelectUnitProductionBuildings",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=C)`*
**Key Values**
- `AnyKey`
- `MouseX`
- `MouseY`
- `Mouse2D`
- `MouseScrollUp`
- `MouseScrollDown`
- `MouseWheelAxis`
- `LeftMouseButton`
- `RightMouseButton`
- `MiddleMouseButton`
- `ThumbMouseButton`
- `ThumbMouseButton2`
- `BackSpace`
- `Tab`
- `Enter`
- `Pause`
- `CapsLock`
- `Escape`
- `SpaceBar`
- `PageUp`
- `PageDown`
- `End`
- `Home`
- `Left`
- `Up`
- `Right`
- `Down`
- `Insert`
- `Delete`
- `Zero`
- `One`
- `Two`
- `Three`
- `Four`
- `Five`
- `Six`
- `Seven`
- `Eight`
- `Nine`
- `A`
- `B`
- `C`
- `D`
- `E`
- `F`
- `G`
- `H`
- `I`
- `J`
- `K`
- `L`
- `M`
- `N`
- `O`
- `P`
- `Q`
- `R`
- `S`
- `T`
- `U`
- `V`
- `W`
- `X`
- `Y`
- `Z`
- `NumPadZero`
- `NumPadOne`
- `NumPadTwo`
- `NumPadThree`
- `NumPadFour`
- `NumPadFive`
- `NumPadSix`
- `NumPadSeven`
- `NumPadEight`
- `NumPadNine`
- `Multiply`
- `Add`
- `Subtract`
- `Decimal`
- `Divide`
- `F1`
- `F2`
- `F3`
- `F4`
- `F5`
- `F6`
- `F7`
- `F8`
- `F9`
- `F10`
- `F11`
- `F12`
- `NumLock`
- `ScrollLock`
- `LeftShift`
- `RightShift`
- `LeftControl`
- `RightControl`
- `LeftAlt`
- `RightAlt`
- `LeftCommand`
- `RightCommand`
- `Semicolon`
- `Equals`
- `Comma`
- `Underscore`
- `Hyphen`
- `Period`
- `Slash`
- `Tilde`
- `LeftBracket`
- `Backslash`
- `RightBracket`
- `Apostrophe`
- `Ampersand`
- `Asterix`
- `Caret`
- `Colon`
- `Dollar`
- `Exclamation`
- `LeftParantheses`
- `RightParantheses`
- `Quote`
- `A_AccentGrave`
- `E_AccentGrave`
- `E_AccentAigu`
- `C_Cedille`
- `Section`
# Transfer the Input.ini file
Drag and drop this new `Input.ini` file into `WindowsClient` folder in IGP. This folder can be found
under `C:\Users\YOUR_USER\AppData\Local\Programs\CURRENT_IMMORTAL_CLIENT\Immortal\Saved\Config\WindowsClient\`. Remember
to use proper values for `YOUR_USER` and `CURRENT_IMMORTAL_CLIENT`.
There will be a blank `Input.ini` file in this folder. You can safely override it with your modified file.
# Testing
Restart the IGP client, and try out your new hotkey setup!
# Trouble Shooting
If running into trouble, ask for help in the `hotkeys` channel in the IGP Discord.
@@ -0,0 +1,69 @@
---
title: Armor Types
summary: Heavy, Medium, and Light. What does it mean?
created_date: 4/13/2022
updated_date: 4/13/2022
---
## Armor Types
All units in "IMMORTAL: Gates of Pyre" have one of the three armor types.
- Heavy
- Medium
- Light
These types mean nothing inherently. A Light armor unit will not take more damage than a Heavy armor unit, from the same
damage source.
Where this change is some units deal particular damage to a particular armor type.
For example, a Dervish deals 16 damage generally, 24 to medium, and 32 damage to heavy armor types. As an opposite
example, the Zephyr will deal 20 damage generally, 24 to medium and 28 to heavy armor types. And as a different example,
Sipari deals 20 damage generally, 18 damage to heavy armor.
Wait? There was no pattern to what general and specific damage is.
Ya, don't read too much into these damage labels, they are just what is most readable on a per-unit basis.
## So what do I do with this knowledge?
Let's assume you are playing Q'Rath, and your opponent is playing Aru.
They have two armies, one is a pack of Masked Hunters, and one is a pack of Xacals.
Masked Hunters have the light armor type and Xacals have the heavy armor type.
Well, you know Dervish deals the most damage to Light, and Zephyr deals the most damage to heavy, so you should
generally have your Dervish attack the Masked Hunters and the Zephyr's attack the Xacals.
But wait, your opponent moved their two armies together!
Defensively, Masked Hunters deal less damage to the heavy armor type, and Xacals do less damage to Light.
So while you want your Dervish to attack Masked Hunters, and the Zephyrs to attack the Xacals, you also want your
Dervish to defend against the Xacals and Zephyrs to defend against the Masked Hunters.
In mixed battles, you can spend some micro on your units to defend against enemy attackers they are strong against while
having them attack potentially different enemies.
So what should you do? Build some Sipari, a Light armor unit, to help tank some Xacal shots, to give your Dervish and
Zephyrs room to maneuver against their key targets.
## What else is there?
Some units have additional tags, such as Structure and Etheric. Units can deal particular, or BONUS damage against
enemies with those tags.
**What are structures?** Structures are any building. Pretty much anything not a part of your moving army.
As an example of bonus damage, the Absolver deals +8 damage to structures, giving it 29 DPS to heavy structures.
Conversely, it has 23 DPS against heavy structures when deployed, meaning Absolvers are better at sniping most
structures when mobilized.
Heads up though, that not all structures are heavy, examples include Radiant Wards generated by the Dervish.
**What are etherics?** Etherics are most caster units in the game. These casters can generally win long drawn-out
fights, so are good to snipe with units that deal extra damage against them.
An etheric example would be the Bloodbounds Culling Strike ability, which deals bonus damage against etheric.
@@ -0,0 +1,6 @@
---
title: Economy Overview
summary: Alloy, Ether and Supply. Don't forget to expand!
created_date: 1/01/2000
updated_date: 1/01/2000
---
@@ -0,0 +1,6 @@
---
title: Families, Factions, and Immortal Vanguards
summary: How IMMORTAL: Gates of Pyre handle your army selection choices
created_date: 1/01/2000
updated_date: 1/01/2000
---
@@ -0,0 +1,14 @@
---
title: Immortals Spells and Pyre
summary: Customize your hotkeys in the pre-alpha.
created_date: 1/01/2000
updated_date: 1/01/2000
---
## What are Immortals
## Immortal Spells
## Getting Pure
## Bringing it Together
@@ -0,0 +1,12 @@
---
title: Timing and Scouting
summary: Knowing is half the battle.
created_date: 1/01/2000
updated_date: 1/01/2000
---
## Timing
## Scouting
## Reacting

Some files were not shown because too many files have changed in this diff Show More