feedback5 WG: 补位 on the server — room.ownership { notOwned } (lenient check, stored per session / seat, a match takes the list its seat had at the start), PlayerState.standIns: the player's not-owned NORMAL chess fight as their stand-ins (battleInput standIn: true in every field, the stand-in's position / range, no summons), m.private.standIns, stand-in art in prep scouting
This commit is contained in:
1 parent
7d20695adf
commit
24205581e1
17 files changed
+590
-24
No files matched your search
@@ -1,13 +1,14 @@
|
||||
// server/match/player/basics.js — PlayerState methods: the basics — seat flags and counts (isHumanActive,
|
||||
// botControlled, deployCap, deployCount, tempEmpty), the prep that resolves a temp piece (tempDue, _putTemp: every
|
||||
// write into a temp slot), the operator loadout (DESIGN §16: setLoadout, loadoutFor), the deploy map on the field the
|
||||
// write into a temp slot), the operator loadout (DESIGN §16: setLoadout, loadoutFor), the not-owned operators fielded
|
||||
// as their stand-ins (0.2.0 补位: setNotOwned, fieldsStandIn, fieldRecord), the deploy map on the field the
|
||||
// player deploys on (Match.deployFieldOf) and the withdrawal of pieces a terrain / deploy-field change left on tiles
|
||||
// they may no longer occupy (_evictIllegal), dirty.
|
||||
// Installed on PlayerState.prototype by server/match/PlayerState.js (a method container: never instantiated; `this` is
|
||||
// the player state).
|
||||
|
||||
import { PHASE } from '../../../shared/constants.js';
|
||||
import { checkLoadout, resolveLoadout } from '../../../shared/protocol.js';
|
||||
import { checkLoadout, checkNotOwned, resolveLoadout } from '../../../shared/protocol.js';
|
||||
import { tileKey, boardOrder } from '../board.js';
|
||||
|
||||
export class PlayerBasics {
|
||||
@@ -73,11 +74,59 @@ export class PlayerBasics {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** The skill index / module a chess record fights with under this player's loadout (DESIGN §16). */
|
||||
/**
|
||||
* The skill index / module a chess record fights with under this player's loadout (DESIGN §16) — for a chess this
|
||||
* player fields as its stand-in (0.2.0 补位) the stand-in's backup selection, whatever the loadout says.
|
||||
*/
|
||||
loadoutFor(chessRecord) {
|
||||
if (this.fieldsStandIn(chessRecord)) {
|
||||
return resolveLoadout(null, this.gd.standIn(chessRecord.chessId), (id) => this.gd.standIn(id) || this.gd.chess(id));
|
||||
}
|
||||
return resolveLoadout(this.loadout, chessRecord, (id) => this.gd.chess(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the not-owned list (0.2.0 补位; the seat's list at the match start — the setting never changes during a
|
||||
* match): keeps the droppable chess (checkNotOwned) whose stand-in this match's data has. Bots own every operator.
|
||||
* @param {any} list base chess ids
|
||||
* @returns {boolean} false when the list is malformed (nothing changes) or the player is a bot
|
||||
*/
|
||||
setNotOwned(list) {
|
||||
if (this.isBot) return false;
|
||||
const res = checkNotOwned(list, (id) => this.gd.chess(id));
|
||||
if (!res || !res.ok) {
|
||||
this.m.log?.warn?.(`[match ${this.m.roomCode}] not-owned list of ${this.playerId} ignored: ${res && res.detail}`);
|
||||
return false;
|
||||
}
|
||||
const ids = res.notOwned.filter((id) => !!this.gd.standIn(id));
|
||||
this.standIns = Object.freeze(ids);
|
||||
this._standInSet = new Set(ids);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this player's piece of chess record (or id) `rec` fights as its stand-in (0.2.0 补位): its base chess is
|
||||
* in `standIns` and the data has the stand-in. Normal and elite alike (the elite uses the same backup).
|
||||
* @param {object|string|null} rec
|
||||
*/
|
||||
fieldsStandIn(rec) {
|
||||
if (!this._standInSet || this._standInSet.size === 0 || !rec) return false;
|
||||
const r = typeof rec === 'string' ? this.gd.chess(rec) : rec;
|
||||
if (!r || typeof r.chessId !== 'string') return false;
|
||||
return this._standInSet.has(r.baseId || r.chessId) && !!this.gd.standIn(r.chessId);
|
||||
}
|
||||
|
||||
/**
|
||||
* The record this player's piece of chess record `rec` fights with (0.2.0 补位): the stand-in record
|
||||
* (gd.standIn — the chess's identity, the stand-in's body) when the player fields its stand-in, else `rec` itself.
|
||||
* Rules about the unit's body read it (placement class, summon / bot ranges, the scouting art); rules about the chess
|
||||
* (price, bonds, 特质, merges, pools) keep reading gd.chess.
|
||||
* @param {object|null} rec
|
||||
*/
|
||||
fieldRecord(rec) {
|
||||
return rec && this.fieldsStandIn(rec) ? this.gd.standIn(rec.chessId) || rec : rec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deploy classes of the board tiles (server/match/board.js buildDeployMap) on the field the player deploys on now
|
||||
* (Match.deployFieldOf: the own board, or its half of the boss field in a boss round — user playtest #5 item 7).
|
||||
|
||||
@@ -181,6 +181,8 @@ export class PlayerPieces {
|
||||
grantTokensFor(owner) {
|
||||
const rec = owner && owner.kind === 'chess' ? this.gd.chess(owner.id) : null;
|
||||
if (!rec) return;
|
||||
// 0.2.0 补位: a chess fielded as its stand-in makes the stand-in's summons — none of the 17 has one (DATA.md §18)
|
||||
if (this.fieldsStandIn(rec)) return;
|
||||
for (const { tokenId, count } of this.gd.placeableTokens(owner.id, this.loadoutFor(rec))) {
|
||||
const missing = count - this._tokenCountOf(owner.uid, tokenId);
|
||||
if (missing <= 0) continue;
|
||||
|
||||
@@ -66,9 +66,10 @@ export class PlayerPlacement {
|
||||
if (!piece || piece.kind !== 'token' || this.gd.token(piece.id)?.ownerRange !== true) return null;
|
||||
let at = owner;
|
||||
if (!at) for (const [key, p] of this.board) if (p.uid === piece.ownerUid && p.kind === 'chess') { at = { key, piece: p, dir: pieceDir(p) }; break; }
|
||||
const rec = at && this.gd.chess(at.piece.id);
|
||||
if (!rec) return null;
|
||||
const grid = attackRangeGrid(loadoutRecord(rec, resolveRecordLoadout(rec, this.loadoutFor(rec)))) || rec.rangeGrid;
|
||||
const chess = at && this.gd.chess(at.piece.id);
|
||||
if (!chess) return null;
|
||||
const rec = this.fieldRecord(chess); // 0.2.0 补位: a stand-in's own range (its backup selection)
|
||||
const grid = attackRangeGrid(loadoutRecord(rec, resolveRecordLoadout(rec, this.loadoutFor(chess)))) || rec.rangeGrid;
|
||||
const [r, c] = parseKey(at.key);
|
||||
return ownerRangeKeys(grid, r, c, at.dir);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// server/match/player/round.js — PlayerState methods: the round lifecycle the match calls — startRound (income, pending
|
||||
// coins, the shop, summon stacks topped up), endPrep, eliminate (every copy back to the pool), recompute (legality,
|
||||
// out-of-range summons, temp pieces into free hand slots, bonds), the bond views — and battleInput (the player's
|
||||
// PlayerBattleInput: board units with their loadout, carried 联防 state, the reached layers).
|
||||
// PlayerBattleInput: board units with their loadout or their 补位 mark, carried 联防 state, the reached layers).
|
||||
// Installed on PlayerState.prototype by server/match/PlayerState.js (a method container: never instantiated; `this` is
|
||||
// the player state).
|
||||
|
||||
@@ -102,10 +102,16 @@ export class PlayerRound {
|
||||
for (const { r, c, piece } of boardOrder(this.board)) {
|
||||
if (piece.kind === 'chess') {
|
||||
const u = { uid: piece.uid, kind: 'chess', chessId: piece.id, row: r, col: c, dir: pieceDir(piece), items: (piece.items || []).map((i) => i.id) };
|
||||
// DESIGN §16: the equipped skill / module (elite only) from the loadout (defaults when absent)
|
||||
const lo = this.loadoutFor(this.gd.chess(piece.id));
|
||||
u.skillIndex = lo.skillIndex;
|
||||
u.moduleId = lo.moduleId;
|
||||
if (this.fieldsStandIn(piece.id)) {
|
||||
// 0.2.0 补位: a chess this player does not own fights as its stand-in (sim getChess(id, { standIn: true }): the
|
||||
// backup skill / module — no loadout fields, docs/SIM.md §12)
|
||||
u.standIn = true;
|
||||
} else {
|
||||
// DESIGN §16: the equipped skill / module (elite only) from the loadout (defaults when absent)
|
||||
const lo = this.loadoutFor(this.gd.chess(piece.id));
|
||||
u.skillIndex = lo.skillIndex;
|
||||
u.moduleId = lo.moduleId;
|
||||
}
|
||||
if (carry && carry.has(piece.uid)) u.carryState = carry.get(piece.uid);
|
||||
units.push(u);
|
||||
} else if (piece.kind === 'token') {
|
||||
|
||||
@@ -88,6 +88,9 @@ export class PlayerViews {
|
||||
nextEnemies: this.m.nextEnemiesFor(this),
|
||||
// DESIGN §16: the effective operator loadout ({ [baseChessId]: { skill, module } }; chess not listed use defaults)
|
||||
loadout: this.loadout,
|
||||
// 0.2.0 补位: the base chess ids this player fields as their stand-ins in this match (the not-owned list the seat had
|
||||
// at the match start; [] = every operator owned) — the client's 「替补:X」 badges, the stand-in's art and card
|
||||
standIns: this.standIns,
|
||||
stats: {
|
||||
dmgDealt: Math.round(this.stats.dmgDealt), kills: this.stats.kills, leaks: this.stats.leaks, gold: this.stats.gold,
|
||||
refreshes: this.stats.refreshes, merges: this.stats.merges,
|
||||
|
||||
Reference in new issue
Block a user