Factions, Shouts, and Attacking My Enemy

From NWN Lexicon
Jump to: navigation, search

This tutorial will give a general overview of how factions and shouts work together in the game.

Editor's Note: This tutorial is a work in progress. You can help improve it by fixing any unclear language and expanding areas that are lacking.

Information icon.png This tutorial is adapted from one posted by David Gaider on the (now-defunct) nwn.bioware.com website. It has been edited to be more useful in a wiki setting. The original tutorial can still be accessed via the Wayback Machine.

Just what is a faction?

A faction is one of three things: a party of player characters and their associates, a single player character (who is not a member of a party) and his associates, or a group of NPCs or single NPCs that have been assigned to a set default relationship to other factions.

This is not to be mistaken with an "organization", like a guild of thieves or a city's garrison. Players do not "join" factions, and any organization could conceivably comprise of a number of factions (and vice versa).

What do factions do, then?

Just what they are supposed to do: set the default stance of the NPC towards other factions. The generic AI takes a stance of "Friendly" or "Hostile" and has the NPC react accordingly in its scripts. An NPC will maintain that default stance until and unless it is told to do otherwise.

What is probably most important to understand about factions, however, is that there is a difference between how the faction feels and how the individual NPC feels. If an individual NPC within a faction is attacked, he will go hostile and defend himself. He will remain hostile until told to do otherwise. The other members of his faction not present, however, will not necessarily go hostile along with him. This difference of standing towards someone is considered reputation, which is different from that someone's reputation with the faction. The NWScript commands, unfortunately, refer to both types of reputation in the same manner, so distinguishing between them can be tricky.

To better explain the difference, consider this:

A party of PCs is inside a tavern. The rogue in the party decides to attack a nearby peasant (who is a member of the Commoner faction). Upon being attacked, that peasant goes hostile to the rogue... and issues a shout to any nearby NPC's who are friendly to his faction to likewise go hostile to the rogue. The rest of the party hasn't done anything, yet, so nobody is hostile towards them. If the rest of the party gets involved in the fight, the witnessing NPCs will likewise go hostile towards them. Let's say the entire party flees the tavern immediately and escapes their pursuers. Those individuals who turned hostile towards the rogue (or other interfering party members) will remain hostile until told otherwise. Other members of the Commoner faction, however, will react normally.

The only way this wouldn't happen is when a faction has its "Global Effect" box checked (this can be found in the Faction Editor). This stops individuals within a faction from maintaining separate relations: if you attack one and it goes hostile, all members of the faction go hostile. Any change to a single member's relationship affects the entire faction. (There are instances in module design where this is desirable, but be aware of it, especially due to the fact that new factions are global in effect by default.)

What does the standard AI do with factions?

To better understand why something is or isn't happening, here's a rundown on how factions affect the standard AI:

  • in the OnPerception event (which, remember, fires only when the target first comes into perception range or if the perception type changes), if the NPC perceives a hostile and is not already in combat it issues the "NW_I_WAS_ATTACKED" shout (see below) and attacks.
  • if the creature is attacked and it is not already in combat, it issues the "NW_I_WAS_ATTACKED" and "NW_ATTACK_ MY_TARGET" shouts and attacks its attacker.
  • if the creature is killed it automatically issues a "NW_ATTACK_MY_TARGET" and "NW_I_AM_DEAD" shout.

And what do these shouts do? For one thing, they are simply spoken words that the creature has been told to speak, the same as any PC typing words with their chat system. These shouts, however, are 'silent'... players don't hear them or see the floating text, but NPC's do, and the generic AI in the OnSpawn script tells them to listen for them and react. Shouts are not heard through walls or beyond doors, and do have a limited range.

  • The "NW_ATTACK_ MY_TARGET" shout only has an effect on those NPC's who have uncommented the line SetSpawnInCondition(NW_FLAG_SHOUT_ATTACK_MY_TARGET); in their OnSpawn script. If heard by an ally (one whose faction is friendly to the shouter), it sets the faction relationship to the shouter's attacker to hostile (if not already so), stops what it's doing, and attacks any enemy within sight.
  • The "NW_I_WAS_ATTACKED" shout can be heard by anyone. If the listener is an ally of the shouter and is neither currently in combat nor has less than 10 Commoner levels, it will attack the attacker of the shouter.
  • The "NW_I_AM_DEAD" shout can also be heard by anyone. It's effect is the same as the "I was attacked" shout above.

How do I make my NPCs attack?

If a creature starts out as hostile and spots a PC, its OnPerception event will fire and it will immediately attack. No problem there. The situation arises fairly often, however, where you will have a NPC who was neutral to the PC when the OnPerception event fired and will become hostile through the course of a dialogue or scripting.

