feedback5 WK: 自选编队 in the browser — the 干员调配 overlay's third tab 自选编队 (5阶 ×2, 6阶 ×2 slots; the slot picker lists the kitted picks of the tier with avatar, name, class, derived bonds and a 原型 / 已持有 tag; an owned pick chooses its skill and module — 集成战略 modules left out —, a prototype shows its locked skill; sp.pref.diy, export / import, 全部清空, synced as room.diy through the shared engine, welcome.diyKitted kept), the operator on shop / reward cards, own pieces (「自选」 tag, its model on bench and board), the detail card (pick's skill and module), placement and range previews, bond popups (own and a teammate's 自选 member), the result screen; new UI strings through t() with English; the e2e fast server's SP_START_LEVEL hook
Bonds of 自选 pieces checked everywhere membership is decided (the owner's reminder of 2026-10-05): the match's counts through the player's data view (煌 in 炎 and 维多利亚, one member per slot, 绝技 elites), the sim's bond effects through def.bonds (叙拉古 on deployment, 协防 ×1.2 / elite ×1.4), the client's strip, popups, cards and detail card. Golden unchanged. Tests: test/ui/diy-ui.test.js (15), test/match/diy-bonds.test.js (4), test/i18n.test.js pins screens/diy.js; test/ui/diy.e2e.test.js (SP_E2E=1, run once: pass).
This commit is contained in:
1 parent
e480b1cb26
commit
0def7babfd
31 files changed
+1637
-78
No files matched your search
@@ -276,6 +276,7 @@ export async function createFieldView(host, options = {}) {
|
||||
let penList = null;
|
||||
let ownPen = null; // the own m.private.nextEnemies (fallback composition of a scouted teammate's pen)
|
||||
let standInList = []; // the own m.private.standIns (0.2.0 补位): board pieces of these chess draw the stand-in
|
||||
let diyPicks = {}; // the own m.private.diy (0.2.0 自选编队): pieces of these DIY slots draw the operator
|
||||
let camBeforePen = null; // { kind, opts } the camera the pen returns to
|
||||
let leader = null; // { key, view, stand, area } the round leader standing on the boss field in the prep (setLeader)
|
||||
let leaderHidden = true; // shown only by the boss-field prep camera (leaderShown)
|
||||
@@ -288,7 +289,7 @@ export async function createFieldView(host, options = {}) {
|
||||
timeScale: () => (mode === 'battle' ? interp.rate : 1),
|
||||
// (a chess fighting as its 补位 stand-in — `standInFor` — reads the stand-in's record: its attack interval)
|
||||
lookupDef: (info) => (info.side === 'enemy' ? data.enemy(info.defId)
|
||||
: (info.standInFor && data.standIn(info.defId)) || data.chess(info.defId) || data.token(info.defId)),
|
||||
: (info.standInFor && data.standIn(info.defId)) || (info.diy && data.diy(info.defId, info.diy)) || data.chess(info.defId) || data.token(info.defId)),
|
||||
crowded: () => views.size > 90,
|
||||
renderer: app.renderer,
|
||||
frameNo: () => frameNo,
|
||||
@@ -304,7 +305,7 @@ export async function createFieldView(host, options = {}) {
|
||||
timeScale: () => ctx.timeScale(),
|
||||
loadLevel: () => loadLevel,
|
||||
fieldRect: () => (mode === 'battle' && battleMeta ? battleMeta.rect : null),
|
||||
subProfOf: (defId, info = null) => ((info && info.standInFor && data.standIn(defId)) || data.chess(defId))?.subProfessionId || null,
|
||||
subProfOf: (defId, info = null) => ((info && info.standInFor && data.standIn(defId)) || (info && info.diy && data.diy(defId, info.diy)) || data.chess(defId))?.subProfessionId || null,
|
||||
view: (id) => views.get(id) || null,
|
||||
screenSize: size,
|
||||
fieldTop: () => {
|
||||
@@ -633,12 +634,16 @@ export async function createFieldView(host, options = {}) {
|
||||
// 0.2.0 补位: a board piece of a chess the player does not own (m.private.standIns) is deployed as its stand-in —
|
||||
// the stand-in's model; on the bench it keeps the chess's own (the hand shows the original operator)
|
||||
const si = area === 'board' && chess && standInList.includes(chess.baseId || chess.chessId) ? data.standIn(piece.id) : null;
|
||||
const rec = si || chess;
|
||||
// 0.2.0 自选编队: a DIY slot the player filled is its operator, on the board and on the bench alike
|
||||
const pick = chess && chess.isDiy ? diyPicks[chess.baseId || chess.chessId] : null;
|
||||
const dr = pick ? data.diy(piece.id, pick) : null;
|
||||
const rec = si || dr || chess;
|
||||
return {
|
||||
kind: 'op', side: 'ally', defId: piece.id,
|
||||
spine: rec?.assets?.spine || rec?.charId || null, avatar: rec?.assets?.avatar || rec?.charId || null,
|
||||
tier: chess?.tier || piece.tier || 1, golden: !!(piece.golden || chess?.isGolden), dir,
|
||||
...(si ? { standInFor: si.standInFor } : null),
|
||||
...(dr ? { diy: { charId: pick.charId, skillIndex: pick.skillIndex ?? null, uniEquipId: pick.uniEquipId ?? null } } : null),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -687,6 +692,7 @@ export async function createFieldView(host, options = {}) {
|
||||
};
|
||||
const src = ps && typeof ps === 'object' ? ps : {};
|
||||
standInList = Array.isArray(src.standIns) ? src.standIns.filter((x) => typeof x === 'string') : [];
|
||||
diyPicks = src.diy && typeof src.diy === 'object' ? src.diy : {};
|
||||
ownPen = Array.isArray(src.nextEnemies) ? src.nextEnemies : null;
|
||||
setPenList(ownPen);
|
||||
addList(src.hand, 'hand');
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { createAssets, assets as defaultAssets } from '../../assets.js';
|
||||
import { standInRecord } from '../../../../shared/standIn.js';
|
||||
import { diyRecordOf } from '../../../../shared/diy.js';
|
||||
|
||||
function withTimeout(p, ms) {
|
||||
return Promise.race([p, new Promise((resolve) => setTimeout(resolve, ms))]);
|
||||
@@ -40,10 +41,26 @@ function makeData(src) {
|
||||
standIns.set(c, rec);
|
||||
return rec;
|
||||
};
|
||||
// 0.2.0 自选编队: the composed 自选 record of a DIY slot filled with a pick (shared/diy.js over chess.json + backups.json)
|
||||
const diys = new Map();
|
||||
const chessMap = () => {
|
||||
try { return (src && typeof src.get === 'function' ? src.get('chess') : src && src.chess) || null; } catch { return null; }
|
||||
};
|
||||
const diy = (id, pick) => {
|
||||
const c = look('chess', id);
|
||||
const b = backups();
|
||||
if (!c || !c.isDiy || !b || !pick || typeof pick.charId !== 'string') return null;
|
||||
const key = `${id}|${pick.charId}|${pick.skillIndex ?? ''}|${pick.uniEquipId ?? ''}`;
|
||||
if (diys.has(key)) return diys.get(key);
|
||||
let rec;
|
||||
try { rec = diyRecordOf(c, pick, { chess: chessMap(), backups: b }); } catch { rec = null; }
|
||||
diys.set(key, rec);
|
||||
return rec;
|
||||
};
|
||||
return {
|
||||
chess: (id) => look('chess', id), token: (id) => look('tokens', id), item: (id) => look('items', id),
|
||||
enemy: (id) => look('enemies', id), stage: (id) => look('stages', id), bond: (id) => look('bonds', id),
|
||||
standIn,
|
||||
standIn, diy,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -45,5 +45,10 @@ export function renderInfo(u) {
|
||||
// 0.2.0 补位: the replaced operator's charId of a chess fighting as its stand-in (UnitInfo.standInFor; spine / avatar /
|
||||
// name are already the stand-in's) — the detail card and the view's data lookups (attack interval, splash FX) follow it
|
||||
standInFor: typeof u.standInFor === 'string' && u.standInFor ? u.standInFor : undefined,
|
||||
// 0.2.0 自选编队: the pick of a DIY slot's unit (UnitInfo.diy { charId, skillIndex, uniEquipId }; spine / avatar / name
|
||||
// are already the operator's) — the detail card composes the operator from it, the data lookups follow it
|
||||
diy: u.diy && typeof u.diy === 'object' && typeof u.diy.charId === 'string'
|
||||
? { charId: u.diy.charId, skillIndex: Number.isInteger(u.diy.skillIndex) ? u.diy.skillIndex : null, uniEquipId: typeof u.diy.uniEquipId === 'string' ? u.diy.uniEquipId : null }
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user