mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-17 22:31:26 +02:00
283 lines
11 KiB
JavaScript
283 lines
11 KiB
JavaScript
// Themes / packs / entitlement policy for the render kit. Packs are resolved by capability
|
|
// (top-down, isometric, 3D) so the same `appearance` identity can render through different
|
|
// visual projections without changing Room state or join flow.
|
|
(function (root) {
|
|
'use strict';
|
|
var RK = root.RK = root.RK || {};
|
|
|
|
RK.TILE_KINDS = ['floor', 'wall', 'bar', 'table'];
|
|
var FREE_EQUIPMENT = { body: 'none', head: 'none', shield: 'none', weapon: 'none' };
|
|
var FREE_COLORS = { base: 'none', skin: 'natural', hair: 'copper', body: 'none', head: 'none', shield: 'none', weapon: 'none' };
|
|
|
|
RK.PACKS = {
|
|
'roguelike-interior': {
|
|
id: 'roguelike-interior',
|
|
label: 'Kenney Roguelike Interior',
|
|
premium: false,
|
|
projection: 'topdown'
|
|
},
|
|
'iso-dungeon': {
|
|
id: 'iso-dungeon',
|
|
label: 'Kenney Isometric Dungeon',
|
|
premium: true,
|
|
projection: 'iso',
|
|
unlock: { kind: 'credits_purchase', minTotalCreditsPurchased: 1 }
|
|
},
|
|
'kenney-3d-characters': {
|
|
id: 'kenney-3d-characters',
|
|
label: 'Kenney Animated 3D Characters',
|
|
premium: true,
|
|
projection: '3d',
|
|
unlock: { kind: 'credits_purchase', minTotalCreditsPurchased: 1 }
|
|
},
|
|
'generated-skins': {
|
|
id: 'generated-skins',
|
|
label: 'Premium Generated Skins',
|
|
premium: true,
|
|
projection: 'topdown',
|
|
unlock: { kind: 'credits_purchase', minTotalCreditsPurchased: 1 }
|
|
}
|
|
};
|
|
|
|
RK.entitlements = RK.entitlements || { premium: false, level: 'free', packs: {}, credits: 0, totalCreditsPurchased: 0 };
|
|
|
|
RK.setEntitlementSnapshot = function (data) {
|
|
data = data || {};
|
|
var prev = RK.entitlements || {};
|
|
var hasTotal = data.totalCreditsPurchased != null || data.total_credits_purchased != null;
|
|
var hasPremium = data.premium != null;
|
|
var hasPacks = !!data.packs;
|
|
var hasCredits = data.credits != null || data.balance != null;
|
|
var total = hasTotal
|
|
? Number(data.totalCreditsPurchased || data.total_credits_purchased || 0)
|
|
: Number(prev.totalCreditsPurchased || 0);
|
|
var credits = hasCredits
|
|
? Number(data.credits != null ? data.credits : data.balance)
|
|
: Number(prev.credits || 0);
|
|
var premium = hasPremium ? !!data.premium : (hasTotal ? total > 0 : !!prev.premium);
|
|
var packs = {};
|
|
for (var id in RK.PACKS) {
|
|
var p = RK.PACKS[id];
|
|
if (!p.premium) {
|
|
packs[id] = true;
|
|
} else if (hasPacks && Object.prototype.hasOwnProperty.call(data.packs, id)) {
|
|
packs[id] = !!data.packs[id];
|
|
} else if (!hasTotal && !hasPacks && prev.packs && Object.prototype.hasOwnProperty.call(prev.packs, id)) {
|
|
packs[id] = !!prev.packs[id];
|
|
} else {
|
|
packs[id] = premium;
|
|
}
|
|
}
|
|
for (var pid in packs) {
|
|
if (packs[pid] && RK.PACKS[pid] && RK.PACKS[pid].premium) premium = true;
|
|
}
|
|
RK.entitlements = {
|
|
premium: premium,
|
|
level: data.level || (premium ? ((prev.level && prev.level !== 'free') ? prev.level : 'credits') : 'free'),
|
|
packs: packs,
|
|
credits: credits,
|
|
totalCreditsPurchased: total
|
|
};
|
|
try { localStorage.setItem('rk_entitlements', JSON.stringify(RK.entitlements)); } catch (_) {}
|
|
return RK.entitlements;
|
|
};
|
|
|
|
RK.loadEntitlements = function () {
|
|
try {
|
|
var raw = JSON.parse(localStorage.getItem('rk_entitlements') || 'null');
|
|
if (raw) return RK.setEntitlementSnapshot(raw);
|
|
} catch (_) {}
|
|
return RK.entitlements;
|
|
};
|
|
RK.loadEntitlements();
|
|
|
|
RK.pack = function (id) { return RK.PACKS[id] || null; };
|
|
RK.canUsePack = function (id) {
|
|
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;
|
|
return !!(RK.entitlements && RK.entitlements.packs && RK.entitlements.packs[id]);
|
|
};
|
|
RK.canUseAppearance = function (a) {
|
|
if (!a || !a.premium) return true;
|
|
return a.pack ? RK.canUsePack(a.pack) : !!(RK.entitlements && RK.entitlements.premium);
|
|
};
|
|
|
|
RK.THEMES = {
|
|
'roguelike-interior': {
|
|
id: 'roguelike-interior',
|
|
label: 'Kenney Roguelike Interior',
|
|
tileset: { url: 'assets/kenney/roguelikeIndoor.png', tile: 16, spacing: 1 },
|
|
// Defaults are null until mapped via the tile-picker (localStorage overrides win).
|
|
tiles: { floor: null, wall: null, bar: null, table: null }
|
|
}
|
|
};
|
|
RK.activeThemeId = 'roguelike-interior';
|
|
|
|
RK.theme = function (id) { return RK.THEMES[id || RK.activeThemeId] || null; };
|
|
|
|
// Effective tile map = theme defaults merged with localStorage overrides from the picker.
|
|
RK.tileMap = function (id) {
|
|
var t = RK.theme(id); if (!t) return {};
|
|
var map = {};
|
|
for (var k in t.tiles) map[k] = t.tiles[k];
|
|
try {
|
|
var ov = JSON.parse(localStorage.getItem('rk_tilemap_' + t.id) || '{}');
|
|
for (var kk in ov) map[kk] = ov[kk];
|
|
} catch (_) { /* ignore */ }
|
|
return map;
|
|
};
|
|
RK.setTile = function (id, kind, col, row) {
|
|
var t = RK.theme(id); if (!t) return;
|
|
var key = 'rk_tilemap_' + t.id, ov = {};
|
|
try { ov = JSON.parse(localStorage.getItem(key) || '{}'); } catch (_) {}
|
|
ov[kind] = [col, row];
|
|
try { localStorage.setItem(key, JSON.stringify(ov)); } catch (_) {}
|
|
};
|
|
|
|
// Lazy tileset atlas per theme.
|
|
var atlases = {};
|
|
RK.themeAtlas = function (id) {
|
|
var t = RK.theme(id); if (!t || !t.tileset) return null;
|
|
if (!atlases[t.id]) {
|
|
atlases[t.id] = new RK.TilesetAtlas(t.tileset.url, { tile: t.tileset.tile, spacing: t.tileset.spacing });
|
|
}
|
|
return atlases[t.id];
|
|
};
|
|
RK.loadTheme = function (id, cb) {
|
|
var a = RK.themeAtlas(id);
|
|
if (!a) { if (cb) cb(null); return; }
|
|
a.onReady(function () { if (cb) cb(a); });
|
|
};
|
|
|
|
RK.EXTRA_APPEARANCES = {
|
|
'kenney-survivor-male': {
|
|
id: 'kenney-survivor-male',
|
|
label: 'Survivor',
|
|
kind: 'model3d',
|
|
premium: true,
|
|
pack: 'kenney-3d-characters'
|
|
},
|
|
'kenney-survivor-female': {
|
|
id: 'kenney-survivor-female',
|
|
label: 'Survivor Scout',
|
|
kind: 'model3d',
|
|
premium: true,
|
|
pack: 'kenney-3d-characters'
|
|
}
|
|
};
|
|
|
|
function normalizeEquipment(eq) {
|
|
eq = eq || {};
|
|
var out = {};
|
|
for (var k in FREE_EQUIPMENT) out[k] = eq[k] || 'none';
|
|
return out;
|
|
}
|
|
|
|
function normalizeColors(colors, tint) {
|
|
if (RK.normalizeCharColors) return RK.normalizeCharColors(colors, tint);
|
|
colors = colors || {};
|
|
var out = {};
|
|
for (var k in FREE_COLORS) out[k] = colors[k] || (k === 'base' ? (tint || FREE_COLORS[k]) : FREE_COLORS[k]);
|
|
return out;
|
|
}
|
|
|
|
RK.packAppearances = function () {
|
|
var out = [];
|
|
for (var id in RK.EXTRA_APPEARANCES) out.push(RK.EXTRA_APPEARANCES[id]);
|
|
return out;
|
|
};
|
|
|
|
RK.isoAssets = {
|
|
pack: 'iso-dungeon',
|
|
tile: { w: 84, h: 42, imageW: 92, imageH: 184 },
|
|
tiles: {
|
|
floor: 'assets/kenney/iso-dungeon/tiles/planks_S.png',
|
|
wall: 'assets/kenney/iso-dungeon/tiles/stoneWall_S.png',
|
|
bar: 'assets/kenney/iso-dungeon/tiles/barrelsStacked_S.png',
|
|
table: 'assets/kenney/iso-dungeon/tiles/chestClosed_S.png',
|
|
fallback: 'assets/kenney/iso-dungeon/tiles/planks_S.png'
|
|
},
|
|
character: {
|
|
idle: 'assets/kenney/iso-dungeon/characters/male0/Male_0_Idle0.png',
|
|
run: [
|
|
'assets/kenney/iso-dungeon/characters/male0/Male_0_Run0.png',
|
|
'assets/kenney/iso-dungeon/characters/male0/Male_0_Run1.png',
|
|
'assets/kenney/iso-dungeon/characters/male0/Male_0_Run2.png',
|
|
'assets/kenney/iso-dungeon/characters/male0/Male_0_Run3.png',
|
|
'assets/kenney/iso-dungeon/characters/male0/Male_0_Run4.png',
|
|
'assets/kenney/iso-dungeon/characters/male0/Male_0_Run5.png',
|
|
'assets/kenney/iso-dungeon/characters/male0/Male_0_Run6.png',
|
|
'assets/kenney/iso-dungeon/characters/male0/Male_0_Run7.png',
|
|
'assets/kenney/iso-dungeon/characters/male0/Male_0_Run8.png',
|
|
'assets/kenney/iso-dungeon/characters/male0/Male_0_Run9.png'
|
|
],
|
|
imageW: 74,
|
|
imageH: 148
|
|
}
|
|
};
|
|
|
|
RK.threeAssets = {
|
|
pack: 'kenney-3d-characters',
|
|
models: {
|
|
'kenney-survivor-male': {
|
|
url: 'assets/generated/3d/kenney-animated-characters/survivorMaleB.glb',
|
|
clips: { idle: 'idle', run: 'run', jump: 'jump' }
|
|
},
|
|
'kenney-survivor-female': {
|
|
url: 'assets/generated/3d/kenney-animated-characters/survivorFemaleA.glb',
|
|
clips: { idle: 'idle', run: 'run', jump: 'jump' }
|
|
},
|
|
fallback: {
|
|
url: 'assets/generated/3d/kenney-animated-characters/survivorMaleB.glb',
|
|
clips: { idle: 'idle', run: 'run', jump: 'jump' }
|
|
}
|
|
}
|
|
};
|
|
|
|
RK.packForProjection = function (projection) {
|
|
for (var id in RK.PACKS) {
|
|
if (RK.PACKS[id].projection === projection) return RK.PACKS[id];
|
|
}
|
|
return null;
|
|
};
|
|
|
|
RK.resolveAppearance = function (entity, projection) {
|
|
entity = entity || {};
|
|
var baseAppearance = entity.appearance || { avatar: entity.avatar || 'default', tint: 'none', equipment: FREE_EQUIPMENT };
|
|
if (RK.avatarVisuals && RK.avatarVisuals.resolve) {
|
|
return RK.avatarVisuals.resolve(baseAppearance, {
|
|
projection: projection || 'topdown',
|
|
context: 'room-entity',
|
|
entity: entity
|
|
});
|
|
}
|
|
var appearance = entity.appearance || { avatar: entity.avatar || 'default', tint: 'none', equipment: FREE_EQUIPMENT };
|
|
var avatar = appearance.avatar || entity.avatar || 'default';
|
|
if (projection === 'iso') {
|
|
return {
|
|
projection: 'iso',
|
|
pack: 'iso-dungeon',
|
|
avatar: avatar,
|
|
tint: appearance.tint || 'none',
|
|
equipment: normalizeEquipment(appearance.equipment),
|
|
colors: normalizeColors(appearance.colors, appearance.tint),
|
|
character: RK.isoAssets.character
|
|
};
|
|
}
|
|
if (projection === '3d') {
|
|
return {
|
|
projection: '3d',
|
|
pack: 'kenney-3d-characters',
|
|
avatar: avatar,
|
|
model: RK.threeAssets.models[avatar] || RK.threeAssets.models.fallback,
|
|
tint: appearance.tint || 'none',
|
|
equipment: normalizeEquipment(appearance.equipment),
|
|
colors: normalizeColors(appearance.colors, appearance.tint)
|
|
};
|
|
}
|
|
return { projection: 'topdown', avatar: avatar, appearance: appearance };
|
|
};
|
|
})(window);
|