136 lines
5.3 KiB
JavaScript
136 lines
5.3 KiB
JavaScript
// The map-mode switch: terrain, political, and the economic colour modes. It
|
|
// owns which fill the map is showing, keeps the effective political flag in
|
|
// step with the camera zoom (terrain mode still turns political when far out),
|
|
// and drives the economic fill and its legend. The political names themselves
|
|
// live in ./political.js; this file only decides what is on screen.
|
|
|
|
import { HEX_W } from "../../../shared/hex.js";
|
|
import { POLITICAL_ZOOM } from "./constants.js";
|
|
import { ECONOMIC_GRADIENT, ECONOMIC_MODES, economicColour, isEconomicMode } from "./economic.js";
|
|
import { NEUTRAL_LAND, POLITICAL_WATER } from "./political.js";
|
|
import { isWaterTile } from "./textures.js";
|
|
|
|
export const MAP_MODES = ["terrain", "political", "gdp", "population"];
|
|
|
|
export const modeMethods = {
|
|
// Selects a mode and repaints. Terrain mode keeps the far-out auto-switch, so
|
|
// "political" forces the political map at any zoom while "terrain" only shows
|
|
// it once the camera has pulled back.
|
|
setMapMode(mode) {
|
|
if (!MAP_MODES.includes(mode) || mode === this.mapMode) return;
|
|
this.mapMode = mode;
|
|
this._syncModeState();
|
|
this._refreshMapColours();
|
|
},
|
|
|
|
// Installs the per-tile figures an economic mode paints. Called by the game
|
|
// screen whenever a snapshot arrives while such a mode is active. A cheap
|
|
// signature skips the rebuild when nothing moved.
|
|
setEconomicData(kind, values, min, max) {
|
|
this.economicKind = kind;
|
|
this.economicValues = values instanceof Map ? values : new Map(values || []);
|
|
this.economicRange = { min: min || 0, max: max || 0 };
|
|
const signature = this._economicSignature();
|
|
if (signature === this._economicSignatureDone) return;
|
|
this._economicSignatureDone = signature;
|
|
if (this.economic) this._refreshMapColours();
|
|
},
|
|
|
|
_economicSignature() {
|
|
let sum = 0;
|
|
for (const value of this.economicValues.values()) sum += value;
|
|
return `${this.economicKind}:${this.economicValues.size}:${Math.round(sum)}`;
|
|
},
|
|
|
|
// Political shows when the user asked for it, or while terrain mode is
|
|
// zoomed far enough out. Economic modes are exclusive with it.
|
|
_effectivePolitical() {
|
|
return this.mapMode === "political" ||
|
|
(this.mapMode === "terrain" && this.camera.zoom <= POLITICAL_ZOOM);
|
|
},
|
|
|
|
_isEconomic() {
|
|
return isEconomicMode(this.mapMode);
|
|
},
|
|
|
|
// Recomputes the two effective flags and reflects them on the world element.
|
|
// The renderers read `political` / `economic` off the view.
|
|
_syncModeState() {
|
|
this.political = this._effectivePolitical();
|
|
this.economic = this._isEconomic();
|
|
this.$world.toggleClass("political", this.political);
|
|
this.$world.toggleClass("economic", this.economic);
|
|
},
|
|
|
|
// Called from the camera whenever the zoom changes: terrain mode may have
|
|
// crossed into or out of the far-out political map.
|
|
_updatePoliticalMode() {
|
|
const on = this._effectivePolitical();
|
|
if (on === this.political) return;
|
|
this._syncModeState();
|
|
this._refreshMapColours();
|
|
},
|
|
|
|
// Repaints the active fill and the labels/legend that go with it. The DOM
|
|
// renderer recolours its chunks; WebGL rebuilds the fill passes.
|
|
_refreshMapColours() {
|
|
if (this.glRenderer) {
|
|
this.glRenderer.buildPolitical(this);
|
|
this.glRenderer.buildEconomic(this);
|
|
} else if (this.topology) {
|
|
this._rebuildDomTerrain();
|
|
}
|
|
if (this.political) this._renderPoliticalLabels();
|
|
else if (this.$politicalLabels) this.$politicalLabels.empty();
|
|
this._updateLegend();
|
|
},
|
|
|
|
// Rebuilds every materialised chunk so a change of mode or territory colour
|
|
// is reflected in the DOM terrain. Only the chunks near the camera exist, so
|
|
// this stays cheap.
|
|
_rebuildDomTerrain() {
|
|
if (!this._chunks) return;
|
|
this._destroyAllChunks();
|
|
this._chunkDirty = true;
|
|
this._chunkView = { x: null, y: null, zoom: null };
|
|
this._syncChunks();
|
|
},
|
|
|
|
// Shows the gradient scale while an economic mode is active, with the range's
|
|
// low, middle and high figures for the current mode.
|
|
_updateLegend() {
|
|
const $legend = this.$legend;
|
|
if (!$legend || !$legend.length) return;
|
|
const meta = ECONOMIC_MODES[this.mapMode];
|
|
if (!this.economic || !meta) {
|
|
$legend.addClass("hidden");
|
|
return;
|
|
}
|
|
$legend.removeClass("hidden");
|
|
$legend.find("#map-legend-title").text(meta.label);
|
|
const { min, max } = this.economicRange;
|
|
$legend.find("#map-legend-min").text(meta.format(min));
|
|
$legend.find("#map-legend-mid").text(meta.format((min + max) / 2));
|
|
$legend.find("#map-legend-max").text(meta.format(max));
|
|
$legend.find("#map-legend-bar")
|
|
.css("background", `linear-gradient(to right, ${ECONOMIC_GRADIENT.join(", ")})`);
|
|
},
|
|
|
|
// Paints one DOM terrain tile with its economic colour. Water and tiles with
|
|
// no figure stay neutral; the atlas mask still clips the hexagon.
|
|
_applyEconomicColour(div, tile, k) {
|
|
let colour = POLITICAL_WATER;
|
|
if (!isWaterTile(tile)) {
|
|
const value = this.economicValues.get(k);
|
|
colour = value === undefined
|
|
? NEUTRAL_LAND
|
|
: economicColour(value, this.economicRange.min, this.economicRange.max);
|
|
}
|
|
div.style.backgroundImage = "none";
|
|
div.style.backgroundColor = colour;
|
|
const maskPosition = `${-tile.col * HEX_W}px 0`;
|
|
div.style.maskPosition = maskPosition;
|
|
div.style.webkitMaskPosition = maskPosition;
|
|
},
|
|
};
|