- 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
140 lines
5.9 KiB
JavaScript
140 lines
5.9 KiB
JavaScript
/* Feature screenshots: taming at dusk, lake raider, wall assault */
|
|
'use strict';
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { chromium } = require('/tmp/node_modules/playwright-core');
|
|
|
|
const EXE = '/root/.cache/ms-playwright/chromium_headless_shell-1148/chrome-linux/headless_shell';
|
|
const URL = 'http://127.0.0.1:8933/index.html';
|
|
const OUT = path.join(__dirname, '..', 'shots');
|
|
|
|
(async () => {
|
|
const browser = await chromium.launch({ executablePath: EXE, args: ['--no-sandbox'] });
|
|
const page = await browser.newPage({ viewport: { width: 1440, height: 860 } });
|
|
const errors = [];
|
|
page.on('pageerror', e => errors.push(String(e)));
|
|
await page.goto(URL);
|
|
await page.waitForTimeout(700);
|
|
|
|
// start a normal game
|
|
await page.evaluate(() => {
|
|
document.querySelector('[data-diff="normal"]').click();
|
|
});
|
|
await page.waitForTimeout(500);
|
|
|
|
// ---- diorama 1: dusk base with Primal Pen, tamed raptor, tamer at work ----
|
|
await page.evaluate(() => {
|
|
const s = RTS.sim.state();
|
|
s.res.gold += 9000; s.res.wood += 9000; s.res.stone += 9000;
|
|
const hq = RTS.sim.hq();
|
|
const put = (id, dx, dy) => {
|
|
const x = Math.round(hq.x + dx), y = Math.round(hq.y + dy);
|
|
if (!RTS.sim.canPlace(id, x, y).ok) return null;
|
|
const r = RTS.sim.place(id, x, y);
|
|
if (!r.ok) return null;
|
|
r.b.done = true; r.b.progress = 1; r.b.hp = r.b.maxHp;
|
|
return r.b;
|
|
};
|
|
const tryPut = (id, spots) => { for (const [dx, dy] of spots) { const b = put(id, dx, dy); if (b) return b; } return null; };
|
|
// a tidy little colony
|
|
put('generator', -4, -3); put('generator', 4, 3);
|
|
put('house', -3, 2); put('house', 3, -3); put('house', -5, 1);
|
|
put('farm', 2, 4); put('forester', -6, -2); put('quarry', 6, -1);
|
|
put('watchtower', -2, -5); put('watchtower', 5, 1); put('watchtower', 0, 5); put('watchtower', -6, 4);
|
|
put('barracks', 1, -5);
|
|
window.__pen = tryPut('primalpen', [[-2, 3], [-3, 3], [-2, 4], [2, -4], [4, -1]]);
|
|
// rangers on guard
|
|
for (let i = 0; i < 5; i++) {
|
|
const u = RTS.entities.makeUnit('ranger', hq.x + (i - 2) * 1.2, hq.y + 2.5);
|
|
s.units.push(u);
|
|
}
|
|
const tamer = RTS.entities.makeUnit('tamer',
|
|
(window.__pen ? window.__pen.x : hq.x) + 1.5,
|
|
(window.__pen ? window.__pen.y : hq.y) + 1.8);
|
|
s.units.push(tamer);
|
|
// a weakened raptor right next to him → capture starts on its own
|
|
const wild = RTS.entities.makeDino('raptor', tamer.x + 1.2, tamer.y + 0.4, 'roam');
|
|
wild.hp = wild.maxHp * 0.2;
|
|
s.dinos.push(wild);
|
|
// an already-loyal trike guarding the gate side
|
|
const pet = RTS.entities.makeDino('trike', hq.x + 4.5, hq.y + 4, 'roam');
|
|
pet.tamed = true; pet.mode = 'pet'; pet.homeX = pet.x; pet.homeY = pet.y;
|
|
pet.hp = pet.maxHp * 0.85;
|
|
s.dinos.push(pet);
|
|
// golden hour
|
|
s.time = Math.floor(RTS.CONFIG.WORLD.DAY_LENGTH * 0.52);
|
|
});
|
|
// let the capture channel run while rendering
|
|
await page.evaluate(() => { for (let i = 0; i < 30 * 2; i++) RTS.sim.tick(1 / 30); });
|
|
await page.waitForTimeout(400);
|
|
await page.screenshot({ path: OUT + '/taming.png' });
|
|
|
|
// ---- diorama 2: suchomimus rising from a lake ----
|
|
await page.evaluate(() => {
|
|
const s = RTS.sim.state();
|
|
const W = RTS.CONFIG.WORLD.W, H = RTS.CONFIG.WORLD.H;
|
|
// find a lake tile reasonably near the visible area
|
|
let spot = null;
|
|
outer:
|
|
for (let y = 6; y < H - 6; y++)
|
|
for (let x = 6; x < W - 6; x++)
|
|
if (s.world.tiles.terrain[y * W + x] === 3) { spot = { x: x + .5, y: y + .5 }; break outer; }
|
|
if (spot) {
|
|
// a colonist on the shore keeps the fog revealed (updateFog stamps units)
|
|
const shore = { x: spot.x + 1.5, y: spot.y - 1 };
|
|
s.units.push(RTS.entities.makeUnit('colonist', shore.x, shore.y));
|
|
const d = RTS.entities.makeDino('sucho', spot.x - 1, spot.y + 0.5, 'final');
|
|
d.aggro = true;
|
|
s.dinos.push(d);
|
|
RTS.render.cam.x = (spot.x + shore.x) / 2;
|
|
RTS.render.cam.y = (spot.y + shore.y) / 2;
|
|
RTS.render.cam.zoom = 1.3;
|
|
s.shakeT = 0; // keep the shot steady
|
|
}
|
|
s.time = Math.floor(RTS.CONFIG.WORLD.DAY_LENGTH * 0.30); // daylight
|
|
});
|
|
await page.waitForTimeout(600);
|
|
await page.screenshot({ path: OUT + '/lake.png' });
|
|
|
|
// ---- diorama 3: full assault on the walls ----
|
|
await page.evaluate(() => {
|
|
const s = RTS.sim.state();
|
|
const hq = RTS.sim.hq();
|
|
// wall arc between the horde and the colony
|
|
for (let k = -4; k <= 4; k++) {
|
|
if (k === 0) continue;
|
|
const x = Math.round(hq.x + 7), y = Math.round(hq.y + k * 1.15);
|
|
if (RTS.sim.canPlace('wall', x, y).ok) {
|
|
const r = RTS.sim.place('wall', x, y);
|
|
if (r.ok) { r.b.done = true; r.b.progress = 1; }
|
|
}
|
|
}
|
|
// horde: mixed ground force + air support
|
|
const comp = { raptor: 8, compy: 12, trike: 2, dilo: 3 };
|
|
let i = 0;
|
|
for (const type in comp)
|
|
for (let k = 0; k < comp[type]; k++) {
|
|
const d = RTS.entities.makeDino(type, hq.x + 13 + (i % 4), hq.y - 6 + (i % 13), 'final');
|
|
d.aggro = true; i++;
|
|
s.dinos.push(d);
|
|
}
|
|
for (let k = 0; k < 6; k++) {
|
|
const d = RTS.entities.makeDino('ptera', hq.x + 9 + (k % 3), hq.y - 4 + k * 1.4, 'final');
|
|
d.aggro = true;
|
|
s.dinos.push(d);
|
|
}
|
|
s.time = Math.floor(RTS.CONFIG.WORLD.DAY_LENGTH * 0.62); // night falls on battle
|
|
RTS.render.cam.x = hq.x + 5; RTS.render.cam.y = hq.y;
|
|
s.warnT = 0;
|
|
});
|
|
// simulate ~4s so projectiles fly and muzzle flashes show
|
|
await page.evaluate(() => { for (let k = 0; k < 30 * 4; k++) RTS.sim.tick(1 / 30); });
|
|
await page.waitForTimeout(300);
|
|
await page.screenshot({ path: OUT + '/battle2.png' });
|
|
|
|
console.log('screenshots written:', ['taming.png', 'lake.png', 'battle2.png'].map(f => 'shots/' + f).join(', '));
|
|
console.log('console errors:', errors.length ? errors : 'none');
|
|
await browser.close();
|
|
process.exit(errors.length ? 1 : 0);
|
|
})().catch(e => { console.error(e); process.exit(1); });
|