player/src/engine/GdPortalArch.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

134 lines
6.0 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.

/**
* GdPortalArch — рендер 3D-арок поверх портал-примитивов (gd_ship, gd_cube, …).
*
* Работа:
* 1. attach(scene, scene3d) пробегает по primitiveManager.instances.
* 2. Для каждого primitive с typeDef.kind === 'gd_portal':
* - Читает выбор юзера: gd_portal_choices[gdMode] (например 'ship_v3').
* - Если выбора нет — берёт дефолт `${gdMode}_v1`.
* - Спавнит арку из PORTAL_CATALOG в той же позиции.
* - Скрывает оригинальный mesh (visible=false), но НЕ удаляет — GdLevelManager
* читает позицию из primitiveManager и обрабатывает коллизию.
*
* Загрузка выбора:
* GET /api/v1/storys/kubikon3d/savegame/295/<userId>/gd_portal_choices
* data = { cube: 'cube_v5', ship: 'ship_v5', ... }
*
* Дефолты (если нет выбора):
* все группы = `${gdMode}_v1` (классический).
*/
import { Vector3, TransformNode } from '@babylonjs/core';
import { PORTAL_CATALOG } from '../AdminPreview/gdPortals/portalFactories';
import { STORYS_addres as STORYS_BASE } from '../api/API';
// project_id выбора порталов = 295 (sandbox-проект, где юзер выбирал)
const PORTAL_CHOICES_PID = 295;
const PORTAL_CHOICES_NS = 'gd_portal_choices';
const DEFAULT_PORTAL_BY_MODE = {
cube: 'cube_v1',
ship: 'ship_v1',
ball: 'ball_v1',
ufo: 'ufo_v1',
wave: 'wave_v1',
robot: 'robot_v1',
};
export class GdPortalArch {
constructor() {
this.scene = null;
this._scene3d = null;
this._instances = []; // [{ portalPrimId, handle }]
this._choices = null;
}
/**
* Привязать к сцене.
* userId — для загрузки выбора порталов из savegame (project_id=295, ns=gd_portal_choices).
* Если userId не передан или загрузка не удалась — используются дефолты `${mode}_v1`.
*/
async attach(scene, scene3d, userId = null) {
if (!scene || !scene3d) return;
this.scene = scene;
this._scene3d = scene3d;
this._choices = {};
if (userId) {
try {
const url = `${STORYS_BASE}/kubikon3d/savegame/${PORTAL_CHOICES_PID}/${userId}/${PORTAL_CHOICES_NS}`;
const r = await fetch(url, {
headers: { Authorization: localStorage.getItem('Authorization') || '' },
});
if (r.ok) {
const j = await r.json();
this._choices = j?.data || {};
}
} catch (e) {
console.warn('[GdPortalArch] не загрузил выбор:', e);
}
}
// Откладываем — примитивы могут быть не созданы в момент enterPlayMode
let attempts = 0;
const tryRender = () => {
attempts++;
const pm = scene3d.primitiveManager;
if (!pm || !pm.instances) {
if (attempts < 10) setTimeout(tryRender, 200);
return;
}
this._renderAll();
};
tryRender();
}
_renderAll() {
const pm = this._scene3d.primitiveManager;
if (!pm || !pm.instances) return;
let rendered = 0;
for (const data of pm.instances.values()) {
// type у primitive — строка вроде 'gd_ship', 'gd_cube'.
// У этих типов в PrimitiveTypes.js kind = 'gd_portal' и есть gdMode.
// Импортировать getPrimitiveType из engine — там есть.
// Но проще проверить по type-строке:
const portalType = data.type || '';
const m = portalType.match(/^gd_(\w+)$/);
if (!m) continue;
const gdMode = m[1]; // 'cube', 'ship', …
if (!DEFAULT_PORTAL_BY_MODE[gdMode]) continue; // не порталовый
const chosenId = (this._choices && this._choices[gdMode]) || DEFAULT_PORTAL_BY_MODE[gdMode];
const factory = PORTAL_CATALOG.find(p => p.id === chosenId);
if (!factory) {
console.warn('[GdPortalArch] не найдена фабрика:', chosenId);
continue;
}
try {
const handle = factory.make(this.scene, `portal_inst_${data.id}`);
if (!handle || !handle.root) continue;
// Низ колонн на y=0; ставим на y=1 (поверх пола), как GdStartArch
handle.root.position = new Vector3(data.x, 1, data.z);
// Поворот по Y 90° — фабрики строят арку проёмом вдоль X (колонны на ±X).
// Игрок бежит по +X — арка должна стоять поперёк, колонны на ±Z, проём вдоль X.
handle.root.rotation.y = Math.PI / 2;
// Скрыть оригинальный primitive-меш (GdLevelManager всё ещё видит data.x для коллизии)
if (data.mesh) {
data.mesh.setEnabled(false);
}
this._instances.push({ portalPrimId: data.id, handle });
rendered++;
} catch (e) {
console.warn('[GdPortalArch] ошибка при создании арки:', chosenId, e);
}
}
console.log(`[GdPortalArch] отрисовано ${rendered} порталов`);
}
dispose() {
for (const inst of this._instances) {
try { inst.handle && inst.handle.dispose && inst.handle.dispose(); } catch (e) {}
}
this._instances = [];
this.scene = null;
this._scene3d = null;
}
}