From 853566129c59ad9e9a20327213ec5798f8db7d6d Mon Sep 17 00:00:00 2001 From: sganggs Date: Tue, 6 Oct 2026 00:23:54 +0800 Subject: [PATCH] =?UTF-8?q?feedback5=20WK:=20the=20=E9=9B=86=E6=88=90?= =?UTF-8?q?=E6=88=98=E7=95=A5=20(ISW-A)=20module=20rule=20is=20a=20roster?= =?UTF-8?q?=20rule=20=E2=80=94=20validateDiyPicks=20(so=20room.diy,=20Play?= =?UTF-8?q?erState.setDiy=20and=20the=20picker)=20refuses=20it,=20checkDiy?= =?UTF-8?q?Pick=20/=20diyRecordOf=20/=20the=20sim=20still=20compose=20any?= =?UTF-8?q?=20module=20of=20the=20form=20(the=20kits'=20tests=20and=20the?= =?UTF-8?q?=20golden=20diy=20family=20field=20them);=20shared/diy.js=20diy?= =?UTF-8?q?ModuleOf;=20docs=20follow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Golden unchanged. --- docs/DATA.md | 9 +++++---- shared/diy.js | 29 ++++++++++++++++++++++------- test/diy.test.js | 10 ++++++---- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/docs/DATA.md b/docs/DATA.md index 9c9431d9..9af8a0b3 100644 --- a/docs/DATA.md +++ b/docs/DATA.md @@ -660,15 +660,16 @@ composes the same record (`public/js/ui/gameLogic/standIn.js standInOf`, the ren A 自选 piece is composed by `shared/diy.js`: `checkDiyPick(slotId, pick, data)` checks one pick `{ charId, skillIndex?, uniEquipId? }` against a slot (a pick of the slot's tier; a prototype takes its `diy.locked` selection, another skill is refused; an owned pick names one of its three -skills and optionally a module of its elite form at the slot's stage — never a 集成战略 one, `isDiyModule`: the ISW-A -modules of 凯尔希, 傀影, 菲亚梅塔, 提丰, 艾丽妮 and 霍尔海雅 [ASSUMED], the owner's decision of 2026-10-05), -`diySlot(id, data)` names a slot's tier, elite twin and `shopLevel`, `diyRecord(slotId, pick, { elite, data })` / +skills and optionally a module of its elite form at the slot's stage), `diySlot(id, data)` names a slot's tier, elite +twin and `shopLevel`, `diyRecord(slotId, pick, { elite, data })` / `diyRecordOf(slot, pick, data)` give the record — the slot's identity (tier, price, merge, status; no 特质), the pick's derived bonds, the operator's form at the slot's status with that skill and module (active on the elite only), plus `diyFor` = the slot's base id —, `diyPool(tier, { data, kitted })` the legal picks of a tier (prototypes, then the owned pool; with `kitted`, only operators with a kit: server/sim/content/kits/index.js `KITTED_CHARS`) and `validateDiyPicks(picks, { data, kitted })` a roster (a prototype may fill a tier-5 and a tier-6 slot, an owned operator -one slot, the picks of a tier differ). **In battle** a PlayerBattleInput entry of a DIY slot carries `diy` (the pick) and +one slot, the picks of a tier differ, no 集成战略 module — `isDiyModule`: the ISW-A modules of 凯尔希, 傀影, 菲亚梅塔, 提丰, +艾丽妮 and 霍尔海雅 are never a player's choice [ASSUMED], the owner's decision of 2026-10-05, while the record and the sim +still compose them for the kits' tests). **In battle** a PlayerBattleInput entry of a DIY slot carries `diy` (the pick) and is fielded as `getChess(slotId, { diy })` (docs/SIM.md §12); its kit is `KITS[charId]` (kits/README.md "How to add an operator (自选)"). (`shared/standIn.js diyRecord(slot, charId, backups, sel)` is the older prototype-only form.) diff --git a/shared/diy.js b/shared/diy.js index ca4e7597..b7dc3e31 100644 --- a/shared/diy.js +++ b/shared/diy.js @@ -12,8 +12,10 @@ // - no 特质 (PRTS "甄选加入的干员不会拥有任何特质"); bonds from the operator's factions (`diy.operators[id].bonds`); // - the normal form is E2 Lv1, skill rank 4, no module; the elite E2 Lv60, rank 7, its module at stage 1 (tier 5) or 3 // (tier 6) — the slot record's `status` (activity_table diyChessDict) picks the unit form; -// - an owned pick chooses its skill (any of 3) and module (any of its modules but a 集成战略-only one — ISW-A, "在集成战略 -// 中…" [ASSUMED: not usable outside 集成战略, the owner's decision of 2026-10-05] — or none); a prototype carries the skill +// - an owned pick chooses its skill (any of 3) and module (any of its modules, or none — a roster never carries a +// 集成战略-only one: ISW-A, "在集成战略中…" [ASSUMED: not usable outside 集成战略, the owner's decision of 2026-10-05], +// validateDiyPicks / isDiyModule; the composed record and the sim still field any module, as the kits test them); a +// prototype carries the skill // and module of its 补位 rows at that tier (`diy.locked`: "技能携带规则与系统补位时一致" — [ASSUMED] that reading, the // owner's decision of 2026-10-05); // - potential 0 for everyone [ASSUMED: no account]. @@ -26,8 +28,9 @@ import { composeUnitRecord, unitForm, statusKey } from './standIn.js'; export const DIY_TIERS = Object.freeze([5, 6]); /** - * Module types a 自选 pick never carries: the 集成战略 modules (ISW-A — their own effect applies only "在集成战略中") - * [ASSUMED: not usable outside 集成战略, the owner's decision of 2026-10-05]. + * Module types a player's 自选 roster never carries: the 集成战略 modules (ISW-A — their own effect applies only "在集成 + * 战略中") [ASSUMED: not usable outside 集成战略, the owner's decision of 2026-10-05]. A roster rule (validateDiyPicks, + * the client's picker), like the kit list: checkDiyPick / diyRecordOf still compose any module of the form. */ export const DIY_EXCLUDED_MODULE_TYPE = /^ISW-/; @@ -179,7 +182,6 @@ export function checkDiyPick(slotId, pick, data) { if (uniEquipId !== null && !mod) { return { error: `${charId}: no module ${uniEquipId} at stage ${slot.golden?.status?.equipLevel ?? '?'}` }; } - if (mod && !proto && !isDiyModule(mod)) return { error: `${charId}: ${uniEquipId} is a 集成战略 module (${mod.typeName})` }; return { ok: true, pick: { charId, skillIndex, uniEquipId } }; } @@ -221,10 +223,21 @@ export function diyRecord(slotId, pick, { elite = false, data } = { data: null } return diyRecordOf(elite || slot.elite ? slot.golden : slot.normal, pick, data); } +/** + * The module record `uniEquipId` of `charId`'s elite form at a slot's stage (data/backups.json form `modules[]`), or null. + * @param {string} slotId @param {string} charId @param {string} uniEquipId @param {DiyData} data + */ +export function diyModuleOf(slotId, charId, uniEquipId, data) { + const slot = diySlot(slotId, data); + const form = slot?.golden ? unitForm(backupsOf(data), charId, slot.golden.status) : null; + return (form?.modules ?? []).find((m) => m && m.uniEquipId === uniEquipId) ?? null; +} + /** * Check a whole 自选 roster: `picks` maps slot base ids to a pick (absent / null = an empty slot). Strict: an unknown - * slot, an illegal pick (checkDiyPick), an operator without a kit (when `kitted` is given), the same operator twice in - * one tier, or an owned operator in two slots rejects the roster; a prototype may fill a tier-5 and a tier-6 slot. + * slot, an illegal pick (checkDiyPick), a 集成战略 module (isDiyModule), an operator without a kit (when `kitted` is + * given), the same operator twice in one tier, or an owned operator in two slots rejects the roster; a prototype may fill + * a tier-5 and a tier-6 slot. * @param {any} picks * @param {{ data: DiyData, kitted?: Kitted }} opts * @returns {{ ok: true, picks: Record } | { error: 'BAD_MSG'|'BAD_TARGET', detail: string }} @@ -245,6 +258,8 @@ export function validateDiyPicks(picks, { data, kitted = null } = { data: null } if (!('ok' in c)) return { error: 'BAD_TARGET', detail: c.error }; const { charId } = c.pick; if (!ok(charId)) return { error: 'BAD_TARGET', detail: `${charId} has no kit yet` }; + const mod = c.pick.uniEquipId ? diyModuleOf(slotId, charId, c.pick.uniEquipId, data) : null; + if (mod && !isDiyModule(mod)) return { error: 'BAD_TARGET', detail: `${charId}: ${c.pick.uniEquipId} is a 集成战略 module (${mod.typeName})` }; const tier = diy.slots[slotId].tier; const inTier = byTier.get(tier) ?? new Set(); if (inTier.has(charId)) return { error: 'BAD_TARGET', detail: `${charId} fills two tier-${tier} slots` }; diff --git a/test/diy.test.js b/test/diy.test.js index a51799c6..bf686bc1 100644 --- a/test/diy.test.js +++ b/test/diy.test.js @@ -103,15 +103,17 @@ test('checkDiyPick: an owned 6★ chooses any of its 3 skills and any module of for (const [slot, pick, re] of bad) assert.match(checkDiyPick(slot, pick, DATA).error, re, JSON.stringify(pick)); }); -test('checkDiyPick: an owned pick never carries a 集成战略 module (ISW-A) [ASSUMED, the owner\'s decision of 2026-10-05]', () => { +test('validateDiyPicks: a roster never carries a 集成战略 module (ISW-A) [ASSUMED, the owner\'s decision of 2026-10-05]; the record still composes it', () => { const backups = DATA.backups; let checked = 0; for (const charId of backups.diy.ownedPool) { for (const [slot, key] of [[T5, '2/60/7/1'], [T6, '2/60/7/3']]) { for (const mod of backups.units[charId].forms[key]?.modules ?? []) { - const c = checkDiyPick(slot, { charId, skillIndex: 0, uniEquipId: mod.uniEquipId }, DATA); - if (/^ISW-/.test(mod.typeName)) { assert.match(c.error || '', /集成战略 module/, `${charId} ${mod.uniEquipId}`); assert.equal(isDiyModule(mod), false); checked++; } - else assert.ok(c.ok, `${charId} ${mod.uniEquipId}: ${c.error}`); + const pick = { charId, skillIndex: 0, uniEquipId: mod.uniEquipId }; + assert.ok(checkDiyPick(slot, pick, DATA).ok, `${charId} ${mod.uniEquipId}: a legal record (the sim and the kits field it)`); + const v = validateDiyPicks({ [slot]: pick }, { data: DATA }); + if (/^ISW-/.test(mod.typeName)) { assert.match(v.detail || '', /集成战略 module/, `${charId} ${mod.uniEquipId}`); assert.equal(isDiyModule(mod), false); checked++; } + else assert.ok(v.ok, `${charId} ${mod.uniEquipId}: ${v.detail}`); } } }