2290 字
11 分钟
无限塔防
2026-01-06
<!DOCTYPE html>
<!--无限塔防 - v4.1-->
<html lang=zh-CN><meta charset=UTF-8><meta content="width=device-width,initial-scale=1.0" name=viewport><title>无限塔防--亿万富翁</title><style>:root{--bg-color:#0b0c10;--ui-bg:#1f2833cc;--text-color:#66fcf1;--accent:#45a29e;--danger:#f44;--gold:gold;--purple:#b829ea;--cosmic:#ff6a00}body{background-color:var(--bg-color);color:#fff;user-select:none;background-image:linear-gradient(#45a29e0d 1px,#0000 1px),linear-gradient(90deg,#45a29e0d 1px,#0000 1px);background-size:40px 40px;margin:0;font-family:Segoe UI,sans-serif;overflow:hidden}#game-container{width:100vw;height:100vh;position:relative}canvas{touch-action:none;display:block}.glass-panel{background:var(--ui-bg);backdrop-filter:blur(12px)saturate(120%);border:1px solid #66fcf140;border-radius:12px;transition:all .3s;box-shadow:0 6px 25px #0009}#left-controls{z-index:10;gap:10px;padding:8px;display:flex;position:absolute;top:20px;left:20px}#right-controls{z-index:10;flex-direction:column;align-items:flex-end;gap:10px;display:flex;position:absolute;top:20px;right:20px}.control-btn{color:#aaa;cursor:pointer;background:#0b0c10cc;border:1px solid #333;border-radius:5px;padding:6px 14px;font-size:16px;font-weight:700;transition:all .2s ease-out}.control-btn:hover{border-color:var(--text-color);color:#fff;transform:translateY(-2px)}.speed-btn.active{color:var(--text-color);border-color:var(--text-color);background:#66fcf133;box-shadow:0 0 10px #66fcf166}#top-bar{pointer-events:none;z-index:10;gap:30px;padding:10px 30px;display:flex;position:absolute;top:20px;left:50%;transform:translate(-50%)}.stat-item{text-shadow:0 0 5px #000c;align-items:center;gap:10px;font-size:20px;font-weight:700;display:flex}#money-display{color:var(--gold);text-shadow:0 0 10px #ffd70080}#wave-display{color:var(--text-color)}#lives-display{color:var(--danger)}#tower-menu{z-index:10;gap:15px;padding:15px 25px;display:flex;position:absolute;bottom:20px;left:50%;transform:translate(-50%)}.tower-btn{cursor:pointer;color:#aaa;background:#0b0c10cc;border:2px solid #333;border-radius:10px;flex-direction:column;justify-content:center;align-items:center;width:80px;height:95px;font-size:13px;transition:all .2s ease-out;display:flex;position:relative}.tower-btn:active{transition-duration:.1s;transform:scale(.95)}.tower-btn:hover{border-color:var(--text-color);color:#fff;transform:translateY(-5px);box-shadow:0 5px 15px #66fcf14d}.tower-icon{border:2px solid;border-radius:50%;width:35px;height:35px;margin-bottom:8px;box-shadow:0 0 10px}.cost-tag{margin-top:4px;font-weight:700}.hotkey-badge{color:#fff;text-transform:uppercase;background:#333;border-radius:3px;padding:2px 5px;font-size:10px;position:absolute;top:4px;left:4px}#skill-menu{z-index:10;gap:10px;padding:10px;display:flex;position:absolute;bottom:20px;right:20px}.skill-btn{border:2px solid var(--purple);width:60px;height:60px;color:var(--purple);cursor:pointer;background:#14001ecc;border-radius:50%;justify-content:center;align-items:center;font-size:24px;transition:all .2s ease-out;display:flex;position:relative;box-shadow:0 0 15px #b829ea66}.skill-btn:hover:not(:disabled){background:var(--purple);color:#fff;box-shadow:0 0 25px var(--purple);transform:scale(1.1)}.skill-btn:after{content:attr(data-cost);color:#fff;white-space:nowrap;text-shadow:0 2px 4px #000;font-size:12px;font-weight:700;position:absolute;bottom:-22px;left:50%;transform:translate(-50%)}.skill-hotkey{color:#000;text-transform:uppercase;background:#fff;border-radius:50%;justify-content:center;align-items:center;width:20px;height:20px;font-size:12px;font-weight:700;display:flex;position:absolute;top:-5px;right:-5px}#action-menu{z-index:20;opacity:0;pointer-events:none;flex-direction:column;gap:6px;width:170px;padding:12px;transition:opacity .2s,transform .2s;display:flex;position:absolute;transform:translate(-50%,-110%)scale(.9)}#action-menu.visible{opacity:1;pointer-events:auto;transform:translate(-50%,-120%)scale(1)}#action-menu.visible.bottom{transform:translate(-50%,20%)scale(1)}#action-title{text-align:center;color:#fff;margin-bottom:2px;font-size:14px;font-weight:700}#action-dmg{text-align:center;color:#fa0;margin-bottom:8px;font-size:12px}.action-btn{cursor:pointer;color:#fff;border:none;border-radius:5px;justify-content:space-between;padding:8px;font-size:12px;font-weight:700;transition:all .2s;display:flex}#btn-upgrade{background:#45a29e}#btn-upgrade:hover{color:#000;background:#66fcf1}#btn-awaken{background:var(--purple);box-shadow:0 0 10px var(--purple);display:none}#btn-awaken:hover{color:#fff;background:#d355ff}#btn-ult-awaken{background:var(--cosmic);box-shadow:0 0 15px var(--cosmic);display:none}#btn-ult-awaken:hover{color:#000;background:#fa0}#btn-sell{background:#c53030}#btn-sell:hover{color:#000;background:#fc8181}.target-select{width:100%;color:var(--text-color);border:1px solid var(--accent);background:#111;border-radius:4px;outline:none;margin-bottom:4px;padding:4px;font-size:12px}#event-banner{border:1px solid var(--gold);color:var(--gold);pointer-events:none;z-index:9;background:#ffd70033;border-radius:20px;padding:5px 20px;font-weight:700;animation:2s infinite pulse;display:none;position:absolute;top:80px;left:50%;transform:translate(-50%)}@keyframes pulse{0%{box-shadow:0 0 5px var(--gold)}50%{box-shadow:0 0 20px var(--gold)}to{box-shadow:0 0 5px var(--gold)}}.modal-box{z-index:90;opacity:0;pointer-events:none;flex-direction:column;gap:15px;padding:25px;transition:opacity .3s,transform .3s;display:flex;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)scale(.95)}.modal-box.visible{opacity:1;pointer-events:auto;transform:translate(-50%,-50%)scale(1)}.modal-header{border-bottom:1px solid #333;justify-content:space-between;align-items:center;padding-bottom:10px;display:flex}#talent-modal{width:400px}.talent-item{background:#00000080;border:1px solid #333;border-radius:8px;justify-content:space-between;align-items:center;padding:10px;transition:all .2s;display:flex}.talent-item:hover{border-color:var(--accent);background-color:#45a29e1a}.talent-btn{background:var(--accent);color:#fff;cursor:pointer;border:none;border-radius:5px;padding:5px 10px;font-weight:700;transition:background-color .2s}.talent-btn:hover:not(:disabled){background:var(--text-color);color:#000}.talent-btn:disabled{color:#666;cursor:not-allowed;background:#333}#tutorial-modal,#settings-modal{width:600px;max-height:80vh;overflow-y:auto}::-webkit-scrollbar{width:6px}::-webkit-scrollbar-thumb{background:var(--accent);border-radius:3px}.tut-section{border-left:4px solid var(--accent);background:#0006;border-radius:8px;margin-bottom:15px;padding:15px}.tut-section h3{color:var(--gold);margin-top:0}.setting-row{background:#0000004d;border-radius:5px;justify-content:space-between;align-items:center;margin-bottom:10px;padding:8px;display:flex}.setting-row input[type=number]{width:50px;color:var(--text-color);border:1px solid var(--accent);text-align:center;background:#111;border-radius:3px;outline:none;padding:4px}.key-bind-btn{border:1px solid var(--accent);color:var(--text-color);cursor:pointer;text-transform:uppercase;background:#222;border-radius:4px;min-width:60px;padding:4px 12px;font-weight:700}.key-bind-btn.listening{background:var(--text-color);color:#000;animation:1s infinite pulse}.action-group{gap:10px;margin-top:15px;display:flex}.action-group .control-btn{text-align:center;flex:1}.danger-zone{border:1px solid var(--danger);border-radius:8px;margin-top:15px;padding:15px}.danger-input-group{gap:10px;margin-top:10px;display:none}.diff-text{margin:5px 0;font-family:monospace;font-size:16px}.diff-old{color:#aaa;margin-right:10px;text-decoration:line-through}.diff-new{color:#0fa;font-weight:700}.overlay{backdrop-filter:blur(8px);z-index:80;opacity:0;pointer-events:none;background:#0009;flex-direction:column;justify-content:center;align-items:center;width:100%;height:100%;transition:opacity .4s;display:flex;position:absolute;top:0;left:0}.overlay.visible{opacity:1;pointer-events:auto}.overlay h1,.overlay p,.overlay .big-btn{opacity:0;transition:opacity .4s .2s,transform .4s .2s;transform:translateY(20px)}.overlay.visible h1,.overlay.visible p,.overlay.visible .big-btn{opacity:1;transform:translateY(0)}.overlay h1{color:var(--text-color);text-shadow:0 0 20px var(--accent);letter-spacing:8px;margin-bottom:20px;font-size:60px}#game-over{z-index:100;background:#0b0c10f2}#game-over h1{color:var(--danger);text-shadow:0 0 20px red}.big-btn{color:var(--text-color);border:2px solid var(--text-color);cursor:pointer;text-transform:uppercase;letter-spacing:2px;background:0 0;border-radius:8px;padding:15px 40px;font-size:24px;transition:all .3s}.big-btn:hover{background:var(--text-color);color:#000;box-shadow:0 0 30px var(--text-color);transform:scale(1.05)}.floating-text{pointer-events:none;text-shadow:0 2px 4px #000c;z-index:50;font-weight:900;animation:forwards floatUp;position:absolute}@keyframes floatUp{0%{opacity:1;transform:translateY(0)scale(.8)}20%{transform:translateY(-10px)scale(1.2)}to{opacity:0;transform:translateY(-60px)scale(1)}}.dmg-text{pointer-events:none;color:#fff;text-shadow:1px 1px 2px #000;z-index:55;font-family:Impact,sans-serif;font-size:14px;font-weight:700;animation:.8s forwards popUp;position:absolute}.dmg-crit{color:gold;text-shadow:0 0 6px #f40,2px 2px #000;z-index:60;font-size:24px;animation:1s forwards critPop}@keyframes popUp{0%{opacity:1;transform:translate(-50%)scale(.5)}50%{transform:translate(-50%,-25px)scale(1.2)}to{opacity:0;transform:translate(-50%,-45px)scale(1)}}@keyframes critPop{0%{opacity:1;transform:translate(-50%)scale(.5)}30%{transform:translate(-50%,-35px)scale(1.6)rotate(-5deg)}to{opacity:0;transform:translate(-50%,-60px)scale(1.2)rotate(5deg)}}</style><body>
<div id=game-container><canvas id=gameCanvas></canvas><div class=glass-panel id=left-controls><button class=control-btn id=btn-pause onclick=togglePause()><span style=color:#66fcf1>⏸ 暂停 (<span id=ui-key-pause>Space</span>)</span></button><button class=control-btn onclick=openTutorial()>📖 详细图鉴指南</button></div><div id=right-controls><div class=glass-panel style=gap:5px;padding:8px;display:flex><button class="control-btn active speed-btn" id=speed-btn-0 onclick=setSpeedIndex(0)>1x</button><button class="control-btn speed-btn" id=speed-btn-1 onclick=setSpeedIndex(1)>2x</button><button class="control-btn speed-btn" id=speed-btn-2 onclick=setSpeedIndex(2)>3x</button></div><div style=gap:10px;display:flex><button class="control-btn glass-panel" onclick=openSettings()>⚙ 设置</button><button class="control-btn glass-panel" onclick=openTalents()>⚙ 科技树</button></div></div><div class=glass-panel id=top-bar><div title="资金 (每波结束享5%理财利息)" class=stat-item><span style=color:gold>💰</span><span id=money-display></span></div><div class=stat-item><span style=color:#66fcf1>☣</span> 第 <span id=wave-display>1</span> 波</div><div class=stat-item><span style=color:#f44;font-size:24px>❤</span><span id=lives-display></span></div><div class=stat-item title=历史最佳>🏆 <span id=best-wave-display>1</span></div></div><div id=event-banner>📈 <span id=event-text>事件</span></div><div class=glass-panel id=skill-menu><button data-cost="耗资 100M" title="EMP 轨道炮:秒杀普通怪,Boss扣血50%" class=skill-btn id=btn-emp onclick=castEMP()>📡<div class=skill-hotkey id=ui-key-emp>Q</div></button><button data-cost="耗资 100M" title="黑客入侵:全场冰冻并暂停刷怪 5 秒" class=skill-btn id=btn-hacker onclick=castHacker()>💻<div class=skill-hotkey id=ui-key-hacker>W</div></button></div><div class=glass-panel id=action-menu><div id=action-title>Lv.1 机枪塔</div><div id=action-dmg>总输出: 0</div><select class=target-select id=target-mode onchange=changeTargetMode()><option value=first>索敌: 优先最前<option value=close>索敌: 优先最近<option value=strong>索敌: 优先高血</select><button class=action-btn id=btn-upgrade onclick=upgradeTower()><span>升级</span><span id=txt-up-cost></span></button><button class=action-btn id=btn-awaken onclick=awakenTower()><span>💎 终极觉醒</span><span id=txt-awk-cost></span></button><button class=action-btn id=btn-ult-awaken onclick=ultimateAwakenTower()><span>🌌 究极觉醒</span><span id=txt-ult-cost></span></button><button class=action-btn id=btn-sell onclick=sellTower()><span>出售</span><span id=txt-sell-price></span></button></div><div class=glass-panel id=tower-menu><div onpointerdown="startMenuDrag(event, 'machinegun')" class=tower-btn title=射速快,适合破盾><div class=hotkey-badge id=ui-key-t1>1</div><div class=tower-icon style=color:#aaa></div><div>机枪塔</div><div class=cost-tag>$5M</div></div><div onpointerdown="startMenuDrag(event, 'sniper')" class=tower-btn title=超高单体伤害><div class=hotkey-badge id=ui-key-t2>2</div><div class=tower-icon style=color:#48f></div><div>狙击塔</div><div class=cost-tag>$15M</div></div><div onpointerdown="startMenuDrag(event, 'ice')" class=tower-btn title=减速敌人><div class=hotkey-badge id=ui-key-t3>3</div><div class=tower-icon style=color:#0ff></div><div>冰冻塔</div><div class=cost-tag>$30M</div></div><div onpointerdown="startMenuDrag(event, 'splash')" class=tower-btn title=范围爆炸伤害><div class=hotkey-badge id=ui-key-t4>4</div><div class=tower-icon style=color:#fa0></div><div>榴弹塔</div><div class=cost-tag>$50M</div></div><div onpointerdown="startMenuDrag(event, 'laser')" class=tower-btn title=穿透无视护甲,反隐><div class=hotkey-badge id=ui-key-t5>5</div><div class=tower-icon style=color:#f0f></div><div>天基激光</div><div class=cost-tag>$100M</div></div><div onpointerdown="startMenuDrag(event, 'signal')" class=tower-btn title=自身无伤,大幅增强周围友军,反隐><div class=hotkey-badge id=ui-key-t6>6</div><div class=tower-icon style=color:#0fa;border-style:dashed></div><div>信号塔</div><div class=cost-tag>$20M</div></div></div><div class="glass-panel modal-box" id=settings-modal><div class=modal-header><h2 style=color:var(--text-color);margin:0>⚙ 系统设置</h2><button class=control-btn onclick=closeSettings()>关闭</button></div><div style=color:#ccc;font-size:14px><div class=tut-section><h3>⏱ 倍速设置 (1x - 5x)</h3><div class=setting-row><span>倍速档位一</span><input id=set-speed-1 max=5 min=1 type=number></div><div class=setting-row><span>倍速档位二</span><input id=set-speed-2 max=10 min=1 type=number></div><div class=setting-row><span>倍速档位三</span><input id=set-speed-3 max=15 min=1 type=number></div></div><div class=tut-section><h3>⌨ 自定义快捷键</h3><div class=setting-row><span>暂停/继续</span><button onclick="listenKey('pause')" class=key-bind-btn id=bind-pause>Space</button></div><div style=gap:20px;display:flex><div style=flex:1><div class=setting-row><span>机枪塔</span><button onclick="listenKey('t1')" class=key-bind-btn id=bind-t1>1</button></div><div class=setting-row><span>狙击塔</span><button onclick="listenKey('t2')" class=key-bind-btn id=bind-t2>2</button></div><div class=setting-row><span>冰冻塔</span><button onclick="listenKey('t3')" class=key-bind-btn id=bind-t3>3</button></div><div class=setting-row><span>榴弹塔</span><button onclick="listenKey('t4')" class=key-bind-btn id=bind-t4>4</button></div><div class=setting-row><span>激光塔</span><button onclick="listenKey('t5')" class=key-bind-btn id=bind-t5>5</button></div><div class=setting-row><span>信号塔</span><button onclick="listenKey('t6')" class=key-bind-btn id=bind-t6>6</button></div></div><div style=flex:1><div class=setting-row><span>倍速一</span><button onclick="listenKey('s1')" class=key-bind-btn id=bind-s1>7</button></div><div class=setting-row><span>倍速二</span><button onclick="listenKey('s2')" class=key-bind-btn id=bind-s2>8</button></div><div class=setting-row><span>倍速三</span><button onclick="listenKey('s3')" class=key-bind-btn id=bind-s3>9</button></div><div class=setting-row><span>技能:EMP轨道炮</span><button onclick="listenKey('emp')" class=key-bind-btn id=bind-emp>Q</button></div><div class=setting-row><span>技能:黑客入侵</span><button onclick="listenKey('hacker')" class=key-bind-btn id=bind-hacker>W</button></div></div></div></div><div class=tut-section><h3>💾 数据管理</h3><div class=action-group><button class=control-btn onclick=exportSave() style=color:var(--text-color);border-color:var(--text-color)>📥 导出战绩 (生成HTML)</button><button onclick="document.getElementById('import-file').click()" class=control-btn style=color:var(--gold);border-color:var(--gold)>📤 导入战绩</button><input accept=.html id=import-file onchange=handleImport(event) style=display:none type=file></div><div class=danger-zone><h4 style="color:var(--danger);margin:0 0 10px">⚠️ 危险区域</h4><button class=control-btn onclick=showClearData() style=color:#f44;background:#f003;border-color:#f44;width:100%>🗑️ 清除本地数据</button></div></div></div></div><div class="glass-panel modal-box" id=import-confirm-modal style=z-index:95;border-color:var(--gold)><h2 style="color:var(--gold);margin:0 0 15px">⚠️ 导入战绩警告</h2><p style=color:#fff>导入战绩会覆盖当前存档,是否继续?<div style=background:#00000080;border-radius:8px;margin-bottom:15px;padding:15px><div class=diff-text>最高波次: <span class=diff-old id=diff-wave-old>0</span> ➔ <span class=diff-new id=diff-wave-new>0</span></div><div class=diff-text>科技币余量: <span class=diff-old id=diff-tokens-old>0</span> ➔ <span class=diff-new id=diff-tokens-new>0</span></div><div class=diff-text>资金科技Lv: <span class=diff-old id=diff-tm-old>0</span> ➔ <span class=diff-new id=diff-tm-new>0</span></div><div class=diff-text>生命科技Lv: <span class=diff-old id=diff-tl-old>0</span> ➔ <span class=diff-new id=diff-tl-new>0</span></div><div class=diff-text>暴击科技Lv: <span class=diff-old id=diff-tc-old>0</span> ➔ <span class=diff-new id=diff-tc-new>0</span></div></div><div style=justify-content:flex-end;gap:10px;display:flex><button class=control-btn onclick=closeImportConfirm()>取消</button><button class=control-btn onclick=executeImport() style=background:var(--gold);color:#000>确认覆盖并刷新</button></div></div><div class="glass-panel modal-box" id=clear-confirm-modal style=z-index:95;border-color:var(--danger)><h2 style="color:var(--danger);margin:0 0 15px">⚠️ 危险操作警告</h2><p style=color:#fff;font-weight:700>此操作不可逆,将会清除全部数据!<div style=text-align:center;background:#00000080;border-radius:8px;margin-bottom:15px;padding:15px><p style="color:#ccc;margin:0 0 10px;font-size:14px">输入历史最佳战绩(<span id=clear-hint-wave style=color:var(--danger);font-weight:700>0</span>)确认</p><input style="box-sizing:border-box;color:#fff;border:1px solid var(--danger);text-align:center;background:#111;border-radius:5px;outline:none;width:100%;padding:10px;font-size:16px" id=clear-confirm-input placeholder=请输入最高波次 type=number></div><div style=justify-content:space-between;gap:10px;display:flex><button class=control-btn onclick=closeClearConfirm() style=flex:1>取消</button><button class=control-btn onclick=confirmClearData() style=background:var(--danger);color:#fff;flex:1>确认删除</button></div></div><div class="glass-panel modal-box" id=talent-modal><div class=modal-header><h2 style=color:var(--gold);margin:0>⚙ 科技研发</h2><button class=control-btn onclick=closeTalents()>关闭</button></div><div style=color:#ccc;font-size:14px>当前拥有 <span id=meta-tokens style=color:var(--text-color);font-size:18px;font-weight:700>0</span> 枚科技币</div><div class=talent-item><div><div style=color:#fff;font-weight:700>初始资金大亨 (Lv.<span id=tlvl-money>0</span>)</div><div style=color:#aaa;font-size:12px>+20% 初始资金/级</div></div><button onclick="buyTalent('money')" class=talent-btn id=tbtn-money>升级 (1币)</button></div><div class=talent-item><div><div style=color:#fff;font-weight:700>强化城防 (Lv.<span id=tlvl-lives>0</span>)</div>
<div style=color:#aaa;font-size:12px>+10 基础生命/级</div></div><button onclick="buyTalent('lives')" class=talent-btn id=tbtn-lives>升级 (1币)</button></div><div class=talent-item><div><div style=color:#fff;font-weight:700>弱点瞄准 (Lv.<span id=tlvl-crit>0</span>)</div><div style=color:#aaa;font-size:12px>+5% 全局暴击率/级</div></div><button onclick="buyTalent('crit')" class=talent-btn id=tbtn-crit>升级 (1币)</button></div></div><div class="glass-panel modal-box" id=tutorial-modal><div class=modal-header><h2 style=color:var(--text-color);margin:0>📖 游戏指南与图鉴</h2><button class=control-btn onclick=closeTutorial()>关闭</button></div><div style=color:#ddd;font-size:14px;line-height:1.6><div class=tut-section><h3>⌨ 快捷键操作</h3><p>可通过右上角 <b>⚙设置</b> 自由绑定所有快捷键!<p>快捷键支持:选择炮台、调整倍速、施放技能、暂停游戏。</div><div class=tut-section><h3>💎 觉醒与企业购并</h3><p><b>无尽升级:</b> 点击场上炮台可花钱无限升级,等级越高成长曲线越恐怖。<p><b>终极觉醒:</b> Lv.5 开启,获得 3倍伤害、专属特效及射程。<p><b>🌌究极觉醒:</b> <span style=color:var(--cosmic)>Lv.20 开启!耗资50B!</span>在终极觉醒的基础上再次赋予 2倍伤害提升及射程增幅,毁天灭地!<p><b>🏢 企业购并:</b> 将一个炮台拖拽到另一个<b>同类型且同等级(必须均为Lv.5以上)</b>的炮台身上,即可合并为 <b>Tier 2 集团炮台</b>。占用单格空间,伤害激增,机枪塔更是会升级为双管齐射!</div><div class=tut-section><h3>🗼 炮台图鉴</h3><ul style=margin:0;padding-left:20px><li><b>机枪塔:</b> 低成本、高射速,非常适合用来剥离敌人的蓝色护盾。<li><b>狙击塔:</b> 极高单体爆发,觉醒后子弹附带穿透效果,并自带 20% 血量斩杀。<li><b>冰冻塔:</b> 发射冷冻弹降低敌人移速,觉醒后拥有接近定身的效果。<li><b>榴弹塔:</b> 范围溅射伤害,适合清理成堆的敌人,觉醒后爆炸范围激增。<li><b>天基激光:</b> 伤害无视护甲/护盾,自带<b>反隐形</b>功能。<li><b>信号塔:</b> 不攻击,但大幅提升范围内友军的伤害倍率,并自带<b>大范围反隐</b>。</ul></div><div class=tut-section><h3>👾 怪物图鉴与特殊机制</h3><ul style=margin:0;padding-left:20px><li><b>普通怪 (红):</b> 随波次增加血量和移速。<li><b>疾风怪 (黄):</b> 每5波出现,血量少但移速极快。<li><b>护盾怪 (蓝盾):</b> 随机生成,拥有免疫固定次数攻击的次数盾(高射速武器克星)。<li><b>分裂怪 (绿):</b> 死亡后会分裂成 3 只移速更快的小怪。<li><b>潜行怪 (半透明白):</b> 免疫常规炮台锁定,必须被激光塔或信号塔照亮。<li><b>巨型 Boss (紫):</b> 每10波出现,血量极其恐怖,漏掉扣除 5 点生命!</ul></div></div></div>
<div class=overlay id=modal-overlay style=z-index:89></div><div class=overlay id=pause-overlay onclick=togglePause() style=cursor:pointer><h1>已暂停</h1><p style=color:#aaa;letter-spacing:2px;margin-top:10px;font-size:24px>[ 点击屏幕 或 快捷键 恢复 ]</div><div class=overlay id=game-over><h1>破产清算</h1><p style=color:#ccc;margin-bottom:20px;font-size:28px>防线在第 <span id=final-wave style=color:var(--gold);font-weight:700>0</span> 波崩溃<p style=color:var(--text-color);margin-bottom:30px;font-size:18px>本次获得 <span id=earned-tokens style=font-weight:700>0</span> 枚科技币</p><button class=big-btn onclick=restartGame()>重新投资</button></div></div>
<script>const canvas=document.getElementById(`gameCanvas`),ctx=canvas.getContext(`2d`);let metaInfo=JSON.parse(localStorage.getItem(`infinity_td_meta`))||{bestWave:1,tokens:0,tMoney:0,tLives:0,tCrit:0};function saveMeta(){localStorage.setItem(`infinity_td_meta`,JSON.stringify(metaInfo))}const DEFAULT_SETTINGS={speeds:[1,2,3],keys:{t1:`1`,t2:`2`,t3:`3`,t4:`4`,t5:`5`,t6:`6`,s1:`7`,s2:`8`,s3:`9`,pause:` `,emp:`q`,hacker:`w`}};let userSettings=JSON.parse(localStorage.getItem(`infinity_td_settings`));if(!userSettings)userSettings=JSON.parse(JSON.stringify(DEFAULT_SETTINGS));else if(userSettings.speeds||=[...DEFAULT_SETTINGS.speeds],!userSettings.keys)userSettings.keys={...DEFAULT_SETTINGS.keys};else for(let k in DEFAULT_SETTINGS.keys)userSettings.keys[k]===void 0&&(userSettings.keys[k]=DEFAULT_SETTINGS.keys[k]);function saveSettings(){localStorage.setItem(`infinity_td_settings`,JSON.stringify(userSettings)),applySettingsToUI()}let pendingImportMeta=null;const SALT=`INF_TD_SECURE_V1`,CONFIG={baseMoney:5e8,baseLives:20,baseEnemySpeed:1.2,baseEnemyHP:200,pathLineWidth:45},TOWER_TYPES={machinegun:{name:`机枪塔`,cost:5e6,range:140,damage:25,cooldown:6,color:`#aaaaaa`,attackType:`single`},sniper:{name:`狙击塔`,cost:15e6,range:350,damage:800,cooldown:70,color:`#4488ff`,attackType:`single`},ice:{name:`冰冻塔`,cost:3e7,range:120,damage:10,cooldown:15,color:`#00ffff`,attackType:`slow`,slowFactor:.4},splash:{name:`榴弹塔`,cost:5e7,range:140,damage:250,cooldown:55,color:`#ffaa00`,attackType:`splash`,radius:80},laser:{name:`天基激光`,cost:1e8,range:180,damage:10,cooldown:1,color:`#ff00ff`,attackType:`laser`},signal:{name:`信号塔`,cost:2e7,range:160,damage:0,cooldown:999,color:`#00ffaa`,attackType:`buff`}};let gameState={},gameSpeed=userSettings.speeds[0];function initGameState(){let startMoney=Math.floor(CONFIG.baseMoney*(1+metaInfo.tMoney*.2)),startLives=CONFIG.baseLives+metaInfo.tLives*10;document.querySelectorAll(`.floating-text, .dmg-text`).forEach(e=>e.remove()),gameState={money:startMoney,lives:startLives,wave:1,empCost:1e8,hackerCost:1e8,empCooldown:0,hackerCooldown:0,hackerActiveTimer:0,gameOver:!1,isPaused:!1,frames:0,screenShake:0,enemies:[],towers:[],projectiles:[],particles:[],floatingTexts:[],obstacles:[],mapPath:[],pathPercentages:[],mousePos:{x:-1e3,y:-1e3},draggingMenuTower:null,draggingBoardTower:null,dragStartX:0,dragStartY:0,selectedTower:null,currentEvent:{type:`normal`,name:``,multiplier:1,endWave:0}},canvas.width||(canvas.width=window.innerWidth,canvas.height=window.innerHeight),generateMap(),generateObstacles(),applySettingsToUI(),setSpeedIndex(0)}function formatMoney(num){return num>=0xe8d4a51000?(num/0xe8d4a51000).toFixed(2)+`T`:num>=1e9?(num/1e9).toFixed(1)+`B`:num>=1e6?(num/1e6).toFixed(1)+`M`:num>=1e3?(num/1e3).toFixed(1)+`K`:Math.floor(num).toString()}function updateUI(){document.getElementById(`money-display`).innerText=`$`+formatMoney(gameState.money),document.getElementById(`wave-display`).innerText=gameState.wave,document.getElementById(`lives-display`).innerText=Math.max(0,gameState.lives),document.getElementById(`best-wave-display`).innerText=metaInfo.bestWave;let empBtn=document.getElementById(`btn-emp`);empBtn&&(gameState.empCooldown>0?(empBtn.style.filter=`grayscale(100%)`,empBtn.setAttribute(`data-cost`,`冷却 ${Math.ceil(gameState.empCooldown/60)}s`)):(empBtn.style.filter=`none`,empBtn.setAttribute(`data-cost`,`耗资 ${formatMoney(gameState.empCost)}`)));let hackerBtn=document.getElementById(`btn-hacker`);hackerBtn&&(gameState.hackerCooldown>0?(hackerBtn.style.filter=`grayscale(100%)`,hackerBtn.setAttribute(`data-cost`,`冷却 ${Math.ceil(gameState.hackerCooldown/60)}s`)):(hackerBtn.style.filter=`none`,hackerBtn.setAttribute(`data-cost`,`耗资 ${formatMoney(gameState.hackerCost)}`))),gameState.selectedTower&&(document.getElementById(`action-dmg`).innerText=`总输出: ${formatMoney(gameState.selectedTower.damageDealt)}`)}const EVENTS=[{type:`military`,name:`军工大涨`,text:`所有炮台造价与售价翻倍!`},{type:`crisis`,name:`金融海啸`,text:`击杀怪物收益减半!`},{type:`tech`,name:`科技突破`,text:`所有炮台冷却时间减半!`}];function rollEvent(){if(Math.random()<.4)return;let e=EVENTS[Math.floor(Math.random()*EVENTS.length)];gameState.currentEvent={...e,endWave:gameState.wave+3};let banner=document.getElementById(`event-banner`);document.getElementById(`event-text`).innerText=`${e.name}: ${e.text}`,banner.style.display=`block`,setTimeout(()=>banner.style.display=`none`,6e3)}let autoPausedByModal=!1;window.isAnyModalOpen=function(){return document.getElementById(`settings-modal`).classList.contains(`visible`)||document.getElementById(`tutorial-modal`).classList.contains(`visible`)||document.getElementById(`talent-modal`).classList.contains(`visible`)||document.getElementById(`import-confirm-modal`).classList.contains(`visible`)||document.getElementById(`clear-confirm-modal`).classList.contains(`visible`)},window.saveAndHideSettings=function(){if(document.getElementById(`settings-modal`).classList.contains(`visible`)){try{userSettings.speeds=[Math.min(5,Math.max(1,parseInt(document.getElementById(`set-speed-1`).value)||1)),Math.min(10,Math.max(1,parseInt(document.getElementById(`set-speed-2`).value)||2)),Math.min(15,Math.max(1,parseInt(document.getElementById(`set-speed-3`).value)||3))],saveSettings()}catch(e){console.error(`Failed to save settings:`,e)}document.getElementById(`settings-modal`).classList.remove(`visible`),listeningKeyFor&&=(document.getElementById(`bind-${listeningKeyFor}`).classList.remove(`listening`),null)}},window.closeAllModals=function(){saveAndHideSettings(),document.getElementById(`tutorial-modal`).classList.remove(`visible`),document.getElementById(`talent-modal`).classList.remove(`visible`),document.getElementById(`import-confirm-modal`).classList.remove(`visible`),document.getElementById(`clear-confirm-modal`).classList.remove(`visible`),document.getElementById(`modal-overlay`).classList.remove(`visible`)},window.handleModalOpen=function(){let anyOpen=isAnyModalOpen();!anyOpen&&!gameState.isPaused&&!gameState.gameOver?(togglePause(),autoPausedByModal=!0):anyOpen||(autoPausedByModal=!1),closeAllModals()},window.handleModalClose=function(){!isAnyModalOpen()&&autoPausedByModal&&gameState.isPaused&&(autoPausedByModal=!1,togglePause())},window.setSpeedIndex=function(idx){gameSpeed=userSettings.speeds[idx],document.querySelectorAll(`.speed-btn`).forEach(b=>b.classList.remove(`active`)),document.getElementById(`speed-btn-${idx}`).classList.add(`active`)},window.togglePause=function(){if(gameState.gameOver)return;gameState.isPaused=!gameState.isPaused;let btn=document.getElementById(`btn-pause`),overlay=document.getElementById(`pause-overlay`),keyName=userSettings.keys.pause===` `?`Space`:userSettings.keys.pause.toUpperCase();gameState.isPaused?(btn.innerHTML=`<span style="color:#66fcf1">▶</span> 继续`,btn.style.color=`var(--text-color)`,overlay.classList.add(`visible`)):(btn.innerHTML=`<span style="color:#66fcf1">⏸ 暂停 (${keyName})</span>`,btn.style.color=`#aaa`,overlay.classList.remove(`visible`),closeAllModals(),autoPausedByModal=!1)},window.openTutorial=function(){handleModalOpen(),document.getElementById(`tutorial-modal`).classList.add(`visible`)},window.closeTutorial=function(){document.getElementById(`tutorial-modal`).classList.remove(`visible`),handleModalClose()},window.openTalents=function(){handleModalOpen(),document.getElementById(`talent-modal`).classList.add(`visible`),document.getElementById(`meta-tokens`).innerText=metaInfo.tokens,document.getElementById(`tlvl-money`).innerText=metaInfo.tMoney,document.getElementById(`tlvl-lives`).innerText=metaInfo.tLives,document.getElementById(`tlvl-crit`).innerText=metaInfo.tCrit;let cm=metaInfo.tMoney>0?metaInfo.tMoney:1,cl=metaInfo.tLives>0?metaInfo.tLives:1,cc=metaInfo.tCrit>0?metaInfo.tCrit:1,bm=document.getElementById(`tbtn-money`),bl=document.getElementById(`tbtn-lives`),bc=document.getElementById(`tbtn-crit`);bm.disabled=metaInfo.tokens<cm,bm.innerText=`升级 (${cm}币)`,bl.disabled=metaInfo.tokens<cl,bl.innerText=`升级 (${cl}币)`,bc.disabled=metaInfo.tokens<cc,bc.innerText=`升级 (${cc}币)`},window.closeTalents=function(){document.getElementById(`talent-modal`).classList.remove(`visible`),handleModalClose()},window.buyTalent=function(type){let lvl=type===`money`?metaInfo.tMoney:type===`lives`?metaInfo.tLives:metaInfo.tCrit,cost=lvl>0?lvl:1;metaInfo.tokens>=cost&&(metaInfo.tokens-=cost,type===`money`&&metaInfo.tMoney++,type===`lives`&&metaInfo.tLives++,type===`crit`&&metaInfo.tCrit++,saveMeta(),openTalents())};let listeningKeyFor=null;window.openSettings=function(){for(let k in handleModalOpen(),document.getElementById(`settings-modal`).classList.add(`visible`),document.getElementById(`set-speed-1`).value=userSettings.speeds[0],document.getElementById(`set-speed-2`).value=userSettings.speeds[1],document.getElementById(`set-speed-3`).value=userSettings.speeds[2],userSettings.keys){let btn=document.getElementById(`bind-${k}`);btn&&(btn.innerText=userSettings.keys[k]===` `?`SPACE`:(userSettings.keys[k]||``).toUpperCase())}},window.closeSettings=function(){saveAndHideSettings(),handleModalClose()},window.listenKey=function(action){listeningKeyFor&&document.getElementById(`bind-${listeningKeyFor}`).classList.remove(`listening`),listeningKeyFor=action;let btn=document.getElementById(`bind-${action}`);btn.innerText=`按下按键...`,btn.classList.add(`listening`)};function applySettingsToUI(){document.getElementById(`speed-btn-0`).innerText=userSettings.speeds[0]+`x`,document.getElementById(`speed-btn-1`).innerText=userSettings.speeds[1]+`x`,document.getElementById(`speed-btn-2`).innerText=userSettings.speeds[2]+`x`,document.getElementById(`ui-key-pause`).innerText=userSettings.keys.pause===` `?`Space`:(userSettings.keys.pause||` `).toUpperCase(),[`t1`,`t2`,`t3`,`t4`,`t5`,`t6`,`emp`,`hacker`].forEach(k=>{if(document.getElementById(`ui-key-${k}`)){let keyVal=userSettings.keys[k]||DEFAULT_SETTINGS.keys[k];document.getElementById(`ui-key-${k}`).innerText=keyVal.toUpperCase()}})}window.exportSave=function(){let dataStr=btoa(encodeURIComponent(JSON.stringify(metaInfo)+`|||INF_TD_SECURE_V1`)),htmlTemplate=`<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8"><title>亿万富翁塔防 - 战绩证明</title>
<style>
body{background:#0b0c10;color:#66fcf1;text-align:center;font-family:'Segoe UI',sans-serif;padding:50px;}
.card{background:rgba(31,40,51,0.8);border:1px solid #45a29e;border-radius:15px;padding:40px;max-width:500px;margin:0 auto;box-shadow:0 0 20px #45a29e;}
h1{color:#ffd700;text-shadow:0 0 10px rgba(255,215,0,0.5);}
.stat{font-size:24px;margin:20px 0;}
.meta-data{font-size:12px;color:#444;margin-top:50px;word-wrap:break-word;}
</style>
</head>
<body>
<div class="card">
<h1>🏆 塔防大亨战绩证明</h1>
<div class="stat">历史最高防线:<b style="color:#ff4444">${metaInfo.bestWave} 波</b></div>
<div class="stat">科技币储备:<b>${metaInfo.tokens} 枚</b></div>
<div class="stat">初始资金等级:<b>Lv.${metaInfo.tMoney}</b></div>
<div class="stat">强化城防等级:<b>Lv.${metaInfo.tLives}</b></div>
<div class="stat">弱点瞄准等级:<b>Lv.${metaInfo.tCrit}</b></div>
<hr style="border-color:#333; margin:30px 0;">
<p style="color:#aaa;">此文件可用于在其他设备上导入您的成就数据。</p>
<div id="secure-save-data" style="display:none;">${dataStr}</div>
</div></body></html>`,
blob=new Blob([htmlTemplate],{type:`text/html`}),url=URL.createObjectURL(blob),a=document.createElement(`a`);a.href=url,a.download=`InfinityTD_Save_Wave${metaInfo.bestWave}.html`,document.body.appendChild(a),a.click(),document.body.removeChild(a),URL.revokeObjectURL(url)},window.handleImport=function(event){let file=event.target.files[0];if(!file)return;let reader=new FileReader;reader.onload=e=>{let match=e.target.result.match(/<div id="secure-save-data" style="display:none;">(.*?)<\/div>/);if(match&&match[1])try{let parts=decodeURIComponent(atob(match[1])).split(`|||`);parts[1]===`INF_TD_SECURE_V1`?(pendingImportMeta=JSON.parse(parts[0]),showImportConfirm()):alert(`存档验证失败!文件可能被篡改。`)}catch{alert(`存档解析失败!`)}else alert(`无效的战绩文件!未找到存档数据。`)},reader.readAsText(file),event.target.value=``};function showImportConfirm(){pendingImportMeta&&(document.getElementById(`diff-wave-old`).innerText=metaInfo.bestWave,document.getElementById(`diff-wave-new`).innerText=pendingImportMeta.bestWave,document.getElementById(`diff-tokens-old`).innerText=metaInfo.tokens,document.getElementById(`diff-tokens-new`).innerText=pendingImportMeta.tokens,document.getElementById(`diff-tm-old`).innerText=metaInfo.tMoney,document.getElementById(`diff-tm-new`).innerText=pendingImportMeta.tMoney,document.getElementById(`diff-tl-old`).innerText=metaInfo.tLives,document.getElementById(`diff-tl-new`).innerText=pendingImportMeta.tLives,document.getElementById(`diff-tc-old`).innerText=metaInfo.tCrit,document.getElementById(`diff-tc-new`).innerText=pendingImportMeta.tCrit,saveAndHideSettings(),document.getElementById(`import-confirm-modal`).classList.add(`visible`),document.getElementById(`modal-overlay`).classList.add(`visible`))}window.closeImportConfirm=function(){document.getElementById(`import-confirm-modal`).classList.remove(`visible`),document.getElementById(`modal-overlay`).classList.remove(`visible`),handleModalClose()},window.executeImport=function(){pendingImportMeta&&(metaInfo=pendingImportMeta,saveMeta(),document.getElementById(`modal-overlay`).classList.remove(`visible`),location.reload())},window.showClearData=function(){saveAndHideSettings(),document.getElementById(`clear-hint-wave`).innerText=metaInfo.bestWave,document.getElementById(`clear-confirm-input`).value=``,document.getElementById(`clear-confirm-modal`).classList.add(`visible`),document.getElementById(`modal-overlay`).classList.add(`visible`)},window.closeClearConfirm=function(){document.getElementById(`clear-confirm-modal`).classList.remove(`visible`),document.getElementById(`modal-overlay`).classList.remove(`visible`),handleModalClose()},window.confirmClearData=function(){let input=document.getElementById(`clear-confirm-input`).value;parseInt(input)===metaInfo.bestWave?(localStorage.removeItem(`infinity_td_meta`),localStorage.removeItem(`infinity_td_settings`),location.reload()):(document.getElementById(`clear-confirm-modal`).classList.remove(`visible`),document.getElementById(`modal-overlay`).classList.remove(`visible`),alert(`输入的波次与记录不符,清除取消!`),handleModalClose())},window.castEMP=function(){if(!(gameState.isPaused||gameState.gameOver||gameState.empCooldown>0)){if(gameState.money<gameState.empCost){createFloatingText(`资金不足!`,canvas.width/2,canvas.height/2,varColor(`danger`),30);return}gameState.money-=gameState.empCost,gameState.empCost*=2,gameState.empCooldown=900,gameState.screenShake=30,createFloatingText(`EMP启动!`,canvas.width/2,100,varColor(`purple`),40),[...gameState.enemies].forEach(e=>{e.shield=0,hitEnemy(e,e.type===`boss`?e.hp*.5:e.maxHp+999,null,null,!1),createParticle(e.x,e.y,varColor(`purple`),20,5)}),updateUI()}},window.castHacker=function(){if(!(gameState.isPaused||gameState.gameOver||gameState.hackerCooldown>0)){if(gameState.money<gameState.hackerCost){createFloatingText(`资金不足!`,canvas.width/2,canvas.height/2,varColor(`danger`),30);return}gameState.money-=gameState.hackerCost,gameState.hackerCost*=2,gameState.hackerCooldown=900,createFloatingText(`系统劫持! 冻结刷怪`,canvas.width/2,100,`#00ffaa`,40),gameState.enemies.forEach(e=>{e.slowTimer=300,e.isFrozen=!0}),gameState.hackerActiveTimer=300,updateUI()}},window.addEventListener(`keydown`,e=>{if(document.activeElement.tagName===`INPUT`&&document.activeElement.type!==`file`)return;if(listeningKeyFor){e.preventDefault();let key=e.key.toLowerCase();key===` `&&(key=` `),userSettings.keys[listeningKeyFor]=key,document.getElementById(`bind-${listeningKeyFor}`).innerText=key===` `?`SPACE`:key.toUpperCase(),document.getElementById(`bind-${listeningKeyFor}`).classList.remove(`listening`),listeningKeyFor=null;return}if(e.key===`Escape`&&(gameState.draggingMenuTower=null),gameState.gameOver)return;let k=e.key.toLowerCase();k===userSettings.keys.t1?startMenuDragHotkey(`machinegun`):k===userSettings.keys.t2?startMenuDragHotkey(`sniper`):k===userSettings.keys.t3?startMenuDragHotkey(`ice`):k===userSettings.keys.t4?startMenuDragHotkey(`splash`):k===userSettings.keys.t5?startMenuDragHotkey(`laser`):k===userSettings.keys.t6?startMenuDragHotkey(`signal`):k===userSettings.keys.s1?setSpeedIndex(0):k===userSettings.keys.s2?setSpeedIndex(1):k===userSettings.keys.s3?setSpeedIndex(2):k===userSettings.keys.pause?(e.preventDefault(),togglePause()):k===userSettings.keys.emp?castEMP():k===userSettings.keys.hacker&&castHacker()});function startMenuDragHotkey(type){gameState.isPaused||gameState.gameOver||(gameState.draggingMenuTower=type,gameState.selectedTower=null,hideActionMenu(),gameState.mousePos.x===-1e3&&(gameState.mousePos.x=canvas.width/2,gameState.mousePos.y=canvas.height/2))}function resize(){let oldW=canvas.width||window.innerWidth,oldH=canvas.height||window.innerHeight;if(canvas.width=window.innerWidth,canvas.height=window.innerHeight,gameState&&gameState.mapPath&&gameState.mapPath.length>0){let sx=canvas.width/oldW,sy=canvas.height/oldH;gameState.mapPath.forEach(p=>{p.x*=sx,p.y*=sy}),gameState.towers.forEach(t=>{t.x*=sx,t.y*=sy}),gameState.enemies.forEach(e=>{e.x*=sx,e.y*=sy}),gameState.obstacles.forEach(o=>{o.x*=sx,o.y*=sy,o.w*=sx,o.h*=sy}),gameState.projectiles.forEach(p=>{p.x*=sx,p.y*=sy,p.lastX&&(p.lastX*=sx),p.lastY&&(p.lastY*=sx),p.tx&&(p.tx*=sx),p.ty&&(p.ty*=sy)})}}window.addEventListener(`resize`,resize);function generateMap(){let path=[],startY=.25+Math.random()*.4;path.push({x:0,y:startY});let x1=.15+Math.random()*.15;path.push({x:x1,y:startY});let y1=startY>.45?.25+Math.random()*.1:.55+Math.random()*.1;path.push({x:x1,y:y1});let x2=.45+Math.random()*.15;path.push({x:x2,y:y1});let y2=y1>.45?.25+Math.random()*.1:.55+Math.random()*.1;path.push({x:x2,y:y2});let x3=.75+Math.random()*.15;path.push({x:x3,y:y2});let endChoice=Math.random();endChoice<.33?path.push({x:x3,y:1.1}):endChoice<.66?path.push({x:x3,y:-.1}):path.push({x:1.1,y:y2}),gameState.pathPercentages=path;let w=canvas.width,h=canvas.height;gameState.mapPath=path.map(p=>({x:p.x*w,y:p.y*h}))}function generateObstacles(){gameState.obstacles=[];for(let i=0;i<6;i++){let ox=Math.random()*canvas.width,oy=Math.random()*canvas.height,w=60+Math.random()*80,h=60+Math.random()*80,overlap=!1;for(let j=0;j<gameState.mapPath.length-1;j++)if(pointToSegmentDistance(ox+w/2,oy+h/2,gameState.mapPath[j].x,gameState.mapPath[j].y,gameState.mapPath[j+1].x,gameState.mapPath[j+1].y)<CONFIG.pathLineWidth+Math.max(w,h)){overlap=!0;break}overlap||gameState.obstacles.push({x:ox,y:oy,w,h})}}let isPointerDownOnMenu=!1;window.startMenuDrag=function(e,type){if(!(gameState.isPaused||gameState.gameOver)){if(gameState.draggingMenuTower===type){gameState.draggingMenuTower=null;return}gameState.draggingMenuTower=type,gameState.selectedTower=null,hideActionMenu(),updateMousePos(e),isPointerDownOnMenu=!0,gameState.dragStartX=e.clientX,gameState.dragStartY=e.clientY}},window.addEventListener(`pointermove`,e=>{updateMousePos(e),gameState.draggingBoardTower&&(gameState.draggingBoardTower.x=gameState.mousePos.x,gameState.draggingBoardTower.y=gameState.mousePos.y,hideActionMenu())}),window.addEventListener(`pointerup`,e=>{if(gameState.draggingMenuTower&&isPointerDownOnMenu&&(isPointerDownOnMenu=!1,Math.hypot(e.clientX-gameState.dragStartX,e.clientY-gameState.dragStartY)>10&&(attemptPlaceTower(),gameState.draggingMenuTower=null)),gameState.draggingBoardTower){let t1=gameState.draggingBoardTower;t1.isDragging=!1;let merged=!1;for(let t2 of gameState.towers)if(t1!==t2&&Math.hypot(t1.x-t2.x,t1.y-t2.y)<30)if(t1.type===t2.type&&t1.level>=5&&t1.level===t2.level&&t1.tier===1&&t2.tier===1){t2.tier=2,t2.damage*=3.5,t2.range=Math.floor(t2.range*1.2),t2.totalCost+=t1.totalCost,(t1.awakened||t2.awakened)&&(t2.awakened=!0),(t1.ultimateAwakened||t2.ultimateAwakened)&&(t2.ultimateAwakened=!0),t2.type===`machinegun`&&(t2.cooldown=Math.max(1,Math.floor(t2.cooldown*.7))),t2.type===`splash`&&(t2.radius*=1.3),gameState.towers=gameState.towers.filter(t=>t!==t1),gameState.selectedTower===t1&&(gameState.selectedTower=t2),createFloatingText(`🤝 集团购并!`,t2.x,t2.y-30,varColor(`gold`),24),createParticle(t2.x,t2.y,varColor(`gold`),50,6),gameState.screenShake=15,merged=!0,updateUI();break}else createFloatingText(`无法购并(需同类、同等级且Lv5+)`,t1.x,t1.y-20,varColor(`danger`));if(!merged){let tx=t1.x,ty=t1.y;t1.x=-1e3,t1.y=-1e3,isValidPlacement(tx,ty)?(t1.x=tx,t1.y=ty):(t1.x=gameState.dragStartX,t1.y=gameState.dragStartY)}gameState.draggingBoardTower=null,gameState.selectedTower&&gameState.towers.includes(gameState.selectedTower)&&showActionMenu(gameState.selectedTower)}});function updateMousePos(e){let rect=canvas.getBoundingClientRect();gameState.mousePos.x=e.clientX-rect.left,gameState.mousePos.y=e.clientY-rect.top}window.addEventListener(`contextmenu`,e=>{(gameState.draggingMenuTower||gameState.draggingBoardTower)&&(e.preventDefault(),gameState.draggingMenuTower=null)}),canvas.addEventListener(`pointerdown`,e=>{if(gameState.gameOver||gameState.isPaused)return;if(updateMousePos(e),gameState.draggingMenuTower){attemptPlaceTower()&&(gameState.draggingMenuTower=null);return}let{x,y}=gameState.mousePos,clicked=null;
for(let t of gameState.towers)if(Math.hypot(t.x-x,t.y-y)<25){clicked=t;break}clicked?(gameState.selectedTower=clicked,showActionMenu(clicked),gameState.draggingBoardTower=clicked,gameState.dragStartX=clicked.x,gameState.dragStartY=clicked.y,clicked.isDragging=!0):(gameState.selectedTower=null,hideActionMenu())});function attemptPlaceTower(){let{x,y}=gameState.mousePos;if(y>canvas.height-130||y<80)return!1;let config=TOWER_TYPES[gameState.draggingMenuTower],cost=config.cost;return gameState.currentEvent.type===`military`&&(cost*=2),gameState.money<cost?(createFloatingText(`资金不足!`,x,y,varColor(`danger`)),!1):isValidPlacement(x,y)?(gameState.money-=cost,gameState.towers.push({...JSON.parse(JSON.stringify(config)),x,y,type:gameState.draggingMenuTower,level:1,tier:1,angle:0,cooldownTimer:0,damageDealt:0,targetMode:`first`,awakened:!1,ultimateAwakened:!1,buffMult:1,totalCost:cost,recoil:0,isDragging:!1}),createFloatingText(`-$${formatMoney(cost)}`,x,y,varColor(`gold`)),createParticle(x,y,config.color,15),updateUI(),!0):(createFloatingText(`位置非法!`,x,y,varColor(`danger`)),!1)}function isValidPlacement(x,y){for(let t of gameState.towers)if(Math.hypot(t.x-x,t.y-y)<40)return!1;for(let o of gameState.obstacles)if(x>o.x-20&&x<o.x+o.w+20&&y>o.y-20&&y<o.y+o.h+20)return!1;for(let i=0;i<gameState.mapPath.length-1;i++)if(pointToSegmentDistance(x,y,gameState.mapPath[i].x,gameState.mapPath[i].y,gameState.mapPath[i+1].x,gameState.mapPath[i+1].y)<CONFIG.pathLineWidth/2+25)return!1;return!0}function pointToSegmentDistance(px,py,x1,y1,x2,y2){let A=px-x1,B=py-y1,C=x2-x1,D=y2-y1,dot=A*C+B*D,len_sq=C*C+D*D,param=-1;len_sq!==0&&(param=dot/len_sq);let xx,yy;return param<0?(xx=x1,yy=y1):param>1?(xx=x2,yy=y2):(xx=x1+param*C,yy=y1+param*D),Math.hypot(px-xx,py-yy)}function showActionMenu(t){let menu=document.getElementById(`action-menu`);menu.classList.add(`visible`),menu.style.left=Math.max(80,Math.min(canvas.width-80,t.x))+`px`,menu.classList.remove(`bottom`),t.y<160?(menu.classList.add(`bottom`),menu.style.top=t.y+30+`px`):menu.style.top=t.y-40+`px`;let titleTag=t.ultimateAwakened?` 🌌`:t.awakened?` 💎`:``;document.getElementById(`action-title`).innerText=`Lv.${t.level} ${t.tier>1?`集团`:``}${t.name}${titleTag}`;let targetSelect=document.getElementById(`target-mode`);t.type===`signal`?targetSelect.style.display=`none`:(targetSelect.style.display=`block`,targetSelect.value=t.targetMode);let upCost=t.cost*1.6**(t.level-1),sellPrice=Math.floor(t.totalCost*.5);gameState.currentEvent.type===`military`&&(upCost*=2,sellPrice=t.totalCost),document.getElementById(`btn-upgrade`).style.display=`flex`,document.getElementById(`txt-up-cost`).innerText=`-$${formatMoney(upCost)}`;let awkBtn=document.getElementById(`btn-awaken`),ultBtn=document.getElementById(`btn-ult-awaken`);t.level>=5&&!t.awakened&&!t.ultimateAwakened&&t.type!==`signal`?(awkBtn.style.display=`flex`,document.getElementById(`txt-awk-cost`).innerText=`-$${formatMoney(t.cost*10)}`):awkBtn.style.display=`none`,t.level>=20&&t.awakened&&!t.ultimateAwakened&&t.type!==`signal`?(ultBtn.style.display=`flex`,document.getElementById(`txt-ult-cost`).innerText=`-$${formatMoney(5e10)}`):ultBtn.style.display=`none`,document.getElementById(`txt-sell-price`).innerText=`+$${formatMoney(sellPrice)}`,updateUI()}function hideActionMenu(){document.getElementById(`action-menu`).classList.remove(`visible`,`bottom`)}window.changeTargetMode=function(){gameState.selectedTower&&(gameState.selectedTower.targetMode=document.getElementById(`target-mode`).value)},window.upgradeTower=function(){if(gameState.gameOver||gameState.isPaused)return;let t=gameState.selectedTower;if(!t)return;let upCost=t.cost*1.6**(t.level-1);if(gameState.currentEvent.type===`military`&&(upCost*=2),gameState.money<upCost){createFloatingText(`资金不足!`,t.x,t.y,varColor(`danger`));return}gameState.money-=upCost,t.totalCost+=upCost,t.damage=Math.floor(t.damage*(1.5+t.level*.1)),t.range<TOWER_TYPES[t.type].range*2.5&&(t.range=Math.floor(t.range*1.05)),t.level++,createFloatingText(`升级成功!`,t.x,t.y-20,`#66fcf1`),createParticle(t.x,t.y,`#66fcf1`,20),updateUI(),showActionMenu(t)},window.awakenTower=function(){if(gameState.gameOver||gameState.isPaused)return;let t=gameState.selectedTower;if(!t||t.level<5||t.awakened||t.ultimateAwakened)return;let cost=t.cost*10;if(gameState.money<cost){createFloatingText(`资金不足!`,t.x,t.y,varColor(`danger`));return}gameState.money-=cost,t.totalCost+=cost,t.awakened=!0,t.damage*=3,t.range*=1.3,t.type===`machinegun`&&(t.cooldown=Math.max(1,Math.floor(t.cooldown*.4))),t.type===`ice`&&(t.slowFactor=.1),t.type===`splash`&&(t.radius*=1.5),createFloatingText(`💎 终极觉醒!`,t.x,t.y-30,varColor(`purple`),24),createParticle(t.x,t.y,varColor(`purple`),50,5),gameState.screenShake=10,updateUI(),showActionMenu(t)},window.ultimateAwakenTower=function(){if(gameState.gameOver||gameState.isPaused)return;let t=gameState.selectedTower;if(!t||t.level<20||!t.awakened||t.ultimateAwakened)return;let cost=5e10;if(gameState.money<cost){createFloatingText(`资金不足!`,t.x,t.y,varColor(`danger`));return}gameState.money-=cost,t.totalCost+=cost,t.ultimateAwakened=!0,t.damage*=2,t.range*=1.2,t.type===`machinegun`&&(t.cooldown=Math.max(1,Math.floor(t.cooldown*.5))),t.type===`ice`&&(t.slowFactor=.05),t.type===`splash`&&(t.radius*=1.5),createFloatingText(`🌌 究极觉醒!`,t.x,t.y-30,varColor(`cosmic`),28),createParticle(t.x,t.y,varColor(`cosmic`),80,7),gameState.screenShake=20,updateUI(),showActionMenu(t)},window.sellTower=function(){if(gameState.gameOver||gameState.isPaused)return;let t=gameState.selectedTower;if(!t)return;let price=Math.floor(t.totalCost*.5);gameState.currentEvent.type===`military`&&(price=t.totalCost),gameState.money+=price,gameState.towers=gameState.towers.filter(tower=>tower!==t),createFloatingText(`+$${formatMoney(price)}`,t.x,t.y,varColor(`gold`)),createParticle(t.x,t.y,`#ccc`,15),updateUI(),hideActionMenu(),gameState.selectedTower=null};let lastTime=0,enemySpawnTimer=0;function spawnEnemy(){let isBoss=gameState.wave%10==0,isFast=gameState.wave%5==0&&!isBoss,hp=CONFIG.baseEnemyHP*1.25**gameState.wave,speed=CONFIG.baseEnemySpeed+gameState.wave*.015,radius=14,color=`#ff4444`,type=`normal`,shield=0,isStealth=!1,isSplitter=!1;if(isBoss)hp*=10,speed*=.35,radius=28,color=`#b829ea`,type=`boss`;else if(isFast)hp*=.4,speed*=2.2,radius=10,color=`#ffff00`,type=`fast`;else{let r=Math.random();gameState.wave>5&&r<.2?shield=5+Math.floor(gameState.wave/5):gameState.wave>8&&r<.4?(isSplitter=!0,color=`#44ff44`):gameState.wave>12&&r<.5&&(isStealth=!0,color=`#fff`)}gameState.enemies.push({x:gameState.mapPath[0].x,y:gameState.mapPath[0].y-50,pathIndex:0,speed,maxHp:hp,hp,radius,color,type,slowTimer:0,isFrozen:!1,shield,isStealth,isSplitter:!1,revealed:!1,hitFlashTimer:0})}function gameLoop(timestamp){lastTime||=timestamp;let deltaTime=timestamp-lastTime;if(lastTime=timestamp,!gameState.gameOver){if(!gameState.isPaused)for(let i=0;i<gameSpeed;i++)update();draw()}requestAnimationFrame(gameLoop)}function update(){if(gameState.frames++,gameState.empCooldown>0){let p=Math.ceil(gameState.empCooldown/60);gameState.empCooldown--,(p!==Math.ceil(gameState.empCooldown/60)||gameState.empCooldown===0)&&updateUI()}if(gameState.hackerCooldown>0){let p=Math.ceil(gameState.hackerCooldown/60);gameState.hackerCooldown--,(p!==Math.ceil(gameState.hackerCooldown/60)||gameState.hackerCooldown===0)&&updateUI()}let currentWaveCycle=Math.floor(gameState.frames/900)+1;if(currentWaveCycle>gameState.wave){let interest=gameState.money*.05;gameState.money+=interest,createFloatingText(`理财收益 +$${formatMoney(interest)}`,canvas.width/2,160,varColor(`gold`),24),metaInfo.tokens+=currentWaveCycle%10==0?6:1,saveMeta(),gameState.wave=currentWaveCycle,gameState.wave>metaInfo.bestWave&&(metaInfo.bestWave=gameState.wave,saveMeta()),gameState.wave%5==0&&rollEvent(),updateUI(),createFloatingText(gameState.wave%10==0?`⚠️ 首领来袭 ⚠️`:gameState.wave%5==0?`⚡ 疾风突袭 ⚡`:`第 ${gameState.wave} 波`,canvas.width/2,120,`#66fcf1`,36)}if(gameState.currentEvent.endWave>0&&gameState.wave>=gameState.currentEvent.endWave&&(gameState.currentEvent={type:`normal`,multiplier:1,endWave:0}),gameState.hackerActiveTimer>0)gameState.hackerActiveTimer--;else{let interval=Math.max(20,100-gameState.wave*2);gameState.wave%10==0&&(interval*=4),enemySpawnTimer++,enemySpawnTimer>interval&&(spawnEnemy(),enemySpawnTimer=0)}gameState.towers.forEach(t=>t.buffMult=1),gameState.enemies.forEach(e=>e.revealed=!1);for(let t of gameState.towers){if(t.recoil>0&&(t.recoil=Math.max(0,t.recoil-1)),t.type===`signal`){for(let ot of gameState.towers)ot!==t&&Math.hypot(ot.x-t.x,ot.y-t.y)<=t.range&&(ot.buffMult=Math.max(ot.buffMult,t.level>=5?1.5:1.25));for(let e of gameState.enemies)e.isStealth&&Math.hypot(e.x-t.x,e.y-t.y)<=t.range&&(e.revealed=!0)}if(t.type===`laser`)for(let e of gameState.enemies)e.isStealth&&Math.hypot(e.x-t.x,e.y-t.y)<=t.range&&(e.revealed=!0)}for(let i=gameState.enemies.length-1;i>=0;i--){let enemy=gameState.enemies[i];if(enemy.hitFlashTimer>0&&enemy.hitFlashTimer--,enemy.isFrozen){enemy.slowTimer--,enemy.slowTimer<=0&&(enemy.isFrozen=!1);continue}let currentSpeed=enemy.speed*(enemy.slowTimer>0?.4:1);enemy.slowTimer>0&&enemy.slowTimer--;let target=gameState.mapPath[enemy.pathIndex];if(!target)continue;let dx=target.x-enemy.x,dy=target.y-enemy.y,dist=Math.hypot(dx,dy);dist<currentSpeed?(enemy.x=target.x,enemy.y=target.y,enemy.pathIndex++,enemy.pathIndex>=gameState.mapPath.length&&(gameState.lives-=enemy.type===`boss`?5:1,gameState.enemies.splice(i,1),updateUI(),gameState.lives<=0&&endGame())):(enemy.x+=dx/dist*currentSpeed,enemy.y+=dy/dist*currentSpeed)}gameState.towers.forEach(tower=>{if(tower.type===`signal`)return;let cdMult=gameState.currentEvent.type===`tech`?.5:1;tower.cooldownTimer>0&&(tower.cooldownTimer-=1/cdMult);let validEnemies=gameState.enemies.filter(e=>(!e.isStealth||e.revealed)&&Math.hypot(e.x-tower.x,e.y-tower.y)<=tower.range);if(validEnemies.length>0){let target=validEnemies[0];target=tower.targetMode===`close`?validEnemies.reduce((a,b)=>Math.hypot(a.x-tower.x,a.y-tower.y)<Math.hypot(b.x-tower.x,b.y-tower.y)?a:b):tower.targetMode===`strong`?validEnemies.reduce((a,b)=>a.hp>b.hp?a:b):validEnemies.reduce((a,b)=>a.pathIndex>b.pathIndex?a:b),tower.isDragging||(tower.angle=Math.atan2(target.y-tower.y,target.x-tower.x)),tower.cooldownTimer<=0&&!tower.isDragging&&(fireTower(tower,target),tower.cooldownTimer=tower.cooldown)}});
for(let i=gameState.projectiles.length-1;i>=0;i--){let proj=gameState.projectiles[i];if(proj.type===`laser_beam`){proj.life--,proj.life<=0&&gameState.projectiles.splice(i,1);continue}let dx=proj.target.x-proj.x,dy=proj.target.y-proj.y,dist=Math.hypot(dx,dy);proj.lastX=proj.x,proj.lastY=proj.y,dist<proj.speed||!gameState.enemies.includes(proj.target)?(gameState.enemies.includes(proj.target)&&(hitEnemy(proj.target,proj.damage,proj.effect,proj.tower,proj.isCrit),proj.effect===`pierce`&&gameState.enemies.forEach(e=>{e!==proj.target&&pointToSegmentDistance(e.x,e.y,proj.startX,proj.startY,proj.target.x+dx*5,proj.target.y+dy*5)<e.radius&&hitEnemy(e,proj.damage,null,proj.tower,proj.isCrit)}),proj.effect===`splash`?(gameState.screenShake=2,gameState.enemies.forEach(e=>{e!==proj.target&&Math.hypot(e.x-proj.target.x,e.y-proj.target.y)<=proj.radius&&hitEnemy(e,proj.damage,null,proj.tower,proj.isCrit)}),createParticle(proj.target.x,proj.target.y,proj.color,25,4)):createParticle(proj.x,proj.y,proj.color,6)),gameState.projectiles.splice(i,1)):(proj.x+=dx/dist*proj.speed,proj.y+=dy/dist*proj.speed)}for(let i=gameState.particles.length-1;i>=0;i--){let p=gameState.particles[i];p.x+=p.vx,p.y+=p.vy,p.life--,p.alpha=p.life/p.maxLife,p.life<=0&&gameState.particles.splice(i,1)}for(let i=gameState.floatingTexts.length-1;i>=0;i--){let ft=gameState.floatingTexts[i];--ft.y,ft.life--,ft.life<=0&&(ft.element.remove(),gameState.floatingTexts.splice(i,1))}}function fireTower(tower,target){let finalDamage=tower.damage*tower.buffMult,isCrit=Math.random()<metaInfo.tCrit*.05;if(isCrit&&(finalDamage*=2),tower.type===`sniper`?tower.recoil=12:tower.type!==`laser`&&tower.type!==`signal`&&(tower.recoil=6),tower.type===`laser`){hitEnemy(target,finalDamage,null,tower,isCrit),gameState.projectiles.push({type:`laser_beam`,x:tower.x,y:tower.y,tx:target.x,ty:target.y,life:4,color:isCrit?`#fff`:tower.color,width:2+Math.min(5,tower.level)});return}let speed=12+Math.min(10,tower.level);tower.type===`sniper`&&(speed=35);let effect=tower.type===`ice`?`slow`:tower.type===`splash`?`splash`:null;tower.awakened&&tower.type===`sniper`&&(effect=`pierce`);let baseProj={target,damage:finalDamage,tower,speed,effect,color:isCrit?`#fff`:tower.color,radius:tower.type===`splash`?tower.radius:0,isCrit};if(tower.type===`machinegun`&&tower.tier===2){let ox=Math.cos(tower.angle+Math.PI/2)*6,oy=Math.sin(tower.angle+Math.PI/2)*6;gameState.projectiles.push({...baseProj,x:tower.x+ox,y:tower.y+oy,lastX:tower.x+ox,lastY:tower.y+oy,startX:tower.x+ox,startY:tower.y+oy}),gameState.projectiles.push({...baseProj,x:tower.x-ox,y:tower.y-oy,lastX:tower.x-ox,lastY:tower.y-oy,startX:tower.x-ox,startY:tower.y-oy})}else gameState.projectiles.push({...baseProj,x:tower.x,y:tower.y,lastX:tower.x,lastY:tower.y,startX:tower.x,startY:tower.y})}function hitEnemy(enemy,damage,effect,tower,isCrit){if(enemy.shield>0){enemy.shield--,enemy.shield<=0&&createFloatingText(`破盾!`,enemy.x,enemy.y-10,`#4488ff`);return}if(tower&&tower.type===`sniper`&&tower.awakened&&enemy.hp<enemy.maxHp*.2&&enemy.type!==`boss`&&(damage=enemy.hp,createFloatingText(`斩杀!`,enemy.x,enemy.y,varColor(`danger`))),enemy.hp-=damage,tower&&(tower.damageDealt+=damage),effect===`slow`&&(enemy.slowTimer=tower.awakened?150:80),enemy.hitFlashTimer=4,tower&&tower.type===`laser`?gameState.frames%10==0&&createDamageText(damage*10,enemy.x+(Math.random()-.5)*20,enemy.y-10,isCrit):createDamageText(damage,enemy.x+(Math.random()-.5)*20,enemy.y-10,isCrit),enemy.hp<=0&&gameState.enemies.includes(enemy)){let index=gameState.enemies.indexOf(enemy);if(gameState.enemies.splice(index,1),enemy.type===`boss`&&(gameState.screenShake=15),enemy.isSplitter)for(let i=0;i<3;i++)gameState.enemies.push({x:enemy.x+(Math.random()-.5)*20,y:enemy.y+(Math.random()-.5)*20,pathIndex:enemy.pathIndex,speed:enemy.speed*1.5,maxHp:enemy.maxHp*.3,hp:enemy.maxHp*.3,radius:8,color:`#22cc22`,type:`normal`,slowTimer:0,isFrozen:!1,shield:0,isStealth:!1,isSplitter:!1,revealed:!0,hitFlashTimer:0});let reward=8e5*1.25**gameState.wave;enemy.type===`boss`&&(reward*=20),enemy.type===`fast`&&(reward*=2),gameState.currentEvent.type===`crisis`&&(reward*=.5),gameState.money+=reward,createFloatingText(`+$${formatMoney(reward)}`,enemy.x,enemy.y,varColor(`gold`),12),gameState.selectedTower?updateUI():document.getElementById(`money-display`).innerText=`$`+formatMoney(gameState.money),createParticle(enemy.x,enemy.y,enemy.color,15)}}function draw(){if(ctx.clearRect(0,0,canvas.width,canvas.height),ctx.save(),gameState.screenShake>0&&(ctx.translate((Math.random()-.5)*15,(Math.random()-.5)*15),gameState.screenShake--),ctx.fillStyle=`rgba(20, 25, 30, 0.8)`,ctx.strokeStyle=`#333`,ctx.lineWidth=2,gameState.obstacles.forEach(o=>{ctx.fillRect(o.x,o.y,o.w,o.h),ctx.strokeRect(o.x,o.y,o.w,o.h),ctx.beginPath(),ctx.moveTo(o.x+10,o.y+10),ctx.lineTo(o.x+o.w-10,o.y+o.h-10),ctx.stroke()}),ctx.lineCap=`round`,ctx.lineJoin=`round`,ctx.lineWidth=CONFIG.pathLineWidth+10,ctx.strokeStyle=`rgba(69, 162, 158, 0.2)`,ctx.shadowBlur=20,ctx.shadowColor=`#45a29e`,ctx.beginPath(),gameState.mapPath.forEach((p,i)=>i===0?ctx.moveTo(p.x,p.y):ctx.lineTo(p.x,p.y)),ctx.stroke(),ctx.shadowBlur=0,ctx.lineWidth=CONFIG.pathLineWidth,ctx.strokeStyle=`#111`,ctx.stroke(),ctx.lineWidth=2,ctx.strokeStyle=`#45a29e`,ctx.stroke(),gameState.towers.forEach(t=>{if(ctx.save(),ctx.translate(t.x,t.y),ctx.globalAlpha=t.isDragging?.7:1,gameState.selectedTower===t&&(ctx.fillStyle=`rgba(255,255,255,0.05)`,ctx.beginPath(),ctx.arc(0,0,t.range,0,Math.PI*2),ctx.fill(),ctx.strokeStyle=t.color,ctx.lineWidth=1,ctx.stroke(),ctx.strokeStyle=t.ultimateAwakened?varColor(`cosmic`):t.awakened?varColor(`purple`):`#ffd700`,ctx.lineWidth=2,ctx.beginPath(),ctx.arc(0,0,25,0,Math.PI*2),ctx.stroke()),gameState.draggingBoardTower&&gameState.draggingBoardTower!==t){let dt=gameState.draggingBoardTower;Math.hypot(t.x-dt.x,t.y-dt.y)<30&&t.type===dt.type&&t.level>=5&&dt.level===t.level&&t.tier===1&&dt.tier===1&&(ctx.shadowBlur=20,ctx.shadowColor=varColor(`gold`),ctx.beginPath(),ctx.arc(0,0,30,0,Math.PI*2),ctx.fillStyle=`rgba(255,215,0,0.3)`,ctx.fill(),ctx.shadowBlur=0)}t.buffMult>1&&(ctx.shadowBlur=10,ctx.shadowColor=`#00ffaa`),t.tier===2&&(ctx.strokeStyle=varColor(`gold`),ctx.lineWidth=3,ctx.setLineDash([8,6]),ctx.beginPath(),ctx.arc(0,0,24+Math.sin(gameState.frames*.05)*2,0,Math.PI*2),ctx.stroke(),ctx.setLineDash([])),ctx.fillStyle=`#222`,ctx.beginPath(),ctx.arc(0,0,18,0,Math.PI*2),ctx.fill(),ctx.strokeStyle=t.ultimateAwakened?varColor(`cosmic`):t.awakened?varColor(`purple`):t.color,ctx.lineWidth=t.tier===2?3:2,ctx.stroke(),ctx.shadowBlur=0,ctx.rotate(t.angle),ctx.translate(-t.recoil,0),ctx.fillStyle=t.tier===2?varColor(`gold`):t.color;let vLvl=Math.min(5,t.level);t.type===`signal`?(ctx.beginPath(),ctx.arc(0,0,8,0,Math.PI*2),ctx.fill(),ctx.strokeStyle=`#00ffaa`,ctx.beginPath(),ctx.arc(0,0,14+Math.sin(gameState.frames*.1)*3,0,Math.PI*2),ctx.stroke()):t.type===`machinegun`?(t.tier===2?(ctx.fillRect(0,-9,20+vLvl*2,6),ctx.fillRect(0,3,20+vLvl*2,6)):ctx.fillRect(0,-4,20+vLvl*2,8),ctx.beginPath(),ctx.arc(0,0,12,0,Math.PI*2),ctx.fill()):t.type===`sniper`?(ctx.fillRect(0,-2-(t.tier===2?2:0),35+vLvl*3,t.tier===2?8:4),ctx.fillRect(10,-5,15,10)):t.type===`ice`?(ctx.beginPath(),ctx.moveTo(20+(t.tier===2?5:0),0),ctx.lineTo(-10,10+(t.tier===2?5:0)),ctx.lineTo(-10,-10-(t.tier===2?5:0)),ctx.fill()):t.type===`splash`?(ctx.fillRect(0,-8-(t.tier===2?2:0),15,16+(t.tier===2?4:0)),ctx.beginPath(),ctx.arc(0,0,14+(t.tier===2?3:0),0,Math.PI*2),ctx.fill()):t.type===`laser`&&(ctx.fillRect(0,-3-(t.tier===2?2:0),25,6+(t.tier===2?4:0)),ctx.beginPath(),ctx.arc(0,0,10,0,Math.PI*2),ctx.fill()),ctx.translate(t.recoil,0),ctx.rotate(-t.angle),ctx.fillStyle=t.ultimateAwakened?`#ffaa00`:t.awakened?varColor(`purple`):`#fff`,t.ultimateAwakened&&(ctx.shadowBlur=10,ctx.shadowColor=`#ffaa00`);let dots=Math.min(5,t.level);for(let i=0;i<dots;i++)ctx.beginPath(),ctx.arc(-10+i*5,0,2,0,Math.PI*2),ctx.fill();t.level>5&&(ctx.fillStyle=`#66fcf1`,ctx.font=`bold 10px Arial`,ctx.fillText(`+`,15,3)),ctx.restore()}),gameState.enemies.forEach(e=>{if(ctx.save(),ctx.translate(e.x,e.y),ctx.globalAlpha=e.isStealth&&!e.revealed?.2:1,e.slowTimer>0&&(ctx.shadowBlur=10,ctx.shadowColor=`#00ffff`),e.hitFlashTimer>0?(ctx.fillStyle=`#ffffff`,ctx.shadowBlur=15,ctx.shadowColor=`#ffffff`):e.isFrozen?ctx.fillStyle=`#fff`:ctx.fillStyle=e.slowTimer>0?`#aaffff`:e.color,e.type===`boss`?ctx.fillRect(-e.radius,-e.radius,e.radius*2,e.radius*2):e.type===`fast`?(ctx.beginPath(),ctx.moveTo(e.radius,0),ctx.lineTo(-e.radius,e.radius),ctx.lineTo(-e.radius,-e.radius),ctx.fill()):(ctx.beginPath(),ctx.arc(0,0,e.radius,0,Math.PI*2),ctx.fill()),ctx.shadowBlur=0,e.shield>0&&(ctx.strokeStyle=`#4488ff`,ctx.lineWidth=3,ctx.beginPath(),ctx.arc(0,0,e.radius+5,0,Math.PI*2*(e.shield/10)),ctx.stroke()),!e.isStealth||e.revealed){let hpPct=Math.max(0,e.hp/e.maxHp);ctx.fillStyle=`rgba(255,0,0,0.5)`,ctx.fillRect(-15,-e.radius-10,30,4),ctx.fillStyle=`#66fcf1`,ctx.fillRect(-15,-e.radius-10,30*hpPct,4)}ctx.restore()}),gameState.projectiles.forEach(p=>{p.type===`laser_beam`?(ctx.save(),ctx.strokeStyle=p.color,ctx.lineWidth=p.width,ctx.shadowBlur=15,ctx.shadowColor=p.color,ctx.globalAlpha=p.life/4,ctx.beginPath(),ctx.moveTo(p.x,p.y),ctx.lineTo(p.tx,p.ty),ctx.stroke(),ctx.restore()):(ctx.save(),ctx.strokeStyle=p.color,ctx.lineWidth=p.radius>0?8:4,ctx.lineCap=`round`,ctx.shadowBlur=10,ctx.shadowColor=p.color,ctx.beginPath(),ctx.moveTo(p.lastX,p.lastY),ctx.lineTo(p.x,p.y),ctx.stroke(),ctx.restore())}),gameState.particles.forEach(p=>{ctx.save(),ctx.globalAlpha=p.alpha,ctx.fillStyle=p.color,ctx.beginPath(),ctx.arc(p.x,p.y,p.radius||2,0,Math.PI*2),ctx.fill(),ctx.restore()}),gameState.draggingMenuTower){let{x,y}=gameState.mousePos,config=TOWER_TYPES[gameState.draggingMenuTower],valid=isValidPlacement(x,y);ctx.save(),ctx.translate(x,y),ctx.fillStyle=valid?`rgba(102, 252, 241, 0.15)`:`rgba(255, 68, 68, 0.15)`,ctx.strokeStyle=valid?`#66fcf1`:`#ff4444`,ctx.lineWidth=1,ctx.beginPath(),ctx.arc(0,0,config.range,0,Math.PI*2),ctx.fill(),ctx.stroke(),ctx.globalAlpha=.6,ctx.fillStyle=config.color,ctx.beginPath(),ctx.arc(0,0,18,0,Math.PI*2),ctx.fill(),ctx.restore()}ctx.restore()}
function varColor(name){return getComputedStyle(document.documentElement).getPropertyValue(`--${name}`).trim()}function createParticle(x,y,color,count,r=0){for(let i=0;i<count;i++)gameState.particles.push({x,y,vx:(Math.random()-.5)*8,vy:(Math.random()-.5)*8,life:20+Math.random()*20,maxLife:40,color,alpha:1,radius:2+Math.random()*r})}function createFloatingText(text,x,y,color,size=16){let el=document.createElement(`div`);el.className=`floating-text`,el.innerText=text,el.style.left=x+`px`,el.style.top=y+`px`,el.style.color=color,el.style.fontSize=size+`px`,el.style.animationDuration=1.2/gameSpeed+`s`,document.getElementById(`game-container`).appendChild(el),gameState.floatingTexts.push({element:el,x,y,life:72})}function createDamageText(damage,x,y,isCrit){let el=document.createElement(`div`);el.className=isCrit?`dmg-text dmg-crit`:`dmg-text`,el.innerText=Math.floor(damage),el.style.left=x+`px`,el.style.top=y+`px`,el.style.animationDuration=(isCrit?1:.8)/gameSpeed+`s`,document.getElementById(`game-container`).appendChild(el),gameState.floatingTexts.push({element:el,x,y,life:isCrit?60:48})}function endGame(){gameState.gameOver=!0,gameState.isPaused=!1,document.getElementById(`pause-overlay`).classList.remove(`visible`),hideActionMenu(),closeAllModals();let earned=Math.floor(gameState.wave/10)*5+gameState.wave;document.getElementById(`earned-tokens`).innerText=earned,document.getElementById(`game-over`).classList.add(`visible`),document.getElementById(`final-wave`).innerText=gameState.wave}window.restartGame=function(){initGameState(),document.getElementById(`game-over`).classList.remove(`visible`);let pauseBtn=document.getElementById(`btn-pause`);pauseBtn.innerHTML=`⏸ 暂停 (${userSettings.keys.pause===` `?`Space`:userSettings.keys.pause.toUpperCase()})`,pauseBtn.style.color=`#aaa`,autoPausedByModal=!1,updateUI()},initGameState(),resize(),updateUI(),requestAnimationFrame(gameLoop);</script>