feedback3: merge WJ — 高台 only for elite 歌蕾蒂娅 with HOK-Y, live prep watch, silenced 折射, grant toasts, emote timeout glyph

This commit is contained in:
sganggs committed 2026-10-04 21:26:46 +08:00
commit c9dd2a6392
41 files changed
+700 -216

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
// Enemy-card ability lines (detailPanel EnemyDetail). A line whose data format is SILENCE
// (折射: `<$ba.refraction>折射</>`, format "SILENCE") is off while the enemy is silenced: the card greys it.
// The sim already drops the RES bonus; this is only the line.
/**
* @param {any[]|null} abilities enemies.json abilities
* @param {boolean} silenced live unit (`unitStatsEntry.silenced`)
* @returns {Array<{ text: string, off: boolean }>}
*/
export function abilityRows(abilities, silenced) {
const list = Array.isArray(abilities) ? abilities : [];
return list.map((a) => {
const text = typeof a === 'string' ? a : (a && (a.textRaw || a.text)) || '';
const off = !!(silenced && a && typeof a === 'object' && a.format === 'SILENCE');
return { text, off };
});
}
+2 -1
View File
@@ -35,6 +35,7 @@ import { html, Icon, TierChip, MicroLabel, Button, confirmDialog, useTicker } fr
import { Img, RichText, UnitThumb, BondGlyph, GIcon } from './gameComponents.js';
import { attackInterval, rangeGridBox, fmtNum, tileKey, chessLoadout, nextThreshold, bondTier, briefingBondTip, pieceBondIds, grantedBonds, morphPairings } 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';
import { attackRangeGrid } from '../../../shared/loadoutRecord.js';
import { SKILL_SUMMON_START_DEPLOY } from '../../../shared/constants.js';
@@ -522,7 +523,7 @@ function EnemyDetail({ enemy, snapHp, count, live = null }) {
</div>
${imm.length ? html`<p class="dhint"><${Icon} name="shield" />免疫:${imm.join('、')}</p>` : null}
${Array.isArray(enemy.abilities) && enemy.abilities.length ? html`<${Section} title="能力" micro="ABILITIES">
<ul class="dabil">${enemy.abilities.map((a, i) => html`<li key=${i}><${RichText} text=${typeof a === 'string' ? a : a.textRaw || a.text} /></li>`)}</ul>
<ul class="dabil">${abilityRows(enemy.abilities, !!live?.silenced).map((a, i) => html`<li key=${i} class=${a.off ? 'is-off' : null}><${RichText} text=${a.text} /></li>`)}</ul>
<//>` : enemy.descRaw || enemy.desc ? html`<${Section} title="说明"><${RichText} as="p" text=${enemy.descRaw || enemy.desc} class="dtext" /><//>` : null}`;
}
+4 -3
View File
@@ -4,8 +4,9 @@
//
// EmoteArt the emote picture: the local-client art (data/local-assets.json → emoticon/<dir>/<picId>) first, else
// the copy setup downloads from the public mirror (data/assets.json → ui['emoticon/<dir>/<picId>'];
// GitHub issue #42), each tried in turn when one fails to load; a neutral glyph when neither is there,
// an empty box while a manifest is still loading.
// GitHub issue #42), each tried in turn when one fails to load; a neutral glyph when neither is there.
// An empty box only while a manifest is still in flight; a timeout or a failed manifest (GitHub #99)
// uses that glyph, and a later success replaces it.
// EmoteBubble the pop bubble beside the sender's avatar in the team panel (official emoji_bubble_bkg: a dark rounded
// square with a tail pointing left + the icon only); pop-in, 3 s, fade. The parent keys it by the emote's
// seq so a newer emote replaces the old one and pops again, and passes the arrival time (`at`) so a
@@ -68,7 +69,7 @@ export function emoteArtUrl(id) {
return emoteArtUrls(id)[0] || null;
}
/** True while the local-art manifest or the asset manifest has not settled (EmoteArt shows an empty box meanwhile). */
/** True while local or assets is still in flight (idle or loading). A timeout or a failure is `missing`: the glyph. */
const artManifestsPending = () => ['local', 'assets'].some((n) => { const st = data.status(n); return st === 'loading' || st === 'idle'; });
/** Official emote UI sprite (ui/battle: emoji_bubble_bkg, emoji_bkg, emoji_cell_bkg, emoji_btn, emoji_btn_disable). */
+9 -6
View File
@@ -4,8 +4,8 @@
// light legal tiles while dragging; the server stays authoritative and may still refuse a move.
//
// Board = own normal field (GEO.FIELD rows 9–12, cols 2–10). Melee chess stand on `melee` deploy tiles
// (LOW, buildable ALL/MELEE) — a 钩索师 / 推击手 ("可以放置于远程位", chess.json `placement` 'all') on any deploy
// tile, the 高台 included (piecePosition 'ALL'); ranged chess on `melee ∪ rangedOnly` (stages.json → deployTiles.normal,
// (LOW, buildable ALL/MELEE) — elite 歌蕾蒂娅 carrying HOK-Y (shared/highGround.js, the player's loadout) on any
// deploy tile, the 高台 included (piecePosition 'ALL'); ranged chess on `melee ∪ rangedOnly` (stages.json → deployTiles.normal,
// derived from the tile legend when missing — the legend's `buildable` is the effective type: 深水区 tile_deepsea
// refuses deployment, PRTS 深水区 地形信息 "拒绝部署(待补充)", player report #3 after 0.1.0). Tokens follow their
// own `position`; a summon whose text reads "只能部署在召唤者攻击范围内" (tokens.json `ownerRange`: 伺夜's 狼群,
@@ -24,6 +24,7 @@
import { GEO, PHASE, UF } from '../../../shared/constants.js';
import { resolveLoadout, loadoutOptions, MODULE_NONE } from '../../../shared/protocol.js';
import { resolveRecordLoadout, loadoutRecord, attackRangeGrid } from '../../../shared/loadoutRecord.js';
import { meleeOnHighGround } from '../../../shared/highGround.js';
import { rangeTiles, pieceDir } from './facing.js';
import { layoutPen } from '../render/pen.js';
import { BOSS_ROW_SHIFT, MAX_COL } from '../render/prepfield.js';
@@ -1041,15 +1042,17 @@ export function placementContext({ priv, stage, editable, field = 'normal', getC
}
/**
* Deploy position ('MELEE'|'RANGED'|'ALL') of a chess/token piece, or null for items. A MELEE operator whose branch
* trait reads "可以放置于远程位" (钩索师, 推击手: chess.json `placement` 'all') is 'ALL': any deployable tile, the 高台
* included (server/match/board.js positionClass; GitHub issue #32 item 4, DESIGN §22.6 [ASSUMED]).
* Deploy position ('MELEE'|'RANGED'|'ALL') of a chess/token piece, or null for items. Elite 歌蕾蒂娅 carrying HOK-Y
* (the viewer's loadout, shared/highGround.js) is 'ALL': any deployable tile, the 高台 included
* (server/match/board.js placeClass; owner's decision 2026-10-04). Every other MELEE chess is ground-only.
*/
export function piecePosition(ctx, piece) {
if (!isObj(piece)) return null;
if (piece.kind === 'chess') {
const rec = ctx.getChess(piece.id);
if (rec?.placement === 'all') return 'ALL';
let moduleId = null;
try { moduleId = resolveLoadout(ctx.priv?.loadout ?? null, rec, ctx.getChess)?.moduleId ?? null; } catch { moduleId = null; }
if (meleeOnHighGround(rec, moduleId)) return 'ALL';
return rec?.position === 'MELEE' ? 'MELEE' : 'RANGED';
}
// tokens: MELEE → ground only; RANGED / ALL → any deployable tile