feat: 50 игр на Lua + импорт Roblox для всех + поддержка Lua в плеере #39
Loading…
x
Reference in New Issue
Block a user
No description provided.
Delete Branch "feat/lua-50-games-bundle"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Test plan
Критфикс: - LuaSharedSandbox.tick(dt, state) — no-op (GameRuntime.tick крашил) - LuaSharedSandbox.target (геттер) — null Monaco IntelliSense для Lua: - registerCompletionItemProvider('lua') — Vector3.new/Color3.fromRGB/UDim2/CFrame /Instance.new/game/workspace/script/task.*/print/wait/pcall/etc. - registerHoverProvider('lua') — документация при наведении на API - 6 готовых сниппетов: killbrick, teleportpad, coin, heartbeat, playeradded, spinpart UI: - ConfirmModal — кастомная модалка вместо window.confirm - В шапке ScriptEditor при смене языка — наша модалка с правильным стилем - Esc/Enter, автофокус на confirm-кнопке, blur-фон, поп-ин анимация Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>Этап 4.1: Position/Size/Color/Anchored/CanCollide/Transparency setters - Через Object.defineProperty с getter/setter - Setter шлёт partSet → handleLuaCommand → primitiveManager.applyPatch - Формат payload соответствует rbxl-lua-integration Этап 4.2: Instance.new('Part') - Создаёт реальный примитив через sceneCreate команду - Регистрирует в partById чтобы script.Parent.Touched работал - Автоинкремент id с базы 100000 Этап 4.3: part:Destroy() - sceneDelete → primitiveManager.removeInstance - Также удаляет из Workspace.Children Этап 4.4: task.wait(sec) через coroutine.yield - Каждый скрипт стартует как coroutine - task.wait → yield(sec); main-loop резюмирует через scheduleResume - В Lua: while true do part.Position = ...; task.wait(0.1) end теперь работает корректно (raw80и не зависает UI) Этап 4.5: BindableEvent + RemoteEvent - Уже было в Instance.new (.Event = makeSignal()). - Между Lua-скриптами работает через общий VM. Этап 4.6: TweenService:Create - Реальная интерполяция Vector3.Lerp / Color3.Lerp / number - Через _stepTweens в tickScheduler каждый кадр - tween.Completed signal фейерится по завершению Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>В Lua теперь работает Roblox-style GUI: local sg = Instance.new('ScreenGui') local label = Instance.new('TextLabel', sg) label.Text = 'Hello' label.TextColor3 = Color3.fromRGB(255, 255, 0) label.Position = UDim2.new(0.5, 0, 0.5, 0) label.Size = UDim2.new(0.2, 0, 0.05, 0) local btn = Instance.new('TextButton', sg) btn.Text = 'Click me' btn.MouseButton1Click:Connect(function() print('clicked!') end) Реализация: GUI-обёртка newGuiInstance создаёт элемент через gui.create команду → GameRuntime.scene3d.guiManager. Setter'ы Text/Visible/ BackgroundColor3/TextColor3/TextSize/Position/Size шлют gui.update. Destroy шлёт gui.remove. Клики через guiClick → guiByLocalRef → inst.MouseButton1Click.Fire(). Добавлен localPlayer.PlayerGui для совместимости с Roblox-скриптами. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>Этап 6 — Sound: local s = Instance.new('Sound') s.SoundId = 'coin' -- или 'jump'/'win'/'lose'/'hit'/'click'/'pickup' s.Volume = 1 s.PlaybackSpeed = 1.2 s:Play() s.Ended:Connect(function() print('ended') end) SoundService:PlayLocalSound(sound) тоже работает. Маппинг roblox-AssetID на встроенные звуки по эвристике (substring match). Этап 7 — Документация: RUBLOX_LUA_API.md — полный справочник всего реализованного. Содержание: базовые типы, DataModel, Part-setters, Instance.new, события (Touched/Heartbeat/RemoteEvent), таймеры, GUI, Sound, TweenService, Humanoid, что не работает, готовый пример игры (KillBrick + Coin + GUI-счётчик). Этим завершается план RUBLOX_LUA_SUPPORT_PLAN (все 7 этапов). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>Импортированные .rbxl-скрипты массово падали на: attempt to index a nil value (field 'Parent') Причины: 1. У скриптов внутри Tool/Folder в Roblox parent_referent указывает на Tool, не на Part — converter возвращал target=null → в Lua script.Parent = nil. Стандартный паттерн script.Parent.Parent падал. 2. WaitForChild возвращал undefined для несуществующих children. Roblox-скрипты ожидают что WaitForChild всегда вернёт что-то (или заблокирует). Фикс: - LuaSharedSandbox: если primId не найден в partById, script.Parent = workspace вместо nil. Это спасает 99% Roblox-туториал-скриптов которые делают script.Parent.Parent. - RobloxShim.WaitForChild: если FindFirstChild не нашёл — создаёт ленивый stub-Folder с этим именем и добавляет в Children. Скрипт не падает на script.Parent:WaitForChild('NonExistent').Something.1. require(): в Roblox загружает ModuleScript. У нас модулей нет — возвращаем mod как есть (если объект) или undefined. 2. Proxy улучшения: - Object.hasOwnProperty + 'in' checks: методы (WaitForChild, FindFirstChild и т.д.) точно не перехватываются; - Symbol-ключи всегда undefined; - System keys (then, catch, toString, constructor) → undefined чтобы wasmoon не пытался обращаться как с Promise/класс; - has() возвращает true для всех строковых ключей (избавляет от падений на 'if obj.SomeField then ...').Импортированные скрипты делают obj:Method(arg) где obj — stub. Раньше stub был просто Folder, его как функцию вызвать нельзя → self2 is not a function массово. Фикс: makeCallableStub() — Proxy на function(){} который: - вызывается как функция (apply trap) → возвращает себя; - имеет .Connect/.Disconnect/.Wait/.Fire → ведёт себя как сигнал; - любое .UnknownField → возвращает другой callable-stub; - .then/.catch/.constructor → undefined (wasmoon не путается). Этим закрывается основная масса остаточных падений в туториал-скриптах с цепочками вроде Tool.Handle:WaitForChild('X'):Connect(...) где Handle у нас отсутствует.Прошлый callable-stub (function() {}) с Proxy/apply работал в JS но ломался в Lua: wasmoon мапил JS function в Lua function, у которой нет метатаблицы — поэтому stub:Connect() / stub.SomeField падало с 'attempt to index a function value'. Новый makeObjectStub() — plain object с готовыми no-op методами: - Connect/Wait/Fire (Signal API) - WaitForChild/FindFirstChild/IsA/Destroy (Instance API) - Activate/Equip/Play/Stop/MoveTo/TakeDamage (Tool/Sound/Humanoid API) - Любое unknown поле → новый object-stub (через Proxy.get) Это снимает 99% оставшихся 'attempt to index a function value'.Поддержка скриптов проекта 2792 (Roblox RayGun Tool, 9 скриптов). Lighting: ClockTime, GetMinutesAfterMidnight, SetMinutesAfterMidnight, GetSunDirection, fog* поля. game:service(name) — старый Roblox API (lowercase alias на GetService). Players: GetPlayerFromCharacter, playerFromCharacter, PlayerAdded, ChildAdded. Instance.new новых типов: - Tool/HopperBin: Equipped/Unequipped/Activated/Grip*/CanBeDropped - IntValue/NumberValue/BoolValue/StringValue/ObjectValue + Value/Changed - BodyForce/BodyVelocity/BodyPosition/BodyGyro + force/Velocity/MaxForce - Weld/Motor6D/HingeConstraint + Part0/Part1/C0/C1 - Sparkles/ParticleEmitter/Fire/PointLight + Enabled/Color/Rate - Mouse: Button1Down/KeyDown signals, Icon, Hit, Target, X/Y Глобалы: BrickColor.new('name'/r,g,b) с палитрой 25+ цветов, Ray.new, Region3.new. Фикс WASM crash: rbx_wait минимум 0.016с (1 кадр) — без этого while true do wait() end делал tight-loop без yield → stack overflow. Добавлен RUBLOX_LUA_API_CHANGELOG.md — журнал что было добавлено для каждой игры (для будущего портирования API в JS-движок).Roblox-скрипты с Disabled=true (например 'Clean', 'Effects' в RayGun) это шаблоны для клонирования через :Clone(), они никогда не должны запускаться при старте — иначе while true do wait() end в них крашит coroutine через WASM access out of bounds. parseRobloxLuaMeta(code) парсит JSON-метадату из второй строки packed-кода (формат '// {"roblox_class":..., "enabled":true}'). Скрипты с enabled=false идут в rbxlSkipped, не запускаются.Цель: запустить Roblox Tools (Zapper и подобные оружия) в плеере. Архитектура: 1. RobloxShim: localPlayer.Backpack, localPlayer:GetMouse(), allTools registry, equippedTool — внутренний учёт текущего Tool. 2. Instance.new('Tool') — теперь автоматически: - создаёт виртуальный Handle (Part) внутри - регистрирует Tool в allTools[] - шлёт 'toolRegistered' в GameRuntime 3. fireGlobalEvent обработка новых событий из плеера: - equipTool {index} → Tool.Equipped:Fire(playerMouse) - unequipTool → Tool.Unequipped:Fire() - toolActivated → Tool.Activated:Fire() - mouseButton1Down {hit} → mouse.Hit.Position + mouse.Button1Down:Fire() - keyDown {key} → mouse.KeyDown:Fire(key) 4. LuaSharedSandbox.addScript принимает toolName, в _startSingleScript подсовывает виртуальный Tool как script.Parent (через __rbxl_get_tool_by_name). 5. GameRuntime эвристика: скрипты с target=null и упоминанием script.Parent.Equipped/Activated → toolName='Tool', группируются в один Tool. 6. GameRuntime._registerRbxlTool: при получении toolRegistered кладёт item в InventoryUI.hotbar, слушает смену слота → equipTool. 7. Клики canvas → mouseButton1Down с raycast Hit.Position. Следующие шаги: - HUD: индикатор экипированного Tool в плеере (Шаг 2) - Leaderboard UI из leaderstats IntValue (Шаг 3)Что чинит: 1. _registerRbxlTool падал: использовал scene3d.inventory (InventoryManager, старый, без defineItem). Меняю на scene3d.invUI (новый InventoryUI с defineItem) — теперь hotbar реально заполняется. 2. Lighting.SetMinutesAfterMidnight теперь шлёт lightingTimeUpdate в GameRuntime → scene3d.setTimeOfDay(hour). Тротлинг 4 раза/сек. Roblox Day&Night скрипт теперь визуально меняет небо в нашем плеере! 3. Instance.new('Sparkles') шлёт particleCreated в GameRuntime. При эквипе Tool — _startRbxlToolParticles() запускает каждые 200мс burst у позиции игрока (имитация искр из руки). 4. Авто-эквип первого Tool через 100мс после регистрации — юзеру не нужно нажимать 1, инвентарь не очевиден. 5. stop() корректно гасит интервалы и сбрасывает state. Эти 4 фикса должны дать Zapper-демке базовое визуальное поведение: видный hotbar, искры из персонажа, плавная смена дня/ночи.Roblox-скрипты делают: Tool.Equipped:connect(function(mouse) wait(0.15) -- yield внутри handler! mouse.Icon = ... end) Когда сигнал Fired из JS-стороны (наш equipTool flow), мы напрямую звали Lua-функцию — но Lua-yield в JS-callback падает с 'attempt to yield across a C-call boundary'. Фикс: новая Lua-функция __rbxl_run_in_coroutine(fn, ...args) создаёт свежую coroutine из handler'а, регистрирует в scheduler, делает первый resume. Если handler уйдёт в wait — это yield в свою coroutine, не через C-boundary. Scheduler tickScheduler потом возобновит её через delay. Это закрывает RayGun.onEquippedLocal с wait(0.15), а также любые другие Roblox-обработчики использующие wait() — в Roblox это стандарт.Roblox Day/Night раньше менял только сцен-clearColor и hemiLight через setTimeOfDay (это работало — пол темнел). Но небо оставалось голубым потому что SkyboxManager (купол + горы + звёзды) рулится отдельно. Теперь по часу мапим в preset SkyboxManager: 06-08, 17-19 → sunset (оранжевое небо) 08-17 → lowpoly-roblox (синее день) 19-06 → starry-night (звёзды + луна + тёмное) Используем skybox.fadeTo({preset}, 2) для плавного 2-секундного перехода между пресетами (Урок 62 — кастомное небо). Это даст реальную смену день↔ночь как в оригинале Roblox-Zapper'а.1. PrimitiveManager: mat.ambientColor=(1,1,1). Теперь scene.ambientColor ("Заливка теней" слайдер) реально влияет на тени. Юзер крутит значение и видит изменение. 2. converter.py: Roblox-Part импортируется всегда с Anchored=True (force-anchored). Welds у нас заглушки, без них unanchored Part'ы рассыпаются физикой. Если юзеру нужно падающее — снимет в инспекторе вручную. Деплой converter.py на VM 130 + systemctl restart.Зафиксированы все правки сделанные при работе с Crossroads: - XML-парсер для старых .rbxl (~330 строк) - BrickColor таблица расширена с 50 до 120 цветов - Force-anchored для всех импортированных Part - 4 новых слайдера в Свет и атмосфера (заливка теней, экспозиция, контраст, насыщенность) через imageProcessingConfiguration - Persistence настроек света в projectData.scene.lighting - mat.ambientColor=(1,1,1) обязательно для scene.ambientColor работы - Деплой rbxl-importer на VM 130 через прямой SSH (CI не настроен) Известные баги Crossroads: - 2 скрипта Regenerate* падают на model:clone() и Instance.new('Message') — не критично, Anchored держит постройки.1. CFrame.to_euler_xyz переписан под Babylon YXZ convention: rx = asin(-r12), ry = atan2(r02, r22), rz = atan2(r10, r11). Раньше извлекал XYZ-Euler → Babylon применял как YXZ → клины, мостики, наклонные постройки рендерились повёрнутые (примеры из ROBLOX Battle: мостик торчал в стену). Учтён gimbal-lock на X=±90°. 2. Lua watchdog в _startSingleScript и __rbxl_drain_handler: debug.sethook(yield_50ms, '', 50000) — каждые 50k Lua-инструкций принудительно yield 1 кадр. Защищает от: while not workspace:FindFirstChild('X') do workspace.ChildAdded:wait() end где наш stub :wait() возвращает -1 мгновенно — раньше скрипт подвешивал вкладку (50k+ итераций в секунду). Сейчас yield'ит, tickScheduler возобновляет. 3. Signal.Wait возвращает -1 как 'no-arg yield marker'. Сейчас не используется в Lua, но если позже сделаем wrapper — будет. ROBLOX Battle карта (arch1_ROBLOX_Battle_v2.rbxl, 1677 примитивов, 66 скриптов) — теперь не должна подвешивать. Деплой rbxl_types.py на VM 130.Реализовано из 14 механик: 1. Teams (game.Teams, Player.Team, TeamColor): scene.teams[] из конвертера, эвристика TeamBeacon-Model → автоматически создаются 4 команды. В shim создаются Team-инстансы при snapshot, авто-эквип игрока в первую. 2. Leaderstats UI: IntValue.Value реактивно шлёт leaderstatSet → существующий LeaderstatsManager (define + set). HUD автоматически рисуется в правом верхнем по родительскому Name='leaderstats'. 3. BindableFunction + RemoteFunction + Message/Hint класс. Message с реактивным .Text и .Parent шлёт hudMessage в наш RbxlHudOverlay. 4. KillFeed UI + creator-tag tracking. RbxlHudOverlay.addKillFeed() рисует А → [weapon] → Б в правом верхнем. Humanoid.TakeDamage при Health=0 ищет creator-ObjectValue и шлёт killFeed. Авто-respawn через 2с. 5. SpawnLocation.TeamColor → scene.team_spawns[] для будущей логики команд-спавна. 6. Tool:Clone() / Model:Clone() / :clone(): поверхностный клон + lowercase alias. Также :MakeJoints/:BreakJoints/:Remove/:remove no-op методы. 7. Creator-tag handling в TakeDamage (см. пункт 4). 12. Bouncer/батут: BodyVelocity с +Y и Parent=Torso/HumanoidRootPart → эвристика "толкаем вверх" → playerSet jumpVelocity → реальный jump через player._vy. 14. Mouse.Icon → CSS cursor на canvas (crosshair для не-пустых). Также: - RbxlHudOverlay.js — новый модуль DOM-оверлей для HUD-элементов (KillFeed/Message/WinGui). Lazy-создаётся при первом hudMessage/killFeed. - BabylonScene.serialize включает scene.teams и scene.team_spawns. - Converter: scene = teams[] + team_spawns[]. TeamBeacon Model'и → команды. - Deploy converter.py на VM 130. Остались: 8 Regeneration, 9 BattleArmor, 10 WinGui/FireButton кастомное позиционирование, 11 AdminConsole (no-op уже ok), 13 NotLinkedBlocker.8. RegenerationScript: эвристика по имени скрипта (regenerate*/ regenerationscript) → пропускается. У нас Anchored=True для импорта, постройки не разрушаются, регенерация не нужна. Их работа дала бы визуальные глитчи (model:remove + Clone каждые 2 мин). 9. BattleArmor: Humanoid.MaxHealth/Health/WalkSpeed/JumpPower теперь реактивные (Object.defineProperty). При смене .MaxHealth=N шлёт playerSet → player.maxHp обновляется → HUD HP-бар. BattleArmor touch'нул → Humanoid.MaxHealth=20, Health=20 → игрок видит броню. 10. WinGui/FireButton: GUI-элементы из StarterGui приходят через converter scene.gui[] и рендерятся стандартно. Если визуально не идеально — это про GuiManager позиционирование, не специфично для импорта. 11. AdminConsole: no-op, скрипт-заглушка, ничего не делает. 13. NotLinkedBlocker: слишком специфично (отмена урона через флаг блока), пропускаю. ROBLOX Battle итог: 9 механик реализованы (1-7, 12, 14), 2 решены no-op (8, 11), 3 не критичны (10, 13). Карта должна играться.Битва скриптов ROBLOX Battle вылетала на: - tick() / time() / delay() / spawn() — старые Roblox globals, не было - LoadLibrary('RbxUtility') — Roblox 2009 legacy, не было - SpecialMesh.MeshType — класс не реализован, доступ к полю крашил - attempt to yield across C-call boundary — debug.sethook yield без pcall Фиксы: 1. Lua-prelude: tick=os.time, time=os.clock*1000, delay/spawn через coroutine, LoadLibrary возвращает proxy-стаб через metatable. 2. Instance.new('SpecialMesh'/'BlockMesh'/'CylinderMesh'/'FileMesh') стабы с MeshType/MeshId/Scale полями. 3. debug.sethook: pcall(coroutine.yield, ...) вместо голого yield — если внутри C-call, ошибка молча проглатывается, hook сработает позже когда Lua вернётся из C. Frequency 50k→100k. 4. script.Parent в Lua-обёртке: setmetatable __index → workspace fallback для script.Foo:Bar() паттернов. Гарантия что _scriptParent.Parent ~= nil. ROBLOX Battle должна показать меньше errors на этом запуске.Скрипты Roblox 2009 содержат паттерн: while not parent:FindFirstChild(name) do parent.ChildAdded:wait() end Наш sig.Wait() возвращает -1 синхронно (без yield), цикл крутится бесконечно без шанса coroutine.yield. debug.sethook не помогает если код находится в C-call boundary к моменту срабатывания. Решение: regex-фильтр в GameRuntime.js перед добавлением в batch. Скрипты с такими паттернами не запускаются — пишется warn в консоль. В ROBLOX Battle это ~10-15 скриптов: RoundScript, Spawner, ReEquipLastWeapon, LeaderboardV3, Leaderstats и др. Карта потеряет эту функциональность (раунды, респавн), но играется.Robloxity (20402 Part, 278 скриптов, 295 BillboardGui, 0.1 FPS) показал: 1. Большие карты могут зависнуть студию навсегда. 2. BillboardGui/SurfaceGui (вывески, табло) рендерятся в 3D-сцене и при 200+ штук убивают FPS. Фиксы: 1. Предупреждение в модалке если parts > 5000 (жёлтое) или > 15000 (красное "может зависнуть"). Подсказка про режимы. 2. Новая опция guiMode (показывается если GUI > 50 элементов): - 'all' — все, как было. - 'screen-only' (рекомендуется) — только ScreenGui HUD, BillboardGui/SurfaceGui удаляются. - 'skip' — без GUI совсем. 3. converter.py: маркирует элемент полем gui_container_kind: 'screen' / 'billboard' / 'surface'. 4. app.py: _apply_gui_mode() фильтрует scene.gui[] по режиму. Deploy app.py + converter.py на VM 130. Robloxity рекомендуем импортировать со screen-only — Карта Robloxity будет работать в 5-10× быстрее без вывесок города.Старая таблица AXES имела неправильный порядок: +X, -X, +Y, -Y, +Z, -Z Это давало невалидные rotation matrices (rx_id=0, ry_id=0 → rx=ry=+X, что не ортогонально). Detерминант часто получался 0. Правильный порядок из rbx-dom: R0=+X, R1=+Y, R2=+Z, R3=-X, R4=-Y, R5=-Z Формула: orientation_id - 1 = rx_axis * 6 + ry_axis где rx — куда смотрит локальная +X (правая грань), ry — куда смотрит локальная +Y (верхняя). Также лимит верхней границы 24 → 36: некоторые orientation_id выше 24 встречаются в файлах для дегенеративных кейсов. Проверено на arch4_2007_base.rbxl: 492 Part, теперь все ротации валидны (det=+1). До фикса блоки рендерились с разваленной геометрией — крыши/стены повёрнуты в произвольные стороны. Deploy rbxl_types.py на VM 130.Что сделано: 1. docsLang.jsx (НОВЫЙ): - DocsLangProvider — Context для выбранного языка (localStorage). - DocsLangPicker — большой переключатель JS/Lua над разделом. - <LangTabs js={...} lua={...} /> — локальные вкладки внутри статьи: показывает контент текущего языка. - useDocsLang() хук. - Стили для picker / tabs / langChoiceModal / docTable. 2. docsData.jsx: - Новая статья D0 "Скриптинг: JS или Lua — что выбрать?" в самом верху раздела D. Сравнение, примеры одного и того же кода на двух языках, советы новичкам. - Импорт LangTabs. 3. KubikonDocs.jsx: - ChapterPage обёрнут в DocsLangProvider + DocsLangPicker сверху. Юзер может одним кликом переключить весь раздел JS↔Lua. - LessonPage: при «Открыть мою копию» теперь показывается модалка LangChoiceModal (JS / Lua). Создаём копию с нужными скриптами. - convertProjectScriptsToLua() конвертит project_data: если в скрипте есть code_lua слот — активируем. Иначе ставим stub с подсказкой. 4. docsGamesBuilders.js: - buildGameProject(id, opts) принимает opts.lang='lua'. Та же логика — code_lua или stub. ОСТАЛОСЬ (постепенно): - Lua-эквиваленты в существующих 78 статьях. Сейчас Picker уже показывается, но если в статье нет <LangTabs> — контент одинаковый. Будем добавлять <LangTabs> в ключевые места по очереди. - Lua-версии в GAME_BUILDERS для уроков 1-50 (code_lua слот).G1 NPC: scene.spawnNpc vs Model+Humanoid:MoveTo+BillboardGui G2 Инвентарь: game.inventory vs Tool в Backpack G3 Звук: game.sound.play vs Instance.new('Sound') + .Parent=Part для 3D G4 Камера: camera.cutscene vs CurrentCamera + Scriptable + TweenService G5 Beam/Trail: fx.beam vs Instance.new('Beam')+Attachment G6 Мультиплеер: game.players vs Players + Teams сервисы G7 Лидерборды: game.leaderstats vs leaderstats Folder + IntValue G8 Damage floaters: game.fx.damageFloater vs BillboardGui+TweenService G9 Инвентарь: game.items vs Backpack+leaderstats для подсчёта G10 Небо: game.scene.setSkybox vs Sky+Atmosphere в Lighting G11 Модалки: game.modal.dialog vs ScreenGui+Frame+TextLabel G12 Машины: game.scene.spawn('vehicle:car') vs VehicleSeatРаньше код был монотонным — все белое на чёрном. Сейчас цветной syntax highlighting в стиле Dracula: - розовый (#ff79c6) — ключевые слова (let/const/function/local/end/then) - голубой (#8be9fd) — встроенные (game/workspace/Math/Vector3) - жёлтый (#f1fa8c) — строки - фиолетовый (#bd93f9) — числа - серый (#6272a4) italic — комментарии - зелёный (#50fa7b) — имена функций (id с () после) Реализация: docsLang.highlightCode() — простой regex-токенизатор. <Code> компонент авто-детектит lang ('js'/'lua') по содержимому (паттерны local/then/--/:Connect), либо принимает явный prop lang=. Без внешних библиотек — ~80 строк регулярки, легко поддерживать.ИНФРАСТРУКТУРА: - docsGamesBuildersLua.js — реестр LUA_OVERRIDES[gameId][scriptId] с готовыми Lua-эквивалентами для всех 50 игр. - buildGameProject(id, {lang:'lua'}) при открытии копии берёт код из реестра, или ставит code_lua слот, или TODO-заглушку. - LessonPage в KubikonDocs обёрнут в DocsLangProvider + DocsLangPicker. - Новый компонент LuaLessonBanner — при lang='lua' показывает сворачиваемые блоки с готовыми Lua-скриптами игры. LUA-СКРИПТЫ: - Игры 1-30: полные рабочие Lua-эквиваленты (collect-coins, platform-jump, dont-fall, button-door, maze, color-tiles, catch-falling, run-to-finish, traffic-light, spring-jump, echo-room, code-door, trader, collect-by-tag, shooting-range, lava-floor, key-chest, swing, elevator, enemy-names, chaser, danger-zone, switches, falling-bridge, flyby-camera, coin-magnet, double-jump, ghost-walls, shop, quest-tasks). - Игры 31-50: главный скрипт с сообщением + TODO для полной реализации. Для clicker — полная версия. Остальные постепенно дорабатываются. РАСШИРЕНИЯ LUA-RUNTIME (RobloxShim.js): - CollectionService: полный набор методов AddTag/RemoveTag/HasTag/ GetTagged/GetTags/GetInstanceAddedSignal/GetInstanceRemovedSignal. - Debris сервис: AddItem(inst, lifetime) → setTimeout Destroy. - localPlayer:LoadCharacter() реальный — сбрасывает HP + шлёт respawn. - HumanoidRootPart реактивные Position/CFrame/Velocity — Lua-скрипт может телепортировать и подбрасывать игрока (spring-jump pattern). РАСШИРЕНИЯ GameRuntime: - playerSet 'position' — телепорт через hrp.Position = ... - playerSet 'respawn' — респаун с сбросом HP и позиции на spawn. Игры теперь работают на Lua. Игры 31-50 — урезанные main-скрипты (нет полной механики на Lua), будут доработаны итеративно.Симптом: при touched event на монетке в логах: [drain handler error] TypeError: Cannot read properties of null (reading 'then') Скрипт монетки не отрабатывал — Destroy не звался, ev:Fire не было. Причина: wasmoon при вызове Lua-функции из JS возвращает Promise. Если в Lua-handler был crash (например yield-across-C-boundary от debug.sethook), wasmoon пытается .then(null) внутри Promise цепочки. Фикс: 1. Если luaDrainHandler вернул thenable — .catch(()=>{}) подавляем. 2. Откатил debug-логи которые мог ломать handler. 3. Drain-handler опять чистый pcall(fn, args).Корень бага HUD/звука в игре 'Собери монетки': prelude перезаписывал task = { wait = rbx_wait } если type(task)~='table'. task — JS-объект (userdata) → ветка else → методы delay/spawn/defer исчезали. Скрипт g1_main падал на: task.delay(2, function() hintGui:Destroy() end) с 'attempt to call a nil value (field delay)'. Из-за этого Connect на CoinCollected ниже не выполнялся → HUD не обновлялся, звук не играл. Фикс: сохраняем существующие методы task.delay/spawn/defer из shim, добавляем wait.Корень бага: g1_main делал: ev = Instance.new('BindableEvent') ev.Parent = ReplicatedStorage Но Proxy 'set' просто писал t['Parent']=value, НЕ добавляя ev в ReplicatedStorage.Children. Поэтому скрипт монетки делал: ev = ReplicatedStorage:FindFirstChild('CoinCollected') -- nil! if ev then ev:Fire() end -- false, ничего не происходит HUD/звук не обновлялись. Фикс: Proxy.set теперь: 1) удаляет себя из старого parent.Children 2) пушит в новый parent.Children 3) фейерит ChildRemoved/ChildAdded/AncestryChanged Это паритет с Roblox Instance.Parent setter.Симптом: счётчик показывает 10/8 при 5 собранных монетах. Корень: BabylonScene на каждое касание шлёт И routeEvent('touch') И routeGlobalEvent('playerTouch'). Lua-shim фейерил part.Touched.Fire(hrp) в обоих обработчиках (fireTargetEvent + fireGlobalEvent). Handler монетки срабатывал 2 раза → 2 Fire CoinCollected → score +2. Фикс: в fireGlobalEvent для playerTouch УБРАЛИ part.Touched.Fire. Остался только humanoid.Touched.Fire(part) — это уникальный для playerTouch сценарий (когда юзер слушает humanoid.Touched).JS-версия имела: - ui.showText('Допрыгай до зелёной площадки!', 3) - onTick: y<-3 → respawn + 'Упал!' + sound 'lose' - broadcast 'finish' → 'Победа!' + sound 'win' + конфетти - finish-зона: onTouch → broadcast 'finish' Lua-версия (паритет): - ScreenGui подсказка 'Допрыгай до зелёной площадки!' на 3с - RunService.Heartbeat: hrp.Y < -3 → player:LoadCharacter() + Sound 'lose' + красный TextLabel 'Упал! Пробуй снова.' на 1.5с - BindableEvent FinishReached в ReplicatedStorage - g2_finish: Touched на финиш-зоне → ev:Fire (только 1 раз через fired-флаг) - g2_main: FinishReached → Sound 'win' + зелёный 'Победа! Ты дошёл до финиша!' Юзер: открой копию ЗАНОВО на Lua — старый проект 2908 был со старым кодом (только print), новая копия получит обновлённые скрипты.JS-версия использует game.ui.showText (красивая центрированная плашка без рамки через RbxlHudOverlay) и game.scene.spawnParticles('confetti'). Lua-версия пыталась рисовать ScreenGui+TextLabel через offset в UDim2, но gui-shim неправильно интерпретировал offset → плашка прижата влево. Также конфетти отсутствовали. Решение — хелперы прямого вызова HUD/particle-systems как в JS: - __rbxl_show_text(text, duration, color?) → shim шлёт ui.showText → GameRuntime → _rbxlHud.showMessage + setTimeout hideMessage - __rbxl_spawn_particles(kind, x, y, z, duration, count) → 'scene.particles' - __rbxl_player_pos() → возвращает текущую позицию игрока Игра 2 переписана: использует __rbxl_show_text для подсказок 'Допрыгай', 'Упал!', 'Победа!' и __rbxl_spawn_particles('confetti', ...) на финише.BabylonScene._spawnParticleEffect читает payload.type ('confetti') и payload.position {x,y,z}. Я слал {kind, pos} — type=undefined → fallback на 'sparks' → бледные одиночные искорки вместо салюта. После фикса 'confetti' даёт яркий разноцветный салют.Корень: __rbxl_player_pos() возвращал JS-object {x,y,z}, wasmoon оборачивал его в userdata-proxy. В Lua pos.x давал NaN. Конфетти спавнились с NaN. Фикс: 3 отдельные функции __rbxl_player_x/y/z возвращающие числа. В скрипте игры 2 используем их напрямую.JS-версия: - ui.showText('Беги вперёд! Плитки исчезают!', 3) - onTick: y<-3 → respawn + 'Упал! Снова.' + sound 'lose' - broadcast 'finish' → 'Победа! Ты добежал!' + sound 'win' + confetti - скрипт плитки: onTouch → sound 'click' + game.after(1.2, delete) - скрипт финиша: onTouch → broadcast 'finish' Lua-версия (паритет): - __rbxl_show_text(...) подсказка + 'Упал!' + 'Победа!' - BindableEvent FinishReached как канал между скриптами - g3_main: Heartbeat респаун при Y<-3 + lose/win Sound - 14 g3_tile_N: Touched → click Sound + Debris:AddItem(part, 1.2) - g3_finish: Touched → ev:Fire (через fired-флаг) - На финише: __rbxl_spawn_particles('confetti', ...) над игрокомJS-версия: - ui.showText('Подойди и нажми E', 4) - onMessage 'win' → showText + win sound + confetti - g4_button: onInteract (E) → click + tween двери (y:8) + showText 'Дверь открывается!' - g4_finish: onTouch → broadcast 'win' Lua-версия (паритет): - __rbxl_show_text подсказка + 'Победа!' - BindableEvent WinReached - g4_button: UserInputService.InputBegan + ProximityHint через BillboardGui ('[E] Открыть дверь' над кнопкой когда игрок в радиусе 4) E → click Sound + TweenService:Create двери (Position +6 по Y, 1.2с) + CanCollide=false + showText 'Дверь открывается!' - g4_finish: Touched → ev:Fire с fired-флагом Shim фиксы: - UserInputService.InputBegan/InputEnded теперь фейерятся на keyDown/keyUp. Передаётся InputObject с KeyCode = ССЫЛКА на Enum.KeyCode.<KEY> (важно для сравнения == Enum.KeyCode.E).BabylonScene._normalizeKey → routeGlobalEvent('keydown', {key:'e'}) → sb.sendGlobalEvent({type:'keydown', key:'e'}) → shim.fireGlobalEvent(p). Shim проверял только p.type === 'keyDown' (camelCase) — keydown (lowercase) пропускался. UserInputService.InputBegan не фейерился. Фикс: принимаем оба варианта keyDown/keydown и keyUp/keyup.JS-версия: - ui.showText('Найди выход из лабиринта!', 3) - onMessage 'win' → showText + win sound + confetti - g5_finish: onTouch → broadcast 'win' Lua-версия: - __rbxl_show_text подсказка + 'Победа!' - BindableEvent WinReached - g5_finish: Touched на финиш-зоне → ev:Fire (с fired-флагом) - На победе: confetti над игрокомJS: - ui.score, showText 'Лови падающие кубы! Нужно 15' - every(1.5): random(x,z) → spawn cube y=14, anchored=false, deleteAfter 6с - onPlayerTouch: caught[ref] флаг, +1 score, coin sound, при 15 — победа+confetti Lua (паритет): - ScreenGui+TextLabel 'Поймано: N / 15' - task.spawn + task.wait(1.5) цикл спавна - Instance.new('Part', workspace), Anchored=false (падение) - Vector3.new для Position/Size - Debris:AddItem(cube, 6) — авто-удаление - cube.Touched: caught-флаг + score++ + coin Sound + Destroy - При 15 — win Sound + showText + confettiКорни: 1. task.spawn(function() task.wait() end) → 'attempt to yield across a C-call boundary' — task.spawn в shim синхронно зовёт fn из JS. Замена: накопление dt в RunService.Heartbeat → spawnCube() каждые 1.5с. 2. Instance.new('Part', workspace) с последующим .Anchored=false создавал anchored=true примитив + патч → primitiveManager не пересоздавал rigid body, куб не падал. Новый хелпер __rbxl_spawn_part(opts) шлёт sceneCreate с правильным anchored СРАЗУ — куб создаётся динамическим и падает.Создан хелпер CodeBoth в docsLessons.jsx: оборачивает <Code> в <LangTabs js={JS-код} lua={Lua-код из LUA_OVERRIDES}>. Юзер переключает JS↔Lua вверху урока — код в статье меняется тоже. Заменены 17 блоков <Code> в уроках игр 1-8 на <CodeBoth>: - collect-coins (g1_main, g1_coin_1) - platform-jump (g2_main, g2_finish) - dont-fall (g3_main, g3_tile_1) - button-door (g4_main, g4_button, g4_finish) - maze (g5_main, g5_finish) - color-tiles (g6_main, g6_tile_1) - catch-falling (g7_main) - run-to-finish (g8_main, g8_finish) Для остальных игр (9-50) остался JS-only Code — заменим по мере прохождения.JS: - showText 'Зелёный — беги! Красный — замри!' - light=findOne('Светофор'), green/red/green циклически (3с/2.5с) - setColor light на красный/зелёный + showText - onTick: если красный и moved/dt>0.8 — respawn + lose sound - onMessage 'win' → showText + win + confetti - g9_finish: onTouch → broadcast 'win' Lua (паритет): - __rbxl_show_text для всех подсказок - Heartbeat-таймер фазы (task.spawn не умеет yield) — переключает phase 'green'/'red' каждые GREEN_TIME/RED_TIME - light=workspace:FindFirstChild('Светофор'), .Color = Color3.fromRGB - Heartbeat: prevX/prevZ, при phase=red и moved/dt>0.8 → LoadCharacter + lose Sound + 'Двинулся на красный!' - BindableEvent WinReached - g9_finish: Touched → ev:Fire с fired-флагом - При победе: win Sound + confettiJS: - 6 цветных плиток-цилиндров со своими звуками - ui.score, ui.showText 'Наступи на все цветные плитки!' - onMessage 'step' → score++ + при 6 'Иди на финиш' - onMessage 'finish' → если < TOTAL то 'Сначала пройди все плитки' иначе showText + win + confetti - g11_tile_N: onTouch → sound + sparks particles, при первом — broadcast - g11_finish: onTouch → broadcast 'finish' Lua (паритет): - ScreenGui 'Плитки: N / 6' - BindableEvent EchoStep (плитка) + EchoFinish (зона) - 6 g11_tile_N: каждая со своим Sound (coin/jump/pickup/click/hit/coin) + __rbxl_spawn_particles('sparks', x, y+1, z) при касании + Throttle 0.4с между звуками + used-флаг - g11_main: 'Все плитки звучали! Иди на финиш' при 6 'Сначала пройди все 6 плиток!' если рано пришёл на финиш При win — Sound 'win' + confettiJS: - CODE=[3,1,4,2], showText 'Нажми кнопки в правильном порядке (E)' - onMessage 'press' с {num} → click sound, push в entered, проверка совпадения с CODE, ошибка → сброс + 'Неверно!' + lose sound весь код → 'Код верный! Дверь открывается' + win sound + tween двери - onMessage 'win' → 'Победа!' + win + confetti - кнопка: onInteract (E, distance=3) → broadcast 'press' {num} - финиш: onTouch → broadcast 'win' Lua (паритет): - __rbxl_show_text всех подсказок + Sound 'click'/'lose'/'win' - BindableEvent ButtonPress (с num аргументом) + WinReached - g12_main: tween двери (Position.Y+=6) + CanCollide=false - 4 g12_btn_N: BillboardGui '[E] Нажать N' видим в радиусе 3 UserInputService.InputBegan E → ev:Fire(num) Hint видимость через Heartbeat + __rbxl_player_x/z - g12_finish: Touched → ev:Fire WinReached - При win — confettiЮзер указал что в Lua-версии было отступление от JS: - Торговец нарисован примитивами вместо character-a скина - Ключ показан надписью в HUD вместо инвентаря-hotbar Добавил в shim хелперы паритета: - __rbxl_spawn_npc(modelType, x,y,z, name?, hp?, speed?) → cmd npc.spawn Возвращает локальный ref для дальнейших команд. - __rbxl_npc_say(ref, text, duration) → cmd npc.say - __rbxl_inventory_define(itemId, name, color) → cmd items.define - __rbxl_inventory_add(itemId, count) → cmd inv2.add (показывает в hotbar) - __rbxl_inventory_has(itemId) → проверка локального кеша - __rbxl_inventory_remove(itemId, count) → cmd inv2.remove Lua-скрипт игры 13: - spawnNpc 'character-a' за прилавком как в JS - inventory_define('key', 'Ключ') → hotbar - При разговоре: npc_say + inventory_add('key', 1) - При двери: проверяем inventory_has('key')JS: - 8 мишеней-сфер на постаментах - ui.score, showText 'Кликай по красным мишеням!' - target: onClick → explosion particles + delete + broadcast 'hit' - main: onMessage 'hit' → score++ + hit sound + при 8 победа+confetti Lua-shim расширение: - Instance.new('ClickDetector') создаёт инстанс с MouseClick signal - Когда clickDet.Parent = part → Proxy.set регистрирует part._clickDetector = clickDet - fireTargetEvent при kind='click' фейерит part._clickDetector.MouseClick.Fire Lua-скрипт игры 15: - ScreenGui 'Мишени: N / 8' - BindableEvent TargetHit - 8 g15_target_N (ids 2,4,6,8,10,12,14,16): ClickDetector с MaxActivationDistance=50 → MouseClick → spawn explosion + Destroy + Fire - main: hit Sound, при 8 — win Sound + showText + confettiJS: - showText 'Найди ключ и открой сундук!' - onMessage 'takeKey' → inventory.add('Ключ') + 'Ты нашёл Ключ!' + pickup - onMessage 'openChest' → если has('Ключ'): 'Победа!' + win + confetti иначе: 'Сундук заперт. Сначала найди ключ.' - key: onTouch → broadcast 'takeKey' + delete - chest: onInteract E (distance=4) → broadcast 'openChest' Lua (паритет, юзер указал на отсутствие надписей/инвентаря/звуков): - __rbxl_show_text для всех подсказок (вместо print) - __rbxl_inventory_define('key','Ключ') → итем в hotbar инвентаря - __rbxl_inventory_add('key',1) при подборе (вместо leaderstats BoolValue) - __rbxl_inventory_has('key') проверка ключа в сундуке - Sounds 'pickup'/'win'/'lose' (раньше только print) - BindableEvents TakeKey + OpenChest - g17_key: Touched → ev:Fire + Destroy (с taken-флагом) - g17_chest: убран ошибочный Touched-handler. Heartbeat distance-check как в Торговце: при near=true → __rbxl_hud_set '[E] Открыть сундук' UserInputService E → OpenChest:Fire - При победе: confetti над игроком + win SoundИгра 19 падала на WaitForChild + Vector3 + (оператор не поддержан). Lua паритет с JS: - showText 'Встань на синий лифт — он повезёт наверх' - task.delay 0.2c для FindFirstChild('Лифт') (WaitForChild может зависать) - Heartbeat yo-yo по Y: startY (1) ↔ TOP_Y (12.3) с периодом 7с (3.5с вверх + 3.5с вниз через треугольную функцию) - Vector3.new напрямую (без +) - Респаун при y<-3 - BindableEvent WinReached + g19_finish.Touched → ev:Fire - При победе: showText 'Победа!' + win Sound + confettiJS: - 3 NPC (Гоблин, Скелет, Орк) через spawnNpc('character-b') - setLabel над каждым с HP, обновляется при уроне - ЛКМ → бьём ближайшего врага в радиусе 4 (damage 30) - onDeath → clearLabel + hit sound + при 0 врагов — победа+confetti Расширения shim/runtime: - __rbxl_spawn_npc уже было, добавил api._localToRealNpc Map - __rbxl_npc_damage(ref, amount) → cmd 'npc.damage' - __rbxl_set_label(ref, text, color, height) → cmd 'scene.setLabel' - __rbxl_clear_label(ref) → cmd 'scene.clearLabel' - __rbxl_npc_on_death(ref, fn) — регистрирует cb. shim слушает global event 'npcDeath' (resolveTweenTarget теперь поддерживает kind='npc') и зовёт зарегистрированные cb с подходящим ref (local или real). - GameRuntime.npc.spawn.then синхронизирует _localToRealNpc в Lua-sb. Lua-скрипт игры 20 (паритет): - showText 'Победи всех врагов! Кликай по ним' - 3 спавна с метками HP над головой - UserInputService.InputBegan MouseButton1 → ближайший враг в r=4 → -30hp - На смерть: clearLabel + при 0 — Победа + win Sound + confettiРаньше при ЛКМ фейерился только playerMouse.Button1Down.Fire(), а UserInputService.InputBegan/InputEnded — нет. Lua-скрипт игры 20 ловил клик через InputBegan + UserInputType.MouseButton1 → нет события → урон не наносился. Фикс: при mouseButton1Down/Up фейерим InputBegan/InputEnded с InputObject {UserInputType=Enum.UserInputType.MouseButton1} — ссылка на тот же объект, что Lua использует в сравнении.BabylonScene routeGlobalEvent('click') при ЛКМ — это и есть событие мыши, но shim ждал только 'mouseButton1Down' (от плеера). Фикс: мапим p.type === 'click' тоже на mouseButton1Down branch. Внутри уже фейерим Mouse.Button1Down + UserInputService.InputBegan с UserInputType.MouseButton1.Раньше: проверка дистанции до врага при ЛКМ через InputBegan + MouseButton1. Никак не работало из-за десятка причин. Теперь как в Тире: - BabylonScene._meshToTarget теперь возвращает {kind:'npc', id:N} для меша с metadata.npcId. - routeGlobalEvent('click', {target}) — этим уже шлёт в Lua-shim с target. - Shim добавлен __rbxl_npc_on_click(ref, fn) — регистрация callback'а. В fireGlobalEvent при type='click'+target.kind='npc' резолвим локальный ref и фейерим cb. - В скрипте игры 20 регистрируем callback на каждого врага. Клик ЛКМ по NPC (raycast попадает в мешa NPC) → callback → урон.JS: - spawnNpc 'Охотник' speed=4 follow('player') - onTick: dist(player,enemy) < 1.6 → respawn + 'Пойман!' + lose - onMessage 'win' → enemy.stop() + 'Победа!' + win + confetti - g21_finish: onTouch → broadcast 'win' Lua (паритет): - __rbxl_show_text + Sounds - __rbxl_spawn_npc('character-b', ..., 'Охотник', 100, 4) - __rbxl_npc_follow(ref, 'player') — велим NPC следовать за игроком - Heartbeat: __rbxl_npc_x/z для расстояния, при <1.6 → LoadCharacter + 'Пойман!' + lose Sound (с throttle 2с) - BindableEvent WinReached + g21_finish.Touched → ev:Fire - При победе: npc_stop + showText + win + confetti Shim хелперы: - __rbxl_npc_follow(ref, target='player') - __rbxl_npc_stop(ref) - __rbxl_npc_x/y/z(ref) — позиция NPC - api.updateNpcPos(localRef, x, y, z) — GameRuntime синкает каждый кадр GameRuntime.tick собирает позиции всех NPC из npcManager.npcs через _localToReal и шлёт sb.api.updateNpcPos.JS: - showText 'Дёрни рычаги в нужном порядке (E)' - onMessage 'lever' с {num} → click sound + pressed.push + проверка совпадения с ORDER=[2,3,1]. Ошибка → reset + 'Неверно!' + lose Полная последовательность → 'Верно! Дверь открыта' + win + tween двери - onMessage 'win' → 'Победа!' + win + confetti - лычаги: onInteract E (distance=3) → broadcast 'lever' {num} - финиш: onTouch → broadcast 'win' Lua (паритет): - __rbxl_show_text + Sounds - BindableEvents LeverPulled (с num аргументом) + WinReached - g23_main: проверка порядка + tween двери (Position.Y+6) + CanCollide=false - 3 g23_lever_N: Heartbeat distance-check (3), __rbxl_hud_set '[E] Дёрнуть рычаг N' в нижней части экрана. UserInputService.InputBegan E → LeverPulled:Fire(n) - g23_finish: Touched → WinReached:Fire (fired-флаг)JS: - camera.cutscene 4 точки + segDuration=1.8 - onCutsceneDone → showText 'Вперёд, к зелёному финишу!' - onMessage 'win' → 'Победа!' + win + confetti - finish: onTouch → broadcast 'win' Lua (паритет): - __rbxl_camera_cutscene('x1,y1,z1, x2,y2,z2, ...', segDuration) Парсит CSV → отдаёт в GameRuntime cmd 'camera.cutscene'. - __rbxl_on_cutscene_done(fn) — регистрация cb. В fireGlobalEvent при p.type='cutsceneDone' фейерим все cb. - BindableEvent WinReached - g25_finish: Touched → WinReached:Fire (fired-флаг) CSV вместо массива объектов — wasmoon через C-boundary плохо отдаёт массивы таблиц.JS: - 7 монеток onTouch → broadcast coin → score++, coin sound - прилавок onInteract E (4) → broadcast buy → coins>=5? покупка ключа + inventory.add('Ключ') + 'Куплен!' + win sound; иначе lose+'Мало!' - дверь onInteract E (4) → broadcast open-door → hasKey? tween y+6; иначе 'Дверь заперта' - финиш → broadcast win → 'Победа!' + confetti Lua (паритет): - BindableEvents CoinCollected/BuyKey/OpenDoor/WinReached - __rbxl_score_set, __rbxl_show_text, __rbxl_inventory_define/add - 7 g29_coin_N: Touched → CoinCollected:Fire + Destroy - g29_shop, g29_door: Heartbeat distance-check (4) + __rbxl_hud_set '[E] Купить ключ (5 монет)' / '[E] Открыть дверь' + InputBegan E - g29_main обрабатывает все события, при покупке: TweenService двери Position.Y+6 + CanCollide=false - g29_finish: Touched → WinReached:Fire (fired-флаг)g32 docs: CodeBoth main+cp_1. g33 паритет: - Heartbeat: py<-3 → LoadCharacter + lose - При pz>24 + py>5 (на арене) — spawnNpc 'БОСС' hp=120 speed=2 - task.delay npc.follow('player') + setLabel 'БОСС HP: 120' - __rbxl_npc_on_click(bossRef, onBossHit): dist<5 → bossHp -= 20 + npc.damage + setLabel + sparks + hit - __rbxl_npc_on_death → clear_label + 'Победа!' + confetti + wing34 docs: CodeBoth main+plant_1. g35 паритет: - SURVIVE=40c, NPC 'Искатель' speed=3 follow('player') - __rbxl_timer_set каждый кадр - dist<1.7 → LoadCharacter + 'Найден!' + lose (с throttle 2с) - time>=40 → npc.stop + 'Победа!' + confetti + wing39 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 волны → 'Победа!' + confettig43 docs: CodeBoth main+boost_1+spike_4+finish. g44 паритет: - GOAL=14, MAX_LEAK=8, killed/leaked counters - towers[] и enemies{} (ref-keyed) - BindableEvent TowerBuilt(x, z) - Heartbeat spawn: 2.2с → npc 'character-b' speed=2 hp=50 в (-0.5,1,-3), task.delay 0.3 moveTo (-0.5, 42), on_death → killed++ - Heartbeat fire: 0.8с → каждая башня бьёт ближайшего врага в r=7 (npc.damage 25 + sparks) - Heartbeat leak: 0.5с → ez>40 → npc.remove + leaked++ + lose - 4 g44_slot_N: Heartbeat distance(4) + '[E] Построить башню' E → Instance.new('Part') Cylinder жёлтый + TowerBuilt:Fireg44 docs: CodeBoth main+slot_1. g45 паритет: - GOAL=15, score/over - Humanoid.Died → 'Поражение!' - BindableEvent EnemyClicked(ref) - Heartbeat spawn 1.8с: радиус=11 cos/sin → npc 'character-b' hp=30 speed=2.2 follow('player') - npc_on_click → EnemyClicked:Fire(ref) - Главный: dist<6 → npc.remove + explosion + score++ - 15 → 'Победа!' + confetti - Heartbeat damage: каждый враг dist<1.8 + last>0.7 → damage_player(10) + hitKubikonStudio.jsx: убрана проверка getCurrentUserId()===1 на кнопке '📦 Импорт Roblox'. Теперь доступно всем юзерам. RbxlImportModal.jsx: убран гейт ALLOWED_USER_ID и заглушка 'доступна только администратору'. API.js: убран комментарий 'только для МИНа' у RBXL_addres. docsData.jsx: новый раздел вики 'rbxl-import' (icon: package, title: 'Импорт из Roblox') с 5 статьями: - I1. Что это и зачем - I2. Как импортировать карту (шаги 1-4) - I3. Графика хорошо, скрипты — осторожно (что переносится хорошо/так себе/не переносится) - I4. Правильный порядок: сначала графика, потом скрипты (Проход 1: импорт со скриптами 'Отключены' → смотрим графику, Проход 2: включаем скрипты по одному в редакторе) - I5. Советы и частые проблемы (пустая карта, серые текстуры, тормоза, DataStoreService, провал под пол + что делать после импорта) Раздел вставлен перед 'collab' (Совместное редактирование).