微件:LevelEditor:修订间差异
来自植物大战僵尸杂交版Wiki
更多操作
创建页面,内容为“<includeonly> <div class="level-editor-widget"> <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes, maximum-scale=1.5"> <link rel="icon" type="image/x-icon" href="favicon.jpg"> <title>关卡 · Wiki编辑器</title> <style> * { box-sizing: border-box; font-family: system-ui, 'Segoe UI', Roboto, 'Helvetica Neue', 'Noto Sans', sans-serif; } body {…” |
无编辑摘要 |
||
| 第80行: | 第80行: | ||
.card-item input { font-size: 0.8rem; padding: 4px 8px; border-radius: 14px; } | .card-item input { font-size: 0.8rem; padding: 4px 8px; border-radius: 14px; } | ||
.map-grid-container { background: #16222caa; border-radius: 20px; padding: 16px; border: 1px solid #4a7088; margin-bottom: 24px; } | .map-grid-container { background: #16222caa; border-radius: 20px; padding: 16px; border: 1px solid #4a7088; margin-bottom: 24px; } | ||
.map-grid-scroll { overflow-x: auto; max-width: 100%; } | |||
.map-grid-row { display: flex; gap: 2px; margin-bottom: 2px; align-items: center; } | .map-grid-row { display: flex; gap: 2px; margin-bottom: 2px; align-items: center; } | ||
.map-grid-cell { width: | .map-grid-cell { width: 44px; height: 44px; background: #11ff00; border: 1px solid #ffffff; border-radius: 4px; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 0.55rem; color: #000; text-align: center; overflow: hidden; position: relative; transition: all 0.15s; flex-shrink: 0; } | ||
.map-grid-cell:hover { filter: brightness(1.3); transform: scale(1. | .map-grid-cell:hover { filter: brightness(1.3); transform: scale(1.08); z-index: 2; } | ||
.map-grid-cell.placed { background: #4ade80; } | .map-grid-cell.placed { background: #4ade80; } | ||
.map-grid-cell.placed::after { content: '🌱'; font-size: | .map-grid-cell.placed::after { content: '🌱'; font-size: 16px; position: absolute; pointer-events: none; } | ||
.map-grid-label { width: | .map-grid-label { width: 34px; color: #80b8d0; font-size: 0.65rem; text-align: center; flex-shrink: 0; } | ||
.map-toolbar { display: flex; gap: | .map-toolbar { display: flex; gap: 6px; align-items: center; margin-bottom: 10px; flex-wrap: wrap; } | ||
.map-toolbar input { width: | .map-toolbar input { width: 130px; font-size: 0.78rem; padding: 5px 8px; } | ||
.map-toolbar button { padding: | .map-toolbar select { background: #12202a; border: 2px solid #5b80a0; border-radius: 16px; padding: 5px 8px; color: #d8f0ff; font-size: 0.78rem; } | ||
.map-toolbar button { padding: 5px 10px; font-size: 0.78rem; } | |||
.map-toolbar .dim-label { color: #80b8d0; font-size: 0.75rem; margin: 0 2px; } | |||
.map-toolbar .dim-input { width: 50px; text-align: center; } | |||
.cell-popup { display: none; position: fixed; background: #1a2a36; border: 2px solid #80c8e0; border-radius: 16px; padding: 12px; z-index: 100; min-width: 180px; box-shadow: 0 8px 24px rgba(0,0,0,0.6); } | .cell-popup { display: none; position: fixed; background: #1a2a36; border: 2px solid #80c8e0; border-radius: 16px; padding: 12px; z-index: 100; min-width: 180px; box-shadow: 0 8px 24px rgba(0,0,0,0.6); } | ||
.cell-popup input { font-size: 0.8rem; margin-bottom: 6px; } | .cell-popup input { font-size: 0.8rem; margin-bottom: 6px; } | ||
| 第95行: | 第99行: | ||
.tab-btn { padding: 8px 18px; border-radius: 30px; border: 1px solid #80b8d0; background: #1a2a36; color: #c0d8e8; cursor: pointer; font-weight: 600; font-size: 0.9rem; transition: all 0.15s; } | .tab-btn { padding: 8px 18px; border-radius: 30px; border: 1px solid #80b8d0; background: #1a2a36; color: #c0d8e8; cursor: pointer; font-weight: 600; font-size: 0.9rem; transition: all 0.15s; } | ||
.tab-btn.active { background: #3a6a7a; border-color: #c0e8f0; color: #fff; box-shadow: 0 0 12px #80c8e080; } | .tab-btn.active { background: #3a6a7a; border-color: #c0e8f0; color: #fff; box-shadow: 0 0 12px #80c8e080; } | ||
.preset-row { display: flex; gap: 4px; flex-wrap: wrap; margin-bottom: 4px; } | |||
.preset-btn { font-size: 0.7rem; padding: 3px 8px; border-radius: 12px; border: 1px solid #4a7088; background: #1a2a36; color: #80b8d0; cursor: pointer; } | |||
.preset-btn:hover { background: #2a4a5a; color: #c0e8f0; } | |||
</style> | </style> | ||
</head> | </head> | ||
| 第135行: | 第142行: | ||
<label style="margin-bottom:8px;">🗺️ 地图预放置 <i>(点击格子放置/编辑植物,右键清除)</i></label> | <label style="margin-bottom:8px;">🗺️ 地图预放置 <i>(点击格子放置/编辑植物,右键清除)</i></label> | ||
<div class="map-toolbar"> | <div class="map-toolbar"> | ||
<span style="color:#80b8d0; font-size:0. | <span style="color:#80b8d0; font-size:0.75rem;">预设:</span> | ||
<input type=" | <div class="preset-row"> | ||
<input type=" | <button class="preset-btn" data-rows="5" data-cols="9">5×9</button> | ||
<button type="button" class="btn-icon" id=" | <button class="preset-btn" data-rows="6" data-cols="9">6×9</button> | ||
<button type="button" class="btn-icon" id=" | <button class="preset-btn" data-rows="7" data-cols="11">7×11</button> | ||
<button class="preset-btn" data-rows="5" data-cols="7">5×7</button> | |||
<button class="preset-btn" data-rows="3" data-cols="5">3×5</button> | |||
</div> | |||
<span class="dim-label">行:</span><input type="number" id="mapRows" class="dim-input" value="5" min="1" max="15" title="行数"> | |||
<span class="dim-label">列:</span><input type="number" id="mapCols" class="dim-input" value="9" min="1" max="15" title="列数"> | |||
<button type="button" class="btn-icon" id="applySizeBtn">应用尺寸</button> | |||
<span style="color:#80b8d0; font-size:0.7rem; margin-left:4px;">植物:</span> | |||
<input type="text" id="currentPlantFile" value="豌豆射手.png" placeholder="文件名" style="width:110px;"> | |||
<input type="text" id="currentPlantLink" value="豌豆射手" placeholder="链接" style="width:90px;"> | |||
<button type="button" class="btn-icon" id="clearMapBtn">清空</button> | |||
</div> | </div> | ||
<div id="mapGrid"></div> | <div class="map-grid-scroll"><div id="mapGrid"></div></div> | ||
</div> | </div> | ||
| 第222行: | 第239行: | ||
</div> | </div> | ||
<div class="wiki-output" id="wikiOutput"></div> | <div class="wiki-output" id="wikiOutput"></div> | ||
<div class="note">⚡ | <div class="note">⚡ 实时生成。点击格子放置植物,右键清除。可自定义行列数。</div> | ||
</div> | </div> | ||
</div> | </div> | ||
| 第287行: | 第304行: | ||
]; | ]; | ||
// 地图数据: | // 地图数据: 动态行列 | ||
let mapData = Array.from({length: | let mapRows = 5, mapCols = 9; | ||
let mapData = []; | |||
function initMapData() { mapData = Array.from({length:mapRows}, ()=>Array.from({length:mapCols}, ()=>null)); } | |||
initMapData(); | |||
const attrContainer = document.getElementById('attrEditorContainer'); | const attrContainer = document.getElementById('attrEditorContainer'); | ||
| 第310行: | 第331行: | ||
const mapGrid = document.getElementById('mapGrid'); | const mapGrid = document.getElementById('mapGrid'); | ||
const cellPopup = document.getElementById('cellPopup'); | const cellPopup = document.getElementById('cellPopup'); | ||
const mapRowsInput = document.getElementById('mapRows'); | |||
const mapColsInput = document.getElementById('mapCols'); | |||
let currentEditRow = -1, currentEditCol = -1; | let currentEditRow = -1, currentEditCol = -1; | ||
| 第350行: | 第373行: | ||
// ========== 地图预放置 ========== | // ========== 地图预放置 ========== | ||
function resizeMap(newRows, newCols) { | |||
const oldData = mapData; | |||
mapRows = newRows; mapCols = newCols; | |||
mapData = Array.from({length:mapRows}, (_,r)=>Array.from({length:mapCols}, (_,c)=>(r<oldData.length && c<oldData[0].length ? oldData[r][c] : null))); | |||
mapRowsInput.value = mapRows; | |||
mapColsInput.value = mapCols; | |||
renderMapGrid(); | |||
refreshPreview(); | |||
} | |||
function renderMapGrid() { | function renderMapGrid() { | ||
mapGrid.innerHTML = ''; | mapGrid.innerHTML = ''; | ||
const headerRow = document.createElement('div'); headerRow.className='map-grid-row'; | const headerRow = document.createElement('div'); headerRow.className='map-grid-row'; | ||
headerRow.innerHTML = '<div class="map-grid-label"></div>'; | headerRow.innerHTML = '<div class="map-grid-label"></div>'; | ||
for(let c=1;c<= | for(let c=1;c<=mapCols;c++) headerRow.innerHTML += `<div class="map-grid-label">列${c}</div>`; | ||
mapGrid.appendChild(headerRow); | mapGrid.appendChild(headerRow); | ||
for(let r=0;r<mapRows;r++) { | |||
for(let r=0;r< | |||
const row = document.createElement('div'); row.className='map-grid-row'; | const row = document.createElement('div'); row.className='map-grid-row'; | ||
row.innerHTML = `<div class="map-grid-label">行${r+1}</div>`; | row.innerHTML = `<div class="map-grid-label">行${r+1}</div>`; | ||
for(let c=0;c< | for(let c=0;c<mapCols;c++) { | ||
const cell = document.createElement('div'); | const cell = document.createElement('div'); | ||
cell.className = 'map-grid-cell'; | cell.className = 'map-grid-cell'; | ||
if(mapData[r][c]) { | if(mapData[r][c]) { cell.classList.add('placed'); cell.title = mapData[r][c].file; } | ||
cell.addEventListener('click',()=>openCellPopup(r,c)); | cell.addEventListener('click',()=>openCellPopup(r,c)); | ||
cell.addEventListener('contextmenu',(e)=>{ e.preventDefault(); mapData[r][c]=null; renderMapGrid(); refreshPreview(); }); | cell.addEventListener('contextmenu',(e)=>{ e.preventDefault(); mapData[r][c]=null; renderMapGrid(); refreshPreview(); }); | ||
| 第378行: | 第405行: | ||
currentEditRow = r; currentEditCol = c; | currentEditRow = r; currentEditCol = c; | ||
const d = mapData[r][c]; | const d = mapData[r][c]; | ||
document.getElementById('popupFile').value = d ? d.file : ''; | document.getElementById('popupFile').value = d ? d.file : (document.getElementById('currentPlantFile').value || ''); | ||
document.getElementById('popupLink').value = d ? d.link : ''; | document.getElementById('popupLink').value = d ? d.link : (document.getElementById('currentPlantLink').value || ''); | ||
cellPopup.style.display = 'block'; | cellPopup.style.display = 'block'; | ||
const cells = mapGrid.querySelectorAll('.map-grid-cell'); | const cells = mapGrid.querySelectorAll('.map-grid-cell'); | ||
const idx = r* | const idx = r*mapCols + c + mapCols; | ||
if(cells[idx]) { | if(cells[idx]) { | ||
const rect = cells[idx].getBoundingClientRect(); | const rect = cells[idx].getBoundingClientRect(); | ||
| 第403行: | 第429行: | ||
refreshPreview(); | refreshPreview(); | ||
} | } | ||
function clearMap() { | function clearMap() { initMapData(); renderMapGrid(); refreshPreview(); } | ||
function buildMapTable() { | function buildMapTable() { | ||
let hasAny = false; | let hasAny = false; | ||
for(let r=0;r< | for(let r=0;r<mapRows;r++) for(let c=0;c<mapCols;c++) if(mapData[r][c]) hasAny=true; | ||
if(!hasAny) return ''; | if(!hasAny) return ''; | ||
let table = `\n== 地图预放置 ==\n{| style="width:100%;max-width:900px;table-layout:fixed;text-align:center;background:#1a6b0a;border-collapse:collapse;"\n|-\n! !! | let table = `\n== 地图预放置 ==\n{| style="width:100%;max-width:900px;table-layout:fixed;text-align:center;background:#1a6b0a;border-collapse:collapse;"\n|-\n! !!`; | ||
for(let r=0;r< | for(let c=1;c<=mapCols;c++) table += ` 列${c} !!`; | ||
table = table.slice(0,-2) + '\n'; | |||
for(let r=0;r<mapRows;r++) { | |||
table += `|-\n! 行${r+1}\n`; | table += `|-\n! 行${r+1}\n`; | ||
for(let c=0;c< | for(let c=0;c<mapCols;c++) { | ||
const d = mapData[r][c]; | const d = mapData[r][c]; | ||
const content = d ? `[[文件:${d.file}|50px|link=${d.link}]]` : ''; | const content = d ? `[[文件:${d.file}|50px|link=${d.link}]]` : ''; | ||
| 第421行: | 第449行: | ||
} | } | ||
function parseMapTable(src) { | function parseMapTable(src) { | ||
const tableMatch = src.match(/== 地图预放置 ==\s*\n\{\|[\s\S]*?\n\|\}/); | const tableMatch = src.match(/== 地图预放置 ==\s*\n\{\|[\s\S]*?\n\|\}/); | ||
if(!tableMatch) return; | if(!tableMatch) { initMapData(); renderMapGrid(); return; } | ||
// 解析表头列数 | |||
const headerMatch = tableMatch[0].match(/! !!([\s\S]*?)\n\|-/); | |||
let cols = 9; | |||
if(headerMatch) { | |||
const colHeaders = headerMatch[1].match(/列\d+/g); | |||
if(colHeaders) cols = colHeaders.length; | |||
} | |||
const rows = tableMatch[0].split('\n|-\n'); | const rows = tableMatch[0].split('\n|-\n'); | ||
const rowMatches = rows.filter(r=>/! 行\d/.test(r)); | |||
const newRows = rowMatches.length; | |||
if(newRows>0 && cols>0) { | |||
mapRows = newRows; mapCols = cols; | |||
mapRowsInput.value = mapRows; mapColsInput.value = mapCols; | |||
initMapData(); | |||
rowMatches.forEach(row => { | |||
const rm = row.match(/! 行(\d+)/); | |||
if(!rm) return; | |||
const rowIdx = parseInt(rm[1])-1; | |||
if(rowIdx<0||rowIdx>=mapRows) return; | |||
const cells = [...row.matchAll(/\| style="[^"]*" \| (.*?)(?=\n|$)/g)]; | |||
cells.forEach((m,ci) => { | |||
if(ci>=mapCols) return; | |||
const content = m[1].trim(); | |||
const fm = content.match(/\[\[文件:([^|]+)\|[^|]+\|link=([^\]]+)\]\]/); | |||
if(fm) mapData[rowIdx][ci] = {file:fm[1], link:fm[2]}; | |||
}); | |||
}); | }); | ||
}); | } | ||
renderMapGrid(); | |||
} | } | ||
| 第514行: | 第556行: | ||
rewardsContainer.appendChild(div); | rewardsContainer.appendChild(div); | ||
} | } | ||
function clearContainer(c) { while(c.children.length>0) c.removeChild(c.firstChild); } | function clearContainer(c) { while(c.children.length>0) c.removeChild(c.firstChild); } | ||
| 第522行: | 第563行: | ||
const sz = infocardImageSize.value.trim() || '200px'; | const sz = infocardImageSize.value.trim() || '200px'; | ||
const attrs = collectAttrs(); | const attrs = collectAttrs(); | ||
const preplaced = []; | const preplaced = []; | ||
for(let r=0;r< | for(let r=0;r<mapRows;r++) for(let c=0;c<mapCols;c++) if(mapData[r][c]) preplaced.push(`${mapData[r][c].link}(位置:第${r+1}行第${c+1}列)`); | ||
if(preplaced.length>0 && !attrs.some(a=>a.name==='地图预放置')) { | if(preplaced.length>0 && !attrs.some(a=>a.name==='地图预放置')) { | ||
attrs.push({name:'地图预放置', value:preplaced.join('<br>'), color:'red', bold:true}); | attrs.push({name:'地图预放置', value:preplaced.join('<br>'), color:'red', bold:true}); | ||
| 第536行: | 第576行: | ||
return `{{Infocard|图片=${img}|图片尺寸=${sz}|属性=${parts.join(',')}}}`; | return `{{Infocard|图片=${img}|图片尺寸=${sz}|属性=${parts.join(',')}}}`; | ||
} | } | ||
function buildCardsBlock() { | function buildCardsBlock() { | ||
const cards = collectCards(); | const cards = collectCards(); | ||
| 第545行: | 第584行: | ||
return `<div style="float:left" class="pvzhe-card-container">\n${lines.join('\n')}\n</div>`; | return `<div style="float:left" class="pvzhe-card-container">\n${lines.join('\n')}\n</div>`; | ||
} | } | ||
function buildWikiText() { | function buildWikiText() { | ||
const infocard = buildInfocard(); | const infocard = buildInfocard(); | ||
| 第558行: | 第596行: | ||
const rewards = collectRewards(); | const rewards = collectRewards(); | ||
const hf = hardModeFile.value.trim(), hw = hardModeWidth.value.trim()||'250px', hl = hardModeLink.value.trim(); | const hf = hardModeFile.value.trim(), hw = hardModeWidth.value.trim()||'250px', hl = hardModeLink.value.trim(); | ||
let es = ''; | let es = ''; | ||
if(initE.length>0 || prepE.length>0 || startE.length>0) { | if(initE.length>0 || prepE.length>0 || startE.length>0) { | ||
| 第573行: | 第609行: | ||
else startE.forEach(e=>es+=`*${e}<br>\n`); | else startE.forEach(e=>es+=`*${e}<br>\n`); | ||
} | } | ||
let rs = ''; | let rs = ''; | ||
if(rewards.length>0 || hf) { | if(rewards.length>0 || hf) { | ||
| 第582行: | 第616行: | ||
rs += `</div>\n`; | rs += `</div>\n`; | ||
} | } | ||
let headerBlock = `{{返回关卡列表}} | let headerBlock = `{{返回关卡列表}} | ||
<div style="float:right; margin-left:0px; width:100%; max-width:650px; overflow:hidden; display:flex; align-items:center;"> | <div style="float:right; margin-left:0px; width:100%; max-width:650px; overflow:hidden; display:flex; align-items:center;"> | ||
| 第590行: | 第622行: | ||
[[File:${mf}|${mw}|link=${ml}]] | [[File:${mf}|${mw}|link=${ml}]] | ||
</div>`; | </div>`; | ||
if (cardsBlock) headerBlock += `\n${cardsBlock}`; | if (cardsBlock) headerBlock += `\n${cardsBlock}`; | ||
if (mapTable) headerBlock += `\n${mapTable}`; | if (mapTable) headerBlock += `\n${mapTable}`; | ||
headerBlock += `\n</div>\n</div>`; | headerBlock += `\n</div>\n</div>`; | ||
return `${headerBlock} | return `${headerBlock} | ||
${infocard} | ${infocard} | ||
${es}${rs}`; | ${es}${rs}`; | ||
} | } | ||
function refreshPreview() { | function refreshPreview() { | ||
const t = buildWikiText(); | const t = buildWikiText(); | ||
| 第625行: | 第654行: | ||
const bm=value.match(/^'''(.+)'''$/); | const bm=value.match(/^'''(.+)'''$/); | ||
if(bm){ bold=true; value=bm[1]; } | if(bm){ bold=true; value=bm[1]; } | ||
if(name!=='地图预放置') attrs.push({name,value,color,bold}); | if(name!=='地图预放置') attrs.push({name,value,color,bold}); | ||
}); | }); | ||
} | } | ||
| 第669行: | 第698行: | ||
if(d.hardWidth) hardModeWidth.value=d.hardWidth; | if(d.hardWidth) hardModeWidth.value=d.hardWidth; | ||
if(d.hardLink) hardModeLink.value=d.hardLink; | if(d.hardLink) hardModeLink.value=d.hardLink; | ||
const src = importTextarea.value; | const src = importTextarea.value; | ||
if(src) parseMapTable(src); | if(src) parseMapTable(src); | ||
renderMapGrid(); | else { renderMapGrid(); } | ||
refreshPreview(); | refreshPreview(); | ||
} | } | ||
| 第679行: | 第707行: | ||
const d=parseWikiSource(src); | const d=parseWikiSource(src); | ||
if(d){ | if(d){ | ||
importTextarea.value = src; | importTextarea.value = src; | ||
applyParsedData(d); | applyParsedData(d); | ||
alert('✅ 导入成功!'); | alert('✅ 导入成功!'); | ||
| 第703行: | 第731行: | ||
document.getElementById('pasteToImportBtn').addEventListener('click',()=>importFromSource(buildWikiText())); | document.getElementById('pasteToImportBtn').addEventListener('click',()=>importFromSource(buildWikiText())); | ||
importModal.addEventListener('click',e=>{ if(e.target===importModal) importModal.style.display='none'; }); | importModal.addEventListener('click',e=>{ if(e.target===importModal) importModal.style.display='none'; }); | ||
// | // 地图尺寸 | ||
document.getElementById('applySizeBtn').addEventListener('click',()=>{ | |||
let nr = parseInt(mapRowsInput.value)||5; | |||
let nc = parseInt(mapColsInput.value)||9; | |||
nr = Math.max(1,Math.min(15,nr)); | |||
nc = Math.max(1,Math.min(15,nc)); | |||
resizeMap(nr, nc); | |||
}); | |||
// 预设按钮 | |||
document.querySelectorAll('.preset-btn').forEach(btn=>{ | |||
btn.addEventListener('click',()=>{ | |||
const nr = parseInt(btn.dataset.rows); | |||
const nc = parseInt(btn.dataset.cols); | |||
if(nr&&nc) resizeMap(nr, nc); | |||
}); | |||
}); | |||
document.getElementById('clearMapBtn').addEventListener('click',clearMap); | document.getElementById('clearMapBtn').addEventListener('click',clearMap); | ||
document.getElementById('popupSave').addEventListener('click',saveCellPopup); | document.getElementById('popupSave').addEventListener('click',saveCellPopup); | ||
document.getElementById('popupClear').addEventListener('click',()=>{ | document.getElementById('popupClear').addEventListener('click',()=>{ | ||
| 第743行: | 第779行: | ||
clearContainer(rewardsContainer); | clearContainer(rewardsContainer); | ||
createRewardItem('钱袋子.png', '钱袋子'); | createRewardItem('钱袋子.png', '钱袋子'); | ||
initMapData(); | |||
mapData[2][0] = {file:'豌豆射手.png', link:'豌豆射手'}; | mapData[2][0] = {file:'豌豆射手.png', link:'豌豆射手'}; | ||
renderMapGrid(); | renderMapGrid(); | ||