- Isometric canvas RTS vs dinosaur waves (fan demake of Repterra) - Economy: houses/taxes, farms, foresters, quarries; colonist staffing - Power grid: generators extend build range; brownout + recovery - Defense: walls/gates, watchtowers (AA), cannon towers (ground-only) - Taming: Primal Pen + Tamers collar weakened dinos; pets obey commands - Breeding: tamed pairs incubate eggs at the pen; hatchlings grow up - 7 dino species incl. flying Pteranodons and lake-raiding Suchomimus - Telegraphed waves with direction arrows; day-15 final horde; 3 difficulties - Day/night cycle, fog of war, minimap, synth audio, 1x-3x speeds - Save/Load/Continue + dawn autosave (full JSON state snapshots) - Tests: 80-assertion headless suite, browser boot + E2E, balance harness
143 lines
4.0 KiB
JavaScript
143 lines
4.0 KiB
JavaScript
/* =========================================================
|
|
* REPRTERRA WEB — entities.js
|
|
* Factories & shared helpers for buildings/units/dinos.
|
|
* ========================================================= */
|
|
'use strict';
|
|
window.RTS = window.RTS || {};
|
|
|
|
RTS.entities = (function () {
|
|
const U = RTS.util;
|
|
let nextId = 1;
|
|
|
|
const E = {};
|
|
E.resetIds = () => { nextId = 1; };
|
|
// after loading a save, keep new ids above everything deserialized
|
|
E.setIdFloor = (n) => { if (n >= nextId) nextId = n + 1; };
|
|
|
|
// ---------- footprint ----------
|
|
// returns [x0,y0] of top-left tile for a building centered at cx,cy
|
|
E.footOrigin = function (cx, cy, size) {
|
|
return [Math.round(cx - size / 2), Math.round(cy - size / 2)];
|
|
};
|
|
|
|
// ---------- buildings ----------
|
|
E.makeBuilding = function (defId, cx, cy, opts) {
|
|
const C = RTS.CONFIG;
|
|
const def = C.BUILDINGS[defId];
|
|
const instant = opts && opts.instant;
|
|
const b = {
|
|
kind: 'building',
|
|
id: nextId++,
|
|
defId,
|
|
name: def.name,
|
|
size: def.size,
|
|
x: Math.round(cx), y: Math.round(cy), // center tile coords
|
|
hp: instant ? def.hp : def.hp * 0.15,
|
|
maxHp: def.hp,
|
|
done: !!instant,
|
|
progress: instant ? 1 : 0,
|
|
buildTime: instant ? 0 : 2.5 + def.size * 2.5,
|
|
workersNeed: def.workers || 0,
|
|
workers: 0,
|
|
powered: true,
|
|
active: true, // production on/off
|
|
cool: 0, // tower fire cooldown
|
|
trainQ: [], // barracks queue [{t, unit}]
|
|
trainT: 0,
|
|
rallyX: null, rallyY: null,
|
|
burnT: 0,
|
|
};
|
|
return b;
|
|
};
|
|
|
|
// ---------- human units ----------
|
|
E.makeUnit = function (unitId, x, y) {
|
|
const C = RTS.CONFIG;
|
|
const def = C.UNITS[unitId];
|
|
const simSt = (RTS.sim && RTS.sim.state) ? RTS.sim.state() : null;
|
|
const armorLvl = (simSt && simSt.upgrades) ? simSt.upgrades.armor || 0 : 0;
|
|
const hp = def.hp * (1 + 0.25 * armorLvl);
|
|
return {
|
|
kind: 'unit',
|
|
id: nextId++,
|
|
unitId,
|
|
name: def.name,
|
|
x, y,
|
|
hp, maxHp: hp,
|
|
speed: def.speed,
|
|
cool: 0,
|
|
path: null, pathI: 0,
|
|
tx: null, ty: null, // move goal
|
|
attackMove: false,
|
|
targetId: 0, // focused enemy
|
|
selected: false,
|
|
facing: U.rng() * Math.PI * 2,
|
|
animT: U.rng() * 10,
|
|
dead: false,
|
|
};
|
|
};
|
|
|
|
// ---------- dinosaurs ----------
|
|
E.makeDino = function (dinoId, x, y, mode, home) {
|
|
const C = RTS.CONFIG;
|
|
const def = C.DINOS[dinoId];
|
|
const rng = U.rng;
|
|
return {
|
|
kind: 'dino',
|
|
id: nextId++,
|
|
dinoId,
|
|
name: def.name,
|
|
x, y,
|
|
hp: def.hp, maxHp: def.hp,
|
|
dmg: def.dmg,
|
|
rof: def.rof,
|
|
speed: def.speed * rng.range(0.9, 1.12),
|
|
r: def.r,
|
|
flying: !!def.flying,
|
|
amphibious: !!def.amphibious,
|
|
ranged: def.ranged || 0,
|
|
bldMult: def.bldMult || 1,
|
|
bounty: def.bounty,
|
|
scale: def.scale,
|
|
color: def.color,
|
|
mode: mode || 'wave', // 'roam' | 'wave' | 'final'
|
|
homeX: home ? home.x : x, homeY: home ? home.y : y,
|
|
targetId: 0, targetType: '',
|
|
path: null, pathI: 0,
|
|
repathT: 0,
|
|
stuckT: 0,
|
|
lastDist: Infinity,
|
|
cool: 0,
|
|
facing: rng() * Math.PI * 2,
|
|
animT: rng() * 10,
|
|
wanderT: 0,
|
|
wx: x, wy: y,
|
|
aggro: !!(mode === 'wave'),
|
|
dead: false,
|
|
hitFlash: 0,
|
|
};
|
|
};
|
|
|
|
// ---------- projectiles ----------
|
|
E.makeProj = function (type, x, y, tx, ty, opts) {
|
|
return {
|
|
kind: 'proj',
|
|
id: nextId++,
|
|
type, // 'bullet' | 'shell' | 'spit'
|
|
x, y, z: opts && opts.z != null ? opts.z : 8,
|
|
tx, ty,
|
|
speed: opts && opts.speed || 14,
|
|
dmg: opts && opts.dmg || 5,
|
|
aoe: opts && opts.aoe || 0,
|
|
air: !!(opts && opts.air),
|
|
fromPlayer: !(opts && opts.enemy),
|
|
color: opts && opts.color || '#ffd76a',
|
|
trail: [],
|
|
arc: opts && opts.arc || 0, // lob height for shells/spits
|
|
t: 0, total: 1,
|
|
};
|
|
};
|
|
|
|
return E;
|
|
})();
|