119 lines
5 KiB
JavaScript
119 lines
5 KiB
JavaScript
import { PRODUCTS, getNextMilestone, getUnitMilestoneMultiplier } from '../../data/products.js';
|
|
|
|
export function calculateMultiCost(baseCost, currentCount, multiplier, discount = 0.0) {
|
|
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;
|
|
const factor = Math.max(0.5, 1.0 - discount);
|
|
for (let i = 0; i < countToBuy; i++) {
|
|
totalCost += baseCost * Math.pow(growth, currentCount + i) * factor;
|
|
}
|
|
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 perks = state.boardroomPerks || {};
|
|
const repAlloc = state.repAllocations || {};
|
|
const brandRep = repAlloc.brand || 0;
|
|
const clientsRep = repAlloc.clients || 0;
|
|
|
|
const roboticsSpeedMult = (1.0 + 0.20 * (perks['robotics_overclock'] || 0)) * (1.0 + 0.0015 * clientsRep);
|
|
const baseCycleTime = Math.max(0.05, p.baseCycleTime / (milestoneMult * roboticsSpeedMult));
|
|
const targetThrottle = pState.productionTarget !== undefined ? pState.productionTarget : 1.0;
|
|
const isBottlenecked = !!pState.isBottlenecked || targetThrottle <= 0;
|
|
|
|
// Output velocity: units per second
|
|
const isRaw = !p.inputs || p.inputs.length === 0;
|
|
const supplyYieldMult = isRaw ? (1.0 + 0.15 * (perks['supply_sync'] || 0)) * (1.0 + 0.002 * clientsRep) : 1.0;
|
|
const cyclesPerSec = (1 / baseCycleTime) * targetThrottle;
|
|
const prodRate = isBottlenecked ? 0 : cyclesPerSec * p.outputAmount * pState.lines * supplyYieldMult;
|
|
|
|
// 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 = (1.0 + 0.25 * (perks['logistics_efficiency'] || 0)) * (1.0 + 0.0025 * brandRep);
|
|
const logisticsThroughputMult = 1.0 + 0.0025 * clientsRep;
|
|
const salePrice = p.basePrice * logisticsPriceMult;
|
|
const maxSellCapacity = logisticsUnits * 0.75 * logisticsThroughputMult;
|
|
// 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
|
|
};
|
|
}
|
|
|
|
|