feat: 50 игр на Lua + импорт Roblox для всех + поддержка Lua в плеере #39
@ -30,7 +30,7 @@ import BillboardEditorModal from './BillboardEditorModal';
|
|||||||
import TerrainGenPanel from './TerrainGenPanel';
|
import TerrainGenPanel from './TerrainGenPanel';
|
||||||
import ScriptConsole from './ScriptConsole';
|
import ScriptConsole from './ScriptConsole';
|
||||||
import SceneTabs from './SceneTabs';
|
import SceneTabs from './SceneTabs';
|
||||||
import ScriptEditor from './ScriptEditor';
|
import ScriptEditor, { LUA_TEMPLATE_PART, LUA_TEMPLATE_GLOBAL, JS_TEMPLATE_GLOBAL } from './ScriptEditor';
|
||||||
import GameHud from './GameHud';
|
import GameHud from './GameHud';
|
||||||
import MinimapOverlay from './MinimapOverlay';
|
import MinimapOverlay from './MinimapOverlay';
|
||||||
import GuiOverlay from './GuiOverlay';
|
import GuiOverlay from './GuiOverlay';
|
||||||
@ -3327,13 +3327,40 @@ const KubikonEditor = () => {
|
|||||||
language={sc.language || 'js'}
|
language={sc.language || 'js'}
|
||||||
flushRef={scriptEditorFlushRef}
|
flushRef={scriptEditorFlushRef}
|
||||||
isSoloRunning={soloScriptId === sc.id}
|
isSoloRunning={soloScriptId === sc.id}
|
||||||
onLanguageChange={(lang, newCode) => {
|
onLanguageChange={(lang, currentEditorCode) => {
|
||||||
sceneRef.current?.upsertScript(sc.id, newCode, undefined, undefined, lang);
|
// Два слота: code_js и code_lua живут в самом скрипте.
|
||||||
|
// При переключении: сохраняем текущий код в слот ТЕКУЩЕГО
|
||||||
|
// языка, достаём слот ЦЕЛЕВОГО языка (или шаблон если пусто).
|
||||||
|
const fromLang = sc.language === 'lua' ? 'lua' : 'js';
|
||||||
|
if (fromLang === lang) return;
|
||||||
|
const fromSlotKey = fromLang === 'lua' ? 'code_lua' : 'code_js';
|
||||||
|
const toSlotKey = lang === 'lua' ? 'code_lua' : 'code_js';
|
||||||
|
// Сохраняем текущий редактируемый код в слот текущего языка
|
||||||
|
const savedSlots = {
|
||||||
|
...(sc.code_js !== undefined ? { code_js: sc.code_js } : {}),
|
||||||
|
...(sc.code_lua !== undefined ? { code_lua: sc.code_lua } : {}),
|
||||||
|
[fromSlotKey]: currentEditorCode || '',
|
||||||
|
};
|
||||||
|
// Достаём слот целевого языка или подставляем шаблон
|
||||||
|
let nextCode = savedSlots[toSlotKey];
|
||||||
|
if (nextCode === undefined || nextCode === '') {
|
||||||
|
nextCode = lang === 'lua'
|
||||||
|
? (sc.target ? LUA_TEMPLATE_PART : LUA_TEMPLATE_GLOBAL)
|
||||||
|
: JS_TEMPLATE_GLOBAL;
|
||||||
|
}
|
||||||
|
sceneRef.current?.upsertScript(
|
||||||
|
sc.id, nextCode, undefined, undefined, lang, savedSlots
|
||||||
|
);
|
||||||
setScriptsList(sceneRef.current?.getScripts?.() || []);
|
setScriptsList(sceneRef.current?.getScripts?.() || []);
|
||||||
markDirty();
|
markDirty();
|
||||||
}}
|
}}
|
||||||
onSave={(code) => {
|
onSave={(code) => {
|
||||||
sceneRef.current?.upsertScript(sc.id, code, sc.target);
|
// Зеркалим в слот активного языка чтобы при swap не потерять.
|
||||||
|
const slotKey = (sc.language === 'lua') ? 'code_lua' : 'code_js';
|
||||||
|
sceneRef.current?.upsertScript(
|
||||||
|
sc.id, code, sc.target, undefined, undefined,
|
||||||
|
{ [slotKey]: code }
|
||||||
|
);
|
||||||
setScriptsList(sceneRef.current?.getScripts?.() || []);
|
setScriptsList(sceneRef.current?.getScripts?.() || []);
|
||||||
markDirty();
|
markDirty();
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -38,21 +38,21 @@ import ConfirmModal from './ConfirmModal';
|
|||||||
// командой `python _build_bundle.py` в той же папке.
|
// командой `python _build_bundle.py` в той же папке.
|
||||||
// Дефолтный шаблон Lua-скрипта для нового скрипта (на Part или глобальный).
|
// Дефолтный шаблон Lua-скрипта для нового скрипта (на Part или глобальный).
|
||||||
// Используется при смене языка JS→Lua когда текущий код выглядит «пустым».
|
// Используется при смене языка JS→Lua когда текущий код выглядит «пустым».
|
||||||
const LUA_TEMPLATE_PART = `-- Скрипт привязан к Part. script.Parent = эта часть.
|
export const LUA_TEMPLATE_PART = `-- Скрипт привязан к Part. script.Parent = эта часть.
|
||||||
local part = script.Parent
|
local part = script.Parent
|
||||||
|
|
||||||
part.Touched:Connect(function(hit)
|
part.Touched:Connect(function(hit)
|
||||||
print("Касание:", hit.Name)
|
print("Касание:", hit.Name)
|
||||||
end)
|
end)
|
||||||
`;
|
`;
|
||||||
const LUA_TEMPLATE_GLOBAL = `-- Глобальный Lua-скрипт. Работает на стороне сервера/клиента.
|
export const LUA_TEMPLATE_GLOBAL = `-- Глобальный Lua-скрипт. Работает на стороне сервера/клиента.
|
||||||
local Players = game:GetService("Players")
|
local Players = game:GetService("Players")
|
||||||
|
|
||||||
Players.PlayerAdded:Connect(function(player)
|
Players.PlayerAdded:Connect(function(player)
|
||||||
print("Игрок зашёл:", player.Name)
|
print("Игрок зашёл:", player.Name)
|
||||||
end)
|
end)
|
||||||
`;
|
`;
|
||||||
const JS_TEMPLATE_GLOBAL = `// Глобальный JS-скрипт. Подробнее: см. game.* API в /справочник.
|
export const JS_TEMPLATE_GLOBAL = `// Глобальный JS-скрипт. Подробнее: см. game.* API в /справочник.
|
||||||
game.onPlayerJoined((player) => {
|
game.onPlayerJoined((player) => {
|
||||||
game.chat.say('Привет, ' + player.name + '!');
|
game.chat.say('Привет, ' + player.name + '!');
|
||||||
});
|
});
|
||||||
@ -115,6 +115,15 @@ function ScriptEditor({ value, onSave, onRunSolo, isSoloRunning, scriptId, targe
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
|
// При смене языка — принудительно синхронизируем код со слотом нового языка.
|
||||||
|
// (родитель swap'нул code_js ↔ code_lua и прислал свежий value.)
|
||||||
|
useEffect(() => {
|
||||||
|
if (value !== undefined && value !== localCodeRef.current) {
|
||||||
|
setLocalCode(value || '');
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [language]);
|
||||||
|
|
||||||
// Дебаунс-сохранение
|
// Дебаунс-сохранение
|
||||||
const scheduleSave = useCallback((code) => {
|
const scheduleSave = useCallback((code) => {
|
||||||
if (debounceRef.current) clearTimeout(debounceRef.current);
|
if (debounceRef.current) clearTimeout(debounceRef.current);
|
||||||
@ -340,16 +349,9 @@ function ScriptEditor({ value, onSave, onRunSolo, isSoloRunning, scriptId, targe
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (active) return;
|
if (active) return;
|
||||||
if (!onLanguageChange) return;
|
if (!onLanguageChange) return;
|
||||||
if (isCodeLikelyEmptyTemplate(localCode)) {
|
// Логика двух слотов (code_js / code_lua) живёт в родителе.
|
||||||
const nextCode = lang === 'lua'
|
// Здесь только сигналим: «переключи на lang».
|
||||||
? (target ? LUA_TEMPLATE_PART : LUA_TEMPLATE_GLOBAL)
|
// Текущий код отдаём чтобы родитель сохранил в слот.
|
||||||
: JS_TEMPLATE_GLOBAL;
|
|
||||||
setLocalCode(nextCode);
|
|
||||||
onLanguageChange(lang, nextCode);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Код не пустой — переключаем сразу, без модалки.
|
|
||||||
// Код остаётся как есть, только подсветка синтаксиса меняется.
|
|
||||||
onLanguageChange(lang, localCodeRef.current);
|
onLanguageChange(lang, localCodeRef.current);
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@ -6734,7 +6734,7 @@ export class BabylonScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Установить код одного скрипта по id. Если id нет — создать новый. */
|
/** Установить код одного скрипта по id. Если id нет — создать новый. */
|
||||||
upsertScript(id, code, target = undefined, name = undefined, language = undefined) {
|
upsertScript(id, code, target = undefined, name = undefined, language = undefined, slots = undefined) {
|
||||||
const i = this._scripts.findIndex(s => s.id === id);
|
const i = this._scripts.findIndex(s => s.id === id);
|
||||||
if (i >= 0) {
|
if (i >= 0) {
|
||||||
this._scripts[i] = {
|
this._scripts[i] = {
|
||||||
@ -6743,6 +6743,10 @@ export class BabylonScene {
|
|||||||
...(target !== undefined ? { target } : {}),
|
...(target !== undefined ? { target } : {}),
|
||||||
...(name !== undefined ? { name } : {}),
|
...(name !== undefined ? { name } : {}),
|
||||||
...(language !== undefined ? { language } : {}),
|
...(language !== undefined ? { language } : {}),
|
||||||
|
// Слоты code_js и code_lua — сохраняемый код для каждого языка.
|
||||||
|
// Передаются при переключении языка, чтобы код другого языка
|
||||||
|
// не пропадал.
|
||||||
|
...(slots && typeof slots === 'object' ? slots : {}),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
this._scripts.push({
|
this._scripts.push({
|
||||||
@ -6751,6 +6755,7 @@ export class BabylonScene {
|
|||||||
target: target !== undefined ? target : null,
|
target: target !== undefined ? target : null,
|
||||||
name: name || null,
|
name: name || null,
|
||||||
language: language || 'js',
|
language: language || 'js',
|
||||||
|
...(slots && typeof slots === 'object' ? slots : {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Скрипты — часть сцены: фиксируем в истории, иначе undo откатит
|
// Скрипты — часть сцены: фиксируем в истории, иначе undo откатит
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user