"use strict"; /* ================= 敌人图鉴 ================= */ const ENEMIES={ scout:{hp:10,spd:1.15,score:100,r:5,gems:2,coin:.3,beh:"drift",fire:null,cost:1,min:0}, weaver:{hp:16,spd:1.3,score:160,r:6,gems:2,coin:.35,beh:"sine",fire:null,cost:1.4,min:2}, kami:{hp:8,spd:1.5,score:120,r:5,gems:2,coin:.25,beh:"dive",fire:null,death:"ring",cost:1.2,min:3}, splitter:{hp:26,spd:1,score:220,r:7,gems:2,coin:.4,beh:"sine",fire:{t:"aim",cd:145,spd:1.9},death:"split",cost:2,min:4}, mine:{hp:22,spd:.45,score:180,r:6,gems:2,coin:.4,beh:"drift",fire:{t:"ring8",cd:185,spd:1.25},cost:2,min:5}, gunship:{hp:55,spd:.7,score:420,r:8,gems:4,coin:.6,beh:"hover",fire:{t:"burst5",cd:160,spd:1.65},cost:4,min:6}, sniper:{hp:30,spd:.6,score:300,r:6,gems:3,coin:.5,beh:"hover",fire:{t:"snipe",cd:210,spd:3.0},cost:3,min:8}, rock:{hp:30,spd:1,score:40,r:8,gems:0,coin:.1,beh:"spin",fire:null,cost:0,min:0,neutral:true}, mini:{hp:5,spd:1.8,score:60,r:4,gems:1,coin:.15,beh:"dive",fire:null,cost:.5,min:0}, gaunt:{hp:26,spd:1.45,score:150,r:6,gems:1,coin:.2,beh:"sidle",fire:null,cost:1.5,min:99}, // 泰伦刀虫(母舰专属) sporeT:{hp:14,spd:.5,score:120,r:6,gems:1,coin:.15,beh:"drift",fire:null,death:"ring",cost:1,min:99}, // 孢子囊:慢飘,死亡酸环 ripper:{hp:6,spd:2.2,score:50,r:3.5,gems:1,coin:.1,beh:"dive",fire:null,cost:.4,min:99}, // 撕裂虫:小快速 boomer:{hp:8,spd:2.0,score:130,r:5,gems:1,coin:.15,beh:"boom",fire:null,cost:1,min:99}, // 爆虫:冲锋自爆 spitter:{hp:20,spd:.8,score:170,r:6,gems:2,coin:.25,beh:"hover",fire:{t:"acid3",cd:150,spd:1.7},cost:1.6,min:99}, // 吐酸虫:远程散弹 spinegun:{hp:22,spd:.75,score:180,r:6,gems:2,coin:.25,beh:"hover",fire:{t:"spike",cd:170,spd:3.2},cost:1.7,min:99} // 棘枪虫:骨钉直线 }; function tierN(){ // 难度层数:无尽=波次,闯关=关卡*2+小波(图鉴演示等 wave=0 场景会为负,按0计,防止hpMul产生NaN) return Math.max(0,G.mode==="endless"?G.wave-1:(G.stage-1)*2+G.wave-1); } function spawnEnemy(type,x,y,elite){ const d=ENEMIES[type],n=tierN(); const hpM=BAL.hpMul(n)*(G.diff===1?BAL.hard.hp:1)*(G.mutator==="ARMOR"?1.25:1)*(elite?4:1)*enemyScale(); const sMul=(type==="kami"||type==="mini")?Math.min(1.25,1+0.015*n):BAL.spdMul(n); // 突袭机保持高速,其余随波次减速 const e={type,x,y,hp:d.hp*hpM,maxhp:d.hp*hpM,r:d.r*(elite?1.4:1),spd:d.spd*sMul*(G.mutator==="GOLD"?1.1:1), score:d.score*(elite?5:1),gems:elite?5:d.gems,coin:elite?1:d.coin,elite:!!elite, beh:d.beh,fire:d.fire?Object.assign({},d.fire):null,death:d.death||null,neutral:!!d.neutral, t:0,ph:rnd(0,TAU),ft:rnd(0,60),hy:rnd(50,90),vx:0,vy:0,ax:0,flash:0,dead:false,spr:SPR[(elite?"e_":"")+type]||SPR[type]||SPR.scout}; e.dw=Math.round(e.spr.width*(elite?1.4:1)); e.dh=Math.round(e.spr.height*(elite?1.4:1)); if(elite&&!e.fire)e.fire={t:"aim",cd:140,spd:1.9}; // 精英杂兵获得弱射击能力 if(e.fire)e.fire.cd=Math.round(e.fire.cd/(BAL.fireMul(n)*(G.diff===1?BAL.hard.fire:1)*(G.mutator==="FRENZY"?1.3:1)*(elite?0.7:1))); e.x0=x; if(e.beh==="dive"){const a=Math.atan2(G.p.y-y,G.p.x-x);e.vx=Math.cos(a)*e.spd;e.vy=Math.max(0.6,Math.sin(a)*e.spd);} if(e.beh==="spin"){e.vx=rnd(-0.5,0.5);e.rot=rnd(0,TAU);} G.enemies.push(e); } /* ---- 泰伦触手扫击:角度制扇形动作(demo验证版移植,渲染与判定同源) ---- */ function tentPose(tn,side,tB){ // 返回{ang(0=正下,side向为正),stretch,glow} const idle=Math.sin(tB*0.06)*0.07*side; if(tn.warn>0){const f=1-tn.warn/50,e=f*f; // 前摇:向扫击反方向后拉蓄力 return{ang:idle*(1-f)+side*0.73*e+Math.sin(tB*0.9)*0.012*e,stretch:1+e*0.15,glow:f};} if(tn.act>17){const f=(45-tn.act)/28,e=f*f*f; // 扫击:加速挥出(右下→左下60°扇形,伸长+50%) return{ang:side*(0.73-e*1.31),stretch:1.15+e*1.25,glow:1};} if(tn.act>0){return{ang:side*(-0.58)+Math.sin(tB*1.2)*0.05*(tn.act/17),stretch:2.4,glow:1};} // 顿挫 const f=1-tn.rec/40,e=1-(1-f)*(1-f); // 收回 return{ang:side*(-0.58)+(idle-side*(-0.58))*e,stretch:2.4-1.4*e,glow:1-e};} function tentSegs(b,side,opF){ // 触手7节局部坐标(相对Boss中心),逐节延迟形成鞭弧 const pts=[];opF=opF===undefined?1:opF; for(let i=0;i<7;i++){ let ang=Math.sin((b.t-i*2.2)*0.06)*0.07*side,stretch=1,glow=0; if(b.tent&&b.tent.side===side){const s=tentPose(b.tent,side,b.t-i*2.2);ang=s.ang;stretch=s.stretch;glow=s.glow;} // 仅扫击侧动作,另一侧保持待机摆动 const f=i/6,L=40*stretch*f*opF; pts.push({x:side*9+Math.sin(ang)*L,y:24+Math.cos(ang)*L,glow}); } return pts;} /* ---- 敌机行为 ---- */ function updateEnemy(e){ e.t++; if(e.flash>0)e.flash--; if(e.hpt>0)e.hpt--; let spM=1; if(e.slow>0){e.slow--;spM=0.45;} // 冻结减速 if(e.frozen>0){e.frozen--;spM=0; // 冰冻枪:彻底冻结 if(G.time%8===0)G.parts.push({x:e.x+rnd(-5,5),y:e.y+rnd(-5,5),vx:0,vy:0.2,life:14,col:"#cfeaff",sz:1});} for(const z of G.zones)if(z.slow&&dist2(e.x,e.y,z.x,z.y)<(z.r+e.r)*(z.r+e.r)){spM*=0.4;break;} // 时间力场 if(e.dot){e.dot.t--;e.hp-=e.dot.dps/60; // 中毒蚀甲 if(e.hp<=0){killEnemy(e);return;} if(e.dot.t<=0)e.dot=null;} if(e.leech){e.leech.t--;e.hp-=e.leech.dps/60; // 寄生电浆:吸血 G.hp=Math.min(G.maxHp,G.hp+e.leech.dps*0.3/60); if(G.time%10===0)G.parts.push({x:e.x,y:e.y,vx:0,vy:0,life:12,col:"#b05ce8",sz:1,suck:{x:G.p.x,y:G.p.y}}); if(e.hp<=0){killEnemy(e);return;} if(e.leech.t<=0)e.leech=null;} if(spM>0)switch(e.beh){ // 冻结(spM=0)时完全定身 case "drift": e.y+=e.spd*spM; e.x=clamp(e.x0+Math.sin(e.t*0.025+e.ph)*10,10,W-10); break; case "sine": e.y+=e.spd*spM; e.x=clamp(e.x0+Math.sin(e.t*0.035+e.ph)*18,10,W-10); break; case "dive": e.x+=e.vx*spM; e.y+=e.vy*spM; if(e.type==="kami"&&e.t%30===0){const ap=aimPt(),a=Math.atan2(ap.y-e.y,ap.x-e.x);e.vx=lerp(e.vx,Math.cos(a)*e.spd*1.4,0.15);e.vy=lerp(e.vy,Math.sin(a)*e.spd*1.4,0.15);} break; case "hover": if(e.y0){e.sidleT--;e.x+=e.vx*spM;e.y+=e.vy*spM;} else{const ap3=aimPt(),a3=Math.atan2(ap3.y-e.y,ap3.x-e.x); e.vx=lerp(e.vx,Math.cos(a3)*e.spd,0.06);e.vy=lerp(e.vy,Math.sin(a3)*e.spd,0.06); e.x+=e.vx*spM;e.y+=e.vy*spM;} break; case "boom": { // 泰伦爆虫:追踪冲锋,近身自爆(酸液飞溅) const ap4=aimPt(),a4=Math.atan2(ap4.y-e.y,ap4.x-e.x); e.vx=lerp(e.vx,Math.cos(a4)*e.spd*1.35,0.08);e.vy=lerp(e.vy,Math.sin(a4)*e.spd*1.35,0.08); e.x+=e.vx*spM;e.y+=e.vy*spM; if(G.time%6===0)G.parts.push({x:e.x,y:e.y,vx:0,vy:-0.2,life:8,col:"#a8ff5c",sz:1}); // 酸液拖尾 if(dist2(e.x,e.y,G.p.x,G.p.y)<17*17){e.dead=true; for(let i2=0;i2<8;i2++)G.parts.push({x:e.x,y:e.y,vx:Math.cos(i2*TAU/8)*rnd(0.8,1.7),vy:Math.sin(i2*TAU/8)*rnd(0.8,1.7),life:16,col:"#a8ff5c",sz:2}); SFX.hit(); if(dist2(e.x,e.y,G.p.x,G.p.y)<26*26&&G.p.dead<=0)hitPlayer(20);} break;} } // 开火(独立计时器,与摆动相位完全分离) if(e.fire&&e.y>6&&e.ye.fire.cd){e.ft=0;enemyFire(e);} } if(e.y>H+20||e.x<-30||e.x>W+30)e.dead=true; } function ebul(x,y,a,spd,spr,dmg){ spr=spr||"orbR"; const bb={x,y,vx:Math.cos(a)*spd,vy:Math.sin(a)*spd,r:2.4,spr,halo:BHALO[spr]||"#ff5566", dmg:Math.round((dmg||16)*(G.diff===1?1.3:1)),grazed:false}; G.eb.push(bb);SFX.eshoot();return bb; } function aimPt(){return{x:G.p.x,y:G.p.y};} function enemyFire(e){ const f=e.fire,ap=aimPt(),a0=Math.atan2(ap.y-e.y,ap.x-e.x); if(f.t==="aim")ebul(e.x,e.y+4,a0,f.spd,"orbR",16); else if(f.t==="spread3")for(let i=-1;i<=1;i++)ebul(e.x,e.y+4,a0+i*0.32,f.spd,"orbP",16); else if(f.t==="burst5")for(let i=-2;i<=2;i++)ebul(e.x,e.y+6,a0+i*0.2,f.spd,"orbO",18); else if(f.t==="ring8")for(let i=0;i<8;i++)ebul(e.x,e.y,i/8*TAU+e.t*0.01,f.spd,"orbG",18); else if(f.t==="snipe"){ebul(e.x,e.y+4,a0,f.spd,"orbP",24);ebul(e.x,e.y+4,a0+0.05,f.spd,"orbP",24);ebul(e.x,e.y+4,a0-0.05,f.spd,"orbP",24);} else if(f.t==="acid3")for(let i=-1;i<=1;i++)ebul(e.x,e.y+4,a0+i*0.3,f.spd,"orbG",14); // 泰伦:酸液散弹 else if(f.t==="spike")ebul(e.x,e.y+4,a0,f.spd,"spikeB",16); // 泰伦:骨钉直线 } /* ================= 突变词缀 ================= */ const MUTS={ METEOR:{name:"陨石带",ds:"陨石坠落 · 星币+20%",col:"#ff9a3d"}, ELITE:{name:"精英潮",ds:"精英概率大幅提升",col:"#ffd23d"}, SWARM:{name:"虫群",ds:"额外小型机群来袭",col:"#b05ce8"}, FRENZY:{name:"弹雨狂欢",ds:"敌射速+30% · 分数+30%",col:"#ff5566"}, GOLD:{name:"黄金波",ds:"星币×2 · 敌速+10%",col:"#ffcf5c"}, ARMOR:{name:"装甲波",ds:"敌甲+25% · 经验+30%",col:"#8c9ab8"} }; /* ================= 波次生成 ================= */ function startWave(){ G.wave++; G.waveT=0; G.spawning=[]; const n=tierN(); // 词缀 G.mutator=null; if(G.mode==="endless"&&G.wave>=3)G.mutator=pick(Object.keys(MUTS)); else if(G.mode==="stage"&&G.stage>=3&&Math.random()<0.5)G.mutator=pick(Object.keys(MUTS)); // Boss 波? const isBoss = G.mode==="endless" ? G.wave%5===0 : G.wave>(G.stage>=3?4:3); if(isBoss){startBoss();return;} // 预算制生成:不开火的杂兵为主(~70%),射手为少数(~30%且有同屏上限) let budget=(6+n*2.2)*(G.mode==="stage"?1+G.stage*0.12:1)*budgetScale(); const fodder=["scout","weaver","kami"].filter(k=>ENEMIES[k].min<=n); const shooters=["splitter","mine","gunship","sniper"].filter(k=>ENEMIES[k].min<=n); const eC=BAL.eliteC(n)*(G.mutator==="ELITE"?4:1); const shooterCap=2+Math.floor(n/3); let shooterN=0,delay=30; while(budget>0){ let type; if(shooters.length&&shooterNG.spawning.push({type,x:clamp(x,12,W-12),y:-12-(form==="col"?i*26:0),delay:delay+i*(form==="v"?12:20),elite:elite&&i===0})); } function updateSpawner(){ for(const s of G.spawning){s.delay--;if(s.delay<=0&&!s.done){s.done=true;spawnEnemy(s.type,s.x,s.y,s.elite);}} G.spawning=G.spawning.filter(s=>!s.done); if(G.mode==="rush")return; // Boss挑战:无波次推进 // 波次结束判定 if(!G.boss&&G.spawning.length===0&&G.enemies.length===0){ G.betweenT--; if(G.betweenT<=0){G.betweenT=80; // 波次通关奖励 if(G.wave>0){const bc=G.wave*2;addCoins(bc); G.texts.push({x:W/2,y:H*0.5,t:"波次奖励 ✦"+Math.round(bc*pCoinMul()),col:"#ffcf5c",life:80});} startWave(); } }else G.betweenT=80; } /* ================= Boss 战 ================= */ /* 8大Boss:1种主攻击模式 + 同时1~2种随机小攻击;血量阈值触发二/三阶段变形 */ const BOSSES=[ {name:"巡逻巡洋舰",sub:"PATROL CRUISER",hp:950,col:"#e0393e",r:22,main:"cruiser",minors:["snipe","ring"],phases:2}, {name:"太空原种",sub:"SPACE SPAWN",hp:1350,col:"#ff8fc8",r:20,rr:[20,34],ryy:[26,18],wide:1,main:"spawn",minors:["wiggle","spikeSnipe","sporeArc","tentBeam","mawSnipe","wingRain"],phases:2}, {name:"歼灭者",sub:"ANNIHILATOR",hp:1700,col:"#b05ce8",r:22,main:"annihilate",minors:["aimSpread","spawnMine"],phases:3}, {name:"暗黑突击",sub:"DARK ASSAULT",hp:2100,col:"#ff3355",r:9,rr:[9,10,11],ryy:[5,6,7],main:"assault",minors:["rain","aimSpread"],phases:3}, {name:"激光钢琴",sub:"LASER PIANO",hp:2500,col:"#43e8ff",r:26,rr:[100,100],ryy:[30,30],main:"piano",minors:["topRain","ring"],phases:2,static:1}, {name:"母巢核心",sub:"HIVE CORE",hp:2800,col:"#ff9a3d",r:22,main:"hive",minors:["spawnMini","aimSpread","ring"],phases:3}, {name:"夺权者X",sub:"USURPER X",hp:3700,col:"#e8f4ff",r:24,rr:[52,26],ryy:[34,40],wide:1,main:"usurper",minors:["chain","pillar","aimSpread"],phases:2}, {name:"暗黑魔碟",sub:"DARK SAUCER",hp:4500,col:"#8a5cff",r:24,rr:[30,32,34],main:"saucer",minors:["ringBig","coneFan","crossEye","dartSpiral"],phases:3}, {name:"黑石要塞",sub:"BLACKSTONE",hp:3400,col:"#5cff9d",r:30,rr:[34,37],ryy:[26,28],wide:1,main:"blackstone",minors:["deckGuns","crackRift","starLine"],phases:2}, {name:"泰伦虫族母舰",sub:"TYRANID HIVESHIP",hp:3200,col:"#7dff8a",r:26,rr:[26,29,32],ryy:[36,38,40],main:"hivespawn",minors:["sporeBloom","tentSweep","acidGas"],phases:3} ]; /* Boss命中判定:支持椭圆(宽扁机型),rr=x半径,ry=y半径 */ function bossHit(x,y,r){ const B=G.boss;if(!B)return false; const dx=x-B.x,dy=y-B.y,rr=r||0; if(B.ry){const rx=B.r+rr,ry2=B.ry+rr;return dx*dx/(rx*rx)+dy*dy/(ry2*ry2)<1;} const c=B.r+rr;return dx*dx+dy*dy0;i--){const j=rndi(0,i),t=a[i];a[i]=a[j];a[j]=t;}return a;} function bossBeam(x1,y1,x2,y2,w,col,tele,dur,dmg,src){ // Boss光束:tele帧预警→dur帧伤害;src={b,ox,oy,followX2}时发射点跟随Boss G.bbeams.push({x1,y1,x2,y2,w,col,tele,life:dur,dmg:dmg||20,cd:0,src:src||null}); } function spawnFunnels(b,n){ // 夺权者X:蓝白长钉无人机(环绕僚机;定期脱离环绕摆阵型齐射) for(let i=0;i170)b.form=null;} // 阵型演出:40帧入位→4轮齐射→归位 else if((b.laserT||0)<=0&&--b.formT<=0){ // 巨宽激光期间不排阵 b.form={pat:rndi(0,2),t:0}; G.funnels.forEach((f,i)=>{f.slot=i;}); b.formT=rndi(480,660); } } for(const f of G.funnels){ f.t++;if(f.flashT>0)f.flashT--; if(b&&!b.dead){ f.sa+=0.014; const ringX=b.x+Math.cos(f.sa)*46,ringY=b.y+16+Math.sin(f.sa)*20; if(b.form){ // 脱离环绕摆阵型:横队垂直向下 / 两翼外张扇形 / 集火瞄准 const fm2=b.form,i=f.slot||0,nn=Math.max(1,n-1); let tx,ty,fa; if(fm2.pat===0){tx=b.x+(i-nn/2)*26;ty=b.y-30;fa=Math.PI/2;} else if(fm2.pat===1){const sd=i%2?1:-1;tx=b.x+sd*(36+Math.floor(i/2)*16);ty=b.y+6+Math.floor(i/2)*8;fa=Math.PI/2+sd*0.55;} else{tx=b.x+(i-nn/2)*18;ty=b.y+26;fa=null;} if(fm2.t<150){f.x+=(tx-f.x)*0.11;f.y+=(ty-f.y)*0.11;} else{f.x+=(ringX-f.x)*0.09;f.y+=(ringY-f.y)*0.09;} // 归位回环绕 if((b.laserT||0)>0)continue; if(fm2.t>=40&&fm2.t<140&&fm2.t%26===0){ const a2=fa===null?Math.atan2(aimPt().y-f.y,aimPt().x-f.x):fa; ebul(f.x,f.y,a2,4.6,"dartS",14);f.flashT=5;} }else{ f.x+=(ringX-f.x)*0.09;f.y+=(ringY-f.y)*0.09; if((b.laserT||0)>0)continue; // 巨宽核心激光期间停火(仍环绕) f.fireT--; if(f.fireT<=0){const a=Math.atan2(aimPt().y-f.y,aimPt().x-f.x); ebul(f.x,f.y,a,4.8,"dartS",15);f.fireT=rndi(70,120);f.flashT=5;} } } } G.funnels=G.funnels.filter(f=>!f.dead); } function startBoss(){ const seq=[0,1,2,3,4,6]; // 闯关顺序:stage6=夺权者X压轴;母巢/魔碟在无尽轮换 const idx=G.mode==="rush"?G.rushIdx:(G.mode==="stage"?seq[Math.min(5,G.stage-1)]:(G.bossN%10)); const cyc=G.mode==="stage"?0:Math.floor(G.bossN/8); const b=BOSSES[idx],n=tierN(); const hpM=(G.diff===1?BAL.hard.hp:1)*(1+0.35*cyc)*(G.mode==="endless"?1+0.55*G.bossN+0.1*G.wave:1+0.12*(G.stage-1))*(G.mode==="rush"?3:1); // Boss挑战模式血量倍数(调平衡改这里) G.boss={idx,x:W/2,y:-60,ty:64,hp:b.hp*hpM,maxhp:b.hp*hpM,r:(b.rr?b.rr[0]:b.r),ry:(b.ryy?b.ryy[0]:undefined),t:0,phase:0,transT:0, col:b.col,name:b.name,sub:b.sub,fireM:BAL.fireMul(n)*(G.diff===1?BAL.hard.fire:1),dead:false, mainT:100,minorT:9999,minorSet:[],rerollT:340,ax:W/2,ay:64,mt:0,landed:false}; if(b.main==="piano")G.boss.mainT=24; // 激光钢琴:缩短开局空窗 G.bossN++;SFX.warn();MUS.start(true); banner("WARNING — "+b.name,"#ff5566",150); } function updateBoss(){ const b=G.boss;if(!b)return; b.t++; if(!b.landed){if(b.yb.phase){b.phase=np;b.transT=70;b.r=(def.rr&&def.rr[np])||b.r;b.ry=(def.ryy&&def.ryy[np])||undefined; G.eb.length=0;G.bbeams.length=0;b.keys=null;SFX.warn();G.shake=Math.max(G.shake,5); if(b.idx===6){G.funnels.length=0;b.form=null;if(np===1){spawnFunnels(b,4);b.plT=Math.round(300/b.fireM);}} // 夺权者X二阶段:增至4架僚机 banner(b.name+" — 第"+["一","二","三"][np]+"形态","#ff5566",90);} if(b.transT>0){b.transT--; if(b.transT===0){explodeFx(b.x,b.y,"dark",1.3);G.shake=Math.max(G.shake,6); for(let i=0;i<10;i++)ebul(b.x,b.y,i/10*TAU,1.5,"orbP",12);} // 变形完成爆发环弹 updateFunnels();return;} // 变形期间不攻击 // —— 冲撞状态机(暗黑突击) —— if(b.dashSt){ const ds=b.dashSt; if(ds.ph==="tele"){ds.t--; if(ds.t<=0){const ap3=aimPt(),a=Math.atan2(ap3.y-b.y,ap3.x-b.x); ds.ph="go";ds.t=85;ds.vx=Math.cos(a)*7.2;ds.vy=Math.sin(a)*7.2;SFX.warn();}} else if(ds.ph==="go"){b.x+=ds.vx;b.y+=ds.vy;ds.t--; if(G.time%9===0){const a5=Math.atan2(aimPt().y-b.y,aimPt().x-b.x);ebul(b.x,b.y,a5,2.7,"orbR",13);} // 冲撞途中瞄准弹 if(ds.t<=0||b.y>H+26||b.y<-46||b.x<-26||b.x>W+26){ds.ph="back";ds.t=160;}} else{b.x+=(b.ax-b.x)*0.045;b.y+=(b.ay-b.y)*0.045;ds.t--; if(ds.t<=0||(Math.abs(b.x-b.ax)<5&&Math.abs(b.y-b.ay)<5)){b.dashSt=null;b.mainT=Math.round(105/b.fireM);}} // 冲撞间隔+50% updateFunnels();return; } // —— 常规移动 —— if(def.static){b.x+=(W/2-b.x)*0.05;b.y+=(52-b.y)*0.02;} else if(b.idx===3){b.x=W/2+Math.sin(b.mt*0.034)*62;b.y=b.ty+Math.sin(b.mt*0.021)*18;} // 暗黑突击:小体型高机动(空闲大幅提速) else{b.x=W/2+Math.sin(b.mt*0.013)*(def.wide?44:70);b.y=b.ty+Math.sin(b.mt*0.011)*(def.wide?8:12);} // 通用游走提速+上下浮动(钢琴站桩除外) b.mt++; const fm=b.fireM*(1+0.22*b.phase); if(b.fnT>0)b.fnT--;if(b.plT>0)b.plT--;if(b.eyeT>0)b.eyeT--;if(b.hornT>0)b.hornT--;if(b.laserT>0)b.laserT--;if(b.eyeGlow>0)b.eyeGlow--;if(b.rainT>0)b.rainT--; // 帧级技能计时器 if(b.idx===1&&b.rainT>0&&b.rainT<94&&b.rainT%3===0){ // 原种红雨:前摇16帧双翼高频抖动后,翼展范围内红光点随机洒落 const span=46*(0.22+0.78*(b.phase>=1?1:0.62)); ebul(b.x+rnd(-span,span),b.y+rnd(-10,2),Math.PI/2+rnd(-0.08,0.08),rnd(2.2,3.4),"orbR",12);} if(b.idx===8){ // 黑石要塞:亚空间主炮序列(纹路充能52帧→巨柱扫动80帧) if(!b.plT||b.plT<=0)b.plT=Math.round((b.phase>=1?380:460)/fm); else{ if(b.plT===142){b.laserT=142;b.warpChg=0.001;SFX.warn();} if(b.plT<=142&&b.plT>90)b.warpChg=Math.min(1,(142-b.plT)/52); if(b.plT===90){b.warpChg=0;bossBeam(b.x,b.y+6,b.x,H+8,b.phase>=1?24:20,"#5cff9d",10,80,30,{b,ox:0,oy:6,followX2:true,sweep:1,amp:b.phase>=1?70:50});} } } if(b.idx===9){ // 泰伦触手扫击:9秒一次左右随机 + 玩家靠近立即触发对应侧 if(b.phase>=1&&!b.tent){const p=G.p; if(p.dead<=0&&Math.abs(p.x-b.x)<52&&p.y>b.y&&p.y0){tn.warn--;if(tn.warn<=0){tn.act=45;tn.hitDone=false;}} else if(tn.act>0){tn.act--; if(!tn.hitDone){ // 扫击+顿挫全期:玩家与触手各节线段做点-线段碰撞 const p=G.p; if(p.dead<=0){const pts=tentSegs(b,tn.side),lx=p.x-b.x,ly=p.y-b.y; for(let i=1;i<7;i++){const f=i/6,sr=3.2*(1-f*0.45)+G.stat.hitR+1; const ax=pts[i-1].x,ay=pts[i-1].y,dx=pts[i].x-ax,dy=pts[i].y-ay,L2=dx*dx+dy*dy||1; const tt=clamp(((lx-ax)*dx+(ly-ay)*dy)/L2,0,1),cx=ax+dx*tt,cy=ay+dy*tt; if(dist2(lx,ly,cx,cy)0){tn.rec--;if(tn.rec<=0)b.tent=null;} } } const lck=(b.laserT||0)>0; // 巨宽核心激光期间压制其他攻击 // —— 主攻击 —— b.mainT--; if(b.mainT<=0&&!lck)bossMain(b,def,fm); // —— 小攻击:定期重抽1~2种 —— b.rerollT--; if(b.rerollT<=0&&!lck){b.rerollT=rndi(340,460); const pool=def.minors,cnt=b.phase>=1?2:1; b.minorSet=[];for(let i=0;i0)b.minorT--; // 暗黑突击:空闲攻击频率翻倍 if(b.minorT<=0&&!lck)for(const mk of b.minorSet)bossMinor(b,mk,fm); updateFunnels(); } /* ---- Boss 主攻击模式 ---- */ function bossMain(b,def,fm){ const ap2=aimPt(),a0=Math.atan2(ap2.y-b.y,ap2.x-b.x),ph=b.phase; switch(def.main){ case "cruiser": // 巡洋舰:扇形主炮 for(let i=-2;i<=2;i++)ebul(b.x+i*12,b.y+16,a0+i*0.13,2.2+0.2*ph,"orbR"); b.mainT=Math.round((ph?52:70)/fm);break; case "spawn": { // 太空原种:花弹从腹末生殖腔喷出(发射点=生殖腔摆动,与drawProgenitor同源公式);P1三波起手偏左 / P2生殖腔血光柱+翼尖花弹 if(ph===0){const swx=Math.sin(b.t*0.055+2.8)*4.8; for(let k=0;k<3;k++)for(let i=-2;i<=2;i++){ const bb=ebul(b.x+swx,b.y+34,a0+i*0.16+(k-1)*0.09+[0.2,0.1,0][k],1.5+k*0.2,"flowr",14); bb.grow=0.007;bb.maxS=2.8;bb.scale=0.9;} b.mainT=Math.round(84/fm); }else{bossBeam(b.x,b.y+40,b.x+clamp(ap2.x-b.x,-50,50)*0.5,H+10,9,"#ff5c8a",30,44,22,{b,ox:0,oy:40}); for(const sd of[-1,1])for(let i=0;i<3;i++){ const bb=ebul(b.x+sd*50,b.y-2,Math.PI/2+sd*(0.35+i*0.2),1.6,"flowr",13); bb.grow=0.008;bb.maxS=3;bb.scale=0.9;} b.mainT=Math.round(130/fm);} break;} case "annihilate": { // 歼灭者:螺旋臂 const arms=2+ph; for(let arm=0;arm0){ks.hotT--; if(ks.hotT===0){const list=ks.hotList||[ks.hot]; for(const kh of list){if(kh<0)continue;const kx=-(n-1)*5+kh*10; // 与drawPiano键位一致(整数键距10) bossBeam(b.x+kx,b.y+18,b.x+kx,H+8,4,"#43e8ff",7,22,18,{b,ox:kx,oy:18,followX2:true});} ks.hotList=null;ks.hot=-1;SFX.eshoot();}} ks.t--; if(ks.t<=0&&ks.hotT<=0){ if(ks.i>=ks.seq.length){ // 布下一局:轮换模式 ks.mode=(ks.mode+1)%4; if(ks.mode===0){ks.seq=[];for(let i=0;i=3;i--)ks.seq.push(i);} // 反扫止于3号:最左3键永远安全 else if(ks.mode===2){ks.seq=shuffled(n);} else{ks.seq=[];for(let i=0;i<6+ph*2;i++)ks.seq.push(-1);} // -1=齐射标记 ks.i=0;ks.t=Math.round((ph?14:22)/fm); }else{const cur=ks.seq[ks.i++]; if(cur===-1){const pool=shuffled(n);ks.hotList=pool.slice(0,4+ph*2);ks.hotT=9;ks.hotT0=9;ks.t=Math.round((ph?21:30)/fm);} // 齐射:慢50% else if(ks.mode<=1){ks.hot=cur;ks.hotT=1;ks.hotT0=1;ks.t=Math.round((ph?2:3)/fm);} // 顺/反扫:前摇快50% else{ks.hot=cur;ks.hotT=3;ks.hotT0=3;ks.t=Math.round((ph?3:5)/fm);} // 乱序:慢50% } } b.mainT=4;break;} case "blackstone": { // 黑石要塞:五重旋转花瓣弹幕(往复旋摆);签名亚空间主炮见updateBoss帧级序列 const base=Math.sin(b.t*0.008)*1.1; for(let k=0;k<5;k++)ebul(b.x,b.y,k*TAU/5+base,1.55,"orbG",13); if(ph>=1)for(let k=0;k<5;k++)ebul(b.x,b.y,k*TAU/5-base+TAU/10,1.55,"orbG",13); // P2双层反向编织 b.mainT=Math.round((ph>=1?38:46)/fm);break;} case "hivespawn": { // 泰伦母舰:从体内向两侧排出虫群(无弹幕);P2虫群血量变厚;P3嘴吐骨钉扇 b.wvC=(b.wvC||0)+1; const big=b.wvC%4===0,n1=big?7+ph*2:3+ph; if(b.wvC%2===0)for(let k=-2;k<=2;k++)ebul(b.x,b.y+26,Math.PI/2+k*0.22,0.95,"orbG",10); // 间歇喷孢子(慢速散弹,威胁不大) for(let i=0;i=1){e.hp*=1.5;e.maxhp*=1.5;} // P2起虫群血量变厚 spawnPart(b.x+sd*24,b.y+8,"#a8ff5c",2);} b.mainT=Math.round((big?210:125)/fm); if(ph>=2){for(let k=-2;k<=2;k++)ebul(b.x,b.y+30,Math.PI/2+k*0.14,2.6,"spikeB",14); // 嘴吐骨钉 b.mainT=Math.round((big?230:150)/fm);} break;} case "hive": // 母巢核心:环弹+孵化 for(let i=0;i<10+ph*4;i++)ebul(b.x,b.y,i/(10+ph*4)*TAU+b.t*0.03,1.6+0.2*ph,"orbO"); if(b.t%240<3)for(let i=0;i<1+ph;i++)spawnEnemy(ph>=2?"kami":"mini",b.x+rnd(-30,30),b.y+8,false); b.mainT=Math.round(Math.max(34,70-8*ph)/fm);break; case "usurper": // 夺权者X:蓝白长钉无人机(P1两架/P2四架僚机);P2周期性巨宽核心激光(压制其他攻击) if(ph===0){ if(!b.fn0){b.fn0=1;spawnFunnels(b,2);} for(const sd of[-1,1]){const px2=b.x+sd*37,py2=b.y-4,a6=Math.atan2(ap2.y-py2,ap2.x-px2); ebul(px2,py2,a6,4.4,"dartS",15);} b.mainT=Math.round(46/fm); }else{ if(!b.plT||b.plT<=0){bossBeam(b.x,b.y+20,b.x,H+8,22,"#7ce9ff",46,80,30,{b,ox:0,oy:20,followX2:true}); b.laserT=130;b.plT=Math.round(420/fm);b.mainT=40;SFX.warn();} else{for(let k=-1;k<=1;k++)ebul(b.x,b.y+30,a0+k*0.14,4.4,"dartS",16); b.mainT=Math.round(88/fm);} } break; case "saucer": { // 暗黑魔碟:高密度锥形弹环 + P2起双眼激光(跟随) + P3犄角炮台 const n2=12+ph*5; for(let i=0;i=2?24:ph===1?30:36)/fm); if(ph>=1&&(!b.eyeT||b.eyeT<=0)){const ex=ap2.x; for(const sd of[-1,1])bossBeam(b.x+sd*9,b.y-3,ex+sd*6,H+8,4,"#ff3355",22,26,20,{b,ox:sd*9,oy:-3}); b.eyeT=Math.round(150/fm);} if(ph>=2&&b.hornT<=0){const sd=(b.hornSide=(b.hornSide||1)*-1); const hx=b.x+sd*41,hy=b.y-33,ha=Math.atan2(ap2.y-hy,ap2.x-hx); for(let k=-1;k<=1;k++)ebul(hx,hy,ha+k*0.16,2.8,"orbR",15); b.hornT=Math.round(46/fm);} break;} } } /* ---- Boss 小攻击库 ---- */ function bossMinor(b,mk,fm){ const ap2=aimPt(),a0=Math.atan2(ap2.y-b.y,ap2.x-b.x); switch(mk){ case "snipe": for(let k=0;k<3;k++)ebul(b.x,b.y+10,a0+(k-1)*0.06,3.2,"orbR",14);b.minorT=Math.round(150/fm);break; case "ring": for(let i=0;i<12;i++)ebul(b.x,b.y,i/12*TAU+b.t*0.02,1.6,"orbG",14);b.minorT=Math.round(190/fm);break; case "wiggle": for(const sd of[-1,1])for(let i=0;i<4;i++){const bb=ebul(b.x+sd*10,b.y+6,Math.PI/2+sd*0.55-i*sd*0.14,1.5,"flowr",13);bb.grow=0.008;bb.maxS=2.6;bb.scale=0.85;}b.minorT=Math.round(150/fm);break; case "spikeSnipe": for(let k=0;k<3;k++)ebul(b.x,b.y-16,a0+(k-1)*0.12,3.4,"spikeB",15);b.minorT=Math.round(170/fm);break; // 原种:大颚骨钉三连狙(白色骨钉高速直线,非花弹) case "wingRain": b.rainT=110;b.minorT=Math.round(300/fm);break; // 原种:抖翅红雨——双翼高频抖动前摇后,翼展范围内红色光点如雨随机洒落 case "spawnMini": for(let i=0;i<2;i++)spawnEnemy(pick(["scout","mini"]),b.x+rnd(-26,26),b.y+10,false);b.minorT=Math.round(250/fm);break; case "spawnMine": spawnEnemy("mine",b.x,b.y+10,false);b.minorT=Math.round(280/fm);break; case "aimSpread": for(let i=-2;i<=2;i++)ebul(b.x,b.y+12,a0+i*0.19,2.5,"orbO",15);b.minorT=Math.round(140/fm);break; case "rain": for(let i=0;i<5;i++)ebul(rnd(20,W-20),-6,Math.PI/2+rnd(-0.1,0.1),2.2,"orbO",14);b.minorT=Math.round(125/fm);break; case "topRain": for(let i=0;i<4;i++)ebul(rnd(30,W-30),-6,Math.PI/2+rnd(-0.15,0.15),1.9,"orbG",13);b.minorT=Math.round(150/fm);break; case "chain": for(let arm=0;arm<2;arm++)for(let i=0;i<4;i++)ebul(b.x,b.y+8,b.t*0.09+arm*Math.PI+i*0.11,1.7,"orbO",14);b.minorT=Math.round(44/fm);break; case "pillar": {const px2=rnd(40,W-40);bossBeam(px2,-6,px2,H+8,4,"#b98cff",26,26,18);b.minorT=Math.round(220/fm);break;} case "eyeBeam": bossBeam(b.x,b.y+6,ap2.x,H+8,4,"#ff3355",22,24,20);b.minorT=Math.round(180/fm);break; case "ringBig": for(let i=0;i<18;i++)ebul(b.x,b.y,i/18*TAU-b.t*0.03,1.8,"orbP",15);b.minorT=Math.round(230/fm);break; case "sporeArc": for(let i=0;i<7;i++)ebul(b.x,b.y+14,Math.PI/2+(i-3)*0.26,1.2,"orbG",13);b.minorT=Math.round(210/fm);break; // 原种:慢速孢子幕(绿,与花弹差异化) case "tentBeam": { // 原种:双触手鞭击光束——从头侧管囊伸出,锚定相位从垂直向下逐帧向外鞭展(sweep连续判定,扫到哪判定到哪) const ph0=-G.time*0.014; for(const sd of[-1,1])bossBeam(b.x+sd*15,b.y-16,b.x+sd*15,H+8,3,"#ff5c8a",26,74,16,{b,ox:sd*15,oy:-16,sweep:1,amp:sd*42,swPh:ph0}); b.minorT=Math.round(280/fm);break;} case "mawSnipe": { // 原种:颚部四联狙击——双大颚+头侧管囊齐射快速红点弹(发射点与drawProgenitor同源;击发后大颚+感器缝闪白余辉) const MP=[[-5,-22],[5,-22],[-15,-16],[15,-16]]; for(let e=0;e<4;e++){const ex2=b.x+MP[e][0],ey2=b.y+MP[e][1]; const ea=Math.atan2(ap2.y-ey2,ap2.x-ex2); ebul(ex2,ey2,ea+(e-1.5)*0.04,3.6,"orbR",14);} b.eyeGlow=20;b.minorT=Math.round(200/fm);break;} case "coneFan": for(let i=-2;i<=2;i++)ebul(b.x,b.y+8,a0+i*0.15,2.9,"dartP",14);b.minorT=Math.round(150/fm);break; // 魔碟:瞄准锥扇 case "crossEye": for(const sd of[-1,1])bossBeam(b.x+sd*9,b.y-3,ap2.x-sd*26,H+8,3.5,"#ff3355",22,24,18,{b,ox:sd*9,oy:-3});b.minorT=Math.round(230/fm);break; // 魔碟:交叉赤瞳(斜线X) case "dartSpiral": for(let arm=0;arm<3;arm++)ebul(b.x,b.y,b.t*0.11+arm*TAU/3,2.0,"dartP",13);b.minorT=Math.round(46/fm);break; // 魔碟:三臂锥弹螺旋 case "deckGuns": { // 黑石:甲板炮群——五臂侧舷齐射交叉火力网 const rt=b.t*0.0032; for(let k=0;k<5;k++){const a=rt+k*TAU/5,gx=b.x+Math.sin(a)*22,gy=b.y-Math.cos(a)*22; for(const sd of[-1,1])ebul(gx+Math.cos(a)*sd*8,gy+Math.sin(a)*sd*8,Math.PI/2+Math.sin(a)*0.7+sd*0.3,1.5,"orbG",12);} b.minorT=Math.round(225/fm);break;} case "crackRift": // 黑石:亚空间裂隙——现身预瞄,延迟变长钉射出 for(let i=0;i<3+b.phase*2;i++){const rb=ebul(clamp(ap2.x+rnd(-70,70),16,W-16),clamp(ap2.y+rnd(-60,30),H*0.32,H-30),0,0,"orbG",14); rb.rift=rndi(26,44);rb.scale=1.5;rb.r=4;} b.minorT=Math.round(255/fm);break; case "starLine": { // 黑石:星芒脉冲——十向高速弹线,交替偏相半格 b.stC=(b.stC||0)+1;const off=(b.stC%2)*(TAU/20); for(let k=0;k<10;k++)ebul(b.x,b.y,k*TAU/10+off,3.1,"orbG",13); b.minorT=Math.round(215/fm);break;} case "sporeBloom": // 泰伦:孢子囊投放(慢飘碍走位,死亡酸液环爆) for(let i=0;i<2+b.phase;i++)spawnEnemy("sporeT",b.x+rnd(-20,20),b.y+rnd(2,16),false); b.minorT=Math.round(280/fm);break; case "tentSweep": // 泰伦:触手扫击(P2+):随机一侧触手,前摇后拉蓄力→60°扇形挥鞭 if(b.phase<1){b.minorT=80;break;} if(!b.tent){b.tent={side:pick([-1,1]),warn:50,act:0,rec:0,trail:null};SFX.warn();} b.minorT=Math.round(540/fm);break; // 自动扫击:9秒一次 case "acidGas": // 泰伦:酸蚀气体(P3):触手末端释放毒雾向下飘 if(b.phase<2){b.minorT=80;break;} for(const sd of[-1,1])G.zones.push({x:b.x+sd*16,y:b.y+30,r:19,vy:0.75,life:520,col:"#a8ff5c",kind:"gas"}); b.minorT=Math.round(240/fm);break; } } function hurtBoss(dmg){ const b=G.boss;if(!b||b.dead||b.transT>0||!b.landed)return; // 入场未到位/变形转场期间不掉血(landed锁:防站桩Boss爬升后永远满足 y0&&b.hp{if(G&&UI==="play"&&G.lives>0)offerCards(true);},900); if(G.mode==="rush"){ // Boss挑战:记录最快击破,返回选择界面 const ct=Math.round((G.time-(G.rushT0||0))/60); SAVE.rush=SAVE.rush||{}; const best=SAVE.rush[b.idx]; if(!best||ct{if(!G||G.lives<=0)return;buildRush();UI="rush";show("o-rush");},1500); } else if(G.mode==="stage"){ if(G.stage>=6){setTimeout(()=>{if(G&&UI!=="over")endRun(true);},1600);} else setTimeout(()=>{if(!G||UI==="over"||G.lives<=0)return;G.stage++;G.wave=0;SAVE.best.stage=Math.max(SAVE.best.stage,G.stage-1);persist();banner("STAGE "+G.stage+" — "+STAGE_NAMES[G.stage-1],"#41e6ff",140);startWave();},2400); } } const STAGE_NAMES=["碎石边境","血色星云","钢铁舰队","死寂空间站","虫群巢穴","核心决战"];