@layout PageLayout @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 ma 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 on GitHub
@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 = 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 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 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(); } }