打开/关闭搜索
搜索
打开/关闭菜单
933
1525
112
1.4万
植物大战僵尸杂交版Wiki
导航
首页
最近更改
随机页面
MediaWiki帮助
特殊页面
上传文件
打开/关闭外观设置菜单
notifications
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。
user-interface-preferences
个人工具
创建账号
登录
查看“︁微件:LevelEditor”︁的源代码
来自植物大战僵尸杂交版Wiki
查看
阅读
查看源代码
查看历史
associated-pages
微件
讨论
更多操作
←
微件:LevelEditor
因为以下原因,您没有权限编辑该页面:
您请求的操作仅限属于该用户组的用户执行:
用户
您没有权限编辑
微件
命名空间内的页面。
您可以查看和复制此页面的源代码。
<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 { background: #1e2b3b; background-image: radial-gradient(circle at 20% 30%, #3a5068, #1a2a3a); min-height: 100vh; margin: 0; padding: 24px 16px; display: flex; justify-content: center; align-items: center; } .app-container { max-width: 1400px; width: 100%; background: rgba(20, 30, 40, 0.75); backdrop-filter: blur(8px); border: 2px solid #6b8fa0; box-shadow: 0 20px 40px rgba(0,0,0,0.6), inset 0 0 0 1px #a0c8d8; border-radius: 40px; padding: 28px 24px 32px; } h1 { margin: 0 0 8px 0; font-weight: 700; font-size: 2.5rem; letter-spacing: 2px; color: #b0d8e8; text-shadow: 3px 3px 0 #1a2838; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; } h1 small { font-size: 1rem; background: #2a4a5a; padding: 6px 14px; border-radius: 40px; color: #c8e8f0; border: 1px solid #80b8cc; box-shadow: inset 0 1px 3px #0e1a1e; } .subtitle { color: #a0c8d6; margin: 4px 0 24px 8px; font-style: italic; border-left: 5px solid #80b8d0; padding-left: 18px; font-size: 1.1rem; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; } .import-btn { background: #3a5a6a; border: 2px solid #90c0d0; color: #d8f0f8; padding: 8px 20px; border-radius: 40px; font-weight: bold; cursor: pointer; box-shadow: 0 4px 0 #1a2a38; font-size: 0.95rem; transition: all 0.1s ease; } .import-btn:active { transform: translateY(2px); box-shadow: 0 2px 0 #1a2a38; } .editor-layout { display: flex; flex-wrap: wrap; gap: 24px; } .form-panel { flex: 2; min-width: 420px; background: #1e2e38cc; backdrop-filter: blur(4px); border: 2px solid #6b90a8; border-radius: 32px; padding: 24px 22px; box-shadow: inset 0 0 0 1px #80a8b8, 0 8px 0 #101a20; max-height: 800px; overflow-y: auto; } .form-panel::-webkit-scrollbar { width: 8px; } .form-panel::-webkit-scrollbar-track { background: #142028; border-radius: 20px; } .form-panel::-webkit-scrollbar-thumb { background: #6b90a8; border-radius: 20px; } .infocard-sim { background: #162228dd; border-radius: 24px; padding: 16px 20px; margin-bottom: 28px; border-left: 12px solid #80c8e0; box-shadow: inset 0 0 0 1px #6090a8; } .infocard-header { display: flex; align-items: center; justify-content: space-between; color: #c0e0f0; font-weight: bold; margin-bottom: 16px; font-size: 1.3rem; flex-wrap: wrap; gap: 8px; } .infocard-header span { background: #5a3a2e; padding: 4px 14px; border-radius: 40px; font-size: 0.9rem; border: 1px solid #d0a080; } .image-input-row { display: flex; align-items: center; gap: 8px; background: #1a2830aa; padding: 10px 14px; border-radius: 40px; border: 1px solid #80c0d0; margin-bottom: 16px; flex-wrap: wrap; } .image-input-row label { color: #b0d8e8; font-weight: 600; font-size: 0.85rem; white-space: nowrap; margin: 0; } .image-input-row input { background: #0e1a22; border: 1px solid #5b80a0; border-radius: 20px; padding: 6px 12px; color: #d8f0ff; font-size: 0.85rem; flex: 1; min-width: 80px; } .image-input-row .size-input { flex: 0.4; min-width: 60px; } .attr-editor { margin-bottom: 20px; } .attr-item { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; background: #1a2830aa; padding: 8px 12px; border-radius: 40px; border: 1px solid #6b8fa0; flex-wrap: wrap; } .attr-name { width: 100px; background: transparent; border: none; color: #b0d8e8; font-weight: 600; padding: 4px 0; font-size: 0.85rem; } .attr-value { flex: 1; min-width: 200px; background: #0e1a22; border: 1px solid #5b80a0; border-radius: 20px; padding: 6px 12px; color: #d8f0ff; font-size: 0.85rem; } .attr-color-select { width: 70px; background: #2a3a48; border: 1px solid #80a8c0; border-radius: 20px; padding: 5px 6px; color: #d8f0ff; font-size: 0.85rem; } .attr-bold-check { display: flex; align-items: center; gap: 4px; color: #b8d8f0; font-size: 0.85rem; white-space: nowrap; } .attr-bold-check input { width: 18px; height: 18px; accent-color: #80c8e0; } .attr-remove-btn { background: #5a3a2e; border: 1px solid #c88060; color: #ffd8c8; border-radius: 30px; width: 30px; height: 30px; font-weight: bold; cursor: pointer; font-size: 16px; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 0 #2a1a10; flex-shrink: 0; } .add-attr-btn { background: #3a6a7a; border: 1px solid #80c0d0; color: #d8f0f8; padding: 8px 18px; border-radius: 40px; font-weight: bold; cursor: pointer; box-shadow: 0 4px 0 #1a3a48; margin-top: 8px; display: inline-block; } .input-group { margin-bottom: 24px; } label { display: block; color: #c0d8e8; font-weight: 600; margin-bottom: 6px; font-size: 1rem; } label i { font-weight: 400; color: #80a8c0; margin-left: 8px; } input, textarea { width: 100%; background: #12202a; border: 2px solid #5b80a0; border-radius: 24px; padding: 12px 18px; color: #d8f0ff; font-size: 1rem; outline: none; box-shadow: inset 0 3px 8px #081018; } input::placeholder, textarea::placeholder { color: #6080a0; } input:focus, textarea:focus { border-color: #90c8e0; box-shadow: 0 0 0 3px #60a0b888; } textarea { min-height: 100px; resize: vertical; line-height: 1.5; font-family: 'Courier New', monospace; font-size: 0.9rem; } .event-item { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; background: #1a2a36; padding: 6px 12px; border-radius: 30px; border: 1px solid #4a7088; flex-wrap: wrap; } .event-item input { flex: 1; background: #0e1822; border: 1px solid #4a6880; border-radius: 20px; padding: 5px 12px; font-size: 0.9rem; min-width: 120px; } .event-remove-btn { background: #4a2a2a; border: 1px solid #a86050; color: #ffc8c0; border-radius: 20px; width: 26px; height: 26px; font-weight: bold; cursor: pointer; font-size: 14px; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 0 #1a0e0e; flex-shrink: 0; } .add-event-btn { background: #2a5a6a; border: 1px solid #70b0c0; color: #c8f0f8; padding: 6px 14px; border-radius: 30px; font-weight: bold; cursor: pointer; box-shadow: 0 3px 0 #1a3848; font-size: 0.85rem; margin-top: 8px; } .image-row { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; } .image-row input { flex: 1; } .btn-icon { background: #3a5060; border: 1px solid #80b0c8; color: #d8f0f8; border-radius: 30px; padding: 6px 16px; font-weight: bold; cursor: pointer; box-shadow: 0 4px 0 #1a2838; font-size: 0.9rem; } .btn-primary { background: #80b8c8; border: none; color: #0e1a28; font-weight: 800; padding: 14px 24px; border-radius: 60px; font-size: 1.2rem; box-shadow: 0 8px 0 #4a7088; border: 1px solid #c0e8f0; cursor: pointer; width: 100%; margin-top: 20px; letter-spacing: 1px; transition: all 0.1s ease; } .btn-primary:active { transform: translateY(4px); box-shadow: 0 4px 0 #4a7088; } .preview-panel { flex: 3; min-width: 420px; background: #182630e0; backdrop-filter: blur(4px); border: 2px solid #6b90a8; border-radius: 32px; padding: 20px 18px; box-shadow: inset 0 0 0 1px #80a8b8, 0 8px 0 #101a20; display: flex; flex-direction: column; } .preview-header { display: flex; gap: 12px; align-items: center; margin-bottom: 18px; flex-wrap: wrap; } .preview-header h2 { margin: 0; color: #a8d0e8; font-size: 1.8rem; text-shadow: 2px 2px 0 #1a2838; } .preview-actions { margin-left: auto; display: flex; gap: 10px; } .copy-btn, .paste-btn { background: #3a6070; border: 1px solid #80b8d0; color: #d8f0f8; padding: 10px 20px; border-radius: 40px; font-weight: bold; cursor: pointer; box-shadow: 0 4px 0 #1a3848; transition: all 0.1s ease; } .paste-btn { background: #4a5a30; border-color: #a0c870; } .copy-btn:active, .paste-btn:active { transform: translateY(2px); box-shadow: 0 2px 0 #1a3848; } .wiki-output { background: #081018; border-radius: 20px; padding: 18px 20px; border: 2px solid #6090a8; box-shadow: inset 0 0 10px #020508; font-family: 'Courier New', monospace; white-space: pre-wrap; word-break: break-word; color: #a8c8e0; font-size: 0.85rem; line-height: 1.4; max-height: 600px; overflow: auto; flex: 1; } .note { margin-top: 16px; color: #80a8c0; font-size: 0.9rem; } .section-divider { border: 1px dashed #4a7088; margin: 20px 0; } .top-link { text-align: center; margin: 0 0 16px 0; display: flex; flex-wrap: wrap; justify-content: center; gap: 16px; } .top-link a { color: #ffaaaa; font-weight: bold; } .modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.7); display: none; justify-content: center; align-items: center; z-index: 1000; } .modal { background: #142028; border: 3px solid #6b90a8; border-radius: 32px; padding: 28px; max-width: 700px; width: 90%; max-height: 80vh; box-shadow: 0 20px 40px black; } .modal h3 { color: #b0d8e8; margin: 0 0 16px 0; font-size: 1.6rem; } .modal p { color: #80a8c0; margin-bottom: 12px; } .modal textarea { width: 100%; min-height: 300px; background: #0e1822; border: 2px solid #6090a8; color: #d8f0ff; font-family: 'Courier New', monospace; font-size: 0.9rem; } .modal-actions { display: flex; gap: 16px; margin-top: 20px; justify-content: flex-end; } .modal-btn { padding: 12px 28px; border-radius: 40px; font-weight: bold; cursor: pointer; border: none; font-size: 1rem; } .modal-btn.primary { background: #80b8c8; color: #0e1a28; box-shadow: 0 4px 0 #4a7088; } .modal-btn.cancel { background: #3a4a5a; color: #c0d8e0; box-shadow: 0 4px 0 #1a2a3a; } .card-item { background: #16222caa; border-radius: 16px; padding: 10px 14px; margin-bottom: 8px; border: 1px solid #4a6880; } .card-item .card-row { display: flex; gap: 6px; align-items: center; margin-bottom: 4px; flex-wrap: wrap; } .card-item .card-row:last-child { margin-bottom: 0; } .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-scroll { overflow-x: auto; max-width: 100%; } .map-grid-row { display: flex; gap: 2px; margin-bottom: 2px; align-items: center; } .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.08); z-index: 2; } .map-grid-cell.placed { background: #4ade80; } .map-grid-cell.placed::after { content: '🌱'; font-size: 16px; position: absolute; pointer-events: none; } .map-grid-label { width: 34px; color: #80b8d0; font-size: 0.65rem; text-align: center; flex-shrink: 0; } .map-toolbar { display: flex; gap: 6px; align-items: center; margin-bottom: 10px; flex-wrap: wrap; } .map-toolbar input { width: 130px; font-size: 0.78rem; padding: 5px 8px; } .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 input { font-size: 0.8rem; margin-bottom: 6px; } .cell-popup button { font-size: 0.75rem; padding: 4px 10px; margin-right: 4px; } .tab-bar { display: flex; gap: 4px; margin-bottom: 16px; flex-wrap: wrap; } .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; } .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> </head> <body> <div class="app-container"> <div class="top-link"> <font color="#ffaaaa"> 点击前往植物Wiki编辑器:<a href="https://new.pvzhe.wiki/w/%E6%A4%8D%E7%89%A9%E9%A1%B5%E9%9D%A2%E7%BC%96%E8%BE%91%E5%99%A8" target="_blank">前往</a> 点击前往僵尸Wiki编辑器:<a href="https://new.pvzhe.wiki/w/%E5%83%B5%E5%B0%B8%E9%A1%B5%E9%9D%A2%E7%BC%96%E8%BE%91%E5%99%A8" target="_blank">前往</a> </font> </div> <h1>🗺️🚩 关卡 · Wiki编辑器 <small>关卡信息</small></h1> <div class="subtitle"> <span>填写数据,一键生成标准Wiki文本 (兼容MediaWiki)</span> <button class="import-btn" id="importBtn">📥 导入现有源码</button> </div> <div class="editor-layout"> <div class="form-panel" id="formPanel"> <!-- 关卡大图 --> <div class="input-group"> <label>🖼️ 关卡大图</label> <div class="image-row"> <input type="text" id="mapImageFile" value="射击模式.png" placeholder="文件名" style="flex:2;"> <input type="text" id="mapImageWidth" value="350px" placeholder="宽度" style="flex:1;"> <input type="text" id="mapImageLink" value="射击模式" placeholder="链接" style="flex:1;"> </div> </div> <!-- 僵尸卡片编辑器 --> <div class="input-group" style="background:#162228aa; border-radius:20px; padding:16px; border:1px solid #4a7088;"> <label>🧟 僵尸卡片 <i>(出现在关卡大图下方)</i></label> <div id="cardsContainer"></div> <button type="button" id="addCardBtn" class="add-event-btn">➕ 添加僵尸卡片</button> </div> <!-- 地图预放置编辑器 --> <div class="map-grid-container" id="mapGridEditor"> <label style="margin-bottom:8px;">🗺️ 地图预放置 <i>(点击格子放置/编辑植物,右键清除)</i></label> <div class="map-toolbar"> <span style="color:#80b8d0; font-size:0.75rem;">预设:</span> <div class="preset-row"> <button class="preset-btn" data-rows="5" data-cols="9">5×9</button> <button class="preset-btn" data-rows="6" data-cols="9">6×9</button> <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 class="map-grid-scroll"><div id="mapGrid"></div></div> </div> <!-- Infocard --> <div class="infocard-sim"> <div class="infocard-header"> <span>📋 Infocard 属性编辑器</span> <span style="background:#5a3a2e;">图片=xxx|图片尺寸=xx|属性=...</span> </div> <div class="image-input-row"> <label>🖼️ 图片=</label> <input type="text" id="infocardImage" value="豌豆进化.png" placeholder="文件名"> <label>图片尺寸=</label> <input type="text" id="infocardImageSize" value="200px" placeholder="200px" class="size-input"> </div> <div id="attrEditorContainer" class="attr-editor"></div> <button type="button" id="addAttrBtn" class="add-attr-btn">➕ 添加属性项</button> </div> <div class="section-divider"></div> <!-- 事件标签页 --> <div class="tab-bar"> <button class="tab-btn active" data-tab="init">初始化</button> <button class="tab-btn" data-tab="prepare">准备阶段</button> <button class="tab-btn" data-tab="start">开始后行为</button> </div> <div id="tabInit" class="tab-content"> <div class="input-group"> <label>📋 地图行为 - 初始化 <i>(每行一个事件,写"无"表示无事件)</i></label> <div id="initEventsContainer"></div> <button type="button" id="addInitEventBtn" class="add-event-btn">➕ 添加初始化事件</button> </div> </div> <div id="tabPrepare" class="tab-content" style="display:none;"> <div class="input-group"> <label>📋 地图行为 - 准备阶段 <i>(每行一个事件)</i></label> <div id="prepareEventsContainer"></div> <button type="button" id="addPrepareEventBtn" class="add-event-btn">➕ 添加准备阶段事件</button> </div> </div> <div id="tabStart" class="tab-content" style="display:none;"> <div class="input-group"> <label>📋 地图行为 - 开始后行为 <i>(每行一个事件)</i></label> <div id="startEventsContainer"></div> <button type="button" id="addStartEventBtn" class="add-event-btn">➕ 添加开始后事件</button> </div> </div> <div class="section-divider"></div> <!-- 通关奖励 --> <div class="input-group"> <label>🎁 通关奖励 <i>(图片文件名 | 链接)</i></label> <div id="rewardsContainer"></div> <button type="button" id="addRewardBtn" class="add-event-btn">➕ 添加奖励</button> </div> <!-- 困难模式图片 --> <div class="input-group"> <label>🖼️ 困难模式图片 <i>(可选)</i></label> <div class="image-row"> <input type="text" id="hardModeFile" value="困难.png" placeholder="文件名" style="flex:2;"> <input type="text" id="hardModeWidth" value="250px" placeholder="宽度" style="flex:1;"> <input type="text" id="hardModeLink" value="【豌豆进化】困难" placeholder="链接" style="flex:1;"> </div> </div> <button class="btn-primary" id="generateBtn">🗺️ 立即生成 Wiki 文本</button> </div> <div class="preview-panel"> <div class="preview-header"> <h2>📄 Wiki输出</h2> <div class="preview-actions"> <button class="paste-btn" id="pasteToImportBtn">📋 从预览导入</button> <button class="copy-btn" id="copyBtn">📋 复制代码</button> </div> </div> <div class="wiki-output" id="wikiOutput"></div> <div class="note">⚡ 实时生成。点击格子放置植物,右键清除。可自定义行列数。</div> </div> </div> </div> <!-- 格子编辑弹窗 --> <div class="cell-popup" id="cellPopup"> <label style="color:#b0d8e8; font-size:0.8rem;">植物文件名</label> <input type="text" id="popupFile" placeholder="xxx.png"> <label style="color:#b0d8e8; font-size:0.8rem;">链接</label> <input type="text" id="popupLink" placeholder="植物名"> <div style="display:flex; gap:6px; margin-top:8px; justify-content:flex-end;"> <button type="button" class="btn-icon" id="popupClear">清除</button> <button type="button" class="btn-icon" id="popupSave" style="background:#3a6a7a;">保存</button> </div> </div> <div class="modal-overlay" id="importModal"> <div class="modal"> <h3>📥 导入 Wiki 源码</h3> <p>粘贴现有的关卡 Wiki 文本,程序将自动解析并填充到表单中</p> <textarea id="importTextarea" placeholder="在此粘贴 Wiki 源码..."></textarea> <div class="modal-actions"> <button class="modal-btn cancel" id="cancelImportBtn">取消</button> <button class="modal-btn primary" id="confirmImportBtn">解析并导入</button> </div> </div> </div> <script> (function(){ 'use strict'; const COLOR_OPTIONS = [ { value: '', label: '默认' },{ value: 'red', label: '红色' },{ value: 'blue', label: '蓝色' }, { value: 'green', label: '绿色' },{ value: 'orange', label: '橙色' },{ value: 'purple', label: '紫色' }, { value: 'gold', label: '金色' },{ value: 'brown', label: '棕色' } ]; const COLOR_MAP = Object.fromEntries(COLOR_OPTIONS.filter(o=>o.value).map(o=>[o.value,o.value])); const DEFAULT_ATTRS = [ { name: '地图', value: '前院【白天】(5行 × 9列)', color: 'red', bold: true }, { name: '音乐', value: '射击模式', color: 'red', bold: true }, { name: '初始阳光', value: '500', color: 'red', bold: true }, { name: '模式', value: '波模式、射击模式', color: 'red', bold: true }, { name: '升级线路', value: '1、线路1:<br>①豌豆射手<br>(500阳光,间隔0.35s)→<br>②双发射手<br>(1000阳光,间隔0.2s)→<br>③三线射手<br>(1750阳光,间隔0.15s)→<br>④机枪射手<br>(2500阳光,间隔0.15s)→<br>⑤狂野机枪射手<br>(3500阳光,间隔0.1s)<br><br>2、线路2:<br>①南瓜壳(500阳光)→<br>②火焰南瓜壳(1000阳光)→<br>③冰焰南瓜壳(2000阳光)', color: 'red', bold: true }, { name: '出怪方式', value: '固定出怪', color: 'red', bold: true }, { name: '僵尸', value: '①普通僵尸 × 39<br>②路障僵尸 × 59<br>③铁桶僵尸 × 35<br>④铁栅门僵尸 × 41<br>⑤撑杆僵尸 × 32<br>⑥豌豆僵尸 × 29<br>⑦橄榄球僵尸 × 27<br>⑧小摔哥僵尸 × 26<br>⑨狂野机枪僵尸 × 7<br>⑩伽刚特尔 × 6', color: 'red', bold: true }, { name: '事件', value: '0个', color: 'red', bold: true }, { name: '旗数', value: '3', color: 'red', bold: true }, { name: '每旗波次', value: '10', color: 'red', bold: true } ]; const DEFAULT_CARDS = [ { image: '普通僵尸.png', name: '普通僵尸', health: '270', speed: '慢', type: '普通', version: '0.1', color: 'linear-gradient(135deg, #E8E0F0, #D1C4E9)', border: '#7E57C2' }, { image: '路障僵尸.png', name: '路障僵尸', health: '', speed: '', type: '', version: '', color: 'linear-gradient(135deg, #E8E0F0, #D1C4E9)', border: '#7E57C2' }, { image: '铁桶僵尸.png', name: '铁桶僵尸', health: '', speed: '', type: '', version: '', color: 'linear-gradient(135deg, #E8E0F0, #D1C4E9)', border: '#7E57C2' }, { image: '铁栅门僵尸.png', name: '铁栅门僵尸', health: '', speed: '', type: '', version: '', color: 'linear-gradient(135deg, #E8E0F0, #D1C4E9)', border: '#7E57C2' }, { image: '撑杆僵尸.png', name: '撑杆僵尸', health: '', speed: '', type: '', version: '', color: 'linear-gradient(135deg, #E8E0F0, #D1C4E9)', border: '#7E57C2' }, { image: '豌豆僵尸.png', name: '豌豆僵尸', health: '', speed: '', type: '', version: '', color: 'linear-gradient(135deg, #E8E0F0, #D1C4E9)', border: '#7E57C2' }, { image: '橄榄球僵尸.png', name: '橄榄球僵尸', health: '', speed: '', type: '', version: '', color: 'linear-gradient(135deg, #E8E0F0, #D1C4E9)', border: '#7E57C2' }, { image: '小摔哥僵尸.png', name: '小摔哥僵尸', health: '', speed: '', type: '', version: '', color: 'linear-gradient(135deg, #E8E0F0, #D1C4E9)', border: '#7E57C2' }, { image: '狂野机枪僵尸.png', name: '狂野机枪僵尸', health: '', speed: '', type: '', version: '', color: 'linear-gradient(135deg, #E8E0F0, #D1C4E9)', border: '#7E57C2' }, { image: '伽刚特尔.png', name: '伽刚特尔', health: '', speed: '', type: '', version: '', color: 'linear-gradient(135deg, #E8E0F0, #D1C4E9)', border: '#7E57C2' } ]; // 地图数据: 动态行列 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 infocardImage = document.getElementById('infocardImage'); const infocardImageSize = document.getElementById('infocardImageSize'); const mapImageFile = document.getElementById('mapImageFile'); const mapImageWidth = document.getElementById('mapImageWidth'); const mapImageLink = document.getElementById('mapImageLink'); const cardsContainer = document.getElementById('cardsContainer'); const initEventsContainer = document.getElementById('initEventsContainer'); const prepareEventsContainer = document.getElementById('prepareEventsContainer'); const startEventsContainer = document.getElementById('startEventsContainer'); const rewardsContainer = document.getElementById('rewardsContainer'); const hardModeFile = document.getElementById('hardModeFile'); const hardModeWidth = document.getElementById('hardModeWidth'); const hardModeLink = document.getElementById('hardModeLink'); const wikiOutputDiv = document.getElementById('wikiOutput'); const formPanel = document.getElementById('formPanel'); const importModal = document.getElementById('importModal'); const importTextarea = document.getElementById('importTextarea'); const mapGrid = document.getElementById('mapGrid'); const cellPopup = document.getElementById('cellPopup'); const mapRowsInput = document.getElementById('mapRows'); const mapColsInput = document.getElementById('mapCols'); let currentEditRow = -1, currentEditCol = -1; // ========== 属性编辑器 ========== function createColorSelect(sel='') { const s = document.createElement('select'); s.className='attr-color-select'; COLOR_OPTIONS.forEach(o => { const op = document.createElement('option'); op.value=o.value; op.textContent=o.label; if(o.value===sel) op.selected=true; s.appendChild(op); }); return s; } function createBoldCheck(chk=false) { const d = document.createElement('div'); d.className='attr-bold-check'; d.innerHTML = `<input type="checkbox" ${chk?'checked':''}><span>加粗</span>`; return d; } function createAttrItem(d={name:'',value:'',color:'',bold:false}) { const div = document.createElement('div'); div.className='attr-item'; const ni = document.createElement('input'); ni.type='text'; ni.className='attr-name'; ni.placeholder='属性名'; ni.value=d.name||''; const vi = document.createElement('input'); vi.type='text'; vi.className='attr-value'; vi.placeholder='属性值'; vi.value=d.value||''; const cs = createColorSelect(d.color||''); const bc = createBoldCheck(d.bold||false); const cb = bc.querySelector('input'); const rb = document.createElement('button'); rb.type='button'; rb.className='attr-remove-btn'; rb.textContent='✕'; rb.addEventListener('click',()=>{ if(attrContainer.children.length>1){ div.remove(); refreshPreview(); } else alert('至少保留一个属性项'); }); div.append(ni,vi,cs,bc,rb); ni.addEventListener('input',refreshPreview); vi.addEventListener('input',refreshPreview); cs.addEventListener('change',refreshPreview); cb.addEventListener('change',refreshPreview); return div; } function setAttrItems(a){ attrContainer.innerHTML=''; a.forEach(x=>attrContainer.appendChild(createAttrItem(x))); } function collectAttrs(){ const a=[]; attrContainer.querySelectorAll('.attr-item').forEach(item=>{ const n=item.querySelector('.attr-name').value.trim(); const v=item.querySelector('.attr-value').value.trim(); if(!n&&!v) return; a.push({name:n||'?',value:v||'',color:item.querySelector('.attr-color-select').value,bold:item.querySelector('.attr-bold-check input').checked}); }); return a; } // ========== 地图预放置 ========== 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() { mapGrid.innerHTML = ''; const headerRow = document.createElement('div'); headerRow.className='map-grid-row'; headerRow.innerHTML = '<div class="map-grid-label"></div>'; for(let c=1;c<=mapCols;c++) headerRow.innerHTML += `<div class="map-grid-label">列${c}</div>`; mapGrid.appendChild(headerRow); for(let r=0;r<mapRows;r++) { const row = document.createElement('div'); row.className='map-grid-row'; row.innerHTML = `<div class="map-grid-label">行${r+1}</div>`; for(let c=0;c<mapCols;c++) { const cell = document.createElement('div'); cell.className = 'map-grid-cell'; if(mapData[r][c]) { cell.classList.add('placed'); cell.title = mapData[r][c].file; } cell.addEventListener('click',()=>openCellPopup(r,c)); cell.addEventListener('contextmenu',(e)=>{ e.preventDefault(); mapData[r][c]=null; renderMapGrid(); refreshPreview(); }); row.appendChild(cell); } mapGrid.appendChild(row); } } function openCellPopup(r,c) { currentEditRow = r; currentEditCol = c; const d = mapData[r][c]; document.getElementById('popupFile').value = d ? d.file : (document.getElementById('currentPlantFile').value || ''); document.getElementById('popupLink').value = d ? d.link : (document.getElementById('currentPlantLink').value || ''); cellPopup.style.display = 'block'; const cells = mapGrid.querySelectorAll('.map-grid-cell'); const idx = r*mapCols + c + mapCols; if(cells[idx]) { const rect = cells[idx].getBoundingClientRect(); cellPopup.style.left = Math.min(rect.right+8, window.innerWidth-220)+'px'; cellPopup.style.top = Math.min(rect.top, window.innerHeight-200)+'px'; } } function saveCellPopup() { if(currentEditRow<0) return; const file = document.getElementById('popupFile').value.trim(); const link = document.getElementById('popupLink').value.trim(); if(file) { mapData[currentEditRow][currentEditCol] = {file, link: link||file.replace('.png','')}; } else { mapData[currentEditRow][currentEditCol] = null; } cellPopup.style.display = 'none'; renderMapGrid(); refreshPreview(); } function clearMap() { initMapData(); renderMapGrid(); refreshPreview(); } function buildMapTable() { let hasAny = false; for(let r=0;r<mapRows;r++) for(let c=0;c<mapCols;c++) if(mapData[r][c]) hasAny=true; 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! !!`; 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`; for(let c=0;c<mapCols;c++) { const d = mapData[r][c]; const content = d ? `[[文件:${d.file}|50px|link=${d.link}]]` : ''; table += `| style="height:80px;border:2px solid #ffffff;background:#11ff00;" | ${content}\n`; } } table += '|}\n'; return table; } function parseMapTable(src) { const tableMatch = src.match(/== 地图预放置 ==\s*\n\{\|[\s\S]*?\n\|\}/); 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 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(); } // ========== 僵尸卡片 ========== function createCardItem(d) { d = d || { image:'', name:'', health:'', speed:'', type:'', version:'', color:'linear-gradient(135deg, #E8E0F0, #D1C4E9)', border:'#7E57C2' }; const div = document.createElement('div'); div.className='card-item'; div.innerHTML = ` <div class="card-row"> <input type="text" class="card-image" value="${d.image||''}" placeholder="图片" style="flex:1.5; min-width:80px;"> <input type="text" class="card-name" value="${d.name||''}" placeholder="名称" style="flex:1; min-width:60px;"> <input type="text" class="card-health" value="${d.health||''}" placeholder="血量" style="flex:1; min-width:60px;"> <button type="button" class="event-remove-btn card-remove">✕</button> </div> <div class="card-row"> <input type="text" class="card-speed" value="${d.speed||''}" placeholder="速度" style="flex:0.8;"> <input type="text" class="card-type" value="${d.type||''}" placeholder="类型" style="flex:0.8;"> <input type="text" class="card-version" value="${d.version||''}" placeholder="版本" style="flex:0.6;"> <input type="text" class="card-color" value="${d.color||''}" placeholder="渐变" style="flex:2;"> <input type="text" class="card-border" value="${d.border||''}" placeholder="边框" style="flex:1;"> </div>`; cardsContainer.appendChild(div); div.querySelector('.card-remove').addEventListener('click',()=>{ if(cardsContainer.children.length>1){ div.remove(); refreshPreview(); } else alert('至少保留一个卡片'); }); div.querySelectorAll('input').forEach(i=>i.addEventListener('input',refreshPreview)); } function collectCards() { const cards = []; cardsContainer.querySelectorAll('.card-item').forEach(item => { cards.push({ image: item.querySelector('.card-image')?.value.trim()||'', name: item.querySelector('.card-name')?.value.trim()||'', health: item.querySelector('.card-health')?.value.trim()||'', speed: item.querySelector('.card-speed')?.value.trim()||'', type: item.querySelector('.card-type')?.value.trim()||'', version: item.querySelector('.card-version')?.value.trim()||'', color: item.querySelector('.card-color')?.value.trim()||'', border: item.querySelector('.card-border')?.value.trim()||'' }); }); return cards; } // ========== 事件 ========== function collectEvents(container, cls) { const e = []; container.querySelectorAll(`.${cls}`).forEach(inp => { const v = inp.value.trim(); if(v) e.push(v); }); return e; } function createEventItem(container, cls, value='') { const div = document.createElement('div'); div.className='event-item'; const inp = document.createElement('input'); inp.type='text'; inp.className=cls; inp.value=value; inp.placeholder='事件描述'; inp.addEventListener('input', refreshPreview); const rb = document.createElement('button'); rb.type='button'; rb.className='event-remove-btn'; rb.textContent='✕'; rb.addEventListener('click', ()=>{ if(container.children.length>1){ div.remove(); refreshPreview(); } else alert('至少保留一项'); }); div.append(inp, rb); container.appendChild(div); } // ========== 奖励 ========== function collectRewards() { const r = []; rewardsContainer.querySelectorAll('.event-item').forEach(item => { const f = item.querySelector('.reward-file'); if(!f) return; const file = f.value.trim(), link = item.querySelector('.reward-link')?.value.trim() || ''; if(file) r.push({file, link}); }); return r; } function createRewardItem(file='', link='') { const div = document.createElement('div'); div.className='event-item'; const fi = document.createElement('input'); fi.type='text'; fi.className='reward-file'; fi.value=file; fi.placeholder='文件名'; fi.style.flex='2'; fi.addEventListener('input', refreshPreview); const li = document.createElement('input'); li.type='text'; li.className='reward-link'; li.value=link; li.placeholder='链接'; li.style.flex='1'; li.addEventListener('input', refreshPreview); const rb = document.createElement('button'); rb.type='button'; rb.className='event-remove-btn reward-remove'; rb.textContent='✕'; rb.addEventListener('click', ()=>{ if(rewardsContainer.children.length>1){ div.remove(); refreshPreview(); } else alert('至少保留一项奖励'); }); div.append(fi, li, rb); rewardsContainer.appendChild(div); } function clearContainer(c) { while(c.children.length>0) c.removeChild(c.firstChild); } // ========== 构建Wiki文本 ========== function buildInfocard() { const img = infocardImage.value.trim() || '豌豆进化.png'; const sz = infocardImageSize.value.trim() || '200px'; const attrs = collectAttrs(); const preplaced = []; 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==='地图预放置')) { attrs.push({name:'地图预放置', value:preplaced.join('<br>'), color:'red', bold:true}); } const parts = attrs.map(a => { let v = a.value; if(a.bold) v = `'''${v}'''`; if(a.color) v = `<font color=${a.color}>${v}</font>`; return `${a.name}:${v}`; }); return `{{Infocard|图片=${img}|图片尺寸=${sz}|属性=${parts.join(',')}}}`; } function buildCardsBlock() { const cards = collectCards(); if (cards.length === 0) return ''; const lines = cards.map(c => { return `{{Card|image=[[文件:${c.image}|75px|${c.name}|link=${c.name}]]|name=${c.name}|health=${c.health}|speed=${c.speed}|type=${c.type}|version=${c.version}|color=${c.color}|border=${c.border}}}`; }); return `<div style="float:left" class="pvzhe-card-container">\n${lines.join('\n')}\n</div>`; } function buildWikiText() { const infocard = buildInfocard(); const mf = mapImageFile.value.trim() || '射击模式.png'; const mw = mapImageWidth.value.trim() || '350px'; const ml = mapImageLink.value.trim() || '射击模式'; const cardsBlock = buildCardsBlock(); const mapTable = buildMapTable(); const initE = collectEvents(initEventsContainer, 'init-event'); const prepE = collectEvents(prepareEventsContainer, 'prepare-event'); const startE = collectEvents(startEventsContainer, 'start-event'); const rewards = collectRewards(); const hf = hardModeFile.value.trim(), hw = hardModeWidth.value.trim()||'250px', hl = hardModeLink.value.trim(); let es = ''; if(initE.length>0 || prepE.length>0 || startE.length>0) { es = '== 事件 ==\n'; es += `=== 地图行为(初始化) ===\n<hr/>\n`; if(initE.length===0 || (initE.length===1 && initE[0]==='无')) es += '*无\n'; else initE.forEach(e=>es+=`*${e}<br>\n`); es += `=== 地图行为(准备阶段) ===\n<hr/>\n`; if(prepE.length===0 || (prepE.length===1 && prepE[0]==='无')) es += '*无\n'; else prepE.forEach(e=>es+=`*${e}\n`); es += `=== 地图行为(开始后行为) ===\n<hr/>\n`; if(startE.length===0 || (startE.length===1 && startE[0]==='无')) es += '*无\n'; else startE.forEach(e=>es+=`*${e}<br>\n`); } let rs = ''; if(rewards.length>0 || hf) { rs = `== 通关奖励 ==\n<div style="display:flex; align-items:center;">\n`; rewards.forEach(r=>rs+=`[[文件:${r.file}|link=${r.link||r.file}]]\n`); if(hf) { rs += `<div style="flex:1; display:flex; justify-content:center;">\n[[File:${hf}|${hw}|link=${hl||''}]]\n</div>\n`; } rs += `</div>\n`; } 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;width:100%;"> <div style="display: flex; justify-content: flex-end;"> [[File:${mf}|${mw}|link=${ml}]] </div>`; if (cardsBlock) headerBlock += `\n${cardsBlock}`; if (mapTable) headerBlock += `\n${mapTable}`; headerBlock += `\n</div>\n</div>`; return `${headerBlock} ${infocard} ${es}${rs}`; } function refreshPreview() { const t = buildWikiText(); wikiOutputDiv.innerHTML = t.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); } // ========== 解析导入 ========== function parseWikiSource(src) { try { let img='豌豆进化.png', sz='200px'; const attrs=[]; const im = src.match(/{{Infocard\|图片=([^|]+)\|图片尺寸=([^|]+)\|属性=([^}]+)}}/); if(im) { img=im[1].trim(); sz=im[2].trim(); const attrStr=im[3]; const parts=[]; let depth=0, cur=''; for(let i=0;i<attrStr.length;i++){ const ch=attrStr[i]; if(ch==='<') depth++; if(ch==='>') depth--; if(ch===','&&depth===0){ parts.push(cur); cur=''; } else cur+=ch; } if(cur) parts.push(cur); parts.forEach(p=>{ const ci=p.indexOf(':'); if(ci===-1) return; const name=p.substring(0,ci).trim(); let value=p.substring(ci+1).trim(); let color='', bold=false; const fm=value.match(/<font color=([^>]+)>(.*?)<\/font>/i); if(fm){ const cn=fm[1].replace(/['"]/g,''); if(COLOR_MAP[cn]) color=cn; value=fm[2]; } const bm=value.match(/^'''(.+)'''$/); if(bm){ bold=true; value=bm[1]; } if(name!=='地图预放置') attrs.push({name,value,color,bold}); }); } let mf='射击模式.png', mw='350px', ml='射击模式'; const fm=src.match(/\[\[File:([^|]+)\|([^|]+)\|link=([^\]]+)\]\]/); if(fm){ mf=fm[1]; mw=fm[2]; ml=fm[3]; } const cards=[]; const cardRegex = /{{Card\|image=\[\[文件:([^|]+)\|[^|]+\|([^|]+)\|link=([^\]]+)\]\]\|name=([^|]+)\|health=([^|]*)\|speed=([^|]*)\|type=([^|]*)\|version=([^|]*)\|color=([^|]+)\|border=([^}]+)}}/g; let cm; while((cm=cardRegex.exec(src))!==null){ cards.push({image:cm[1], name:cm[4], health:cm[5], speed:cm[6], type:cm[7], version:cm[8], color:cm[9], border:cm[10]}); } const initE=[], prepE=[], startE=[]; const is=src.match(/=== 地图行为(初始化) ===\s*<hr\/>\s*([\s\S]*?)(?====|$)/); if(is){ const ms=is[1].matchAll(/\*([^<\n]*)/g); for(const m of ms){ const v=m[1].trim(); if(v&&v!=='无') initE.push(v); } } const ps=src.match(/=== 地图行为(准备阶段) ===\s*<hr\/>\s*([\s\S]*?)(?====|$)/); if(ps){ const ms=ps[1].matchAll(/\*([^\n]*)/g); for(const m of ms){ const v=m[1].trim(); if(v&&v!=='无') prepE.push(v); } } const ss=src.match(/=== 地图行为(开始后行为) ===\s*<hr\/>\s*([\s\S]*?)(?====|$)/); if(ss){ const ms=ss[1].matchAll(/\*([^<\n]*)/g); for(const m of ms){ const v=m[1].trim(); if(v&&v!=='无') startE.push(v); } } const rewards=[]; const rs=src.match(/== 通关奖励 ==\s*([\s\S]*?)(?=\n\n|$)/); if(rs){ const ms=rs[1].matchAll(/\[\[文件:([^|]+)\|link=([^\]]+)\]\]/g); for(const m of ms) rewards.push({file:m[1],link:m[2]}); } let hf='', hw='250px', hl=''; if(rs){ const ms=rs[1].matchAll(/\[\[File:([^|]+)\|([^|]+)\|link=([^\]]+)\]\]/g); const arr=Array.from(ms); if(arr.length>1){ hf=arr[1][1]; hw=arr[1][2]; hl=arr[1][3]; } } return {image:img,imageSize:sz,attrs:attrs.length>0?attrs:DEFAULT_ATTRS,mapFile:mf,mapWidth:mw,mapLink:ml,cards:cards,initEvents:initE,prepareEvents:prepE,startEvents:startE,rewards,hardFile:hf,hardWidth:hw,hardLink:hl}; } catch(e){ console.error(e); return null; } } function applyParsedData(d) { if(!d) return; if(d.image) infocardImage.value=d.image; if(d.imageSize) infocardImageSize.value=d.imageSize; if(d.attrs&&d.attrs.length>0) setAttrItems(d.attrs); if(d.mapFile) mapImageFile.value=d.mapFile; if(d.mapWidth) mapImageWidth.value=d.mapWidth; if(d.mapLink) mapImageLink.value=d.mapLink; if(d.cards&&d.cards.length>0){ clearContainer(cardsContainer); d.cards.forEach(c=>createCardItem(c)); } if(d.initEvents!==undefined){ clearContainer(initEventsContainer); if(d.initEvents.length===0) createEventItem(initEventsContainer,'init-event','无'); else d.initEvents.forEach(e=>createEventItem(initEventsContainer,'init-event',e)); } if(d.prepareEvents!==undefined){ clearContainer(prepareEventsContainer); if(d.prepareEvents.length===0) createEventItem(prepareEventsContainer,'prepare-event','无'); else d.prepareEvents.forEach(e=>createEventItem(prepareEventsContainer,'prepare-event',e)); } if(d.startEvents!==undefined){ clearContainer(startEventsContainer); if(d.startEvents.length===0) createEventItem(startEventsContainer,'start-event','无'); else d.startEvents.forEach(e=>createEventItem(startEventsContainer,'start-event',e)); } if(d.rewards&&d.rewards.length>0){ clearContainer(rewardsContainer); d.rewards.forEach(r=>createRewardItem(r.file,r.link)); } if(d.hardFile) hardModeFile.value=d.hardFile; if(d.hardWidth) hardModeWidth.value=d.hardWidth; if(d.hardLink) hardModeLink.value=d.hardLink; const src = importTextarea.value; if(src) parseMapTable(src); else { renderMapGrid(); } refreshPreview(); } function importFromSource(src){ const d=parseWikiSource(src); if(d){ importTextarea.value = src; applyParsedData(d); alert('✅ 导入成功!'); } else alert('❌ 解析失败'); } function bindEvents() { document.getElementById('addAttrBtn').addEventListener('click',()=>{ attrContainer.appendChild(createAttrItem()); refreshPreview(); }); document.getElementById('addCardBtn').addEventListener('click',()=>{ createCardItem(); refreshPreview(); }); document.getElementById('addInitEventBtn').addEventListener('click',()=>{ createEventItem(initEventsContainer,'init-event',''); refreshPreview(); }); document.getElementById('addPrepareEventBtn').addEventListener('click',()=>{ createEventItem(prepareEventsContainer,'prepare-event',''); refreshPreview(); }); document.getElementById('addStartEventBtn').addEventListener('click',()=>{ createEventItem(startEventsContainer,'start-event',''); refreshPreview(); }); document.getElementById('addRewardBtn').addEventListener('click',()=>{ createRewardItem('',''); refreshPreview(); }); infocardImage.addEventListener('input',refreshPreview); infocardImageSize.addEventListener('input',refreshPreview); [mapImageFile,mapImageWidth,mapImageLink,hardModeFile,hardModeWidth,hardModeLink].forEach(i=>i.addEventListener('input',refreshPreview)); document.getElementById('generateBtn').addEventListener('click',refreshPreview); document.getElementById('copyBtn').addEventListener('click',async()=>{ try{ await navigator.clipboard.writeText(buildWikiText()); alert('✅ 已复制'); } catch{ alert('❌ 复制失败'); } }); formPanel.addEventListener('input',e=>{ if(e.target.tagName==='INPUT'||e.target.tagName==='TEXTAREA') refreshPreview(); }); document.getElementById('importBtn').addEventListener('click',()=>{ importTextarea.value=''; importModal.style.display='flex'; }); document.getElementById('cancelImportBtn').addEventListener('click',()=>importModal.style.display='none'); document.getElementById('confirmImportBtn').addEventListener('click',()=>{ const s=importTextarea.value.trim(); if(s){ importFromSource(s); importModal.style.display='none'; } else alert('请粘贴源码'); }); document.getElementById('pasteToImportBtn').addEventListener('click',()=>importFromSource(buildWikiText())); 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('popupSave').addEventListener('click',saveCellPopup); document.getElementById('popupClear').addEventListener('click',()=>{ if(currentEditRow>=0){ mapData[currentEditRow][currentEditCol]=null; renderMapGrid(); refreshPreview(); } cellPopup.style.display='none'; }); document.addEventListener('click',(e)=>{ if(!cellPopup.contains(e.target) && !e.target.classList.contains('map-grid-cell')) cellPopup.style.display='none'; }); // 标签页 document.querySelectorAll('.tab-btn').forEach(btn=>{ btn.addEventListener('click',()=>{ document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active')); btn.classList.add('active'); const tab=btn.dataset.tab; document.getElementById('tabInit').style.display = tab==='init'?'block':'none'; document.getElementById('tabPrepare').style.display = tab==='prepare'?'block':'none'; document.getElementById('tabStart').style.display = tab==='start'?'block':'none'; }); }); } function init(){ setAttrItems(DEFAULT_ATTRS); clearContainer(cardsContainer); DEFAULT_CARDS.forEach(c=>createCardItem(c)); clearContainer(initEventsContainer); createEventItem(initEventsContainer,'init-event','无'); clearContainer(prepareEventsContainer); createEventItem(prepareEventsContainer,'prepare-event','无'); clearContainer(startEventsContainer); createEventItem(startEventsContainer,'start-event','无'); clearContainer(rewardsContainer); createRewardItem('钱袋子.png', '钱袋子'); initMapData(); mapData[2][0] = {file:'豌豆射手.png', link:'豌豆射手'}; renderMapGrid(); bindEvents(); refreshPreview(); } init(); })(); </script> </body> </html> </div> </includeonly>
返回
微件:LevelEditor
。
查看“︁微件:LevelEditor”︁的源代码
来自植物大战僵尸杂交版Wiki