feat: New features (research, invest, gambling, new game+, trophies)

This commit is contained in:
gauvainboiche
2026-10-08 14:03:00 +02:00
parent 1caa4d92b9
commit fc0d5e3ba4
56 changed files with 4332 additions and 249 deletions
+75
View File
@@ -0,0 +1,75 @@
// Investments: nine societies in three risk tiers. Every `intervalSeconds` each
// price moves by a random percentage within the tier's swing (low ±5 %, mid
// ±15 %, high ±50 %), then drifts back towards its fair value (more weakly for the wilder tiers), and
// holdings pay a small dividend. Buying and selling carries a small fee.
import { clamp } from './formulas.js';
export function holdingValue(state, id) {
return state.invest.holdings[id] * state.invest.prices[id];
}
export function portfolio(state, ctx) {
let value = 0;
let basis = 0;
for (const s of ctx.investments.societies) {
value += holdingValue(state, s.id);
basis += state.invest.basis[s.id];
}
return { value, basis, profit: value - basis };
}
function repriceAll(state, ctx, rng, dividendMultiplier) {
const cfg = ctx.investments;
const inv = state.invest;
for (const s of cfg.societies) {
const tier = cfg.tiers[s.tier];
const before = inv.prices[s.id];
let price = before * (1 + (rng() * 2 - 1) * tier.swing);
price += (s.basePrice - price) * tier.reversion;
price = clamp(price, s.basePrice * cfg.minRatio, s.basePrice * cfg.maxRatio);
inv.prices[s.id] = price;
inv.change[s.id] = price / before - 1;
state.funds += inv.holdings[s.id] * price * tier.dividend * dividendMultiplier;
}
}
export function stepInvestments(state, ctx, dt, rng, mods) {
state.investTimer += dt;
while (state.investTimer >= ctx.investments.intervalSeconds) {
state.investTimer -= ctx.investments.intervalSeconds;
repriceAll(state, ctx, rng, mods.dividend);
}
}
/** Spend `fraction` of the current funds on society `id`. */
export function buyShares(state, ctx, id, fraction) {
if (!ctx.societiesById.has(id)) return false;
const spend = state.funds * clamp(fraction, 0, 1);
if (!(spend >= 0.01)) return false;
state.funds -= spend;
state.invest.holdings[id] += (spend * (1 - ctx.investments.fee)) / state.invest.prices[id];
state.invest.basis[id] += spend;
return true;
}
/** Sell `fraction` of what is held in society `id`. */
export function sellShares(state, ctx, id, fraction) {
if (!ctx.societiesById.has(id)) return false;
const inv = state.invest;
const share = clamp(fraction, 0, 1);
if (!(inv.holdings[id] > 0) || share <= 0) return false;
const proceeds = inv.holdings[id] * share * inv.prices[id] * (1 - ctx.investments.fee);
const basisPart = inv.basis[id] * share;
state.funds += proceeds;
state.stats.investProfit += proceeds - basisPart;
if (share >= 1) {
inv.holdings[id] = 0;
inv.basis[id] = 0;
} else {
inv.holdings[id] -= inv.holdings[id] * share;
inv.basis[id] -= basisPart;
}
return true;
}