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:
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);
}
void SetFacingWrapper(float fDirection)
{
SetFacing(fDirection);
}
int GetIsCurrentActionAggro(int iAction)
{
return (iAction == ACTION_ATTACKOBJECT
|| iAction == ACTION_CASTSPELL
|| iAction == ACTION_COUNTERSPELL
|| iAction == ACTION_HEAL
|| iAction == ACTION_ITEMCASTSPELL
|| iAction == ACTION_SETTRAP
|| iAction == ACTION_USEOBJECT);
}
void ActionAfterSit(object oSittable)
{
float fSittableFacing = GetFacing(oSittable);
SetFacingWrapper(fSittableFacing);
}
void ActionMoveAndSit(object oSittable, int bTrackSpeaker)
{
float fDistanceToSittable = GetDistanceToObject(oSittable);
if (fDistanceToSittable > 2.0f
|| fDistanceToSittable < 0.0f)
{
ActionForceMoveToObject(oSittable);
}
if (bTrackSpeaker == FALSE)
{
ActionDoCommand(DelayCommand(0.5f, ActionAfterSit(oSittable)));
}
ActionSit(oSittable);
}
void ConditionalActionSit(object oSittable, int bTrackSpeaker)
{
if (!GetIsInCombat())
{
ActionMoveAndSit(oSittable, bTrackSpeaker);
}
else
{
}
}
void SitMe(int bTrackSpeaker, int bDelayed=FALSE)
{
object oSittable = GetMySittable();
if (!GetIsObjectValid(oSittable))
{
return;
}
int iCurrentAction = GetCurrentAction();
if (iCurrentAction == ACTION_SIT)
{
SpeakString("I am already sitting...");
return;
}
if (GetIsCurrentActionAggro(iCurrentAction))
{
return;
}
object oItTookMyChair = OBJECT_INVALID;
oItTookMyChair = GetSittingCreature(oSittable);
if (GetIsObjectValid(oItTookMyChair))
{
ActionAttack(oItTookMyChair);
}
else if (iCurrentAction != ACTION_MOVETOPOINT)
{
if (bDelayed)
{
DelayCommand(4.0f, ConditionalActionSit(oSittable, bTrackSpeaker));
}
else
{
ActionMoveAndSit(oSittable, bTrackSpeaker);
}
DelayCommand(8.0f, ActionDoCommand(SitMe(bTrackSpeaker)));
}
}
void DetermineSitTask(int iEventID, int bTrackSpeaker=FALSE)
{
if (iEventID == 1000)
{
SitMe(bTrackSpeaker);
}
if (iEventID == 1004)
{
object oSpeaker = GetLastSpeaker();
string sSpeakerTag = GetTag(oSpeaker);
int iListenPatternNumber = GetListenPatternNumber();
if (iListenPatternNumber == -1)
{
SitMe(bTrackSpeaker);
}
else if (!GetIsInCombat())
{
int bDelayed = TRUE; SitMe(bTrackSpeaker, bDelayed);
}
}
if (iEventID == 1003)
{
SitMe(bTrackSpeaker);
}
}
void main()
{
int iEventID = GetUserDefinedEventNumber();
DetermineSitTask(iEventID);
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.
|