feedback5 CB5: a module's added trait line (official additionalDescription, PRTS 特性追加) shows after the class trait, not instead of it — the 干员调配 module card (特性 + 特性追加 rows), its 局内数值, the detail card and the 自选 module row (community report of 2026-10-06, item 16.2: 114 of the 164 modules the screen offers showed the extra line alone)
Text only: data/chess.json keeps both lines (traitOverride.desc = the class trait or the module's rewrite, moduleDesc = the added line) and the battle unit keeps the class trait with the module on — the same trait text, targeting and attack shape as 不装备 for every added-line module (圣约送葬人 REA-Y also rewrites it). New helpers ui/loadoutModel.js traitLines / fullTraitText; traitText (ui/detailPanel.js) composes the two. Golden unchanged (no sim change). Tests: test/ui/feedback5-module-trait.test.js (new), loadout-stats updated.
This commit is contained in:
1 parent
30d9a41c51
commit
02353e13f8
8 files changed
+196
-20
No files matched your search
@@ -13,7 +13,7 @@ import { html, Icon, Button, TierChip } from '../ui/components.js';
|
||||
import { Img, RichText, BondGlyph } from '../ui/gameComponents.js';
|
||||
import { chessAvatarUrl, chessPortraitUrl, profIconUrl, skillRecordIconUrl, moduleTypeIconUrl } from '../ui/assetUrls.js';
|
||||
import { data } from '../data.js';
|
||||
import { PROF_NAME, skillLabel, moduleBadge } from '../ui/loadoutModel.js';
|
||||
import { PROF_NAME, skillLabel, moduleBadge, fullTraitText } from '../ui/loadoutModel.js';
|
||||
import { diySlotList, pickChoices, pickOptions, slotRecord, defaultPick } from '../ui/diyModel.js';
|
||||
import { t } from '../../../shared/i18n.js';
|
||||
|
||||
@@ -117,7 +117,8 @@ function SkillRow({ m, s, on, locked, onPick }) {
|
||||
function ModuleRow({ id, rec, on, locked, onPick }) {
|
||||
const none = id == null;
|
||||
const talents = rec && Array.isArray(rec.talentChanges) ? rec.talentChanges.filter((x) => x && x.desc && !x.hidden) : [];
|
||||
const trait = rec?.traitOverride ? (rec.traitOverride.moduleDescRaw || rec.traitOverride.moduleDesc || rec.traitOverride.descRaw || rec.traitOverride.desc) : null;
|
||||
// the trait this module gives: the class trait (or the module's rewrite), then its extra line (item 16.2)
|
||||
const trait = rec?.traitOverride ? fullTraitText(rec.traitOverride) || null : null;
|
||||
return html`<button type="button" role="radio" aria-checked=${on ? 'true' : 'false'} data-module=${none ? 'none' : id} disabled=${locked}
|
||||
class=${cx('diy-choice', 'diy-choice--mod', on && 'is-on')} onClick=${() => onPick(id)}>
|
||||
<span class="diy-choice__icon">${none ? html`<b>—</b>` : html`<${Img} src=${moduleTypeIconUrl(data.get('local'), rec.typeName)} fallback=${html`<b class="num">${moduleBadge(rec)}</b>`} />`}</span>
|
||||
|
||||
@@ -29,7 +29,7 @@ import { useStore } from '../store.js';
|
||||
import { PHASE } from '../../../shared/constants.js';
|
||||
import {
|
||||
MODULE_NONE, PROF_ORDER, PROF_NAME, rosterOf, filterRoster, recordsOf, chessOptions, effectiveChoice, setChoice, resetChoice,
|
||||
changedCount, skillLabel, moduleBadge, attrRows, skillTags, serializeExport, parseImport, LOADOUT_IMPORT_MAX_BYTES,
|
||||
changedCount, skillLabel, moduleBadge, attrRows, skillTags, traitLines, serializeExport, parseImport, LOADOUT_IMPORT_MAX_BYTES,
|
||||
} from '../ui/loadoutModel.js';
|
||||
import { loadoutStore, openLoadout, closeLoadout, setEntries, applyLoadoutEntries, setNotOwned, applyOwnershipImport, setDiyPicks, applyDiyImport } from '../ui/loadoutSync.js';
|
||||
import { setOwned, notOwnedCount, serializeOwnership, parseOwnershipImport, OWNERSHIP_IMPORT_MAX_BYTES } from '../ui/ownershipModel.js';
|
||||
@@ -198,7 +198,7 @@ function SkillOption({ m, opt, on, level, onPick }) {
|
||||
</button>`;
|
||||
}
|
||||
|
||||
function ModuleInfo({ m, golden, opt }) {
|
||||
export function ModuleInfo({ m, golden, opt }) {
|
||||
if (!golden) return null;
|
||||
const rec = opt.id === MODULE_NONE ? null : opt.rec;
|
||||
if (!rec) {
|
||||
@@ -209,8 +209,9 @@ function ModuleInfo({ m, golden, opt }) {
|
||||
</div>`;
|
||||
}
|
||||
const rows = attrRows(rec.attr);
|
||||
const trait = rec.traitOverride;
|
||||
const traitText = trait ? (trait.moduleDescRaw || trait.moduleDesc || trait.descRaw || trait.desc) : null;
|
||||
// 特性: the trait the elite fights with under this module (the class trait, or the module's rewrite of it), then the
|
||||
// module's extra line under PRTS's own label 特性追加 — the line adds to the class trait (item 16.2)
|
||||
const lines = traitLines(rec.traitOverride);
|
||||
const talents = (Array.isArray(rec.talentChanges) ? rec.talentChanges : []).filter((t) => t && (t.name || t.desc) && !t.hidden);
|
||||
return html`<div class="lo-minfo">
|
||||
<div class="lo-minfo__title"><${Img} src=${moduleIconOf(m, rec)} class="lo-minfo__icon" /><span class="lo-minfo__type num">${rec.typeName || ''}</span><b>${rec.name || rec.uniEquipId}</b>
|
||||
@@ -219,7 +220,8 @@ function ModuleInfo({ m, golden, opt }) {
|
||||
<span class="lo-minfo__k">${t('属性')}</span>
|
||||
<span class="lo-minfo__v lo-attrs">${rows.length ? rows.map((r) => html`<span key=${r.key} class=${cx('lo-attr', r.positive ? 'is-up' : 'is-down')}>${r.label}<b class="num">${r.text}</b></span>`) : html`<span class="t-dim">${t('无属性加成')}</span>`}</span>
|
||||
</div>
|
||||
${traitText ? html`<div class="lo-minfo__row"><span class="lo-minfo__k">${t('特性')}</span><${RichText} class="lo-minfo__v" text=${traitText} /></div>` : null}
|
||||
${lines.base ? html`<div class="lo-minfo__row" data-trait="base"><span class="lo-minfo__k">${t('特性')}</span><${RichText} class="lo-minfo__v" text=${lines.base} /></div>` : null}
|
||||
${lines.added ? html`<div class="lo-minfo__row" data-trait="added"><span class="lo-minfo__k">${t('特性追加')}</span><${RichText} class="lo-minfo__v" text=${lines.added} /></div>` : null}
|
||||
${talents.map((tal, i) => html`<div key=${i} class="lo-minfo__row"><span class="lo-minfo__k">${t('天赋')}</span>
|
||||
<span class="lo-minfo__v">${tal.name ? html`<b class="lo-minfo__tname">${tal.name}</b>` : null}<${RichText} text=${tal.descRaw || tal.desc || ''} /></span></div>`)}
|
||||
</div>`;
|
||||
|
||||
@@ -40,7 +40,7 @@ import { abilityRows } from './abilityLines.js';
|
||||
import { data } from '../data.js';
|
||||
import { attackRangeGrid } from '../../../shared/loadoutRecord.js';
|
||||
import { SKILL_SUMMON_START_DEPLOY } from '../../../shared/constants.js';
|
||||
import { moduleBadge } from './loadoutModel.js';
|
||||
import { moduleBadge, fullTraitText } from './loadoutModel.js';
|
||||
import { t, tc, N_ } from '../../../shared/i18n.js';
|
||||
import { audio } from '../audio.js';
|
||||
|
||||
@@ -277,11 +277,11 @@ export function BondChips({ bondIds, bonds = [], onBond = null, off = null, gran
|
||||
*/
|
||||
export function traitText(c, golden, lo) {
|
||||
const t = (lo?.record || c).trait || {};
|
||||
const base = t.descRaw || t.desc || '';
|
||||
if (!golden) return base;
|
||||
// the record's own module line, also for a record cloned for another skill or module (a 不装备 record carries none) —
|
||||
// until 0.1.2 a clone fell back to the class trait (Grok review of GitHub #64; the in-match card had it too)
|
||||
return t.moduleDescRaw || base;
|
||||
if (!golden) return t.descRaw || t.desc || '';
|
||||
// the class trait (or the module's rewrite of it), then the record's own module line — also for a record cloned for
|
||||
// another skill or module (a 不装备 record carries none). Until 0.1.2 a clone fell back to the class trait (Grok review
|
||||
// of GitHub #64); until 0.2.0 the module line replaced the class trait (community report of 2026-10-06, item 16.2).
|
||||
return fullTraitText(t);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -325,6 +325,29 @@ export function moduleBadge(rec, id = null) {
|
||||
return m ? m[1] : t.slice(-1) || '?';
|
||||
}
|
||||
|
||||
/**
|
||||
* The two lines of a trait record (data `trait` / `traitBase`, ModuleRecord `traitOverride`, DATA.md §2): `base` = the
|
||||
* 特性 the unit fights with — the class trait, or the module's own wording where the module rewrites it (official
|
||||
* `overrideDescripton`) — and `added` = the module's extra line (official `additionalDescription`), or null. The extra
|
||||
* line comes after the class trait, never instead of it: PRTS flags it 「特性追加」 on every such module, and the sim keeps
|
||||
* the class trait with the module equipped (community report of 2026-10-06, item 16.2: until 0.2.0 the 干员调配 module
|
||||
* card, its 局内数值 and the detail card showed the extra line alone on 114 of the 164 modules the screen offers).
|
||||
* @param {any} trait
|
||||
* @returns {{ base: string, added: string|null }}
|
||||
*/
|
||||
export function traitLines(trait) {
|
||||
if (!isObj(trait)) return { base: '', added: null };
|
||||
const base = String(trait.descRaw || trait.desc || '');
|
||||
const added = trait.moduleDescRaw || trait.moduleDesc || null;
|
||||
return { base, added: added ? String(added) : null };
|
||||
}
|
||||
|
||||
/** The whole 特性 text of a trait record: its base line, then the module's extra line (`\n` between; RichText breaks it). */
|
||||
export function fullTraitText(trait) {
|
||||
const { base, added } = traitLines(trait);
|
||||
return base && added ? `${base}\n${added}` : base || added || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Module stat bonus as display rows (non-zero entries only).
|
||||
* @param {Record<string, number> | null | undefined} attr
|
||||
|
||||
Reference in new issue
Block a user