bourgade/src/update.ts

134 lines
4.7 KiB
TypeScript
Raw Normal View History

import { produce } from 'immer';
import { CULTURE_TO_WIN } from './constants';
import { createQuest } from './create';
2024-11-07 14:20:34 +01:00
import { resolveMission } from './missions';
import type { ProductionType } from './types';
import { getProduction, getRegionsWithMissions, getStorage, shuffle } from './utils';
import village, { type VillageState } from "./village";
let lastFrame: number;
export default function update(timestamp: number) {
if (!lastFrame) {
lastFrame = timestamp;
return;
}
const delta = timestamp - lastFrame;
village.update(state => {
2024-11-04 18:43:03 +01:00
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;
b.behavior.triggers?.onLevelUp?.(V, b);
}
});
2024-11-07 14:20:34 +01:00
// Advance missions.
getRegionsWithMissions(V).forEach(region => {
2024-11-07 14:20:34 +01:00
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));
}
});
2024-11-05 11:35:32 +01:00
// 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;
2024-11-05 11:35:32 +01:00
});
2024-11-05 12:06:44 +01:00
// 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]--;
}
2024-11-05 11:35:32 +01:00
// 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];
}
2024-10-23 11:59:50 +02:00
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;
}
});
2024-11-04 18:43:03 +01:00
// Check if the game is won.
if (V.resources.culture >= CULTURE_TO_WIN) {
2024-11-04 18:43:03 +01:00
V.victory = true;
}
return V;
});
});
lastFrame = timestamp;
}