0.2.5-dev: merge claude/036-touch
# Conflicts: # CHANGELOG.md # docs/history/0.2.5.md
This commit is contained in:
commit
a8ff5e7c1d
22 files changed
+294
-59
No files matched your search
+29
-9
@@ -265,6 +265,7 @@ export async function createFieldView(host, options = {}) {
|
||||
let pendingView = null; // tile band/focus to apply when the camera transition ends
|
||||
const views = new Map(); // key → view (prep: 'p:'+uid; battle: unit id)
|
||||
let prepPieces = []; // { uid, piece, area, idx, row, col, key }
|
||||
let prepMapChars = []; // { unit (board space), display } — read-only, but inspectable
|
||||
// the last prep pieces before a battle / scouting board emptied prepPieces: a merge completed between two preps (a band
|
||||
// or 机变 grant at ROUND_START, a SETTLE merge) is still recognised by the next setPrep (QA 6b)
|
||||
let promoBase = [];
|
||||
@@ -812,16 +813,17 @@ export async function createFieldView(host, options = {}) {
|
||||
if (e.area === 'board' && info.dir && !held.has(e.uid) && typeof v.setDir === 'function') v.setDir(info.dir);
|
||||
}
|
||||
for (const k of [...views.keys()]) if (String(k).startsWith('p:') && !seen.has(Number(String(k).slice(2)))) dropView(k);
|
||||
// The 外勤医疗 reserve medic is visible before battle but not a movable board piece. Map its board-space slot to
|
||||
// the displayed boss half exactly as for the player's own pieces; the partner's units already use field coordinates.
|
||||
const mapChars = Array.isArray(src.prepMapChars) ? src.prepMapChars.map((u) => {
|
||||
if (!u || !Number.isInteger(u.x) || !Number.isInteger(u.y)) return null;
|
||||
// The 外勤医疗 reserve medic is visible before battle and can be inspected, but is not a movable board piece.
|
||||
// Keep its original board-space record for the detail card/range; only the rendered view is transformed to the
|
||||
// displayed boss half. The partner's units already use field coordinates and are not own prep map characters.
|
||||
prepMapChars = Array.isArray(src.prepMapChars) ? src.prepMapChars.map((u) => {
|
||||
if (!u || !Number.isInteger(u.uid) || !Number.isInteger(u.x) || !Number.isInteger(u.y)) return null;
|
||||
const p = prepXf.toDisp(u.y, u.x);
|
||||
const dir = prepXf.dirToDisp(u.dir);
|
||||
return { ...u, x: p.col, y: p.row, dir, facing: dir === 'LEFT' ? -1 : 1 };
|
||||
return { unit: u, display: { ...u, x: p.col, y: p.row, dir, facing: dir === 'LEFT' ? -1 : 1 } };
|
||||
}).filter(Boolean) : [];
|
||||
const mate = prepXf.kind === 'bossPrep' && src.bossMate && Array.isArray(src.bossMate.units) ? src.bossMate.units : [];
|
||||
syncReadOnlyPrepUnits([...mapChars, ...mate]);
|
||||
syncReadOnlyPrepUnits([...prepMapChars.map((e) => e.display), ...mate]);
|
||||
prepPieces = list.filter((e) => e.key && views.has(e.key));
|
||||
if (dragState && !views.has(dragState.key)) { drag.reset(); endDragVisual(false); }
|
||||
holdScene(false); // the prep pieces reference their models now (a battle's hold ends here)
|
||||
@@ -829,8 +831,8 @@ export async function createFieldView(host, options = {}) {
|
||||
}
|
||||
|
||||
/**
|
||||
* The reserve medic and a boss pair's partner pieces: read-only 'm:<uid>' views. These are not prep pieces, so they
|
||||
* are never picked, dragged or swept with the own board; a view is rebuilt when its body, tile or facing changes.
|
||||
* The reserve medic and a boss pair's partner pieces: read-only 'm:<uid>' views. These are not movable prep pieces;
|
||||
* only the owner's prepMapChars may be picked for inspection. Rebuild when body, tile or facing changes.
|
||||
*/
|
||||
function syncReadOnlyPrepUnits(units) {
|
||||
const keep = new Set();
|
||||
@@ -1024,6 +1026,17 @@ export async function createFieldView(host, options = {}) {
|
||||
return { uid: best.uid, kind: best.piece.kind, id: best.piece.id, area: best.area, idx: best.idx, row: best.row, col: best.col, piece: best.piece, draggable: true };
|
||||
}
|
||||
|
||||
/** The owner's read-only preparation map character under a canvas point (never a drag target). */
|
||||
function mapCharAt(x, y) {
|
||||
if (mode !== 'prep') return null;
|
||||
const units = [];
|
||||
for (const e of prepMapChars) {
|
||||
const u = pickUnitOf(views.get(`m:${e.unit.uid}`));
|
||||
if (u) { u.unit = e.unit; units.push(u); }
|
||||
}
|
||||
return pickOnTile(units, groundTile(x, y))?.unit || null;
|
||||
}
|
||||
|
||||
const drag = createDragController({
|
||||
hitPiece: (x, y) => pieceAt(x, y),
|
||||
pickTile: (x, y) => pickBoardTile(x, y),
|
||||
@@ -1226,6 +1239,12 @@ export async function createFieldView(host, options = {}) {
|
||||
return;
|
||||
}
|
||||
if (drag.pointerDown(ev)) { try { canvas.setPointerCapture(e.pointerId); } catch { /* ignore */ } return; }
|
||||
const mapChar = mapCharAt(ev.x, ev.y);
|
||||
if (mapChar) {
|
||||
emit('pieceClick', { unit: mapChar, unitId: null, uid: mapChar.uid, prepMapChar: true,
|
||||
button: e.button, detail: e.button === 2, clientX: e.clientX, clientY: e.clientY });
|
||||
return;
|
||||
}
|
||||
if (mode === 'prep') { const lv = leaderAt(ev.x, ev.y); if (lv) { emitPenClick(lv, e); return; } }
|
||||
if (penViews.size && mode === 'prep') {
|
||||
const pv = penUnitAt(ev.x, ev.y);
|
||||
@@ -1307,6 +1326,7 @@ export async function createFieldView(host, options = {}) {
|
||||
clearViews();
|
||||
if (prepPieces.length) promoBase = prepPieces;
|
||||
prepPieces = [];
|
||||
prepMapChars = [];
|
||||
infos.clear();
|
||||
gone.clear();
|
||||
interp.reset();
|
||||
@@ -1946,7 +1966,7 @@ export async function createFieldView(host, options = {}) {
|
||||
app, get cam() { return cam; }, get board3d() { return board3d; }, tiles, views, penViews, get leader() { return leader; }, interp, fx, ctx, drag, get camKind() { return viewKind(camKind, camOpts); },
|
||||
promotions,
|
||||
// picking (render/pick.js) at canvas px: the prep piece / battle view / pen view there, the ground tile under it
|
||||
pick: { pieceAt, battleUnitAt, penUnitAt, groundTile },
|
||||
pick: { pieceAt, mapCharAt, battleUnitAt, penUnitAt, groundTile },
|
||||
},
|
||||
};
|
||||
// All teardown dependencies now exist. Aborting during either asset wait immediately releases
|
||||
|
||||
@@ -1004,7 +1004,7 @@ function MatchScreen() {
|
||||
// an enemy of the preview pen (research 09 §2.2 "Intel": tap it for its detail card)
|
||||
const penKey = previewEnemyKey(e);
|
||||
if (penKey) { setDetail({ kind: 'enemy', id: penKey }); return; }
|
||||
if (e.unitId != null || e.unit) { setDetail({ kind: 'unit', unit: e.unit || null, unitId: e.unitId, uid: e.uid }); return; }
|
||||
if (e.unitId != null || e.unit) { setDetail({ kind: 'unit', unit: e.unit || null, unitId: e.unitId, uid: e.uid, prepMapChar: e.prepMapChar === true }); return; }
|
||||
if (!Number.isInteger(e.uid)) return;
|
||||
setDetail({ kind: 'piece', uid: e.uid });
|
||||
// a tap selects an own piece (underframe + range); right-click / long-press only opens its detail card. A tap on
|
||||
@@ -1404,7 +1404,7 @@ function MatchScreen() {
|
||||
return priv?.loadout ?? null; // own pieces, shop / reward / bond-member cards
|
||||
})();
|
||||
// the open detail card's unit draws its attack range on the field (screens/game/range.js inspectRange, GitHub PR #281):
|
||||
// an own board piece in prep (its loadout / stand-in record, its facing); in battle or on a scouted board an ally
|
||||
// an own board piece or read-only prepared map character in prep; in battle or on a scouted board an ally
|
||||
// operator / summon on the field — the range it fights with now (the live stats' `range` and `dir`, the snapshot's
|
||||
// tile) — never an enemy, a dead unit or a bench unit (UnitInfo `area` hand / temp); drag, the wheel and the pen
|
||||
// draw their own
|
||||
|
||||
@@ -13,15 +13,18 @@ export function inspectRange({ target, detail, pieces, showPrep = false, field,
|
||||
const e = pieces?.get(target.uid);
|
||||
if (!e || e.area !== 'board') return null;
|
||||
row = e.row; col = e.col; dir = normDir(e.piece.dir);
|
||||
} else if (target?.kind === 'unit' && !showPrep) {
|
||||
} else if (target?.kind === 'unit' && (!showPrep || target.prepMapChar === true)) {
|
||||
const unit = target.unit;
|
||||
if (!unit || unit.side !== 'ally' || (unit.kind !== 'op' && unit.kind !== 'token')) return null;
|
||||
if (unit.area != null && unit.area !== 'board') return null;
|
||||
// A prepared map character is a read-only view, not a simulated unit yet. Its board-space coordinates come from
|
||||
// m.private.prepMapChars and must not be read from a previous battle's snapshot or transformed twice on boss R.
|
||||
if (showPrep && (unit.kind !== 'token' || detail.type !== 'token' || detail.token?.kind !== 'mapChar')) return null;
|
||||
// Snapshots exclude undeployed, hidden and removed units. Dead units remain briefly for their death animation.
|
||||
if (!field?.prep && (!snapshot || snapshot[3] <= 0)) return null;
|
||||
row = Math.round(snapshot ? snapshot[2] : unit.y);
|
||||
col = Math.round(snapshot ? snapshot[1] : unit.x);
|
||||
dir = normDir(live?.dir, null) || normDir(unit.dir, null) || (unit.facing === -1 ? 'LEFT' : 'RIGHT');
|
||||
if (!showPrep && !field?.prep && (!snapshot || snapshot[3] <= 0)) return null;
|
||||
row = Math.round(!showPrep && snapshot ? snapshot[2] : unit.y);
|
||||
col = Math.round(!showPrep && snapshot ? snapshot[1] : unit.x);
|
||||
dir = (!showPrep && normDir(live?.dir, null)) || normDir(unit.dir, null) || (unit.facing === -1 ? 'LEFT' : 'RIGHT');
|
||||
} else return null;
|
||||
if (!Number.isFinite(row) || !Number.isFinite(col)) return null;
|
||||
const record = detail.type === 'token' ? { ...detail.token, ...tokenVariantFor(detail.token, detail.ownerId) }
|
||||
|
||||
@@ -361,7 +361,9 @@ export function createFallbackView(host, opts = {}) {
|
||||
const pos = tilePos(L, u.y, u.x);
|
||||
const src = tokenAvatarUrl(m(), u.defId);
|
||||
pieces.push(html`<div key=${`map:${u.uid}`} class="ff-piece ff-piece--token ff-piece--map-char"
|
||||
style=${`transform:translate(${pos.x}px,${pos.y}px);width:${L.tile}px;height:${L.tile}px;pointer-events:none`}
|
||||
style=${`transform:translate(${pos.x}px,${pos.y}px);width:${L.tile}px;height:${L.tile}px`}
|
||||
onPointerDown=${(e) => { if (e.button === 0) emit('pieceClick', { unit: u, unitId: null, uid: u.uid, prepMapChar: true, button: 0, clientX: e.clientX, clientY: e.clientY }); }}
|
||||
onContextMenu=${(e) => { e.preventDefault(); emit('pieceClick', { unit: u, unitId: null, uid: u.uid, prepMapChar: true, button: 2, detail: true, clientX: e.clientX, clientY: e.clientY }); }}
|
||||
title=${u.name || u.defId}>
|
||||
<div class="ff-piece__art">${src ? html`<img src=${src} alt="" draggable=${false} />` : html`<span>${[...(u.name || '?')][0]}</span>`}</div>
|
||||
</div>`);
|
||||
|
||||
Reference in new issue
Block a user