You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
351 lines
14 KiB
351 lines
14 KiB
@layout PageLayout |
|
|
|
@inject IDataCollectionService DataCollectionService |
|
@using Model |
|
@inherits BasePage |
|
|
|
@page "/harass-calculator" |
|
|
|
<LayoutMediumContentComponent> |
|
<WebsiteTitleComponent>Harass Calculator</WebsiteTitleComponent> |
|
|
|
<PaperComponent> |
|
Credit to Zard for deriving the formula. |
|
</PaperComponent> |
|
|
|
<PaperComponent> |
|
<LayoutRowComponent> |
|
<LayoutColumnComponent> |
|
<FormLayoutComponent> |
|
<FormDisplayComponent Label="Cost of worker"> |
|
<Display>@CostOfWorker</Display> |
|
</FormDisplayComponent> |
|
|
|
<FormDisplayComponent Label="Alloy mined per second by worker"> |
|
<Display>@AlloyMinedPerSecondByWorker</Display> |
|
</FormDisplayComponent> |
|
|
|
<FormDisplayComponent Label="Time to produce worker"> |
|
<Display>@TimeToProduceWorker</Display> |
|
</FormDisplayComponent> |
|
</FormLayoutComponent> |
|
</LayoutColumnComponent> |
|
|
|
<LayoutColumnComponent> |
|
<FormLayoutComponent> |
|
<FormNumberComponent Min="1" |
|
Id="numberOfWorkersLostToHarass" |
|
Value="@((int)NumberOfWorkersLostToHarass)" |
|
OnChange="@(e => { NumberOfWorkersLostToHarass = int.Parse(e.Value!.ToString()!); Calculate();})"> |
|
<FormLabelComponent>Number of workers lost to harass</FormLabelComponent> |
|
</FormNumberComponent> |
|
|
|
<FormNumberComponent Min="1" |
|
Id="numberOfTownHallsExisting" |
|
Value="@((int)NumberOfTownHallsExisting)" |
|
OnChange="OnTownHallsChanged"> |
|
<FormLabelComponent>Number of townhalls you have</FormLabelComponent> |
|
</FormNumberComponent> |
|
|
|
<div id="numberOfTownHallTravelTimes"> |
|
@{ |
|
var index = 0; |
|
} |
|
@foreach (var travelTime in TravelTimes) { |
|
index++; |
|
if (index == 1) { |
|
continue; |
|
} |
|
var id = $"numberOfTownHallsExisting_{index}"; |
|
<FormNumberComponent Min="0" |
|
Id="@id" |
|
Value="@((int)travelTime.Value)" |
|
OnChange="e => { OnTownHallTravelTimeChanged(e, travelTime); }"> |
|
<FormLabelComponent>Worker travel time from other base @(travelTime.Index + 1)</FormLabelComponent> |
|
</FormNumberComponent> |
|
} |
|
</div> |
|
|
|
|
|
<FormDisplayComponent Label="Total alloy lost"> |
|
<Display> |
|
<div style="font-size: 1.5rem; font-weight: 800;"> |
|
<span id="totalAlloyHarassment"> |
|
@TotalAlloyHarassment |
|
</span> |
|
</div> |
|
</Display> |
|
</FormDisplayComponent> |
|
|
|
|
|
</FormLayoutComponent> |
|
|
|
<br/> |
|
<div> |
|
(<b>Worker replacement costs:</b> <span id="workerReplacementCost">@WorkerReplacementCost()</span>) |
|
</div> |
|
<div> |
|
(<b>Delayed mining time:</b> <span id="delayedMiningCost">@DelayedMiningCost()</span>) |
|
</div> |
|
<div> |
|
(<b>Average travel time:</b> <span id="getAverageTravelTime">@GetAverageTravelTime()</span>) |
|
</div> |
|
|
|
</LayoutColumnComponent> |
|
</LayoutRowComponent> |
|
</PaperComponent> |
|
|
|
<ContentDividerComponent></ContentDividerComponent> |
|
|
|
<PaperComponent> |
|
<InfoBodyComponent> |
|
<InfoQuestionComponent> |
|
What is this tool? |
|
</InfoQuestionComponent> |
|
<InfoAnswerComponent> |
|
The Harass Calculator allows you to calculate damage done to an enemy alloy line. For example, if you |
|
were to attack with Ichors, and kill 6 enemy workers, you can set the <b>Number of workers lost to |
|
harass</b> to 6. This would determine a loss of <span id="exampleTotalAlloyLoss">@ExampleTotalAlloyLoss</span> alloy. Quite |
|
the large number. |
|
</InfoAnswerComponent> |
|
</InfoBodyComponent> |
|
|
|
<InfoBodyComponent> |
|
<InfoQuestionComponent> |
|
What can I learn from this? |
|
</InfoQuestionComponent> |
|
<InfoAnswerComponent> |
|
Well, let's assume you lost a full alloy line of workers, and have to take that |
|
@ExampleTotalAlloyLoss alloy cost (<span id="exampleWorkerCost">@ExampleWorkerCost</span> |
|
to rebuy the workers, and <span id="exampleMiningTimeCost">@ExampleMiningTimeCost</span> in lost mining |
|
time.) |
|
<br/><br/> |
|
If you were to set the <b>Number of townhalls you have</b> to 2, the calculator will consider worker |
|
transfer micro. Allowing you to cut the total cost by roughly |
|
<span id="exampleTotalAlloyLossDifference">@ExampleTotalAlloyLossDifference</span> alloy. However, that number isn't |
|
entirely accurate, you are also going to have to bump up the <b>Worker travel time to alloy</b> to account for the time it takes the transferred workers to arrive at the decimated alloy line. |
|
<br/><br/> |
|
Let's say it takes 10 seconds for workers to transfer from your second base. Let's enter that for the |
|
second base travel time for the more accurate loss of <span |
|
id="exampleTotalAlloyLossAccurate">@ExampleTotalAlloyLossAccurate</span> alloy |
|
(saving you <span id="exampleTotalAlloyLossAccurateDifference">@ExampleTotalAlloyLossAccurateDifference</span> alloy.) <i>Which is |
|
much better than not transferring workers!</i> |
|
</InfoAnswerComponent> |
|
</InfoBodyComponent> |
|
|
|
<InfoBodyComponent> |
|
<InfoQuestionComponent> |
|
Can I see the formula for the calculation? |
|
</InfoQuestionComponent> |
|
<InfoAnswerComponent> |
|
The Harass Calculator is based on the following calculation. |
|
|
|
<br/> |
|
<div class="mathContainer"> |
|
<div> =c*m+r*g*(t+l) + </div> |
|
|
|
<MathLoopSumComponent> |
|
<LoopStart><i>x</i> =1</LoopStart> |
|
<LoopEnd> |
|
⌊ |
|
<MathDivisionComponent> |
|
<Dividee>m</Dividee><Divider>a</Divider> |
|
</MathDivisionComponent>⌋ |
|
</LoopEnd> |
|
<IndexSymbol> |
|
<i>x</i> |
|
</IndexSymbol> |
|
</MathLoopSumComponent> |
|
|
|
<div style="width: 132px">g*<i>x</i>*(t+l)</div> |
|
</div> |
|
<br/> |
|
<div style="font-family:monospace;"> |
|
<div>c is CostOfWorker </div> |
|
<div>m is NumberOfWorkersLostToHarass, <i>m for [M]otes</i></div> |
|
<div>a is NumberOfTownHallsExisting, <i>a for [A]cropolis</i></div> |
|
<div>r is m mod a is LeftOverWorkersToProduceCount()</div> |
|
<div>g is AlloyMinedPerSecondByWorker</div> |
|
<div>t is TimeToProduceWorker</div> |
|
<div>l is TravelTime</div> |
|
<div><i>x</i> is workerProductionIndex</div> |
|
</div> |
|
<br/> |
|
This logic has since been changed slightly to allow client to enter different travel times per base. |
|
<br/> |
|
</InfoAnswerComponent> |
|
</InfoBodyComponent> |
|
<InfoBodyComponent> |
|
<InfoQuestionComponent> |
|
Can I see the code for the calculation? |
|
</InfoQuestionComponent> |
|
<InfoAnswerComponent> |
|
<br/> |
|
<LinkButtonComponent Href="https://github.com/JonathanMcCaffrey/IGP-Fan-Reference/blob/main/IGP/Pages/HarassCalculatorPage.razor"> |
|
View on GitHub <i class="fa-brands fa-github" style="font-size: 24px; margin-left: 5px;"></i> |
|
</LinkButtonComponent> |
|
</InfoAnswerComponent> |
|
</InfoBodyComponent> |
|
</PaperComponent> |
|
|
|
|
|
</LayoutMediumContentComponent> |
|
|
|
<style> |
|
.mathContainer { |
|
font-family: monospace; |
|
display: flex; |
|
flex-direction: row; |
|
align-items: flex-start; |
|
border: 1px black solid; |
|
padding: 32px; |
|
background-color: #1A1B1E |
|
} |
|
|
|
@@media only screen and (max-width: 1025px) { |
|
.mathContainer { |
|
padding-left: 2px; |
|
padding-right: 2px; |
|
} |
|
} |
|
</style> |
|
|
|
@code { |
|
// Example calcs |
|
float ExampleTotalAlloyLoss => Calculate( |
|
WorkerReplacementCost(6), |
|
SimultaneousProductionFloor(1,6), |
|
6, |
|
new List<float> { 0 }); |
|
|
|
float ExampleWorkerCost => WorkerReplacementCost(6); |
|
|
|
float ExampleMiningTimeCost => ExampleTotalAlloyLoss - ExampleWorkerCost; |
|
|
|
float ExampleTotalAlloyLossDifference => ExampleTotalAlloyLoss - Calculate( |
|
WorkerReplacementCost(6), |
|
SimultaneousProductionFloor(2,6), |
|
6, |
|
new List<float> { 0, 0 }); |
|
|
|
float ExampleTotalAlloyLossAccurate => Calculate( |
|
WorkerReplacementCost(6), |
|
SimultaneousProductionFloor(2,6), |
|
6, |
|
new List<float> { 0, 10 }); |
|
|
|
float ExampleTotalAlloyLossAccurateDifference => ExampleTotalAlloyLoss - ExampleTotalAlloyLossAccurate; |
|
|
|
float TotalAlloyHarassment = 0; |
|
|
|
readonly float CostOfWorker = 50; |
|
readonly float AlloyMinedPerSecondByWorker = 1; |
|
readonly float TimeToProduceWorker = 20; |
|
float NumberOfWorkersLostToHarass = 1; |
|
float NumberOfTownHallsExisting = 1; |
|
|
|
float GetAverageTravelTime() { |
|
if (TravelTimes.Count == 0) { |
|
return 0; |
|
} |
|
|
|
float sum = 0; |
|
|
|
foreach (var travelTime in TravelTimes) { |
|
sum += travelTime.Value; |
|
} |
|
|
|
return sum / NumberOfTownHallsExisting; |
|
} |
|
|
|
|
|
float SimultaneousProductionFloor() { |
|
if (NumberOfTownHallsExisting <= 0 || NumberOfWorkersLostToHarass <= 0) { |
|
return 0; |
|
} |
|
|
|
return NumberOfWorkersLostToHarass / Math.Min(NumberOfTownHallsExisting, NumberOfWorkersLostToHarass); |
|
} |
|
|
|
float SimultaneousProductionFloor(float existingTownHalls, float numberOfWorkersLost) { |
|
if (existingTownHalls <= 0 || numberOfWorkersLost <= 0) { |
|
return 0; |
|
} |
|
|
|
return numberOfWorkersLost / Math.Min(existingTownHalls, numberOfWorkersLost); |
|
} |
|
|
|
float WorkerReplacementCost() { |
|
return CostOfWorker * NumberOfWorkersLostToHarass; |
|
} |
|
|
|
float WorkerReplacementCost(int numberOfWorkersLostToHarass) { |
|
return CostOfWorker * numberOfWorkersLostToHarass; |
|
} |
|
|
|
float DelayedMiningCost() { |
|
return TotalAlloyHarassment - WorkerReplacementCost(); |
|
} |
|
|
|
void Calculate() { |
|
TotalAlloyHarassment = Calculate(WorkerReplacementCost(), |
|
SimultaneousProductionFloor(), |
|
NumberOfWorkersLostToHarass, |
|
TravelTimes.Select(x => x.Value).ToList(), |
|
TimeToProduceWorker, |
|
AlloyMinedPerSecondByWorker); |
|
} |
|
|
|
float Calculate(float workerReplacementCost, |
|
float simultaneousProductionFloor, |
|
float numberOfWorkersLostToHarass, |
|
IList<float> travelTimes, |
|
float timeToProduceWorker = 20, |
|
float alloyMinedPerSecondByWorker = 1) { |
|
|
|
float totalAlloyHarassment = workerReplacementCost; |
|
|
|
for (var workerProductionIndex = 0; workerProductionIndex < simultaneousProductionFloor; workerProductionIndex++) { |
|
totalAlloyHarassment += alloyMinedPerSecondByWorker * timeToProduceWorker * (workerProductionIndex + 1); |
|
} |
|
|
|
var remainder = (int)(numberOfWorkersLostToHarass % simultaneousProductionFloor); |
|
for (var remainderIndex = 0; remainderIndex < remainder; remainderIndex++) { |
|
totalAlloyHarassment += alloyMinedPerSecondByWorker * timeToProduceWorker * (simultaneousProductionFloor + 1); |
|
} |
|
|
|
for (var travelTimeIndex = 0; travelTimeIndex < numberOfWorkersLostToHarass; travelTimeIndex++) { |
|
var townHallIndex = travelTimeIndex % travelTimes.Count; |
|
totalAlloyHarassment += alloyMinedPerSecondByWorker * travelTimes[townHallIndex]; |
|
} |
|
|
|
return totalAlloyHarassment; |
|
} |
|
|
|
protected override void OnInitialized() { |
|
base.OnInitialized(); |
|
Calculate(); |
|
} |
|
|
|
|
|
public List<TravelTime> TravelTimes { get; set; } = new() { new TravelTime(0, 0) }; |
|
|
|
private void OnTownHallsChanged(ChangeEventArgs obj) { |
|
NumberOfTownHallsExisting = int.Parse(obj.Value!.ToString()!); |
|
|
|
while (TravelTimes.Count > NumberOfTownHallsExisting) |
|
TravelTimes.Remove(TravelTimes.Last()); |
|
|
|
while (TravelTimes.Count < NumberOfTownHallsExisting) |
|
TravelTimes.Add(new TravelTime(TravelTimes.Count, 10 * (TravelTimes.Count))); |
|
Calculate(); |
|
} |
|
|
|
private void OnTownHallTravelTimeChanged(ChangeEventArgs obj, TravelTime travelTime) { |
|
travelTime.Value = (int)obj.Value!; |
|
|
|
Calculate(); |
|
StateHasChanged(); |
|
} |
|
|
|
} |