Skip to content

Waypoint Script

How to make a script only fire on waypoints in a certain area only.
«1

Comments

  • ProlericProleric Member Posts: 1,281
    Not sure what you're trying to do, exactly?

    Waypoints don't have events, so they don't fire scripts.

    Some things you can do:
    • Area OnHeartbeat can run a script if PC is close to a waypoint
    • A trigger around a waypoint can fire a script OnEnter
    • WalkWayPoints() can be modified in an area (but not so easy)
  • TheTinmanTheTinman Member Posts: 72
    I have a script that fires at a waypoints location. But it fires at all waypoints in the whole module. Was curious if i could make it only fire at waypoints in a given area.
  • TerrorbleTerrorble Member Posts: 169
    Definitely need to know more about what the script is doing and especially what is calling the script to setup the variables. At a basic level, if you know what area you are in, then you can cycle through all the objects in the area and do something if you identify a waypoint.
    //Define the area object by getting the area (that something-like a player-is currently in).
    object oArea = GetArea(...);
    
    object oWP;
    
    //Get the first object in that area (mind you, this object could be anything in that area and not necessarily a waypoint)
    oWP = GetFirstObjectInArea(oArea);
    
    while( GetIsObject Valid(oWP) )
    {
    	//If what we found is actually a waypoint, then run this script or do whatever.
    	if( GetObjectType(oWP) == OBJECT_TYPE_WAYPOINT )
    	{
    		ExecuteScript(...);
    	}
    //Get the next object in the area
    oWP = GetNextObjectInArea(oArea);
    }
    
  • TheTinmanTheTinman Member Posts: 72
    void main()
    {
    // The PC.
    object oPC;
    
    // Only fire once per PC.
    if ( GetLocalInt(OBJECT_SELF, "DO_ONCE__" + ObjectToString(oPC)) )
    return;
    SetLocalInt(OBJECT_SELF, "DO_ONCE__" + ObjectToString(oPC), TRUE);
    
    
        object oArea = OBJECT_SELF;
        object oObject = GetFirstObjectInArea(oArea);
        while(GetIsObjectValid(oObject))
        {
             if(GetTag(oObject) == "junk1")
    
             {
    
             CreateObject( OBJECT_TYPE_CREATURE, "NW_RAT001", GetLocation(oObject));
    
             }
             oObject = GetNextObjectInArea(oArea);
        }
    }
    
  • TheTinmanTheTinman Member Posts: 72
    edited February 2023
    This works now, except it spawns 3 rats at each waypoint. I tried it in a triggers on enter event. and on an invisible objects heartbeat script.

    Could the PC, the script, and the spawned rat each be firing this script somehow?
    Post edited by TheTinman on
  • meaglynmeaglyn Member Posts: 149
    edited February 2023
    You haven't said what event is firing the script. Since you are using OBJECT_SELF as the area I assume it's one of the area scripts probably OnEnter. You might want to initialize oPC with GetEnteringObject() and then maybe add "if (!GetIsPC(oPC)) return;" after that (and before the DO_ONCE part).

    Edit: the above will also work on a trigger's onEnter event.
  • MelkiorMelkior Member Posts: 181
    This sounds like the type of question asked by someone who doesn't know what question to ask. I know this because I've often been in that situation in the past. ;)

    Probably the reason why you're spawning multiple rats is because every time a rat is spawned, that counts as something "entering" the area and re-triggers the script to spawn another rat which then re-triggers the script and so on, although I'm unsure why it doesn't just keep triggering until the game crashes. Maybe there's a safety feature built into the game to prevent that, similar to the Too Many Instructions error.

    If I'm correct, then doing what Meaglyn suggested will fix the problem as it will abort the script if the entering object isn't a PC.
  • TheTinmanTheTinman Member Posts: 72
    Added the PC as the entering object and got no change. It still spawns 3 rats at each waypoint. :/
  • TarotRedhandTarotRedhand Member Posts: 1,481
    edited February 2023
    I have a variation on the last script you posted you might like to try. Note - I have not tested this but it should work (I can't see any obvious syntax errors but...). Try -

    void MakeRats(object oPC, object oArea)
    {
        object oObject;
        string sThisVariable = "DO_ONCE__" + ObjectToString(oPC)); // only need to call ObjectToString(oPC)) once
        string sThisTag = "junk1"; // by using a variable you only have to type the tag once
        int iIndex = 1;
    
        // Only fire once per PC.
    
        if(!(GetLocalInt(OBJECT_SELF, sThisVariable))
        {
            SetLocalInt(OBJECT_SELF, sThisVariable, TRUE);
    
            oObject = GetNearestObjectByTag(sThisTag, oPC, iIndex++); // just get objects with tag of "junk1" that are closest to PC
    
            while(GetIsObjectValid(oObject))
            {
                if(GetArea(oObject) != oArea) // all objects with tag of "junk1" in oArea have been found
                    break;
            
                CreateObject(OBJECT_TYPE_CREATURE, "NW_RAT001", GetLocation(oObject));
    
                oObject = GetNearestObjectByTag(sThisTag, oPC, iIndex++);
            }
        )
    }
    
    void main()
    {
        object oPC GetEnteringObject();
        object oArea = OBJECT_SELF;
    
        if(GetIsPC()) // only create rats for actual PCs
            DelayCommand(1.0, MakeRats(oPC, oArea)); // delay to allow PC to finish entering area
    }
    

    I have added a one second delay before the rat creation actually fires. This is because it might be that the reason for multiple rats being created is that the PC hasn't fully entered the area when your code runs. In order to do this it was necessary to create a separate void function. That is because the DelayCommand() function needs a function to call, that returns a void.

    Also, I noticed that your code was stepping through all the objects in an area, testing each to see if they had the right tag. This seemed to me to be inefficient. I have used the GetNearestObjectByTag() function. This does have one drawback though, in that it will return objects with the required tag even those not in the area the PC just entered. In order to stop this I have included a test in the while loop that exits the loop once the returned object is in another area.

    FWIW, there is a more efficient way to do this but it requires a bit more work on your end. Instead of giving all waypoints the exact same tag, you would need to enumerate them. So instead of them all having the tag "junk1" you would instead have only one with that exact tag. So for three waypoints with tags of "junk1", "junk2" and "junk3" respectively. That would allow you to use the following function instead of the one I posted above -

    void MakeRats(object oPC, object oArea) // more efficient version - only works where you give each waypoint a different number
    {
        object oObject;
        string sThisVariable = "DO_ONCE__" + ObjectToString(oPC)); // only need to call ObjectToString(oPC)) once
        string sThisTag;
        int iIndex;
    
        // Only fire once per PC.
    
        if(!(GetLocalInt(OBJECT_SELF, sThisVariable))
        {
            SetLocalInt(OBJECT_SELF, sThisVariable, TRUE);
    
            for(iIndex = 0; iIndex < 11 ; iIndex++) // assuming there are 10 consecutively numbered waypoints
            {
                sThisTag = "junk" + IntToString(iIndex);
                
                oObject = GetObjectByTag(sThisTag, iIndex); // just get objects with tag of "junk1" that are closest to PC
                
                if(!(GetIsObjectValid(oObject)))
                    continue;
            }
        )
    }
    

    Hope this is useful.

    TR
  • meaglynmeaglyn Member Posts: 149
    GetNearestObjectByTag() should not be returning objects from a different area than the target object. I have not seen that happen and it would break a lot of stuff if it did.

    TheTinman: did you add the check for it being a PC? And is this on the area enter event?
  • TheTinmanTheTinman Member Posts: 72
    Yes, and yes.
  • TarotRedhandTarotRedhand Member Posts: 1,481
    GetNearestObjectByTag() should not be returning objects from a different area than the target object.

    My error. According to the lexicon it's GetObjectByTag() that will search in other areas.

    TR
  • meaglynmeaglyn Member Posts: 149
    GetObjectByTag() doesn't search areas at all. It just looks at the (hash table I think?) of tags of objects in the game. Areas don't come into it :)

    An interesting (maybe?) note about that is that the difference between those two is essentially how cross area waypoint walking is implemented.
  • meaglynmeaglyn Member Posts: 149
    Can you post what it looks like now?
  • TarotRedhandTarotRedhand Member Posts: 1,481
    edited February 2023
    To quote the lexicon -
    Remarks
    The nNth parameter has been determined to return the nNth object with the specified sTag starting at position 0. Outside of the current area, the scan will begin starting with the last (most recent) area added to the module following this hierarchy:

    OBJECT_TYPE_STORE (128)
    OBJECT_TYPE_PLACEABLE (64)
    OBJECT_TYPE_WAYPOINT (32)
    OBJECT_TYPE_AREA_OF_EFFECT (16) (spell effects, like web)
    OBJECT_TYPE_DOOR (8)
    OBJECT_TYPE_TRIGGER (4)
    OBJECT_TYPE_ITEM (2)
    OBJECT_TYPE_CREATURE (1)

    GetObjectByTag can also be used to get areas, but not the module (use GetModule() instead). It is unknown what will be returned if an area has the same tag as a non-object area.

    @TheTinman Does it still create 3 instead of a single rat and did you try any of the code I posted?

    FWIW here is the code without the area check -
    void MakeRats(object oPC, object oArea)
    {
        object oObject;
        string sThisVariable = "DO_ONCE__" + ObjectToString(oPC)); // only need to call ObjectToString(oPC)) once
        string sThisTag = "junk1"; // by using a variable you only have to type the tag once
        int iIndex = 1;
    
        // Only fire once per PC.
    
        if(!(GetLocalInt(OBJECT_SELF, sThisVariable))
        {
            SetLocalInt(OBJECT_SELF, sThisVariable, TRUE);
    
            oObject = GetNearestObjectByTag(sThisTag, oPC, iIndex++); // just get objects with tag of "junk1" that are closest to PC
    
            while(GetIsObjectValid(oObject))
            {
                CreateObject(OBJECT_TYPE_CREATURE, "NW_RAT001", GetLocation(oObject));
    
                oObject = GetNearestObjectByTag(sThisTag, oPC, iIndex++);
            }
        )
    }
    
    void main()
    {
        object oPC GetEnteringObject();
        object oArea = OBJECT_SELF;
    
        if(GetIsPC()) // only create rats for actual PCs
            DelayCommand(1.0, MakeRats(oPC, oArea)); // delay to allow PC to finish entering area
    }
    

    TR
  • TheTinmanTheTinman Member Posts: 72
    void MakeRats(object oPC, object oArea)
    {
        object oObject;
        string sThisVariable = "DO_ONCE__" + ObjectToString(oPC)); // only need to call ObjectToString(oPC)) once
        string sThisTag = "junk1"; // by using a variable you only have to type the tag once
        int iIndex = 1;
    
        // Only fire once per PC.
    
        if(!(GetLocalInt(OBJECT_SELF, sThisVariable))
        {
            SetLocalInt(OBJECT_SELF, sThisVariable, TRUE);
    
            oObject = GetNearestObjectByTag(sThisTag, oPC, iIndex++); // just get objects with tag of "junk1" that are closest to PC
    
            while(GetIsObjectValid(oObject))
            {
                if(GetArea(oObject) != oArea) // all objects with tag of "junk1" in oArea have been found
                    break;
            
                CreateObject(OBJECT_TYPE_CREATURE, "NW_RAT001", GetLocation(oObject));
    
                oObject = GetNearestObjectByTag(sThisTag, oPC, iIndex++);
            }
        )
    }
    

    Line 4 caused an error parsing variable list code
  • TarotRedhandTarotRedhand Member Posts: 1,481
    edited February 2023
    Here is a version with all the syntax errors fixed -

    void MakeRats(object oPC, object oArea)
    {
        object oObject;
        string sThisVariable = "DO_ONCE__" + ObjectToString(oPC);
        string sThisTag = "junk1"; // by using a variable you only have to type the tag once
        int iIndex = 1;
    
        // Only fire once per PC.
    
        if(!(GetLocalInt(OBJECT_SELF, sThisVariable)))
        {
            SetLocalInt(OBJECT_SELF, sThisVariable, TRUE);
    
            oObject = GetNearestObjectByTag(sThisTag, oPC, iIndex++); // just get objects with tag of "junk1" that are closest to PC
    
            while(GetIsObjectValid(oObject))
            {
                CreateObject(OBJECT_TYPE_CREATURE, "NW_RAT001", GetLocation(oObject));
    
                oObject = GetNearestObjectByTag(sThisTag, oPC, iIndex++);
            }
        }
    }
    
    void main()
    {
        object oPC = GetEnteringObject();
        object oArea = OBJECT_SELF;
    
        if(GetIsPC(oPC)) // only create rats for actual PCs
            DelayCommand(1.0, MakeRats(oPC, oArea)); // delay to allow PC to finish entering area
    }
    

    Still haven't tested it though.

    TR
  • meaglynmeaglyn Member Posts: 149
    The lexicon is not always right. I don't believe GetObjectByTag() searches through areas to find things. Yes, it can return areas but that's different. There was at one point a whole long discussion of the performance differences of the two (this one and GetNearestObjectByTag()). I'm pretty sure it was determined that gobt was more efficient due to being just a lookup in a table/list.
    Also, if it looked at the current area first people would not hit the issues they do of getting the wrong object with a non-unique tag when they should be using GetNearest.

    More recently created objects will be towards the front so those objects in the most recently added area would tend to be returned earlier.

    Someone from Beamdog with access to the source code could tell us for sure though...

    @Thetinman: There is an extra closing paren, ")", on that line. Remove one and try again.
  • TheTinmanTheTinman Member Posts: 72
    void CreatVoid (object oObject) { }

    void main()
    {

      / The PC.
    object oPC;

    // Only fire once per PC.
    if ( GetLocalInt(OBJECT_SELF, "DO_ONCE__" + ObjectToString(oPC)) )
    return;
    SetLocalInt(OBJECT_SELF, "DO_ONCE__" + ObjectToString(oPC), TRUE);

      object oSP =  GetFirstObjectInArea();

        while(GetIsObjectValid(oSP))
        {
            if(GetObjectType(oSP) == OBJECT_TYPE_WAYPOINT
            && GetTag(oSP) == "junk1")
            {
             DelayCommand(0.1,  CreatVoid(  CreateObject(OBJECT_TYPE_CREATURE, "nw_rat001", GetLocation(oSP), FALSE)));
            }
        oSP = GetNextObjectInArea();
        }
    }

    How about this way?
  • meaglynmeaglyn Member Posts: 149
    TheTinman wrote: »
    ... 
     / The PC.
    object oPC;
    ...

    How about this way?

    This part really wants to be
    //the PC.
    object oPC = GetEnteringObject():
    if (!GetIsPC(oPC)) return;
    

    Then I'd go with GetNearestObjectByTag(oPC, OBJECT_TYPE_WAYPOINT, "junk1") to find them the waypoint. If there are multiple you can still use a loop on the nth argument (after the tag).

    There should be no reason to delay the creation unless you want to. Area onEnter should not fire until the PC is actually in the area (unlike oncliententer where the PC may not be in a area when it fires).

  • TarotRedhandTarotRedhand Member Posts: 1,481
    edited February 2023
    Then I'd go with GetNearestObjectByTag(oPC, OBJECT_TYPE_WAYPOINT, "junk1")

    You have the wrong parameter list there. I think you have mixed up the GetNearestObjectByTag() with GetNearestObject() here. From the lexicon -

    object GetNearestObjectByTag(
        string sTag,
        object oTarget = OBJECT_SELF,
        int nNth = 1
    );
    
    vs.

    object GetNearestObject(
        int nObjectType = OBJECT_TYPE_ALL,
        object oTarget = OBJECT_SELF,
        int nNth = 1
    );
    

    TR
  • meaglynmeaglyn Member Posts: 149
    Indeed I do. I should have looked them up instead of relying on aging memory... thanks for keeping me honest :)
  • TheTinmanTheTinman Member Posts: 72
    edited February 2023
    Finally got it working! Tweeked it a bit so i could spawn any standard or custom creature with this one script. Works great! Thanks for all the help everyone.
    //The Tinmans Simple "Spawns All" script.
    //Can be used with a Triggers on enter event,  an Areas on enter event, or an invisible objects Heartbeat event.
    
    void CreatVoid (object oObject) { }
    
    void main()
    {
    
    // Only fires for the PC
    object oPC; //= GetEnteringObject();
    //if(!GetIsPC(oPC)) { return; }
    
    // Only fire once per PC.
    if ( GetLocalInt(OBJECT_SELF, "DO_ONCE__" + ObjectToString(oPC)) )
    return;
    SetLocalInt(OBJECT_SELF, "DO_ONCE__" + ObjectToString(oPC), TRUE);
    
    //The Waypoint Spawner
    object oSP =  GetFirstObjectInArea();
    
    //Checks for Waypoints in the Area
    while(GetIsObjectValid(oSP))
        {
    
    //The Tag of the Waypoint
            if(GetObjectType(oSP) == OBJECT_TYPE_WAYPOINT
            && GetTag(oSP) == "Spawner")
            {
    
    //Change the name of the waypoint to the Tag of a Standard Creature.  Change the name of the waypoint to the ResRef of a Custom Creature.
             string aNAME = GetName(oSP);
    
    //Slight delay to fix multiple creature spawns
             DelayCommand(0.1,  CreatVoid(  CreateObject(OBJECT_TYPE_CREATURE, aNAME, GetLocation(oSP), FALSE)));
            }
    
    //Loops through all of the Waypoints with the Spawner tag.
        oSP = GetNextObjectInArea();
        }
    }
    
    Post edited by TheTinman on
  • meaglynmeaglyn Member Posts: 149
    Why do you have the PC check (and the initialization of oPC) commented out? It will probably only fire once for one PC not once per PC as the comment implies. Maybe that's all you need...?
  • TheTinmanTheTinman Member Posts: 72
    I commented them out when testing it with the invisible objects heartbeat script. It works fine with them commented out.
  • TheTinmanTheTinman Member Posts: 72
    Works for me because I have an on exit script that resets that "do once" integer. My bad.
  • TheTinmanTheTinman Member Posts: 72
    After multiple tests, this is my revised script. Seems to work well.
    //The Tinmans Simple "Spawns All" script.
    //Can be used with a Triggers on enter event, or an Areas on enter event.
    
    void CreatVoid (object oObject) { }
    
    void main()
    {
    
    // Only fires for the PC
    object oPC = GetEnteringObject();
    
     //If this line is commented out it can be used on an objects heartbeat script
    if(!GetIsPC(oPC)) { return; } 
    
    // Only fire once per PC.
    if ( GetLocalInt(OBJECT_SELF, "DO_ONCE__" + ObjectToString(oPC)) )
    return;
    SetLocalInt(OBJECT_SELF, "DO_ONCE__" + ObjectToString(oPC), TRUE);
    
    //The Waypoint Spawner
    object oSP =  GetFirstObjectInArea();
    
    //Checks for Waypoints in the Area
    while(GetIsObjectValid(oSP))
        {
    
    //Change the tag of the Waypoint to whatever is needed.
            string tNAME = GetTag(oSP);
            if(GetObjectType(oSP) == OBJECT_TYPE_WAYPOINT
            && GetTag(oSP) == tNAME)
            {
    
    //Change the name of the waypoint to the Tag of a Standard Creature.  Change the name of the waypoint to the ResRef of a Custom Creature.
             string aNAME = GetName(oSP);
    
    //Slight delay to fix multiple creature spawns
             DelayCommand(0.1,  CreatVoid(  CreateObject(OBJECT_TYPE_CREATURE, aNAME, GetLocation(oSP), FALSE)));
            }
    
    //Loops through all of the Waypoints with the Spawner tag.
        oSP = GetNextObjectInArea();
        }
    }
    
  • MelkiorMelkior Member Posts: 181
    The script above should not even compile, and even if it compiles, should not run, and even if it runs, would be very "fragile" and break easily.
    It shouldn't compile or run because you're defining variables within a loop, and in C you can only define a variable once. While it's possible that NWN scripting has "loosened" that up a bit, whenever I've tried it, it's cause an error.
    Also, in these lines:
    //Change the tag of the Waypoint to whatever is needed.
            string tNAME = GetTag(oSP);
            if(GetObjectType(oSP) == OBJECT_TYPE_WAYPOINT
            && GetTag(oSP) == tNAME)
            {
    
    You are getting an object's name and then comparing that name to the name of the same object, so "GetTag(oSP)==tNAME" will always return TRUE. All you're doing is applying the CreatVoid function to ALL waypoints in the area, not to only "spawn" waypoints.
    To make it work correctly, you should be setting tNAME equal to the tag which you use for all of the spawn waypoints, before entering the loop.
  • TheTinmanTheTinman Member Posts: 72
    That is strange. I tested it multiple times before posting to make sure it worked. (Didn't want to post a broken script.)
  • MelkiorMelkior Member Posts: 181
    The first part is a bit of a head-scratcher for me, but either EE has loosened up the rules a bit or I was mistaken about the error when declaring a variable while in a loop.

    As for the second part (waypoints always being selected), what's happening is that the script is attempting to create an enemy for all waypoints in the area, but because it depends on the waypoint name to find the ResRef of the enemy, it's attempting to create something which isn't in the blueprints for all waypoints which are not monster-creation waypoints. This is fragile because if you happen to accidentally name a waypoint the same as a creature ResRef which you've created, the creature will be created at that waypoint even when you didn't intend it to.

    It also wastes time while your script attempts to create a non-existent creature blueprint for each waypoint which is not a monster-creation waypoint.

    But then again... perhaps I'm just a perfectionist. I do tend that way with my scripting.

    The summary is that the mistakes might not matter for this script since it works, but it's useful to follow good scripting habits because sooner or later, a bad habit could cause problems when it does matter.
Sign In or Register to comment.