DMH-Online

Dungeon Masters Helper · Est. 2005

Waypoints Scripts

NWN Script: hourly_003.htm

Author Sticky: Post Working Scripts Here
Rhone11 Posted: Sunday, 28 July 09:16PM
Hour-based Waypoint Walker version 1.2

This is the second update to my Hour-based Waypoint Walker. It includes a couple bug fixes, a change to help with "NPC getting stuck in a PC-less area" situations, a small change to the numbering (now use 00-23 for the hours instead of 01-24--and use 24 for 24-hour waypoints instead of 00), and support for ambient animations at posts. See the introductory comments for details.

If you're installing this for the first time in your module, follow the directions in the comments.

If you're upgrading from version 1.1, just replace your rh_inc_hrwpoints and your heartbeat script with the new ones posted below, change any existing 24-hour waypoints to 24 instead of 00 (e.g. what was POST_00_NPCTag should now be POST_24_NPCTag), and change any existing midnight waypoints from 24 to 00 (e.g. what was WP_24_NPCTag_01 should now be WP_00_NPCTag_01).

If you're upgrading from version 1.0, follow the directions for upgrading to 1.1 using the scripts in this post instead of the 1.1 scripts, and, as directed above, update any existing tags for 24-hour or midnight waypoints/posts. The instructions for upgrading to version 1.1 can be found here (third post from the top).

Check the "common issues" section of the comments for tips on dealing with some tricky situations. This should be relatively bug free now, but if you think you've found one, feel free to E-mail me or send me a message a forum message.

Here are the scripts:

rh_inc_hrwpoints:

