新增海盗、植物两大种族敌人各15种

- 海盗族15种:5杂兵+6小兵+4精英,锈红/金/骨白配色
- 植物族15种:5杂兵+6小兵+4精英,叶绿/深绿/花粉粉配色
- 各族子弹配色严格≤3色:海盗锈钉/炮弹/燃烧瓶,植物荆棘/种子/孢子
- 敌表新增 race/tier 标签,波次生成按层级池抽取
- 更新 CODEX_KEYS 与客户端图鉴总数
This commit is contained in:
JOJO 2026-08-06 21:20:39 +08:00
parent 676f4344ed
commit 3ffb51098b
6 changed files with 145 additions and 26 deletions

View File

@ -136,6 +136,10 @@ function updateBullets(){
if(tg){b.dead=true;explodeOnce(b);} if(tg){b.dead=true;explodeOnce(b);}
else{b.fuse=(b.fuse||0)-1;if(b.fuse<=0){b.dead=true;explodeOnce(b);}} else{b.fuse=(b.fuse||0)-1;if(b.fuse<=0){b.dead=true;explodeOnce(b);}}
} }
if(b.t==="pmol"&&!b.dead){ // 海盗燃烧瓶:抛物线,落地/触敌爆炸并留火场
b.vy+=b.grav||0.08;
if(b.y>=H-10||b.fuse<=0){b.dead=true;explodeOnce(b);}
}
if(!b.back&&b.bt!==undefined){b.bt--;if(b.bt<=0&&!b.dead){b.dead=true;explodeOnce(b);}} 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==="mt"&&b.ty2!==undefined&&b.y<=b.ty2&&!b.dead){b.dead=true;explodeOnce(b);}
if(b.t==="s"&&!b.dead){ // 分裂弹:飞行一段距离后自动分裂 if(b.t==="s"&&!b.dead){ // 分裂弹:飞行一段距离后自动分裂

View File

@ -333,6 +333,11 @@ function explodeBullet(b){
for(let i=0;i<bees;i++){const a=i/bees*TAU+rnd(-0.3,0.3); for(let i=0;i<bees;i++){const a=i/bees*TAU+rnd(-0.3,0.3);
pbul(b.x,b.y,Math.cos(a)*2.8,Math.sin(a)*2.8,b.beeDmg||b.dmg*0.4,{t:"bee",spr:"bee",r:2,homing:true,maxSpd:4.5,life:90,trailCol:"#ffd23d"});} pbul(b.x,b.y,Math.cos(a)*2.8,Math.sin(a)*2.8,b.beeDmg||b.dmg*0.4,{t:"bee",spr:"bee",r:2,homing:true,maxSpd:4.5,life:90,trailCol:"#ffd23d"});}
SFX.boom(false); SFX.boom(false);
}else if(b.t==="pmol"){ // 海盗燃烧瓶:火焰区域
areaDamage(b.x,b.y,b.aoe||26,b.dmg);
G.zones.push({x:b.x,y:b.y,r:b.aoe||26,dps:b.dmg*0.12,life:160,col:"#ff9a3d",kind:"fire"});
explodeFx(b.x,b.y,"fire",1);
SFX.boom(false);G.shake=Math.max(G.shake,3);
} }
} }
function aimShot(x,y,dmg,spd,spr,slow){ function aimShot(x,y,dmg,spd,spr,slow){

View File

@ -1,21 +1,53 @@
"use strict"; "use strict";
/* ================= 敌人图鉴 ================= */ /* ================= 敌人图鉴 ================= */
const ENEMIES={ const ENEMIES={
scout:{hp:10,spd:1.15,score:100,r:5,gems:2,coin:.3,beh:"drift",fire:null,cost:1,min:0}, scout:{hp:10,spd:1.15,score:100,r:5,gems:2,coin:.3,beh:"drift",fire:null,cost:1,min:0,race:"mech",tier:"grunt"},
weaver:{hp:16,spd:1.3,score:160,r:6,gems:2,coin:.35,beh:"sine",fire:null,cost:1.4,min:2}, weaver:{hp:16,spd:1.3,score:160,r:6,gems:2,coin:.35,beh:"sine",fire:null,cost:1.4,min:2,race:"mech",tier:"grunt"},
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}, 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,race:"mech",tier:"grunt"},
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}, 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,race:"mech",tier:"soldier"},
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}, 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,race:"mech",tier:"soldier"},
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}, 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,race:"mech",tier:"elite"},
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}, 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,race:"mech",tier:"elite"},
rock:{hp:30,spd:1,score:40,r:8,gems:0,coin:.1,beh:"spin",fire:null,cost:0,min:0,neutral:true}, rock:{hp:30,spd:1,score:40,r:8,gems:0,coin:.1,beh:"spin",fire:null,cost:1,min:0,neutral:true,race:"cosmic",tier:"grunt"},
mini:{hp:5,spd:1.8,score:60,r:4,gems:1,coin:.15,beh:"dive",fire:null,cost:.5,min:0}, mini:{hp:5,spd:1.8,score:60,r:4,gems:1,coin:.15,beh:"dive",fire:null,cost:.5,min:0,race:"mech",tier:"grunt"},
gaunt:{hp:26,spd:1.45,score:150,r:6,gems:1,coin:.2,beh:"sidle",fire:null,cost:1.5,min:99}, // 泰伦刀虫(母舰专属) gaunt:{hp:26,spd:1.45,score:150,r:6,gems:1,coin:.2,beh:"sidle",fire:null,cost:1.5,min:99,race:"bug",tier:"grunt"}, // 泰伦刀虫(母舰专属)
sporeT:{hp:14,spd:.5,score:120,r:6,gems:1,coin:.15,beh:"drift",fire:null,death:"ring",cost:1,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,race:"bug",tier:"soldier"}, // 孢子囊:慢飘,死亡酸环
ripper:{hp:6,spd:2.2,score:50,r:3.5,gems:1,coin:.1,beh:"dive",fire:null,cost:.4,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,race:"bug",tier:"grunt"}, // 撕裂虫:小快速
boomer:{hp:8,spd:2.0,score:130,r:5,gems:1,coin:.15,beh:"boom",fire:null,cost:1,min:99}, // 爆虫:冲锋自爆 boomer:{hp:8,spd:2.0,score:130,r:5,gems:1,coin:.15,beh:"boom",fire:null,cost:1,min:99,race:"bug",tier:"grunt"}, // 爆虫:冲锋自爆
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}, // 吐酸虫:远程散弹 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,race:"bug",tier:"soldier"}, // 吐酸虫:远程散弹
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} // 棘枪虫:骨钉直线 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,race:"bug",tier:"soldier"}, // 棘枪虫:骨钉直线
/* ---- 海盗族15种 ---- */
p_scav:{hp:9,spd:1.2,score:90,r:5,gems:2,coin:.25,beh:"drift",fire:null,cost:1,min:0,race:"pirate",tier:"grunt"},
p_raid:{hp:12,spd:1.45,score:130,r:5,gems:2,coin:.3,beh:"sine",fire:null,cost:1.1,min:1,race:"pirate",tier:"grunt"},
p_board:{hp:8,spd:1.6,score:120,r:5,gems:2,coin:.25,beh:"dive",fire:null,death:"ring",cost:1.1,min:2,race:"pirate",tier:"grunt"},
p_drift:{hp:26,spd:.4,score:40,r:7,gems:0,coin:.1,beh:"drift",fire:null,cost:1,min:0,neutral:true,race:"pirate",tier:"grunt"},
p_swab:{hp:5,spd:2.0,score:50,r:4,gems:1,coin:.15,beh:"dive",fire:null,cost:.5,min:0,race:"pirate",tier:"grunt"},
p_cut:{hp:18,spd:1.1,score:150,r:6,gems:2,coin:.3,beh:"drift",fire:{t:"pAim",cd:150,spd:2.4},cost:1.4,min:3,race:"pirate",tier:"soldier"},
p_blund:{hp:22,spd:.9,score:170,r:6,gems:2,coin:.35,beh:"sine",fire:{t:"pSpread",cd:170,spd:2.0},cost:1.6,min:4,race:"pirate",tier:"soldier"},
p_keg:{hp:20,spd:.5,score:160,r:6,gems:2,coin:.3,beh:"drift",fire:null,death:"ring",cost:1.4,min:4,race:"pirate",tier:"soldier"},
p_harp:{hp:24,spd:.7,score:200,r:6,gems:3,coin:.4,beh:"hover",fire:{t:"pSnipe",cd:220,spd:3.4},cost:1.8,min:6,race:"pirate",tier:"soldier"},
p_cannon:{hp:32,spd:.6,score:240,r:7,gems:3,coin:.45,beh:"hover",fire:{t:"pBurst",cd:180,spd:1.8},cost:2.2,min:7,race:"pirate",tier:"soldier"},
p_flint:{hp:26,spd:.8,score:190,r:6,gems:2,coin:.35,beh:"hover",fire:{t:"pRing",cd:200,spd:1.6},cost:1.8,min:6,race:"pirate",tier:"soldier"},
p_mara:{hp:55,spd:.7,score:400,r:8,gems:4,coin:.6,beh:"hover",fire:{t:"pBroad",cd:160,spd:2.0},cost:4,min:8,race:"pirate",tier:"elite"},
p_bucc:{hp:45,spd:.65,score:340,r:8,gems:4,coin:.55,beh:"hover",fire:{t:"pMolotov",cd:190,spd:1.4},cost:3.5,min:8,race:"pirate",tier:"elite"},
p_frig:{hp:80,spd:.5,score:520,r:9,gems:5,coin:.7,beh:"hover",fire:{t:"pCannon",cd:210,spd:1.7},cost:5,min:10,race:"pirate",tier:"elite"},
p_dread:{hp:95,spd:.55,score:600,r:9,gems:5,coin:.8,beh:"hover",fire:{t:"pDread",cd:240,spd:2.0},cost:6,min:12,race:"pirate",tier:"elite"},
/* ---- 植物族15种 ---- */
f_spore:{hp:7,spd:1.0,score:70,r:5,gems:2,coin:.2,beh:"drift",fire:null,cost:1,min:0,race:"plant",tier:"grunt"},
f_seed:{hp:9,spd:1.7,score:110,r:5,gems:2,coin:.25,beh:"dive",fire:null,cost:1.1,min:2,race:"plant",tier:"grunt"},
f_sprout:{hp:12,spd:1.1,score:100,r:5,gems:2,coin:.25,beh:"sine",fire:null,cost:1,min:1,race:"plant",tier:"grunt"},
f_thorn:{hp:6,spd:2.1,score:60,r:4,gems:1,coin:.15,beh:"dive",fire:null,cost:.5,min:0,race:"plant",tier:"grunt"},
f_mote:{hp:16,spd:.35,score:35,r:6,gems:0,coin:.1,beh:"drift",fire:null,cost:1,min:0,neutral:true,race:"plant",tier:"grunt"},
f_vine:{hp:18,spd:1.2,score:130,r:6,gems:2,coin:.3,beh:"sine",fire:null,cost:1.2,min:3,race:"plant",tier:"grunt"},
f_shoot:{hp:16,spd:.8,score:140,r:6,gems:2,coin:.3,beh:"hover",fire:{t:"fAim",cd:160,spd:1.8},cost:1.5,min:4,race:"plant",tier:"soldier"},
f_archer:{hp:22,spd:.65,score:180,r:6,gems:3,coin:.4,beh:"hover",fire:{t:"fSnipe",cd:210,spd:3.0},cost:1.8,min:6,race:"plant",tier:"soldier"},
f_bomb:{hp:24,spd:.6,score:190,r:6,gems:2,coin:.35,beh:"hover",fire:{t:"fBurst",cd:180,spd:1.6},cost:1.8,min:6,race:"plant",tier:"soldier"},
f_pollen:{hp:20,spd:.5,score:170,r:6,gems:2,coin:.3,beh:"drift",fire:{t:"fRing",cd:200,spd:1.4},cost:1.6,min:5,race:"plant",tier:"soldier"},
f_bud:{hp:28,spd:.55,score:210,r:7,gems:3,coin:.4,beh:"hover",fire:{t:"fVine",cd:140,spd:2.4},cost:2,min:7,race:"plant",tier:"soldier"},
f_nettle:{hp:26,spd:.7,score:200,r:6,gems:3,coin:.35,beh:"hover",fire:{t:"fSpread",cd:170,spd:1.9},cost:1.8,min:6,race:"plant",tier:"soldier"},
f_over:{hp:60,spd:.5,score:380,r:8,gems:4,coin:.6,beh:"hover",fire:{t:"fBeam",cd:230,spd:3.0},cost:4,min:9,race:"plant",tier:"elite"},
f_mother:{hp:50,spd:.45,score:320,r:8,gems:4,coin:.55,beh:"hover",fire:{t:"fSummon",cd:260,spd:1.6},cost:3.5,min:8,race:"plant",tier:"elite"},
f_treant:{hp:85,spd:.4,score:500,r:9,gems:5,coin:.7,beh:"hover",fire:{t:"fSalvo",cd:250,spd:1.8},cost:5,min:10,race:"plant",tier:"elite"}
}; };
function tierN(){ // 难度层数:无尽=波次,闯关=关卡*2+小波(图鉴演示等 wave=0 场景会为负按0计防止hpMul产生NaN 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); return Math.max(0,G.mode==="endless"?G.wave-1:(G.stage-1)*2+G.wave-1);
@ -116,6 +148,30 @@ function enemyFire(e){
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==="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==="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); // 泰伦:骨钉直线 else if(f.t==="spike")ebul(e.x,e.y+4,a0,f.spd,"spikeB",16); // 泰伦:骨钉直线
// 海盗族子弹
else if(f.t==="pAim")ebul(e.x,e.y+4,a0,f.spd,"pBolt",16);
else if(f.t==="pSpread")for(let i=-1;i<=1;i++)ebul(e.x,e.y+4,a0+i*0.32,f.spd,"pShrap",15);
else if(f.t==="pSnipe"){ebul(e.x,e.y+4,a0,f.spd,"pChain",24);ebul(e.x,e.y+4,a0+0.05,f.spd,"pChain",24);ebul(e.x,e.y+4,a0-0.05,f.spd,"pChain",24);}
else if(f.t==="pBurst")for(let i=-2;i<=2;i++)ebul(e.x,e.y+6,a0+i*0.2,f.spd,"pBall",18);
else if(f.t==="pRing")for(let i=0;i<8;i++)ebul(e.x,e.y+4,i/8*TAU+e.t*0.01,f.spd,"pBolt",18);
else if(f.t==="pBroad")for(const sd of[-1,1])for(let i=-1;i<=1;i++)ebul(e.x,e.y+6,sd*Math.PI/2+i*0.18,f.spd,"pBall",20);
else if(f.t==="pMolotov"){const m=ebul(e.x,e.y+4,a0,f.spd*0.6,"pMolotov",14);m.vy=-f.spd*0.35;m.grav=0.08;m.fuse=120;m.aoe=26;m.t="pmol";}
else if(f.t==="pCannon"){for(let i=-1;i<=1;i++)ebul(e.x,e.y+6,a0+i*0.22,f.spd,"pBall",22);
for(let i=-2;i<=2;i++)ebul(e.x,e.y+6,a0+i*0.35,f.spd*1.15,"pShrap",12);}
else if(f.t==="pDread"){for(let i=0;i<8;i++)ebul(e.x,e.y+4,i/8*TAU+e.t*0.01,f.spd*0.8,"pBall",16);
for(const sd of[-1,1])for(let i=-1;i<=1;i++)ebul(e.x,e.y+6,sd*Math.PI/2+i*0.18,f.spd*0.9,"pChain",18);
for(let i=-2;i<=2;i++)ebul(e.x,e.y+8,a0+i*0.28,f.spd*1.1,"pShrap",14);}
// 植物族子弹
else if(f.t==="fAim")ebul(e.x,e.y+4,a0,f.spd,"fSpore",15);
else if(f.t==="fSpread")for(let i=-1;i<=1;i++)ebul(e.x,e.y+4,a0+i*0.32,f.spd,"fThorn",14);
else if(f.t==="fSnipe"){ebul(e.x,e.y+4,a0,f.spd,"fThorn",22);ebul(e.x,e.y+4,a0+0.05,f.spd,"fThorn",22);ebul(e.x,e.y+4,a0-0.05,f.spd,"fThorn",22);}
else if(f.t==="fBurst")for(let i=-2;i<=2;i++)ebul(e.x,e.y+6,a0+i*0.2,f.spd,"fSeed",16);
else if(f.t==="fRing")for(let i=0;i<8;i++)ebul(e.x,e.y+4,i/8*TAU+e.t*0.01,f.spd,"fPollen",16);
else if(f.t==="fVine"){const v=ebul(e.x,e.y+4,a0,f.spd*1.2,"fLeaf",18);v.life=30;v.t="fleaf";}
else if(f.t==="fBeam"){for(let i=-2;i<=2;i++){ebul(e.x,e.y+6,a0+i*0.18,f.spd*1.25,"fThorn",18);ebul(e.x,e.y+6,a0+i*0.18+Math.PI,f.spd*1.25,"fThorn",18);}}
else if(f.t==="fSummon"){G.spawning.push({type:"f_spore",x:clamp(e.x,12,W-12),y:e.y-10,delay:15});}
else if(f.t==="fSalvo"){for(let i=-2;i<=2;i++)ebul(e.x,e.y+6,a0+i*0.2,f.spd,"fThorn",20);
for(let i=-1;i<=1;i++)ebul(e.x,e.y+8,a0+i*0.35,f.spd*0.9,"fSeed",16);}
} }
/* ================= 突变词缀 ================= */ /* ================= 突变词缀 ================= */
const MUTS={ const MUTS={
@ -138,21 +194,26 @@ function startWave(){
// Boss 波? // Boss 波?
const isBoss = G.mode==="endless" ? G.wave%5===0 : G.wave>(G.stage>=3?4:3); const isBoss = G.mode==="endless" ? G.wave%5===0 : G.wave>(G.stage>=3?4:3);
if(isBoss){startBoss();return;} if(isBoss){startBoss();return;}
// 预算制生成:不开火的杂兵为主(~70%),射手为少数(~30%且有同屏上限) // 预算制生成:按种族/层级池抽取,杂兵为主,射手为辅,精英少量
let budget=(6+n*2.2)*(G.mode==="stage"?1+G.stage*0.12:1)*budgetScale(); 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 all=Object.keys(ENEMIES).filter(k=>ENEMIES[k].min<=n);
const shooters=["splitter","mine","gunship","sniper"].filter(k=>ENEMIES[k].min<=n); const fodder=all.filter(k=>ENEMIES[k].tier==="grunt");
const shooters=all.filter(k=>ENEMIES[k].tier==="soldier");
const elites=all.filter(k=>ENEMIES[k].tier==="elite");
const eC=BAL.eliteC(n)*(G.mutator==="ELITE"?4:1); const eC=BAL.eliteC(n)*(G.mutator==="ELITE"?4:1);
const shooterCap=2+Math.floor(n/3); const shooterCap=2+Math.floor(n/3);
let shooterN=0,delay=30; const eliteCap=1+Math.floor(n/8);
let shooterN=0,eliteN=0,delay=30;
while(budget>0){ while(budget>0){
let type; let type,elite=false;
if(shooters.length&&shooterN<shooterCap&&Math.random()<0.32){type=pick(shooters);shooterN++;} const pool=fodder.length?fodder:shooters;
else type=pick(fodder.length?fodder:shooters); if(elites.length&&eliteN<eliteCap&&Math.random()<0.12){type=pick(elites);elite=true;eliteN++;}
else if(shooters.length&&shooterN<shooterCap&&Math.random()<0.32){type=pick(shooters);shooterN++;}
else type=pick(pool);
const d=ENEMIES[type]; const d=ENEMIES[type];
const form=pick(["line","v","col","scatter","pincer"]); const form=pick(["line","v","col","scatter","pincer"]);
const cnt=Math.min(rndi(2,4),Math.max(1,Math.floor(budget/d.cost))); const cnt=Math.min(rndi(2,4),Math.max(1,Math.floor(budget/d.cost)));
const elite=Math.random()<eC; if(!elite&&d.tier==="elite")elite=true; // 独立精英类型强制精英化
placeFormation(form,type,cnt,delay,elite); placeFormation(form,type,cnt,delay,elite);
budget-=d.cost*cnt*(elite?3:1); budget-=d.cost*cnt*(elite?3:1);
delay+=rndi(55,110); delay+=rndi(55,110);

View File

@ -3,7 +3,7 @@
const NET = { const NET = {
user: null, // {username,email} user: null, // {username,email}
stats: null, // {bossKills,endlessHigh,runs} stats: null, // {bossKills,endlessHigh,runs}
codexTotal: 63, codexTotal: 60,
online: false, online: false,
runSave: undefined, // 无尽断点存档缓存undefined=未拉取null=无档object=有档 runSave: undefined, // 无尽断点存档缓存undefined=未拉取null=无档object=有档
@ -63,7 +63,7 @@ const NET = {
_apply(d) { _apply(d) {
this.user = d.user; this.user = d.user;
this.stats = d.stats; this.stats = d.stats;
this.codexTotal = d.codexTotal || 63; this.codexTotal = d.codexTotal || 60;
this.online = true; this.online = true;
SAVE.codex = {}; SAVE.codex = {};
if (Array.isArray(d.codex)) for (const k of d.codex) SAVE.codex[k] = true; if (Array.isArray(d.codex)) for (const k of d.codex) SAVE.codex[k] = true;

View File

@ -180,6 +180,49 @@ SPR.dMsl=bake(["..KKK..",".KWWWK.","KWKWKWK",".KWWWK.","..KKK.."],{K:"#14142b",W
SPR.dBlade=bake(["K..K..K",".K.K.K.","..KKK..",".K.K.K.","K..K..K"],{K:"#0d2a1a",W:"#5cff9d"}); SPR.dBlade=bake(["K..K..K",".K.K.K.","..KKK..",".K.K.K.","K..K..K"],{K:"#0d2a1a",W:"#5cff9d"});
SPR.dSweep=bake(["K.K.K.K","KWKWKWK",".KWWWK.","..KWK..","...K..."],{K:"#1a1206",W:"#ffb03d"}); // 扫射僚机:横排多炮管 SPR.dSweep=bake(["K.K.K.K","KWKWKWK",".KWWWK.","..KWK..","...K..."],{K:"#1a1206",W:"#ffb03d"}); // 扫射僚机:横排多炮管
SPR.swep=bake([".W.","WCW","WCW",".K."],{K:"#331d0a",W:"#ffd23d",C:"#ff9a3d"}); // 扫射弹:金黄弹丸 SPR.swep=bake([".W.","WCW","WCW",".K."],{K:"#331d0a",W:"#ffd23d",C:"#ff9a3d"}); // 扫射弹:金黄弹丸
/* ---- 海盗族15种 ---- 主色锈红R 金G 骨白W */
SPR.p_scav=bake(["...K...","..KRK..",".KRRRK.","KRGWRGK",".KRRRK.","..K.K.."],{K:"#2a1a12",R:"#b86a3a",G:"#e0c060",W:"#f4e8d8"});
SPR.p_raid=bake(["....K....","...KWK...","..KRRRK..",".KRRWRK..","KRRGGRRKK",".KRRRK...","..K.K...."],{K:"#2a1a12",R:"#b86a3a",G:"#e0c060",W:"#f4e8d8"});
SPR.p_board=bake(["...K...","..KWK..",".KRRRK.","KRRRRRK",".KWRWK.","..KWK.."],{K:"#2a1a12",R:"#b86a3a",W:"#f4e8d8"});
SPR.p_drift=bake(["...K...",".KRRK.","KRRRRK","KRRRRK",".KRRK.","...K..."],{K:"#2a1a12",R:"#b86a3a"});
SPR.p_swab=bake(["..K..",".KRK.","KRGKK",".KRK.","..K.."],{K:"#2a1a12",R:"#b86a3a",G:"#e0c060"});
SPR.p_cut=bake(["...K...","..KWK..",".KRRRK.","KRGWRGK","KRRRRRK",".K.K.K."],{K:"#2a1a12",R:"#b86a3a",G:"#e0c060",W:"#f4e8d8"});
SPR.p_blund=bake(["....K....","...KWK...","..KRRRK..",".KRRRRRK.","KRRGWGRRK",".KRRRRRK.","...K.K..."],{K:"#2a1a12",R:"#b86a3a",G:"#e0c060",W:"#f4e8d8"});
SPR.p_keg=bake(["..KKK..",".KRRRK.","KRRWRRK","KRRGRRK","KRRWRRK",".KRRRK.","..KKK.."],{K:"#2a1a12",R:"#b86a3a",G:"#e0c060",W:"#f4e8d8"});
SPR.p_harp=bake(["....K....","...KWK...","..KRRRK..",".KRRRRRK.","KRRGWGRRK",".KRRRRRK.","...K.K...","..K...K.."],{K:"#2a1a12",R:"#b86a3a",G:"#e0c060",W:"#f4e8d8"});
SPR.p_cannon=bake(["....K....","...KWK...","..KRRRK..",".KRRRRRK.","KRRGGGRRK",".KRRRRRK.","...K.K..."],{K:"#2a1a12",R:"#b86a3a",G:"#e0c060",W:"#f4e8d8"});
SPR.p_flint=bake(["...K...","..KWK..",".KRRRK.","KRRGRRK","KRWWRWK",".KRRRK.","..K.K.."],{K:"#2a1a12",R:"#b86a3a",G:"#e0c060",W:"#f4e8d8"});
SPR.p_mara=bake(["....K....","...KWK...","..KRRRK..",".KRRRRRK.","KRRGWGRRK","KRRRRRRRK",".KRRRRRK.","...K.K..."],{K:"#2a1a12",R:"#b86a3a",G:"#e0c060",W:"#f4e8d8"});
SPR.p_bucc=bake([".....K.....","....KWK....","...KRRRK...","..KRRRRRK..",".KRRGWGRRK.","KRRRRRRRRRK",".KRRRRRRRK.","...K...K..."],{K:"#2a1a12",R:"#b86a3a",G:"#e0c060",W:"#f4e8d8"});
SPR.p_frig=bake([".....K.....","....KWK....","...KRRRK...","..KRRRRRK..",".KRRRRRRRK.","KRRGGGWGRRK","KRRRRRRRRRK",".KRRRRRRRK.","...K...K..."],{K:"#2a1a12",R:"#b86a3a",G:"#e0c060",W:"#f4e8d8"});
SPR.p_dread=bake(["......K......",".....KWK.....","....KRRRK....","...KRRRRRK...","..KRRGWGRRK..",".KRRRRRRRRRK.","KRRGGGWGWGRRK","KRRRRRRRRRRRK",".KRRRRRRRRRK.","....K...K...."],{K:"#2a1a12",R:"#b86a3a",G:"#e0c060",W:"#f4e8d8"});
/* ---- 植物族15种 ---- 主色叶绿L 深绿D 花粉粉F */
SPR.f_spore=bake(["...K...","..KLK..",".KLW LK".replace(" ",""),"..KLK..","...K..."],{K:"#0d2a0d",L:"#5cff9d",W:"#eafff0"});
SPR.f_seed=bake(["..K..",".KLK.","KLW LK".replace(" ",""),".KLK.","..K.."],{K:"#0d2a0d",L:"#5cff9d",W:"#eafff0"});
SPR.f_sprout=bake(["...K...","..KLK..",".KLDLK.","..KLK..","...K..."],{K:"#0d2a0d",L:"#5cff9d",D:"#3a8f5f"});
SPR.f_thorn=bake(["..K..",".KLK.","KLDLK",".KLK.","..K.."],{K:"#0d2a0d",L:"#5cff9d",D:"#3a8f5f"});
SPR.f_mote=bake(["..K..",".KFK.","KFWFK",".KFK.","..K.."],{K:"#0d2a0d",F:"#e8a0c8",W:"#eafff0"});
SPR.f_vine=bake(["...K...","..KDK..",".KLDLK.","KLDLDLK",".KLDLK.","..KDK.."],{K:"#0d2a0d",L:"#5cff9d",D:"#3a8f5f"});
SPR.f_shoot=bake(["...K...","..KLK..",".KLDLK.","KLWFLWK",".KLDLK.","..KDK.."],{K:"#0d2a0d",L:"#5cff9d",D:"#3a8f5f",F:"#e8a0c8",W:"#eafff0"});
SPR.f_archer=bake(["...K...","..KDK..",".KLDLK.","KLWFLWK",".KLDLK.","..KDK..","...K..."],{K:"#0d2a0d",L:"#5cff9d",D:"#3a8f5f",F:"#e8a0c8",W:"#eafff0"});
SPR.f_bomb=bake(["...K...",".KLDLK.","KLDLDLK","KLWFLWK","KLDLDLK",".KLDLK.","...K..."],{K:"#0d2a0d",L:"#5cff9d",D:"#3a8f5f",F:"#e8a0c8",W:"#eafff0"});
SPR.f_pollen=bake(["...K...","..KFK..",".KFWFK.","KFWWFWK",".KFWFK.","..KFK..","...K..."],{K:"#0d2a0d",F:"#e8a0c8",W:"#eafff0"});
SPR.f_bud=bake(["...K...","..KFK..",".KFLFK.","KFLWLFK",".KFLFK.","..KFK..","...K..."],{K:"#0d2a0d",F:"#e8a0c8",L:"#5cff9d",W:"#eafff0"});
SPR.f_nettle=bake(["...K...",".KLDLK.","KLDLDLK","DLWFLWD","KLDLDLK",".KLDLK.","...K..."],{K:"#0d2a0d",L:"#5cff9d",D:"#3a8f5f",F:"#e8a0c8",W:"#eafff0"});
SPR.f_over=bake(["....K....","...KDK...","..KLDLK..",".KLWFLWK.","KLDLDLDLK",".KLWFLWK.","...KDK..."],{K:"#0d2a0d",L:"#5cff9d",D:"#3a8f5f",F:"#e8a0c8",W:"#eafff0"});
SPR.f_mother=bake(["....K....","...KFK...","..KFWFK..",".KFLWLFK.","KFLFWFLFK",".KFLWLFK.","...KFK..."],{K:"#0d2a0d",F:"#e8a0c8",L:"#5cff9d",W:"#eafff0"});
SPR.f_treant=bake([".....K.....","....KDK....","...KLDLK...","..KLWFLWK..",".KLDLDLDLK.","KLDLDLDLDLK",".KLWFLWK..","...KDK....."],{K:"#0d2a0d",L:"#5cff9d",D:"#3a8f5f",F:"#e8a0c8",W:"#eafff0"});
/* ---- 海盗/植物子弹各族配色限制3色 ---- */
SPR.pBolt=bake([".K.","KWK",".K."],{K:"#2a1a12",W:"#f4e8d8"}); // 海盗:锈钉
SPR.pBall=bake([".KK.","KRRK","KRRK",".KK."],{K:"#2a1a12",R:"#b86a3a"}); // 海盗:炮弹
SPR.pShrap=bake(["K..K",".RR.",".RR.","K..K"],{K:"#2a1a12",R:"#b86a3a"}); // 海盗:霰弹片
SPR.pMolotov=bake(["..K..",".KWK.",".KOK.",".KKK."],{K:"#2a1a12",W:"#f4e8d8",O:"#ff9a3d"}); // 海盗燃烧瓶3色暗/骨/火)
SPR.pChain=bake([".K.","KRK",".R.","KRK",".K."],{K:"#2a1a12",R:"#b86a3a"}); // 海盗:链弹
SPR.fThorn=bake(["..K..",".KLK.","..L..",".KLK.","..K.."],{K:"#0d2a0d",L:"#5cff9d"}); // 植物:荆棘
SPR.fSeed=bake(["..K..",".KLK.","KLW LK".replace(" ",""),".KLK.","..K.."],{K:"#0d2a0d",L:"#5cff9d",W:"#eafff0"}); // 植物:种子
SPR.fPollen=bake([".K.","KFK",".K."],{K:"#0d2a0d",F:"#e8a0c8"}); // 植物:花粉
SPR.fLeaf=bake(["...L...","..KLK..",".KLW LK".replace(" ",""),"..KLK..","...L..."],{K:"#0d2a0d",L:"#5cff9d",W:"#eafff0"}); // 植物:叶刃
SPR.fSpore=bake(["...K...","..KFK..",".KFWFK.","..KFK..","...K..."],{K:"#0d2a0d",F:"#e8a0c8",W:"#eafff0"}); // 植物:孢子
/* ---- 升级卡片图标8×8 单色) ---- */ /* ---- 升级卡片图标8×8 单色) ---- */
function icon(rows,color){return bake(rows,{X:color});} function icon(rows,color){return bake(rows,{X:color});}
const ICONS={ const ICONS={

View File

@ -5,7 +5,7 @@ const db = require("./db");
const auth = require("./auth"); const auth = require("./auth");
const { vUsername, vEmail, vPassword, vInt, rateLimit, clientIp } = require("./util"); const { vUsername, vEmail, vPassword, vInt, rateLimit, clientIp } = require("./util");
/* 图鉴合法 key 白名单(与 js/data.js 保持同步27主武+15副武+10僚机+8被动=60 */ /* 图鉴合法 key 白名单(与 js/data.js 保持同步27主武+15副武+10僚机+8被动+45敌人=105 */
const CODEX_KEYS = new Set([ const CODEX_KEYS = new Set([
"vulcan", "shotgun", "needle", "flame", "shotgun2", "tesla", "railgun", "deathray", "vulcan", "shotgun", "needle", "flame", "shotgun2", "tesla", "railgun", "deathray",
"crescent", "fission", "frost", "swarm", "bubble", "starfall", "boomerang", "singularity", "crescent", "fission", "frost", "swarm", "bubble", "starfall", "boomerang", "singularity",
@ -16,6 +16,12 @@ const CODEX_KEYS = new Set([
"gunner", "gunner2", "sniperd", "beamer", "bomber", "rammer", "gunner", "gunner2", "sniperd", "beamer", "bomber", "rammer",
"healer", "shieldd", "missileD", "bladeD", "healer", "shieldd", "missileD", "bladeD",
"fire", "crit", "shield", "nano", "mag", "bounty", "odcore", "ghost", "fire", "crit", "shield", "nano", "mag", "bounty", "odcore", "ghost",
"scout", "weaver", "kami", "splitter", "mine", "gunship", "sniper", "rock", "mini",
"gaunt", "sporeT", "ripper", "boomer", "spitter", "spinegun",
"p_scav", "p_raid", "p_board", "p_drift", "p_swab", "p_cut", "p_blund", "p_keg", "p_harp", "p_cannon", "p_flint",
"p_mara", "p_bucc", "p_frig", "p_dread",
"f_spore", "f_seed", "f_sprout", "f_thorn", "f_mote", "f_vine", "f_shoot", "f_archer", "f_bomb", "f_pollen", "f_bud",
"f_nettle", "f_over", "f_mother", "f_treant"
]); ]);
const CODEX_TOTAL = CODEX_KEYS.size; const CODEX_TOTAL = CODEX_KEYS.size;
const RUN_MODES = new Set(["stage", "endless", "rush"]); const RUN_MODES = new Set(["stage", "endless", "rush"]);