export type UpgradeId = | 'moveSpeed' | 'chopSpeed' | 'logValue' | 'twigValue' | 'meatValue' | 'pickupRadius' | 'coinSpeed' | 'dayLength' | 'maxHealth' | 'attackDamage' | 'healthRegenSpeed'; export interface UpgradeDef { id: UpgradeId; name: string; baseCost: number; costGrowth: number; /** The upgrade's stat value at a given level (levels start at 1, unbounded above). */ effectAtLevel: (level: number) => number; formatEffect: (value: number, level: number) => string; } export const UPGRADE_LIST: UpgradeDef[] = [ { id: 'moveSpeed', name: 'Скорость движения', baseCost: 5, costGrowth: 1.35, effectAtLevel: (level) => 160 + (level - 1) * 20, formatEffect: (v) => `${Math.round(v)} px/с`, }, { id: 'chopSpeed', name: 'Скорость рубки', baseCost: 6, costGrowth: 1.35, effectAtLevel: (level) => (1 / 0.6) * Math.pow(1.15, level - 1), formatEffect: (v) => `${v.toFixed(2)} бревен/с`, }, { id: 'logValue', name: 'Стоимость бревна', baseCost: 8, costGrowth: 1.4, effectAtLevel: (level) => level, formatEffect: (v) => `${v} монет/бревно`, }, { id: 'twigValue', name: 'Стоимость ветки', baseCost: 5, costGrowth: 1.3, effectAtLevel: (level) => level * 0.1, formatEffect: (v) => `${v.toFixed(2)} монет/ветка`, }, { id: 'meatValue', name: 'Стоимость мяса', baseCost: 9, costGrowth: 1.4, effectAtLevel: (level) => level, formatEffect: (v) => `${v} монет/мясо`, }, { id: 'pickupRadius', name: 'Радиус подбора', baseCost: 5, costGrowth: 1.3, effectAtLevel: (level) => 46 + (level - 1) * 6, formatEffect: (v) => `${Math.round(v)} px`, }, { id: 'coinSpeed', name: 'Скорость обмена на монеты', baseCost: 7, costGrowth: 1.35, // Seconds to convert one unit of a resource into coins — every held resource type converts // on this same cadence at once, so this is the only knob for how fast any of them drain. effectAtLevel: (level) => 1 / Math.pow(1.15, level - 1), formatEffect: (v) => `${v.toFixed(2)} с`, }, { id: 'dayLength', name: 'Длина дня', baseCost: 6, costGrowth: 1.3, // Game-hours per real second during morning/day/evening (16 game-hours by default spread // over the default 10s day-phase of the 20s day-night cycle) — night has its own fixed speed. effectAtLevel: (level) => 16 * Math.pow(1.15, level - 1), formatEffect: (v) => `${v.toFixed(0)} с`, }, { id: 'maxHealth', name: 'Живучесть', baseCost: 10, costGrowth: 1.45, effectAtLevel: (level) => 4 + level, formatEffect: (v) => `${v} здоровья`, }, { id: 'attackDamage', name: 'Сила удара по кролику', baseCost: 8, costGrowth: 1.4, // Damage dealt per hit — the swing cadence itself comes from chopSpeed, shared with // wood-chopping, so this is purely how hard each swing lands. Health is allowed to be // fractional, so this is left unrounded. effectAtLevel: (level) => 0.5 * Math.pow(1.4, level - 1), formatEffect: (v) => `${v.toFixed(1)} здоровья`, }, { id: 'healthRegenSpeed', name: 'Скорость регена здоровья', baseCost: 9, costGrowth: 1.35, // Seconds per regenerated hp — starts at 1 hp per 10s and shrinks (faster regen) with level, // same interval-based shape as coinSpeed. effectAtLevel: (level) => 10 / Math.pow(1.15, level - 1), formatEffect: (v) => `${v.toFixed(1)} с/очко`, }, ]; export const UPGRADES: Record = Object.fromEntries( UPGRADE_LIST.map((def) => [def.id, def]), ) as Record; /** Cost (in coins) to go from `currentLevel` to `currentLevel + 1`. */ export function costForNextLevel(def: UpgradeDef, currentLevel: number): number { return Math.ceil(def.baseCost * Math.pow(def.costGrowth, currentLevel - 1)); }