Spaces:
Paused
Paused
File size: 9,474 Bytes
7a2b109 d32ddf2 e6ecfb1 7a2b109 d32ddf2 e6ecfb1 d32ddf2 cae3dc6 d32ddf2 9eded26 d32ddf2 842eae1 2a07a69 755b556 17641e3 cce54f5 406718a cce54f5 9eded26 7a2b109 4f2ab31 de0a7f9 3f927e2 cce54f5 de0a7f9 17641e3 cce54f5 17641e3 2d6f3fe 17641e3 7a2b109 cce54f5 17641e3 d90ceb8 cce54f5 d90ceb8 2d6f3fe cce54f5 2d6f3fe d088d6d e6ecfb1 cce54f5 e6ecfb1 d088d6d e6ecfb1 cce54f5 e6ecfb1 cae3dc6 cce54f5 cae3dc6 2d6f3fe cae3dc6 cce54f5 cae3dc6 d32ddf2 cce54f5 d32ddf2 cae3dc6 a4bbde9 d32ddf2 cae3dc6 d32ddf2 cae3dc6 6fe5e5b 4e300be cce54f5 4e300be d32ddf2 4e300be a4bbde9 d32ddf2 4e300be e7bc56d d32ddf2 cce54f5 d088d6d e7bc56d 0fdd1b1 d32ddf2 2d6f3fe | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | /* ============================================================
Entry point / composition root. The game is split across focused modules โ
dom (element handles), session (the live state/input holder), setup (splash),
boot (the run loop + frame events), hud (selection panel + readouts),
overlays (chips/objectives/help), saveload (localStorage). Importing them here
evaluates each module, which self-wires its own listeners; this file owns the
remaining top-level chrome (mute, volume, the canvases' resize/backing store,
the minimap commands, the panel-fold toggles (portrait sheet + desktop side panel),
the touch-mode flag) and kicks the first screen off.
============================================================ */
"use strict";
import { game } from "./session.js";
import {
canvas, ctx, minimapCanvas, minimapCtx, muteBtn, volumeEl,
idleWorkersEl, idleProductionEl, sheetToggleEl, panelToggleEl, MINIMAP_W, MINIMAP_H, isTouchMode,
} from "./dom.js";
import { clampCamera } from "./camera.js";
import { minimapToWorld } from "./minimap.js";
import { issueMove, issueAttackMove } from "./engine/commands.js";
import { UNITS } from "./engine/entities.js";
import * as sound from "./sound.js";
import { buildHelpOverlay } from "./overlays.js";
import { renderMapSelect } from "./setup.js";
import "./starmap.js"; // self-wires the galaxy-map button + M key
import "./techChart.js"; // self-wires the Tech & Industry Chart button + T key
import "./update.js"; // self-wires the version chip + auto-update check
if (idleWorkersEl) idleWorkersEl.addEventListener("click", () => { if (game.input) game.input.focusIdleWorker(); });
if (idleProductionEl) idleProductionEl.addEventListener("click", () => { if (game.input) game.input.focusIdleProducer(); });
if (muteBtn) muteBtn.addEventListener("click", () => {
const next = !sound.isMuted();
sound.setMuted(next);
muteBtn.setAttribute("aria-pressed", String(next));
muteBtn.textContent = next ? "๐" : "๐";
});
if (volumeEl) {
volumeEl.addEventListener("input", () => sound.setVolume(Number(volumeEl.value) / 100));
}
// Right-click is a game command (move / attack / gather / queue a waypoint),
// so the browser's own context menu must never pop over the game. The canvas
// already suppresses it for clicks that land squarely on it, but the view has
// padding around the canvas and the minimap sits on top of it โ a right-click
// on either of those would otherwise open the native menu. One window-level
// listener covers the whole window in one place.
if (typeof window !== "undefined") window.addEventListener("contextmenu", e => e.preventDefault());
function resizeCanvas() {
if (typeof window === "undefined" || !canvas) return; // import-safe under Node (dom.js idiom)
const dpr = window.devicePixelRatio || 1;
canvas.width = Math.round(canvas.clientWidth * dpr); // round so a fractional DPR (e.g. 1.25) doesn't
canvas.height = Math.round(canvas.clientHeight * dpr); // leave a sub-pixel sliver unrendered at the edges
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
if (typeof window !== "undefined") window.addEventListener("resize", resizeCanvas);
// The canvas fills a flex region, so its box can change from a LAYOUT shift that fires NO window
// `resize` event โ most visibly the topbar wrapping to a second line on a narrow window (its
// `flex-wrap` grows it, shrinking the view below), but also the Home button appearing or the
// portrait sheet collapsing. Without a refit, resizeCanvas never ran: the backing store kept its
// old, taller size, so the never-redrawn strip showed as a thick band at the bottom AND โ because
// the render viewport and the clickโworld transform then disagreed on the canvas height โ every
// selection landed offset. A ResizeObserver refits the backing store the instant the box changes,
// in one place (superseding the ad-hoc triggers above), and re-clamps the camera into the map.
if (typeof window !== "undefined" && window.ResizeObserver) {
new ResizeObserver(() => {
resizeCanvas();
if (game.state && game.input) {
clampCamera(game.input.getCamera(), game.state.map, canvas.clientWidth, canvas.clientHeight);
}
}).observe(canvas);
}
// A monitor move (or an OS scaling change) can change devicePixelRatio WITHOUT firing a resize
// event โ the CSS layout size is unchanged โ leaving the backing store at the old DPR (blurry on
// the sharper screen, oversampled on the other). matchMedia on the current dppx fires once when
// the ratio leaves it; refit both canvases and re-arm for the new ratio.
function watchDPR() {
if (typeof window === "undefined" || !window.matchMedia) return;
window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`)
.addEventListener("change", () => { resizeCanvas(); resizeMinimap(); watchDPR(); }, { once: true });
}
// Portrait bottom-sheet collapse: hide the panel to reclaim the whole screen,
// then resize the canvas into the freed space. The button only shows in the
// portrait layout (style.css); the class is scoped to that layout too, so it's
// inert on desktop/landscape. Clears panel-collapsed (panelToggleEl's own class, just below)
// so resizing across the portrait/landscape breakpoint can never leave the OTHER toggle's
// button showing an "expanded" glyph while its class still has the panel hidden.
if (sheetToggleEl) sheetToggleEl.addEventListener("click", () => {
document.body.classList.remove("panel-collapsed");
const collapsed = document.body.classList.toggle("sheet-collapsed");
sheetToggleEl.textContent = collapsed ? "โด" : "โพ";
requestAnimationFrame(resizeCanvas); // the view grew/shrank โ refit the backing store
});
// Side-panel collapse: the same fold-it-away idea as the portrait handle above, for the
// normal (non-portrait) side-column layout โ hide the panel to widen the view, then resize the
// canvas into the freed space. The button only shows outside the portrait layout (style.css);
// see sheetToggleEl's own handler above for why each toggle clears the other's class.
if (panelToggleEl) panelToggleEl.addEventListener("click", () => {
document.body.classList.remove("sheet-collapsed");
const collapsed = document.body.classList.toggle("panel-collapsed");
panelToggleEl.textContent = collapsed ? "โ" : "โธ";
requestAnimationFrame(resizeCanvas);
});
function resizeMinimap() {
if (typeof window === "undefined" || !minimapCanvas) return; // import-safe under Node (dom.js idiom)
const dpr = window.devicePixelRatio || 1;
minimapCanvas.width = Math.round(MINIMAP_W * dpr);
minimapCanvas.height = Math.round(MINIMAP_H * dpr);
minimapCtx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
if (typeof window !== "undefined") window.addEventListener("resize", resizeMinimap);
// Attached once, not per-game: state/input are read fresh from the shared session
// at click time, so this stays correct across a "choose another battlefield"
// restart without needing to re-wire on every startGame().
if (minimapCanvas) minimapCanvas.addEventListener("click", e => {
if (!game.state || !game.input) return;
const rect = minimapCanvas.getBoundingClientRect();
// Convert against the minimap's ACTUAL rendered size, not the fixed logical MINIMAP_W/H:
// the CSS shrinks the element on small/portrait viewports (style.css), so using the
// constant sent a phone tap on the right edge to ~56% of the map instead of the edge.
const world = minimapToWorld(game.state.map, rect.width, rect.height, e.clientX - rect.left, e.clientY - rect.top);
const camera = game.input.getCamera();
camera.x = world.x;
camera.y = world.y;
clampCamera(camera, game.state.map, canvas.clientWidth, canvas.clientHeight);
});
// Right-click the minimap to command the current selection to that spot without
// scrolling the main view first โ move for workers, attack-move for combat, so
// you can respond to a raid on a far flank straight from the map.
if (minimapCanvas) minimapCanvas.addEventListener("contextmenu", e => {
e.preventDefault();
if (!game.state || !game.input) return;
const rect = minimapCanvas.getBoundingClientRect();
const world = minimapToWorld(game.state.map, rect.width, rect.height, e.clientX - rect.left, e.clientY - rect.top); // actual size, not MINIMAP_W/H (see the click handler)
const selected = game.state.selection.map(id => game.state.units.get(id)).filter(Boolean);
if (!selected.length) return;
const combatants = selected.filter(u => UNITS[u.type].role === "combat");
const others = selected.filter(u => UNITS[u.type].role !== "combat");
if (combatants.length) issueAttackMove(combatants, world.x, world.y);
if (others.length) issueMove(others, world.x, world.y);
});
// Touch mode: the first finger anywhere grows the tap targets (style.css) and
// switches the HUD hints to touch phrasing. input.js sets the class on a canvas
// touch too; doing it here covers a first touch that lands on a HUD button. The
// panel signature includes isTouchMode(), so the loop's next renderHUD rebuilds
// the legend/hints in touch phrasing on its own.
if (typeof window !== "undefined") window.addEventListener("touchstart", () => {
if (!isTouchMode()) document.body.classList.add("touch");
}, { passive: true });
// ---- kickoff (all modules are evaluated by now, so these cross-module calls
// are safe) ----
buildHelpOverlay();
renderMapSelect();
resizeCanvas();
resizeMinimap();
watchDPR();
|