NWScript:
/*******************************************************************************
Name:      Rhone's Hour-based WayPoint Walker
Author:    Thomas J. Hamman (Rhone)
E-Mail:    hammant@panther.adelphi.edu
Filename:  RH_INC_HRWPOINTS
Version:   1.2
_Changelog_
    _v1.2 (7-27-02)_
1.  A couple things that could lead to infinite looping under certain circumstances
    have been fixed.
2.  The heartbeat script now will not try to run HourWayPoints if the NPC is in
    combat or a conversation.
3.  ActionForceMoveToObject is now used for both posts and waypoints (previously
    it was used only for posts and ActionMoveToObject was used for waypoints, which
    is what the original WalkWayPoints code uses).  If there is a PC in the area,
    the function is called with a timeout value of 60.0 (the same value used for
    posts in the original code), but now if there is no PC in the NPC's area,
    a timeout of 20.0 is used.  This means that if there is no PC in the area and
    the NPC gets "stuck", the NPC will be jumped to his next waypoint or post
    after 20 seconds.
4.  The hour numbers are now more sensible, correct, and consistent with numbers
    returned by GetTimeHour().  For some reason I used 01-24 (with 24 being midnight)
    before.  Don't ask me why--it was probably in the middle of one of my
    up-until-5am-working-on-scripts nights.  00-23 (with 00 being midnight) are
    now the numbers to use for hour-based waypoints.  This also means that 24-hour
    waypoints (which previously used "00") now use 24.  So, if you previously had
    any midnight waypoints, change the 24 to 00, and if you previously had any
    24-hour waypoints, change 00 to 24.  This goes for both the waypoint tags and
    the user-defined event numbers (e.g. WP_00_NPCTag_02 and 90002 for a second
    midnight waypoint, POST_24_NPCTag and 92400 for a 24-hour post).
5.  Ambient animations can now easily be toggled on for specific posts.  See #8
    in the list of features below for directions on how to use them.
    _v1.1 (7-19-02)_
1.  NPCs now keep track of which waypoint they were walking toward with a local
    variable.  After an interruption, they will resume heading toward the next
    waypoint instead of simply going toward the nearest waypoint.
2.  The directions (see below the description) have been changed to instruct users
    to use SignalEvent(OBJECT_SELF, UserDefinedEvent(2000)) in place of
    HourWayPoints in their scripts.  Now, to have different NPCs use HourWayPoints
    with different parameters, only their OnUserDefined scripts need to be different.
    The OnHeartbeat script provided incorporates this change, and a default
    OnUserDefined script is included now as well.
    _v1.0 (7-18-02)_
Original release.

_Description_
This is a complete rewrite of the waypoint walking system in NW_I0_GENERIC,
using the original as a rough guide.  Here are its advantages:
1.  Hour-based waypoints--the main feature.  It allows for NPCs to easily be
    given daily schedules.  The tags for hour-based waypoints and posts
    (respectively) use this format:
        WP_XX_NPCTag_YY
        POST_XX_NPCTag
    Where XX is the hour -- 00 through 23 -- and YY is the order number for the
    waypoint.  NPCTag is, of course, the tag of the NPC who uses the waypoint.
    24-hour waypoints can still be used, with 24 in place of XX, and Day/Night
    waypoints can still be used with DY (for day) and NT (for night) in place of
    XX.  You must choose one system for each NPC, though--if Bob has the waypoint
    WP_24_Bob_01 then the script will never check for day/night or hour-based
    waypoints for Bob, since that wouldn't make sense.  Likewise, if Bob has
    POST_DY_Bob then the script will not check for hour-based waypoints for
    him.
2.  Circular waypoints--follow the waypoints as, for example, 1 2 3 4 1 2 3 4,
    instead of 1 2 3 4 3 2 1.  To use this, have any script for the NPC that
    runs HourWayPoints call it with the nCircle parameter TRUE, e.g.:
        HourWayPoints(FALSE, 1.0, TRUE);
3.  Terminal waypoints--i.e. follow the waypoints through to the end once, and
    then stop.  This is done by having waypoints and a post for the same time;
    the NPC will go through the waypoints once, ending and stopping at the post.
4.  The ability to run a script at each waypoint.  This is done by putting
    a script in the NPC's OnUserDefined event, with event numbers in this format:
        9XXYY
    XX is the hour of the waypoint (00 through 23 -- or 24 for 24-hour waypoints,
    25 for day waypoints, and 26 for night waypoints), and YY is the order
    number for the waypoint (use 00 for posts).  Here are some examples:
        91200   A noon post.
        90503   The third waypoint at 5am.
        92405   The fifth 24-hour waypoint.
        92501   The first day waypoint.
        92600   An all-night post.
    This opens up a world of possibilities, starting with very simple but fun
    things like having Bob go to POST_16_Bob, which triggers his 91600 event
    that has him sit in a chair.
5.  A workaround for the current bug in the GetFacing function, which allows NPC's
    to face the correct direction at their posts.  The workaround will be harmless
    when GetFacing is fixed.
6.  NPCs remember the last waypoint they were walking toward and now walk toward
    it (rather than simply the nearest waypoint) when they resume waypoints after
    an interruption.
7.  If an NPC in an area without a PC gets "stuck" (which often happens), he will
    be jumped to his next waypoint after about 20 seconds.
8.  Ambient animations can be easily toggled on for certain posts, using a
    "nAmbientAnimations" variable on the NPC.  In the user-defined event for a
    post, simply set nAmbientAnimations for the NPC to 1 (for mobile ambient
    animations) or 2 (for immobile ambient animations).  The OnHeartbeat will
    then run the ambient animations (as long as the NPC isn't busy in combat or
    conversation).  HourWayPoints safely resets nAmbientAnimations back to 0
    whenever it sends the NPC to another waypoint/post, so the animations will
    only be used where you want them to be used and won't interrupt waypoint
    walking.
_Common issues and how to deal with them_
If you call "HourWayPoints()" in a script (which you should only be doing in the
user defined event 2000), and the compiler gives you a declaration error, try
writing out the full paramters, e.g.:  HourWayPoints(FALSE, 1.0, FALSE)
I honestly have no idea why the compiler won't let my own functions just use
default parameters.
The problem with NPCs being stuck in areas without NPCs--unable to leave their
homes and go to their workplaces, for example--is now worked around by jumping
them to their next waypoint after 20 seconds of being stuck if there's no PC in
the area.  This could be problematic, however, if you actually _want_ an NPC to
get stuck and be unable to reach a waypoint.  If you have a situation like that
planned, you can temporarily set nDontWalkWP to TRUE (using SetLocalInt) for the
NPC, which will prevent HourWayPoints from running for that NPC.  If you want NPCs
to be able to get stuck as the general rule, however, you'll have to change the
ActionForceMoveToObject functions (there are 4 of these--2 in HourRunCircuit and
2 in HourGoToPost) to ActionMoveToObject (don't forget to take out the last two
parameters, which are used in ActionForceMoveToObject but not ActionMoveToObject).
If you make that change, though, don't come crying to me when your NPCs never leave
their houses. :) 
Speaking of waypoints and areas, please note that NPCs will not go straight from
a spot in one area to a waypoint that requires crossing more than one area
transition; the pathfinding just doesn't handle that, or at least didn't in my
tests.  If you want an NPC to go from, say, the second floor of his house to a
post at the local pub (which requires at least three area transitions), put a
waypoint (number 1) in the first floor of his house, a second waypoint outside his
house, and then the post in the pub.  That way none of the ActionForceMoveTos are
trying to send him through more than one area transition.
NPC pathfinding is absolutely abysmal when they have to go through a non-transition
door.  They get to area transition doors okay, but with a non-transition door they
generally just try to walk in a straight line to their waypoint and stop when
they bump into a wall.  To avoid this problem, make the path for the NPC explicit
by placing a waypoint directly in front of the door, and the next waypoint directly
on the other side of the door.  That way the NPC will bump into the door instead
of the wall, which will trigger his OnBlocked script to open it.
There's also a friendly little bug I can take no credit for which causes NPCs
walking waypoints to sometimes head in the wrong after an area transition until
they bump into something, at which point they _then_ head to a waypoint/post.  One
workaround is to use an OnEnter script for your areas which checks to see if the
entering object isn't a PC, and if so, to run HourWayPoints() for the NPC to
send it in the right direction.
Finally, the action queue can be a bit tricky when you're making your user-defined
waypoint scripts.  All I can tell you is that sometimes you have to experiment a
bit.  If an action isn't taking its turn in the action queue like it should, or
you need something that's not explicitly an action to fit into the action queue,
try putting it in a separate function and calling it inside ActionDoCommand.
_INSTRUCTIONS for using the code_
Considering this has to replace a function in a standard include file that is
included by many default scripts (several of which call the WalkWayPoints function
which we're trying to replace), the instructions unfortunately cannot be as
simple as "just put this script in this place and you're all set."  If you're
trying this in an existing module you're working on, consider making a backup
copy first.  With that aside, here is a set of steps to follow:
1.  Save this script in the script editor with whatever name you like.  For the
    rest of these instructions I'll assume you saved it as rh_inc_hrwpoints.
2.  Open nw_i0_generic in the script editor (you'll have to click on All Resources
    for it to show up).  Yes, we must tamper with this very important include file.
    After the comments and before any of the code, put in this line:
//        #include "RH_INC_HRWPOINTS"
    (Don't include the slashes--for some reason when I try saving this the
    compiler reads the #include as if it's not commented unless I use a C++ style
    comment with it.)
3.  Still in nw_i0_generic, scroll down to the end of the DetermineCombatRounds
    function, right before the SetListeningPatterns function, and look for code
    that looks like this:
        ClearAllActions();
        SetLocalObject(OBJECT_SELF, "NW_GENERIC_LAST_ATTACK_TARGET", OBJECT_INVALID);
        WalkWayPoints();
    The line numbers where this is should be in the 750s.  Running a Find on
    "WalkWayPoints()" (without the quotes) should find it.  What you need to
    do there is change the WalkWayPoints(); line to this:
        SignalEvent(OBJECT_SELF, EventUserDefined(2000));
    Now DetermineCombatRound will use the new function to return NPCs to their
    waypoints/posts after they finish combat.  (The purpose of using SignalEvent
    will be explained.)
4.  Save the edited nw_i0_generic under a new name--for the rest of these
    instructions I'll assume you saved it as my_i0_generic.
5.  Now what's important is that any script which calls WalkWayPoints() needs to
    be changed so that it runs SignalEvent(OBJECT_SELF, EventUserDefined(2000))
    instead, and includes my_i0_generic instead of nw_i0_generic.  In particular
    you need to edit these default NPC scripts:
    nw_c2_default9:  This is the default OnSpawn script for NPCs.
                     Just change WalkWayPoints() (near the end) to the SignalEvent
                     command given above and replace the include line for
                     NW_I0_GENERIC with one that includes MY_I0_GENERIC instead.
    nw_c2_default3:  This is the default OnCombatRoundEnd script.  Just change
                     the include from NW_I0_GENERIC to MY_I0_GENERIC.
6.  Save those files.  If you are going to use the new waypoint script for just
    a few NPCs and want to use the default WalkWayPoints for the rest, then save
    those files under a new name and use those in place of the defaults for the
    appropriate NPCs.  If you want all NPCs with waypoints to use the new waypoint
    script then don't be shy about saving those files with the default names so that
    they are used automatically when you create new NPCs.  This WILL NOT hurt the
    original scripts.  The toolset's built-in scripts are kept separate from your
    own scripts, and you can easily revert to the original scripts by deleting the
    new ones of the same name.  Your "overriding" scripts also don't affect other
    modules.
7.  The OnHeartbeat script for NPCs needs some added code.  A modified OnHeartbeat
    script should be included in the Post/Archive/Whatever where you found this.
    Save that as nw_c2_default1 to use it as the default OnHeartbeat for NPCs.
8.  Now that we are using SignalEvent to trigger a userdefined event, we need to
    put event number 2000 into the OnUserDefined script.  Included where you found
    this script should be a sample OnUserDefined script; save that as nw_c2_defaultd
    to use it as the default OnUserDefined for NPCs.  Then, for any NPC that you
    want using different HourWayPoints parameters, you only have to change his
    OnUserDefined.
9.  Don't forget that NPCs need an End Conversation Script that calls
    SignalEvent(OBJECT_SELF, EventUserDefined(2000)) to have them resume their
    waypoints after conversations.
10. Recompile the module after you make those changes, and ignore any compiler
    errors given for include files (like rh_inc_hrwpoints and my_i0_generic).
Have fun with the script, I hope it works well for everyone who tries it. :) 
*******************************************************************************/
void HourWayPoints(int nRun = FALSE, float fPause = 1.0, int nCircle = FALSE);
void HourRunCircuit(string sPrefix, int nWPStart = 1, int nReverse = FALSE, int nRun=FALSE, float fPause = 1.0, int nCircle = FALSE);
void HourGoToPost(object oPost, int nRun = FALSE);
int GetEventForWP(object oWP, int nNum);
string PosIntTo2DigitString(int nNum);
float GetFacingCorrectly(object oTarget);
//This should be called in place of WalkWayPoints.  There is one extra parameter
//(nCircle), which allows NPCs to walk in a circle instead of back and forth.
void HourWayPoints(int nRun = FALSE, float fPause = 1.0, int nCircle = FALSE)
{
    //nDontWalkWP can be set in scripts to temporarily disable waypoint walking
    //for a particular NPC.
    if (GetLocalInt(OBJECT_SELF, "nDontWalkWP"))
        return;
    string sWalker = GetTag(OBJECT_SELF);
    string sWPPrefix;
    string sPostPrefix;
    int nWPStart = GetLocalInt(OBJECT_SELF, "nNextWP");
    int nDay;
    object oWP;
    object oPost;
    ClearAllActions();
    //Before we start checking the hour and day/night status, let's see if the
    //NPC uses 24-hour waypoints/post.
    sWPPrefix = "WP_24_";
    sPostPrefix = "POST_24_";
    oWP = GetObjectByTag(sWPPrefix + sWalker + "_01");
    oPost = GetObjectByTag(sPostPrefix + sWalker);
    //Send NPC to 24-hour WP or Post; resume if the NPC has already started waypoints.
    if (GetIsObjectValid(oWP)) {
        if (!nWPStart)
            nWPStart = 1;
        HourRunCircuit(sWPPrefix, nWPStart, FALSE, nRun, fPause, nCircle);
        return;
    } else if (GetIsObjectValid(oPost)) {
        HourGoToPost(oPost, nRun);
        return;
    }
    //No 24-hour waypoints or posts, so let's try night/day.
    if (GetIsDay() || GetIsDawn()) {
        sWPPrefix = "WP_DY_";
        sPostPrefix = "POST_DY_";
        nDay = 1;
    } else {
        sWPPrefix = "WP_NT_";
        sPostPrefix = "POST_NT_";
        nDay = 2;
    }
    oWP = GetObjectByTag(sWPPrefix + sWalker + "_01");
    oPost = GetObjectByTag(sPostPrefix + sWalker);
    //If the NPC has day/night waypoints or posts, send him to them.
    if (GetIsObjectValid(oWP)) {
        int nOldDay = GetLocalInt(OBJECT_SELF, "nDayPoints");
        if (!(nWPStart && nOldDay && nOldDay == nDay)) {
            SetLocalInt(OBJECT_SELF, "nDayPoints", nDay);
            nWPStart = 1;
        }
        HourRunCircuit(sWPPrefix, nWPStart, FALSE, nRun, fPause, nCircle);
        return;
    } else if (GetIsObjectValid(oPost)) {
        SetLocalInt(OBJECT_SELF, "nDayPoints", nDay);
        HourGoToPost(oPost);
        return;
    }
    //Code for hour-based waypoints starts here.
    int nHour = GetTimeHour();
    sWPPrefix = "WP_" + PosIntTo2DigitString(nHour) + "_";
    sPostPrefix = "POST_" + PosIntTo2DigitString(nHour) + "_";
    oWP = GetObjectByTag(sWPPrefix + sWalker + "_01");
    oPost = GetObjectByTag(sPostPrefix + sWalker);
    //The important thing we're doing in the following loop is checking every hour,
    //starting with the current hour and counting backwards, until we either find
    //an hour for which the NPC has waypoints to walk (so an NPC with 8am waypoints
    //will keep walking them for every hour until the next hour he has waypoints
    //set for) or we reach nLastHourWalked (at which point we don't need to look
    //back any further).
    //
    //If the NPC is running HourWayPoints for the first time (e.g. he just spawned)
    //then nStartedWaypoints isn't set yet, so we set nLastHourWalked to an hour
    //ahead of the current time--that way, every hour can be checked for waypoints.
    //
    //nLastHourChecked is being set for use in the OnHeartbeat script.
    if (!GetLocalInt(OBJECT_SELF, "nStartedWaypoints")) {
        if (nHour == 23)
            SetLocalInt(OBJECT_SELF, "nLastHourWalked", 0);
        else
            SetLocalInt(OBJECT_SELF, "nLastHourWalked", nHour + 1);
    }
    while (nHour != GetLocalInt(OBJECT_SELF, "nLastHourWalked")) {
        if (GetIsObjectValid(oWP)) {
            nWPStart = 1;
            SetLocalInt(OBJECT_SELF, "nLastHourWalked", nHour);
            SetLocalInt(OBJECT_SELF, "nLastHourChecked", GetTimeHour());
            SetLocalInt(OBJECT_SELF, "nStartedWaypoints", TRUE);
            HourRunCircuit(sWPPrefix, nWPStart, FALSE, nRun, fPause, nCircle);
            return;
        } else if (GetIsObjectValid(oPost)) {
            SetLocalInt(OBJECT_SELF, "nLastHourWalked", nHour);
            SetLocalInt(OBJECT_SELF, "nLastHourChecked", GetTimeHour());
            SetLocalInt(OBJECT_SELF, "nStartedWaypoints", TRUE);
            HourGoToPost(oPost, nRun);
            return;
        } else {
            nHour--;
            if (nHour < 0)
                nHour = 23;
            sWPPrefix = "WP_" + PosIntTo2DigitString(nHour) + "_";
            sPostPrefix = "POST_" + PosIntTo2DigitString(nHour) + "_";
            oWP = GetObjectByTag(sWPPrefix + sWalker + "_01");
            oPost = GetObjectByTag(sPostPrefix + sWalker);
        }
    }
    //If we reach nLastHourWalked before finding waypoints for later hours, we
    //check to make sure that there are valid waypoints/posts for that hour and
    //send the NPC to them if so.
    if (GetIsObjectValid(oWP)) {
        SetLocalInt(OBJECT_SELF, "nLastHourWalked", nHour);
        SetLocalInt(OBJECT_SELF, "nLastHourChecked", GetTimeHour());
        SetLocalInt(OBJECT_SELF, "nStartedWaypoints", TRUE);
        HourRunCircuit(sWPPrefix, nWPStart, FALSE, nRun, fPause, nCircle);
        return;
    }
    if (GetIsObjectValid(oPost)) {
        SetLocalInt(OBJECT_SELF, "nLastHourWalked", nHour);
        SetLocalInt(OBJECT_SELF, "nLastHourChecked", GetTimeHour());
        SetLocalInt(OBJECT_SELF, "nStartedWaypoints", TRUE);
        HourGoToPost(oPost, nRun);
        return;
    }
}
//This is the replacement for the original RunCircuit (it does not use a
//RunNextCircuit equivalent), which sends the NPC to his waypoints in order.
//
//It was originally designed with a while loop to loop through the waypoints,
//but I had to change it to loop by recursion instead.  Using a while loop, I
//was unable to get the UserDefined actions (triggered by the SignalEvent at
//each waypoint) to fit in their correct place in the action queue.  No matter
//what I tried, the scripted actions for every waypoint would run at the end of
//the circuit (right before the function called itself again).  So, I made the
//function call itself again for every waypoint, and viola, the userdefined
//actions take place when they should.
//
//Note that this functions allows for circular waypoint walking through a function
//parameter (filtered down from HourWayPoints), and it also allows for waypoints
//and a post to be set for the same time--what happens then is that the NPC walks
//his waypoints once and then goes to his post, where he stays.
void HourRunCircuit(string sPrefix, int nNext = 1, int nReverse=FALSE, int nRun=FALSE, float fPause = 1.0, int nCircle = FALSE)
{
    string sWalker = GetTag(OBJECT_SELF);
    object oWP = GetWaypointByTag(sPrefix + sWalker + "_" + PosIntTo2DigitString(nNext));
    object oPost = GetWaypointByTag("POST_" + GetSubString(sPrefix, 3, 3) + sWalker);
    object oArea = GetArea(OBJECT_SELF);
    object oNearestPCArea = GetArea(GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC, OBJECT_SELF));
    if (GetIsObjectValid(oWP)) {
        SetLocalInt(OBJECT_SELF, "nNextWP", nNext);
        SetLocalInt(OBJECT_SELF, "nAmbientAnimations", 0);
        if (oArea == oNearestPCArea)
            ActionForceMoveToObject(oWP, nRun, 1.0, 60.0);
        else
            ActionForceMoveToObject(oWP, nRun, 1.0, 20.0);
        ActionWait(fPause);
        SignalEvent(OBJECT_SELF, EventUserDefined(GetEventForWP(oWP, nNext)));
        if (!nReverse)
            nNext++;
        else
            nNext--;
    } else if (GetIsObjectValid(oPost)) {
        ActionDoCommand(HourGoToPost(oPost, nRun));
        return;
    } else {
        if (nCircle) {
            nReverse = FALSE;
            nNext = 1;
        } else {
            nReverse = !nReverse;
            if (!nReverse)
                nNext += 2;
            else
                nNext -= 2;
        }
    }
    ActionDoCommand(HourRunCircuit(sPrefix, nNext, nReverse, nRun, fPause, nCircle));
}
//Go to a Post.
void HourGoToPost(object oPost, int nRun = FALSE)
{
    object oArea = GetArea(OBJECT_SELF);
    object oNearestPCArea = GetArea(GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC, OBJECT_SELF));
    SetLocalInt(OBJECT_SELF, "nNextWP", 100);
    SetLocalInt(OBJECT_SELF, "nAmbientAnimations", 0);
    if (oArea == oNearestPCArea)
        ActionForceMoveToObject(oPost, nRun, 1.0, 60.0);
    else
        ActionForceMoveToObject(oPost, nRun, 1.0, 20.0);
    float fFacing = GetFacingCorrectly(oPost);
    ActionDoCommand(SetFacing(fFacing));
    SignalEvent(OBJECT_SELF, EventUserDefined(GetEventForWP(oPost, 0)));
}

