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