Author Sticky: Post Working Scripts Here
tealeaf Posted: Friday, 05 July 10:37AM
This is a Release 1.1 of my smart NPC sitting pseudo-AI

Feature List:
 


     

  • new! Now supports chair groups! What this means is that you no longer need to have a unique chair-NPC pair. You can now put 10 chairs with the same tag name (for example SIT_NW_BARTENDER), then spawn 12 NPC with the same tag name (in this example, they will use their default tag: NW_BARTENDER), and 10 of them will sit down. Kill one of sitting ones, and one of the two without a chair will grab a seat. Kill one more sitting one, and the last one without a chair will grab a seat. You can spawn more of them, rinse, lather, repeat. NPCs without chairs need to be within a certain range of their dying clones, otherwise they won't know that one of them has died and they won't attempt to check if a chair has opened up.
     
  • Workable for almost any non-hostile alignment of NPC, but from RP perspective, the NPC pseudo-AI is selfish, so it probably falls under neutral to evil range in terms of morality.
     
  • Supports many NPC's sitting together in the same room. If you attack one of them, they get up from their chairs and do what their AI commands! Some attack you, others are frozen in fear, and others run away. When NPCs determine that it is safe, they return to their chairs!
     
  • If you want to talk to an NPC, they will remain sitting in the chair while they are talking to you.
     
  • NPC will go to great lengths to ensure that it gets back to its chair. If for some reason someone is sitting in their favorite chair, they will attack the offender! If the offender runs away, they will break pursuit and return to their chair.
     
  • NPC will return to their chair even if they followed an attacked into a different zone! The same restrictions apply here as with other "between zones" travel via waypoints: your area transitions must be generated via the toolset (as opposed to being hand scripted). But, even if NPC is somehow lured into a place where they are trapped, they will return to their chair anyway, unless they die .
     
  • Extremely easy to use plug-n-play! Everything is tightly wrapped into one function called void DetermineSitTask(int iEventID) (which in turn uses some other functions). You just need to put these functions somewhere above your void main() function inside OnUserDefined event handler and then simply put void DetermineSitTask(int iEventID) function anywhere inside void main() and just make sure to pass it an event ID number. That's all! You can keep all your other stuff there and it shouldn't bother anything (unless it is other pseudo-AI type stuff).
     
  • Lots of debugging stuff commented out but left there for you in case you decide to continue development.
     
  • Restriction on use: It does not work together with NW_FLAG_AMBIENT_ANIMATIONS. It probably doesn't work with other optional behaviors from BioWare. Those ambient animations are disabled by default. If you didn't touch those things inside the OnSpawn script, you are fine.
     



Directions:

1. Put all the functions from my script inside your OnUserDefined event handler script. I am giving you an example of my event handler script below. If you didn't do anything with your OnUserDefined event script, you can just overwrite everything with mine. Otherwise, you should know what to do .

2. Enable the following user defined events (inside the OnSpawn script):
NW_FLAG_END_COMBAT_ROUND_EVENT
NW_FLAG_ON_DIALOGUE_EVENT
NW_FLAG_DEATH_EVENT

WARNING! The script now also requires the NW_FLAG_DEATH_EVENT user defined event! Don't forget to open it up.

3. Add a new user defined event at the very bottom of your OnSpawn script, right before the last closing brace:

SignalEvent(OBJECT_SELF, EventUserDefined(1000));

What this does is create a new user defined event that BioWare did not create for us. I've reserved number 1000 for the user defined OnSpawn event! If you are already using it, you will have to read the source Luke. Most people shouldn't be affected.

4. Create some creature NPCs and some chairs (stools, etc.). Now, here is the important part. An NPC will sit in a chair that has this tag: "SIT_" + <tag name of NPC>! For example, you have an NPC with the tag ct_orc. This NPC will look for a chair (or stool, etc.) with the tag SIT_ct_orc, and will then sit there. Keep this in mind. It's a good idea to spawn these NPCs close to their chairs, but they should get to their chair from further away as well.

Now, if you followed everything here, all your NPC's and monsters will become SITable in this fashion! It will be a global change! If you do *not* want a global change like this, you just have to rename the OnUserDefined script to something else. I use the default name so that all the NPCs in the entire module are SITable in this way. If there is no chair for an NPC to sit in, then nothing special happens.

That's it!

Here is the code:

NWScript:
// Copyright (C) 2002, Leo Lipelis

// -------------------------------

// Version 1.1

// -------------------------------

// this speaks interesting current actions for debugging

void SpeakAction()