//Convert 1, 2, 3 ... 9 ints into 01, 02, 03 ... 09 strings.
string PosIntTo2DigitString(int nNum)
{
    string sString;
    if (nNum > 9)
        return IntToString(nNum);
    else if (nNum < 10 && nNum >= 0)
        return ("0" + IntToString(nNum));
    else
        return "This function is not for negative numbers.";
}
//A workaround for GetFacing, which is currently broken.  This will cease to
//be necessary when/if Bioware fixes GetFacing, but it won't break anything.
float GetFacingCorrectly(object oTarget)
{
    float fFacing;
    fFacing = GetFacing(oTarget);
    if (fFacing >= 360.0)
        fFacing = 720.0 - fFacing;
    return fFacing;
}
//This is the function for putting together a number for an event to trigger in
//the NPC's OnUserDefined script.
int GetEventForWP(object oWP, int nNum)
{
    string sTime;
    string sNum;
    string sEventNum;
    int nEventNum;
    if (!nNum) {
        sNum = "00";
        sTime = GetSubString(GetTag(oWP), 5, 2);
    } else {
        sNum = GetStringRight(GetTag(oWP), 2);
        sTime = GetSubString(GetTag(oWP), 3, 2);
    }
    if (sTime == "DY")
        sTime = "25";
    else if (sTime == "NT")
        sTime = "26";
    sEventNum = "9" + sTime + sNum;
    nEventNum = StringToInt(sEventNum);
    return nEventNum;
}



