Files
deepseek ac00687480 Arcane Tycoon — Heroes & Magic theme park tycoon game
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
2026-08-23 06:59:21 +00:00

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;
}