feedback5 R5: split the in-match UI logic into modules
public/js/ui/gameLogic.js becomes a re-export barrel. The bodies move verbatim into public/js/ui/gameLogic/*.js (shared, phases, camera, watch, bonds, shop, placement, draft, enemies, format, shortcuts, settings, result, loadout, panel). The barrel re-exports the same 96 names in the same order. isObj and int gain export on shared.js so sibling modules can import them; they stay off the barrel. No importer changes.
This commit is contained in:
1 parent
8ab6d021da
commit
eb04f5dfa4
16 files changed
+1864
-1778
No files matched your search
@@ -0,0 +1,118 @@
|
||||
// ui/gameLogic/watch.js — field ids, the watch switcher, emote bubbles. Re-exported from ../gameLogic.js.
|
||||
|
||||
import { isObj, sortedPlayers } from './shared.js';
|
||||
import { isCombatPhase } from './phases.js';
|
||||
|
||||
|
||||
/** Own normal field id (DESIGN §8.3). */
|
||||
export const ownFieldId = (playerId) => `n:${playerId}`;
|
||||
|
||||
/**
|
||||
* Field the player sits in right now (own normal field, or the unite/boss field that lists them).
|
||||
* @param {any} pub
|
||||
* @param {string} playerId
|
||||
*/
|
||||
export function homeFieldId(pub, playerId) {
|
||||
const fields = Array.isArray(pub?.fields) ? pub.fields : [];
|
||||
const special = fields.find((f) => isObj(f) && f.kind !== 'normal' && Array.isArray(f.players) && f.players.includes(playerId) && f.live !== false);
|
||||
if (special) return special.fieldId;
|
||||
const me = sortedPlayers(pub).find((p) => p.playerId === playerId);
|
||||
if (me && typeof me.fieldId === 'string' && me.fieldId) return me.fieldId;
|
||||
return ownFieldId(playerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Next/previous watchable field for the ‹ › view switcher.
|
||||
* @param {Array<{fieldId:string, live?:boolean}>} fields
|
||||
* @param {string|null} current
|
||||
* @param {1|-1} dir
|
||||
* @returns {string|null}
|
||||
*/
|
||||
export function cycleField(fields, current, dir = 1) {
|
||||
const list = (Array.isArray(fields) ? fields : []).filter((f) => isObj(f) && typeof f.fieldId === 'string' && f.live !== false);
|
||||
if (list.length === 0) return null;
|
||||
const i = list.findIndex((f) => f.fieldId === current);
|
||||
if (i < 0) return list[0].fieldId;
|
||||
const n = list.length;
|
||||
return list[(((i + dir) % n) + n) % n].fieldId;
|
||||
}
|
||||
|
||||
/**
|
||||
* What clicking a teammate row asks the server to watch, or why it can't (mirror of server Match.watch): an
|
||||
* eliminated player has no board to watch, and during 最终攻势 / 隐秘核心 a fighting player can't see the other
|
||||
* pair's boss field ("无法查看另一组队友的战场情况").
|
||||
* @param {any} p m.public player row
|
||||
* @param {any} pub
|
||||
* @param {string} myId
|
||||
* @returns {{ fieldId: string } | { reason: string }}
|
||||
*/
|
||||
export function watchTarget(p, pub, myId) {
|
||||
if (!isObj(p)) return { reason: '无效的目标' };
|
||||
if (p.alive === false) return { reason: '该队友已被淘汰,无法查看其阵地' };
|
||||
const combat = isCombatPhase(pub?.phase);
|
||||
const fieldId = (combat && typeof p.fieldId === 'string' && p.fieldId) || ownFieldId(p.playerId);
|
||||
if (combat) {
|
||||
const fields = Array.isArray(pub?.fields) ? pub.fields.filter(isObj) : [];
|
||||
const f = fields.find((x) => x.fieldId === fieldId);
|
||||
const me = sortedPlayers(pub).find((x) => x.playerId === myId);
|
||||
const mine = fields.find((x) => Array.isArray(x.players) && x.players.includes(myId));
|
||||
if (f && (f.kind === 'boss' || f.kind === 'hidden') && me?.alive !== false && mine && mine.fieldId !== f.fieldId) {
|
||||
return { reason: '无法查看另一组队友的战场' };
|
||||
}
|
||||
}
|
||||
return { fieldId };
|
||||
}
|
||||
|
||||
/**
|
||||
* Label of the ‹ › view switcher: the watched field (also one whose battle already ended), else the own field —
|
||||
* or, for an eliminated spectator (no own field), the watched player / 观战.
|
||||
* @param {any} pub
|
||||
* @param {string|null} watching fieldId on screen
|
||||
* @param {string} myId
|
||||
* @param {boolean} [spectating]
|
||||
*/
|
||||
export function switcherLabel(pub, watching, myId, spectating = false) {
|
||||
const fields = (Array.isArray(pub?.fields) ? pub.fields : []).filter(isObj);
|
||||
const cur = fields.find((f) => f.fieldId === watching);
|
||||
if (cur) return fieldLabel(cur, pub, myId);
|
||||
if (typeof watching === 'string' && watching.startsWith('n:') && watching !== ownFieldId(myId)) {
|
||||
return sortedPlayers(pub).find((p) => p.playerId === watching.slice(2))?.name || '队友';
|
||||
}
|
||||
return spectating ? '观战' : '自己';
|
||||
}
|
||||
|
||||
/**
|
||||
* Human label of a field for the view switcher.
|
||||
* @param {any} field { fieldId, kind, players }
|
||||
* @param {any} pub
|
||||
* @param {string} myId
|
||||
*/
|
||||
export function fieldLabel(field, pub, myId) {
|
||||
if (!isObj(field)) return '—';
|
||||
const names = new Map(sortedPlayers(pub).map((p) => [p.playerId, p.name || '博士']));
|
||||
const ps = Array.isArray(field.players) ? field.players : [];
|
||||
if (field.kind === 'unite') return ps.includes(myId) ? '联防(自己)' : '联防阵地';
|
||||
if (field.kind === 'boss' || field.kind === 'hidden') {
|
||||
if (ps.includes(myId)) return ps.length > 1 ? '全景' : '自己';
|
||||
return ps.map((id) => names.get(id) || '博士').join(' · ') || '领袖战场';
|
||||
}
|
||||
if (ps.includes(myId) || field.fieldId === ownFieldId(myId)) return '自己';
|
||||
const id = ps[0] ?? String(field.fieldId || '').replace(/^n:/, '');
|
||||
return names.get(id) || '队友';
|
||||
}
|
||||
|
||||
/**
|
||||
* Active emote bubbles: playerId → { id, seq, at } for emotes younger than ttl.
|
||||
* @param {Array<{seq:number, playerId:string, id:string, at:number}>} emotes
|
||||
* @param {number} now
|
||||
* @param {number} [ttl]
|
||||
*/
|
||||
export function activeBubbles(emotes, now, ttl = 3000) {
|
||||
const out = new Map();
|
||||
for (const e of Array.isArray(emotes) ? emotes : []) {
|
||||
if (!isObj(e) || !(now - e.at < ttl) || e.at > now + 1000) continue;
|
||||
const cur = out.get(e.playerId);
|
||||
if (!cur || cur.seq < e.seq) out.set(e.playerId, { id: e.id, seq: e.seq, at: e.at });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
Reference in new issue
Block a user