; SL0P MENU - a top-level cheat menu opened with SELECT in the overworld ; (hooked in home/overworld.asm). Table-driven: each entry has a name in ; SlopMenuText and a handler in SlopMenuHandlers. A handler returns: ; carry SET -> a map warp was started; exit the menu and let it proceed ; carry CLEAR -> return to the SL0P menu (redraw) ; Handlers may be submenus (WarpSubmenu, GiveItemSubmenu) or direct actions. ; ; 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 14 SECTION "Slop Menu", ROMX SlopMenu:: ; ---- open the menu display (shared by the top menu and every submenu) ---- ldh a, [hUILayoutFlags] set BIT_SINGLE_SPACED_LINES, a set BIT_DOUBLE_SPACED_MENU, a ldh [hUILayoutFlags], a ld a, $ff ld [wUpdateSpritesEnabled], a call ClearSprites .redraw call ClearScreen hlcoord 5, 0 ld de, SlopMenuTitle call PlaceString hlcoord 4, 2 lb bc, SLOP_MENU_COUNT + 2, 11 call TextBoxBorder hlcoord 6, 3 ld de, SlopMenuText call PlaceString ; ON marker for the NOWILD toggle (item 10 -> row 13) ld a, [wUnusedFlag] and a jr z, .nowildOff hlcoord 13, 13 ld de, SlopOnText call PlaceString .nowildOff call SlopMenuShow xor a ld [wCurrentMenuItem], a ld [wLastMenuItem], a ld [wMenuWatchMovingOutOfBounds], a ld [wMenuJoypadPollCount], a ld a, 5 ld [wTopMenuItemX], a ld a, 3 ld [wTopMenuItemY], a ld a, PAD_A | PAD_B ld [wMenuWatchedKeys], a ld a, SLOP_MENU_COUNT - 1 ld [wMaxMenuItem], a call HandleMenuInput bit B_PAD_B, a jr nz, .close ; dispatch: call SlopMenuHandlers[wCurrentMenuItem] ld a, [wCurrentMenuItem] add a ld e, a ld d, 0 ld hl, SlopMenuHandlers add hl, de ld a, [hli] ld h, [hl] ld l, a ld de, .afterHandler push de jp hl .afterHandler jr c, .exitProceed jr .redraw .exitProceed call SlopMenuRestoreFlags ret .close call SlopMenuRestoreFlags ld a, $01 ld [wUpdateSpritesEnabled], a ; CloseTextDisplay rebuilds the overworld (LoadCurrentMapView restores the ; collision map) and ends with `pop af` + bankswitch expecting the caller's bank ; to have been pushed. We bypass DisplayTextID, so push it ourselves - otherwise ; the pop eats a stack word and corrupts the bank/collision state. ldh a, [hLoadedROMBank] push af jp CloseTextDisplay SlopMenuRestoreFlags: ldh a, [hUILayoutFlags] res BIT_SINGLE_SPACED_LINES, a res BIT_DOUBLE_SPACED_MENU, a ldh [hUILayoutFlags], a ret ; push wTileMap into VRAM and switch on the menu window display SlopMenuShow: ld b, HIGH(vBGMap1) call CopyScreenTileBufferToVRAM xor a ldh [hWY], a call LoadFontTilePatterns ld a, $01 ldh [hAutoBGTransferEnabled], a ret SlopMenuTitle: db "SL0P MENU@" SlopMenuText: db "WARP" next "HEAL" next "MONEY" next "COINS" next "BADGES" next "DEX" next "MAXTEAM" next "TEACH" next "HMS" next "ITEMS" next "NOWILD" next "REPEL" next "EXPLORE" next "SAVER@" SlopOnText: db "ON@" SlopMenuHandlers: dw WarpSubmenu dw SlopHealParty dw SlopMaxCash dw SlopMaxCoins dw SlopAllBadges dw SlopDex dw SlopMaxTeam dw SlopTeach dw SlopGiveHMs dw GiveItemSubmenu dw SlopToggleNoWild dw SlopRepel dw SlopExplore dw SlopSaver ; =========================================================================== ; WARP submenu ; =========================================================================== DEF WARP_COUNT EQU 13 WarpSubmenu: call ClearScreen hlcoord 4, 0 ld de, WarpTitleText1 call PlaceString hlcoord 5, 1 ld de, WarpTitleText2 call PlaceString hlcoord 3, 2 lb bc, 13, 11 call TextBoxBorder hlcoord 5, 3 ld de, WarpMenuText call PlaceString call SlopMenuShow xor a ld [wCurrentMenuItem], a ld [wLastMenuItem], a ld [wMenuWatchMovingOutOfBounds], a ld [wMenuJoypadPollCount], a ld a, 4 ld [wTopMenuItemX], a ld a, 3 ld [wTopMenuItemY], a ld a, PAD_A | PAD_B ld [wMenuWatchedKeys], a ld a, WARP_COUNT - 1 ld [wMaxMenuItem], a call HandleMenuInput bit B_PAD_B, a jr nz, .cancel ld a, [wCurrentMenuItem] ld e, a ld d, 0 ld hl, WarpMapTable add hl, de ld a, [hl] ld [wDestinationMap], a ld hl, wStatusFlags6 set BIT_FLY_WARP, [hl] scf ret .cancel and a ret WarpTitleText1: db "SECRET SL0P@" WarpTitleText2: db "WARP MENU@" WarpMenuText: db "PALLET" next "VIRIDIAN" next "PEWTER" next "CERULEAN" next "LAVENDER" next "VERMILION" next "CELADON" next "FUCHSIA" next "CINNABAR" next "INDIGO" next "SAFFRON" next "ROUTE 4" next "ROUTE 10@" WarpMapTable: db PALLET_TOWN, VIRIDIAN_CITY, PEWTER_CITY, CERULEAN_CITY, LAVENDER_TOWN db VERMILION_CITY, CELADON_CITY, FUCHSIA_CITY, CINNABAR_ISLAND, INDIGO_PLATEAU db SAFFRON_CITY, ROUTE_4, ROUTE_10 ; =========================================================================== ; EXPLORE: a screen-paging overworld camera. Each d-pad press INSTANTLY redraws ; the next screenful of the world -- no character walking. At a map edge it hops ; to the connected map using that connection's alignment offset (or clamps if ; there's none). B restores the origin view and returns to the SL0P menu. It is ; self-contained: it renders straight from the map block buffer (wOverworldMap) ; and never touches the walk engine. ; =========================================================================== DEF SLOP_PAGE_X EQU 10 ; coords moved per horizontal page (~one screen) DEF SLOP_PAGE_Y EQU 8 ; coords moved per vertical page ; smooth-scroll animation speed. SLOP_SCROLL_PX must divide 16 (one coord-step ; is 16 px). Bigger = faster/steppier, smaller = slower/smoother. 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 ld a, [wXCoord] ld c, a push bc ld a, [wYBlockCoord] ld b, a ld a, [wXBlockCoord] ld c, a push bc ld a, [wCurrentTileBlockMapViewPointer] ld b, a ld a, [wCurrentTileBlockMapViewPointer + 1] ld c, a push bc ld a, [wCurMap] 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 SlopClampCoords ; a cross can land outside the new map -> clamp 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] and a jr nz, .putUp ld a, [wNorthConnectedMap] inc a jr z, .skipUp .putUp ld de, .arrUp call .putArrow .skipUp ; down: movable unless at the bottom edge with no south connection ld a, [wCurrentMapHeight2] dec a ld b, a ld a, [wYCoord] cp b jr nz, .putDown ld a, [wSouthConnectedMap] inc a jr z, .skipDown .putDown ld de, .arrDown call .putArrow .skipDown ; left: movable unless at the left edge with no west connection ld a, [wXCoord] and a jr nz, .putLeft ld a, [wWestConnectedMap] inc a jr z, .skipLeft .putLeft ld de, .arrLeft call .putArrow .skipLeft ; right: movable unless at the right edge with no east connection ld a, [wCurrentMapWidth2] dec a ld b, a ld a, [wXCoord] cp b jr nz, .putRight ld a, [wEastConnectedMap] inc a jr z, .skipRight .putRight ld de, .arrRight call .putArrow .skipRight jr .arrowsDone .putArrow ; de -> Y, X, tile, attr; write to [hl], advance hl ld a, [de] inc de ld [hli], a ld a, [de] inc de ld [hli], a ld a, [de] inc de ld [hli], a ld a, [de] ld [hli], a ret .arrUp db 18, 80, $ee, $40 ; down-arrow glyph, Y-flipped -> up .arrDown db 144, 80, $ee, $00 .arrLeft db 80, 12, $ed, $20 ; right-arrow glyph, X-flipped -> left .arrRight db 80, 156, $ed, $00 .arrowsDone ld a, $ff ld [wUpdateSpritesEnabled], a ; freeze shadow OAM so PrepareOAMData keeps our arrows ldh a, [rLCDC] res B_LCDC_OBJ_SIZE, a ; 8x8 sprites -> single-tile arrows set B_LCDC_OBJS, a ; show the arrow sprites ldh [rLCDC], a ld a, $90 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 jp nz, .warp bit B_PAD_UP, d jr z, .ckDown ld a, [wYCoord] sub SLOP_PAGE_Y jp nc, .moveUp jp .edgeUp .ckDown bit B_PAD_DOWN, d jr z, .ckLeft ld a, [wCurrentMapHeight2] dec a ld b, a ; b = max Y ld a, [wYCoord] add SLOP_PAGE_Y cp b jp c, .moveDown jp z, .moveDown jp .edgeDown .ckLeft bit B_PAD_LEFT, d jr z, .ckRight ld a, [wXCoord] sub SLOP_PAGE_X jp nc, .moveLeft jp .edgeLeft .ckRight bit B_PAD_RIGHT, d jp z, .input ld a, [wCurrentMapWidth2] dec a ld b, a ; b = max X ld a, [wXCoord] add SLOP_PAGE_X cp b jp c, .moveRight jp z, .moveRight jp .edgeRight ; in-map page moves: smooth-scroll one screen, then just redraw the arrows ; (the scroll already left the destination view on screen in rotated VRAM). .moveUp ld c, SLOP_PAGE_Y call SlopScrollUp jp .renderArrows .moveDown ld c, SLOP_PAGE_Y call SlopScrollDown jp .renderArrows .moveLeft ld c, SLOP_PAGE_X call SlopScrollLeft jp .renderArrows .moveRight ld c, SLOP_PAGE_X call SlopScrollRight jp .renderArrows ; ---- map-edge handling ---- ; A full page won't fit before the edge, so smooth-scroll the remaining (even) ; distance right up to the edge, then either cross into the connected map (one ; unavoidable instant load) or, if there's no connection that way, just stop at ; the edge. This keeps the motion smooth instead of hard-jumping a whole screen. .edgeUp ld a, [wYCoord] and $fe ; even coords remaining to the top edge jr z, .edgeUpCross ld c, a call SlopScrollUp .edgeUpCross ld hl, wNorthConnectedMap ld de, wNorthConnectedMapYAlignment jp .crossV .edgeDown ld a, [wCurrentMapHeight2] dec a ; max Y ld hl, wYCoord sub [hl] ; maxY - wYCoord and $fe jr z, .edgeDownCross ld c, a call SlopScrollDown .edgeDownCross ld hl, wSouthConnectedMap ld de, wSouthConnectedMapYAlignment jp .crossV .edgeLeft ld a, [wXCoord] and $fe jr z, .edgeLeftCross ld c, a call SlopScrollLeft .edgeLeftCross ld hl, wWestConnectedMap ld de, wWestConnectedMapYAlignment jp .crossH .edgeRight ld a, [wCurrentMapWidth2] dec a ; max X ld hl, wXCoord sub [hl] ; maxX - wXCoord and $fe jr z, .edgeRightCross ld c, a call SlopScrollRight .edgeRightCross ld hl, wEastConnectedMap ld de, wEastConnectedMapYAlignment jp .crossH ; vertical cross: hl -> connected-map byte, de -> its YAlignment. With no ; connection we're already scrolled to the edge, so just redraw the arrows. .crossV ld a, [hl] inc a jr z, .clampArrows ; $ff -> no connection ld a, [de] ; YAlignment -> new Y (absolute) ld c, a inc de ld a, [de] ; XAlignment -> added to X ld b, a ld a, [wXCoord] add b ld [wXCoord], a ld a, c ld [wYCoord], a ld a, [hl] ld [wCurMap], a call LoadMapData jp .renderView ; horizontal cross: hl -> connected-map byte, de -> its YAlignment. .crossH ld a, [hl] inc a jr z, .clampArrows ld a, [de] ; YAlignment -> added to Y ld c, a inc de ld a, [de] ; XAlignment -> new X (absolute) ld [wXCoord], a ld a, [wYCoord] add c ld [wYCoord], a ld a, [hl] ld [wCurMap], a call LoadMapData jp .renderView .clampArrows jp .renderArrows ; ---- A: warp the player to the walkable tile nearest the screen centre ---- ; the centre tile is wTileMap(col 8,row 9) = coord (wYCoord,wXCoord); a coord ; offset (dy,dx) maps to wTileMap(8+2dx, 9+2dy). Scan outward for a passable tile. .warp ld hl, SlopWarpOffsets .warpScan ld a, [hli] cp $80 jr z, .warpGo ; nothing walkable found -> land on the centre ld d, a ; d = dy ld a, [hli] ld e, a ; e = dx push hl ld a, d add a add 9 ; row = 9 + 2*dy ld b, a ld hl, wTileMap .warpRow ld a, b and a jr z, .warpCol ld a, 20 add l ld l, a jr nc, .warpRowNC inc h .warpRowNC dec b jr .warpRow .warpCol ld a, e add a add 8 ; col = 8 + 2*dx add l ld l, a jr nc, .warpColNC inc h .warpColNC ld a, [hl] ld c, a call IsTilePassable ; carry clear -> walkable pop hl jr c, .warpScan ld a, [wYCoord] add d ld [wYCoord], a ld a, [wXCoord] add e ld [wXCoord], a .warpGo ld a, $ff ld [wDestinationWarpID], a ; enter at wYCoord/wXCoord (not a warp position) ld a, [wYCoord] and 1 ld [wYBlockCoord], a ld a, [wXCoord] and 1 ld [wXBlockCoord], a call SlopSetViewPtr call LoadMapData ; enter the map (player sprite, wild data, warps) pop af ; drop the 4 saved-origin values (we're staying here) pop bc pop bc pop bc scf ; carry set -> close menu, resume overworld here ret .exit pop af ld [wCurMap], a pop bc ld a, b ld [wCurrentTileBlockMapViewPointer], a ld a, c ld [wCurrentTileBlockMapViewPointer + 1], a pop bc ld a, b ld [wYBlockCoord], a ld a, c ld [wXBlockCoord], a pop bc ld a, b ld [wYCoord], a ld a, c ld [wXCoord], a call LoadMapData ; restore origin view (renders from restored pointer) and a ; carry clear -> back to SL0P menu ret ; Clamp wYCoord to [0,maxY] and wXCoord to [0,maxX] of the current map. Crossing a ; connection applies its alignment offset blindly, so at an edge position the ; connection doesn't span the camera can land outside the neighbour; rendering ; that reads past wOverworldMap into other WRAM = on-screen garbage. SlopClampCoords: ld a, [wCurrentMapHeight2] dec a ld b, a ; maxY ld a, [wYCoord] call .clamp ld [wYCoord], a ld a, [wCurrentMapWidth2] dec a ld b, a ; maxX ld a, [wXCoord] call .clamp ld [wXCoord], a ret ; a = coord, b = max -> a clamped to [0,max]. Out-of-range values >= $80 are ; treated as "past the near edge" (negative) and clamped to 0, otherwise to max. .clamp cp b ret c ret z bit 7, a jr nz, .zero ld a, b ret .zero xor a ret ; wCurrentTileBlockMapViewPointer = wOverworldMap + (wYCoord/2 + 1)*stride + wXCoord/2 SlopSetViewPtr: ld a, [wCurMapWidth] add MAP_BORDER * 2 ld c, a ld b, 0 ld a, [wYCoord] srl a inc a ld hl, 0 .loop add hl, bc dec a jr nz, .loop ld a, [wXCoord] srl a add l ld l, a jr nc, .nc inc h .nc ld bc, wOverworldMap add hl, bc ld a, l ld [wCurrentTileBlockMapViewPointer], a ld a, h ld [wCurrentTileBlockMapViewPointer + 1], a ret ; render the current view freshly to vBGMap0 and reset the hardware scroll so ; the camera is block-aligned at SCX/SCY = 0. Used on entry, map-cross and ; clamp (i.e. everywhere except the animated in-map page moves). SlopExploreRenderBG: xor a ld [wYBlockCoord], a ; block-aligned camera centred on wYCoord/wXCoord ld [wXBlockCoord], a ldh [hAutoBGTransferEnabled], a ; use RedrawRowOrColumn path, not AutoBgMapTransfer ld [wMapViewVRAMPointer], a ldh [hSCX], a ldh [hSCY], a ld a, HIGH(vBGMap0) ld [wMapViewVRAMPointer + 1], a call SlopSetViewPtr call LoadCurrentMapView ld b, HIGH(vBGMap0) jp CopyScreenTileBufferToVRAM ; --------------------------------------------------------------------------- ; Smooth EXPLORE scrolling. Each routine pans the camera `c` coords in one ; direction, 16 px per coord-step, reusing the walking engine's rotating-VRAM ; machinery: advance wMapViewVRAMPointer, re-render wTileMap for the new ; position, inject the freshly exposed 2-tile edge with a Schedule*Redraw ; (applied by RedrawRowOrColumn in VBlank), and ramp hSCX/hSCY SLOP_SCROLL_PX ; px per frame. After an even coord count it ends block-aligned exactly where ; SlopExploreRenderBG would have placed the view, so wTileMap / the block ; pointer / arrow logic / warp scan are all consistent afterwards. ; --------------------------------------------------------------------------- SlopScrollRight: xor a ldh [hAutoBGTransferEnabled], a .loop push bc ld a, [wXCoord] inc a ld [wXCoord], a ld a, [wMapViewVRAMPointer] ; VRAM col +2 (wrap within the 32-tile row) ld e, a and $e0 ld d, a ld a, e add 2 and $1f or d ld [wMapViewVRAMPointer], a ld hl, wXBlockCoord inc [hl] ld a, [hl] cp 2 jr nz, .go ld [hl], 0 ; crossed a block east -> advance block pointer ld hl, wCurrentTileBlockMapViewPointer inc [hl] jr nz, .go inc hl inc [hl] .go call LoadCurrentMapView call ScheduleEastColumnRedraw ld c, SLOP_SCROLL_FRAMES .frames ldh a, [hSCX] add SLOP_SCROLL_PX ldh [hSCX], a call DelayFrame dec c jr nz, .frames pop bc dec c jr nz, .loop ret SlopScrollLeft: xor a ldh [hAutoBGTransferEnabled], a .loop push bc ld a, [wXCoord] dec a ld [wXCoord], a ld a, [wMapViewVRAMPointer] ; VRAM col -2 ld e, a and $e0 ld d, a ld a, e sub 2 and $1f or d ld [wMapViewVRAMPointer], a ld hl, wXBlockCoord dec [hl] ld a, [hl] inc a jr nz, .go ; was 0 -> now $ff: crossed a block west ld [hl], 1 ld hl, wCurrentTileBlockMapViewPointer ld a, [hl] sub 1 ld [hl], a jr nc, .go inc hl dec [hl] .go call LoadCurrentMapView call ScheduleWestColumnRedraw ld c, SLOP_SCROLL_FRAMES .frames ldh a, [hSCX] sub SLOP_SCROLL_PX ldh [hSCX], a call DelayFrame dec c jr nz, .frames pop bc dec c jr nz, .loop ret SlopScrollDown: xor a ldh [hAutoBGTransferEnabled], a .loop push bc ld a, [wYCoord] inc a ld [wYCoord], a ld a, [wMapViewVRAMPointer] ; VRAM row +1 (=+$40), wrap high byte into vBGMap0 add $40 ld [wMapViewVRAMPointer], a jr nc, .vramDone ld a, [wMapViewVRAMPointer + 1] inc a and $03 or $98 ld [wMapViewVRAMPointer + 1], a .vramDone ld hl, wYBlockCoord inc [hl] ld a, [hl] cp 2 jr nz, .go ld [hl], 0 ; crossed a block south -> advance block pointer ld a, [wCurMapWidth] add MAP_BORDER * 2 ld b, a ld hl, wCurrentTileBlockMapViewPointer ld a, [hl] add b ld [hl], a jr nc, .go inc hl inc [hl] .go call LoadCurrentMapView call ScheduleSouthRowRedraw ld c, SLOP_SCROLL_FRAMES .frames ldh a, [hSCY] add SLOP_SCROLL_PX ldh [hSCY], a call DelayFrame dec c jr nz, .frames pop bc dec c jr nz, .loop ret SlopScrollUp: xor a ldh [hAutoBGTransferEnabled], a .loop push bc ld a, [wYCoord] dec a ld [wYCoord], a ld a, [wMapViewVRAMPointer] ; VRAM row -1 (=-$40) sub $40 ld [wMapViewVRAMPointer], a jr nc, .vramDone ld a, [wMapViewVRAMPointer + 1] dec a and $03 or $98 ld [wMapViewVRAMPointer + 1], a .vramDone ld hl, wYBlockCoord dec [hl] ld a, [hl] inc a jr nz, .go ; was 0 -> now $ff: crossed a block north ld [hl], 1 ld a, [wCurMapWidth] add MAP_BORDER * 2 ld b, a ld hl, wCurrentTileBlockMapViewPointer ld a, [hl] sub b ld [hl], a jr nc, .go inc hl dec [hl] .go call LoadCurrentMapView call ScheduleNorthRowRedraw ld c, SLOP_SCROLL_FRAMES .frames ldh a, [hSCY] sub SLOP_SCROLL_PX ldh [hSCY], a call DelayFrame dec c jr nz, .frames pop bc dec c 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 directions the screensaver can actually make progress in. ; A direction is open if there are >= 2 coords of room that way (enough for one ; even scroll step) OR a map connection to cross into. The >= 2 test matters ; because moves round down to even coords, and a map's far edge (maxY/maxX = ; wCurrentMap*2 - 1) is always odd -- so "coord != max" would never register the ; edge as blocked and the wander would freeze one step short of it. SlopValidDirMask: ld c, 0 ; up: room upward = wYCoord ld a, [wYCoord] cp 2 jr nc, .up ld a, [wNorthConnectedMap] inc a jr z, .noUp .up set B_PAD_UP, c .noUp ; down: room downward = maxY - wYCoord ld a, [wCurrentMapHeight2] dec a ld b, a ; b = maxY ld a, [wYCoord] ld d, a ld a, b sub d ; a = maxY - wYCoord cp 2 jr nc, .down ld a, [wSouthConnectedMap] inc a jr z, .noDown .down set B_PAD_DOWN, c .noDown ; left: room leftward = wXCoord ld a, [wXCoord] cp 2 jr nc, .left ld a, [wWestConnectedMap] inc a jr z, .noLeft .left set B_PAD_LEFT, c .noLeft ; right: room rightward = maxX - wXCoord ld a, [wCurrentMapWidth2] dec a ld b, a ; b = maxX ld a, [wXCoord] ld d, a ld a, b sub d ; a = maxX - wXCoord cp 2 jr nc, .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 ; pick the next screensaver direction; store in wSlopSaverDir, return it in d. ; Strategy: keep heading the same way until that direction gets blocked, then ; turn onto a random still-open direction, preferring not to double back. 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 nz, .keepCurrent ; still valid -> carry straight on ; blocked at a bound: turn onto another open direction (avoid a U-turn if we can) ld a, b call SlopRevDir cpl and e ; valid directions excluding a U-turn jr nz, .turn ld a, e ; only a U-turn is available .turn 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 db -1, 0, 1, 0, 0,-1, 0, 1 db -1,-1, -1, 1, 1,-1, 1, 1 db -2, 0, 2, 0, 0,-2, 0, 2 db -2,-1, -2, 1, 2,-1, 2, 1, -1,-2, -1, 2, 1,-2, 1, 2 db -3, 0, 3, 0, 0,-3, 0, 3 db $80 ; =========================================================================== ; ITEMS submenu: give 99 of the chosen item (stacks via GiveItem) ; =========================================================================== DEF GIVE_COUNT EQU 12 GiveItemSubmenu: call ClearScreen hlcoord 6, 0 ld de, GiveTitleText call PlaceString hlcoord 1, 1 lb bc, GIVE_COUNT + 2, 16 call TextBoxBorder hlcoord 3, 2 ld de, GiveItemText call PlaceString call SlopMenuShow xor a ld [wCurrentMenuItem], a ld [wLastMenuItem], a ld [wMenuWatchMovingOutOfBounds], a ld [wMenuJoypadPollCount], a ld a, 2 ld [wTopMenuItemX], a ld [wTopMenuItemY], a ld a, PAD_A | PAD_B ld [wMenuWatchedKeys], a ld a, GIVE_COUNT - 1 ld [wMaxMenuItem], a call HandleMenuInput bit B_PAD_B, a jr nz, .back ld a, [wCurrentMenuItem] ld e, a ld d, 0 ld hl, GiveItemTable add hl, de ld b, [hl] ld c, 99 call GiveItem .back and a ret GiveTitleText: db "GIVE x99@" GiveItemText: db "MASTER BALL" next "ULTRA BALL" next "RARE CANDY" next "FULL RESTORE" next "MAX REVIVE" next "MAX ELIXER" next "PP UP" next "MOON STONE" next "FIRE STONE" next "THUNDERSTONE" next "WATER STONE" next "LEAF STONE@" GiveItemTable: db MASTER_BALL, ULTRA_BALL, RARE_CANDY, FULL_RESTORE db MAX_REVIVE, MAX_ELIXER, PP_UP, MOON_STONE db FIRE_STONE, THUNDER_STONE, WATER_STONE, LEAF_STONE ; =========================================================================== ; Direct-action cheats (return carry clear -> back to the SL0P menu) ; =========================================================================== ; HEAL: full HP + clear status for the whole party. SlopHealParty: ld a, [wPartyCount] and a jr z, .done ld c, a ld hl, wPartyMon1 .loop push hl ld de, $04 add hl, de xor a ld [hl], a ; status = 0 pop hl push hl ld de, $22 add hl, de ; -> maxHP ld a, [hli] ld b, a ld e, [hl] ld d, b pop hl push hl inc hl ; -> curHP ld a, d ld [hli], a ld a, e ld [hl], a pop hl ld de, $2C add hl, de dec c jr nz, .loop .done and a ret ; TEACH: give every party member an OP coverage moveset + PP ; (Hyper Beam / Earthquake / Ice Beam / Thunderbolt) SlopTeach: ld a, [wPartyCount] and a jr z, .done ld c, a ld hl, wPartyMon1 .loop push bc push hl ld de, $08 ; -> moves add hl, de ld a, $3F ; Hyper Beam ld [hli], a ld a, $59 ; Earthquake ld [hli], a ld a, $3A ; Ice Beam ld [hli], a ld a, $55 ; Thunderbolt ld [hli], a pop hl push hl ld de, $1D ; -> PP add hl, de ld b, 4 .pp ld a, $1F ; 31 PP each ld [hli], a dec b jr nz, .pp pop hl ld de, $2C add hl, de pop bc dec c jr nz, .loop .done and a ret ; HMS: give all five HMs (Cut/Fly/Surf/Strength/Flash) SlopGiveHMs: ld b, HM_CUT .loop push bc ld c, 1 call GiveItem pop bc inc b ld a, b cp HM_FLASH + 1 jr nz, .loop and a ret ; MONEY: 999999 (BCD) SlopMaxCash: ld a, $99 ld [wPlayerMoney], a ld [wPlayerMoney + 1], a ld [wPlayerMoney + 2], a and a ret ; COINS: 9999 game-corner coins (BCD) SlopMaxCoins: ld a, $99 ld [wPlayerCoins], a ld [wPlayerCoins + 1], a and a ret ; NOWILD toggle: no wild encounters (hooked in TryDoWildEncounter) SlopToggleNoWild: ld a, [wUnusedFlag] xor $01 ld [wUnusedFlag], a and a ret ; REPEL: refill repel steps SlopRepel: ld a, 255 ld [wRepelRemainingSteps], a and a ret ; BADGES: all 8 SlopAllBadges: ld a, $ff ld [wObtainedBadges], a and a ret ; DEX: mark every Pokemon owned + seen (19-byte flag arrays; last byte = 7 bits) SlopDex: ld hl, wPokedexOwned call .fill ld hl, wPokedexSeen call .fill and a ret .fill ld c, 18 .floop ld a, $ff ld [hli], a dec c jr nz, .floop ld [hl], $7f ret ; MAXTEAM: level 100, perfect DVs, maxed EVs, 999 stats + HP, exp = lvl 100 SlopMaxTeam: ld a, [wPartyCount] and a jr z, .done ld c, a ld hl, wPartyMon1 .loop push bc push hl ; current HP (+$01) = 999 inc hl ld a, $03 ld [hli], a ld a, $E7 ld [hl], a pop hl push hl ; exp (+$0E) = 1,000,000 ld de, $0E add hl, de ld a, $0F ld [hli], a ld a, $42 ld [hli], a ld a, $40 ld [hli], a ; EVs (+$11..$1A) = FFFF x5, then DVs (+$1B..$1C) = FFFF -> 12 bytes of $FF ld b, 12 .ffloop ld a, $ff ld [hli], a dec b jr nz, .ffloop pop hl push hl ; level (+$21) = 100 ld de, $21 add hl, de ld a, 100 ld [hli], a ; maxHP + 4 stats (+$22..$2B) = 999 x5 ld b, 5 .statloop ld a, $03 ld [hli], a ld a, $E7 ld [hli], a dec b jr nz, .statloop pop hl ld de, $2C add hl, de pop bc dec c jr nz, .loop .done and a ret