Graziano Lenzi - Custom WalkWayPoints

From NWN Lexicon
Jump to: navigation, search

Graziano Lenzi - Custom WalkWayPoints

Custom WalkWayPoints


The following script evolved from the need of more intelligent way for NPCs to move with waypoints. Normally when an NPC is walking waypoints but is interrupted between points (i.e. engages in combat), then the NPC goes to the nearest waypoint and resumes walking the points. This seems normal at first, but what if you actually wanted the NPC to continue pressing forward even if they are near the previous waypoint?


The specific example used is for a simple quest. A caravan of NPCs must be moved from one trading post to another, directed by the PC. Ten waypoints were used, and bandits attacked the caravan in six different places; the chances that the caravan would make no progress after an attack and frustrating the player by returning to the previous waypoint were great.


The following NPCs were created for this particular quest:

  • Caravan Leader (walks the waypoints and interacts with the PC by direct commands in conversation)
  • Caravan Men (follows the Caravan Leader)

Events are scripted to run as follows:

  • OnConversation (Caravan Leader): Listens for particular commands ("Stop", "Go") and sets a local variable "strCommand" with the command ("Stop" or "Go").
  • OnHeartbeat (Caravan Leader): Checks the local variable "strCommand" with GetLocalString():
    • if (strCommand == "Go") then it calls "MyWalkWayPoint2" (see code below) and sets this variable to "WALK_WAY_POINT" (this way because MyWalkWayPoint2 can be called only 1 time for any "Go" spoken by the PC)
    • if (strCommand == "Stop") then the Caravan Leader stops walking (and the Caravan Men do likewise)
  • OnAttack (Caravan Leader): when the bandits attack the caravan, the default OnAttack script fires and performs a ClearAllActions() command.
  • When the attack has ended, the PC can command the Caravan Leader to "Go" again to make the Caravan Leader and his Caravan continue the route (instead of going to the closest waypoint which could potentially be one that has already been past).
  • Custom waypoints cannot be named with the normal "WP_" prefix as this confuses the WalkWayPoints() command found in default scripts (like OnSpawn) and other functions (like DetermineCombatRound()). For the MyWalkWayPoint2 script, you should use the default header of "LW" (or change the value of sWayPointHeader in the script to your own custom header).


    Also note that calling ActionDoCommand() during the OnSpawn event for a creature can cause that creature to assume unpredictable behavior. Because MyWalkWayPoint2() makes use of ActionDoCommand() you should not use this script during the OnSpawn event, but instead place it in a creature's OnHeartbeat event.


    //::///////////////////////////////////////////////////////////
    //:: MyWalkWayPoint2 ()
    //:: version: recursively-standalone
    //::///////////////////////////////////////////////////////////
    //::///
    //::/// don't use in the OnSpawn event:
    //::/// there is a kind of queue-initialize during this event,
    //::/// so this routine will not run;
    //::///
    //::/// If a creature is walking, and a ClearAllAction() occurs,
    //::/// you can call this routine to continue walking to the right
    //::/// direction until end of circuit
    //::///
    //::/// N.B.: the original WalkWayPoint restarts the circuit from
    //::///       the nearest valid WayPoint... nice... but what if the
    //::///       nearest waypoint is behind ?
    //::///
    //::///////////////////////////////////////////////////////////
    //::/// 
    //::/// in simple words....
    //::/// - if no waypoint active to walk to, set it to the
    //::///   first waypoint (if exists),
    //::///   else set the next waypoint;
    //::/// - ActionMoveToObject (waypoint);
    //::/// - Reschedule itself by ActionDoCommand;
    //::///////////////////////////////////////////////////////////
    //::/// it is an asyncronous way to do the same as:
    //::///     while next-waypoint-is-valid
    //::///         move to next-waypoint
    //::///         wait until next-waypoint reached
    //::///         waypoint++
    //::///     end
    //::///////////////////////////////////////////////////////////
     
     
    void MyWalkWayPoint2()
    {
    //::///////////////////////////////////////////////////////////
    //::
    //:: init variables
    //::
    //::///////////////////////////////////////////////////////////
    //::
    //:: NOTE: the WayPoint header (standard is "WP_") has to be different from
    //::       standard. Why? Because we have not to get in conflict with the
    //::       Bioware's WalkWayPoint. This Because the Bioware's WalkWayPoint is
    //::       called in many Bioware's standard scripts...
    //::
    //::///////////////////////////////////////////////////////////
     
        string sLocalWalkToName = "WALK_TARGET";
        string sLocalWalkTo = "";
     
        string  sWayPointHeader = "LW";
        string  sWayPointNum = "01";
        int iWayPointNum = 1;
        string  sWayPoint = "";
        object  oWayPoint;
     
    //::///////////////////////////////////////////////////////////
    //::
    //:: the current destination is stored in an OBJECT_SELF's local variable:
    //::
    //::///////////////////////////////////////////////////////////
     
        sWayPoint = GetLocalString (OBJECT_SELF, sLocalWalkToName);
        oWayPoint = GetObjectByTag (sWayPoint);
     
    //::///////////////////////////////////////////////////////////
    //::
    //:: if local variable exists, we have to find the NEXT WayPoint
    //::
    //::///////////////////////////////////////////////////////////
     
        if (GetIsObjectValid(oWayPoint) && (sWayPoint != "") )
        {
            sWayPoint = GetStringRight(sWayPoint, 2);
            iWayPointNum = StringToInt(sWayPoint);
            if (iWayPointNum > 0)
            {
                iWayPointNum++;
                if (iWayPointNum < 10)
                {
                    sWayPointNum = "0" + IntToString(iWayPointNum);
                } else
                {
                    sWayPointNum = IntToString(iWayPointNum);
                }
                sWayPoint = sWayPointHeader  + "_"  + GetTag(OBJECT_SELF) + "_" + sWayPointNum;
                oWayPoint = GetObjectByTag (sWayPoint);
            }
        }
    //::///////////////////////////////////////////////////////////
    //::
    //:: if local variable doesn't exist, or invalid,
    //:: the creature has to start the circuit, so
    //:: we have to set this local variable to its first waypoint
    //::
    //::///////////////////////////////////////////////////////////
        else
        {
            sWayPoint = sWayPointHeader  + "_"  + GetTag(OBJECT_SELF) + "_" + sWayPointNum;
            oWayPoint = GetObjectByTag (sWayPoint);
        }
     
    //::///////////////////////////////////////////////////////////
    //::
    //:: The waypoint we have found is a valid waypoint:
    //::  - the creature has to move to it!
    //::  - it is possible a next waypoint esists, so,
    //::    after the creature has moved to the new destination,
    //::    it is better to check all again,
    //::    so we put this routine in the Action Queue; 
    //::    doing this way, when creature stops, the first action
    //::    the creature do will be the executing of MyWalkWayPoint2!
    //::    
    //:: The waypoint we have found is not valid, it doesn't exist:
    //::  - the creature has reached the end of the circuit, so
    //::    it is a good way to reset the local variable and to stop
    //::
    //::///////////////////////////////////////////////////////////
     
     
        if (GetIsObjectValid(oWayPoint) )
        {
            SetLocalString(OBJECT_SELF, sLocalWalkToName,sWayPoint);
            ActionMoveToObject(oWayPoint);
            ActionDoCommand(MyWalkWayPoint2() );
        } 
    //::///////////////////////////////////////////////////////////
    //::    
    //:: The waypoint we have found is not valid, it doesn't exist:
    //::  - the creature has reached the end of the circuit, so
    //::    it is a good way to reset the local variable and to stop
    //::
    //::///////////////////////////////////////////////////////////
     
        else
        {
            SetLocalString(OBJECT_SELF, sLocalWalkToName,"");
        }
     
    //::///////////////////////////////////////////////////////////
    //::    
    //:: return to the main call
    //::
    //::///////////////////////////////////////////////////////////
     
        return;
    }




     author: Graziano Lenzi, editors: Charles Feduke, Mistress, contributor: Ken Cotterill