From 1080c18ae0051f9b02d8f6097864a0bb27317cd0 Mon Sep 17 00:00:00 2001 From: min Date: Mon, 8 Jun 2026 13:57:37 +0300 Subject: [PATCH] =?UTF-8?q?feat(rbxl):=20Tool/Backpack/Mouse=20flow=20?= =?UTF-8?q?=E2=80=94=20=D0=A8=D0=B0=D0=B3=201/3=20(Zapper)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Цель: запустить Roblox Tools (Zapper и подобные оружия) в плеере. Архитектура: 1. RobloxShim: localPlayer.Backpack, localPlayer:GetMouse(), allTools registry, equippedTool — внутренний учёт текущего Tool. 2. Instance.new('Tool') — теперь автоматически: - создаёт виртуальный Handle (Part) внутри - регистрирует Tool в allTools[] - шлёт 'toolRegistered' в GameRuntime 3. fireGlobalEvent обработка новых событий из плеера: - equipTool {index} → Tool.Equipped:Fire(playerMouse) - unequipTool → Tool.Unequipped:Fire() - toolActivated → Tool.Activated:Fire() - mouseButton1Down {hit} → mouse.Hit.Position + mouse.Button1Down:Fire() - keyDown {key} → mouse.KeyDown:Fire(key) 4. LuaSharedSandbox.addScript принимает toolName, в _startSingleScript подсовывает виртуальный Tool как script.Parent (через __rbxl_get_tool_by_name). 5. GameRuntime эвристика: скрипты с target=null и упоминанием script.Parent.Equipped/Activated → toolName='Tool', группируются в один Tool. 6. GameRuntime._registerRbxlTool: при получении toolRegistered кладёт item в InventoryUI.hotbar, слушает смену слота → equipTool. 7. Клики canvas → mouseButton1Down с raycast Hit.Position. Следующие шаги: - HUD: индикатор экипированного Tool в плеере (Шаг 2) - Leaderboard UI из leaderstats IntValue (Шаг 3) --- .../src/__pycache__/converter.cpython-314.pyc | Bin 0 -> 59456 bytes src/editor/engine/GameRuntime.js | 95 ++++++++++++++- src/editor/engine/lua/LuaSharedSandbox.js | 29 +++-- src/editor/engine/lua/RobloxShim.js | 109 +++++++++++++++++- 4 files changed, 223 insertions(+), 10 deletions(-) create mode 100644 rbxl-importer/src/__pycache__/converter.cpython-314.pyc diff --git a/rbxl-importer/src/__pycache__/converter.cpython-314.pyc b/rbxl-importer/src/__pycache__/converter.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..af16ebfa9b81825da1e43612d31bb842b8b90668 GIT binary patch literal 59456 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 { + const sl = invUI.active; + const item = invUI.hotbar[sl]; + const sb = this._luaUserSandbox; + if (!sb) return; + if (item && item.itemId.startsWith('rbxlTool_')) { + const idx = +item.itemId.slice('rbxlTool_'.length); + sb.sendGlobalEvent?.({ type: 'equipTool', index: idx }); + this._rbxlActiveSlot = sl; + } else if (this._rbxlActiveSlot >= 0) { + sb.sendGlobalEvent?.({ type: 'unequipTool' }); + this._rbxlActiveSlot = -1; + } + }); + // Клики мыши при экипированном Tool — Activated/mouseButton1Down + try { + const canvas = this.scene3d?.engine?.getRenderingCanvas?.(); + if (canvas) { + const sb = this._luaUserSandbox; + canvas.addEventListener('mousedown', (e) => { + if (e.button !== 0) return; + if (this._rbxlActiveSlot < 0) return; + // Hit-position: raycast от камеры в сцену + const hit = this._raycastFromCamera?.() || { x: 0, y: 5, z: 0 }; + sb?.sendGlobalEvent?.({ type: 'mouseButton1Down', hit }); + sb?.sendGlobalEvent?.({ type: 'toolActivated' }); + }); + canvas.addEventListener('mouseup', (e) => { + if (e.button !== 0) return; + if (this._rbxlActiveSlot < 0) return; + sb?.sendGlobalEvent?.({ type: 'mouseButton1Up' }); + }); + } + } catch (_) {} + } + } + + /** Простой raycast от камеры — для mouse.Hit. */ + _raycastFromCamera() { + try { + const cam = this.scene3d?.scene?.activeCamera; + if (!cam) return { x: 0, y: 5, z: 0 }; + const forward = cam.getForwardRay?.()?.direction; + const pos = cam.position; + if (!pos || !forward) return { x: 0, y: 5, z: 0 }; + const t = 50; + return { x: pos.x + forward.x * t, y: pos.y + forward.y * t, z: pos.z + forward.z * t }; + } catch (_) { + return { x: 0, y: 5, z: 0 }; + } + } + stop() { if (this.sandboxes.length > 0) { this._log('info', 'Остановка скриптов'); diff --git a/src/editor/engine/lua/LuaSharedSandbox.js b/src/editor/engine/lua/LuaSharedSandbox.js index f2943e5..ce0b15e 100644 --- a/src/editor/engine/lua/LuaSharedSandbox.js +++ b/src/editor/engine/lua/LuaSharedSandbox.js @@ -43,12 +43,13 @@ export class LuaSharedSandbox { get target() { return null; } tick(_dt, _state) { /* no-op: main-loop запускается изнутри (setInterval) */ } - addScript(id, code, target, name) { + addScript(id, code, target, name, extra) { const entry = { id: String(id || `lua_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`), code: String(code || ''), target: target == null ? null : target, name: name || null, + toolName: extra?.toolName || null, }; this._scriptsById.set(entry.id, entry); if (!this._isKickedOff) { @@ -152,12 +153,26 @@ export class LuaSharedSandbox { // Регистрируем coroutine в __rbxl_coroutines с id для возобновления. // Скрипт оборачиваем в coroutine. task.wait()→coroutine.yield(sec) возвращает // delay из resume → планируем следующий resume через scheduleResume. - // Fallback Parent = workspace для скриптов без target (или с невалидным - // target). Это спасает массу Roblox-скриптов которые делают - // script.Parent.Parent — если бы Parent был nil, упало бы сразу. - const parentExpr = primId != null - ? `(__rbxl_get_part_by_id(${Number(primId)}) or workspace)` - : 'workspace'; + // Fallback Parent: если скрипт связан с Tool (по имени Tool из metadata) — + // подсовываем виртуальный Tool как script.Parent. Иначе primitive по id, + // иначе workspace. + let parentExpr; + if (entry.toolName) { + // Tool создаётся в shim как Instance.new('Tool'). По имени достаём. + // Если не нашли — fallback на новый Tool того же имени. + const safeName = JSON.stringify(entry.toolName); + parentExpr = `(function() + local existing = __rbxl_get_tool_by_name(${safeName}) + if existing then return existing end + local t = Instance.new("Tool") + t.Name = ${safeName} + return t + end)()`; + } else if (primId != null) { + parentExpr = `(__rbxl_get_part_by_id(${Number(primId)}) or workspace)`; + } else { + parentExpr = 'workspace'; + } const wrapped = ` do local script = { diff --git a/src/editor/engine/lua/RobloxShim.js b/src/editor/engine/lua/RobloxShim.js index 63fd219..8429f36 100644 --- a/src/editor/engine/lua/RobloxShim.js +++ b/src/editor/engine/lua/RobloxShim.js @@ -733,9 +733,48 @@ export function registerRobloxShim(lua, opts) { localPlayer.Children.push(playerGui); localPlayer.PlayerGui = playerGui; localPlayer.DisplayName = 'Player'; + // Backpack — инвентарь, содержит Tools. В Roblox StarterPack автоматически + // клонируется в Backpack каждого спавнящегося игрока. + const backpack = newInstance('Backpack', 'Backpack'); + backpack.Parent = localPlayer; + localPlayer.Children.push(backpack); + localPlayer.Backpack = backpack; + // Глобальный Mouse — единственный экземпляр на игрока, привязан к окну + // браузера. Реальные Button1Down/Hit фейерятся в GameRuntime. + const playerMouse = (function makePlayerMouse() { + const m = newInstance('Mouse', 'Mouse'); + m.Button1Down = makeSignal(); + m.Button1Up = makeSignal(); + m.Button2Down = makeSignal(); + m.Button2Up = makeSignal(); + m.Move = makeSignal(); + m.KeyDown = makeSignal(); + m.KeyUp = makeSignal(); + m.WheelForward = makeSignal(); + m.WheelBackward = makeSignal(); + m.Idle = makeSignal(); + m.Icon = ''; + m.X = 0; m.Y = 0; + m.ViewSizeX = 1920; m.ViewSizeY = 1080; + m.Hit = { Position: new RbxVector3(0, 0, 0), p: new RbxVector3(0, 0, 0), + Lookvector: new RbxVector3(0, 0, -1) }; + m.Origin = { Position: new RbxVector3(0, 5, 0) }; + m.Target = undefined; + m.TargetFilter = undefined; + m.TargetSurface = 'Top'; + return m; + })(); + localPlayer.GetMouse = function () { return playerMouse; }; + localPlayer.playerMouse = playerMouse; players.Children.push(localPlayer); players.LocalPlayer = localPlayer; + // === Tool registry === + // Tracks все Tool-инстансы — для UI (hotbar) и equip-flow. + // GameRuntime читает API equipTool/unequipTool на main-loop. + const allTools = []; // [Tool, ...] в порядке создания (для hotbar 1-9) + let equippedTool = null; + const character = newInstance('Model', 'Player'); character.Parent = localPlayer; localPlayer.Children.push(character); @@ -1175,8 +1214,6 @@ export function registerRobloxShim(lua, opts) { inst = newGuiInstance(className); guiByLocalRef.set(inst.__guiLocalRef, inst); } else if (className === 'Tool' || className === 'HopperBin') { - // Tool — оружие в Roblox. У нас нет инвентаря, поэтому это - // просто контейнер с правильными событиями + Handle (заглушка). inst = newInstance(className, 'Tool'); inst.Equipped = makeSignal(); inst.Unequipped = makeSignal(); @@ -1191,6 +1228,21 @@ export function registerRobloxShim(lua, opts) { inst.RequiresHandle = true; inst.TextureId = ''; inst.ToolTip = ''; + // Виртуальный Handle — Roblox-скрипты делают Tool.Handle.Position + const handle = newInstance('Part', 'Handle'); + handle.Parent = inst; + handle.Position = new RbxVector3(0, 5, 0); + handle.Size = new RbxVector3(1, 1, 1); + inst.Handle = handle; + inst.Children = inst.Children || []; + inst.Children.push(handle); + // Регистрируем Tool, чтобы плеер показал его в hotbar + allTools.push(inst); + inst.__toolIndex = allTools.length; + send('toolRegistered', { + index: inst.__toolIndex, + name: inst.Name || `Tool ${inst.__toolIndex}`, + }); } else if (className === 'IntValue' || className === 'NumberValue' || className === 'BoolValue' || className === 'StringValue' || className === 'ObjectValue' || className === 'CFrameValue' @@ -1265,6 +1317,7 @@ export function registerRobloxShim(lua, opts) { // === Helpers для скриптов === const partById = new Map(); global.set('__rbxl_get_part_by_id', (id) => partById.get(Number(id)) || undefined); + global.set('__rbxl_get_tool_by_name', (name) => allTools.find(t => t.Name === name) || undefined); global.set('__rbxl_send_error', (id, errStr) => { send('log', { level: 'error', text: `[Lua ${id}] ${errStr || 'unknown error'}` }); }); @@ -1452,7 +1505,59 @@ export function registerRobloxShim(lua, opts) { guiEl.MouseButton1Click.Fire(); } } + // Tool equip/unequip — клавиши 1-9 в плеере шлют + // {type:'equipTool', index:N}, {type:'unequipTool'} + if (p.type === 'equipTool') { + const idx = Number(p.index) - 1; + if (idx < 0 || idx >= allTools.length) return; + const tool = allTools[idx]; + if (equippedTool === tool) return; + // Снимаем предыдущий + if (equippedTool) { + try { equippedTool.Unequipped.Fire(); } catch (_) {} + } + equippedTool = tool; + // В Roblox Tool при equip перемещается в Character + tool.Parent = character; + try { tool.Equipped.Fire(playerMouse); } catch (_) {} + } + if (p.type === 'unequipTool') { + if (!equippedTool) return; + try { equippedTool.Unequipped.Fire(); } catch (_) {} + equippedTool.Parent = backpack; + equippedTool = null; + } + if (p.type === 'toolActivated') { + if (!equippedTool) return; + try { equippedTool.Activated.Fire(); } catch (_) {} + } + if (p.type === 'toolDeactivated') { + if (!equippedTool) return; + try { equippedTool.Deactivated.Fire(); } catch (_) {} + } + // Mouse-события из плеера: клики, движение, клавиши при equipped Tool + if (p.type === 'mouseButton1Down') { + if (p.hit) { + playerMouse.Hit.Position = new RbxVector3(p.hit.x, p.hit.y, p.hit.z); + playerMouse.Hit.p = playerMouse.Hit.Position; + } + try { playerMouse.Button1Down.Fire(); } catch (_) {} + } + if (p.type === 'mouseButton1Up') { + try { playerMouse.Button1Up.Fire(); } catch (_) {} + } + if (p.type === 'keyDown') { + try { playerMouse.KeyDown.Fire(String(p.key || '').toLowerCase()); } catch (_) {} + } + if (p.type === 'keyUp') { + try { playerMouse.KeyUp.Fire(String(p.key || '').toLowerCase()); } catch (_) {} + } }, + // Tool registry (для GameRuntime: какой Tool сделать script.Parent) + getToolByName(name) { + return allTools.find(t => t.Name === name); + }, + getAllTools() { return allTools.slice(); }, // Доступ к ключевым объектам (для тестов и отладки) partById, localPlayer, humanoid, character, workspace, players, game, };