Skip to content
rodmcosta edited this page May 26, 2024 · 16 revisions

Gen 4 introduced Rock Climb, a field move that can scale rocky walls, as HM08. Its in-battle effect, to deal damage with a chance of confusion, already exists (for Psybeam); and its field effect is not too complex to implement.

(The code for this feature was adapted from Pokémon Orange.)

Contents

  1. Prepare the Rock Climb move
  2. Prepare the HM08 item
  3. Define a collision type for rocky walls
  4. Start to prepare the Rock Climb field move effect
  5. Define a utility function to check for rocky walls
  6. Define text related to using Rock Climb
  7. Finish the Rock Climb field move effect
  8. Talk to rocky walls to use Rock Climb
  9. Add rocky walls to a map

1. Prepare the Rock Climb move

We're going to add a field effect for climbing rocky walls; but first, we have to add the move Rock Climb, following this tutorial.

Replace MOVE_OR_ANIM_FC with ROCK_CLIMB; give it a name, description, and battle properties (ROCK_CLIMB, EFFECT_CONFUSE_HIT, 90, NORMAL, 85, 20, 20); give it an animation (it can share BattleAnim_Waterfall); and add it to Pokémon learnsets (Sandshrew, Geodude, Onix, Rhyhorn, and Gligar learn it as an Egg move).

2. Prepare the HM08 item

We also have to add the item HM08, following this tutorial.

Add an HM for ROCK_CLIMB; give it a name and attributes (0, HELD_NONE, 0, CANT_SELECT | CANT_TOSS, TM_HM, ITEMMENU_PARTY, ITEMMENU_NOUSE); associate it with the move ROCK_CLIMB; make it unforgettable; and add it to Pokémon base learnsets (48 Pokémon are compatible with it, including all three fully-evolved Johto starters).

3. Define a collision type for rocky walls

