feedback7: merge fb7-loadout — per-operator 潜能 (1–6) and 练度 (未精英化 / 精英1 / 精英2 / 精英2 Lv.60) set in 干员调配, defaults 潜能 6 and 精英2 Lv.60 (the owner's decision of 2026-10-08); PR #301 ported as one aligned list; #398 the skill frame, #400 REA-Y's heal text
- Conflicts (the branch started at ae22335c): the four i18n packs (union of keys), game.js DetailPanel props (ops + the
voices' voice=true), detailPanel.js resolveDetail (cultOpts + the voices' tap), match/views.js (both helpers),
docs/PLAYING.md and design/network.md merged line by line against the base. test/match/bosshp.test.js's ATK formula
now includes the unit's 练度 multiplier (cultMul).
- Grok review: MERGE AFTER FIXES — the note and the module card at the set potential fixed on the branch.
- Golden: every owned operator ×1.1 ATK / DEF / max HP by default — roster 49, bonds 46, fields 22, matches 18, diy 134
(stand-in battles and prototype picks unchanged). Broad node --test 5666 (0 fail after the bosshp formula; 2 skipped);
lint 0 errors; typecheck; i18n strict clean (1152 msgids).
This commit is contained in:
commit
0ced21d321
82 files changed
+9156
-6840
No files matched your search
@@ -8,6 +8,8 @@
|
||||
// player's units say it themselves (UnitInfo `diy`: the sim's, Match.prepFieldMeta's for scouting, m.result lineups). The
|
||||
// composed record is shared/diy.js diyRecordOf over chess.json + backups.json — the one the server and the sim use.
|
||||
// `data` below = `{ chess, backups }` (the client's data.get('chess') / data.get('backups'), localized copies).
|
||||
// 0.2.2: a pick may carry `potential` (the owner's 潜能 of the operator — m.private.ops for the player's own, UnitInfo
|
||||
// `potential` for another player's unit): the record is composed at it (an owned pick; a prototype has none).
|
||||
|
||||
import { diyRecordOf } from '../../../../shared/diy.js';
|
||||
import { isObj } from './shared.js';
|
||||
@@ -26,7 +28,10 @@ export function ownDiyPick(priv, chess) {
|
||||
if (!isObj(chess) || !chess.isDiy) return null;
|
||||
const slot = typeof chess.diyFor === 'string' ? chess.diyFor : chess.baseId || chess.chessId;
|
||||
const p = diyPicks(priv)[slot];
|
||||
return isObj(p) && typeof p.charId === 'string' ? p : null;
|
||||
if (!isObj(p) || typeof p.charId !== 'string') return null;
|
||||
// 0.2.2: at the player's 潜能 of that operator (m.private.ops; none set = 6)
|
||||
const pot = isObj(priv?.ops) && isObj(priv.ops[p.charId]) ? priv.ops[p.charId].potential : null;
|
||||
return Number.isInteger(pot) && pot >= 1 && pot < 6 ? { ...p, potential: pot } : p;
|
||||
}
|
||||
|
||||
/** Per backups object: `chessId|charId|skill|module` → composed record (or null). */
|
||||
@@ -44,10 +49,11 @@ export function diyRecordFor(chess, pick, data) {
|
||||
if (!isObj(pick) || !isObj(data?.backups)) return null;
|
||||
let m = CACHE.get(data.backups);
|
||||
if (!m) { m = new Map(); CACHE.set(data.backups, m); }
|
||||
const key = `${chess.chessId}|${pick.charId}|${pick.skillIndex ?? ''}|${pick.uniEquipId ?? ''}`;
|
||||
const pot = Number.isInteger(pick.potential) ? pick.potential : null;
|
||||
const key = `${chess.chessId}|${pick.charId}|${pick.skillIndex ?? ''}|${pick.uniEquipId ?? ''}|${pot ?? ''}`;
|
||||
if (m.has(key)) return m.get(key);
|
||||
let rec;
|
||||
try { rec = diyRecordOf(chess, pick, data); } catch { rec = null; }
|
||||
try { rec = diyRecordOf(chess, pick, data, { potential: pot }); } catch { rec = null; }
|
||||
m.set(key, rec);
|
||||
return rec;
|
||||
}
|
||||
@@ -65,10 +71,16 @@ export function ownDiyRecord(chess, priv, data) {
|
||||
*/
|
||||
export function cardDiy(chess, { priv = null, unit = null, data = null } = {}) {
|
||||
if (!isObj(chess) || !chess.isDiy) return null;
|
||||
if (unit) return isObj(unit.diy) ? diyRecordFor(chess, unit.diy, data) : null;
|
||||
if (unit) return isObj(unit.diy) ? diyRecordFor(chess, unitPick(unit), data) : null;
|
||||
return ownDiyRecord(chess, priv, data);
|
||||
}
|
||||
|
||||
/** A unit's 自选 pick with its potential (UnitInfo `diy` + `potential`, 0.2.2), or null. */
|
||||
export function unitPick(unit) {
|
||||
if (!isObj(unit) || !isObj(unit.diy)) return null;
|
||||
return Number.isInteger(unit.potential) ? { ...unit.diy, potential: unit.potential } : unit.diy;
|
||||
}
|
||||
|
||||
/**
|
||||
* id → the player's 自选 record of a filled DIY slot (both forms), else the chess record: the lookup the player's own
|
||||
* pieces and cards resolve against (names, art, position, the loadout / range of a 自选 piece — shared/protocol.js
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { MODULE_NONE, loadoutOptions, resolveLoadout } from '../../../../shared/protocol.js';
|
||||
import { loadoutRecord, resolveRecordLoadout } from '../../../../shared/loadoutRecord.js';
|
||||
import { cultivationOf, cultivateMul, cultivatedStats, isPotential, isCultivate } from '../../../../shared/potential.js';
|
||||
import { isObj } from './shared.js';
|
||||
import { t } from '../../../../shared/i18n.js';
|
||||
|
||||
@@ -21,14 +22,21 @@ export function tokenVariantFor(token, ownerId = null) {
|
||||
* The skill / module a chess fights with under the player's loadout (m.private.loadout, DESIGN §16) — what the shop
|
||||
* card and the detail panel show. Works with data that already lists every selectable skill (`skills[]`, elite
|
||||
* `modules[]`) and with older data (only the default `skill` / `module`).
|
||||
* 0.2.2: the record (stats, talents) is at the operator's 潜能 and its stats carry the 练度 multiplier — the player's
|
||||
* settings (`opts.ops`: m.private.ops / the 干员调配 store; missing = 潜能 6, 精英2 Lv.60), or a unit's own
|
||||
* (`opts.cultivation`: a teammate's UnitInfo — null = none); a 补位 stand-in / prototype 自选 record has neither
|
||||
* (shared/potential.js cultivationOf). The 练度 needs `opts.effects` (data/effects.json). A composed 自选 record is
|
||||
* already at its potential (gameLogic/diy.js: the pick's `potential`).
|
||||
* @param {any} chess the chess record (normal or elite)
|
||||
* @param {any} loadout m.private.loadout `{ [baseChessId]: { skill, module } }` (entries equal to the defaults may be
|
||||
* missing); null → defaults
|
||||
* @param {(id: string) => any} [getChess]
|
||||
* @param {{ ops?: any, cultivation?: { potential?: number|null, cultivate?: number|null } | null, effects?: any }} [opts]
|
||||
* @returns {{ skill: any, skillIndex: number|null, defaultSkill: boolean, module: { id: string, name: string, typeName: string, none: boolean }|null,
|
||||
* defaultModule: boolean, changed: boolean, choices: number } | null}
|
||||
* defaultModule: boolean, changed: boolean, choices: number, record: any,
|
||||
* cultivation: { potential: number, cultivate: number|null } | null } | null}
|
||||
*/
|
||||
export function chessLoadout(chess, loadout, getChess = () => null) {
|
||||
export function chessLoadout(chess, loadout, getChess = () => null, opts = {}) {
|
||||
if (!isObj(chess)) return null;
|
||||
const lo = isObj(loadout) && !Array.isArray(loadout) ? loadout : null;
|
||||
let r = { skillIndex: null, moduleId: null };
|
||||
@@ -52,12 +60,42 @@ export function chessLoadout(chess, loadout, getChess = () => null) {
|
||||
}
|
||||
defaultModule = opt.defaultModule == null || id === opt.defaultModule;
|
||||
}
|
||||
// the record the unit fights with (stats / 特性 / talents of the chosen module or none — the battle's own composition,
|
||||
// shared/loadoutRecord.js): the detail card must show what the sim runs
|
||||
// 0.2.2: the operator's 潜能 / 练度 (the player's settings, or the unit's own)
|
||||
const cv = cultivationFor(chess, opts);
|
||||
// the record the unit fights with (stats / 特性 / talents of the chosen module or none, at its potential — the
|
||||
// battle's own composition, shared/loadoutRecord.js): the detail card must show what the sim runs
|
||||
let record = chess;
|
||||
try { record = loadoutRecord(chess, resolveRecordLoadout(chess, { skillIndex: r.skillIndex, moduleId: r.moduleId })) || chess; } catch { /* the record as is */ }
|
||||
try { record = loadoutRecord(chess, resolveRecordLoadout(chess, { skillIndex: r.skillIndex, moduleId: r.moduleId, potential: cv ? cv.potential : null })) || chess; } catch { /* the record as is */ }
|
||||
const mul = cv && isCultivate(cv.cultivate) ? cultivateMul(opts?.effects, cv.cultivate) : null;
|
||||
if (mul && record && record.stats) record = { ...record, stats: cultivatedStats(record.stats, mul) };
|
||||
return { skill, skillIndex: skill?.index ?? null, defaultSkill, module, defaultModule, changed: !defaultSkill || !defaultModule,
|
||||
choices: Math.max(skills.length, opt.skills?.length || 0), record };
|
||||
choices: Math.max(skills.length, opt.skills?.length || 0), record, cultivation: cv };
|
||||
}
|
||||
|
||||
/**
|
||||
* The 潜能 / 练度 of a card's record (chessLoadout `opts`): the unit's own (`cultivation`, null = none) or the player's
|
||||
* settings (`ops`, shared/potential.js cultivationOf — null for a stand-in / prototype 自选 record).
|
||||
* @returns {{ potential: number, cultivate: number|null } | null}
|
||||
*/
|
||||
function cultivationFor(chess, opts) {
|
||||
if (opts && opts.cultivation !== undefined) {
|
||||
const c = opts.cultivation;
|
||||
if (!isObj(c) || !isObj(chess) || chess.standInFor || chess.diyProto) return null;
|
||||
return { potential: isPotential(c.potential) ? c.potential : 6, cultivate: isCultivate(c.cultivate) ? c.cultivate : null };
|
||||
}
|
||||
return cultivationOf(chess, opts ? opts.ops : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* The 潜能 / 练度 a battle / scouting unit fights at (UnitInfo `potential` — below 6 only — and `cultivate`, 0.2.2): for
|
||||
* chessLoadout `opts.cultivation`; null when the unit has neither (a stand-in, a prototype 自选 piece, a raw unit).
|
||||
* @param {any} unit UnitInfo
|
||||
*/
|
||||
export function unitCultivation(unit) {
|
||||
if (!isObj(unit)) return null;
|
||||
const pot = isPotential(unit.potential) ? unit.potential : 6;
|
||||
const cult = isCultivate(unit.cultivate) ? unit.cultivate : null;
|
||||
return cult == null && pot === 6 ? null : { potential: pot, cultivate: cult };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user