2024-10-21 17:35:32 +02:00
|
|
|
import { produce } from 'immer';
|
|
|
|
|
2024-11-05 12:44:24 +01:00
|
|
|
import { CULTURE_TO_WIN } from './constants';
|
2024-10-25 19:03:32 +02:00
|
|
|
import type { ProductionType } from './types';
|
2024-11-05 12:44:24 +01:00
|
|
|
import { getProduction, getStorage, shuffle } from './utils';
|
|
|
|
import village, { type VillageState } from "./village";
|
2024-10-21 17:35:32 +02:00
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-10-21 17:35:32 +02:00
|
|
|
return produce(state, (V: VillageState) => {
|
2024-10-24 19:27:33 +02:00
|
|
|
// Advance building construction.
|
2024-11-05 12:42:39 +01:00
|
|
|
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;
|
2024-10-24 19:27:33 +02:00
|
|
|
}
|
2024-11-05 12:42:39 +01:00
|
|
|
});
|
2024-10-24 19:27:33 +02:00
|
|
|
|
2024-11-05 11:35:32 +01:00
|
|
|
// Make all buildings and units produce and consume.
|
2024-10-22 17:15:35 +02:00
|
|
|
const productionPerMinute = getProduction(V);
|
|
|
|
const storage = getStorage(V);
|
|
|
|
|
|
|
|
Object.keys(productionPerMinute).forEach((key) => {
|
2024-10-25 19:03:32 +02:00
|
|
|
const resource = key as keyof ProductionType;
|
2024-10-22 17:15:35 +02:00
|
|
|
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;
|
2024-10-22 17:15:35 +02:00
|
|
|
|
|
|
|
if (V.resources[resource] > storage[resource]) {
|
|
|
|
V.resources[resource] = storage[resource];
|
2024-10-21 17:35:32 +02:00
|
|
|
}
|
2024-10-23 11:59:50 +02:00
|
|
|
else if (V.resources[resource] < 0) {
|
|
|
|
V.resources[resource] = 0;
|
|
|
|
}
|
2024-10-21 17:35:32 +02:00
|
|
|
});
|
2024-10-22 17:15:35 +02:00
|
|
|
|
2024-10-25 19:03:32 +02:00
|
|
|
// 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.
|
2024-11-05 12:44:24 +01:00
|
|
|
if (V.resources.culture >= CULTURE_TO_WIN) {
|
2024-11-04 18:43:03 +01:00
|
|
|
V.victory = true;
|
|
|
|
}
|
2024-11-04 18:10:26 +01:00
|
|
|
|
2024-10-21 17:35:32 +02:00
|
|
|
return V;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
lastFrame = timestamp;
|
|
|
|
}
|