Togglable Infinite Repel - pret/pokecrystal GitHub Wiki

Inspired by Radical Red's Infinite Repel system, I decided to try and implement it in Crystal.

Contents

  1. Change Repel to a Key Item
  2. Modify Repel Functions
  3. Addendum: Make Repel an option in the options menu

1. Change Repel to a Key Item

First make the proper edits to the base Repel item in data/items/attributes.asm, as well as data/items/descriptions.asm.

 ItemAttributes:
 ; entries correspond to item ids (see constants/item_constants.asm)
 	table_width ITEMATTR_STRUCT_LENGTH, ItemAttributes
 	...
 ; REPEL
-	item_attribute 350, HELD_NONE, 0, CANT_SELECT, ITEM, ITEMMENU_CURRENT, ITEMMENU_NOUSE
+	item_attribute 0, HELD_NONE, 0, CANT_SELECT | CANT_TOSS, KEY_ITEM, ITEMMENU_CURRENT, ITEMMENU_NOUSE
 	...
 	assert_table_length $100
 RepelDesc:
	db   "Repels weak #-"
-	next "MON for 100 steps.@"
+	next "MON.@"

2. Modify Repel Functions

Now that the Repel is properly a Key Item, we'll need to modify its behavior. First let's open engine/items/item_effects.asm and remove Super Repel and Max Repel effects.

 ItemEffects:
 ; entries correspond to item ids (see constants/item_constants.asm)
	table_width 2, ItemEffects
	dw GuardSpecEffect     ; GUARD_SPEC
-	dw SuperRepelEffect    ; SUPER_REPEL
-	dw MaxRepelEffect      ; MAX_REPEL
+	dw NoEffect            ; SUPER_REPEL
+	dw NoEffect            ; MAX_REPEL
	dw DireHitEffect       ; DIRE_HIT
	dw NoEffect            ; ITEM_2D
 	...
	assert_table_length ITEM_B3
-SuperRepelEffect:
-	ld b, 200
-	jr UseRepel

- MaxRepelEffect:
-	ld b, 250
-	jr UseRepel

Now we'll change up RepelEffect function a bit.

 RepelEffect:
-	ld b, 100

-UseRepel:
+	ld b, 1
	ld a, [wRepelEffect]
	and a
-	ld hl, RepelUsedEarlierIsStillInEffectText
-	jp nz, PrintText
-
+	jr nz, .RepelisOn
	ld a, b
	ld [wRepelEffect], a
-	jp UseItemText
+	ld hl, RepelTurnOnText
+	jp PrintText

-RepelUsedEarlierIsStillInEffectText:
-	text_far _RepelUsedEarlierIsStillInEffectText
+.RepelisOn
+	xor a
+	ld [wRepelEffect], a
+	ld hl, RepelTurnOffText
+	jp PrintText
+
+RepelTurnOffText:
+	text_far _RepelTurnOffText
+	text_end
+	
+RepelTurnOnText:
+	text_far _RepelTurnOnText
	text_end

The repel items used to load the number of steps in register b and check if there's a repel active by checking wRepelEffect; if it was zero then it would load the value of b into it, if not then it would print a text indicating a repel is still in effect. What we did here is rework it so that we only need to load either 0 or 1 into wRepelEffect to indicate whether it's active or not and print the appropriate message.

Finally, go to data/text/common_3.asm edit a proper message:

-_RepelUsedEarlierIsStillInEffectText::
-	text "The REPEL used"
-	line "earlier is still"
-	cont "in effect."
+_RepelTurnOffText::
+	text "The REPEL is off."
	prompt
+	
+_RepelTurnOnText::
+	text "The REPEL is on."
+	prompt	

Now the issue is that the function for subtracting steps from Repel still exists, so next we'll head over to engine/overworld/events.asm and make these edits:

 CountStep:
 	; Don't count steps in link communication rooms.
 	ld a, [wLinkMode]
 	and a
 	jr nz, .done
 
 	farcall CheckSpecialPhoneCall
 	jr c, .doscript
 
-	; If Repel wore off, don't count the step.
-	call DoRepelStep
-	jr c, .doscript
-
 	; Count the step for poison and total steps
 	ld hl, wPoisonStepCount
 	inc [hl]

	...

-DoRepelStep:
-	ld a, [wRepelEffect]
-	and a
-	ret z
-
-	dec a
-	ld [wRepelEffect], a
-	ret nz
-
-	ld a, BANK(RepelWoreOffScript)
-	ld hl, RepelWoreOffScript
-	call CallScript
-	scf
-	ret
-