Edit constants/collision_constants.asm:

 ; collision data types (see data/tilesets/*_collision.asm)
 ; TileCollisionTable indexes (see data/collision/collision_permissions.asm)
 COLL_FLOOR             EQU $00
 COLL_01                EQU $01 ; garbage
 COLL_03                EQU $03 ; garbage
 COLL_04                EQU $04 ; garbage
+COLL_ROCKY_WALL        EQU $06
 COLL_WALL              EQU $07
 ...

And edit data/collision/collision_permissions.asm:

 TileCollisionTable::
 ; entries correspond to COLL_* constants
 	db LANDTILE ; COLL_FLOOR
 	db LANDTILE ; COLL_01
 	db LANDTILE ; 02
 	db LANDTILE ; COLL_03
 	db LANDTILE ; COLL_04
 	db LANDTILE ; 05
-	db LANDTILE ; 06
+	db WALLTILE ; COLL_ROCKY_WALL
 	db WALLTILE ; COLL_WALL
 	...

Rocky walls will prompt to use Rock Climb when talked to. It needs to be "WALLTILE" so the player can't walk on it.

4. Start to prepare the Rock Climb field move effect

Now we can start to add the field move effect, following this tutorial.

Define MONMENUITEM_ROCKCLIMB; associate it with the move ROCK_CLIMB; and implement MonMenu_RockClimb for its menu action (in engine/pokemon/mon_menu.asm, or engine/menus/start_menu.asm in older versions of pokecrystal) like this:

+MonMenu_RockClimb:
+	farcall RockClimbFunction
+	ld a, [wFieldMoveSucceeded]
+	cp $1
+	jr nz, .Fail
+	ld b, $4
+	ld a, $2
+	ret
+
+.Fail:
+	ld a, $3
+	ret

We still have to implement RockClimbFunction; but first, let's define some more components for it.

5. Define a utility function to check for rocky walls

Edit home/map_objects.asm:

 CheckHeadbuttTreeTile::
 	cp COLL_HEADBUTT_TREE
 	ret z
 	cp COLL_HEADBUTT_TREE_1D
 	ret
+
+CheckRockyWallTile::
+	cp COLL_ROCKY_WALL
+	ret

This isn't strictly necessary—we could just directly use cp COLL_ROCKY_WALL instead of call CheckRockyWallTile—but it's how the other field moves work. And this approach is easier to extend, if for some reason more than one collision type should act like a rocky wall.

6. Define text related to using Rock Climb

Edit data/text/common_2.asm:

+_AskRockClimbText::
+	text "The wall is very"
+	line "rocky…"
+
+	para "Want to use"
+	line "ROCK CLIMB?"
+	done
+
+_UsedRockClimbText::
+	text_ram wStringBuffer2
+	text " used"
+	line "ROCK CLIMB!"
+	prompt
+
+_CantRockClimbText::
+	text "The wall is very"
+	line "rocky…"
+
+	para "Will a #MON's"
+	line "move scale it?"
+	done

This could have gone in common_1.asm or common_3.asm instead, but common_2.asm has text related to other HM moves already.

We'll need this text, and the previous CheckRockyWallTile routine, when we define RockClimbFunction next.

7. Finish the Rock Climb field move effect

Edit engine/events/overworld.asm:

+RockClimbFunction:
+	call FieldMoveJumptableReset
+.loop
+	ld hl, .jumptable
+	call FieldMoveJumptable
+	jr nc, .loop
+	and $7f
+	ld [wFieldMoveSucceeded], a
+	ret
+
+.jumptable:
+	dw .TryRockClimb
+	dw .DoRockClimb
+	dw .FailRockClimb
+
+.TryRockClimb:
+	ld de, ENGINE_EARTHBADGE
+	farcall CheckBadge
+	jr c, .noearthbadge
+	call TryRockClimbMenu
+	jr c, .failed
+	ld a, $1
+	ret
+
+.noearthbadge
+	ld a, $80
+	ret
+
+.failed
+	ld a, $2
+	ret
+
+.DoRockClimb:
+	ld hl, RockClimbFromMenuScript
+	call QueueScript
+	ld a, $81
+	ret
+
+.FailRockClimb:
+	call FieldMoveFailed
+	ld a, $80
+	ret
+
+TryRockClimbMenu:
+	call GetFacingTileCoord
+	ld c, a
+	push de
+	call CheckRockyWallTile
+	pop de
+	jr nz, .failed
+	xor a
+	ret
+
+.failed
+	scf
+	ret
+
+TryRockClimbOW::
+	ld de, ENGINE_EARTHBADGE
+	call CheckEngineFlag
+	jr c, .cant_climb
+
+	ld d, ROCK_CLIMB
+	call CheckPartyMove
+	jr c, .cant_climb
+
+	ld a, BANK(AskRockClimbScript)
+	ld hl, AskRockClimbScript
+	call CallScript
+	scf
+	ret
+
+.cant_climb
+	ld a, BANK(CantRockClimbScript)
+	ld hl, CantRockClimbScript
+	call CallScript
+	scf
+	ret
+
+AskRockClimbScript:
+	opentext
+	writetext AskRockClimbText
+	yesorno
+	iftrue UsedRockClimbScript
+	closetext
+	end
+
+CantRockClimbScript:
+	jumptext CantRockClimbText
+
+RockClimbFromMenuScript:
+	reloadmappart
+	special UpdateTimePals
+
+UsedRockClimbScript:
+	callasm GetPartyNick
+	writetext UsedRockClimbText
+	closetext
+	loadvar VAR_MOVEMENT, PLAYER_NORMAL
+	special UpdatePlayerSprite
+	waitsfx
+	playsound SFX_STRENGTH
+	readvar VAR_FACING
+	if_equal DOWN, .Down
+.loop_up
+	applymovement PLAYER, .RockClimbUpStep
+	callasm .CheckContinueRockClimb
+	iffalse .loop_up
+	end
+
+.Down:
+	applymovement PLAYER, .RockClimbFixFacing
+.loop_down
+	applymovement PLAYER, .RockClimbDownStep
+	callasm .CheckContinueRockClimb
+	iffalse .loop_down
+	applymovement PLAYER, .RockClimbRemoveFixedFacing
+	end
+
+.CheckContinueRockClimb:
+	xor a
+	ld [wScriptVar], a
+	ld a, [wPlayerTileCollision]
+	call CheckRockyWallTile
+	ret z
+	ld a, $1
+	ld [wScriptVar], a
+	ret
+
+.RockClimbUpStep:
+	step UP
+	step_end
+
+.RockClimbDownStep:
+	step DOWN
+	step_end
+
+.RockClimbFixFacing:
+	turn_head UP
+	fix_facing
+	step_end
+
+.RockClimbRemoveFixedFacing:
+	remove_fixed_facing
+	turn_head DOWN
+	step_end
+
+AskRockClimbText:
+	text_far _AskRockClimbText
+	text_end
+
+UsedRockClimbText:
+	text_far _UsedRockClimbText
+	text_end
+
+CantRockClimbText:
+	text_far _CantRockClimbText
+	text_end

You can study how this routine works; it's similar to other field moves. Just like in HG/SS, you need the Earth Badge to use it. (If you don't want that, remove the two checks for ENGINE_EARTHBADGE.) When you're facing up or down toward a rocky wall, you can climb it. Just like waterfalls, you'll continue moving until you reach a different type of tile.

loadvar VAR_MOVEMENT, PLAYER_NORMAL followed special UpdatePlayerSprite will make sure you won't climb with a bike.

Anyway, the requisite move, HM, and field effect are all done, but we still have a few improvements to make.

8. Talk to rocky walls to use Rock Climb

Edit engine/overworld/events.asm:

 TryTileCollisionEvent::
 	...

 .headbutt
 	ld a, [wFacingTileID]
 	call CheckHeadbuttTreeTile
-	jr nz, .surf
+	jr nz, .rock_climb
 	farcall TryHeadbuttOW
	jr c, .done
	jr .noevent
+
+.rock_climb
+	ld a, [wFacingTileID]
+	call CheckRockyWallTile
+	jr nz, .surf
+	farcall TryRockClimbOW
+	jr .done

 .surf
 	farcall TrySurfOW
 	jr nc, .noevent
 	jr .done

 .noevent
 	xor a
 	ret

 .done
 	call PlayClickSFX
 	ld a, $ff
 	scf
 	ret

TryTileCollisionEvent checks for various relevant collision types, one after another, so we just add a case for rocky walls.

That's it! Now COLL_ROCKY_WALL can be used like any other collision type—try assigning it to a block in data/tilesets/*_collision.asm.

…Although, there aren't any suitable tiles for rocky walls. So let's make some.

9. Add rocky walls to a map

Here are some rocky wall tiles edited from Pokémon Orange:

Screenshot

Let's say you want to copy HG/SS and require Rock Climb for an item ball on Route 45. Since maps/Route45.blk uses the johto tileset, here's what that would involve:

  1. Add the rocky wall tiles to gfx/tilesets/johto.png
  2. Assign the BROWN color to those tiles in gfx/tilesets/johto_palette_map.asm
  3. Design a rocky wall block in data/tilesets/johto_metatiles.bin
  4. Assign the ROCKY_WALL collision type to that block in data/tilesets/johto_collision.asm
  5. Redesign maps/Route45.blk to use the rocky wall block

You can use Polished Map to edit maps and tilesets; refer to the new map and new tileset tutorials for more information.

Now test it out!

Screenshot

By the way, if you're walking on top of cliffs, it's possible to walk down onto a cave entrance. That's because the clifftops all use COLL_FLOOR, cave entrances use COLL_CAVE, and of course it's possible to walk from the former to the latter, since there's no real concept of "elevation" in Gen 2. The solution is to use COLL_DOWN_WALL for clifftops above cave entrances. That way you can walk onto the space but not down from it. (The top edges of cliffs already use COLL_UP_WALL so you can't walk between them and the ground level.)

Clone this wiki locally