From 377a041afa8b259862b075bb8ef65884ac7fe45f Mon Sep 17 00:00:00 2001 From: 6d486f49 Date: Thu, 4 Jun 2026 11:12:23 -0400 Subject: [PATCH] ... --- Pages/Localizations.Designer.cs | 114 +++++ Pages/Localizations.resx | 68 +++ Pages/Pages.csproj | 42 ++ Pages/Pages/AboutPage.razor | 51 +++ Pages/Pages/BasePage.razor | 37 ++ .../BuildCalculator/BuildCalculatorPage.razor | 295 +++++++++++++ .../BuildCalculator/Parts/ArmyComponent.razor | 156 +++++++ .../BuildCalculator/Parts/BankComponent.razor | 134 ++++++ .../Parts/BuildChartComponent.razor | 331 +++++++++++++++ .../Parts/BuildOrderComponent.razor | 47 ++ .../Cosmetic/FactionBorderComponent.razor | 32 ++ .../Cosmetic/ImmortalBorderComponent.razor | 37 ++ .../Parts/EntityClickViewComponent.razor | 81 ++++ .../Parts/FilterComponent.razor | 80 ++++ .../Parts/HighlightsComponent.razor | 89 ++++ .../Parts/HotkeyViewerComponent.razor | 400 ++++++++++++++++++ .../Parts/InputPanelComponent.razor | 52 +++ .../Parts/OptionsComponent.razor | 124 ++++++ .../Parts/TimelineComponent.razor | 88 ++++ .../Parts/TimingComponent.razor | 105 +++++ .../Parts/TutorialHelperComponent.razor | 82 ++++ Pages/Pages/ContactPage.razor | 21 + Pages/Pages/DataCollectionPage.razor | 23 + Pages/Pages/DataTables/DataTablesPage.razor | 46 ++ .../DataTables/Parts/MovementTable.razor | 21 + .../DataTables/Parts/ProductionTable.razor | 22 + .../DataTables/Parts/VitalityTable.razor | 25 ++ .../Pages/DataTables/Parts/WeaponTable.razor | 31 ++ Pages/Pages/Database/DatabasePage.razor | 260 ++++++++++++ Pages/Pages/Database/DatabaseSinglePage.razor | 99 +++++ .../Database/Entity/EntityViewComponent.razor | 56 +++ .../Parts/EntityAbilitiesComponent.razor | 134 ++++++ .../Entity/Parts/EntityHeaderComponent.razor | 72 ++++ .../Entity/Parts/EntityInfoComponent.razor | 134 ++++++ .../Parts/EntityMechanicsComponent.razor | 30 ++ .../Parts/EntityPassivesComponent.razor | 139 ++++++ .../Parts/EntityProductionComponent.razor | 225 ++++++++++ .../Parts/EntityPyreSpellsComponent.razor | 98 +++++ .../Entity/Parts/EntityStatsComponent.razor | 180 ++++++++ .../Parts/EntityUpgradesComponent.razor | 63 +++ .../Parts/EntityVanguardAddedComponent.razor | 49 +++ .../Parts/EntityVanguardsComponent.razor | 71 ++++ .../Entity/Parts/EntityWeaponsComponent.razor | 263 ++++++++++++ .../Parts/EntityFilterComponent.razor | 264 ++++++++++++ .../Parts/EntityFilterComponent.razor.css | 106 +++++ .../EconomyComparisonPage.razor | 56 +++ .../Parts/ChartComponent.razor | 200 +++++++++ .../Parts/EconomyDifferenceComponent.razor | 127 ++++++ .../Parts/EconomyInputComponent.razor | 85 ++++ Pages/Pages/Glossary/GlossaryDetailPage.razor | 97 +++++ Pages/Pages/Glossary/GlossaryPage.razor | 177 ++++++++ Pages/Pages/HarassCalculatorPage.razor | 392 +++++++++++++++++ Pages/Pages/Home/HomePage.razor | 64 +++ .../Parts/ContentHighlightComponent.razor | 74 ++++ .../Pages/MemoryTester/MemoryTesterPage.razor | 47 ++ .../Pages/MemoryTester/Parts/UnitMemory.razor | 129 ++++++ .../Parts/UnitMemoryManager.razor | 104 +++++ Pages/Pages/Notes/NotesIndexPage.razor | 144 +++++++ Pages/Pages/Notes/NotesPage.razor | 120 ++++++ Pages/Pages/Notes/Parts/NoteComponent.razor | 78 ++++ .../Notes/Parts/NoteInnerNavComponent.razor | 71 ++++ .../Pages/Notes/Parts/NoteNavComponent.razor | 42 ++ Pages/Pages/PermissionsPage.razor | 131 ++++++ Pages/Pages/RawDatabase.razor | 15 + Pages/Pages/RawDatabase.razor.css | 15 + Pages/Pages/StoragePage.razor | 247 +++++++++++ Pages/Pages/StreamsPage.razor | 49 +++ Pages/Pages/TechTree/TechTreePage.razor | 222 ++++++++++ Pages/Utils/Interval.cs | 9 + Pages/Utils/Markdown.cs | 52 +++ Pages/Utils/Project.cs | 7 + Pages/_Imports.razor | 56 +++ Web/App.razor | 3 +- 73 files changed, 7689 insertions(+), 1 deletion(-) create mode 100644 Pages/Localizations.Designer.cs create mode 100644 Pages/Localizations.resx create mode 100644 Pages/Pages.csproj create mode 100644 Pages/Pages/AboutPage.razor create mode 100644 Pages/Pages/BasePage.razor create mode 100644 Pages/Pages/BuildCalculator/BuildCalculatorPage.razor create mode 100644 Pages/Pages/BuildCalculator/Parts/ArmyComponent.razor create mode 100644 Pages/Pages/BuildCalculator/Parts/BankComponent.razor create mode 100644 Pages/Pages/BuildCalculator/Parts/BuildChartComponent.razor create mode 100644 Pages/Pages/BuildCalculator/Parts/BuildOrderComponent.razor create mode 100644 Pages/Pages/BuildCalculator/Parts/Cosmetic/FactionBorderComponent.razor create mode 100644 Pages/Pages/BuildCalculator/Parts/Cosmetic/ImmortalBorderComponent.razor create mode 100644 Pages/Pages/BuildCalculator/Parts/EntityClickViewComponent.razor create mode 100644 Pages/Pages/BuildCalculator/Parts/FilterComponent.razor create mode 100644 Pages/Pages/BuildCalculator/Parts/HighlightsComponent.razor create mode 100644 Pages/Pages/BuildCalculator/Parts/HotkeyViewerComponent.razor create mode 100644 Pages/Pages/BuildCalculator/Parts/InputPanelComponent.razor create mode 100644 Pages/Pages/BuildCalculator/Parts/OptionsComponent.razor create mode 100644 Pages/Pages/BuildCalculator/Parts/TimelineComponent.razor create mode 100644 Pages/Pages/BuildCalculator/Parts/TimingComponent.razor create mode 100644 Pages/Pages/BuildCalculator/Parts/TutorialHelperComponent.razor create mode 100644 Pages/Pages/ContactPage.razor create mode 100644 Pages/Pages/DataCollectionPage.razor create mode 100644 Pages/Pages/DataTables/DataTablesPage.razor create mode 100644 Pages/Pages/DataTables/Parts/MovementTable.razor create mode 100644 Pages/Pages/DataTables/Parts/ProductionTable.razor create mode 100644 Pages/Pages/DataTables/Parts/VitalityTable.razor create mode 100644 Pages/Pages/DataTables/Parts/WeaponTable.razor create mode 100644 Pages/Pages/Database/DatabasePage.razor create mode 100644 Pages/Pages/Database/DatabaseSinglePage.razor create mode 100644 Pages/Pages/Database/Entity/EntityViewComponent.razor create mode 100644 Pages/Pages/Database/Entity/Parts/EntityAbilitiesComponent.razor create mode 100644 Pages/Pages/Database/Entity/Parts/EntityHeaderComponent.razor create mode 100644 Pages/Pages/Database/Entity/Parts/EntityInfoComponent.razor create mode 100644 Pages/Pages/Database/Entity/Parts/EntityMechanicsComponent.razor create mode 100644 Pages/Pages/Database/Entity/Parts/EntityPassivesComponent.razor create mode 100644 Pages/Pages/Database/Entity/Parts/EntityProductionComponent.razor create mode 100644 Pages/Pages/Database/Entity/Parts/EntityPyreSpellsComponent.razor create mode 100644 Pages/Pages/Database/Entity/Parts/EntityStatsComponent.razor create mode 100644 Pages/Pages/Database/Entity/Parts/EntityUpgradesComponent.razor create mode 100644 Pages/Pages/Database/Entity/Parts/EntityVanguardAddedComponent.razor create mode 100644 Pages/Pages/Database/Entity/Parts/EntityVanguardsComponent.razor create mode 100644 Pages/Pages/Database/Entity/Parts/EntityWeaponsComponent.razor create mode 100644 Pages/Pages/Database/Parts/EntityFilterComponent.razor create mode 100644 Pages/Pages/Database/Parts/EntityFilterComponent.razor.css create mode 100644 Pages/Pages/EconomyComparison/EconomyComparisonPage.razor create mode 100644 Pages/Pages/EconomyComparison/Parts/ChartComponent.razor create mode 100644 Pages/Pages/EconomyComparison/Parts/EconomyDifferenceComponent.razor create mode 100644 Pages/Pages/EconomyComparison/Parts/EconomyInputComponent.razor create mode 100644 Pages/Pages/Glossary/GlossaryDetailPage.razor create mode 100644 Pages/Pages/Glossary/GlossaryPage.razor create mode 100644 Pages/Pages/HarassCalculatorPage.razor create mode 100644 Pages/Pages/Home/HomePage.razor create mode 100644 Pages/Pages/Home/Parts/ContentHighlightComponent.razor create mode 100644 Pages/Pages/MemoryTester/MemoryTesterPage.razor create mode 100644 Pages/Pages/MemoryTester/Parts/UnitMemory.razor create mode 100644 Pages/Pages/MemoryTester/Parts/UnitMemoryManager.razor create mode 100644 Pages/Pages/Notes/NotesIndexPage.razor create mode 100644 Pages/Pages/Notes/NotesPage.razor create mode 100644 Pages/Pages/Notes/Parts/NoteComponent.razor create mode 100644 Pages/Pages/Notes/Parts/NoteInnerNavComponent.razor create mode 100644 Pages/Pages/Notes/Parts/NoteNavComponent.razor create mode 100644 Pages/Pages/PermissionsPage.razor create mode 100644 Pages/Pages/RawDatabase.razor create mode 100644 Pages/Pages/RawDatabase.razor.css create mode 100644 Pages/Pages/StoragePage.razor create mode 100644 Pages/Pages/StreamsPage.razor create mode 100644 Pages/Pages/TechTree/TechTreePage.razor create mode 100644 Pages/Utils/Interval.cs create mode 100644 Pages/Utils/Markdown.cs create mode 100644 Pages/Utils/Project.cs create mode 100644 Pages/_Imports.razor diff --git a/Pages/Localizations.Designer.cs b/Pages/Localizations.Designer.cs new file mode 100644 index 0000000..4d28fc6 --- /dev/null +++ b/Pages/Localizations.Designer.cs @@ -0,0 +1,114 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +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); + } + } + } +} diff --git a/Pages/Localizations.resx b/Pages/Localizations.resx new file mode 100644 index 0000000..2ebc420 --- /dev/null +++ b/Pages/Localizations.resx @@ -0,0 +1,68 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + + Hello + + + + Shows economy at each game interval. Use to determine if spending additional resources on harvesters will help or hinder overall timing attack. + + + + Select build details, such as Faction and Immortal. + +Affects entities you can build. + + + Summary of the entity you just selected. + + + Bank at time of last requested action. Use this section to determine if your build is floating too much alloy or ether. + + + Overview of current army, and when it will be ready to begin an attack. + + + Timeline highlights of your build order. Shows when you start a new action and when the action is done. + + + Some raw JSON data to represent your build order. + + + Enter build details. + + + Click on the desired entity to build it. <i>You cannot build entities you cannot afford, construct an ether extractor before spending ether.</i> + +You can also use the default Immortal hotkeys, but the above hotkey UI must have focus for this to work. <i>I.e. click on it if hotkeys aren't working, and a white border should appear after key input to indicate focus.</i> + +Additionally, more entities will appear as you build the required technology. You can click or press ` to remove the last made entity. <i>But you cannot remove the starting entities at interval 0.</i> + + + Misc calculator controls. + + \ No newline at end of file diff --git a/Pages/Pages.csproj b/Pages/Pages.csproj new file mode 100644 index 0000000..9c9cbef --- /dev/null +++ b/Pages/Pages.csproj @@ -0,0 +1,42 @@ + + + + net10.0 + enable + enable + IGP + + + + + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Localizations.Designer.cs + + + + + + True + True + Localizations.resx + + + + diff --git a/Pages/Pages/AboutPage.razor b/Pages/Pages/AboutPage.razor new file mode 100644 index 0000000..a46750c --- /dev/null +++ b/Pages/Pages/AboutPage.razor @@ -0,0 +1,51 @@ + +@inherits BasePage + +@inject IDataCollectionService DataCollectionService + + +@page "/about" + + + About + + + + + What is this website for? + + + 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. + + + + + So what is this specific tool for? + + + 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. + + + + + The game hasn't even been released. Isn't it a bit early for publishing community tools? + + + Maybe ?? + + + + + Any disclaimers? + + + 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. + + + + \ No newline at end of file diff --git a/Pages/Pages/BasePage.razor b/Pages/Pages/BasePage.razor new file mode 100644 index 0000000..5608935 --- /dev/null +++ b/Pages/Pages/BasePage.razor @@ -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 { { "page", rootUrl } }; + if (splitData.Length > 1) + { + eventData["inner-page"] = splitData.Last(); + } + + DataCollectionService.SendEvent(DataCollectionKeys.PageInitialized, eventData); + } + +} \ No newline at end of file diff --git a/Pages/Pages/BuildCalculator/BuildCalculatorPage.razor b/Pages/Pages/BuildCalculator/BuildCalculatorPage.razor new file mode 100644 index 0000000..a33d47d --- /dev/null +++ b/Pages/Pages/BuildCalculator/BuildCalculatorPage.razor @@ -0,0 +1,295 @@ + +@inherits BasePage + +@inject IStringLocalizer 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 IGP.Pages.BuildCalculator.Parts.Cosmetic +@using Services.Website +@implements IDisposable + + + Build Calculator + + + +
+
+ + + + + + + + + Clear Build Order + + + + + + + + + + + + +
+ + +
+ + + + + +
+ +
+ + + + + + + + +
+ +
+ + + + + +
+ +
+ + + + + +
+ +
+ + + + + +
+ +
+ + + + + +
+ +
+ + + + + +
+
+ + + + + + + + What is this tool? + + + 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. + + + + + + How does it work? + + + The tool calculates every second of game time. So if you attempt to build a Legion Hall 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. +
+
+ If you then build 2 Apostle of Bindings a Soul Foundry and a 3 Absolvers you + should see yourself roughly floating 500 alloy, with barely having any ether. Which means you could + of gotten an Acropolis and a Zentari without hurting your build. +
+
+ Try building Apostle of Bindings before the Legion Hall and see how that changes the + timing of your 3 Absolvers. (Spoiler: + your Absolvers will be built much faster, and you won't be floating so + much alloy. + + ) +
+
+ + + + What is CONTROL key for? + + + Economy and tech related upgrades for townhalls. + + + + + + What is SHIFT key for? + + + Misc building related upgrades. (Omnivores) + + + + + + What is 2 key for? + + + It will be for Pyre camps. Currently not implemented. + + +
+
+
+ + + + + +@code { + + protected override void OnInitialized() + { + base.OnInitialized(); + + EconomyService.Calculate(BuildOrderService, TimingService, 0); + + KeyService.Subscribe(HandleClick); + + DataCollectionService.SendEvent( + DataCollectionKeys.PageInitialized, + new Dictionary { { "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()); + } + } + +} \ No newline at end of file diff --git a/Pages/Pages/BuildCalculator/Parts/ArmyComponent.razor b/Pages/Pages/BuildCalculator/Parts/ArmyComponent.razor new file mode 100644 index 0000000..95c515f --- /dev/null +++ b/Pages/Pages/BuildCalculator/Parts/ArmyComponent.razor @@ -0,0 +1,156 @@ +@inject IJSRuntime jsRuntime + +@inject IBuildOrderService buildOrder +@inject ITimingService timingService + +@implements IDisposable + +
+ + +
+ + @lastInterval | T @Interval.ToTime(lastInterval) + + + @(lastInterval + timingService.GetTravelTime()) | + T @Interval.ToTime(lastInterval + timingService.GetTravelTime()) + +
+ + +
+ @foreach (var unit in armyCount) + { +
+
+
@unit.Value.ToString()x
+
+
@unit.Key
+
+ } +
+
+
+
+ + +
+ + + +@code { + private int lastInterval; + + readonly Dictionary armyCount = new(); + + List 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(); + } + } + +} \ No newline at end of file diff --git a/Pages/Pages/BuildCalculator/Parts/BankComponent.razor b/Pages/Pages/BuildCalculator/Parts/BankComponent.razor new file mode 100644 index 0000000..2f50693 --- /dev/null +++ b/Pages/Pages/BuildCalculator/Parts/BankComponent.razor @@ -0,0 +1,134 @@ +@inject IJSRuntime jsRuntime; + +@inject IEconomyService economyService + + +@implements IDisposable + +
+ + @(BuildOrderService.GetLastRequestInterval() + 1) | + T @Interval.ToTime(BuildOrderService.GetLastRequestInterval() + 1) + +
+ + @_economy.Alloy +@_economy.AlloyIncome + + + @Math.Round(_economy.Ether) +@Math.Round(_economy.EtherIncome) + +
+
+ + @_economy.Pyre + + + @_supplyTaken / @_supplyGranted (@(_supplyGranted / 16)@(_extraBuildings > 0 ? "+" + _extraBuildings : "")) + +
+ +
+
Workers
+
+ + @_economy.WorkerCount + + + @_economy.BusyWorkerCount + + + @_economy.CreatingWorkerCount + +
+
+
+ + + +@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(); + } + +} \ No newline at end of file diff --git a/Pages/Pages/BuildCalculator/Parts/BuildChartComponent.razor b/Pages/Pages/BuildCalculator/Parts/BuildChartComponent.razor new file mode 100644 index 0000000..2669314 --- /dev/null +++ b/Pages/Pages/BuildCalculator/Parts/BuildChartComponent.razor @@ -0,0 +1,331 @@ +@inject IEconomyService EconomyService +@inject IBuildOrderService BuildOrderService +@inject ITimingService TimingService +@inject IJSRuntime JsRuntime; +@implements IDisposable + +@if (lastRequestedRefreshIndex != requestedRefreshIndex) +{ + +} +else +{ +
+ @foreach (var chart in charts) + { + var takenPixels = new Dictionary(); + +
+
+ @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); + +
+
+
+
+ } +
+
+ } +
+ + + + + + + @highestAlloyPoint + + + @highestEtherPoint + + + + @highestEtherPoint + + + + @highestArmyPoint + + +} + +@code { + + private readonly int width = 250; + + List valueList = new(); + + readonly List 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 + where harvester.Harvest().Resource == ResourceType.Alloy + select harvester; + + var etherAutomaticHarvesters = from harvester in economyAtSecond.HarvestPoints + where harvester.Harvest() != null + where !harvester.Harvest().RequiresWorker + 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(); + } + +} \ No newline at end of file diff --git a/Pages/Pages/BuildCalculator/Parts/BuildOrderComponent.razor b/Pages/Pages/BuildCalculator/Parts/BuildOrderComponent.razor new file mode 100644 index 0000000..81eca91 --- /dev/null +++ b/Pages/Pages/BuildCalculator/Parts/BuildOrderComponent.razor @@ -0,0 +1,47 @@ +@inject IJSRuntime jsRuntime; + +@inject IBuildOrderService buildOrderService + +@implements IDisposable + + + + + +@code { + + /** + * // TODO: Make this more elegant, and useful. Also, it currently doesn't clear properly + * + * + */ + 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 + } + +} \ No newline at end of file diff --git a/Pages/Pages/BuildCalculator/Parts/Cosmetic/FactionBorderComponent.razor b/Pages/Pages/BuildCalculator/Parts/Cosmetic/FactionBorderComponent.razor new file mode 100644 index 0000000..301fcfa --- /dev/null +++ b/Pages/Pages/BuildCalculator/Parts/Cosmetic/FactionBorderComponent.razor @@ -0,0 +1,32 @@ +@inject IImmortalSelectionService FilterService +@implements IDisposable + + +
+ @ChildContent +
+
+ +@code { + + [Parameter] public RenderFragment? ChildContent { get; set; } + + protected override void OnInitialized() + { + base.OnInitialized(); + FilterService.Subscribe(StateHasChanged); + } + + void IDisposable.Dispose() + { + FilterService.Unsubscribe(StateHasChanged); + } + + string GetBorderStyle() + { + var faction = FilterService.GetFaction(); + var color = faction == DataType.FACTION_Aru ? "var(--faction-aru)" : "var(--faction-qrath)"; + return $"border-top: 4px solid {color}; padding-top: 14px; margin-top: -12px;"; + } + +} diff --git a/Pages/Pages/BuildCalculator/Parts/Cosmetic/ImmortalBorderComponent.razor b/Pages/Pages/BuildCalculator/Parts/Cosmetic/ImmortalBorderComponent.razor new file mode 100644 index 0000000..4f5f4a8 --- /dev/null +++ b/Pages/Pages/BuildCalculator/Parts/Cosmetic/ImmortalBorderComponent.razor @@ -0,0 +1,37 @@ +@inject IImmortalSelectionService FilterService +@implements IDisposable + + +
+ @ChildContent +
+
+ +@code { + + [Parameter] public RenderFragment? ChildContent { get; set; } + + protected override void OnInitialized() + { + base.OnInitialized(); + FilterService.Subscribe(StateHasChanged); + } + + void IDisposable.Dispose() + { + FilterService.Unsubscribe(StateHasChanged); + } + + string GetBorderStyle() + { + var immortal = FilterService.GetImmortal(); + var color = "#666666"; + if (immortal == DataType.IMMORTAL_Orzum) color = "var(--immortal-orzum)"; + else if (immortal == DataType.IMMORTAL_Ajari) color = "var(--immortal-ajari)"; + else if (immortal == DataType.IMMORTAL_Atzlan) color = "var(--immortal-atzlan)"; + else if (immortal == DataType.IMMORTAL_Mala) color = "var(--immortal-mala)"; + else if (immortal == DataType.IMMORTAL_Xol) color = "var(--immortal-xol)"; + return $"border-top: 4px solid {color}; padding-top: 14px; margin-top: -12px;"; + } + +} diff --git a/Pages/Pages/BuildCalculator/Parts/EntityClickViewComponent.razor b/Pages/Pages/BuildCalculator/Parts/EntityClickViewComponent.razor new file mode 100644 index 0000000..3275e84 --- /dev/null +++ b/Pages/Pages/BuildCalculator/Parts/EntityClickViewComponent.razor @@ -0,0 +1,81 @@ +@inject IJSRuntime JsRuntime +@inject IKeyService KeyService +@inject IImmortalSelectionService FilterService +@inject IStorageService StorageService +@inject IBuildOrderService BuildOrderService +@using Services.Website +@implements IDisposable + +@if (_entity != null) +{ +
+ + + + + +
+} + + +@code { + private EntityModel? _entity; + private string _viewType = EntityViewType.Detailed; + + protected override void OnInitialized() + { + base.OnInitialized(); + KeyService.Subscribe(HandleClick); + StorageService.Subscribe(RefreshDefaults); + BuildOrderService.Subscribe(OnBuildOrderServiceChanged); + + RefreshDefaults(); + } + + void IDisposable.Dispose() + { + KeyService.Unsubscribe(HandleClick); + StorageService.Unsubscribe(RefreshDefaults); + BuildOrderService.Unsubscribe(OnBuildOrderServiceChanged); + } + + + void OnBuildOrderServiceChanged() + { + if (BuildOrderService.GetLastRequestInterval() == 0) + { + _entity = null; + StateHasChanged(); + } + } + + void RefreshDefaults() + { + _viewType = StorageService.GetValue(StorageKeys.IsPlainView) ? EntityViewType.Plain : EntityViewType.Detailed; + } + + 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(); + } + } + +} \ No newline at end of file diff --git a/Pages/Pages/BuildCalculator/Parts/FilterComponent.razor b/Pages/Pages/BuildCalculator/Parts/FilterComponent.razor new file mode 100644 index 0000000..d212cdd --- /dev/null +++ b/Pages/Pages/BuildCalculator/Parts/FilterComponent.razor @@ -0,0 +1,80 @@ +@inject IJSRuntime JsRuntime; +@inject IImmortalSelectionService FilterService + + + + Faction + + + + + + + + Immortal + + @if (FilterService.GetFaction() == DataType.FACTION_QRath) + { + + + } + @if (FilterService.GetFaction() == DataType.FACTION_Aru) + { + + + + } + + + + +@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 + } + +} \ No newline at end of file diff --git a/Pages/Pages/BuildCalculator/Parts/HighlightsComponent.razor b/Pages/Pages/BuildCalculator/Parts/HighlightsComponent.razor new file mode 100644 index 0000000..be65a80 --- /dev/null +++ b/Pages/Pages/BuildCalculator/Parts/HighlightsComponent.razor @@ -0,0 +1,89 @@ +@inject IJSRuntime jsRuntime; +@inject IEconomyService economyService +@inject IBuildOrderService buildOrderService +@inject ITimingService timingService + +@implements IDisposable + +
+
+
Requested
+ + @foreach (var ordersAtTime in buildOrderService.StartedOrders.Reverse()) + { + foreach (var order in ordersAtTime.Value) + { +
+ @ordersAtTime.Key | T @Interval.ToTime(ordersAtTime.Key) +
+
+ @order.Info().Name +
+
+ } + } + +
+
+
Finished
+ + @foreach (var ordersAtTime in buildOrderService.CompletedOrders.Reverse()) + { + foreach (var order in ordersAtTime.Value) + { +
+ + @ordersAtTime.Key | T @Interval.ToTime(ordersAtTime.Key) +
+
+ @order.Info().Name +
+
+ } + } +
+
+ + + +@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 + } + +} \ No newline at end of file diff --git a/Pages/Pages/BuildCalculator/Parts/HotkeyViewerComponent.razor b/Pages/Pages/BuildCalculator/Parts/HotkeyViewerComponent.razor new file mode 100644 index 0000000..5d266fb --- /dev/null +++ b/Pages/Pages/BuildCalculator/Parts/HotkeyViewerComponent.razor @@ -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 + + + +
+ @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; + +
+ +
+ @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;"; + } + + +
@entity.Info()?.Name
+ } +
+
+ } +
+
+ + + +@code { + + [Parameter] public int Size { get; set; } = 100; + + readonly Dictionary data = EntityModel.GetDictionary(); + readonly List 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 { { "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); + } + } + +} \ No newline at end of file diff --git a/Pages/Pages/BuildCalculator/Parts/InputPanelComponent.razor b/Pages/Pages/BuildCalculator/Parts/InputPanelComponent.razor new file mode 100644 index 0000000..7f24342 --- /dev/null +++ b/Pages/Pages/BuildCalculator/Parts/InputPanelComponent.razor @@ -0,0 +1,52 @@ +@using Services.Website +@inject IKeyService KeyService + +@inject IDataCollectionService DataCollectionService +@inject IJSRuntime JsRuntime + + +
+ @ChildContent +
+ +@code { + + [Parameter] public RenderFragment ChildContent { get; set; } = default!; + + private void HandleKeyDown(KeyboardEventArgs e) + { + DataCollectionService.SendEvent( + DataCollectionKeys.BuildCalcInput, + new Dictionary { { "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 + } + +} \ No newline at end of file diff --git a/Pages/Pages/BuildCalculator/Parts/OptionsComponent.razor b/Pages/Pages/BuildCalculator/Parts/OptionsComponent.razor new file mode 100644 index 0000000..d0c24b3 --- /dev/null +++ b/Pages/Pages/BuildCalculator/Parts/OptionsComponent.razor @@ -0,0 +1,124 @@ +@inject IJSRuntime JsRuntime; + +@inject IBuildOrderService BuildOrderService +@inject IEconomyService EconomyService +@inject IToastService ToastService +@inject ITimingService TimingService +@implements IDisposable + + + + Building Input Delay + Add a input delay to constructing buildings for simulating worker movement and player + micro. + + +
+ + + Wait Time + + Add Wait + + + + Wait To + + Add Wait + +
+
+ + + +@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 + } + +} \ No newline at end of file diff --git a/Pages/Pages/BuildCalculator/Parts/TimelineComponent.razor b/Pages/Pages/BuildCalculator/Parts/TimelineComponent.razor new file mode 100644 index 0000000..825d7f3 --- /dev/null +++ b/Pages/Pages/BuildCalculator/Parts/TimelineComponent.razor @@ -0,0 +1,88 @@ +@inject IJSRuntime jsRuntime +@inject IEconomyService economyService +@inject IBuildOrderService buildOrderService + +@implements IDisposable + + +
+
+
+ @economyAtSecond.Interval +
+
+ T @Interval.ToTime(economyAtSecond.Interval) | A @economyAtSecond.Alloy | E @economyAtSecond.Ether +
+
+ Worker Count: @(economyAtSecond.WorkerCount) +
+
+ Free Worker Count: @(economyAtSecond.WorkerCount - economyAtSecond.BusyWorkerCount) +
+
+ Busy Worker Count: @economyAtSecond.BusyWorkerCount +
+
+ Creating Worker Count: @economyAtSecond.CreatingWorkerCount +
+
+
+
+ + @if (buildOrderService.StartedOrders.TryGetValue(economyAtSecond.Interval, out var ordersAtTime)) + { + @foreach (var order in ordersAtTime) + { +
+ Requested: @order.Info().Name +
+ } + } + + + @if (buildOrderService.CompletedOrders.TryGetValue(economyAtSecond.Interval, out var ordersCompletedAtTime)) + { + @foreach (var order in ordersCompletedAtTime) + { +
+ New: @order.Info().Name +
+ } + } +
+
+
+ + +@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 + } + +} \ No newline at end of file diff --git a/Pages/Pages/BuildCalculator/Parts/TimingComponent.razor b/Pages/Pages/BuildCalculator/Parts/TimingComponent.razor new file mode 100644 index 0000000..7dde2bb --- /dev/null +++ b/Pages/Pages/BuildCalculator/Parts/TimingComponent.razor @@ -0,0 +1,105 @@ +@inject IJSRuntime jsRuntime; + +@inject IBuildOrderService buildOrderService +@inject IEconomyService economyService +@inject IToastService toastService +@inject ITimingService timingService +@implements IDisposable + + + + Attack Time + +   T @Interval.ToTime(timingService.GetAttackTime()) + + + + + Travel Time + +   T @Interval.ToTime(timingService.GetTravelTime()) + + + + + +@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 + } + +} \ No newline at end of file diff --git a/Pages/Pages/BuildCalculator/Parts/TutorialHelperComponent.razor b/Pages/Pages/BuildCalculator/Parts/TutorialHelperComponent.razor new file mode 100644 index 0000000..5d6227d --- /dev/null +++ b/Pages/Pages/BuildCalculator/Parts/TutorialHelperComponent.razor @@ -0,0 +1,82 @@ +@inject IImmortalSelectionService FilterService + +@implements IDisposable + + + + + What is this tool? + + + 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. + + + + + + How does it work? + + + The tool calculates every second of game time. So if you attempt to build a Legion Hall 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. +
+
+ If you then build 2 Apostle of Bindings a Soul Foundry and a 3 Absolvers you + should see yourself roughly floating 500 alloy, with barely having any ether. Which means you could + of gotten an Acropolis and a Zentari without hurting your build. +
+
+ Try building Apostle of Bindings before the Legion Hall and see how that changes the + timing of your 3 Absolvers. (Spoiler: + your Absolvers will be built much faster, and you won't be floating so + much alloy. + + ) +
+
+ + + + What is CONTROL key for? + + + Economy and tech related upgrades for townhalls. + + + + + + What is SHIFT key for? + + + Misc building related upgrades. (Omnivores) + + + + + + What is 2 key for? + + + It will be for Pyre camps. Currently not implemented. + + +
+ +@code { + + protected override void OnInitialized() + { + base.OnInitialized(); + FilterService.Subscribe(StateHasChanged); + } + + void IDisposable.Dispose() + { + FilterService.Unsubscribe(StateHasChanged); + } + + +} \ No newline at end of file diff --git a/Pages/Pages/ContactPage.razor b/Pages/Pages/ContactPage.razor new file mode 100644 index 0000000..0e25ccd --- /dev/null +++ b/Pages/Pages/ContactPage.razor @@ -0,0 +1,21 @@ + +@inject IDataCollectionService DataCollectionService +@inherits BasePage + +@page "/contact" + + + Contact + + + + + How do I contact you for feature requests and bug reports? + + + You can message me at JonathanMcCaffrey#3544 on my Discord channel. + + + + \ No newline at end of file diff --git a/Pages/Pages/DataCollectionPage.razor b/Pages/Pages/DataCollectionPage.razor new file mode 100644 index 0000000..060761b --- /dev/null +++ b/Pages/Pages/DataCollectionPage.razor @@ -0,0 +1,23 @@ +@page "/data-collection" + +@inject IDataCollectionService DataCollectionService +@inherits BasePage + + + + + + Not Implemented + + + + + TODO + + + + + + + + \ No newline at end of file diff --git a/Pages/Pages/DataTables/DataTablesPage.razor b/Pages/Pages/DataTables/DataTablesPage.razor new file mode 100644 index 0000000..0581e54 --- /dev/null +++ b/Pages/Pages/DataTables/DataTablesPage.razor @@ -0,0 +1,46 @@ +@using IGP.Pages.DataTables.Parts +@inherits BasePage + +@page "/data-tables" + + + Data Tables + + + + + + + + + + + + + + + + + + + + + What is this tool? + + + 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. + + + + + + + +@code { + + +} \ No newline at end of file diff --git a/Pages/Pages/DataTables/Parts/MovementTable.razor b/Pages/Pages/DataTables/Parts/MovementTable.razor new file mode 100644 index 0000000..04e28ab --- /dev/null +++ b/Pages/Pages/DataTables/Parts/MovementTable.razor @@ -0,0 +1,21 @@ + + + + + + + + + + +@code { + + readonly IEnumerable _data = EntityData.Get() + .SelectMany(e => e.Value.EntityParts) + .OfType() + .ToList(); + +} \ No newline at end of file diff --git a/Pages/Pages/DataTables/Parts/ProductionTable.razor b/Pages/Pages/DataTables/Parts/ProductionTable.razor new file mode 100644 index 0000000..3014fc6 --- /dev/null +++ b/Pages/Pages/DataTables/Parts/ProductionTable.razor @@ -0,0 +1,22 @@ + + + + + + + + + + + +@code { + + readonly IEnumerable data = EntityData.Get() + .SelectMany(e => e.Value.EntityParts) + .OfType() + .ToList(); + +} \ No newline at end of file diff --git a/Pages/Pages/DataTables/Parts/VitalityTable.razor b/Pages/Pages/DataTables/Parts/VitalityTable.razor new file mode 100644 index 0000000..6bc38d4 --- /dev/null +++ b/Pages/Pages/DataTables/Parts/VitalityTable.razor @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + +@code { + + readonly IEnumerable _data = EntityData.Get() + .SelectMany(e => e.Value.EntityParts) + .OfType() + .ToList(); + +} \ No newline at end of file diff --git a/Pages/Pages/DataTables/Parts/WeaponTable.razor b/Pages/Pages/DataTables/Parts/WeaponTable.razor new file mode 100644 index 0000000..fc106a7 --- /dev/null +++ b/Pages/Pages/DataTables/Parts/WeaponTable.razor @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + +@code { + + readonly IEnumerable _data = EntityData.Get() + .SelectMany(e => e.Value.EntityParts) + .OfType() + .ToList(); + +} \ No newline at end of file diff --git a/Pages/Pages/Database/DatabasePage.razor b/Pages/Pages/Database/DatabasePage.razor new file mode 100644 index 0000000..78be906 --- /dev/null +++ b/Pages/Pages/Database/DatabasePage.razor @@ -0,0 +1,260 @@ +@inherits BasePage +@page "/database" +@using Model +@implements IDisposable + +@inject IEntityDisplayService EntityDisplayService + + + + Database + + + + + Game Patch: @Variables.GamePatch + + + + +
+ +
+ + + + + @if (searches != null) + { +
+ @foreach (var entity in searches) + { + + + + + + } +
+ } +
+ + + + + + + + What is this tool? + + + This is a reference database. Mostly so unit stats can be reviewed outside of the game, IMMORTAL: Gates + of Pyre. + + + + + + Is this database complete? + + + 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. + + + + + + Is this database updated to the latest version? + + + Maybe. Check this @Variables.GamePatch version number, and compare it to the + number on discord, in the #game-updates channel. That should give a general sense of how out of + date the data is. + + + + + + This database has some errors in it. + + + Yup. The content is being transferred by hand, so some mistakes are expected. + + + +
+ + + + +@code { + + [Inject] public IEntityFilterService EntityFilterService { get; set; } = default!; + + readonly List defaults = (from entity in EntityModel.GetList() + where !entity.IsSpeculative + select entity).ToList(); + + List factions = default!; + List immortals = default!; + List entities = default!; + List 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(); + } + +} \ No newline at end of file diff --git a/Pages/Pages/Database/DatabaseSinglePage.razor b/Pages/Pages/Database/DatabaseSinglePage.razor new file mode 100644 index 0000000..28b66b7 --- /dev/null +++ b/Pages/Pages/Database/DatabaseSinglePage.razor @@ -0,0 +1,99 @@ + +@inherits BasePage + +@page "/database/{text}" + +@inject IEntityDisplayService EntityDisplayService +@using Model +@implements IDisposable + + + + + + + + Game Patch: @Variables.GamePatch + + + + +
+ +
+ + + @if (Text!.Trim().ToLower().Equals("walter")) + { + + + Unhandled Exception: EXCEPTION_MEMORY_SIZE_VIOLATION + UNIT_WALTER too powerful to be displayed. + + This SHOULD NEVER HAPPEN! + + + } + else if (_entity == null) + { + +
Invalid entity name entered: @Text
+
No such entity. Did you mean "Throne"?
+
+ } + + else + { + + + + + + + + + + } + +
+ + +@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); + } + +} \ No newline at end of file diff --git a/Pages/Pages/Database/Entity/EntityViewComponent.razor b/Pages/Pages/Database/Entity/EntityViewComponent.razor new file mode 100644 index 0000000..be9e7ae --- /dev/null +++ b/Pages/Pages/Database/Entity/EntityViewComponent.razor @@ -0,0 +1,56 @@ +@if (Entity != null) +{ + var isVanguard = Entity.VanguardAdded() != null ? " vanguard" : ""; + +
+ +
+ + + + + + + + + + + +
+
+} + + + +@code { + + [CascadingParameter] public EntityModel? Entity { get; set; } + + [CascadingParameter] public string? StyleType { get; set; } + +} \ No newline at end of file diff --git a/Pages/Pages/Database/Entity/Parts/EntityAbilitiesComponent.razor b/Pages/Pages/Database/Entity/Parts/EntityAbilitiesComponent.razor new file mode 100644 index 0000000..7e9c2a6 --- /dev/null +++ b/Pages/Pages/Database/Entity/Parts/EntityAbilitiesComponent.razor @@ -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(); + +
+
+ Ability Name: @spell.Info().Name +
+
+ - Description: @((MarkupString)info.Description) +
+ + @if (!info.Notes.Trim().Equals("")) + { +
+ - Notes: @((MarkupString)info.Notes) +
+ } + +
+ @if (production != null) + { + if (production.Energy != 0) + { +
+ - Energy: @production.Energy +
+ } + + @if (!production.DefensiveLayer.Equals(0)) + { +
+ - Shields: @production.DefensiveLayer +
+ } + + if (production.BuildTime != 0) + { +
+ - BuildTime: @production.BuildTime +
+ } + + if (production.Cooldown != 0) + { +
+ - Cooldown: @production.Cooldown +
+ } + } +
+
+ } + } + else + { + + @foreach (var idAbility in Entity.IdAbilities()) + { + var spell = EntityModel.Get(idAbility.Id); + + var info = spell.Info(); + var production = spell.Production(); + +
+
+ Name: + +
+
+ Description: @((MarkupString)info.Description) +
+ + @if (!info.Notes.Trim().Equals("")) + { +
+ Notes: @((MarkupString)info.Notes) +
+ } + +
+ @if (production != null) + { + if (production.Energy != 0) + { +
+ Energy: @production.Energy +
+ } + + @if (!production.DefensiveLayer.Equals(0)) + { +
+ Shields: @production.DefensiveLayer +
+ } + + if (production.BuildTime != 0) + { +
+ BuildTime: @production.BuildTime +
+ } + + if (production.Cooldown != 0) + { +
+ Cooldown: @production.Cooldown +
+ } + } +
+
+ } +
+ } +} + + +@code { + + [CascadingParameter] public EntityModel? Entity { get; set; } + + [CascadingParameter] public string StyleType { get; set; } = "Detailed"; + + +} \ No newline at end of file diff --git a/Pages/Pages/Database/Entity/Parts/EntityHeaderComponent.razor b/Pages/Pages/Database/Entity/Parts/EntityHeaderComponent.razor new file mode 100644 index 0000000..da12eac --- /dev/null +++ b/Pages/Pages/Database/Entity/Parts/EntityHeaderComponent.razor @@ -0,0 +1,72 @@ +@if (StyleType.Equals("Plain")) +{ +
+ @Entity?.Info().Name + @if (Entity?.Info().Descriptive != DescriptiveType.None) + { + , @Entity?.Info().Descriptive.Replace("_", " ") + } +
+} +else +{ +
+
+ @Entity?.Info().Name +
+
+ @Entity?.EntityType.Replace("_", " ") + @if (Entity?.Info().Descriptive != DescriptiveType.None) + { + + : @Entity!.Info().Descriptive.Replace("_", " ") + + } +
+ + +
+ @if (Entity.Info().FlavorText != "") + { +
+ @((MarkupString)Entity.Info().FlavorText) +
+ } + +
+
+ + +} + +@code { + + [CascadingParameter] public EntityModel? Entity { get; set; } + + + [CascadingParameter] public string StyleType { get; set; } = "Detailed"; + +} \ No newline at end of file diff --git a/Pages/Pages/Database/Entity/Parts/EntityInfoComponent.razor b/Pages/Pages/Database/Entity/Parts/EntityInfoComponent.razor new file mode 100644 index 0000000..d66699a --- /dev/null +++ b/Pages/Pages/Database/Entity/Parts/EntityInfoComponent.razor @@ -0,0 +1,134 @@ +@inject IGlossaryService glossaryService + +@if (StyleType.Equals("Plain")) +{ + @if (Entity!.Info().Description != "") + { +
+ Description: @((MarkupString)glossaryService.LinkifyText(Entity.Info().Description)) +
+ } + + @if (Entity.Info().Notes != "") + { +
+ Notes: @((MarkupString)glossaryService.LinkifyText(Entity.Info().Notes)) +
+ } + + @if (Entity.Info().FlavorText != "") + { +
+ @((MarkupString)glossaryService.LinkifyText(Entity.Info().FlavorText)) +
+ } + +
+
+ @if (Entity.Faction() != null) + { +
+ Faction: @EntityData.Get()[Entity.Faction().Faction].Info().Name +
+ } + @if (Entity.Tier() != null) + { +
+ Tier: @Entity.Tier().Tier +
+ } +
+ + @if (Entity.Hotkey() != null) + { +
+
+ Hotkey Group: @Entity.Hotkey().HotkeyGroup +
+
+ Hotkey: @Entity.Hotkey().Hotkey +
+
+ Hold Space: @(Entity.Hotkey().HoldSpace ? "Yes" : "No") +
+
+ } +
+} +else +{ + + @if (Entity!.Info().Description != "") + { +
+ Description: @((MarkupString)glossaryService.LinkifyText(Entity.Info().Description)) +
+ } + + @if (Entity.Info().Notes != "") + { +
+ Notes: @((MarkupString)glossaryService.LinkifyText(Entity.Info().Notes)) +
+ } + +
+
+ @if (Entity.Faction() != null) + { +
+ Faction: @EntityData.Get()[Entity.Faction().Faction].Info().Name +
+ } + @if (Entity.Tier() != null) + { +
+ Tier: @Entity.Tier().Tier +
+ } +
+ + @if (Entity.Hotkey() != null) + { +
+
+ Hotkey Group: @Entity.Hotkey().HotkeyGroup +
+
+ Hotkey: @Entity.Hotkey().Hotkey +
+
+ Hold Space: @(Entity.Hotkey().HoldSpace ? "Yes" : "No") +
+
+ } +
+ + +
+ + + +} + + + +@code { + + [CascadingParameter] public EntityModel? Entity { get; set; } + + [CascadingParameter] public string StyleType { get; set; } = "Detailed"; + +} \ No newline at end of file diff --git a/Pages/Pages/Database/Entity/Parts/EntityMechanicsComponent.razor b/Pages/Pages/Database/Entity/Parts/EntityMechanicsComponent.razor new file mode 100644 index 0000000..ef25f0a --- /dev/null +++ b/Pages/Pages/Database/Entity/Parts/EntityMechanicsComponent.razor @@ -0,0 +1,30 @@ +@if (Entity!.Mechanics().Count > 0) +{ + +
+ @foreach (var data in Entity.Mechanics()) + { +
+
+ + Name: @data.Name + +
+
+ Description: @data.Description +
+
+
+ } +
+ +
+} + +@code { + + [CascadingParameter] public EntityModel? Entity { get; set; } + + [CascadingParameter] public string StyleType { get; set; } = "Detailed"; + +} \ No newline at end of file diff --git a/Pages/Pages/Database/Entity/Parts/EntityPassivesComponent.razor b/Pages/Pages/Database/Entity/Parts/EntityPassivesComponent.razor new file mode 100644 index 0000000..9656bd0 --- /dev/null +++ b/Pages/Pages/Database/Entity/Parts/EntityPassivesComponent.razor @@ -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(); + + +
+
+ Passive Name: @info.Name +
+
+ - Description: @((MarkupString)info.Description) +
+ + @if (!info.Notes.Trim().Equals("")) + { +
+ - Notes: @((MarkupString)info.Notes) +
+ } +
+ + @if (production != null) + { +
+ @if (production.Pyre != 0) + { +
+ - Pyre: @production.Pyre +
+ } + @if (production.Cooldown != 0) + { +
+ - Cooldown: @production.Cooldown.ToString()s +
+ } +
+ } + + @if (requirements.Count > 0) + { + @foreach (var requirement in requirements) + { + var requirementModel = EntityData.Get()[requirement.Id]; +
+ + - @requirement.Requirement.Replace("_", " "): @requirementModel.Info().Name + +
+ } + } + } + } + else + { + + @foreach (var idPassive in Entity.IdPassives()) + { + var passive = EntityModel.Get(idPassive.Id); + + var info = passive.Info(); + + var production = passive.Production(); + + var requirements = passive.Requirements(); + + +
+
+ Name: + +
+
+ Description: @((MarkupString)info.Description) +
+ + @if (!info.Notes.Trim().Equals("")) + { +
+ Notes: @((MarkupString)info.Notes) +
+ } + + + @if (requirements.Count > 0) + { + @foreach (var requirement in requirements) + { + var requirementModel = EntityData.Get()[requirement.Id]; +
+ + @requirement.Requirement.Replace("_", " "): @requirementModel.Info().Name + +
+ } + } +
+ + @if (production != null) + { +
+ @if (production.Pyre != 0) + { +
+ Pyre: @production.Pyre +
+ } + @if (production.Cooldown != 0) + { +
+ Cooldown: @production.Cooldown.ToString()s +
+ } +
+ } + } +
+ } +} + + +@code { + + [CascadingParameter] public EntityModel? Entity { get; set; } + + + [CascadingParameter] public string StyleType { get; set; } = "Detailed"; + +} \ No newline at end of file diff --git a/Pages/Pages/Database/Entity/Parts/EntityProductionComponent.razor b/Pages/Pages/Database/Entity/Parts/EntityProductionComponent.razor new file mode 100644 index 0000000..84a69ea --- /dev/null +++ b/Pages/Pages/Database/Entity/Parts/EntityProductionComponent.razor @@ -0,0 +1,225 @@ +@if (Production != null || Supply != null || Requirements.Count > 0) +{ + @if (StyleType.Equals("Plain")) + { + @if (Requirements.Count() > 0) + { +
+ @foreach (var requirement in Requirements) + { + var requirementModel = EntityData.Get()[requirement.Id]; +
+ + + @requirement.Requirement.Replace("_", " "): @requirementModel.Info().Name + +
+ } +
+ } + + @if (Production != null && (!Production.Alloy.Equals(0) + || !Production.Ether.Equals(0) + || !Production.BuildTime.Equals(0) + || !Production.Cooldown.Equals(0))) + { +
+ @if (!Production.Alloy.Equals(0)) + { +
+ Alloy: @Production.Alloy +
+ } + @if (!Production.Ether.Equals(0)) + { +
+ Ether: @Production.Ether +
+ } + @if (!Production.Pyre.Equals(0)) + { +
+ Pyre: @Production.Pyre +
+ } + @if (!Production.Pyre.Equals(0)) + { +
+ Shields: @Production.DefensiveLayer +
+ } + + @if (!Production.BuildTime.Equals(0)) + { +
+ Build Time: @Production.BuildTime.ToString()s +
+ } + + @if (!Production.Energy.Equals(0)) + { +
+ Energy: @Production.Energy +
+ } + + + @if (!Production.Cooldown.Equals(0)) + { +
+ Cooldown: @Production.Cooldown.ToString()s +
+ } +
+ } + + @if (Supply != null) + { +
+ @if (!Supply.Grants.Equals(0)) + { +
+ Grants: @Supply.Grants +
+ } + @if (!Supply.Takes.Equals(0)) + { +
+ Takes: @Supply.Takes Supply +
+ } +
+ } + } + + else + { + +
+ @if (Requirements.Count() > 0) + { +
+ @foreach (var requirement in Requirements) + { +
+ + + @requirement.Requirement.Replace("_", " "): + +
+ } +
+ } + + @if (Production != null && (!Production.Alloy.Equals(0) + || !Production.Ether.Equals(0) + || !Production.BuildTime.Equals(0) + || !Production.Cooldown.Equals(0))) + { +
+ @if (!Production.Alloy.Equals(0)) + { +
+ Alloy: @Production.Alloy +
+ } + @if (!Production.Ether.Equals(0)) + { +
+ Ether: @Production.Ether +
+ } + @if (!Production.Pyre.Equals(0)) + { +
+ Pyre: @Production.Pyre +
+ } + @if (!Production.DefensiveLayer.Equals(0)) + { +
+ Shields: @Production.DefensiveLayer +
+ } + + @if (!Production.BuildTime.Equals(0)) + { +
+ Build Time: @Production.BuildTime.ToString()s +
+ } + + @if (!Production.Energy.Equals(0)) + { +
+ Energy: @Production.Energy +
+ } + + + @if (!Production.Cooldown.Equals(0)) + { +
+ Cooldown: @Production.Cooldown.ToString()s +
+ } +
+ } + @if (Supply != null) + { +
+ @if (!Supply.Grants.Equals(0)) + { +
+ Grants: @Supply.Grants +
+ } + @if (!Supply.Takes.Equals(0)) + { +
+ Takes: @Supply.Takes Supply +
+ } +
+ } +
+
+ + + + } +} + +@code { + + [CascadingParameter] public EntityModel? Entity { get; set; } + + + [CascadingParameter] public string StyleType { get; set; } = "Detailed"; + + + private EntityProductionModel Production => Entity!.Production(); + private List Requirements => Entity!.Requirements(); + private EntitySupplyModel Supply => Entity!.Supply(); + + + protected override void OnParametersSet() + { + StateHasChanged(); + } + + +} \ No newline at end of file diff --git a/Pages/Pages/Database/Entity/Parts/EntityPyreSpellsComponent.razor b/Pages/Pages/Database/Entity/Parts/EntityPyreSpellsComponent.razor new file mode 100644 index 0000000..b192a61 --- /dev/null +++ b/Pages/Pages/Database/Entity/Parts/EntityPyreSpellsComponent.razor @@ -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(); + +
+
+ Spell Name: @spell.Info().Name +
+
+ - Description: @((MarkupString)info.Description) +
+
+ @if (production != null) + { + if (production.Pyre != 0) + { + - Pyre: + @production.Pyre + } + + if (production.BuildTime != 0) + { + - BuildTime: + @production.BuildTime + } + + if (production.Cooldown != 0) + { + - Cooldown: + @production.Cooldown + } + } +
+
+ } + } + else + { + + @foreach (var pyreSpell in Entity.IdPyreSpells()) + { + var spell = EntityModel.Get(pyreSpell.Id); + + var info = spell.Info(); + var production = spell.Production(); + +
+
+ Name: + +
+
+ Description: @((MarkupString)info.Description) +
+
+ @if (production != null) + { + if (production.Pyre != 0) + { + Pyre: + @production.Pyre + } + + if (production.BuildTime != 0) + { + BuildTime: + @production.BuildTime + } + + if (production.Cooldown != 0) + { + Cooldown: + @production.Cooldown + } + } +
+
+ } +
+ } +} + + +@code { + + [CascadingParameter] public EntityModel? Entity { get; set; } + + + [CascadingParameter] public string StyleType { get; set; } = "Detailed"; + +} \ No newline at end of file diff --git a/Pages/Pages/Database/Entity/Parts/EntityStatsComponent.razor b/Pages/Pages/Database/Entity/Parts/EntityStatsComponent.razor new file mode 100644 index 0000000..acea165 --- /dev/null +++ b/Pages/Pages/Database/Entity/Parts/EntityStatsComponent.razor @@ -0,0 +1,180 @@ +@if (Vitality != null || Movement != null) +{ + @if (StyleType.Equals("Plain")) + { + @if (Vitality != null) + { +
+ @if (!Vitality.DefenseLayer.Equals(0)) + { +
+ Shield: @Vitality.DefenseLayer +
+ } + @if (!Vitality.Health.Equals(0)) + { +
+ Health: @Vitality.Health +
+ } + @if (!Vitality.Energy.Equals(0)) + { +
+ Energy: @Vitality.Energy +
+ } + @if (!Vitality.Lasts.Equals(0)) + { +
+ Lasts: @Vitality.Lasts.ToString()s +
+ } + + @if (Vitality.Armor != "") + { +
+ Armor: @Vitality.Armor +
+ } + + @if (Vitality.IsEtheric) + { +
+ + Etheric +
+ } + @if (Vitality.IsStructure) + { +
+ + Structure +
+ } +
+ } + + + @if (Movement != null) + { +
+ @if (!Movement.Speed.Equals(0)) + { +
+ Speed: @Movement.Speed +
+ } + else + { +
+ Speed: Immobile +
+ } +
+ Move Type: @Movement.Movement +
+
+ } + } + else + { + +
+ @if (Vitality != null) + { +
+ @if (!Vitality.DefenseLayer.Equals(0)) + { +
+ Shield: @Vitality.DefenseLayer +
+ } + @if (!Vitality.Health.Equals(0)) + { +
+ Health: @Vitality.Health +
+ } + @if (!Vitality.Energy.Equals(0)) + { +
+ Energy: @Vitality.Energy +
+ } + @if (!Vitality.Lasts.Equals(0)) + { +
+ Lasts: @Vitality.Lasts.ToString()s +
+ } + @if (Vitality.Armor != "") + { +
+ Armor: @Vitality.Armor +
+ } + + @if (Vitality.IsEtheric) + { +
+ + Etheric +
+ } + @if (Vitality.IsStructure) + { +
+ + Structure +
+ } +
+ } + + + @if (Movement != null) + { +
+ @if (!Movement.Speed.Equals(0)) + { +
+ Speed: @Movement.Speed +
+ } + else + { +
+ Speed: Immobile +
+ } +
+ Move Type: @Movement.Movement +
+
+ } +
+
+ + } +} + + +@code { + + [CascadingParameter] public EntityModel? Entity { get; set; } + + + [CascadingParameter] public string StyleType { get; set; } = "Detailed"; + + + private EntityVitalityModel Vitality => Entity!.Vitality(); + private EntityMovementModel Movement => Entity!.Movement(); +} \ No newline at end of file diff --git a/Pages/Pages/Database/Entity/Parts/EntityUpgradesComponent.razor b/Pages/Pages/Database/Entity/Parts/EntityUpgradesComponent.razor new file mode 100644 index 0000000..c2baf16 --- /dev/null +++ b/Pages/Pages/Database/Entity/Parts/EntityUpgradesComponent.razor @@ -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); +
+
+ Upgrade Name: @entity.Info().Name +
+
+ - Description: @entity.Info().Description +
+
+ } + } + else + { + +
+ @foreach (var upgradeId in Entity.IdUpgrades()) + { + var entity = EntityModel.Get(upgradeId.Id); +
+
+ Name: + +
+
+ Description: @entity.Info().Description +
+
+ } +
+
+ + + + } +} + + +@code { + + [CascadingParameter] public EntityModel? Entity { get; set; } + + + [CascadingParameter] public string StyleType { get; set; } = "Detailed"; + +} \ No newline at end of file diff --git a/Pages/Pages/Database/Entity/Parts/EntityVanguardAddedComponent.razor b/Pages/Pages/Database/Entity/Parts/EntityVanguardAddedComponent.razor new file mode 100644 index 0000000..545723a --- /dev/null +++ b/Pages/Pages/Database/Entity/Parts/EntityVanguardAddedComponent.razor @@ -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")) + { +
+ Immortal: @immortal.Info().Name +
+ @if (!Vanguard.ReplaceId.Equals("")) + { +
+ Replaces: @replaced.Info().Name +
+ } + } + else + { + +
+
+ Immortal: + +
+ @if (!Vanguard.ReplaceId.Equals("")) + { +
+ Replaces: + +
+ } +
+
+ } +} + +@code { + + [CascadingParameter] public EntityModel? Entity { get; set; } + + private EntityVanguardAddedModel? Vanguard => Entity?.VanguardAdded(); + + [CascadingParameter] public string StyleType { get; set; } = "Detailed"; + + +} \ No newline at end of file diff --git a/Pages/Pages/Database/Entity/Parts/EntityVanguardsComponent.razor b/Pages/Pages/Database/Entity/Parts/EntityVanguardsComponent.razor new file mode 100644 index 0000000..260970b --- /dev/null +++ b/Pages/Pages/Database/Entity/Parts/EntityVanguardsComponent.razor @@ -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; + +
+
+ Name: @entity.Info().Name +
+
+ - Replaces: @replaced.Info().Name +
+
+ - Built From: @immortal.Info().Name +
+
+ } + } + else + { + + @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; + +
+
+ Name: + +
+
+ Replaces: + +
+
+ Built From: + +
+
+ } +
+ } +} + +@code { + + [CascadingParameter] public EntityModel? Entity { get; set; } + + + [CascadingParameter] public string StyleType { get; set; } = "Detailed"; + +} \ No newline at end of file diff --git a/Pages/Pages/Database/Entity/Parts/EntityWeaponsComponent.razor b/Pages/Pages/Database/Entity/Parts/EntityWeaponsComponent.razor new file mode 100644 index 0000000..b830599 --- /dev/null +++ b/Pages/Pages/Database/Entity/Parts/EntityWeaponsComponent.razor @@ -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++; + +
+
+
+
+ Weapon @index +
+ +
+ - Damage: @data.Damage +
+ @if (data.LightDamage != 0) + { +
+ - vs Light: @data.LightDamage  +
+ } + @if (data.MediumDamage != 0) + { +
+ - vs Medium: @data.MediumDamage  +
+ } + @if (data.HeavyDamage != 0) + { +
+ - vs Heavy: @data.HeavyDamage  +
+ } + @if (data.EthericDamageBonus != 0) + { +
+ - vs Etheric +@data.EthericDamageBonus  +
+ } + @if (data.StructureDamageBonus != 0) + { +
+ - vs Structure: +@data.StructureDamageBonus  +
+ } +
+
+
+ - Range: @data.Range +
+ @if (data.SecondsBetweenAttacks > 0) + { +
+ - AttacksPerSecond: @(1 / data.SecondsBetweenAttacks) +
+
+ - (or SecondsBetweenAttacks: @data.SecondsBetweenAttacks) +
+ } + else if (data.AttacksPerSecond > 0) + { +
+ - AttacksPerSecond: @data.AttacksPerSecond +
+
+ - (or SecondsBetweenAttacks: @(1 / data.AttacksPerSecond)) +
+ } + + +
+ - Targets: @data.Targets +
+ @if (data.AttacksPerSecond != 0) + { + + - DPS: @(Math.Round(data.Damage * data.AttacksPerSecond))  + + @if (data.LightDamage != 0) + { + + - Light DPS: @(Math.Round(data.LightDamage * data.AttacksPerSecond))  + + } + + @if (data.MediumDamage != 0) + { + + - Medium DPS: @(Math.Round(data.MediumDamage * data.AttacksPerSecond))  + + } + + @if (data.HeavyDamage != 0) + { + + - Heavy DPS: @(Math.Round(data.HeavyDamage * data.AttacksPerSecond))  + + } + } +
+ } + } + else + { + +
+ @foreach (var data in Entity.Weapons()) + { +
+
+
+
+ Damage: @data.Damage +
+ @if (data.LightDamage != 0) + { +
+ vs Light: @data.LightDamage  +
+ } + @if (data.MediumDamage != 0) + { +
+ vs Medium: @data.MediumDamage  +
+ } + @if (data.HeavyDamage != 0) + { +
+ vs Heavy: @data.HeavyDamage  +
+ } + @if (data.EthericDamageBonus != 0) + { +
+ vs Etheric +@data.EthericDamageBonus  +
+ } + @if (data.StructureDamageBonus != 0) + { +
+ vs Structure: +@data.StructureDamageBonus  +
+ } +
+
+
+ Range: @data.Range +
+ + @if (data.SecondsBetweenAttacks > 0) + { +
+ AttacksPerSecond: @(1 / data.SecondsBetweenAttacks) +
+
+ (or SecondsBetweenAttacks: @data.SecondsBetweenAttacks) +
+ } + else if (data.AttacksPerSecond > 0) + { +
+ AttacksPerSecond: @data.AttacksPerSecond +
+
+ (or SecondsBetweenAttacks: @(1 / data.AttacksPerSecond)) +
+ } + +
+ Targets: @data.Targets +
+ @if (data.AttacksPerSecond != 0) + { +
+ DPS: @(Math.Round(data.Damage * data.AttacksPerSecond))  +
+ @if (data.LightDamage != 0) + { +
+ Light DPS: @(Math.Round(data.LightDamage * data.AttacksPerSecond))  +
+ } + + @if (data.MediumDamage != 0) + { +
+ Medium DPS: @(Math.Round(data.MediumDamage * data.AttacksPerSecond))  +
+ } + + @if (data.HeavyDamage != 0) + { +
+ Heavy DPS: @(Math.Round(data.HeavyDamage * data.AttacksPerSecond))  +
+ } + } +
+ } +
+
+ + + } +} + +@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(StorageKeys.IsDynamicFormatting); + } + + void IDisposable.Dispose() + { + StorageService.Unsubscribe(RefreshDefaults); + } + +} \ No newline at end of file diff --git a/Pages/Pages/Database/Parts/EntityFilterComponent.razor b/Pages/Pages/Database/Parts/EntityFilterComponent.razor new file mode 100644 index 0000000..76ba4ff --- /dev/null +++ b/Pages/Pages/Database/Parts/EntityFilterComponent.razor @@ -0,0 +1,264 @@ +
+
+
+
+ @foreach (var choice in EntityFilterService.GetFactionChoices()) + { + var styleClass = ""; + if (choice.Equals(EntityFilterService.GetFactionType())) + { + styleClass = "selected"; + } + + + } +
+ + @if (EntityFilterService.GetFactionType() != "Any" && EntityFilterService.GetFactionType() != "None") + { +
+ @foreach (var choice in EntityFilterService.GetImmortalChoices()) + { + var name = EntityData.Get()[choice].Info().Name; + + var styleClass = ""; + if (choice.Equals(EntityFilterService.GetImmortalType())) + { + styleClass = "selected"; + } + + + } +
+ } +
+
+ @foreach (var choice in EntityFilterService.GetEntityChoices()) + { + var styleClass = ""; + if (choice.Equals(EntityFilterService.GetEntityType())) + { + styleClass = "selected"; + } + + + } +
+ +
+
+ +
+ + + Faction + + + + + + + + + Immortal + + + + + + + + + + + + Entity + + + + + + + + + + + + + + + + + + + + +
+ + + + +@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()!); + } + +} \ No newline at end of file diff --git a/Pages/Pages/Database/Parts/EntityFilterComponent.razor.css b/Pages/Pages/Database/Parts/EntityFilterComponent.razor.css new file mode 100644 index 0000000..59b6962 --- /dev/null +++ b/Pages/Pages/Database/Parts/EntityFilterComponent.razor.css @@ -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; + } +} \ No newline at end of file diff --git a/Pages/Pages/EconomyComparison/EconomyComparisonPage.razor b/Pages/Pages/EconomyComparison/EconomyComparisonPage.razor new file mode 100644 index 0000000..a29acfe --- /dev/null +++ b/Pages/Pages/EconomyComparison/EconomyComparisonPage.razor @@ -0,0 +1,56 @@ +@page "/economy-comparison" + +@inherits BasePage + +@implements IDisposable +@inject IEconomyComparisonService EconomyComparisonService + + + Economy Comparision + + +
You
+ +
+ + +
Them
+ +
+ + + + + + + + + + + + + + + What is this tool for? + + + Compare two economies together to determine best attack timing windows. + + + +
+ +@code { + + protected override void OnInitialized() + { + base.OnInitialized(); + EconomyComparisonService.Subscribe(StateHasChanged); + } + + void IDisposable.Dispose() + { + EconomyComparisonService.Unsubscribe(StateHasChanged); + } + +} \ No newline at end of file diff --git a/Pages/Pages/EconomyComparison/Parts/ChartComponent.razor b/Pages/Pages/EconomyComparison/Parts/ChartComponent.razor new file mode 100644 index 0000000..4902908 --- /dev/null +++ b/Pages/Pages/EconomyComparison/Parts/ChartComponent.razor @@ -0,0 +1,200 @@ +@inject IEconomyComparisonService economyComparisonService +@inject IJSRuntime jsRuntime; +@using Model.BuildOrders +@implements IDisposable + +
+ + @{ + var index = 0; + } + @foreach (var chart in charts) + { + index++; + +
+
+ @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) + { +
+
+
+
+ } + } +
+
+ } +
+ + + +@code { + + private readonly int width = 800; + private readonly int height = 700; + + private List 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(); + 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 + 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); + } + +} \ No newline at end of file diff --git a/Pages/Pages/EconomyComparison/Parts/EconomyDifferenceComponent.razor b/Pages/Pages/EconomyComparison/Parts/EconomyDifferenceComponent.razor new file mode 100644 index 0000000..643c5cb --- /dev/null +++ b/Pages/Pages/EconomyComparison/Parts/EconomyDifferenceComponent.razor @@ -0,0 +1,127 @@ +@inject IEconomyComparisonService economyComparisonService +@implements IDisposable + +
+
+
+ Starting Advantage +
+
+ At Time: @StartingAdvantageAtTime | T @Interval.ToTime(StartingAdvantageAtTime) +
+
+ +
+
+ Peak Advantage +
+
+ By Alloy: @PeakAdvantageByAlloy +
+
+ At Time: @PeakAdvantageAtTime | T @Interval.ToTime(PeakAdvantageAtTime) +
+
+ +
+
+ Worsening Time +
+
+ At Time: @WorseningTime | T @Interval.ToTime(WorseningTime) +
+
+ +
+
+ Miracle Time +
+
+ At Time: @MiracleTime | T @Interval.ToTime(MiracleTime) +
+
+
+ + + + +@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(); + } + +} \ No newline at end of file diff --git a/Pages/Pages/EconomyComparison/Parts/EconomyInputComponent.razor b/Pages/Pages/EconomyComparison/Parts/EconomyInputComponent.razor new file mode 100644 index 0000000..fdfec82 --- /dev/null +++ b/Pages/Pages/EconomyComparison/Parts/EconomyInputComponent.razor @@ -0,0 +1,85 @@ +@inject IEconomyComparisonService economyComparisonService +@implements IDisposable + + + + Faction + + + + + + + + Number of TownHall Expansions + + + + @{ + var index = 0; + } + @foreach (var timing in TownHallTimings) + { + index++; + + + + TownHall build time + + + } + + + + + + + +@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 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!); + } + +} \ No newline at end of file diff --git a/Pages/Pages/Glossary/GlossaryDetailPage.razor b/Pages/Pages/Glossary/GlossaryDetailPage.razor new file mode 100644 index 0000000..edaa19b --- /dev/null +++ b/Pages/Pages/Glossary/GlossaryDetailPage.razor @@ -0,0 +1,97 @@ +@inject IGlossaryService glossaryService +@inject IGlossaryDialogService glossaryDialogService +@inject NavigationManager NavigationManager + +@page "/glossary/{TermId}" + + + @if (term != null) + { + @term.Term + + +
@term.Category
+
@((MarkupString)RenderMarkdown(term.LongDefinition))
+
+ + @if (term.RelatedEntityIds.Count > 0) + { + +
Related Entities
+
+ @foreach (var entityId in term.RelatedEntityIds) + { + + } +
+
+ } + + @if (term.RelatedTermIds.Count > 0) + { + +
Related Terms
+
+ @foreach (var relatedId in term.RelatedTermIds) + { + + } +
+
+ } + } + else + { + Term not found + +

The glossary term you're looking for doesn't exist.

+
+ } +
+ + + +@code { + + [Parameter] public string TermId { get; set; } = ""; + + private GlossaryTermModel? term; + + protected override void OnParametersSet() + { + term = glossaryService.GetTerm(TermId); + } + + string RenderMarkdown(string text) + { + return Markdown.ToHtml(text); + } + +} diff --git a/Pages/Pages/Glossary/GlossaryPage.razor b/Pages/Pages/Glossary/GlossaryPage.razor new file mode 100644 index 0000000..e6000c4 --- /dev/null +++ b/Pages/Pages/Glossary/GlossaryPage.razor @@ -0,0 +1,177 @@ +@inherits BasePage +@inject IGlossaryService glossaryService +@inject IGlossaryDialogService glossaryDialogService + +@page "/glossary" + + + Glossary + +
+ +
+ + @foreach (var cat in categories) + { + + } +
+
+ +
+ @foreach (var term in filteredTerms) + { + +
+
+ @term.Term + @term.Category +
+
@term.ShortDefinition
+
+
+ } +
+
+ + + +@code { + private string searchText = ""; + private string? selectedCategory; + private List categories = new(); + private List allTerms = new(); + private List filteredTerms = new(); + + protected override void OnInitialized() + { + base.OnInitialized(); + allTerms = glossaryService.GetAllTerms(); + categories = glossaryService.GetCategories(); + ApplyFilter(); + } + + void OnSearchChanged() + { + ApplyFilter(); + } + + void SelectCategory(string? category) + { + selectedCategory = category; + ApplyFilter(); + } + + void ApplyFilter() + { + var query = allTerms.AsEnumerable(); + + if (!string.IsNullOrWhiteSpace(searchText)) + { + query = query.Where(t => + t.Term.Contains(searchText, StringComparison.OrdinalIgnoreCase) || + t.ShortDefinition.Contains(searchText, StringComparison.OrdinalIgnoreCase)); + } + + if (selectedCategory != null) + { + query = query.Where(t => + t.Category.Equals(selectedCategory, StringComparison.OrdinalIgnoreCase)); + } + + filteredTerms = query.ToList(); + } + +} diff --git a/Pages/Pages/HarassCalculatorPage.razor b/Pages/Pages/HarassCalculatorPage.razor new file mode 100644 index 0000000..febbdf3 --- /dev/null +++ b/Pages/Pages/HarassCalculatorPage.razor @@ -0,0 +1,392 @@ + +@inject IDataCollectionService DataCollectionService +@using Model +@inherits BasePage + +@page "/harass-calculator" + + + Harass Calculator + + + Credit to Zard for deriving the formula. + + + + + + + + @CostOfWorker + + + + @AlloyMinedPerSecondByWorker + + + + @TimeToProduceWorker + + + + + + + + Number of workers lost to harass + + + + Number of townhalls you have + + +
+ @{ + var index = 0; + } + @foreach (var travelTime in TravelTimes) + { + index++; + if (index == 1) + { + continue; + } + + var id = $"numberOfTownHallsExisting_{index}"; + + Worker travel time from other + base @(travelTime.Index + 1) + + } +
+ + + + +
+ + @TotalAlloyHarassment + +
+
+
+ + +
+ +
+
+ (Worker replacement costs: @WorkerReplacementCost()) +
+
+ (Delayed mining time: @DelayedMiningCost()) +
+
+ (Average travel time: @GetAverageTravelTime()) +
+ +
+
+
+ + + + + + + What is this tool? + + + 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 + + Number of workers lost to + harass + to 6. This would determine a loss of @ExampleTotalAlloyLoss + alloy. Quite + the large number. + + + + + + What can I learn from this? + + + Well, let's assume you lost a full alloy line of workers, and have to take that + @ExampleTotalAlloyLoss alloy cost (@ExampleWorkerCost + to rebuy the workers, and @ExampleMiningTimeCost in lost mining + time.) +

