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
+21
-12
@@ -37,7 +37,7 @@
|
||||
import { useEffect } from '../../vendor/hooks.module.js';
|
||||
import { html, Icon, TierChip, MicroLabel, Button, confirmDialog, useTicker } from './components.js';
|
||||
import { Img, RichText, UnitThumb, BondGlyph, GIcon, diyToken } from './gameComponents.js';
|
||||
import { attackInterval, rangeGridBox, fmtNum, tileKey, chessLoadout, nextThreshold, bondTier, briefingBondTip, pieceBondIds, grantedBonds, morphPairings, ownStandIn, standInOf, standInLoadout, standInLabel, standInTip, standInForText, ownDiyRecord, ownDiyPick, diyRecordFor, pickGetter } from './gameLogic.js';
|
||||
import { attackInterval, rangeGridBox, fmtNum, tileKey, chessLoadout, nextThreshold, bondTier, briefingBondTip, pieceBondIds, grantedBonds, morphPairings, ownStandIn, standInOf, standInLoadout, standInLabel, standInTip, standInForText, ownDiyRecord, ownDiyPick, diyRecordFor, pickGetter, unitPick, unitCultivation } from './gameLogic.js';
|
||||
import { chessPortraitUrl, skillIconUrl, skillRecordIconUrl, profIconUrl, subProfIconUrl, itemIconUrl, enemyIconUrl, tokenAvatarUrl, factionIconUrl, uiUrl, moduleTypeIconUrl } from './assetUrls.js';
|
||||
import { abilityRows } from './abilityLines.js';
|
||||
import { data } from '../data.js';
|
||||
@@ -316,7 +316,7 @@ export function garrisonTypeIconKey(garrison) {
|
||||
}
|
||||
|
||||
/** The operator's own effect (特质, garrisons.json): trigger chip + description, compact. */
|
||||
function GarrisonBlock({ garrison, m }) {
|
||||
export function GarrisonBlock({ garrison, m }) {
|
||||
return html`<section class="dgarrison" aria-label=${t('特质')} data-garrison=${garrison.garrisonId || ''}>
|
||||
<div class="dgarrison__head">
|
||||
<span class="dgarrison__k">${t('特质')}</span>
|
||||
@@ -384,7 +384,7 @@ function skillTextNote(sk) {
|
||||
return note ? html`<p class="dhint dhint--rule" data-skill-note=${sk.skillId}><${Icon} name="info" />${t(note)}</p>` : null;
|
||||
}
|
||||
|
||||
export function ChessDetail({ chess, piece, unit, snapHp, editable, onSell, bonds, offBonds = null, loadout, onBond, live = null, hint = null, unitItems = null, standIn = null, diy = null }) {
|
||||
export function ChessDetail({ chess, piece, unit, snapHp, editable, onSell, bonds, offBonds = null, loadout, onBond, live = null, hint = null, unitItems = null, standIn = null, diy = null, cultOpts = null }) {
|
||||
const m = data.get('assets');
|
||||
const hp = hpOf(live, snapHp);
|
||||
// 0.2.0 自选编队: `chess` is then the composed 自选 record (the operator, the slot's tier / price); its skill and module are
|
||||
@@ -396,7 +396,9 @@ export function ChessDetail({ chess, piece, unit, snapHp, editable, onSell, bond
|
||||
// still apply (the owner's recall of the official mode, 2026-10-06)
|
||||
const si = standIn && standIn.standInFor ? standIn : null;
|
||||
const body = si || chess;
|
||||
const lo = si ? standInLoadout(si, getChess, data.get('backups')) : chessLoadout(chess, loadout, getChess);
|
||||
// 0.2.2: at the operator's 潜能 / 练度 — the player's settings (`cultOpts.ops`) or a teammate's unit's own
|
||||
// (`cultOpts.cultivation`); a stand-in has neither
|
||||
const lo = si ? standInLoadout(si, getChess, data.get('backups')) : chessLoadout(chess, loadout, getChess, { ...(cultOpts || {}), effects: data.get('effects') });
|
||||
const c = chess;
|
||||
// stats / talents the unit fights with: the chosen module's (or none — statsBase) for an elite (DESIGN §16)
|
||||
const fr = lo?.record || body;
|
||||
@@ -776,9 +778,12 @@ export function resolveDetail(target, pieces, { priv = null, backups = data.get(
|
||||
const d = mate ? { chess: mate, diy: target.diy } : foreign ? null : ownDiy(c);
|
||||
// `tap`: which card tap opened it (game.js numbers every shop / reward card it opens) — selectVoiceKey
|
||||
const tap = target.tap != null ? { tap: target.tap } : null;
|
||||
if (d) return { type: 'chess', chess: d.chess, hint: target.hint || null, standIn: null, diy: d.diy, ...(items.length ? { unitItems: items } : {}), ...tap };
|
||||
// 0.2.2: a teammate's member card reads its unit's 潜能 / 练度 when the row hands it on (`target.cultivation`), else
|
||||
// the defaults (never the viewer's own settings)
|
||||
const cv = foreign ? (target.cultivation !== undefined ? { cultivation: target.cultivation } : { ops: null }) : null;
|
||||
if (d) return { type: 'chess', chess: d.chess, hint: target.hint || null, standIn: null, diy: d.diy, ...(items.length ? { unitItems: items } : {}), ...(cv ? { cultOpts: cv } : {}), ...tap };
|
||||
const si = !c ? null : foreign ? (typeof target.standInFor === 'string' && target.standInFor ? standInOf(c, backups) : null) : ownSi(c);
|
||||
return c ? { type: 'chess', chess: c, hint: target.hint || null, standIn: si, ...(items.length ? { unitItems: items } : {}), ...tap } : null;
|
||||
return c ? { type: 'chess', chess: c, hint: target.hint || null, standIn: si, ...(items.length ? { unitItems: items } : {}), ...(cv ? { cultOpts: cv } : {}), ...tap } : null;
|
||||
}
|
||||
if (target.kind === 'item') { const it = data.lookup('items', target.id); return it ? { type: 'item', item: it } : null; }
|
||||
if (target.kind === 'enemy') { const en = data.lookup('enemies', target.id); return en ? { type: 'enemy', enemy: en, count: target.count } : null; }
|
||||
@@ -797,10 +802,13 @@ export function resolveDetail(target, pieces, { priv = null, backups = data.get(
|
||||
else if (c && own?.piece) si = ownSi(c);
|
||||
// 0.2.0 自选编队: a unit says itself which operator fills its DIY slot (UnitInfo diy); an own piece's unit follows
|
||||
// m.private.diy like the piece
|
||||
const pick = c && u.diy && typeof u.diy === 'object' ? u.diy : own?.piece ? ownDiyPick(priv, c) : null;
|
||||
const pick = c && u.diy && typeof u.diy === 'object' ? unitPick(u) : own?.piece ? ownDiyPick(priv, c) : null;
|
||||
const dr = pick ? diyRecordFor(c, pick, dd) : null;
|
||||
if (dr) return { type: 'chess', chess: dr, piece: own?.piece || null, unitId: u.id, unitItems: Array.isArray(u.items) ? u.items : null, standIn: null, diy: pick };
|
||||
if (c) return { type: 'chess', chess: c, piece: own?.piece || null, unitId: u.id, unitItems: Array.isArray(u.items) ? u.items : null, standIn: si };
|
||||
// 0.2.2: another player's unit says its 潜能 / 练度 itself (UnitInfo potential / cultivate); an own piece's unit follows
|
||||
// m.private.ops (the panel's `cultOpts`)
|
||||
const cv = own?.piece ? null : { cultOpts: { cultivation: unitCultivation(u) } };
|
||||
if (dr) return { type: 'chess', chess: dr, piece: own?.piece || null, unitId: u.id, unitItems: Array.isArray(u.items) ? u.items : null, standIn: null, diy: pick, ...cv };
|
||||
if (c) return { type: 'chess', chess: c, piece: own?.piece || null, unitId: u.id, unitItems: Array.isArray(u.items) ? u.items : null, standIn: si, ...cv };
|
||||
const t = data.lookup('tokens', u.defId) || diyToken(u.defId);
|
||||
if (t) return { type: 'token', token: t, unitId: u.id, ownerId: tokenOwnerId(own?.piece, pieces) };
|
||||
const en = data.lookup('enemies', u.defId);
|
||||
@@ -828,7 +836,8 @@ export function selectVoiceKey(detail) {
|
||||
* bonds: the owner's m.private.bonds (counts / tiers of the bond chips); offBonds: the bonds this mode never activates
|
||||
* (gameLogic modeOffBonds — their chips and the 变形同构体 pairing lines read 本局禁用); loadout: m.private.loadout (DESIGN §16) for
|
||||
* the player's own operators and shop cards; a teammate's unit gets its owner's choice (gameLogic unitLoadout); null
|
||||
* = the defaults
|
||||
* = the defaults; ops: m.private.ops (0.2.2 潜能 / 练度 of the player's own operators and cards — a unit the detail
|
||||
* resolved as another player's carries its own, `detail.cultOpts`)
|
||||
* live: the unit's live stats (unitStatsEntry + src 'battle' | 'prep') — an object, or a getter the panel re-reads 4×
|
||||
* a second (the battle's own sim, battle/runner.js unitStats); null ⇒ the record's numbers
|
||||
* voice: whether the panel may speak — 选中干员 (audio.voice 'select') when it opens on an operator the player tapped:
|
||||
@@ -836,7 +845,7 @@ export function selectVoiceKey(detail) {
|
||||
* (the owner's request of 2026-10-08 「添加一下干员点击上去的语气一样的语音」 lifted 2026-10-03's 「整备阶段不需要干员语音」
|
||||
* for this line only; [ASSUMED] the official prep tap says 选中干员 like the battle's FOCUS_CHAR)
|
||||
*/
|
||||
export function DetailPanel({ detail, editable, snapHp, onClose, onSell, onDestroy, bonds = [], offBonds = null, loadout = null, onBond = null, side = 'left', shopOpen = false, live = null, voice = false }) {
|
||||
export function DetailPanel({ detail, editable, snapHp, onClose, onSell, onDestroy, bonds = [], offBonds = null, loadout = null, ops = null, onBond = null, side = 'left', shopOpen = false, live = null, voice = false }) {
|
||||
const getter = typeof live === 'function' ? live : null;
|
||||
useTicker(detail && getter ? 250 : 0);
|
||||
// 选中干员 voice (audio.voice 'select'): once per opened operator — the panel stays mounted while the target changes,
|
||||
@@ -869,7 +878,7 @@ export function DetailPanel({ detail, editable, snapHp, onClose, onSell, onDestr
|
||||
<div class="dpanel__scroll">
|
||||
${detail.type === 'chess' ? html`<${ChessDetail} chess=${detail.chess} piece=${detail.piece} snapHp=${snapHp} editable=${editable} onSell=${sellIt}
|
||||
bonds=${bonds} offBonds=${offBonds} loadout=${loadout} onBond=${onBond} live=${liveNow} hint=${detail.hint || null} unitItems=${detail.unitItems || null}
|
||||
standIn=${detail.standIn || null} diy=${detail.diy || null} />` : null}
|
||||
standIn=${detail.standIn || null} diy=${detail.diy || null} cultOpts=${detail.cultOpts || { ops }} />` : null}
|
||||
${detail.type === 'item' ? html`<${ItemDetail} item=${detail.item} piece=${detail.piece} editable=${editable} onDestroy=${destroyIt} offBonds=${offBonds} />` : null}
|
||||
${detail.type === 'enemy' ? html`<${EnemyDetail} enemy=${detail.enemy} snapHp=${snapHp} count=${detail.count} live=${liveNow} />` : null}
|
||||
${detail.type === 'token' ? html`<${TokenDetail} token=${detail.token} piece=${detail.piece} ownerId=${detail.ownerId ?? null} snapHp=${snapHp} live=${liveNow} />` : null}
|
||||
|
||||
Reference in new issue
Block a user