Roads are pre-generated between cities with a greedy geometric spanner and flatten unit movement cost. City improvements now only affect the tiles their region controls, with per-region tax collection and a Cities tab. The budget is a grouped, scrollable table with 25%/year tax and a 10%-of-GDP opening treasury. Also merged the tile info into the city panel, kept conflict and budget expansion state across snapshots, added join notifications, filled border corners, drew battle sides as stacks and made tab overflow scroll.
129 lines
4.4 KiB
JavaScript
129 lines
4.4 KiB
JavaScript
// The world map view. It owns no game logic: the server generates and
|
|
// simulates the world, and this class rebuilds the terrain once from the shared
|
|
// seed and mirrors units, cities, territory and fog from each snapshot, then
|
|
// turns pointer input into selection and move orders. All rendering is done
|
|
// with ordinary DOM elements, as required.
|
|
//
|
|
// The behaviour is split across the modules in ./map_view/ and composed onto the
|
|
// prototype below, so each concern (terrain, entities, motion, input) stays in
|
|
// its own file.
|
|
|
|
import { terrainMethods } from "./map_view/terrain.js";
|
|
import { entityMethods } from "./map_view/entities.js";
|
|
import { motionMethods } from "./map_view/motion.js";
|
|
import { inputMethods } from "./map_view/input.js";
|
|
|
|
// Re-exported for tests and callers that use the border geometry directly.
|
|
export { inwardOffset } from "./map_view/utils.js";
|
|
|
|
export class MapView {
|
|
constructor($viewport, $world, layers) {
|
|
this.$viewport = $viewport;
|
|
this.$world = $world;
|
|
this.$terrain = layers.terrain;
|
|
this.$roads = layers.roads && layers.roads.length
|
|
? layers.roads
|
|
: $("#layer-roads");
|
|
this.$borders = layers.borders;
|
|
this.$fog = layers.fog;
|
|
this.$paths = layers.paths;
|
|
this.$entities = layers.entities;
|
|
// The simplified map used when zoomed out. It is created on demand so
|
|
// callers (and tests) that predate it keep working unchanged.
|
|
this.$overview = layers.overview && layers.overview.length
|
|
? layers.overview
|
|
: $("#layer-overview");
|
|
if (!this.$overview.length) {
|
|
this.$overview = $('<div id="layer-overview" class="layer overview"></div>');
|
|
this.$world.prepend(this.$overview);
|
|
}
|
|
// City names live on their own layer above the entities so they are never
|
|
// hidden behind a unit or city marker.
|
|
this.$labels = layers.labels && layers.labels.length
|
|
? layers.labels
|
|
: $("#layer-labels");
|
|
if (!this.$labels.length) {
|
|
this.$labels = $('<div id="layer-labels" class="layer city-label-layer"></div>');
|
|
this.$world.append(this.$labels);
|
|
}
|
|
|
|
this.seed = -1;
|
|
this.topology = null;
|
|
this.tiles = {};
|
|
this.terrainStats = null;
|
|
this.explored = new Set();
|
|
this.visible = new Set();
|
|
this.territory = new Map();
|
|
// Pre-generated road tiles, keyed "x,y".
|
|
this.roads = new Set();
|
|
this.civilisations = [];
|
|
this.localCiv = -1;
|
|
this.protoUnits = [];
|
|
// Battlefield tiles, keyed "x,y" and mapped to { defender, attacker }.
|
|
this._battles = new Map();
|
|
|
|
this.camera = { x: 0, y: 0, zoom: 1.0 };
|
|
this._centered = false;
|
|
this._selectedUnitIds = new Set();
|
|
this._selectedTile = null;
|
|
|
|
this._unitViews = new Map();
|
|
this._unitMotion = new Map();
|
|
this._unitData = new Map();
|
|
this._cityViews = new Map();
|
|
this._cityCiv = new Map();
|
|
this._battleViews = new Map();
|
|
// In-flight pathfinding job, cancelled when a newer request supersedes it.
|
|
this._pathToken = null;
|
|
this._maxStepLength = 1.0;
|
|
|
|
this._territorySignature = "";
|
|
this._roadsSignature = null;
|
|
this._exploredCount = -1;
|
|
this._visibleSignature = null;
|
|
|
|
// Cylindrical wrap state. `_period` is the horizontal pixel period (0 on a
|
|
// flat map); every entity is shifted to the copy nearest the camera.
|
|
this._period = 0;
|
|
this._worldWidth = 0;
|
|
this._worldHeight = 0;
|
|
this._worldMinX = 0;
|
|
this._worldMinY = 0;
|
|
// Chunked detailed layers: only the chunks near the viewport exist as DOM.
|
|
// The tile wrappers are pooled so panning recycles nodes instead of
|
|
// allocating a fresh one for every tile that scrolls in.
|
|
this._chunkSize = 0;
|
|
this._chunkCols = 0;
|
|
this._chunkRows = 0;
|
|
this._chunks = new Map();
|
|
this._chunkCache = new Map();
|
|
this._chunkCacheLimit = 0;
|
|
this._chunkOriginCache = new Map();
|
|
this._chunkView = { x: null, y: null, zoom: null };
|
|
this._chunkDirty = true;
|
|
this._hexPool = [];
|
|
this._borderPool = [];
|
|
this._roadPool = [];
|
|
this._wrapperPool = [];
|
|
this.lod = false;
|
|
|
|
this.onUnitSelected = () => {};
|
|
this.onCitySelected = () => {};
|
|
this.onTileRequested = () => {};
|
|
this.onMoveOrdered = () => {};
|
|
this.onAttackOrdered = () => {};
|
|
this.onScheduleOrdered = () => {};
|
|
this.onStackMenu = () => {};
|
|
|
|
this._setupCameraInput();
|
|
}
|
|
}
|
|
|
|
Object.assign(
|
|
MapView.prototype,
|
|
terrainMethods,
|
|
entityMethods,
|
|
motionMethods,
|
|
inputMethods
|
|
);
|