/** * GdPlayerModeSkin — подмена текстуры кубика игрока на "обложку режима" в ship/ball/ufo/... * * Концепция (как в GD): куб остаётся кубом, но его текстура подменяется на 2D-обложку * корабля/НЛО/шара когда игрок в соответствующем гейммоде. * * Сейчас реализован SHIP-режим. Остальные (ufo/ball/wave/robot) — в будущем * через те же фабрики (ufoSkinFactories, ballSkinFactories, …). * * Работа: * 1. attach(scene, scene3d, userId) — подгружает выбор из gd_ship_skin namespace. * 2. Рисует ship-текстуру на OffscreenCanvas → DynamicTexture. * 3. onBeforeRender следит за scene3d.player._shipMode: * - true → сохраняет mesh.material.diffuseTexture, ставит свою. * - false → восстанавливает оригинальную. * * При смене режима куб мгновенно "одевается" в корабль и обратно. */ import { DynamicTexture } from '@babylonjs/core'; import { SHIP_SKIN_CATALOG } from '../AdminPreview/gdShipSkins/shipSkinFactories'; import { STORYS_addres as STORYS_BASE } from '../api/API'; const PLAYER_PRIM_ID = 10001; const SHIP_SKIN_PID = 295; const SHIP_SKIN_NS = 'gd_ship_skin'; const DEFAULT_SHIP_SKIN = 'ss_v1'; const TEX_SIZE = 256; export class GdPlayerModeSkin { constructor() { this.scene = null; this._scene3d = null; this._cubeMesh = null; this._origTexture = null; this._shipTexture = null; this._onBeforeRender = null; this._wasShip = false; } async attach(scene, scene3d, userId = null) { if (!scene || !scene3d) return; this.scene = scene; this._scene3d = scene3d; // Подгружаем выбор let skinId = DEFAULT_SHIP_SKIN; if (userId) { try { const url = `${STORYS_BASE}/kubikon3d/savegame/${SHIP_SKIN_PID}/${userId}/${SHIP_SKIN_NS}`; const r = await fetch(url, { headers: { Authorization: localStorage.getItem('Authorization') || '' }, }); if (r.ok) { const j = await r.json(); if (j?.data?.equipped) skinId = j.data.equipped; } } catch (e) {} } const skin = SHIP_SKIN_CATALOG.find(s => s.id === skinId) || SHIP_SKIN_CATALOG[0]; // Рисуем DynamicTexture с обложкой корабля try { this._shipTexture = new DynamicTexture(`ship_skin_${skinId}`, { width: TEX_SIZE, height: TEX_SIZE }, scene, true); const ctx = this._shipTexture.getContext(); ctx.imageSmoothingEnabled = false; skin.draw(ctx, '#66ff66', '#44aa44', 0); this._shipTexture.hasAlpha = false; this._shipTexture.update(); } catch (e) { console.warn('[GdPlayerModeSkin] не удалось создать текстуру:', e); return; } // Ждём REF_BODY let attempts = 0; const tryAttach = () => { attempts++; const pm = scene3d.primitiveManager; const data = pm?.instances?.get(PLAYER_PRIM_ID); const mesh = data?.mesh; if (!mesh) { if (attempts < 10) setTimeout(tryAttach, 200); return; } this._cubeMesh = mesh; this._setupWatch(); console.log('[GdPlayerModeSkin] прикреплён, ship-skin=', skinId); }; tryAttach(); } _setupWatch() { const player = this._scene3d?.player; if (!player || !this._cubeMesh) return; this._onBeforeRender = () => { const isShip = !!player._shipMode; if (isShip !== this._wasShip) { const mat = this._cubeMesh.material; if (!mat) { this._wasShip = isShip; return; } if (isShip) { // Сохраняем оригинальную и накладываем ship this._origTexture = mat.diffuseTexture; mat.diffuseTexture = this._shipTexture; } else { // Восстанавливаем оригинальную if (this._origTexture !== null) { mat.diffuseTexture = this._origTexture; } } this._wasShip = isShip; } }; this.scene.onBeforeRenderObservable.add(this._onBeforeRender); } dispose() { if (!this.scene) return; try { if (this._onBeforeRender) { this.scene.onBeforeRenderObservable.removeCallback(this._onBeforeRender); this._onBeforeRender = null; } } catch (e) {} try { // Восстановим оригинальную, если что if (this._cubeMesh?.material && this._origTexture !== null && this._wasShip) { this._cubeMesh.material.diffuseTexture = this._origTexture; } } catch (e) {} try { this._shipTexture && this._shipTexture.dispose(); } catch (e) {} this._shipTexture = null; this._cubeMesh = null; this._scene3d = null; this.scene = null; } }