explodorama / commit
Controller: the cursor stays off during offers; a waking press only shows it (0.2.0 work)
README.md | 3 ++- src/main.ts | 23 +++++++++++++++++++---- src/ui/padTiles.ts | 6 +++--- test/padTiles.test.ts | 3 ++- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 0eb3259..a980888 100644 --- a/README.md +++ b/README.md @@ -524,7 +524,8 @@ own tile. A small legend above the line shows the L1/LB and R1/RB pictures and w between those tiles, and Cross (or RB again) explains the tile; LB leaves it. **Start** opens the menu (and closes it), **R3** brings the view back to you, and the **right stick** looks around the board. The left stick does nothing for now. A key or the mouse hands the board back to the keyboard -and mouse. +and mouse; the next pad press brings the cursor back on your tile (a face button pressed then only +shows it, it does not act). Dialogs and the screens after a death, a level or a run are played by pad too, so a whole run needs no keyboard or mouse. A dialog's buttons sit on the face buttons, with their pictures on them: the diff --git a/src/main.ts b/src/main.ts index 30a16af..1f07103 100644 --- a/src/main.ts +++ b/src/main.ts @@ -959,6 +959,8 @@ const LOOK_TILES_PER_SECOND = 9 /** The controller is what the player is using now (body.pad); a mouse or key takes it back. */ let padActive = false +/** This pad input is the one that woke the pad (a key or the mouse had had the board): a face button then only shows the cursor. */ +let padWoke = false /** The demo changes scene at most this often from the pad: a held stick repeats faster than a scene can be made. */ const PAD_SCENE_MS = 400 let lastPadScene = -Infinity @@ -1001,7 +1003,7 @@ function refreshCursor(): void { * run, and after a dialog), and gone otherwise. Called every frame, and by the pad's handlers. */ function ensureCursor(): void { - const wanted = cursorWanted({ padActive, demo: !!demo, dialogOpen: dialogOpen(), inspecting, status: state.status }) + const wanted = cursorWanted({ padActive, demo: !!demo, dialogOpen: dialogOpen(), inspecting, offerPending: !!(state.offer || state.chargeOffer), status: state.status }) if (!wanted) cursor = null else if (!cursor) setCursor(restingCursor(state)) } @@ -1060,8 +1062,12 @@ function aimShown(): boolean { let lastBarKey = '' let lastNote = '' let lastHintKey = '' +/** The label's height plus its gap, for the hint above it; -1 = to be measured. */ +let padLabelHeight = -1 +addEventListener('resize', () => (padLabelHeight = -1)) const pad = createPadInput({ active() { + padWoke = !padActive padActive = true document.body.classList.add('pad') }, @@ -1122,8 +1128,12 @@ const pad = createPadInput({ // Cross explains the aimed tile while "What is that?" is on; the other face buttons do nothing then. if (inspecting) { if (face === 0 && aim?.target) tapTile(aim.target) - } else if (!cursor) ensureCursor() // the pad just woke (a key or mouse had put the cursor away): show it, do nothing - else padDo(FACE_TO_BUTTON[face]) + } else { + // The press that woke the pad (a key or mouse had put the cursor away) only shows the cursor. + const woke = padWoke + ensureCursor() + if (!woke) padDo(FACE_TO_BUTTON[face]) + } } }, look(dx, dy) { @@ -1948,6 +1958,7 @@ await engineInit( } if (lastNote !== note) { lastNote = note + padLabelHeight = -1 // The cursor's lines each start with the button's own picture; the inspect aim's is plain text. if (shownCursor) { padLabel.replaceChildren( @@ -1990,7 +2001,11 @@ await engineInit( padHint.replaceChildren(item('l', l1.text, l1.dim), item('r', 'What is that?', false)) } } - if (hintKey) padHint.style.bottom = `${controls.insets().bottom + 8 + (padLabel.hidden ? 0 : padLabel.offsetHeight + 6)}px` + if (hintKey) { + // The label's height is measured when its text changes or the window does, not every frame. + if (padLabelHeight < 0) padLabelHeight = padLabel.hidden ? 0 : padLabel.offsetHeight + 6 + padHint.style.bottom = `${controls.insets().bottom + 8 + padLabelHeight}px` + } snapCamera = false pruneAnimation(anim, time) autoFinish() diff --git a/src/ui/padTiles.ts b/src/ui/padTiles.ts index d2ab99b..88ba53d 100644 --- a/src/ui/padTiles.ts +++ b/src/ui/padTiles.ts @@ -72,10 +72,10 @@ export function restingCursor(s: GameState): Vec { /** * Whether the cursor is on screen: always, while the pad is in use and the board is being - * played (no dialog, not the demo, not "What is that?", the run not between levels or over). + * played (no dialog, not the demo, not "What is that?", no gem or charge offer waiting for its dialog, the run not between levels or over). */ -export function cursorWanted(c: { padActive: boolean; demo: boolean; dialogOpen: boolean; inspecting: boolean; status: GameState['status'] }): boolean { - return c.padActive && !c.demo && !c.dialogOpen && !c.inspecting && c.status === 'playing' +export function cursorWanted(c: { padActive: boolean; demo: boolean; dialogOpen: boolean; inspecting: boolean; offerPending: boolean; status: GameState['status'] }): boolean { + return c.padActive && !c.demo && !c.dialogOpen && !c.inspecting && !c.offerPending && c.status === 'playing' } /** The cursor moved by the d-pad: the best actionable tile that way, or where it is if none. */ diff --git a/test/padTiles.test.ts b/test/padTiles.test.ts index 4089e9e..1f1d8ed 100644 --- a/test/padTiles.test.ts +++ b/test/padTiles.test.ts @@ -95,12 +95,13 @@ describe('the cursor', () => { expect(moveCursor([v(3, 3)], v(3, 3), 1, 0)).toEqual(v(3, 3)) }) it('is wanted only while a pad plays the board', () => { - const ok = { padActive: true, demo: false, dialogOpen: false, inspecting: false, status: 'playing' as const } + const ok = { padActive: true, demo: false, dialogOpen: false, inspecting: false, offerPending: false, status: 'playing' as const } expect(cursorWanted(ok)).toBe(true) expect(cursorWanted({ ...ok, padActive: false })).toBe(false) expect(cursorWanted({ ...ok, demo: true })).toBe(false) expect(cursorWanted({ ...ok, dialogOpen: true })).toBe(false) expect(cursorWanted({ ...ok, inspecting: true })).toBe(false) + expect(cursorWanted({ ...ok, offerPending: true })).toBe(false) for (const status of ['dead', 'cleared', 'gameOver', 'won'] as const) expect(cursorWanted({ ...ok, status })).toBe(false) }) it('moves between actionable tiles, staying put at the edge', () => {