Rogue 冒险 · 层级远征
by QuantumPenguin78915 lines71.3 KB
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
<title>Rogue 冒险 · 层级远征</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}html{-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}
body{font-family:-apple-system,'SF Pro Text','Segoe UI','Microsoft YaHei',sans-serif;background:linear-gradient(135deg,#0d0d1a,#1a1a2e,#16213e);min-height:100vh;min-height:100dvh;color:#e0e0e0;display:flex;flex-direction:column;align-items:center;padding:max(8px,env(safe-area-inset-top)) max(8px,env(safe-area-inset-right)) max(8px,env(safe-area-inset-bottom)) max(8px,env(safe-area-inset-left));user-select:none;-webkit-user-select:none;touch-action:manipulation}
button,.card,.ecard{touch-action:manipulation;-webkit-tap-highlight-color:transparent}
.top-bar{display:flex;gap:10px;flex-wrap:wrap;justify-content:center;width:100%;max-width:700px;margin-bottom:6px;font-size:0.9rem}
.top-bar span{background:rgba(255,255,255,0.08);padding:3px 10px;border-radius:6px}
.top-bar .floor{color:#ffd700;font-weight:700}.top-bar .lvl{color:#a8e6cf}.top-bar .gold{color:#f9ca24}
h1.title{font-size:clamp(1.1rem,4vw,1.5rem);color:#e94560;text-shadow:0 0 15px rgba(233,69,96,0.5);margin-bottom:4px}
.main{display:flex;flex-direction:column;gap:8px;width:100%;max-width:700px}
.panel{background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.12);border-radius:12px;padding:12px;display:flex;flex-direction:column;gap:5px}
.panel-hdr{display:flex;align-items:center;gap:8px;margin-bottom:2px}
.av{width:36px;height:36px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:1.2rem;flex-shrink:0}
.av.p{background:linear-gradient(135deg,#3498db,#2980b9)}.av.e{background:linear-gradient(135deg,#e74c3c,#c0392b)}
.pname{font-size:0.95rem;font-weight:700;color:#fff}.psub{font-size:0.75rem;color:#8892b0}
.bars{display:flex;flex-direction:column;gap:4px}
.bar-r{display:flex;align-items:center;gap:6px}
.bar-l{font-size:0.75rem;width:22px;font-weight:600;flex-shrink:0}.bar-l.hp{color:#e74c3c}.bar-l.mp{color:#3498db}.bar-l.fa{color:#f1c40f}
.bar-t{flex:1;height:14px;background:rgba(255,255,255,0.1);border-radius:7px;overflow:hidden;position:relative}
.bar-f{height:100%;border-radius:7px;transition:width 0.3s}
.bar-f.hp{background:linear-gradient(90deg,#e74c3c,#ff6b6b)}.bar-f.mp{background:linear-gradient(90deg,#2984b9,#3498db)}.bar-f.fa{background:linear-gradient(90deg,#f1c40f,#f39c12)}
.bar-tx{position:absolute;right:5px;top:50%;transform:translateY(-50%);font-size:0.65rem;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,0.8);font-weight:600}
.stats{display:flex;gap:6px;flex-wrap:wrap;font-size:0.82rem}
.stat{background:rgba(255,255,255,0.07);border-radius:5px;padding:2px 8px;font-weight:600}
.stat .l{color:#8892b0;font-weight:400}
.equip-row,.cal-row{display:flex;gap:6px;align-items:center;flex-wrap:wrap;font-size:0.78rem}
.wpn-badge{background:rgba(255,215,0,0.1);border:1px solid rgba(255,215,0,0.25);border-radius:6px;padding:2px 8px;font-weight:600}
.acc-badge{font-size:0.95rem;background:rgba(255,215,0,0.1);border-radius:5px;padding:1px 4px;border:1px solid rgba(255,215,0,0.2)}
.enemy-area{display:flex;gap:8px;overflow-x:auto;-webkit-overflow-scrolling:touch;scroll-snap-type:x mandatory;padding-bottom:4px;width:100%}
.enemy-area::-webkit-scrollbar{height:3px}.enemy-area::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.15);border-radius:2px}
.ecard{flex:0 0 auto;min-width:170px;background:rgba(255,255,255,0.04);border:2px solid rgba(255,255,255,0.1);border-radius:10px;padding:10px;cursor:pointer;transition:border-color 0.15s,transform 0.08s;display:flex;flex-direction:column;gap:4px;min-height:48px;scroll-snap-align:start}
.ecard:active{transform:scale(0.97)}.ecard.tgt{border-color:#e94560;box-shadow:0 0 10px rgba(233,69,96,0.3)}.ecard.dead{opacity:0.25;pointer-events:none;filter:grayscale(0.8)}
.ecard .eh{display:flex;align-items:center;gap:5px}.ecard .ei{font-size:1.1rem}.ecard .en{font-size:0.85rem;font-weight:700;color:#fff}.ecard .et{font-size:0.65rem;color:#e94560;margin-left:auto;font-weight:600}
.ecard .efx{font-size:0.68rem;color:#ffa502;display:flex;gap:4px;flex-wrap:wrap}
.log{width:100%;background:rgba(0,0,0,0.3);border:1px solid rgba(255,255,255,0.06);border-radius:8px;padding:8px 10px;height:120px;overflow-y:auto;-webkit-overflow-scrolling:touch;scroll-behavior:smooth}
.log::-webkit-scrollbar{width:3px}.log::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.15);border-radius:2px}
.le{font-size:0.82rem;line-height:1.5;padding:1px 0;border-bottom:1px solid rgba(255,255,255,0.03);color:#c0c8e0;animation:li 0.12s ease}
.le.pl{color:#7ec8f8}.le.en{color:#f8a0a0}.le.sys{color:#a8e6cf}.le.art{color:#ffd700}
@keyframes li{from{opacity:0;transform:translateX(-3px)}to{opacity:1;transform:none}}
.acts{display:flex;gap:6px;flex-wrap:wrap;justify-content:center;width:100%;padding-bottom:env(safe-area-inset-bottom)}
.abtn{flex:1 1 72px;min-width:72px;min-height:50px;border:none;border-radius:10px;font-size:0.92rem;font-weight:700;cursor:pointer;color:#fff;transition:transform 0.08s,opacity 0.12s;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1px;padding:5px 6px}
.abtn:active:not(:disabled){transform:scale(0.93);opacity:0.85}.abtn:disabled{opacity:0.3;cursor:not-allowed;filter:grayscale(0.6)}
.abtn .s{font-size:0.68rem;font-weight:400;opacity:0.8}
.b-a{background:linear-gradient(135deg,#636e72,#2d3436)}.b-h{background:linear-gradient(135deg,#e17055,#d63031)}
.b-d{background:linear-gradient(135deg,#0984e3,#074b8a)}.b-m{background:linear-gradient(135deg,#6c5ce7,#4834d4)}
.ov{position:fixed;inset:0;background:rgba(0,0,0,0.75);backdrop-filter:blur(4px);display:none;align-items:center;justify-content:center;z-index:100;padding:max(12px,env(safe-area-inset-top)) max(12px,env(safe-area-inset-right)) max(12px,env(safe-area-inset-bottom)) max(12px,env(safe-area-inset-left));overflow-y:auto;-webkit-overflow-scrolling:touch}
.ov.show{display:flex}.ov.z2{z-index:110}.ov.z3{z-index:120}
.mdl{background:rgba(25,25,50,0.97);border:1px solid rgba(255,255,255,0.15);border-radius:14px;padding:20px 16px;max-width:520px;width:100%;text-align:center;animation:mi 0.2s ease;max-height:92vh;overflow-y:auto;position:relative}
@keyframes mi{from{opacity:0;transform:scale(0.92)}to{opacity:1;transform:scale(1)}}
.mdl h2{margin-bottom:4px;font-size:1.15rem}.mdl p{color:#8892b0;font-size:0.85rem;margin-bottom:12px}
.bgrid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px}
@media(max-width:500px){.bgrid{grid-template-columns:1fr}.ecard{min-width:180px}.class-grid{grid-template-columns:1fr 1fr!important}}
.bcard{background:rgba(255,255,255,0.06);border:2px solid rgba(255,255,255,0.12);border-radius:10px;padding:14px 8px;cursor:pointer;transition:border-color 0.15s,transform 0.08s;display:flex;flex-direction:column;align-items:center;gap:5px;min-height:80px}
.bcard:active{transform:scale(0.96);border-color:#ffd700}
.bcard .bi{font-size:1.8rem}.bcard .bn{font-weight:700;font-size:0.9rem}.bcard .bd{font-size:0.75rem;color:#8892b0;line-height:1.3}
.shop-item{display:flex;align-items:center;gap:10px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);border-radius:8px;padding:10px;margin-bottom:6px;cursor:pointer;transition:background 0.1s}
.shop-item:active{background:rgba(255,255,255,0.15)}.shop-item.off{opacity:0.35;pointer-events:none}
.shop-item .si-name{font-weight:700;font-size:0.88rem;flex:1}.shop-item .si-price{color:#f9ca24;font-weight:700;font-size:0.85rem}
.q1{color:#bbb}.q2{color:#2ecc71}.q3{color:#3498db}.q4{color:#9b59b6}.q5{color:#f39c12}.q6{color:#e74c3c}
.rbtn{background:linear-gradient(135deg,#e94560,#c0392b);border:none;color:#fff;font-size:1rem;font-weight:700;padding:12px 28px;border-radius:10px;cursor:pointer;min-height:50px;margin-top:10px}
.rbtn:active{transform:scale(0.95)}
.compare{display:flex;gap:10px;justify-content:center;margin:10px 0}
.cmp-box{flex:1;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);border-radius:8px;padding:10px;text-align:center;font-size:0.8rem}
.cmp-box .cb-n{font-weight:700;font-size:0.9rem;margin-bottom:4px}.cmp-box .cb-s{color:#8892b0;font-size:0.75rem}
.cbtn-row{display:flex;gap:8px;justify-content:center;margin-top:8px}
.cbtn{padding:10px 20px;border:none;border-radius:8px;font-weight:700;font-size:0.9rem;cursor:pointer;color:#fff;min-height:44px}
.cbtn:active{transform:scale(0.95)}.cbtn.yes{background:#27ae60}.cbtn.no{background:#636e72}
.debuff-row,.cal-row{display:flex;gap:4px;flex-wrap:wrap;font-size:0.7rem}
.debuff{background:rgba(255,100,50,0.2);border:1px solid rgba(255,100,50,0.3);border-radius:4px;padding:1px 5px;color:#ffa502;position:relative;cursor:help}
.debuff-tip{display:none;position:absolute;bottom:calc(100% + 6px);left:50%;transform:translateX(-50%);background:rgba(20,20,40,0.97);border:1px solid rgba(255,255,255,0.2);border-radius:8px;padding:8px 12px;white-space:nowrap;font-size:0.78rem;color:#e0e0e0;z-index:50;pointer-events:none;box-shadow:0 4px 12px rgba(0,0,0,0.5);min-width:140px}
.debuff-tip::after{content:'';position:absolute;top:100%;left:50%;transform:translateX(-50%);border:5px solid transparent;border-top-color:rgba(255,255,255,0.2)}
.debuff:hover .debuff-tip,.debuff:active .debuff-tip{display:block}
.pause-btn{position:absolute;top:10px;right:10px;background:rgba(255,255,255,0.1);border:1px solid rgba(255,255,255,0.2);border-radius:8px;padding:6px 12px;color:#aaa;font-size:0.8rem;cursor:pointer;z-index:5;min-height:36px}
.pause-btn:active{transform:scale(0.93);background:rgba(255,255,255,0.2)}
.resume-float{position:fixed;bottom:max(20px,env(safe-area-inset-bottom));left:50%;transform:translateX(-50%);background:linear-gradient(135deg,#e94560,#c0392b);border:none;color:#fff;font-size:0.95rem;font-weight:700;padding:12px 24px;border-radius:24px;cursor:pointer;z-index:200;box-shadow:0 4px 20px rgba(233,69,96,0.5);display:none;min-height:48px;animation:floatPulse 2s ease-in-out infinite}
@keyframes floatPulse{0%,100%{box-shadow:0 4px 20px rgba(233,69,96,0.5)}50%{box-shadow:0 4px 30px rgba(233,69,96,0.8)}}
.resume-float.show{display:block}
.acc-badge,.cal-icon{position:relative;cursor:help}
.acc-tip,.cal-tip{display:none;position:absolute;bottom:calc(100% + 6px);left:50%;transform:translateX(-50%);background:rgba(20,20,40,0.97);border:1px solid rgba(255,255,255,0.2);border-radius:8px;padding:8px 12px;white-space:nowrap;font-size:0.78rem;color:#e0e0e0;z-index:50;pointer-events:none;box-shadow:0 4px 12px rgba(0,0,0,0.5);min-width:120px}
.acc-tip::after,.cal-tip::after{content:'';position:absolute;top:100%;left:50%;transform:translateX(-50%);border:5px solid transparent;border-top-color:rgba(255,255,255,0.2)}
.acc-badge:hover .acc-tip,.acc-badge:active .acc-tip,.cal-icon:hover .cal-tip,.cal-icon:active .cal-tip{display:block}
.wpn-badge{position:relative;cursor:help}.wpn-tip{display:none;position:absolute;bottom:calc(100% + 6px);left:50%;transform:translateX(-50%);background:rgba(20,20,40,0.97);border:1px solid rgba(255,255,255,0.2);border-radius:8px;padding:8px 12px;white-space:nowrap;font-size:0.78rem;color:#e0e0e0;z-index:50;pointer-events:none;box-shadow:0 4px 12px rgba(0,0,0,0.5)}
.wpn-tip::after{content:'';position:absolute;top:100%;left:50%;transform:translateX(-50%);border:5px solid transparent;border-top-color:rgba(255,255,255,0.2)}
.wpn-badge:hover .wpn-tip,.wpn-badge:active .wpn-tip{display:block}
.class-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;margin-top:12px}
.class-card{background:rgba(255,255,255,0.06);border:2px solid rgba(255,255,255,0.12);border-radius:12px;padding:14px 10px;cursor:pointer;transition:border-color 0.15s,transform 0.08s;display:flex;flex-direction:column;align-items:center;gap:6px;text-align:center}
.class-card:active{transform:scale(0.96);border-color:#ffd700}
.class-card .cc-icon{font-size:2rem}.class-card .cc-name{font-weight:900;font-size:1rem;color:#fff}
.class-card .cc-stats{font-size:0.75rem;color:#8892b0;line-height:1.5}.class-card .cc-spec{font-size:0.7rem;color:#e94560;line-height:1.3;margin-top:2px}
.cal-icon{background:rgba(255,50,50,0.15);border:1px solid rgba(255,50,50,0.3);border-radius:5px;padding:1px 5px;font-size:0.85rem}
.drop-acc{background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.15);border-radius:10px;padding:12px;margin:6px 0;text-align:center}
.class-spec-row{display:flex;gap:8px;justify-content:center;flex-wrap:wrap;margin-top:6px}
.spec-chip{background:rgba(233,69,96,0.15);border:1px solid rgba(233,69,96,0.3);border-radius:6px;padding:2px 8px;font-size:0.72rem;color:#e94560}
.feed-btn{background:rgba(76,175,80,0.2);border:1px solid rgba(76,175,80,0.4);border-radius:6px;padding:2px 10px;font-size:0.72rem;color:#66bb6a;cursor:pointer;margin-left:8px;transition:all 0.2s}
.feed-btn:hover:not(:disabled){background:rgba(76,175,80,0.35);border-color:rgba(76,175,80,0.6)}
.feed-btn:disabled{opacity:0.4;cursor:not-allowed;color:#888}
</style>
</head>
<body>
<h1 class="title">⚔️ 层级远征</h1>
<div class="top-bar" id="topBar">
<span class="floor" id="dFloor">第 1 / 30 层</span>
<span class="lvl" id="dLvl">Lv.1</span>
<span class="gold" id="dGold">💰 0</span>
</div>
<div class="main">
<div class="panel" id="playerPanel">
<div class="panel-hdr"><div class="av p" id="pAv">🗡️</div><div><div class="pname" id="pName">勇者</div><div class="psub" id="pSub">Lv.1 冒险者</div></div></div>
<div class="bars" id="barsArea">
<div class="bar-r"><span class="bar-l hp">HP</span><div class="bar-t"><div class="bar-f hp" id="pHp" style="width:100%"></div><span class="bar-tx" id="pHpTx">30/30</span></div></div>
<div class="bar-r"><span class="bar-l mp">MP</span><div class="bar-t"><div class="bar-f mp" id="pMp" style="width:100%"></div><span class="bar-tx" id="pMpTx">10/10</span></div></div>
</div>
<div class="stats"><div class="stat"><span class="l">ATK </span><span id="pAtk">5</span></div><div class="stat"><span class="l">DEF </span><span id="pDef">2</span></div></div>
<div class="equip-row" id="equipRow"></div>
<div class="cal-row" id="calRow"></div>
<div class="debuff-row" id="debuffRow"></div>
<div id="classMechRow" style="display:none"></div>
</div>
<div class="enemy-area" id="enemyArea" style="display:none"></div>
<div class="log" id="logBox"></div>
<div class="acts" id="actBar" style="display:none">
<button class="abtn b-a" id="bAtk" onclick="doAction('atk')">普通攻击<span class="s">0 MP</span></button>
<button class="abtn b-h" id="bHvy" onclick="doAction('hvy')">强力攻击<span class="s" id="bHvyC">5 MP</span></button>
<button class="abtn b-d" id="bDef" onclick="doAction('def')">防御<span class="s">减伤50%</span></button>
<button class="abtn b-m" id="bMp" onclick="doAction('mp')">回蓝<span class="s">+5 MP</span></button>
</div>
</div>
<div class="ov z3" id="classOv"><div class="mdl"><h2>⚔️ 选择职业</h2><p>你的职业将决定冒险的走向</p><div class="class-grid" id="classGrid"></div></div></div>
<div class="ov" id="branchOv"><div class="mdl"><button class="pause-btn" onclick="pauseOv('branchOv')">⏸ 暂离</button><h2>🗺️ 选择前进路线</h2><p id="branchHint">选择一条路线继续冒险</p><div class="bgrid" id="branchGrid"></div></div></div>
<div class="ov" id="eventOv"><div class="mdl" id="eventMdl"><button class="pause-btn" id="eventPauseBtn" onclick="pauseOv('eventOv')">⏸ 暂离</button></div></div>
<div class="ov z2" id="wpnOv"><div class="mdl" id="wpnMdl"></div></div>
<div class="ov z2" id="accDropOv"><div class="mdl" id="accDropMdl"></div></div>
<div class="ov" id="endOv"><div class="mdl"><div id="endTitle" style="font-size:1.5rem;font-weight:900;margin-bottom:6px"></div><p id="endMsg"></p><div id="endSummary" style="text-align:left;font-size:0.82rem;line-height:1.8;margin:10px 0"></div><button class="rbtn" onclick="restart()">🔄 重新开始</button></div></div>
<button class="resume-float" id="resumeFloat" onclick="resumeOv()">▶ 继续</button>
<script>
// ============ CONFIG ============
const TOTAL=30,ELITE=[5,15,25],BOSS=[10,20,30];
const QCOLORS=['','q1','q2','q3','q4','q5','q6'];
const QNAMES=['','普通','优秀','稀有','史诗','传说','神话'];
const BRANCH_W={enemy:40,heal:25,treasure:15,shop:15,mystery:5};
const WPN_NAMES=[[],['小木棍','树枝杖','石匕首','粗铁剑','朽木杖'],['铁剑','阔刀','长戟','双刃斧','战锤'],['精钢刀','寒铁剑','秘银刃','符文斧','月光匕'],['暗影刃','噬魂剑','风暴战斧','雷霆之锤','炎魔刀'],['龙牙剑','圣光斩刀','血月弯刀','天罚之枪','星辰之刃'],['灭世之刃','混沌神剑','虚空裂斧','命运之矛','永恒之刃']];
const WPN_EFF=['none','fire','poison','cold','lifesteal','blood_sac','iron_wall','charge'];
const WPN_EFF_NAME={none:'无',fire:'🔥火焰附加',poison:'☠️毒素附加',cold:'❄️寒冷附加',lifesteal:'🩸吸血',blood_sac:'🩸血祭',iron_wall:'🛡️铁壁',charge:'⚡蓄力'};
// Weapon effect coefficient tables: [q1,q2,q3,q4,q5,q6] as percentages
const FIRE_COEFF=[0,12,16,20,25,30,40];
const POISON_COEFF=[0,3,4,6,7,8,10];
const COLD_COEFF=[0,8,12,16,20,25,30];
const FIRE_TURNS=[0,2,2,2,3,3,3];
const POISON_TURNS=[0,3,3,3,4,4,4];
const COLD_TURNS=[0,2,2,2,3,3,3];
// Beast tamer pet skills pool
const PET_SKILLS=[
{id:'fire_claw',name:'🔥火焰爪',desc:'攻击附带火焰(15%)'},
{id:'poison_fang',name:'☠️毒牙',desc:'攻击附带毒素(4%)'},
{id:'crit_up',name:'⚡暴击加强',desc:'暴击率+20% 伤害1.8x'},
{id:'guard',name:'🛡️守护',desc:'每回合抵挡3点伤害'},
{id:'gale',name:'💨疾风',desc:'玩家下次攻击+20%'},
{id:'vampire',name:'🩸吸血',desc:'恢复伤害20%HP'},
{id:'frost',name:'❄️冰霜吐息',desc:'攻击附带寒冷(12%)'},
{id:'double_hit',name:'🌀连击',desc:'15%概率攻击两次'}
];
const ACC_POOL=[
{id:'blood_blade',name:'血刃',desc:'胜利恢复{v}%HP',base:20},
{id:'berserker',name:'狂战怒意',desc:'强攻倍率+{v}',base:0.3},
{id:'crit_ring',name:'暴击指环',desc:'{v}%暴击率',base:12},
{id:'thorn',name:'荆棘之甲',desc:'反弹{v}伤害',base:2},
{id:'sharp',name:'锐利之刃',desc:'无视防御{v}',base:2},
{id:'regen',name:'复苏之风',desc:'每回合恢复{v}HP',base:2},
{id:'energy',name:'能量过载',desc:'强攻耗蓝-{v}',base:1},
{id:'greed',name:'贪婪指环',desc:'金币+{v}%',base:30},
{id:'fang',name:'嗜血之牙',desc:'普攻恢复{v}HP',base:3},
{id:'thunder',name:'雷霆之击',desc:'{v}%闪电链5-10伤害',base:15},
{id:'weakness',name:'弱点洞悉',desc:'无视{v}%防御',base:20},
{id:'rage_blood',name:'狂战之血',desc:'每损失1%HP伤害+{v}%',base:0.3},
{id:'executioner',name:'处刑者',desc:'敌人HP<30%伤害+{v}%',base:40},
{id:'ricochet',name:'弹射之刃',desc:'击杀溢出伤害*{v}%传递',base:50},
{id:'dragon_scale',name:'龙鳞甲',desc:'减伤最大生命值的{v}%',base:5},
{id:'life_pact',name:'生命契约',desc:'HP<30%恢复10HP/场',base:10},
{id:'meditation',name:'冥想之石',desc:'每回合额外恢复{v}MP',base:3},
{id:'stone_skin',name:'石肤术',desc:'防御减伤提升至{v}%',base:65},
{id:'life_fountain',name:'生命之泉',desc:'层结束恢复{v}%HP',base:15},
{id:'treasure_hunter',name:'宝箱猎手',desc:'宝箱怪概率{v}%',base:10},
{id:'lucky_coin',name:'幸运币',desc:'击杀额外+{v}金币',base:10},
{id:'collector',name:'收藏家',desc:'每获得饰品ATK+1*{v}%',base:50},
{id:'greedy_pot',name:'贪婪之壶',desc:'{v}%双倍金币',base:30},
{id:'warrior_mark',name:'勇者之印',desc:'战胜敌人获得ATK(递减)',base:1},
{id:'exp_badge',name:'经验徽章',desc:'击杀额外升级(品质相关)',base:1},
{id:'heritage',name:'传承护符',desc:'升级额外ATK+HP(递减)',base:1},
{id:'echo_bell',name:'回响之铃',desc:'回蓝额外恢复{v}HP',base:2},
{id:'energy_conv',name:'能量转换',desc:'每回合3HP→3MP',base:3}
];
const ENEMY_EFF=['fire','poison','cold','lifesteal','berserk'];
const ENEMY_EFF_NAME={fire:'🔥火焰',poison:'☠️毒素',cold:'❄️寒冷',lifesteal:'🩸吸血',berserk:'💢狂暴'};
const MYSTERY_POOL=['cursed_acc','blessing','trap','gambling'];
const PAUSE_BTN_FN=(id)=>`<button class="pause-btn" onclick="pauseOv('${id}')">⏸ 暂离</button>`;
const PAUSE_BTN=PAUSE_BTN_FN('eventOv');
// ============ CLASS DATA ============
const CLASSES=[
{id:'assassin',name:'刺客',icon:'🗡️',hp:25,atk:10,def:1,mp:10,growA:3,growD:1,growH:8,spec:'初始暴击率20%,暴击伤害2.2倍',specDesc:'高攻低防的致命一击者'},
{id:'berserker',name:'狂战士',icon:'🩸',hp:45,atk:4,def:1,mp:10,growA:2,growD:2,growH:8,spec:'ATK/DEF = 基本值+(最大HP-当前HP)×20%',specDesc:'血量越低越强'},
{id:'shield',name:'神盾使',icon:'🛡️',hp:35,atk:2,def:8,mp:10,growA:1,growD:3,growH:8,spec:'受击反弹DEF×0.5伤害',specDesc:'以守为攻的铁壁'},
{id:'werewolf',name:'狼人',icon:'🐺',hp:80,atk:15,def:0,mp:10,growA:2,growD:0,growH:8,spec:'DEF=0,DEF加成→HP上限;攻击吸血15%',specDesc:'永不防御的野兽'},
{id:'knight',name:'骑士',icon:'⚔️',hp:40,atk:7,def:5,mp:10,growA:3,growD:3,growH:10,spec:'全面成长的精英战士',specDesc:'攻守兼备的全能者'},
{id:'noble',name:'没落贵族',icon:'👑',hp:30,atk:5,def:3,mp:10,growA:2,growD:2,growH:8,spec:'初始150金;商店+1件;击杀金+50%;紫品+概率',specDesc:'以财富弥补实力'},
{id:'beast',name:'驯兽师',icon:'🐾',hp:32,atk:6,def:3,mp:10,growA:2,growD:2,growH:8,spec:'拥有宠物,喂食升级获得技能',specDesc:'与宠物并肩作战'},
{id:'undead',name:'亡灵猎手',icon:'🕯️',hp:32,atk:6,def:3,mp:10,growA:2,growD:2,growH:8,spec:'提灯系统:火/毒/冰三附加效果',specDesc:'三重诅咒猎手'},
{id:'angel',name:'残翼天使',icon:'😇',hp:38,atk:8,def:5,mp:10,growA:3,growD:3,growH:10,spec:'信仰值系统:消耗信仰伤害+50%',specDesc:'信仰之力'}
];
// ============ CALAMITY DATA ============
const CALAMITIES=[
{id:'price_up',name:'物价飞涨',icon:'💰',desc:'商店所有商品价格+50%'},
{id:'enemy_rage',name:'敌人狂怒',icon:'⚡',desc:'敌人攻击力+25%,火焰系数15%'},
{id:'life_decay',name:'生命凋零',icon:'☠️',desc:'每回合开始损失2点HP'},
{id:'thorn_shield',name:'荆棘之盾',icon:'🛡️',desc:'敌人20%概率减伤50%,毒素系数3%'},
{id:'fog',name:'迷雾笼罩',icon:'🌫️',desc:'分支选项不显示类型(盲选)'},
{id:'hunger',name:'饥饿诅咒',icon:'🍖',desc:'医疗恢复量减半'},
{id:'greed_punish',name:'贪婪惩罚',icon:'👹',desc:'击杀金币-30%'},
{id:'fragile',name:'脆弱躯体',icon:'💔',desc:'最大生命值-20%'},
{id:'blind',name:'失明诅咒',icon:'🕶️',desc:'战斗中隐藏敌人属性'},
{id:'breath',name:'喘息之敌',icon:'💨',desc:'敌人每回合恢复3HP'},
{id:'bad_luck',name:'厄运缠绕',icon:'🎲',desc:'暴击率减半'},
{id:'weak_touch',name:'虚弱之触',icon:'🤲',desc:'攻击力-15%'},
{id:'mp_block',name:'回蓝阻碍',icon:'🔮',desc:'每回合MP恢复-2'},
{id:'chest_trap',name:'宝箱陷阱',icon:'🎁',desc:'宝箱怪概率40%,属性×1.5'},
{id:'frost_realm',name:'极寒领域',icon:'❄️',desc:'敌人获寒冷附加(10%),玩家每回合-2HP'}
];
// ============ STATE ============
let P,enemies,tgtIdx,busy,logs,gameOver,endlessMode=false,calamities=[],shopItems=[];
let stats={kills:0,elites:0,bosses:0,goldTotal:0,weapons:[],accessories:[],maxFloor:0};
function showClassSelect(){
let h='';CLASSES.forEach((c,i)=>{
h+=`<div class="class-card" onclick="selectClass(${i})">
<div class="cc-icon">${c.icon}</div><div class="cc-name">${c.name}</div>
<div class="cc-stats">ATK ${c.atk} / DEF ${c.def} / HP ${c.hp}<br>${c.specDesc}</div>
<div class="cc-spec">${c.spec}</div></div>`;
});$('classGrid').innerHTML=h;$('classOv').classList.add('show');
}
function selectClass(i){
let c=CLASSES[i];$('classOv').classList.remove('show');
P={hp:c.hp,maxHp:c.hp,atk:c.atk,def:c.def,mp:c.mp||10,maxMp:10,level:1,gold:c.id==='noble'?150:0,floor:1,
cls:c.id,growA:c.growA,growD:c.growD,growH:c.growH,
weapon:{name:'小木棍',quality:1,atkBonus:0,mpCost:5,mult:2.0,effect:'none'},
accessories:[],fireStacks:[],poisonStacks:[],coldRedStacks:[],chargeBonus:0,
cursedTurns:0,cursedAcc:null,warriorMarks:0,collectorTriggers:0,heritageTriggers:0,expBadgeTriggers:0,
lifePactUsed:false,
// Class-specific
pet:c.id==='beast'?{level:1,atk:3,skills:[],nextCost:3}:null,
lanternLv:c.id==='undead'?1:0,
faith:c.id==='angel'?100:0,maxFaith:c.id==='angel'?100:0,
galeBonus:0};
$('pName').textContent=c.name;$('pAv').textContent=c.icon;
addCalamity();renderAll();showBranch();
}
function newGame(){enemies=[];tgtIdx=0;busy=false;logs=[];gameOver=false;endlessMode=false;calamities=[];shopItems=[];
stats={kills:0,elites:0,bosses:0,goldTotal:0,weapons:[],accessories:[],maxFloor:0};
stats.weapons.push('小木棍(白)');showClassSelect();
}
// ============ PAUSE / RESUME ============
let pausedOvId=null;
function pauseOv(id){$(id).classList.remove('show');pausedOvId=id;$('resumeFloat').classList.add('show');renderAll()}
function resumeOv(){if(pausedOvId){$(pausedOvId).classList.add('show');pausedOvId=null;$('resumeFloat').classList.remove('show')}}
// ============ UTIL ============
const rng=(a,b)=>Math.floor(Math.random()*(b-a+1))+a;
const $=id=>document.getElementById(id);
function addLog(t,m){logs.push({t,m});if(logs.length>80)logs.shift();renderLog();}
function hasAcc(id){return P.accessories.some(a=>a.id===id)}
function accVal(id){let v=0;P.accessories.forEach(a=>{if(a.id===id)v+=a.value});return v}
function hasCal(id){return calamities.some(c=>c.id===id)}
// ============ DIMINISHING RETURNS ============
function getWarriorMarkBonus(n){if(n<=3)return 1;if(n<=6)return(n%2===0)?1:0;return(n%3===1)?1:0}
let collectorBonus=0;
function getHeritageBonus(n){let a=0,h=0;if(n<=5){a=1;h=3}else if(n<=10){a=1;h=2}else if(n<=15){a=(n%2===1)?1:0;h=1}else{a=(n%3===1)?1:0;h=1}return{atk:a,hp:h}}
function onCollectorTrigger(){if(!hasAcc('collector'))return;let n=P.accessories.length,v=accVal('collector'),b=Math.floor(v/100);if(b>0){P.atk+=b;collectorBonus+=b;addLog('art',`【收藏家】第${n}件饰品,ATK+${b}`)}else addLog('art',`【收藏家】第${n}件饰品,ATK+0`)}
// ============ STAT CALCULATIONS ============
function getEffAtk(){
let a=P.atk+(P.weapon?P.weapon.atkBonus:0);
if(hasAcc('warrior_mark'))a+=P.warriorMarks;
if(P.cls==='berserker'){let m=Math.max(0,P.maxHp-P.hp);a+=Math.floor(m*0.2)}
if(P.cls==='angel'&&P.faith<50)a=Math.floor(a*0.1);// Fallen: ATK-90%
if(hasCal('weak_touch'))a=Math.floor(a*0.85);
if(P.galeBonus>0){a=Math.floor(a*1.2);P.galeBonus=0}
return a;
}
function getEffDef(){
let d=P.def;if(P.cls==='berserker'){let m=Math.max(0,P.maxHp-P.hp);d+=Math.floor(m*0.2)}
if(P.cls==='werewolf')d=0;
// Fallen: no DEF bonus
return d;
}
function getEffMult(){let m=P.weapon?P.weapon.mult:2.0;P.accessories.forEach(a=>{if(a.id==='berserker')m+=a.value});return m}
function getEffMpCost(){let c=P.weapon?P.weapon.mpCost:5;P.accessories.forEach(a=>{if(a.id==='energy')c=Math.max(0,c-Math.floor(a.value))});return c}
function getSharp(){let s=0;P.accessories.forEach(a=>{if(a.id==='sharp')s+=Math.floor(a.value)});return s}
function getThorn(){let v=0;P.accessories.forEach(a=>{if(a.id==='thorn')v+=Math.floor(a.value)});return v}
function getCrit(){let r=0,d=1.8;if(P.cls==='assassin'){r=0.2;d=2.2}P.accessories.forEach(a=>{if(a.id==='crit_ring')r+=a.value/100});if(hasCal('bad_luck'))r*=0.5;return{rate:r,dmg:d}}
function getRegen(){let v=0;P.accessories.forEach(a=>{if(a.id==='regen')v+=Math.floor(a.value)});return v}
function getBloodBlade(){let v=0;P.accessories.forEach(a=>{if(a.id==='blood_blade')v+=a.value});return v}
function getGreedMult(){let g=0;P.accessories.forEach(a=>{if(a.id==='greed')g+=a.value});let m=1+g/100;if(P.cls==='noble')m+=0.5;if(hasCal('greed_punish'))m*=0.7;return m}
function getDefRate(){return hasAcc('stone_skin')?accVal('stone_skin')/100:0.5}
function getShopDiscount(){return 1}
function getMimicChance(){if(hasCal('chest_trap'))return 0.4;return hasAcc('treasure_hunter')?accVal('treasure_hunter')/100:0.2}
function getRageBonus(){if(!hasAcc('rage_blood'))return 0;let p=Math.max(0,(P.maxHp-P.hp)/P.maxHp*100);return Math.min(30,Math.floor(p*accVal('rage_blood')))}
function getExecBonus(e){if(!hasAcc('executioner'))return 1;if(e.alive&&e.hp<e.maxHp*0.3)return 1+accVal('executioner')/100;return 1}
function getDragonReduce(){if(!hasAcc('dragon_scale'))return 0;return Math.floor(P.maxHp*accVal('dragon_scale')/100)}
// ============ STATUS EFFECT SYSTEM ============
function addFireToEnemy(e,dmgPerTick,turns){e.fireStacks=e.fireStacks||[];e.fireStacks.push({dmg:dmgPerTick,turns});addLog('pl',`🔥 火焰附加!(${dmgPerTick}/回合×${turns})`)}
function addPoisonToEnemy(e,dmgPerTick,turns){e.poisonStacks=e.poisonStacks||[];e.poisonStacks.push({dmg:dmgPerTick,turns});addLog('pl',`☠️ 毒素附加!(${dmgPerTick}/回合×${turns})`)}
function addColdToEnemy(e,atkRed,turns){e.coldStacks=e.coldStacks||[];e.coldStacks.push({val:atkRed,turns});addLog('pl',`❄️ 寒冷附加!(ATK-${atkRed}×${turns})`)}
function getEnemyColdRed(e){if(!e.coldStacks||!e.coldStacks.length)return 0;return Math.max(...e.coldStacks.map(s=>s.val))}
function tickEnemyStatus(e){
let total=0;
if(e.fireStacks){e.fireStacks.forEach(s=>{if(s.turns>0){total+=s.dmg;s.turns--}});e.fireStacks=e.fireStacks.filter(s=>s.turns>0)}
if(e.poisonStacks){e.poisonStacks.forEach(s=>{if(s.turns>0){total+=s.dmg;s.turns--}});e.poisonStacks=e.poisonStacks.filter(s=>s.turns>0)}
if(total>0){e.hp=Math.max(0,e.hp-total);addLog('pl',`${e.name}持续伤害-${total}HP`);if(e.hp<=0){e.alive=false;processKill(e)}}
}
// Player status (from enemy effects)
function addFireToPlayer(dmgPerTick,turns){P.fireStacks.push({dmg:dmgPerTick,turns});addLog('en',`🔥 你被点燃了!(${dmgPerTick}/回合)`)}
function addPoisonToPlayer(dmgPerTick,turns){P.poisonStacks.push({dmg:dmgPerTick,turns});addLog('en',`☠️ 你中毒了!(${dmgPerTick}/回合)`)}
function addColdToPlayer(atkRed,turns){P.coldRedStacks.push({val:atkRed,turns});addLog('en',`❄️ 你被冻结了!(ATK-${atkRed})`)}
function getPlayerColdRed(){if(!P.coldRedStacks||!P.coldRedStacks.length)return 0;return Math.max(...P.coldRedStacks.map(s=>s.val))}
function tickPlayerStatus(){
let total=0;
P.fireStacks.forEach(s=>{if(s.turns>0){total+=s.dmg;s.turns--}});P.fireStacks=P.fireStacks.filter(s=>s.turns>0);
P.poisonStacks.forEach(s=>{if(s.turns>0){total+=s.dmg;s.turns--}});P.poisonStacks=P.poisonStacks.filter(s=>s.turns>0);
P.coldRedStacks.forEach(s=>{if(s.turns>0)s.turns--});P.coldRedStacks=P.coldRedStacks.filter(s=>s.turns>0);
if(total>0){P.hp=Math.max(0,P.hp-total);addLog('sys',`持续伤害-${total}HP`);if(P.hp<=0){gameOver=true;addLog('sys','你倒下了...');renderAll();setTimeout(()=>showEnd(false),600);return true}}
return false;
}
// ============ WEAPON EFFECT APPLICATION ============
function getPlayerCoeffs(){
let w=P.weapon;
if(P.cls==='undead'){
let lc=P.lanternLv;
let fc=Math.min(40,15+lc*0.5),pc=Math.min(10,3+lc*0.2),cc=Math.min(30,10+lc*0.4);
if(w&&w.effect==='fire')fc=Math.max(fc,FIRE_COEFF[w.quality]);
if(w&&w.effect==='poison')pc=Math.max(pc,POISON_COEFF[w.quality]);
if(w&&w.effect==='cold')cc=Math.max(cc,COLD_COEFF[w.quality]);
return{fire:fc,poison:pc,cold:cc,fireT:3,poisonT:3,coldT:3};
}
if(!w||w.effect==='none'||!['fire','poison','cold'].includes(w.effect))return null;
let q=w.quality;
if(w.effect==='fire')return{fire:FIRE_COEFF[q],poison:0,cold:0,fireT:FIRE_TURNS[q],poisonT:0,coldT:0};
if(w.effect==='poison')return{fire:0,poison:POISON_COEFF[q],cold:0,fireT:0,poisonT:POISON_TURNS[q],coldT:0};
if(w.effect==='cold')return{fire:0,poison:0,cold:COLD_COEFF[q],fireT:0,poisonT:0,coldT:COLD_TURNS[q]};
return null;
}
function applyPlayerEffectsToEnemy(e){
let c=getPlayerCoeffs();if(!c)return;
let atk=getEffAtk();
let isUndead=P.cls==='undead';
if(c.fire>0){let d=Math.floor(atk*c.fire/100);if(isUndead&&d<1)d=1;if(d>0)addFireToEnemy(e,d,c.fireT)}
if(c.poison>0){let missing=(e.maxHp-e.hp)/e.maxHp;let d=Math.floor(e.maxHp*missing*c.poison/100);if(isUndead&&d<1&&missing>0)d=1;if(d>0)addPoisonToEnemy(e,d,c.poisonT)}
if(c.cold>0){let d=Math.floor(atk*c.cold/100);if(isUndead&&d<1)d=1;if(d>0)addColdToEnemy(e,d,c.coldT)}
}
function applyEnemyEffectsToPlayer(e){
let eatk=e.atk-getEnemyColdRed(e);if(eatk<1)eatk=1;
// Enemy fire
if(e.eff.includes('fire')){
let fc=hasCal('enemy_rage')?15:10;
let d=Math.floor(eatk*fc/100);if(d>0)addFireToPlayer(d,2);
}
// Enemy poison
if(e.eff.includes('poison')){
let pc=hasCal('thorn_shield')?3:2;
let missing=(P.maxHp-P.hp)/P.maxHp;
let d=Math.floor(P.maxHp*missing*pc/100);if(d>0)addPoisonToPlayer(d,2);
}
// Enemy cold
if(e.eff.includes('cold')){
let cc=hasCal('frost_realm')?10:8;
let d=Math.floor(eatk*cc/100);if(d>0)addColdToPlayer(d,2);
}
}
// ============ CALAMITY ============
function addCalamity(){
let pool=CALAMITIES.filter(c=>!hasCal(c.id));if(!pool.length)return;
let cal=pool[rng(0,pool.length-1)];calamities.push(cal);
if(cal.id==='fragile'){P.maxHp=Math.max(1,Math.floor(P.maxHp*0.8));P.hp=Math.min(P.hp,P.maxHp)}
addLog('art',`⚠️ 灾厄降临:${cal.icon} ${cal.name} — ${cal.desc}`);
}
// ============ QUALITY ============
function rollQuality(floor){
let p=[0,0,0,0,0,0],f=floor/TOTAL;
p[1]=Math.max(5,50-floor*2);p[2]=25+Math.floor(floor*0.5);p[3]=10+Math.floor(floor*0.8);
p[4]=5+Math.floor(floor*0.5);p[5]=Math.max(0,2+Math.floor((floor-15)*0.4));p[6]=0;
let total=p.reduce((a,b)=>a+b,0),r=Math.random()*total,acc=0;
for(let i=1;i<=6;i++){acc+=p[i];if(r<acc)return i}return 1;
}
// ============ EQUIPMENT GEN ============
function genWeapon(floor,qb=0){
let q=Math.min(6,rollQuality(floor)+qb);
let atk=[0,0,3,6,10,15,22][q]+rng(0,Math.floor(q*0.8));
let mpCost=Math.max(2,5-Math.floor(q/2)),mult=Math.round((1.5+q*0.25)*10)/10;
let eff='none';if(q>=2){let pool=WPN_EFF.slice(1);eff=pool[rng(0,pool.length-1)]}
let np=WPN_NAMES[Math.min(6,q)]||WPN_NAMES[1];let name=np[rng(0,np.length-1)];
return{name,quality:q,atkBonus:atk,mpCost:Math.round(mpCost),mult,effect:eff};
}
function genAccessory(floor,qb=0){
let q=Math.min(6,rollQuality(floor)+qb),pool=ACC_POOL.slice();
let def=pool[rng(0,pool.length-1)],value=Math.round(def.base*(0.5+q*0.4)*10)/10;
let desc=def.desc.replace('{v}',value);
if(def.id==='exp_badge'){let ei=[0,5,4,3,2,1,1],eb=[0,1,1,1,1,1,2];value=q;desc=`每${ei[q]}杀+${eb[q]}级`}
return{id:def.id,name:def.name,quality:q,desc,value};
}
function genCursedAccessory(floor){
let q=Math.min(6,rollQuality(floor)+2),pool=ACC_POOL.slice();
let def=pool[rng(0,pool.length-1)],value=Math.round(def.base*(0.5+q*0.4)*10)/10;
return{id:def.id,name:'诅咒·'+def.name,quality:q,desc:def.desc.replace('{v}',value)+' (诅咒)',value,cursed:true};
}
// ============ ENEMY GEN ============
function genEnemy(floor,type){
let hp=10+floor*3,atk=2+Math.floor(floor*0.8),def=1+Math.floor(floor/3),mp=Math.min(8,5+Math.floor(floor/10));
if(floor>TOTAL){let ed=floor-TOTAL;let sc=1+ed*0.25;hp=Math.floor(hp*sc);atk=Math.floor(atk*sc);def=Math.floor(def*sc)}
let mult=1,enm=mp,name='哥布林',icon='👹',eff=[];
if(type==='elite'){
mult=1.6;let en=['精英骑士','暗黑卫兵','魔化将军','血煞统领','影刃队长','诅咒斗士','深渊先锋'];
let ei=['⚔️','🛡️','💀','👺','🗡️','🔱','⚔️'],idx=rng(0,en.length-1);name=en[idx];icon=ei[idx];
enm=7;eff.push(ENEMY_EFF[rng(0,ENEMY_EFF.length-1)]);
}else if(type==='boss'){
if(floor<=30){mult=3.0;enm=12;
if(floor===10){name='深渊领主';icon='👿';eff=['fire']}
else if(floor===20){name='暗黑龙';icon='🐉';eff=['fire','poison']}
else{name='深渊霸主';icon='💀';eff=['fire','poison','cold','lifesteal']}
}else{mult=3.0+Math.floor((floor-TOTAL)/20)*0.5;enm=12;name='深渊霸主';icon='💀';eff=['fire','poison','cold','lifesteal']}
}else{
let tier=Math.min(9,Math.floor((floor-1)/3));
let np=[['史莱姆','绿泥怪','果冻怪'],['骷髅','骷髅弓手','不死士兵'],['兽人','半兽人','兽人战士'],['暗精灵','暗精灵刺客','影行者'],['魔像','石魔像','铁魔像'],['恶灵','怨灵','幽灵骑士'],['魔蛛','巨型毒蛛','暗影蜘蛛'],['石像鬼','翼魔','石翼兽'],['暗影刺客','幻影杀手','夜行者'],['地精','地精萨满','疯狂地精']];
let ip=['👹','💀','👺','🧝','🗿','👻','🕷️','🦇','🥷','👾'];
name=np[tier][rng(0,2)];icon=ip[tier];
if(Math.random()<0.15+floor*0.012||(floor>TOTAL&&Math.random()<0.5))eff.push(ENEMY_EFF[rng(0,ENEMY_EFF.length-1)]);
}
// Calamity: frost realm adds cold to all enemies
if(hasCal('frost_realm')&&!eff.includes('cold'))eff.push('cold');
let ehp=Math.floor(hp*mult),ea=Math.floor(atk*mult),ed=Math.floor(def*mult);
if(hasCal('enemy_rage')&&type!=='boss')ea=Math.floor(ea*1.25);
return{name,icon,hp:ehp,maxHp:ehp,atk:ea,def:ed,mp:enm,maxMp:enm,type,eff,alive:true,fireStacks:[],poisonStacks:[],coldStacks:[]};
}
function spawnEnemies(floor){
enemies=[];
if(!endlessMode&&(ELITE.includes(floor)||BOSS.includes(floor))){
if(ELITE.includes(floor)){enemies.push(genEnemy(floor,'elite'));enemies.push(genEnemy(floor,'elite'))}
else enemies.push(genEnemy(floor,'boss'));
}else if(endlessMode){
let ed=floor-TOTAL;
if(floor%20===0)enemies.push(genEnemy(floor,'boss'));
else{let cnt=Math.min(3,1+Math.floor(ed/5));for(let i=0;i<cnt;i++){let t=Math.random()<0.15+ed*0.02?'elite':'normal';enemies.push(genEnemy(floor,t))}}
}else{
let cnt=floor>=16?2:(floor>=8?rng(1,2):1);for(let i=0;i<cnt;i++)enemies.push(genEnemy(floor,'normal'));
}
tgtIdx=enemies.findIndex(e=>e.alive);
}
// ============ RENDER ============
function renderAll(){renderTop();renderPlayer();renderEnemies();renderLog();renderActs()}
function renderTop(){$('dFloor').textContent=endlessMode?`无尽 第${P.floor}层`:`第 ${P.floor} / ${TOTAL} 层`;$('dLvl').textContent=`Lv.${P.level}`;$('dGold').textContent=`💰 ${P.gold}`}
function renderPlayer(){
$('pHp').style.width=(P.hp/P.maxHp*100)+'%';$('pHpTx').textContent=P.hp+'/'+P.maxHp;
$('pMp').style.width=(P.mp/P.maxMp*100)+'%';$('pMpTx').textContent=P.mp+'/'+P.maxMp;
// Faith bar for angel
let barsHtml='';
if(P.cls==='angel'){
$('barsArea').innerHTML=`<div class="bar-r"><span class="bar-l hp">HP</span><div class="bar-t"><div class="bar-f hp" id="pHp" style="width:${P.hp/P.maxHp*100}%"></div><span class="bar-tx" id="pHpTx">${P.hp}/${P.maxHp}</span></div></div><div class="bar-r"><span class="bar-l mp">MP</span><div class="bar-t"><div class="bar-f mp" id="pMp" style="width:${P.mp/P.maxMp*100}%"></div><span class="bar-tx" id="pMpTx">${P.mp}/${P.maxMp}</span></div></div><div class="bar-r"><span class="bar-l fa">信</span><div class="bar-t"><div class="bar-f fa" style="width:${P.faith/P.maxFaith*100}%"></div><span class="bar-tx">${P.faith}/${P.maxFaith}</span></div></div>`;
}else{$('barsArea').innerHTML=`<div class="bar-r"><span class="bar-l hp">HP</span><div class="bar-t"><div class="bar-f hp" id="pHp" style="width:${P.hp/P.maxHp*100}%"></div><span class="bar-tx" id="pHpTx">${P.hp}/${P.maxHp}</span></div></div><div class="bar-r"><span class="bar-l mp">MP</span><div class="bar-t"><div class="bar-f mp" id="pMp" style="width:${P.mp/P.maxMp*100}%"></div><span class="bar-tx" id="pMpTx">${P.mp}/${P.maxMp}</span></div></div>`}
$('pAtk').textContent=getEffAtk();$('pDef').textContent=getEffDef();$('pSub').textContent=`Lv.${P.level} ${CLASSES.find(c=>c.id===P.cls)?.name||'冒险者'}`;
if(P.cls==='angel'){$('pAv').textContent=P.faith<50?'😭':'😇'}
$('bHvyC').textContent=getEffMpCost()+' MP';
let eq='';
if(P.weapon){let we=P.weapon.effect;eq+=`<span class="wpn-badge ${QCOLORS[P.weapon.quality]}">${P.weapon.name} +${P.weapon.atkBonus}ATK<div class="wpn-tip"><span class="${QCOLORS[P.weapon.quality]}">[${QNAMES[P.weapon.quality]}]</span> ATK+${P.weapon.atkBonus}<br>耗蓝${P.weapon.mpCost} 倍率${P.weapon.mult}x<br>特效: ${WPN_EFF_NAME[we]}</div></span>`}
P.accessories.forEach(a=>{
let dep=false;
let st=dep?'style="opacity:0.4;filter:grayscale(1)"':'';
let ns=dep?`${a.name}<span style="color:#666;font-size:0.7rem">(已耗尽)</span>`:a.name;
eq+=`<span class="acc-badge ${QCOLORS[a.quality]}" ${st}>${a.quality>=5?'✨':''}${ns}<div class="acc-tip"><span class="${QCOLORS[a.quality]}">[${QNAMES[a.quality]}]</span> ${a.name}<br>${a.desc}${dep?'<br><em style="color:#888">已耗尽</em>':''}</div></span>`;
});
$('equipRow').innerHTML=eq;
let cr='';calamities.forEach(c=>{cr+=`<span class="cal-icon">${c.icon}<div class="cal-tip"><strong>${c.name}</strong><br>${c.desc}</div></span>`});$('calRow').innerHTML=cr;
let db='';
let pFire=P.fireStacks.reduce((a,s)=>a+(s.turns>0?s.dmg:0),0);if(pFire>0)db+=`<span class="debuff">🔥灼伤${pFire}/回合</span>`;
let pPoi=P.poisonStacks.reduce((a,s)=>a+(s.turns>0?s.dmg:0),0);if(pPoi>0)db+=`<span class="debuff">☠️中毒${pPoi}/回合</span>`;
let pCold=getPlayerColdRed();if(pCold>0)db+=`<span class="debuff" style="color:#74b9ff">❄️寒冷ATK-${pCold}</span>`;
if(P.cursedTurns>0)db+=`<span class="debuff">💀诅咒${P.cursedTurns}战</span>`;
if(P.chargeBonus>0)db+=`<span class="debuff" style="color:#7ec8f8">⚡蓄力+${P.chargeBonus}%</span>`;
if(P.cls==='angel'&&P.faith<50)db+=`<span class="debuff" style="background:rgba(100,100,255,0.2);border-color:rgba(100,100,255,0.3)">😭堕落<div class="debuff-tip">信仰<50 进入堕落状态<br>ATK -90%<br>普攻仍可获得信仰之力+50%伤害</div></span>`;
$('debuffRow').innerHTML=db;
// Class mechanic row
let mr='';
if(P.cls==='beast'&&P.pet){mr+=`<span class="spec-chip">🐾 宠物 Lv.${P.pet.level} ATK ${P.pet.atk}`;if(P.pet.skills.length)mr+=` | ${P.pet.skills.map(s=>s.name).join(' ')}`;mr+=`</span>`;let fc=P.pet.nextCost;mr+=`<button class="feed-btn${P.gold<fc?' disabled':''}" onclick="feedPetDirect()" ${P.gold<fc?'disabled':''}>🐾 喂食 💰${fc}</button>`}
if(P.cls==='undead')mr+=`<span class="spec-chip">🕯️ 提灯 Lv.${P.lanternLv}</span>`;
if(P.cls==='angel')mr+=`<span class="spec-chip">🕊️ 信仰 ${P.faith}/${P.maxFaith}${P.faith<50?' (堕落)':''}</span>`;
if(mr){$('classMechRow').style.display='flex';$('classMechRow').innerHTML=mr}else{$('classMechRow').style.display='none'}
}
function renderEnemies(){
if(!enemies.length){$('enemyArea').style.display='none';return}
$('enemyArea').style.display='flex';
$('enemyArea').innerHTML=enemies.map((e,i)=>{
let cls='ecard'+(i===tgtIdx?' tgt':'')+(e.alive?'':' dead');
let tt=e.type==='boss'?'BOSS':e.type==='elite'?'精英':'';
let fx=e.eff.map(f=>ENEMY_EFF_NAME[f]||'').join(' ');
// Status display
let stx='';
if(e.fireStacks&&e.fireStacks.length){let fd=e.fireStacks.reduce((a,s)=>a+s.dmg,0);stx+=`🔥${fd} `}
if(e.poisonStacks&&e.poisonStacks.length){let pd=e.poisonStacks.reduce((a,s)=>a+s.dmg,0);stx+=`☠${pd} `}
let cr=getEnemyColdRed(e);if(cr>0)stx+=`❄ATK-${cr} `;
let statLine=hasCal('blind')?'<div class="efx">???</div>':`<div style="font-size:0.7rem;color:#8892b0">ATK ${e.atk} DEF ${e.def}</div>`;
return`<div class="${cls}" onclick="setTgt(${i})"><div class="eh"><span class="ei">${e.icon}</span><span class="en">${e.name}</span>${tt?`<span class="et">${tt}</span>`:''}</div><div class="bars"><div class="bar-r"><span class="bar-l hp">HP</span><div class="bar-t"><div class="bar-f hp" style="width:${e.alive?e.hp/e.maxHp*100:0}%"></div><span class="bar-tx">${e.alive?e.hp+'/'+e.maxHp:'已阵亡'}</span></div></div></div>${statLine}${fx?`<div class="efx">${fx}</div>`:''}${stx?`<div class="efx" style="color:#74b9ff">${stx}</div>`:''}</div>`;
}).join('');
}
function renderLog(){$('logBox').innerHTML=logs.map(l=>`<div class="le ${l.t}">${l.m}</div>`).join('');$('logBox').scrollTop=$('logBox').scrollHeight}
function renderActs(){let s=!gameOver&&enemies.some(e=>e.alive);$('actBar').style.display=s?'flex':'none';if(!s)return;$('bAtk').disabled=busy;$('bHvy').disabled=busy||P.mp<getEffMpCost();$('bDef').disabled=busy;$('bMp').disabled=busy}
function setTgt(i){if(enemies[i].alive){tgtIdx=i;renderEnemies()}}
// ============ BRANCH SYSTEM ============
let enemyWeightBoost=false;
function showBranch(){
if(gameOver){showEnd(P.floor>TOTAL);return}
if(!endlessMode&&P.floor>TOTAL){showEndlessChoice();return}
if(!endlessMode&&(ELITE.includes(P.floor)||BOSS.includes(P.floor))){
spawnEnemies(P.floor);addLog('sys',`⚠️ 第${P.floor}层:${BOSS.includes(P.floor)?'BOSS':'精英'}战!`);
renderAll();$('branchOv').classList.remove('show');return;
}
if(endlessMode&&P.floor%10===0&&P.floor>TOTAL&&!BOSS.includes(P.floor)){
spawnEnemies(P.floor);addLog('sys',`⚠️ 无尽第${P.floor}层:超级Boss战!`);renderAll();$('branchOv').classList.remove('show');return;
}
let w=Object.assign({},BRANCH_W);if(enemyWeightBoost)w.enemy+=20;
let total=Object.values(w).reduce((a,b)=>a+b,0),cards=[],used=[];
while(cards.length<3){let r=Math.random()*total,acc=0;
for(let k of Object.keys(w)){acc+=w[k];if(r<acc&&!used.includes(k)){used.push(k);cards.push(k);break}}}
let fog=hasCal('fog');
let info={enemy:{icon:'⚔️',name:'敌人',desc:'战斗获取经验和金币'},heal:{icon:'❤️',name:'医疗',desc:P.cls==='angel'?'恢复生命值+50信仰':'恢复生命值'},treasure:{icon:'🎁',name:'宝藏',desc:'获取武器或金币'},shop:{icon:'🏪',name:'商店',desc:'购买装备和补给'},mystery:{icon:'🔮',name:'神秘房间',desc:'未知的机遇与风险'}};
let hintHtml='';if(hasAcc('map_frag')&&!fog){/* map_frag removed, no preview */}
let h='';cards.forEach(c=>{let inf=info[c];
h+=`<div class="bcard" onclick="chooseBranch('${c}')"><div class="bi">${fog?'❓':inf.icon}</div><div class="bn">${fog?'???':inf.name}</div><div class="bd">${fog?'???':inf.desc}</div></div>`;
});
$('branchGrid').innerHTML=h;$('branchOv').classList.add('show');enemyWeightBoost=false;renderAll();
}
function chooseBranch(type){$('branchOv').classList.remove('show');
if(type==='enemy'){startCombat();enemyWeightBoost=false}
else if(type==='heal')doHeal();
else if(type==='treasure')doTreasure();
else if(type==='shop')doShop();
else if(type==='mystery')doMystery();
}
// ============ COMBAT ============
function startCombat(){
spawnEnemies(P.floor);P.lifePactUsed=false;
P.fireStacks=[];P.poisonStacks=[];P.coldRedStacks=[];
addLog('sys',`— 第${P.floor}层 — 战斗开始!`);renderAll();
}
function doAction(act){
if(busy||gameOver)return;
if(!enemies[tgtIdx].alive){let a=enemies.findIndex(e=>e.alive);if(a===-1)return;tgtIdx=a}
busy=true;renderActs();
if(hasCal('life_decay')){P.hp=Math.max(1,P.hp-2);addLog('sys','☠️ 生命凋零:-2HP')}
if(hasCal('frost_realm')){P.hp=Math.max(1,P.hp-2);addLog('sys','❄️ 极寒冻伤:-2HP')}
let rg=getRegen();if(rg>0){P.hp=Math.min(P.maxHp,P.hp+rg);addLog('sys',`复苏之风恢复${rg}HP`)}
let med=accVal('meditation');let medGain=Math.floor(med);if(hasCal('mp_block'))medGain=Math.max(0,medGain-2);
if(medGain>0){P.mp=Math.min(P.maxMp,P.mp+medGain);addLog('sys',`冥想恢复${medGain}MP`)}
if(hasAcc('energy_conv')&&P.hp>3){P.hp-=3;P.mp=Math.min(P.maxMp,P.mp+3);addLog('sys','能量转换: -3HP +3MP')}
if(hasAcc('life_pact')&&!P.lifePactUsed&&P.hp<P.maxHp*0.3){P.hp=Math.min(P.maxHp,P.hp+10);P.lifePactUsed=true;addLog('art','生命契约触发!+10HP')}
if(P.cls==='angel'&&act==='atk'){P.faith=Math.min(P.maxFaith,P.faith+5);addLog('pl','🕊️ +5信仰')}
if(act==='atk')doPlayerAttack(false);
else if(act==='hvy'){if(P.cls==='angel'&&P.faith>=10){P.faith-=10;addLog('pl','🕊️ 消耗10信仰')}else if(P.cls==='angel'){addLog('pl','🕊️ 信仰不足')}P.mp-=getEffMpCost();doPlayerAttack(true)}
else if(act==='def'){if(P.cls==='angel'){P.faith=Math.min(P.maxFaith,P.faith+5);addLog('pl','🕊️ +5信仰')}addLog('pl',`选择防御,减伤${Math.floor(getDefRate()*100)}%`);P.defending=true;setTimeout(()=>petThenEnemy(),300)}
else if(act==='mp'){let mpGain=5;if(hasCal('mp_block'))mpGain=Math.max(0,mpGain-2);P.mp=Math.min(P.maxMp,P.mp+mpGain);if(P.cls==='angel'){P.faith=Math.min(P.maxFaith,P.faith+20);addLog('pl','🕊️ +20信仰')}let echoHp=accVal('echo_bell');if(echoHp>0){P.hp=Math.min(P.maxHp,P.hp+Math.floor(echoHp));addLog('pl',`回蓝+${mpGain}MP,回响恢复${Math.floor(echoHp)}HP`)}else addLog('pl',`回蓝 +${mpGain} MP`);renderAll();setTimeout(()=>petThenEnemy(),300)}
}
function doPlayerAttack(heavy){
let e=enemies[tgtIdx];if(!e.alive){busy=false;renderActs();return}
let atk=getEffAtk(),def=e.def;
if(hasAcc('weakness'))def=Math.floor(def*(1-accVal('weakness')/100));
let sharp=getSharp();def=Math.max(0,def-sharp);
let dmg,bonus='';
let faithBonus=false;
if(P.cls==='angel'&&!heavy&&P.faith>=10){faithBonus=true;addLog('pl','🕊️ 信仰之力+50%')}
if(P.cls==='angel'&&heavy)faithBonus=true;
if(heavy){let m=getEffMult();dmg=Math.max(1,Math.floor(atk*m)-def);bonus='(强攻)'}
else{dmg=Math.max(1,atk-def)}
if(faithBonus){dmg=Math.floor(dmg*1.5);bonus+='(信仰+50%)'}
if(P.chargeBonus>0){dmg=Math.floor(dmg*(1+P.chargeBonus/100));bonus+=`(蓄力+${P.chargeBonus}%)`;P.chargeBonus=0}
if(hasCal('thorn_shield')&&Math.random()<0.2){dmg=Math.floor(dmg*0.5);addLog('en','🛡️ 荆棘之盾:减伤50%!')}
let rage=getRageBonus();if(rage>0){dmg=Math.floor(dmg*(1+rage/100));bonus+=`(怒血+${rage}%)`}
dmg=Math.floor(dmg*getExecBonus(e));
let crit=getCrit(),isCrit=Math.random()<crit.rate;if(isCrit){dmg=Math.floor(dmg*crit.dmg);bonus+=' 暴击!'}
let prevHp=e.hp;e.hp=Math.max(0,e.hp-dmg);addLog('pl',`${heavy?'强力攻击':'普通攻击'}→${e.name} ${dmg}伤害${bonus}`);
if(!heavy&&hasAcc('fang')){let h=Math.floor(accVal('fang'));P.hp=Math.min(P.maxHp,P.hp+h)}
if(P.cls==='werewolf'){let h=Math.max(1,Math.floor(dmg*0.15));P.hp=Math.min(P.maxHp,P.hp+h)}
// Apply weapon effects (fire/poison/cold) using coefficient tables
applyPlayerEffectsToEnemy(e);
if(heavy){let we=P.weapon.effect;
if(we==='lifesteal'){let h=Math.floor(dmg*0.15);P.hp=Math.min(P.maxHp,P.hp+h);addLog('pl',`🩸 吸血${h}HP`)}
if(we==='blood_sac'){let cost=Math.floor(P.maxHp*0.05);P.hp-=cost;let extra=Math.floor(dmg*0.5);e.hp=Math.max(0,e.hp-extra);addLog('pl',`🩸 血祭-${cost}HP 额外${extra}伤害`)}
if(we==='iron_wall'){P.defending=true;addLog('pl','🛡️ 铁壁防御')}
if(we==='charge'){P.chargeBonus+=20;addLog('pl',`⚡ 蓄力+20%`)}
}
if(hasAcc('thunder')&&Math.random()<accVal('thunder')/100){let tn=rng(5,10);let other=enemies.find(x=>x.alive&&x!==e);if(other){other.hp=Math.max(0,other.hp-tn);addLog('art',`⚡ 闪电链→${other.name} ${tn}伤害`);if(other.hp<=0){other.alive=false;processKill(other)}}}
renderAll();
if(e.hp<=0){e.alive=false;let overflow=Math.max(0,dmg-prevHp);processKill(e);
let ricPct=accVal('ricochet');let ricDmg=Math.floor(overflow*ricPct/100);
if(hasAcc('ricochet')&&ricDmg>0){let nxt=enemies.find(x=>x.alive);if(nxt){nxt.hp=Math.max(0,nxt.hp-ricDmg);addLog('art',`弹射之刃→${nxt.name} ${ricDmg}(${ricPct}%)`);if(nxt.hp<=0){nxt.alive=false;processKill(nxt)}}}}
if(!enemies.some(x=>x.alive)){busy=false;combatWin();return}
setTimeout(()=>petThenEnemy(),350);
}
// ============ PET PHASE ============
function petThenEnemy(){
if(gameOver)return;
if(P.cls==='beast'&&P.pet){
let tgt=enemies.find(x=>x.alive);if(tgt){
let pd=P.pet.atk,isCrit=P.pet.skills.some(s=>s.id==='crit_up')&&Math.random()<0.2;
if(isCrit){pd=Math.floor(pd*1.8);addLog('art','🐾 宠物暴击!')}
tgt.hp=Math.max(0,tgt.hp-pd);addLog('art',`🐾 宠物→${tgt.name} ${pd}伤害`);
if(P.pet.skills.some(s=>s.id==='fire_claw')){let fd=Math.floor(P.pet.atk*0.15);if(fd>0)addFireToEnemy(tgt,fd,2)}
if(P.pet.skills.some(s=>s.id==='poison_fang')){let m=(tgt.maxHp-tgt.hp)/tgt.maxHp;let d=Math.floor(tgt.maxHp*m*0.04);if(d>0)addPoisonToEnemy(tgt,d,2)}
if(P.pet.skills.some(s=>s.id==='frost')){let cd=Math.floor(P.pet.atk*0.12);if(cd>0)addColdToEnemy(tgt,cd,2)}
if(P.pet.skills.some(s=>s.id==='vampire')){let h=Math.floor(pd*0.2);P.hp=Math.min(P.maxHp,P.hp+h)}
if(P.pet.skills.some(s=>s.id==='gale'))P.galeBonus=1;
if(P.pet.skills.some(s=>s.id==='double_hit')&&Math.random()<0.15&&tgt.alive){tgt.hp=Math.max(0,tgt.hp-P.pet.atk);addLog('art',`🐾 连击!→${tgt.name} ${P.pet.atk}`)}
if(tgt.hp<=0){tgt.alive=false;processKill(tgt)}
if(!enemies.some(x=>x.alive)){busy=false;combatWin();return}
}
}
renderAll();setTimeout(()=>enemyPhase(),200);
}
function processKill(e){
let gold=rng(10,20);if(e.type==='elite')gold=rng(30,50);if(e.type==='boss')gold=rng(80,120);
if(hasAcc('greedy_pot')&&Math.random()<accVal('greedy_pot')/100){gold*=2;addLog('art','💰 贪婪之壶触发!双倍金币!')}
gold=Math.floor(gold*getGreedMult());gold+=Math.floor(accVal('lucky_coin')||0);
P.gold+=gold;stats.goldTotal+=gold;
// Undead hunter lantern level up
if(P.cls==='undead'){let lu=e.type==='boss'?3:e.type==='elite'?2:1;P.lanternLv+=lu;addLog('art',`🕯️ 提灯+${lu} → Lv.${P.lanternLv}`)}
let lvls=1;
if(e.type==='elite')lvls=2;
else if(e.type==='boss'){lvls=3}
let extraLv=0;
if(hasAcc('exp_badge')){
let expQ=Math.max(...P.accessories.filter(a=>a.id==='exp_badge').map(a=>a.quality));
let expIntervals=[0,5,4,3,2,1,1],expBonuses=[0,1,1,1,1,1,2];
let expInt=expIntervals[expQ],expBon=expBonuses[expQ];
P.expBadgeTriggers++;
if(P.expBadgeTriggers>=expInt){extraLv=expBon;P.expBadgeTriggers=0;addLog('art',`【经验徽章】额外+${expBon}级 (每${expInt}杀)`)}else{addLog('art',`【经验徽章】${P.expBadgeTriggers}/${expInt}`)}
}
lvls+=extraLv;
let hAtkTotal=0,hHpTotal=0;
for(let i=0;i<lvls;i++){P.level++;P.heritageTriggers++;
let da=P.growA||2,dd=P.growD||2,dh=P.growH||8;
if(e.type==='elite'){da=Math.floor(da*1.5);dd=Math.floor(dd*1.5);dh=Math.floor(dh*1.5)}
if(e.type==='boss'){da=Math.floor(da*2);dd=Math.floor(dd*2);dh=Math.floor(dh*2)}
P.atk+=da;
if(P.cls==='werewolf'){P.maxHp+=dd}else{P.def+=dd}
P.maxHp+=dh;P.hp=Math.min(P.maxHp,P.hp+dh);
if(hasAcc('heritage')){
let hb=getHeritageBonus(P.heritageTriggers);
P.atk+=hb.atk;P.maxHp+=hb.hp;P.hp=Math.min(P.maxHp,P.hp+hb.hp);
hAtkTotal+=hb.atk;hHpTotal+=hb.hp;
}
}
if(hAtkTotal>0||hHpTotal>0)addLog('art',`【传承护符】额外ATK+${hAtkTotal} HP+${hHpTotal}`);
if(e.type==='normal')stats.kills++;else if(e.type==='elite')stats.elites++;else stats.bosses++;
addLog('sys',`💀 ${e.name} 击败!+${gold}💰 +${lvls}级`);
let bb=getBloodBlade();if(bb>0){let h=Math.max(1,Math.floor(P.maxHp*bb/100));P.hp=Math.min(P.maxHp,P.hp+h);addLog('art',`血刃恢复${h}HP`)}
renderAll();
}
function enemyPhase(){
if(gameOver)return;
for(let e of enemies){if(!e.alive)continue;
// Tick enemy status effects
tickEnemyStatus(e);if(!e.alive){renderAll();continue}
// Enemy attacks
let eatk=e.atk-getEnemyColdRed(e);if(eatk<1)eatk=1;
if(e.eff.includes('berserk')&&e.hp<e.maxHp*0.5)eatk=Math.floor(eatk*1.3);
let eDmg=Math.max(1,eatk-getEffDef());
if(P.defending){eDmg=Math.floor(eDmg*(1-getDefRate()));P.defending=false}
if(P.cls==='beast'&&P.pet&&P.pet.skills.some(s=>s.id==='guard')){eDmg=Math.max(0,eDmg-3);addLog('pl','🐾 宠物抵挡3伤害')}
let dr=getDragonReduce();if(dr>0){eDmg=Math.max(0,eDmg-dr);addLog('pl',`龙鳞甲减伤${dr}`)}
P.hp-=eDmg;addLog('en',`${e.name}攻击你 ${eDmg}伤害`);
// Apply enemy effects to player
applyEnemyEffectsToPlayer(e);
if(P.cls==='shield'){let sdef=getEffDef();let ref=Math.max(1,Math.floor(sdef*0.5));e.hp=Math.max(0,e.hp-ref);addLog('pl',`🛡️ 反弹${ref}伤害`);if(e.hp<=0){e.alive=false;processKill(e)}}
if(e.eff.includes('lifesteal')){let h=Math.floor(eDmg*0.2);e.hp=Math.min(e.maxHp,e.hp+h)}
if(hasCal('breath')){e.hp=Math.min(e.maxHp,e.hp+3)}
let th=getThorn();if(th>0){e.hp=Math.max(0,e.hp-th);addLog('pl',`荆棘反弹${th}伤害`);if(e.hp<=0){e.alive=false;processKill(e)}}
if(P.hp<=0){gameOver=true;addLog('sys','你倒下了...');renderAll();setTimeout(()=>showEnd(false),600);return}
renderAll();
}
if(!enemies.some(x=>x.alive)){busy=false;combatWin();return}
// Tick player status effects
if(tickPlayerStatus())return;
busy=false;renderAll();
}
function combatWin(){
addLog('sys','🎉 战斗胜利!');
// Warrior mark: diminishing ATK per enemy victory
if(hasAcc('warrior_mark')){
P.warriorMarks++;
let bonus=getWarriorMarkBonus(P.warriorMarks);
if(bonus>0){P.atk+=bonus;addLog('art',`【勇者之印】第${P.warriorMarks}次触发,ATK+${bonus}`)}
else{addLog('art',`【勇者之印】第${P.warriorMarks}次触发,ATK+0`)}
}
// Check if boss floor → 6-pick-2 accessory drop
if(!endlessMode&&BOSS.includes(P.floor)){
window._bossPickPool=[];for(let i=0;i<6;i++)window._bossPickPool.push(genAccessory(P.floor,2));
window._bossPicks=0;showBossPick();return;
}
// Endless super boss
if(endlessMode&&P.floor%20===0&&P.floor>TOTAL){
window._bossPickPool=[];for(let i=0;i<6;i++)window._bossPickPool.push(genAccessory(P.floor,2));
window._bossPicks=0;showBossPick();return;
}
renderAll();setTimeout(()=>nextFloor(),400);
}
// ============ BOSS DROP (6 pick 2) ============
function showBossPick(){
if(window._bossPicks>=2){renderAll();setTimeout(()=>nextFloor(),400);return}
let pool=window._bossPickPool;
let html=PAUSE_BTN_FN('accDropOv')+`<h2>🏆 Boss掉落饰品</h2><p>从${pool.length}件饰品中选择第${window._bossPicks+1}件(共2件)</p>`;
pool.forEach((a,i)=>{
html+=`<div class="shop-item" onclick="pickBossAcc(${i})"><div class="si-name"><span class="${QCOLORS[a.quality]}">[${QNAMES[a.quality]}]</span> <strong>${a.name}</strong><br><span style="color:#8892b0;font-size:0.78rem">${a.desc}</span></div></div>`;
});
$('accDropMdl').innerHTML=html;$('accDropOv').classList.add('show');
}
function pickBossAcc(idx){
let pool=window._bossPickPool;let acc=pool[idx];
P.accessories.push(acc);stats.accessories.push(acc.name);
addLog('art',`获得饰品:${acc.name}`);
onCollectorTrigger();
pool.splice(idx,1);window._bossPicks++;
$('accDropOv').classList.remove('show');
if(window._bossPicks<2&&pool.length>0)setTimeout(()=>showBossPick(),200);
else{renderAll();setTimeout(()=>nextFloor(),400)}
}
// ============ MIMIC DROP (3 pick 1) ============
function giveMimicAcc(){
let pool=[];for(let i=0;i<3;i++)pool.push(genAccessory(P.floor,1));
let html=PAUSE_BTN_FN('accDropOv')+`<h2>🎁 宝箱怪掉落</h2><p>从3件饰品中选择1件</p>`;
pool.forEach((a,i)=>{
html+=`<div class="shop-item" onclick="pickMimicAcc(${i})"><div class="si-name"><span class="${QCOLORS[a.quality]}">[${QNAMES[a.quality]}]</span> <strong>${a.name}</strong><br><span style="color:#8892b0;font-size:0.78rem">${a.desc}</span></div></div>`;
});
window._mimicPool=pool;
$('accDropMdl').innerHTML=html;$('accDropOv').classList.add('show');
}
function pickMimicAcc(idx){
let acc=window._mimicPool[idx];
P.accessories.push(acc);stats.accessories.push(acc.name);
addLog('art',`🎁 宝箱怪掉落饰品:${acc.name} (${QNAMES[acc.quality]})`);
onCollectorTrigger();
$('accDropOv').classList.remove('show');
renderAll();setTimeout(()=>nextFloor(),400);
}
// ============ HEAL ============
function doHeal(){
P.mp=P.maxMp;let pct=hasCal('hunger')?rng(15,25):rng(30,50);
let heal=Math.floor(P.maxHp*pct/100);P.hp=Math.min(P.maxHp,P.hp+heal);
if(P.cls==='angel'){P.faith=Math.min(P.maxFaith,P.faith+50);addLog('art','🕊️ 医疗额外+50信仰')}
addLog('sys',`❤️ 找到医疗点,恢复${heal}HP (${pct}%)`);renderAll();setTimeout(()=>nextFloor(),400);
}
// ============ TREASURE (3 pick 1) ============
function doTreasure(){
P.mp=P.maxMp;
if(Math.random()<getMimicChance()){
let mimic=genEnemy(P.floor,'normal');mimic.name='宝箱怪';mimic.icon='📦';
if(hasCal('chest_trap')){mimic.hp=Math.floor(mimic.hp*1.5);mimic.maxHp=mimic.hp;mimic.atk=Math.floor(mimic.atk*1.5)}
enemies=[mimic];tgtIdx=0;addLog('sys','🎁 宝藏!...等等,这是宝箱怪!');renderAll();
window._mimicFight=true;return;
}
// Accessory 3-pick-1
let pool=[];for(let i=0;i<3;i++)pool.push(genAccessory(P.floor));
let html=PAUSE_BTN+`<h2>🎁 宝藏饰品</h2><p>从3件饰品中选择1件</p>`;
pool.forEach((a,i)=>{
html+=`<div class="shop-item" onclick="pickTreasureAcc(${i})"><div class="si-name"><span class="${QCOLORS[a.quality]}">[${QNAMES[a.quality]}]</span> <strong>${a.name}</strong><br><span style="color:#8892b0;font-size:0.78rem">${a.desc}</span></div></div>`;
});
window._treasPool=pool;
$('eventMdl').innerHTML=html;$('eventOv').classList.add('show');
}
function pickTreasureAcc(idx){
let acc=window._treasPool[idx];
P.accessories.push(acc);stats.accessories.push(acc.name);
addLog('art',`🎁 获得饰品:${acc.name} (${QNAMES[acc.quality]})`);
onCollectorTrigger();
$('eventOv').classList.remove('show');
renderAll();setTimeout(()=>nextFloor(),300);
}
// ============ WEAPON COMPARE ============
function showWeaponCompare(w){
let cur=P.weapon;
let html=PAUSE_BTN_FN('wpnOv')+`<h2>⚔️ 发现武器</h2><p>比较后决定是否替换</p><div class="compare">`;
html+=`<div class="cmp-box"><div class="cb-n ${QCOLORS[cur.quality]}">${cur.name}</div><div class="cb-s">ATK+${cur.atkBonus} 耗蓝${cur.mpCost} ${WPN_EFF_NAME[cur.effect]}</div></div>`;
html+=`<div class="cmp-box"><div class="cb-n ${QCOLORS[w.quality]}">${w.name}</div><div class="cb-s">ATK+${w.atkBonus} 耗蓝${w.mpCost} ${WPN_EFF_NAME[w.effect]}</div></div></div>`;
html+=`<div class="cbtn-row"><button class="cbtn yes" onclick="equipWeapon()">装备新武器</button><button class="cbtn no" onclick="discardWeapon()">保留现有</button></div>`;
window._pendingWpn=w;$('wpnMdl').innerHTML=html;$('wpnOv').classList.add('show');
}
function equipWeapon(){let w=window._pendingWpn;stats.weapons.push(`${w.name}(${QNAMES[w.quality]})`);P.weapon=w;window._pendingWpn=null;$('wpnOv').classList.remove('show');renderAll();setTimeout(()=>nextFloor(),300)}
function discardWeapon(){window._pendingWpn=null;$('wpnOv').classList.remove('show');renderAll();setTimeout(()=>nextFloor(),300)}
// ============ SHOP ============
function shopItemHtml(it,idx){
let d='';
if(it.type==='weapon'){let w=it.item;d=`<span class="${QCOLORS[w.quality]}">[${QNAMES[w.quality]}]</span> ATK+${w.atkBonus} 耗蓝${w.mpCost} 倍率${w.mult}x ${WPN_EFF_NAME[w.effect]}`}
else if(it.type==='acc'){let a=it.item;d=`<span class="${QCOLORS[a.quality]}">[${QNAMES[a.quality]}]</span> ${a.desc}`}
return`<div class="shop-item${it.price>P.gold?' off':''}" onclick="buyItem(${idx})"><div class="si-name">${it.name}${d?' <span style="font-weight:400;font-size:0.78rem;color:#8892b0;display:block;margin-top:2px">'+d+'</span>':''}</div><span class="si-price">${it.price}💰</span></div>`;
}
function refreshShop(){$('eventMdl').innerHTML=PAUSE_BTN+shopItems.map((it,i)=>it.sold?'':shopItemHtml(it,i)).join('')+`<div class="cbtn-row"><button class="cbtn no" onclick="$('eventOv').classList.remove('show');renderAll();setTimeout(()=>nextFloor(),300)">离开商店</button></div>`}
function doShop(){
P.mp=P.maxMp;shopItems=[];
let noble=P.cls==='noble';
let disc=getShopDiscount(),calMult=hasCal('price_up')?1.5:1;
let wCnt=noble?2:1,aCnt=noble?3:2;
for(let i=0;i<wCnt;i++){let w=genWeapon(P.floor);let price=Math.floor((80+P.floor*5.6)*disc*calMult);shopItems.push({type:'weapon',name:w.name,item:w,price,sold:false})}
for(let i=0;i<aCnt;i++){let a=genAccessory(P.floor);let price=Math.floor((70+P.floor*4.3)*disc*calMult);shopItems.push({type:'acc',name:a.name,item:a,price,sold:false})}
if(P.cls==='beast'&&P.pet){let cost=P.pet.nextCost;shopItems.push({type:'petfood',name:`🐾 宠物饲料(Lv.${P.pet.level}→${P.pet.level+1})`,item:null,price:cost,sold:false})}
let hPrice=Math.floor(80*disc*calMult);let hName=P.cls==='angel'?'恢复生命(50%HP+50信仰)':'恢复生命(50%HP)';shopItems.push({type:'heal',name:hName,item:null,price:hPrice,sold:false});
refreshShop();$('eventOv').classList.add('show');
}
function buyItem(idx){
let it=shopItems[idx];if(!it||it.sold||P.gold<it.price)return;
P.gold-=it.price;it.sold=true;
if(it.type==='weapon'){showShopWeaponCompare(it.item,idx)}
else if(it.type==='acc'){P.accessories.push(it.item);stats.accessories.push(it.item.name);addLog('sys',`购买了饰品:${it.item.name}`);onCollectorTrigger();refreshShop();renderAll()}
else if(it.type==='petfood'){feedPet();refreshShop();renderAll()}
else if(it.type==='heal'){let h=Math.floor(P.maxHp*0.5);P.hp=Math.min(P.maxHp,P.hp+h);if(P.cls==='angel'){P.faith=Math.min(P.maxFaith,P.faith+50);addLog('sys',`使用恢复,+${h}HP,+50信仰`)}else{addLog('sys',`使用恢复,+${h}HP`)}refreshShop();renderAll()}
}
function feedPetDirect(){if(!P.pet||P.gold<P.pet.nextCost)return;P.gold-=P.pet.nextCost;feedPet();renderAll()}
function feedPet(){
if(!P.pet)return;
let costs=[3,5,8,12,18,25,35,50,70,100];
let cost=costs[Math.min(P.pet.level-1,costs.length-1)]||100;
P.pet.level++;
let growth=Math.floor(cost*0.5)+1;
P.pet.atk+=growth;
if(P.pet.level%5===0&&P.pet.skills.length<5){
let avail=PET_SKILLS.filter(s=>!P.pet.skills.some(ps=>ps.id===s.id));
if(avail.length){let sk=avail[rng(0,avail.length-1)];P.pet.skills.push(sk);addLog('art',`🐾 宠物学会了 ${sk.name}!`)}
}
P.pet.nextCost=costs[Math.min(P.pet.level-1,costs.length-1)]||100;
addLog('sys',`🐾 宠物升级到 Lv.${P.pet.level}!ATK +${growth} (总计 ${P.pet.atk})`);
}
function showShopWeaponCompare(w,idx){
let cur=P.weapon;
let html=`<h2>⚔️ 购买武器</h2><div class="compare">`;
html+=`<div class="cmp-box"><div class="cb-n ${QCOLORS[cur.quality]}">${cur.name}</div><div class="cb-s">ATK+${cur.atkBonus}</div></div>`;
html+=`<div class="cmp-box"><div class="cb-n ${QCOLORS[w.quality]}">${w.name}</div><div class="cb-s">ATK+${w.atkBonus}</div></div></div>`;
html+=`<div class="cbtn-row"><button class="cbtn yes" onclick="buyWeapon(${idx})">装备</button><button class="cbtn no" onclick="skipShopWpn()">丢弃</button></div>`;
$('wpnMdl').innerHTML=html;$('wpnOv').classList.add('show');window._shopWpnIdx=idx;
}
function buyWeapon(idx){
let it=shopItems[idx];let w=it.item;stats.weapons.push(`${w.name}(${QNAMES[w.quality]})`);P.weapon=w;
addLog('sys',`购买并装备武器:${w.name}`);$('wpnOv').classList.remove('show');refreshShop();renderAll();
}
function skipShopWpn(){let idx=window._shopWpnIdx;addLog('sys','丢弃了购买的武器');$('wpnOv').classList.remove('show');refreshShop();renderAll()}
// ============ MYSTERY ============
function doMystery(){
P.mp=P.maxMp;let type=MYSTERY_POOL[rng(0,MYSTERY_POOL.length-1)];
let html=PAUSE_BTN;
if(type==='cursed_acc'){let a=genCursedAccessory(P.floor);html+=`<h2>🔮 诅咒饰品</h2><p>一个强大的诅咒饰品出现了</p><div class="drop-acc"><span class="${QCOLORS[a.quality]}">[${QNAMES[a.quality]}]</span> <strong>${a.name}</strong><br>${a.desc}</div><div class="cbtn-row"><button class="cbtn yes" onclick="takeCurseAcc()">接受诅咒</button><button class="cbtn no" onclick="closeMystery()">拒绝</button></div>`;window._curseAcc=a}
else if(type==='blessing'){let boost=rng(1,3);P.atk+=boost;P.maxHp+=boost*5;P.hp=Math.min(P.maxHp,P.hp+boost*5);html+=`<h2>✨ 祝福</h2><p>神秘力量祝福了你!ATK+${boost},HP+${boost*5}</p><button class="rbtn" onclick="closeMystery()">继续</button>`}
else if(type==='trap'){let d=rng(5,15);P.hp=Math.max(1,P.hp-d);html+=`<h2>💥 陷阱</h2><p>你触发了陷阱!受到${d}点伤害</p><button class="rbtn" onclick="closeMystery()">继续</button>`}
else if(type==='gambling'){html+=`<h2>🎰 赌博</h2><p>赌一把?50%概率获得50金,50%概率失去30金</p><div class="cbtn-row"><button class="cbtn yes" onclick="doGamble(true)">下注</button><button class="cbtn no" onclick="closeMystery()">离开</button></div>`}
$('eventMdl').innerHTML=html;$('eventOv').classList.add('show');
}
function takeCurseAcc(){let a=window._curseAcc;P.accessories.push(a);stats.accessories.push(a.name);P.cursedTurns=2;P.cursedAcc=a;addLog('sys',`接受了诅咒饰品:${a.name}`);onCollectorTrigger();closeMystery()}
function closeMystery(){$('eventOv').classList.remove('show');renderAll();setTimeout(()=>nextFloor(),300)}
function doGamble(go){if(go){if(Math.random()<0.5){P.gold+=50;stats.goldTotal+=50;addLog('art','🎰 赢了!+50💰')}else{P.gold=Math.max(0,P.gold-30);addLog('sys','🎰 输了...-30💰')}closeMystery()}else closeMystery()}
// ============ FLOOR PROGRESSION ============
function nextFloor(){
P.floor++;stats.maxFloor=Math.max(stats.maxFloor,P.floor);
P.mp=P.maxMp;
if(hasAcc('life_fountain')){let h=Math.max(1,Math.floor(P.maxHp*accVal('life_fountain')/100));P.hp=Math.min(P.maxHp,P.hp+h);addLog('art',`🌊 生命之泉恢复${h}HP`)}
// Calamity at multiples of 10
if(P.floor>1&&P.floor%10===0){addCalamity()}
// Check mimic fight result
if(window._mimicFight){window._mimicFight=false;giveMimicAcc()}
renderAll();showBranch();
}
// ============ ENDLESS MODE ============
function showEndlessChoice(){
let html=`<h2>🏆 通关成功!</h2><p>你征服了全部30层深渊!</p>`;
html+=`<div class="cbtn-row"><button class="cbtn yes" onclick="startEndless()" style="background:linear-gradient(135deg,#e94560,#c0392b)">⚔️ 无尽挑战</button><button class="cbtn no" onclick="showEnd(true)">查看总结</button></div>`;
$('eventMdl').innerHTML=html;$('eventOv').classList.add('show');
}
function startEndless(){
endlessMode=true;$('eventOv').classList.remove('show');
addLog('sys','🔥 无尽模式开启!敌人将变得极其强大!');
renderAll();showBranch();
}
// ============ END GAME ============
function showEnd(win){
gameOver=true;renderActs();
let mf=stats.maxFloor||P.floor;
$('endTitle').textContent=endlessMode?'⚔️ 无尽挑战结束':win?'🏆 通关成功!':'💀 冒险失败';
$('endTitle').style.color=endlessMode?'#e94560':win?'#ffd700':'#e74c3c';
$('endMsg').textContent=endlessMode?`你在无尽模式到达了第${mf}层!`:win?'你征服了全部30层深渊!':`你倒在了第${P.floor}层...`;
let s=`<strong>📊 通关总结</strong><br>${endlessMode?'最高层数':'到达层数'}: ${mf}${!endlessMode?'/'+TOTAL:''}<br>最终等级: Lv.${P.level}<br>`;
s+=`击败敌人: ${stats.kills+stats.elites+stats.bosses} (普通${stats.kills}/精英${stats.elites}/Boss${stats.bosses})<br>`;
s+=`总金币: ${stats.goldTotal} (剩余: ${P.gold})<br>武器: ${stats.weapons[stats.weapons.length-1]}<br>`;
s+=`饰品(${P.accessories.length}件): ${stats.accessories.length?stats.accessories.join('、'):'无'}<br>`;
s+=`灾厄(${calamities.length}个): ${calamities.map(c=>c.name).join('、')}`;
$('endSummary').innerHTML=s;$('endOv').classList.add('show');
}
function restart(){$('endOv').classList.remove('show');$('logBox').innerHTML='';logs=[];newGame()}
newGame();
</script>
</body>
</html>
Game Source: Rogue 冒险 · 层级远征
Creator: QuantumPenguin78
Libraries: none
Complexity: complex (915 lines, 71.3 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: rogue-quantumpenguin78-msikvw45" to link back to the original. Then publish at arcadelab.ai/publish.