/* ========================================================= * REPRTERRA WEB — main.js * Bootstrap + game loop + app states. * ========================================================= */ 'use strict'; window.RTS = window.RTS || {}; RTS.main = (function () { let appState = 'menu'; // 'menu' | 'playing' | 'over' let paused = false; let lastT = 0; let acc = 0; let hudT = 0, mmT = 0; let lastDaySeen = 1; const M = {}; M.state = () => (appState === 'playing' && paused) ? 'playing' : appState; M.isPaused = () => paused; // --------------------------------------------------------- // SAVE STORAGE — localStorage glue around sim.serialize // --------------------------------------------------------- const SAVE_KEY = 'repterra-web-save-v2'; const storage = { ok: (() => { try { localStorage.setItem('_rt', '1'); localStorage.removeItem('_rt'); return true; } catch (e) { return false; } })(), has() { try { return !!localStorage.getItem(SAVE_KEY); } catch (e) { return false; } }, save(manual) { if (!this.ok || appState !== 'playing') return false; const snap = RTS.sim.serialize(); if (!snap || snap.st.over) return false; try { localStorage.setItem(SAVE_KEY, JSON.stringify(snap)); RTS.ui.toast(manual ? '💾 Colony saved.' : '💾 Dawn autosave.', ''); return true; } catch (e) { return false; } }, autosave() { this.save(false); }, load() { if (!this.ok) return false; try { const raw = localStorage.getItem(SAVE_KEY); if (!raw) return false; if (!RTS.sim.deserialize(raw)) return false; const q = RTS.sim.hq(); if (q) { RTS.render.cam.x = q.x; RTS.render.cam.y = q.y; RTS.render.cam.zoom = Math.max(RTS.render.cam.zoom, 1.0); } paused = false; document.getElementById('pauseoverlay').style.display = 'none'; appState = 'playing'; lastDaySeen = RTS.sim.state().day; RTS.ui.hideEnd && RTS.ui.hideEnd(); RTS.ui.showMenu(false); RTS.ui.select(null); RTS.ui.setPlacing(null); RTS.ui.toast('📂 Welcome back — Day ' + RTS.sim.state().day + '.', ''); return true; } catch (e) { console.error('load failed', e); return false; } }, }; RTS.storage = storage; M.boot = function () { const cv = document.getElementById('game'); RTS.render.init(cv); RTS.ui.init(); RTS.input.attach(cv); RTS.input.ui = RTS.ui; // minimap const mm = document.getElementById('minimap'); mm.width = 176; mm.height = 176; window.addEventListener('resize', () => RTS.render.resize()); // minimap click-to-jump mm.addEventListener('mousedown', (e) => { const st = RTS.sim.state(); if (!st) return; const r = mm.getBoundingClientRect(); RTS.render.cam.x = (e.clientX - r.left) / r.width * st.world.tiles.W; RTS.render.cam.y = (e.clientY - r.top) / r.height * st.world.tiles.H; RTS.render.clampCam(); }); RTS.ui.showMenu(true); requestAnimationFrame(loop); }; M.startGame = function (diff) { RTS.audio.resume(); RTS.sim.newGame(diff); const q = RTS.sim.hq(); RTS.render.cam.x = q.x; RTS.render.cam.y = q.y; RTS.render.cam.zoom = 1.1; paused = false; appState = 'playing'; lastDaySeen = 1; RTS.ui.hideEnd(); RTS.ui.showMenu(false); RTS.ui.setPlacing(null); RTS.ui.select(null); RTS.ui.toast('Colony established. Build houses and generators first!', ''); setTimeout(() => { if (appState === 'playing') RTS.ui.toast('Tip: Pteranodons FLY over walls — keep Watchtowers ready.', ''); }, 6000); }; M.restart = function () { M.startGame(RTS.sim.state() ? RTS.sim.state().diff : 'normal'); }; M.loadSave = function () { storage.load(); }; M.toMenu = function () { appState = 'menu'; RTS.ui.showEnd ? null : null; document.getElementById('endscreen').style.display = 'none'; RTS.ui.showMenu(true); }; M.togglePause = function () { if (appState !== 'playing') return; paused = !paused; document.getElementById('pauseoverlay').style.display = paused ? 'flex' : 'none'; }; M.setSpeed = function (s) { if (!RTS.sim.state()) return; RTS.sim.state().speed = s; if (paused && s > 0) M.togglePause(); markSpeedBtns(s); }; M.bumpSpeed = function (d) { const st = RTS.sim.state(); if (!st) return; M.setSpeed(Math.min(3, Math.max(1, st.speed + d))); }; function markSpeedBtns(s) { document.querySelectorAll('#topbtns .tbtn').forEach((b) => { b.classList.toggle('active', b.textContent === s + '×'); }); } M.hotkey = function (key) { if (key === 'Enter') { // start with selected/default difficulty from menu const sel = document.querySelector('.diffbtn.sel') || document.querySelector('[data-diff="normal"]'); if (sel && appState === 'menu') sel.click(); } }; // --------------------------------------------------------- function loop(t) { requestAnimationFrame(loop); const dtReal = Math.min(0.05, (t - lastT) / 1000 || 0.016); lastT = t; RTS.input.tick(dtReal); const st = RTS.sim.state(); if (st && appState === 'playing') { if (!paused) { acc += dtReal * st.speed; let steps = 0; while (acc > 1 / 120 && steps < 12) { const step = Math.min(acc, 1 / 30); RTS.sim.tick(step); acc -= step; steps++; } checkEvents(st); if (st.over && !st.endShown) { st.endShown = true; setTimeout(() => { if (st.over) RTS.ui.showEnd(st); }, 1400); } } // draw RTS.render.draw(st, RTS.ui, t / 1000); hudT -= dtReal; if (hudT <= 0) { hudT = 0.25; RTS.ui.updateHUD(); } mmT -= dtReal; if (mmT <= 0) { mmT = 0.3; const mmCtx = document.getElementById('minimap').getContext('2d'); RTS.render.drawMinimap(mmCtx, st, 176); } drawPings(st, dtReal); } else if (st && appState === 'over') { RTS.render.draw(st, RTS.ui, t / 1000); } } function drawPings(st, dt) { for (const p of RTS.ui.pings) p.t += dt; RTS.ui.pings = RTS.ui.pings.filter(p => p.t < 0.8); } // --------------------------------------------------------- let wasWarned = false, hqHitToast = false, brownToast = false; function checkEvents(st) { // wave spawn toast const w = st.waves[Math.max(0, st.waveIdx - 1)]; if (w && w.spawned && !w._toast) { w._toast = true; RTS.ui.toast(w.final ? '☠ THE FINAL ASSAULT HAS BEGUN!' : '🌊 A dinosaur wave is attacking!', w.final ? 'bad' : 'warn'); if (w.final) RTS.audio.roar(); } // warning siren once if (st.warnT > 0 && !wasWarned) { wasWarned = true; RTS.ui.toast('⚠ Dinosaur horde spotted approaching the colony!', 'bad'); } if (st.warnT <= 0) wasWarned = false; // HQ under attack const q = RTS.sim.hq(); if (q && q.hp < q.maxHp && !hqHitToast) { hqHitToast = true; RTS.ui.toast('🏛️ THE COMMAND CENTER IS UNDER ATTACK!', 'bad'); RTS.audio.alarm(); } if (q && q.hp >= q.maxHp) hqHitToast = false; // brownout if (st.energyUse > st.energyCap && !brownToast) { brownToast = true; RTS.ui.toast('⚡ Power shortage! Newest buildings are offline — build Generators.', 'warn'); } if (st.energyUse <= st.energyCap) brownToast = false; // starving if (st.starving && !checkEvents._starve) { checkEvents._starve = true; RTS.ui.toast('🍖 Food shortage! Colonists are starving.', 'warn'); } if (!st.starving) checkEvents._starve = false; } return M; })(); window.addEventListener('DOMContentLoaded', () => RTS.main.boot());