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 {}
|
data = request.get_json(silent=True) or {}
|
||||||
preview_hash = data.get('preview_hash')
|
preview_hash = data.get('preview_hash')
|
||||||
title = (data.get('title') or '').strip() or 'Импортировано из Roblox'
|
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:
|
if not preview_hash:
|
||||||
return jsonify({'error': 'preview_hash required'}), 400
|
return jsonify({'error': 'preview_hash required'}), 400
|
||||||
@ -274,6 +280,10 @@ def create():
|
|||||||
# Подставляем URLs в project_data
|
# Подставляем URLs в project_data
|
||||||
_resolve_asset_urls(project_data, asset_url_map)
|
_resolve_asset_urls(project_data, asset_url_map)
|
||||||
|
|
||||||
|
# Применяем scripts_mode: меняем поле enabled в метадате каждого скрипта
|
||||||
|
# либо удаляем все скрипты полностью.
|
||||||
|
_apply_scripts_mode(project_data, scripts_mode)
|
||||||
|
|
||||||
# Создаём проект в kubikon3d_projects
|
# Создаём проект в kubikon3d_projects
|
||||||
# Используем bridge на user-service / storys API, ИЛИ напрямую в storys_db.
|
# Используем bridge на user-service / storys API, ИЛИ напрямую в storys_db.
|
||||||
# Прямой INSERT — проще для MVP. id автогенерируется.
|
# Прямой INSERT — проще для MVP. id автогенерируется.
|
||||||
@ -335,5 +345,46 @@ def _resolve_asset_urls(project_data: dict, asset_map: dict) -> None:
|
|||||||
snd['url'] = asset_map[rid]
|
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__':
|
if __name__ == '__main__':
|
||||||
app.run(host='0.0.0.0', port=8690, debug=False)
|
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 }.
|
* Создаёт проект из 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`, {
|
const resp = await fetch(`${RBXL_addres}/import/rbxl/create`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { ...authHeaders(), 'Content-Type': 'application/json' },
|
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) {
|
if (!resp.ok) {
|
||||||
const text = await resp.text();
|
const text = await resp.text();
|
||||||
|
|||||||
@ -26,6 +26,9 @@ export default function RbxlImportModal({ open, onClose, currentUserId, onCreate
|
|||||||
const [previewHash, setPreviewHash] = useState(null);
|
const [previewHash, setPreviewHash] = useState(null);
|
||||||
const [title, setTitle] = useState('');
|
const [title, setTitle] = useState('');
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
// Режим скриптов: 'disabled' (импортнуть выключенными — для чтения),
|
||||||
|
// 'enabled' (попытаться запустить — может вешать карту), 'skip' (удалить).
|
||||||
|
const [scriptsMode, setScriptsMode] = useState('disabled');
|
||||||
const fileInputRef = useRef(null);
|
const fileInputRef = useRef(null);
|
||||||
|
|
||||||
if (!open) return null;
|
if (!open) return null;
|
||||||
@ -45,6 +48,7 @@ export default function RbxlImportModal({ open, onClose, currentUserId, onCreate
|
|||||||
const reset = () => {
|
const reset = () => {
|
||||||
setFile(null); setReport(null); setPreviewHash(null);
|
setFile(null); setReport(null); setPreviewHash(null);
|
||||||
setTitle(''); setError(null); setAnalyzing(false); setCreating(false);
|
setTitle(''); setError(null); setAnalyzing(false); setCreating(false);
|
||||||
|
setScriptsMode('disabled');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = () => { reset(); onClose?.(); };
|
const handleClose = () => { reset(); onClose?.(); };
|
||||||
@ -88,7 +92,7 @@ export default function RbxlImportModal({ open, onClose, currentUserId, onCreate
|
|||||||
setCreating(true);
|
setCreating(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
const result = await createRbxlProject(previewHash, title);
|
const result = await createRbxlProject(previewHash, title, { scriptsMode });
|
||||||
onCreated?.(result);
|
onCreated?.(result);
|
||||||
handleClose();
|
handleClose();
|
||||||
// редирект на редактор
|
// редирект на редактор
|
||||||
@ -206,6 +210,58 @@ export default function RbxlImportModal({ open, onClose, currentUserId, onCreate
|
|||||||
</div>
|
</div>
|
||||||
</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 }}>
|
<div style={{ marginTop: 16 }}>
|
||||||
<label style={{ display: 'block', marginBottom: 6 }}>Название игры:</label>
|
<label style={{ display: 'block', marginBottom: 6 }}>Название игры:</label>
|
||||||
<input
|
<input
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user