Prompt for reusing Repels - pret/pokeemerald GitHub Wiki

Credit to ExpoSeed for this feature.

This tutorial will ask the player, upon a Repel running out, if they want to use another repel, and use the repel if they say yes.

Contents

  1. Store last used Repel
  2. Copy VAR string
  3. Ask the player
  4. Cycle through remaining repels

1. Store last used Repel

The tricky part of implementing this is that the game does not actually store which repel is used. It only changes VAR_REPEL_STEP_COUNT. In order to remedy this, we must store the repel used.

We can use any of the unused vars to do this. This tutorial will use VAR_UNUSED_0x404E, but you can use whatever you like. Edit include/constants/vars.h:

-#define VAR_UNUSED_0x404E                    0x404E // Unused Var
+#define VAR_REPEL_LAST_USED                  0x404E

Next, we need to write to the variable when a repel is used. Edit src/item_use.c:

 static void Task_UseRepel(u8 taskId)
 {
     if (!IsSEPlaying())
     {
         VarSet(VAR_REPEL_STEP_COUNT, ItemId_GetHoldEffectParam(gSpecialVar_ItemId));
+        VarSet(VAR_REPEL_LAST_USED, gSpecialVar_ItemId);
         RemoveUsedItem();
         if (!InBattlePyramid())
             DisplayItemMessage(taskId, FONT_NORMAL, gStringVar4, CloseItemMessage);
         else
             DisplayItemMessageInBattlePyramid(taskId, gStringVar4, Task_CloseBattlePyramidBagMessage);
     }
 }

We also need a value to write to VAR_REPEL_STEP_COUNT. From a script, we cannot get this from the item ID alone. While we could use another unused variable, there is a way to avoid having to do this. callnative in scripts lets you call any function from a script. So, we can define a new function and use it with callnative. callnative is not the only way to use C code in scripts. You can also define specials and save some (but not much space), but it is largely unneeded.

In src/item.c, we will declare and define a new function (note: declare the function in include/item.h):

void ItemId_GetHoldEffectParam_Script();
void ItemId_GetHoldEffectParam_Script()
{
    VarSet(VAR_RESULT, ItemId_GetHoldEffectParam(VarGet(VAR_0x8004)));
}

2. Copy VAR string

There is one last issue we need to solve before adding the script. We need to create a copy of gText_PlayerUsedVar2 and remove the {PAUSE_UNTIL_PRESS} portion of the string. We can't edit it directly, since gText_PlayerUsedVar2 is used in other parts of the game. If we were to use the original gText_PlayerUsedVar2 string, the player needs to press A twice after using a repel to get rid of the message box, which is obviously unintended. Making a copy of the string without {PAUSE_UNTIL_PRESS} bypasses this problem.

In src/strings.c and include/strings.h, add a new string called gText_PlayerUsedRepel.

strings.c

 const u8 gText_TMHMContainedVar1[] = _("It contained\n{STR_VAR_1}.\pTeach {STR_VAR_1}\nto a POKéMON?");
 const u8 gText_PlayerUsedVar2[] = _("{PLAYER} used the\n{STR_VAR_2}.{PAUSE_UNTIL_PRESS}");
+const u8 gText_PlayerUsedRepel[] = _("{PLAYER} used the\n{STR_VAR_2}.");
 const u8 gText_RepelEffectsLingered[] = _("But the effects of a REPEL\nlingered from earlier.{PAUSE_UNTIL_PRESS}");

strings.h

 extern const u8 gText_TMHMContainedVar1[];
 extern const u8 gText_PlayerUsedVar2[];
+extern const u8 gText_PlayerUsedRepel[];
 extern const u8 gText_RepelEffectsLingered[];

Next, we will modify the script to ask the player if they want to use another repel.

3. Ask the player

Now, we have everything we need. Edit data/scripts/repel.inc:

 EventScript_RepelWoreOff::
+	lockall
+	checkitem VAR_REPEL_LAST_USED, 1
+	compare VAR_RESULT, FALSE
+	goto_if_eq EventScript_RepelWoreOff_NoMoreRepels
+	msgbox Text_RepelWoreOff_UseAnother, MSGBOX_YESNO
+	compare VAR_RESULT, 0
+	goto_if_eq EventScript_RepelWoreOff_ChooseNo
+	copyvar VAR_0x8004, VAR_REPEL_LAST_USED
+	callnative ItemId_GetHoldEffectParam_Script
+	copyvar VAR_REPEL_STEP_COUNT, VAR_RESULT
+	bufferitemname 1, VAR_REPEL_LAST_USED
+	removeitem VAR_REPEL_LAST_USED, 1
+	playse SE_REPEL
+	msgbox gText_PlayerUsedRepel, MSGBOX_SIGN
+	goto EventScript_RepelWoreOff_End
+EventScript_RepelWoreOff_ChooseNo:
+	closemessage
+	goto EventScript_RepelWoreOff_End
+EventScript_RepelWoreOff_NoMoreRepels:
	msgbox Text_RepelWoreOff, MSGBOX_SIGN
