aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--SL0P.md25
-rw-r--r--engine/slop_menu.asm193
-rw-r--r--ram/wram.asm6
3 files changed, 215 insertions, 9 deletions
diff --git a/SL0P.md b/SL0P.md
index be3de2b2..28c2df16 100644
--- a/SL0P.md
+++ b/SL0P.md
@@ -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