Repterra Web — full game: base building, power grid, taming & breeding, aquatic raiders, day/night, save/load
- 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
This commit is contained in:
+142
@@ -0,0 +1,142 @@
|
||||
/* =========================================================
|
||||
* 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;
|
||||
})();
|
||||
Reference in New Issue
Block a user