feedback7 R22-LATE: a watched teammate's bond popup counts their bench like their own popup — hand operators owned (and counted for 投资人 / 远见 / 奇迹), temp ones never, neither 在场; golden: unchanged
GitHub #385 / PR #387 by @zasx77771. The prep scout sends the hand and the 临时整备区 as units, and ownerBoard put every op unit on the popup's synthetic board, so the popup marked bench operators 在场 and its 成员 header disagreed with the server's ring (炎: ring 1, header 3; 远见: ring 2, header 3 with the temp copy). The scout's units now carry `area` 'board' | 'hand' | 'temp' (the server tags from PR #387; the tag survives the boss-half remap, the row does not). ownerBoard sorts them into board / hand / temp instead of dropping the bench as the PR did: dropping the hand would break the bonds that count the 整备区 (投资人 / 远见 / 奇迹 — a hand-only 远见 member: ring 1, header 0). bondMembers / memberHeadCount then count exactly as the player's own popup: hand = owned and in hand, temp = owned only. A unit without an area (battles, older servers) stays on the board. Docs: PLAYING.md's 观看谁 paragraph (the bench is listed in prep); comments in bondStrip.js / game.js. Tests: test/ui/watch-bonds.test.js (36; 2 fail on the parent), test/match/prep-bench.test.js + feedback5-boss-pair-prep.test.js (area tags; 4 fail on the parent). Golden unchanged (views only).
This commit is contained in:
1 parent
19c7b26850
commit
2df1cf15bf
8 files changed
+80
-30
No files matched your search
+1
-1
@@ -137,7 +137,7 @@
|
|||||||
- 休整期、或自己的作战结束后(显示「作战结束,等待队友完成作战」),点左侧队友头像 →「前往查看」观看他的战场;自己的作战进行中不能观战。联防和最终攻势中可以用 ‹ › 切换左半 / 全景 / 右半。
|
- 休整期、或自己的作战结束后(显示「作战结束,等待队友完成作战」),点左侧队友头像 →「前往查看」观看他的战场;自己的作战进行中不能观战。联防和最终攻势中可以用 ‹ › 切换左半 / 全景 / 右半。
|
||||||
- 被淘汰后可以自由观看任何战场,而且**不用每回合重选**:你上次点「前往查看」的是谁,之后每个阶段(回合开始、机变、休整期、作战、最终攻势)都会自动跟到他的阵地或战场;没选过或他也被淘汰时,跟随座位顺序第一位还在场的博士(包括 AI 队友)。阶段中途换人立即生效,之后就跟随新选的人。
|
- 被淘汰后可以自由观看任何战场,而且**不用每回合重选**:你上次点「前往查看」的是谁,之后每个阶段(回合开始、机变、休整期、作战、最终攻势)都会自动跟到他的阵地或战场;没选过或他也被淘汰时,跟随座位顺序第一位还在场的博士(包括 AI 队友)。阶段中途换人立即生效,之后就跟随新选的人。
|
||||||
- **观战席**(本作新增,原版没有):每个同盟房间另有 2 个观战席。在大厅输入同盟密钥后点「观战」进入(模拟进行中也可以),不占博士席位、不算参战人数,也不能购买、部署、准备或发送表情;观看方式与被淘汰的玩家相同——自动跟随你上次查看的博士(没查看过则是座位顺序第一位还在场的博士):休整期显示他的阵地,作战时观看他的战场,点左侧成员头像可切换查看任何人。断线重连会回到观战席;创建者可以在等待室用观战者名字旁的 ✕ 将其移出;模拟开始后,顶栏延迟旁会出现一个「👁 人数」的小胶囊(没有观战者时不显示),点开是观战者名单(离线的有标记),创建者可以在名单里用 ✕ 把观战者移出,被移出的人立刻回到大厅、不再收到战场画面(观战席上限仍是 2 个,不能调整);观战者在等待室有空位时可以点「入座」成为博士。
|
- **观战席**(本作新增,原版没有):每个同盟房间另有 2 个观战席。在大厅输入同盟密钥后点「观战」进入(模拟进行中也可以),不占博士席位、不算参战人数,也不能购买、部署、准备或发送表情;观看方式与被淘汰的玩家相同——自动跟随你上次查看的博士(没查看过则是座位顺序第一位还在场的博士):休整期显示他的阵地,作战时观看他的战场,点左侧成员头像可切换查看任何人。断线重连会回到观战席;创建者可以在等待室用观战者名字旁的 ✕ 将其移出;模拟开始后,顶栏延迟旁会出现一个「👁 人数」的小胶囊(没有观战者时不显示),点开是观战者名单(离线的有标记),创建者可以在名单里用 ✕ 把观战者移出,被移出的人立刻回到大厅、不再收到战场画面(观战席上限仍是 2 个,不能调整);观战者在等待室有空位时可以点「入座」成为博士。
|
||||||
- **观看谁,盟约栏就显示谁的盟约**:查看队友的阵地或战场时(休整期、作战结束后、被淘汰后自动观战),左上的盟约栏换成**该队友的盟约、人数和层数**,前面有琥珀色的「👁 队友名」标签,点开的盟约详情也写着「队友名 的盟约」(成员一栏只标出他在场上的干员,整备区看不到);作战中层数实时增长(与他自己看到的一致)。联防和最终攻势里跟随 ‹ ›:镜头在队友的半场时显示他的盟约,在你自己的半场时显示你自己的;「全景」时,你在这个战场上作战就显示你自己的,你不在这个战场上(漏怪后观看联防、被淘汰后观战)则显示你点「前往查看」选中的那名队友,没选过就显示该战场的第一名玩家(1 号联防者 / 这一组的第一位),**不会显示你自己的**。在这样的战场上点某名队友的头像 →「前往查看」,镜头会直接移到他的半场,‹ › 上的「👁 队友名」与盟约栏的标签一致;‹ › 旁边有「返回战场」(被淘汰时没有)。盟约栏换人时,从盟约栏打开的盟约详情会自动关闭。点开场上某个干员的详情卡时,卡上的盟约(以及点它打开的盟约详情)属于**这个干员的主人**,与盟约栏当前显示谁无关——例如在「全景」点队友的干员,看到的是「队友名 的盟约」及他的人数和层数;在队友的半场点你自己的干员,看到的是你自己的。回到自己的阵地 / 战场后恢复成你自己的盟约。作战结束后观看队友时刷新页面 / 断线重连,仍会回到正在观看的队友战场,「👁 队友名」和「返回战场」都还在。已被淘汰的队友不再显示盟约(也无法被查看)。
|
- **观看谁,盟约栏就显示谁的盟约**:查看队友的阵地或战场时(休整期、作战结束后、被淘汰后自动观战),左上的盟约栏换成**该队友的盟约、人数和层数**,前面有琥珀色的「👁 队友名」标签,点开的盟约详情也写着「队友名 的盟约」(成员一栏标出他在场上的干员;休整期查看时他整备区、临时整备区里的干员也会列出、标为「整备区」,和他自己看到的一样计数:投资人、远见、奇迹这类含整备区的盟约把整备区的算进成员人数,临时整备区的哪个盟约都不算;作战中只列出场上的);作战中层数实时增长(与他自己看到的一致)。联防和最终攻势里跟随 ‹ ›:镜头在队友的半场时显示他的盟约,在你自己的半场时显示你自己的;「全景」时,你在这个战场上作战就显示你自己的,你不在这个战场上(漏怪后观看联防、被淘汰后观战)则显示你点「前往查看」选中的那名队友,没选过就显示该战场的第一名玩家(1 号联防者 / 这一组的第一位),**不会显示你自己的**。在这样的战场上点某名队友的头像 →「前往查看」,镜头会直接移到他的半场,‹ › 上的「👁 队友名」与盟约栏的标签一致;‹ › 旁边有「返回战场」(被淘汰时没有)。盟约栏换人时,从盟约栏打开的盟约详情会自动关闭。点开场上某个干员的详情卡时,卡上的盟约(以及点它打开的盟约详情)属于**这个干员的主人**,与盟约栏当前显示谁无关——例如在「全景」点队友的干员,看到的是「队友名 的盟约」及他的人数和层数;在队友的半场点你自己的干员,看到的是你自己的。回到自己的阵地 / 战场后恢复成你自己的盟约。作战结束后观看队友时刷新页面 / 断线重连,仍会回到正在观看的队友战场,「👁 队友名」和「返回战场」都还在。已被淘汰的队友不再显示盟约(也无法被查看)。
|
||||||
|
|
||||||
## 9. 结算称号
|
## 9. 结算称号
|
||||||
|
|
||||||
|
|||||||
@@ -1296,7 +1296,7 @@ function MatchScreen() {
|
|||||||
}, [strip.ownerId]);
|
}, [strip.ownerId]);
|
||||||
// the popup shows the player it was opened for (ui/watchBonds.js popupView): their entry + live layers, their name,
|
// the popup shows the player it was opened for (ui/watchBonds.js popupView): their entry + live layers, their name,
|
||||||
// and as members your pieces or their operators on the field on screen — under client-side combat the battle's own
|
// and as members your pieces or their operators on the field on screen — under client-side combat the battle's own
|
||||||
// (the runner's field meta is taken before they deploy); their hand is never sent
|
// (the runner's field meta is taken before they deploy); a battle sends no bench (a prep scout does: ownerBoard)
|
||||||
const popOps = cc && battleRunner && field?.local && bondOpen && bondOpen.ownerId !== myId ? battleRunner.ownerOps(bondOpen.ownerId, field.fieldId) : null;
|
const popOps = cc && battleRunner && field?.local && bondOpen && bondOpen.ownerId !== myId ? battleRunner.ownerOps(bondOpen.ownerId, field.fieldId) : null;
|
||||||
const bondPop = popupView({ open: bondOpen, pub, priv, myId, field, units: popOps, live: liveLayers });
|
const bondPop = popupView({ open: bondOpen, pub, priv, myId, field, units: popOps, live: liveLayers });
|
||||||
const openBond = (id, ownerId, from) => { setBondOpen((b) => toggleBond(b, id, ownerId, from)); audio.sfx('click', { volume: 0.4 }); };
|
const openBond = (id, ownerId, from) => { setBondOpen((b) => toggleBond(b, id, ownerId, from)); audio.sfx('click', { volume: 0.4 }); };
|
||||||
|
|||||||
@@ -17,7 +17,9 @@
|
|||||||
// already did.
|
// already did.
|
||||||
// Watching a teammate (DESIGN §20.15, ui/watchBonds.js) the strip and the popup show THAT player's bonds and layers:
|
// Watching a teammate (DESIGN §20.15, ui/watchBonds.js) the strip and the popup show THAT player's bonds and layers:
|
||||||
// the strip carries an amber "👁 name" tag (`owner`, the observing pill's spelling, research 09 §3.1) and amber rings,
|
// the strip carries an amber "👁 name" tag (`owner`, the observing pill's spelling, research 09 §3.1) and amber rings,
|
||||||
// the popup a "👁 name 的盟约" line; its member list reads the teammate's operators on the field (no hand: never sent).
|
// the popup a "👁 name 的盟约" line; its member list reads the teammate's operators on the field — in prep their bench
|
||||||
|
// too, counted like their own popup counts it (ui/watchBonds.js ownerBoard: hand / temp by the scout's `area`, GitHub
|
||||||
|
// #385); a battle sends no bench.
|
||||||
// 0.2.0 补位: a member the player fields as its stand-in (the own m.private.standIns; a teammate's unit that says so —
|
// 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
|
// 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).
|
// opens the stand-in's card (gameLogic memberStandIn; the owner's recall of the official mode, 2026-10-06).
|
||||||
|
|||||||
+19
-16
@@ -151,27 +151,30 @@ export function withLiveLayers(bonds, live) {
|
|||||||
* A teammate's board as far as the screen shows it, in the m.private shape the bond popup's member list reads
|
* A teammate's board as far as the screen shows it, in the m.private shape the bond popup's member list reads
|
||||||
* (gameLogic bondMembers): their operators on the field meta on screen (the prep scouting board, a server-run battle
|
* (gameLogic bondMembers): their operators on the field meta on screen (the prep scouting board, a server-run battle
|
||||||
* field's units) and, under client-side combat, the battle on screen's (`extra` = battle/runner.js ownerOps: the meta
|
* field's units) and, under client-side combat, the battle on screen's (`extra` = battle/runner.js ownerOps: the meta
|
||||||
* the runner publishes is taken before the operators deploy). Their hand is never sent — members count as owned only
|
* the runner publishes is taken before the operators deploy). A prep scout also carries their bench, each unit tagged
|
||||||
* when they are on the field. Their operators keep the items the unit info carries (UnitInfo `items`), so a 变形同构体
|
* with its `area` (server prepFieldMeta): a 'hand' operator goes to the hand list, a 'temp' one to the temp list, so the
|
||||||
* wearer is listed as a member of the bond it grants (gameLogic grantedBonds), and a 补位 unit its `standInFor` (the
|
* popup counts them as the player's own popup does (GitHub #385, PR #387) — owned, not 在场; a hand operator counts for
|
||||||
* popup draws the stand-in). null without a field or battle.
|
* the bonds that count the 整备区 (投资人 远见 奇迹: memberHeadCount), the 5 temporary slots for none. A unit without
|
||||||
* @param {any} field @param {string} ownerId @param {any[]|null} [extra] UnitInfo-like { kind, ownerId, defId, items?, standInFor? }
|
* an area (a battle's, an older server's) is on the board; a battle sends no bench. Their operators keep the items the
|
||||||
|
* unit info carries (UnitInfo `items`), so a 变形同构体 wearer is listed as a member of the bond it grants (gameLogic
|
||||||
|
* grantedBonds), and a 补位 unit its `standInFor` (the popup draws the stand-in). null without a field or battle.
|
||||||
|
* @param {any} field @param {string} ownerId @param {any[]|null} [extra] UnitInfo-like { kind, ownerId, defId, area?, items?, standInFor? }
|
||||||
*/
|
*/
|
||||||
export function ownerBoard(field, ownerId, extra = null) {
|
export function ownerBoard(field, ownerId, extra = null) {
|
||||||
const fromField = isObj(field) && Array.isArray(field.units) ? field.units : null;
|
const fromField = isObj(field) && Array.isArray(field.units) ? field.units : null;
|
||||||
const fromBattle = Array.isArray(extra) ? extra : null;
|
const fromBattle = Array.isArray(extra) ? extra : null;
|
||||||
if (!fromField && !fromBattle) return null;
|
if (!fromField && !fromBattle) return null;
|
||||||
const board = [...(fromField || []), ...(fromBattle || [])]
|
const out = { board: /** @type {any[]} */ ([]), hand: /** @type {any[]} */ ([]), temp: /** @type {any[]} */ ([]) };
|
||||||
.filter((u) => isObj(u) && u.ownerId === ownerId && u.kind === 'op' && typeof u.defId === 'string')
|
for (const u of [...(fromField || []), ...(fromBattle || [])]) {
|
||||||
.map((u) => {
|
if (!isObj(u) || u.ownerId !== ownerId || u.kind !== 'op' || typeof u.defId !== 'string') continue;
|
||||||
const p = Array.isArray(u.items) && u.items.length ? { kind: 'chess', id: u.defId, items: u.items.filter((x) => typeof x === 'string') } : { kind: 'chess', id: u.defId };
|
const p = Array.isArray(u.items) && u.items.length ? { kind: 'chess', id: u.defId, items: u.items.filter((x) => typeof x === 'string') } : { kind: 'chess', id: u.defId };
|
||||||
// 0.2.0 自选编队: a DIY slot's unit names its operator (UnitInfo diy) — the popup counts it for that operator's bonds
|
// 0.2.0 自选编队: a DIY slot's unit names its operator (UnitInfo diy) — the popup counts it for that operator's bonds
|
||||||
if (isObj(u.diy) && typeof u.diy.charId === 'string') p.diy = u.diy;
|
if (isObj(u.diy) && typeof u.diy.charId === 'string') p.diy = u.diy;
|
||||||
// 0.2.0 补位: a unit fighting as its stand-in says so (UnitInfo standInFor) — the popup draws that member as the stand-in
|
// 0.2.0 补位: a unit fighting as its stand-in says so (UnitInfo standInFor) — the popup draws that member as the stand-in
|
||||||
if (typeof u.standInFor === 'string' && u.standInFor) p.standInFor = u.standInFor;
|
if (typeof u.standInFor === 'string' && u.standInFor) p.standInFor = u.standInFor;
|
||||||
return p;
|
out[u.area === 'hand' ? 'hand' : u.area === 'temp' ? 'temp' : 'board'].push(p);
|
||||||
});
|
}
|
||||||
return { board, hand: [], temp: [] };
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -225,7 +225,8 @@ export class MatchViews {
|
|||||||
return p ? { charId: p.charId, skillIndex: p.skillIndex, uniEquipId: p.uniEquipId } : undefined;
|
return p ? { charId: p.charId, skillIndex: p.skillIndex, uniEquipId: p.uniEquipId } : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** UnitInfo of a player's board pieces on their (board) tiles — a prep scout's board, the boss partner's (bossMateView). */
|
/** UnitInfo of a player's board pieces on their (board) tiles — a prep scout's board, the boss partner's (bossMateView).
|
||||||
|
* `area: 'board'` (a bench unit says 'hand' / 'temp': prepFieldMeta). */
|
||||||
_prepBoardUnits(ps) {
|
_prepBoardUnits(ps) {
|
||||||
const units = [];
|
const units = [];
|
||||||
// the player's own view of the data (0.2.0 自选编队: its slotted DIY slots are its operators — player/diy.js)
|
// the player's own view of the data (0.2.0 自选编队: its slotted DIY slots are its operators — player/diy.js)
|
||||||
@@ -241,6 +242,7 @@ export class MatchViews {
|
|||||||
const lo = piece.kind === 'chess' && chess ? ps.loadoutFor(chess) : null;
|
const lo = piece.kind === 'chess' && chess ? ps.loadoutFor(chess) : null;
|
||||||
units.push({
|
units.push({
|
||||||
id: piece.uid, uid: piece.uid, kind: piece.kind === 'token' ? 'token' : 'op', side: 'ally', ownerId: ps.playerId, defId: piece.id,
|
id: piece.uid, uid: piece.uid, kind: piece.kind === 'token' ? 'token' : 'op', side: 'ally', ownerId: ps.playerId, defId: piece.id,
|
||||||
|
area: 'board',
|
||||||
name: rec ? rec.name : piece.id, tier: rec && Number.isInteger(rec.tier) ? rec.tier : 1, golden: !!(rec && rec.isGolden),
|
name: rec ? rec.name : piece.id, tier: rec && Number.isInteger(rec.tier) ? rec.tier : 1, golden: !!(rec && rec.isGolden),
|
||||||
spine: assets.spine || (rec && rec.charId) || piece.id, avatar: assets.avatar || (rec && rec.charId) || piece.id,
|
spine: assets.spine || (rec && rec.charId) || piece.id, avatar: assets.avatar || (rec && rec.charId) || piece.id,
|
||||||
x: c, y: r, dir: pieceDir(piece), facing: pieceDir(piece) === 'LEFT' ? -1 : 1, maxHp: rec && rec.stats && Number.isFinite(rec.stats.maxHp) ? rec.stats.maxHp : 1,
|
x: c, y: r, dir: pieceDir(piece), facing: pieceDir(piece) === 'LEFT' ? -1 : 1, maxHp: rec && rec.stats && Number.isFinite(rec.stats.maxHp) ? rec.stats.maxHp : 1,
|
||||||
@@ -292,7 +294,11 @@ export class MatchViews {
|
|||||||
// (test/match/spectator.test.js). User playtest #2 item 1 (GitHub #44).
|
// (test/match/spectator.test.js). User playtest #2 item 1 (GitHub #44).
|
||||||
// (a held chess the player fields as its stand-in is the stand-in there too — name, art, max HP — and carries
|
// (a held chess the player fields as its stand-in is the stand-in there too — name, art, max HP — and carries
|
||||||
// `standInFor`, like a board piece: the owner's recall of the official mode, 2026-10-06, the hand shows the stand-in)
|
// `standInFor`, like a board piece: the owner's recall of the official mode, 2026-10-06, the hand shows the stand-in)
|
||||||
const benchUnit = (piece, i, y) => {
|
// Each unit says where it waits (`area` 'hand' | 'temp'; a board unit 'board'): the bond popup of a watched player
|
||||||
|
// counts it like the player's own (ui/watchBonds.js ownerBoard — a hand operator is owned and counts for 投资人 远见
|
||||||
|
// 奇迹, a temp one counts for nothing; GitHub #385, PR #387). The tag survives the boss-half remap (_onBossHalf), the
|
||||||
|
// row does not.
|
||||||
|
const benchUnit = (piece, i, y, area) => {
|
||||||
const rec = piece.kind === 'item' ? gd.item(piece.id) : piece.kind === 'token' ? gd.token(piece.id) : gd.chess(piece.id);
|
const rec = piece.kind === 'item' ? gd.item(piece.id) : piece.kind === 'token' ? gd.token(piece.id) : gd.chess(piece.id);
|
||||||
const standIn = piece.kind === 'chess' && rec && ps.fieldsStandIn(rec) ? this.gd.standIn(rec.chessId) : null;
|
const standIn = piece.kind === 'chess' && rec && ps.fieldsStandIn(rec) ? this.gd.standIn(rec.chessId) : null;
|
||||||
const body = standIn || rec;
|
const body = standIn || rec;
|
||||||
@@ -300,7 +306,7 @@ export class MatchViews {
|
|||||||
const lo = piece.kind === 'chess' && rec ? ps.loadoutFor(rec) : null;
|
const lo = piece.kind === 'chess' && rec ? ps.loadoutFor(rec) : null;
|
||||||
units.push({
|
units.push({
|
||||||
id: piece.uid, uid: piece.uid, kind: piece.kind === 'token' ? 'token' : piece.kind === 'item' ? 'item' : 'op',
|
id: piece.uid, uid: piece.uid, kind: piece.kind === 'token' ? 'token' : piece.kind === 'item' ? 'item' : 'op',
|
||||||
side: 'ally', ownerId: ps.playerId, defId: piece.id,
|
side: 'ally', ownerId: ps.playerId, defId: piece.id, area,
|
||||||
name: body ? body.name : piece.id, tier: rec && Number.isInteger(rec.tier) ? rec.tier : 1, golden: !!(rec && rec.isGolden),
|
name: body ? body.name : piece.id, tier: rec && Number.isInteger(rec.tier) ? rec.tier : 1, golden: !!(rec && rec.isGolden),
|
||||||
spine: assets.spine || (body && body.charId) || piece.id, avatar: assets.avatar || (body && body.charId) || piece.id,
|
spine: assets.spine || (body && body.charId) || piece.id, avatar: assets.avatar || (body && body.charId) || piece.id,
|
||||||
x: i, y, maxHp: body && body.stats && Number.isFinite(body.stats.maxHp) ? body.stats.maxHp : 1,
|
x: i, y, maxHp: body && body.stats && Number.isFinite(body.stats.maxHp) ? body.stats.maxHp : 1,
|
||||||
@@ -312,10 +318,10 @@ export class MatchViews {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
for (let i = 0; i < ps.hand.length; i++) {
|
for (let i = 0; i < ps.hand.length; i++) {
|
||||||
if (ps.hand[i]) benchUnit(ps.hand[i], i, GEO.HAND_ROW);
|
if (ps.hand[i]) benchUnit(ps.hand[i], i, GEO.HAND_ROW, 'hand');
|
||||||
}
|
}
|
||||||
for (let i = 0; i < ps.temp.length; i++) {
|
for (let i = 0; i < ps.temp.length; i++) {
|
||||||
if (ps.temp[i]) benchUnit(ps.temp[i], GEO.TEMP_C0 + i, GEO.TEMP_ROW);
|
if (ps.temp[i]) benchUnit(ps.temp[i], GEO.TEMP_C0 + i, GEO.TEMP_ROW, 'temp');
|
||||||
}
|
}
|
||||||
// `nextEnemies`: the scouted player's coming enemies — their preview pen shows on the scouting board too (research 09
|
// `nextEnemies`: the scouted player's coming enemies — their preview pen shows on the scouting board too (research 09
|
||||||
// §2.2 "Teammates"; render/app.js enterBattle({ prep: true, nextEnemies }))
|
// §2.2 "Teammates"; render/app.js enterBattle({ prep: true, nextEnemies }))
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ test('a move of the partner reaches the player\'s m.private and a scout of eithe
|
|||||||
const h = bossPrep(4, 5102);
|
const h = bossPrep(4, 5102);
|
||||||
const m = h.m;
|
const m = h.m;
|
||||||
const b = place(h, 'p_1', chessOfTier(1)[1]);
|
const b = place(h, 'p_1', chessOfTier(1)[1]);
|
||||||
|
const held = give(m, h.ps('p_0'), chessOfTier(1)[2]); // into p_0's hand
|
||||||
m.flush(true);
|
m.flush(true);
|
||||||
// p_3 (eliminated) scouts p_0: the scout shows p_0's half and p_1's board on the other half
|
// p_3 (eliminated) scouts p_0: the scout shows p_0's half and p_1's board on the other half
|
||||||
h.ps('p_3').eliminate(13);
|
h.ps('p_3').eliminate(13);
|
||||||
@@ -63,6 +64,9 @@ test('a move of the partner reaches the player\'s m.private and a scout of eithe
|
|||||||
let scout = h.lastTo('p_3', 'm.field');
|
let scout = h.lastTo('p_3', 'm.field');
|
||||||
assert.deepEqual([scout.kind, scout.side, scout.mate], ['boss', 'L', { playerId: 'p_1', side: 'R' }]);
|
assert.deepEqual([scout.kind, scout.side, scout.mate], ['boss', 'L', { playerId: 'p_1', side: 'R' }]);
|
||||||
assert.ok(scout.units.some((u) => u.uid === b.piece.uid && u.x >= 10), 'the partner\'s piece on the right half');
|
assert.ok(scout.units.some((u) => u.uid === b.piece.uid && u.x >= 10), 'the partner\'s piece on the right half');
|
||||||
|
// the area tags survive the boss-half remap (the rows do not): the bond popup tells the bench from the board (GitHub #385)
|
||||||
|
assert.equal(scout.units.find((u) => u.uid === b.piece.uid).area, 'board');
|
||||||
|
assert.equal(scout.units.find((u) => u.uid === held.uid).area, 'hand', 'a held operator stays tagged as held');
|
||||||
// p_1 moves its piece: p_0's m.private and p_3's scout of p_0 follow
|
// p_1 moves its piece: p_0's m.private and p_3's scout of p_0 follow
|
||||||
const ps1 = h.ps('p_1');
|
const ps1 = h.ps('p_1');
|
||||||
const to = legalTileFor(m, ps1, b.piece.id, new Set([`${b.tile[0]},${b.tile[1]}`]));
|
const to = legalTileFor(m, ps1, b.piece.id, new Set([`${b.tile[0]},${b.tile[1]}`]));
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ test('#44 prep scout: held pieces are units on the hand row; deploying moves the
|
|||||||
const u = first.units.find((x) => x.uid === piece.uid);
|
const u = first.units.find((x) => x.uid === piece.uid);
|
||||||
assert.ok(u, `${pid}: the held operator is a unit of the scout`);
|
assert.ok(u, `${pid}: the held operator is a unit of the scout`);
|
||||||
assert.deepEqual([u.x, u.y], [0, GEO.HAND_ROW], `${pid}: standing on the first hand slot`);
|
assert.deepEqual([u.x, u.y], [0, GEO.HAND_ROW], `${pid}: standing on the first hand slot`);
|
||||||
|
assert.equal(u.area, 'hand', 'tagged with its area: the bond popup counts it as held, not in play (GitHub #385)');
|
||||||
assert.equal(u.kind, 'op');
|
assert.equal(u.kind, 'op');
|
||||||
assert.equal(u.items, undefined, 'no items equipped yet');
|
assert.equal(u.items, undefined, 'no items equipped yet');
|
||||||
}
|
}
|
||||||
@@ -48,6 +49,7 @@ test('#44 prep scout: held pieces are units on the hand row; deploying moves the
|
|||||||
const last = h.lastTo('p_0', 'm.field');
|
const last = h.lastTo('p_0', 'm.field');
|
||||||
const u = last.units.find((x) => x.uid === piece.uid);
|
const u = last.units.find((x) => x.uid === piece.uid);
|
||||||
assert.deepEqual([u.x, u.y], [c, r], 'deployed onto the watched board');
|
assert.deepEqual([u.x, u.y], [c, r], 'deployed onto the watched board');
|
||||||
|
assert.equal(u.area, 'board');
|
||||||
assert.equal(last.units.some((x) => x.uid === piece.uid && x.y === GEO.HAND_ROW), false, 'and gone from the hand row');
|
assert.equal(last.units.some((x) => x.uid === piece.uid && x.y === GEO.HAND_ROW), false, 'and gone from the hand row');
|
||||||
m.dispose();
|
m.dispose();
|
||||||
});
|
});
|
||||||
@@ -74,6 +76,7 @@ test('#44 prep scout: a held item is a kind-item unit on the hand row; equipped
|
|||||||
assert.ok(iu, 'the held item is a unit of the scout');
|
assert.ok(iu, 'the held item is a unit of the scout');
|
||||||
assert.equal(iu.kind, 'item');
|
assert.equal(iu.kind, 'item');
|
||||||
assert.equal(iu.y, GEO.HAND_ROW);
|
assert.equal(iu.y, GEO.HAND_ROW);
|
||||||
|
assert.equal(iu.area, 'hand');
|
||||||
// equip the item onto the operator (still in the hand): the operator's unit carries the item id
|
// equip the item onto the operator (still in the hand): the operator's unit carries the item id
|
||||||
assert.deepEqual(m.handle('p_1', { t: 'g.equip', itemUid: item.uid, targetUid: op.uid }), { ok: true });
|
assert.deepEqual(m.handle('p_1', { t: 'g.equip', itemUid: item.uid, targetUid: op.uid }), { ok: true });
|
||||||
const after = h.lastTo('p_0', 'm.field');
|
const after = h.lastTo('p_0', 'm.field');
|
||||||
@@ -109,7 +112,10 @@ test("PR #129 review: moving a hand piece to another slot is a scout change (x f
|
|||||||
const first = h.lastTo('p_0', 'm.field');
|
const first = h.lastTo('p_0', 'm.field');
|
||||||
const tu = first.units.find((x) => x.uid === c.uid);
|
const tu = first.units.find((x) => x.uid === c.uid);
|
||||||
assert.deepEqual([tu.x, tu.y], [GEO.TEMP_C0, GEO.TEMP_ROW], 'the temp piece scouts on the temp row (first slot, col 4)');
|
assert.deepEqual([tu.x, tu.y], [GEO.TEMP_C0, GEO.TEMP_ROW], 'the temp piece scouts on the temp row (first slot, col 4)');
|
||||||
assert.equal(first.units.find((x) => x.uid === a.uid).x, 0);
|
assert.equal(tu.area, 'temp');
|
||||||
|
const hu = first.units.find((x) => x.uid === a.uid);
|
||||||
|
assert.equal(hu.x, 0);
|
||||||
|
assert.equal(hu.area, 'hand');
|
||||||
|
|
||||||
// a hand piece moved to another slot keeps uid and id — the slot must be in the signature for the push to fire
|
// a hand piece moved to another slot keeps uid and id — the slot must be in the signature for the push to fire
|
||||||
assert.deepEqual(m.handle('p_1', { t: 'g.move', uid: a.uid, to: { area: 'hand', idx: 3 } }), { ok: true });
|
assert.deepEqual(m.handle('p_1', { t: 'g.move', uid: a.uid, to: { area: 'hand', idx: 3 } }), { ok: true });
|
||||||
|
|||||||
@@ -281,16 +281,45 @@ describe('§20.15 the bonds: views + live layers', () => {
|
|||||||
assert.deepEqual(mate.bonds.map((b) => [b.bondId, b.layers]), [['sargonShip', 44], ['kjeragShip', 7]], 'THEIR bonds, THEIR live count');
|
assert.deepEqual(mate.bonds.map((b) => [b.bondId, b.layers]), [['sargonShip', 44], ['kjeragShip', 7]], 'THEIR bonds, THEIR live count');
|
||||||
assert.deepEqual(playerBonds({ pub, priv: PRIV, myId: ME, ownerId: 'p3', live }).map((b) => b.layers), [120]);
|
assert.deepEqual(playerBonds({ pub, priv: PRIV, myId: ME, ownerId: 'p3', live }).map((b) => b.layers), [120]);
|
||||||
});
|
});
|
||||||
test('ownerBoard: the teammate\'s operators on the field on screen feed the popup\'s member list (no hand)', () => {
|
test('ownerBoard: the teammate\'s operators on the field on screen feed the popup\'s member list; a scout\'s bench goes to hand / temp', () => {
|
||||||
const field = { fieldId: 'n:p2', prep: true, units: [
|
const field = { fieldId: 'n:p2', prep: true, units: [
|
||||||
{ id: 1, kind: 'op', side: 'ally', ownerId: 'p2', defId: 'chess_char_1_19_a' },
|
{ id: 1, kind: 'op', side: 'ally', ownerId: 'p2', defId: 'chess_char_1_19_a', area: 'board' },
|
||||||
{ id: 2, kind: 'token', side: 'ally', ownerId: 'p2', defId: 'tok' },
|
{ id: 2, kind: 'token', side: 'ally', ownerId: 'p2', defId: 'tok', area: 'board' },
|
||||||
{ id: 3, kind: 'op', side: 'ally', ownerId: 'p3', defId: 'chess_other' },
|
{ id: 3, kind: 'op', side: 'ally', ownerId: 'p3', defId: 'chess_other' },
|
||||||
{ id: 4, kind: 'enemy', side: 'enemy', ownerId: null, defId: 'enemy_1' },
|
{ id: 4, kind: 'enemy', side: 'enemy', ownerId: null, defId: 'enemy_1' },
|
||||||
|
{ id: 5, kind: 'op', side: 'ally', ownerId: 'p2', defId: 'chess_char_1_03_a', area: 'hand' },
|
||||||
|
{ id: 6, kind: 'op', side: 'ally', ownerId: 'p2', defId: 'chess_char_2_04_a', area: 'temp' },
|
||||||
|
{ id: 7, kind: 'item', side: 'ally', ownerId: 'p2', defId: 'chess_item_1_01_e_a', area: 'hand' },
|
||||||
] };
|
] };
|
||||||
assert.deepEqual(ownerBoard(field, 'p2'), { board: [{ kind: 'chess', id: 'chess_char_1_19_a' }], hand: [], temp: [] });
|
assert.deepEqual(ownerBoard(field, 'p2'), {
|
||||||
|
board: [{ kind: 'chess', id: 'chess_char_1_19_a' }], hand: [{ kind: 'chess', id: 'chess_char_1_03_a' }], temp: [{ kind: 'chess', id: 'chess_char_2_04_a' }],
|
||||||
|
});
|
||||||
|
// a unit without an area (a battle's, an older server's prep scout) is on the board
|
||||||
|
assert.deepEqual(ownerBoard({ units: [{ kind: 'op', ownerId: 'p2', defId: 'chess_char_1_19_a' }] }, 'p2').board, [{ kind: 'chess', id: 'chess_char_1_19_a' }]);
|
||||||
assert.equal(ownerBoard(null, 'p2'), null);
|
assert.equal(ownerBoard(null, 'p2'), null);
|
||||||
});
|
});
|
||||||
|
test('GitHub #385: a scouted bench counts in the popup\'s 成员 header as in the player\'s own (hand for 远见 / 奇迹 / 投资人, temp never)', () => {
|
||||||
|
// 远见 (BOARD_AND_DECK, countsHand: the server's 在场 is 2 — 赫默 on the board, 初雪 in the hand; 伊内丝 waits in temp)
|
||||||
|
// 炎 (BOARD: 在场 1 — 小满; 琳琅诗怀雅 in the hand, 烛煌 in temp)
|
||||||
|
const u = (id, defId, area) => ({ id, kind: 'op', side: 'ally', ownerId: 'p2', defId, area });
|
||||||
|
const field = { fieldId: 'n:p2', prep: true, units: [
|
||||||
|
u(1, 'chess_char_2_02_a', 'board'), u(2, 'chess_char_2_04_a', 'board'),
|
||||||
|
u(3, 'chess_char_3_14_a', 'hand'), u(4, 'chess_char_3_04_a', 'hand'),
|
||||||
|
u(5, 'chess_char_4_04_a', 'temp'), u(6, 'chess_char_5_03_a', 'temp'),
|
||||||
|
] };
|
||||||
|
const head = (bondId) => {
|
||||||
|
const v = BondPopup({ bondId, entry: bond(bondId, 0, 0, 0), priv: ownerBoard(field, 'p2'), owner: '阿米娅', onClose() {} });
|
||||||
|
const h4 = [...walk(v)].find((x) => x.type === 'h4' && /成员/.test(textOf(x)));
|
||||||
|
const members = [...walk(v)].filter((x) => hasClass(x, 'bpop__member'));
|
||||||
|
return { head: textOf(h4).replace(/\s+/g, ''), on: members.filter((x) => hasClass(x, 'is-on')).length, owned: members.filter((x) => hasClass(x, 'is-owned')).length };
|
||||||
|
};
|
||||||
|
const visi = head('visiShip');
|
||||||
|
assert.match(visi.head, /^成员2\//, '远见: the board member and the hand member (含整备区), not the temp one');
|
||||||
|
assert.deepEqual([visi.on, visi.owned], [1, 2], 'only 赫默 is 在场; 初雪 and 伊内丝 are owned');
|
||||||
|
const yan = head('yanShip');
|
||||||
|
assert.match(yan.head, /^成员1\//, '炎: the board member only');
|
||||||
|
assert.deepEqual([yan.on, yan.owned], [1, 2]);
|
||||||
|
});
|
||||||
test('ownerBoard: under client-side combat the battle on screen\'s operators join (the runner\'s meta predates their deploy)', () => {
|
test('ownerBoard: under client-side combat the battle on screen\'s operators join (the runner\'s meta predates their deploy)', () => {
|
||||||
const meta = { fieldId: 'b1', local: true, units: [{ id: 90, kind: 'enemy', side: 'enemy', ownerId: null, defId: 'boss' }] };
|
const meta = { fieldId: 'b1', local: true, units: [{ id: 90, kind: 'enemy', side: 'enemy', ownerId: null, defId: 'boss' }] };
|
||||||
const ops = [{ kind: 'op', ownerId: 'p2', defId: 'chess_char_1_09_a' }, { kind: 'op', ownerId: 'p2', defId: 'chess_char_1_03_a' }];
|
const ops = [{ kind: 'op', ownerId: 'p2', defId: 'chess_char_1_09_a' }, { kind: 'op', ownerId: 'p2', defId: 'chess_char_1_03_a' }];
|
||||||
@@ -405,7 +434,7 @@ describe('§20.15 the popup of a card\'s bond chip: the UNIT owner\'s bond (a sh
|
|||||||
assert.deepEqual([v.entry.bondId, v.entry.count, v.entry.active, v.entry.layers], ['sargonShip', 2, true, 55], 'the partner\'s count / tier, their live layers');
|
assert.deepEqual([v.entry.bondId, v.entry.count, v.entry.active, v.entry.layers], ['sargonShip', 2, true, 55], 'the partner\'s count / tier, their live layers');
|
||||||
assert.notEqual(v.priv, PRIV, 'not the viewer\'s pieces');
|
assert.notEqual(v.priv, PRIV, 'not the viewer\'s pieces');
|
||||||
assert.deepEqual(v.priv.board.map((x) => x.id).sort(), ['chess_char_1_09_a', 'chess_char_1_12_a'], 'the partner\'s operators on the field on screen');
|
assert.deepEqual(v.priv.board.map((x) => x.id).sort(), ['chess_char_1_09_a', 'chess_char_1_12_a'], 'the partner\'s operators on the field on screen');
|
||||||
assert.deepEqual([v.priv.hand, v.priv.temp], [[], []], 'their hand is never sent');
|
assert.deepEqual([v.priv.hand, v.priv.temp], [[], []], 'a battle field sends no bench');
|
||||||
});
|
});
|
||||||
test('popupView: your own bond (m.private entry with thresholds, your pieces, no name); a bond not listed → no entry; nothing open → null', () => {
|
test('popupView: your own bond (m.private entry with thresholds, your pieces, no name); a bond not listed → no entry; nothing open → null', () => {
|
||||||
const own = popupView({ open: { id: 'yanShip', ownerId: ME, from: 'detail' }, pub, priv: PRIV, myId: ME, field: b1, live: null });
|
const own = popupView({ open: { id: 'yanShip', ownerId: ME, from: 'detail' }, pub, priv: PRIV, myId: ME, field: b1, live: null });
|
||||||
|
|||||||
Reference in new issue
Block a user