studio/src/editor/engine/GdSpikes.js
МИН 61fba4e174
Some checks failed
CI / Lint + Format (push) Failing after 32s
CI / Build (push) Failing after 37s
CI / Secret scan (push) Failing after 37s
CI / PR size check (push) Has been skipped
fix: починка билда + studio.rublox.pro инфра
Большой консолидирующий коммит после поднятия 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>
2026-05-28 05:01:13 +03:00

132 lines
5.4 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.

/**
* GdSpikes — заменяем cone-примитивы на выбранный шип из spikeFactories.
*
* Размеры подгоняем под габариты оригинального cone (PrimitiveTypes.js
* defaultScale 1.3×1.6×1.3). Низ шипа = верх пола (y=1).
*
* Hitbox/коллизия не трогается — она в скрипте уровня по SPIKES[] (по x).
*/
import { Vector3 } from '@babylonjs/core';
import { SPIKE_CATALOG } from '../../admin-preview/gdSpikes/spikeFactories';
// Выбор юзера через /admin-preview/gd-spikes. Эпоха → spike-id.
// Меняется когда юзер сохраняет новый выбор в gd_spike_choices.
const DEFAULT_SPIKE_BY_EPOCH = {
1: 'e1_v5', // Терновник
2: 'e2_v1', // Кристалл синий
3: 'e3_v4',
4: 'e4_v4',
5: 'e5_v4',
6: 'e6_v6',
7: 'e7_v8',
8: 'e8_v7',
9: 'e9_v7',
10: 'e10_v7',
};
export class GdSpikes {
constructor() {
this.scene = null;
this._scene3d = null;
this._spikeId = null;
this._spikeRoots = [];
this._spikeHandles = [];
this._hiddenOriginals = [];
this._onBeforeRender = null;
this._t = 0;
}
attach(scene, scene3d, epoch = 1, spikeId = null) {
if (!scene) return;
this.scene = scene;
this._scene3d = scene3d;
this._spikeId = spikeId || DEFAULT_SPIKE_BY_EPOCH[epoch] || 'e1_v5';
this._replaceCones();
this._setupSpin();
}
_replaceCones() {
const pm = this._scene3d?.primitiveManager;
if (!pm) return;
const factory = SPIKE_CATALOG.find(s => s.id === this._spikeId);
if (!factory) {
console.warn('[GdSpikes] фабрика не найдена:', this._spikeId);
return;
}
// Подгонка: шип должен быть как cone, но реально hitbox = радиус 0.7
// вокруг данной точки. Визуальный диаметр должен быть ~0.8м, высота ~1.4м.
const TARGET_H = 1.4;
const TARGET_W = 0.8;
// Наши фабрики строят шип высотой ~1.4, диаметром ~0.9
const FACTORY_H = 1.4;
const FACTORY_W = 0.9;
const scaleY = TARGET_H / FACTORY_H;
const scaleXZ = TARGET_W / FACTORY_W;
const FLOOR_TOP = 1.0;
let n = 0;
for (const data of pm.instances.values()) {
if (String(data.type) !== 'cone') continue;
if (typeof data.x !== 'number') continue;
const handle = factory.make(this.scene, `gd_spike_inst_${data.id}`);
if (!handle || !handle.root) continue;
const root = handle.root;
// Если шип на полу (y близко к 0.8 = центр стандартного cone у пола) —
// ставим на FLOOR_TOP=1. Иначе сохраняем оригинальную y (потолочные,
// на платформах, в воздухе).
const origY = typeof data.y === 'number' ? data.y : FLOOR_TOP;
const isFloorSpike = Math.abs(origY - 0.8) < 0.3;
const targetY = isFloorSpike ? FLOOR_TOP : origY;
root.position = new Vector3(data.x, targetY, data.z || 0);
root.scaling = new Vector3(scaleXZ, scaleY, scaleXZ);
// Если оригинал был перевёрнут (rotationX≈π) — переворачиваем и копию.
const origRotX = data.rotationX || 0;
if (Math.abs(origRotX - Math.PI) < 0.1) {
root.rotation.x = Math.PI;
}
if (data.mesh) {
data.mesh.setEnabled(false);
this._hiddenOriginals.push(data.mesh);
}
this._spikeRoots.push(root);
this._spikeHandles.push(handle);
n++;
}
console.log(`[GdSpikes] заменено ${n} конусов на '${this._spikeId}', scale=(${scaleXZ.toFixed(2)}, ${scaleY.toFixed(2)})`);
}
_setupSpin() {
this._onBeforeRender = () => {
this._t += 0.01;
for (let i = 0; i < this._spikeRoots.length; i++) {
const root = this._spikeRoots[i];
if (!root) continue;
// Лёгкое смещение фазы между шипами — чтобы не все одновременно
root.rotation.y = this._t * 1.3 + i * 0.5;
}
};
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) {}
for (const m of this._hiddenOriginals) {
try { m.setEnabled(true); } catch (e) {}
}
// dispose handles — фабрика умеет освобождать всё, что создала
for (const h of this._spikeHandles) {
try { h.dispose && h.dispose(); } catch (e) {}
}
this._spikeHandles = [];
this._spikeRoots = [];
this._hiddenOriginals = [];
this._scene3d = null;
this.scene = null;
}
}