studio/src/editor/engine/InventoryManager.js
МИН 31adbf151b Initial public release: Студия Рублокса v1.0
Open-source веб-студия для создания игр Рублокса, двойная лицензия
AGPL-3.0 + Коммерческая.

Главное:
- Vite 5 + React 18 + Babylon 7.54.3 + Monaco Editor + Colyseus 0.16
- Самодостаточный движок ~28к строк (66 файлов): BlockManager,
  TerrainVoxelBuilder, ModelManager, DecoManager, PlayerController,
  ScriptSandboxWorker, MultiplayerSync, 30+ GD-гейммодов
- Главный редактор KubikonEditor (~37к строк) + панели, ScriptEditor (Monaco)
- Витрина игр (KubikonFeed, KubikonStudio, KubikonDocs, KubikonLearn)
- Geometry Dash sub-app (GdMenu, GdShop, GdRules, GdCoverArt)
- 10 admin-preview каталогов для дизайнеров (скины, музыка, порталы и т.д.)
- Конфигурируемый бэкенд через VITE_API_BASE — работает со staging
  (dev-api.rublox.pro) без настройки
- Standalone-режим (VITE_STANDALONE=true) — открыть пустой редактор без бэка
- Полная документация (на русском): README, ARCHITECTURE, CONTRIBUTING,
  SECURITY, CHANGELOG
- ESLint + Prettier + EditorConfig
- Legal: LICENSE (AGPL-3.0), LICENSE-COMMERCIAL.md, CLA.md, COPYRIGHT.md
- Issue templates: bug_report, feature_request, security_disclosure

Перед публикацией:
- Все импорты из minecraftia заменены на локальные
- Все хардкоды URL (minecraftia-school.ru) и внутренних IP убраны → env
- Admin-эндпоинты Kubikon3DService вырезаны (остаются в приватном репо)
- AdminKubikonModeration не публикуется (модерация — в team.rublox.pro)
- 93 МБ ассетов public/kubikon-assets вынесены в .gitignore
  (раздаются через release artifact)
2026-05-27 23:41:10 +03:00

105 lines
3.1 KiB
JavaScript

/**
* InventoryManager — инвентарь игрока (9 слотов hot-bar).
*
* Каждый слот хранит {id, kind, modelTypeId, name, params} или null.
* kind:
* 'weapon' — оружие (бластер/меч/...). params: { damage, fireRate, range, ammo, ... }
* 'tool' — инструмент.
* 'item' — обычный предмет (зелье и т.п.).
*
* События:
* change → onChange callback (для GUI hot-bar в React).
*/
const SLOTS = 5;
let _seq = 1;
export class InventoryManager {
constructor() {
/** @type {Array<object|null>} */
this.slots = new Array(SLOTS).fill(null);
this.activeIndex = 0;
this._onChange = null;
}
setOnChange(cb) { this._onChange = cb; }
_notify() { if (this._onChange) try { this._onChange(); } catch (e) { /* ignore */ } }
/** Положить предмет в первый свободный слот. Возвращает индекс или -1. */
add(item) {
const idx = this.slots.findIndex(s => s == null);
if (idx < 0) return -1;
this.slots[idx] = this._normalize(item);
this._notify();
return idx;
}
/** Положить в конкретный слот (заменяет содержимое). */
setSlot(index, item) {
if (index < 0 || index >= SLOTS) return;
this.slots[index] = item ? this._normalize(item) : null;
this._notify();
}
removeSlot(index) {
if (index < 0 || index >= SLOTS) return;
this.slots[index] = null;
this._notify();
}
getActive() {
return this.slots[this.activeIndex] || null;
}
setActive(index) {
if (index < 0 || index >= SLOTS) return;
this.activeIndex = index;
this._notify();
}
serialize() {
return {
slots: this.slots.map(s => s ? { ...s } : null),
activeIndex: this.activeIndex,
};
}
loadFromArray(data) {
if (!data) {
this.slots = new Array(SLOTS).fill(null);
this.activeIndex = 0;
this._notify();
return;
}
if (Array.isArray(data.slots)) {
this.slots = new Array(SLOTS).fill(null);
for (let i = 0; i < Math.min(SLOTS, data.slots.length); i++) {
this.slots[i] = data.slots[i] ? this._normalize(data.slots[i]) : null;
}
}
if (Number.isFinite(data.activeIndex)) {
this.activeIndex = Math.max(0, Math.min(SLOTS - 1, data.activeIndex));
}
this._notify();
}
clear() {
this.slots = new Array(SLOTS).fill(null);
this.activeIndex = 0;
this._notify();
}
_normalize(item) {
return {
id: item.id || `inv_${Date.now()}_${(_seq++).toString(36)}`,
kind: item.kind || 'item',
modelTypeId: item.modelTypeId || null,
name: item.name || 'Предмет',
params: item.params ? { ...item.params } : {},
};
}
}
InventoryManager.SLOTS = SLOTS;