From bec3ee830c365fb324ef60f75020814357a9a6de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=98=D0=9D?= Date: Sat, 30 May 2026 14:06:56 +0300 Subject: [PATCH] =?UTF-8?q?fix(player):=20=D1=81=D0=BC=D0=B5=D0=BD=D0=B0?= =?UTF-8?q?=20=D1=81=D0=BA=D0=B8=D0=BD=D0=B0=20-=20=D0=BC=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=84=D0=B5=D1=81=D1=82=20=D0=BE=D0=B1=D1=8A=D0=B5=D0=B4?= =?UTF-8?q?=D0=B8=D0=BD=D1=8F=D0=B5=D1=82=20=D1=81=D1=82=D0=B0=D1=82=D0=B8?= =?UTF-8?q?=D1=87=D0=BD=D1=8B=D0=B9=20JSON=20+=20rublox/avatars=20(=D1=84?= =?UTF-8?q?=D0=B8=D0=BA=D1=81=20=D0=B8=D1=81=D1=87=D0=B5=D0=B7=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=81=D0=BA=D0=B8=D0=BD=D0=B0?= =?UTF-8?q?=20=D0=BF=D1=80=D0=B8=20setSkin=20non-humanoid)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/engine/PlayerController.js | 56 +++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/src/engine/PlayerController.js b/src/engine/PlayerController.js index 68dcc31..a741629 100644 --- a/src/engine/PlayerController.js +++ b/src/engine/PlayerController.js @@ -653,45 +653,51 @@ export class PlayerController { */ async _loadSkinManifest() { if (this._skinManifest) return this._skinManifest; - // 2026-05-27: сначала пробуем БД (rublox_avatars), там и легаси и - // дизайнерские аватары после approve. Только при сетевой ошибке — - // fallback на статичный manifest.json. + // ВАЖНО: объединяем ОБА источника, а не «или-или». + // Баг (2026-05-30): раньше при непустом /rublox/avatars возвращался + // ТОЛЬКО он, а статичный 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 { const resp = await fetch(_storysApiUrl('/rublox/avatars')); if (resp.ok) { const json = await resp.json(); const items = json.items || []; - // Нормализуем под формат старого manifest: - // {id, file (без /kubikon-assets/ префикса), overrides} - // — потому что _resolveModelSource дальше добавляет - // '/kubikon-assets/' + entry.file. - // Дизайнерский file_path может быть /api-storys/... — оставляем - // как есть и добавляем спец-флаг entry.absolute_file=true, - // _resolveModelSource учтёт. - this._skinManifest = items.map((a) => ({ + // Нормализуем: file уже полный путь (absolute_file=true), т.к. + // _resolveModelSource иначе добавляет '/kubikon-assets/' префикс. + const avatars = items.map((a) => ({ id: a.code, name: a.name, file: a.file_path, 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) { // 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 - try { - 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; + this._skinManifest = combined; + return combined; } /**