/*** 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); } }