-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Use Cut, Surf, and Strength From the Overworld
In Generation 2 and onward you can use Cut, Surf, or Strength simply by interacting with their respective overworld elements. This tutorial will show how to implement the same thing in Generation 1. You'll be able to use all these moves with no menu-ing required!
One warning before we start: we'll be adding a lot stuff in a lot of places. It's entirely possible you'll overfill your HOME bank during this process. It's highly recommended you follow one of the handful of existing tutorials here on how to free up HOME bank space prior to proceeding with this.
First up, a very simple change. Open ram\wram.asm and find a spot to insert a new named address we'll call wSearchedMove. I opted to delete the unused wUnusedCardKeyGateID and replace it, like so:
...
wWhichDungeonWarp:: db
-wUnusedCardKeyGateID:: db
+wSearchedMove:: db
ds 8
wStatusFlags1:: db
...This will serve us by creating a space to hold move ID data. This will become critical for the routines we are about to create. For now, we have to get rid of all the stuff the CardKey address was related to. Go to engine\items\item_effects.asm and change the Card Key to an UnusableItem:
...
dw ItemUseEvoStone ; LEAF_STONE
- dw ItemUseCardKey ; CARD_KEY
+ dw UnusableItem ; CARD_KEY
dw UnusableItem ; NUGGET
...Now delete the ItemUseCardKey routine (no worries... as the comments indicate, this routine is actually never called and is totally useless).
...
ItemUseXAccuracy:
ld a, [wIsInBattle]
and a
jp z, ItemUseNotTime
ld hl, wPlayerBattleStatus2
set USING_X_ACCURACY, [hl] ; X Accuracy bit
jp PrintItemUseTextAndRemoveItem
-; This function is bugged and never works. It always jumps to ItemUseNotTime.
-; The Card Key is handled in a different way.
-ItemUseCardKey:
- xor a
- ld [wUnusedCardKeyGateID], a
- call GetTileAndCoordsInFrontOfPlayer
- ld a, [GetTileAndCoordsInFrontOfPlayer]
- cp $18
- jr nz, .next0
- ld hl, CardKeyTable1
- jr .next1
-.next0
- cp $24
- jr nz, .next2
- ld hl, CardKeyTable2
- jr .next1
-.next2
- cp $5e
- jp nz, ItemUseNotTime
- ld hl, CardKeyTable3
-.next1
- ld a, [wCurMap]
- ld b, a
-.loop
- ld a, [hli]
- cp -1
- jp z, ItemUseNotTime
- cp b
- jr nz, .nextEntry1
- ld a, [hli]
- cp d
- jr nz, .nextEntry2
- ld a, [hli]
- cp e
- jr nz, .nextEntry3
- ld a, [hl]
- ld [wUnusedCardKeyGateID], a
- jr .done
-.nextEntry1
- inc hl
-.nextEntry2
- inc hl
-.nextEntry3
- inc hl
- jr .loop
-.done
- ld hl, ItemUseText00
- call PrintText
- ld hl, wStatusFlags1
- set BIT_UNUSED_CARD_KEY, [hl] ; never checked
- ret
-
-INCLUDE "data/events/card_key_coords.asm"
-
ItemUsePokeDoll:
...Open engine\overworld\field_move_messages.asm and insert the following routine:
...
CanMoveBouldersText:
text_far _CanMoveBouldersText
text_end
+; checks all PartyMon movesets for move stored in 'a'
+; sets carry flag if move is not found
+CheckPartyForMove::
+ push de ; preserves value in 'de' to avoid game crash later
+ ld a, [wSearchedMove] ; copy MoveID from wSearchedMove into 'a'
+ ld d, a ; d = move that is being searched for
+ ld e, 1 ; e = constant representing Mon teamslot that is being checked
+ ld hl, wPartyMon1Moves ; hl = PartyMon1's first move
+ push hl ; saves value in 'hl' to the stack
+.getNumMoves
+ ld c, NUM_MOVES
+.checkCurrentMonMoves
+ ld a, [hli] ; loads current move into 'a', increments 'hl' to next move
+ cp d ; check move ID against move that is being searched for
+ jr z, .checkIfStrength ; if the move IDs match, jump to the next step of the process
+ dec c ; reduce number of moves to check
+ jr nz, .checkCurrentMonMoves ; if c != 0, loop back
+.prepNextMonMoves ; if current Mon doesn't have desired move, load data for next Mon
+ inc e ; e = next teamslot
+ ld a, [wPartyCount] ; how many Mons are in the party?
+ cp e ; compare 'e' to total number of PartyMons
+ jr c, .done ; if e > a, exit routine (all PartyMons were checked without success)
+ pop hl ; retrieves current Mon's moveset data from the stack
+ ld bc, wPartyMon2 - wPartyMon1 ; bc = gap between corresponding elements of PartyMon data
+ add hl, bc ; moves 'hl' from current Mon's moveset to next Mon's moveset
+ push hl ; saves new moveset data to the stack
+ jr .getNumMoves ; loop back to check moves in the new moveset
+.checkIfStrength ; check if we can skip Mon cry data, since only Strength requires it
+ ld a, d ; a = move being searched for
+ cp STRENGTH ; compare move ID in 'a' to STRENGTH
+ jr nz, .getMonName ; if no match, skip getting Mon's cry data.
+ ld hl, wPartySpecies ; hl = PartyMon1 species data
+ ld a, e ; a = whichever teamslot had to be reached before finding Strength
+ dec a ; accounts for not having to go anywhere for Mon1's data
+ jr z, .loadMonCry ; if a = 0, we're dealing with Mon1, we have what we need
+.getMonCry
+ inc hl ; move to next PartyMon's species data
+ dec a ; reduce teamslot counter
+ jr nz, .getMonCry ; loop back until a = 0
+.loadMonCry
+ ld a, [hl] ; load MonID into 'a'
+ ld [wCurPartySpecies], a ; load MonID to be used as Mon's cry
+.getMonName ; begin process of getting Mon's name to display in move text
+ ld hl, wPartyMon1Nick ; hl = Mon1's nickname
+ ld a, e ; 'a' once again equals whichever teamslot was reached
+ dec a ; again accounts for starting at Mon1's data
+ jr z, .loadMonName ; look familiar? If a = 0, jump because we're dealing with Mon1
+ ld bc, wPartyMon2Nick - wPartyMon1Nick ; bc = gap between PartyMon nickname data
+ call AddNTimes ; moves 'hl' the proper distance to point to appropriate Mon's nickname
+.loadMonName ; loads Mon's nickname to be displayed during text
+ ld de, wNameBuffer
+ ld bc, 11
+ call CopyData
+ and a ; resets carry flag before exiting routine
+; if jumping here because no Mon had the move, carry flag will have been set as a result of cp instruction
+.done
+ pop de ; retrieves value originally in 'de' to avoid game crash
+ ret ; finally, we jump all the way back to where we originally came from
+
IsSurfingAllowed:
...This will enable the game to search our party for any move stored in register a. This is important because we need the game to know whether a specific field move is present on any Mon's moveset prior to allowing us to use it from the overworld. Additionally, if the move in question is found, the user's name (and cry, if applicable) will be loaded in for use in the text that follows.
Before we leave this file, we'll also need to export the following (by adding an extra colon):
-PrintStrengthText:
+PrintStrengthText::
ld hl, wStatusFlags1
...And also...
-IsSurfingAllowed:
+IsSurfingAllowed::
; Returns whether surfing is allowed in BIT_SURF_ALLOWED of wStatusFlags1.
...Time for another small, easy addition. Open data\text_predef_pointers.asm and make the following changes:
...
add_tx_pre UnusedBenchGuyText2 ; 1C XXX unused
- add_tx_pre UnusedBenchGuyText3 ; 1D XXX unused
- add_tx_pre UnusedPredefText ; 1E XXX unused
+ add_tx_pre CutTreeText ; 1D
+ add_tx_pre CalmWaterText ; 1E
add_tx_pre PokemonCenterPCText ; 1F
...This is needed to eventually get our Water and Cut Tree text working. We don't need to include anything about boulders here; boulders are sprites, and we can already interact with them by default. However, water and trees are tiles, not sprites, and it's much more difficult to interact with a tile than a sprite!
Before we move on, some housekeeping is in order. Since we eliminated the predef for UnusedBenchGuy3Text, we also need to delete the text itself. Go to engine\events\hidden_objects\bench_guys.asm and delete his text pointers there:
...
UnusedBenchGuyText2::
text_far _UnusedBenchGuyText2
text_end
-UnusedBenchGuyText3::
- text_far _UnusedBenchGuyText3
- text_end
-
VermilionCityPokecenterBenchGuyText::
text_far _VermilionPokecenterGuyText
text_end
...For good measure, also go to data\text\text_2.asm and delete the text there. It'll never be called, so no reason to keep it around.
..._UnusedBenchGuyText2::
text "I'm tired from"
line "all the fun..."
done
-_UnusedBenchGuyText3::
- text "SILPH's manager"
- line "is hiding in the"
- cont "SAFARI ZONE."
- done
-
_VermilionPokecenterGuyText::
...And that's it for this step.
Now for the reason we had to clear HOME bank space. Open home\overworld_text.asm and insert this big block of stuff:
...
BoulderText::
- text_far _BoulderText
- text_end
+ text_asm
+ ld a, [wStatusFlags1]
+ bit BIT_STRENGTH_ACTIVE, a ; check if Strength is already active
+ jr nz, .BouldersCanBeMovedText ; if so, display alternate boulder text
+ ld hl, RequiresStrengthText
+ call PrintText
+ ld a, [wObtainedBadges]
+ bit BIT_RAINBOWBADGE, a ; check for Erika's badge
+ jr z, .done ; if no RainbowBadge, text ends here
+ ld a, STRENGTH
+ ld [wSearchedMove], a ; store MoveID here, else it won't persist through the farcall
+ farcall CheckPartyForMove
+ jr c, .done
+.useStrengthYesNo
+ call WaitForTextScrollButtonPress
+ ld hl, LikeToUseStrengthText
+ call PrintText
+ call YesNoChoice
+ ld a, [wCurrentMenuItem]
+ and a
+ jr nz, .closeTextBox
+ farcall PrintStrengthText
+.closeTextBox ; prevents certain situations requiring an extra button press to close text box
+ ld a, 1
+ ld [wDoNotWaitForButtonPressAfterDisplayingText], a
+ jr .done
+.BouldersCanBeMovedText
+ ld hl, BouldersCanBeMovedText
+ call PrintText
+.done
+ jr TextScriptEnd
+
+RequiresStrengthText:
+ text_far _RequiresStrengthText
+ text_end
+
+LikeToUseStrengthText:
+ text_far _LikeToUseStrengthText
+ text_end
+
+BouldersCanBeMovedText:
+ text_far _BouldersCanBeMovedText
+ text_end
+
+CalmWaterText::
+ text_asm
+ farcall IsSurfingAllowed ; checks for Cycling Road and Seafoam Islands B4F
+ ld hl, wStatusFlags1
+ bit BIT_SURF_ALLOWED, [hl]
+ res BIT_SURF_ALLOWED, [hl]
+ jp z, .closeTextBox ; exit routine if surfing is disallowed
+ ld hl, WaterIsCalmText
+ call PrintText
+ ld a, [wObtainedBadges]
+ bit BIT_SOULBADGE, a ; check for Koga's badge
+ jr z, .done ; if no SoulBadge, text ends here
+ ld a, SURF
+ ld [wSearchedMove], a ; store MoveID here, else it won't persist through the farcall
+ farcall CheckPartyForMove
+ jr c, .done
+.useSurfYesNo
+ call WaitForTextScrollButtonPress
+ ld hl, LikeToUseSurfText
+ call PrintText
+ call YesNoChoice
+ ld a, [wCurrentMenuItem]
+ and a
+ jr nz, .closeTextBox
+.MonUsingSurf
+ ld a, SURFBOARD ; using SURF from the Party Menu works the exact same way
+ ld [wCurItem], a
+ ld [wPseudoItemID], a
+ call UseItem
+.closeTextBox
+ ld a, 1
+ ld [wDoNotWaitForButtonPressAfterDisplayingText], a
+.done
+ jp TextScriptEnd
+
+WaterIsCalmText:
+ text_far _WaterIsCalmText
+ text_end
+
+LikeToUseSurfText:
+ text_far _LikeToUseSurfText
+ text_end
+
+CutTreeText::
+ text_asm
+ ld hl, TreeCanBeCutText
+ call PrintText
+ ld a, [wObtainedBadges]
+ bit BIT_CASCADEBADGE, a ; check for Misty's badge
+ jr z, .done ; if no CascadeBadge, text ends here
+ ld a, CUT
+ ld [wSearchedMove], a ; store MoveID here, else it won't persist through the farcall
+ farcall CheckPartyForMove
+ jr c, .done
+.useCutYesNo
+ call WaitForTextScrollButtonPress
+ ld hl, LikeToUseCutText
+ call PrintText
+ call YesNoChoice
+ ld a, [wCurrentMenuItem]
+ and a
+ jr nz, .closeTextBox
+.MonUsingCut
+ ld hl, UsedCutAltText
+ call PrintText
+ predef UsedCut
+.closeTextBox ; see commented note below
+ xor a
+ ld [wSearchedMove], a ; make sure wSearchedMove != CUT before exiting the routine
+ ld a, 1
+ ld [wDoNotWaitForButtonPressAfterDisplayingText], a
+.done
+ jp TextScriptEnd
+
+; Note: The above instance of .closeTextBox is different because we've inserted a conditional jump in 'predef UsedCut'. This jump determines
+; whether to execute the routine as normal, when the player uses Cut from the Party Menu. If using Cut from the overworld, the routine skips
+; the screen white-out back to the overworld. It also skips loading the Cut user's name, since we already got it from CheckPartyForMove. The
+; short version of the routine gets executed ONLY when wSearchedMove = CUT. We MUST therefore ensure that we change that value just in case
+; we need the full routine should Cut ever get used from the menu. Trying to execute Cut from the menu while using the abridged routine has
+; some fairly disastrous graphical consequences... and for what it's worth, the text will be wrong, too.
+
+TreeCanBeCutText:
+ text_far _TreeCanBeCutText
+ text_end
+
+LikeToUseCutText:
+ text_far _LikeToUseCutText
+ text_end
+
+UsedCutAltText:
+ text_far _UsedCutText
+ text_end
+
MartSignText::
text_far _MartSignText
text_end
...That covers EVERYTHING about talking to boulders to use Strength, talking to trees to use Cut, and talking to water to use Surf. Badge checks, forbidden Surfing areas, what happens if the player says Yes/No when prompted... it's all taken care of. However, there's still important work to do...
The earlier work with Predefs was only part of the battle. As I mentioned, interacting with tiles is WAY harder than with sprites. The best way I found might seem a bit goofy, but it works just fine... we'll be expanding the code that allows interaction with bookshelves!
Yes, bookshelves. They're tiles that can be "talked" to, so their code will serve us well. We're going to greatly expand the game's definition of a bookshelf, allowing us to interact with water and Cut trees while leaving the original bookshelf interactions completely unchanged.
Open engine\events\hidden_objects\bookshelves.asm. We'll be adding the following mass of code to check whether we're talking to water or trees:
...
; prints text for bookshelves in buildings without sign events
PrintBookshelfText::
+ lda_coord 8, 9
+ ld [wTilePlayerStandingOn], a ; so the game knows EXACTLY where we are and what we're standing on
+ predef GetTileAndCoordsInFrontOfPlayer ; so the game knows EXACTLY what's in front of us
+ call UpdateSprites
+ farcall IsNextTileShoreOrWater ; are we looking at water?
+ jr c, .checkIfCutTreeMap ; if not, check if we're on a map that has Cut trees
+.shoreOrWater
+ predef GetTileAndCoordsInFrontOfPlayer ; stores id of tile in front of player in c
+ ld a, [wSpritePlayerStateData1FacingDirection]
+ cp SPRITE_FACING_UP
+ jr nz, .foundPlayerSpriteFacing ; if we're NOT facing up, all is good
+.checkCurrentMapTileset ; check to prevent unintended northward surfing from elevated areas in CAVERN and FOREST maps
+ lda_coord 8, 9
+ ld b, a
+ ld a, [wCurMapTileset]
+ cp CAVERN
+ ld a, $05 ; elevated cavern floor tile
+ jr z, .checkIfTileIsPassable
+ ld a, [wCurMapTileset]
+ cp FOREST
+ ld a, $2e ; elevated forest floor tile
+ jr nz, .foundPlayerSpriteFacing ; move on if we're not in a CAVERN or FOREST map
+.checkIfTileIsPassable
+ cp b ; are we standing on elevated floor? If so, there's no way we should be able to Surf...
+ jr z, .end
+.foundPlayerSpriteFacing
+ ld a, [wCurMapTileset]
+ ld b, a ; b = current tileset
+ jr .loadBookshelfTileIDs
+.checkIfCutTreeMap
+ predef GetTileAndCoordsInFrontOfPlayer
+ ld a, [wCurMapTileset] ; Cut trees can only be found in the OVERWORLD and GYM tilesets
+ ld b, a
+ cp OVERWORLD
+ jr z, .loadBookshelfTileIDs
+ cp GYM
+ jr z, .loadBookshelfTileIDs
+.checkIfBookshelf ; if we're not facing water and we're not on an OVERWORLD or GYM map, then we check for standard bookshelf stuff
ld a, [wSpritePlayerStateData1FacingDirection]
cp SPRITE_FACING_UP
jr nz, .noMatch
; facing up
ld a, [wCurMapTileset]
ld b, a
lda_coord 8, 7
ld c, a
+.loadBookshelfTileIDs
ld hl, BookshelfTileIDs
.loop
ld a, [hli]
cp $ff
jr z, .noMatch
cp b
jr nz, .nextBookshelfEntry1
ld a, [hli]
cp c
jr nz, .nextBookshelfEntry2
ld a, [hl]
push af
call EnableAutoTextBoxDrawing
pop af
call PrintPredefTextID
+.end
xor a
...Now we follow up by expanding the list of BookshelfTileIDs. Open data\tilesets\bookshelf_tile_ids.asm and insert the following entries at the end of the list:
...
bookshelf_tile SHIP, $36, BookOrSculptureText
+ bookshelf_tile OVERWORLD, $14, CalmWaterText ; begin list of water tiles
+ bookshelf_tile OVERWORLD, $32, CalmWaterText
+ bookshelf_tile FOREST, $14, CalmWaterText
+ bookshelf_tile FOREST, $48, CalmWaterText
+ bookshelf_tile GYM, $14, CalmWaterText
+ bookshelf_tile SHIP, $14, CalmWaterText
+ bookshelf_tile SHIP_PORT, $14, CalmWaterText
+ bookshelf_tile CAVERN, $14, CalmWaterText
+ bookshelf_tile FACILITY, $14, CalmWaterText
+ bookshelf_tile PLATEAU, $14, CalmWaterText
+ bookshelf_tile PLATEAU, $32, CalmWaterText
+ bookshelf_tile OVERWORLD, $3D, CutTreeText ; begin list of Cut tree tiles
+ bookshelf_tile GYM, $50, CutTreeText
db -1 ; endNow the game knows if we're looking at the water or a tree, and it knows what routine to execute (though we haven't actually yet defined the text that will be displayed... we'll get to that in just a moment).
We also need to create a check to prevent triggering the Water text while we're already surfing. This is simple enough... go to home\overworld.asm and insert this within the OverworldLoop: section:
...
; if A is pressed
ld a, [wStatusFlags5]
bit BIT_UNKNOWN_5_2, a
jp nz, .noDirectionButtonsPressed
call IsPlayerCharacterBeingControlledByGame
jr nz, .checkForOpponent
+ ld a, [wWalkBikeSurfState] ; prevent triggering water text while surfing
+ ld [wWalkBikeSurfStateCopy], a
+ cp 2 ; value used while surfing
+ jr z, .checkNextTileForSprite ; if we're surfing, skip to check if we're looking at a sign/sprite
call CheckForHiddenObjectOrBookshelfOrCardKeyDoor
ldh a, [hItemAlreadyFound]
and a
jp z, OverworldLoop ; jump if a hidden object or bookshelf was found, but not if a card key door was found
+.checkNextTileForSprite
call IsSpriteOrSignInFrontOfPlayer
...The end is in sight... just two changes left.
Within the UsedCut routine is a section that gets the name of the Cut user and then clears the screen to white as things transfer from the party menu back to the overworld. Both of those things are unnecessary if Cut is being used from the overworld (and conveniently, they're bunched right up against each other!), so we can insert a conditional jump to skip over the parts we don't need.
Move over to engine\overworld\cut.asm and insert this simple check:
...
.canCut
ld [wCutTile], a
ld a, 1
ld [wActionResultOrTookBattleTurn], a ; used cut
+ ld a, [wSearchedMove] ; check if we need to use abridged routine for overworld Cut
+ cp CUT ; see CutTreeText in 'home\overworld_text.asm' for full explanation
+ jr z, .usedCutFromOverworld ; skip getting Mon name and screen white out if using overworld Cut
ld a, [wWhichPokemon]
ld hl, wPartyMonNicks
call GetPartyMonName
ld hl, wStatusFlags5
set BIT_NO_TEXT_DELAY, [hl]
call GBPalWhiteOutWithDelay3
call ClearSprites
call RestoreScreenTilesAndReloadTilePatterns
ld a, SCREEN_HEIGHT_PX
ldh [hWY], a
call Delay3
call LoadGBPal
call LoadCurrentMapView
call SaveScreenTilesToBuffer2
call Delay3
xor a
ldh [hWY], a
ld hl, UsedCutText
call PrintText
call LoadScreenTilesFromBuffer2
ld hl, wStatusFlags5
res BIT_NO_TEXT_DELAY, [hl]
+.usedCutFromOverworld
ld a, $ff
ld [wUpdateSpritesEnabled], a
call InitCutAnimOAM
...And finally, the end:
Go to data\text\text_5.asm and paste this to define our new text:
...
_CableClubNPCMakingPreparationsText::
text "We're making"
line "preparations."
cont "Please wait."
done
+_RequiresStrengthText::
+ text "This requires"
+ line "STRENGTH to move!"
+ done
+
+_LikeToUseStrengthText::
+ text "Would you like to"
+ line "use STRENGTH?"
+ done
+
_UsedStrengthText::
text_ram wNameBuffer
text " used"
line "STRENGTH.@"
text_end
_CanMoveBouldersText::
text_ram wNameBuffer
text " can"
line "move boulders."
prompt
+_BouldersCanBeMovedText::
+ text "Boulders can now"
+ line "be moved."
+ done
+
+_TreeCanBeCutText::
+ text "This tree can be"
+ line "CUT."
+ done
+
+_LikeToUseCutText::
+ text "Would you like to"
+ line "use CUT?"
+ done
+
+_WaterIsCalmText::
+ text "The water is calm."
+ done
+
+_LikeToUseSurfText::
+ text "Would you like to"
+ line "use SURF?"
+ done
+
_CurrentTooFastText::
text "The current is"
line "much too fast!"
prompt
...At last, we've reached the end.
I hope this works out for you. I've tested these exact steps in both my own highly modified ROMHack as well as the totally unmodded game, and it's worked in both places. It's my hope that this process treats you the same.