+EventScript_RepelWoreOff_End:
+	releaseall
	end

 Text_RepelWoreOff:
	.string "REPEL's effect wore off…$"

+Text_RepelWoreOff_UseAnother:
+	.string "REPEL's effect wore off…\n"
+	.string "Use another?$"

Let's walk through this script. First, we check whether the player has another of the same repel in their bag. If they don't, we jump to the end. But if they do, we ask the player if they want to use another. If they say "No", then we close the message box and end the script. If they say "Yes", then we set the step count variable appropriately, deduct a repel from the bag, play the appropriate sound effect, and show a message saying the player used a repel.

And that's it!

Alternatively, check out DizzyEgg's repel branch for a version which uses a multichoice box instead.

4. Cycle Through Repels

If you've still got repels in your bag the player will likely want to use those up as well. This is a modification of above to allow for that! First, we add an additional function. This can be placed right below Task_UseRepel in src/item_use.c. And don't forget to declare the function up at the top!!

static void Task_UseRepel(u8);
+void Cycle_Through_Repels(void);
static void Task_CloseCantUseKeyItemMessage(u8);

This is a great time to point out how the function has static omitted.

From my understanding, static makes the function local to a file and therefore unable to be called from outside. Because we're going to use callnative again over in the assembly side of things, we want to declare it just like we did ItemId_GetHoldEffectParam_Script. (Btw, feel free to move this over to src/item.c with ItemId_GetHoldEffectParam_Script if you like the consistency, but just make sure to declare it in that .h file as well.)

Now, onto the function!

static void Task_UseRepel(u8 taskId)
{
...
}

+ void Cycle_Through_Repels(void)
+ {//Once the last repel of the chosen type has been depleted, find the next lowest repel class 
+  //and start using it! (Set it as VAR_REPEL_LAST_USED)
+
+     u16 RepelCycle[] = {ITEM_REPEL, ITEM_SUPER_REPEL, ITEM_MAX_REPEL};    
+     u8 i = 0;
+
+     while (gSpecialVar_Result == FALSE){
+         gSpecialVar_Result = CheckBagHasItem(RepelCycle[i],1);
+         if (gSpecialVar_Result == TRUE)
+             VarSet(VAR_REPEL_LAST_USED, RepelCycle[i]);
+         i++;
+         if (i > 2)
+             return;
+     }
+
+     return;
+ }

Since we walked through it above, let's do the same here. Remember, this function occurs after all previous repels of a type were depleted (remember compare VAR_RESULT, FALSE?), so now we need to find if there are any others and store them in VAR_REPEL_LAST_USED to update the script.

First we make an array with all the repel item IDs and declare an increment variable i. Next, we loop through the repels one by one and check if they exist in the bag - note that since ITEM_REPEL is listed first (in the 0th array index), it will look for this first in the loop. If none are found (VAR_RESULT still shows as FALSE) it'll look for super repels, then finally max repels. In this way all the smaller items are used first and the player's bag can clear them out. Finally, if no other repels are found, we want to stop the loop at i > 2 and return to the script empty-handed, which is better than spiralling away to infinity in misery.

NOTE: If you want to just grab the smallest repel type after the first one gets depleted (rather than waiting for all repels of its kind to run out), just change the while to a For (i = 0, i = 3, i++) loop, add a Return; to the 'if TRUE' statement, and get rid of the i++ & if (i > 2) stopper.

Sweet! Now, throw in a callnative in the assembly script and we're there. Over in data/scripts/repel.inc:

 EventScript_RepelWoreOff::
	lockall
	checkitem VAR_REPEL_LAST_USED, 1
+	callnative Cycle_Through_Repels
	compare VAR_RESULT, FALSE
	goto_if_eq EventScript_RepelWoreOff_NoMoreRepels
	msgbox Text_RepelWoreOff_UseAnother, MSGBOX_YESNO
	compare VAR_RESULT, 0
	goto_if_eq EventScript_RepelWoreOff_ChooseNo
	copyvar VAR_0x8004, VAR_REPEL_LAST_USED
	callnative ItemId_GetHoldEffectParam_Script
	copyvar VAR_REPEL_STEP_COUNT, VAR_RESULT
	bufferitemname 1, VAR_REPEL_LAST_USED
	removeitem VAR_REPEL_LAST_USED, 1
	playse SE_REPEL
	msgbox gText_PlayerUsedRepel, MSGBOX_SIGN
	goto EventScript_RepelWoreOff_End
...

Happy hunting!