feat(rbxl): выбор режима скриптов в модалке импорта
3 опции в модалке (только если в карте есть скрипты): - 'disabled' (default) — скрипты импортируются с enabled=false в метадате → GameRuntime их не запускает, но видны в иерархии для чтения как референс при написании своих Lua-скриптов. - 'enabled' — скрипты активны (старое поведение). Может вешать игру на старых Roblox 2007-2010 паттернах. - 'skip' — scripts[] обнуляется, чистый импорт только геометрии. Реализация: - RbxlImportModal.jsx: state scriptsMode + radio-блок над названием игры, показывается только если report.scripts_total > 0. - rbxlImporterApi.js: передача scripts_mode в /import/rbxl/create. - app.py: _apply_scripts_mode() патчит JSON-метадату на 2-й строке packed-кода скрипта (или удаляет scripts[] для 'skip'). GameRuntime уже умеет уважать meta.enabled === false — пропускает скрипт. Deploy app.py на VM 130.
This commit is contained in:
parent
cc5e6d60e5
commit
16223e06ef
BIN
rbxl-importer/src/__pycache__/app.cpython-314.pyc
Normal file
BIN
rbxl-importer/src/__pycache__/app.cpython-314.pyc
Normal file
Binary file not shown.
@ -210,6 +210,12 @@ def create():
|
||||
data = request.get_json(silent=True) or {}
|
||||
preview_hash = data.get('preview_hash')
|
||||
title = (data.get('title') or '').strip() or 'Импортировано из Roblox'
|
||||
# scripts_mode: 'disabled' (default) — оставить в проекте, но enabled=False
|
||||
# 'enabled' — попытаться запустить, может вешать
|
||||
# 'skip' — не импортировать совсем
|
||||
scripts_mode = data.get('scripts_mode', 'disabled')
|
||||
if scripts_mode not in ('disabled', 'enabled', 'skip'):
|
||||
scripts_mode = 'disabled'
|
||||
|
||||
if not preview_hash:
|
||||
return jsonify({'error': 'preview_hash required'}), 400
|
||||
@ -274,6 +280,10 @@ def create():
|
||||
# Подставляем URLs в project_data
|
||||
_resolve_asset_urls(project_data, asset_url_map)
|
||||
|
||||
# Применяем scripts_mode: меняем поле enabled в метадате каждого скрипта
|
||||
# либо удаляем все скрипты полностью.
|
||||
_apply_scripts_mode(project_data, scripts_mode)
|
||||
|
||||
# Создаём проект в kubikon3d_projects
|
||||
# Используем bridge на user-service / storys API, ИЛИ напрямую в storys_db.
|
||||
# Прямой INSERT — проще для MVP. id автогенерируется.
|
||||
@ -335,5 +345,46 @@ def _resolve_asset_urls(project_data: dict, asset_map: dict) -> None:
|
||||
snd['url'] = asset_map[rid]
|
||||
|
||||
|
||||
def _apply_scripts_mode(project_data: dict, mode: str) -> None:
|
||||
"""Применяет режим scripts_mode к проекту.
|
||||
|
||||
mode='disabled' (default): для каждого скрипта меняем JSON-метадату
|
||||
на 2-й строке packed-кода — выставляем enabled=False. GameRuntime
|
||||
уже умеет уважать этот флаг и не запускает.
|
||||
mode='enabled': оставляем как было (как пришло из конвертера).
|
||||
mode='skip': удаляем все scripts из scene.scripts полностью.
|
||||
"""
|
||||
scene = project_data.get('scene', {})
|
||||
scripts = scene.get('scripts', [])
|
||||
if not scripts:
|
||||
return
|
||||
|
||||
if mode == 'skip':
|
||||
scene['scripts'] = []
|
||||
return
|
||||
|
||||
if mode == 'enabled':
|
||||
return # ничего не делаем
|
||||
|
||||
# mode == 'disabled' — патчим метадату каждого скрипта.
|
||||
# Формат packed-кода (см. converter._convert_script):
|
||||
# "// @roblox-lua\n// {JSON}\n/* lua_source:\n...source...\n*/\n"
|
||||
for s in scripts:
|
||||
code = s.get('code', '')
|
||||
lines = code.split('\n', 2)
|
||||
if len(lines) < 2 or not lines[0].startswith('// @roblox-lua'):
|
||||
continue
|
||||
meta_line = lines[1]
|
||||
if not meta_line.startswith('// '):
|
||||
continue
|
||||
try:
|
||||
meta = json.loads(meta_line[3:])
|
||||
meta['enabled'] = False
|
||||
new_meta_line = '// ' + json.dumps(meta, ensure_ascii=False)
|
||||
s['code'] = lines[0] + '\n' + new_meta_line + '\n' + (lines[2] if len(lines) > 2 else '')
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
continue
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=8690, debug=False)
|
||||
|
||||
@ -52,11 +52,18 @@ export async function analyzeRbxl(file) {
|
||||
/**
|
||||
* Создаёт проект из preview_hash. Возвращает { project_id, redirect, assets_downloaded, assets_failed }.
|
||||
*/
|
||||
export async function createRbxlProject(previewHash, title) {
|
||||
export async function createRbxlProject(previewHash, title, opts = {}) {
|
||||
const resp = await fetch(`${RBXL_addres}/import/rbxl/create`, {
|
||||
method: 'POST',
|
||||
headers: { ...authHeaders(), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ preview_hash: previewHash, title: title || '' }),
|
||||
body: JSON.stringify({
|
||||
preview_hash: previewHash,
|
||||
title: title || '',
|
||||
// 'disabled' (default) — импортнуть выключенными, читать можно
|
||||
// 'enabled' — попытаться запустить (может вешать карту)
|
||||
// 'skip' — не импортировать совсем
|
||||
scripts_mode: opts.scriptsMode || 'disabled',
|
||||
}),
|
||||
});
|
||||
if (!resp.ok) {
|
||||
const text = await resp.text();
|
||||
|
||||
@ -26,6 +26,9 @@ export default function RbxlImportModal({ open, onClose, currentUserId, onCreate
|
||||
const [previewHash, setPreviewHash] = useState(null);
|
||||
const [title, setTitle] = useState('');
|
||||
const [error, setError] = useState(null);
|
||||
// Режим скриптов: 'disabled' (импортнуть выключенными — для чтения),
|
||||
// 'enabled' (попытаться запустить — может вешать карту), 'skip' (удалить).
|
||||
const [scriptsMode, setScriptsMode] = useState('disabled');
|
||||
const fileInputRef = useRef(null);
|
||||
|
||||
if (!open) return null;
|
||||
@ -45,6 +48,7 @@ export default function RbxlImportModal({ open, onClose, currentUserId, onCreate
|
||||
const reset = () => {
|
||||
setFile(null); setReport(null); setPreviewHash(null);
|
||||
setTitle(''); setError(null); setAnalyzing(false); setCreating(false);
|
||||
setScriptsMode('disabled');
|
||||
};
|
||||
|
||||
const handleClose = () => { reset(); onClose?.(); };
|
||||
@ -88,7 +92,7 @@ export default function RbxlImportModal({ open, onClose, currentUserId, onCreate
|
||||
setCreating(true);
|
||||
setError(null);
|
||||
try {
|
||||
const result = await createRbxlProject(previewHash, title);
|
||||
const result = await createRbxlProject(previewHash, title, { scriptsMode });
|
||||
onCreated?.(result);
|
||||
handleClose();
|
||||
// редирект на редактор
|
||||
@ -206,6 +210,58 @@ export default function RbxlImportModal({ open, onClose, currentUserId, onCreate
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{report.scripts_total > 0 && (
|
||||
<div style={{ marginTop: 16, padding: 12, background: '#1f1f1f', borderRadius: 6, border: '1px solid #333' }}>
|
||||
<div style={{ fontSize: 13, fontWeight: 600, marginBottom: 8 }}>
|
||||
Что делать со скриптами ({report.scripts_total} шт.)?
|
||||
</div>
|
||||
<label style={{ display: 'flex', alignItems: 'flex-start', gap: 8, padding: '6px 0', cursor: 'pointer' }}>
|
||||
<input
|
||||
type="radio" name="scriptsMode" value="disabled"
|
||||
checked={scriptsMode === 'disabled'}
|
||||
onChange={() => setScriptsMode('disabled')}
|
||||
style={{ marginTop: 3 }}
|
||||
/>
|
||||
<div>
|
||||
<div style={{ fontSize: 13 }}>Импортировать <b>выключенными</b> (рекомендуется)</div>
|
||||
<div style={{ fontSize: 11, color: '#888' }}>
|
||||
Скрипты видны в иерархии и редакторе, можно читать как референс,
|
||||
но не исполняются. Карта не подвиснет.
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<label style={{ display: 'flex', alignItems: 'flex-start', gap: 8, padding: '6px 0', cursor: 'pointer' }}>
|
||||
<input
|
||||
type="radio" name="scriptsMode" value="enabled"
|
||||
checked={scriptsMode === 'enabled'}
|
||||
onChange={() => setScriptsMode('enabled')}
|
||||
style={{ marginTop: 3 }}
|
||||
/>
|
||||
<div>
|
||||
<div style={{ fontSize: 13 }}>Импортировать <b>активными</b></div>
|
||||
<div style={{ fontSize: 11, color: '#888' }}>
|
||||
Попытаться запустить. Старые Roblox-скрипты могут подвешивать игру —
|
||||
тогда вернись и переимпортируй с другим режимом.
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<label style={{ display: 'flex', alignItems: 'flex-start', gap: 8, padding: '6px 0', cursor: 'pointer' }}>
|
||||
<input
|
||||
type="radio" name="scriptsMode" value="skip"
|
||||
checked={scriptsMode === 'skip'}
|
||||
onChange={() => setScriptsMode('skip')}
|
||||
style={{ marginTop: 3 }}
|
||||
/>
|
||||
<div>
|
||||
<div style={{ fontSize: 13 }}>Не импортировать совсем</div>
|
||||
<div style={{ fontSize: 11, color: '#888' }}>
|
||||
Только геометрия. Скрипты не попадут в проект — чистое начало.
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<label style={{ display: 'block', marginBottom: 6 }}>Название игры:</label>
|
||||
<input
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user