Defines WorkerType, a sealed data class describing types of hired workers in the game. It holds index, name, description, icon, base cost and per-second output, provides formulas for cost and output at a given level, a hardcoded roster of 15 worker archetypes, and builds a static All array with scaled BaseCost and PerSecond values.
namespace DesertPump;
/// <summary>
/// Somebody you hire to pump for you. Each level adds a flat litres-per-second on top
/// of whatever the pump itself makes, so workers are the hands-off half of the economy.
/// </summary>
/// <remarks>
/// The fifteen are spaced about seven pumps apart. Output steps 89x per worker, which
/// is what seven pumps produce (1.9^7), and cost steps 500x - which is that output
/// times the 5.6x a litre gains in value over the same stretch (1.28^7). Matching cost
/// to the *coins value* of the output is what keeps every worker paying for itself in
/// about half an hour. An earlier pass stepped cost at 1300x to match what seven pumps
/// cost, and the fifteenth worker took three thousand days to break even.
/// </remarks>
public sealed class WorkerType
{
/// <summary>0-14, and the index into the save's level array.</summary>
public int Index { get; init; }
public string Name { get; init; }
public string Description { get; init; }
/// <summary>Material icon name.</summary>
public string Icon { get; init; }
/// <summary>Cost of the first level - hiring them.</summary>
public double BaseCost { get; init; }
/// <summary>Litres per second each level adds.</summary>
public double PerSecond { get; init; }
/// <summary>Each level costs this much more than the last.</summary>
public const double CostGrowth = 1.15;
public const int MaxLevel = 50;
/// <summary>What the next level costs, given how many you already have.</summary>
public double CostAt( int level ) => BaseCost * Math.Pow( CostGrowth, level );
/// <summary>Litres per second this worker contributes at a given level.</summary>
public double OutputAt( int level ) => PerSecond * level;
// The two ratios that space the roster across the whole game.
const double CostStep = 500;
const double OutputStep = 89;
static readonly (string Name, string Icon, string Description)[] Roster =
{
("Bucket Carrier", "person", "Two buckets, no complaints."),
("Well Digger", "construction", "Goes down until the sand turns wet."),
("Donkey Cart", "agriculture", "Slow, stubborn, carries more than you'd think."),
("Windmill Hand", "wind_power", "Keeps the sails turned into the breeze."),
("Steam Stoker", "local_fire_department", "Feeds the boiler so the pressure never drops."),
("Pipe Fitter", "plumbing", "Every joint they touch stops leaking."),
("Rig Crew", "engineering", "Six people who can run a derrick in the dark."),
("Hydrologist", "science", "Knows where the water is before you dig."),
("Drill Foreman", "hardware", "Keeps three crews on shift around the clock."),
("Aquifer Surveyor", "travel_explore", "Maps the water nobody else has found yet."),
("Storm Chaser", "thunderstorm", "Drives toward the rain, not away from it."),
("Ice Cutter", "ac_unit", "Quarries the glacier a block at a time."),
("Desalination Tech", "water_damage", "Turns the sea into something drinkable."),
("Sky Engineer", "rocket_launch", "Maintains the condensers in the upper air."),
("Void Diver", "blur_on", "Goes where the water comes from. Comes back wet."),
};
/// <summary>Re-read on hotload so balance edits apply without restarting - see PumpModel.All.</summary>
[SkipHotload]
public static readonly WorkerType[] All = Build();
static WorkerType[] Build()
{
var list = new WorkerType[Roster.Length];
for ( var i = 0; i < Roster.Length; i++ )
{
var (name, icon, description) = Roster[i];
list[i] = new WorkerType
{
Index = i,
Name = name,
Icon = icon,
Description = description,
BaseCost = 60 * Math.Pow( CostStep, i ),
PerSecond = 0.03 * Math.Pow( OutputStep, i )
};
}
return list;
}
public static int Count => All.Length;
public static WorkerType Get( int index ) => All[Math.Clamp( index, 0, Count - 1 )];
}