feedback7 UIBACK: a stage device explains itself on a tap — 阻隔工事 / “双眼皮” / 射击台 / 源石流发生装置 get the terrain tip's card (GitHub #228, idea and first version in PR #229 by @rickylxw, ported onto the 0.2.1 modules with t() and the four packs): numbers from the stage's own device entry, the 「场地装置」 chip, the turret's stat grid, the tile fact; in a battle the crate and the turret are found by their device UNIT (the PR matched the stage pos: a turret outside the rect stands elsewhere, the units are not in the field meta of a battle shown from its start, a destroyed crate left the snapshots and still answered), the card carries the live HP and closes with the device; the dev mock boards ?stage= and streams crate units on ?variant=devices; golden: unchanged (client only)
This commit is contained in:
1 parent
ae22335cfe
commit
df540b6210
13 files changed
+802
-18
No files matched your search
@@ -96,7 +96,7 @@ import {
|
||||
phaseMode, phaseBanner, isCombatPhase, showDeadPill, isBossPhase, placementContext, canPlace, boardTargets, dropIntent,
|
||||
battleOverSfx, uniteResultBox, battleResultBox,
|
||||
snapHud, activeBubbles, shortcutFor, shortcutBlocked, closesOnFieldPress, phaseTotalSeconds, homeFieldId, ownFieldId, normalizeSp, sortedPlayers,
|
||||
terrainInfo,
|
||||
terrainInfo, deviceInfo, deviceTipAt, noteDeviceUnits,
|
||||
countdownState, shopBlockReason, stageOverrides, effectiveStage, watchTarget, dropFailureReason,
|
||||
previewEnemyKey, prepCamera, prepCameraFor, foldCamera, deployFieldOf, fieldTile, panelSide, panelSlots, bondPopupPlace, unitLoadout, deployedRecord,
|
||||
mergeTarget, modeOffBonds, readyFundsPrompt, ownerBandId, ownDiyRecord, ownStandIn,
|
||||
@@ -146,6 +146,9 @@ export function GameScreen() {
|
||||
|
||||
// ---- match screen ----------------------------------------------------------------------------------------
|
||||
|
||||
/** A card of a unit of the battle on screen: a unit's, or a crate's / turret's (a device card with its `unitId`, #228). */
|
||||
const inBattleCard = (d) => d?.kind === 'unit' || (d?.kind === 'device' && d.device?.unitId != null);
|
||||
|
||||
function MatchScreen() {
|
||||
useDocClass('sp-in-match');
|
||||
const pub = useStore((s) => s.match.public);
|
||||
@@ -335,6 +338,9 @@ function MatchScreen() {
|
||||
const lastFieldRef = useRef(null);
|
||||
const viewModeRef = useRef(null);
|
||||
const snapUnitsRef = useRef(new Map());
|
||||
// the shown battle's device units (crates / “双眼皮” turrets: UnitInfo by id) — the meta's `units` plus the 'spawn' events,
|
||||
// for the tap on a device (gameLogic.deviceTipAt); the renderer's pick skips devices, a tap on one lands in tileClick
|
||||
const deviceUnitsRef = useRef(new Map());
|
||||
const hudRef = useRef(null);
|
||||
// b.ev / b.snap that arrive before the UI entered their field (the battle's first ticks race the m.public /
|
||||
// m.field re-render): kept per fieldId and replayed on enter so no unit's 'spawn' UnitInfo is ever lost.
|
||||
@@ -372,6 +378,9 @@ function MatchScreen() {
|
||||
live.current.terrainTile = showPrep && (deployField === 'bossL' || deployField === 'bossR')
|
||||
? (row, col) => fieldTile(deployField, row, col)
|
||||
: (row, col) => [row, col];
|
||||
// a battle on screen (not the prep board, not a scouted prep board): its crates and turrets are device UNITS, found by
|
||||
// unit; everything else answers by the stage's own device entries (gameLogic.deviceTipAt / deviceInfo, #228)
|
||||
live.current.deviceBattle = !!shownField && !shownField.prep;
|
||||
const staleFieldRef = useRef(null);
|
||||
const enteredFieldRef = useRef(null);
|
||||
const pressSel = useRef(null); // the selected piece when the current field press began
|
||||
@@ -402,7 +411,7 @@ function MatchScreen() {
|
||||
const wanted = combat || mode === 'settle' || watchingOther;
|
||||
if (!wanted || !field || !field.fieldId || field === staleFieldRef.current || field === enteredFieldRef.current) return;
|
||||
if (watchingOther && !combat && field.fieldId !== watching) return; // an older push while switching
|
||||
if (lastFieldRef.current && lastFieldRef.current !== field.fieldId) setDetail((d) => (d?.kind === 'unit' ? null : d));
|
||||
if (lastFieldRef.current && lastFieldRef.current !== field.fieldId) setDetail((d) => (inBattleCard(d) ? null : d));
|
||||
enteredFieldRef.current = field;
|
||||
lastFieldRef.current = field.fieldId;
|
||||
reentryRef.current = null;
|
||||
@@ -411,6 +420,7 @@ function MatchScreen() {
|
||||
view.enterBattle(field);
|
||||
const early = evBufRef.current.get(field.fieldId);
|
||||
evBufRef.current.delete(field.fieldId);
|
||||
deviceUnitsRef.current = noteDeviceUnits(new Map(), { units: field.units, events: early });
|
||||
const earlySnap = snapBufRef.current.get(field.fieldId);
|
||||
snapBufRef.current.delete(field.fieldId);
|
||||
// a scouted prep board frames like the own prep with the shop folded (the bench row included, app.js camRect);
|
||||
@@ -498,6 +508,7 @@ function MatchScreen() {
|
||||
return;
|
||||
}
|
||||
view?.pushEvents(msg);
|
||||
noteDeviceUnits(deviceUnitsRef.current, { events: msg.ev });
|
||||
audio.handleBattleEvents(msg.ev);
|
||||
};
|
||||
// 干员语音 (结算): the own battle's result just came in — the operator's line depends on how it went
|
||||
@@ -630,7 +641,7 @@ function MatchScreen() {
|
||||
// the pen is a 休整期 view: leaving prep returns the camera (the next setCam would, too)
|
||||
if (phase !== PHASE.PREP && penRef.current.on) togglePenRef.current(false);
|
||||
// a battle unit's panel (live HP of a unit of the fight that just ended) never outlives its battle
|
||||
if (!isCombatPhase(phase) && phase !== PHASE.SETTLE) setDetail((d) => (d?.kind === 'unit' ? null : d));
|
||||
if (!isCombatPhase(phase) && phase !== PHASE.SETTLE) setDetail((d) => (inBattleCard(d) ? null : d));
|
||||
if (isCombatPhase(phase)) { setCollapsed(false); setDrag(null); view?.highlightTiles(null, null); }
|
||||
setSel(null);
|
||||
if (phase !== PHASE.PREP) setRewardMin(false);
|
||||
@@ -928,13 +939,24 @@ function MatchScreen() {
|
||||
setSel(wasSel ? null : { uid: e.uid });
|
||||
if (wasSel) setDetail((d) => (d?.kind === 'piece' && d.uid === e.uid ? null : d));
|
||||
}),
|
||||
// a tap on the ground itself: a special terrain tile explains itself (GitHub issue #184 「建议加入对于特殊地形的单击
|
||||
// 信息提示」) — 活性源石 / 沼泽 / 排气格栅 / 深水区 / 红蓝门 / 传送, with the numbers of the stage behind the board.
|
||||
// An ordinary tile (road / floor / wall) says nothing, so the press keeps its other meanings (deselect, close).
|
||||
// a tap on the ground itself: a stage device on the tile (阻隔工事 / “双眼皮” / 射击台 / 源石流发生装置: GitHub #228, PR
|
||||
// #229) explains itself first — the renderer's pick skips devices, so the tap lands here —, then a special terrain
|
||||
// tile (GitHub issue #184 「建议加入对于特殊地形的单击信息提示」) — 活性源石 / 沼泽 / 排气格栅 / 深水区 / 红蓝门 /
|
||||
// 传送, with the numbers of the stage behind the board. An ordinary tile (road / floor / wall) says nothing, so the
|
||||
// press keeps its other meanings (deselect, close).
|
||||
view.on('tileClick', (t) => {
|
||||
if (!t || !Number.isInteger(t.row) || !Number.isInteger(t.col)) return;
|
||||
const L = live.current;
|
||||
const [row, col] = L.terrainTile(t.row, t.col);
|
||||
const device = L.deviceBattle
|
||||
? deviceTipAt(L.terrainStage, row, col, { units: [...deviceUnitsRef.current.values()], snap: snapUnitsRef.current })
|
||||
: deviceInfo(L.terrainStage, row, col);
|
||||
if (device) {
|
||||
audio.sfx('click', { volume: 0.4 });
|
||||
setSel(null);
|
||||
setDetail({ kind: 'device', device });
|
||||
return;
|
||||
}
|
||||
const info = terrainInfo(L.terrainStage, row, col);
|
||||
if (!info) return;
|
||||
audio.sfx('click', { volume: 0.4 });
|
||||
@@ -1107,6 +1129,13 @@ function MatchScreen() {
|
||||
const t = id != null ? snapUnitsRef.current.get(id) : null;
|
||||
return t ? { hp: t[3], max: t[4] } : null;
|
||||
})();
|
||||
// a crate's / turret's card closes with it: destroyed (HP gone, or its tuple left the snapshots once the die animation
|
||||
// is over — before the first snapshot of a battle nothing is judged)
|
||||
useEffect(() => {
|
||||
const id = detail?.kind === 'device' ? detail.device?.unitId : null;
|
||||
const snap = snapUnitsRef.current;
|
||||
if (id != null && snap.size > 0 && !(snap.get(id)?.[3] > 0)) setDetail(null);
|
||||
}, [hud, detail]);
|
||||
|
||||
// ---- live stats of the detail card (user playtest #4 item 7) ------------------------------------------------------
|
||||
// battle: the local sim's unit (battle/runner.js unitStats — a getter the panel re-reads 4× a second; any unit of the
|
||||
|
||||
Reference in new issue
Block a user