Amon_PRC8/_module/nss/x3_pl_tool01.nss
Jaysyn904 c5cffc37af Initial Commit
Initial Commit [v1.01]
2025-04-03 19:00:46 -04:00

862 lines
31 KiB
Plaintext

/***
Balkoth's Minion Control v2_01 (BMC_v2_01)
- Allows some direction of henchmen, summons, animal companions, and familiars
- Should be attached to an instant feat like the player tools or an
item's activate item (long range)
- If targeted on a hostile creature, commands all minions to attack that creature
- If targeted on a location, commands all minions to move to that location
- If targeted on a friendly/neutral creature, commands all minions to follow
that creature.
- If targeted on a locked door/object, commands any minions designated to help
with locked doors/objects to try to open the lock (including bashing if necessary)
- If targeted on a trapped door/object/trigger, commands any minions designated
to help with traps to attempt to disarm the trap (including triggering the trap
if necessary)
Installation is fairly simple with a short checklist:
1. Replace Player Tool 1 (x3_pl_tool01) with this file (or if you don't want to
use a player tool, then set it up so this script is called with however you're
calling it -- though I'd strongly suggest something instant like a player tool)
2. Ensure the players actually HAVE the player tool or whatever you're using
to call this script
3. Copy/paste four lines of code to the start of five events (if your henchmen
use different scripts than summons/animal companions/familiars then you'll need
to do this part for each set of scripts)
4. Copy/paste a unique segment of code to the start of OnCombatRoundEnd (again, this
may need to be done multiple times if you have multiple OnCombatRoundEnd scripts)
5. Copy/paste a unique segment of code to the start of OnDialogue (again, this may
need to be done multiple times if you have multiple OnDialogue scripts)
6. Nothing else, you're done!
IMPORTANT NOTE!!!
IMPORTANT NOTE!!!
IMPORTANT NOTE!!!
If you ever want to force a cancellation of this tool (for the purposes of a cutscene
or something), then simply call the following line on each minion (oMinion being the
minion in question):
SetLocalInt(oMinion, "bmc_active", 0)
The exact method doesn't matter as long as the minon has the local int "bmc_active"
set to 0. That serves as a kill switch in effect.
END IMPORTANT NOTE!!!
Let's begin!
1. This is straightforward if you're replacing a player tool script. Otherwise,
you'll have to do...whatever it is you're doing. That's rather hard for me to guess,
but if you need help you can contact me via the email listed at the bottom of this
header section.
2. If you're not sure how to grant access to Player Tool 1 to players, then assuming
your module ensures PCs have creature skins you can place the following code segment
into your module's Heartbeat:
object oPC = GetFirstPC();
while (GetIsObjectValid(oPC))
{
if (!GetHasFeat(FEAT_PLAYER_TOOL_01, oPC))
{
object oHide = GetItemInSlot(INVENTORY_SLOT_CARMOUR, oPC);
if (GetIsObjectValid(oHide))
{
FloatingTextStringOnCreature("Player Tool 1 acquired. Use this to direct companions.", oPC, FALSE);
AddItemProperty(DURATION_TYPE_PERMANENT, ItemPropertyBonusFeat(IP_CONST_FEAT_PLAYER_TOOL_01), oHide);
}
}
oPC = GetNextPC();
}
This will loop through all PCs and attempt to give them the player tool if they lack it.
A better way to handle it would be to use OnClientEnter or OnAreaEnter or something --
but then you also have to worry about timing issues (the code won't do anything if
the PC doesn't have a creature skin equipped yet). Ultimately it's your call -- and
again, I'm happy to help if you require assistance.
If your PCs don't have creature skins for whatever reason, you could either
adjust this code to spawn a creature skin if necessary or change it to the PC's armor
or something I guess. Again, this is very module dependent so hard for me to be precise.
3. In order to avoid things like telling the minion to move to a location and then
having it decide to attack something instead, the AI needs to be disabled.
You will need to put the following lines of code in five creature events. I suggest
starting by placing the code right at the start of main for each event and then
consulting the ADVANCED TROUBLESHOOTING section below if you notice any oddities.
// Don't do anything if we have have been recently commanded
if (GetLocalInt(OBJECT_SELF, "bmc_active"))
{
return;
}
This literally just tells the AI to not do anything new if we've been issued an order.
The five creature events are listed below (along with the corresponding default
script for summons/familiars/animal companions -- NPC companions will usually
have different scripts)
OnHeartbeat (nw_ch_ac1)
OnPerceive (nw_ch_ac2)
OnAttacked (nw_ch_ac5)
OnDamaged (nw_ch_ac6)
OnSpellCastAt (nw_ch_acb)
4. Great, we've disabled the AI in some cases. Unfortunately, we WANT the AI to respond to
some things still -- such as healing itself if injured and using spells properly. So we
need to do something slightly different for
OnCombatRoundEnd (nw_c2_ac3)
As the name suggests, this is called at the end of each round to determine what to do next.
We basically want to rerun part of the AI without changing targets, so we put the following
block of code at the start of the event (the first lines exist to fix Bioware bugs):
// Fixes some Bioware bugs
object oBiowareBash = GetLocalObject(OBJECT_SELF, "NW_GENERIC_DOOR_TO_BASH");
if (GetIsObjectValid(oBiowareBash))
{
// This happens if we have a door transition that was destroyed
if (GetCurrentHitPoints(oBiowareBash) <= 0)
{
ResetHenchmenState();
}
else
{
// Just use feats as appropriate rather than having to damage the
// object first (default Bioware behavior)
if(GetHasFeat(FEAT_IMPROVED_POWER_ATTACK))
{
ActionUseFeat(FEAT_IMPROVED_POWER_ATTACK, oBiowareBash);
return;
}
else if(GetHasFeat(FEAT_POWER_ATTACK))
{
ActionUseFeat(FEAT_POWER_ATTACK, oBiowareBash);
return;
}
else
{
ActionAttack(oBiowareBash);
return;
}
}
}
// Don't do anything if we have have been recently commanded
if (GetLocalInt(OBJECT_SELF, "bmc_active"))
{
// See if we have a valid creature to attack
object oTarget = GetLocalObject(OBJECT_SELF, "bmc_attacktarget");
if (GetIsObjectValid(oTarget) && GetIsEnemy(oTarget) && !GetIsDead(oTarget))
{
DetermineCombatRound(oTarget);
return;
}
else
{
DeleteLocalObject(OBJECT_SELF, "bmc_attacktarget");
}
oTarget = GetAttackTarget();
if (GetIsObjectValid(oTarget) && GetIsEnemy(oTarget) && !GetIsDead(oTarget))
{
DetermineCombatRound(oTarget);
return;
}
oTarget = GetAttemptedAttackTarget();
if (GetIsObjectValid(oTarget) && GetIsEnemy(oTarget) && !GetIsDead(oTarget))
{
DetermineCombatRound(oTarget);
return;
}
return;
}
Now if we're attacking a target or trying to attack a target, we'll check for things like
self-healing. If we're doing anything else, then we'll trust it was triggered by this script
since the normal AI is disabled and thus not interfere.
5. We've disabled the bulk of the AI except for some specific things we want to check. But what
if the PLAYER wants to order the minions around with the voice lines (things like
Attack Nearest, Follow Me, and so on)? That's handled in
OnDialogue (nw_ch_ac4)
We need a new chunk of code to cancel our AI override in response to certain shouts (but
still ignore other shouts at times):
object oMinionMaster = GetMaster(), oMinionShouter = GetLastSpeaker();
int nMinionMatch = GetListenPatternNumber();
if(GetIsObjectValid(oMinionShouter) && oMinionMaster == oMinionShouter)
{
switch (nMinionMatch)
{
// These are orders we want to re-enable AI for
case ASSOCIATE_COMMAND_ATTACKNEAREST:
case ASSOCIATE_COMMAND_FOLLOWMASTER:
case ASSOCIATE_COMMAND_GUARDMASTER:
case ASSOCIATE_COMMAND_STANDGROUND:
case ASSOCIATE_COMMAND_HEALMASTER:
case ASSOCIATE_COMMAND_PICKLOCK:
case ASSOCIATE_COMMAND_DISARMTRAP:
case ASSOCIATE_COMMAND_MASTERFAILEDLOCKPICK:
case ASSOCIATE_COMMAND_LEAVEPARTY:
DeleteLocalObject(OBJECT_SELF, "bmc_attacktarget");
SetLocalInt(OBJECT_SELF, "bmc_active", 0);
break;
// These are orders we want to ignore
case ASSOCIATE_COMMAND_MASTERGOINGTOBEATTACKED:
case ASSOCIATE_COMMAND_MASTERATTACKEDOTHER:
case ASSOCIATE_COMMAND_MASTERUNDERATTACK:
if (GetLocalInt(OBJECT_SELF, "bmc_active"))
{
return;
}
break;
}
}
6. You're done! Enjoy! Ignore the rest of this header if you value your sanity!
*** ADVANCED TROUBLESHOOTING ***
I'll be blunt: this is a rough workaround designed for easy installation. I've sacrificed some
efficiency to make it easier for people who aren't programmers to get it working. This is minor
enough that it should NEVER matter, but before you come to me shouting about how this could be
more efficient and some stuff is processed twice please think of this from the perspective of
"How can I make this one code block people can just throw at the top of a main function?" If you
STILL think it can be improved I would happy to listen, but I am aware (for example) that some code
is getting duplicated for the OnDialogue code. Again, trying to make this something you can just
plop down start of main for each script without needing to know anything about how programming works.
This also has some other implications such as concentration checks for something like the Black Blade
of Disaster. Technically speaking the AI suppression should be placed AFTER the concentration check on
the Heartbeat script as an example. All that really matters is that you return BEFORE stuff like the
DetermineCombatRound calls if we're in a command state. But that requires people to be able to
read the code and figure out where to properly place it.
If you have custom behavior in these events, that may also cause problems if you avoid that part of the
code by returning from this AI suppression. Feel free to move the code segments down in the code --
as I just mentioned you don't HAVE to put them at the start of main, strictly speaking. Just keep them
before the main combat AI calls because you don't want your minion to change its mind about attacking
that wizard in the back just because the wizard's summoned badger tried to bite the minion.
If you're still having trouble getting this to work properly, feel free to email me at
balkothwarcraft@gmail.com
and I would be happy to assist you. I did a bunch of custom work for Aielund, as an example.
***/
// This is used to check whether we got removed from the party mid-command
object oOriginalMaster;
// Distance (in meters) that we consider to be "close enough" when moving
float fCloseEnough = 1.5;
// Necessary for DetermineCombatRound
#include "NW_I0_GENERIC"
// Necessary for opening locks and disarming traps
#include "x0_inc_henai"
// Aielund specific
//#include "hench_i0_strings"
// Handles command status stuff
int MakeUncommandable();
// Handles whether we're trying to start or in a conversation
int CheckConversation(object oTalker, int nAction);
// Regroups the party so no one misses any dialogue
void RegroupParty();
// Check if the creature is a minion directly or indirectly
int IsMinion(object oMinion);
// Handles attacking
void GroupAttack(object oTarget);
void SingleAttack(object oTarget);
void AttackAI(object oTarget, int nCommand, float fTracker, object oArea);
// Handles moving to creature
void GroupMoveToCreature(object oTarget);
void SingleMoveToCreature(object oTarget);
void TryToMoveToCreature(object oTarget, int nCommand, object oArea);
// Handles moving to a location
void GroupMoveToLocation(location lTarget);
void SingleMoveToLocation(location lTarget);
void TryToMoveToLocation(location lTarget, int nCommand);
// Handles unlocking (or bashing) something
void GroupUnlock(object oTarget);
void SingleUnlock(object oTarget);
void CheckLockStatus(object oTarget, int nCommand, object oArea);
// What if it's trapped?
void GroupDisarm(object oTarget);
void SingleDisarm(object oTarget);
void CheckDisarmStatus(object oTarget, int nCommand, int nDisarmActive, object oArea);
void main()
{
// This will need to be changed if an item is used instead of a player feat
oOriginalMaster = OBJECT_SELF;
// Find what we targeted
object oTarget = GetSpellTargetObject();
// If we have a valid target, use that location, otherwise get the location
location lTarget = GetLocation(oTarget);
if (oTarget == OBJECT_INVALID)
{
lTarget = GetSpellTargetLocation();
}
// If we're in a conversation, prevent new commands as it might
// break the dialogue if minions move around. Regroup party instead.
if (IsInConversation(oOriginalMaster))
{
FloatingTextStringOnCreature("In a conversation, regrouping party.", oOriginalMaster, FALSE);
RegroupParty();
return;
}
// Targeted on object
if (GetIsObjectValid(oTarget))
{
if (GetObjectType(oTarget) == OBJECT_TYPE_CREATURE)
{
// Attack enemies
if (GetIsEnemy(oTarget, oOriginalMaster))
{
GroupAttack(oTarget);
}
// Move to friendly/neutral targets
else
{
GroupMoveToCreature(oTarget);
}
}
else if (GetObjectType(oTarget) == OBJECT_TYPE_DOOR)
{
// Disarm trapped doors
if (GetTrapDetectedBy(oTarget, oOriginalMaster))
{
GroupDisarm(oTarget);
}
// Open locked doors
else if (GetLocked(oTarget))
{
GroupUnlock(oTarget);
}
else
{
FloatingTextStringOnCreature("That door is not locked or trapped.", oOriginalMaster, FALSE);
}
}
else if (GetObjectType(oTarget) == OBJECT_TYPE_ITEM)
{
// Not even sure what could be done with this
FloatingTextStringOnCreature("No functionality for items currently.", oOriginalMaster, FALSE);
}
else if (GetObjectType(oTarget) == OBJECT_TYPE_PLACEABLE)
{
// Disarm trapped doors
if (GetTrapDetectedBy(oTarget, oOriginalMaster))
{
GroupDisarm(oTarget);
}
// Open locked chests and such
else if (GetLocked(oTarget))
{
GroupUnlock(oTarget);
}
else
{
FloatingTextStringOnCreature("That object is not locked or trapped.", oOriginalMaster, FALSE);
}
}
else if (GetObjectType(oTarget) == OBJECT_TYPE_TRIGGER && GetTrapDetectedBy(oTarget, oOriginalMaster))
{
GroupDisarm(oTarget);
}
}
// Targeted on location
else
{
// Move to location
GroupMoveToLocation(lTarget);
}
}
int MakeUncommandable()
{
int nCommand = GetLocalInt(OBJECT_SELF, "bmc_value");
SetLocalInt(OBJECT_SELF, "bmc_active", 1);
// This progresses our bmc_value so we know whether the order is current
// The %10000 prevents it from potentially going out of bounds -- not sure how
// NWN handles that so let's be safe
nCommand = (++nCommand)%10000;
SetLocalInt(OBJECT_SELF, "bmc_value", nCommand);
return nCommand;
}
// Handles whether we're trying to start or in a conversation
int CheckConversation(object oTalker, int nAction)
{
return (nAction == ACTION_WAIT || IsInConversation(oTalker));
}
// Regroups the party so no one misses any dialogue
void RegroupParty()
{
// Going to loop through members of the party
object oMinion = GetFirstFactionMember(oOriginalMaster, FALSE);
while (GetIsObjectValid(oMinion))
{
// Regroup the party for a nice chat
if (IsMinion(oMinion))
{
SetLocalInt(oMinion, "bmc_active", 0);
if (!CheckConversation(oMinion, GetCurrentAction(oMinion)))
{
AssignCommand(oMinion, ClearAllActions(TRUE));
AssignCommand(oMinion, JumpToObject(oOriginalMaster));
}
}
oMinion = GetNextFactionMember(oOriginalMaster, FALSE);
}
}
// Check if the creature is a minion directly or indirectly
int IsMinion(object oMinion)
{
return (GetMaster(oMinion) == oOriginalMaster || GetMaster(GetMaster(oMinion)) == oOriginalMaster || GetMaster(GetMaster(GetMaster(oMinion))) == oOriginalMaster);
}
void GroupAttack(object oTarget)
{
// Going to loop through members of the party
object oMinion = GetFirstFactionMember(oOriginalMaster, FALSE);
while (GetIsObjectValid(oMinion))
{
// If the party member's master (or master's master for minion summons or
// even master's master's master) is the PC, issue orders to attack
if (IsMinion(oMinion))
{
AssignCommand(oMinion, SingleAttack(oTarget));
}
oMinion = GetNextFactionMember(oOriginalMaster, FALSE);
}
}
void SingleAttack(object oTarget)
{
int nCommand = MakeUncommandable();
ResetHenchmenState();
SetAssociateState(NW_ASC_MODE_DEFEND_MASTER, FALSE);
SetAssociateState(NW_ASC_MODE_STAND_GROUND, FALSE);
// Aielund specific, cancel peaceful follow mode
//if (GetLocalInt(OBJECT_SELF,"DoNotAttack"))
//{
// SetLocalInt(OBJECT_SELF,"DoNotAttack",FALSE);
// SpeakString(sHenchPeacefulModeCancel);
//}
SetLocalObject(OBJECT_SELF, "bmc_attacktarget", oTarget);
DetermineCombatRound(oTarget);
DelayCommand(0.5, AttackAI(oTarget, nCommand, 0.0, GetArea(OBJECT_SELF)));
}
void AttackAI(object oTarget, int nCommand, float fTracker, object oArea)
{
// Has our command value changed?
if (nCommand != GetLocalInt(OBJECT_SELF, "bmc_value") || !GetLocalInt(OBJECT_SELF, "bmc_active"))
{
return;
}
int nAction = GetCurrentAction();
if (CheckConversation(OBJECT_SELF, nAction) || IsInConversation(oOriginalMaster))
{
DeleteLocalObject(OBJECT_SELF, "bmc_attacktarget");
SetLocalInt(OBJECT_SELF, "bmc_active", 0);
RegroupParty();
return;
}
if (GetIsDead(oTarget) || !GetIsEnemy(oTarget) || GetArea(oTarget) != oArea || GetIsDead(OBJECT_SELF) ||
(GetMaster() != oOriginalMaster && GetMaster(GetMaster()) != oOriginalMaster && GetMaster(GetMaster(GetMaster())) != oOriginalMaster))
{
DeleteLocalObject(OBJECT_SELF, "bmc_attacktarget");
SetLocalInt(OBJECT_SELF, "bmc_active", 0);
return;
}
if (nAction == ACTION_ATTACKOBJECT)
{
if (fTracker >= 4.0)
{
DetermineCombatRound(oTarget);
if (GetCurrentAction() != ACTION_INVALID)
{
DelayCommand(0.5, AttackAI(oTarget, nCommand, 0.0, oArea));
}
else
{
ActionAttack(oTarget);
DelayCommand(0.5, AttackAI(oTarget, nCommand, fTracker + 0.5, oArea));
}
}
else if (fTracker >= 2.0)
{
DelayCommand(0.5, AttackAI(oTarget, nCommand, fTracker + 0.5, oArea));
}
else
{
DelayCommand(0.5, AttackAI(oTarget, nCommand, 0.0, oArea));
}
}
else if (nAction == ACTION_INVALID)
{
if (fTracker >= 1.5)
{
ActionAttack(oTarget);
DelayCommand(0.5, AttackAI(oTarget, nCommand, fTracker + 0.5, oArea));
}
else
{
DetermineCombatRound(oTarget);
DelayCommand(0.5, AttackAI(oTarget, nCommand, fTracker + 0.5, oArea));
}
}
else if (nAction == ACTION_FOLLOW)
{
ClearAllActions();
DetermineCombatRound(oTarget);
DelayCommand(0.5, AttackAI(oTarget, nCommand, fTracker + 0.5, oArea));
}
else
{
DelayCommand(0.5, AttackAI(oTarget, nCommand, 0.0, oArea));
}
}
void GroupMoveToLocation(location lTarget)
{
// Going to loop through members of the party
object oMinion = GetFirstFactionMember(oOriginalMaster, FALSE);
while (GetIsObjectValid(oMinion))
{
// If the party member's master (or master's master for minion summons or
// even master's master's master) is the PC, issue orders to move to the location
if (IsMinion(oMinion))
{
AssignCommand(oMinion, SingleMoveToLocation(lTarget));
}
oMinion = GetNextFactionMember(oOriginalMaster, FALSE);
}
}
void SingleMoveToLocation(location lTarget)
{
int nCommand = MakeUncommandable();
ResetHenchmenState();
SetAssociateState(NW_ASC_MODE_DEFEND_MASTER, FALSE);
SetAssociateState(NW_ASC_MODE_STAND_GROUND, FALSE);
DeleteLocalObject(OBJECT_SELF, "bmc_attacktarget");
TryToMoveToLocation(lTarget, nCommand);
}
void TryToMoveToLocation(location lTarget, int nCommand)
{
// Has our command value changed?
if (nCommand != GetLocalInt(OBJECT_SELF, "bmc_value") || !GetLocalInt(OBJECT_SELF, "bmc_active"))
{
return;
}
float fDist = GetDistanceBetweenLocations(lTarget, GetLocation(OBJECT_SELF));
int nAction = GetCurrentAction();
if (CheckConversation(OBJECT_SELF, nAction) || IsInConversation(oOriginalMaster))
{
SetLocalInt(OBJECT_SELF, "bmc_active", 0);
RegroupParty();
return;
}
// Are we in another area or dead or fired?
if (fDist < 0.0 || GetIsDead(OBJECT_SELF) || (GetMaster() != oOriginalMaster && GetMaster(GetMaster()) != oOriginalMaster && GetMaster(GetMaster(GetMaster())) != oOriginalMaster))
{
SetLocalInt(OBJECT_SELF, "bmc_active", 0);
return;
}
// Only give the move command if we're too far away and not moving
if (fDist > fCloseEnough && nAction != ACTION_MOVETOPOINT)
{
ClearAllActions();
ActionMoveToLocation(lTarget, TRUE);
}
// Call again until we're there, bmc_value changes, or we time out
DelayCommand(0.5, TryToMoveToLocation(lTarget, nCommand));
}
void GroupMoveToCreature(object oTarget)
{
// Going to loop through members of the party
object oMinion = GetFirstFactionMember(oOriginalMaster, FALSE);
while (GetIsObjectValid(oMinion))
{
// If the party member's master (or master's master for minion summons or
// even master's master's master) is the PC, issue orders to follow the creature
if (IsMinion(oMinion))
{
AssignCommand(oMinion, SingleMoveToCreature(oTarget));
}
oMinion = GetNextFactionMember(oOriginalMaster, FALSE);
}
}
void SingleMoveToCreature(object oTarget)
{
int nCommand = MakeUncommandable();
ResetHenchmenState();
SetAssociateState(NW_ASC_MODE_DEFEND_MASTER, FALSE);
SetAssociateState(NW_ASC_MODE_STAND_GROUND, FALSE);
DeleteLocalObject(OBJECT_SELF, "bmc_attacktarget");
TryToMoveToCreature(oTarget, nCommand, GetArea(OBJECT_SELF));
}
void TryToMoveToCreature(object oTarget, int nCommand, object oArea)
{
// Has our command value changed?
if (nCommand != GetLocalInt(OBJECT_SELF, "bmc_value") || !GetLocalInt(OBJECT_SELF, "bmc_active"))
{
return;
}
int nAction = GetCurrentAction();
if (CheckConversation(OBJECT_SELF, nAction) || IsInConversation(oOriginalMaster))
{
SetLocalInt(OBJECT_SELF, "bmc_active", 0);
RegroupParty();
return;
}
// Are we in another area or dead or fired?
if (GetArea(OBJECT_SELF) != oArea || GetIsDead(OBJECT_SELF) ||
(GetMaster() != oOriginalMaster && GetMaster(GetMaster()) != oOriginalMaster && GetMaster(GetMaster(GetMaster())) != oOriginalMaster))
{
SetLocalInt(OBJECT_SELF, "bmc_active", 0);
return;
}
float fDist = GetDistanceBetween(oTarget, OBJECT_SELF);
// Only give the command if we're too far away and not moving
if (fDist > fCloseEnough && GetCurrentAction() != ACTION_MOVETOPOINT)
{
ClearAllActions();
ActionMoveToObject(oTarget, TRUE);
}
// Call again until bmc_value changes or something else cancels us out
DelayCommand(0.5, TryToMoveToCreature(oTarget, nCommand, oArea));
}
void GroupUnlock(object oTarget)
{
// Going to loop through members of the party
object oMinion = GetFirstFactionMember(oOriginalMaster, FALSE);
while (GetIsObjectValid(oMinion))
{
// If the party member's master is the PC, issue orders to unlock (NOT including summons of minions)
if (GetMaster(oMinion) == oOriginalMaster)
{
if(GetAssociateState(NW_ASC_RETRY_OPEN_LOCKS, oMinion))
{
FloatingTextStringOnCreature("Sending " + GetName(oMinion) + " to deal with the lock.", oOriginalMaster, FALSE);
AssignCommand(oMinion, SingleUnlock(oTarget));
}
}
oMinion = GetNextFactionMember(oOriginalMaster, FALSE);
}
}
void SingleUnlock(object oTarget)
{
int nCommand = MakeUncommandable();
ResetHenchmenState();
SetAssociateState(NW_ASC_MODE_DEFEND_MASTER, FALSE);
SetAssociateState(NW_ASC_MODE_STAND_GROUND, FALSE);
DeleteLocalObject(OBJECT_SELF, "bmc_attacktarget");
if (!bkAttemptToOpenLock(oTarget))
{
FloatingTextStringOnCreature(GetName(OBJECT_SELF) + " cannot deal with the lock.", GetMaster(), FALSE);
ResetHenchmenState();
SetLocalInt(OBJECT_SELF, "bmc_active", 0);
return;
}
DelayCommand(0.5, CheckLockStatus(oTarget, nCommand, GetArea(OBJECT_SELF)));
}
void CheckLockStatus(object oTarget, int nCommand, object oArea)
{
// Has our command value changed?
if (nCommand != GetLocalInt(OBJECT_SELF, "bmc_value") || !GetLocalInt(OBJECT_SELF, "bmc_active"))
{
return;
}
int nAction = GetCurrentAction();
if (CheckConversation(OBJECT_SELF, nAction) || IsInConversation(oOriginalMaster))
{
SetLocalInt(OBJECT_SELF, "bmc_active", 0);
RegroupParty();
return;
}
// Are we in another area or dead or fired?
if (GetArea(OBJECT_SELF) != oArea || GetIsDead(OBJECT_SELF) ||
(GetMaster() != oOriginalMaster && GetMaster(GetMaster()) != oOriginalMaster && GetMaster(GetMaster(GetMaster())) != oOriginalMaster))
{
SetLocalInt(OBJECT_SELF, "bmc_active", 0);
return;
}
if (GetLocked(oTarget) && GetCurrentHitPoints(oTarget) > 0)
{
if (GetCurrentAction() == ACTION_INVALID)
{
ClearAllActions();
bkAttemptToOpenLock(oTarget);
}
DelayCommand(0.5, CheckLockStatus(oTarget, nCommand, oArea));
}
else
{
ResetHenchmenState();
SetLocalInt(OBJECT_SELF, "bmc_active", 0);
}
}
void GroupDisarm(object oTarget)
{
// Going to loop through members of the party
object oMinion = GetFirstFactionMember(oOriginalMaster, FALSE);
while (GetIsObjectValid(oMinion))
{
// If the party member's master is the PC, issue orders to disarm (NOT including summons of minions)
if (GetMaster(oMinion) == oOriginalMaster)
{
if(GetAssociateState(NW_ASC_DISARM_TRAPS, oMinion))
{
FloatingTextStringOnCreature("Sending " + GetName(oMinion) + " to deal with the trap.", oOriginalMaster, FALSE);
AssignCommand(oMinion, SingleDisarm(oTarget));
}
}
oMinion = GetNextFactionMember(oOriginalMaster, FALSE);
}
}
void SingleDisarm(object oTarget)
{
int nCommand = MakeUncommandable();
ResetHenchmenState();
SetAssociateState(NW_ASC_MODE_DEFEND_MASTER, FALSE);
SetAssociateState(NW_ASC_MODE_STAND_GROUND, FALSE);
DeleteLocalObject(OBJECT_SELF, "bmc_attacktarget");
CheckDisarmStatus(oTarget, nCommand, 0, GetArea(OBJECT_SELF));
}
void CheckDisarmStatus(object oTarget, int nCommand, int nDisarmActive, object oArea)
{
// Has our command value changed?
if (nCommand != GetLocalInt(OBJECT_SELF, "bmc_value") || !GetLocalInt(OBJECT_SELF, "bmc_active"))
{
return;
}
int nAction = GetCurrentAction();
if (CheckConversation(OBJECT_SELF, nAction) || IsInConversation(oOriginalMaster))
{
SetLocalInt(OBJECT_SELF, "bmc_active", 0);
RegroupParty();
return;
}
// Are we in another area or dead or fired?
if (GetArea(OBJECT_SELF) != oArea || GetIsDead(OBJECT_SELF) ||
(GetMaster() != oOriginalMaster && GetMaster(GetMaster()) != oOriginalMaster && GetMaster(GetMaster(GetMaster())) != oOriginalMaster))
{
SetLocalInt(OBJECT_SELF, "bmc_active", 0);
return;
}
if (GetTrapDetectedBy(oTarget, OBJECT_SELF))
{
if (GetDistanceToObject(oTarget) > 15.0 || !bkGetIsInLineOfSight(oTarget))
{
ActionMoveToObject(oTarget, TRUE);
}
else
{
if (!nDisarmActive)
{
ClearAllActions();
if (!bkAttemptToDisarmTrap(oTarget, TRUE))
{
FloatingTextStringOnCreature(GetName(OBJECT_SELF) + " cannot deal with the trap.", GetMaster(), FALSE);
ResetHenchmenState();
SetLocalInt(OBJECT_SELF, "bmc_active", 0);
return;
}
nDisarmActive = 1;
}
else if (nAction == ACTION_INVALID && nDisarmActive)
{
ClearAllActions();
bkAttemptToDisarmTrap(oTarget, TRUE);
}
}
DelayCommand(0.1, CheckDisarmStatus(oTarget, nCommand, nDisarmActive, oArea));
}
else
{
ResetHenchmenState();
SetLocalInt(OBJECT_SELF, "bmc_active", 0);
}
}