To turn a single NPC hostile to the PC and make him attack, you must first identify the target. In dialogue, this is easy:

object oTarget = GetPCSpeaker();

Outside of dialogue, in a normal script, it can vary:

// This targets the nearest player character to the caller of the script
object oTarget = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC);
 
// This is the last perceived object, only to be used inside the OnPerception event
object oTarget = GetLastPerceived();

The above are two possibilities. Regardless, once you've identified your target, you can call the rest of the commands as normal. For the following scripts, we'll assume that we're starting combat off of dialogue (by placing the script in the "Actions Taken" section of the dialogue line where we want combat to start).

First, do I want the attacker to turn the whole faction hostile? To do so, I use this:

#include "nw_i0_generic"
 
void main()
{
    // Lower my faction's relationship with the PC by 100 (to hostile)
    AdjustReputation(GetPCSpeaker(), OBJECT_SELF, -100);
 
    // Start combat (this requires the nw_i0_generic file to be included, above)
    DetermineCombatRound();
}

And that's it (this is also the "nw_d1_attonend" generic script). Should I just want the attacker to go hostile (and no-one else):

#include "nw_i0_generic "
 
void main()
{
    // Set myself to hostile against the PC
    // Note that the "temporary" is 3 minutes in length unless a specific
    // amount of time is supplied in the command.
    SetIsTemporaryEnemy(GetPCSpeaker());
 
    // Start combat (this requires the nw_i0_generic file to be included, above)
    DetermineCombatRound();
}

But suppose I have other friends nearby who I want to make part of the combat, as well? Even if I use AdjustReputation() to make their faction hostile, they still won't attack. Why? Look at the cases under the generic AI: I wasn't attacked first and am already in combat, so I won't issue those shouts. We've all already perceived the PC, and he wasn't an enemy then. And I'm not dead, so my pals will sit there until attacked or until I'm dead.

I've got two choices. Either send them the command via AssignCommand() like this:

#include "nw_i0_generic"
 
void main()
{
    // This script works great if I know exactly who I want to involve in the fight
    // and they have unique resrefs
    object oGoblin2 = GetObjectByTag("GOBLIN2");
    object oGoblin3 = GetObjectByTag("GOBLIN3");
 
    // Lower my faction's relationship with the PC by 100 (to hostile)
    AdjustReputation(GetPCSpeaker(), OBJECT_SELF, -100);
 
    // Tell my friends to start fighting
    AssignCommand(oGoblin2, DetermineCombatRound());
    AssignCommand(oGoblin3, DetermineCombatRound());
 
    // Start combat (this requires the nw_i0_generic file to be included, above)
    DetermineCombatRound();
}

Or like this:

#include "nw_i0_generic"
 
void main()
{
    // This script will make every ally within the current area attack
    location oHere = GetLocation(OBJECT_SELF);
 
    // Lower my faction's relationship with the PC by 100 (to hostile)
    AdjustReputation(GetPCSpeaker(), OBJECT_SELF, -100);
 
    // Now cycle through all objects in my area
    object oFriend = GetFirstObjectInArea(GetArea(OBJECT_SELF));
 
    while (GetIsObjectValid(oFriend))
    {
        // If the object is a creature that is a member of my faction
        if (GetFactionEqual(oFriend) && (GetObjectType(oFriend) == OBJECT_TYPE_CREATURE))
        {
            // ...and it can see the PC
            if (GetObjectSeen(GetPCSpeaker(), oFriend))
            {
                // Tell him to start combat
                AssignCommand(oFriend, DetermineCombatRound());
            }
            else // Otherwise, if he can't see the PC
            {
                // Tell him to stop what he's doing
                AssignCommand(oFriend, ClearAllActions());
 
                // ...and come to my location
                AssignCommand(oFriend, ActionMoveToLocation(oHere, TRUE));
            }
        }
 
        oFriend = GetNextObjectInArea(GetArea(OBJECT_SELF));
    }
 
    // Now start fighting, myself
    DetermineCombatRound();
}

There are also other variations to do the above; those are really just examples. Another easy way to start a group fight is to make sure all the combatants have the SetSpawnInCondition(NW_FLAG_SHOUT_ ATTACK_MY_TARGET); line uncommented in their OnSpawn script. Then, when you start the attack, you can just do this:

#include "nw_i0_generic"
 
