feat: 50 игр на Lua + импорт Roblox для всех + поддержка Lua в плеере #39

Merged
min merged 215 commits from feat/lua-50-games-bundle into main 2026-06-09 21:59:25 +00:00
2 changed files with 95 additions and 24 deletions
Showing only changes of commit 8abbde9d67 - Show all commits

View File

@ -1277,38 +1277,83 @@ end)`;
// ═══════════════════════════════════════════════════════════════
// ИГРА 15 — «Тир»
// ═══════════════════════════════════════════════════════════════
'shooting-range': {
g15_main: `-- === ИГРА «ТИР» (Lua) ===
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")
'shooting-range': (function() {
// Мишени имеют id 2, 4, 6, 8, 10, 12, 14, 16 (постамент → нечётный, мишень → чётный)
const TARGET_IDS = [2, 4, 6, 8, 10, 12, 14, 16];
const TOTAL = TARGET_IDS.length;
const overrides = {
g15_main: `-- === ИГРА «ТИР» — главный скрипт (Lua) ===
${SNIPPET_BROADCAST}
local Players = game:GetService("Players")
local player = Players.LocalPlayer
local score = 0
print("Стреляй ЛКМ по красным шарам!")
local TOTAL = ${TOTAL}
local won = false
local ev = getEvent("TargetHit")
ev.Event:Connect(function()
__rbxl_show_text("Кликай по красным мишеням!", 3)
-- Счётчик в правом верхнем углу
local screenGui = Instance.new("ScreenGui", player.PlayerGui)
local label = Instance.new("TextLabel", screenGui)
label.Size = UDim2.new(0, 220, 0, 50)
label.Position = UDim2.new(1, -240, 0, 20)
label.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
label.BackgroundTransparency = 0.4
label.TextColor3 = Color3.fromRGB(255, 100, 100)
label.TextScaled = true
label.Font = Enum.Font.SourceSansBold
label.Text = "Мишени: 0 / " .. TOTAL
local hitSound = Instance.new("Sound", workspace)
hitSound.SoundId = "hit"; hitSound.Volume = 0.7
local winSound = Instance.new("Sound", workspace)
winSound.SoundId = "win"; winSound.Volume = 1
local hitEvent = getEvent("TargetHit")
hitEvent.Event:Connect(function()
if won then return end
score = score + 1
print("Попал! Очки: " .. score)
end)
-- ЛКМ пускаем луч из камеры
UserInputService.InputBegan:Connect(function(input, gp)
if gp then return end
if input.UserInputType ~= Enum.UserInputType.MouseButton1 then return end
local player = Players.LocalPlayer
local mouse = player:GetMouse()
local target = mouse.Target
if target and target:GetAttribute("IsTarget") then
local hitEv = workspace:FindFirstChild("TargetHit") or ev
ev:Fire()
target:Destroy()
label.Text = "Мишени: " .. score .. " / " .. TOTAL
hitSound:Play()
if score >= TOTAL then
won = true
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
end)`,
g15_target: `-- === Скрипт мишени (Lua) ===
};
// Скрипт каждой мишени — ClickDetector + взрыв искр + сообщение
const targetScript = `-- === Скрипт мишени (Lua) ===
-- Клик ЛКМ по мишени = выстрел. ClickDetector ловит клик в 3D.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local part = script.Parent
part:SetAttribute("IsTarget", true)`,
},
local hit = false
local clickDet = Instance.new("ClickDetector", part)
clickDet.MaxActivationDistance = 50
clickDet.MouseClick:Connect(function()
if hit then return end
hit = true
-- Взрыв искр на месте мишени
local pos = part.Position
__rbxl_spawn_particles("explosion", pos.X, pos.Y, pos.Z, 0.5, 1)
-- Сообщаем главному скрипту
local ev = ReplicatedStorage:FindFirstChild("TargetHit")
if ev then ev:Fire() end
-- Мишень исчезает
part:Destroy()
end)`;
for (const tid of TARGET_IDS) {
overrides['g15_target_' + tid] = targetScript;
}
return overrides;
})(),
// ═══════════════════════════════════════════════════════════════
// ИГРА 16 — «Лавовый пол»

View File

@ -436,6 +436,11 @@ function newInstance(className, name) {
value.Children.push(proxyRef);
try { value.ChildAdded && value.ChildAdded.Fire(proxyRef); } catch (_) {}
}
// Спец-регистрация для ClickDetector — чтобы клик по Part
// мог сфейерить MouseClick через fireTargetEvent.
if (t.ClassName === 'ClickDetector' && value) {
try { value._clickDetector = proxyRef; } catch (_) {}
}
try { t.AncestryChanged && t.AncestryChanged.Fire(proxyRef, value); } catch (_) {}
return true;
}
@ -1387,6 +1392,15 @@ export function registerRobloxShim(lua, opts) {
inst.Scale = new RbxVector3(1, 1, 1);
inst.Offset = new RbxVector3(0, 0, 0);
inst.VertexColor = new RbxVector3(1, 1, 1);
} else if (className === 'ClickDetector') {
// ClickDetector — клик по 3D-объекту (нужен Тиру и т.п.).
// Регистрация в part._clickDetector происходит автоматически
// через Proxy.set когда юзер делает clickDet.Parent = part.
inst = newInstance('ClickDetector', 'ClickDetector');
inst.MouseClick = makeSignal();
inst.MouseHoverEnter = makeSignal();
inst.MouseHoverLeave = makeSignal();
inst.MaxActivationDistance = 32;
} else if (className === 'BindableEvent') {
inst = newInstance('BindableEvent', 'BindableEvent');
inst.Event = makeSignal();
@ -2164,6 +2178,18 @@ export function registerRobloxShim(lua, opts) {
part.Touched.Fire(hrp);
} else if (p.kind === 'untouch' || p.kind === 'untouched') {
part.TouchEnded.Fire(hrp);
} else if (p.kind === 'click') {
// ClickDetector создаётся лениво — стрельба по 3D-объектам.
// Юзер делает: part.ClickDetector = Instance.new('ClickDetector', part)
// + clickDet.MouseClick:Connect(fn). Здесь фейерим сигнал.
try {
const cd = part._clickDetector;
if (cd && cd.MouseClick) cd.MouseClick.Fire(localPlayer);
} catch (_) {}
// Также фейерим Part.Clicked если есть (наш расширенный API).
try {
if (part.Clicked) part.Clicked.Fire();
} catch (_) {}
}
},
fireGlobalEvent(p) {