From cce9d2e293fc2cc9133060ee5859955e800a3752 Mon Sep 17 00:00:00 2001
From: min
Date: Tue, 9 Jun 2026 22:46:21 +0300
Subject: [PATCH] =?UTF-8?q?docs(39)=20+=20feat(g40):=20=C2=AB=D0=92=D1=8B?=
=?UTF-8?q?=D0=B6=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BE=D1=82=20?=
=?UTF-8?q?=D0=B2=D0=BE=D0=BB=D0=BD=C2=BB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
g39 docs: CodeBoth main+spot_1.
g40 паритет:
- WAVES=3, count=wave+2 (3,4,5 врагов)
- Враги по кругу radius=10 (cos/sin), npc.follow('player')
- BindableEvent EnemyClicked(ref)
- __rbxl_npc_on_click(ref, fn) — каждый враг шлёт ref в общий event
- Главный проверяет dist<5, npc.remove + explosion + aliveInWave--
- 0 живых → task.delay 2 startWave (forward-decl)
- 3 волны → 'Победа!' + confetti
---
src/community/docsGamesBuildersLua.js | 83 ++++++++++++++++++++++++++-
src/community/docsLessons.jsx | 8 +--
2 files changed, 86 insertions(+), 5 deletions(-)
diff --git a/src/community/docsGamesBuildersLua.js b/src/community/docsGamesBuildersLua.js
index f775cc2..5c717cd 100644
--- a/src/community/docsGamesBuildersLua.js
+++ b/src/community/docsGamesBuildersLua.js
@@ -3604,7 +3604,88 @@ end)`;
})(),
// ═══════════════════════════════════════════════════════════════
- // ИГРЫ 40-50: явных Lua-версий пока нет.
+ // ИГРА 40 — «Выживание от волн»
+ // ═══════════════════════════════════════════════════════════════
+ 'wave-survival': {
+ g40_main: `-- === ИГРА «ВЫЖИВАНИЕ ОТ ВОЛН» — главный скрипт (Lua) ===
+${SNIPPET_BROADCAST}
+
+local WAVES = 3
+local wave = 0
+local won = false
+
+__rbxl_show_text("Отбей 3 волны врагов! Кликай по ним", 3)
+
+local hitSound = Instance.new("Sound", workspace)
+hitSound.SoundId = "hit"; hitSound.Volume = 0.6
+local winSound = Instance.new("Sound", workspace)
+winSound.SoundId = "win"; winSound.Volume = 1
+
+-- Текущие живые враги в волне: { ref → true }
+local aliveInWave = 0
+
+-- Каждый враг при клике шлёт EnemyClicked:Fire(ref).
+-- Главный скрипт проверяет dist<5 и наносит урон.
+local clickEvent = getEvent("EnemyClicked")
+local enemiesRefs = {} -- ref → true (живые)
+clickEvent.Event:Connect(function(localRef)
+ if won then return end
+ if not enemiesRefs[localRef] then return end
+ local px = __rbxl_player_x()
+ local pz = __rbxl_player_z()
+ local ex = __rbxl_npc_x(localRef)
+ local ez = __rbxl_npc_z(localRef)
+ 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 < 5 then
+ enemiesRefs[localRef] = nil
+ __rbxl_spawn_particles("explosion", ex, 2, ez, 0.4, 1)
+ __rbxl_npc_remove(localRef)
+ hitSound:Play()
+ aliveInWave = aliveInWave - 1
+ if aliveInWave <= 0 then
+ if wave >= WAVES then
+ won = true
+ __rbxl_show_text("Победа! Все волны отбиты!", 5)
+ winSound:Play()
+ __rbxl_spawn_particles("confetti", px, 3, pz, 3, 3)
+ else
+ task.delay(2, startWave)
+ end
+ end
+ end
+end)
+
+function startWave()
+ if won then return end
+ wave = wave + 1
+ __rbxl_show_text("Волна " .. wave .. " из " .. WAVES .. "!", 3)
+ hitSound:Play()
+ local count = wave + 2
+ aliveInWave = count
+ for i = 1, count do
+ local angle = (i - 1) / count * math.pi * 2
+ local ex = math.cos(angle) * 10
+ local ez = math.sin(angle) * 10
+ local ref = __rbxl_spawn_npc("character-b", ex, 1, ez, "Враг", 40, 2)
+ enemiesRefs[ref] = true
+ task.delay(0.3, function()
+ __rbxl_npc_follow(ref, "player")
+ end)
+ __rbxl_npc_on_click(ref, function()
+ local ev = game:GetService("ReplicatedStorage"):FindFirstChild("EnemyClicked")
+ if ev then ev:Fire(ref) end
+ end)
+ end
+end
+
+task.delay(2, startWave)`,
+ },
+
+ // ═══════════════════════════════════════════════════════════════
+ // ИГРЫ 41-50: явных Lua-версий пока нет.
// buildGameProject в docsGamesBuilders.js использует generateFallbackLua
// (главный скрипт → показ подсказки + слушает FinishReached →
// победа+конфетти; скрипт на финиш-примитиве → шлёт FinishReached;
diff --git a/src/community/docsLessons.jsx b/src/community/docsLessons.jsx
index 43c7134..8ce5884 100644
--- a/src/community/docsLessons.jsx
+++ b/src/community/docsLessons.jsx
@@ -5550,7 +5550,7 @@ game.self.onInteract(() => {
Шаг 2. Главный скрипт
- {`// === ИГРА «БАШНЯ — СТРОЙКА» — главный скрипт ===
+ {`// === ИГРА «БАШНЯ — СТРОЙКА» — главный скрипт ===
const STEPS = 8;
let placed = 0; // сколько блоков поставлено
@@ -5580,7 +5580,7 @@ game.onMessage('place', (d) => {
} else {
game.ui.showText('Блок ' + placed + ' из ' + STEPS, 1.5);
}
-});`}
+});`}
Разберём:
placed — сколько блоков уже стоит;
@@ -5599,7 +5599,7 @@ game.onMessage('place', (d) => {
у остальных поменяй число в place.
- {`// === Скрипт места под блок 1 ===
+ {`// === Скрипт места под блок 1 ===
let built = false;
game.self.onInteract(() => {
if (built) return;
@@ -5610,7 +5610,7 @@ game.self.onInteract(() => {
game.scene.setCollide(game.self.ref, true);
built = true;
game.broadcast('place', { n: 1 });
-}, { text: 'Поставить блок', distance: 4 });`}
+}, { text: 'Поставить блок', distance: 4 });`}
Что происходит при нажатии:
passThrough(ref, false) — сквозь блок