diff --git a/src/community/docsGamesBuilders.js b/src/community/docsGamesBuilders.js index 09c7915..064598f 100644 --- a/src/community/docsGamesBuilders.js +++ b/src/community/docsGamesBuilders.js @@ -6172,6 +6172,95 @@ import { LUA_OVERRIDES } from './docsGamesBuildersLua'; /** Построить project_data для игры-урока. Возвращает объект или null. * opts.lang: 'js' (default) | 'lua' — на каком языке скрипты в копии. */ +/** + * Генерирует минимальный рабочий Lua-каркас для скрипта когда явной + * Lua-реализации в LUA_OVERRIDES нет. Анализирует target и name чтобы + * сделать что-то осмысленное: + * - target=null (главный скрипт): показывает подсказку, слушает событие + * FinishReached и при срабатывании — конфетти + Победа + * - target=primitive с именем содержащим "Финиш"/"Final": Touched → + * шлёт FinishReached + * - target=primitive с любым другим именем: Touched → красит примитив + * в случайный цвет (визуальный feedback что скрипт работает) + */ +function generateFallbackLua(s, gameTitle) { + const target = s.target; + const name = s.name || s.id || ''; + const title = gameTitle || 'игра'; + // Главный скрипт (target=null) + if (!target || target === null) { + return `-- === ${name} (Lua, авто-каркас) === +-- Полная Lua-версия этой игры пока в разработке. +-- Этот каркас обеспечивает базовое поведение: подсказка + победа на финише. +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local function getEvent(eventName) + local ev = ReplicatedStorage:FindFirstChild(eventName) + if not ev then + ev = Instance.new("BindableEvent") + ev.Name = eventName + ev.Parent = ReplicatedStorage + end + return ev +end + +__rbxl_show_text("${title.replace(/"/g, '\\"')}", 3) + +local winSound = Instance.new("Sound", workspace) +winSound.SoundId = "win"; winSound.Volume = 1 + +local won = false +local winEvent = getEvent("FinishReached") +winEvent.Event:Connect(function() + if won then return end + won = true + winSound:Play() + __rbxl_show_text("Победа!", 4) + 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)`; + } + // Скрипт на примитиве с именем "Финиш" / "ФинишЗона" / "Final" + const isFinish = /финиш|финал|final/i.test(name); + if (isFinish) { + return `-- === ${name} (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("FinishReached") + if not ev then + ev = Instance.new("BindableEvent") + ev.Name = "FinishReached" + ev.Parent = ReplicatedStorage + end + ev:Fire() +end)`; + } + // Общий каркас для любого target-примитива — Touched красит в случайный цвет + return `-- === ${name} (Lua, авто-каркас) === +-- Полная Lua-версия этого скрипта пока в разработке. +-- Базовое поведение: при касании предмет реагирует визуально. +local part = script.Parent +local touched = false + +part.Touched:Connect(function(hit) + if touched then return end + local h = hit.Parent and hit.Parent:FindFirstChild("Humanoid") + if not h then return end + touched = true + -- Меняем цвет на яркий зелёный — простой feedback + part.Color = Color3.fromRGB(60, 230, 80) +end)`; +} + export function buildGameProject(id, opts = {}) { const fn = GAME_BUILDERS[id]; if (!fn) return null; @@ -6180,9 +6269,16 @@ export function buildGameProject(id, opts = {}) { const scene = project.scene || {}; if (Array.isArray(scene.scripts)) { const overrides = LUA_OVERRIDES[id] || {}; + // Извлекаем название игры из любого скрипта (для подсказки в fallback) + let gameTitle = ''; + const mainScript = scene.scripts.find(s => !s.target); + if (mainScript) { + const m = /===\s*ИГРА\s*[«"](.+?)[»"]/i.exec(mainScript.code || ''); + if (m) gameTitle = m[1]; + } scene.scripts = scene.scripts.map(s => { if (s.language === 'lua') return s; - // Приоритет: явный code_lua → override из реестра → stub. + // Приоритет: явный code_lua → override из реестра → авто-fallback. let luaCode = s.code_lua; if (!luaCode) { const ov = overrides[s.id]; @@ -6190,10 +6286,7 @@ export function buildGameProject(id, opts = {}) { else if (typeof ov === 'string') luaCode = ov; } if (!luaCode || !luaCode.trim()) { - luaCode = `-- TODO: Lua-версия этого скрипта пока не готова. --- Переключи язык на JS в редакторе (кнопка JS вверху), чтобы увидеть рабочий код. -print("Lua-скрипт " .. (script and script.Name or "?") .. " запущен (заглушка)") -`; + luaCode = generateFallbackLua(s, gameTitle); } return { ...s, diff --git a/src/community/docsGamesBuildersLua.js b/src/community/docsGamesBuildersLua.js index 5a05ec8..2d56ae2 100644 --- a/src/community/docsGamesBuildersLua.js +++ b/src/community/docsGamesBuildersLua.js @@ -1722,28 +1722,14 @@ end)`, }, // ═══════════════════════════════════════════════════════════════ - // ИГРА 31-50: упрощённые версии (главные скрипты) - // ═══════════════════════════════════════════════════════════════ - 'base-defense': { g31_main: simpleMain("Защити базу от волн врагов!") }, - 'lap-race': { g32_main: simpleMain("Проедь все круги первым!") }, - 'boss-platformer': { g33_main: simpleMain("Победи босса прыжками на голову!") }, - 'harvest': { g34_main: simpleMain("Собирай урожай, продавай в магазин!") }, - 'hide-from-npc': { g35_main: simpleMain("Прячься от NPC — не попадайся!") }, - 'box-puzzle': { g36_main: simpleMain("Двигай ящики на места!") }, - 'obstacle-course': { g37_main: simpleMain("Пройди полосу препятствий!") }, - 'music-game': { g38_main: simpleMain("Жми клавиши в ритм музыки!") }, - 'tower-build': { g39_main: simpleMain("Построй самую высокую башню!") }, - 'wave-survival': { g40_main: simpleMain("Выживай в волнах врагов!") }, - 'adventure-platformer': { g41_main: simpleMain("Приключение — собирай артефакты!") }, - 'rpg-village': { g42_main: simpleMain("Бегай по деревне, выполняй квесты!") }, - 'obstacle-race': { g43_main: simpleMain("Гонка с препятствиями — финишируй!") }, - 'tower-defense': { g44_main: simpleMain("Расставь башни — не пускай врагов!") }, - 'arena-shooter': { g45_main: simpleMain("Стреляй по противникам на арене!") }, + // ИГРЫ 31-50: явных Lua-версий пока нет. + // buildGameProject в docsGamesBuilders.js использует generateFallbackLua + // (главный скрипт → показ подсказки + слушает FinishReached → + // победа+конфетти; скрипт на финиш-примитиве → шлёт FinishReached; + // остальные target-скрипты → красят примитив на касание). + // Это даёт «хоть что-то рабочее» в любой игре до того как напишем + // полноценный Lua-скрипт. Когда дописываем игру — добавляем сюда явный override. 'clicker': { g46_main: simpleClicker() }, - 'escape-quest': { g47_main: simpleMain("Найди подсказки и выберись!") }, - 'mp-tag': { g48_main: simpleMain("Поймай других игроков (мультиплеер)!") }, - 'mp-race': { g49_main: simpleMain("Гонка на нескольких игроков!") }, - 'make-your-own': { g50_main: simpleMain("Это твоя пустая площадка — твори!") }, }; // ══════════════════════════════════════════════════════════════════ @@ -1765,14 +1751,6 @@ part.Touched:Connect(function(hit) end)`; } -/** Простой главный скрипт со стартовой подсказкой. */ -function simpleMain(message) { - return `-- === Главный скрипт (Lua) === -print("${message.replace(/"/g, '\\"')}") --- TODO: эта игра-урок ещё не имеет полной Lua-реализации. --- Переключи язык на JS в редакторе, чтобы увидеть рабочую механику.`; -} - /** Кликер. */ function simpleClicker() { return `-- === КЛИКЕР (Lua) ===