Files
Battle-for-Tismo/client/js/game_screen/completions.js
T

100 lines
3.3 KiB
JavaScript

// Detects what the local player finished between two snapshots: a city building
// that gained a level, or a unit that joined the army.
//
// The first snapshot only primes the baseline, so opening or joining a game in
// progress does not fire a burst of completion sounds. A fresh unit id is
// usually a training completion, but landing a transport's cargo mints new
// units too, so a unit only counts when no local transport has just unloaded.
// The local player's cities, as a map of city id -> building index -> level.
export function localBuildingLevels(state, localCiv) {
const levels = new Map();
for (const city of state.cities || []) {
if (city.civ !== localCiv) continue;
levels.set(city.id, { ...(city.buildings || {}) });
}
return levels;
}
// The ids of every unit the local player owns.
export function localUnitIds(state, localCiv) {
const ids = new Set();
for (const unit of state.units || []) {
if (unit.civ === localCiv) ids.add(unit.id);
}
return ids;
}
// How many passengers each local transport is carrying, by unit id. Only loaded
// transports are listed, so unloading one makes it disappear from the map.
export function localCargo(state, localCiv) {
const cargo = new Map();
for (const unit of state.units || []) {
if (unit.civ !== localCiv) continue;
const count = (unit.cargo || []).length + (unit.embarking ? unit.embarking.count : 0);
if (count > 0) cargo.set(unit.id, count);
}
return cargo;
}
// True when any city the player already held has raised a building level. A
// city absent from `previous` was just captured and is not a completion.
export function buildingLevelGained(previous, next) {
for (const [cityId, levels] of next) {
const before = previous.get(cityId);
if (!before) continue;
for (const index of Object.keys(levels)) {
if ((before[index] || 0) < levels[index]) return true;
}
}
return false;
}
// True when a transport carried fewer passengers than before, which is the
// signature of a disembark (the units it minted are not freshly trained).
export function cargoUnloaded(previous, next) {
for (const [unitId, count] of previous) {
if ((next.get(unitId) || 0) < count) return true;
}
return false;
}
function hasFreshUnit(previous, next) {
for (const id of next) {
if (!previous.has(id)) return true;
}
return false;
}
export class CompletionWatcher {
constructor() {
this.reset();
}
reset() {
this._buildings = null;
this._units = null;
this._cargo = null;
}
// Feeds one snapshot and reports what changed. `first` marks the priming
// snapshot, which never reports a completion.
update(state, localCiv) {
const buildings = localBuildingLevels(state, localCiv);
const units = localUnitIds(state, localCiv);
const cargo = localCargo(state, localCiv);
if (this._buildings === null) {
this._buildings = buildings;
this._units = units;
this._cargo = cargo;
return { first: true, building: false, unit: false };
}
const building = buildingLevelGained(this._buildings, buildings);
const unit = !cargoUnloaded(this._cargo, cargo) && hasFreshUnit(this._units, units);
this._buildings = buildings;
this._units = units;
this._cargo = cargo;
return { first: false, building, unit };
}
}