+ If you were to set the Number of townhalls you have to 2, the calculator will consider worker + transfer micro. Allowing you to cut the total cost by roughly + @ExampleTotalAlloyLossDifference alloy. However, that + number isn't + entirely accurate, you are also going to have to bump up the Worker travel time to alloy to + account for the time it takes the transferred workers to arrive at the decimated alloy line. +

+ 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 + + @ExampleTotalAlloyLossAccurate + alloy + (saving you @ExampleTotalAlloyLossAccurateDifference alloy.) + + Which is + much better than not transferring workers! + +
+
+ + + + Can I see the formula for the calculation? + + + The Harass Calculator is based on the following calculation. + +
+
+
=c*m+r*g*(t+l) +
+ + + x =1 + + ? + + m + a + + ? + + + x + + + +
g*x*(t+l)
+
+
+
+
c is CostOfWorker
+
m is NumberOfWorkersLostToHarass, m for [M]otes
+
a is NumberOfTownHallsExisting, a for [A]cropolis
+
r is m mod a is LeftOverWorkersToProduceCount()
+
g is AlloyMinedPerSecondByWorker
+
t is TimeToProduceWorker
+
l is TravelTime
+
x is workerProductionIndex
+
+
+ This logic has since been changed slightly to allow client to enter different travel times per base. +
+
+
+ + + Can I see the code for the calculation? + + +
+ + View Here + +
+
+
+ + +
+ + + +@code { + + // Example calcs + float ExampleTotalAlloyLoss => Calculate( + WorkerReplacementCost(6), + SimultaneousProductionFloor(1, 6), + 6, + new List { 0 }); + + float ExampleWorkerCost => WorkerReplacementCost(6); + + float ExampleMiningTimeCost => ExampleTotalAlloyLoss - ExampleWorkerCost; + + float ExampleTotalAlloyLossDifference => ExampleTotalAlloyLoss - Calculate( + WorkerReplacementCost(6), + SimultaneousProductionFloor(2, 6), + 6, + new List { 0, 0 }); + + float ExampleTotalAlloyLossAccurate => Calculate( + WorkerReplacementCost(6), + SimultaneousProductionFloor(2, 6), + 6, + new List { 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 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 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(); + } + +} \ No newline at end of file diff --git a/Pages/Pages/Home/HomePage.razor b/Pages/Pages/Home/HomePage.razor new file mode 100644 index 0000000..df1a64c --- /dev/null +++ b/Pages/Pages/Home/HomePage.razor @@ -0,0 +1,64 @@ + +@inherits BasePage + +@page "/immortal-home" + + + +
+
+ Fan Reference +
+ +
+ Refer to various aspects of "IMMORTAL: Gates of Pyre" from this external reference! +
+
+
+ + + + +
+ + + + + + +
+
+ +
+ + \ No newline at end of file diff --git a/Pages/Pages/Home/Parts/ContentHighlightComponent.razor b/Pages/Pages/Home/Parts/ContentHighlightComponent.razor new file mode 100644 index 0000000..b71b0d7 --- /dev/null +++ b/Pages/Pages/Home/Parts/ContentHighlightComponent.razor @@ -0,0 +1,74 @@ + +
+ @Title +
+ @Title +
+ @Description +
+
+ + + + +@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!; + + +} \ No newline at end of file diff --git a/Pages/Pages/MemoryTester/MemoryTesterPage.razor b/Pages/Pages/MemoryTester/MemoryTesterPage.razor new file mode 100644 index 0000000..e0c457e --- /dev/null +++ b/Pages/Pages/MemoryTester/MemoryTesterPage.razor @@ -0,0 +1,47 @@ + +@inherits BasePage + +@page "/memory-tester" + + + Memory Tester + + + + + + + + + + + What is this tool? + + + 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. + +

