sots-engine/src/game/sim/economy.cpp

164 lines
7.1 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) {
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<std::int64_t>(availableBeforeExpenses) - minimums;
const std::int64_t total = minimums + std::min(extras, 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;
b.bonusIncome = Ftol((in.techIncomeMult - 1.0) * static_cast<double>(running()));
b.savingsGiven = static_cast<int>(std::min<std::int64_t>(std::max<std::int64_t>(running(), 0),
std::max(0, in.aidSavings)));
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) {
BankruptcyLimits l;
l.eliminationFloor = -Ftol(t.BANKRUPTCY_PROTECTION_LIMIT_FACTOR * static_cast<double>(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