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:
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;
}
return OBJECT_INVALID;
}
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 = GetMyFirstSittable();
if (!GetIsObjectValid(oSittable))
{
return;
}
int iCurrentAction = GetCurrentAction();
if (iCurrentAction == ACTION_SIT)
{
return;
}
if (GetIsCurrentActionAggro(iCurrentAction))
{
return;
}
oSittable = GetMyUniqueSittable();
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);
}
if (iEventID == 1007)
{
object oSittable = GetMyAssignedSittable();
if (GetIsObjectValid(oSittable))
{
SetIsSittableTaken(oSittable, FALSE);
}
}
}
void main()
{
int iEventID = GetUserDefinedEventNumber();
DetermineSitTask(iEventID);
return;
}
If you find bugs, or if you have made improvements, please reply
here or send me a message through this forum.
|