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)
138 lines
5.5 KiB
JavaScript
138 lines
5.5 KiB
JavaScript
/**
|
||
* 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;
|
||
}
|
||
}
|