+ 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! + 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. + +
+
+ + + + Why is this tool here? + + + 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. +

+ It may get expanded upon later. +
+
+
+ +
\ No newline at end of file diff --git a/Pages/Pages/MemoryTester/Parts/UnitMemory.razor b/Pages/Pages/MemoryTester/Parts/UnitMemory.razor new file mode 100644 index 0000000..cc2282c --- /dev/null +++ b/Pages/Pages/MemoryTester/Parts/UnitMemory.razor @@ -0,0 +1,129 @@ +@implements IDisposable; +@inject IMemoryTesterService MemoryTesterService; + +
+ + + @EntityMemory.Name + + + @foreach (var question in questions) + { + var questionWrong = hasBeenSubmitted && !question.IsRevealed && question.Guess != question.Answer; + + + + @if (questionWrong) + { +
The correct answer was @question.Answer
+ } + } + +
+
+ + + +@code { + + [Parameter] public MemoryEntityModel EntityMemory { get; set; } = default!; + + private List 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(); + } + +} \ No newline at end of file diff --git a/Pages/Pages/MemoryTester/Parts/UnitMemoryManager.razor b/Pages/Pages/MemoryTester/Parts/UnitMemoryManager.razor new file mode 100644 index 0000000..c8ae343 --- /dev/null +++ b/Pages/Pages/MemoryTester/Parts/UnitMemoryManager.razor @@ -0,0 +1,104 @@ +@implements IDisposable; + +@inject IMemoryTesterService MemoryTesterService; + +
+
+ @if (entities != null && questions != null) + { + @foreach (var entityMemory in entities) + { + + } + } +
+ +
+ Refresh + Submit +
+
+ + + + +@code { + private List entities = null!; + private List 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(); + } + +} \ No newline at end of file diff --git a/Pages/Pages/Notes/NotesIndexPage.razor b/Pages/Pages/Notes/NotesIndexPage.razor new file mode 100644 index 0000000..ee0614a --- /dev/null +++ b/Pages/Pages/Notes/NotesIndexPage.razor @@ -0,0 +1,144 @@ + +@inherits BasePage + +@inject INoteService NoteService +@implements IDisposable + +@inject IDataCollectionService DataCollectionService + +@page "/notes" + + +@if (!NoteService.IsLoaded()) +{ + +} +else +{ + + + Notes + + + @foreach (var noteSection in NoteService.NoteSectionModels) + { +
+
@noteSection.Name
+
+ @foreach (var noteContent in noteSection.NoteContentModels) + { + +
@noteContent.Name
+
@noteContent.Description
+
+ } +
+
+ } +
+
+} + + +@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(); + } + +} \ No newline at end of file diff --git a/Pages/Pages/Notes/NotesPage.razor b/Pages/Pages/Notes/NotesPage.razor new file mode 100644 index 0000000..34c2ff1 --- /dev/null +++ b/Pages/Pages/Notes/NotesPage.razor @@ -0,0 +1,120 @@ + +@inherits BasePage + +@inject INoteService NoteService +@implements IDisposable + +@inject IDataCollectionService DataCollectionService + +@page "/notes/{href1}/{href2?}/{href3?}/{href4?}/{href5?}" + + +@if (!NoteService.IsLoaded()) +{ + +} +else +{ + + + + + + + @foreach (var note in NoteService.NoteContentModels) + { + if (!note.Href.Equals(Href)) + { + continue; + } + + + } + + + +} + + + +@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); + } + +} \ No newline at end of file diff --git a/Pages/Pages/Notes/Parts/NoteComponent.razor b/Pages/Pages/Notes/Parts/NoteComponent.razor new file mode 100644 index 0000000..29552bb --- /dev/null +++ b/Pages/Pages/Notes/Parts/NoteComponent.razor @@ -0,0 +1,78 @@ +@inject HttpClient httpClient + +@if (content == null) +{ + +} +else +{ +
+
+

@noteFrontMatter.Title

+
+
Updated: @noteFrontMatter.UpdatedDate.ToString("MM/dd/yyyy")
+
Created: @noteFrontMatter.CreatedDate.ToString("MM/dd/yyyy")
+
+
+
@((MarkupString)Markdown.ToHtml(content, Pipeline))
+
+ + Edit on GitHub + +
+
+} + + + +@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 LoadContent() + { + content = await MarkdownFiles.LoadMarkdown(httpClient, Filepath); + + return noteFrontMatter = + await MarkdownFiles.LoadFrontMatter(httpClient, Filepath); + } + + protected override async Task OnParametersSetAsync() + { + await LoadContent(); + } + + protected override async Task OnInitializedAsync() + { + await LoadContent(); + } + +} \ No newline at end of file diff --git a/Pages/Pages/Notes/Parts/NoteInnerNavComponent.razor b/Pages/Pages/Notes/Parts/NoteInnerNavComponent.razor new file mode 100644 index 0000000..8d64a0a --- /dev/null +++ b/Pages/Pages/Notes/Parts/NoteInnerNavComponent.razor @@ -0,0 +1,71 @@ +@if (Note!.NoteContentModels.Count > 0) +{ +
+ @foreach (var innerNote in Note.NoteContentModels) + { + var linkStyle = $"noteInnerNavButton inner_{Layers}"; + @innerNote.Name + + } +
+} + + + +@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}"; + } + +} \ No newline at end of file diff --git a/Pages/Pages/Notes/Parts/NoteNavComponent.razor b/Pages/Pages/Notes/Parts/NoteNavComponent.razor new file mode 100644 index 0000000..6c1c949 --- /dev/null +++ b/Pages/Pages/Notes/Parts/NoteNavComponent.razor @@ -0,0 +1,42 @@ +
+ @foreach (var note in Notes) + { + if (note.Parent == null) + { + @note.Name + + } + } +
+ + + +@code { + + [Parameter] public List Notes { get; set; } = default!; + + [Parameter] public List Connections { get; set; } = default!; + +} \ No newline at end of file diff --git a/Pages/Pages/PermissionsPage.razor b/Pages/Pages/PermissionsPage.razor new file mode 100644 index 0000000..e955dc6 --- /dev/null +++ b/Pages/Pages/PermissionsPage.razor @@ -0,0 +1,131 @@ +@page "/permissions" + +@inject IPermissionService PermissionService + +@inject IMyDialogService MyDialogService + +@inherits BasePage +@using Services.Website +@implements IDisposable + + + + + + + + + + + + + + + + What's this page? + This page has options to enable and disable certain permissions settings. Such as + Storage and Data Collection. + + + + + What does this website store? + This website usages storage to track user defined default on the Storage page. + + + + + Why would I enable storage? + Enable storage if you want to website to remeber past settings whenever you visit the + website on the same web browser. + + + + + What data does this website collect? + + This website usages Google Analytics to collect data on usage of this website. +

