import type { GameConfig, Location, ShipModel } from "@deadhead/content"; /** * The distance pay and fuel are charged on (decision 0.5.84): the route distance * scaled down for long legs, `reference × (distance / reference)^exponent`. * A leg of the reference length (Earth–Moon) is unchanged; an exponent of 1 * makes it the plain distance. Travel time and XP use the plain distance. */ export function economicDistance(distanceMillionKm: number, config: GameConfig): number { const { referenceMillionKm, exponent } = config.economicDistance; return referenceMillionKm * (distanceMillionKm / referenceMillionKm) ** exponent; } /** The plain distance whose economic distance is `economic` (the inverse of the curve). */ export function plainDistance(economic: number, config: GameConfig): number { const { referenceMillionKm, exponent } = config.economicDistance; return referenceMillionKm * (economic / referenceMillionKm) ** (1 / exponent); } /** * Fuel a leg burns, in fu (decision 0.5.95): burn rate × the economic distance * of the leg shortened by the origin's range factor, so launches from * low-gravity bodies burn less. */ export function legFuel( ship: ShipModel, origin: Location, distanceMillionKm: number, config: GameConfig, ): number { return ( ship.burnFuPerMillionKm * economicDistance(distanceMillionKm / origin.launchRangeFactor, config) ); } /** * How far a full tank flies from an origin, in million km (decisions 0.5.78, * 0.5.95): the plain distance whose leg fuel fills the tank. */ export function fullTankReach(ship: ShipModel, origin: Location, config: GameConfig): number { return ( plainDistance(ship.tankCapacityFu / ship.burnFuPerMillionKm, config) * origin.launchRangeFactor ); } /** A location's fuel price in credits per fu (decision 0.5.96). */ export function fuelPriceAt(location: Location, config: GameConfig): number { return config.baseFuelPricePerFu * location.fuelPrice; } /** What a ship's tank holds (decision 0.5.94): fuel, and its average purchase price. */ export interface Tank { fuelFu: number; /** Credits per fu, weighted by how much was bought at each price (decision 0.5.98). */ avgPrice: number; } export interface Refuel { tank: Tank; boughtFu: number; /** Whole credits. */ cost: number; } /** * Adds up to `amountFu` at `price`, never past `capacityFu` (decision 0.5.97), * and keeps the tank's weighted average price. */ export function refuel(tank: Tank, capacityFu: number, amountFu: number, price: number): Refuel { const boughtFu = Math.max(0, Math.min(amountFu, capacityFu - tank.fuelFu)); const fuelFu = tank.fuelFu + boughtFu; const avgPrice = fuelFu > 0 ? (tank.fuelFu * tank.avgPrice + boughtFu * price) / fuelFu : tank.avgPrice; return { tank: { fuelFu, avgPrice }, boughtFu, cost: Math.min(Math.round(boughtFu * price), Math.ceil(boughtFu * price)), }; } /** The most fuel up to a full tank that `credits` can pay for (Auto top-up, decision 0.5.97). */ export function affordableRefuel( tank: Tank, capacityFu: number, price: number, credits: number, ): Refuel { const affordable = Math.max(0, credits) / price; const result = refuel(tank, capacityFu, affordable, price); // Rounding never charges more than the player has. return { ...result, cost: Math.min(result.cost, Math.max(0, Math.floor(credits))) }; } /** What a leg's fuel cost the player (decision 0.5.98): fu burned × the tank's average price. */ export function legFuelCost(burnedFu: number, tank: Tank): number { return Math.round(burnedFu * tank.avgPrice); }