{

    int iAction = GetCurrentAction();

    if (iAction == ACTION_WAIT)

    {

        SpeakString("WAIT");

    }

    else if (iAction == ACTION_SIT)

    {

        SpeakString("SIT");

    }

    else if (iAction == ACTION_ATTACKOBJECT)

    {

        SpeakString("ATTACK!");

    }

    else if (iAction == ACTION_MOVETOPOINT)

    {

        SpeakString("MOVE");

    }

    else if (iAction == ACTION_REST)

    {

        SpeakString("REST");

    }

    else if (iAction == ACTION_CASTSPELL)

    {

        SpeakString("CASTSPELL");

    }

    else if (iAction == ACTION_COUNTERSPELL)

    {

        SpeakString("COUNTERSPELL");

    }

    else if (iAction == ACTION_HEAL)

    {

        SpeakString("HEAL");

    }

    else if (iAction == ACTION_ITEMCASTSPELL)

    {

        SpeakString("ITEMCASTSPELL");

    }

    else if (iAction == ACTION_SETTRAP)

    {

        SpeakString("SETTRAP");

    }

    else if (iAction == ACTION_USEOBJECT)

    {

        SpeakString("USEOBJECT");

    }

    else if (iAction == ACTION_INVALID)

    {

        SpeakString("INVALID");

    }

    else

    {

        SpeakString("UKNOWN ACTION!");

    }

}



string GetMySittableTag()

{

    string sMyTagName = GetTag(OBJECT_SELF);

    string sSittableTagName = "SIT_" + sMyTagName;

    return sSittableTagName;

}

object GetMyFirstSittable()

{

    return GetObjectByTag(GetMySittableTag());

}

int GetIsSittableTaken(object oSittable)

{

    return GetLocalInt(oSittable, "SittableTaken");

}

void SetIsSittableTaken(object oSittable, int bTaken)

{

    SetLocalInt(oSittable, "SittableTaken", bTaken);

}

object ClaimSittable(string sSittableTagName)

{

    int i = 0;

    object oSittable = GetObjectByTag(sSittableTagName, i);

    while (GetIsObjectValid(oSittable))

    {

        if (GetIsSittableTaken(oSittable) == FALSE)

        {

            SetIsSittableTaken(oSittable, TRUE);

            return oSittable;

        }

        i++;

        oSittable = GetObjectByTag(sSittableTagName, i);

    }

    return OBJECT_INVALID;

}

object GetMyAssignedSittable()

{

    return GetLocalObject(OBJECT_SELF, "AssignedSittable");

}

void SetMyAssignedSittable(object oSittable)

{

    SetLocalObject(OBJECT_SELF, "AssignedSittable", oSittable);

}

object GetMyUniqueSittable()

{

    object oSittable = OBJECT_INVALID;

    oSittable = GetMyAssignedSittable();

    if (GetIsObjectValid(oSittable))

    {

        return oSittable;

    }

    string sSittableTagName = GetMySittableTag();

    oSittable = ClaimSittable(sSittableTagName);

    if (GetIsObjectValid(oSittable))

    {

        SetMyAssignedSittable(oSittable);

        return oSittable;

    }

    // at this point you can code a function to claim an already claimed

    // sittable to make your creatures fight each other for limited seating;

    // it would be amusing to see, but I am too lazy to add it right now

    return OBJECT_INVALID;

}

// using wrapper for debugging purposes

void SetFacingWrapper(float fDirection)

{

    //SpeakString("Setting Facing...");

    SetFacing(fDirection);

}

// returns TRUE if current action can be considered aggressive

int GetIsCurrentActionAggro(int iAction)

{

    //SpeakAction();

    return (iAction == ACTION_ATTACKOBJECT

            || iAction == ACTION_CASTSPELL

            || iAction == ACTION_COUNTERSPELL

            || iAction == ACTION_HEAL

            || iAction == ACTION_ITEMCASTSPELL

            || iAction == ACTION_SETTRAP

            || iAction == ACTION_USEOBJECT);

}

// this is a function used to work around ActionSit() limitation

// it is queued as a DelayedAction before ActionSit()

void ActionAfterSit(object oSittable)

{

    //SpeakString("Sitting done!");

    //

    float fSittableFacing = GetFacing(oSittable);

    SetFacingWrapper(fSittableFacing);

}

void ActionMoveAndSit(object oSittable, int bTrackSpeaker)

{

    float fDistanceToSittable = GetDistanceToObject(oSittable);

    /*

    ActionSpeakString("DISTANCE TO SITTABLE: "

                + FloatToString(fDistanceToSittable));

    */

    if (fDistanceToSittable > 2.0f // in the same zone

            || fDistanceToSittable < 0.0f) // in a different zone

    {

        //ActionSpeakString("MOVE REQUIRED");

        ActionForceMoveToObject(oSittable); // otherwise they run

    }

    if (bTrackSpeaker == FALSE)

    {

        // Need to use this trick to execute some commands after ActionSit()!

        ActionDoCommand(DelayCommand(0.5f, ActionAfterSit(oSittable)));

    }

    // once you exec ActionSit(), no other action is possible until

    // you ClearAllActions()!!!!  This is a limitation of ActionSit()

    // that has caused me LOTS of hair pulling, be warned!

    ActionSit(oSittable);

}

