Game/WorkerType.cs

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.

File Access
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 )];
}