Большой консолидирующий коммит после поднятия studio.rublox.pro (28 мая 2026). Содержит изменения которые делались в процессе подготовки прод-окружения: Фиксы импортов после выноса из minecraftia: - Массовая замена путей ../../components → ../components (40+ файлов в src/community/, src/admin-preview/) - Замена ../KubikonEditor/ → ../editor/, ../KubikonStudio/ → ../community/, ../AdminPreview/ → ../admin-preview/ - API.js скопирован из минки целиком (было 8 экспортов, стало 312) - Добавлены PLAYER_URL, MyButton_1, недостающие компоненты - Заменены require() на статические ES-imports в BabylonScene, PrimitiveManager, GameRuntime (Vite не поддерживает CJS require) Структура ассетов: - public/kubikon-templates/ → public/assets/kubikon-templates/ - public/kubikon-learn/ → public/assets/kubikon-learn/ - (код искал в /assets/, файлы лежали без /assets/) Навигация роутов внутри студии: - /kubikon-studio/docs → /docs (90+ навигационных вызовов sed-replaced) - /kubikon-editor/X → /edit/X, /kubikon/play/X → /play/X, /kubikon/gd/X → /gd/X UI: - Новый компонент StudioHeader (61px, как в минке) + копия favicon - WithHeader wrapper в App.jsx для всех страниц кроме fullscreen-редактора/плеера - SSO ticket-flow в AuthContext (auto-redeem #ticket= при загрузке) - Тёмная тема карточек игр в ВИКИ (фон #1c2231 вместо #fff, картинка впритык) Документация: - docs/ONBOARDING.md — путь нового контрибьютора от нуля до PR - docs/TUTORIAL_ADD_SCRIPT_API.md — как добавить game.* API - API_USAGE.md — список эндпоинтов backend - README в подпапках engine/, engine/terrain/, engine/voxel/, engine/robloxterrain/, engine/types/ .gitignore: - public/wiki/ исключён (73МБ PNG, будут на CDN отдельной задачей) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
134 lines
6.0 KiB
JavaScript
134 lines
6.0 KiB
JavaScript
/**
|
||
* 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 '../../admin-preview/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;
|
||
}
|
||
}
|