- 8个Boss全部重做:主攻击模式+1~2种随机小攻击+二/三阶段变形动画 - 新增5个雷霆战机参考Boss:太空原种/暗黑突击/激光钢琴/夺权者X/暗黑魔碟 - 多管血条(蓝/红/橙)+锁血转场+入场不掉血(b.y<b.ty-1免疫) - 签名技能:花弹成长/高机动瞄准弹/21键4模式激光/银钉无人机+巨宽激光压制/锥形弹环+眼激光 - 所有激光发射点跟随Boss移动(bossBeam src机制) - Boss挑战模式:随机装备3主武Lv3+3副武Lv3+2僚机Lv2,可选老虎机抽奖动画 - rush模式Boss血量x10,击破计时存档 - 武器修正:黎明重炮近炸引信26px/死光伤害x2/椭圆判定bossHit等
386 lines
23 KiB
JavaScript
386 lines
23 KiB
JavaScript
"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}
|
||
};
|
||
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);
|
||
}
|
||
/* ---- 敌机行为 ---- */
|
||
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.y<e.hy)e.y+=e.spd*1.6*spM; else e.x=clamp(e.x0+Math.sin(e.t*0.015+e.ph)*8,10,W-10); break;
|
||
case "spin": e.y+=e.spd*spM; e.x+=e.vx*spM; e.rot+=0.03; break;
|
||
}
|
||
// 开火(独立计时器,与摆动相位完全分离)
|
||
if(e.fire&&e.y>6&&e.y<H-60){
|
||
e.ft+=spM;
|
||
if(e.ft>e.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);}
|
||
}
|
||
/* ================= 突变词缀 ================= */
|
||
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&&shooterN<shooterCap&&Math.random()<0.32){type=pick(shooters);shooterN++;}
|
||
else type=pick(fodder.length?fodder:shooters);
|
||
const d=ENEMIES[type];
|
||
const form=pick(["line","v","col","scatter","pincer"]);
|
||
const cnt=Math.min(rndi(2,4),Math.max(1,Math.floor(budget/d.cost)));
|
||
const elite=Math.random()<eC;
|
||
placeFormation(form,type,cnt,delay,elite);
|
||
budget-=d.cost*cnt*(elite?3:1);
|
||
delay+=rndi(55,110);
|
||
}
|
||
if(G.mutator==="SWARM")for(let i=0;i<8;i++)G.spawning.push({type:"mini",x:rnd(15,W-15),y:-10-i*8,delay:60+i*25});
|
||
if(G.mutator==="METEOR")for(let i=0;i<6;i++)G.spawning.push({type:"rock",x:rnd(10,W-10),y:-14,delay:40+i*70});
|
||
const mn=G.mutator?" · "+MUTS[G.mutator].name:"";
|
||
banner((G.mode==="endless"?"WAVE "+G.wave:"第 "+G.stage+"-"+G.wave+" 波")+mn, G.mutator?MUTS[G.mutator].col:"#41e6ff");
|
||
}
|
||
function placeFormation(form,type,cnt,delay,elite){
|
||
const xs=[];
|
||
if(form==="line")for(let i=0;i<cnt;i++)xs.push(W*(i+1)/(cnt+1));
|
||
else if(form==="v")for(let i=0;i<cnt;i++)xs.push(W/2+(i-(cnt-1)/2)*34);
|
||
else if(form==="col")for(let i=0;i<cnt;i++)xs.push(rnd(40,W-40));
|
||
else if(form==="pincer")for(let i=0;i<cnt;i++)xs.push(i%2?rnd(15,50):rnd(W-50,W-15));
|
||
else for(let i=0;i<cnt;i++)xs.push(rnd(20,W-20));
|
||
xs.forEach((x,i)=>G.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","spawnMini","flowerSnipe"],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","rain"],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*dy<c*c;
|
||
}
|
||
function shuffled(n){const a=[];for(let i=0;i<n;i++)a.push(i);for(let i=n-1;i>0;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;i<n;i++)
|
||
G.funnels.push({x:b.x,y:b.y,sa:i/n*TAU+0.5,t:rndi(0,40),fireT:rndi(50,110)+i*18,mode:i%2?"aim":"scatter",flashT:0});
|
||
}
|
||
function updateFunnels(){
|
||
const b=G.boss;
|
||
for(const f of G.funnels){
|
||
f.t++;if(f.flashT>0)f.flashT--;
|
||
if(b&&!b.dead){f.sa+=0.014;
|
||
f.x+=(b.x+Math.cos(f.sa)*46-f.x)*0.09;
|
||
f.y+=(b.y+16+Math.sin(f.sa)*20-f.y)*0.09;}
|
||
if(b&&b.laserT>0)continue; // 巨宽核心激光期间僚机停火(仍环绕)
|
||
f.fireT--;
|
||
if(f.fireT<=0){ // 银色高弹速长钉:瞄准单发 / 三发散射 轮换
|
||
const ap=aimPt(),a=Math.atan2(ap.y-f.y,ap.x-f.x);
|
||
if(f.mode==="aim"){ebul(f.x,f.y,a,4.8,"dartS",15);f.mode="scatter";f.fireT=rndi(64,96);}
|
||
else{for(let k=-1;k<=1;k++)ebul(f.x,f.y,a+k*0.24,3.9,"dartS",13);f.mode="aim";f.fireT=rndi(100,140);}
|
||
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%8));
|
||
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"?10:1); // Boss挑战模式血量×10
|
||
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};
|
||
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.y<b.ty){b.y+=1.2;b.x=W/2;return;}
|
||
if(b.arr===undefined)b.arr=b.t; // 降落结束时刻,正弦从0起振,杜绝瞬移
|
||
const def=BOSSES[b.idx];
|
||
// —— 阶段检测与变形(锁血转场:清弹+停火+播动画) ——
|
||
const ratio=b.hp/b.maxhp,thr=def.phases===2?[0.5]:[0.66,0.33];
|
||
let np=0;for(let i=0;i<thr.length;i++)if(ratio<=thr[i])np=i+1;
|
||
if(np>b.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;if(np===1){spawnFunnels(b,2);b.plT=Math.round(300/b.fireM);}} // 夺权者X二阶段:换为2架僚机
|
||
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(70/b.fireM);}}
|
||
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.t-b.arr)*0.021)*62;b.y=b.ty+Math.sin((b.t-b.arr)*0.013)*15;} // 暗黑突击:小体型高机动
|
||
else b.x=W/2+Math.sin((b.t-b.arr)*0.008)*(def.wide?44:70); // 宽体Boss减小游走幅度,防止巨翼出屏
|
||
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--; // 帧级技能计时器
|
||
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;i<cnt&&pool.length;i++)b.minorSet.push(pool[rndi(0,pool.length-1)]);
|
||
b.minorT=50;}
|
||
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": // 太空原种:花弹(圆形八刺,越变越大)P1三波瞄准 / P2腹柱+翼尖花弹
|
||
if(ph===0){for(let k=0;k<3;k++)for(let i=-2;i<=2;i++){
|
||
const bb=ebul(b.x,b.y+8+k*4,a0+i*0.16+(k-1)*0.09,1.5+k*0.2,"flowr",14);
|
||
bb.grow=0.007;bb.maxS=2.8;bb.scale=0.9;}
|
||
b.mainT=Math.round(76/fm);
|
||
}else{bossBeam(b.x,b.y+10,b.x+clamp(ap2.x-b.x,-50,50)*0.5,H+10,7,"#ff8fc8",30,44,22,{b,ox:0,oy:10});
|
||
for(const sd of[-1,1])for(let i=0;i<3;i++){
|
||
const bb=ebul(b.x+sd*52,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;arm<arms;arm++)for(let i=0;i<2;i++)ebul(b.x,b.y,b.t*0.06+arm*(TAU/arms)+i*0.14,2+0.2*ph,"orbP");
|
||
b.mainT=Math.round(10/fm);break;}
|
||
case "assault": // 暗黑突击:启动冲撞
|
||
b.dashSt={ph:"tele",t:52};b.mainT=9999;break;
|
||
case "piano": { // 激光钢琴:21键,四种模式轮换——顺扫(逼走位)/反扫/乱序/齐射
|
||
if(!b.keys)b.keys={n:21,seq:[],i:0,t:40,hot:-1,hotT:0,hotList:null,mode:3};
|
||
const ks=b.keys,n=ks.n;
|
||
if(ks.hotT>0){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)*4.75+kh*9.5;
|
||
bossBeam(b.x+kx,b.y+18,b.x+kx,H+8,4,"#43e8ff",12,20,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<n;i++)ks.seq.push(i);}
|
||
else if(ks.mode===1){ks.seq=[];for(let i=n-1;i>=0;i--)ks.seq.push(i);}
|
||
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?36:56)/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=16;ks.t=Math.round((ph?34:50)/fm);}
|
||
else{ks.hot=cur;ks.hotT=7;ks.t=Math.round((ph?3:5)/fm);} // 扫射模式:快速连发
|
||
}
|
||
}
|
||
b.mainT=6;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,4);}
|
||
for(const sd of[-1,1]){const px2=b.x+sd*55,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<n2;i++){const ea=(b.rotA||0)+i/n2*TAU;
|
||
ebul(b.x+Math.cos(ea)*30,b.y+Math.sin(ea)*30,ea,1.9+0.15*ph,"dartP",15);}
|
||
b.rotA=(b.rotA||0)+0.22;
|
||
b.mainT=Math.round((ph>=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 "flowerSnipe": for(let k=0;k<3;k++){const bb=ebul(b.x,b.y+10,a0+(k-1)*0.12,2.6,"flowr",15);bb.grow=0.006;bb.maxS=2.4;bb.scale=0.9;}b.minorT=Math.round(170/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;
|
||
}
|
||
}
|
||
function hurtBoss(dmg){
|
||
const b=G.boss;if(!b||b.dead||b.transT>0||b.y<b.ty-1)return; // 入场未到位/变形转场期间不掉血
|
||
const def=BOSSES[b.idx],thr=def.phases===2?[0.5]:[0.66,0.33];
|
||
const floor=b.phase<thr.length?thr[b.phase]:0; // 当前血管的底
|
||
b.hp-=dmg;b.flash=2;
|
||
if(floor>0&&b.hp<b.maxhp*floor)b.hp=b.maxhp*floor; // 锁血:单管打空即停,等待转场
|
||
if(Math.random()<0.3)spawnPart(b.x+rnd(-18,18),b.y+rnd(-10,10),"#fff",2);
|
||
if(b.hp<=0){b.dead=true;killBoss();}
|
||
}
|
||
function killBoss(){
|
||
const b=G.boss;
|
||
SFX.boom(true);G.shake=16;G.flash=20;
|
||
for(let i=0;i<40;i++)spawnPart(b.x+rnd(-24,24),b.y+rnd(-14,14),pick(["#ffcf5c","#ff5566","#fff","#ff9a3d"]),3);
|
||
addScore(5000*(1+G.bossN*0.5),b.x,b.y);
|
||
addCoins(rndi(30,50));
|
||
for(let i=0;i<12;i++)dropPick(b.x+rnd(-30,30),b.y+rnd(-10,10),"gem");
|
||
dropPick(b.x-14,b.y,"heart");dropPick(b.x+14,b.y,"odc");
|
||
banner("击坠 "+b.name+"!","#ffcf5c",120);
|
||
G.boss=null;MUS.start(false);
|
||
G.eb.length=0;G.bbeams.length=0;G.funnels.length=0;
|
||
// Boss 必掉稀有以上三选一(仅当局仍在进行时才弹出;挑战模式不打断)
|
||
if(G.mode!=="rush")setTimeout(()=>{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<best){SAVE.rush[b.idx]=ct;persist();}
|
||
setTimeout(()=>{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=["碎石边境","血色星云","钢铁舰队","死寂空间站","虫群巢穴","核心决战"];
|