Author Sticky: Post Working Scripts Here
tealeaf Posted: Thursday, 04 July 04:42AM
Script Purpose: Smart sitting NPC pseudo-AI

Feature List:
 


     

  • 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 remains facing in the chair's direction even if you talk to this NPC from behind or from the side. Optionally you can enable the NPC to track PC's location during talking.
     
  • 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 (like if you want to have a different sittable object during the night, etc.).
     
  • 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

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.

5. (Optional) Read the source code inside void main() function if you want to know how to enable NPC to face the speaker at all times during convo.

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.01

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

// 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!");

    }

}

object GetMySittable()

{

    string sMyTagName = GetTag(OBJECT_SELF);

    string sSittableTagName = "SIT_" + sMyTagName;

    return GetObjectByTag(sSittableTagName);

}

// 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!");

    //

    // SetFacing seems not to work quite right here, however...

    // if you use it, creatures won't track you during convo;

    // if you comment it out, they will always try to look in

    // your direction during convo (it might look better that

    // way depending on your taste);

    // the reason I wanted facing originally is because sitting

    // on a stool seems to always face in the same direction,

    // which is wrong (sitter should face in stool's direction).

    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)

{

    object oSittable = GetMySittable();

    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;

    }

    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

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);

    }

}



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 want to make comments, please use this thread. If you want to file a bug report, please send me a private message (so I know about the bug for sure) and please also post your report in this thread as well so that other ppl using the script are alerted!

Thank you.