189 lines
8.6 KiB
C++
189 lines
8.6 KiB
C++
#include "game/sim/economy.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
#include "game/sim/numeric.h"
|
|
|
|
namespace sots::sim {
|
|
|
|
int SavingsInterest(int savings, bool ownsSystems) {
|
|
if (savings < 0 || !ownsSystems) return 0;
|
|
return Ftol(static_cast<double>(savings) * 0.01);
|
|
}
|
|
|
|
int DebtInterest(int savings) {
|
|
if (savings >= 0) return 0;
|
|
return Ftol(-static_cast<double>(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<ExpenseSlider>& sliders, int availableBeforeExpenses) {
|
|
constexpr int kUnlimited = 2000000000;
|
|
const int availPre = availableBeforeExpenses;
|
|
// The game multiplies the slider fraction by the available income in single
|
|
// precision, so the income is rounded to a float before the product.
|
|
const double availAsFloat = static_cast<double>(static_cast<float>(availPre));
|
|
|
|
std::int64_t minimums = 0;
|
|
std::int64_t takes = 0;
|
|
for (const ExpenseSlider& s : sliders) {
|
|
const int minC = std::max(s.minimum, 0);
|
|
int maxC = ClampT(s.maximum, 0, kUnlimited);
|
|
if (maxC == 0) maxC = kUnlimited;
|
|
const std::int64_t room = static_cast<std::int64_t>(maxC) - minC;
|
|
const std::int64_t request =
|
|
static_cast<std::int64_t>(Ftol(static_cast<double>(s.fraction) * availAsFloat)) - minC;
|
|
const std::int64_t take = std::min(std::max<std::int64_t>(request, 0), room);
|
|
minimums += minC;
|
|
takes += take;
|
|
}
|
|
const std::int64_t headroom = static_cast<std::int64_t>(availPre) - minimums;
|
|
const std::int64_t total = minimums + std::min(std::max<std::int64_t>(takes, 0), headroom);
|
|
return static_cast<int>(ClampT<std::int64_t>(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<double>(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<std::int64_t>(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<int>(std::max<std::int64_t>(0, running())));
|
|
b.available = static_cast<int>(std::max<std::int64_t>(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<double>(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<int>(static_cast<std::int64_t>(b.researchMoney) * pct / 100);
|
|
b.researchPointsGiven = static_cast<int>(static_cast<std::int64_t>(b.totalResearchPoints) * pct / 100);
|
|
b.totalResearchPoints -= b.researchPointsGiven;
|
|
b.hasResearchAllocation = in.hasResearchTarget;
|
|
b.researchMoneyKept = b.researchMoney - b.researchMoneyGiven;
|
|
|
|
// Tech income bonus: a share of the full net so far, only when that net is positive.
|
|
const std::int64_t netBeforeBonus = running();
|
|
if (netBeforeBonus > 0) {
|
|
b.bonusIncome = std::max(0, Ftol((in.techIncomeMult - 1.0) * static_cast<double>(netBeforeBonus)));
|
|
}
|
|
// Savings aid: capped by the projected treasury after this turn (bonus included).
|
|
if (in.aidSavings != 0) {
|
|
const int netWithBonus = static_cast<int>(ClampT<std::int64_t>(running(), -2147483648LL, 2147483647LL));
|
|
const int projectedSavings = SaturatingAdd(in.savings, netWithBonus);
|
|
b.savingsGiven = std::min(std::max(projectedSavings, 0), std::max(in.aidSavings, 0));
|
|
}
|
|
|
|
b.net = static_cast<int>(ClampT<std::int64_t>(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<std::int64_t>(n) * perFreighter[c];
|
|
capLeft -= n;
|
|
}
|
|
double v = static_cast<double>(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<double>(TradeRouteGrossIncome(route, t)) * part * difficultyTradeMult);
|
|
}
|
|
|
|
// ---- bankruptcy -----------------------------------------------------------------------
|
|
|
|
BankruptcyLimits ComputeBankruptcyLimits(int maxIncome, const TuningTable& t) {
|
|
constexpr int kTreasuryLimit = 2000000000;
|
|
BankruptcyLimits l;
|
|
// Elimination: the debt whose 15 %/turn interest equals the maximum income.
|
|
l.eliminationFloor = std::max(Ftol(static_cast<double>(maxIncome) / -0.15), -kTreasuryLimit);
|
|
// Protection: the tuned factor times the maximum income, never below the floor.
|
|
l.protectionLimit = std::max(-Ftol(t.BANKRUPTCY_PROTECTION_LIMIT_FACTOR * static_cast<double>(maxIncome)),
|
|
l.eliminationFloor);
|
|
return l;
|
|
}
|
|
|
|
int BankruptcyLevel(int savings, const BankruptcyLimits& limits) {
|
|
if (savings < limits.eliminationFloor) return 2;
|
|
if (savings < limits.protectionLimit) return 1;
|
|
return 0;
|
|
}
|
|
|
|
BankruptcyDecision BankruptcyStep(BankruptcyState& state, int level, int currentTurn,
|
|
const TuningTable& t) {
|
|
const BankruptcyState old = state;
|
|
if (level != state.warningLevel) {
|
|
state.warningLevel = level;
|
|
state.startTurn = level != 0 ? currentTurn : -1;
|
|
}
|
|
BankruptcyDecision d;
|
|
d.costCutting = level != 0 && old.warningLevel != 0;
|
|
d.eliminate = old.warningLevel == 2 &&
|
|
(currentTurn - old.startTurn) >= t.BANKRUPTCY_ELIMINATION_TURNS;
|
|
return d;
|
|
}
|
|
|
|
} // namespace sots::sim
|