import { produce } from 'immer'; import { CULTURE_TO_WIN } from './constants'; import { resolveMission } from './missions'; import type { ProductionType } from './types'; import { getProduction, getStorage, shuffle } from './utils'; import village, { type VillageState } from "./village"; import { createQuest } from './quests'; let lastFrame: number; export default function update(timestamp: number) { if (!lastFrame) { lastFrame = timestamp; return; } const delta = timestamp - lastFrame; village.update(state => { if (state.victory) { return state; } return produce(state, (V: VillageState) => { // Advance building construction. V.buildings.filter(b => b.state.upgrade.isUpgrading).forEach(b => { b.state.upgrade.remainingTime -= delta; if (b.state.upgrade.remainingTime <= 0) { b.level++; b.state.upgrade.isUpgrading = false; } }); // Advance missions. V.worldmap.forEach(region => { if (!region.state.mission) { return; } const mission = region.state.mission; mission.remainingTime -= delta; if (mission.remainingTime <= 0) { resolveMission(V, region); } }); // Advance quests. V.quests.filter(q => q.started).forEach(quest => { if (!quest.remainingTime) { return; } quest.remainingTime -= delta; if (quest.remainingTime <= 0) { V.resources.wood += quest.reward.wood; V.resources.stone += quest.reward.stone; V.resources.iron += quest.reward.iron; V.resources.food += quest.reward.food; V.resources.culture += quest.reward.culture; // Replace the finished quest with a new one. V.quests = V.quests.filter(q => q.id !== quest.id); V.quests.push(createQuest(quest.level + 1)); } }); // Make all buildings and units produce and consume. const productionPerMinute = getProduction(V); const storage = getStorage(V); Object.keys(productionPerMinute).forEach((key) => { const resource = key as keyof ProductionType; const outputPerMinute = productionPerMinute[resource]; const outputPerMilisecond = outputPerMinute / 60.0 / 1000.0; V.resources[resource] += outputPerMilisecond * delta; }); // Kill units if food is negative. if (V.resources.food < 0) { // Choose a unit type at random. const type = shuffle(Object.keys(V.units).filter(t => V.units[t] > 0))[0]; // Kill one unit of that type. V.units[type]--; } // Make sure resources do not overflow. Object.keys(productionPerMinute).forEach((key) => { const resource = key as keyof ProductionType; if (V.resources[resource] > storage[resource]) { V.resources[resource] = storage[resource]; } else if (V.resources[resource] < 0) { V.resources[resource] = 0; } }); // Recruit units. V.buildings.forEach(b => { if (!b.state.recruitment || !b.state.recruitment.count) { return; } const recruitment = b.state.recruitment; recruitment.elapsedTime += delta; const timeToRecruit = b.behavior.units?.recruitmentTime(V, b) * 1000; if (recruitment.elapsedTime >= timeToRecruit) { const unitType = b.behavior.units?.type || ''; if (!V.units.hasOwnProperty(unitType)) { V.units[unitType] = 0; } V.units[unitType]++; recruitment.count--; recruitment.elapsedTime = (recruitment.count === 0) ? 0 : timeToRecruit - recruitment.elapsedTime; } }); // Check if the game is won. if (V.resources.culture >= CULTURE_TO_WIN) { V.victory = true; } return V; }); }); lastFrame = timestamp; }