mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-24 01:28:33 +02:00
fix(tavern): repaint permitted avatars and prioritize the live arena
This commit is contained in:
@@ -128,7 +128,7 @@
|
||||
|
||||
function rememberLoad(key, start, onReady) {
|
||||
if (loading[key]) {
|
||||
if (onReady) loading[key].push(onReady);
|
||||
if (onReady && loading[key].indexOf(onReady) === -1) loading[key].push(onReady);
|
||||
return;
|
||||
}
|
||||
loading[key] = onReady ? [onReady] : [];
|
||||
|
||||
@@ -72,13 +72,20 @@
|
||||
host.appendChild(this.canvas);
|
||||
this._snap = null; // offscreen snapshot of the base scene (tiles + entities)
|
||||
this._lastScene = null;
|
||||
this._avatarReadyRepaintScheduled = false;
|
||||
this._destroyed = false;
|
||||
var self = this;
|
||||
this._onAvatarReady = function () {
|
||||
if (self._avatarAssetsReady(self._lastScene)) self._scheduleAvatarReadyRepaint();
|
||||
};
|
||||
this._embers = [];
|
||||
this._raf = null;
|
||||
this._t = 0;
|
||||
}
|
||||
|
||||
TileRenderer.prototype.render = function (scene) {
|
||||
if (!scene || !this.ctx) return;
|
||||
if (!scene || !this.ctx || this._destroyed) return;
|
||||
this._lastScene = scene;
|
||||
var cell = this.cell, ctx = this.ctx;
|
||||
this.canvas.width = scene.cols * cell;
|
||||
this.canvas.height = scene.rows * cell;
|
||||
@@ -171,6 +178,7 @@
|
||||
}
|
||||
|
||||
var now = Date.now();
|
||||
var onAvatarReady = this._onAvatarReady;
|
||||
for (var i = 0; i < scene.entities.length; i++) {
|
||||
var e = scene.entities[i];
|
||||
// Dungeon features (entrance / exit / treasure): real tiles, glyph fallback.
|
||||
@@ -216,7 +224,7 @@
|
||||
var mAvatar = e.avatar || 'char-goblin';
|
||||
if (window.RK && RK.avatarVisuals && RK.avatarVisuals.drawTopdownWorld) {
|
||||
var mv = RK.avatarVisuals.resolve({ avatar: mAvatar }, { projection: 'topdown', context: 'dungeon', entity: e });
|
||||
if (RK.avatarVisuals.drawTopdownWorld(ctx, mv, e, { screenX: e.x, screenY: e.y, cell: cell }, { now: now })) continue;
|
||||
if (RK.avatarVisuals.drawTopdownWorld(ctx, mv, e, { screenX: e.x, screenY: e.y, cell: cell }, { now: now, onReady: onAvatarReady })) continue;
|
||||
}
|
||||
if (window.RK && RK.drawCharCanvas && RK.drawCharCanvas(ctx, { x: e.x, y: e.y, avatar: mAvatar }, cell, now)) continue;
|
||||
var mx = e.x * cell + cell / 2, my = e.y * cell + cell / 2, mr = cell * 0.34;
|
||||
@@ -253,7 +261,7 @@
|
||||
screenX: e.x,
|
||||
screenY: e.y,
|
||||
cell: cell
|
||||
}, { now: now })) continue;
|
||||
}, { now: now, onReady: onAvatarReady })) continue;
|
||||
} else {
|
||||
if (window.RK && RK.isChar && RK.isChar(e.avatar) && RK.drawCharCanvas(ctx, e, cell, now)) continue;
|
||||
if (window.RK && RK.isSkin && RK.isSkin(e.avatar) && RK.drawSkinCanvas(ctx, e, cell, now)) continue;
|
||||
@@ -292,7 +300,6 @@
|
||||
var _fp = findPlayer(scene);
|
||||
if (_fp) this.focusPoint = { x: (_fp.x + 0.5) * cell, y: (_fp.y + 0.5) * cell };
|
||||
|
||||
this._lastScene = scene;
|
||||
this._collectEmitters(scene);
|
||||
this._snapshot();
|
||||
this._ensureEmbers(scene);
|
||||
@@ -300,6 +307,36 @@
|
||||
this._startLoop();
|
||||
};
|
||||
|
||||
// Character atlases load asynchronously. Coalesce callbacks from multiple entities and repaint
|
||||
// the latest retained scene once the ready assets can be drawn.
|
||||
TileRenderer.prototype._scheduleAvatarReadyRepaint = function () {
|
||||
if (this._destroyed || !this.ctx || this._avatarReadyRepaintScheduled) return;
|
||||
this._avatarReadyRepaintScheduled = true;
|
||||
var self = this;
|
||||
root.requestAnimationFrame(function () {
|
||||
self._avatarReadyRepaintScheduled = false;
|
||||
if (self._destroyed || !self.ctx || !self._lastScene) return;
|
||||
self.render(self._lastScene);
|
||||
});
|
||||
};
|
||||
|
||||
TileRenderer.prototype._avatarAssetsReady = function (scene) {
|
||||
var rk = window.RK;
|
||||
if (!scene || !Array.isArray(scene.entities) || !rk || !rk.avatarVisuals || !rk.avatarVisuals.resolve) return false;
|
||||
for (var i = 0; i < scene.entities.length; i++) {
|
||||
var entity = scene.entities[i];
|
||||
var appearance = entity.appearance || { avatar: entity.avatar || (entity.kind === 'monster' ? 'char-goblin' : 'default') };
|
||||
var visual = rk.avatarVisuals.resolve(appearance, { projection: 'topdown', context: 'tavern', entity: entity });
|
||||
if (!visual.allowed) continue;
|
||||
if (visual.kind === 'char' && rk.charAtlas && rk.charAtlas()) return true;
|
||||
if (visual.kind === 'skin' && rk.skinSheet && rk.skinSheet(visual.appearance.avatar)) return true;
|
||||
// A failed generated sheet changes the next draw into the permitted Villager fallback path.
|
||||
// Repaint once for that state change so the fallback atlas can start loading.
|
||||
if (visual.kind === 'skin' && rk.skinSheetFailed && rk.skinSheetFailed(visual.appearance.avatar)) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Cache the fire/hazard tile positions once per scene so the flicker loop can animate them
|
||||
// without rescanning the grid every frame.
|
||||
TileRenderer.prototype._collectEmitters = function (scene) {
|
||||
@@ -493,6 +530,7 @@
|
||||
};
|
||||
|
||||
TileRenderer.prototype.destroy = function () {
|
||||
this._destroyed = true;
|
||||
if (this._raf != null) { root.cancelAnimationFrame(this._raf); this._raf = null; }
|
||||
if (this.canvas && this.canvas.parentNode) this.canvas.parentNode.removeChild(this.canvas);
|
||||
this.canvas = null;
|
||||
|
||||
+27
-14
@@ -127,7 +127,7 @@
|
||||
|
||||
<div id="modebar" class="hidden" role="group" aria-label="Render style"></div>
|
||||
<div id="packbar" class="hidden" role="group" aria-label="Visual pack"></div>
|
||||
<div id="stage" class="hidden" aria-label="Shared tavern room"></div>
|
||||
<div id="stage" class="hidden" aria-label="Shared tavern room" tabindex="-1"></div>
|
||||
<div id="tavernDpad" class="touch-pad hidden" role="group" aria-label="Tavern movement controls">
|
||||
<button type="button" data-dir="up" aria-label="Move up">▲</button>
|
||||
<button type="button" data-dir="left" aria-label="Move left">◀</button>
|
||||
@@ -137,6 +137,21 @@
|
||||
<p id="status" class="muted" role="status" aria-live="polite"></p>
|
||||
<p id="movementHint" class="muted hidden">Move with arrow keys or WASD · drag/pinch to explore · ★ may require credits · ⛔ is unavailable on this server.</p>
|
||||
|
||||
<!-- Keep the live arena ahead of chat and controls in the reading and visual order. -->
|
||||
<section id="spectatorStage" class="hidden" aria-label="Live arena" tabindex="-1">
|
||||
<div class="spectator-head">
|
||||
<span id="spectatorLabel" class="muted"></span>
|
||||
<button id="leaveSpectateBtn" class="secondary">← Back to tavern</button>
|
||||
</div>
|
||||
<div id="spectatorRender" class="spectator-render"></div>
|
||||
<div id="spectatorDpad" class="touch-pad hidden" role="group" aria-label="Match movement controls">
|
||||
<button type="button" data-dir="up" aria-label="Move up">▲</button>
|
||||
<button type="button" data-dir="left" aria-label="Move left">◀</button>
|
||||
<button type="button" data-dir="down" aria-label="Move down">▼</button>
|
||||
<button type="button" data-dir="right" aria-label="Move right">▶</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div id="chatPanel" class="panel hidden">
|
||||
<div id="chatLog" aria-live="polite"></div>
|
||||
<form id="chatForm" autocomplete="off">
|
||||
@@ -171,19 +186,6 @@
|
||||
<div id="activeMatchList" style="max-height:160px;overflow-y:auto;font-size:13px;"></div>
|
||||
<button id="closeMatchPanelBtn" class="secondary" style="margin-top:8px;">Close</button>
|
||||
</div>
|
||||
<div id="spectatorStage" class="hidden">
|
||||
<div class="spectator-head">
|
||||
<span id="spectatorLabel" class="muted"></span>
|
||||
<button id="leaveSpectateBtn" class="secondary">← Back to tavern</button>
|
||||
</div>
|
||||
<div id="spectatorRender" class="spectator-render"></div>
|
||||
<div id="spectatorDpad" class="touch-pad hidden" role="group" aria-label="Match movement controls">
|
||||
<button type="button" data-dir="up" aria-label="Move up">▲</button>
|
||||
<button type="button" data-dir="left" aria-label="Move left">◀</button>
|
||||
<button type="button" data-dir="down" aria-label="Move down">▼</button>
|
||||
<button type="button" data-dir="right" aria-label="Move right">▶</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
@@ -219,6 +221,7 @@
|
||||
var spectatorRender = document.getElementById('spectatorRender');
|
||||
var spectatorDpad = document.getElementById('spectatorDpad');
|
||||
var leaveSpectateBtn = document.getElementById('leaveSpectateBtn');
|
||||
var spectatorReturnFocus = null;
|
||||
|
||||
// Character customization: persisted appearance identity, chosen before entering.
|
||||
var customizeBtn = document.getElementById('customizeBtn');
|
||||
@@ -699,6 +702,7 @@
|
||||
if (spectatorCamera) spectatorCamera.update(spectatorRenderer);
|
||||
}
|
||||
function openSpectatorView(kind, label, initialState) {
|
||||
spectatorReturnFocus = document.activeElement;
|
||||
spectatorKind = kind;
|
||||
spectatorPendingState = initialState || null;
|
||||
joinPanel.classList.add('hidden');
|
||||
@@ -713,6 +717,7 @@
|
||||
spectatorDpad.classList.toggle('hidden', kind !== 'player-match');
|
||||
spectatorLabel.textContent = label || 'Live dungeon';
|
||||
leaveSpectateBtn.textContent = kind === 'player-match' ? 'Forfeit match' : '← Back to tavern';
|
||||
spectatorStage.focus();
|
||||
destroySpectatorRenderer();
|
||||
var request = ++spectatorRequest;
|
||||
var renderMode = currentMode || (RK.loadMode && RK.loadMode('tiles')) || 'tiles';
|
||||
@@ -746,6 +751,14 @@
|
||||
joinPanel.classList.remove('hidden');
|
||||
}
|
||||
leaveSpectateBtn.textContent = '← Back to tavern';
|
||||
var returnFocus = spectatorReturnFocus;
|
||||
spectatorReturnFocus = null;
|
||||
window.requestAnimationFrame(function () {
|
||||
if (spectatorKind) return;
|
||||
if (!returnFocus || !document.contains(returnFocus) || !returnFocus.getClientRects().length
|
||||
|| returnFocus.disabled) returnFocus = state.you ? stageEl : joinBtn;
|
||||
if (returnFocus && typeof returnFocus.focus === 'function') returnFocus.focus();
|
||||
});
|
||||
}
|
||||
|
||||
function populateSoloGames(data) {
|
||||
|
||||
Reference in New Issue
Block a user