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:
parent
cc5717f5a3
commit
7f3b81a531
@ -6172,6 +6172,95 @@ import { LUA_OVERRIDES } from './docsGamesBuildersLua';
|
|||||||
/** Построить project_data для игры-урока. Возвращает объект или null.
|
/** Построить project_data для игры-урока. Возвращает объект или null.
|
||||||
* opts.lang: 'js' (default) | 'lua' — на каком языке скрипты в копии.
|
* 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 = {}) {
|
export function buildGameProject(id, opts = {}) {
|
||||||
const fn = GAME_BUILDERS[id];
|
const fn = GAME_BUILDERS[id];
|
||||||
if (!fn) return null;
|
if (!fn) return null;
|
||||||
@ -6180,9 +6269,16 @@ export function buildGameProject(id, opts = {}) {
|
|||||||
const scene = project.scene || {};
|
const scene = project.scene || {};
|
||||||
if (Array.isArray(scene.scripts)) {
|
if (Array.isArray(scene.scripts)) {
|
||||||
const overrides = LUA_OVERRIDES[id] || {};
|
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 => {
|
scene.scripts = scene.scripts.map(s => {
|
||||||
if (s.language === 'lua') return s;
|
if (s.language === 'lua') return s;
|
||||||
// Приоритет: явный code_lua → override из реестра → stub.
|
// Приоритет: явный code_lua → override из реестра → авто-fallback.
|
||||||
let luaCode = s.code_lua;
|
let luaCode = s.code_lua;
|
||||||
if (!luaCode) {
|
if (!luaCode) {
|
||||||
const ov = overrides[s.id];
|
const ov = overrides[s.id];
|
||||||
@ -6190,10 +6286,7 @@ export function buildGameProject(id, opts = {}) {
|
|||||||
else if (typeof ov === 'string') luaCode = ov;
|
else if (typeof ov === 'string') luaCode = ov;
|
||||||
}
|
}
|
||||||
if (!luaCode || !luaCode.trim()) {
|
if (!luaCode || !luaCode.trim()) {
|
||||||
luaCode = `-- TODO: Lua-версия этого скрипта пока не готова.
|
luaCode = generateFallbackLua(s, gameTitle);
|
||||||
-- Переключи язык на JS в редакторе (кнопка JS вверху), чтобы увидеть рабочий код.
|
|
||||||
print("Lua-скрипт " .. (script and script.Name or "?") .. " запущен (заглушка)")
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...s,
|
...s,
|
||||||
|
|||||||
@ -1722,28 +1722,14 @@ end)`,
|
|||||||
},
|
},
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════
|
||||||
// ИГРА 31-50: упрощённые версии (главные скрипты)
|
// ИГРЫ 31-50: явных Lua-версий пока нет.
|
||||||
// ═══════════════════════════════════════════════════════════════
|
// buildGameProject в docsGamesBuilders.js использует generateFallbackLua
|
||||||
'base-defense': { g31_main: simpleMain("Защити базу от волн врагов!") },
|
// (главный скрипт → показ подсказки + слушает FinishReached →
|
||||||
'lap-race': { g32_main: simpleMain("Проедь все круги первым!") },
|
// победа+конфетти; скрипт на финиш-примитиве → шлёт FinishReached;
|
||||||
'boss-platformer': { g33_main: simpleMain("Победи босса прыжками на голову!") },
|
// остальные target-скрипты → красят примитив на касание).
|
||||||
'harvest': { g34_main: simpleMain("Собирай урожай, продавай в магазин!") },
|
// Это даёт «хоть что-то рабочее» в любой игре до того как напишем
|
||||||
'hide-from-npc': { g35_main: simpleMain("Прячься от NPC — не попадайся!") },
|
// полноценный Lua-скрипт. Когда дописываем игру — добавляем сюда явный override.
|
||||||
'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("Стреляй по противникам на арене!") },
|
|
||||||
'clicker': { g46_main: simpleClicker() },
|
'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)`;
|
end)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Простой главный скрипт со стартовой подсказкой. */
|
|
||||||
function simpleMain(message) {
|
|
||||||
return `-- === Главный скрипт (Lua) ===
|
|
||||||
print("${message.replace(/"/g, '\\"')}")
|
|
||||||
-- TODO: эта игра-урок ещё не имеет полной Lua-реализации.
|
|
||||||
-- Переключи язык на JS в редакторе, чтобы увидеть рабочую механику.`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Кликер. */
|
/** Кликер. */
|
||||||
function simpleClicker() {
|
function simpleClicker() {
|
||||||
return `-- === КЛИКЕР (Lua) ===
|
return `-- === КЛИКЕР (Lua) ===
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user