"use strict";
/* ================= 界面流转 ================= */
const OVLS=["o-auth","o-home","o-title","o-setup","o-hangar","o-cards","o-pause","o-over","o-codex","o-demo","o-rush","o-slot","o-unsaved"];
function show(id){for(const o of OVLS)$(o).classList.toggle("hide",o!==id);}
const SETUP={mode:"endless",ship:"falcon",diff:0}; // 出击默认无尽模式
function bestLine(){
const b=SAVE.best;
$("best-line").textContent=`无尽纪录 ${Math.max(b.e0,b.e1)} · 闯关进度 ${b.stage}/6`;
}
$("b-go").onclick=()=>{AU.init();show("o-setup");refreshRunSave();};
$("b-back1").onclick=()=>show("o-title");
$("b-back2").onclick=()=>show("o-title");
$("b-hangar").onclick=()=>{AU.init();buildShop();show("o-hangar");};
$("b-codex").onclick=()=>{AU.init();buildCodex();show("o-codex");};
$("b-rush").onclick=()=>{AU.init();buildRush();show("o-rush");};
$("b-back6").onclick=()=>{UI="title";show("o-title");};
$("b-back4").onclick=()=>{UI="title";bestLine();show("o-title");};
$("b-back5").onclick=exitDemoSim;
/* 图鉴测试模式(隐藏入口):连点图鉴标题“军”字10次开关。
纯内存开关——不动 SAVE.codex(persist/结算上报都读它,动了会污染后端),刷新页面即失效 */
let codexTestAll=false,cxClicks=0,cxLastClick=0;
$("cx-secret").onclick=()=>{
const now=Date.now();
if(now-cxLastClick>2000)cxClicks=0; // 间隔超2秒重新计数
cxLastClick=now;
if(++cxClicks<10)return;
cxClicks=0;codexTestAll=!codexTestAll;
SFX.pick();buildCodex();
};
/* 开始出击:新开无尽对局(不分难度)前,若云端有未完成对局,先弹结算确认 */
$("b-start").onclick=async()=>{
if(SETUP.mode==="endless"&&typeof NET!=="undefined"&&NET.user){
if(NET.runSave===undefined)await NET.fetchRunSave();
if(NET.runSave){showUnsaved(NET.runSave);return;}
}
startGame();
};
/* ================= 无尽模式:断点续玩(波次检查点) =================
每波开始拍快照上传(startWave 调用);中途退出=从当前波开头重打,
该波内的得分/击杀不保留,build与层数完整保留 */
function takeRunSnapshot(){
return {v:1,mode:"endless",diff:G.diff,ship:G.ship,
wave:G.wave,score:Math.round(G.score),coins:Math.round(G.coins),kills:G.kills,
level:G.level,xp:Math.round(G.xp),xpNeed:G.xpNeed,luck:G.luck,
weapons:Object.assign({},G.weapons),loadout:G.loadout.slice(),
subs:Object.assign({},G.subs),subLoadout:G.subLoadout.slice(),
drones:Object.assign({},G.drones),droneLoadout:G.droneLoadout.slice(),
u:Object.assign({},G.u),
lives:G.lives,maxHp:G.maxHp,hp:Math.round(G.hp),
shield:G.shield,od:Math.round(G.od),
bossN:G.bossN,bossKills:G.bossKills||0,time:G.time};
}
/* startWave 开头调用:仅无尽模式实战(演示/主页模拟/未登录跳过) */
function maybeSaveRun(){
if(!G||G.mode!=="endless"||G.demoSim)return;
if(typeof NET==="undefined"||!NET.user)return;
NET.saveRunSnapshot(takeRunSnapshot());
}
/* 出击页:拉取云端存档并渲染“继续对局”卡片 */
async function refreshRunSave(){
if(typeof NET==="undefined"||!NET.user){buildResumeSlot();return;}
await NET.fetchRunSave();
buildResumeSlot();
}
function buildResumeSlot(){
const box=$("resume-slot");if(!box)return;box.innerHTML="";
const s=typeof NET!=="undefined"?NET.runSave:null;
if(!s||!NET.user)return;
const d=document.createElement("div");d.className="panel";
d.style.cssText="border-color:var(--gold);margin-bottom:8px;padding:8px 10px";
d.innerHTML=`
⏸ 未完成的无尽对局
WAVE ${s.wave} · ${s.diff?"困难":"普通"} · 得分 ${s.score} · ${SHIPS[s.ship]?SHIPS[s.ship].name:"猎鹰"}
`;
const b=document.createElement("button");
b.className="btn primary";b.style.minWidth="min(56vw,200px)";
b.textContent="▶ 继续对局";
b.onclick=()=>{SFX.pick();resumeSavedRun(s);};
d.appendChild(b);box.appendChild(d);
}
/* 从波次检查点重建对局:该波从头重打 */
function resumeSavedRun(s){
AU.init();if(AU.ctx&&AU.ctx.state==="suspended")AU.ctx.resume();
newRun("endless",s.ship,s.diff);
G.score=s.score;G.coins=s.coins;G.kills=s.kills;
G.level=s.level;G.xp=s.xp;G.xpNeed=s.xpNeed;G.luck=s.luck;
G.weapons=Object.assign({},s.weapons);G.loadout=s.loadout.slice();
G.subs=Object.assign({},s.subs);G.subLoadout=s.subLoadout.slice();
G.drones=Object.assign({},s.drones);G.droneLoadout=s.droneLoadout.slice();
G.u=Object.assign({fire:0,crit:0,shield:0,nano:0,mag:0,bounty:0,odcore:0,ghost:0},s.u);
G.lives=s.lives;G.maxHp=s.maxHp;G.hp=Math.min(s.hp,s.maxHp);
G.shield=Math.min(s.shield,shieldCap());G.od=s.od;
G.bossN=s.bossN;G.bossKills=s.bossKills;G.time=s.time;
G.wave=s.wave-1; // startWave 会 ++ 回这一波:整波重打,Boss波则重抽Boss(bossN未预增,不偏移)
UI="play";show("");acc=0;
MUS.start(false);
banner("继续对局 — WAVE "+s.wave,"#41e6ff",130);
startWave();
}
/* 结算未完成对局:星币入账+本地纪录+排行榜上报,返回删除云档的 Promise */
function settleSavedRun(s){
SAVE.coins+=s.coins;
SAVE.best["e"+s.diff]=Math.max(SAVE.best["e"+s.diff]||0,s.score);
persist();
if(typeof NET!=="undefined"&&NET.user){
NET.submitRun({mode:"endless",diff:s.diff,score:s.score,wave:s.wave,stage:1,
timeSec:Math.round((s.time||0)/60),bossKills:s.bossKills||0,win:false,
codex:Object.keys(SAVE.codex)});
return NET.deleteRunSave();
}
return Promise.resolve();
}
/* “将直接结算未完成的对局”确认弹窗 */
function showUnsaved(s){
$("unsaved-info").innerHTML=
`抵达波次WAVE ${s.wave}
难度${s.diff?"困难":"普通"}
当前得分${s.score}
持有星币✦ ${s.coins}`;
show("o-unsaved");
}
$("b-settle-go").onclick=async()=>{ // 结算旧档(等删除完成再开新局,防队列外竞态已由 NET 队列兜底)
const s=typeof NET!=="undefined"?NET.runSave:null;
if(s)await settleSavedRun(s);
startGame();
};
$("b-unsaved-resume").onclick=()=>{
const s=typeof NET!=="undefined"?NET.runSave:null;
if(s)resumeSavedRun(s);else show("o-setup");
};
$("b-unsaved-cancel").onclick=()=>show("o-setup");
if("ontouchstart" in window){const pb=$("tb-pause");pb.style.display="block";pb.onclick=()=>togglePause();}
$("b-resume").onclick=()=>togglePause();
$("b-restart").onclick=()=>{show("");restartSameRun();};
/* 放弃任务:无尽模式且已有云端断点存档时【不结算】——星币/分数留在波次快照里,回基地可续玩;其余情况照旧结算 */
$("b-quit").onclick=()=>{
const keep=G&&G.mode==="endless"&&!G.demoSim&&typeof NET!=="undefined"&&NET.user&&NET.runSave;
if(!keep)bank();
G=null;MUS.stop();UI="title";bestLine();show("o-title");
};
/* 再来一局:按刚结束那一局自己的 mode/ship/diff 重开(修复 SETUP 漂移导致进错模式,如 rush/闯关死后进了无尽) */
function restartSameRun(){
if(!G){startGame();return;}
if(G.mode==="rush"){startRush(G.rushIdx);return;}
SETUP.mode=G.mode;SETUP.ship=G.ship;SETUP.diff=G.diff;
startGame();
}
$("b-again").onclick=()=>restartSameRun();
$("b-home").onclick=()=>{G=null;UI="title";bestLine();show("o-title");};
$("mode-pick").addEventListener("click",e=>{
const c=e.target.closest(".pcard");if(!c)return;
SETUP.mode=c.dataset.m;
for(const el of $("mode-pick").children)el.classList.toggle("sel",el===c);
});
$("diff-pick").addEventListener("click",e=>{
const b=e.target.closest("button");if(!b)return;
SETUP.diff=+b.dataset.d;
for(const el of $("diff-pick").children)el.classList.toggle("primary",el===b);
});
/* 机体选择/购买(机库内):购买后整店刷新(星币/按钮态变化),选择只刷新机体卡 */
function buildShips(){
const box=$("ship-pick");box.innerHTML="";
for(const k in SHIPS){
const s=SHIPS[k],owned=SAVE.ships.includes(k);
const d=document.createElement("div");
d.className="pcard"+(owned?"":" lock")+(SETUP.ship===k?" sel":"");
const c=document.createElement("canvas");c.width=52;c.height=64;
const g=c.getContext("2d");g.imageSmoothingEnabled=false;
const spr=SPR[s.spr];g.drawImage(spr,(52-spr.width*3)/2,(64-spr.height*3)/2,spr.width*3,spr.height*3);
d.appendChild(c);
d.insertAdjacentHTML("beforeend",`${s.name}
${s.trait}
`+(owned?"":`✦ ${s.cost} 解锁
`));
d.onclick=()=>{
if(!SAVE.ships.includes(k)){
if(SAVE.coins>=s.cost){SAVE.coins-=s.cost;SAVE.ships.push(k);persist();SFX.level();buildShop();}
else SFX.hurt();
return;
}
SETUP.ship=k;persist();buildShips(); // persist 触发云端同步选中机体
};
box.appendChild(d);
}
}
/* ================= 机库商店 ================= */
const META=[
{key:"weapon",name:"主武器校准",ds:"出击时主武器等级 +1",max:2,costs:[300,800]},
{key:"armor",name:"强化装甲",ds:"出击时生命上限 +1",max:2,costs:[400,1000]},
{key:"hpup",name:"结构强化",ds:"出击时结构值 +15",max:2,costs:[250,700]},
{key:"luck",name:"幸运币",ds:"高品质强化概率提升",max:2,costs:[350,900]},
{key:"wallet",name:"收藏家协议",ds:"星币获取 +15%",max:3,costs:[300,800,2000]},
{key:"mag2",name:"引力透镜",ds:"拾取范围 +15%",max:2,costs:[200,600]},
{key:"odstart",name:"超频预载",ds:"出击时超频已充能 50%",max:1,costs:[500]}
];
function buildShop(){
$("wallet").textContent=SAVE.coins;
buildShips();
const box=$("shop");box.innerHTML="";
for(const m of META){
const lv=SAVE.m[m.key]||0,maxed=lv>=m.max,cost=maxed?0:m.costs[lv];
const d=document.createElement("div");d.className="sitem";
d.innerHTML=`${m.name}
${m.ds}
${"■".repeat(lv)}${"□".repeat(m.max-lv)}
`;
if(!maxed)d.querySelector("button").onclick=()=>{
if(SAVE.coins{
const sec=document.createElement("div");
sec.innerHTML=`${title}(${active.length}/${max})· 点击切换装备
`;
const row=document.createElement("div");
for(const k in owned){
const d=document.createElement("span");
const on=active.includes(k);
d.className="litem"+(on?" on":"");
d.textContent=`${dict[k].name} Lv${owned[k]}`;
d.onclick=()=>{
const i=active.indexOf(k);
if(i>=0){if(active===G.loadout&&active.length===1){SFX.hurt();return;}active.splice(i,1);}
else{if(active.length>=max){SFX.hurt();return;}active.push(k);}
SFX.pick();buildLoadoutUI();
};
row.appendChild(d);
}
if(Object.keys(owned).length===0)row.innerHTML='尚未获得';
sec.appendChild(row);box.appendChild(sec);
};
mk("主武器",WEAPONS,G.weapons,G.loadout,5);
mk("副武器",SUBS,G.subs,G.subLoadout,5);
mk("僚机",DRONES,G.drones,G.droneLoadout,3);
}
/* ================= 图鉴 ================= */
function iconCanvas(img,size,sil){
const c=document.createElement("canvas");c.width=c.height=size;
const g=c.getContext("2d");g.imageSmoothingEnabled=false;
const sc=Math.min(size/img.width,size/img.height);
g.drawImage(img,(size-img.width*sc)/2,(size-img.height*sc)/2,img.width*sc,img.height*sc);
if(sil){g.globalCompositeOperation="source-atop";g.fillStyle="#232f52";g.fillRect(0,0,size,size);g.globalCompositeOperation="source-over";}
return c;
}
function buildCodex(){
const box=$("codex");box.innerHTML="";
const total=MAINS.length+SUB_KEYS.length+DRONE_KEYS.length+PAS_KEYS.length;
$("codex-t").textContent=total;
$("codex-n").textContent=codexCount(); // 计数永远显示真实收集数,测试模式不影响
$("codex-test").style.display=codexTestAll?"":"none";
$("cx-secret").style.color=codexTestAll?"var(--gold)":""; // 开启后“军”字变金
const mk=(title,keys,dict,kind)=>{
const h=document.createElement("div");h.className="cx-sec";h.textContent=title;box.appendChild(h);
const grid=document.createElement("div");grid.className="cx-grid";
for(const k of keys){
const def=dict[k],owned=!!SAVE.codex[k]||codexTestAll; // 测试模式:全部可见可演示
const d=document.createElement("div");
d.className="cx-item"+(owned?"":" lock"); // 未收集:剪影+???,不可演示
const img=def.icon?ICONS[def.icon]:SPR[def.spr];
d.appendChild(iconCanvas(img,28,!owned));
d.insertAdjacentHTML("beforeend",`${owned?def.name:"???"}
`);
if(owned)d.onclick=()=>{SFX.pick();startDemoSim(kind,k);};
grid.appendChild(d);
}
box.appendChild(grid);
};
mk("主武器",MAINS,WEAPONS,"m");
mk("副武器",SUB_KEYS,SUBS,"s");
mk("僚机",DRONE_KEYS,DRONES,"d");
mk("被动强化",PAS_KEYS,PASSIVES,"p");
}
document.addEventListener("visibilitychange",()=>{if(document.hidden&&UI==="play")togglePause();});
function bank(){
if(!G||G.banked)return;G.banked=true;
SAVE.coins+=G.coins;
if(G.mode!=="rush"){const bk=(G.mode==="endless"?"e":"s")+G.diff;
SAVE.best[bk]=Math.max(SAVE.best[bk]||0,G.score);}
persist();
}
/* ---- Boss 挑战模式 ---- */
function buildRush(){
G=null; // 清掉上一局残留(rush胜利后G仍在),防背景卡残影
SAVE.rush=SAVE.rush||{};
const ra=$("rush-anim"); // 老虎机开关(持久化)
if(ra){ra.checked=!!SAVE.rushAnim;ra.onchange=()=>{SAVE.rushAnim=ra.checked?1:0;persist();};}
const el=$("rush-pick");el.innerHTML="";
BOSSES.forEach((b,i)=>{
const best=SAVE.rush[i];
const d=document.createElement("div");d.className="pcard";
d.innerHTML=`${b.name}${best?" ✓":""}
${b.sub}
${best?("最快 "+Math.floor(best/60)+":"+("0"+best%60).slice(-2)):"未击破"}
`;
d.onclick=()=>startRush(i);
el.appendChild(d);
});
}
function rollRushBuild(){ // 随机武装:3主武Lv3 + 3副武Lv3 + 2僚机Lv2
const sh=a=>{const x=a.slice();for(let i=x.length-1;i>0;i--){const j=rndi(0,i),t2=x[i];x[i]=x[j];x[j]=t2;}return x;};
return {m:sh(Object.keys(WEAPONS)).slice(0,3),s:sh(Object.keys(SUBS)).slice(0,3),d:sh(Object.keys(DRONES)).slice(0,2)};
}
function startRush(i){
AU.init();if(AU.ctx&&AU.ctx.state==="suspended")AU.ctx.resume();
const build=rollRushBuild();
if(SAVE.rushAnim)playSlot(build,()=>startRushGo(i,build)); // 老虎机动画抽取
else startRushGo(i,build); // 直接随机
}
function startRushGo(i,build){
newRun("rush","falcon",0);
G.weapons={};build.m.forEach(k=>G.weapons[k]=3);G.loadout=build.m.slice();
G.subs={};build.s.forEach(k=>G.subs[k]=3);G.subLoadout=build.s.slice();
G.drones={};build.d.forEach(k=>G.drones[k]=2);G.droneLoadout=build.d.slice();
G.xpNeed=1e9; // 挑战模式无升级打断
G.rushIdx=i;G.rushT0=0;
UI="play";show("");acc=0;
MUS.start(true);
banner("BOSS 挑战 — "+BOSSES[i].name,"#ff5566",130);
G.rushT0=G.time;
startBoss();
}
/* ---- 老虎机抽奖动画 ---- */
let slotTimers=[];
function playSlot(build,cb){
UI="slot";show("o-slot");
const row=$("slot-row");row.innerHTML="";
$("slot-hint").textContent=" ";
const items=[
...build.m.map(k=>({cat:"主武器",pool:WEAPONS,key:k})),
...build.s.map(k=>({cat:"副武器",pool:SUBS,key:k})),
...build.d.map(k=>({cat:"僚机",pool:DRONES,key:k}))];
let done=0;
items.forEach((it,idx)=>{
const d=document.createElement("div");d.className="slotbox";
d.innerHTML=`${it.cat}
???
`;
row.appendChild(d);
const sn=d.querySelector(".sn"),names=Object.values(it.pool).map(x=>x.name);
const iv=setInterval(()=>{sn.textContent=names[rndi(0,names.length-1)];},60);
slotTimers.push(iv);
setTimeout(()=>{clearInterval(iv);
sn.textContent=it.pool[it.key].name;sn.classList.add("hit");
SFX.graze();done++;
if(done>=items.length){$("slot-hint").textContent="— 出击 —";setTimeout(cb,700);}
},700+idx*280);
});
}
/* 结算上报服务端(每局一次,含rush胜利;图鉴演示除外) */
function submitRun(win){
if(!G||G.submitted||G.demoSim)return;
G.submitted=true;
if(typeof NET==="undefined"||!NET.user)return;
NET.submitRun({mode:G.mode,diff:G.diff,score:G.score,wave:G.wave,stage:G.stage,
timeSec:Math.round(G.time/60),bossKills:G.bossKills||0,win:!!win,
codex:Object.keys(SAVE.codex)}); // 注意:不上报 localStorage 历史最高分(SAVE.best 是浏览器级共享,会串号)
}
function endRun(win){
if(UI==="over")return;
bank();MUS.stop();
submitRun(win);
if(G.mode==="endless"&&typeof NET!=="undefined"&&NET.user)NET.deleteRunSave(); // 对局真正结束:清除云端断点存档
UI="over";
$("over-title").textContent=win?"★ 任务完成 ★":"任务失败";
$("over-title").style.color=win?"#ffcf5c":"#ff5566";
const mins=Math.floor(G.time/3600),secs=Math.floor(G.time/60)%60;
$("over-stats").innerHTML=
`最终得分${G.score}
${G.mode==="endless"?"抵达波次":G.mode==="rush"?"挑战目标":"抵达关卡"}${G.mode==="endless"?G.wave:G.mode==="rush"?BOSSES[G.rushIdx].name:G.stage+"-"+G.wave}
击坠数${G.kills}
强化等级Lv.${G.level}
存活时间${mins}分${secs}秒
持有军械${Object.keys(G.weapons).length}主/${Object.keys(G.subs).length}副/${Object.keys(G.drones).length}机`;
$("over-coins").textContent=G.coins;
setTimeout(()=>show("o-over"),win?400:900);
}
/* ================= 三选一卡片 ================= */
function offerCards(bossRare){
if(!G||G.p.dead>0&&!bossRare)return;
G.bossCard=!!bossRare;
const opts=rollCards(bossRare);
G.cardOpts=opts;
const box=$("cards");box.innerHTML="";
opts.forEach((c,i)=>{
let nm,ds,rare,img;
if(c.kind==="wlvl"){const w=WEAPONS[c.key],lv=G.weapons[c.key];nm=w.name;rare=w.rare;ds=`主武器强化 Lv.${lv} → Lv.${lv+1}`;img=w.icon?ICONS[w.icon]:SPR[w.spr];}
else if(c.kind==="weapon"){const w=WEAPONS[c.key];nm="主武器·"+w.name;rare=w.rare;ds=w.ds;img=w.icon?ICONS[w.icon]:SPR[w.spr];}
else if(c.kind==="slvl"){const u=SUBS[c.key],lv=G.subs[c.key];nm=u.name;rare=u.rare;ds=`副武器强化 Lv.${lv} → Lv.${lv+1}`;img=u.icon?ICONS[u.icon]:SPR[u.spr];}
else if(c.kind==="sub"){const u=SUBS[c.key];nm="副武器·"+u.name;rare=u.rare;ds=typeof u.ds==="function"?u.ds(0):u.ds;img=u.icon?ICONS[u.icon]:SPR[u.spr];}
else if(c.kind==="dlvl"){const u=DRONES[c.key],lv=G.drones[c.key];nm=u.name;rare=u.rare;ds=`僚机强化 Lv.${lv} → Lv.${lv+1}`;img=SPR[u.spr];}
else if(c.kind==="drone"){const u=DRONES[c.key];nm="僚机·"+u.name;rare=u.rare;ds=typeof u.ds==="function"?u.ds(0):u.ds;img=SPR[u.spr];}
else if(c.kind==="upg"){const u=PASSIVES[c.key];nm=u.name;rare=u.rare;ds=u.ds(G.u[c.key]);img=u.icon?ICONS[u.icon]:SPR[u.spr];}
else{rare=0;
if(c.key==="heal"){nm="紧急修复";ds="生命 +1";img=SPR.heart;}
else if(c.key==="coins"){nm="星币补给";ds="星币 +50";img=SPR.coin;}
else{nm="超频充能";ds="超频立即充满";img=SPR.odc;}}
const d=document.createElement("div");
d.className="ucard r"+rare;
const cv2=document.createElement("canvas");cv2.width=40;cv2.height=40;
const g=cv2.getContext("2d");g.imageSmoothingEnabled=false;
const sc=Math.min(40/img.width,40/img.height);
g.drawImage(img,(40-img.width*sc)/2,(40-img.height*sc)/2,img.width*sc,img.height*sc);
d.appendChild(cv2);
d.insertAdjacentHTML("beforeend",
`${i+1}
${nm}
${["普通","稀有","史诗"][rare]}
${ds}
`);
d.onclick=()=>pickCard(i);
box.appendChild(d);
});
UI="cards";show("o-cards");SFX.level();
}
function pickCard(i){
if(UI!=="cards"||!G.cardOpts||!G.cardOpts[i])return;
const c=G.cardOpts[i];G.cardOpts=null;
if(c.kind==="wlvl")G.weapons[c.key]++;
else if(c.kind==="weapon"){G.weapons[c.key]=1;if(G.loadout.length<5)G.loadout.push(c.key);banner("获得 "+WEAPONS[c.key].name,"#b98cff",80);}
else if(c.kind==="slvl")G.subs[c.key]++;
else if(c.kind==="sub"){G.subs[c.key]=1;if(G.subLoadout.length<5)G.subLoadout.push(c.key);banner("获得 "+SUBS[c.key].name,"#b98cff",80);}
else if(c.kind==="dlvl")G.drones[c.key]++;
else if(c.kind==="drone"){G.drones[c.key]=1;if(G.droneLoadout.length<3)G.droneLoadout.push(c.key);banner("获得 "+DRONES[c.key].name,"#b98cff",80);}
else if(c.kind==="upg"){
G.u[c.key]++;
if(c.key==="nano"){G.maxHp+=25;G.hp=Math.min(G.maxHp,G.hp+40);}
if(c.key==="shield")G.shield=Math.min(shieldCap(),G.shield+1);
}else{
if(c.key==="heal")G.lives=Math.min(G.stat.maxLives,G.lives+1);
if(c.key==="coins")addCoins(50);
if(c.key==="od")G.od=100;
}
if(c.key&&c.kind!=="fill"){SAVE.codex[c.key]=true;persist();} // 图鉴收录
SFX.pick();
if(G.bossCard){G.bossCard=false;UI="play";show("");}
else{G.cardQ=(G.cardQ||1)-1;
if(G.cardQ>0)offerCards(false);
else{UI="play";show("");}}
}
/* ================= 图鉴:全屏实战模拟 =================
复用真实战斗引擎:只装备选定军械,杂兵+射手+精英持续来袭 */
function startDemoSim(kind,key){
const def=kind==="m"?WEAPONS[key]:kind==="s"?SUBS[key]:kind==="p"?PASSIVES[key]:DRONES[key];
newRun("stage","falcon",0);
G.p.inv=0; // 图鉴演示:取消出生保护闪烁
const lv=3;
G.demoSim={kind,key};
if(kind==="m"){G.weapons={};G.weapons[key]=lv;G.loadout=[key];}
else if(kind==="s"){G.weapons={vulcan:1};G.loadout=["vulcan"];G.subs={};G.subs[key]=lv;G.subLoadout=[key];}
else if(kind==="d"){G.weapons={};G.loadout=[];G.drones={};G.drones[key]=lv;G.droneLoadout=[key];}
else{G.weapons={vulcan:2};G.loadout=["vulcan"];G.u[key]=3;
if(key==="nano"){G.maxHp+=75;G.hp=G.maxHp;}
if(key==="shield")G.shield=shieldCap();}
$("demo-name").textContent=def.name;
const ds=typeof def.ds==="function"?def.ds(lv):def.ds;
$("demo-ds").textContent=ds; // 只显示介绍本身,单行不折行(nowrap 在 index.html 上)
UI="demoSim";show("o-demo");acc=0;
banner(def.name,"#41e6ff",90);
}
function exitDemoSim(){G=null;UI="codex";buildCodex();show("o-codex");}
function demoSpawn(){
const D=G.demoSim;
D.t=(D.t||0)-1;
if(D.t>0||G.enemies.length>=10)return;
D.t=Math.max(30,52-Math.floor(G.time/900)); // 渐进增密
const r=Math.random();
if(r<0.5)spawnEnemy(pick(["scout","scout","weaver","kami"]),rnd(20,W-20),-12,false); // 杂兵为主
else if(r<0.84)spawnEnemy(pick(["gunship","splitter","sniper","mine"]),rnd(20,W-20),-12,false); // 射手少数
else spawnEnemy(pick(["gunship","weaver","splitter"]),rnd(20,W-20),-12,true); // 精英
}
bestLine();