diff options
| author | Ash Ketchum <no-reply@sl0p.foo> | 2026-07-15 15:23:39 +0200 |
|---|---|---|
| committer | Ash Ketchum <no-reply@sl0p.foo> | 2026-07-15 15:23:39 +0200 |
| commit | eeb61bd9aa3ad514d685b82a635a8b7d4511339a (patch) | |
| tree | aa963e1dbb7d0bc503fc46756313b2b61a0ae9f0 | |
| parent | EXPLORE: animate the approach to map edges instead of hard-jumping (diff) | |
| download | pokeyellow-eeb61bd9aa3ad514d685b82a635a8b7d4511339a.tar.gz pokeyellow-eeb61bd9aa3ad514d685b82a635a8b7d4511339a.tar.xz pokeyellow-eeb61bd9aa3ad514d685b82a635a8b7d4511339a.zip | |
SL0P menu: add SAVER screensaver (autopilot EXPLORE)
New menu entry that auto-drives the EXPLORE camera across the overworld:
- persistent-direction wander (DFS-ish): keep heading, turn only when blocked
or occasionally at a junction, never an immediate U-turn. SlopSaverPickDir
synthesises a d-pad direction into the normal EXPLORE dispatch, so it inherits
smooth scrolling and smooth map-edge crossing.
- arrows hidden; player/Pikachu never drawn (clear+freeze OAM right after every
LoadMapData, before the first frame renders - also fixes a brief player flash
at crossings in manual EXPLORE).
- any button wakes it back to the menu.
State byte wSlopSaverDir reuses wUnusedCreditsByte (0=off, else current dir).
| -rw-r--r-- | SL0P.md | 25 | ||||
| -rw-r--r-- | engine/slop_menu.asm | 193 | ||||
| -rw-r--r-- | ram/wram.asm | 6 |
3 files changed, 215 insertions, 9 deletions
@@ -37,23 +37,36 @@ A table-driven cheat menu. Cursor + A to pick, B to back out / close. | **ITEMS** | submenu: give ×99 of balls, Rare Candy, Full Restore, Max Revive, Max Elixer, PP Up, and all five evolution stones | | **NOWILD** | toggle wild encounters off (shows `ON`) | | **REPEL** | refill repel steps | -| **EXPLORE** | a screen-paging overworld camera: each d-pad press instantly redraws the next screenful of the world (no walking), hopping to the connected map at each edge. **▲▼◀▶ arrows** show which directions you can move; **A** warps the player onto the tile you're looking at; **B** returns to the menu. | +| **EXPLORE** | a screen-paging overworld camera: each d-pad press smooth-scrolls the next screenful of the world (no walking), gliding across the connected map at each edge. **▲▼◀▶ arrows** show which directions you can move; **A** warps the player onto the tile you're looking at; **B** returns to the menu. | +| **SAVER** | screensaver: EXPLORE on autopilot. Drifts the camera across the overworld on its own (persistent direction, DFS-ish: keeps going, turns when blocked or occasionally at a junction). Arrows and the player/Pikachu sprites are hidden. Any button wakes it back to the menu. | Everything is in `engine/slop_menu.asm`. Adding a new entry = add a name to `SlopMenuText`, a `dw Handler` to `SlopMenuHandlers`, and bump `SLOP_MENU_COUNT`. Handlers return carry set (a warp/exit was started) or clear (back to the menu), and can be submenus or one-shot actions. -Persistent toggle flag: `wUnusedFlag` (NOWILD). +Persistent flags (reused unused WRAM): `wUnusedFlag` (NOWILD), +`wUnusedCreditsByte` aliased as `wSlopSaverDir` (screensaver: 0 = off, else the +current auto-scroll `PAD_*` direction). **EXPLORE** is a self-contained camera loop in `engine/slop_menu.asm`. It renders straight out of the map block buffer (`wOverworldMap`): compute a view pointer `wOverworldMap + (by+1)*stride + bx` (by/bx = player block, stride = width+6), `LoadCurrentMapView`, push to VRAM, OBJ off. A d-pad press moves the camera coords -by ~one screen (`SLOP_PAGE_X/Y`) and re-renders; at a map edge it crosses using -that connection's `*Alignment` offset (vertical: Y=align, X+=align; horizontal: -X=align, Y+=align) and `LoadMapData`s the neighbour, or clamps if there's none. -No walk engine, no character movement. +by ~one screen (`SLOP_PAGE_X/Y`). Movement is a real hardware smooth-scroll built +on the walk engine's own machinery: `SlopScroll{Up,Down,Left,Right}` ramp +`hSCX/hSCY` `SLOP_SCROLL_PX` px/frame, advance `wMapViewVRAMPointer`, and inject +the newly exposed edge with the `Schedule*Redraw` helpers (applied in VBlank). At +a map edge it smooth-scrolls the remaining distance up to the boundary, then +crosses using that connection's `*Alignment` offset and `LoadMapData`s the +neighbour (one unavoidable load), or stops if there's no connection. + +**SAVER** is the same loop with `wSlopSaverDir` set: instead of reading the +d-pad, `SlopSaverPickDir` synthesises a direction into the normal dispatch (so it +inherits smooth scroll + edge crossing), keeping a persistent heading and only +turning when blocked or occasionally at a junction (never an immediate U-turn). +Arrows are skipped, and OAM is cleared + frozen right after every `LoadMapData` +so the reloaded player/Pikachu are never drawn. Any real button exits. - **Direction arrows** are 4 OBJ sprites in `wShadowOAM` using the font's down/ right glyphs (`$ee`/`$ed`) plus CGB OBJ X/Y-flip for up/left; set diff --git a/engine/slop_menu.asm b/engine/slop_menu.asm index 36654111..09650150 100644 --- a/engine/slop_menu.asm +++ b/engine/slop_menu.asm @@ -8,7 +8,7 @@ ; To add an entry: add a "NAME" line to SlopMenuText, a `dw Handler` to ; SlopMenuHandlers, and bump SLOP_MENU_COUNT. -DEF SLOP_MENU_COUNT EQU 13 +DEF SLOP_MENU_COUNT EQU 14 SECTION "Slop Menu", ROMX @@ -121,7 +121,8 @@ SlopMenuText: next "ITEMS" next "NOWILD" next "REPEL" - next "EXPLORE@" + next "EXPLORE" + next "SAVER@" SlopOnText: db "ON@" SlopMenuHandlers: @@ -138,6 +139,7 @@ SlopMenuHandlers: dw SlopToggleNoWild dw SlopRepel dw SlopExplore + dw SlopSaver ; =========================================================================== @@ -230,7 +232,23 @@ DEF SLOP_PAGE_Y EQU 8 ; coords moved per vertical page DEF SLOP_SCROLL_PX EQU 8 DEF SLOP_SCROLL_FRAMES EQU 16 / SLOP_SCROLL_PX +SlopSaver: +; SCREENSAVER: auto-scroll the overworld. Seed an initial direction; the rest is +; identical to EXPLORE (shared body), driven by SlopSaverPickDir in the loop. + ld a, PAD_DOWN + ld [wSlopSaverDir], a + jr SlopExploreShared SlopExplore: + xor a + ld [wSlopSaverDir], a +SlopExploreShared: +; wait for the opening button to be released so it isn't read as EXPLORE input +.waitRelease + call DelayFrame + call Joypad ; VBlank only reads raw input; Joypad updates hJoyHeld + ldh a, [hJoyHeld] + and a + jr nz, .waitRelease ; save origin (map, coords, block coords, view pointer) to restore on exit ld a, [wYCoord] ld b, a @@ -251,12 +269,21 @@ SlopExplore: push af call LoadMapData ; reload current map + tileset (menu clobbered VRAM) .renderView +; LoadMapData (entry/cross) re-enables sprites and reloads the player + Pikachu +; follower. Freeze and clear OAM now, before the first frame renders, so they are +; never drawn during EXPLORE/screensaver. + ld a, $ff + ld [wUpdateSpritesEnabled], a + call ClearSprites call SlopExploreRenderBG ; fresh render to vBGMap0, hardware scroll reset .renderArrows call LoadFontTilePatterns ; arrow glyphs ($ed/$ee) into VRAM for the OBJ layer ; draw a direction-arrow sprite for each direction the camera can currently move: ; either there's more map that way, or the map has a connection that way. call ClearSprites + ld a, [wSlopSaverDir] + and a + jp nz, .arrowsDone ; screensaver hides the direction arrows ld hl, wShadowOAM ; up: movable unless at the top edge (wYCoord == 0) with no north connection ld a, [wYCoord] @@ -337,9 +364,20 @@ SlopExplore: ldh [hWY], a .input call DelayFrame + ld a, [wSlopSaverDir] + and a + jr nz, .saverInput call JoypadLowSensitivity ldh a, [hJoyPressed] ld d, a + jr .dispatch +.saverInput + call Joypad ; refresh hJoyHeld (VBlank only reads raw input) + ldh a, [hJoyHeld] ; any real button wakes/exits the screensaver + and a + jp nz, .exit + call SlopSaverPickDir ; d = auto-picked scroll direction +.dispatch bit B_PAD_B, d jp nz, .exit bit B_PAD_A, d @@ -825,6 +863,157 @@ SlopScrollUp: jr nz, .loop ret +; =========================================================================== +; SCREENSAVER direction logic: a persistent-direction wander. Keep drifting one +; way; only turn when the current direction gets blocked, or occasionally at a +; junction. Avoids immediate U-turns so it glides smoothly (DFS-ish) across the +; overworld instead of jittering. The chosen direction is fed into the normal +; EXPLORE dispatch as if the d-pad were pressed, so it inherits smooth scrolling +; and smooth map-edge crossing for free. +; =========================================================================== +SlopDirBits: + db PAD_UP, PAD_DOWN, PAD_LEFT, PAD_RIGHT + +; -> a = bitmask of currently-movable directions (same rule as the arrows) +SlopValidDirMask: + ld c, 0 + ld a, [wYCoord] + and a + jr nz, .up + ld a, [wNorthConnectedMap] + inc a + jr z, .noUp +.up + set B_PAD_UP, c +.noUp + ld a, [wCurrentMapHeight2] + dec a + ld b, a + ld a, [wYCoord] + cp b + jr nz, .down + ld a, [wSouthConnectedMap] + inc a + jr z, .noDown +.down + set B_PAD_DOWN, c +.noDown + ld a, [wXCoord] + and a + jr nz, .left + ld a, [wWestConnectedMap] + inc a + jr z, .noLeft +.left + set B_PAD_LEFT, c +.noLeft + ld a, [wCurrentMapWidth2] + dec a + ld b, a + ld a, [wXCoord] + cp b + jr nz, .right + ld a, [wEastConnectedMap] + inc a + jr z, .noRight +.right + set B_PAD_RIGHT, c +.noRight + ld a, c + ret + +; a = nonzero mask -> a = one set bit of it, chosen pseudo-randomly +SlopRandomBit: + ld b, a ; b = mask + ldh a, [hRandomSub] + and $03 + ld c, a ; random start index 0..3 + ld d, 4 +.loop + ld a, c + and $03 + ld hl, SlopDirBits + add l + ld l, a + ld a, 0 + adc h + ld h, a + ld a, [hl] + and b + ret nz ; this direction is in the mask -> use it + inc c + dec d + jr nz, .loop + ld a, b ; fallback (a nonzero mask always hits above) + ret + +; a = dir bit -> a = the opposite direction bit +SlopRevDir: + cp PAD_UP + jr nz, .notUp + ld a, PAD_DOWN + ret +.notUp + cp PAD_DOWN + jr nz, .notDown + ld a, PAD_UP + ret +.notDown + cp PAD_LEFT + jr nz, .notLeft + ld a, PAD_RIGHT + ret +.notLeft + ld a, PAD_LEFT ; was RIGHT + ret + +; a = dir bit -> a = mask of the two perpendicular directions +SlopPerpMask: + and PAD_UP | PAD_DOWN + ld a, PAD_LEFT | PAD_RIGHT ; vertical dir -> horizontal perpendicular + ret nz + ld a, PAD_UP | PAD_DOWN + ret + +; pick the next screensaver direction; store in wSlopSaverDir, return it in d +SlopSaverPickDir: + call SlopValidDirMask + ld e, a ; e = valid-direction mask + and a + jr z, .keepCurrent ; nothing valid (shouldn't happen) + ld a, [wSlopSaverDir] + ld b, a ; b = current direction + and e + jr z, .blocked ; current direction no longer valid -> turn +; current direction still valid: mostly keep going, occasionally turn + ldh a, [hRandomAdd] + and $07 + jr nz, .keepCurrent ; 7/8: carry straight on + ld a, b + call SlopPerpMask + and e ; valid perpendicular directions + jr z, .keepCurrent ; none -> keep going + call SlopRandomBit + jr .store +.blocked + ld a, b + call SlopRevDir + cpl + and e ; valid directions excluding a U-turn + jr z, .onlyReverse + call SlopRandomBit + jr .store +.onlyReverse + ld a, e ; only a U-turn is available + call SlopRandomBit + jr .store +.keepCurrent + ld a, [wSlopSaverDir] +.store + ld [wSlopSaverDir], a + ld d, a + ret + ; (dy, dx) coord offsets scanned outward from the centre for a walkable tile SlopWarpOffsets: db 0, 0 diff --git a/ram/wram.asm b/ram/wram.asm index e0488e62..0e530a77 100644 --- a/ram/wram.asm +++ b/ram/wram.asm @@ -971,7 +971,11 @@ wBadgeOrFaceTiles:: ds NUM_BADGES + 1 wTempObtainedBadgesBooleans:: ds NUM_BADGES NEXTU -wUnusedCreditsByte:: db +wUnusedCreditsByte:: +; SL0P: EXPLORE screensaver state. 0 = normal EXPLORE (manual). Nonzero = the +; screensaver is running and the value is the current auto-scroll direction +; (a PAD_* bit). Overlaps a credits-only scratch byte, unused during overworld. +wSlopSaverDir:: db ; the number of credits mons that have been displayed so far wNumCreditsMonsDisplayed:: db |
