100 lines
3.3 KiB
JavaScript
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 };
|
|
}
|
|
}
|