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:
+146
@@ -0,0 +1,146 @@
|
||||
/* =========================================================
|
||||
* REPRTERRA WEB — world.js
|
||||
* Procedural map generation (per run, like Repterra):
|
||||
* grass/dirt/water, forest & rock deposits, dino caves.
|
||||
* ========================================================= */
|
||||
'use strict';
|
||||
window.RTS = window.RTS || {};
|
||||
|
||||
RTS.world = (function () {
|
||||
const U = RTS.util;
|
||||
const W = RTS.CONFIG.WORLD.W;
|
||||
const H = RTS.CONFIG.WORLD.H;
|
||||
|
||||
// value noise with bilinear interp + octaves
|
||||
function makeNoise(rng, size) {
|
||||
const g = new Float32Array(size * size);
|
||||
for (let i = 0; i < g.length; i++) g[i] = rng();
|
||||
return function (x, y) {
|
||||
const xi = Math.floor(x), yi = Math.floor(y);
|
||||
const xf = x - xi, yf = y - yi;
|
||||
const sx = xf * xf * (3 - 2 * xf), sy = yf * yf * (3 - 2 * yf);
|
||||
const x0 = ((xi % size) + size) % size, y0 = ((yi % size) + size) % size;
|
||||
const x1 = (x0 + 1) % size, y1 = (y0 + 1) % size;
|
||||
const a = U.lerp(g[y0 * size + x0], g[y0 * size + x1], sx);
|
||||
const b = U.lerp(g[y1 * size + x0], g[y1 * size + x1], sx);
|
||||
return U.lerp(a, b, sy);
|
||||
};
|
||||
}
|
||||
|
||||
function generate(seed) {
|
||||
const rng = U.makeRng(seed || ((Date.now() / 1000) | 0));
|
||||
const T = {
|
||||
W, H,
|
||||
terrain: new Uint8Array(W * H), // 0 grass, 1 grass2, 2 dirt, 3 water
|
||||
tree: new Uint8Array(W * H), // wood units on tile (0..8)
|
||||
treeMax: new Uint8Array(W * H),
|
||||
rock: new Uint8Array(W * H), // stone units
|
||||
rockMax: new Uint8Array(W * H),
|
||||
variant: new Float32Array(W * H), // visual noise
|
||||
};
|
||||
|
||||
const n1 = makeNoise(rng, 16), n2 = makeNoise(rng, 32), n3 = makeNoise(rng, 64);
|
||||
|
||||
// --- base terrain + water ---
|
||||
for (let y = 0; y < H; y++) {
|
||||
for (let x = 0; x < W; x++) {
|
||||
const i = y * W + x;
|
||||
let h = n1(x / 11, y / 11) * 0.55 + n2(x / 5.5, y / 5.5) * 0.3 + n3(x / 2.6, y / 2.6) * 0.15;
|
||||
T.variant[i] = n3(x * 1.7, y * 1.7);
|
||||
// keep center playable
|
||||
const cx = x - W / 2, cy = y - H / 2;
|
||||
const dc = Math.sqrt(cx * cx + cy * cy);
|
||||
h += Math.max(0, (dc - 9)) * 0.004; // slight rise away from base
|
||||
if (h < 0.30 && dc > 10) { T.terrain[i] = 3; continue; } // lake
|
||||
T.terrain[i] = h < 0.42 ? 1 : (h < 0.47 ? 2 : 0);
|
||||
if (rng() < 0.02 && T.terrain[i] === 1) T.terrain[i] = 2;
|
||||
}
|
||||
}
|
||||
|
||||
const clearArea = (cx, cy, r) => {
|
||||
for (let y = Math.max(0, cy - r); y <= Math.min(H - 1, cy + r); y++)
|
||||
for (let x = Math.max(0, cx - r); x <= Math.min(W - 1, cx + r); x++) {
|
||||
const i = y * W + x;
|
||||
if (U.dist(x, y, cx, cy) <= r) { T.tree[i] = 0; T.rock[i] = 0; }
|
||||
}
|
||||
};
|
||||
const unwaterArea = (cx, cy, r) => {
|
||||
for (let y = Math.max(0, cy - r); y <= Math.min(H - 1, cy + r); y++)
|
||||
for (let x = Math.max(0, cx - r); x <= Math.min(W - 1, cx + r); x++) {
|
||||
const i = y * W + x;
|
||||
if (U.dist(x, y, cx, cy) <= r && T.terrain[i] === 3) T.terrain[i] = 1;
|
||||
}
|
||||
};
|
||||
|
||||
// --- HQ site: near center ---
|
||||
const hq = { x: Math.floor(W / 2), y: Math.floor(H / 2) };
|
||||
while (T.terrain[hq.y * W + hq.x] === 3) hq.x--;
|
||||
unwaterArea(hq.x, hq.y, 9);
|
||||
clearArea(hq.x, hq.y, 7);
|
||||
|
||||
// --- forests: blobby clusters ---
|
||||
const forests = [];
|
||||
const nForest = 14;
|
||||
for (let f = 0; f < nForest; f++) {
|
||||
const fx = rng.int(4, W - 5), fy = rng.int(4, H - 5);
|
||||
if (U.dist(fx, fy, hq.x, hq.y) < 9) continue;
|
||||
const fr = rng.range(2.5, 4.8);
|
||||
forests.push({ fx, fy, fr });
|
||||
for (let y = Math.max(0, fy - 6); y <= Math.min(H - 1, fy + 6); y++)
|
||||
for (let x = Math.max(0, fx - 6); x <= Math.min(W - 1, fx + 6); x++) {
|
||||
const i = y * W + x;
|
||||
const d = U.dist(x, y, fx, fy) + n3(x / 2.2, y / 2.2) * 1.6;
|
||||
if (d < fr && T.terrain[i] !== 3 && !(Math.abs(x - hq.x) < 7 && Math.abs(y - hq.y) < 7)) {
|
||||
T.treeMax[i] = 8; T.tree[i] = 8;
|
||||
T.rock[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- rocks: scattered outcrops ---
|
||||
const nRock = 12;
|
||||
for (let f = 0; f < nRock; f++) {
|
||||
const rx = rng.int(4, W - 5), ry = rng.int(4, H - 5);
|
||||
if (U.dist(rx, ry, hq.x, hq.y) < 10) continue;
|
||||
const rr = rng.range(1.6, 3.0);
|
||||
for (let y = Math.max(0, ry - 4); y <= Math.min(H - 1, ry + 4); y++)
|
||||
for (let x = Math.max(0, rx - 4); x <= Math.min(W - 1, rx + 4); x++) {
|
||||
const i = y * W + x;
|
||||
const d = U.dist(x, y, rx, ry) + n3(x / 1.8, y / 1.8) * 1.2;
|
||||
if (d < rr && T.terrain[i] !== 3 && !(Math.abs(x - hq.x) < 8 && Math.abs(y - hq.y) < 8)) {
|
||||
T.rockMax[i] = 10; T.rock[i] = 10;
|
||||
T.tree[i] = 0; T.treeMax[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// guarantee some starting resources within reach of HQ
|
||||
const ensureNear = (field, fieldMax, want) => {
|
||||
let placed = 0, guard = 0;
|
||||
while (placed < want && guard++ < 400) {
|
||||
const ang = rng() * Math.PI * 2;
|
||||
const dd = rng.range(4.5, 8.5);
|
||||
const x = Math.round(hq.x + Math.cos(ang) * dd);
|
||||
const y = Math.round(hq.y + Math.sin(ang) * dd);
|
||||
if (!U.inBounds(x, y, W, H)) continue;
|
||||
const i = y * W + x;
|
||||
if (T.terrain[i] === 3) { T.terrain[i] = 1; }
|
||||
// small patch
|
||||
for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) {
|
||||
const j = (y + dy) * W + (x + dx);
|
||||
if (!U.inBounds(x + dx, y + dy, W, H)) continue;
|
||||
if (T.terrain[j] !== 3 && rng() < 0.75 && !T.rock[j]) {
|
||||
field[j] = fieldMax[j] = field === T.tree ? 8 : 10;
|
||||
}
|
||||
}
|
||||
placed++;
|
||||
}
|
||||
};
|
||||
ensureNear(T.tree, T.treeMax, 4);
|
||||
ensureNear(T.rock, T.rockMax, 3);
|
||||
|
||||
return { tiles: T, hq, seed, rng };
|
||||
}
|
||||
|
||||
return { generate };
|
||||
})();
|
||||
Reference in New Issue
Block a user