feedback7 UIBACK: a stage device explains itself on a tap — 阻隔工事 / “双眼皮” / 射击台 / 源石流发生装置 get the terrain tip's card (GitHub #228, idea and first version in PR #229 by @rickylxw, ported onto the 0.2.1 modules with t() and the four packs): numbers from the stage's own device entry, the 「场地装置」 chip, the turret's stat grid, the tile fact; in a battle the crate and the turret are found by their device UNIT (the PR matched the stage pos: a turret outside the rect stands elsewhere, the units are not in the field meta of a battle shown from its start, a destroyed crate left the snapshots and still answered), the card carries the live HP and closes with the device; the dev mock boards ?stage= and streams crate units on ?variant=devices; golden: unchanged (client only)
This commit is contained in:
1 parent
ae22335cfe
commit
df540b6210
13 files changed
+802
-18
No files matched your search
+23
-4
@@ -23,6 +23,9 @@ import { PHASE, GEO } from '/shared/constants.js';
|
||||
const params = new URLSearchParams(location.search);
|
||||
const SHOT = params.get('shot') === '1';
|
||||
const VARIANTS = new Set((params.get('variant') || '').split(',').filter(Boolean));
|
||||
// the match's stage (data/stages.json id): ?stage=act1autochess_m01 boards a stage whose own devices (crates / turrets)
|
||||
// stand on the prep field — the default act2autochess_m01 keeps its blowers and turrets off it (rows 6 / 8 / 13)
|
||||
const STAGE_ID = params.get('stage') || 'act2autochess_m01';
|
||||
|
||||
// ---- deterministic rng ----------------------------------------------------------------------------------
|
||||
let seed = 20260927;
|
||||
@@ -119,7 +122,7 @@ function pushPublic() {
|
||||
function buildState() {
|
||||
uidSeq = 100;
|
||||
seed = 20260927;
|
||||
const stage = data.lookup('stages', 'act2autochess_m01');
|
||||
const stage = data.lookup('stages', STAGE_ID) || data.lookup('stages', 'act2autochess_m01');
|
||||
const melee = stage.deployTiles.normal.melee.slice();
|
||||
const ranged = stage.deployTiles.normal.rangedOnly.slice();
|
||||
const bonds = data.list('bonds');
|
||||
@@ -206,7 +209,7 @@ function buildState() {
|
||||
|
||||
const pub = {
|
||||
phase: PHASE.PREP, round: 6, lastRound: 14, bossRound: 14, hiddenRound: 15, deadline: Date.now() + 74000, serverNow: Date.now(), modeId: 'mode_multi_hard', difficulty: 'HARD',
|
||||
stageId: 'act2autochess_m01', factions: ['FLY', 'TIMES', 'SPECIAL'], disabledBonds: [...disabled], bannedChess: banned, bossId: 'boss_5',
|
||||
stageId: STAGE_ID, factions: ['FLY', 'TIMES', 'SPECIAL'], disabledBonds: [...disabled], bannedChess: banned, bossId: 'boss_5',
|
||||
hiddenBossId: 'boss_8', teamLp: null, bossHp: null, draft: null, sp: null, players, fields: [],
|
||||
};
|
||||
if (VARIANTS.has('boss') || VARIANTS.has('bossR')) pub.round = pub.bossRound;
|
||||
@@ -472,6 +475,13 @@ function startCombat(phase) {
|
||||
const e = data.lookup('enemies', bossRec.enemyKey);
|
||||
enemies.push({ id: id++, kind: 'enemy', side: 'enemy', ownerId: ME, defId: bossRec.enemyKey, name: bossRec.name, spine: bossRec.enemyKey, avatar: bossRec.enemyKey, x: 10, y: 3, facing: -1, maxHp: e?.stats?.maxHp || 1e5, hp: (e?.stats?.maxHp || 1e5) * 0.62, spawnAt: 0, dir: 0, boss: true, dead: false });
|
||||
}
|
||||
// ?variant=devices — the stage's crates inside the rect are device UNITS (kind 'device', defId = the device key), as in the
|
||||
// real sim: absent from the field meta (a battle shown from its start), they come in with 'spawn' events once it steps;
|
||||
// __MOCK__.breakDevice(i) destroys one (hp 0 for a second in the snapshots, then gone)
|
||||
const deviceUnits = !VARIANTS.has('devices') ? [] : (S.stage.devices || [])
|
||||
.filter((d) => d.role === 'crate' && (typeof d.active === 'boolean' ? d.active : !d.hidden)
|
||||
&& d.pos[0] >= rect.r0 && d.pos[0] <= rect.r1 && d.pos[1] >= rect.c0 && d.pos[1] <= rect.c1)
|
||||
.map((d) => ({ id: id++, kind: 'device', side: 'ally', ownerId: null, defId: d.key, name: d.name, x: d.pos[1], y: d.pos[0], facing: 1, maxHp: 100, hp: 100, deadAt: null }));
|
||||
// every field — 联防 too — is fought on the round's battlefield (server unite.js; 0.2.0's escaped-level map withdrawn)
|
||||
const field = { fieldId, kind, rect, stageId: pub.stageId, units: units.map((u) => ({ ...u })) };
|
||||
store.patch('match', { field });
|
||||
@@ -489,6 +499,7 @@ function startCombat(phase) {
|
||||
if (pausedSince != null) { pausedFor += performance.now() - pausedSince; pausedSince = null; }
|
||||
const t = (performance.now() - t0 - pausedFor) / 1000 + 4;
|
||||
const ev = [];
|
||||
for (const d of deviceUnits) if (!spawned.has(d.id)) { spawned.add(d.id); ev.push(['spawn', { ...d }]); }
|
||||
for (const e of enemies) {
|
||||
if (e.dead || t < e.spawnAt) continue;
|
||||
if (!spawned.has(e.id)) { spawned.add(e.id); ev.push(['spawn', { ...e }]); }
|
||||
@@ -508,6 +519,7 @@ function startCombat(phase) {
|
||||
if (rnd() < 0.15) { const a = pick(allyState); a.hp = Math.min(a.maxHp, a.hp + 180); ev.push(['heal', a.id, 180]); }
|
||||
const snapUnits = [
|
||||
...allyState.map((a) => [a.id, a.x, a.y, a.hp, a.maxHp, a.sp, a.spMax, a.sp > 15 ? 16 : 0, 2]),
|
||||
...deviceUnits.filter((d) => d.deadAt == null || t - d.deadAt < 1).map((d) => [d.id, d.x, d.y, Math.max(0, d.hp), d.maxHp, 0, 0, 0, 2]),
|
||||
...enemies.filter((e) => !e.dead && t >= e.spawnAt).map((e) => [e.id, e.x, e.y, Math.max(1, e.hp), e.maxHp, 0, 0, e.y === 5 ? 512 : 0, e.dir ? 1 : 2]),
|
||||
];
|
||||
// wire frames exactly like server/match/fields.js: `t` is the frame type, game time travels as `gt`; b.ev first
|
||||
@@ -517,7 +529,13 @@ function startCombat(phase) {
|
||||
net._emit('b.snap', snap);
|
||||
};
|
||||
const e2boss = (e) => !!e.boss;
|
||||
S.battle = { timer: setInterval(tick, 50), fieldRef };
|
||||
S.battle = { timer: setInterval(tick, 50), fieldRef, devices: deviceUnits, hurtDevice: (i = 0, dmg = 100) => {
|
||||
const d = deviceUnits[i];
|
||||
if (!d || d.deadAt != null) return false;
|
||||
d.hp = Math.max(0, d.hp - dmg);
|
||||
if (d.hp <= 0) { d.deadAt = (performance.now() - t0 - pausedFor) / 1000 + 4; net._emit('b.ev', { t: 'b.ev', fieldId: fieldRef.id, gt: d.deadAt, ev: [['die', d.id]] }); }
|
||||
return true;
|
||||
} };
|
||||
setTimeout(tick, 60);
|
||||
}
|
||||
|
||||
@@ -790,6 +808,7 @@ async function boot() {
|
||||
render(html`<div class="app-root"><div class="app-bg" aria-hidden="true"></div><${GameScreen} /><${ConnectionBanner} /><${ToastHost} /><${UiHosts} /><${GuideHost} /></div>`, document.getElementById('app'));
|
||||
renderBar();
|
||||
store.subscribe(() => renderBar());
|
||||
globalThis.__MOCK__ = { store, S: () => S, setPhase, mutate: (fn) => { fn(S); refreshPrivate(); }, pushPublic: () => pushPublic() };
|
||||
globalThis.__MOCK__ = { store, S: () => S, setPhase, mutate: (fn) => { fn(S); refreshPrivate(); }, pushPublic: () => pushPublic(),
|
||||
hurtDevice: (i, dmg) => S?.battle?.hurtDevice?.(i, dmg) ?? false };
|
||||
}
|
||||
boot().catch((err) => console.error('[mock] boot failed', err));
|
||||
Reference in new issue
Block a user