Add Shift-run, live minimap, README; bump assets to v11

This commit is contained in:
2026-08-23 10:04:45 +00:00
parent 8680f79102
commit 862d09b3ee
4 changed files with 112 additions and 12 deletions
+1 -1
View File
@@ -357,7 +357,7 @@ const UI = {
v.innerHTML = `
<div class="w2d-wrap">
<canvas id="w2d" width="960" height="540"></canvas>
<div class="w2d-hint"><b>Click / tap map</b> to walk there (tap NPC to talk) · <b>WASD / Arrows</b> also move · <b>E</b> interact · <b>D-pad</b> bottom-right for touch · glowing <b>➜</b> arches travel · touch beasts to fight</div>
<div class="w2d-hint"><b>Click / tap map</b> to walk there (tap NPC to talk) · <b>WASD / Arrows</b> move · <b>Shift</b> run · <b>E</b> interact · <b>D-pad</b> bottom-right for touch · glowing <b>➜</b> arches travel · touch beasts to fight · minimap top-right</div>
</div>
<div class="scene-head"><h2>${Util.esc(L.name)} <span class="tag">${L.region}</span>${L.danger ? `<span class="tag red">danger ${'★'.repeat(L.danger)}</span>` : ''}${L.nightOnly ? '<span class="tag blue">night only</span>' : ''}</h2></div>
<p class="scene-desc">${L.desc}</p>
+48 -1
View File
@@ -432,6 +432,7 @@ W2D.enter = function (locId, canvas) {
W2D.ground = W2D._flatGround(W2D.grid);
}
try { W2D.initParts(); } catch (_) { W2D.partMode = null; }
try { W2D.mini = W2D.prerenderMini(W2D.grid); } catch (_) { W2D.mini = null; }
W2D.cv = canvas; W2D.ctx = canvas.getContext('2d');
const gr = W2D.grid;
@@ -579,7 +580,7 @@ W2D.uiBlocked = function () {
/* ---------------- update ---------------- */
W2D.update = function (dt, now) {
const sp = 0.14 * dt; // px per ms
const sp = 0.14 * dt * (W2D.keys['shift'] ? 1.75 : 1); // px per ms (Shift = run)
const K = W2D.keys, V = W2D.vk || {};
let dx = 0, dy = 0;
if (K['w'] || V.up) dy -= 1;
@@ -825,6 +826,34 @@ W2D.draw = function (now) {
c.font = '20px serif'; c.textAlign = 'center';
c.fillText(LOCS[W2D.cur].name, W2D.VW / 2, 43);
}
// minimap (screen space, top-right)
safe(() => {
if (!W2D.mini || !gr) return;
const mw = W2D.mini.width, mh = W2D.mini.height;
const mx = W2D.VW - mw - 14, my = 14;
const sx = mw / (gr.w * T), sy = mh / (gr.h * T);
c.fillStyle = 'rgba(8,10,16,.78)';
c.fillRect(mx - 5, my - 5, mw + 10, mh + 10);
c.strokeStyle = 'rgba(216,179,106,.55)';
c.strokeRect(mx - 4.5, my - 4.5, mw + 9, mh + 9);
c.drawImage(W2D.mini, mx, my);
for (const ex of gr.exits) {
const L2 = LOCS[ex.to];
if (L2.hidden && !G.flags[L2.hidden]) continue;
c.fillStyle = '#d8b36a';
c.fillRect(mx + ex.x * T * sx - 2, my + ex.y * T * sy - 2, 4, 4);
}
for (const e of W2D.ents) {
if (e.dead) continue;
if (e.kind === 'npc') { c.fillStyle = '#efe6cf'; c.fillRect(mx + e.x * sx - 1.5, my + e.y * sy - 1.5, 3, 3); }
else if (e.kind === 'roam') { c.fillStyle = '#d05a48'; c.fillRect(mx + e.x * sx - 1.5, my + e.y * sy - 1.5, 3, 3); }
else if (e.kind === 'spot') { c.fillStyle = '#7dc25c'; c.fillRect(mx + e.x * sx - 1, my + e.y * sy - 1, 2, 2); }
}
const pulse = 2.5 + Math.sin(now / 180) * 1;
c.fillStyle = '#ff5a4e';
c.beginPath(); c.arc(mx + W2D.px * sx, my + W2D.py * sy, pulse, 0, 7); c.fill();
});
};
W2D.figure = function (c, x, y, s, robe, now, walking) {
@@ -1088,6 +1117,24 @@ W2D.drawVignette = function (c) {
c.fillRect(W2D.cam.x, W2D.cam.y, W2D.VW, W2D.VH);
};
/* minimap: prerendered tile thumbnail + live dots drawn each frame */
W2D.prerenderMini = function (grid) {
const S = 3;
const cv = document.createElement('canvas');
cv.width = grid.w * S; cv.height = grid.h * S;
const c = cv.getContext('2d');
for (let y = 0; y < grid.h; y++) for (let x = 0; x < grid.w; x++) {
const t = grid.g[y][x];
c.fillStyle = t === 2 || t === 19 ? '#1c3a52'
: t === 1 ? '#63553f'
: t === 8 ? '#7a5c3c'
: BLOCKED.has(t) ? '#20262c'
: '#3a5239';
c.fillRect(x * S, y * S, S, S);
}
return cv;
};
/* flat fallback ground (used only if the rich renderer throws) */
W2D._flatGround = function (grid) {
const T = W2D.T;