Skip to content

Trainer Scripts

ghoulslash edited this page Jul 3, 2020 · 13 revisions

Run Script On Trainer Sight

Credit to ghoulslash.

Default Pokemon Trainers require the first script command to be trainerbattle to function correctly. This feature allows you to run any script while including the "trainer spotting" effect. Here's an example:

Modify Trainer Sight

1. Open src/trainer_see.c. Find the function CheckForTrainersWantingBattle. Modify it to the following:

bool8 CheckForTrainersWantingBattle(void)
{
    u8 i;
    u8 numTrainers;

    gNoOfApproachingTrainers = 0;
    gApproachingTrainerId = 0;

    for (i = 0; i < OBJECT_EVENTS_COUNT; i++)
    {
        if (!gObjectEvents[i].active)
            continue;
        
        if (gObjectEvents[i].trainerType == TRAINER_TYPE_NONE || gObjectEvents[i].trainerType == TRAINER_TYPE_SEE_ALL_DIRECTIONS)
            continue;

        numTrainers = CheckTrainer(i);
        if (numTrainers == 0xFF)    //run script
            break;
        
        if (numTrainers == 2)
            break;

        if (numTrainers == 0)
            continue;

        if (gNoOfApproachingTrainers > 1)
            break;
        if (GetMonsStateToDoubles_2() != 0) // one trainer found and cant have a double battle
            break;
    }
        
    if (numTrainers == 0xFF)
    {
        u8 objectEventId = gApproachingTrainers[gNoOfApproachingTrainers - 1].objectEventId;
        
        gSelectedObjectEvent = objectEventId;
        gSpecialVar_LastTalked = gObjectEvents[objectEventId].localId;
        ScriptContext1_SetupScript(EventScript_ObjectApproachPlayer);
        ScriptContext2_Enable();
        return TRUE;
    }
    
    if (gNoOfApproachingTrainers == 1)
    {
        ResetTrainerOpponentIds();
        ConfigureAndSetUpOneTrainerBattle(gApproachingTrainers[gNoOfApproachingTrainers - 1].objectEventId,
                                          gApproachingTrainers[gNoOfApproachingTrainers - 1].trainerScriptPtr);
        gTrainerApproachedPlayer = TRUE;
        return TRUE;
    }
    else if (gNoOfApproachingTrainers == 2)
    {
        ResetTrainerOpponentIds();
        for (i = 0; i < gNoOfApproachingTrainers; i++, gApproachingTrainerId++)
        {
            ConfigureTwoTrainersBattle(gApproachingTrainers[i].objectEventId,
                                       gApproachingTrainers[i].trainerScriptPtr);
        }
        SetUpTwoTrainersBattle();
        gApproachingTrainerId = 0;
        gTrainerApproachedPlayer = TRUE;
        return TRUE;
    }
    else
    {
        gTrainerApproachedPlayer = FALSE;
        return FALSE;
    }
}

2. Next, find CheckTrainer and change it to:

static u8 CheckTrainer(u8 objectEventId)
{
    const u8 *scriptPtr;
    u8 ret = 1;
    u8 approachDistance;
    u16 scriptFlag = GetObjectEventTrainerSightFlagByObjectEventId(objectEventId);
    
    if (InTrainerHill() == TRUE)
        scriptPtr = GetTrainerHillTrainerScript();
    else
        scriptPtr = GetObjectEventScriptPointerByObjectEventId(objectEventId);

    if (InBattlePyramid())
    {
        if (GetBattlePyramidTrainerFlag(objectEventId))
            return 0;
    }
    else if (InTrainerHill() == TRUE)
    {
        if (GetHillTrainerFlag(objectEventId))
            return 0;
    }
    else
    {
        if (GetTrainerFlagFromScriptPointer(scriptPtr))
            return 0;
    }

    approachDistance = GetTrainerApproachDistance(&gObjectEvents[objectEventId]);

    if (approachDistance != 0)
    {
        if (scriptFlag >= TRAINER_TYPE_RUN_SCRIPT)
        {
            if (!FlagGet(scriptFlag) && scriptPtr != NULL)
            {
                // TRAINER_TYPE_RUN_SCRIPT
                FlagSet(scriptFlag);
                ret = 0xFF;
            }
            else
            {
                return 0;
            }
        }
        else
        {
            if (scriptPtr[1] == TRAINER_BATTLE_DOUBLE
                || scriptPtr[1] == TRAINER_BATTLE_REMATCH_DOUBLE
                || scriptPtr[1] == TRAINER_BATTLE_CONTINUE_SCRIPT_DOUBLE)
            {
                if (GetMonsStateToDoubles_2() != 0)
                    return 0;

                ret = 2;
            }
        }

        gApproachingTrainers[gNoOfApproachingTrainers].objectEventId = objectEventId;
        gApproachingTrainers[gNoOfApproachingTrainers].trainerScriptPtr = scriptPtr;
        gApproachingTrainers[gNoOfApproachingTrainers].radius = approachDistance;
        TrainerApproachPlayer(&gObjectEvents[objectEventId], approachDistance - 1);
        gNoOfApproachingTrainers++;

        return ret;
    }

    return 0;
}

3. Create the function GetObjectEventTrainerSightFlagByObjectEventId First, open src/event_object_movement.c. Somewhere, add the function GetObjectEventTrainerSightFlagByObjectEventId:

u16 GetObjectEventTrainerSightFlagByObjectEventId(u8 objEventId)
{
    return GetObjectEventTemplateByLocalIdAndMap(gObjectEvents[objEventId].localId, gObjectEvents[objEventId].mapNum, gObjectEvents[objEventId].mapGroup)->trainerType;
}

Next, define the new function in include/event_object_movement.h. Add u16 GetObjectEventTrainerSightFlagByObjectEventId(u8 objEventId); somewhere in the file.

Define TRAINER_TYPE_RUN_SCRIPT

add #define TRAINER_TYPE_RUN_SCRIPT 4 to include/constants/trainer_types.h

This is actually superfluous, but is a nice way to demonstrate that any value >= 4 will allow you to run any script.

Make a new approach script

Open data/scripts/trainer_script.inc and add the following to the bottom

EventScript_ObjectApproachPlayer::
	lock
	special EndTrainerApproach
	waitstate
	gotonative LoadTrainerObjectScript
	end

gotonative LoadTrainerObjectScript will allow us to dynamically branch to our objects own script.

Next, add extern const u8 EventScript_ObjectApproachPlayer[]; somewhere to include/event_scripts.h

Create LoadTrainerObjectScript

Open src/script.c and add the following function:

bool8 LoadTrainerObjectScript(void)
{
    sScriptContext1.scriptPtr = gApproachingTrainers[gNoOfApproachingTrainers - 1].trainerScriptPtr;
}

Also, add #include "trainer_see.h" to the top of the file for this function to recognize gApproachingTrainers

Finally, add #include/event_scripts.h at the top of src/trainer_see.c

How to Set Up:

Rather than make a new object event template field, we can use the fact that .trainerType is conveniently 16 bits. Set up your object event the following way:

Here's a brief run-down of the important fields:

  • Script: the script you want the object to run upon approaching the player.
  • Trainer Type: the flag that allows the script to run. This must be >3, although you wouldn't want to use a temporary flag anyway. When the flag in this field is set, the object won't approach you anymore.
  • Sight Radius: the same as for regular trainers

Note: there are two bytes free at the end of ObjectEventTemplate. You could easily add the trainer script flag here (don't forget to change the porymap json, GetObjectEventTrainerSightFlagByObjectEventId, and global.fieldmap.h!), and keep the trainer type field as intended.

Clone this wiki locally