Complete browser game inspired by OpenRCT2 with fantasy twist: - Custom roller coaster designer with physics-based ratings + on-ride POV - 10 animated rides, 7 shops, 16 scenery items, path network & guest AI - Heroes guild vs monster invasions (5 classes, XP/gear/bosses) - Magic spell system (8 spells), research tree, economy/marketing/loans - Day-night cycle, weather, park rating, awards, 4 scenarios - Save/load slots + autosave, procedural WebAudio SFX/music - Isometric canvas renderer, minimap, diagnostics overlay - Test suites: smoke(13), linkcheck, inputcheck, framecheck, rendercheck, flow
97 lines
2.9 KiB
JavaScript
97 lines
2.9 KiB
JavaScript
// ============ economy.js — money, finance cycles, marketing ============
|
|
import { fmtMoney } from '../core/util.js';
|
|
|
|
export function pay(state, amount, category, note) {
|
|
state.cash -= amount;
|
|
pushFin(state, category, -amount);
|
|
return true;
|
|
}
|
|
export function earn(state, amount, category, note) {
|
|
state.cash += amount;
|
|
pushFin(state, category, amount);
|
|
return true;
|
|
}
|
|
|
|
function pushFin(state, category, amount) {
|
|
const m = state.finance.current;
|
|
m[category] = (m[category] || 0) + amount;
|
|
}
|
|
|
|
export const FIN_CATEGORIES = [
|
|
['rideTickets', 'Ride tickets'],
|
|
['shopSales', 'Shop sales'],
|
|
['entrance', 'Entrance fees'],
|
|
['construction', 'Construction'],
|
|
['wages', 'Staff wages'],
|
|
['research', 'Research'],
|
|
['marketing', 'Marketing'],
|
|
['heroes', 'Heroes & gear'],
|
|
['loot', 'Monster loot'],
|
|
['runCosts', 'Ride running costs'],
|
|
['misc', 'Misc'],
|
|
['loans', 'Loans'],
|
|
];
|
|
|
|
/** Monthly finance close: wages, interest, archive */
|
|
export function monthClose(state) {
|
|
// wages
|
|
let wages = 0;
|
|
for (const s of state.staff) wages += s.wage;
|
|
for (const r of state.rides) wages += Math.round(r.def.runCost);
|
|
pay(state, wages, 'wages');
|
|
// loan interest 1%/month
|
|
if (state.loan > 0) {
|
|
const interest = Math.ceil(state.loan * 0.01);
|
|
pay(state, interest, 'loans');
|
|
}
|
|
// archive current month
|
|
const cur = { ...state.finance.current };
|
|
state.finance.history.push(cur);
|
|
if (state.finance.history.length > 36) state.finance.history.shift();
|
|
state.finance.current = {};
|
|
state.lastMonthProfit = Object.entries(cur).reduce((a, [, v]) => a + v, 0);
|
|
return wages;
|
|
}
|
|
|
|
// ---------------- Marketing campaigns ----------------
|
|
export const CAMPAIGNS = [
|
|
{ id: 'flyers', name: 'Fairy Flyer Drop', cost: 400, weeks: 6, pull: 1.6 },
|
|
{ id: 'heralds', name: 'Town Crier Heralds', cost: 900, weeks: 8, pull: 2.4 },
|
|
{ id: 'crystal', name: 'Crystal Ball Vision', cost: 1800, weeks: 10, pull: 3.6 },
|
|
];
|
|
|
|
export function startCampaign(state, id) {
|
|
const c = CAMPAIGNS.find(c => c.id === id);
|
|
if (!c || state.cash < c.cost) return false;
|
|
pay(state, c.cost, 'marketing');
|
|
state.campaigns.push({ id: c.id, name: c.name, weeksLeft: c.weeks, pull: c.pull });
|
|
return true;
|
|
}
|
|
export function campaignPull(state) {
|
|
let p = 0;
|
|
for (const c of state.campaigns) p += c.pull;
|
|
return p;
|
|
}
|
|
export function tickCampaigns(state) {
|
|
for (let i = state.campaigns.length - 1; i >= 0; i--) {
|
|
state.campaigns[i].weeksLeft--;
|
|
if (state.campaigns[i].weeksLeft <= 0) state.campaigns.splice(i, 1);
|
|
}
|
|
}
|
|
|
|
// ---------------- Loan ----------------
|
|
export function takeLoan(state, amount) {
|
|
amount = Math.min(amount, state.loanLimit - state.loan);
|
|
if (amount <= 0) return false;
|
|
state.loan += amount;
|
|
earn(state, amount, 'loans');
|
|
return true;
|
|
}
|
|
export function repayLoan(state, amount) {
|
|
amount = Math.min(amount, state.loan, state.cash);
|
|
if (amount <= 0) return false;
|
|
state.loan -= amount;
|
|
pay(state, amount, 'loans');
|
|
return true;
|
|
}
|