feat(g20): клик по NPC через pick (как Тир ClickDetector)

Раньше: проверка дистанции до врага при ЛКМ через InputBegan
+ MouseButton1. Никак не работало из-за десятка причин.

Теперь как в Тире:
- BabylonScene._meshToTarget теперь возвращает {kind:'npc', id:N}
  для меша с metadata.npcId.
- routeGlobalEvent('click', {target}) — этим уже шлёт в Lua-shim
  с target.
- Shim добавлен __rbxl_npc_on_click(ref, fn) — регистрация callback'а.
  В fireGlobalEvent при type='click'+target.kind='npc' резолвим
  локальный ref и фейерим cb.
- В скрипте игры 20 регистрируем callback на каждого врага.
  Клик ЛКМ по NPC (raycast попадает в мешa NPC) → callback → урон.
This commit is contained in:
min 2026-06-09 21:10:22 +03:00
parent 6aaab1a3f1
commit 9eebbd302e
3 changed files with 34 additions and 24 deletions

View File

@ -1763,30 +1763,21 @@ for i, e in ipairs(enemies) do
end)
end
-- Клик по сцене бьём ближайшего врага в радиусе 4
UserInputService.InputBegan:Connect(function(input, gp)
if gp then return end
if input.UserInputType ~= Enum.UserInputType.MouseButton1 then return end
if won then return end
local px = __rbxl_player_x()
local pz = __rbxl_player_z()
-- Клик по конкретному NPC (как в Тире pick по 3D-объекту).
-- BabylonScene выполняет raycast при ЛКМ и шлёт click с target=NPC.
-- Регистрируем callback для каждого врага по его локальному ref.
for _, e in ipairs(enemies) do
if not e._dead and e.hp > 0 then
local dx = e.x - px
local dz = e.z - pz
local dist = math.sqrt(dx*dx + dz*dz)
if dist < 4 then
e.hp = e.hp - 30
if e.hp < 0 then e.hp = 0 end
__rbxl_npc_damage(e.ref, 30)
__rbxl_set_label(e.ref, e.name .. " HP: " .. e.hp, "#ff5555", 3)
__rbxl_spawn_particles("sparks", e.x, 2, e.z, 0.4, 1)
local enemy = e -- замыкание
__rbxl_npc_on_click(enemy.ref, function()
if enemy._dead or won then return end
enemy.hp = enemy.hp - 30
if enemy.hp < 0 then enemy.hp = 0 end
__rbxl_npc_damage(enemy.ref, 30)
__rbxl_set_label(enemy.ref, enemy.name .. " HP: " .. enemy.hp, "#ff5555", 3)
__rbxl_spawn_particles("sparks", enemy.x, 2, enemy.z, 0.4, 1)
hitSound:Play()
break -- бьём только одного за клик
end
end
end
end)`,
end)
end`,
},
// ═══════════════════════════════════════════════════════════════

View File

@ -3035,6 +3035,7 @@ export class BabylonScene {
if (md.isBlock) {
return { kind: 'block', ref: { x: md.gridX, y: md.gridY, z: md.gridZ } };
}
if (md.npcId != null) return { kind: 'npc', id: md.npcId };
if (md.isModel) return { kind: 'model', id: md.instanceId };
if (md.isPrimitive) return { kind: 'primitive', id: md.primitiveId };
return null;

View File

@ -1895,6 +1895,10 @@ export function registerRobloxShim(lua, opts) {
global.set('__rbxl_npc_on_death', (ref, fn) => {
if (typeof fn === 'function') _npcDeathCbs.set(String(ref || ''), fn);
});
const _npcClickCbs = new Map(); // localRef → fn
global.set('__rbxl_npc_on_click', (ref, fn) => {
if (typeof fn === 'function') _npcClickCbs.set(String(ref || ''), fn);
});
// Инвентарь invUI — паритет с JS game.inventory.add(itemId, count).
// Сначала определяем итем (один раз), потом добавляем.
const _localInventory = new Map();
@ -2302,6 +2306,20 @@ export function registerRobloxShim(lua, opts) {
try { equippedTool.Deactivated.Fire(); } catch (_) {}
}
// Mouse-события из плеера: клики, движение, клавиши при equipped Tool
// BabylonScene шлёт глобальный 'click' при ЛКМ. Если в payload
// target — это попадание по 3D-объекту. Для NPC фейерим cb.
if (p.type === 'click' && p.target && p.target.kind === 'npc' && p.target.id != null) {
const realRef = 'npc:' + p.target.id;
let localRef = null;
for (const [k, v] of _localToRealNpc.entries()) {
if (v === realRef) { localRef = k; break; }
}
for (const [ref, fn] of _npcClickCbs.entries()) {
if (ref === realRef || ref === localRef) {
_pendingHandlerQueue.push({ fn, args: [] });
}
}
}
// BabylonScene шлёт глобальный 'click' при ЛКМ — это эквивалент
// mouseButton1Down. Мапим в наши handler-ы.
if (p.type === 'click' || p.type === 'mouseButton1Down') {