The OnHeartbeat script:

NWScript:
//::///////////////////////////////////////////////
//:: Default On Heartbeat
//:: NW_C2_DEFAULT1
//:: Copyright (c) 2001 Bioware Corp.
//:://////////////////////////////////////////////
/*
    This script will have people perform default
    animations.
*/
//:://////////////////////////////////////////////
//:: Created By: Preston Watamaniuk
//:: Created On: Nov 23, 2001
//:://////////////////////////////////////////////
//This script has been altered for use with Rhone's Hour-Based Waypoint Walker.
//Default WalkWayPoints-related code has been removed and replaced with new
//code for HourWayPoints.  
//
//The new code handles rerunning HourWayPoints() (through the userdefined event)
//at the appropriate times for NPCs with day/night waypoints or hour-based waypoints.
//
//It has been updated to supported ambient animations at posts.
//--Rhone
#include "MY_I0_GENERIC"
void main()
{
    if(GetSpawnInCondition(NW_FLAG_FAST_BUFF_ENEMY))
    {
        if(TalentAdvancedBuff(40.0))
        {
            SetSpawnInCondition(NW_FLAG_FAST_BUFF_ENEMY, FALSE);
            return;
        }
    }
    //If nDay has been set, and it's not equal to the current day/night status,
    //check to see if the NPC has new waypoints/posts to walk.
    if (GetLocalInt(OBJECT_SELF, "nDayPoints")) {
        int nDay;
        if (GetIsDay() || GetIsDawn())
            nDay = 1;
        else
            nDay = 2;
        if (GetLocalInt(OBJECT_SELF, "nDayPoints") != nDay)
            SignalEvent(OBJECT_SELF, EventUserDefined(2000));
    }
    //If nLastHourChecked hasn't been set for the NPC, then he's probably not
    //walking hour-based waypoints and we don't do anything else.  If it is
    //checked, and it's not equal to the current hour, we check to see if there
    //are new waypoints.  nLastHourChecked will end up being set to the current
    //hour so OnHearbeat knows not to bother checking for new waypoints again
    //until the next hour.
    int nHour = GetTimeHour();
    int nLastHourChecked = GetLocalInt(OBJECT_SELF, "nLastHourChecked");
    if (GetLocalInt(OBJECT_SELF, "nStartedWaypoints") && nHour != nLastHourChecked
       && !GetIsInCombat(OBJECT_SELF) && !IsInConversation(OBJECT_SELF)) {
        string sWalker = GetTag(OBJECT_SELF);
        object oNewWP;
        object oNewPost;
        while (nHour != nLastHourChecked) {
            oNewWP = GetObjectByTag("WP_" + PosIntTo2DigitString(nHour) + "_" + sWalker + "_01");
            oNewPost = GetObjectByTag("POST_" + PosIntTo2DigitString(nHour) + "_" + sWalker);
            if (GetIsObjectValid(oNewWP) || GetIsObjectValid(oNewPost)) {
                nHour = GetTimeHour();
                nLastHourChecked = nHour;
            } else {
                nHour--;
                if (nHour < 0)
                    nHour = 23;
            }
        }
        if (GetIsObjectValid(oNewWP) || GetIsObjectValid(oNewPost))
            SignalEvent(OBJECT_SELF, EventUserDefined(2000));
        else
            SetLocalInt(OBJECT_SELF, "nLastHourChecked", GetTimeHour());
    }
    if(!GetHasEffect(EFFECT_TYPE_SLEEP))
    {
        if(GetLocalInt(OBJECT_SELF, "nAmbientAnimations"))
        {
            if(!GetIsObjectValid(GetAttemptedAttackTarget()) && !GetIsObjectValid(GetAttemptedSpellTarget()))
            {
                if(!GetIsObjectValid(GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, 1, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN)))
                {
                    if(!GetBehaviorState(NW_FLAG_BEHAVIOR_SPECIAL) && !IsInConversation(OBJECT_SELF))
                    {
                        if(GetLocalInt(OBJECT_SELF, "nAmbientAnimations") == 1)
                        {
                            PlayMobileAmbientAnimations();
                        }
                        else if(GetIsEncounterCreature() &&
                        !GetIsObjectValid(GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, 1, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN)))
                        {
                            PlayMobileAmbientAnimations();
                        }
                        else if(GetLocalInt(OBJECT_SELF, "nAmbientAnimations") == 2 &&
                           !GetIsObjectValid(GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, 1, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN)))
                        {
                            PlayImmobileAmbientAnimations();
                        }
                    }
                    else
                    {
                        DetermineSpecialBehavior();
                    }
                }
                else
                {
                    //DetermineCombatRound();
                }
            }
        }
    }
    else
    {
        if(GetSpawnInCondition(NW_FLAG_SLEEPING_AT_NIGHT))
        {
            effect eVis = EffectVisualEffect(VFX_IMP_SLEEP);
            if(d10() > 6)
            {
                ApplyEffectToObject(DURATION_TYPE_INSTANT, eVis, OBJECT_SELF);
            }
        }
    }
    if(GetSpawnInCondition(NW_FLAG_HEARTBEAT_EVENT)) {
        SignalEvent(OBJECT_SELF, EventUserDefined(1001));
    }
}



Finally, the OnUserDefined script (add case statements for your own userdefined events, of course):

NWScript:
//OnUserDefined script for NPCs using hour-based waypoints.
#include "MY_I0_GENERIC"
void main()
{
    int nEvent = GetUserDefinedEventNumber();
    switch(nEvent)
    {
        case 2000:
            HourWayPoints(FALSE, 1.0, FALSE);
            break;
    }
}



_________________
"In the global sense, bringing peace means either appeasing the needs of--or, more likely, destroying--everyone." --Guardian of Helm

⌂ All Scripts ← PreviousNext → 👁 Category List

All Waypoints Scripts

📄 hourly 📄 hourly_002 📄 hourly_003 📄 walk_any_waypoints 📄 walk_wp_afterconverse 📄 waypoints_001 📄 waypoints_002 📄 waypoints_003 📄 waypoints_004