• Just a reminder that providing specifics on, sharing links to, or naming websites where ROMs can be accessed is against the rules. If your post has any of this information it will be removed.
  • Ever thought it'd be cool to have your art, writing, or challenge runs featured on PokéCommunity? Click here for info - we'd love to spotlight your work!
  • Our weekly protagonist poll is now up! Vote for your favorite Conquest protagonist in the poll by clicking here.
  • Welcome to PokéCommunity! Register now and join one of the best fan communities on the 'net to talk Pokémon and more! We are not affiliated with The Pokémon Company or Nintendo.

[Solved] [Pokeemerald] Teleport Item

  • 14
    Posts
    6
    Years
    • Seen Apr 28, 2025
    In case it's useful to someone, here is the code for a teleport item which modifies the escape rope to teleport the player between two locations, the last heal location and a location which is dynamically set when the player uses the item. Think of it as vaguely analogous to something like the Mark and Recall spells in the Elder Scrolls:

    Code:
    static void EscapeRopeWarpOutEffect_Spin(struct Task *task)
    {
        struct ObjectEvent *objectEvent;
        bool8 warpedYet = FlagGet(FLAG_WARP_DEVICE);
        u8 spinDirections[5] =  {DIR_SOUTH, DIR_WEST, DIR_EAST, DIR_NORTH, DIR_SOUTH};
        if (task->tTimer != 0 && (--task->tTimer) == 0)
        {
            TryFadeOutOldMapMusic();
            WarpFadeOutScreen();
        }
        objectEvent = &gObjectEvents[gPlayerAvatar.objectEventId];
        if (!ObjectEventIsMovementOverridden(objectEvent) || ObjectEventClearHeldMovementIfFinished(objectEvent))
        {
            if (task->tTimer == 0 && !gPaletteFade.active && BGMusicStopped() == TRUE)
            {
                SetObjectEventDirection(objectEvent, task->tStartDir);
                if (!warpedYet)
                {
                    FlagSet(FLAG_WARP_DEVICE);
                    SetEscapeWarp(gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum, WARP_ID_NONE, gSaveBlock1Ptr->pos.x, gSaveBlock1Ptr->pos.y);
                    SetWarpDestinationToLastHealLocation();
                }
                else
                {
                    FlagClear(FLAG_WARP_DEVICE);
                    SetWarpDestinationToEscapeWarp();
                }
                WarpIntoMap();
                gFieldCallback = FieldCallback_EscapeRopeWarpIn;
                SetMainCallback2(CB2_LoadMap);
                DestroyTask(FindTaskIdByFunc(Task_EscapeRopeWarpOut));
            }
            else if (task->tSpinDelay == 0 || (--task->tSpinDelay) == 0)
            {
                ObjectEventSetHeldMovement(objectEvent, GetFaceDirectionMovementAction(spinDirections[objectEvent->facingDirection]));
               
                if (gSaveBlock2Ptr->follower.inProgress)
                {
                    ObjectEventClearHeldMovement(&gObjectEvents[gSaveBlock2Ptr->follower.objId]);
                    switch(objectEvent->facingDirection)
                    {
                        case DIR_SOUTH:
                            gSprites[gObjectEvents[gSaveBlock2Ptr->follower.objId].spriteId].x2 = 0;
                            ObjectEventSetHeldMovement(&gObjectEvents[gSaveBlock2Ptr->follower.objId], 25);
                            break;
                        case DIR_NORTH:
                            gSprites[gObjectEvents[gSaveBlock2Ptr->follower.objId].spriteId].x2 = 0;
                            ObjectEventSetHeldMovement(&gObjectEvents[gSaveBlock2Ptr->follower.objId], 26);
                            break;
                        case DIR_WEST:
                            gSprites[gObjectEvents[gSaveBlock2Ptr->follower.objId].spriteId].x2 = 8;
                            ObjectEventSetHeldMovement(&gObjectEvents[gSaveBlock2Ptr->follower.objId], 27);
                            break;
                        case DIR_EAST:
                            gSprites[gObjectEvents[gSaveBlock2Ptr->follower.objId].spriteId].x2 = -8;
                            ObjectEventSetHeldMovement(&gObjectEvents[gSaveBlock2Ptr->follower.objId], 28);
                            break;
                    }
                }
               
                if (task->tNumTurns < 12)
                    task->tNumTurns++;
                task->tSpinDelay = 8 >> (task->tNumTurns >> 2);
            }
        }
    }

    Just replace the function of the same name in item_use.c. You will also either need to create a new "FLAG_WARP_DEVICE" or just use any other unused flag. Thanks Anon882 for the help.

    Original post below.

    ________________________________________


    I am trying to make an item which will warp the player between two locations. I am modifying the escape rope code to do this.

    If used in the wilderness, it should teleport the player back to the central hub city. This bit I have done fine.

    Then, when used in the city, I would like it to warp the player back to wherever they were in the wilderness.

    I know how to alternate between the two warp destinations easily enough, first by setting and clearing a flag (in item_use.c):

    Code:
    void ItemUseOutOfBattle_EscapeRope(u8 taskId)
    {
        bool8  warpedYet = FlagGet(FLAG_WARP_DEVICE);
        if (!warpedYet)
        {
            FlagSet (FLAG_WARP_DEVICE);
            sItemUseOnFieldCB = ItemUseOnFieldCB_EscapeRope;
            SetUpItemUseOnFieldCallback(taskId);
        }
        else
        {
            FlagClear(FLAG_WARP_DEVICE);
            sItemUseOnFieldCB = ItemUseOnFieldCB_EscapeRope;
            SetUpItemUseOnFieldCallback(taskId);
        }
    }

    And then checking this flag when the escape rope is choosing which location to warp to (field_effect.c, line 18 below):

    Code:
    static void EscapeRopeWarpOutEffect_Spin(struct Task *task)
    {
        struct ObjectEvent *objectEvent;
    +    bool8  warpedYet = FlagGet(FLAG_WARP_DEVICE);
        u8 spinDirections[5] =  {DIR_SOUTH, DIR_WEST, DIR_EAST, DIR_NORTH, DIR_SOUTH};
        if (task->tTimer != 0 && (--task->tTimer) == 0)
        {
            TryFadeOutOldMapMusic();
            WarpFadeOutScreen();
        }
        objectEvent = &gObjectEvents[gPlayerAvatar.objectEventId];
        if (!ObjectEventIsMovementOverridden(objectEvent) || ObjectEventClearHeldMovementIfFinished(objectEvent))
        {
            if (task->tTimer == 0 && !gPaletteFade.active && BGMusicStopped() == TRUE)
            {
                SetObjectEventDirection(objectEvent, task->tStartDir);
                SetWarpDestinationToLastHealLocation();
    +            if (!warpedYet)
    +            {
    +                SetWarpDestinationToLastHealLocation();
    +            }
                WarpIntoMap();
                gFieldCallback = FieldCallback_EscapeRopeWarpIn;
                SetMainCallback2(CB2_LoadMap);
                DestroyTask(FindTaskIdByFunc(Task_EscapeRopeWarpOut));
            }
            else if (task->tSpinDelay == 0 || (--task->tSpinDelay) == 0)
            {
                ObjectEventSetHeldMovement(objectEvent, GetFaceDirectionMovementAction(spinDirections[objectEvent->facingDirection]));
              
                if (gSaveBlock2Ptr->follower.inProgress)
                {
                    ObjectEventClearHeldMovement(&gObjectEvents[gSaveBlock2Ptr->follower.objId]);
                    switch(objectEvent->facingDirection)
                    {
                        case DIR_SOUTH:
                            gSprites[gObjectEvents[gSaveBlock2Ptr->follower.objId].spriteId].x2 = 0;
                            ObjectEventSetHeldMovement(&gObjectEvents[gSaveBlock2Ptr->follower.objId], 25);
                            break;
                        case DIR_NORTH:
                            gSprites[gObjectEvents[gSaveBlock2Ptr->follower.objId].spriteId].x2 = 0;
                            ObjectEventSetHeldMovement(&gObjectEvents[gSaveBlock2Ptr->follower.objId], 26);
                            break;
                        case DIR_WEST:
                            gSprites[gObjectEvents[gSaveBlock2Ptr->follower.objId].spriteId].x2 = 8;
                            ObjectEventSetHeldMovement(&gObjectEvents[gSaveBlock2Ptr->follower.objId], 27);
                            break;
                        case DIR_EAST:
                            gSprites[gObjectEvents[gSaveBlock2Ptr->follower.objId].spriteId].x2 = -8;
                            ObjectEventSetHeldMovement(&gObjectEvents[gSaveBlock2Ptr->follower.objId], 28);
                            break;
                    }
                }
              
                if (task->tNumTurns < 12)
                    task->tNumTurns++;
                task->tSpinDelay = 8 >> (task->tNumTurns >> 2);
            }
        }
    }

    I know what I need to do is to insert a line of code somewhere to save the player's map and coordinate data, then set this as the escape rope's warp destination (depending on flag status). But I am lost on how to do that. Usually I try to think of some occasion in the base game where something similar to what I want to achieve happens already, and use that code as a starting point. But this has stumped me.

    Anyone able to help me with this? Or able to suggest an easier alternative?
     
    Last edited:
    I am trying to make an item which will warp the player between two locations. I am modifying the escape rope code to do this.

    If used in the wilderness, it should teleport the player back to the central hub city. This bit I have done fine.

    Then, when used in the city, I would like it to warp the player back to wherever they were in the wilderness.

    I know how to alternate between the two warp destinations easily enough, first by setting and clearing a flag (in item_use.c):

    Code:
    void ItemUseOutOfBattle_EscapeRope(u8 taskId)
    {
        bool8  warpedYet = FlagGet(FLAG_WARP_DEVICE);
        if (!warpedYet)
        {
            FlagSet (FLAG_WARP_DEVICE);
            sItemUseOnFieldCB = ItemUseOnFieldCB_EscapeRope;
            SetUpItemUseOnFieldCallback(taskId);
        }
        else
        {
            FlagClear(FLAG_WARP_DEVICE);
            sItemUseOnFieldCB = ItemUseOnFieldCB_EscapeRope;
            SetUpItemUseOnFieldCallback(taskId);
        }
    }

    And then checking this flag when the escape rope is choosing which location to warp to (field_effect.c, line 18 below):

    Code:
    static void EscapeRopeWarpOutEffect_Spin(struct Task *task)
    {
        struct ObjectEvent *objectEvent;
    +    bool8  warpedYet = FlagGet(FLAG_WARP_DEVICE);
        u8 spinDirections[5] =  {DIR_SOUTH, DIR_WEST, DIR_EAST, DIR_NORTH, DIR_SOUTH};
        if (task->tTimer != 0 && (--task->tTimer) == 0)
        {
            TryFadeOutOldMapMusic();
            WarpFadeOutScreen();
        }
        objectEvent = &gObjectEvents[gPlayerAvatar.objectEventId];
        if (!ObjectEventIsMovementOverridden(objectEvent) || ObjectEventClearHeldMovementIfFinished(objectEvent))
        {
            if (task->tTimer == 0 && !gPaletteFade.active && BGMusicStopped() == TRUE)
            {
                SetObjectEventDirection(objectEvent, task->tStartDir);
                SetWarpDestinationToLastHealLocation();
    +            if (!warpedYet)
    +            {
    +                SetWarpDestinationToLastHealLocation();
    +            }
                WarpIntoMap();
                gFieldCallback = FieldCallback_EscapeRopeWarpIn;
                SetMainCallback2(CB2_LoadMap);
                DestroyTask(FindTaskIdByFunc(Task_EscapeRopeWarpOut));
            }
            else if (task->tSpinDelay == 0 || (--task->tSpinDelay) == 0)
            {
                ObjectEventSetHeldMovement(objectEvent, GetFaceDirectionMovementAction(spinDirections[objectEvent->facingDirection]));
               
                if (gSaveBlock2Ptr->follower.inProgress)
                {
                    ObjectEventClearHeldMovement(&gObjectEvents[gSaveBlock2Ptr->follower.objId]);
                    switch(objectEvent->facingDirection)
                    {
                        case DIR_SOUTH:
                            gSprites[gObjectEvents[gSaveBlock2Ptr->follower.objId].spriteId].x2 = 0;
                            ObjectEventSetHeldMovement(&gObjectEvents[gSaveBlock2Ptr->follower.objId], 25);
                            break;
                        case DIR_NORTH:
                            gSprites[gObjectEvents[gSaveBlock2Ptr->follower.objId].spriteId].x2 = 0;
                            ObjectEventSetHeldMovement(&gObjectEvents[gSaveBlock2Ptr->follower.objId], 26);
                            break;
                        case DIR_WEST:
                            gSprites[gObjectEvents[gSaveBlock2Ptr->follower.objId].spriteId].x2 = 8;
                            ObjectEventSetHeldMovement(&gObjectEvents[gSaveBlock2Ptr->follower.objId], 27);
                            break;
                        case DIR_EAST:
                            gSprites[gObjectEvents[gSaveBlock2Ptr->follower.objId].spriteId].x2 = -8;
                            ObjectEventSetHeldMovement(&gObjectEvents[gSaveBlock2Ptr->follower.objId], 28);
                            break;
                    }
                }
               
                if (task->tNumTurns < 12)
                    task->tNumTurns++;
                task->tSpinDelay = 8 >> (task->tNumTurns >> 2);
            }
        }
    }

    I know what I need to do is to insert a line of code somewhere to save the player's map and coordinate data, then set this as the escape rope's warp destination (depending on flag status). But I am lost on how to do that. Usually I try to think of some occasion in the base game where something similar to what I want to achieve happens already, and use that code as a starting point. But this has stumped me.

    Anyone able to help me with this? Or able to suggest an easier alternative?
    So if I understood correctly:
    - using the item in the wilderness saves the current position and warps you to a specific spot in the hub city.
    - using the item in the hub city takes you to the saved location in the wilderness.
    - there's no other way to travel between these two locations, so a flag storing the last action is enough to determine which of the two actions should be taken.

    The game already stores several locations in the save file with the type WarpData. You could add a new warp variable there, and copy the player's current position into it whenever the player uses the item in the wilderness, and when the player uses the item in the hub you would then copy the saved position into the destination warp.

    For reference you can see how these operations are done with the escapeWarp.
     
    So if I understood correctly:
    - using the item in the wilderness saves the current position and warps you to a specific spot in the hub city.
    - using the item in the hub city takes you to the saved location in the wilderness.
    - there's no other way to travel between these two locations, so a flag storing the last action is enough to determine which of the two actions should be taken.
    Yep, you've essentially got it.

    The game already stores several locations in the save file with the type WarpData. You could add a new warp variable there, and copy the player's current position into it whenever the player uses the item in the wilderness, and when the player uses the item in the hub you would then copy the saved position into the destination warp.

    For reference you can see how these operations are done with the escapeWarp.
    Thank you. I did get to something a bit like this a while after I posted, but not in a way that works. Likely because I don't properly understand what the code is doing. I tried messing around with the setDynamicWarpWithCoords function like this:

    Code:
    static void EscapeRopeWarpOutEffect_Spin(struct Task *task)
    {
        struct ObjectEvent *objectEvent;
        bool8 warpedYet = FlagGet(FLAG_WARP_DEVICE);
        u8 spinDirections[5] =  {DIR_SOUTH, DIR_WEST, DIR_EAST, DIR_NORTH, DIR_SOUTH};
        if (task->tTimer != 0 && (--task->tTimer) == 0)
        {
            TryFadeOutOldMapMusic();
            WarpFadeOutScreen();
        }
        objectEvent = &gObjectEvents[gPlayerAvatar.objectEventId];
        if (!ObjectEventIsMovementOverridden(objectEvent) || ObjectEventClearHeldMovementIfFinished(objectEvent))
        {
            if (task->tTimer == 0 && !gPaletteFade.active && BGMusicStopped() == TRUE)
            {
                SetObjectEventDirection(objectEvent, task->tStartDir);
                if (!warpedYet)
                {
                    FlagSet(FLAG_WARP_DEVICE);
                    SetDynamicWarpWithCoords(0, gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum, WARP_ID_NONE, gSaveBlock1Ptr->pos.x, gSaveBlock1Ptr->pos.y);
                    SetWarpDestinationToLastHealLocation();
                }
                else
                {
                    FlagClear(FLAG_WARP_DEVICE);
                    SetWarpDestinationToDynamicWarp(WARP_ID_DYNAMIC);
                }
                WarpIntoMap();
                gFieldCallback = FieldCallback_EscapeRopeWarpIn;
                SetMainCallback2(CB2_LoadMap);
                DestroyTask(FindTaskIdByFunc(Task_EscapeRopeWarpOut));
            }

    In practice it sort of half works. When I am in the wilderness, it correctly transports me back to the city. However, when I next use it, instead of transporting back to the wilderness, I get teleported to that random house in Petalburg that it seems to send you to whenever it doesn't know a proper warp destination. I can then return to the city again, though. So I basically end up being alternately warped between Petalburg and the hub city.

    I'm assuming that I've just done something stupid whilst trying to save the player's map data with SetDynamicWarpWithCoords. I saw that it was used in the Union Room code and copied it over from there with a vague attempt to store the (x,y) coordinates (no doubt very badly and in a way which demonstrates I don't have a clue how it works).

    Do you know how to use it properly? I will probably switch to the SetEscapeWarp function at some point in any case. It's just that whenever I see SetEscapeWarp being used, the code seems to be setting the warp to a predefined location rather than dynamically setting it to wherever the player is in that moment.

    Thanks for your help.
     
    Yep, you've essentially got it.


    Thank you. I did get to something a bit like this a while after I posted, but not in a way that works. Likely because I don't properly understand what the code is doing. I tried messing around with the setDynamicWarpWithCoords function like this:

    Code:
    static void EscapeRopeWarpOutEffect_Spin(struct Task *task)
    {
        struct ObjectEvent *objectEvent;
        bool8 warpedYet = FlagGet(FLAG_WARP_DEVICE);
        u8 spinDirections[5] =  {DIR_SOUTH, DIR_WEST, DIR_EAST, DIR_NORTH, DIR_SOUTH};
        if (task->tTimer != 0 && (--task->tTimer) == 0)
        {
            TryFadeOutOldMapMusic();
            WarpFadeOutScreen();
        }
        objectEvent = &gObjectEvents[gPlayerAvatar.objectEventId];
        if (!ObjectEventIsMovementOverridden(objectEvent) || ObjectEventClearHeldMovementIfFinished(objectEvent))
        {
            if (task->tTimer == 0 && !gPaletteFade.active && BGMusicStopped() == TRUE)
            {
                SetObjectEventDirection(objectEvent, task->tStartDir);
                if (!warpedYet)
                {
                    FlagSet(FLAG_WARP_DEVICE);
                    SetDynamicWarpWithCoords(0, gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum, WARP_ID_NONE, gSaveBlock1Ptr->pos.x, gSaveBlock1Ptr->pos.y);
                    SetWarpDestinationToLastHealLocation();
                }
                else
                {
                    FlagClear(FLAG_WARP_DEVICE);
                    SetWarpDestinationToDynamicWarp(WARP_ID_DYNAMIC);
                }
                WarpIntoMap();
                gFieldCallback = FieldCallback_EscapeRopeWarpIn;
                SetMainCallback2(CB2_LoadMap);
                DestroyTask(FindTaskIdByFunc(Task_EscapeRopeWarpOut));
            }

    In practice it sort of half works. When I am in the wilderness, it correctly transports me back to the city. However, when I next use it, instead of transporting back to the wilderness, I get teleported to that random house in Petalburg that it seems to send you to whenever it doesn't know a proper warp destination. I can then return to the city again, though. So I basically end up being alternately warped between Petalburg and the hub city.

    I'm assuming that I've just done something stupid whilst trying to save the player's map data with SetDynamicWarpWithCoords. I saw that it was used in the Union Room code and copied it over from there with a vague attempt to store the (x,y) coordinates (no doubt very badly and in a way which demonstrates I don't have a clue how it works).

    Do you know how to use it properly? I will probably switch to the SetEscapeWarp function at some point in any case. It's just that whenever I see SetEscapeWarp being used, the code seems to be setting the warp to a predefined location rather than dynamically setting it to wherever the player is in that moment.

    Thanks for your help.
    I copy pasted the code from this post and tried it in a clean repo and it seems to work just fine. It makes the escape rope teleport you between the last heal location (your "hub" location I presume) and the changing "wilderness" location. Whatever is causing the Petalburg thing is not in that code snippet.

    I'd still recommend creating a new saveblock variable (and functions for using it) for the wilderness warp, since currently any existing mechanic that uses the dynamic warp would overwrite the saved wilderness location and break the item.
     
    I copy pasted the code from this post and tried it in a clean repo and it seems to work just fine. It makes the escape rope teleport you between the last heal location (your "hub" location I presume) and the changing "wilderness" location. Whatever is causing the Petalburg thing is not in that code snippet.

    I'd still recommend creating a new saveblock variable (and functions for using it) for the wilderness warp, since currently any existing mechanic that uses the dynamic warp would overwrite the saved wilderness location and break the item.
    Haha, I really didn't expect that. I haven't been able to figure out why it wasn't working for me, but I changed to using the escapewarp function and variables and it's now working fine. I will make a new saveblock variable and warp function at some point so it's clean, but will update the OP with the code for now.

    Thanks for your help, I would have spent god knows how long messing around with dynamic warps otherwise.
     
    Back
    Top