feat: 50 игр на Lua + импорт Roblox для всех + поддержка Lua в плеере #39
@ -1784,28 +1784,76 @@ end`,
|
||||
// ИГРА 21 — «Догонялки»
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
'chaser': {
|
||||
g21_main: `-- === ИГРА «ДОГОНЯЛКИ» (Lua) ===
|
||||
g21_main: `-- === ИГРА «ПРЕСЛЕДОВАТЕЛЬ» — главный скрипт (Lua) ===
|
||||
${SNIPPET_BROADCAST}
|
||||
|
||||
local Players = game:GetService("Players")
|
||||
local RunService = game:GetService("RunService")
|
||||
local player = Players.LocalPlayer
|
||||
local won = false
|
||||
|
||||
local enemy = workspace:WaitForChild("Догонщик")
|
||||
__rbxl_show_text("Убегай от врага! Добеги до укрытия!", 3)
|
||||
|
||||
RunService.Heartbeat:Connect(function(dt)
|
||||
local player = Players:GetPlayers()[1]
|
||||
if not player or not player.Character then return end
|
||||
local target = player.Character:FindFirstChild("HumanoidRootPart")
|
||||
if not target then return end
|
||||
-- Двигаемся в сторону игрока со скоростью 5
|
||||
local dir = (target.Position - enemy.Position)
|
||||
if dir.Magnitude > 1 then
|
||||
enemy.Position = enemy.Position + dir.Unit * 5 * dt
|
||||
else
|
||||
-- Поймал
|
||||
local h = player.Character:FindFirstChild("Humanoid")
|
||||
if h then h:TakeDamage(10) end
|
||||
local loseSound = Instance.new("Sound", workspace)
|
||||
loseSound.SoundId = "lose"; loseSound.Volume = 0.7
|
||||
local winSound = Instance.new("Sound", workspace)
|
||||
winSound.SoundId = "win"; winSound.Volume = 1
|
||||
|
||||
-- Спавним NPC-преследователя (speed=4, follow за игроком)
|
||||
local enemyRef = __rbxl_spawn_npc("character-b", 0, 1, -3, "Охотник", 100, 4)
|
||||
-- Велим NPC следовать за игроком
|
||||
__rbxl_npc_follow(enemyRef, "player")
|
||||
|
||||
-- Каждый кадр проверяем — не догнал ли враг
|
||||
local lastCaughtTime = 0
|
||||
RunService.Heartbeat:Connect(function()
|
||||
if won then return end
|
||||
local px = __rbxl_player_x()
|
||||
local pz = __rbxl_player_z()
|
||||
local ex = __rbxl_npc_x(enemyRef)
|
||||
local ez = __rbxl_npc_z(enemyRef)
|
||||
-- Если позиции NPC ещё не пришли (ex=0,ez=0 = до спавна) — пропускаем
|
||||
if ex == 0 and ez == 0 then return end
|
||||
local dx = px - ex
|
||||
local dz = pz - ez
|
||||
local dist = math.sqrt(dx*dx + dz*dz)
|
||||
if dist < 1.6 then
|
||||
local now = tick()
|
||||
if now - lastCaughtTime > 2 then
|
||||
lastCaughtTime = now
|
||||
player:LoadCharacter()
|
||||
__rbxl_show_text("Пойман! Беги снова!", 2)
|
||||
loseSound:Play()
|
||||
end
|
||||
end
|
||||
end)
|
||||
print("Убегай от догонщика!")`,
|
||||
|
||||
-- Финиш сообщает о победе
|
||||
local winEvent = getEvent("WinReached")
|
||||
winEvent.Event:Connect(function()
|
||||
if won then return end
|
||||
won = true
|
||||
__rbxl_npc_stop(enemyRef)
|
||||
winSound:Play()
|
||||
__rbxl_show_text("Победа! Ты убежал от врага!", 5)
|
||||
local px = __rbxl_player_x()
|
||||
local py = __rbxl_player_y()
|
||||
local pz = __rbxl_player_z()
|
||||
__rbxl_spawn_particles("confetti", px, py + 3, pz, 3, 3)
|
||||
end)`,
|
||||
g21_finish: `-- === Скрипт финиша (Lua) ===
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local part = script.Parent
|
||||
local fired = false
|
||||
|
||||
part.Touched:Connect(function(hit)
|
||||
if fired then return end
|
||||
local h = hit.Parent and hit.Parent:FindFirstChild("Humanoid")
|
||||
if not h then return end
|
||||
fired = true
|
||||
local ev = ReplicatedStorage:FindFirstChild("WinReached")
|
||||
if ev then ev:Fire() end
|
||||
end)`,
|
||||
},
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
@ -986,6 +986,20 @@ export class GameRuntime {
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
// Собираем позиции NPC для Lua-shim
|
||||
const npcPositions = [];
|
||||
try {
|
||||
const nm = this.scene3d?.npcManager;
|
||||
if (nm && nm.npcs && this._localToReal) {
|
||||
// localRef ('npc_lua_N') → реальный 'npc:<id>' → npc
|
||||
for (const [localRef, realRef] of this._localToReal.entries()) {
|
||||
if (typeof realRef !== 'string' || !realRef.startsWith('npc:')) continue;
|
||||
const npcId = Number(realRef.slice(4));
|
||||
const npc = nm.npcs.get(npcId);
|
||||
if (npc) npcPositions.push([localRef, npc.x, npc.y, npc.z]);
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
for (const sb of this.sandboxes) {
|
||||
// Обновляем реальную позицию игрока для Lua-shim
|
||||
if (realPos && sb.api?.updatePlayerPos) {
|
||||
@ -997,6 +1011,12 @@ export class GameRuntime {
|
||||
try { sb.api.updateSpawnedPos(id, x, y, z); } catch (_) {}
|
||||
}
|
||||
}
|
||||
// Синк позиций NPC
|
||||
if (npcPositions.length > 0 && sb.api?.updateNpcPos) {
|
||||
for (const [ref, x, y, z] of npcPositions) {
|
||||
try { sb.api.updateNpcPos(ref, x, y, z); } catch (_) {}
|
||||
}
|
||||
}
|
||||
// Для скриптов с target — добавляем актуальную позицию self
|
||||
const stateForSb = sb.target
|
||||
? { ...state, selfPosition: this._collectSelfPosition(sb.target) }
|
||||
|
||||
@ -1869,6 +1869,27 @@ export function registerRobloxShim(lua, opts) {
|
||||
duration: +duration || 3,
|
||||
});
|
||||
});
|
||||
global.set('__rbxl_npc_follow', (ref, targetRef) => {
|
||||
send('npc.follow', {
|
||||
ref: String(ref || ''),
|
||||
target: String(targetRef || 'player'),
|
||||
});
|
||||
});
|
||||
global.set('__rbxl_npc_stop', (ref) => {
|
||||
send('npc.stop', { ref: String(ref || '') });
|
||||
});
|
||||
// Позиция NPC — резолвится через GameRuntime по локальному ref.
|
||||
// GameRuntime в tick шлёт api.updateNpcPos(localRef, x, y, z).
|
||||
const _npcPositions = new Map(); // localRef → {x,y,z}
|
||||
global.set('__rbxl_npc_pos', (ref) => {
|
||||
const p = _npcPositions.get(String(ref || ''));
|
||||
if (!p) return { x: 0, y: 0, z: 0, ok: false };
|
||||
return { x: p.x, y: p.y, z: p.z, ok: true };
|
||||
});
|
||||
// Отдельные x/y/z — обходим wasmoon userdata-proxy.
|
||||
global.set('__rbxl_npc_x', (ref) => (_npcPositions.get(String(ref || ''))?.x ?? 0));
|
||||
global.set('__rbxl_npc_y', (ref) => (_npcPositions.get(String(ref || ''))?.y ?? 0));
|
||||
global.set('__rbxl_npc_z', (ref) => (_npcPositions.get(String(ref || ''))?.z ?? 0));
|
||||
global.set('__rbxl_npc_damage', (ref, amount) => {
|
||||
send('npc.damage', {
|
||||
ref: String(ref || ''),
|
||||
@ -2034,6 +2055,10 @@ export function registerRobloxShim(lua, opts) {
|
||||
setNpcLocalRef(localRef, realRef) {
|
||||
_localToRealNpc.set(String(localRef), String(realRef));
|
||||
},
|
||||
// GameRuntime каждый кадр обновляет позиции NPC для Lua-скриптов.
|
||||
updateNpcPos(localRef, x, y, z) {
|
||||
_npcPositions.set(String(localRef), { x: +x, y: +y, z: +z });
|
||||
},
|
||||
onSceneSnapshot(snap) {
|
||||
try {
|
||||
const prims = snap?.primitives || [];
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user