@ -1,10 +1,10 @@
@layout PageLayout
@layout PageLayout
@inject IDataCollectionService DataCollectionService
@inject IDataCollectionService DataCollectionService
@using Model
@inherits BasePage
@inherits BasePage
@page "/harass-calculator"
@page "/harass-calculator"
@using Model
<LayoutMediumContentComponent>
<LayoutMediumContentComponent>
<WebsiteTitleComponent>Harass Calculator</WebsiteTitleComponent>
<WebsiteTitleComponent>Harass Calculator</WebsiteTitleComponent>
@ -18,15 +18,15 @@
<LayoutColumnComponent>
<LayoutColumnComponent>
<FormLayoutComponent>
<FormLayoutComponent>
<FormDisplayComponent Label="Cost of worker">
<FormDisplayComponent Label="Cost of worker">
<Display>@c ostOfWorker</Display>
<Display>@C ostOfWorker</Display>
</FormDisplayComponent>
</FormDisplayComponent>
<FormDisplayComponent Label="Alloy mined per second by worker">
<FormDisplayComponent Label="Alloy mined per second by worker">
<Display>@a lloyMinedPerSecondByWorker</Display>
<Display>@A lloyMinedPerSecondByWorker</Display>
</FormDisplayComponent>
</FormDisplayComponent>
<FormDisplayComponent Label="Time to produce worker">
<FormDisplayComponent Label="Time to produce worker">
<Display>@t imeToProduceWorker</Display>
<Display>@T imeToProduceWorker</Display>
</FormDisplayComponent>
</FormDisplayComponent>
</FormLayoutComponent>
</FormLayoutComponent>
</LayoutColumnComponent>
</LayoutColumnComponent>
@ -34,40 +34,45 @@
<LayoutColumnComponent>
<LayoutColumnComponent>
<FormLayoutComponent>
<FormLayoutComponent>
<FormNumberComponent Min="1"
<FormNumberComponent Min="1"
Value="@((int)numberOfWorkersLostToHarass)"
Id="numberOfWorkersLostToHarass"
OnChange="@(e => { numberOfWorkersLostToHarass = int.Parse(e.Value!.ToString()!); Calculate();})">
Value="@((int)NumberOfWorkersLostToHarass)"
OnChange="@(e => { NumberOfWorkersLostToHarass = int.Parse(e.Value!.ToString()!); Calculate();})">
<FormLabelComponent>Number of workers lost to harass</FormLabelComponent>
<FormLabelComponent>Number of workers lost to harass</FormLabelComponent>
</FormNumberComponent>
</FormNumberComponent>
<FormNumberComponent Min="1"
<FormNumberComponent Min="1"
Value="@((int)numberOfTownHallsExisting)"
Id="numberOfTownHallsExisting"
Value="@((int)NumberOfTownHallsExisting)"
OnChange="OnTownHallsChanged">
OnChange="OnTownHallsChanged">
<FormLabelComponent>Number of townhalls you have</FormLabelComponent>
<FormLabelComponent>Number of townhalls you have</FormLabelComponent>
</FormNumberComponent>
</FormNumberComponent>
<div id="numberOfTownHallTravelTimes">
@{
@{
var index = 0;
var index = 0;
}
}
@foreach (var travelTime in TravelTimes)
@foreach (var travelTime in TravelTimes) {
{
index++;
index++;
if (index == 1)
if (index == 1) {
{
continue;
continue;
}
}
var id = $"numberOfTownHallsExisting_{index}";
<FormNumberComponent Min="0"
<FormNumberComponent Min="0"
Value="@(travelTime.Value)"
Id="@id"
Value="@((int)travelTime.Value)"
OnChange="e => { OnTownHallTravelTimeChanged(e, travelTime); }">
OnChange="e => { OnTownHallTravelTimeChanged(e, travelTime); }">
<FormLabelComponent>Worker travel time from other base @(travelTime.Index + 1)</FormLabelComponent>
<FormLabelComponent>Worker travel time from other base @(travelTime.Index + 1)</FormLabelComponent>
</FormNumberComponent>
</FormNumberComponent>
}
}
</div>
<FormDisplayComponent Label="Total alloy lost">
<FormDisplayComponent Label="Total alloy lost">
<Display>
<Display>
<div style="font-size: 1.5rem; font-weight: 800;">
<div style="font-size: 1.5rem; font-weight: 800;">
@totalAlloyHarassment
<span id="totalAlloyHarassment">
@TotalAlloyHarassment
</span>
</div>
</div>
</Display>
</Display>
</FormDisplayComponent>
</FormDisplayComponent>
@ -77,19 +82,16 @@
<br/>
<br/>
<div>
<div>
(<b>Worker replacement costs:</b> @WorkerReplacementCost())
(<b>Worker replacement costs:</b> <span id="workerReplacementCost"> @WorkerReplacementCost()</span> )
</div>
</div>
<div>
<div>
(<b>Delayed mining time:</b> @DelayedMiningCost())
(<b>Delayed mining time:</b> <span id="delayedMiningCost"> @DelayedMiningCost()</span> )
</div>
</div>
<div>
<div>
(<b>Average travel time:</b> @GetAverageTravelTime())
(<b>Average travel time:</b> <span id="getAverageTravelTime"> @GetAverageTravelTime()</span> )
</div>
</div>
</LayoutColumnComponent>
</LayoutColumnComponent>
</LayoutRowComponent>
</LayoutRowComponent>
</PaperComponent>
</PaperComponent>
@ -101,7 +103,10 @@
What is this tool?
What is this tool?
</InfoQuestionComponent>
</InfoQuestionComponent>
<InfoAnswerComponent>
<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 741 alloy. Quite the large number.
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>
</InfoAnswerComponent>
</InfoBodyComponent>
</InfoBodyComponent>
@ -110,12 +115,21 @@
What can I learn from this?
What can I learn from this?
</InfoQuestionComponent>
</InfoQuestionComponent>
<InfoAnswerComponent>
<InfoAnswerComponent>
Well, let's assume you lost a full alloy line of workers, and have to take that 741 alloy cost (300 to rebuy the workers, and 441 in lost mining time.)
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/>
<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 315 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.
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/>
<br/><br/>
Let's say it takes 10 seconds for workers to transfer from your second base. We can divide that number by 2, to represent our bases, and add those 5 additional seconds to <b>Worker travel time to alloy</b>, for the more accurate loss of 456 alloy (saving you 285 alloy.) <i>Which is much better than not transferring workers!</i>
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>
</InfoAnswerComponent>
</InfoBodyComponent>
</InfoBodyComponent>
@ -194,101 +208,137 @@
</style>
</style>
@code {
@code {
float totalAlloyHarassment = 0;
// Example calcs
float ExampleTotalAlloyLoss => Calculate(
readonly float costOfWorker = 50;
WorkerReplacementCost(6),
readonly float alloyMinedPerSecondByWorker = 1;
SimultaneousProductionFloor(1,6),
readonly float timeToProduceWorker = 20;
6,
float numberOfWorkersLostToHarass = 1;
new List<float> { 0 });
float numberOfTownHallsExisting = 1;
float ExampleWorkerCost => WorkerReplacementCost(6);
float GetAverageTravelTime()
{
float ExampleMiningTimeCost => ExampleTotalAlloyLoss - ExampleWorkerCost;
if (TravelTimes.Count == 0)
{
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;
return 0;
}
}
var sum = 0;
float sum = 0;
foreach (var travelTime in TravelTimes)
foreach (var travelTime in TravelTimes) {
{
sum += travelTime.Value;
sum += travelTime.Value;
}
}
return sum / numberOfTownHallsExisting;
return sum / NumberOfTownHallsExisting;
}
float SimultaneousProductionFloor() {
if (NumberOfTownHallsExisting <= 0 || NumberOfWorkersLostToHarass <= 0) {
return 0;
}
}
return NumberOfWorkersLostToHarass / Math.Min(NumberOfTownHallsExisting, NumberOfWorkersLostToHarass);
}
float SimultaneousProductionFloor()
float SimultaneousProductionFloor(float existingTownHalls, float numberOfWorkersLost) {
{
if (existingTownHalls <= 0 || numberOfWorkersLost <= 0) {
if (numberOfTownHallsExisting <= 0 || numberOfWorkersLostToHarass <= 0)
{
return 0;
return 0;
}
}
return numberOfWorkersLostToHarass / Math.Min(numberOfTownHallsExisting, numberOfWorkersLostToHarass);
return numberOfWorkersLost / Math.Min(existingTownHalls, numberOfWorkersLost );
}
}
float WorkerReplacementCost()
float WorkerReplacementCost() {
{
return CostOfWorker * NumberOfWorkersLostToHarass;
return costOfWorker * numberOfWorkersLostToHarass;
}
}
float DelayedMiningCost()
float WorkerReplacementCost(int numberOfWorkersLostToHarass) {
{
return CostOfWorker * numberOfWorkersLostToHarass;
return totalAlloyHarassment - WorkerReplacementCost();
}
}
void Calculate()
float DelayedMiningCost() {
{
return TotalAlloyHarassment - WorkerReplacementCost();
totalAlloyHarassment = WorkerReplacementCost();
}
for (var workerProductionIndex = 0; workerProductionIndex < SimultaneousProductionFloor(); workerProductionIndex++)
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);
totalAlloyHarassment += alloyMinedPerSecondByWorker * timeToProduceWorker * (workerProductionIndex + 1);
}
}
var remainder = (int)(numberOfWorkersLostToHarass % SimultaneousProductionFloor());
var remainder = (int)(numberOfWorkersLostToHarass % simultaneousProductionFloor);
for (var remainderIndex = 0; remainderIndex < remainder; remainderIndex++)
for (var remainderIndex = 0; remainderIndex < remainder; remainderIndex++) {
{
totalAlloyHarassment += alloyMinedPerSecondByWorker * timeToProduceWorker * (simultaneousProductionFloor + 1);
totalAlloyHarassment += alloyMinedPerSecondByWorker * timeToProduceWorker * (SimultaneousProductionFloor() + 1);
}
}
for (var travelTimeIndex = 0; travelTimeIndex < numberOfWorkersLostToHarass; travelTimeIndex++)
for (var travelTimeIndex = 0; travelTimeIndex < numberOfWorkersLostToHarass; travelTimeIndex++) {
{
var townHallIndex = travelTimeIndex % travelTimes.Count;
var townHallIndex = travelTimeIndex % TravelTimes.Count;
totalAlloyHarassment += alloyMinedPerSecondByWorker * travelTimes[townHallIndex];
totalAlloyHarassment += alloyMinedPerSecondByWorker * TravelTimes[townHallIndex].Value;
}
}
return totalAlloyHarassment;
}
}
protected override void OnInitialized()
protected override void OnInitialized() {
{
base.OnInitialized();
base.OnInitialized();
Calculate();
Calculate();
}
}
void ValueChanged(float test)
{
Calculate();
}
public List<TravelTime> TravelTimes { get; set; } = new() { new TravelTime(0, 0) };
public List<TravelTime> TravelTimes { get; set; } = new() { new TravelTime(0, 0) };
private void OnTownHallsChanged(ChangeEventArgs obj)
private void OnTownHallsChanged(ChangeEventArgs obj) {
{
NumberOfTownHallsExisting = int.Parse(obj.Value!.ToString()!);
numberOfTownHallsExisting = int.Parse(obj.Value!.ToString()!);
while (TravelTimes.Count > n umberOfTownHallsExisting)
while (TravelTimes.Count > N umberOfTownHallsExisting)
TravelTimes.Remove(TravelTimes.Last());
TravelTimes.Remove(TravelTimes.Last());
while (TravelTimes.Count < n umberOfTownHallsExisting)
while (TravelTimes.Count < N umberOfTownHallsExisting)
TravelTimes.Add(new TravelTime(TravelTimes.Count, 0));
TravelTimes.Add(new TravelTime(TravelTimes.Count, 1 0 * (TravelTimes.Count) ));
Calculate();
Calculate();
}
}
private void OnTownHallTravelTimeChanged(ChangeEventArgs obj, TravelTime travelTime)
private void OnTownHallTravelTimeChanged(ChangeEventArgs obj, TravelTime travelTime) {
{
travelTime.Value = (int)obj.Value!;
travelTime.Value = (int)obj.Value!;
Calculate();
Calculate();