Files
ark-stronghold-protocol/public/js/ui/enemyDrawer.js
T
sganggs 7188e0af89 feedback1 draft-info: the strategy draft shows the briefing's disabled bonds and banned operators again (GitHub issue #8 item 1)
One component, public/js/ui/matchInfo.js: matchInfoModel (the drawn set D and
the mode's inactive bonds via disabledBondSets, the briefing's bond order,
banned operators by tier, bannedPerBond) and the briefing's bond rows, legend
and banned-operator grid. The briefing renders it; the band draft opens it
read-only in a dialog (查看禁用盟约与干员 under the order list; 关闭, a tap
outside or Esc close it; its footer repeats the turn and the countdown; a turn
change closes it, the end of the draft unmounts it); the in-game 本局信息 tab
reads the same model, so its banned operators are listed by tier too. In the
dialog every size has a px floor, so phones keep readable text and a
scrollable body.

Tests: test/ui/match-info.test.js; opt-in test/ui/match-info.e2e.test.js
(SP_E2E=1: solo 标准 on desktop, co-op 绝境 with a desktop and a phone).
2026-10-03 16:08:32 +08:00

102 lines
6.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 本局信息 dialog under the top bar (opened by the left 🔍, official btn_check_player / autochess_hud_player_info_dialog,
// research 09 §2.1) with two tabs:
// 本局信息 — own strategy, stage, disabled bonds with banned-member counts and the banned operator list (default) —
// the same bonds, states and banned operators (by tier) as the briefing and the strategy draft's 本局信息
// dialog: ui/matchInfo.js matchInfoModel, drawn here as compact chips and avatars;
// 敌方情报 — the secondary enemy list: m.private.nextEnemies (icons, names, counts, tags, 特训 faction tags) + this
// match's factions and the enemy leader on boss rounds. The primary enemy preview is the pen on the board
// (the right 🔍▶▶ pans the camera there; tapping an enemy in the pen opens its detail card) — research 09 §6.2.
import { html, Icon, Tabs, MicroLabel } from './components.js';
import { Img, UnitThumb, BondGlyph, BandIcon, RichText, GIcon } from './gameComponents.js';
import { groupEnemies, factionTypes, briefingBondTip } from './gameLogic.js';
import { matchInfoModel } from './matchInfo.js';
import { factionIconUrl } from './assetUrls.js';
import { data } from '../data.js';
const cx = (...p) => p.flat().filter(Boolean).join(' ');
const TAG = { boss: '领袖', bounty: '悬赏', escort: '护卫' };
function FactionChips({ types }) {
const f = data.get('factions')?.types || {};
const m = data.get('assets');
return html`<div class="fchips">${types.map((t) => html`<span key=${t} class="fchip" title=${f[t]?.desc || ''}>
<${Img} src=${factionIconUrl(m, f[t]?.icon)} class="fchip__icon" /><span>${f[t]?.name || t}</span></span>`)}</div>`;
}
function EnemiesTab({ pub, priv, onEnemy }) {
const rows = groupEnemies(priv?.nextEnemies, (k) => data.lookup('enemies', k));
const types = factionTypes(pub?.factions);
const f = data.get('factions')?.types || {};
const total = rows.reduce((s, r) => s + r.count, 0);
const boss = pub?.bossId ? data.lookup('bosses', pub.bossId) : null;
return html`<div class="edrawer__body">
<div class="edrawer__sum">
<span>第 <b class="num">${pub?.round ?? '-'}</b> 回合 · 即将迎击 <b class="num t-orange">${total}</b> 名敌人</span>
${types.length ? html`<${FactionChips} types=${types} />` : null}
</div>
${rows.length ? html`<div class="elist">
${rows.map((r) => html`<button key=${`${r.enemyKey}|${r.tag}`} type="button" class=${cx('erow', r.tag && `erow--${r.tag}`, `erow--${r.rank.toLowerCase()}`)}
onClick=${() => onEnemy(r.enemyKey, r.count)}>
<${UnitThumb} kind="enemy" id=${r.enemyKey} size="md" />
<span class="erow__text">
<b class="erow__name">${r.name}</b>
<span class="erow__tags">
${r.tag ? html`<span class=${`etag etag--${r.tag}`}>${TAG[r.tag] || r.tag}</span>` : null}
${r.rank === 'ELITE' ? html`<span class="etag etag--elite">精英</span>` : null}
${r.fly ? html`<span class="etag etag--fly">空中</span>` : null}
${r.acTypes.map((t) => html`<span key=${t} class="etag etag--faction">${(f[t]?.name || t).replace('特训敌人·', '')}</span>`)}
</span>
</span>
<b class="erow__count num">×${r.count}</b>
</button>`)}
</div>` : html`<p class="edrawer__empty"><${GIcon} name="target" />暂无敌方情报</p>`}
${boss ? html`<div class="eboss">
<${UnitThumb} kind="enemy" id=${boss.enemyKey} size="md" />
<div><${MicroLabel} tone="gold">ENEMY LEADER // 敌方领袖</${MicroLabel}><b>${boss.name}</b>
<span class="t-lo">第 ${data.get('config')?.modes?.[pub.modeId]?.bossRound ?? pub.lastRound ?? 14} 回合 · 最终攻势</span></div>
</div>` : null}
</div>`;
}
function InfoTab({ pub, priv, onChess }) {
const { bonds, banned, perBond, stateOf } = matchInfoModel(pub, {
bonds: data.list('bonds'), chess: (id) => data.lookup('chess', id), mode: data.get('config')?.modes?.[pub?.modeId],
});
const disabled = new Set(bonds.filter((b) => stateOf(b.bondId)).map((b) => b.bondId));
const band = priv?.bandId ? data.lookup('bands', priv.bandId) : null;
const stage = pub?.stageId ? data.lookup('stages', pub.stageId) : null;
const withBans = bonds.filter((b) => disabled.has(b.bondId) || (perBond.get(b.bondId) || 0) > 0)
.sort((a, b) => (disabled.has(b.bondId) - disabled.has(a.bondId)) || ((perBond.get(b.bondId) || 0) - (perBond.get(a.bondId) || 0)));
return html`<div class="edrawer__body">
${band ? html`<div class="iband">
<${BandIcon} bandId=${band.bandId} size="md" />
<div><${MicroLabel} tone="mint">STRATEGY // 我的策略</${MicroLabel}><b>${band.name} <small class="t-lo">${band.effectName}</small></b>
<${RichText} text=${band.descRaw || band.desc} class="iband__desc" /></div>
</div>` : null}
${stage ? html`<p class="istage"><${Icon} name="rook" />战场:<b>${stage.name || stage.id}</b></p>` : null}
<h4 class="ihead">本局禁用干员情况 <small class="num">${banned.length}</small></h4>
<div class="ibonds">
${withBans.length ? withBans.map((b) => html`<span key=${b.bondId} class=${cx('ibond', disabled.has(b.bondId) && 'is-off')} title=${briefingBondTip(b.name, stateOf(b.bondId), perBond.get(b.bondId) || 0)}>
<${BondGlyph} bondId=${b.bondId} /><span>${b.name}</span>
${perBond.get(b.bondId) ? html`<span class="ibond__ban num" title="该盟约中被禁用的干员数"><${Icon} name="user" />${perBond.get(b.bondId)}</span>` : null}
</span>`) : html`<span class="t-dim">本局没有禁用盟约</span>`}
</div>
${banned.length ? html`<div class="ibanned">${banned.map((id) => html`<button key=${id} type="button" class="ibanned__one" onClick=${() => onChess(id)}>
<${UnitThumb} kind="chess" id=${id} size="sm" dim=${true} /></button>`)}</div>` : null}
</div>`;
}
/**
* @param {{ tab:'enemies'|'info', onTab:(t:string)=>void, pub:any, priv:any, onClose:Function, onEnemy:(key:string, count:number)=>void, onChess:(id:string)=>void }} props
*/
export function EnemyDrawer({ tab, onTab, pub, priv, onClose, onEnemy, onChess }) {
return html`<div class="edrawer brackets" role="dialog" aria-label=${tab === 'info' ? '本局信息' : '敌方情报'}>
<div class="edrawer__top">
<${Tabs} size="sm" value=${tab} onChange=${onTab} items=${[{ id: 'info', label: '本局信息' }, { id: 'enemies', label: '敌方情报' }]} />
<button type="button" class="edrawer__close tapx" aria-label="关闭" onClick=${onClose}><${Icon} name="close" /></button>
</div>
${tab === 'info' ? html`<${InfoTab} pub=${pub} priv=${priv} onChess=${onChess} />` : html`<${EnemiesTab} pub=${pub} priv=${priv} onEnemy=${onEnemy} />`}
</div>`;
}