feedback6 R21C: the bond popup lists every 自选 pick of the player (m.private.diy, the V and VI slots) in each bond its operator carries — dimmed like an unowned member until a copy is owned (not bought, or not in the shop yet), ✕ like a banned member when all its bonds are off this match (m.private.diyBanned), one row per slot drawn as its normal form with the 「自选」 tag, a tap opening the operator's card; 在场 and the 成员 header count an unowned pick for nothing, the order rule is kept (the owner's report of 2026-10-07 「自选编队的干员在局内对应盟约展开的名单里没有显示出来」); a watched teammate's popup still lists only their 自选 pieces on the field (m.public sends no picks), and such a row now hands the unit's pick to the card (BondPopup onMember → the detail target's diy → resolveDetail) instead of opening the empty 甄选干员 slot's card; golden: unchanged (client)
This commit is contained in:
1 parent
783bd30afd
commit
41fd3c7e39
7 files changed
+264
-17
No files matched your search
@@ -21,6 +21,11 @@
|
||||
// 0.2.0 补位: a member the player fields as its stand-in (the own m.private.standIns; a teammate's unit that says so —
|
||||
// UnitInfo `standInFor`) is drawn as the stand-in — avatar, name, a small 「替补」 mark on the thumbnail — and its row
|
||||
// opens the stand-in's card (gameLogic memberStandIn; the owner's recall of the official mode, 2026-10-06).
|
||||
// 0.2.1 自选编队 (the owner's report of 2026-10-07): each 自选 pick of the player (m.private.diy) is a row of every bond its
|
||||
// operator carries — the operator's name and portrait with the 「自选」 tag, dimmed until owned (not bought, or not in the
|
||||
// shop yet), ✕ when all its bonds are off this match (m.private.diyBanned) — and opens the operator's card; a watched
|
||||
// teammate's 自选 pieces on the field are rows too (their picks are not sent), opening THEIR operator's card (the unit's
|
||||
// pick goes with onMember: never the empty 甄选干员 slot's card).
|
||||
|
||||
import { html, BondDisc, Icon, MicroLabel, Tooltip } from './components.js';
|
||||
import { RichText, UnitThumb, BondGlyph, GIcon } from './gameComponents.js';
|
||||
@@ -86,12 +91,13 @@ export function BondStrip({ bonds, layersDisabled = false, onOpen, openId = null
|
||||
/**
|
||||
* Bond detail popup.
|
||||
* @param {{ bondId: string, entry?: any, priv?: any, banned?: string[], onClose: Function,
|
||||
* onMember?: (chessId:string, items?:string[]|null, standInFor?:string|null)=>void,
|
||||
* onMember?: (chessId:string, items?:string[]|null, standInFor?:string|null, diy?:any)=>void,
|
||||
* place?: 'left'|'beside'|'besideR'|'right'|null, over?: boolean, beside?: 'left'|'right'|null, owner?: string|null, off?: boolean }} props —
|
||||
* `place`: where it opens (gameLogic bondPopupPlace; `beside: 'left'` = the older spelling of 'beside'); `over`: above the
|
||||
* detail card; `owner`: the watched teammate's name (`entry` / `priv` are then theirs: ui/watchBonds.js); `onMember`
|
||||
* gets a 变形同构体 row's item ids too (its card shows the pair and the granted chip), null for a plain member, and a
|
||||
* stand-in row's `standInFor` (0.2.0 补位: a teammate's card then shows the stand-in too); `off`: the
|
||||
* stand-in row's `standInFor` (0.2.0 补位: a teammate's card then shows the stand-in too), and a teammate's 自选 row's
|
||||
* pick (`diy`, its unit's: the card composes their operator; the own rows' card reads m.private.diy); `off`: the
|
||||
* mode never activates this bond (gameLogic modeOffBonds — 标准's 10 inactive bonds): 本局禁用 instead of 未激活, with a
|
||||
* note, no 在场 count and no 当前效果 block (its numbers would promise an effect the mode never gives; the bond text stays).
|
||||
* `entry.harmony` (the server's bond views: the +1 调和 added to `count`): 在场 n(含调和 +1) and the 调和 row
|
||||
@@ -166,8 +172,8 @@ export function BondPopup({ bondId, entry, priv, banned = [], onClose, onMember,
|
||||
const name = si ? si.name : mb.name;
|
||||
const items = mb.granted && Array.isArray(mb.items) ? mb.items : null;
|
||||
return html`<button key=${mb.id} type="button" class=${cx('bpop__member', mb.onBoard && 'is-on', mb.owned && !mb.onBoard && 'is-owned', mb.banned && 'is-banned', mb.granted && 'is-granted', si && 'is-standin')}
|
||||
onClick=${() => (si ? onMember?.(mb.id, items, si.standInFor) : onMember?.(mb.id, items))}
|
||||
data-granted=${mb.granted ? '1' : null} data-standin=${si ? si.charId : null}
|
||||
onClick=${() => (si ? onMember?.(mb.id, items, si.standInFor) : mb.pick ? onMember?.(mb.id, items, null, mb.pick) : onMember?.(mb.id, items))}
|
||||
data-granted=${mb.granted ? '1' : null} data-standin=${si ? si.charId : null} data-diy=${mb.diy ? mb.rec?.charId || '1' : null}
|
||||
title=${`${name}${si ? t('({note})', { note: standInForText(mb.name) }) : ''}${mb.granted ? t('(变形同构体:视为本盟约成员)') : ''}${mb.banned ? t('(本局禁用)') : mb.onBoard ? t('(在场)') : mb.owned ? t('(整备区)') : ''}`}>
|
||||
<${UnitThumb} kind="chess" id=${mb.id} size="sm" dim=${!mb.owned || mb.banned} rec=${si || (mb.diy ? mb.rec : null)} title=${name} />
|
||||
<span class="bpop__mname">${name}</span>
|
||||
|
||||
@@ -644,7 +644,8 @@ function TerrainDetail({ terrain }) {
|
||||
* m.private.standIns — a unit carrying `standInFor`, and a teammate's bond popup row that says it (`target.standInFor`)
|
||||
* — resolve with `standIn` (the composed stand-in record the card shows: portrait, name, body, with the chess's
|
||||
* bonds and 特质); 0.2.0 自选编队: the player's own pieces and cards of a DIY slot it filled (m.private.diy) — and a
|
||||
* unit carrying `diy` — resolve to the composed 自选 record (`chess`, the operator) with `diy` = the pick
|
||||
* unit carrying `diy`, and a teammate's bond popup 自选 row that hands its unit's pick on (`target.diy`, 0.2.1) —
|
||||
* resolve to the composed 自选 record (`chess`, the operator) with `diy` = the pick
|
||||
*/
|
||||
export function resolveDetail(target, pieces, { priv = null, backups = data.get('backups') } = {}) {
|
||||
if (!target) return null;
|
||||
@@ -676,7 +677,9 @@ export function resolveDetail(target, pieces, { priv = null, backups = data.get(
|
||||
// do not read the viewer's 补位 list: a teammate's row shows the stand-in only when its unit says it is one —
|
||||
// `target.standInFor`, from UnitInfo through ui/watchBonds.js ownerBoard)
|
||||
const foreign = !!target.foreign || (target.owner != null && !!priv && target.owner !== priv.playerId);
|
||||
const d = foreign ? null : ownDiy(c);
|
||||
// (a teammate's 自选 row hands its unit's pick on, `target.diy`: their operator, not the empty 甄选干员 slot — 0.2.1)
|
||||
const mate = foreign && c && target.diy && typeof target.diy === 'object' ? diyRecordFor(c, target.diy, dd) : null;
|
||||
const d = mate ? { chess: mate, diy: target.diy } : foreign ? null : ownDiy(c);
|
||||
if (d) return { type: 'chess', chess: d.chess, hint: target.hint || null, standIn: null, diy: d.diy, ...(items.length ? { unitItems: items } : {}) };
|
||||
const si = !c ? null : foreign ? (typeof target.standInFor === 'string' && target.standInFor ? standInOf(c, backups) : null) : ownSi(c);
|
||||
return c ? { type: 'chess', chess: c, hint: target.hint || null, standIn: si, ...(items.length ? { unitItems: items } : {}) } : null;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// ui/gameLogic/bonds.js — bond counts, members, harmony, 本局禁用. Re-exported from ../gameLogic.js.
|
||||
|
||||
import { int, isObj } from './shared.js';
|
||||
import { diyPicks } from './diy.js';
|
||||
import { t } from '../../../../shared/i18n.js';
|
||||
|
||||
|
||||
@@ -158,10 +159,17 @@ export function harmonyMembers(priv, getChess = () => null) {
|
||||
* like the count's distinct members), so "成员 x/y" agrees with the count the server reports (在场; memberHeadCount).
|
||||
* 0.2.0 自选编队: the 自选 pieces of the bond (a DIY slot filled with an operator, whose bonds come from its factions — the
|
||||
* own pieces through `getChess` (gameLogic/diy.js diyGetter), a teammate's through `pieceRecord` (its UnitInfo `diy`
|
||||
* pick) are rows too (`diy: true`, the operator's name and record `rec`).
|
||||
* pick) are rows too (`diy: true`, the operator's name and record `rec`: its normal form, one row per slot like a
|
||||
* member's; `pick`: a teammate's piece's pick — the card the row opens composes the operator from it). Since 0.2.1
|
||||
* (the owner's report of 2026-10-07 「自选编队的干员在局内对应盟约展开的名单里没有显示出来」) every 自选 pick of the
|
||||
* player (m.private.diy: the V and VI slots) is a row of each bond its operator carries, owned or not — an unowned one
|
||||
* (not bought, or not in the shop yet: the slot's 调度中心 level) dimmed like an unowned member, one whose bonds are all
|
||||
* off this match (m.private.diyBanned: no stock, it never shows in the shop) `banned` like a banned member. A
|
||||
* teammate's board carries no picks (m.public sends none): their 自选 pieces on the field only.
|
||||
* `inHand`: in the 整备区 (hand), not the 5 temporary slots — what BOARD_AND_DECK bonds (投资人 远见 奇迹) count.
|
||||
* @param {any} bond bonds.json record
|
||||
* @param {any} priv m.private (hand/board/temp) — or a teammate's field operators (ui/watchBonds.js ownerBoard)
|
||||
* @param {any} priv m.private (hand/board/temp, the 自选 picks `diy` / `diyBanned`) — or a teammate's field operators
|
||||
* (ui/watchBonds.js ownerBoard)
|
||||
* @param {Set<string>|string[]} [banned] banned base chess ids
|
||||
* @param {(id:string)=>any} [getChess]
|
||||
* @param {(id:string)=>any} [getItem] items.json lookup (the 变形同构体 pairings)
|
||||
@@ -178,14 +186,20 @@ export function bondMembers(bond, priv, banned = [], getChess = () => null, getI
|
||||
/** base id → { on: on the board?, hand: in the hand?, items: the wearer's item ids } — operators of the player that join this bond through 变形同构体 */
|
||||
const granted = new Map();
|
||||
const grants = (p) => typeof bond?.bondId === 'string' && grantedBonds(p.items, getItem).includes(bond.bondId);
|
||||
/** base id → { on, hand, rec } — the player's 自选 pieces whose operator carries this bond */
|
||||
const recOf = (p) => (pieceRecord ? pieceRecord(p) : getChess(p.id));
|
||||
/** a composed 自选 record whose operator carries this bond */
|
||||
const diyOfBond = (rec) => isObj(rec) && typeof rec.diyFor === 'string' && Array.isArray(rec.bonds) && rec.bonds.includes(bond?.bondId);
|
||||
/** slot base id → { on, hand, owned, rec, pick } — the player's 自选 pieces and picks whose operator carries this bond */
|
||||
const diy = new Map();
|
||||
const diyOf = (p, on, hand) => {
|
||||
const rec = pieceRecord ? pieceRecord(p) : getChess(p.id);
|
||||
if (!rec || typeof rec.diyFor !== 'string' || !Array.isArray(rec.bonds) || !rec.bonds.includes(bond?.bondId)) return false;
|
||||
const rec = recOf(p);
|
||||
if (!diyOfBond(rec)) return false;
|
||||
const g = diy.get(rec.diyFor);
|
||||
if (!g) diy.set(rec.diyFor, { on, hand, rec });
|
||||
else { g.on = g.on || on; g.hand = g.hand || hand; }
|
||||
if (g) { g.on = g.on || on; g.hand = g.hand || hand; return true; }
|
||||
// the row draws the normal form, as a member row its base chess (an elite copy is the same member)
|
||||
const pick = isObj(p.diy) && typeof p.diy.charId === 'string' ? p.diy : null;
|
||||
const normal = rec.isGolden ? recOf(pick ? { kind: 'chess', id: rec.diyFor, diy: pick } : { kind: 'chess', id: rec.diyFor }) : rec;
|
||||
diy.set(rec.diyFor, { on, hand, owned: true, rec: diyOfBond(normal) && normal.diyFor === rec.diyFor ? normal : rec, pick });
|
||||
return true;
|
||||
};
|
||||
const itemIds = (p) => p.items.map((it) => (typeof it === 'string' ? it : it?.id)).filter((x) => typeof x === 'string');
|
||||
@@ -209,6 +223,14 @@ export function bondMembers(bond, priv, banned = [], getChess = () => null, getI
|
||||
else if (hand) g.hand = true;
|
||||
}
|
||||
}
|
||||
// the player's picks not owned (the slot's record through the own getter: gameLogic/diy.js diyGetter) — rows like an
|
||||
// unowned member's (counted by neither 在场 nor the 成员 header)
|
||||
for (const slotId of Object.keys(diyPicks(priv))) {
|
||||
if (diy.has(slotId)) continue;
|
||||
const rec = recOf({ kind: 'chess', id: slotId });
|
||||
if (diyOfBond(rec) && rec.diyFor === slotId) diy.set(slotId, { on: false, hand: false, owned: false, rec, pick: null });
|
||||
}
|
||||
const diyBanned = new Set(Array.isArray(priv?.diyBanned) ? priv.diyBanned : []);
|
||||
const rows = members.map((id) => {
|
||||
const c = getChess(id);
|
||||
return { id, tier: c?.tier ?? 0, name: c?.name ?? id, onBoard: onBoard.has(id), owned: owned.has(id), inHand: inHand.has(id), banned: bannedSet.has(id) };
|
||||
@@ -217,7 +239,9 @@ export function bondMembers(bond, priv, banned = [], getChess = () => null, getI
|
||||
const c = getChess(id);
|
||||
rows.push({ id, tier: c?.tier ?? 0, name: c?.name ?? id, onBoard: g.on, owned: true, inHand: g.hand, banned: false, granted: true, items: g.items });
|
||||
}
|
||||
for (const [id, g] of diy) rows.push({ id, tier: g.rec.tier ?? 0, name: g.rec.name ?? id, onBoard: g.on, owned: true, inHand: g.hand, banned: false, diy: true, rec: g.rec });
|
||||
for (const [id, g] of diy) {
|
||||
rows.push({ id, tier: g.rec.tier ?? 0, name: g.rec.name ?? id, onBoard: g.on, owned: g.owned, inHand: g.hand, banned: bannedSet.has(id) || diyBanned.has(id), diy: true, rec: g.rec, ...(g.pick ? { pick: g.pick } : {}) });
|
||||
}
|
||||
return rows.sort((a, b) => (b.onBoard - a.onBoard) || (b.owned - a.owned) || (a.tier - b.tier) || (a.id < b.id ? -1 : 1));
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user