mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-18 06:41:27 +02:00
correct iso avatar customization
This commit is contained in:
@@ -203,6 +203,7 @@
|
||||
|
||||
RK.isoAssets = {
|
||||
pack: 'iso-dungeon',
|
||||
avatar: 'char-villager',
|
||||
tile: { w: 84, h: 42, imageW: 92, imageH: 184 },
|
||||
tiles: {
|
||||
floor: 'assets/kenney/iso-dungeon/tiles/planks_S.png',
|
||||
@@ -211,17 +212,18 @@
|
||||
table: 'assets/kenney/iso-dungeon/tiles/chestClosed_S.png',
|
||||
fallback: 'assets/kenney/iso-dungeon/tiles/planks_S.png'
|
||||
},
|
||||
character: isoMale(0),
|
||||
// The Kenney files named Male_0..Male_7 are directional renders of the same body,
|
||||
// not different character classes. The customizer exposes one honest Iso body for now.
|
||||
character: isoMale(3),
|
||||
directions: {
|
||||
down: isoMale(3),
|
||||
up: isoMale(7),
|
||||
left: isoMale(0),
|
||||
right: isoMale(5)
|
||||
},
|
||||
characters: {
|
||||
fallback: isoMale(0),
|
||||
'char-villager': isoMale(0),
|
||||
'char-elder': isoMale(1),
|
||||
'char-barbarian': isoMale(2),
|
||||
'char-monk': isoMale(3),
|
||||
'char-ranger': isoMale(4),
|
||||
'char-bard': isoMale(5),
|
||||
'char-rogue': isoMale(6),
|
||||
'char-merchant': isoMale(7)
|
||||
fallback: isoMale(3),
|
||||
'char-villager': isoMale(3)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -117,17 +117,21 @@
|
||||
return 'topdown';
|
||||
}
|
||||
|
||||
function isoAvatarId() {
|
||||
return (RK.isoAssets && RK.isoAssets.avatar) || 'char-villager';
|
||||
}
|
||||
|
||||
function hasIsoCharacter(a) {
|
||||
return !!(a && a.kind === 'char' && RK.isoAssets && RK.isoAssets.characters && RK.isoAssets.characters[a.id]);
|
||||
return !!(a && a.kind === 'char' && a.id === isoAvatarId());
|
||||
}
|
||||
|
||||
function appearanceVisibleInProjection(a, projection) {
|
||||
if (!a) return false;
|
||||
if (a.kind === 'color') return true;
|
||||
if (projection === 'topdown') return a.kind === 'char' || a.kind === 'skin';
|
||||
if (projection === 'topdown') return a.kind === 'color' || a.kind === 'char' || a.kind === 'skin';
|
||||
if (projection === 'iso') return hasIsoCharacter(a);
|
||||
if (projection === '3d') return a.kind === 'model3d';
|
||||
return false;
|
||||
if (projection === 'ascii') return a.kind === 'color';
|
||||
return a.kind === 'color';
|
||||
}
|
||||
|
||||
function loadThumbImage(url, onReady) {
|
||||
@@ -140,7 +144,8 @@
|
||||
return rec;
|
||||
}
|
||||
|
||||
function tintColorForThumb(a, draft) {
|
||||
function tintColorForThumb(a, draft, projection) {
|
||||
if (projection === 'iso') return null;
|
||||
var source = {};
|
||||
var d = draft || {};
|
||||
for (var dk in d) source[dk] = d[dk];
|
||||
@@ -163,6 +168,13 @@
|
||||
return (chars && (chars[a.id] || chars.fallback)) || (RK.isoAssets && RK.isoAssets.character) || null;
|
||||
}
|
||||
|
||||
function isoSkinTintForDraft(draft) {
|
||||
var colors = cloneColors(draft && draft.colors, draft && draft.tint);
|
||||
if (!colors.skin || colors.skin === 'natural') return null;
|
||||
var tone = RK.CHAR_SKIN_TONES && RK.CHAR_SKIN_TONES[colors.skin];
|
||||
return (tone && tone.color) || null;
|
||||
}
|
||||
|
||||
function imageBounds(img) {
|
||||
var key = img && img.src;
|
||||
if (!img || !key) return null;
|
||||
@@ -210,7 +222,7 @@
|
||||
}
|
||||
|
||||
function drawProjectionThumb(ctx, a, draft, projection, onReady) {
|
||||
var color = tintColorForThumb(a, draft);
|
||||
var color = tintColorForThumb(a, draft, projection);
|
||||
ctx.clearRect(0, 0, 72, 80);
|
||||
if (projection === 'ascii') {
|
||||
ctx.fillStyle = color || '#9aa4b2';
|
||||
@@ -238,7 +250,7 @@
|
||||
var b = imageBounds(rec.img);
|
||||
var dh = 52;
|
||||
var dw = dh * (b.w / b.h);
|
||||
drawTintedImage(ctx, rec.img, b, 36 - dw / 2, 74 - dh, dw, dh, color);
|
||||
drawTintedImage(ctx, rec.img, b, 36 - dw / 2, 74 - dh, dw, dh, isoSkinTintForDraft(draft));
|
||||
} else {
|
||||
ctx.fillStyle = color || '#9aa4b2';
|
||||
ctx.beginPath();
|
||||
@@ -683,6 +695,11 @@
|
||||
else ensureColorDraft();
|
||||
|
||||
var projection = activeProjection();
|
||||
if (projection === 'iso' && isCharDraft()) {
|
||||
sectionTitle('Color', editSection);
|
||||
renderColorRow('Skin', 'skin', 'skin', editSection);
|
||||
return;
|
||||
}
|
||||
if (projection !== 'topdown' || !isCharDraft()) {
|
||||
sectionTitle('Color', editSection);
|
||||
renderColorRow('Color', 'base', 'tint', editSection);
|
||||
@@ -782,7 +799,7 @@
|
||||
}, redrawers));
|
||||
var lab = document.createElement('div');
|
||||
lab.className = 'rk-option-label';
|
||||
lab.textContent = a.label + (a.premium ? ' *' : '');
|
||||
lab.textContent = (activeProjection() === 'iso' ? 'Adventurer' : a.label) + (a.premium ? ' *' : '');
|
||||
card.appendChild(lab);
|
||||
card.onclick = function () { selectAvatar(a); };
|
||||
grid.appendChild(card);
|
||||
@@ -828,7 +845,7 @@
|
||||
} else {
|
||||
drawBaseThumb(pctx, base, draft, 'topdown', redrawAll);
|
||||
}
|
||||
previewName.textContent = base.label || base.id;
|
||||
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'));
|
||||
|
||||
@@ -50,9 +50,16 @@
|
||||
IsoRenderer.prototype._preload = function () {
|
||||
var tiles = this.assets.tiles || {};
|
||||
for (var k in tiles) this._load(tiles[k]);
|
||||
var self = this;
|
||||
function loadCharacter(ch) {
|
||||
if (!ch) return;
|
||||
self._load(ch.idle);
|
||||
(ch.run || []).forEach(self._load.bind(self));
|
||||
}
|
||||
var ch = this.assets.character || {};
|
||||
this._load(ch.idle);
|
||||
(ch.run || []).forEach(this._load.bind(this));
|
||||
loadCharacter(ch);
|
||||
var dirs = this.assets.directions || {};
|
||||
for (var d in dirs) loadCharacter(dirs[d]);
|
||||
};
|
||||
|
||||
IsoRenderer.prototype._project = function (x, y, originX, originY) {
|
||||
@@ -120,6 +127,15 @@
|
||||
return cv;
|
||||
};
|
||||
|
||||
IsoRenderer.prototype._skinTintFor = function (visual) {
|
||||
var ap = visual && visual.appearance;
|
||||
var skin = ap && ap.colors && ap.colors.skin;
|
||||
if (!skin || skin === 'natural') return null;
|
||||
var tones = root.RK && RK.CHAR_SKIN_TONES;
|
||||
var tone = tones && tones[skin];
|
||||
return (tone && tone.color) || null;
|
||||
};
|
||||
|
||||
IsoRenderer.prototype._tileUrl = function (kind) {
|
||||
var tiles = this.assets.tiles || {};
|
||||
return tiles[kind] || tiles.fallback || tiles.floor;
|
||||
@@ -137,11 +153,13 @@
|
||||
IsoRenderer.prototype._charFrame = function (e, now) {
|
||||
var visual = this._visualFor(e);
|
||||
if (visual && visual.allowed === false) return null;
|
||||
var dirs = this.assets.directions || {};
|
||||
var facing = (e && e.facing) || 'down';
|
||||
var key = e.id || 'anon';
|
||||
var st = this.last[key] || (this.last[key] = { x: e.x, y: e.y, t: 0 });
|
||||
if (st.x !== e.x || st.y !== e.y) { st.x = e.x; st.y = e.y; st.t = now; }
|
||||
var moving = (now - st.t) < 360;
|
||||
var ch = (visual && visual.character) || (this.assets.character || {});
|
||||
var ch = dirs[facing] || (visual && visual.character) || (this.assets.character || {});
|
||||
if (!moving || !ch.run || ch.run.length === 0) return { url: ch.idle, character: ch, visual: visual };
|
||||
this._animating = true;
|
||||
return { url: ch.run[Math.floor(now / 80) % ch.run.length], character: ch, visual: visual };
|
||||
@@ -196,9 +214,7 @@
|
||||
rec = this._load(frame && frame.url);
|
||||
if (rec && rec.ready) {
|
||||
var ch = frame.character || this.assets.character || {};
|
||||
var tint = root.RK && RK.avatarVisuals && RK.avatarVisuals.tintColorFor
|
||||
? RK.avatarVisuals.tintColorFor(frame.visual && frame.visual.appearance)
|
||||
: null;
|
||||
var tint = this._skinTintFor(frame.visual);
|
||||
this._drawSpriteImage(this._tintedImage(rec.img, tint), it.sx, it.sy + this.tileH * 0.95, ch.imageH || 92);
|
||||
} else {
|
||||
ctx.beginPath();
|
||||
|
||||
@@ -82,7 +82,7 @@ describe('render pack visual resolver', () => {
|
||||
RK.setEntitlementSnapshot({ totalCreditsPurchased: 1 });
|
||||
const iso = RK.avatarVisuals.resolve({ avatar: 'char-ranger' }, { projection: 'iso' });
|
||||
expect(iso).toMatchObject({ projection: 'iso', kind: 'iso', pack: 'iso-dungeon', allowed: true });
|
||||
expect(iso.character.idle).toContain('Male_4_Idle0.png');
|
||||
expect(iso.character.idle).toContain('Male_3_Idle0.png');
|
||||
|
||||
const compat = RK.resolveAppearance(
|
||||
{ appearance: { avatar: 'kenney-survivor-female' } },
|
||||
@@ -98,12 +98,17 @@ describe('render pack visual resolver', () => {
|
||||
expect(RK.isoAssets.tiles.wall).toContain('stoneWallHalf_S.png');
|
||||
});
|
||||
|
||||
test('iso pack exposes only real imported character variants', () => {
|
||||
test('iso pack treats imported Male files as directions, not class variants', () => {
|
||||
const RK = loadRenderKit();
|
||||
expect(RK.isoAssets.characters['char-villager'].idle).toContain('Male_0_Idle0.png');
|
||||
expect(RK.isoAssets.characters['char-merchant'].idle).toContain('Male_7_Idle0.png');
|
||||
expect(RK.isoAssets.avatar).toBe('char-villager');
|
||||
expect(RK.isoAssets.characters['char-villager'].idle).toContain('Male_3_Idle0.png');
|
||||
expect(RK.isoAssets.characters['char-merchant']).toBeUndefined();
|
||||
expect(RK.isoAssets.characters['char-wizard']).toBeUndefined();
|
||||
expect(RK.isoAssets.characters['char-goblin']).toBeUndefined();
|
||||
expect(RK.isoAssets.directions.down.idle).toContain('Male_3_Idle0.png');
|
||||
expect(RK.isoAssets.directions.up.idle).toContain('Male_7_Idle0.png');
|
||||
expect(RK.isoAssets.directions.left.idle).toContain('Male_0_Idle0.png');
|
||||
expect(RK.isoAssets.directions.right.idle).toContain('Male_5_Idle0.png');
|
||||
});
|
||||
|
||||
test('projection visuals expose the saved base tint as a render color', () => {
|
||||
|
||||
Reference in New Issue
Block a user