打开/关闭搜索
搜索
打开/关闭菜单
933
1525
112
1.4万
植物大战僵尸杂交版Wiki
导航
首页
最近更改
随机页面
MediaWiki帮助
特殊页面
上传文件
打开/关闭外观设置菜单
notifications
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。
user-interface-preferences
个人工具
创建账号
登录
查看“︁微件:LevelAnalyzer”︁的源代码
来自植物大战僵尸杂交版Wiki
查看
阅读
查看源代码
查看历史
associated-pages
微件
讨论
更多操作
←
微件:LevelAnalyzer
因为以下原因,您没有权限编辑该页面:
您请求的操作仅限属于该用户组的用户执行:
用户
您没有权限编辑
微件
命名空间内的页面。
您可以查看和复制此页面的源代码。
<includeonly> <div class="level-analyzer-widget"> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes, viewport-fit=cover, maximum-scale=5.0"> <title>🌻 植物大战僵尸杂交版 · 关卡分析器 Pro 6.3</title> <style> :root { --bg-dark: #1a2c1e; --wood: #6b4f2c; --gold: #d4a745; --light-bg: #f9f3d9; --card-bg: #fffbe6; --green-btn: #5f7a36; --blue-btn: #2c5aa0; --danger: #c44; } * { margin: 0; padding: 0; box-sizing: border-box; } html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; -webkit-tap-highlight-color: transparent; } body { background: var(--bg-dark); font-family: 'Segoe UI', 'Roboto', 'Noto Sans SC', system-ui, sans-serif; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 1rem; } .container { max-width: 1400px; width: 100%; background: linear-gradient(165deg, #f9f3d9, #e8d9a0, #dcc880); border: 4px solid var(--wood); border-radius: 1.5rem; box-shadow: 0 20px 40px rgba(0,0,0,0.6); padding: 1rem; } .header { text-align: center; margin-bottom: 0.8rem; } h1 { font-size: 1.4rem; font-weight: 900; color: #3e2b15; text-shadow: 2px 2px 0 var(--gold); } h1 .badge { background: #5c3b1a; color: #ffda7a; font-size: 0.7rem; padding: 0.2rem 0.9rem; border-radius: 2rem; } .drop-zone { background: rgba(218,194,130,0.4); border: 3px dashed #8b6b3c; border-radius: 1.2rem; padding: 1rem; margin-bottom: 0.8rem; text-align: center; cursor: pointer; min-height: 80px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.3rem; } .drop-zone.drag-over { background: rgba(180,210,120,0.5); border-color: #5f7a36; border-style: solid; } .drop-zone-icon { font-size: 2rem; } .file-zone { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 0.5rem; margin-bottom: 0.8rem; } .file-card { background: var(--card-bg); border-radius: 0.7rem; padding: 0.5rem; box-shadow: 0 4px 12px rgba(0,0,0,0.15); border: 2px solid #bb9740; text-align: center; } .file-card:hover { border-color: #d4a745; } .file-card h3 { font-size: 0.7rem; color: #3d2c15; margin-bottom: 0.2rem; display: flex; align-items: center; justify-content: center; gap: 0.2rem; } .file-card input[type="file"] { display: none; } .file-label { display: inline-block; background: #e0ba75; padding: 0.3rem 0.6rem; border-radius: 0.8rem; font-weight: 700; cursor: pointer; font-size: 0.65rem; border: 2px solid #b8872e; width: 100%; max-width: 130px; text-align: center; } .file-label:hover { background: #cfaa54; } .file-label.loaded { background: #7cb342; color: #fff; border-color: #4a7a1a; } .status-dot { display: inline-block; width: 0.5rem; height: 0.5rem; border-radius: 50%; flex-shrink: 0; } .status-dot.ready { background: #3cb043; box-shadow: 0 0 6px #3cb043; } .status-dot.missing { background: #ccc; } .file-name-display { font-size: 0.55rem; color: #6b4f2c; margin-top: 0.2rem; word-break: break-all; max-height: 2.5em; overflow-y: auto; } .btn { background: var(--green-btn); border: none; color: white; font-weight: 700; padding: 0.4rem 0.8rem; border-radius: 1.5rem; font-size: 0.7rem; cursor: pointer; box-shadow: 0 3px 6px rgba(0,0,0,0.25); border-bottom: 3px solid #334d1a; display: inline-flex; align-items: center; gap: 0.3rem; white-space: nowrap; } .btn:disabled { background: #a5a5a5; cursor: not-allowed; opacity: 0.6; } .btn:hover:not(:disabled) { background: #719541; } .btn-export { background: var(--blue-btn); border-bottom-color: #1a3a6a; } .btn-cache { background: #6b4f2c; font-size: 0.6rem; } .level-input-area { background: rgba(228,209,155,0.7); border-radius: 0.7rem; padding: 0.5rem; margin: 0.5rem 0; display: flex; align-items: center; flex-wrap: wrap; gap: 0.4rem; border: 2px solid #b18530; } .export-name-input { padding: 0.3rem 0.5rem; border-radius: 1rem; border: 2px solid #b8872e; font-size: 0.7rem; background: var(--card-bg); width: 160px; outline: none; } .result-panel { background: #fef9e8; border: 3px solid #7c6033; border-radius: 0.7rem; padding: 0.6rem; min-height: 200px; max-height: 500px; overflow-y: auto; font-size: 0.7rem; scroll-behavior: smooth; } .info-grid { display: flex; flex-wrap: wrap; gap: 0.3rem; margin-bottom: 0.4rem; } .info-badge { background: #c6a45b; padding: 0.15rem 0.5rem; border-radius: 0.6rem; font-weight: 700; font-size: 0.65rem; white-space: nowrap; } .info-badge.survival{background:#5b8c3e;color:#fff}.info-badge.endless{background:#9b4a2e;color:#fff;animation:pulse-glow 2s infinite} .info-badge.shooting{background:#c44;color:#fff}.info-badge.vase{background:#6a4c1a;color:#fff}.info-badge.izm{background:#3a3060;color:#fff} .info-badge.star{background:#d4a017;color:#1a1a1a}.info-badge.shuffle-anim{background:#c44;color:#fff;animation:pulse 1.5s infinite} .info-badge.dynamic{background:#6b3a8a;color:#fff}.info-badge.shuffle{background:#8b3a6a;color:#fff}.info-badge.gem{background:#e04090;color:#fff} .info-badge.conveyor{background:#e67e22;color:#fff} @keyframes pulse{0%,100%{opacity:1}50%{opacity:0.6}}@keyframes pulse-glow{0%,100%{box-shadow:0 0 5px #9b4a2e}50%{box-shadow:0 0 15px #ff6b3a}} .zombie-tag,.plant-tag{display:inline-block;padding:0.12rem 0.4rem;border-radius:0.4rem;margin:0.08rem;font-size:0.6rem;word-break:break-all;max-width:100%} .zombie-tag{background:#2e3b1f;color:#f5e7c6}.plant-tag{background:#2c5c2c;color:#f2f2c0} .reward-block{background:#faf0c8;border:2px solid #d4a017;border-radius:0.5rem;padding:0.3rem;margin:0.25rem 0;font-size:0.65rem} .event-block{background:#f5e8c9;border:1px solid #c9a24b;border-radius:0.5rem;padding:0.3rem;margin:0.25rem 0;font-size:0.65rem} .event-block .event-header{font-weight:700;color:#6b3a1a;display:flex;align-items:baseline;gap:0.2rem;flex-wrap:wrap} .event-block .event-detail{color:#4a3020;font-size:0.6rem;margin-top:0.08rem} .section-title{font-weight:700;font-size:0.75rem;margin-top:0.35rem;color:#3d2e12;border-bottom:2px solid #c9a24b;padding-bottom:0.12rem;display:flex;align-items:center;gap:0.25rem} .event-count{background:#8b5a2b;color:#fff;border-radius:0.3rem;padding:0.04rem 0.3rem;font-size:0.55rem;flex-shrink:0} .grid-container{overflow-x:auto;-webkit-overflow-scrolling:touch;max-width:100%} .grid-row{display:flex} .grid-cell{width:clamp(1.1rem,2.8vw,1.8rem);height:clamp(1.1rem,2.8vw,1.8rem);line-height:clamp(1.1rem,2.8vw,1.8rem);text-align:center;background:#e9dbb0;border-radius:0.2rem;margin:1px;font-size:clamp(0.3rem,0.7vw,0.45rem);font-weight:bold;border:1px solid #8b7a2b;color:#3d2e12;overflow:hidden;cursor:default;position:relative;transition:transform 0.15s;flex-shrink:0} .grid-cell:hover{transform:scale(1.12);z-index:10} .grid-cell.preplant{background:#5a8a3c;color:#fff}.grid-cell.preplant-stacked{background:#3a6a2c;color:#fff} .grid-cell.vase-plant{background:#4a9a3c;color:#fff}.grid-cell.vase-zombie{background:#8b3a3a;color:#fff}.grid-cell.vase-normal{background:#b8863a;color:#fff} .grid-cell.lookstar{background:#d4a017;color:#1a1a1a}.grid-cell.izm-plant{background:#5c3a8a;color:#e8d9ff}.grid-cell.izm-plant-stacked{background:#3c1a6a;color:#e8d9ff} .grid-cell .shuffle-marker{position:absolute;top:0;right:1px;font-size:0.3rem;color:#ff0;text-shadow:0 0 3px #000;font-weight:bold;line-height:1;pointer-events:none} .grid-legend{font-size:clamp(0.35rem,0.8vw,0.5rem);margin:0.08rem 0;color:#6b4f2c} .col-header,.row-label{text-align:center;color:#5a432b}.col-header{width:clamp(1.1rem,2.8vw,1.8rem);font-size:clamp(0.3rem,0.7vw,0.45rem);flex-shrink:0} .row-label{width:0.7rem;font-size:clamp(0.3rem,0.7vw,0.45rem);line-height:clamp(1.1rem,2.8vw,1.8rem);flex-shrink:0} .grid-col-headers{display:flex;margin-left:0.7rem} .upgrade-line{background:#f0e2b0;border:2px solid #c9a24b;border-radius:0.5rem;padding:0.3rem;margin:0.25rem 0;font-size:0.65rem} .gem-upgrade-line{background:#f8e0f0;border:2px solid #e04090} .upgrade-step{display:inline-block;background:#5c3b1a;color:#ffda7a;padding:0.08rem 0.35rem;border-radius:0.35rem;margin:0.06rem;font-size:0.6rem} .arrow{color:#8b5a2b;font-weight:bold;margin:0 0.08rem} .toast{position:fixed;top:20px;right:20px;background:#333;color:#fff;padding:0.5rem 1rem;border-radius:0.7rem;font-size:0.75rem;z-index:9999;opacity:0;transform:translateX(100%);transition:all 0.3s ease;pointer-events:none;box-shadow:0 4px 12px rgba(0,0,0,0.3);max-width:90vw} .toast.show{opacity:1;transform:translateX(0)}.toast.success{background:#5f7a36}.toast.error{background:#c44} .zombie-pool-section{background:#f0e6d0;border:1px dashed #8b6b3c;border-radius:0.4rem;padding:0.25rem 0.4rem;margin:0.15rem 0;font-size:0.6rem;color:#5a3d1a} .table-container{overflow-x:auto;max-width:100%} .vase-table{width:100%;border-collapse:collapse;font-size:0.6rem;margin:0.25rem 0;min-width:400px} .vase-table th{background:#8b6b2c;color:#fff;padding:0.15rem;border:1px solid #5a3d1a;font-size:0.55rem;position:sticky;top:0;z-index:5} .vase-table td{padding:0.12rem;border:1px solid #b8943a;text-align:center;word-break:break-word} .vase-table tr:nth-child(even){background:#f5e8c9}.vase-table tr:nth-child(odd){background:#fef9e8}.vase-table tr:hover{background:#e8d5a0} .tutorial-box{background:#e8f0f8;border:2px solid #3498db;border-radius:0.5rem;padding:0.4rem;margin:0.3rem 0;font-size:0.65rem} .tutorial-box .tutorial-title{font-weight:700;color:#2471a3} .conveyor-section{background:#fdf2e9;border:2px solid #e67e22;border-radius:0.5rem;padding:0.4rem;margin:0.25rem 0;font-size:0.65rem} .loading-spinner{display:inline-block;width:0.9rem;height:0.9rem;border:2px solid rgba(255,255,255,0.3);border-top-color:#fff;border-radius:50%;animation:spin 0.6s linear infinite;vertical-align:middle;margin-right:0.25rem} @keyframes spin{to{transform:rotate(360deg)}} @media(max-width:768px){body{padding:0.3rem}.container{border-radius:1rem;border-width:3px}.file-zone{grid-template-columns:repeat(auto-fit,minmax(110px,1fr));gap:0.3rem}.level-input-area{justify-content:center}.grid-cell{width:1rem;height:1rem;line-height:1rem;font-size:0.28rem}.col-header{width:1rem;font-size:0.28rem}.toast{top:auto;bottom:15px;right:8px;left:8px;max-width:none;transform:translateY(100%)}.toast.show{transform:translateY(0)}} </style> </head> <body> <div class="container"> <div class="header"><h1>🌻 杂交版关卡分析器 <span class="badge">PVZ HE Pro 6.3</span></h1><p style="font-size:0.7rem;color:#5a432b;">⚡ 传送带显示 · 宝石迷阵 · IZM打乱 · 完整事件</p></div> <div class="drop-zone" id="dropZone"><div class="drop-zone-icon">📂</div><div class="drop-zone-text">拖拽文件到此处上传</div><div style="font-size:0.6rem;color:#7a5c33;">options.json / templates.json / 关卡.json / 生存配置.json</div></div> <div class="file-zone"> <div class="file-card"><h3><span class="status-dot missing" id="optDot"></span> options.json</h3><label class="file-label" for="optInput" id="optLabel">📁 选择文件</label><input type="file" id="optInput" accept=".json"><div class="file-name-display" id="optName">未选择</div></div> <div class="file-card"><h3><span class="status-dot missing" id="tplDot"></span> templates.json</h3><label class="file-label" for="tplInput" id="tplLabel">📁 选择文件</label><input type="file" id="tplInput" accept=".json"><div class="file-name-display" id="tplName">未选择</div></div> <div class="file-card"><h3>🎯 关卡文件</h3><label class="file-label" for="levelInput" id="levelLabel">📤 上传关卡</label><input type="file" id="levelInput" accept=".json"><div class="file-name-display" id="levelFileName">未选择</div></div> <div class="file-card"><h3><span class="status-dot missing" id="surDot"></span> 生存配置</h3><label class="file-label" for="surInput" id="surLabel">📁 选择文件</label><input type="file" id="surInput" accept=".json" multiple><div class="file-name-display" id="surName">未选择</div></div> </div> <div class="level-input-area"> <button class="btn btn-cache" id="cacheBtn">📦 缓存加载</button> <button class="btn" id="analyzeBtn" disabled>🔍 解析关卡</button> <span style="font-weight:600;font-size:0.65rem;">📥 导出名:</span> <input type="text" class="export-name-input" id="exportFileName" placeholder="自定义文件名" value="" spellcheck="false"> <button class="btn btn-export" id="exportBtn" disabled>📥 导出CSV</button> <button class="btn" id="resetBtn" style="background:var(--danger);border-bottom-color:#8b1a1a;">🔄 重置</button> </div> <div id="output" class="result-panel">🌿 请上传 options.json 和 templates.json,然后选择或拖入关卡文件并点击解析。<br><span style="font-size:0.6rem;color:#8b6b3c;">💡 可直接拖拽JSON文件到上方区域 | 📦 点击"缓存加载"恢复上次文件</span></div> </div> <div class="toast" id="toast"></div> <script> (function(){'use strict'; const $=id=>document.getElementById(id); const S={optionsData:null,templatesData:null,levelData:null,survivalConfigs:new Map(),lastMaps:null,isAnalyzing:false,MAX_FILE_SIZE:50*1024*1024,TIMEOUT:15000,CACHE_KEY:'pvz_cache_v14'}; let tt=null,_lm=''; function toast(m,t){if(!m||m===_lm)return;_lm=m;const el=$('toast');if(tt)clearTimeout(tt);el.textContent=m;el.className='toast '+(t||'')+' show';tt=setTimeout(()=>{el.classList.remove('show');tt=null;_lm='';},2500);} function saveCache(){try{const c={opt:S.optionsData,tpl:S.templatesData,surv:Array.from(S.survivalConfigs.entries())};const j=JSON.stringify(c);if(j.length<6*1024*1024)localStorage.setItem(S.CACHE_KEY,j);}catch(e){}} function loadCache(){try{const s=localStorage.getItem(S.CACHE_KEY);if(!s)return false;const c=JSON.parse(s);if(c.opt)S.optionsData=c.opt;if(c.tpl)S.templatesData=c.tpl;if(Array.isArray(c.surv))S.survivalConfigs=new Map(c.surv);return!!(c.opt||c.tpl);}catch(e){return false;}} const dZE=$('dropZone');function pd(e){e.preventDefault();e.stopPropagation();} if(dZE){dZE.addEventListener('click',()=>{$('levelInput')?.click();});['dragenter','dragover','dragleave','drop'].forEach(ev=>dZE.addEventListener(ev,pd,false));let dc=0;dZE.addEventListener('dragenter',()=>{dc++;dZE.classList.add('drag-over');},false);dZE.addEventListener('dragleave',()=>{dc--;if(dc<=0){dc=0;dZE.classList.remove('drag-over');}},false);dZE.addEventListener('drop',(e)=>{dc=0;dZE.classList.remove('drag-over');hD(e);},false);} async function hD(e){const dt=e.dataTransfer;if(!dt?.files||!dt.files.length){toast('请拖入JSON文件','error');return;}const files=[...dt.files].filter(f=>(f.name||'').toLowerCase().endsWith('.json'));if(!files.length){toast('仅支持.json','error');return;}let lc=0;const rs=await Promise.allSettled(files.map(f=>rF(f).then(dt=>({file:f,data:dt}))));rs.forEach(r=>{if(r.status==='fulfilled'){const{file,data}=r.value;const nl=(file.name||'').toLowerCase();if(nl.includes('option')){S.optionsData=data;$('optName').textContent='✅ '+file.name;lc++;}else if(nl.includes('template')){S.templatesData=data;$('tplName').textContent='✅ '+file.name;lc++;}else if(data.ZombiePoolSetting||data.RoundLimit!==undefined){S.survivalConfigs.set(file.name.replace('.json',''),data);$('surName').textContent='✅ '+S.survivalConfigs.size+'个';lc++;}else{S.levelData=data;$('levelFileName').textContent='✅ '+file.name;lc++;}}});if(lc>0){updateUI();saveCache();$('output').innerHTML='✅ 成功加载 '+lc+' 个文件';}} document.body.addEventListener('dragover',pd,false); function rF(file){return new Promise((resolve,reject)=>{if(!file||!(file instanceof Blob))return reject(new Error('无效文件'));if(!file.name)return reject(new Error('文件名缺失'));if(file.size===0)return reject(new Error('空文件'));if(file.size>S.MAX_FILE_SIZE)return reject(new Error('文件过大'));const reader=new FileReader();const timeout=setTimeout(()=>{reader.abort();reject(new Error('读取超时'));},S.TIMEOUT);reader.onload=(e)=>{clearTimeout(timeout);try{const text=e.target.result;if(!text||(typeof text==='string'&&text.trim().length===0))return reject(new Error('内容为空'));let data;try{data=JSON.parse(text);}catch(pe){return reject(new Error('JSON格式错误'));}if(data===null||typeof data!=='object')return reject(new Error('不是有效JSON'));resolve(data);}catch(err){reject(new Error('处理失败'));}};reader.onerror=()=>{clearTimeout(timeout);reject(new Error('读取失败'));};reader.onabort=()=>{clearTimeout(timeout);reject(new Error('被中断'));};try{reader.readAsText(file,'UTF-8');}catch(e){clearTimeout(timeout);reject(new Error('无法读取'));}});} function updateUI(){const oR=!!S.optionsData,tR=!!S.templatesData,lR=!!S.levelData;['optDot','tplDot'].forEach(id=>{const el=$(id);if(el)el.className='status-dot '+((id==='optDot'?oR:tR)?'ready':'missing');});const sD=$('surDot');if(sD)sD.className='status-dot '+(S.survivalConfigs.size>0?'ready':'missing');['optLabel','tplLabel','surLabel','levelLabel'].forEach(id=>{const el=$(id);if(el)el.className='file-label'+((id==='optLabel'?oR:id==='tplLabel'?tR:id==='surLabel'?S.survivalConfigs.size>0:lR)?' loaded':'');});const ab=$('analyzeBtn'),eb=$('exportBtn');if(ab)ab.disabled=!(oR&&tR&&lR)||S.isAnalyzing;if(eb)eb.disabled=!(oR&&tR&&lR&&!!S.lastMaps);} function setupFileInput(inputEl,setDataFn,nameSpan){if(!inputEl||!nameSpan)return;inputEl.addEventListener('change',function(){const file=inputEl.files?.[0];if(!file)return;const fileName=(file.name||'').toLowerCase();if(!fileName.endsWith('.json')){toast('仅支持.json','error');inputEl.value='';return;}nameSpan.textContent='⏳ 加载中...';rF(file).then(data=>{setDataFn(data);nameSpan.textContent='✅ '+file.name;updateUI();saveCache();toast('已加载:'+file.name,'success');}).catch(err=>{toast('❌ '+(err.message||'加载失败'),'error');nameSpan.textContent='加载失败';updateUI();}).finally(()=>{inputEl.value='';});});} function setupSurvivalInput(inputEl){if(!inputEl)return;inputEl.addEventListener('change',async function(){const files=[...inputEl.files].filter(f=>f.name.endsWith('.json'));for(const file of files){try{const data=await rF(file);S.survivalConfigs.set(file.name.replace('.json',''),data);}catch(err){toast('❌ '+file.name+': '+err.message,'error');}}if(S.survivalConfigs.size>0){$('surName').textContent='✅ '+S.survivalConfigs.size+'个';}updateUI();saveCache();inputEl.value='';});} $('cacheBtn')?.addEventListener('click',()=>{if(loadCache()){$('optName').textContent=S.optionsData?'✅ 缓存':'未选择';$('tplName').textContent=S.templatesData?'✅ 缓存':'未选择';$('surName').textContent=S.survivalConfigs.size>0?'✅ '+S.survivalConfigs.size+'个':'未选择';updateUI();toast('已从缓存加载','success');}else toast('缓存为空','error');}); $('resetBtn')?.addEventListener('click',()=>{if(confirm('重置所有数据?')){S.optionsData=null;S.templatesData=null;S.levelData=null;S.survivalConfigs.clear();S.lastMaps=null;['optName','tplName','levelFileName','surName'].forEach(id=>{const el=$(id);if(el)el.textContent='未选择';});$('exportFileName').value='';$('output').innerHTML='🌿 已重置。';updateUI();}}); function esc(t){if(t==null)return'';return String(t).replace(/[&<>"]/g,m=>({'&':'&','<':'<','>':'>','"':'"'})[m]);} function gMS(mv){if(!S.optionsData?.map)return{cols:9,rows:5};const mi=S.optionsData.map.find(m=>m.value===mv);if(mi?.size){const p=String(mi.size).split('x');const c=parseInt(p[0],10),r=parseInt(p[1],10);return{cols:(!isNaN(c)&&c>0&&c<=20)?c:9,rows:(!isNaN(r)&&r>0&&r<=10)?r:5};}return{cols:9,rows:5};} function pGP(gp){if(!Array.isArray(gp)||gp.length<2)return null;const c=Math.round(Number(gp[0])),r=Math.round(Number(gp[1]));if(isNaN(c)||isNaN(r)||c<1||r<1)return null;return{col:c,row:r};} function bM(){const m={plant:new Map(),zombie:new Map(),zombieExtra:new Map(),item:new Map(),map:new Map(),bgm:new Map(),projectile:new Map(),collectable:new Map()};if(!S.optionsData)return m;const sa=arr=>Array.isArray(arr)?arr:[];[['plant',m.plant],['zombie',m.zombie],['zombieExtra',m.zombieExtra],['item',m.item],['bgm',m.bgm],['projectile',m.projectile],['collectable',m.collectable]].forEach(([k,t])=>{sa(S.optionsData[k]).forEach(i=>{if(i&&i.value!=null)t.set(String(i.value),i.label||String(i.value));});});sa(S.optionsData.map).forEach(mp=>{if(mp&&mp.value!=null)m.map.set(String(mp.value),{label:mp.label||String(mp.value),size:mp.size||'9x5'});});return m;} function gWI(l){const wm=l.WaveManager||l.Feature?.find(f=>f.Name==='Wave')?.Data;if(!wm?.Wave||!Array.isArray(wm.Wave)||wm.Wave.length===0)return null;const tw=wm.Wave.length,fi=wm.FlagWaveInterval||6;if(typeof fi!=='number'||fi<=0||!isFinite(fi))return{totalWaves:tw,flagInterval:6,flagCount:1};return{totalWaves:tw,flagInterval:fi,flagCount:Math.floor((tw-1)/fi)+1};} function tN(n,m){if(!n||typeof n!=='string')return'';const t=n.trim();if(!t)return'';return m.plant.get(t)||m.zombie.get(t)||m.zombieExtra.get(t)||m.item.get(t)||m.collectable.get(t)||t;} function pN(pv,m){return pv?(m.projectile.get(pv)||pv):'';}function zL(v,m){if(!v||typeof v!=='string')return'';return m.zombie.get(v)||m.zombieExtra.get(v)||v;} function pL(v,m){if(!v||typeof v!=='string')return'';return m.plant.get(v)||v;}function aL(v,m){if(!v||typeof v!=='string')return'';return m.plant.get(v)||m.zombie.get(v)||m.zombieExtra.get(v)||m.item.get(v)||v;} function pR(rw,m){if(!rw?.RewardType||rw.RewardType==='Noone')return'无奖励';const ty=rw.RewardType,f=rw.RewardFirst||'';let r='';switch(ty){case'Coin':case'COIN':r='💰 金币奖励';if(f)r+=`:首次${f}`;break;case'Packet':r='🎴 卡牌奖励';if(f)r+=`:${tN(f,m)}`;break;case'Collectable':r='📦 收藏品奖励';if(f)r+=`:${tN(f,m)}`;break;default:r=`🎁 ${ty}`;if(f)r+=` (${tN(f,m)})`;}return r;} const mFL={'LineUseSet':'设置出怪行状态','UseStripe':'限制种植列(红线分隔)','Row1Create':'创建3行草皮','ShowRow3':'裸地第二三四行显示草皮','Row5CreateFromRow3':'在第一五行创建草皮','ClearAll':'清除所有对象'}; const sTL={'Default':'正常冒出','Bungi':'蹦极放置'}; const cL={'White':'白卡','Gold':'金卡','Diamond':'钻卡','Colour':'彩卡','Star':'星卡','Original':'原卡+紫卡'}; function eDD(ev,m){if(!ev||typeof ev!=='object')return'未知事件';const n=ev.EventName||'';const v=ev.Value||{};switch(n){case'GravestoneCreateRandom':{const ns=Array.isArray(v.GravestoneNames)?v.GravestoneNames.map(x=>aL(x,m)).join('、'):'?';return`随机生成物体:${ns} ×${v.GravestoneNum||'?'}`;}case'GravestoneSpawnZombie':{const ns=Array.isArray(v.ZombieNames)?v.ZombieNames.map(x=>zL(x,m)).join('、'):'?';return`墓碑生成僵尸:${ns} ×${v.ZombieNum||'?'}`;}case'GridSpawnZombie':{const ns=Array.isArray(v.ZombieNames)?v.ZombieNames.map(x=>zL(x,m)).join('、'):'?';let d=`图格生成僵尸:${ns} ×${v.ZombieNum||'?'}`;if(v.SpawnPos)d+=`,区域:[${v.SpawnPos.x??'?'},${v.SpawnPos.y??'?'}]~[${v.SpawnPos.z??'?'},${v.SpawnPos.w??'?'}]`;if(v.SpawnType)d+=`,${sTL[v.SpawnType]||v.SpawnType}`;return d;}case'BungiSpawnZombie':{const ns=Array.isArray(v.ZombieNames)?v.ZombieNames.map(x=>zL(x,m)).join('、'):'?';let d=`蹦极放置僵尸:${ns} ×${v.ZombieNum||'?'}`;if(v.hypnoses)d+=',魅惑';return d;}case'CurrentMapUseWarningLine':return`设置警戒线:第${v.Row??'?'}列`;case'CurrentMapUseStripe':return`限制种植列(红线分隔),左侧列数:${v.Row??'?'}`;case'CreateProtal':return`创建传送门${v.Shape?',样式:'+esc(String(v.Shape)):''}`;case'TipsPlay':{const t=v.Text;const d=v.Duration;let ds='显示提示';if(t!==undefined&&t!==null&&String(t).trim()!=='')ds+=`:"${esc(String(t))}"`;if(d)ds+=`,持续${d}秒`;return ds;}case'SunCreate':{let d=`生成阳光 ×${v.Num||'?'}`;if(v.FromPacket)d+=`(继承卡牌×${v.FromPacketPercentage||1})`;if(v.ByCamp)d+=',跟随阵营';return d;}case'PacketCreate':{let d='创建角色卡牌';if(v.PacketName)d+=`:${aL(v.PacketName,m)}`;else if(v.PacketBankName){d+=`:从选卡范围[${v.PacketBankName}]随机`;if(v.CategoryName)d+=`,品质:${cL[v.CategoryName]||v.CategoryName}`;}if(v.AliveTime)d+=`,存在${v.AliveTime}秒`;return d;}case'PacketSpawn':{let d='生成角色';if(v.PacketName)d+=`:${aL(v.PacketName,m)}`;if(v.Percentage!=null&&v.Percentage<1)d+=`,概率${Math.round(v.Percentage*100)}%`;if(v.ByCamp)d+=',跟随阵营';return d;}case'SetGameMode':return`改变模式:${v.GameMode||'?'}`;case'CurrentMapCharacterClear':return'清除场景所有对象';case'CurrentMapFunctionExecute':{const fn=v.FunctionName||'';const fl=mFL[fn]||(fn||'未知行为');if(fn==='UseStripe'){const cn=Array.isArray(v.Value)?v.Value[0]:(v.Value??'?');return`地图行为:${fl},列数:${cn??'?'}`;}if(fn==='LineUseSet'){const rn=Array.isArray(v.Value)?v.Value[0]:'?';const en=Array.isArray(v.Value)&&v.Value.length>1?v.Value[1]:true;return`地图行为:${fl},行${rn}${en?'允许出怪':'禁止出怪'}`;}return`地图行为:${fl}`;}case'MapChange':{const mn=m.map.get(v.MapName)?.label||v.MapName||'?';return`切换地图:${mn}`;}case'AddPacket':return`传送带增加卡牌:${aL(v.Name,m)},权重${v.Weight||'?'}`;case'ChangePacket':{const pn=v.PacketName||'';return`改变卡牌属性:${pL(pn,m)||zL(pn,m)||pn||'?'}`;}case'Delete':return'删除卡牌';case'CraterCreate':return'创建弹坑';case'ChangeProtalPos':return'改变传送门位置';case'CheckCharacterNum':{const cn=v.CharacterName||'?';const mt=v.Method||'=';const nm=v.Num??'?';const oM={'=':'等于','>':'大于','<':'小于','>=':'大于等于','<=':'小于等于'};return`场上${aL(cn,m)}数量${oM[mt]||mt}${nm}`;}case'CheckSunCollect':return`收集${v.Num||'?'}阳光`;default:return n||'未知事件';}} function cAED(l){const es=[];const a=(list,src)=>{if(Array.isArray(list))list.forEach(e=>{if(e&&typeof e==='object')es.push({...e,_source:src});});};a(l.Event?.EventInit,'初始化');a(l.Event?.EventReady,'准备阶段');a(l.Event?.EventStart,'开局');const wm=l.WaveManager||l.Feature?.find(f=>f.Name==='Wave')?.Data;if(Array.isArray(wm?.Wave))wm.Wave.forEach((w,i)=>{if(w&&typeof w==='object')a(w.Event,`第${i+1}波`);});if(wm?.SpawnOverride?.DieEvent)a(wm.SpawnOverride.DieEvent,'全局出怪死亡');if(wm?.SpawnOverride?.SpawnEvent)a(wm.SpawnOverride.SpawnEvent,'全局出怪生成');return es;} function cAZ(l,m){const zs=new Map();const aZ=(k,c,id=false)=>{if(!k||typeof k!=='string')return;const n=(typeof c==='number'&&c>0&&isFinite(c))?c:1;const ex=zs.get(k);if(ex){ex.count+=n;if(id)ex.isDynamic=true;}else zs.set(k,{label:zL(k,m),count:n,isDynamic:id});};const wm=l.WaveManager||l.Feature?.find(f=>f.Name==='Wave')?.Data;if(Array.isArray(wm?.Wave)){wm.Wave.forEach(w=>{if(!w||typeof w!=='object')return;if(Array.isArray(w.Spawn))w.Spawn.forEach(s=>{if(s?.Zombie)aZ(s.Zombie,s.Num||1);});if(w.Dynamic?.ZombiePool&&Array.isArray(w.Dynamic.ZombiePool))w.Dynamic.ZombiePool.forEach(z=>{if(z&&typeof z==='string')aZ(z,-1,true);});if(Array.isArray(w.Event))w.Event.forEach(ev=>{if(ev&&(ev.EventName==='BungiSpawnZombie'||ev.EventName==='GravestoneSpawnZombie'||ev.EventName==='GridSpawnZombie')&&Array.isArray(ev.Value?.ZombieNames))ev.Value.ZombieNames.forEach(z=>aZ(z,ev.Value?.ZombieNum||1));if(ev&&ev.EventName==='SpawnZombie'&&ev.Value?.ZombieName)aZ(ev.Value.ZombieName,ev.Value.ZombieNum||1);if(ev&&ev.EventName==='PacketSpawn'&&ev.Value?.PacketName&&(m.zombie.has(ev.Value.PacketName)||m.zombieExtra.has(ev.Value.PacketName)))aZ(ev.Value.PacketName,1);});});}if(Array.isArray(l.VaseManager?.Vase))l.VaseManager.Vase.forEach(v=>{if(v?.PacketName&&(m.zombie.has(v.PacketName)||m.zombieExtra.has(v.PacketName)))aZ(v.PacketName,1);});return zs;} function cAP(l,m){const ps=new Map();const aP=(k,c)=>{if(!k||typeof k!=='string'||!m.plant.has(k))return;const n=(typeof c==='number'&&c>0&&isFinite(c))?c:1;const ex=ps.get(k);if(ex)ex.count+=n;else ps.set(k,{label:pL(k,m),count:n});};if(Array.isArray(l.PreSpawn?.Packet))l.PreSpawn.Packet.forEach(p=>{if(p?.Name)aP(p.Name,1);});if(Array.isArray(l.VaseManager?.Vase))l.VaseManager.Vase.forEach(v=>{if(v?.PacketName)aP(v.PacketName,1);});return ps;} function pUC(pb,m){if(!Array.isArray(pb?.Value)||pb.Value.length===0)return null;const cs=[];const MD=20;pb.Value.forEach(pkt=>{if(!pkt)return;const ch=[];let cur=typeof pkt==='string'?{PacketName:pkt}:pkt;if(!cur||!cur.PacketName)return;const ei=(node,dn)=>{const n=dn||node?.PacketName||'?';if(!node||!node.Override)return{name:pL(n,m),cost:0,interval:null,fireNum:null,projLabel:''};const cgs=Array.isArray(node.Override.CharacterOverride?.PropertyChange)?node.Override.CharacterOverride.PropertyChange:[];return{name:pL(n,m),cost:node.Override.Cost??node.Cost??0,interval:cgs.find(c=>c?.PropertyName==='fireInterval')?.Value??null,fireNum:cgs.find(c=>c?.PropertyName==='fireNum')?.Value??null,projLabel:pN(cgs.find(c=>c?.PropertyName==='projectileName')?.Value,m)};};ch.push(ei(cur,cur.PacketName));if(!cur.Override?.EventPlant)return;let d=0;let cn=cur;let ldn=cur.PacketName;while(cn?.Override?.EventPlant&&d<MD){const evt=cn.Override.EventPlant.find(e=>e?.EventName==='ChangePacket');if(!evt?.Value){if(cn.Override.EventPlant.find(e=>e?.EventName==='Delete'))break;break;}const ndn=evt.Value.PacketName||ldn;ldn=ndn;cn=evt.Value;d++;ch.push(ei(cn,ndn));}if(ch.length>1)cs.push(ch);});return cs.length>0?cs:null;} function bCFL(list,m,lf){const cs=new Map();if(!Array.isArray(list))return cs;list.forEach(item=>{if(!item)return;const pos=pGP(item.GridPos);if(pos){const k=`${pos.col},${pos.row}`;const lb=lf(item.PacketName||item.Name,m);if(cs.has(k)){const ex=cs.get(k);ex.labels.push(lb);ex.stacked=true;}else cs.set(k,{labels:[lb],stacked:false});}});return cs;} function getVaseTypeClass(type){return type==='Plant'?'vase-plant':type==='Zombie'?'vase-zombie':'vase-normal';} function getVaseTypeLabel(type){return type==='Plant'?'植物罐':type==='Zombie'?'僵尸罐':'普通罐';} function renderGrid(mv,cs,cc,title,shuffled=false){const{cols,rows}=gMS(mv);let h=`<div class="section-title">${esc(title)}</div><div class="grid-legend"><span>列1-${cols} 行1-${rows}</span></div><div class="grid-col-headers">`;for(let c=1;c<=cols;c++)h+=`<span class="col-header">${c}</span>`;h+=`</div><div class="grid-container">`;for(let r=1;r<=rows;r++){h+=`<div class="grid-row"><span class="row-label">${r}</span>`;for(let c=1;c<=cols;c++){const cell=cs.get(`${c},${r}`);if(cell){let cls=cc||'';if(cell.vaseType)cls=getVaseTypeClass(cell.vaseType);if(cell.stacked)cls+=(cc==='izm-plant'?' izm-plant-stacked':' preplant-stacked');const dl=cell.labels.map(l=>(l||'?').substring(0,2)).join('/');const tt=cell.labels.join(' + ');h+=`<span class="grid-cell ${cls}" title="${esc(tt)}">${esc(dl)}`;if(shuffled||cell.shuffled)h+=`<span class="shuffle-marker">↻</span>`;h+=`</span>`;}else{h+=`<span class="grid-cell">·</span>`;}}h+=`</div>`;}h+=`</div>`;return h;} function parseGemMatch(gm,m){if(!gm?.Data)return'';const d=gm.Data;let h='';h+='<div class="section-title">💎 宝石迷阵模式</div>';const rows=d.boardRows||5,cols=d.boardCols||8;h+=`<div class="info-grid"><span class="info-badge gem">💎 棋盘:${rows}行×${cols}列</span><span class="info-badge">☀️ 每消阳光:${d.sunPerMatch||0}</span><span class="info-badge">💰 每消分数:${d.matchValuePerMatch||0}</span><span class="info-badge">🎯 清空分数:${d.clearMatchValue||0}</span><span class="info-badge">⏬ 下落速度:${d.fallSpeed||0}</span><span class="info-badge">🔧 填洞花费:${d.fillHoleCost||0}</span></div>`;if(Array.isArray(d.plantList)&&d.plantList.length>0){h+='<div class="section-title">🌱 棋盘可用植物</div><div>';d.plantList.forEach(p=>{if(p&&typeof p==='string')h+=`<span class="plant-tag">💎 ${esc(pL(p,m))}</span>`;});h+='</div>';}if(Array.isArray(d.plantUpgradeList)&&d.plantUpgradeList.length>0){h+='<div class="section-title">🔮 宝石迷阵升级路线</div>';const um=new Map(),rm=new Map(),cs=new Map();d.plantUpgradeList.forEach(up=>{um.set(up.from,up.to);rm.set(up.to,up.from);cs.set(up.to,up.cost||0);});const starts=[...new Set(d.plantUpgradeList.filter(up=>!rm.has(up.from)).map(up=>up.from))];starts.forEach((start,si)=>{let cur=start;const ch=[];const visited=new Set();while(cur&&!visited.has(cur)){visited.add(cur);ch.push({name:cur,cost:cs.get(cur)||0});cur=um.get(cur);}if(ch.length>=2){h+=`<div class="upgrade-line gem-upgrade-line"><strong>线路${si+1}:</strong>`;ch.forEach((s,i)=>{if(i>0)h+='<span class="arrow">→</span>';h+=`<span class="upgrade-step" style="background:#8b2050;">${esc(pL(s.name,m))}${s.cost?' ['+s.cost+']':''}</span>`;});h+='</div>';}});}return h;} function pLvl(l,m){ if(!l||!m)return'<span style="color:#c44;">❌ 无效数据</span>'; const isNF=!!l.Feature;let wm,mapName,bgmName,sunData,packetBank,reward,vaseManager,preSpawn,lookStar,finishMethod,gm=null; if(isNF){const wf=l.Feature.find(f=>f.Name==='Wave');const mf=l.Feature.find(f=>f.Name==='Map');const bf=l.Feature.find(f=>f.Name==='BGM');const sf=l.Feature.find(f=>f.Name==='Sun');const sef=l.Feature.find(f=>f.Name==='SeedBank');const pf=l.Feature.find(f=>f.Name==='Process');gm=l.Feature.find(f=>f.Name==='GemMatch');wm=wf?.Data;mapName=mf?.Data?.MapName;bgmName=bf?.Data?.BGMName;sunData=sf?.Data;packetBank=sef?.Data;finishMethod=pf?.Data?.Name||'Wave';reward=l.Reward;vaseManager=l.VaseManager;preSpawn=l.PreSpawn;lookStar=l.LookStarManager;}else{wm=l.WaveManager;mapName=l.Map;bgmName=l.BGM;sunData=l.SunManager;packetBank=l.PacketBank;finishMethod=l.FinishMethod||'Wave';reward=l.Reward;vaseManager=l.VaseManager;preSpawn=l.PreSpawn;lookStar=l.LookStarManager;} const fin=(finishMethod||'Wave').toUpperCase();const isS=!!wm?.Survival;const isE=isS&&String(wm.Survival).includes('Endless');const isV=fin==='VASE'||fin==='Vase';const isI=fin==='IZM'||fin==='IZM2';const isI2=fin==='IZM2';const isSh=!isS&&!isV&&!isI&&packetBank?.Method==='Preset'&&Array.isArray(wm?.Wave)&&wm.Wave.length>0;const isSt=!!lookStar?.Open;const izS=l.IZMManager?.Shuffle;const isConveyor=packetBank?.Method==='Conveyor';let hDyn=false;if(Array.isArray(wm?.Wave))hDyn=wm.Wave.some(w=>w?.Dynamic?.ZombiePool&&Array.isArray(w.Dynamic.ZombiePool)&&w.Dynamic.ZombiePool.length>0); const{rows,cols}=gMS(mapName);const ml=m.map.get(mapName)?.label||mapName||'未知地图';const rw=pR(reward,m);const wi=gWI(l); let h=`<div style="font-weight:bold;font-size:1rem;">📋 ${esc(l.LevelName||l.Name||'未命名')}</div><div class="info-grid">`; h+=`<span class="info-badge">🗺️ ${esc(ml)}(${rows}行×${cols}列)</span><span class="info-badge">🎵 ${esc(m.bgm.get(bgmName)||bgmName||'默认')}</span><span class="info-badge">☀️${sunData?.Begin??'?'}</span><span class="info-badge">🏁${fin}</span>`; if(isNF)h+=`<span class="info-badge" style="background:#e04090;color:#fff;">📦 Feature</span>`;if(gm)h+=`<span class="info-badge gem">💎 宝石迷阵</span>`; if(isS)h+=`<span class="info-badge survival">🔄生存${isE?'(无尽)':'(基础)'}</span>`;if(isE)h+=`<span class="info-badge endless">♾️无尽</span>`; if(isSh)h+=`<span class="info-badge shooting">🔫射击</span>`;if(isV)h+=`<span class="info-badge vase">🏺罐子</span>`; if(isI)h+=`<span class="info-badge izm">🧠${fin}</span>`;if(isSt)h+=`<span class="info-badge star">⭐观星</span>`; if(hDyn)h+=`<span class="info-badge dynamic">🎲动态出怪</span>`;if(isConveyor)h+=`<span class="info-badge conveyor">📦 传送带</span>`; if(izS!==undefined)h+=`<span class="info-badge ${izS?'shuffle-anim':'shuffle'}">🔀 植物${izS?'已打乱':'固定位置'}</span>`; h+=`</div><div class="reward-block"><strong>🎁 通关奖励:</strong>${rw}</div>`; if(wi&&!isS&&!isI)h+=`<div class="reward-block" style="background:#e8f0d0;border-color:#6b8c42;"><strong>🚩 波次:</strong>总${wi.totalWaves}波 | 旗间隔${wi.flagInterval} | 旗${wi.flagCount}面</div>`; if(l.Tutorial?.Step&&Array.isArray(l.Tutorial.Step)){h+='<div class="tutorial-box"><div class="tutorial-title">📖 教程提示</div>';l.Tutorial.Step.forEach(s=>{if(s.BroadCast?.Text)h+=`<div style="margin-top:0.2rem;">💬 ${esc(s.BroadCast.Text)}</div>`;});h+='</div>';} if(gm)h+=parseGemMatch(gm,m); // 【6.3新增】传送带显示 if(isConveyor&&packetBank?.ConveyorPreset){const cp=packetBank.ConveyorPreset; h+='<div class="conveyor-section"><div class="section-title" style="border-bottom-color:#e67e22;">📦 传送带配置</div>'; h+=`<div style="font-size:0.6rem;color:#6b4f2c;">⏱ 初始间隔:${cp.Interval||0}秒 | 每${cp.IntervalIncreaseEvery||0}波缩短 | 倍率:${cp.IntervalMagnification||0}</div>`; if(Array.isArray(cp.Packet)&&cp.Packet.length>0){h+='<div style="margin-top:0.3rem;"><strong>🎴 传送带卡牌:</strong></div><div>';cp.Packet.forEach(p=>{if(!p)return;const n=p.Name||'';if(!n)return;h+=`<span class="plant-tag">🌱${esc(pL(n,m))}<span style="font-size:0.45rem;color:#ffda7a;">[权重:${p.Weight||'?'}]</span></span>`;});h+='</div>';} if(Array.isArray(cp.PacketPrioritySpawnList)&&cp.PacketPrioritySpawnList.length>0){h+='<div style="margin-top:0.3rem;"><strong>⭐ 优先生成:</strong></div><div>';cp.PacketPrioritySpawnList.forEach(n=>{if(!n)return;h+=`<span class="plant-tag" style="background:#8b6b2c;color:#fff;">⭐${esc(pL(n,m))}</span>`;});h+='</div>';} h+='</div>';} // 生存模式 if(isS&&wm?.Survival){const sk=wm.Survival;const cfg=S.survivalConfigs.get(sk);if(cfg){h+=`<div class="reward-block" style="background:#e8f0d0;"><strong>🔄 生存:${cfg.Name||sk}</strong><br>📊 ${cfg.RoundLimit||1}轮 | 起始${cfg.PointBegin||0}分 | 每波+${cfg.PointIncrementPerWave||0}分</div>`;if(cfg.ZombiePoolSetting){h+='<div class="section-title">🧟 生存僵尸池</div><div>';const g={};if(cfg.ZombiePoolSetting.ZombiePoolBase)g['基础池']=cfg.ZombiePoolSetting.ZombiePoolBase.map(z=>zL(z,m));if(cfg.ZombiePoolSetting.ZombiePoolRoundAdd)cfg.ZombiePoolSetting.ZombiePoolRoundAdd.forEach(ra=>{g[`第${ra.Round}轮起`]=ra.Zombie.map(z=>zL(z,m));});for(const[r,zs]of Object.entries(g)){h+=`<div style="font-size:0.6rem;color:#5a7a3a;">📌 ${r}:</div><div>`;zs.forEach(z=>{h+=`<span class="zombie-tag">🧟${esc(z)}</span>`;});h+='</div>';}h+='</div>';}}else{h+=`<div class="reward-block" style="background:#fff3cd;"><strong>⚠️ 生存模式</strong><br>未找到生存配置"${sk}"</div>`;}} // 射击升级 if(isSh&&packetBank){const cs=pUC(packetBank,m);if(cs&&cs.length>0){h+='<div class="section-title">🔫 升级线路</div>';cs.forEach((ch,ci)=>{h+=`<div class="upgrade-line"><strong>线路${ci+1}:</strong>`;ch.forEach((s,i)=>{if(i>0)h+='<span class="arrow">→</span>';const ps=[];if(s.interval!=null)ps.push(`间隔${s.interval}s`);if(s.fireNum!=null)ps.push(`${s.fireNum}发`);if(s.projLabel)ps.push(`子弹:${esc(s.projLabel)}`);h+=`<span class="upgrade-step">${esc(s.name)}${s.cost?' ['+s.cost+']':''}${ps.length?'('+ps.join(',')+')':''}</span>`;});h+='</div>';});}} // 预设卡牌(非传送带模式) if(packetBank?.Value?.length&&!isConveyor){if(packetBank.Method==='Preset'||isI){h+=`<div class="section-title">🎴 预设卡牌</div><div>`;packetBank.Value.forEach(p=>{if(!p)return;const n=typeof p==='string'?p:(p?.PacketName||'');if(!n)return;let ex='';if(typeof p==='object'&&p.Override){const pts=[];if(p.Override.Cost!==undefined&&p.Override.Cost!==0)pts.push(`费:${p.Override.Cost}`);if(p.Override.CharacterOverride?.DieEvent?.some(e=>e.EventName==='PacketSpawn'))pts.push('💥死亡生成');if(p.Override.Type)pts.push(cL[p.Override.Type]||p.Override.Type);if(pts.length)ex=` <span style="font-size:0.45rem;color:#ffda7a;">(${pts.join('|')})</span>`;}if(isI)h+=`<span class="zombie-tag">🧟${esc(aL(n,m))}${ex}</span>`;else h+=`<span class="${m.plant.has(n)?'plant-tag':'zombie-tag'}">${m.plant.has(n)?'🌱':'🧟'}${esc(aL(n,m))}${ex}</span>`;});h+='</div>';}} // 观星 if(isSt&&lookStar?.Check){const cs=bCFL(lookStar.Check,m,pL);if(cs.size>0)h+=renderGrid(mapName,cs,'lookstar','⭐ 观星布局');} // 预放置 if(preSpawn?.Packet?.length){const cs=bCFL(preSpawn.Packet,m,aL);if(cs.size>0){if(izS){for(const[k,cell]of cs)cell.shuffled=true;}h+=renderGrid(mapName,cs,isI?'izm-plant':'preplant',isI?`🧠 防守植物布局${izS?' (已打乱)':''}`:'📍 地图预放置',izS);}else{h+='<div class="section-title">📍 预放置(无坐标)</div><div>';preSpawn.Packet.forEach(p=>{if(p?.Name)h+=`<span class="plant-tag">🌱 ${esc(aL(p.Name,m))}</span>`;});h+='</div>';}} // 罐子 if(isV&&vaseManager?.Vase?.length){const vm=vaseManager;if(vm.Shuffle)h+=`<div class="info-grid"><span class="info-badge shuffle-anim">🔀 罐子已打乱</span></div>`;h+=`<div class="section-title">🏺 罐子位置${vm.Shuffle?' (已打乱)':''}</div><div class="table-container"><table class="vase-table"><thead><tr><th>#</th><th>列</th><th>行</th><th>内容</th><th>类型</th></tr></thead><tbody>`;vm.Vase.forEach((v,idx)=>{if(!v)return;const pos=pGP(v.GridPos);h+=`<tr><td>${idx+1}</td><td>${pos?pos.col:'?'}</td><td>${pos?pos.row:'?'}</td><td>${esc(aL(v.PacketName,m))}</td><td>${getVaseTypeLabel(v.Type)}</td></tr>`;});h+=`</tbody></table></div>`;const vc=new Map();vm.Vase.forEach(v=>{if(!v)return;const pos=pGP(v.GridPos);if(pos){const k=`${pos.col},${pos.row}`;const lb=aL(v.PacketName,m);if(vc.has(k)){const ex=vc.get(k);ex.labels.push(lb);ex.stacked=true;}else vc.set(k,{labels:[lb],stacked:false,vaseType:v.Type||'Normal',shuffled:!!vm.Shuffle});}});if(vc.size>0)h+=renderGrid(mapName,vc,'vase-normal',`🏺 罐子布局${vm.Shuffle?' (原始位置)':''}`,vm.Shuffle);} // 僵尸 if(!isI2&&!isI){const zs=cAZ(l,m);if(zs.size>0){const fz=[],dz=[];for(const[k,v]of zs){if(v.isDynamic)dz.push([k,v]);else fz.push([k,v]);}if(fz.length>0){h+='<div class="section-title">🧟 固定出怪</div><div>';fz.sort((a,b)=>b[1].count-a[1].count).forEach(([k,v])=>{h+=`<span class="zombie-tag">🧟${esc(v.label)}×${v.count}</span>`;});h+='</div>';}if(dz.length>0){h+='<div class="section-title">🎲 动态随机出怪池</div><div class="zombie-pool-section">⚠️ 以下僵尸从出怪池中随机抽取</div><div>';dz.forEach(([k,v])=>{h+=`<span class="zombie-tag">🎲${esc(v.label)}</span>`;});h+='</div>';}}} if(isI){const ps=cAP(l,m);if(ps.size>0){h+='<div class="section-title">🌱 防守植物</div><div>';[...ps.entries()].sort((a,b)=>b[1].count-a[1].count).forEach(([k,v])=>{h+=`<span class="plant-tag">🌱${esc(v.label)}×${v.count}</span>`;});h+='</div>';}if(isI2&&packetBank?.Value?.length){h+='<div class="section-title">🧟 进攻僵尸</div><div>';packetBank.Value.forEach(p=>{const n=typeof p==='string'?p:(p?.PacketName||'');if(n)h+=`<span class="zombie-tag">🧟${esc(zL(n,m))}</span>`;});h+='</div>';}} // 罐中内容 if(isV&&vaseManager?.Vase){const vp=new Map(),vz=new Map();vaseManager.Vase.forEach(v=>{if(!v?.PacketName)return;const n=v.PacketName;if(m.plant.has(n)){const e=vp.get(n);if(e)e.count++;else vp.set(n,{label:pL(n,m),count:1});}else if(m.zombie.has(n)||m.zombieExtra.has(n)){const e=vz.get(n);if(e)e.count++;else vz.set(n,{label:zL(n,m),count:1});}});if(vp.size>0){h+='<div class="section-title">🏺 罐中植物</div><div>';[...vp.entries()].sort((a,b)=>b[1].count-a[1].count).forEach(([k,v])=>{h+=`<span class="plant-tag">🌱${esc(v.label)}×${v.count}</span>`;});h+='</div>';}if(vz.size>0){h+='<div class="section-title">🏺 罐中僵尸</div><div>';[...vz.entries()].sort((a,b)=>b[1].count-a[1].count).forEach(([k,v])=>{h+=`<span class="zombie-tag">🧟${esc(v.label)}×${v.count}</span>`;});h+='</div>';}} // 事件 const aes=cAED(l);if(aes.length>0){h+=`<div class="section-title">⚡ 整局全部事件<span class="event-count">${aes.length}个</span></div>`;const g={};aes.forEach(ev=>{const d=eDD(ev,m);if(!g[d])g[d]={count:0,sources:new Set()};g[d].count++;if(ev._source)g[d].sources.add(ev._source);});for(const[d,i]of Object.entries(g))h+=`<div class="event-block"><div class="event-header">⚡${esc(d)}${i.count>1?`<span class="event-count">×${i.count}</span>`:''}</div>${i.sources.size>0?`<div class="event-detail">📍触发:${[...i.sources].join('、')}</div>`:''}</div>`;} return h;} $('analyzeBtn')?.addEventListener('click',()=>{if(S.isAnalyzing)return;if(!S.optionsData||!S.templatesData||!S.levelData){toast('请上传所有文件','error');return;}S.isAnalyzing=true;const ab=$('analyzeBtn');if(ab){ab.disabled=true;ab.innerHTML='<span class="loading-spinner"></span>解析中...';}requestAnimationFrame(()=>{try{S.lastMaps=bM();const hc=pLvl(S.levelData,S.lastMaps);$('output').innerHTML=hc+'<div style="color:#5a7a3a;margin-top:0.4rem;">✅ 解析完成</div>';toast('解析完成','success');}catch(e){$('output').innerHTML='<span style="color:#c44;">❌ 解析失败:'+esc(e.message)+'</span>';toast('解析失败','error');}finally{S.isAnalyzing=false;if(ab)ab.innerHTML='🔍 解析关卡';updateUI();}});}); document.addEventListener('keydown',function(e){if(e.target.tagName==='INPUT'||e.target.tagName==='TEXTAREA')return;const ab=$('analyzeBtn');if(e.ctrlKey&&e.key==='Enter'&&ab&&!ab.disabled){e.preventDefault();ab.click();}}); function init(){updateUI();setupFileInput($('optInput'),(dt)=>{S.optionsData=dt;},$('optName'));setupFileInput($('tplInput'),(dt)=>{S.templatesData=dt;},$('tplName'));setupFileInput($('levelInput'),(dt)=>{S.levelData=dt;},$('levelFileName'));setupSurvivalInput($('surInput'));if(!S.optionsData||!S.templatesData){if(loadCache()){$('optName').textContent=S.optionsData?'✅ 缓存':'未选择';$('tplName').textContent=S.templatesData?'✅ 缓存':'未选择';$('surName').textContent=S.survivalConfigs.size>0?'✅ '+S.survivalConfigs.size+'个':'未选择';updateUI();}}} if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',init);else init();})(); </script> </body> </html> </div> </includeonly>
返回
微件:LevelAnalyzer
。
查看“︁微件:LevelAnalyzer”︁的源代码
来自植物大战僵尸杂交版Wiki