import { PRODUCTS, getNextMilestone, getUnitMilestoneMultiplier } from '../../data/products.js'; export function calculateMultiCost(baseCost, currentCount, multiplier) { const growth = 1.15; let countToBuy = 1; if (multiplier === '1') countToBuy = 1; else if (multiplier === '10') countToBuy = 10; else if (multiplier === '100') countToBuy = 100; else if (multiplier === 'next') { const nextTarget = getNextMilestone(currentCount); countToBuy = Math.max(1, nextTarget - currentCount); } else if (multiplier === 'max') { countToBuy = 1; } let totalCost = 0; for (let i = 0; i < countToBuy; i++) { totalCost += baseCost * Math.pow(growth, currentCount + i); } return { count: countToBuy, cost: totalCost }; } /** * Computes live individual factory output rates (units/s and wholesale revenue $/s) */ export function getProductOutputRates(p, pState, state) { if (!pState || !pState.unlocked || !pState.lines || pState.lines <= 0) { return { prodRate: 0, revRate: 0, isBottlenecked: false }; } const milestoneMult = getUnitMilestoneMultiplier(pState.lines); const roboticsMult = state.upgrades?.automated_robotics ? 2.0 : 1.0; const baseCycleTime = Math.max(0.1, p.baseCycleTime / (milestoneMult * roboticsMult)); const isBottlenecked = !!pState.isBottlenecked; // Output velocity: units per second const cyclesPerSec = (1 / baseCycleTime); const prodRate = isBottlenecked ? 0 : cyclesPerSec * p.outputAmount * pState.lines; // Logistics revenue rate: items sold passively per second * price const logisticsUnits = pState.logistics || 0; const isLogisticsActive = pState.logisticsEnabled !== false; let revRate = 0; if (isLogisticsActive && logisticsUnits > 0) { const logisticsPriceMult = state.upgrades?.global_supply_chain ? 1.5 : 1.0; const salePrice = p.basePrice * logisticsPriceMult; const maxSellCapacity = logisticsUnits * 0.75; // If stockpiled, sells at max fleet capacity; otherwise bottlenecked by production velocity const effectiveSellRate = Math.min(maxSellCapacity, pState.stock > 0 ? maxSellCapacity : prodRate); revRate = effectiveSellRate * salePrice; } return { prodRate, revRate, isBottlenecked, milestoneMult, baseCycleTime, cyclesPerSec }; } /** * Computes global net supply chain flow (Production - Downstream Manufacturing Consumption - Logistics Sales) */ export function computeProductNetFlow(product, state) { if (!state.products || !state.products[product.id]?.unlocked) { return { netRate: 0, prodRate: 0, consumedRate: 0, soldRate: 0 }; } const pState = state.products[product.id]; const { prodRate, isBottlenecked } = getProductOutputRates(product, pState, state); const effectiveProd = isBottlenecked ? 0 : prodRate; // Calculate how much active downstream factories consume per second let consumedRate = 0; const isSupplyDownstreamActive = pState.supplyDownstreamEnabled !== false; if (isSupplyDownstreamActive) { for (const otherP of PRODUCTS) { if (!otherP.inputs) continue; const inpMatch = otherP.inputs.find(inp => inp.id === product.id); if (inpMatch) { const otherState = state.products[otherP.id]; if (otherState && otherState.unlocked && otherState.lines > 0 && !otherState.isBottlenecked) { const otherRates = getProductOutputRates(otherP, otherState, state); const cyclesPerSec = otherRates.prodRate / (otherP.outputAmount * otherState.lines); consumedRate += inpMatch.amount * otherState.lines * cyclesPerSec; } } } } const logisticsUnits = pState.logistics || 0; const isLogisticsActive = pState.logisticsEnabled !== false; let soldRate = 0; if (isLogisticsActive && logisticsUnits > 0) { const maxSellCapacity = logisticsUnits * 0.75; const availableForSale = Math.max(0, effectiveProd - consumedRate); soldRate = Math.min(maxSellCapacity, pState.stock > 0 ? maxSellCapacity : availableForSale); } const netRate = effectiveProd - consumedRate - soldRate; return { prodRate: effectiveProd, consumedRate, soldRate, netRate }; }