Finally we can delete engine/events/repel.asm and remove it from main.asm:

 SECTION "bank4", ROMX
 ...
 INCLUDE "engine/events/elevator.asm"
 INCLUDE "engine/events/bug_contest/contest.asm"
-INCLUDE "engine/events/repel.asm"
 INCLUDE "engine/events/hidden_item.asm"
 INCLUDE "engine/events/std_collision.asm"
 ...

That's it! Its now togglable from the Items menu.

ScreenshotScreenshotScreenshot

3. Addendum: Make Repel an Option in the Options Menu

As a fun extra, I also figured out how to simply add an infinite repel to the options menu. This will remove the often unused Printer function from the options menu.

First we'll add a new constant in constants/wram_constants.asm:

 ; wOptions2::
 	const_def
 	const MENU_ACCOUNT ; 0
+	const REPEL_OPTION ; 1

Also we'll add a comment in ram/wram.asm indicating what bit our new constant represents:

 wOptions2::
 ; bit 1: menu account off/on
+; bit 2: repel off/on
 	db
 	ds 2
 wOptionsEnd::

wOptions2 is a byte that holds the on/off bits for options. Notice its named 2, this is because there are 2 bytes for options, and wOptions is full. wOptions2, however, only holds the bit for Menu Account, so we can purpose the next bit for Repel.

Next we'll modify engine/menus/options_menu.asm.

 ; GetOptionPointer.Pointers indexes
 	const_def
 	const OPT_BATTLE_SCENE  ; 1
 	const OPT_BATTLE_STYLE  ; 2
 	const OPT_SOUND         ; 3
-	const OPT_PRINT         ; 4
+	const OPT_REPEL         ; 4
 	const OPT_MENU_ACCOUNT  ; 5
 	const OPT_FRAME         ; 6
 	const OPT_CANCEL        ; 7
 DEF NUM_OPTIONS EQU const_value ; 8
 StringOptions:
 	...
 	db "SOUND<LF>"
 	db "        :<LF>"
-	db "PRINT<LF>"
+	db "REPEL<LF>"
 	db "        :<LF>"
 	db "MENU ACCOUNT<LF>"
 	db "        :<LF>"
 	...
 GetOptionPointer:
	jumptable .Pointers, wJumptableIndex
 
 .Pointers:
 ; entries correspond to OPT_* constants
 	dw Options_TextSpeed
        dw Options_BattleScene
 	dw Options_BattleStyle
 	dw Options_Sound
-	dw Options_Print
+	dw Options_Repel
 	dw Options_MenuAccount
 	dw Options_Frame
 	dw Options_Cancel

Next copy the below function underneath Options_MenuAccount.

 Options_Repel:
 	ld hl, wOptions2
 	ldh a, [hJoyPressed]
 	bit D_LEFT_F, a
 	jr nz, .LeftPressed
 	bit D_RIGHT_F, a
 	jr z, .NonePressed
 	bit REPEL_OPTION, [hl]
 	jr nz, .ToggleOff
 	jr .ToggleOn
 
 .LeftPressed:
 	bit REPEL_OPTION, [hl]
 	jr z, .ToggleOn
 	jr .ToggleOff
 
 .NonePressed:
 	bit REPEL_OPTION, [hl]
 	jr nz, .ToggleOn
 
 .ToggleOff:
 	res REPEL_OPTION, [hl]
 	ld de, .Off
 	jr .Display
 
 .ToggleOn:
 	set REPEL_OPTION, [hl]
 	ld de, .On
 
 .Display:
 	hlcoord 11, 11
 	call PlaceString
 	and a
 	ret
 
 .Off: db "OFF@"
 .On:  db "ON @"

This is a copy of Options_MenuAccount but modified to toggle the Repel bit we added earlier.

Next we need to have the Repel function that is called when we actually do the Repel effect check for this bit, instead of wRepelEffect. Let's edit engine/overworld/wildmons.asm

 CheckRepelEffect::
-; If there is no active Repel, there's no need to be here.
-	ld a, [wRepelEffect]
-	and a
+; Repel option is set to off, there's no need to be here.
+	ld a, [wOptions2]
+	and 1 << REPEL_OPTION
	jr z, .encounter
; Get the first Pokemon in your party that isn't fainted.
	ld hl, wPartyMon1HP

ScreenshotScreenshot

And that's it! Now we'll have the option in the Menu to toggle Repel's effect on or off. I would recommend doing one or the other, simply because of redundancy. Also as a bit of clean up, if you go with the Repel in Options Menu, you can now safely remove the Options_Print function from engine/menus/options_menu.asm, as well as repurpose wRepelEffect for something else as it'll be unused.