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
+49
-13
@@ -27,6 +27,10 @@
|
||||
// slot's base id, `def.charId` = the operator (its kit, content/index.js kitOf), `def.loadout.diy` = the checked pick and
|
||||
// `def.tokenOwner` = the key of its summons' variants (data/backups.json `tokens`, read by getToken); null for an illegal
|
||||
// pick.
|
||||
// Potential (潜能 1–6, 0.2.2 — the player's 干员调配 setting; shared/potential.js): a loadout's `potential` composes the
|
||||
// chess / 自选 record at it (`def.loadout.potential`; full potential 6 is the default and the data's own) and the
|
||||
// summons' variants at their owner's potential (getToken / getDiyToken: talents, a 自选 summon's deploy limit / count).
|
||||
// A 补位 stand-in and a prototype 自选 pick have no potential.
|
||||
//
|
||||
// This module is pure ESM shared with browsers (served at /sim/): no Node API is imported here. Under Node the default
|
||||
// loader (./nodeData.js) is imported dynamically at module evaluation; browsers never load it.
|
||||
@@ -36,6 +40,7 @@
|
||||
// getSimData() the injected data (browser) or the generated data (Node), or null.
|
||||
|
||||
import { resolveRecordLoadout, composeStats, composeTalents, loadoutRecord } from '../../shared/loadoutRecord.js';
|
||||
import { atPotential, isPotential, POTENTIAL_DEFAULT, cultivateMul } from '../../shared/potential.js';
|
||||
import { standInRecord } from '../../shared/standIn.js';
|
||||
import { diyRecordOf, diyTokenOwner } from '../../shared/diy.js';
|
||||
import { normHitArea } from './body.js';
|
||||
@@ -230,7 +235,7 @@ export { composeStats, composeTalents, loadoutRecord };
|
||||
|
||||
/** Cache key suffix of a resolved non-default loadout ('' for the default). */
|
||||
export function loadoutKey(lo) {
|
||||
return lo && !lo.isDefault ? `|s${lo.skillIndex}|m${lo.moduleId ?? ''}` : '';
|
||||
return lo && !lo.isDefault ? `|s${lo.skillIndex}|m${lo.moduleId ?? ''}${lo.potentialIsDefault === false ? `|p${lo.potential}` : ''}` : '';
|
||||
}
|
||||
|
||||
/** Build an immunity Set from an object `{stun:true,…}` or an array `['stun', 'feared']`. */
|
||||
@@ -560,6 +565,7 @@ export class DataSource {
|
||||
stages: asMap(unwrap(raw.stages, 'stages'), 'id') ?? {},
|
||||
waves: asMap(unwrap(raw.waves, 'waves'), 'id') ?? {},
|
||||
backups: raw.backups && typeof raw.backups === 'object' && raw.backups.units ? raw.backups : null,
|
||||
effects: raw.effects && typeof raw.effects === 'object' ? raw.effects : null,
|
||||
};
|
||||
this.fallback = fallback;
|
||||
this._chess = new Map();
|
||||
@@ -578,6 +584,20 @@ export class DataSource {
|
||||
rawWave(id) { return this.raw.waves[id] ?? this.fallback?.rawWave(id) ?? null; }
|
||||
/** data/backups.json (`{ units, diy }`) of this source or its fallback, or null. */
|
||||
rawBackups() { return this.raw.backups ?? (typeof this.fallback?.rawBackups === 'function' ? this.fallback.rawBackups() : null); }
|
||||
/** A data/effects.json record of this source, its fallback, or the default game data (Node / the injected data). */
|
||||
rawEffect(id) {
|
||||
const own = this.raw.effects && Object.prototype.hasOwnProperty.call(this.raw.effects, id) ? this.raw.effects[id] : null;
|
||||
if (own) return own;
|
||||
if (typeof this.fallback?.rawEffect === 'function') return this.fallback.rawEffect(id);
|
||||
const fx = generatedCache && generatedCache.effects;
|
||||
return fx && Object.prototype.hasOwnProperty.call(fx, id) ? fx[id] : null;
|
||||
}
|
||||
/**
|
||||
* The 练度 (自持有) multipliers of tier `cultivate` (0–3): ATK / DEF / max HP from the effects.json CHAR_MAP record's
|
||||
* char_attribute_mul (shared/potential.js cultivateMul), or null.
|
||||
* @param {number} cultivate
|
||||
*/
|
||||
cultivateMul(cultivate) { return cultivateMul((id) => this.rawEffect(id), cultivate); }
|
||||
|
||||
/**
|
||||
* Chess def for the default loadout, or for `loadout` `{ skillIndex, moduleId }` (DESIGN §16; resolved with
|
||||
@@ -586,10 +606,11 @@ export class DataSource {
|
||||
* `loadout.standIn === true`: the 补位 def of the chess (getStandIn) — its skill and module are the chess's backup
|
||||
* selection whatever `skillIndex` / `moduleId` say; a chess without a stand-in (PRESET, DIY, no backups data) gives
|
||||
* its own def for that loadout. `loadout.diy` (`{ charId, skillIndex, uniEquipId }`): the 自选 def of a DIY slot
|
||||
* (getDiy; null when the pick is not legal for that slot).
|
||||
* (getDiy; null when the pick is not legal for that slot). `loadout.potential` (1–6): the record at that potential
|
||||
* (loadoutRecord; not for a stand-in).
|
||||
*/
|
||||
getChess(id, loadout = null) {
|
||||
if (loadout && loadout.diy && typeof loadout.diy === 'object') return this.getDiy(id, loadout.diy);
|
||||
if (loadout && loadout.diy && typeof loadout.diy === 'object') return this.getDiy(id, loadout.diy, loadout.potential);
|
||||
if (loadout && loadout.standIn === true) {
|
||||
const sd = this.getStandIn(id);
|
||||
if (sd) return sd;
|
||||
@@ -636,19 +657,26 @@ export class DataSource {
|
||||
* module, `def.charId` the operator's, `def.diyFor` the slot's base id, `def.loadout` = the selection (`isDefault`) with
|
||||
* `diy` = the checked pick, `def.tokenOwner` = its summons' variant key (shared/diy.js diyTokenOwner). Null for an
|
||||
* illegal pick (not a pick of the slot's tier, a prototype off its locked skill, an unknown skill or module — checkDiyPick)
|
||||
* or when the data lacks it. Cached per (id, pick).
|
||||
* or when the data lacks it. An owned pick is composed at `potential` (1–6, default 6; shared/diy.js diyRecordOf —
|
||||
* `def.loadout.potential`, its summons' variants follow); a prototype has none. Cached per (id, pick, potential).
|
||||
* @param {string} id
|
||||
* @param {{ charId?: string, skillIndex?: number|null, uniEquipId?: string|null }} pick
|
||||
* @param {number|null} [potential]
|
||||
*/
|
||||
getDiy(id, pick) {
|
||||
const key = `${id}|diy|${pick?.charId ?? ''}|${pick?.skillIndex ?? ''}|${pick?.uniEquipId ?? ''}`;
|
||||
getDiy(id, pick, potential = null) {
|
||||
const pot = isPotential(potential) ? potential : POTENTIAL_DEFAULT;
|
||||
const key = `${id}|diy|${pick?.charId ?? ''}|${pick?.skillIndex ?? ''}|${pick?.uniEquipId ?? ''}|p${pot}`;
|
||||
if (this._chess.has(key)) return this._chess.get(key);
|
||||
const r = this.rawChess(id);
|
||||
const rec = r && r.isDiy ? diyRecordOf(r, pick, this) : null;
|
||||
const rec = r && r.isDiy ? diyRecordOf(r, pick, this, { potential: pot }) : null;
|
||||
let d = null;
|
||||
if (rec) {
|
||||
d = normalizeChess({ ...rec, chessId: id });
|
||||
d.loadout = { ...resolveLoadout(rec, null), diy: { charId: rec.charId, skillIndex: rec.skill.index, uniEquipId: rec.module?.id ?? null } };
|
||||
const own = rec.diyProto ? POTENTIAL_DEFAULT : pot;
|
||||
d.loadout = {
|
||||
...resolveLoadout(rec, null), potential: own, potentialIsDefault: own === POTENTIAL_DEFAULT,
|
||||
diy: { charId: rec.charId, skillIndex: rec.skill.index, uniEquipId: rec.module?.id ?? null },
|
||||
};
|
||||
d.tokenOwner = diyTokenOwner(rec.charId, rec.status);
|
||||
freezeDef(d);
|
||||
}
|
||||
@@ -671,7 +699,7 @@ export class DataSource {
|
||||
* than 'display'; PlayerState / the kits' skill summons read them from here).
|
||||
*/
|
||||
getToken(id, ownerChessId = null, ownerLoadout = null) {
|
||||
if (ownerChessId && ownerLoadout && ownerLoadout.diy && typeof ownerLoadout.diy === 'object') return this.getDiyToken(id, ownerChessId, ownerLoadout.diy);
|
||||
if (ownerChessId && ownerLoadout && ownerLoadout.diy && typeof ownerLoadout.diy === 'object') return this.getDiyToken(id, ownerChessId, ownerLoadout.diy, ownerLoadout.potential);
|
||||
const olo = ownerChessId && ownerLoadout ? resolveLoadout(this.rawChess(ownerChessId), ownerLoadout) : null;
|
||||
const ck = id + '|' + (ownerChessId ?? '') + loadoutKey(olo);
|
||||
if (this._token.has(ck)) return this._token.get(ck);
|
||||
@@ -695,6 +723,8 @@ export class DataSource {
|
||||
}
|
||||
if (r && r.variants && !owner) owner = Object.keys(r.variants)[0] ?? null;
|
||||
let variant = r && r.variants && owner ? r.variants[owner] ?? null : null;
|
||||
// the owner's potential (0.2.2): the variant's talents (and counts) at it — before the skill / module parts merge
|
||||
if (variant && olo && olo.potentialIsDefault === false) variant = atPotential(variant, olo.potential);
|
||||
if (variant && olo && !olo.isDefault) {
|
||||
if (!olo.skillIsDefault && variant.bySkill && variant.bySkill[olo.skillIndex]) variant = { ...variant, ...variant.bySkill[olo.skillIndex] };
|
||||
if (!olo.moduleIsDefault && variant.byModule && variant.byModule[olo.moduleId]) variant = { ...variant, ...variant.byModule[olo.moduleId] };
|
||||
@@ -712,14 +742,17 @@ export class DataSource {
|
||||
* @param {string} id token id
|
||||
* @param {string} slotId the owner's DIY slot record id (`_a` / `_b`)
|
||||
* @param {{ charId?: string, skillIndex?: number|null, uniEquipId?: string|null }} pick the owner def's `loadout.diy`
|
||||
* @param {number|null} [potential] the owner's potential (its `loadout.potential`; default 6): the variant at it
|
||||
*/
|
||||
getDiyToken(id, slotId, pick) {
|
||||
getDiyToken(id, slotId, pick, potential = null) {
|
||||
const slot = this.rawChess(slotId);
|
||||
const owner = slot && typeof pick?.charId === 'string' ? diyTokenOwner(pick.charId, slot.status) : null;
|
||||
const ck = `${id}|diy|${owner ?? ''}|${pick?.skillIndex ?? ''}|${pick?.uniEquipId ?? ''}`;
|
||||
const pot = isPotential(potential) ? potential : POTENTIAL_DEFAULT;
|
||||
const ck = `${id}|diy|${owner ?? ''}|${pick?.skillIndex ?? ''}|${pick?.uniEquipId ?? ''}|p${pot}`;
|
||||
if (this._token.has(ck)) return this._token.get(ck);
|
||||
const r = this.rawToken(id);
|
||||
let variant = r && r.variants && owner ? r.variants[owner] ?? null : null;
|
||||
if (variant) variant = atPotential(variant, pot);
|
||||
if (variant) {
|
||||
const si = pick.skillIndex;
|
||||
if (Number.isInteger(si) && variant.bySkill && variant.bySkill[si]) variant = { ...variant, ...variant.bySkill[si] };
|
||||
@@ -821,7 +854,7 @@ export function toDataSource(data) {
|
||||
* loadout, and an existing view unchanged (`isLoadoutView`). Used by spec.js createBattleFromSpec and by
|
||||
* content/index.js installContent (every Battle, however constructed). An entry with `standIn: true` (补位) maps its
|
||||
* chess id to the stand-in def (`getChess(id, { standIn: true })`) the same way, and one with `diy` (自选: `{ charId,
|
||||
* skillIndex, uniEquipId }`) to its 自选 def (`getChess(id, { diy })`).
|
||||
* skillIndex, uniEquipId }`) to its 自选 def (`getChess(id, { diy })`); a `potential` (1–6) rides along (0.2.2).
|
||||
* @param {object} ds DataSource
|
||||
* @param {object[]} players PlayerBattleInput[] (spec players)
|
||||
*/
|
||||
@@ -834,13 +867,16 @@ export function withUnitLoadouts(ds, players) {
|
||||
for (const u of (p && Array.isArray(p.units) ? p.units : [])) {
|
||||
if (!u || u.kind === 'token' || typeof u.chessId !== 'string') continue;
|
||||
const diy = u.diy && typeof u.diy === 'object' ? u.diy : null;
|
||||
if (u.skillIndex != null || u.moduleId != null || u.standIn === true || diy) any = true;
|
||||
const pot = u.standIn !== true && isPotential(u.potential) ? u.potential : null;
|
||||
if (u.skillIndex != null || u.moduleId != null || u.standIn === true || diy || pot != null) any = true;
|
||||
const lo = { skillIndex: u.skillIndex ?? null, moduleId: u.moduleId ?? null };
|
||||
if (u.standIn === true) lo.standIn = true;
|
||||
if (diy) lo.diy = diy;
|
||||
if (pot != null) lo.potential = pot;
|
||||
const prev = map.get(u.chessId);
|
||||
if (!prev) map.set(u.chessId, lo);
|
||||
else if (prev.skillIndex !== lo.skillIndex || prev.moduleId !== lo.moduleId || !!prev.standIn !== !!lo.standIn
|
||||
|| (prev.potential ?? null) !== (lo.potential ?? null)
|
||||
|| JSON.stringify(prev.diy ?? null) !== JSON.stringify(lo.diy ?? null)) conflicts.add(u.chessId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user