ProductionGame/frontend/js/ui/production/ratesCalculator.js

108 lines
4.2 KiB
JavaScript

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 prodRate = isBottlenecked ? 0 : (1 / baseCycleTime) * 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 };
}
/**
* 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
};
}