fea: 添加地图管理

This commit is contained in:
cancerai committed 2026-10-22 23:16:27 +08:00
1 parent cc2883458c
commit 22157d4baa
7 files changed
+201 -44

No files matched your search

+57 -37
View File
@@ -65,7 +65,6 @@ export const Editor = {
this.bind();
$('#ed-w').addEventListener('change', () => this.resize(+$('#ed-w').value, +$('#ed-h').value));
$('#ed-h').addEventListener('change', () => this.resize(+$('#ed-w').value, +$('#ed-h').value));
this.renderMyLevels();
},
buildPalette() {
@@ -133,6 +132,8 @@ export const Editor = {
on('ed-clear', () => { if (typeof confirm === 'function' && !confirm('清空当前面?')) return; const f = this.curFace(); f.grid = blank(f.w, f.h); f.doors = []; f.props = propsBlank(f.w, f.h); this.selTile = null; this.draw(); this.renderProps(); });
on('ed-test', () => this.test());
on('ed-save', () => this.save());
on('ed-import', () => this.openImport());
on('ed-import-cancel', () => this.closeImport());
on('ed-addface', () => this.addFace());
on('ed-copyface', () => this.copyFace());
on('ed-delface', () => this.delFace());
@@ -142,18 +143,33 @@ export const Editor = {
curFace() { return this.faces[this.face]; },
open() {
if (!this.faces) {
const w = +$('#ed-w').value || 25, h = +$('#ed-h').value || 19;
this.faces = [{ name: '面1', w, h, seq: 0, grid: blank(w, h), doors: [], props: propsBlank(w, h) }];
this.face = 0;
}
// 从菜单进入编辑器:始终是新地图(宽高沿用输入框里的值)
const w = +$('#ed-w').value || 25, h = +$('#ed-h').value || 19;
this.faces = [{ name: '面1', w, h, seq: 0, grid: blank(w, h), doors: [], props: propsBlank(w, h) }];
this.face = 0;
this.cur = null; // 未关联任何已存地图 → 首次保存为新建
this.show();
},
/** 仅重绘当前状态(不从零重置):用于试玩后「返回编辑器」等场景,保留正在编辑的地图 */
show() {
if (!this.faces) { this.open(); return; }
this.selTile = null;
$('#ed-w').value = this.curFace().w;
$('#ed-h').value = this.curFace().h;
this.draw();
this.renderFaces();
this.renderProps();
this.renderMyLevels();
this.updateSaveState();
},
/** 「保存」按钮旁的提示:会更新哪张图,还是另存为新图 */
updateSaveState() {
const el = $('#ed-savestate');
if (!el) return;
const l = this.cur ? Save.customs().find(o => o.id === this.cur) : null;
el.textContent = l ? '→ 更新「' + (l.name || 'MY MAP') + '」' : '→ 另存为新图';
el.classList.toggle('on', !!l);
},
resize(w, h) {
@@ -408,41 +424,45 @@ export const Editor = {
save() {
const err = this.validate();
if (err) { alert(err); return; }
const lv = makeCustomLevel(this.buildData());
Save.addCustom(lv);
let lv = makeCustomLevel(this.buildData());
if (this.cur) lv.id = this.cur; // 保存当前地图:沿用已有 id,就地更新
Save.updateCustom(lv);
this.cur = lv.id;
this.renderMyLevels();
this.updateSaveState();
this.api.refresh?.();
},
// ---------------- 我的关卡 ----------------
renderMyLevels() {
const box = $('#my-levels');
// ---------------- 导入地图(从已保存的地图里选) ----------------
openImport() {
const box = $('#ed-importbox');
if (!box) return;
box.innerHTML = '';
const list = Save.customs();
if (!list.length) box.innerHTML = '<div style="color:#666;font-size:.8em">还没有保存的关卡</div>';
list.forEach((l, i) => {
const d = document.createElement('div');
const fn = (l.faces || []).length;
d.className = 'my-lv' + (l.id === this.cur ? ' on' : '');
d.innerHTML = `<span>${i + 1}. ${escapeHtml(l.name)}${fn ? ` <b style="color:#a86bff">[${fn}面]</b>` : ''}</span><span class="x">✕</span>`;
d.querySelector('span').onclick = () => {
this.cur = l.id;
this.hydrate(l);
$('#ed-name').value = l.name || '';
this.afterFaceSwitch();
this.renderMyLevels();
};
d.querySelector('.x').onclick = (e) => {
e.stopPropagation();
if (typeof confirm === 'function' && !confirm('删除 ' + l.name + '?')) return;
Save.removeCustom(l.id);
this.renderMyLevels();
this.api.refresh?.();
};
box.appendChild(d);
});
const list = $('#ed-import-list');
const customs = Save.customs();
list.innerHTML = '';
if (!customs.length) {
list.innerHTML = '<div class="imp-empty">还没有保存过的地图,先「保存」一个吧</div>';
} else {
customs.forEach(l => {
const fn = (l.faces || []).length;
const d = document.createElement('div');
d.className = 'imp-row' + (l.id === this.cur ? ' on' : '');
d.innerHTML = `<span class="imp-nm">${escapeHtml(l.name || 'MY MAP')}</span><span class="imp-meta">${fn ? fn + ' 面' : '单关'}</span>`;
d.onclick = () => this.pickImport(l.id);
list.appendChild(d);
});
}
box.classList.remove('hidden');
},
closeImport() { $('#ed-importbox')?.classList.add('hidden'); },
pickImport(id) {
const l = Save.customs().find(o => o.id === id);
if (!l) return;
this.cur = l.id;
this.hydrate(l);
$('#ed-name').value = l.name || '';
this.closeImport();
this.afterFaceSwitch();
this.updateSaveState();
},
// 从存档/数据对象恢复多面 + 每格属性(按扫描顺序回填 messages/turrets/camzones/bosses)
+8 -1
View File
@@ -169,7 +169,13 @@ function nextLevel() {
}
function toLevels() { G.state = 'menu'; UI.show('scr-levels'); Touch.hide(); }
function toTitle() { G.state = 'menu'; UI.show('scr-title'); Touch.hide(); }
function toEditor() { G.state = 'editor'; UI.show('scr-editor'); Touch.hide(); Editor.open(); }
function toEditor() {
G.state = 'editor';
UI.show('scr-editor');
Touch.hide();
// 试玩后返回:保留正在编辑的地图;从未进过编辑器时才新建
if (Editor.faces) Editor.show(); else Editor.open();
}
function pause() {
if (G.state !== 'playing') return;
G.state = 'paused';
@@ -289,6 +295,7 @@ Audio2.enabled = Save.data.audio !== false;
UI.init({
play: (lv) => { G.fromEditor = false; startLevel(lv, lv.builtin === false ? -1 : (lv.index ?? -1)); },
levels: toLevels,
manage: () => UI.setManage(!UI.manage),
help: () => UI.show('scr-help'),
editor: toEditor,
back: toTitle,
+9
View File
@@ -42,6 +42,15 @@ export const Save = {
this.flush();
return lv;
},
/** 更新已保存的自制关卡(按 id 就地替换,不新增);id 不存在则追加 */
updateCustom(lv) {
this.data.customs = this.data.customs || [];
const i = this.data.customs.findIndex(l => l.id === lv.id);
if (i >= 0) this.data.customs[i] = lv;
else this.data.customs.push(lv);
this.flush();
return lv;
},
removeCustom(id) {
this.data.customs = (this.data.customs || []).filter(l => l.id !== id);
this.flush();
+43 -3
View File
@@ -14,6 +14,7 @@ const TABS = [
export const UI = {
H: {}, // handlers
filter: 'all',
manage: false, // 自制页「管理」态:开启后每张自制地图显示删除按钮
cur: 'scr-title',
toastTimer: 0,
@@ -37,6 +38,7 @@ export const UI = {
b.textContent = label;
b.onclick = () => {
this.filter = k;
this.setManage(false); // 切换分类时退出管理模式
$$('#level-tabs .tab').forEach(t => t.classList.remove('on'));
b.classList.add('on');
this.renderLevels();
@@ -49,7 +51,7 @@ export const UI = {
SCREENS.forEach(s => $('#' + s).classList.toggle('hidden', s !== name));
this.cur = name;
$('#hud').classList.toggle('hidden', name !== null && name !== '');
if (name === 'scr-levels') this.renderLevels();
if (name === 'scr-levels') { this.setManage(false); this.renderLevels(); }
},
showGame() {
SCREENS.forEach(s => $('#' + s).classList.add('hidden'));
@@ -64,6 +66,9 @@ export const UI = {
const all = [...LEVELS, ...customs];
const list = all.filter(l => this.filter === 'all' ? true : l.type === this.filter);
// 「管理地图」按钮只在自制分类下显示
this.updateManageBtn();
if (!list.length) {
grid.innerHTML = '<div style="color:#666;padding:20px">这个分类下还没有关卡。去「自制关卡」造一个吧。</div>';
return;
@@ -71,7 +76,7 @@ export const UI = {
list.forEach(l => {
const rec = Save.get(l.id);
const card = document.createElement('div');
card.className = 'lv-card' + (rec ? ' cleared' : '');
card.className = 'lv-card' + (rec ? ' cleared' : '') + (this.manage && l.builtin === false ? ' manage' : '');
const stars = '★'.repeat(l.diff || 1) + '☆'.repeat(Math.max(0, 5 - (l.diff || 1)));
card.innerHTML = `
<div class="idx">${l.builtin === false ? 'U' + (l.no || '') : String(l.index + 1).padStart(2, '0')}</div>
@@ -83,11 +88,46 @@ export const UI = {
<div class="st">${rec ? `✔ 死亡 ${rec.deaths} · ${fmtTime(rec.time)}` : '未通关'}</div>
${rec ? '<div class="chk">✔</div>' : ''}
`;
card.onclick = () => this.H.play(l);
// 管理态下自制卡不再可进入,只保留删除按钮,避免误点开始
if (!(this.manage && l.builtin === false)) card.onclick = () => this.H.play(l);
// 管理态:自制地图显示删除按钮
if (l.builtin === false && this.manage) {
const del = document.createElement('button');
del.className = 'lv-del';
del.textContent = '✕';
del.title = '删除这张地图';
del.onclick = (e) => { e.stopPropagation(); this.delCustom(l); };
card.appendChild(del);
}
grid.appendChild(card);
});
},
/** 刷新「管理地图」按钮:仅在自制分类可见;管理态文案变「完成」并高亮 */
updateManageBtn() {
const mb = $('#manage-btn');
if (!mb) return;
mb.classList.toggle('hidden', this.filter !== 'custom');
mb.textContent = this.manage ? '完成' : '管理地图';
mb.classList.toggle('on', this.manage);
},
/** 切换自制页的管理态(显示/隐藏删除按钮) */
setManage(on) {
// 仅自制分类可进入管理态;非自制分类的开启请求按退出处理(按钮不可见,须自动复位)
if (on && this.filter !== 'custom') on = false;
this.manage = !!on;
this.updateManageBtn();
this.renderLevels();
},
/** 删除一张自制地图(从关卡选择界面管理) */
delCustom(l) {
if (typeof confirm === 'function' && !confirm('删除地图「' + (l.name || 'MY MAP') + '」?此操作不可恢复')) return;
Save.removeCustom(l.id);
this.renderLevels();
},
hud({ name, deaths, ms, apples, totalApples, hasGun, save, totalSaves }) {
$('#hud-level').textContent = name || '';
$('#hud-deaths').textContent = 'DEATH ' + deaths;