void main()
{
    // Lower my faction's relationship with the PC by 100 (to hostile)
    AdjustReputation(GetPCSpeaker(), OBJECT_SELF, -100);
 
    // Start combat (this requires the nw_i0_generic file to be included, above)
    DetermineCombatRound();
 
    // Shout to my allies in the vicinity to join me
    SpeakString("NW_ATTACK_MY_TARGET", TALKVOLUME_SILENT_TALK);
}

You won't see the string displayed, but any allies within hearing range who can see the target will run to the attack.

Now how do I make my NPCs neutral again?

If you want to make an NPC non-hostile once it's been in combat with the PC (usually if the PC has died or for plot reasons), you have to make sure two things have happened: one, that the faction's attitude towards the player is non-hostile (either neutral or friendly). Two, if the faction doesn't have "Global Effect" checked, you have to clear the the NPC's personal feelings towards the PC.

The "standard" factions in the game (Commoner, Defender, etc) are the only ones that can do both of these in one single command. This is done through the SetStandardFactionReputation() command, and an example of this is in the default death script ("nw_o0_death"), which resets all standard factions to neutrality on a player's death. (NOTE: Several people that have reported that the Defender faction is not reset when this command is used, but can be reset if treated as a custom faction below.)

For custom factions, you need two commands: AdjustReputation() to change the faction's relationship with the target and ClearPersonalReputation() to change an NPC's feelings. In both cases, you must single out a target that is a member of a faction (which may be difficult if you have many members with separate tags or that may be dead). Custom factions aren't identified by name: you can only say "the faction that so-and-so belongs to". And if you want to clear the a PC's personal reputation with an entire custom faction, you have to cycle through it.

Here's an example script that will adjust a faction towards neutral and cycle through it, restoring every member to a normal relationship with the PC. This faction does not have its "Global Effect" checked, obviously, and has members with the same tag "GOBLIN01". The PC has died and this script is within his OnPlayerDeath event:

// I'm creating a custom ClearAllFactionMembers command here,
// first declaring the API for the new command, then what it does.
// I could reasonably have put this in a separate script and
// used #include to put it into this script (and others), as well
void ClearAllFactionMembers(object oMember, object oPlayer)
{
    object oClear = GetFirstFactionMember(oMember, FALSE);
 
    while(GetIsObjectValid(oClear))
    {
        ClearPersonalReputation(oPlayer, oClear);
        oClear = GetNextFactionMember(oMember, FALSE);
    }
}
 
// Here's the main body of my script
void main()
{
    // Identify the player
    object oPlayer = GetLastPlayerDied();
 
    // Identify a member of the faction. I'm assuming these members are alive.
    // Otherwise, I would have to try several things to turn up a member that is
    // first valid and then do the if command
    object oGoblin = GetObjectByTag("GOBLIN1");
 
    if (GetIsObjectValid(oGoblin))
    {
        // Adjust the faction relation back up by 100
        AdjustReputation(oPlayer, oGoblin, 100);
 
        // Run my custom command to cycle through the faction
        ClearAllFactionMembers(oGoblin, oPlayer);
    }
}

Fun with shouts

A couple of things to try out or use, with regards to shouts.

First one: I always hate the fact that an NPC may hear the "NW_ATTACK_ MY_TARGET" shout... but they won't do anything if they can't see the shouter or the target. They go hostile, but won't attack until the enemy comes into sight. Here's a way I've successfully used to make out-of-sight listeners come to investigate the shout.

Step 1
First thing that must be done is that all members of the faction who will respond to the shout and also investigate a shout from nearby must have their OnSpawn script set up accordingly. Go into their OnSpawn script and uncomment the following lines:
  • SetSpawnInCondition(NW_FLAG_SHOUT_ATTACK_MY_TARGET);
  • SetSpawnInCondition(NW_FLAG_ON_DIALOGUE_EVENT);
