#include "game/sim/economy.h" #include #include #include "game/sim/numeric.h" namespace sots::sim { int SavingsInterest(int savings, bool ownsSystems) { if (savings < 0 || !ownsSystems) return 0; return Ftol(static_cast(savings) * 0.01); } int DebtInterest(int savings) { if (savings >= 0) return 0; return Ftol(-static_cast(savings) * 0.15); } int MaintenanceCost(int maintenance, double difficultyDivisor) { const int d = Ftol(difficultyDivisor); if (d == 0) return maintenance; return maintenance / d; } int ExpenseTotal(const std::vector& sliders, int availableBeforeExpenses) { std::int64_t minimums = 0; std::int64_t extras = 0; for (const ExpenseSlider& s : sliders) { minimums += s.minimum; const int span = std::max(0, s.maximum - s.minimum); extras += ClampT(s.requested - s.minimum, 0, span); } const std::int64_t headroom = static_cast(availableBeforeExpenses) - minimums; const std::int64_t total = minimums + std::min(extras, headroom); return static_cast(ClampT(total, -2147483648LL, 2147483647LL)); } int ResearchPointsFromMoney(int researchMoney, double difficultyMult, double resMod, double shrm, double trm, double techMult, double serverResMod, double resScl) { const double base = (static_cast(researchMoney) / 50.0) * 1.15 * 0.5 * 0.85; const double rp = difficultyMult * base * (resMod + shrm + trm) * techMult * serverResMod * resScl; return Ftol(rp); } Budget ComputeBudget(const BudgetInputs& in, bool projected) { Budget b; b.savingsInterest = SavingsInterest(in.savings, in.ownsSystems); b.debtInterest = DebtInterest(in.savings); for (int inc : in.systemIncome) { if (inc >= 0) b.systemIncomePositive = SaturatingAdd(b.systemIncomePositive, inc); else b.systemIncomeNegative = SaturatingAdd(b.systemIncomeNegative, -inc); } b.tradeIncome = in.tradeIncome; b.secondaryManagerIncome = in.secondaryManagerIncome; b.shipCarriedPopIncome = in.shipCarriedPopIncome; b.maintenance = MaintenanceCost(in.maintenance, in.maintenanceDivisor); // Running total of the fixed lines; the slots filled later start at zero. auto running = [&]() -> std::int64_t { return static_cast(b.systemIncomePositive) + b.tradeIncome + b.shipCarriedPopIncome + b.secondaryManagerIncome + b.savingsInterest + b.bonusIncome - b.systemIncomeNegative - b.maintenance - b.researchMoneyKept - b.debtInterest - b.construction - b.expenses - b.researchMoneyGiven - b.savingsGiven; }; b.expenses = ExpenseTotal(in.expenses, static_cast(std::max(0, running()))); b.available = static_cast(std::max(0, running())); if (!in.isAI && b.available > 0) { b.construction = std::min(std::max(0, in.constructionDemand), b.available); } const int availAfterConstruction = b.available - b.construction; const double rate = projected ? 0.0 : in.researchRate; b.researchMoney = std::max(0, Ftol(static_cast(availAfterConstruction) * rate)); b.researchPoints = ResearchPointsFromMoney(b.researchMoney, in.researchDifficultyMult, in.resMod, in.shrm, in.trm, in.techResearchMult, in.serverResMod, in.resScl); b.totalResearchPoints = std::max(0, b.researchPoints + in.tra + in.trp); const int pct = ClampT(in.aidResearchPercent, 0, 100); b.researchMoneyGiven = static_cast(static_cast(b.researchMoney) * pct / 100); b.researchPointsGiven = static_cast(static_cast(b.totalResearchPoints) * pct / 100); b.totalResearchPoints -= b.researchPointsGiven; b.hasResearchAllocation = in.hasResearchTarget; b.researchMoneyKept = b.researchMoney - b.researchMoneyGiven; b.bonusIncome = Ftol((in.techIncomeMult - 1.0) * static_cast(running())); b.savingsGiven = static_cast(std::min(std::max(running(), 0), std::max(0, in.aidSavings))); b.net = static_cast(ClampT(running(), -2147483648LL, 2147483647LL)); return b; } // ---- trade --------------------------------------------------------------------------- int TradeRoutesSupported(double civilianPop, double imperialPop, const TuningTable& t) { double routes = 0; if (t.TRADE_ROUTE_REQ_CIVPOPULATION > 0) routes += std::ceil(civilianPop / t.TRADE_ROUTE_REQ_CIVPOPULATION); if (t.TRADE_ROUTE_REQ_IMPPOPULATION > 0) routes += std::ceil(imperialPop / t.TRADE_ROUTE_REQ_IMPPOPULATION); return std::max(1, Ftol(routes)); } int TradeRouteGrossIncome(const TradeRouteState& route, const TuningTable& t) { if (route.ageTurns < t.TRADE_ROUTE_STARTUP_TURNS) return t.TRADE_ROUTE_STARTUP_INCOME; const int perFreighter[3] = {t.TRADE_ROUTE_INCOME_PERFREIGHTER_CRQ, t.TRADE_ROUTE_INCOME_PERFREIGHTER_CR, t.TRADE_ROUTE_INCOME_PERFREIGHTER_DE}; int capLeft = t.TRADE_ROUTE_MAX_FREIGHTERS; std::int64_t income = t.TRADE_ROUTE_MIN_INCOME; for (int c = 0; c < 3; ++c) { const int n = std::min(std::max(0, route.freighters[c]), std::max(0, capLeft)); income += static_cast(n) * perFreighter[c]; capLeft -= n; } double v = static_cast(income); v *= 1.0 + t.STATION_BONUS_TRADE_INCOME * route.tradeStationsAtSystem; if (route.partnerAddicted) v *= t.ADDICTION_TRADE_MOD; return Ftol(v); } int TradeRouteIncome(const TradeRouteState& route, bool asOwner, double difficultyTradeMult, const TuningTable& t) { const double share = Clamp01(t.TRADE_ROUTE_OWNERS_SHARE); const double part = asOwner ? share : 1.0 - share; return Ftol(static_cast(TradeRouteGrossIncome(route, t)) * part * difficultyTradeMult); } // ---- bankruptcy ----------------------------------------------------------------------- BankruptcyLimits ComputeBankruptcyLimits(int maxIncome, const TuningTable& t) { BankruptcyLimits l; l.eliminationFloor = -Ftol(t.BANKRUPTCY_PROTECTION_LIMIT_FACTOR * static_cast(maxIncome)); l.protectionLimit = std::max(l.eliminationFloor, -maxIncome); return l; } int BankruptcyLevel(int savings, const BankruptcyLimits& limits) { if (savings < limits.eliminationFloor) return 2; if (savings < limits.protectionLimit) return 1; return 0; } bool BankruptcyStep(BankruptcyState& state, int level, int currentTurn, const TuningTable& t) { if (level == 0) { state.warningLevel = 0; state.startTurn = 0; return false; } if (state.warningLevel != level) { state.warningLevel = level; state.startTurn = currentTurn; } return level == 2 && (currentTurn - state.startTurn) >= t.BANKRUPTCY_ELIMINATION_TURNS; } } // namespace sots::sim