- 顺扫/反扫/顺滑奏/反滑奏开局前蓄力从1秒改为0.3秒(60→18帧) - 乱序模式:每次2个随机键+2个玩家正上方最近键(P2额外+2随机=6键) - 正上方键取法:round((玩家相对x+100)/10)得最近键,次近键取相邻方向
603 lines
42 KiB
JavaScript
603 lines
42 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},
|
||
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.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;
|
||
case "sidle": if(e.sidleT===undefined)e.sidleT=30; // 泰伦:从母舰体内向两侧排出,随后扑向玩家
|
||
if(e.sidleT>0){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.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);}
|
||
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=[];
|
||
if(typeof maybeSaveRun==="function")maybeSaveRun(); // 无尽模式:波次检查点上传云端(内部已做模式/登录门控)
|
||
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种随机小攻击;血量阈值触发二/三阶段变形 */
|
||
/* 难度tier:0简单/1中等/2困难/3地狱(rush界面分组;用户2026-08-06分级) */
|
||
const BOSSES=[
|
||
{name:"巡逻巡洋舰",sub:"PATROL CRUISER",hp:950,col:"#e0393e",r:22,main:"cruiser",minors:["snipe","ring"],phases:2,tier:0},
|
||
{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,tier:1},
|
||
{name:"歼灭者",sub:"ANNIHILATOR",hp:1700,col:"#b05ce8",r:22,main:"annihilate",minors:["aimSpread","spawnMine"],phases:3,tier:0},
|
||
{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,tier:2},
|
||
{name:"激光钢琴",sub:"LASER PIANO",hp:5000,col:"#43e8ff",r:26,rr:[100,100],ryy:[30,30],main:"piano",minors:["topRain","pedal"],phases:2,static:1,tier:1},
|
||
{name:"母巢核心",sub:"HIVE CORE",hp:2800,col:"#ff9a3d",r:22,main:"hive",minors:["spawnMini","aimSpread","ring"],phases:3,tier:0},
|
||
{name:"夺权者X",sub:"USURPER X",hp:3700,col:"#e8f4ff",r:24,rr:[58,26],ryy:[34,40],wide:1,main:"usurper",minors:["deckVolley","pillar","aimSpread"],phases:2,tier:2},
|
||
{name:"暗黑魔碟",sub:"DARK SAUCER",hp:4500,col:"#8a5cff",r:24,rr:[30,32,34],main:"saucer",minors:["coneFan","crossEye","dartSpiral"],phases:3,tier:3}, // 间歇期无圆圈弹幕(去ringBig,P3主攻击仍两层)
|
||
{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,tier: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,tier:1}
|
||
];
|
||
/* 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,slot:i,flashT:0});
|
||
}
|
||
function updateFunnels(){
|
||
const b=G.boss,n=G.funnels.length;
|
||
if(b&&!b.dead&&n){
|
||
if(b.formT===undefined)b.formT=rndi(340,520);
|
||
if(b.form){b.form.t++;if(b.form.t>170)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){
|
||
if(fm2.pat===2){const ap4=aimPt();bossBeam(f.x,f.y,ap4.x,ap4.y,2.5,"#7ce9ff",18,14,10);} // 集火阵型改细光束
|
||
else{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; // 激光钢琴:缩短开局空窗
|
||
else if(b.main==="assault")G.boss.mainT=rndi(240,600); // 暗黑突击:首次冲撞4~10秒随机
|
||
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.y<b.ty){b.y+=1.2;b.x=W/2;return;}b.landed=true;} // 入场只跑一次:到位后锁定,防正弦浮沉跌回触发重置
|
||
// b.mt:移动时钟,只在常规移动时推进——冲撞/变身期间冻结,恢复时位置连续不瞬移
|
||
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;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--;b.dashTrail=null; // 变形期间清冲撞残影
|
||
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"){b.dashTrail=null;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)*(b.phase>=2?10.8:7.2);ds.vy=Math.sin(a)*(b.phase>=2?10.8:7.2);SFX.warn();}} // 三阶段冲撞速度150%
|
||
else if(ds.ph==="go"){b.x+=ds.vx;b.y+=ds.vy;ds.t--;
|
||
if(b.phase>=1){const lt=b.dashTrail&&b.dashTrail.length?b.dashTrail[b.dashTrail.length-1]:null; // 二三阶段:冲锋红残影(按距离采样防重叠)
|
||
if(!lt||dist2(b.x,b.y,lt.x,lt.y)>144){b.dashTrail=b.dashTrail||[];b.dashTrail.push({x:b.x,y:b.y});if(b.dashTrail.length>8)b.dashTrail.shift();}}
|
||
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=b.phase>=2?90:160;}}
|
||
else{b.dashTrail=null;
|
||
const k2=b.phase>=2?0.12:0.045; // 三阶段回位更快=惯性更小
|
||
b.x+=(b.ax-b.x)*k2;b.y+=(b.ay-b.y)*k2;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=b.phase>=2?rndi(240,480):rndi(240,600);}} // 冲撞间隔4~10秒,三阶段4~8秒
|
||
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){ // 暗黑突击:小体型高机动;三阶段更快+上下范围更大+惯性更小(速度积分,转折锐利)
|
||
if(b.phase>=2){b.x=clamp(b.x+62*0.052*Math.cos(b.mt*0.052),26,W-26);b.y=b.ty+Math.sin(b.mt*0.03)*27;}
|
||
else{b.x=W/2+Math.sin(b.mt*0.034)*62;b.y=b.ty+Math.sin(b.mt*0.021)*18;}}
|
||
else if(b.idx===6){const usp=b.phase>=1?0.028:0.008,amp=b.phase>=1?70:44;
|
||
b.x=clamp(b.x+amp*usp*Math.cos(b.mt*usp),30,W-30); // 速度积分定位:换阶段参数突变也不瞬移
|
||
b.yA=(b.yA||8)+((b.phase>=1?12:8)-(b.yA||8))*0.05;b.y=b.ty+Math.sin(b.mt*0.011)*b.yA;} // 夺权者X:P1巨大笨重→P2紧凑灵活
|
||
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.turrT>0)b.turrT--;if(b.pedT>0)b.pedT--; // 帧级技能计时器
|
||
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===6&&b.deckT>0){b.deckT--; // 夺权者X:甲板齐射——炮口逐个点亮(前摇26帧)后6发垂直弹雨
|
||
if(b.deckT===0)for(const sd of[-1,1])for(const gx of[50,56,62])ebul(b.x+sd*gx,b.y+8,Math.PI/2,2.6,"dartB",14);}
|
||
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,99999,{b,ox:0,oy:6,followX2:true,sweep:1,amp:b.phase>=1?70:50});} // 亚空间主炮:99999伤害
|
||
}
|
||
}
|
||
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.y<b.y+95){
|
||
b.tent={side:p.x<b.x?-1:1,warn:50,act:0,rec:0,trail:null};SFX.warn();}}
|
||
if(b.tent){const tn=b.tent; // 前摇→横扫→顿挫→收回;触手即碰撞体
|
||
if(tn.warn>0){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)<sr*sr){tn.hitDone=true;hitPlayer(99999);break;}}} // 触手抽打:99999伤害
|
||
}
|
||
if(tn.act<=0)tn.rec=40;
|
||
}
|
||
else if(tn.rec>0){tn.rec--;if(tn.rec<=0)b.tent=null;}
|
||
}
|
||
}
|
||
const lck=(b.laserT||0)>0; // 巨宽核心激光期间压制其他攻击
|
||
// 钢琴:大炮塔惩罚激光——玩家站在炮塔水平正下方蓄力1秒后发射99999伤害大激光(不管上下远近,只管左右)
|
||
if(b.idx===4&&!lck){
|
||
const off0=b.phase>=1?16:0,p=G.p;
|
||
const leftX=b.x-(112+off0),rightX=b.x+(112+off0);
|
||
if(p.dead<=0&&Math.abs(p.x-leftX)<14){
|
||
b.pedLasL=(b.pedLasL||0)+1;
|
||
if(b.pedLasL>=60){bossBeam(leftX,b.y+10,leftX,H+8,6,"#ff3355",10,40,99999,{b,ox:-(112+off0),oy:10,followX2:true});b.pedLasL=0;SFX.warn();}
|
||
}else b.pedLasL=0;
|
||
if(p.dead<=0&&Math.abs(p.x-rightX)<14){
|
||
b.pedLasR=(b.pedLasR||0)+1;
|
||
if(b.pedLasR>=60){bossBeam(rightX,b.y+10,rightX,H+8,6,"#ff3355",10,40,99999,{b,ox:(112+off0),oy:10,followX2:true});b.pedLasR=0;SFX.warn();}
|
||
}else b.pedLasR=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.idx===3&&b.minorT>0)b.minorT--; // 暗黑突击:空闲攻击频率翻倍
|
||
if(b.minorT<=0&&!lck&&!(b.rainT>0))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(b.rainT>0){b.mainT=24;break;} // 红雨期间花弹全停(不然躲不了)
|
||
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;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.ax=b.x;b.ay=b.y;b.mainT=9999;break;
|
||
case "piano": { // 激光钢琴:21键,六种模式全部乱序随机,局间无间隔
|
||
if(!b.keys)b.keys={n:21,seq:[],i:0,t:2,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)*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){ // 布下一局:完全随机选模式(6种),局间无间隔
|
||
ks.mode=rndi(0,6);
|
||
if(ks.mode===0){ks.seq=[];for(let i=0;i<n-3;i++)ks.seq.push(i);} // 顺扫:0→17,最右3键安全
|
||
else if(ks.mode===1){ks.seq=[];for(let i=n-1;i>=3;i--)ks.seq.push(i);} // 反扫:20→3,最左3键安全
|
||
else if(ks.mode===2){ks.seq=[];for(let i=0;i<n-3;i++)ks.seq.push(i);for(let i=n-4;i>=0;i--)ks.seq.push(i);} // 顺滑奏:0→17→0,最右3键安全
|
||
else if(ks.mode===3){ks.seq=[];for(let i=n-1;i>=3;i--)ks.seq.push(i);for(let i=3;i<n;i++)ks.seq.push(i);} // 反滑奏:20→3→20,最左3键安全
|
||
else if(ks.mode===4){ks.seq=[];for(let i=0;i<5+ph*2;i++)ks.seq.push(-2);} // 乱序:每次同时随机4/8键,发射5+ph*2次
|
||
else if(ph>=1){ks.seq=[];for(let i=0;i<6+ph*2;i++)ks.seq.push(-1);} // 齐射P2:保持原逻辑(随机6键,多次)
|
||
else{ks.seq=[-3];} // 齐射P1:4连不射其余全射,只发射一次
|
||
ks.i=0;ks.t=ks.mode<=3?Math.round(18/fm):0; // 扫/滑四种模式开局前蓄力0.3秒,其余无间隔
|
||
}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);} // 齐射P2
|
||
else if(cur===-2){ // 乱序:2个随机+2个玩家正上方
|
||
const p=G.p,base=-(n-1)*5,relX=p.x-b.x;
|
||
const kNear=clamp(Math.round((relX-base)/10),0,n-1);
|
||
const kNear2=clamp(kNear+(relX<base+kNear*10?-1:1),0,n-1);
|
||
const pool=shuffled(n).filter(k=>k!==kNear&&k!==kNear2);
|
||
ks.hotList=[kNear,kNear2].concat(pool.slice(0,2+ph*2));
|
||
ks.hotT=9;ks.hotT0=9;ks.t=Math.round((ph?7:10)/fm);}
|
||
else if(cur===-3){const safeStart=rndi(0,n-4);const all=[];for(let i=0;i<n;i++)if(i<safeStart||i>=safeStart+4)all.push(i);ks.hotList=all;ks.hotT=9;ks.hotT0=9;ks.t=Math.round((ph?10:15)/fm);} // 齐射P1:4连不射
|
||
else{ks.hot=cur;ks.hotT=1;ks.hotT0=1;ks.t=Math.max(1,Math.round((ph?1:2)/fm));} // 顺/反扫/滑奏:快速
|
||
}
|
||
}
|
||
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<n1;i++){const sd=i%2?1:-1,tp=pick(["gaunt","gaunt","ripper","sporeT"]);
|
||
spawnEnemy(tp,b.x+sd*30,b.y+rnd(-6,14),false);
|
||
const e=G.enemies[G.enemies.length-1];
|
||
if(tp==="sporeT"){e.x0=e.x;} // 孢子囊缓飘
|
||
else if(tp==="boomer"){e.vx=sd*rnd(0.6,1);e.vy=rnd(0.2,0.4);} // 爆虫:侧弹后直接冲锋
|
||
else if(tp!=="spitter"&&tp!=="spinegun"){e.vx=sd*rnd(0.7,1.1);e.vy=rnd(0.1,0.35);e.sidleT=rndi(26,42);e.beh="sidle";} // 近战虫侧排后扑击
|
||
if(ph>=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*50,py2=b.y+8,a6=Math.atan2(ap2.y-py2,ap2.x-px2);
|
||
ebul(px2,py2,a6,4.4,"dartS",15);} // 发射点=最下甲板炮口(与drawUsurper同源)
|
||
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,99999,{b,ox:0,oy:20,followX2:true}); // 巨宽核心激光:99999伤害
|
||
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);}
|
||
if(ph>=1){const n3=6+ph*3; // P2起内层反向橙弹环(双层反向编织,致敬参考图紫+橙)
|
||
for(let i=0;i<n3;i++){const ea2=-(b.rotA||0)+i/n3*TAU+TAU/(n3*2);
|
||
ebul(b.x+Math.cos(ea2)*22,b.y+Math.sin(ea2)*22,ea2,1.5+0.15*ph,"orbO",13);}}
|
||
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(300/fm);} // 二阶段激光间隔翻倍(150→300);三阶段不变
|
||
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": { // 钢琴:同时随机3个穹顶小炮塔发射(炮位与drawPiano同源;P2随环带滑开28)
|
||
const off2=b.phase>=1?28:0,idxs=shuffled(6).slice(0,3);
|
||
b.turrIs=idxs;b.turrT=8;
|
||
for(const ti of idxs){const tx=b.x+(-75+ti*30)+(ti<3?-1:1)*off2;
|
||
ebul(tx+rnd(-3,3),b.y-2,Math.PI/2+rnd(-0.12,0.12),2.0,"orbG",13);}
|
||
b.minorT=Math.round(50/fm);break;} // 绿色子弹攻击频率翻三倍
|
||
case "pedal": { // 钢琴:踏板重音——左右大炮塔慢速大弹(统一紫色;P2随外环滑开16;炮位与drawPiano同源)
|
||
const off0=b.phase>=1?16:0;
|
||
for(const sd of[-1,1]){const bb=ebul(b.x+sd*(112+off0),b.y+10,Math.PI/2+sd*0.08,1.1,"orbP",18);
|
||
bb.scale=2.4;bb.r=6;}
|
||
b.pedT=8;b.minorT=Math.round(280/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 "deckVolley": if(b.phase>=1){b.minorT=80;break;}b.deckT=26;b.minorT=Math.round(320/fm);break; // 夺权者X:甲板齐射(P1限定)——最下甲板炮口逐个点亮前摇→6发垂直弹雨
|
||
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,"orbO",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爬升后永远满足 y<ty 而免疫)
|
||
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;
|
||
G.bossKills=(G.bossKills||0)+1; // 账号累计击坠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();}
|
||
if(typeof submitRun==="function")submitRun(true); // rush胜利也上报(击坠计入排行榜)
|
||
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=["碎石边境","血色星云","钢铁舰队","死寂空间站","虫群巢穴","核心决战"];
|