Then re-save the script under a different name.
Step 2
Remember that a creature will only shout "attack my target" if he is attacked first or killed. If you want him to shout it, himself, you'll have to put it into a script. There is one supplied in the examples above that has the creature go hostile and make the shout off of dialogue.
Step 3
Put the following script into each creature's OnUserDefined event:
// This script will make the receiver of a "NW_ATTACK_MY_TARGET" shout come and
// investigate, even if he doesn't see an enemy. Once he approaches the shouter,
// if he sees an enemy his OnPerception script will activate and he will attack.
// An interesting modification to this script would be to have the out-of-sight
// receiver of the shout issue a second shout which would carry the original shout
// further... this would, however, require a new shout and new pattern definition.
void main()
{
    int nEvent = GetUserDefinedEventNumber();
 
    if (nEvent == 1004) // OnDialog event
    {
        // This sees if the shout matches the pattern set by the
        // "SetListeningPatterns" in OnSpawn
        int nMatch = GetListenPatternNumber();
        object oShouter = GetLastSpeaker();
        object oIntruder;
 
        // If I recognize the shout and the shouter is a valid, friendly NPC
        if(nMatch != -1 && GetIsObjectValid(oShouter) && !GetIsPC(oShouter) && GetIsFriend(oShouter))
        {
            // And the shout is "NW_ATTACK_MY_TARGET"
            if (nMatch == 5)
            {
                // Attempt to define the shouter's enemy
                oIntruder = GetLastHostileActor(oShouter);
 
                if (!GetIsObjectValid(oIntruder))
                {
                    oIntruder = GetAttemptedAttackTarget();
 
                    if (!GetIsObjectValid(oIntruder))
                    {
                        oIntruder = GetAttemptedSpellTarget();
 
                        if(!GetIsObjectValid(oIntruder))
                        {
                            oIntruder = OBJECT_INVALID;
                        }
                    }
                }
 
                // If I can see neither the shouter nor the enemy
                if (GetIsObjectValid(oShouter) && !GetObjectSeen(oIntruder) && !GetObjectSeen(oShouter))
                {
 
                    // Define the location of the shouter
                    location lShouter = GetLocation(oShouter);
 
                    // ...stop what I am doing
                    ClearAllActions();
 
                    // ...and move to that location
                    ActionMoveToLocation (lShouter, TRUE);
                }
 
                // Otherwise, if I can see the shouter but not the enemy
                else if (GetIsObjectValid(oShouter) && !GetObjectSeen(oIntruder) && GetObjectSeen(oShouter))
                {
                    // Stop what I am doing
                    ClearAllActions();
 
                    // ...and move towards the shouter
                    ActionMoveToObject(oShouter, TRUE);
                }
            }
        }
    }
}

Second one: Here's an interesting exercise that demonstrates how to make your own shout. In this example, I want to have a dialogue that issues a shout and causes all who hear it to perform the 'worship' animation (they think the PC is the 'one foretold').

Step 1
Let's set up the dialogue of the NPC who is going to issue the shout. Let's make it very simple: one line of dialogue which says "It is the One Foretold!" and then the following script attached to the "Actions Taken" area of that line:
void main()
{
    // Set a variable on myself equal to the PC I'm talking to
    SetLocalObject(OBJECT_SELF, "worship", GetPCSpeaker());
 
    // Issue the "BOWDOWN" shout
    SpeakString("BOWDOWN", TALKVOLUME_SILENT_TALK);
 
    // Perform the worship animation for 30 seconds
    ActionPlayAnimation(ANIMATION_LOOPING_WORSHIP, 0.5, 30.0);
}
Step 2
We have to set up the other NPCs in the area to listen to the shout. This involves uncommenting the line SetSpawnInCondition(NW_FLAG_ ON_DIALOGUE_EVENT); in their OnSpawn script as well as adding the following line anywhere:
SetListenPattern(OBJECT_SELF, "BOWDOWN", 100);
This makes the string "BOWDOWN" equal to the number 100 (picked at random) and recognizeable should the NPC be listening (using the command SetListening(OBJECT_SELF, TRUE)... which is issues in the OnSpawn's SetListenPattern() command, anyway).
Step 3
Now we just have to tell the NPCs what to do when they hear that command. Any spoken string sets off their OnConversation event, so the following script can be placed into their OnUserDefined event:
void main()
{
    int nEvent = GetUserDefinedEventNumber();
 
    // If I receive the OnDialogue event
    if (nEvent == 1004)
    {
        // ...see if I heard a matching pattern
        int nMatch = GetListenPatternNumber();
 
        // ...identify who spoke the string
        object oShouter = GetLastSpeaker();
 
        // ...and grab the variable from the NPC as to who they're bowing down to
        object oWorship = GetLocalObject(oShouter, "worship");
 
        // If the string is one I recognize and the speaker is a friendly, valid NPC
        if(nMatch != -1 && GetIsObjectValid(oShouter) && !GetIsPC(oShouter) && GetIsFriend(oShouter))
        {
            // ...and the string pattern was equal to "BOWDOWN"
            if (nMatch == 100)
            {
                // ...turn to face the PC the NPC was talking to
                ActionDoCommand(SetFacingPoint(GetPosition(oWorship)));
 
                // ...and then play the worship animation for 30 seconds
                ActionPlayAnimation(ANIMATION_LOOPING_WORSHIP, 0.5, 30.0);
            }
        }
    }
}

This obviously isn't everything there is to know about factions and shouts, but hopefully the information is of use to some.


author: David Gaider, editor: Squatting Monk