mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-26 01:28:33 +01:00
fix: char customizer flakiness + wire real render-mode entitlement gating
- Equipment option tiles register for async redraw (were stuck on '...') - Render mode switching no longer silently mutates avatar identity - Dungeon renderer falls back to @ tile when overlay draw fails - Main-game customizer locked to topdown (no confusing render-mode picker) - equipRedrawers prevents unbounded redraw array growth - RENDER_MODE_TEST_UNLOCKS flipped to false; ASCII made free (accessible fallback) - Documented COSMETIC_PRODUCTS in .env.example - 5 new tests (239 total, 36 suites, all passing)
This commit is contained in:
@@ -53,7 +53,7 @@
|
||||
if (root.socket && root.socket.emit) {
|
||||
root.socket.emit('identity:update', { appearance: normalized });
|
||||
}
|
||||
});
|
||||
}, { projection: 'topdown' });
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -258,11 +258,18 @@ var RenderEngine = {
|
||||
}
|
||||
|
||||
if (avatarOverlayReady && window.SinglePlayerAvatar.drawPlayer) {
|
||||
window.SinglePlayerAvatar.drawPlayer(gameState, {
|
||||
var drew = window.SinglePlayerAvatar.drawPlayer(gameState, {
|
||||
screenX: centerX,
|
||||
screenY: centerY,
|
||||
cell: window.options ? window.options.tileWidth : 32
|
||||
});
|
||||
// Fallback: if the overlay couldn't render (display not ready, atlas stale, etc.),
|
||||
// draw the @ tile so the player is never invisible. The @ was suppressed above
|
||||
// because canDrawPlayer returned true, but the overlay draw may still fail.
|
||||
if (!drew) {
|
||||
try { display.draw(centerX, centerY, playerTile, "#FFF", "transparent"); }
|
||||
catch (_) { /* ignore — next frame will retry */ }
|
||||
}
|
||||
}
|
||||
|
||||
// Render message at bottom if present
|
||||
|
||||
@@ -96,7 +96,8 @@
|
||||
var p = RK.pack(id);
|
||||
if (!p) return false;
|
||||
if (!p.premium) return true;
|
||||
if (RK.renderModeTestUnlocks && RK.renderModeTestUnlocks() && (p.projection === 'iso' || p.projection === '3d')) return true;
|
||||
// Production QA switch: when enabled, all premium packs are available for testing.
|
||||
if (RK.renderModeTestUnlocks && RK.renderModeTestUnlocks()) return true;
|
||||
return !!(RK.entitlements && RK.entitlements.packs && RK.entitlements.packs[id]);
|
||||
};
|
||||
RK.canUseAppearance = function (a) {
|
||||
|
||||
@@ -112,6 +112,13 @@
|
||||
} catch (_) { return null; }
|
||||
};
|
||||
|
||||
function projectionToMode(projection) {
|
||||
if (projection === 'iso') return 'iso';
|
||||
if (projection === '3d') return '3d';
|
||||
if (projection === 'ascii') return 'ascii';
|
||||
return 'tiles';
|
||||
}
|
||||
|
||||
function projectionForMode(mode) {
|
||||
if (mode === 'iso' || mode === '3d' || mode === 'ascii') return mode;
|
||||
return 'topdown';
|
||||
@@ -424,11 +431,13 @@
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
RK.openCustomize = function (current, onSave) {
|
||||
RK.openCustomize = function (current, onSave, opts) {
|
||||
opts = opts || {};
|
||||
ensureCustomizeStyle();
|
||||
|
||||
var draft = RK.normalizeAppearance(current);
|
||||
var redrawers = [];
|
||||
var redrawers = []; // avatar thumbnail draws (reset in buildAvatarGrid)
|
||||
var equipRedrawers = []; // equipment option tile draws (reset in renderEditor)
|
||||
var cards = {};
|
||||
|
||||
function isCharDraft() {
|
||||
@@ -519,10 +528,14 @@
|
||||
content.className = 'rk-customize-content';
|
||||
body.appendChild(content);
|
||||
|
||||
var selectedMode = RK.loadMode ? RK.loadMode('tiles') : 'tiles';
|
||||
// The main game page only uses top-down rendering, so the customizer there should
|
||||
// default to top-down and not offer the tavern's render-mode picker. The tavern passes
|
||||
// no opts, so it gets the full picker and the user's saved mode.
|
||||
var selectedMode = opts.projection ? projectionToMode(opts.projection) : (RK.loadMode ? RK.loadMode('tiles') : 'tiles');
|
||||
var showRenderModes = !opts.projection && RK.RENDER_MODES && RK.RENDER_MODES.length;
|
||||
var modeCards = {};
|
||||
var renderModeGrid = null;
|
||||
if (RK.RENDER_MODES && RK.RENDER_MODES.length) {
|
||||
if (showRenderModes) {
|
||||
var renderSection = document.createElement('section');
|
||||
renderSection.className = 'rk-customize-section';
|
||||
content.appendChild(renderSection);
|
||||
@@ -690,6 +703,7 @@
|
||||
|
||||
function renderEditor() {
|
||||
editSection.innerHTML = '';
|
||||
equipRedrawers = [];
|
||||
if (!supportsColorDraft()) return;
|
||||
if (isCharDraft()) ensureCharDraft();
|
||||
else ensureColorDraft();
|
||||
@@ -725,7 +739,7 @@
|
||||
draft.equipment[slot] = item.id;
|
||||
renderEditor();
|
||||
redrawAll();
|
||||
}, optionTileCanvas(slot, item, draft, null)));
|
||||
}, optionTileCanvas(slot, item, draft, equipRedrawers)));
|
||||
})(catalog[id]);
|
||||
}
|
||||
if (equipmentColorable(selectedItem)) {
|
||||
@@ -781,7 +795,10 @@
|
||||
}
|
||||
|
||||
function buildAvatarGrid() {
|
||||
ensureVisibleAvatar();
|
||||
// Don't auto-switch the avatar when the render mode changes — only an explicit
|
||||
// click on a different avatar card should change the saved identity. If the current
|
||||
// avatar isn't native to this projection, it stays selected (the resolver handles
|
||||
// fallback rendering) so browsing render modes never silently mutates the identity.
|
||||
grid.innerHTML = '';
|
||||
cards = {};
|
||||
redrawers = [];
|
||||
@@ -799,7 +816,7 @@
|
||||
}, redrawers));
|
||||
var lab = document.createElement('div');
|
||||
lab.className = 'rk-option-label';
|
||||
lab.textContent = (activeProjection() === 'iso' ? 'Adventurer' : a.label) + (a.premium ? ' *' : '');
|
||||
lab.textContent = a.label + (a.premium ? ' *' : '');
|
||||
card.appendChild(lab);
|
||||
card.onclick = function () { selectAvatar(a); };
|
||||
grid.appendChild(card);
|
||||
@@ -845,15 +862,21 @@
|
||||
} else {
|
||||
drawBaseThumb(pctx, base, draft, 'topdown', redrawAll);
|
||||
}
|
||||
previewName.textContent = projection === 'iso' ? 'Adventurer' : (base.label || base.id);
|
||||
previewMeta.textContent = projection !== 'topdown'
|
||||
? (projection.toUpperCase() + ' identity')
|
||||
: (base.premium ? 'Premium pack' : (base.kind === 'char' ? 'Customizable' : 'Free'));
|
||||
var visible = appearanceVisibleInProjection(base, projection);
|
||||
previewName.textContent = base.label || base.id;
|
||||
var metaParts = [];
|
||||
if (projection !== 'topdown') metaParts.push(projection.toUpperCase() + ' view');
|
||||
if (!visible && projection !== 'topdown') metaParts.push('fallback render');
|
||||
if (base.premium) metaParts.push('Premium pack');
|
||||
else if (base.kind === 'char') metaParts.push('Customizable');
|
||||
else if (base.kind === 'color') metaParts.push('Free');
|
||||
previewMeta.textContent = metaParts.join(' · ') || 'Free';
|
||||
}
|
||||
|
||||
function redrawAll() {
|
||||
drawPreview();
|
||||
redrawers.forEach(function (fn) { try { fn(); } catch (_) { /* ignore */ } });
|
||||
equipRedrawers.forEach(function (fn) { try { fn(); } catch (_) { /* ignore */ } });
|
||||
}
|
||||
|
||||
function mark() {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// is client-side, so this is a soft/honour gate for cosmetics, not a hard security boundary.
|
||||
RK.RENDER_MODES = [
|
||||
{ id: 'tiles', label: 'Tiled', premium: false },
|
||||
{ id: 'ascii', label: 'ASCII', premium: true },
|
||||
{ id: 'ascii', label: 'ASCII', premium: false },
|
||||
{ id: 'fancy', label: 'Fancy', premium: true, pack: 'generated-skins' },
|
||||
{ id: 'iso', label: 'Iso', premium: true, pack: 'iso-dungeon' },
|
||||
{ id: '3d', label: '3D', premium: true, pack: 'kenney-3d-characters' }
|
||||
@@ -16,9 +16,9 @@
|
||||
];
|
||||
|
||||
RK.entitlements = RK.entitlements || { premium: false, level: 'free', packs: {} };
|
||||
// Temporary production QA switch while Iso/3D/Fancy are being proven out. Flip this back to
|
||||
// false when render modes return behind credits/pack entitlements.
|
||||
RK.RENDER_MODE_TEST_UNLOCKS = true;
|
||||
// Production QA switch: set to true to temporarily bypass premium render-mode gating for
|
||||
// testing. Default false — premium modes (Fancy/Iso/3D) gate behind pack entitlements.
|
||||
RK.RENDER_MODE_TEST_UNLOCKS = false;
|
||||
RK.renderModeTestUnlocks = function () { return RK.RENDER_MODE_TEST_UNLOCKS === true; };
|
||||
|
||||
RK.modeMeta = function (id) {
|
||||
|
||||
Reference in New Issue
Block a user