mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-21 08:08:32 +02:00
The next-block wait now draws a small lit "mining room" Scene through the ACTIVE renderer (RK.SPGame.showWaiting) — floor/walls/torches in your chosen pack (Tiled/Iso/3D), camera centered, torches flickering via the FX loop — with the "Mining the next block" text as an overlay. So the wait inherits your tiles/mode instead of being the plain ROT animation. Falls back to the ROT WaitingScreenAnimator if the kit is unavailable. SP.render clears the overlay when the real game starts.
242 lines
11 KiB
JavaScript
242 lines
11 KiB
JavaScript
// SP game render bridge (RK.SPGame) — renders the single-player DUNGEON through the render kit
|
|
// (Tiled / ASCII / Iso / 3D + unlocked packs) instead of the legacy ROT display, so a pack you
|
|
// bought applies in actual gameplay, not just the tavern. It mounts a renderer in a dedicated host
|
|
// inside #game-display and hides the legacy ROT canvases while a game is live; the retro splash /
|
|
// win / lose text screens still use ROT. If anything here is unavailable it returns false and the
|
|
// caller falls back to the legacy RenderEngine, so the live game can never hard-break.
|
|
(function (root) {
|
|
'use strict';
|
|
var RK = root.RK = root.RK || {};
|
|
var SP = RK.SPGame = RK.SPGame || {};
|
|
|
|
SP._renderer = null;
|
|
SP._mode = null;
|
|
SP._live = false;
|
|
|
|
function doc() { return root.document; }
|
|
function gameDisplay() { return doc() && doc().getElementById('game-display'); }
|
|
|
|
// A dedicated, SELF-SIZING child host so the RK canvas doesn't collide with the ROT canvas and
|
|
// doesn't depend on the ROT canvas for its dimensions (it's an aspect-ratio viewport the camera
|
|
// transform is clipped to).
|
|
function rkHost() {
|
|
var gd = gameDisplay();
|
|
if (!gd) return null;
|
|
var host = doc().getElementById('rk-game-host');
|
|
if (!host) {
|
|
host = doc().createElement('div');
|
|
host.id = 'rk-game-host';
|
|
// A bounded viewport CENTERED in #game-display (which is position:relative). Absolute
|
|
// positioning dodges the flex centering + sibling ROT/FX canvases; capping the height
|
|
// (vs filling the very-tall #game-display) keeps the player-centered camera on-screen
|
|
// instead of low.
|
|
host.style.cssText = 'display:none; position:absolute; top:50%; left:50%;' +
|
|
' width:min(100%, 1000px); height:min(100%, 78vh); transform:translate(-50%,-50%);' +
|
|
' overflow:hidden; background:#0a0c0f; z-index:6; touch-action:none; cursor:grab;';
|
|
gd.appendChild(host);
|
|
}
|
|
return host;
|
|
}
|
|
|
|
SP.available = function () {
|
|
return !!(RK.createRenderer && RK.sceneFromGameState && gameDisplay());
|
|
};
|
|
|
|
// A small lit "waiting room" Scene, drawn through the active renderer so the wait inherits the
|
|
// player's pack/mode. The tile renderer's FX loop flickers the torches; the camera centres it.
|
|
function waitingScene(opts) {
|
|
opts = opts || {};
|
|
var W = 11, H = 8, grid = [];
|
|
for (var y = 0; y < H; y++) {
|
|
var row = [];
|
|
for (var x = 0; x < W; x++) row.push((x === 0 || y === 0 || x === W - 1 || y === H - 1) ? 'wall' : 'floor');
|
|
grid.push(row);
|
|
}
|
|
grid[3][1] = 'torch'; grid[3][W - 2] = 'torch'; // wall torches (fire FX)
|
|
var px = (W - 1) >> 1;
|
|
var entities = [
|
|
{ id: 'exit', x: px, y: 2, kind: 'feature', char: '>', color: '#d29922', label: null },
|
|
{ id: 'player', x: px, y: H - 3, kind: 'player', you: true, facing: 'up', label: null,
|
|
avatar: (opts.playerAppearance && opts.playerAppearance.avatar) || 'default',
|
|
appearance: opts.playerAppearance || { avatar: 'default' } }
|
|
];
|
|
return { cols: W, rows: H, grid: grid, entities: entities,
|
|
legend: (RK.scene && RK.scene.DUNGEON_LEGEND) || null, isDungeon: true, background: '#0a0c0f' };
|
|
}
|
|
|
|
// Render the waiting-room scene + a text overlay (mining / payment). Returns true if it took over.
|
|
SP.showWaiting = function (title, sub, cryptoType, appearance) {
|
|
if (!SP.available()) return false;
|
|
SP.show();
|
|
SP._camX = null;
|
|
var r = SP._ensureRenderer();
|
|
if (!r) return false;
|
|
try { r.render(waitingScene({ playerAppearance: appearance, cryptoType: cryptoType })); SP._applyCamera(); }
|
|
catch (e) { return false; }
|
|
var host = rkHost();
|
|
var ov = doc().getElementById('rk-wait-overlay');
|
|
if (!ov) {
|
|
ov = doc().createElement('div');
|
|
ov.id = 'rk-wait-overlay';
|
|
ov.style.cssText = 'position:absolute;left:0;right:0;bottom:0;padding:0 12px 7%;text-align:center;' +
|
|
'pointer-events:none;font-family:ui-monospace,monospace;text-shadow:0 2px 8px #000;z-index:4;';
|
|
host.appendChild(ov);
|
|
}
|
|
ov.innerHTML = '<div style="font-size:20px;font-weight:800;letter-spacing:1px;color:#f0a828;">' + title + '</div>' +
|
|
'<div style="font-size:13px;color:#cfe0b8;margin-top:5px;">' + (sub || '') + '</div>';
|
|
ov.style.display = 'block';
|
|
return true;
|
|
};
|
|
SP.hideWaiting = function () {
|
|
var ov = doc() && doc().getElementById('rk-wait-overlay');
|
|
if (ov) ov.style.display = 'none';
|
|
};
|
|
|
|
SP.mode = function () {
|
|
if (!SP._mode) {
|
|
var m = (RK.loadMode && RK.loadMode('tiles')) || 'tiles';
|
|
SP._mode = (RK.canUseMode && !RK.canUseMode(m)) ? 'tiles' : m;
|
|
}
|
|
return SP._mode;
|
|
};
|
|
|
|
SP.setMode = function (mode) {
|
|
if (RK.canUseMode && !RK.canUseMode(mode)) return false;
|
|
SP._mode = mode;
|
|
if (RK.saveMode) RK.saveMode(mode);
|
|
SP._destroyRenderer();
|
|
if (SP._live && SP._lastState) SP.render(SP._lastState, SP._lastOpts);
|
|
return true;
|
|
};
|
|
|
|
// Re-mount after a pack switch (same mode, new active pack/assets).
|
|
SP.refreshPack = function () {
|
|
SP._destroyRenderer();
|
|
if (SP._live && SP._lastState) SP.render(SP._lastState, SP._lastOpts);
|
|
};
|
|
|
|
SP._destroyRenderer = function () {
|
|
if (SP._renderer && SP._renderer.destroy) { try { SP._renderer.destroy(); } catch (_) {} }
|
|
SP._renderer = null;
|
|
};
|
|
|
|
SP._ensureRenderer = function () {
|
|
if (SP._renderer) return SP._renderer;
|
|
var host = rkHost();
|
|
if (!host || !RK.createRenderer) return null;
|
|
SP._renderer = RK.createRenderer(SP.mode(), host, { cell: 24 });
|
|
// The camera owns the canvas transform (centre on the player), so position it absolutely
|
|
// and let it overflow — do NOT use RK.attachZoom (it fights this transform).
|
|
if (SP._renderer && SP._renderer.canvas) {
|
|
var c = SP._renderer.canvas;
|
|
c.style.position = 'absolute'; c.style.top = '0'; c.style.left = '0';
|
|
// Beat `.rotdis canvas { max-width:100% !important }` so the camera controls the size.
|
|
c.style.setProperty('max-width', 'none', 'important');
|
|
c.style.setProperty('image-rendering', 'pixelated', 'important');
|
|
}
|
|
if (!host._rkZoomBound) {
|
|
host._rkZoomBound = true;
|
|
host.addEventListener('wheel', function (ev) {
|
|
ev.preventDefault();
|
|
SP._zoom = Math.max(0.6, Math.min(4, (SP._zoom || 1.7) * Math.exp(-ev.deltaY * 0.0015)));
|
|
SP._applyCamera();
|
|
}, { passive: false });
|
|
}
|
|
return SP._renderer;
|
|
};
|
|
|
|
// Centre the (whole-scene) canvas on the player via a CSS transform, clipped to the host —
|
|
// renderer-agnostic (works for tiled / iso / 3d, each of which reports its own focusPoint).
|
|
SP._applyCamera = function () {
|
|
var r = SP._renderer;
|
|
var host = doc() && doc().getElementById('rk-game-host');
|
|
if (!r || !r.canvas || !host) return;
|
|
var scale = SP._zoom || 1.7;
|
|
var fp = r.focusPoint;
|
|
r.canvas.style.transformOrigin = '0 0';
|
|
if (!fp) { r.canvas.style.transform = 'scale(' + scale + ')'; return; }
|
|
var w = host.clientWidth || 640, h = host.clientHeight || 400;
|
|
var tx = w / 2 - fp.x * scale, ty = h / 2 - fp.y * scale;
|
|
// Smooth follow: glide toward the target so single steps aren't jerky; SNAP on a big jump
|
|
// (level-change teleport / a stale-then-updated focus) rather than slowly panning the map.
|
|
if (SP._camX == null || Math.abs(tx - SP._camX) > w || Math.abs(ty - SP._camY) > h) {
|
|
SP._camX = tx; SP._camY = ty;
|
|
} else {
|
|
SP._camX += (tx - SP._camX) * 0.3;
|
|
SP._camY += (ty - SP._camY) * 0.3;
|
|
}
|
|
r.canvas.style.transform = 'translate(' + SP._camX + 'px,' + SP._camY + 'px) scale(' + scale + ')';
|
|
};
|
|
|
|
function toggleLegacy(hide) {
|
|
var gd = gameDisplay();
|
|
if (!gd) return;
|
|
var kids = gd.children;
|
|
for (var i = 0; i < kids.length; i++) {
|
|
if (kids[i].id === 'rk-game-host') continue;
|
|
kids[i].style.display = hide ? 'none' : '';
|
|
}
|
|
}
|
|
|
|
// Show the render-kit view (hide the ROT splash canvases). Call on game start.
|
|
SP.show = function () {
|
|
if (!SP.available()) return false;
|
|
var host = rkHost();
|
|
if (!host) return false;
|
|
host.style.display = 'block';
|
|
toggleLegacy(true);
|
|
SP._live = true;
|
|
SP._camX = null; // snap-center on the first frame of a new game
|
|
SP._startCameraLoop();
|
|
return true;
|
|
};
|
|
|
|
// Re-apply the camera every frame while live — robust against the renderer resizing its canvas
|
|
// or a transient focusPoint, which was making the view snap to the corner mid-run.
|
|
SP._startCameraLoop = function () {
|
|
if (SP._camRaf != null) return;
|
|
function tick() {
|
|
if (!SP._live) { SP._camRaf = null; return; }
|
|
SP._applyCamera();
|
|
SP._camRaf = root.requestAnimationFrame(tick);
|
|
}
|
|
SP._camRaf = root.requestAnimationFrame(tick);
|
|
};
|
|
|
|
// Back to the ROT view (splash / win / lose). Call on game end.
|
|
SP.hide = function () {
|
|
SP._live = false;
|
|
SP._destroyRenderer();
|
|
var host = doc() && doc().getElementById('rk-game-host');
|
|
if (host) host.style.display = 'none';
|
|
toggleLegacy(false);
|
|
};
|
|
|
|
// Build the Scene from the SP client render-state and draw it. Returns true if it rendered
|
|
// (caller then skips the legacy renderer). Non-destructive on failure.
|
|
SP.render = function (clientState, opts) {
|
|
if (!SP._live || !SP.available()) return false;
|
|
SP.hideWaiting(); // the real game is rendering — drop the waiting-room overlay
|
|
var r = SP._ensureRenderer();
|
|
if (!r) return false;
|
|
opts = opts || {};
|
|
SP._lastState = clientState;
|
|
SP._lastOpts = opts;
|
|
try {
|
|
var scene = RK.sceneFromGameState(clientState, {
|
|
cryptoType: opts.cryptoType,
|
|
playerAppearance: opts.playerAppearance,
|
|
isSpectating: opts.isSpectating
|
|
});
|
|
r.render(scene);
|
|
SP._applyCamera();
|
|
return true;
|
|
} catch (e) {
|
|
if (root.console) console.warn('SPGame render failed; falling back to legacy:', e && e.message);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
if (typeof module !== 'undefined' && module.exports) module.exports = SP;
|
|
})(typeof window !== 'undefined' ? window : this);
|