"use strict"; /* ================= 战斗结算 ================= */ let _uid=1; function banner(t,col,time){G.banner={t,col:col||"#41e6ff"};G.bannerT=time||100;} function addScore(base,x,y){ let s=base*BAL.scoreCombo(G.combo)*(G.mutator==="FRENZY"?1.3:1); s=Math.round(s); G.score+=s; if(base>=400&&x!==undefined)G.texts.push({x,y,t:"+"+s,col:"#ffcf5c",life:50}); } function addCoins(n){G.coins+=Math.round(n*pCoinMul()*BAL.coinGlobal);} function spawnPart(x,y,col,n){ for(let i=0;i<(n||1);i++)G.parts.push({x,y,vx:rnd(-2,2),vy:rnd(-2,2),life:rndi(14,30),col,sz:rndi(1,3)}); } function dropPick(x,y,kind){ G.picks.push({x:clamp(x,8,W-8),y:clamp(y,-20,H-40),kind,vy:0.7,t:0,spr:{gem:"gem",coin:"coin",heart:"heart",bomb:"bombp",odc:"odc"}[kind]}); } function hurtEnemy(e,dmg,canCrit){ if(!e||e.dead)return; let crit=false; if(canCrit&&Math.random()7); G.shake=Math.max(G.shake,e.elite?6:2); const cols=e.elite?["#ffd23d","#fff","#ff9a3d"]:["#ff9a3d","#ff5566","#fff"]; for(let i=0;i<(e.elite?20:8);i++)spawnPart(e.x,e.y,pick(cols),e.elite?3:2); addScore(e.score,e.x,e.y); G.combo++;G.comboT=180; G.od=Math.min(100,G.od+BAL.odGain.kill*(1+0.3*G.u.odcore)); for(let i=0;i0||p.dead>0)return; if(G.demoSim){spawnPart(p.x,p.y,"#41e6ff",3);SFX.graze();return;} // 演示模式无敌 if(Math.random()0){G.shield--;G.shieldT=0;p.inv=70;SFX.hurt();G.shake=6;banner("护盾损毁!","#43e8ff",60);return;} G.hp-=dmg;G.combo=0; SFX.hurt();G.shake=7;G.flash=8;G.hitstop=5;p.inv=55; for(let i=0;i<8;i++)spawnPart(p.x,p.y,pick(["#ff5566","#fff","#ff9a3d"]),2); G.texts.push({x:p.x,y:p.y-10,t:"-"+Math.round(dmg),col:"#ff5566",life:35}); if(G.hp<=0){ G.lives--;G.hp=0; SFX.boom(true);G.shake=12;G.flash=14; for(let i=0;i<20;i++)spawnPart(p.x,p.y,pick(["#ff5566","#fff","#ff9a3d"]),3); if(G.lives<=0){p.dead=99999;endRun(false);} else{p.dead=70;G.hp=G.maxHp;banner("机体损毁 — 重新部署","#ff5566",80);} } } function shieldCap(){return G.stat.shieldMax+(G.u.shield>0?1:0)+(G.u.shield>=3?1:0);} /* ================= 子弹更新 ================= */ function explodeOnce(b){if(b.boom)return;b.boom=true;explodeBullet(b);} function strongestEnemy(x,y,r){ // 斩首巡航弹:Boss/精英优先 if(G.boss)return G.boss; let best=null,bh=-1;const rr=r*r; for(const e of G.enemies){if(e.dead)continue; if(dist2(x,y,e.x,e.y)bh){bh=v;best=e;}}} return best||nearestEnemy(x,y,r); } function updateBullets(){ for(const b of G.pb){ // 附着状态:钻头研磨 / 粘着炸弹 if(b.stuck){ const se=b.stuck; if(se.dead){b.dead=true;if(b.t==="tb")explodeOnce(b);continue;} b.x=se.x+b.ox;b.y=se.y+b.oy; if(b.t==="tb"){b.fuse--;if(b.fuse<=0){b.dead=true;explodeOnce(b);}} continue; } if(b.homing||b.softH){const t=b.eliteHunt?strongestEnemy(b.x,b.y,460):nearestEnemy(b.x,b.y,b.softH?400:220); if(t){const a=Math.atan2(t.y-b.y,t.x-b.x),c=Math.atan2(b.vy,b.vx); let d=a-c;while(d>Math.PI)d-=TAU;while(d<-Math.PI)d+=TAU; if(b.softH){ // 星云奥秘:缓慢转向,不加速 const na=c+clamp(d,-0.05,0.05),s=Math.hypot(b.vx,b.vy); b.vx=Math.cos(na)*s;b.vy=Math.sin(na)*s; }else{const na=c+clamp(d,-0.16,0.16),s=Math.min(6,Math.hypot(b.vx,b.vy)+0.08); b.vx=Math.cos(na)*s;b.vy=Math.sin(na)*s;}}} if(b.back){b.bt--;if(b.bt<=0&&b.vy<0){b.vy*=-1;b.vx*=-0.4;}} if(b.curve){const c=Math.atan2(b.vy,b.vx)+b.curve,s=Math.hypot(b.vx,b.vy)||0.1; b.vx=Math.cos(c)*s;b.vy=Math.sin(c)*s;} if(b.pull)for(const e of G.enemies)if(!e.dead){ const d2=dist2(b.x,b.y,e.x,e.y); if(d21){const d=Math.sqrt(d2);e.x+=(b.x-e.x)/d*0.45;e.y+=(b.y-e.y)/d*0.45;}} // 空爆弹:先向左右平移,再折向直飞,沿途不断爆炸 if(b.t==="am"&&!b.dead){ if(b.phase===1){b.pt++; if(b.pt>=20){b.phase=2;b.vx=0;b.vy=-4.4;}} else if(G.time%6===0){ const ex=b.x+rnd(-8,8),ey=b.y+rnd(-4,8); areaDamage(ex,ey,b.zone||15,b.dmg*0.35); explodeFx(ex,ey,"airburst",1); if(G.time%12===0)SFX.boom(false); } } if(b.trailCol&&G.time%2===0)G.parts.push({x:b.x,y:b.y,vx:rnd(-0.2,0.2),vy:0.4,life:12,col:b.trailCol,sz:1}); if(b.t==="nb"&&G.time%4===0)G.parts.push({x:b.x+rnd(-4,4),y:b.y+rnd(-4,4),vx:rnd(-0.3,0.3),vy:rnd(-0.3,0.3),life:14,col:pick(["#b98cff","#e0b3ff"]),sz:1}); // 电弧手里剑:一路前飞不停留,全程周期性放电 if(b.t==="shk"&&!b.dead){ b.pt2=(b.pt2||0)-1; if(b.pt2<=0){b.pt2=b.pulse||20; const tg=nearEnemies(b.x,b.y,55,3); for(const t2 of tg){zapVisual(b.x,b.y,t2.x,t2.y,pick(["#7ce9ff","#ffffff"]));hurtT(t2,b.zapDmg,true);} if(tg.length)SFX.graze();} } if(b.t==="dw"&&!b.dead){ // 黎明重炮:近炸引信(半径26,无需直接命中) let pz=!!nearestEnemy(b.x,b.y,26); if(!pz&&bossHit(b.x,b.y,26))pz=true; if(pz){b.dead=true;explodeOnce(b);}} if(b.t==="c"&&!b.dead&&b.yMath.PI)d4-=TAU;while(d4<-Math.PI)d4+=TAU; const na=ca+clamp(d4,-0.035,0.035),ss=Math.hypot(b.vx,b.vy); b.vx=Math.cos(na)*ss;b.vy=Math.sin(na)*ss;} if(!b.back&&b.bt!==undefined){b.bt--;if(b.bt<=0&&!b.dead){b.dead=true;explodeOnce(b);}} if(b.t==="mt"&&b.ty2!==undefined&&b.y<=b.ty2&&!b.dead){b.dead=true;explodeOnce(b);} if(b.t==="s"&&!b.dead){ // 分裂弹:飞行一段距离后自动分裂 b.age=(b.age||0)+1; if(b.age>=(b.splitAt||26)){b.dead=true; for(let i=0;iH+16||b.x<-16||b.x>W+16)b.dead=true; } // 敌弹:热诱弹偏转(每枚热诱弹只锁定一颗敌弹)+ 时间力场减速 for(const f of G.flares)if(f.lock&&(f.lock.dead||dist2(f.x,f.y,f.lock.x,f.lock.y)>3600))f.lock=null; for(const b of G.eb){ let esf=1; for(const z of G.zones)if(z.slow&&dist2(b.x,b.y,z.x,z.y)Math.PI)d4-=TAU;while(d4<-Math.PI)d4+=TAU; const na=c+clamp(d4,-0.2,0.2),ss=Math.hypot(b.vx,b.vy); b.vx=Math.cos(na)*ss;b.vy=Math.sin(na)*ss; if(fd<36){b.dead=true;fl.dead=true;spawnPart(b.x,b.y,"#ffd23d",2);SFX.graze();}} if(b.grow){b.scale=Math.min(b.maxS||2.6,(b.scale||1)+b.grow);b.r=2.4*b.scale;} // 花弹:越变越大(有上限) b.x+=b.vx*esf;b.y+=b.vy*esf; if(b.y<-10||b.y>H+10||b.x<-10||b.x>W+10)b.dead=true;} // 热诱弹 for(const f of G.flares){f.x+=f.vx||0;f.y+=(f.vy!==undefined?f.vy:0.35);f.life--; if(G.time%6===0)G.parts.push({x:f.x+rnd(-3,3),y:f.y+rnd(-3,3),vx:0,vy:-0.3,life:10,col:pick(["#ffd23d","#ff9a3d"]),sz:1});} G.flares=G.flares.filter(f=>f.life>0&&!f.dead&&f.y>-10&&f.y-10&&f.x!b.dead); G.eb=G.eb.filter(b=>!b.dead); // 区域效果(电浆/引力井) for(const z of G.zones){z.life--; if(z.pull)for(const e of G.enemies)if(!e.dead){ const d2=dist2(z.x,z.y,e.x,e.y); if(d2<(z.r+44)*(z.r+44)&&d2>1){const d=Math.sqrt(d2);e.x+=(z.x-e.x)/d*0.5;e.y+=(z.y-e.y)/d*0.5;}} if(z.dps&&G.time%6===0){ for(const e of G.enemies)if(!e.dead&&dist2(z.x,z.y,e.x,e.y)<(z.r+e.r)*(z.r+e.r))hurtEnemy(e,z.dps*0.1,false); if(G.boss&&bossHit(z.x,z.y,z.r))hurtBoss(z.dps*0.1);} } G.zones=G.zones.filter(z=>z.life>0); for(const z of G.zaps)z.life--; G.zaps=G.zaps.filter(z=>z.life>0); for(const n of G.novas){n.life--;n.r+=(n.maxr-n.r)*0.18;} G.novas=G.novas.filter(n=>n.life>0); // 圣光十字:逐渐变长 + 原地旋转90°,周期性结算伤害 for(const hc of G.holies){hc.t++;hc.life--; if(hc.t%9===0)crossBeams(hc.x,hc.y,hc.len*Math.min(1,hc.t/12),hc.dmg*0.4,(hc.t/36)*Math.PI/2);} G.holies=G.holies.filter(h=>h.life>0); // 黎明重炮余震:延迟二次爆炸 for(const sk of G.shocks){sk.t--;if(sk.t<=0&&!sk.done){sk.done=true; areaDamage(sk.x,sk.y,sk.r,sk.dmg);explodeFx(sk.x,sk.y,"boom",0.7);G.shake=Math.max(G.shake,3);SFX.boom(false);}} G.shocks=G.shocks.filter(s=>!s.done); } /* ================= 碰撞 ================= */ function collide(){ const p=G.p; // 玩家子弹 vs 敌 for(const b of G.pb){ if(b.dead)continue; for(const e of G.enemies){ if(e.dead)continue; const rr=e.r+b.r+1; if(dist2(b.x,b.y,e.x,e.y)=20){G.shake=Math.max(G.shake,3);}SFX.hit();} else hurtEnemy(e,b.dmg,true); if(b.t==="vl"){ // 电能机炮:命中后电弧连锁跳跃 let vcn=0;const vcm=b.chain||2; for(const o of G.enemies){if(o.dead||o===e||vcn>=vcm)continue; if(dist2(e.x,e.y,o.x,o.y)<2304){vcn++; zapVisual(e.x,e.y,o.x,o.y,pick(["#7ce9ff","#b06cff"])); hurtEnemy(o,b.dmg*0.5,true);}} if(vcn)SFX.graze();} if(b.t==="rg"&&!b.zoneUsed){b.zoneUsed=true; G.zones.push({x:b.x,y:b.y,r:b.zone,dps:b.dmg*0.09,life:220,col:"#43e8ff",kind:"plasma"}); areaDamage(b.x,b.y,18,b.dmg*0.5); explodeFx(b.x,b.y,"plasma",0.8);} SFX.hit(); if(b.dead){if(b.t==="sg"||b.t==="ho"||b.t==="bb"||b.t==="am"||b.t==="tb"||b.t==="tb2"||b.t==="dw"||b.t==="ph"||b.t==="nb"||b.t==="ex")explodeOnce(b);break;} } } if(!b.dead&&G.boss){ const B=G.boss; if(bossHit(b.x,b.y,b.r)){ if(b.pierce){if(!b.hits)b.hits={};if(!b.hits.boss){b.hits.boss=1;hurtBoss(b.dmg);}} else{b.dead=true; if(b.t==="sg"||b.t==="ho"||b.t==="bb"||b.t==="am"||b.t==="tb"||b.t==="tb2"||b.t==="dw"||b.t==="ph"||b.t==="nb"||b.t==="ex")explodeOnce(b); else hurtBoss(b.dmg);} SFX.hit(); } } } // 敌弹 vs 玩家(含擦弹) if(p.dead<=0)for(const b of G.eb){ const d2=dist2(b.x,b.y,p.x,p.y); const hr=G.stat.hitR+b.r; if(d20){bm.tele--;continue;} bm.life--; if(bm.cd>0)bm.cd--; if(p.dead<=0&&bm.cd<=0){ const dx=bm.x2-bm.x1,dy=bm.y2-bm.y1,L2=dx*dx+dy*dy||1; const tt=clamp(((p.x-bm.x1)*dx+(p.y-bm.y1)*dy)/L2,0,1); const cx=bm.x1+dx*tt,cy=bm.y1+dy*tt,br=bm.w/2+G.stat.hitR; if(dist2(p.x,p.y,cx,cy)bm.life>0||bm.tele>0); // 冲撞Boss撞击玩家 if(G.boss&&G.boss.dashSt&&G.boss.dashSt.ph==="go"&&p.dead<=0&&p.inv<=0){ if(bossHit(p.x,p.y,G.stat.hitR+2))hitPlayer(30); } // 敌机撞击(反伤棘甲) if(p.dead<=0&&p.inv<=0)for(const e of G.enemies){ if(e.dead)continue; const rr=e.r+G.stat.hitR+2; if(dist2(e.x,e.y,p.x,p.y)H+10)k.dead=true; } G.picks=G.picks.filter(k=>!k.dead); } /* ================= 经验与三选一 ================= */ function addXp(n){ if(G.demoSim)return; // 演示模式不升级不抽卡 G.xp+=n; while(G.xp>=G.xpNeed){G.xp-=G.xpNeed;G.level++;G.xpNeed=BAL.xpNeed(G.level);G.cardQ=(G.cardQ||0)+1;} if(G.cardQ>0&&UI==="play")offerCards(false); } function buildPool(){ const pool=[]; for(const k of MAINS){ if(!G.weapons[k])pool.push({kind:"weapon",key:k,rare:WEAPONS[k].rare}); else if(G.weapons[k]<5)pool.push({kind:"wlvl",key:k,rare:WEAPONS[k].rare});} for(const k of SUB_KEYS){ if(!G.subs[k])pool.push({kind:"sub",key:k,rare:SUBS[k].rare}); else if(G.subs[k]p.rare===rare&&!out.includes(p)); if(cand.length){out.push(pick(cand));break;} if(tries===7){const rest=pool.filter(p=>!out.includes(p));if(rest.length)out.push(pick(rest));} } } while(out.length<3)out.push({kind:"fill",key:["heal","coins","od"][out.length%3],rare:0}); return out; }