// --------------------------------------------------------------- // 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 };