Lakes no longer pour water to a guessed lip and repair it afterwards. Each lake lowers the land in the nearest valley with a smoothstep bowl, and water fills it to its spill level (the lower of the highest ground on each side, Priority-Flood in one dimension), so it is flat and contained by construction. Clay beds, no caves or loose pockets close under lakes, trees kept off the shore. Removes the levelling pass, the lower-rim lip and the bank-slope cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cj2RPXdPykKMEz5BXXczsj
310 lines
18 KiB
JavaScript
310 lines
18 KiB
JavaScript
// World generation: staged terrain, ore, caves and placement, with synchronous and async entry points.
|
|
// ---------- World generation ----------
|
|
function* worldSteps(seedIn, w, h) {
|
|
yield [0, 'Shaping the land'];
|
|
worldSeed = seedIn >>> 0;
|
|
alloc(w, h);
|
|
RS = (worldSeed ^ 0x9E3779B9) | 1;
|
|
const rng = mulberry32(worldSeed);
|
|
const nSurf = makeNoise(rng), nDetail = makeNoise(rng), nLayer = makeNoise(rng), nPocket = makeNoise(rng), nPocket2 = makeNoise(rng);
|
|
const nCoal = makeNoise(rng), nCu = makeNoise(rng), nFe = makeNoise(rng), nCave = makeNoise(rng), nCave2 = makeNoise(rng), nCave3 = makeNoise(rng), nWater = makeNoise(rng);
|
|
const rng2 = mulberry32(worldSeed ^ 0x5bd1e995); // separate stream so older worlds keep their layout
|
|
const nSn = makeNoise(rng2), nZn = makeNoise(rng2), nAg = makeNoise(rng2), nAu = makeNoise(rng2);
|
|
const rng3 = mulberry32(worldSeed ^ 0x27d4eb2d); // the shapes of the deep rock layers
|
|
const nLay2 = makeNoise(rng3), nWarp = makeNoise(rng3), nIntr = makeNoise(rng3), nDike = makeNoise(rng3), nMagC = makeNoise(rng3);
|
|
const spawnX = w >> 1;
|
|
// Layout: a third of the height is sky. The shallow layers keep a fixed depth (see shallowD),
|
|
// the deep ones stretch to fill the rest, and the air cools gently enough to build tall.
|
|
surfBase = Math.round(h * 0.3); underD = h - surfBase; shallowD = Math.min(140, Math.round(underD * 0.3)); lapse = 0.22;
|
|
const base = surfBase;
|
|
const U = depthY; // y at a fraction of the underground depth
|
|
for (let x = 0; x < w; x++) {
|
|
let s = base + fbm(nSurf, x / 230, 0.3, 4) * 46 + nDetail(x / 38, 4.2) * 5;
|
|
const ds = Math.abs(x - spawnX);
|
|
if (ds < 60) { const k = 1 - ds / 60; s = s * (1 - k * 0.75) + base * k * 0.75; }
|
|
surfY[x] = Math.round(s);
|
|
dirtY[x] = surfY[x] + Math.round(14 + nLayer(x / 60, 1.5) * 7);
|
|
// the deep layers rise and sink in long swells as well as short wiggles, more in deeper worlds
|
|
// (a shared fold moves them together, and each also has its own swells)
|
|
const fold = fbm(nLay2, x / 520, 3.3, 3);
|
|
const gA = underD * 0.16, bA = underD * 0.12, mA = underD * 0.06;
|
|
graniteY[x] = U(0.5) + Math.round(fold * gA * 1.8 + fbm(nLay2, x / 170, 9.1, 3) * gA + nLayer(x / 60, 9.1) * 10);
|
|
basaltY[x] = U(0.72) + Math.round(fold * bA * 1.6 + fbm(nLay2, x / 150, 17.3, 3) * bA + nLayer(x / 50, 17.3) * 8);
|
|
magmaY[x] = U(0.905) + Math.round(fold * mA * 1.4 + fbm(nLay2, x / 110, 23.7, 3) * mA + nLayer(x / 40, 23.7) * 5);
|
|
// and stay in order, with room between them
|
|
graniteY[x] = Math.max(graniteY[x], dirtY[x] + 60);
|
|
basaltY[x] = Math.max(basaltY[x], graniteY[x] + 40);
|
|
magmaY[x] = Math.min(h - 12, Math.max(magmaY[x], basaltY[x] + 30));
|
|
}
|
|
// Landmarks: a lake on one side of spawn and a desert on the other, then mountains, lakes,
|
|
// deserts and hills spaced out toward both edges. The first one past the lake is always a
|
|
// mountain tall enough to reach the freezing air and hold snow.
|
|
const lakeSide = rng() < 0.5 ? -1 : 1;
|
|
const feats = [{ k: 'lake', x: Math.round(spawnX + lakeSide * (170 + rng() * 80)) }, { k: 'desert', x: Math.round(spawnX - lakeSide * (250 + rng() * 90)) }];
|
|
for (const side of [lakeSide, -lakeSide]) {
|
|
let d = 600 + rng() * 90, prev = side === lakeSide ? '' : 'desert';
|
|
for (;;) {
|
|
const x = Math.round(spawnX + side * d);
|
|
if (x < 140 || x > w - 140) break;
|
|
// never the same kind twice in a row
|
|
let k = 'mountain';
|
|
if (prev) { const ks = ['lake', 'desert', 'mountain', 'hills'].filter(q => q !== prev), wt = { lake: 3, desert: 2.5, mountain: 2.5, hills: 2 }; let r = rng() * ks.reduce((a, q) => a + wt[q], 0); k = ks.find(q => (r -= wt[q]) < 0) || ks[0]; }
|
|
feats.push({ k, x });
|
|
prev = k;
|
|
d += 340 + rng() * 260;
|
|
}
|
|
}
|
|
if (!feats.some(f => f.k === 'mountain')) feats.push({ k: 'mountain', x: lakeSide > 0 ? w - 160 : 160 });
|
|
const maxMt = Math.max(60, Math.min(surfBase - 80, 200));
|
|
for (const f of feats) {
|
|
if (f.k !== 'mountain' && f.k !== 'hills') continue;
|
|
const mt = f.k === 'mountain', sig = mt ? 55 + rng() * 30 : 45 + rng() * 35, hgt = mt ? maxMt * (0.8 + rng() * 0.2) : 35 + rng() * 35;
|
|
for (let x = Math.max(0, Math.round(f.x - sig * 3.2)); x < Math.min(w, f.x + sig * 3.2); x++) {
|
|
const g = Math.exp(-0.5 * ((x - f.x) / sig) ** 2);
|
|
if (g < 0.01) continue;
|
|
surfY[x] = Math.round(surfY[x] - hgt * g - nDetail(x / 9, 5.7) * 5 * g);
|
|
if (mt) dirtY[x] = surfY[x] + Math.round(3 + (1 - g) * 11);
|
|
else dirtY[x] = Math.min(dirtY[x], surfY[x] + 20);
|
|
}
|
|
}
|
|
// Lakes: lower the land in the nearest valley with a smooth bump (no slope at its edges, so the
|
|
// banks keep the land's own shape), then fill the basin to its spill level. Water is flat and
|
|
// contained by construction; lakes keep 80 grains clear of spawn and the dock.
|
|
const lakeTop = new Int16Array(w).fill(h), noTree = new Uint8Array(w), lakeBed = new Uint8Array(w), desAt = new Uint8Array(w);
|
|
for (const f of feats) {
|
|
if (f.k !== 'lake') continue;
|
|
const lwDraw = Math.round(90 + rng() * 40), deep = 22 + rng() * 8;
|
|
// water gathers in valleys: take the lowest smoothed ground within 60 grains of the plan
|
|
let c = f.x, low = -1;
|
|
for (let x = f.x - 60; x <= f.x + 60; x++) {
|
|
if (x - lwDraw < 0 || x + lwDraw >= w || Math.abs(x - spawnX) - 80 < lwDraw * 0.85) continue;
|
|
let s = 0; for (let k = -15; k <= 15; k++) s += surfY[x + k];
|
|
if (s > low) { low = s; c = x; }
|
|
}
|
|
const lw = Math.min(lwDraw, Math.abs(c - spawnX) - 80), x0 = Math.max(0, c - lw), x1 = Math.min(w - 1, c + lw);
|
|
const nat = surfY.slice(x0, x1 + 1);
|
|
// on sloping ground the low rim spills early, so deepen the bump until the lake holds water
|
|
for (let scale = 1; scale <= 3; scale += 0.25) {
|
|
for (let x = x0; x <= x1; x++) {
|
|
// a bowl: flat floor in the middle 40%, banks easing in and out along a smoothstep
|
|
const t = Math.min(1, Math.max(0, (1 - Math.abs(x - c) / lw) / 0.6)), b = t * t * (3 - 2 * t);
|
|
surfY[x] = nat[x - x0] + Math.round(deep * scale * b * (1 + 0.15 * nDetail(x / 25, 6.6)));
|
|
}
|
|
if (spillLevels(x0, x1, lakeTop) >= 12) break;
|
|
}
|
|
let wet0 = w, wet1 = -1;
|
|
for (let x = x0; x <= x1; x++) {
|
|
dirtY[x] = Math.max(dirtY[x], surfY[x] + 8);
|
|
if (lakeTop[x] < surfY[x]) { wet0 = Math.min(wet0, x); wet1 = Math.max(wet1, x); } else lakeTop[x] = h;
|
|
}
|
|
for (let x = Math.max(0, wet0 - 6); x <= Math.min(w - 1, wet1 + 6); x++) noTree[x] = 1;
|
|
for (let x = Math.max(0, wet0 - 12); x <= Math.min(w - 1, wet1 + 12); x++) lakeBed[x] = 1;
|
|
}
|
|
for (const f of feats) {
|
|
if (f.k !== 'desert') continue;
|
|
const dw = Math.round(90 + rng() * 40);
|
|
for (let x = Math.max(0, f.x - dw); x <= Math.min(w - 1, f.x + dw); x++) if (Math.abs(x - f.x) < dw - Math.abs(nDetail(x / 20, 3.3)) * 10) desAt[x] = 1;
|
|
}
|
|
computeAmbient();
|
|
yield [0, 'Laying rock and ore'];
|
|
for (let y = 0; y < h; y++) {
|
|
const fy = depthU(y); // fraction of the underground depth (negative in the sky)
|
|
for (let x = 0; x < w; x++) {
|
|
const i = y * w + x;
|
|
const sy = surfY[x];
|
|
let m = EMPTY;
|
|
if (y < sy) {
|
|
if (y >= lakeTop[x]) m = WATER;
|
|
} else {
|
|
const d = y - sy;
|
|
const inDes = desAt[x] === 1, bed = lakeBed[x] === 1;
|
|
// a lake bottom is clay; the grass stops at the waterline
|
|
if (y < dirtY[x]) m = lakeTop[x] < sy && d < 3 ? CLAY : d === 0 && !inDes ? GRASS : DIRT;
|
|
else if (y < graniteY[x] + nWarp(x / 70, y / 45) * 26 + nWarp(x / 22 + 40, y / 15) * 8 + nPocket(x / 9, y / 9) * 3) m = STONE;
|
|
else if (y < basaltY[x] + nWarp(x / 64 + 50, y / 40) * 22 + nWarp(x / 20 + 90, y / 14) * 7 + nPocket(x / 7, y / 7) * 3) m = GRANITE;
|
|
else m = BASALT;
|
|
// granite bulges up into the stone in rounded masses, and stone is caught in the granite in lumps
|
|
if (m === STONE && y > graniteY[x] - 100 && nIntr(x / 46, y / 34) > 0.24 + (graniteY[x] - y) / 100 * 0.3) m = GRANITE;
|
|
else if (m === GRANITE && y < basaltY[x] - 20 && nIntr(x / 38 + 70, y / 30) < -0.36) m = STONE;
|
|
void nDike; // (kept so the noises after it stay the same)
|
|
if (inDes && d < 12 + nDetail(x / 15, 8.1) * 4) m = SAND;
|
|
// pockets
|
|
const pk = nPocket(x / 14, y / 11), pk2 = nPocket2(x / 18, y / 13);
|
|
if (m === DIRT && d > 2 && pk2 > 0.42) m = CLAY;
|
|
else if (m === STONE) {
|
|
if (d < 150 && pk > 0.52) m = DIRT;
|
|
else if (d < 110 && pk2 > 0.5) m = CLAY;
|
|
// no loose pockets close under a lake: one falling into a cave would open a hole in its bed
|
|
else if (pk < -0.56 && (!bed || d > 40)) m = GRAVEL;
|
|
else if (pk2 < -0.6 && d < 220 && (!bed || d > 40)) m = SAND;
|
|
}
|
|
// ores
|
|
if (m === STONE || m === DIRT || m === GRANITE || m === BASALT || m === CLAY) {
|
|
const host = m;
|
|
// Leave the shallow quarter unchanged; deeper rock has broader, richer lenses.
|
|
const rich = Math.min(1, Math.max(0, (fy - 0.25) / 0.6));
|
|
const c = nCoal(x / 13, y / 9);
|
|
if ((d > 6 && fy < 0.45 && c > 0.5 - 0.55 * rich && m !== CLAY) ||
|
|
(fy >= 0.45 && (m === STONE || m === GRANITE) && c > 0.62 - 0.3 * rich) ||
|
|
(m === BASALT && fy > 0.6 && c > 0.58 - 0.4 * rich)) m = COALV;
|
|
else {
|
|
const cu = nCu(x / 9, y / 8);
|
|
const cuBand = d > 14 && fy < 0.48;
|
|
if ((cuBand && (m === STONE || m === DIRT) && cu > (d < 140 ? 0.5 : 0.56) - 0.65 * rich) ||
|
|
(fy > 0.48 && fy < 0.8 && (m === STONE || m === GRANITE) && cu > 0.6 - 0.46 * rich)) m = CUV;
|
|
else {
|
|
// iron and zinc are harder than the stone around them, so they come in fewer, chunkier
|
|
// lenses rather than a scatter of specks that a stone bit would leave in the way
|
|
const fe = nFe(x / 24, y / 13);
|
|
if (fy > 0.28 && (m === STONE || m === GRANITE || m === BASALT) && fe > (m === STONE ? 0.55 : 0.47) - 0.55 * rich) m = FEV;
|
|
// rarer ores in deeper bands: tin, then zinc, silver in granite, gold deepest
|
|
else if (((fy > 0.15 && fy < 0.55 && m === STONE) ||
|
|
(fy >= 0.55 && fy < 0.8 && (m === STONE || m === GRANITE))) &&
|
|
nSn(x / 8, y / 7) > 0.62 + Math.max(0, fy - 0.55) * 0.4 - 0.425 * rich) m = SNV;
|
|
else if (fy > 0.35 && fy < 0.78 && (m === STONE || m === GRANITE) && nZn(x / 22, y / 13) > 0.6 - 0.49 * rich) m = ZNV;
|
|
else if (fy > 0.5 && fy < 0.88 && m === GRANITE && nAg(x / 7, y / 6) > 0.66 - 0.32 * rich) m = AGV;
|
|
else if (fy > 0.68 && (m === GRANITE || m === BASALT) && nAu(x / 6, y / 5) > 0.7 - 0.18 * rich) m = AUV;
|
|
}
|
|
}
|
|
// no ore where the rock is naturally hot enough to light or melt it: it would burn or run forever
|
|
if (m !== host && (IGN[m] ? AMB[y] > IGN[m] - 120 : MELT[m] && AMB[y] > MELT[m] - 200)) m = host;
|
|
}
|
|
// caves (kept deeper under lakes so none opens into one)
|
|
if (d > (bed ? 40 : 9)) {
|
|
const t1 = Math.abs(fbm(nCave, x / 130, y / 80, 2));
|
|
const big = fy > 0.2 ? fbm(nCave2, x / 95, y / 64, 3) : -1;
|
|
const small = nCave3(x / 26, y / 20);
|
|
const depthK = Math.min(1, (d - 9) / 50);
|
|
if (t1 < 0.06 * depthK || big > 0.34 || small > 0.6 - (fy > 0.35 ? 0.05 : 0)) m = EMPTY;
|
|
}
|
|
const my = magmaY[x] + nWarp(x / 40 + 120, y / 20) * 12;
|
|
if (y >= my) m = (nPocket(x / 12, y / 6) > 0.45 && y < my + 6) ? BASALT : MAGMA;
|
|
// pockets of magma sit in the basalt just above the magma sea
|
|
else if (m === BASALT && y > my - 50 && nMagC(x / 40, y / 22) > 0.3 + (my - y) / 50 * 0.3) m = MAGMA;
|
|
if (y >= h - 2) m = BASALT;
|
|
// aquifers fill the low parts of shallow caves
|
|
if (m === EMPTY && d > 20 && fy < 0.46) {
|
|
const wl = nWater(x / 140, 0.5);
|
|
if (wl > 0.15 && nWater(x / 40, y / 40) > -0.1 && ((y + Math.floor(wl * 40)) % 23) > 13) m = WATER;
|
|
}
|
|
}
|
|
mat[i] = m;
|
|
if (m !== EMPTY && m !== WATER && m !== MAGMA) flags[i] = F_NAT;
|
|
else if (m === WATER || m === MAGMA) flags[i] = F_NAT;
|
|
temp[i] = m === MAGMA ? Math.max(AMB[y], 1180) : AMB[y];
|
|
if (PH[m] === PH_LIQUID && FRZ[m]) life[i] = 255;
|
|
}
|
|
if ((y & 63) === 0) yield [y / h * 0.7, 'Laying rock and ore'];
|
|
}
|
|
yield [0.7, 'Carving caves'];
|
|
clearHardSpecks(false);
|
|
yield [0.71, 'Snow and trees'];
|
|
// (tree bases are collected after the trees below)
|
|
// snow lies on the high ground wherever the air is below freezing
|
|
for (let x = 1; x < w - 1; x++) {
|
|
const sy = surfY[x];
|
|
if (sy < 3 || AMB[sy - 1] > 0) continue;
|
|
const i0 = sy * w + x;
|
|
if (mat[i0] === GRASS) mat[i0] = DIRT;
|
|
const depth = Math.abs(surfY[x + 1] - surfY[x - 1]) <= 3 ? 3 : 1;
|
|
for (let k = 1; k <= depth; k++) { const i = (sy - k) * w + x; if (mat[i] === EMPTY) { mat[i] = SNOW; flags[i] = F_NAT; temp[i] = AMB[sy - k]; } }
|
|
}
|
|
yield [0.72, 'Snow and trees'];
|
|
// trees
|
|
for (let x = 12; x < w - 12;) {
|
|
const sy = surfY[x];
|
|
const ok = mat[sy * w + x] === GRASS && mat[sy * w + x + 1] === GRASS && !noTree[x] && Math.abs(x - spawnX) > 6 && !(x > spawnX + 14 && x < spawnX + 72); // none where the dock goes
|
|
if (ok && rng() < 0.7) {
|
|
const th = 9 + Math.floor(rng() * 8);
|
|
const setW = (xx, yy) => { if (xx < 0 || yy < 1 || xx >= w) return; const i = yy * w + xx; if (mat[i] === EMPTY) bgm[i] = WOOD; };
|
|
for (let k = 1; k <= th; k++) for (let dx = 0; dx < 2; dx++) setW(x + dx, sy - k);
|
|
// two branches
|
|
for (const side of [-1, 1]) { const by = sy - th + 3 + Math.floor(rng() * 3); for (let k = 1; k <= 3; k++) setW(x + (side < 0 ? -k : 1 + k), by - (k >> 1)); }
|
|
// lumpy canopy made of a few overlapping blobs
|
|
const blobs = [[0.5, -th - 1, 6 + rng() * 2.5], [-3.5, -th + 1, 4 + rng() * 2], [4.5, -th + 1, 4 + rng() * 2], [0.5, -th - 5, 4 + rng() * 2]];
|
|
for (const [bx, by, r] of blobs) {
|
|
const cx = x + bx, cy = sy + by;
|
|
for (let yy = Math.floor(cy - r); yy <= cy + r; yy++) for (let xx = Math.floor(cx - r); xx <= cx + r; xx++) {
|
|
if (xx < 0 || yy < 1 || xx >= w) continue;
|
|
const ex = (xx - cx) / r, ey = (yy - cy) / (r * 0.85);
|
|
if (ex * ex + ey * ey <= 1 && hash2(xx, yy, worldSeed + 7) % 9 !== 0) { const i = yy * w + xx; if (mat[i] === EMPTY && !bgm[i]) bgm[i] = LEAVES; }
|
|
}
|
|
}
|
|
x += 16 + Math.floor(rng() * 20);
|
|
} else x += 4;
|
|
}
|
|
yield [0.73, 'Springs and water'];
|
|
// springs: in cave side walls just above the cave floor
|
|
{
|
|
const cands = [];
|
|
for (let y = 0; y < h - 4; y++) {
|
|
if (depthU(y) > 0.45) break;
|
|
for (let x = 2; x < w - 2; x++) {
|
|
const i = y * w + x;
|
|
if (!(flags[i] & F_NAT) || PH[mat[i]] !== PH_SOLID || mat[i] === LEAVES || mat[i] === WOOD) continue;
|
|
if (y - surfY[x] < 16) continue;
|
|
for (const dx of [-1, 1]) {
|
|
const e = i + dx;
|
|
if (mat[e] !== EMPTY) continue;
|
|
// floor 2-4 cells below the open cell, and solid above the spring
|
|
if (PH[mat[e + w]] === PH_EMPTY && PH[mat[e + 2 * w]] !== PH_EMPTY && PH[mat[i - w]] === PH_SOLID && PH[mat[i + w]] === PH_SOLID) cands.push(i);
|
|
}
|
|
}
|
|
}
|
|
const picks = [];
|
|
const near = cands.slice().sort((a, b) => Math.abs(a % w - spawnX) - Math.abs(b % w - spawnX));
|
|
for (const c of near) { if (picks.length >= 2) break; if (picks.every(p => Math.abs(p % w - c % w) > 30)) picks.push(c); }
|
|
for (let k = 0; k < 400 && picks.length < 10 && cands.length; k++) {
|
|
const c = cands[Math.floor(rng() * cands.length)];
|
|
if (picks.every(p => Math.abs(p % w - c % w) > 50 || Math.abs(((p / w) | 0) - ((c / w) | 0)) > 40)) picks.push(c);
|
|
}
|
|
// Springs were taken out of the game (rain refills the world instead). The spots are still
|
|
// drawn above so every seed keeps making the same world it always did.
|
|
void picks;
|
|
}
|
|
fillNoise();
|
|
yield [0.75, 'Settling loose ground'];
|
|
// settle loose material at the water and sorting speeds the seeds were made with
|
|
awake.fill(3);
|
|
const lstep = LSTEP, lfilm = LFILM, sortp = SORTP, cornerSeal = CORNER_SEAL, powderFan = POWDER_FAN; LSTEP = LFILM = 1; SORTP = 0.003; CORNER_SEAL = POWDER_FAN = false;
|
|
for (let k = 0; k < 260; k++) {
|
|
tick++; tickStamp = (tickStamp % 255) + 1;
|
|
simMove();
|
|
let any = 0; for (let c = 0; c < NCH; c++) if (awake[c]) { awake[c]--; any++; }
|
|
if (!any) break;
|
|
if ((k & 31) === 0) yield [0.75 + 0.24 * k / 260, 'Settling loose ground'];
|
|
}
|
|
LSTEP = lstep; LFILM = lfilm; SORTP = sortp; CORNER_SEAL = cornerSeal; POWDER_FAN = powderFan;
|
|
placeDock(spawnX + 34);
|
|
yield [0.99, 'Finishing'];
|
|
// re-ambient after settling, then start clean
|
|
for (let i = 0; i < N; i++) { const m = mat[i]; if (m !== MAGMA) temp[i] = AMB[(i / w) | 0]; life[i] = PH[m] === PH_LIQUID && FRZ[m] ? 255 : 0; }
|
|
tick = 0;
|
|
findTreeBases();
|
|
hot.fill(0); awake.fill(0); updateRoof();
|
|
// Wake generated water so it responds from the first tick.
|
|
for (let i = 0; i < N; i++) if (mat[i] === WATER) wakeIdx(i);
|
|
resetEconomy(); resetWeather();
|
|
weather.base = worldWater();
|
|
bot.x = spawnX - 2; bot.y = surfY[spawnX] - 14; bot.vx = 0; bot.vy = 0;
|
|
while (botOverlapSolid(bot.x, bot.y) && bot.y > 2) bot.y -= 1;
|
|
bag = new Int32Array(NMAT); bagTotal = 0;
|
|
player.bitTier = 0; player.tankTier = 0; player.lens = false; player.flask = false;
|
|
seen = new Uint8Array(NMAT); ledger = {};
|
|
stats.dug = stats.moved = stats.placed = stats.ignitions = stats.cast = stats.bars = 0;
|
|
EV.length = 0;
|
|
fogInit(); fogLook(bot.x + bot.w / 2, bot.y + bot.h / 2);
|
|
yield [1, 'Finishing'];
|
|
}
|
|
|
|
function generateWorld(seedIn, w, h, onProgress) {
|
|
for (const [f, label] of worldSteps(seedIn, w, h)) if (onProgress) onProgress(f, label);
|
|
}
|
|
async function generateWorldAsync(seedIn, w, h, onProgress) {
|
|
let last = Date.now();
|
|
for (const [f, label] of worldSteps(seedIn, w, h)) {
|
|
if (onProgress) onProgress(f, label);
|
|
if (Date.now() - last >= 30) { await new Promise(r => setTimeout(r, 0)); last = Date.now(); }
|
|
}
|
|
}
|