+ Items include: if people use keyboard or mouse in build calculator, what pages people visit, and other + usages. +
+
+ + + Why would I enable data collection? + Enable data tracking if you want the website maintainer to know how your using the + website. + + +
+
+ + +@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(this, OnDataCollectionConfirmClicked), + OnCancel = new EventCallback(this, OnDataCollectionCancelClicked), + ConfirmButtonLabel = "Enable Data Collection" + }); + } + else + { + PermissionService.SetIsDataCollectionEnabled(!PermissionService.GetIsDataCollectionEnabled()); + } + } + +} \ No newline at end of file diff --git a/Pages/Pages/RawDatabase.razor b/Pages/Pages/RawDatabase.razor new file mode 100644 index 0000000..cfbcfe7 --- /dev/null +++ b/Pages/Pages/RawDatabase.razor @@ -0,0 +1,15 @@ +@inherits BasePage + +@page "/raw-database" + +
+ + Placeholders and Speculative + The data I am using contains placeholders and speculation on future mechanics. Ignore said data when + using this JSON. + + + + @EntityData.AsJson() + +
\ No newline at end of file diff --git a/Pages/Pages/RawDatabase.razor.css b/Pages/Pages/RawDatabase.razor.css new file mode 100644 index 0000000..3202a60 --- /dev/null +++ b/Pages/Pages/RawDatabase.razor.css @@ -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; + } +} diff --git a/Pages/Pages/StoragePage.razor b/Pages/Pages/StoragePage.razor new file mode 100644 index 0000000..ae36c67 --- /dev/null +++ b/Pages/Pages/StoragePage.razor @@ -0,0 +1,247 @@ +@page "/storage" + + +@inherits BasePage +@inject IStorageService StorageService +@using Services.Website +@implements IDisposable + + + @if (!_enabledPermissions) + { + + Storage Disabled + Enable Storage on the Permissions Page. + + } + else + { + + + + + + + + + + + + + + + + + Attack Time + + @if (_attackTime != null) + { +   T @Interval.ToTime((int)_attackTime) + } + + + + + Travel Time + + @if (_travelTime != null) + { +   T @Interval.ToTime((int)_travelTime) + } + + + + + Faction + + + + + + + + Immortal + + @if (Faction == DataType.FACTION_QRath) + { + + + } + @if (Faction == DataType.FACTION_Aru) + { + + + } + + + + + + Building Input Delay + Add a input delay to constructing buildings for simulating worker movement and + player micro. + + + + Wait Time + + + + + Wait To + + + + + } + + + + + + + + +@code { + bool _enabledPermissions; + + protected override void OnInitialized() + { + base.OnInitialized(); + + _enabledPermissions = StorageService.GetValue(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(StorageKeys.IsPlainView); + _isDynamicFormatting = StorageService.GetValue(StorageKeys.IsDynamicFormatting); + + _attackTime = StorageService.GetValue(StorageKeys.AttackTime); + _travelTime = StorageService.GetValue(StorageKeys.TravelTime); + + + _faction = StorageService.GetValue(StorageKeys.SelectedFaction); + _immortal = StorageService.GetValue(StorageKeys.SelectedImmortal); + + _buildingInputDelay = StorageService.GetValue(StorageKeys.BuildInputDelay); + _waitTime = StorageService.GetValue(StorageKeys.WaitTime); + _waitTo = StorageService.GetValue(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); + } + +} \ No newline at end of file diff --git a/Pages/Pages/StreamsPage.razor b/Pages/Pages/StreamsPage.razor new file mode 100644 index 0000000..eea3317 --- /dev/null +++ b/Pages/Pages/StreamsPage.razor @@ -0,0 +1,49 @@ +@page "/streams" + +@inherits BasePage + + + + Streams + + + + When and where are you streaming? + I stream Sunday updates on Twitch. + + + + What exactly are you streaming? + + The plan will be sprint planning and general development of this website. + +

+ Feel free to jump into the stream to ask questions or make feature requests for the website. +
+
+ + + Why should you watch these streams? + + You shouldn't. By nature of being live coding streams, I think they will have little entertainment and + educational value. +

+ 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. +
+
+ + Anything else I should know? + I'll be streaming under the "Twitch, + Software and Game Development" 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 "Twitch, IMMORTAL: Gates of Pyre" category for some examples. + + +
+
\ No newline at end of file diff --git a/Pages/Pages/TechTree/TechTreePage.razor b/Pages/Pages/TechTree/TechTreePage.razor new file mode 100644 index 0000000..d458ff2 --- /dev/null +++ b/Pages/Pages/TechTree/TechTreePage.razor @@ -0,0 +1,222 @@ +@inherits BasePage +@inject TechTreeService techTreeService +@inject IEntityDialogService entityDialogService +@inject NavigationManager NavigationManager + +@page "/tech-tree" + + + Tech Tree + +
+
+ Faction: + + @foreach (var faction in factions) + { + var factionName = GetFactionName(faction); + + } +
+ +
+ + @if (!string.IsNullOrEmpty(highlightEntityId)) + { + + } +
+
+ +
+ Produces + Requires Building + Requires Research + Morph + Upgrade +
+ + @if (currentGraph != null && currentGraph.Nodes.Count > 0) + { + + } + else + { + +

No data available for the selected faction.

+
+ } +
+ + + +@code { + private TechTreeGraphModel? currentGraph; + private List factions = new(); + private string? selectedFaction; + private string searchText = ""; + private string? highlightEntityId; + + protected override void OnInitialized() + { + base.OnInitialized(); + LoadGraph(); + } + + void LoadGraph() + { + currentGraph = techTreeService.BuildGraph(selectedFaction); + factions = techTreeService.GetFactions(); + } + + void SelectFaction(string? faction) + { + selectedFaction = faction; + highlightEntityId = null; + LoadGraph(); + } + + void OnSearchChanged() + { + if (string.IsNullOrWhiteSpace(searchText)) + { + highlightEntityId = null; + return; + } + + var found = currentGraph?.Nodes + .FirstOrDefault(n => n.Name.Contains(searchText, StringComparison.OrdinalIgnoreCase)); + + highlightEntityId = found?.Id; + } + + void ClearHighlight() + { + highlightEntityId = null; + searchText = ""; + } + + void OnEntitySelected(string entityId) + { + highlightEntityId = entityId; + } + + string GetFactionName(string factionId) + { + var entities = EntityData.Get(); + return entities.TryGetValue(factionId, out var entity) + ? entity.Info()?.Name ?? factionId + : factionId; + } + +} diff --git a/Pages/Utils/Interval.cs b/Pages/Utils/Interval.cs new file mode 100644 index 0000000..e211d2d --- /dev/null +++ b/Pages/Utils/Interval.cs @@ -0,0 +1,9 @@ +namespace IGP.Utils; + +public static class Interval +{ + public static string ToTime(int interval) + { + return TimeSpan.FromSeconds(interval).ToString(@"mm\:ss"); + } +} \ No newline at end of file diff --git a/Pages/Utils/Markdown.cs b/Pages/Utils/Markdown.cs new file mode 100644 index 0000000..53ff197 --- /dev/null +++ b/Pages/Utils/Markdown.cs @@ -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 LoadMarkdown(HttpClient httpClient, string filepath) + { + return await httpClient.GetStringAsync(filepath); + } + + public static async Task LoadFrontMatter(HttpClient httpClient, string filepath) + { + var markdown = await LoadMarkdown(httpClient, filepath); + + var document = Markdown.Parse(markdown, Pipeline); + + var block = document + .Descendants() + .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(yaml); + } +} \ No newline at end of file diff --git a/Pages/Utils/Project.cs b/Pages/Utils/Project.cs new file mode 100644 index 0000000..4f5ec91 --- /dev/null +++ b/Pages/Utils/Project.cs @@ -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/"; +} \ No newline at end of file diff --git a/Pages/_Imports.razor b/Pages/_Imports.razor new file mode 100644 index 0000000..860c0b0 --- /dev/null +++ b/Pages/_Imports.razor @@ -0,0 +1,56 @@ +@using Components.Display +@using Components.Feedback +@using Components.TechTree +@using Components.Form +@using Components.Info +@using Components.Inputs +@using Components.Layout +@using Components.Navigation +@using Components.Shared +@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.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.Glossary +@using Model.Hotkeys +@using Model.MemoryTester +@using Model.Notes +@using Model.RoadMap +@using Model.RoadMap.Enums +@using Model.TechTree +@using Model.Types +@using Model.Website +@using Services +@using Services.Immortal +@using Services.Website +@using System.Globalization +@using System.Reflection +@using System.Timers +@using MudBlazor + +@using IGP.Pages +@using IGP.Pages.BuildCalculator +@using IGP.Pages.BuildCalculator.Parts +@using IGP.Pages.BuildCalculator.Parts.Cosmetic +@using IGP.Pages.Database +@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 +@using IGP.Pages.MemoryTester.Parts +@using IGP.Pages.Notes +@using IGP.Pages.Notes.Parts +@using IGP.Utils diff --git a/Web/App.razor b/Web/App.razor index 1280714..2708d58 100644 --- a/Web/App.razor +++ b/Web/App.razor @@ -3,7 +3,8 @@ @inject IGlossaryDialogService GlossaryDialogService @inject IJSRuntime JsRuntime - + @if (isLoaded) {