主页/基地背景改为实战模拟:startHomeSim复用真实引擎随机3主Lv3+3副Lv3+2僚Lv2自动驾驶打怪(G.homeSim标记+AU.simMute静音+主循环看门狗重建,不伤/不计分/不上报);删除静态武器展柜(用户反馈意义不明且挤压排行榜);排行榜flex:1填满剩余高度;o-title半透明露出战斗;b-back4/b-back6补UI=title防看门狗不触发。playwright全流程断言通过(sim启停/武装3+3+2/刷怪/转场/静音开关),console零报错
This commit is contained in:
parent
548314e22b
commit
4b9ee4e439
@ -96,6 +96,6 @@ star-raid/
|
|||||||
- 客户端流程:boot → `/api/me` → 未登录 o-auth(登陆/注册)→ 已登录 o-home(大厅:用户面板+排行榜)→ o-title(基地菜单)
|
- 客户端流程:boot → `/api/me` → 未登录 o-auth(登陆/注册)→ 已登录 o-home(大厅:用户面板+排行榜)→ o-title(基地菜单)
|
||||||
- 结算上报:`submitRun()`(ui.js)在 endRun 与 rush 胜利(enemies.js killBoss)两处调用;`G.submitted` 防重
|
- 结算上报:`submitRun()`(ui.js)在 endRun 与 rush 胜利(enemies.js killBoss)两处调用;`G.submitted` 防重
|
||||||
- 图鉴:服务端 `codex_unlocks` 为准,登陆时与本地 SAVE.codex 并集合并;未收集=剪影+???不可演示
|
- 图鉴:服务端 `codex_unlocks` 为准,登陆时与本地 SAVE.codex 并集合并;未收集=剪影+???不可演示
|
||||||
- 背景规则:render() 只在 battleUI()(play/demoSim/pause/cards/over/slot)绘制游戏对象;大厅 UI="home" 时 drawHomeShowcase()(30s 重抽);其余=星空
|
- 背景规则:render() 用 battleUI() 门控;**主页/基地背景=实战模拟**(home.js startHomeSim:随机3主Lv3+3副Lv3+2僚Lv2,G.homeSim 标记,复用 demoSim 自动驾驶刷怪,AU.simMute 静音,不伤/不计分/不上报,主循环看门狗自动重建);其余空闲=星空。离场必须 G=null(b-home/b-quit/buildRush 已处理)
|
||||||
- 新增装备时必须同步 `server/api.js` 的 CODEX_KEYS 白名单(现 24+17+13+8=62)
|
- 新增装备时必须同步 `server/api.js` 的 CODEX_KEYS 白名单(现 24+17+13+8=62)
|
||||||
- 星币/机库/机体仍存 localStorage 不同步(v1 边界,见 DEPLOY.md)
|
- 星币/机库/机体仍存 localStorage 不同步(v1 边界,见 DEPLOY.md)
|
||||||
|
|||||||
@ -26,6 +26,7 @@ canvas#game{width:100%;height:100%;display:block;image-rendering:pixelated;image
|
|||||||
.ovl{position:absolute;inset:0;z-index:50;display:flex;flex-direction:column;align-items:center;justify-content:center;
|
.ovl{position:absolute;inset:0;z-index:50;display:flex;flex-direction:column;align-items:center;justify-content:center;
|
||||||
background:rgba(4,7,16,.88); text-align:center; padding:4%; gap:10px; overflow-y:auto}
|
background:rgba(4,7,16,.88); text-align:center; padding:4%; gap:10px; overflow-y:auto}
|
||||||
.ovl.hide{display:none}
|
.ovl.hide{display:none}
|
||||||
|
#o-title{background:rgba(4,7,16,.5)} /* 半透明:露出背后实战模拟 */
|
||||||
h1.logo{font-size:clamp(20px,5.4vw,44px);color:#fff;letter-spacing:4px;line-height:1.35;
|
h1.logo{font-size:clamp(20px,5.4vw,44px);color:#fff;letter-spacing:4px;line-height:1.35;
|
||||||
text-shadow:3px 3px 0 var(--mag), -3px -3px 0 var(--cyan), 0 0 24px rgba(65,230,255,.6)}
|
text-shadow:3px 3px 0 var(--mag), -3px -3px 0 var(--cyan), 0 0 24px rgba(65,230,255,.6)}
|
||||||
.logo-cn{font-size:clamp(12px,2.6vw,20px);color:var(--gold);letter-spacing:12px;text-shadow:2px 2px 0 #7a4b00}
|
.logo-cn{font-size:clamp(12px,2.6vw,20px);color:var(--gold);letter-spacing:12px;text-shadow:2px 2px 0 #7a4b00}
|
||||||
@ -127,7 +128,7 @@ h1.logo{font-size:clamp(20px,5.4vw,44px);color:#fff;letter-spacing:4px;line-heig
|
|||||||
.auth-f .btn{width:100%;min-width:0;margin-top:2px}
|
.auth-f .btn{width:100%;min-width:0;margin-top:2px}
|
||||||
/* ---------- 飞行员大厅 ---------- */
|
/* ---------- 飞行员大厅 ---------- */
|
||||||
#o-home{background:rgba(4,7,16,.42);justify-content:flex-start;gap:8px;overflow-y:auto}
|
#o-home{background:rgba(4,7,16,.42);justify-content:flex-start;gap:8px;overflow-y:auto}
|
||||||
#o-home .lb{width:min(94%,430px);background:var(--panel);border:3px solid var(--line);padding:10px 10px 8px;margin-top:auto;margin-bottom:2%}
|
#o-home .lb{width:min(94%,430px);flex:1;min-height:0;display:flex;flex-direction:column;background:var(--panel);border:3px solid var(--line);padding:10px 10px 8px;margin-bottom:2%}
|
||||||
.lb-tabs{display:flex;gap:6px;justify-content:center;margin:6px 0 8px}
|
.lb-tabs{display:flex;gap:6px;justify-content:center;margin:6px 0 8px}
|
||||||
.lb-tabs button{font:inherit;font-size:clamp(7px,1.3vw,9px);background:var(--panel2);color:var(--dim);
|
.lb-tabs button{font:inherit;font-size:clamp(7px,1.3vw,9px);background:var(--panel2);color:var(--dim);
|
||||||
border:2px solid var(--line);padding:6px 10px;cursor:pointer;letter-spacing:1px}
|
border:2px solid var(--line);padding:6px 10px;cursor:pointer;letter-spacing:1px}
|
||||||
@ -138,5 +139,5 @@ h1.logo{font-size:clamp(20px,5.4vw,44px);color:#fff;letter-spacing:4px;line-heig
|
|||||||
.lb-row span:first-child{color:var(--dim)}
|
.lb-row span:first-child{color:var(--dim)}
|
||||||
.lb-row.lb-head{color:var(--dim);border-bottom:2px solid var(--line)}
|
.lb-row.lb-head{color:var(--dim);border-bottom:2px solid var(--line)}
|
||||||
.lb-row.me{background:rgba(65,230,255,.12);outline:1px solid rgba(65,230,255,.35)}
|
.lb-row.me{background:rgba(65,230,255,.12);outline:1px solid rgba(65,230,255,.35)}
|
||||||
.lb-list{max-height:34%;min-height:60px;overflow-y:auto}
|
.lb-list{flex:1;min-height:100px;overflow-y:auto}
|
||||||
.lb-empty{color:var(--dim);font-size:clamp(7px,1.3vw,9px);padding:14px 0;letter-spacing:2px}
|
.lb-empty{color:var(--dim);font-size:clamp(7px,1.3vw,9px);padding:14px 0;letter-spacing:2px}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
/* ---------------- 音频:程序化合成 ---------------- */
|
/* ---------------- 音频:程序化合成 ---------------- */
|
||||||
const AU={ctx:null,master:null,mgain:null,
|
const AU={ctx:null,master:null,mgain:null,simMute:false,
|
||||||
init(){ if(this.ctx) return;
|
init(){ if(this.ctx) return;
|
||||||
try{ this.ctx=new (window.AudioContext||window.webkitAudioContext)();
|
try{ this.ctx=new (window.AudioContext||window.webkitAudioContext)();
|
||||||
this.master=this.ctx.createGain(); this.master.gain.value=0.5; this.master.connect(this.ctx.destination);
|
this.master=this.ctx.createGain(); this.master.gain.value=0.5; this.master.connect(this.ctx.destination);
|
||||||
@ -9,12 +9,12 @@ const AU={ctx:null,master:null,mgain:null,
|
|||||||
},
|
},
|
||||||
now(){return this.ctx?this.ctx.currentTime:0;},
|
now(){return this.ctx?this.ctx.currentTime:0;},
|
||||||
env(g,t,a,d,v){g.gain.setValueAtTime(0,t);g.gain.linearRampToValueAtTime(v,t+a);g.gain.exponentialRampToValueAtTime(0.001,t+a+d);},
|
env(g,t,a,d,v){g.gain.setValueAtTime(0,t);g.gain.linearRampToValueAtTime(v,t+a);g.gain.exponentialRampToValueAtTime(0.001,t+a+d);},
|
||||||
tone(f,type,dur,vol=0.2,slide=0){ if(!this.ctx||SAVE.mute)return;
|
tone(f,type,dur,vol=0.2,slide=0){ if(!this.ctx||SAVE.mute||this.simMute)return;
|
||||||
const t=this.now(),o=this.ctx.createOscillator(),g=this.ctx.createGain();
|
const t=this.now(),o=this.ctx.createOscillator(),g=this.ctx.createGain();
|
||||||
o.type=type;o.frequency.setValueAtTime(f,t);
|
o.type=type;o.frequency.setValueAtTime(f,t);
|
||||||
if(slide) o.frequency.exponentialRampToValueAtTime(Math.max(20,f+slide),t+dur);
|
if(slide) o.frequency.exponentialRampToValueAtTime(Math.max(20,f+slide),t+dur);
|
||||||
this.env(g,t,0.005,dur,vol); o.connect(g);g.connect(this.master); o.start(t);o.stop(t+dur+0.05); },
|
this.env(g,t,0.005,dur,vol); o.connect(g);g.connect(this.master); o.start(t);o.stop(t+dur+0.05); },
|
||||||
noise(dur,vol=0.25,hp=400){ if(!this.ctx||SAVE.mute)return;
|
noise(dur,vol=0.25,hp=400){ if(!this.ctx||SAVE.mute||this.simMute)return;
|
||||||
const t=this.now(),len=Math.floor(this.ctx.sampleRate*dur),buf=this.ctx.createBuffer(1,len,this.ctx.sampleRate),d=buf.getChannelData(0);
|
const t=this.now(),len=Math.floor(this.ctx.sampleRate*dur),buf=this.ctx.createBuffer(1,len,this.ctx.sampleRate),d=buf.getChannelData(0);
|
||||||
for(let i=0;i<len;i++)d[i]=(Math.random()*2-1)*(1-i/len);
|
for(let i=0;i<len;i++)d[i]=(Math.random()*2-1)*(1-i/len);
|
||||||
const s=this.ctx.createBufferSource();s.buffer=buf;
|
const s=this.ctx.createBufferSource();s.buffer=buf;
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
/* ================= 飞行员大厅(主页):排行榜 + 图鉴展柜背景 ================= */
|
/* ================= 飞行员大厅(主页):排行榜 + 图鉴展柜背景 ================= */
|
||||||
let LB_SORT = "endless";
|
let LB_SORT = "endless";
|
||||||
let HOME_SHOW = null;
|
|
||||||
|
|
||||||
/* 图鉴已收集数(只统计合法 key) */
|
/* 图鉴已收集数(只统计合法 key) */
|
||||||
function codexCount() {
|
function codexCount() {
|
||||||
@ -12,9 +11,8 @@ function codexCount() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function enterHome() {
|
async function enterHome() {
|
||||||
G = null;
|
G = null; // 触发主循环看门狗重建实战模拟背景
|
||||||
UI = "home";
|
UI = "home";
|
||||||
rollHomeShow();
|
|
||||||
show("o-home");
|
show("o-home");
|
||||||
/* 刷新账号数据(stats/codex),再渲染面板与排行榜 */
|
/* 刷新账号数据(stats/codex),再渲染面板与排行榜 */
|
||||||
const r = await NET.api("/api/me", "GET");
|
const r = await NET.api("/api/me", "GET");
|
||||||
@ -64,59 +62,17 @@ async function buildLeaderboard(sort) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- 大厅背景:随机军械展柜 + 随机刷怪巡游 ---------- */
|
/* ---------- 大厅/基地背景:实战模拟 ----------
|
||||||
const HOME_MOB_TYPES = ["scout", "weaver", "kami", "splitter", "mine", "gunship", "sniper"];
|
复用真实战斗引擎:随机 3主武Lv3 + 3副武Lv3 + 2僚机Lv2,自动驾驶,随机刷怪。
|
||||||
|
与图鉴演示同源(G.demoSim 真值→自动走位/无HUD/demoSpawn),G.homeSim 标记用于静音与渲染门控 */
|
||||||
function rollHomeShow() {
|
function startHomeSim() {
|
||||||
HOME_SHOW = {
|
newRun("stage", "falcon", 0);
|
||||||
m: shuffle(Object.keys(WEAPONS)).slice(0, 3),
|
const build = rollRushBuild(); // 与BOSS挑战同款随机武装
|
||||||
s: shuffle(Object.keys(SUBS)).slice(0, 3),
|
G.weapons = {}; build.m.forEach((k) => (G.weapons[k] = 3)); G.loadout = build.m.slice();
|
||||||
d: shuffle(Object.keys(DRONES)).slice(0, 2),
|
G.subs = {}; build.s.forEach((k) => (G.subs[k] = 3)); G.subLoadout = build.s.slice();
|
||||||
t: 0, spawnT: 20, mobs: [],
|
G.drones = {}; build.d.forEach((k) => (G.drones[k] = 2)); G.droneLoadout = build.d.slice();
|
||||||
};
|
G.xpNeed = 1e9; // 不升级打断
|
||||||
}
|
G.p.inv = 999999; // 背景模拟:玩家无伤
|
||||||
|
G.demoSim = { home: true };
|
||||||
/* 由 render() 在 UI==="home" 时每帧调用(dt=1) */
|
G.homeSim = true;
|
||||||
function drawHomeShowcase(dt) {
|
|
||||||
if (!HOME_SHOW || HOME_SHOW.t > 1800) rollHomeShow(); // 30 秒重抽一轮
|
|
||||||
const hs = HOME_SHOW;
|
|
||||||
hs.t += dt;
|
|
||||||
/* 展柜:3主武+3副武+2僚机,两排漂浮 */
|
|
||||||
const items = [
|
|
||||||
...hs.m.map((k) => ({ d: WEAPONS[k] })),
|
|
||||||
...hs.s.map((k) => ({ d: SUBS[k] })),
|
|
||||||
...hs.d.map((k) => ({ d: DRONES[k] })),
|
|
||||||
];
|
|
||||||
ctx.textAlign = "center";
|
|
||||||
items.forEach((it, i) => {
|
|
||||||
const col = i % 4, row = (i / 4) | 0;
|
|
||||||
const x = W / 2 + (col - 1.5) * 56;
|
|
||||||
const y = 118 + row * 52 + Math.sin(hs.t * 0.03 + i * 1.7) * 2.5;
|
|
||||||
const img = it.d.icon ? ICONS[it.d.icon] : SPR[it.d.spr];
|
|
||||||
if (img) {
|
|
||||||
ctx.globalAlpha = 0.85;
|
|
||||||
ctx.drawImage(img, x - img.width * 1.5, y - img.height * 1.5, img.width * 3, img.height * 3);
|
|
||||||
ctx.globalAlpha = 1;
|
|
||||||
}
|
|
||||||
ctx.font = "6px 'Press Start 2P',monospace";
|
|
||||||
ctx.fillStyle = "#7f8cb8";
|
|
||||||
ctx.fillText(it.d.name, x, y + 22);
|
|
||||||
});
|
|
||||||
/* 随机刷怪:从顶部飘入,正弦摆动 */
|
|
||||||
hs.spawnT -= dt;
|
|
||||||
if (hs.spawnT <= 0 && hs.mobs.length < 8) {
|
|
||||||
hs.spawnT = rndi(50, 110);
|
|
||||||
const ty = pick(HOME_MOB_TYPES), elite = Math.random() < 0.2;
|
|
||||||
const spr = SPR[(elite ? "e_" : "") + ty] || SPR[ty] || SPR.scout;
|
|
||||||
hs.mobs.push({ x: rnd(20, W - 20), y: -16, vy: rnd(0.25, 0.6), ph: rnd(0, TAU), amp: rnd(6, 26), spr, sc: elite ? 1.8 : 1.3, t: 0 });
|
|
||||||
}
|
|
||||||
for (const m of hs.mobs) {
|
|
||||||
m.t += dt;
|
|
||||||
m.y += m.vy * dt;
|
|
||||||
const mx = m.x + Math.sin(m.t * 0.02 + m.ph) * m.amp;
|
|
||||||
ctx.globalAlpha = 0.8;
|
|
||||||
ctx.drawImage(m.spr, mx - (m.spr.width * m.sc) / 2, m.y - (m.spr.height * m.sc) / 2, m.spr.width * m.sc, m.spr.height * m.sc);
|
|
||||||
ctx.globalAlpha = 1;
|
|
||||||
}
|
|
||||||
hs.mobs = hs.mobs.filter((m) => m.y < H + 24);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -232,13 +232,12 @@ function drawSeg(x1,y1,x2,y2,w,col,alpha){
|
|||||||
ctx.fillRect(Math.round(lerp(x1,x2,t2)-w/2),Math.round(lerp(y1,y2,t2)-w/2),w,w);}
|
ctx.fillRect(Math.round(lerp(x1,x2,t2)-w/2),Math.round(lerp(y1,y2,t2)-w/2),w,w);}
|
||||||
ctx.globalAlpha=1;
|
ctx.globalAlpha=1;
|
||||||
}
|
}
|
||||||
/* 战斗相关界面才绘制游戏对象;大厅/标题/登陆页只画星空(修复:回基地后背景卡在上场残影) */
|
/* 战斗相关界面才绘制游戏对象;大厅/标题跑实战模拟背景(修复:回基地后背景卡在上场残影) */
|
||||||
function battleUI(){return UI==="play"||UI==="demoSim"||UI==="pause"||UI==="cards"||UI==="over"||UI==="slot";}
|
function battleUI(){return UI==="play"||UI==="demoSim"||UI==="pause"||UI==="cards"||UI==="over"||UI==="slot"||!!(G&&G.homeSim&&(UI==="home"||UI==="title"));}
|
||||||
function render(){
|
function render(){
|
||||||
ctx.save();
|
ctx.save();
|
||||||
if(G&&G.shake>0){ctx.translate(rnd(-G.shake,G.shake)*0.5,rnd(-G.shake,G.shake)*0.5);}
|
if(G&&G.shake>0){ctx.translate(rnd(-G.shake,G.shake)*0.5,rnd(-G.shake,G.shake)*0.5);}
|
||||||
drawBG(1);
|
drawBG(1);
|
||||||
if(UI==="home"&&typeof drawHomeShowcase==="function")drawHomeShowcase(1);
|
|
||||||
if(G&&battleUI()){
|
if(G&&battleUI()){
|
||||||
// 拾取物
|
// 拾取物
|
||||||
for(const k of G.picks){const bob=Math.sin(k.t*0.1)*1.5;drawSprite(SPR[k.spr],k.x,k.y+bob);}
|
for(const k of G.picks){const bob=Math.sin(k.t*0.1)*1.5;drawSprite(SPR[k.spr],k.x,k.y+bob);}
|
||||||
@ -469,7 +468,10 @@ function loop(ts){
|
|||||||
requestAnimationFrame(loop);
|
requestAnimationFrame(loop);
|
||||||
const dt=Math.min(100,ts-last);last=ts;
|
const dt=Math.min(100,ts-last);last=ts;
|
||||||
MUS.tick();
|
MUS.tick();
|
||||||
if(UI==="play"||UI==="demoSim"){
|
// 主页/基地背景=实战模拟(自动驾驶随机武装打怪);离开即销毁,永不计分
|
||||||
|
if((UI==="home"||UI==="title")&&(!G||!G.homeSim)&&typeof startHomeSim==="function")startHomeSim();
|
||||||
|
AU.simMute=!!(G&&G.homeSim); // 背景模拟战静音
|
||||||
|
if(UI==="play"||UI==="demoSim"||(G&&G.homeSim&&(UI==="home"||UI==="title"))){
|
||||||
acc+=dt;
|
acc+=dt;
|
||||||
let steps=0;
|
let steps=0;
|
||||||
while(acc>=1000/60&&steps<4){update();acc-=1000/60;steps++;}
|
while(acc>=1000/60&&steps<4){update();acc-=1000/60;steps++;}
|
||||||
|
|||||||
@ -13,8 +13,8 @@ $("b-back2").onclick=()=>show("o-title");
|
|||||||
$("b-hangar").onclick=()=>{AU.init();buildShop();show("o-hangar");};
|
$("b-hangar").onclick=()=>{AU.init();buildShop();show("o-hangar");};
|
||||||
$("b-codex").onclick=()=>{AU.init();buildCodex();show("o-codex");};
|
$("b-codex").onclick=()=>{AU.init();buildCodex();show("o-codex");};
|
||||||
$("b-rush").onclick=()=>{AU.init();buildRush();show("o-rush");};
|
$("b-rush").onclick=()=>{AU.init();buildRush();show("o-rush");};
|
||||||
$("b-back6").onclick=()=>show("o-title");
|
$("b-back6").onclick=()=>{UI="title";show("o-title");};
|
||||||
$("b-back4").onclick=()=>{bestLine();show("o-title");};
|
$("b-back4").onclick=()=>{UI="title";bestLine();show("o-title");};
|
||||||
$("b-back5").onclick=exitDemoSim;
|
$("b-back5").onclick=exitDemoSim;
|
||||||
$("b-start").onclick=()=>startGame();
|
$("b-start").onclick=()=>startGame();
|
||||||
if("ontouchstart" in window){const pb=$("tb-pause");pb.style.display="block";pb.onclick=()=>togglePause();}
|
if("ontouchstart" in window){const pb=$("tb-pause");pb.style.display="block";pb.onclick=()=>togglePause();}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user