197 lines
9.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 元景游戏工作台 v2game 聚合根单页编辑器(内联输入,无 prompt */
(function () {
function api(path, params) {
return fetch(path, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(params || {})
}).then(function (r) { return r.json(); });
}
function base() { return '/scense_game'; }
var state = { game: null, data: null, sel: null, games: [], cur_script: null, inline: null };
function msg(t, ok) {
var el = document.getElementById('msg');
el.textContent = t;
el.className = ok ? 'ok' : 'err';
}
function newGame() {
state.game = null; state.data = null; state.sel = null; state.cur_script = null;
document.getElementById('game_name').value = '';
document.getElementById('game_sel').value = '';
renderTree(null);
document.getElementById('cur_obj').textContent = '未选择对象';
document.getElementById('code').value = '';
msg('填写名称后点保存游戏(将自动创建 1:1 世界)', true);
}
function saveGame() {
var name = document.getElementById('game_name').value.trim();
if (state.game) { msg('当前游戏: ' + state.game.name + '' + state.game.id + '', true); return; }
if (!name) { msg('先填游戏名称', false); return; }
api(base() + '/game/api/game_create.dspy', {name: name,
description: document.getElementById('game_shared').checked ? 'shared' : 'independent'})
.then(function (r) {
if (r.success) { msg('游戏创建成功,世界已自动建立', true); reload(r.data.game_id); }
else msg(r.message || '创建失败', false);
});
}
function switchGame(gid) {
if (!gid) return;
state.game = null; state.sel = null;
reload(gid);
}
function reload(forceGid) {
api(base() + '/game/api/game_list.dspy', {}).then(function (r) {
var games = (r.data && r.data.games) || r || [];
if (!Array.isArray(games)) games = [];
state.games = games;
var sel = document.getElementById('game_sel');
sel.innerHTML = '<option value="">— 选择游戏 —</option>' +
games.map(function (g) {
var gid = g.game_id || g.id;
return '<option value="' + gid + '">' + (g.game_name || g.name) + '</option>';
}).join('');
var gid = forceGid || (state.game && state.game.id) || (games[0] && (games[0].game_id || games[0].id));
if (!gid) { state.game = null; renderTree(null); return; }
sel.value = gid;
api(base() + '/game/api/game_workbench.dspy', {game_id: gid}).then(function (w) {
if (!w.success) { msg(w.message, false); return; }
state.game = w.data.game; state.data = w.data;
document.getElementById('game_name').value = (w.data.game && w.data.game.name) || '';
renderTree(w.data);
if (state.sel) loadSelKeep(); else selectWorld();
});
});
}
function renderTree(d) {
var el = document.getElementById('tree_body');
if (!d || !d.world) { el.innerHTML = '<div style="color:#9CA3AF">尚无游戏,先填名称保存</div>'; return; }
var h = '';
h += node('world', d.world.id, '🌍 ' + esc(d.world.name));
(d.scenes || []).forEach(function (s) {
h += node('scene', s.id, '🎬 ' + esc(s.name), 12);
});
h += addRow('scene', ' 场景', 12);
(d.entities || []).forEach(function (e) {
h += node('entity', e.id, '📦 ' + esc(e.name) + (e.scene_id ? '' : '(世界级)'), 24);
});
h += addRow('entity', ' 实体', 24);
el.innerHTML = h;
}
function node(type, id, label, ml) {
var sel = state.sel && state.sel.id === id ? ' sel' : '';
return '<div class="node' + sel + '" style="margin-left:' + (ml || 0) + 'px" ' +
'onclick="wb.selObj(\'' + type + '\',\'' + id + '\')">' + label + '</div>';
}
function addRow(type, label, ml) {
if (state.inline === type) {
return '<div class="node" style="margin-left:' + ml + 'px;flex-direction:column;align-items:stretch">' +
'<input type="text" id="inline_name" placeholder="' + (type === 'scene' ? '场景名称' : '实体名称') + '" ' +
'style="padding:4px 8px;border:1px solid #93C5FD;border-radius:4px;margin-bottom:4px" ' +
'onkeydown="if(event.key===\'Enter\')wb.inlineOk(\'' + type + '\')">' +
'<div style="display:flex;gap:6px"><button onclick="wb.inlineOk(\'' + type + '\')" style="flex:1;padding:4px;background:#2563EB;color:#fff;border:none;border-radius:4px;cursor:pointer">添加</button>' +
'<button onclick="wb.inlineCancel()" style="flex:1;padding:4px;background:#E5E7EB;border:none;border-radius:4px;cursor:pointer">取消</button></div></div>';
}
return '<div class="node" style="margin-left:' + ml + 'px;color:#2563EB" onclick="wb.inlineStart(\'' + type + '\')">' + label + '</div>';
}
function esc(s) { return String(s || '').replace(/</g, '&lt;'); }
function inlineStart(type) { state.inline = type; renderTree(state.data); setTimeout(function () { var i = document.getElementById('inline_name'); if (i) i.focus(); }, 50); }
function inlineCancel() { state.inline = null; renderTree(state.data); }
function inlineOk(type) {
var name = (document.getElementById('inline_name') || {}).value;
if (!name || !name.trim()) { msg('名称不能为空', false); return; }
state.inline = null;
if (type === 'scene') {
api(base() + '/game/api/scene_add.dspy', {game_id: state.game.id, name: name.trim()})
.then(function (r) { r.success ? (msg('场景已添加', true), reload()) : msg(r.message, false); });
} else {
var sceneId = state.sel && state.sel.type === 'scene' ? state.sel.id : '';
api(base() + '/game/api/entity_add.dspy', {game_id: state.game.id, scene_id: sceneId, name: name.trim()})
.then(function (r) { r.success ? (msg('实体已添加' + (sceneId ? '' : '(世界级)'), true), reload()) : msg(r.message, false); });
}
}
function selectWorld() {
if (state.data && state.data.world) selObj('world', state.data.world.id);
}
function selObj(type, id) {
var obj = null;
if (type === 'world') obj = state.data.world;
if (type === 'scene') obj = (state.data.scenes || []).find(function (x) { return x.id === id; });
if (type === 'entity') obj = (state.data.entities || []).find(function (x) { return x.id === id; });
if (!obj) return;
state.sel = {type: type, id: id, name: obj.name};
document.getElementById('cur_obj').textContent =
({world: '🌍 世界', scene: '🎬 场景', entity: '📦 实体'})[type] + ' · ' + obj.name;
renderTree(state.data);
loadScript();
}
function loadSelKeep() { if (state.sel) selObj(state.sel.type, state.sel.id); }
function bindParams() {
var s = state.sel;
if (!s) return null;
var p = {game_id: state.game.id, trigger_event: document.getElementById('event_sel').value};
// 世界外键所有级别脚本都归属该游戏的世界world_id NOT NULL 约束)
if (state.data && state.data.world) p.world_id = state.data.world.id;
if (s.type === 'scene') p.scene_id = s.id;
if (s.type === 'entity') p.entity_id = s.id;
return p;
}
function loadScript() {
var p = bindParams();
if (!p) return;
var sc = (state.data.scripts || []).find(function (x) {
return (state.sel.type === 'world' && x.world_id === state.sel.id && x.trigger_event === p.trigger_event) ||
(state.sel.type === 'scene' && x.scene_id === state.sel.id && x.trigger_event === p.trigger_event) ||
(state.sel.type === 'entity' && x.entity_id === state.sel.id && x.trigger_event === p.trigger_event);
});
document.getElementById('code').value = sc ? (sc.content || '') : '';
state.cur_script = sc || null;
msg(sc ? '已加载现有脚本: ' + sc.name : '新脚本(保存后绑定到该对象+事件)', true);
}
function saveScript() {
var p = bindParams();
if (!p) { msg('先在左侧选择对象', false); return; }
p.name = state.sel.name + '_' + p.trigger_event;
p.code = 'SC_' + state.sel.id.slice(0, 8) + '_' + p.trigger_event;
p.content = document.getElementById('code').value;
p.script_type = '0';
if (state.cur_script) p.id = state.cur_script.id;
var url = state.cur_script ? '/script_engine/api/script_engine_update.dspy' : '/script_engine/api/script_engine_create.dspy';
api(url, p).then(function (r) {
if (r && r.success === false) { msg(r.error || r.message || '保存失败', false); return; }
msg(state.cur_script ? '脚本已更新' : '脚本已保存', true);
reload();
}).catch(function (e) { msg('保存失败: ' + e, false); });
}
function validate() {
api('/script_engine/api/validate_script.dspy', {content: document.getElementById('code').value})
.then(function (r) { msg(JSON.stringify(r).slice(0, 200), true); });
}
function test() {
if (!state.game) { msg('先保存游戏', false); return; }
var wid = state.data && state.data.world ? state.data.world.id : '';
window.open('/scense_demo/demo_viewer.ui?world_id=' + wid + '&game_id=' + state.game.id, '_blank');
}
window.wb = {newGame: newGame, saveGame: saveGame, switchGame: switchGame, reload: function () { reload(); },
selObj: selObj, inlineStart: inlineStart, inlineCancel: inlineCancel, inlineOk: inlineOk,
saveScript: saveScript, validate: validate, test: test};
document.getElementById('game_sel').onchange = function () { switchGame(this.value); };
reload();
})();