feedback6 R21D: a consume-on-equip item on a full carrier opens the replace dialog and replaces the picked item first (GitHub #263, PR #264); golden: matches 10 of 18 moved (a targeted consumable on a full carrier now destroys the oldest item), the rest unchanged
PRTS 卫戍协议/帮助 §装备: 「达到上限强行佩戴会改为替换装备:指定一件已佩戴装备替换为将要佩戴的装备,并销毁被指定的装备」, no exception for the 装备时销毁 items. Before: a normal 博士投影 opened no dialog and its kept item pushed the OLDEST equipped item out (replaceUid ignored); 盟约之币 and the other consumables kept both items. - server PlayerState.equip (player/items.js): on a full carrier the consume path takes the pick (replaceUid, else the oldest) off before onEquip (_takeReplaced), destroys it (onDestroy reason replace) once the effect went through — the normal 博士投影 takes the freed slot —, and puts it back at its index when the effect refuses (博士投影 on an elite: nothing destroyed) - client gameLogic equipReplaces: the replace dialog opens for every item on a full carrier, consumables included, except an attaching item that completes an item merge and a 博士投影 on an elite; itemAttaches removed - bots (itemTarget): a consumable whose effect is the same on anyone goes to a carrier with a free slot first (PR #264) - docs: design/match.md equipment rules, META §2.5, PLAYING 装备 row, research 04 golden (matches only; each traced equip by equip against the parent — the first change of state is a bot's, or the AI 托管 human seat's, targeted consumable on a full carrier destroying its oldest item): solo-FUNNY-1 (r9 ai_0 随身身份牌), solo-FUNNY-2 (r9 ai_0 寻呼模块), solo-HARD-2 (r9 ai_0 随身身份牌, r12 简易通讯机), coop2-NORMAL-3 (r9 ai_1 随身身份牌), coop3-ABYSS-5 (r11 ai_1 随身身份牌), coop4-FUNNY-6 (r4 ai_1 随身身份牌), solo-HARD-9-boosted (r14 ai_0 随身身份牌), coop2-ABYSS-10-boosted (r13 ai_0 随身身份牌, r14 ai_1 简易通讯机), coop2-NORMAL-14-standins (r9 ai_0 随身身份牌; now won, 14 rounds), coop2-NORMAL-70-diy (r14 h_0 随身身份牌); the bots' free-slot carrier for target-agnostic consumables moves no scenario by itself; roster / bonds / fields / standins / diy unchanged
This commit is contained in:
1 parent
783bd30afd
commit
8cef750e14
14 files changed
+277
-108
No files matched your search
@@ -392,9 +392,27 @@ function equipCheck(ctx, itemPiece, targetPiece) {
|
||||
return { ok: true, action: 'equip' };
|
||||
}
|
||||
|
||||
/** Whether equipping `itemId` attaches it (true) or consumes it on equip (false: no slot is used / replaced). */
|
||||
export function itemAttaches(item) {
|
||||
return !(typeof item?.kind === 'string' && item.kind.startsWith('consume_on_equip'));
|
||||
/** Whether an item record is consumed on equip (data `kind` consume_on_equip*: it resolves at once and takes no slot). */
|
||||
const consumedOnEquip = (item) => typeof item?.kind === 'string' && item.kind.startsWith('consume_on_equip');
|
||||
/** Item effects that promote the carrier (博士投影): the server refuses them on an elite (builtinMeta 'already elite'). */
|
||||
const PROMOTE_KEYS = new Set(['use_equip_upgrade_char', 'equip_round_start_upgrade_char']);
|
||||
|
||||
/**
|
||||
* Whether dropping item `uid` on the chess `targetPiece` opens the replace dialog: its two slots are used and the server
|
||||
* would replace one of them — every item, a consume-on-equip one too (PRTS 卫戍协议/帮助 "达到上限强行佩戴会改为替换装备";
|
||||
* GitHub #263: the pick is destroyed, then the item resolves and the slot stays free). Not when nothing is replaced: an
|
||||
* attaching item that completes an item merge (the server merges it instead of equipping, `equipMerges`) or a
|
||||
* 博士投影 on an elite (refused by the server).
|
||||
* @param {ReturnType<typeof placementContext>} ctx
|
||||
* @param {number} uid
|
||||
* @param {any} targetPiece
|
||||
*/
|
||||
export function equipReplaces(ctx, uid, targetPiece) {
|
||||
if (!Array.isArray(targetPiece?.items) || targetPiece.items.length < 2) return false;
|
||||
const rec = ctx.getItem(ctx.pieces.get(uid)?.piece?.id);
|
||||
if (!consumedOnEquip(rec)) return !equipMerges(ctx, uid);
|
||||
const promotes = Array.isArray(rec.buffs) && rec.buffs.some((b) => PROMOTE_KEYS.has(b?.key));
|
||||
return !(promotes && targetPiece.golden);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -454,11 +472,8 @@ export function dropIntent(ctx, uid, target) {
|
||||
if (res.action === 'art') return { t: 'g.art', fields: { itemUid: uid, row: target.row, col: target.col } };
|
||||
if (res.action === 'equip') {
|
||||
const occ = target.area === 'hand' ? ctx.handAt.get(target.idx) : ctx.boardAt.get(tileKey(target.row, target.col));
|
||||
// both slots used: the replace dialog picks the equipped item to destroy (g.equip replaceUid) — unless the item is
|
||||
// consumed on equip, or it completes an item merge (the server merges it instead of equipping: nothing replaced)
|
||||
const full = Array.isArray(occ?.piece?.items) && occ.piece.items.length >= 2
|
||||
&& itemAttaches(ctx.getItem(ctx.pieces.get(uid)?.piece?.id)) && !equipMerges(ctx, uid);
|
||||
return { t: 'g.equip', fields: { itemUid: uid, targetUid: occ.piece.uid }, confirmReplace: full };
|
||||
// both slots used: the replace dialog picks the equipped item to destroy (g.equip replaceUid) — see equipReplaces
|
||||
return { t: 'g.equip', fields: { itemUid: uid, targetUid: occ.piece.uid }, confirmReplace: equipReplaces(ctx, uid, occ.piece) };
|
||||
}
|
||||
const to = target.area === 'hand' ? { area: 'hand', idx: target.idx } : { area: 'board', row: target.row, col: target.col };
|
||||
return { t: 'g.move', fields: { uid, to } };
|
||||
|
||||
Reference in new issue
Block a user