player/src/engine/GdPlayerModeSkin.js
МИН 87444ee2c8 Initial public release: Rublox Player v1.0
Open-source web player for Rublox games, dual-licensed under
AGPL-3.0 + Commercial.

Highlights:
- Babylon.js 7 + React 18 + Vite 5 stack
- Self-contained engine (~46k lines): BlockManager, ModelManager,
  PlayerController, ScriptSandboxWorker, MultiplayerSync, 30+ GD
  gamemodes
- Configurable backend via VITE_API_BASE and friends — works against
  staging (dev-api.rublox.pro) out of the box
- Standalone mode (VITE_STANDALONE=true) loads a bundled sample game
  for first-run without any backend
- Full docs: README, ARCHITECTURE, CONTRIBUTING, SECURITY, CHANGELOG
- Lint + format scaffolding (ESLint + Prettier + EditorConfig)
- Legal: LICENSE (AGPL-3.0), LICENSE-COMMERCIAL.md, CLA.md, COPYRIGHT.md
- Issue templates: bug_report, feature_request, security_disclosure

Removed before public release:
- frontend_deploy.py (contained production SSH credentials)
- ~27 admin endpoints (kept in private repo)
- Hard-coded internal URLs and IPs
- All previous git history (clean repo init)
2026-05-27 23:04:04 +03:00

138 lines
5.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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;
}
}