98 lines
3.5 KiB
TypeScript
98 lines
3.5 KiB
TypeScript
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);
|
||
}
|