void ConditionalActionSit(object oSittable, int bTrackSpeaker)

{

    if (!GetIsInCombat())

    {

        //SpeakString("I will sit down right now");

        ActionMoveAndSit(oSittable, bTrackSpeaker);

    }

    else

    {

        //SpeakString("Delayed sit overruled by combat!");

    }

}

void SitMe(int bTrackSpeaker, int bDelayed=FALSE)

{

    // run a quick test to discard the most common case right away

    object oSittable = GetMyFirstSittable();

    if (!GetIsObjectValid(oSittable))

    {

        // we don't have a sittable; there is nothing to do

        return;

    }

    int iCurrentAction = GetCurrentAction();

    if (iCurrentAction == ACTION_SIT)

    {

        // if are already sitting, then we are done

        //SpeakString("I am already sitting...");

        return;

    }

    if (GetIsCurrentActionAggro(iCurrentAction))

    {

        // we're in the middle of aggression, no time to sit

        return;

    }

    // now get a unique oSittable out of a group of 1 or more chairs that

    // is is not yet taken by any creature with the same tag name

    oSittable = GetMyUniqueSittable();

    object oItTookMyChair = OBJECT_INVALID;

    oItTookMyChair = GetSittingCreature(oSittable);

    if (GetIsObjectValid(oItTookMyChair))

    {

        // someone had the guts to take my chair; attack!

        ActionAttack(oItTookMyChair);

    }

    else if (iCurrentAction != ACTION_MOVETOPOINT)

    {

        //ClearAllActions();

        if (bDelayed)

        {

            //SpeakString("DELAYED");

            DelayCommand(4.0f, ConditionalActionSit(oSittable, bTrackSpeaker));

        }

        else

        {

            //SpeakString("NOT DELAYED");

            ActionMoveAndSit(oSittable, bTrackSpeaker);

        }

        // confirm that we are sitting; if not sitting yet, try to sit again

        DelayCommand(8.0f, ActionDoCommand(SitMe(bTrackSpeaker)));

    }

}

// use this function to access sitting pseudo-AI (it doesn't learn new

// behaviors, thus it is not true AI);

// this function expects to have access to the following events:

// ON_SPAWN

// ON_DIALOG

// ON_END_OF_COMBAT_ROUND

// ON_DEATH

void DetermineSitTask(int iEventID, int bTrackSpeaker=FALSE)

{

    if (iEventID == 1000) // ON_SPAWN

    {

        SitMe(bTrackSpeaker);

    }

    if (iEventID == 1004) // ON_DIALOG

    {

        object oSpeaker = GetLastSpeaker();

        string sSpeakerTag = GetTag(oSpeaker);

        //SpeakString(sSpeakerTag + " said something!!!");

        int iListenPatternNumber = GetListenPatternNumber();

        //SpeakString("Listen pattern: " + IntToString(iListenPatternNumber));

        //SpeakAction();

        if (iListenPatternNumber == -1) // PC convo

        {

            //SpeakString("SITME ON DIALOG, IMMEDIATE REQUEST");

            SitMe(bTrackSpeaker);

        }

        else if (!GetIsInCombat()) // NPC AI convo

        {

            //SpeakString("SITME ON DIALOG, DELAYED REQUEST");

            // recieved AI talk message; give AI some priority

            // and delay sitting

            int bDelayed = TRUE; SitMe(bTrackSpeaker, bDelayed);

        }

    }

    if (iEventID == 1003) // ON_END_OF_COMBAT_ROUND

    {

        //SpeakString("SITME ON END OF COMBAT ROUND");

        //SpeakAction();

        SitMe(bTrackSpeaker);

    }

    if (iEventID == 1007) // ON_DEATH

    {

        // release claim on my sittable, if possible

        object oSittable = GetMyAssignedSittable();

        if (GetIsObjectValid(oSittable))

        {

            SetIsSittableTaken(oSittable, FALSE);

        }

    } // ON_DEATH

}



void main()

{

    int iEventID = GetUserDefinedEventNumber();

    DetermineSitTask(iEventID);

    // Be default, NPC in chairs will NOT turn toward speaker!

    // If you want NPC to keep facing the speaker, then comment out

    // the line above and uncomment the line below.

    //int bTrackSpeaker = TRUE; DetermineSitTask(iEventID, bTrackSpeaker);

    return;

}



If you find bugs, or if you have made improvements, please reply here or send me a message through this forum.