feat: умный авто-fallback Lua для всех игр без явной реализации

Раньше для игр 15-50 при открытии 'Открыть мою копию на Lua' юзер
получал TODO-заглушки которые ничего не делали (или simpleMain
который только print). Каждая новая игра без явного override
была полностью неиграбельной.

Новый generateFallbackLua(s, gameTitle) в buildGameProject:

Главный скрипт (target=null):
  - __rbxl_show_text(gameTitle) подсказка
  - Слушает BindableEvent FinishReached → win Sound + Победа! + confetti

Скрипт-финиш (target=primitive с именем 'Финиш'/'ФинишЗона'/'Final'):
  - Touched → создаёт/находит BindableEvent FinishReached → Fire
  - fired-флаг чтобы 1 раз

Прочие target-скрипты:
  - Touched → красит примитив зелёным (визуальный feedback)
  - touched-флаг

Удалил статичные simpleMain stub-ы для игр 31-50 — теперь они
используют умный fallback. Когда дописываем полную Lua-версию
игры — добавляем явный override в LUA_OVERRIDES, fallback
автоматически перестаёт использоваться.

Это даёт минимум: победа на финише + цвет на касании во всех
35 не-готовых играх (15-50).
This commit is contained in:
min 2026-06-09 19:43:20 +03:00
parent cc5717f5a3
commit 7f3b81a531
2 changed files with 105 additions and 34 deletions

View File

@ -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,

View File

@ -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) ===