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

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 '../AdminPreview/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;
}
}