feedback5: merge W5E — a full hand refuses buys and reward picks, temp pieces fill freed slots (#82); 联防 plays on the official escaped maps (#41)
# Conflicts: # test/golden/fields.json # test/golden/matches.json
This commit is contained in:
commit
ece75b6853
37 files changed
+732
-265
No files matched your search
@@ -438,7 +438,9 @@ function startCombat(phase) {
|
||||
for (const p of board.slice(0, 5)) {
|
||||
const c = data.lookup('chess', pick(S.pool).chessId);
|
||||
const r = boss ? p.row - 7 : p.row;
|
||||
units.push({ id: id++, kind: 'op', side: 'ally', ownerId: 'ai_2', defId: c.chessId, name: c.name, tier: c.tier, golden: false, spine: c.charId, avatar: c.assets.avatar, x: 20 - p.col, y: r, facing: -1, maxHp: c.stats.maxHp });
|
||||
// 联防: the second helper's board on the other half, shifted 8 columns (server unite.js); boss pairs: mirrored
|
||||
const x = kind === 'unite' ? p.col + 8 : 20 - p.col;
|
||||
units.push({ id: id++, kind: 'op', side: 'ally', ownerId: 'ai_2', defId: c.chessId, name: c.name, tier: c.tier, golden: false, spine: c.charId, avatar: c.assets.avatar, x, y: r, facing: kind === 'unite' ? 1 : -1, maxHp: c.stats.maxHp });
|
||||
}
|
||||
}
|
||||
const enemyKeys = S.priv.nextEnemies.map((e) => e.enemyKey);
|
||||
@@ -448,7 +450,7 @@ function startCombat(phase) {
|
||||
const key = enemyKeys[i % enemyKeys.length];
|
||||
const e = data.lookup('enemies', key);
|
||||
const y = boss ? [2, 5, 3][i % 3] : [9, 12][i % 2];
|
||||
const startX = boss ? (i % 2 ? 17 : 3) : 10;
|
||||
const startX = boss ? (i % 2 ? 17 : 3) : kind === 'unite' ? 18 : 10; // escaped_multi: the right half's gates
|
||||
enemies.push({ id: id++, kind: 'enemy', side: 'enemy', ownerId: ME, defId: key, name: e?.name, spine: key, avatar: key, x: startX + rnd() * 0.3, y, facing: -1, maxHp: e?.stats?.maxHp || 3000, hp: e?.stats?.maxHp || 3000, spawnAt: i * 0.9, dir: boss ? (i % 2 ? -1 : 1) : -1, dead: false });
|
||||
}
|
||||
if (boss) {
|
||||
@@ -456,7 +458,8 @@ function startCombat(phase) {
|
||||
const e = data.lookup('enemies', bossRec.enemyKey);
|
||||
enemies.push({ id: id++, kind: 'enemy', side: 'enemy', ownerId: ME, defId: bossRec.enemyKey, name: bossRec.name, spine: bossRec.enemyKey, avatar: bossRec.enemyKey, x: 10, y: 3, facing: -1, maxHp: e?.stats?.maxHp || 1e5, hp: (e?.stats?.maxHp || 1e5) * 0.62, spawnAt: 0, dir: 0, boss: true, dead: false });
|
||||
}
|
||||
const field = { fieldId, kind, rect, stageId: pub.stageId, units: units.map((u) => ({ ...u })) };
|
||||
// the 联防 field fights on its own map (server unite.js uniteStageId: 2 helpers → escaped_multi; GitHub #41)
|
||||
const field = { fieldId, kind, rect, stageId: kind === 'unite' ? 'act1autochess_escaped_multi' : pub.stageId, units: units.map((u) => ({ ...u })) };
|
||||
store.patch('match', { field });
|
||||
const allyState = units.map((u) => ({ ...u, hp: u.maxHp * (0.55 + rnd() * 0.45), sp: rnd() * 20, spMax: 20 }));
|
||||
const t0 = performance.now();
|
||||
|
||||
@@ -362,11 +362,13 @@ function MatchScreen() {
|
||||
const shownMembers = shownField ? ((Array.isArray(pub?.fields) ? pub.fields : []).find((f) => f && f.fieldId === shownField.fieldId)?.players
|
||||
|| (Array.isArray(shownField.players) ? shownField.players : null)) : null;
|
||||
const ownView = showPrep || (shownField ? (shownMembers ? shownMembers.includes(myId) : shownField.fieldId === ownFieldId(myId)) : !watchingOther);
|
||||
// a field on a map of its own (联防: the escaped template's map, GitHub #41) shows that map; leaving it restores the board's
|
||||
const fieldStage = gd.ready && shownField && shownField.stageId && shownField.stageId !== pub?.stageId ? gd.stage(shownField.stageId) : null;
|
||||
useEffect(() => {
|
||||
if (!view) return;
|
||||
const st = ownView ? ownStage : baseStage;
|
||||
const st = fieldStage || (ownView ? ownStage : baseStage);
|
||||
if (st) view.setStage(st);
|
||||
}, [view, ownView, ownStage, baseStage]);
|
||||
}, [view, ownView, ownStage, baseStage, fieldStage]);
|
||||
const staleFieldRef = useRef(null);
|
||||
const enteredFieldRef = useRef(null);
|
||||
const pressSel = useRef(null); // the selected piece when the current field press began
|
||||
|
||||
@@ -731,15 +731,16 @@ export function mergeTarget(priv, chessId, getChess = () => null) {
|
||||
return { row: board[0].row, col: board[0].col, dir: board[0].dir || 'RIGHT' };
|
||||
}
|
||||
|
||||
/** Whether every hand slot is taken (the server refuses a purchase / reward that needs a slot: HAND_FULL). */
|
||||
/** Whether every hand slot is taken (the server refuses every purchase / reward pick then: HAND_FULL, GitHub #82). */
|
||||
export function handFull(priv) {
|
||||
const hand = Array.isArray(priv?.hand) ? priv.hand : [];
|
||||
return hand.length >= GEO.HAND_SIZE && hand.every((p) => p != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Would gaining one more `slot` (shop / reward card: { kind: 'chess'|'item', id }) complete a merge right away, so it
|
||||
* needs no hand slot? Mirror of server PlayerState completesChessMerge / completesItemMerge.
|
||||
* Would gaining one more `slot` (shop / reward card: { kind: 'chess'|'item', id }) complete a merge right away? Mirror
|
||||
* of server PlayerState completesChessMerge / completesItemMerge. (A full hand refuses such a purchase all the same:
|
||||
* PRTS 卫戍协议/帮助 §手牌区, GitHub #82.)
|
||||
* @param {any} priv
|
||||
* @param {{kind?:string, id:string}} slot
|
||||
* @param {{ getChess?:(id:string)=>any, getItem?:(id:string)=>any, itemMergeCount?: number }} [o]
|
||||
@@ -795,10 +796,10 @@ export function offerHeader(offer) {
|
||||
/**
|
||||
* Why a shop action is unavailable (null when available).
|
||||
* @param {'buy'|'reward'|'refresh'|'freeze'|'levelUp'|'ready'} kind
|
||||
* @param {{ priv:any, editable:boolean, slot?:any, getChess?:(id:string)=>any, getItem?:(id:string)=>any }} ctx
|
||||
* @param {{ priv:any, editable:boolean, slot?:any }} ctx
|
||||
* @returns {string|null} Chinese reason
|
||||
*/
|
||||
export function shopBlockReason(kind, { priv, editable, slot, getChess, getItem } = {}) {
|
||||
export function shopBlockReason(kind, { priv, editable, slot } = {}) {
|
||||
if (!priv) return '尚未就绪';
|
||||
if (priv.alive === false) return '你已被淘汰';
|
||||
if (kind === 'ready') return priv.canReady === false ? '临时整备区不为空,请先处理溢出的资源' : null;
|
||||
@@ -811,7 +812,8 @@ export function shopBlockReason(kind, { priv, editable, slot, getChess, getItem
|
||||
if (kind === 'buy' || kind === 'reward') {
|
||||
if (!isObj(slot) || slot.sold) return kind === 'reward' ? '已选择' : '已售出';
|
||||
if ((Number(slot.price) || 0) > funds) return '资金不足';
|
||||
if (handFull(priv) && !completesMerge(priv, slot, { getChess, getItem })) return '整备区已满';
|
||||
// a full hand refuses every purchase / pick, a merge-completing one too (PRTS 卫戍协议/帮助 §手牌区, GitHub #82)
|
||||
if (handFull(priv)) return '整备区已满';
|
||||
return null;
|
||||
}
|
||||
if (kind === 'refresh') return (Number(shop.refreshPrice) || 0) > funds ? '资金不足' : null;
|
||||
|
||||
@@ -37,7 +37,7 @@ function PriceHex({ slot, free, poor = false }) {
|
||||
title=${poor ? '资金不足' : tone === 'discount' ? `折扣价(原价 ${slot.basePrice})` : tone === 'premium' ? `加价(原价 ${slot.basePrice})` : '价格'} />`;
|
||||
}
|
||||
|
||||
/** Data lookups for shopBlockReason (full-hand purchases that complete a merge stay allowed). */
|
||||
/** Data lookups (merge progress / target, the loadout; shopBlockReason ignores them — a full hand refuses every purchase). */
|
||||
const LOOKUPS = { getChess: (id) => data.lookup('chess', id), getItem: (id) => data.lookup('items', id) };
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user