feat: ���� 3D-�������-��������� � ����� + dev JWT-������ #9
@ -653,45 +653,51 @@ export class PlayerController {
|
|||||||
*/
|
*/
|
||||||
async _loadSkinManifest() {
|
async _loadSkinManifest() {
|
||||||
if (this._skinManifest) return this._skinManifest;
|
if (this._skinManifest) return this._skinManifest;
|
||||||
// 2026-05-27: сначала пробуем БД (rublox_avatars), там и легаси и
|
// ВАЖНО: объединяем ОБА источника, а не «или-или».
|
||||||
// дизайнерские аватары после approve. Только при сетевой ошибке —
|
// Баг (2026-05-30): раньше при непустом /rublox/avatars возвращался
|
||||||
// fallback на статичный manifest.json.
|
// ТОЛЬКО он, а статичный skins_manifest.json (где встроенные
|
||||||
|
// non-humanoid скины — еда/машины/животные: skin_burger, squirrel-donut
|
||||||
|
// и т.д.) НЕ подгружался. setSkin('burger') не находил entry → fallback
|
||||||
|
// на несуществующий characters/skin_burger/body.glb → 404 → краш GLTF
|
||||||
|
// (Unexpected magic) → старая модель уже выгружена, новая не создаётся →
|
||||||
|
// скин исчезал. Теперь грузим статичный манифест ВСЕГДА, плюс аватары.
|
||||||
|
let combined = [];
|
||||||
|
// 1) Статичный JSON (встроенные скины, включая non-humanoid).
|
||||||
|
try {
|
||||||
|
const resp = await fetch('/kubikon-assets/characters/skins_manifest.json');
|
||||||
|
if (resp.ok) {
|
||||||
|
const json = await resp.json();
|
||||||
|
if (Array.isArray(json.skins)) combined = combined.concat(json.skins);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn('[PlayerController] skins_manifest load failed:', e);
|
||||||
|
}
|
||||||
|
// 2) БД rublox_avatars (легаси + дизайнерские аватары после approve).
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(_storysApiUrl('/rublox/avatars'));
|
const resp = await fetch(_storysApiUrl('/rublox/avatars'));
|
||||||
if (resp.ok) {
|
if (resp.ok) {
|
||||||
const json = await resp.json();
|
const json = await resp.json();
|
||||||
const items = json.items || [];
|
const items = json.items || [];
|
||||||
// Нормализуем под формат старого manifest:
|
// Нормализуем: file уже полный путь (absolute_file=true), т.к.
|
||||||
// {id, file (без /kubikon-assets/ префикса), overrides}
|
// _resolveModelSource иначе добавляет '/kubikon-assets/' префикс.
|
||||||
// — потому что _resolveModelSource дальше добавляет
|
const avatars = items.map((a) => ({
|
||||||
// '/kubikon-assets/' + entry.file.
|
|
||||||
// Дизайнерский file_path может быть /api-storys/... — оставляем
|
|
||||||
// как есть и добавляем спец-флаг entry.absolute_file=true,
|
|
||||||
// _resolveModelSource учтёт.
|
|
||||||
this._skinManifest = items.map((a) => ({
|
|
||||||
id: a.code,
|
id: a.code,
|
||||||
name: a.name,
|
name: a.name,
|
||||||
file: a.file_path,
|
file: a.file_path,
|
||||||
overrides: a.overrides || {},
|
overrides: a.overrides || {},
|
||||||
absolute_file: true, // file уже полный путь, не resolve через /kubikon-assets/
|
absolute_file: true,
|
||||||
}));
|
}));
|
||||||
if (this._skinManifest.length > 0) return this._skinManifest;
|
// Аватары имеют приоритет при совпадении id — кладём в начало.
|
||||||
|
const avatarIds = new Set(avatars.map((a) => a.id));
|
||||||
|
combined = avatars.concat(combined.filter((s) => !avatarIds.has(s.id)));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.warn('[PlayerController] /rublox/avatars failed, fallback to manifest.json:', e);
|
console.warn('[PlayerController] /rublox/avatars failed:', e);
|
||||||
}
|
}
|
||||||
// Fallback на статичный JSON
|
this._skinManifest = combined;
|
||||||
try {
|
return combined;
|
||||||
const resp = await fetch('/kubikon-assets/characters/skins_manifest.json');
|
|
||||||
const json = await resp.json();
|
|
||||||
this._skinManifest = json.skins || [];
|
|
||||||
} catch (e) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.warn('[PlayerController] skins_manifest load failed:', e);
|
|
||||||
this._skinManifest = [];
|
|
||||||
}
|
|
||||||
return this._skinManifest;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user