/* =========================================================
* REPRTERRA WEB β ui.js
* HUD: resource bar, build palette, selection panel,
* warnings, toasts, minimap frame, menus & end screens.
* ========================================================= */
'use strict';
window.RTS = window.RTS || {};
RTS.ui = (function () {
const U = RTS.util;
const C = RTS.CONFIG;
const UI = {};
UI.sel = null; // {kind:'building'|'dino'|'units', id?, ids?}
Object.defineProperty(UI, 'selUnits', {
get() { return (UI.sel && UI.sel.kind === 'units') ? UI.sel.ids.filter(id => !isDead(id)) : []; },
});
UI.placing = null; // {defId,x,y}
UI.dragRect = null;
UI.pings = []; // {x,y,t,color}
UI.tooltipEl = null;
let el = {}; // cached elements
let paletteBtns = [];
const ICONS = { gold: 'π°', wood: 'πͺ΅', stone: 'πͺ¨', food: 'π', energy: 'β‘', pop: 'π₯' };
const BICON = {
hq: 'ποΈ', house: 'π ', farm: 'πΎ', forester: 'π²', quarry: 'βοΈ',
generator: 'π', wall: 'π§±', gate: 'πͺ', watchtower: 'πΉ', cannon: 'π£',
barracks: 'ποΈ', primalpen: 'π¦΄',
};
// ---------------------------------------------------------
UI.init = function () {
el.hud = document.getElementById('hud');
el.top = document.getElementById('topbar');
el.resGold = q('#res-gold .v'); el.resGoldR = q('#res-gold .r');
el.resWood = q('#res-wood .v'); el.resWoodR = q('#res-wood .r');
el.resStone = q('#res-stone .v'); el.resStoneR = q('#res-stone .r');
el.resFood = q('#res-food .v'); el.resFoodR = q('#res-food .r');
el.energy = q('#res-energy .v');
el.pop = q('#res-pop .v');
el.day = document.getElementById('daylabel');
el.wave = document.getElementById('wavelabel');
el.banner = document.getElementById('wavebanner');
el.bannerTxt = document.getElementById('wavebanner-text');
buildPalette();
buildTopButtons();
el.panel = document.getElementById('selpanel');
el.toasts = document.getElementById('toasts');
el.minimap = document.getElementById('minimap');
el.mmWrap = document.getElementById('mmwrap');
// tooltip
el.tip = document.getElementById('tooltip');
window.addEventListener('mousemove', (e) => {
if (el.tip.style.display === 'block') {
el.tip.style.left = Math.min(window.innerWidth - 260, e.clientX + 14) + 'px';
el.tip.style.top = Math.min(window.innerHeight - 120, e.clientY + 16) + 'px';
}
});
// menu
el.menu = document.getElementById('menu');
el.endscreen = document.getElementById('endscreen');
document.querySelectorAll('[data-diff]').forEach(b => {
b.addEventListener('click', () => { RTS.audio.resume(); RTS.audio.click(); RTS.main.startGame(b.dataset.diff); });
});
document.getElementById('btn-how').addEventListener('click', () => toggleHelp());
document.querySelectorAll('.backtomenu').forEach(b => b.addEventListener('click', () => RTS.main.toMenu()));
document.querySelectorAll('[data-restart]').forEach(b => b.addEventListener('click', () => RTS.main.restart()));
// save / load / continue
const cont = document.getElementById('continueBtn');
if (cont) {
if (RTS.storage && RTS.storage.has()) cont.style.display = '';
else if (RTS.storage) { // re-check shortly (storage may just have become available)
setTimeout(() => { if (RTS.storage.has()) cont.style.display = ''; }, 300);
}
cont.addEventListener('click', () => { RTS.audio.resume(); RTS.audio.click(); RTS.main.loadSave(); });
}
const sb = document.getElementById('savebtn');
if (sb) sb.addEventListener('click', () => { RTS.storage.save(true); });
const lb = document.getElementById('loadbtn');
if (lb) {
const refresh = () => { lb.disabled = !RTS.storage.has(); };
refresh();
setInterval(refresh, 1500);
lb.addEventListener('click', () => { RTS.audio.click(); RTS.main.loadSave(); });
}
};
function q(s) { return document.querySelector(s); }
function buildTopButtons() {
const tb = document.getElementById('topbtns');
tb.innerHTML = '';
mkBtn('βΈ', 'Pause (Space)', () => RTS.main.togglePause());
mkBtn('1Γ', 'Normal speed', () => RTS.main.setSpeed(1));
mkBtn('2Γ', 'Fast forward', () => RTS.main.setSpeed(2));
mkBtn('3Γ', 'Very fast', () => RTS.main.setSpeed(3));
mkBtn('π', 'Mute (M)', () => UI.toggleMute(), 'mutebtn');
mkBtn('β', 'Help', () => toggleHelp());
function mkBtn(label, tip, fn, id) {
const b = document.createElement('button');
b.className = 'tbtn';
if (id) b.id = id;
b.textContent = label;
b.title = tip;
b.addEventListener('click', () => { RTS.audio.click(); fn(); });
tb.appendChild(b);
return b;
}
}
function buildPalette() {
const pal = document.getElementById('palette');
pal.innerHTML = '';
paletteBtns = [];
let slot = 0;
C.PALETTE.forEach((group) => {
const col = document.createElement('div');
col.className = 'palcol';
group.forEach((defId) => {
const def = C.BUILDINGS[defId];
const hkNum = slot < 9 ? (slot + 1) : 0;
const b = document.createElement('button');
b.className = 'palbtn';
b.innerHTML =
'' + BICON[defId] + '' +
'' + def.name + '' +
'' + costStr(def.cost) + '' +
'' + (hkNum || '') + '';
b.addEventListener('click', () => { RTS.audio.click(); UI.togglePlacing(defId); });
b.addEventListener('mouseenter', (e) => showTip(buildTip(def), e));
b.addEventListener('mouseleave', hideTip);
col.appendChild(b);
paletteBtns.push({ defId, btn: b });
slot++;
});
pal.appendChild(col);
});
}
function costStr(cost) {
return Object.entries(cost).map(([k, v]) => ICONS[k] + v).join(' ');
}
function energyStr(def) {
if (def.energyUse) return 'Uses β‘' + def.energyUse + (def.workers ? ' Β· π·' + def.workers : '') + '';
if (def.energyProd) return 'Makes β‘' + def.energyProd + '';
return 'No power needed';
}
function buildTip(def) {
return '' + BICON[def.id] + ' ' + def.name + '
' + def.desc + '
' +
costStr(def.cost) + '
' + energyStr(def) + (def.hp ? '
HP ' + def.hp + '' : '');
}
function showTip(html, e) {
el.tip.innerHTML = html;
el.tip.style.display = 'block';
el.tip.style.left = Math.min(window.innerWidth - 260, e.clientX + 14) + 'px';
el.tip.style.top = Math.min(window.innerHeight - 140, e.clientY + 16) + 'px';
}
function hideTip() { el.tip.style.display = 'none'; }
UI.flatPalette = function () { return paletteBtns.map(p => p.defId); };
// ---------------------------------------------------------
UI.togglePlacing = function (defId) {
if (UI.placing && UI.placing.defId === defId) { UI.setPlacing(null); return; }
const def = C.BUILDINGS[defId];
UI.setPlacing({ defId, x: Math.round(RTS.input.mouse.wx), y: Math.round(RTS.input.mouse.wy) });
};
UI.setPlacing = function (p) {
UI.placing = p;
paletteBtns.forEach(pb => pb.btn.classList.toggle('on', !!p && pb.defId === p.defId));
};
UI.select = function (sel) {
UI.sel = sel;
refreshPanel(true);
};
UI.fxPing = function (x, y, color) {
UI.pings.push({ x, y, t: 0, color: color || '#fff' });
};
UI.toast = function (msg, cls) {
const t = document.createElement('div');
t.className = 'toast ' + (cls || '');
t.textContent = msg;
el.toasts.appendChild(t);
setTimeout(() => t.classList.add('show'), 10);
setTimeout(() => { t.classList.remove('show'); setTimeout(() => t.remove(), 400); }, 3800);
while (el.toasts.children.length > 5) el.toasts.firstChild.remove();
};
UI.toggleMute = function () {
const muted = RTS.audio.toggleMute();
const mb = document.getElementById('mutebtn');
if (mb) mb.textContent = muted ? 'π' : 'π';
};
// ---------------------------------------------------------
function isDead(id) {
const st = RTS.sim.state();
return !st.units.some(u => u.id === id && !u.dead);
}
function refreshPanel(rebuild) {
const st = RTS.sim.state();
if (!st || !UI.sel) { el.panel.style.display = 'none'; return; }
el.panel.style.display = 'block';
if (UI.sel.kind === 'units') {
const us = UI.sel.ids.map(id => st.units.find(u => u.id === id && !u.dead)).filter(Boolean);
if (!us.length) { UI.select(null); return; }
if (rebuild || !el.panel.dataset.units) {
el.panel.dataset.units = '1'; delete el.panel.dataset.bld;
const counts = {};
us.forEach(u => { counts[u.unitId] = (counts[u.unitId] || 0) + 1; });
const title = Object.entries(counts).map(([k, n]) => C.UNITS[k].name + ' Γ' + n).join(' Β· ');
el.panel.innerHTML =
'
Right-click: move Β· Right-click a dino: focus it
Drag-select more, double-click: all on screen
Fighting for the colony! ' + (d.flying ? 'Air power!' : '') + '
' + 'Right-click ground: new guard post
Right-click a wild dino: attack it
Your Primal Pen heals it nearby.
' + (d.flying ? 'β Flying β ignores walls!' : (d.amphibious ? 'π Amphibious β strikes from lakes!' : 'Ground')) + ' Β· ' + (d.mode === 'roam' ? 'Roaming the wilds' : 'ATTACKING!') + '
' + (C.UNTAMEABLE[d.dinoId] ? 'Too powerful to tame.
' : 'Weaken below 32% HP, then send a Tamer to collar it.
'); } return; } // building const b = RTS.sim.getBuilding(UI.sel.id); if (!b) { UI.select(null); return; } el.panel.dataset.units = ''; if (el.panel.dataset.bld !== String(b.id)) { el.panel.dataset.bld = String(b.id); rebuildBuildingPanel(b); } updateBuildingPanel(b); } function rebuildBuildingPanel(b) { const def = C.BUILDINGS[b.defId]; let html = 'π₯ Breeding: needs 2+ tamed dinos nearby Β· '
+ C.BREED.foodPerEgg + ' πΎ per egg
'
+ (eggs.length ? 'Incubating ' + eggs.length + '/' + C.BREED.maxPerPen + (e0 ? ' β ' + prog + '%' : '')
: 'No eggs yet (pair up your pets here)')
+ '
𦴠+2 tame slots · heals tamed dinos nearby.
Tamers collar weakened dinos (<32% HP) automatically.
The herds are broken. Repterra breathes againβ¦
' : 'The reptiles reclaim the land. Rebuild, and try again.
'); }; UI.hideEnd = function () { el.endscreen.style.display = 'none'; }; return UI; })();