Files
deepseek 764da2ee6c Dino Isle Online: multiplayer dino survival game (The Isle-like)
- Zero-dependency Node server: HTTP static + hand-rolled RFC6455 WebSocket + 20Hz authoritative simulation
- Seeded island worldgen (tiles, forest decor, spawns)
- 4 playable species, needs (food/water/stamina), rest, growth stages Hatchling->Apex
- AI fauna: critters, fish shoals, Dryosaurus/Psittacosaurus herds; carcass feasting
- PvP with knockback, kill feed, chat, leaderboard, minimap, day/night cycle
- True top-down procedural dino renderer shared by menu cards and in-game
- Developer mode (` key): god-mode, x6 XP, instant evolve, spawn AI, Alt+Click tp
- Headless smoke tests, real-browser visual tests, pixel match checks
2026-08-23 07:00:08 +00:00

122 lines
4.6 KiB
JavaScript

// ---------------------------------------------------------------
// Dino Isle Online — world generation (deterministic, seed based)
// Tiles: 0 deep water, 1 shallow water, 2 sand, 3 grassland, 4 forest
// ---------------------------------------------------------------
'use strict';
const MAP_W = 160;
const MAP_H = 160;
const TILE = 48;
const WORLD_W = MAP_W * TILE;
const WORLD_H = MAP_H * TILE;
function hash2(ix, iy, seed) {
let h = ix * 374761393 + iy * 668265263 + seed * 2246822519;
h = (h ^ (h >>> 13)) >>> 0;
h = Math.imul(h, 1274126177) >>> 0;
return ((h ^ (h >>> 16)) >>> 0) / 4294967296;
}
function smooth(t) { return t * t * (3 - 2 * t); }
function valueNoise(x, y, seed) {
const ix = Math.floor(x), iy = Math.floor(y);
const fx = x - ix, fy = y - iy;
const a = hash2(ix, iy, seed), b = hash2(ix + 1, iy, seed);
const c = hash2(ix, iy + 1, seed), d = hash2(ix + 1, iy + 1, seed);
const u = smooth(fx), v = smooth(fy);
return a * (1 - u) * (1 - v) + b * u * (1 - v) + c * (1 - u) * v + d * u * v;
}
function fbm(x, y, seed, octaves) {
let amp = 1, freq = 1, sum = 0, norm = 0;
for (let i = 0; i < octaves; i++) {
sum += amp * valueNoise(x * freq, y * freq, seed + i * 1013);
norm += amp; amp *= 0.5; freq *= 2.03;
}
return sum / norm;
}
function generateWorld(seed) {
const tiles = new Uint8Array(MAP_W * MAP_H);
for (let ty = 0; ty < MAP_H; ty++) {
for (let tx = 0; tx < MAP_W; tx++) {
// normalized coords centered
const nx = tx / MAP_W - 0.5, ny = ty / MAP_H - 0.5;
const d = Math.sqrt(nx * nx * 1.15 + ny * ny * 1.35) * 2; // 0 center -> ~1.4 corners
const e = fbm(tx / 22, ty / 22, seed, 4) * 0.9 + 0.18 - Math.pow(Math.min(d, 1.45), 2.1) * 0.62;
const m = fbm(tx / 14 + 100, ty / 14 + 100, seed + 7777, 3);
let t;
if (e < 0.30) t = 0; // deep water
else if (e < 0.40) t = 1; // shallow water
else if (e < 0.45) t = 2; // sand
else if (m > 0.60 && e < 0.85) t = 4; // forest
else t = 3; // grassland
tiles[ty * MAP_W + tx] = t;
}
}
return tiles;
}
function makeRng(seed) {
let s = (seed >>> 0) || 1;
return () => {
s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
return s / 4294967296;
};
}
function tileAt(tiles, wx, wy) {
const tx = Math.floor(wx / TILE), ty = Math.floor(wy / TILE);
if (tx < 0 || ty < 0 || tx >= MAP_W || ty >= MAP_H) return 0;
return tiles[ty * MAP_W + tx];
}
// Static decoration: trees on forest, rocks scattered, shoreline foam points
function generateDecor(tiles, seed) {
const rng = makeRng(seed + 991);
const trees = [], rocks = [], flowers = [], foam = [];
for (let ty = 1; ty < MAP_H - 1; ty++) {
for (let tx = 1; tx < MAP_W - 1; tx++) {
const t = tiles[ty * MAP_W + tx];
const cx = tx * TILE + TILE / 2, cy = ty * TILE + TILE / 2;
if (t === 4 && rng() < 0.16) {
trees.push({ x: cx + (rng() - 0.5) * 30, y: cy + (rng() - 0.5) * 30, s: 0.8 + rng() * 0.55 });
} else if (t === 3 && rng() < 0.010) {
rocks.push({ x: cx + (rng() - 0.5) * 34, y: cy + (rng() - 0.5) * 34, s: 0.6 + rng() * 0.9, r: rng() });
} else if ((t === 3 || t === 2) && rng() < 0.05) {
flowers.push({ x: cx + (rng() - 0.5) * 40, y: cy + (rng() - 0.5) * 40, c: Math.floor(rng() * 3) });
}
}
}
// foam: shallow tiles adjacent to sand
for (let ty = 1; ty < MAP_H - 1; ty++) {
for (let tx = 1; tx < MAP_W - 1; tx++) {
if (tiles[ty * MAP_W + tx] !== 1) continue;
let adjSand = false;
for (let oy = -1; oy <= 1 && !adjSand; oy++)
for (let ox = -1; ox <= 1; ox++) {
if (tiles[(ty + oy) * MAP_W + tx + ox] === 2) { adjSand = true; break; }
}
if (adjSand) foam.push({ x: tx * TILE + TILE / 2, y: ty * TILE + TILE / 2, p: rng() * Math.PI * 2 });
}
}
// cap decor for perf
const cap = (arr, n) => arr.length > n ? arr.filter((_, i) => i % Math.ceil(arr.length / n) === 0) : arr;
return { trees: cap(trees, 900), rocks: cap(rocks, 260), flowers: cap(flowers, 700), foam: cap(foam, 1400) };
}
// Find land spawn points (grass/sand near coast preferred)
function findSpawnPoints(tiles, count) {
const rng = makeRng(4242);
const pts = [];
let guard = 0;
while (pts.length < count && guard++ < 20000) {
const tx = 4 + Math.floor(rng() * (MAP_W - 8));
const ty = 4 + Math.floor(rng() * (MAP_H - 8));
const t = tiles[ty * MAP_W + tx];
if (t !== 2 && t !== 3) continue;
pts.push({ x: tx * TILE + TILE / 2, y: ty * TILE + TILE / 2 });
}
return pts;
}
module.exports = { MAP_W, MAP_H, TILE, WORLD_W, WORLD_H, generateWorld, generateDecor, findSpawnPoints, tileAt, makeRng };