HiddenTradition_PRC8/_module/nss/69_inc_henai.nss
Jaysyn904 f50efc97c0 Added Faction Zoo
- igor doesn't give boxes if you choose to explore a bit, but he takes the contract anyway [quest-breaking]
	*Fixed.  Moved some dialog around so you couldn't skip out on taking the quest after you gave up the letter.

- key 'Pilgrims Cave Key' it_pilgrimscavek to plot-locked door isn't provided anywhere [quest-breaking]
	*Not a bug.  Key drops from the Elder Goblin Shaman in Pilgrim's Cave

Full compile.  Updated release archive.

- can't get farmer bouillon's quest if already offered to buy an ox [quest-breaking]
	*Couldn't replicate.  Nothing in the dialog is locked regarding this.

- jerusalem watch captain doesn't recognise letter to ogre mage [quest-breaking]
	*Fixed.  Quest item tag was mispelled.

- plot key 'amulet of thievery' [amuletofthievery] to plot-locked door never given anywhere
	*Not fixed.  This may be for an incomplete area or quest.  No NPC named "Fremmy South" exists in the module, nor is the name mentioned in dialogs.

- key 'Lomendel's Front Door Key' [it_LomFrontKey] to plot-locked door never given anywhere.
	*Not fixed.  This appears to be a DM or system area.  No NPC named Lomendel exists & the only mention of them in the dialogs is by the Efreeti merchant in the area.

- key it_RachelsRoomKey [rachelskey] to plot-locked door never given anywhere
	*Not fixed.  This may be for an incomplete area or quest.  No NPC named "Rachel" exists in the module, nor is the name mentioned in dialogs.  There is nothing of note in the room.

- east tower key given but no doors on any east towers anywhere
	*Not fixed.  There is no "East Tower Key" in the item palette at all.  Balkan East Tower doesn't require a key.

- transitions within areas don't jump henchmen with pc
	*Fixed.  Changed the triggers to use scripted transitions that bring all associates with the PC.

- rd to bethlehem - broken trans [gate to nowhere]
	*Not fixed.  Not an actual transition, There is no "Convent" in the area list.  Gate description now tells you it's barred.

- faction bugs with guards at pilgrims' rest
	*Maybe fixed.  I added a "faction zoo" and set the merchant faction to neutral to all of the other factions.

- faction bugs with watchmen in jerusalem
	*See above

- dwarf captives in svirfneblin lair - invalid exit waypoint
	*Fixed.  Added missing waypoint.

- elvish grove quest journal doesn't close
	*Couldn't replicate.  Journal entry cleared normally upon completion of the quest.

- dwarven mine quest journal doesn't close
	*Fixed.  Final journal entry wasn't set to close the quest.

- idun's apple quest journal doesn't close
	*Fixed.  Final journal entry wasn't set to close the quest.

- implementation of climbing rope, while well-intentioned, doesn't work correctly
	*Couldn't replicate.  Worked fine for me. https://i.imgur.com/45UC3xS.jpeg

- doors close automatically without anyone to close them
	*Not a bug, desired behaivior.

- henchmen don't level up
	*Fixed

- when henchmen are dismissed, they don't just go back to where they were; they're destroyed and respawned
	*Fixed
2024-09-14 22:29:14 -04:00

1151 lines
37 KiB
Plaintext

//::///////////////////////////////////////////////
//:: 69_INC_HENAI
//:: Copyright (c) 2001 Bioware Corp.
//:://////////////////////////////////////////////
/*
This is a wrapper overtop of the 'generic AI'
system with custom behavior for Henchmen.
BENEFIT:
- easier to isolate henchmen behavior
- easier to debug COMBAT-AI because the
advanced Henchmen behaviour won't be in those scripts
CONS:
- code duplicate. The two 'combat round' functions
will share a lot of code because the old-AI still has
to allow any legacy henchmen to work.
NEW RADIALS/COMMANDS:
- Open Inventory "inventory"
- Open Everything
- Remove Traps [even nonthiefs will walk to detected traps]
- NEVER FIGHT mode (or ALWAYS RETREAT) ; SetLocal; Implementation Code inside of DetermineCombatRound DONE
-=-=-=-=-=-=-
MODIFICATIONS
-=-=-=-=-=-=-
// * BK Feb 6 2003
// * Put a check in so that when a henchmen who cannot disarm a trap
// * sees a trap they do not repeat their voiceover forever
*/
//:://////////////////////////////////////////////
//:: Modified By: 69MEH69 JULY 2004
//:: Inventory Access Parameter
//:://////////////////////////////////////////////
// #include "nw_i0_generic" //...and through this also x0_inc_generic
#include "69_hench_lib"
// ****************************
// CONSTANTS
// ****************************
// ~ Behavior Constants
const int BK_HEALINMELEE = 10;
const int BK_CURRENT_AI_MODE = 20; // Can only be in one AI mode at a time
const int BK_AI_MODE_FOLLOW = 9; // default mode, moving after the player
const int BK_AI_MODE_RUN_AWAY = 19; // something is causing AI to retreat
const int BK_NEVERFIGHT = 30;
// ~ Distance Constants
const float BK_HEALTHRESHOLD = 5.0;
const float BK_FOLLOW_THRESHOLD= 15.0;
// difficulty difference at which familiar will flee
//int BK_FAMILIAR_COWARD = 7;
/**********************************************************************
* FUNCTION PROTOTYPES
**********************************************************************/
// * Sets up special additional listening patterns
// * for associates.
void bkSetListeningPatterns();
// * Henchman/associate combat round wrapper
// * passing in OBJECT_INVALID is okay
// * Does special stuff for henchmen and familiars, then
// * falls back to default generic combat code.
void HenchmenCombatRound(object oIntruder=OBJECT_INVALID);
// * Attempt to disarm given trap
// * (called from RespondToShout and heartbeat)
int bkAttemptToDisarmTrap(object oTrap, int bWasShout = FALSE);
// * Attempt to open a given locked object.
int bkAttemptToOpenLock(object oLocked);
// Manually pick the nearest locked object
int bkManualPickNearestLock();
// Handles responses to henchmen commands, including both radial
// menu and voice commands.
void bkRespondToHenchmenShout(object oShouter, int nShoutIndex, object oIntruder = OBJECT_INVALID, int nBanInventory=FALSE);
// * Attempt to heal self then master
int bkCombatAttemptHeal();
// * Attempts to follow master if outside range
int bkCombatFollowMaster();
// * set behavior used by AI
void bkSetBehavior(int nBehavior, int nValue);
// * get behavior used by AI
int bkGetBehavior(int nBehavior);
// ****LINEOFSIGHT*****
// * TRUE if the target door is in line of sight.
int bkGetIsDoorInLineOfSight(object oTarget);
// Get the cosine of the angle between two objects.
float bkGetCosAngleBetween(object Loc1, object Loc2);
// TRUE if target in the line of sight of the seer.
int bkGetIsInLineOfSight(object oTarget, object oSeer=OBJECT_SELF);
// * called from state scripts (nw_g0_charm) to signal
// * to other party members to help me out
void SendForHelp();
/**********************************************************************
* FUNCTION DEFINITIONS
**********************************************************************/
// * called from state scripts (nw_g0_charm) to signal
// * to other party members to help me out
void SendForHelp()
{
if(!GetIsPC(GetFactionLeader(OBJECT_SELF)))
{
// Stop
return;
}
// *
// * September 2003
// * Was this a disabling type spell
// * Signal an event so that my party members
// * can check to see if they can remove it for me
// *
int i = 0;
object oParty = GetFirstFactionMember(OBJECT_SELF, FALSE);
while (GetIsObjectValid(oParty) == TRUE)
{
SignalEvent(oParty, EventUserDefined(46500));
oParty = GetNextFactionMember(OBJECT_SELF, FALSE);
}
}
// * Sets up any special listening patterns in addition to the default
// * associate ones that are used
void bkSetListeningPatterns()
{
SetListening(OBJECT_SELF, TRUE);
SetListenPattern(OBJECT_SELF, "inventory",101);
SetListenPattern(OBJECT_SELF, "pick",102);
SetListenPattern(OBJECT_SELF, "trap", 103);
}
// Special combat round precursor for associates
void HenchmenCombatRound(object oIntruder)
{
// * If someone has surrendered, then don't attack them.
// * feb 25 2003
if (GetIsObjectValid(oIntruder) == TRUE)
{
if (GetIsEnemy(oIntruder) == FALSE)
{
ClearActions(999, TRUE);
ActionAttack(OBJECT_INVALID);
return;
}
}
//SpeakString("in combat round. Is an enemy");
// * This is the nearest enemy
object oNearestTarget = GetNearestCreature(CREATURE_TYPE_REPUTATION,
REPUTATION_TYPE_ENEMY,
OBJECT_SELF, 1,
CREATURE_TYPE_PERCEPTION,
PERCEPTION_SEEN);
HenchRearm69(oNearestTarget, OBJECT_SELF);
// SpeakString("Henchman combat dude");
// ****************************************
// SETUP AND SANITY CHECKS (Quick Returns)
// ****************************************
// * BK: stop fighting if something bizarre that shouldn't happen, happens
if (bkEvaluationSanityCheck(oIntruder, GetFollowDistance()) == TRUE) return;
if(GetAssociateState(NW_ASC_IS_BUSY) || GetAssociateState(NW_ASC_MODE_DYING)) {
return;
}
// June 2/04: Fix for when henchmen is told to use stealth until next fight
if(GetLocalInt(OBJECT_SELF, "X2_HENCH_STEALTH_MODE")==2)
SetLocalInt(OBJECT_SELF, "X2_HENCH_STEALTH_MODE", 0);
// MODIFIED FEBRUARY 13 2003
// The associate will not engage in battle if in Stand Ground mode unless
// he takes damage
if(GetAssociateState(NW_ASC_MODE_STAND_GROUND) == TRUE && GetIsObjectValid(GetLastHostileActor()) == FALSE)
{
return;
}
if(BashDoorCheck(oIntruder)) {return;}
// ** Store how difficult the combat is for this round
int nDiff = GetCombatDifficulty();
SetLocalInt(OBJECT_SELF, "NW_L_COMBATDIFF", nDiff);
object oMaster = GetMaster();
int iHaveMaster = GetIsObjectValid(oMaster);
// * Do henchmen specific things if I am a henchman otherwise run default AI
if (iHaveMaster == TRUE) {
// *******************************************
// Healing
// *******************************************
// The FIRST PRIORITY: self-preservation
// The SECOND PRIORITY: heal master;
if (bkCombatAttemptHeal() == TRUE)
return;
// NEXT priority: follow or return to master for up to three rounds.
if (bkCombatFollowMaster() == TRUE)
return;
//5. This check is to see if the master is being attacked and in need of help
// * Guard Mode -- only attack if master attacking
// * or being attacked.
if(GetAssociateState(NW_ASC_MODE_DEFEND_MASTER))
{
oIntruder = GetLastHostileActor(GetMaster());
if(!GetIsObjectValid(oIntruder))
{
//oIntruder = GetGoingToBeAttackedBy(GetMaster());
// MODIFIED Major change. Defend is now Defend only if I attack
// February 11 2003
object oSelf = OBJECT_SELF;
oIntruder = GetAttackTarget(GetMaster());
// * February 11 2003
// * means that the player was invovled in a battle
if (GetIsObjectValid(oIntruder) || GetLocalInt(oSelf, "X0_BATTLEJOINEDMASTER") == TRUE)
{
SetLocalInt(oSelf, "X0_BATTLEJOINEDMASTER", TRUE);
// * This is turned back to false whenever he hits the end of combat
if (GetIsObjectValid(oIntruder) == FALSE)
{
oIntruder = oNearestTarget;
if (GetIsObjectValid(oIntruder) == FALSE)
{
//* turn off the "I am in battle" sub-mode
SetLocalInt(oSelf, "X0_BATTLEJOINEDMASTER", FALSE);
}
}
}
// * Exit out and do nothing this combat round
else
{
// * August 2003: If I'm getting beaten up and my master
// * is just standing around, I should attempt, one last time
// * to see if there is someone I should be fighting
oIntruder = GetLastAttacker(OBJECT_SELF);
// * EXIT CONDITION = There really is not anyone
// * near me to justify going into combat
if (GetIsObjectValid(oIntruder) == FALSE)
return;
}
}
}
/*
int iAmHenchman = FALSE;
if (GetAssociateType(OBJECT_SELF) == ASSOCIATE_TYPE_HENCHMAN)
{
iAmHenchman = TRUE;
}
if (iAmHenchman)
*/
if (GetAssociateType(OBJECT_SELF) == ASSOCIATE_TYPE_HENCHMAN)
{
// 5% chance per round of speaking the relative challenge of the encounter.
if (d100() > 95) {
if (nDiff <= 1) VoiceLaugh(TRUE);
// MODIFIED February 7 2003. This was confusing testing
// else if (nDiff <= 4) VoiceThreaten(TRUE);
// else VoiceBadIdea();
}
} // is a henchman
// I am a familiar FLEE if tough
/*
MODIFIED FEB10 2003. Q/A hated this.
int iAmFamiliar = (GetAssociate(ASSOCIATE_TYPE_FAMILIAR,oMaster) == OBJECT_SELF);
if (iAmFamiliar) {
// Run away from tough enemies
if (nDiff >= BK_FAMILIAR_COWARD || GetPercentageHPLoss(OBJECT_SELF) < 40) {
VoiceFlee();
ClearActions(CLEAR_X0_INC_HENAI_HCR);
ActionMoveAwayFromObject(oNearestTarget, TRUE, 40.0);
return;
}
}
*/
} // * is an associate
// Fall through to generic combat
// * only go into determinecombatround if there's a valid enemy nearby
// * feb 26 2003: To prevent henchmen from resuming combat
if (GetIsObjectValid(oIntruder) || GetIsObjectValid(oNearestTarget))
{
DetermineCombatRound(oIntruder);
}
}
// Manually pick the nearest locked object
int bkManualPickNearestLock()
{
object oLastObject = GetLockedObject(GetMaster());
//MyPrintString("Attempting to unlock: " + GetTag(oLastObject));
return bkAttemptToOpenLock(oLastObject);
}
// * attempts to disarm last trap (called from RespondToShout and heartbeat
int bkAttemptToDisarmTrap(object oTrap, int bWasShout = FALSE)
{
//MyPrintString("Attempting to disarm: " + GetTag(oTrap));
// * May 2003: Don't try to disarm a trap with no trap
if (GetIsTrapped(oTrap) == FALSE)
{
return FALSE;
}
// * June 2003. If in 'do not disarm trap' mode, then do not disarm traps
if(!GetAssociateState(NW_ASC_DISARM_TRAPS))
{
return FALSE;
}
int bValid = GetIsObjectValid(oTrap);
int bISawTrap = GetTrapDetectedBy(oTrap, OBJECT_SELF);
int bCloseEnough = GetDistanceToObject(oTrap) <= 15.0;
int bInLineOfSight = bkGetIsInLineOfSight(oTrap);
if(bValid == FALSE || bISawTrap == FALSE || bCloseEnough == FALSE || bInLineOfSight == FALSE)
{
//MyPrintString("Failed basic disarm check");
if (bWasShout == TRUE)
VoiceCannotDo();
return FALSE;
}
//object oTrapSaved = GetLocalObject(OBJECT_SELF, "NW_ASSOCIATES_LAST_TRAP");
SetLocalObject(OBJECT_SELF, "NW_ASSOCIATES_LAST_TRAP", oTrap);
// We can tell we can't do it
string sID = ObjectToString(oTrap);
int nSkill = GetSkillRank(SKILL_DISABLE_TRAP);
int nTrapDC = GetTrapDisarmDC(oTrap);
if ( nSkill > 0 && (nSkill + 20) >= nTrapDC && GetTrapDisarmable(oTrap)) {
ClearActions(CLEAR_X0_INC_HENAI_AttemptToDisarmTrap);
ActionUseSkill(SKILL_DISABLE_TRAP, oTrap);
ActionDoCommand(SetCommandable(TRUE));
ActionDoCommand(VoiceTaskComplete());
SetCommandable(FALSE);
return TRUE;
} else if (GetHasSpell(SPELL_FIND_TRAPS) && GetTrapDisarmable(oTrap) && GetLocalInt(oTrap, "NW_L_IATTEMPTEDTODISARMNOWORK") ==0)
{
// SpeakString("casting");
ClearActions(CLEAR_X0_INC_HENAI_AttemptToDisarmTrap);
ActionCastSpellAtObject(SPELL_FIND_TRAPS, oTrap);
SetLocalInt(oTrap, "NW_L_IATTEMPTEDTODISARMNOWORK", 10);
return TRUE;
}
// MODIFIED February 7 2003. Merged the 'attack object' inside of the bshout
// this is not really something you want the henchmen just to go and do
// spontaneously
else if (bWasShout)
{
ClearActions(CLEAR_X0_INC_HENAI_BKATTEMPTTODISARMTRAP_ThrowSelfOnTrap);
//SpeakStringByStrRef(40551); // * Out of game indicator that this trap can never be disarmed by henchman.
if (GetLocalInt(OBJECT_SELF, "X0_L_SAWTHISTRAPALREADY" + sID) != 10)
{
string sSpeak = GetStringByStrRef(40551);
SendMessageToPC(GetMaster(), sSpeak);
SetLocalInt(OBJECT_SELF, "X0_L_SAWTHISTRAPALREADY" + sID, 10);
}
if (GetObjectType(oTrap) != OBJECT_TYPE_TRIGGER)
{
// * because Henchmen are not allowed to switch weapons without the player's
// * say this needs to be removed
// it's an object we can destroy ranged
// ActionEquipMostDamagingRanged(oTrap);
ActionAttack(oTrap);
SetLocalObject(OBJECT_SELF, "NW_GENERIC_DOOR_TO_BASH", oTrap);
return TRUE;
}
// Throw ourselves on it nobly! :-)
/*
ActionMoveToLocation(GetLocation(oTrap));
SetFacingPoint(GetPositionFromLocation(GetLocation(oTrap)));
ActionRandomWalk();
return TRUE;
*/
}
else if (nSkill > 0)
{
// * BK Feb 6 2003
// * Put a check in so that when a henchmen who cannot disarm a trap
// * sees a trap they do not repeat their voiceover forever
if (GetLocalInt(OBJECT_SELF, "X0_L_SAWTHISTRAPALREADY" + sID) != 10)
{
VoiceCannotDo();
SetLocalInt(OBJECT_SELF, "X0_L_SAWTHISTRAPALREADY" + sID, 10);
string sSpeak = GetStringByStrRef(40551);
SendMessageToPC(GetMaster(), sSpeak);
}
return FALSE;
}
return FALSE;
}
//* attempts to cast knock to open the door
int AttemptKnockSpell(object oLocked)
{
// If that didn't work, let's try using a knock spell
if (GetHasSpell(SPELL_KNOCK)
&& (GetIsDoorActionPossible(oLocked,
DOOR_ACTION_KNOCK)
|| GetIsPlaceableObjectActionPossible(oLocked,
PLACEABLE_ACTION_KNOCK)))
{
if (bkGetIsDoorInLineOfSight(oLocked) == FALSE)
{
// For whatever reason, GetObjectSeen doesn't return seen doors.
//if (GetObjectSeen(oLocked))
if (LineOfSightObject(OBJECT_SELF, oLocked) == TRUE)
{
ClearActions(CLEAR_X0_INC_HENAI_AttemptToOpenLock2);
VoiceCanDo();
ActionWait(1.0);
ActionCastSpellAtObject(SPELL_KNOCK, oLocked);
ActionWait(1.0);
return TRUE;
}
}
}
return FALSE;
}
// * Attempt to open a given locked object.
int bkAttemptToOpenLock(object oLocked)
{
// * September 2003
// * if door is set to not be something
// * henchmen should bash open (like mind flayer beds)
// * then ignore it.
if (GetLocalInt(oLocked, "X2_L_BASH_FALSE") == 1)
{
return FALSE;
}
int bNeedKey = FALSE;
int bInLineOfSight = TRUE;
if (GetLockKeyRequired(oLocked) == TRUE)
{
bNeedKey = TRUE ;
}
// * October 17 2003 - BK - Decided that line of sight for doors is not relevant
// * was causing too many errors.
//if (bkGetIsInLineOfSight(oLocked) == FALSE)
//{
// bInLineOfSight = TRUE;
// }
if ( !GetIsObjectValid(oLocked)
|| bNeedKey == TRUE
|| bInLineOfSight == FALSE )
//|| GetObjectSeen(oLocked) == FALSE) This check doesn't work.
{
// Can't open this, so skip the checks
//MyPrintString("Failed basic check");
VoiceCannotDo();
return FALSE;
}
// We might be able to open this
int bCanDo = FALSE;
// First, let's see if we notice that it's trapped
if (GetIsTrapped(oLocked) && GetTrapDetectedBy(oLocked, OBJECT_SELF))
{
// Ick! Try and disarm the trap first
//MyPrintString("Trap on it to disarm");
if (! bkAttemptToDisarmTrap(oLocked))
{
// * Feb 11 2003. Attempt to cast knock because its
// * always safe to cast it, even on a trapped object
if (AttemptKnockSpell(oLocked) == TRUE)
{
return TRUE;
}
//VoicePicklock();
VoiceNo();
return FALSE;
}
}
// Now, let's try and pick the lock first
int nSkill = GetSkillRank(SKILL_OPEN_LOCK);
if (nSkill > 0) {
nSkill += GetAbilityModifier(ABILITY_DEXTERITY);
nSkill += 20;
}
if (nSkill > GetLockUnlockDC(oLocked)
&&
(GetIsDoorActionPossible(oLocked,
DOOR_ACTION_UNLOCK)
|| GetIsPlaceableObjectActionPossible(oLocked,
PLACEABLE_ACTION_UNLOCK))) {
ClearActions(CLEAR_X0_INC_HENAI_AttemptToOpenLock1);
VoiceCanDo();
ActionWait(1.0);
ActionUseSkill(SKILL_OPEN_LOCK,oLocked);
ActionWait(1.0);
bCanDo = TRUE;
}
if (!bCanDo)
bCanDo = AttemptKnockSpell(oLocked);
if (!bCanDo
//&& GetAbilityScore(OBJECT_SELF, ABILITY_STRENGTH) >= 16 Removed since you now have control over their bashing via dialog
&& !GetPlotFlag(oLocked)
&& (GetIsDoorActionPossible(oLocked,
DOOR_ACTION_BASH)
|| GetIsPlaceableObjectActionPossible(oLocked,
PLACEABLE_ACTION_BASH))) {
ClearActions(CLEAR_X0_INC_HENAI_AttemptToOpenLock3);
VoiceCanDo();
ActionWait(1.0);
// MODIFIED February 2003
// Since the player has direct control over weapon, automatic equipping is frustrating.
// removed.
// ActionEquipMostDamagingMelee(oLocked);
ActionAttack(oLocked);
SetLocalObject(OBJECT_SELF, "NW_GENERIC_DOOR_TO_BASH", oLocked);
bCanDo = TRUE;
}
if (!bCanDo && !GetPlotFlag(oLocked) && GetHasSpell(SPELL_MAGIC_MISSILE))
{
ClearActions(CLEAR_X0_INC_HENAI_AttemptToOpenLock3);
ActionCastSpellAtObject(SPELL_MAGIC_MISSILE,oLocked);
return TRUE;
}
// If we did it, let the player know
if(!bCanDo) {
VoiceCannotDo();
} else {
ActionDoCommand(VoiceTaskComplete());
return TRUE;
}
return FALSE;
}
// Handles responses to henchmen commands, including both radial
// menu and voice commands.
void bkRespondToHenchmenShout(object oShouter, int nShoutIndex, object oIntruder = OBJECT_INVALID, int nBanInventory=FALSE)
{
// * if petrified, jump out
if (GetHasEffect(EFFECT_TYPE_PETRIFY, OBJECT_SELF) == TRUE)
{
return;
}
// * MODIFIED February 19 2003
// * Do not respond to shouts if in dying mode
if (GetIsHenchmanDying() == TRUE)
return;
// Do not respond to shouts if you've surrendered.
/* int iSurrendered = GetLocalInt(OBJECT_SELF,"Generic_Surrender");
if (iSurrendered)
return;*/
if(GetLocalInt(OBJECT_SELF,"Generic_Surrender")) return;
object oLastObject;
object oTrap;
object oMaster;
object oTarget;
//ASSOCIATE SHOUT RESPONSES
switch(nShoutIndex)
{
// * toggle search mode for henchmen
case ASSOCIATE_COMMAND_TOGGLESEARCH:
{
if (GetActionMode(OBJECT_SELF, ACTION_MODE_DETECT) == TRUE)
{
SetActionMode(OBJECT_SELF, ACTION_MODE_DETECT, FALSE);
}
else
{
SetActionMode(OBJECT_SELF, ACTION_MODE_DETECT, TRUE);
}
break;
}
// * toggle stealth mode for henchmen
case ASSOCIATE_COMMAND_TOGGLESTEALTH:
{
//SpeakString(" toggle stealth");
if (GetActionMode(OBJECT_SELF, ACTION_MODE_STEALTH) == TRUE)
{
SetActionMode(OBJECT_SELF, ACTION_MODE_STEALTH, FALSE);
}
else
{
SetActionMode(OBJECT_SELF, ACTION_MODE_STEALTH, TRUE);
}
break;
}
// * June 2003: Stop spellcasting
case ASSOCIATE_COMMAND_TOGGLECASTING:
{
if (GetLocalInt(OBJECT_SELF, "X2_L_STOPCASTING") == 10)
{
// SpeakString("Was in no casting mode. Switching to cast mode");
SetLocalInt(OBJECT_SELF, "X2_L_STOPCASTING", 0);
VoiceCanDo();
}
else
if (GetLocalInt(OBJECT_SELF, "X2_L_STOPCASTING") == 0)
{
// SpeakString("Was in casting mode. Switching to NO cast mode");
SetLocalInt(OBJECT_SELF, "X2_L_STOPCASTING", 10);
VoiceCanDo();
}
break;
}
case ASSOCIATE_COMMAND_INVENTORY:
// feb 18. You are now allowed to access inventory during combat.
if (nBanInventory == TRUE || GetLocalInt(OBJECT_SELF, "HenchInv") == FALSE) //69MEH69
{
SpeakStringByStrRef(9066);
}
else
{
// * cannot modify disabled equipment
if (GetLocalInt(OBJECT_SELF, "X2_JUST_A_DISABLEEQUIP") == FALSE)
{
OpenInventory(OBJECT_SELF, oShouter);
}
else
{
// * feedback as to why
SendMessageToPCByStrRef(GetMaster(), 100895);
}
}
break;
case ASSOCIATE_COMMAND_PICKLOCK:
bkManualPickNearestLock();
break;
case ASSOCIATE_COMMAND_DISARMTRAP: // Disarm trap
bkAttemptToDisarmTrap(GetNearestTrapToObject(GetMaster()), TRUE);
break;
case ASSOCIATE_COMMAND_ATTACKNEAREST:
ResetHenchmenState();
SetLocalInt(OBJECT_SELF,"Scouting",FALSE);
SetAssociateState(NW_ASC_MODE_DEFEND_MASTER, FALSE);
SetAssociateState(NW_ASC_MODE_STAND_GROUND, FALSE);
DetermineCombatRound();
// * bonus feature. If master is attacking a door or container, issues VWE Attack Nearest
// * will make henchman join in on the fun
oTarget = GetAttackTarget(GetMaster());
if (GetIsObjectValid(oTarget) == TRUE)
{
if (GetObjectType(oTarget) == OBJECT_TYPE_PLACEABLE || GetObjectType(oTarget) == OBJECT_TYPE_DOOR)
{
ActionAttack(oTarget);
}
}
break;
case ASSOCIATE_COMMAND_FOLLOWMASTER:
ResetHenchmenState();
SetLocalInt(OBJECT_SELF,"Scouting",FALSE);
SetAssociateState(NW_ASC_MODE_STAND_GROUND, FALSE);
DelayCommand(2.5, VoiceCanDo());
//UseStealthMode();
//UseDetectMode();
ActionForceFollowObject(GetMaster(), GetFollowDistance());
SetAssociateState(NW_ASC_IS_BUSY);
DelayCommand(5.0, SetAssociateState(NW_ASC_IS_BUSY, FALSE));
break;
case ASSOCIATE_COMMAND_GUARDMASTER:
{
ResetHenchmenState();
SetLocalInt(OBJECT_SELF,"Scouting",FALSE);
//DelayCommand(2.5, VoiceCannotDo());
//Companions will only attack the Masters Last Attacker
SetAssociateState(NW_ASC_MODE_DEFEND_MASTER);
SetAssociateState(NW_ASC_MODE_STAND_GROUND, FALSE);
object oLastAttacker = GetLastHostileActor(GetMaster());
// * for some reason this is too often invalid. still the routine
// * works corrrectly
SetLocalInt(OBJECT_SELF, "X0_BATTLEJOINEDMASTER", TRUE);
HenchmenCombatRound(oLastAttacker);
break;
}
case ASSOCIATE_COMMAND_HEALMASTER:
//Ignore current healing settings and heal me now
ResetHenchmenState();
//SetCommandable(TRUE);
if(TalentCureCondition())
{
DelayCommand(2.0, VoiceCanDo());
return;
}
if(TalentHeal(TRUE, GetMaster()))
{
DelayCommand(2.0, VoiceCanDo());
return;
}
DelayCommand(2.5, VoiceCannotDo());
break;
case ASSOCIATE_COMMAND_MASTERFAILEDLOCKPICK:
//Check local for re-try locked doors
if(!GetAssociateState(NW_ASC_MODE_STAND_GROUND)
&& GetAssociateState(NW_ASC_RETRY_OPEN_LOCKS))
{
oLastObject = GetLockedObject(GetMaster());
bkAttemptToOpenLock(oLastObject);
}
break;
case ASSOCIATE_COMMAND_STANDGROUND:
//No longer follow the master or guard him
SetAssociateState(NW_ASC_MODE_STAND_GROUND);
SetAssociateState(NW_ASC_MODE_DEFEND_MASTER, FALSE);
DelayCommand(2.0, VoiceCanDo());
ActionAttack(OBJECT_INVALID);
ClearActions(CLEAR_X0_INC_HENAI_RespondToShout1);
break;
// ***********************************
// * AUTOMATIC SHOUTS - not player
// * initiated
// ***********************************
case ASSOCIATE_COMMAND_MASTERSAWTRAP:
if(!GetIsInCombat())
{
if(!GetAssociateState(NW_ASC_MODE_STAND_GROUND) && GetAssociateState(NW_ASC_DISARM_TRAPS)) //69MEH69 via trc
bkAttemptToDisarmTrap(GetNearestTrapToObject(GetMaster()), TRUE);
/*
if(!GetAssociateState(NW_ASC_MODE_STAND_GROUND))
{
oTrap = GetLastTrapDetected(GetMaster());
bkAttemptToDisarmTrap(oTrap);
}
*/
}
break;
case ASSOCIATE_COMMAND_MASTERUNDERATTACK:
// Just go to henchman combat round
//SpeakString("here 728");
// * July 15, 2003: Make this only happen if not
// * in combat, otherwise the henchman will
// * ping pong between targets
if (!GetIsInCombat(OBJECT_SELF))
{
SetLocalInt(OBJECT_SELF,"Scouting",FALSE);
HenchmenCombatRound();
}
break;
case ASSOCIATE_COMMAND_MASTERATTACKEDOTHER:
if(!GetAssociateState(NW_ASC_MODE_STAND_GROUND))
{
if(!GetAssociateState(NW_ASC_MODE_DEFEND_MASTER))
{
if(!GetIsInCombat(OBJECT_SELF))
{
SetLocalInt(OBJECT_SELF,"Scouting",FALSE);
//SpeakString("here 737");
object oAttack = GetAttackTarget(GetMaster());
// April 2003: If my master can see the enemy, then I can too.
if(GetIsObjectValid(oAttack) && GetObjectSeen(oAttack, GetMaster()))
{
ClearActions(CLEAR_X0_INC_HENAI_RespondToShout2);
HenchmenCombatRound(oAttack);
}
}
}
}
break;
case ASSOCIATE_COMMAND_MASTERGOINGTOBEATTACKED:
if(!GetAssociateState(NW_ASC_MODE_STAND_GROUND))
{
if(!GetIsInCombat(OBJECT_SELF))
{ // SpeakString("here 753");
object oAttacker = GetGoingToBeAttackedBy(GetMaster());
// April 2003: If my master can see the enemy, then I can too.
// Potential Side effect : Henchmen may run
// to stupid places, trying to get an enemy
if(GetIsObjectValid(oAttacker) && GetObjectSeen(oAttacker, GetMaster()))
{
// SpeakString("Defending Master");
ClearActions(CLEAR_X0_INC_HENAI_RespondToShout3);
ActionMoveToObject(oAttacker, TRUE, 7.0);
HenchmenCombatRound(oAttacker);
}
}
}
break;
case ASSOCIATE_COMMAND_LEAVEPARTY:
{
oMaster = GetMaster();
string sTag = GetTag(GetArea(oMaster));
// * henchman cannot be kicked out in the reaper realm
// * Followers can never be kicked out
if (sTag == "GatesofCania" || GetIsFollower(OBJECT_SELF) == TRUE)
return;
if(GetIsObjectValid(oMaster))
{
ClearActions(CLEAR_X0_INC_HENAI_RespondToShout4);
SetLocalInt(OBJECT_SELF,"Scouting",FALSE);
if(GetAssociateType(OBJECT_SELF) == ASSOCIATE_TYPE_HENCHMAN)
{
FireHenchman(GetMaster(), OBJECT_SELF);
}
}
break;
}
}
}
//::///////////////////////////////////////////////
//:: bkCombatAttemptHeal
//:: Copyright (c) 2001 Bioware Corp.
//:://////////////////////////////////////////////
/*
Attempt to heal self and then master
*/
//:://////////////////////////////////////////////
//:: Created By:
//:: Created On:
//:://////////////////////////////////////////////
int bkCombatAttemptHeal()
{
// * if master is disabled then attempt to free master
object oMaster = GetMaster();
// *turn into a match function...
if (MatchDoIHaveAMindAffectingSpellOnMe(oMaster)) {
//int nSpellToUse = -1;
if (GetHasSpell(SPELL_DISPEL_MAGIC, OBJECT_SELF) ) {
ClearActions(CLEAR_X0_INC_HENAI_CombatAttemptHeal1);
ActionCastSpellAtLocation(SPELL_DISPEL_MAGIC, GetLocation(oMaster));
return TRUE;
}
}
int iHealMelee = TRUE;
if (bkGetBehavior(BK_HEALINMELEE) == FALSE)
iHealMelee = FALSE;
object oNearestEnemy = GetNearestSeenEnemy();
float fDistance = 0.0;
if (GetIsObjectValid(oNearestEnemy)) {
fDistance = GetDistanceToObject(oNearestEnemy);
}
int iHP = GetPercentageHPLoss(OBJECT_SELF);
// if less than 10% hitpoints then pretend that I am allowed
// to heal in melee. Things are getting desperate
if (iHP < 10)
iHealMelee = TRUE;
int iAmFamiliar = (GetAssociate(ASSOCIATE_TYPE_FAMILIAR,oMaster) == OBJECT_SELF);
// * must be out of Melee range or ALLOWED to heal in melee
if (fDistance > BK_HEALTHRESHOLD || iHealMelee) {
int iAmHenchman = GetAssociateType(OBJECT_SELF) == ASSOCIATE_TYPE_HENCHMAN;
int iAmCompanion = (GetAssociate(ASSOCIATE_TYPE_ANIMALCOMPANION,oMaster) == OBJECT_SELF);
int iAmSummoned = (GetAssociate(ASSOCIATE_TYPE_SUMMONED,oMaster) == OBJECT_SELF);
// Condition for immediate self-healing
// Hit-point at less than 50% and random chance
if (iHP < 50) {
// verbalize
if (iAmHenchman || iAmFamiliar) {
// * when hit points less than 10% will whine about
// * being near death
if (iHP < 10 && Random(5) == 0)
VoiceNearDeath();
}
// attempt healing
if (d100() > iHP-20) {
ClearActions(CLEAR_X0_INC_HENAI_CombatAttemptHeal2);
if (TalentHealingSelf()) return TRUE;
if (iAmHenchman || iAmFamiliar)
if (Random(100) > 80) VoiceHealMe();
}
}
// ********************************
// Heal master if needed.
// ********************************
if (GetAssociateHealMaster()) {
if (TalentHeal())
return TRUE;
else
return FALSE;
}
}
// * No healing done, continue with combat round
return FALSE;
}
//::///////////////////////////////////////////////
//:: bkGetBehavior
//:: Copyright (c) 2001 Bioware Corp.
//:://////////////////////////////////////////////
/*
Set/get functions for CONTROL PANEL behavior
*/
//:://////////////////////////////////////////////
//:: Created By:
//:: Created On:
//:://////////////////////////////////////////////
int bkGetBehavior(int nBehavior)
{
return GetLocalInt(OBJECT_SELF, "NW_L_BEHAVIOR" + IntToString(nBehavior));
}
void bkSetBehavior(int nBehavior, int nValue)
{
SetLocalInt(OBJECT_SELF, "NW_L_BEHAVIOR"+IntToString(nBehavior), nValue);
}
//::///////////////////////////////////////////////
//:: bkCombatFollowMaster
//:: Copyright (c) 2001 Bioware Corp.
//:://////////////////////////////////////////////
/*
Forces the henchman to follow the player.
Will even do this in the middle of combat if the
distance it too great
*/
//:://////////////////////////////////////////////
//:: Created By:
//:: Created On:
//:://////////////////////////////////////////////
int bkCombatFollowMaster()
{
object oMaster = GetMaster();
int iAmHenchman = (GetHenchman(oMaster) == OBJECT_SELF);
int iAmFamiliar = (GetAssociate(ASSOCIATE_TYPE_FAMILIAR,oMaster) == OBJECT_SELF);
if(bkGetBehavior(BK_CURRENT_AI_MODE) != BK_AI_MODE_RUN_AWAY)
{
// * double follow threshold if in combat (May 2003)
float fFollowThreshold = BK_FOLLOW_THRESHOLD;
if (GetIsInCombat(OBJECT_SELF) == TRUE)
{
fFollowThreshold = BK_FOLLOW_THRESHOLD * 2.0;
}
if(GetDistanceToObject(oMaster) > fFollowThreshold)
{
if(GetCurrentAction(oMaster) != ACTION_FOLLOW)
{
ClearActions(CLEAR_X0_INC_HENAI_CombatFollowMaster1);
//MyPrintString("*****EXIT on follow master.*******");
ActionForceFollowObject(GetMaster(), GetFollowDistance());
return TRUE;
}
}
}
// 4. If in 'NEVER FIGHT' mode will not fight but should TELL the player
// that they are in NEVER FIGHT mode
if (bkGetBehavior(BK_NEVERFIGHT) == TRUE)
{
ClearActions(CLEAR_X0_INC_HENAI_CombatFollowMaster2);
// ActionWait(6.0);
// ActionDoCommand(DelayCommand(5.9, SetCommandable(TRUE)));
// SetCommandable(FALSE);
if (d10() > 7)
{
if (iAmHenchman || iAmFamiliar)
VoiceLookHere();
}
return TRUE;
}
return FALSE;
}
//Pausanias: Is Object in the line of sight of the seer
int bkGetIsInLineOfSight(object oTarget,object oSeer=OBJECT_SELF)
{
// * if really close, line of sight
// * is irrelevant
// * if this check is removed it gets very annoying
// * because the player can block line of sight
if (GetDistanceBetween(oTarget, oSeer) < 6.0)
{
return TRUE;
}
return LineOfSightObject(oSeer, oTarget);
}
// Get the cosine of the angle between the two objects
float bkGetCosAngleBetween(object Loc1, object Loc2)
{
vector v1 = GetPositionFromLocation(GetLocation(Loc1));
vector v2 = GetPositionFromLocation(GetLocation(Loc2));
vector v3 = GetPositionFromLocation(GetLocation(OBJECT_SELF));
v1.x -= v3.x; v1.y -= v3.y; v1.z -= v3.z;
v2.x -= v3.x; v2.y -= v3.y; v2.z -= v3.z;
float dotproduct = v1.x*v2.x+v1.y*v2.y+v1.z*v2.z;
return dotproduct/(VectorMagnitude(v1)*VectorMagnitude(v2));
}
//Pausanias: Is there a closed door in the line of sight.
// * is door in line of sight
int bkGetIsDoorInLineOfSight(object oTarget)
{
float fMeDoorDist;
object oView = GetFirstObjectInShape(SHAPE_SPHERE, 40.0,
GetLocation(OBJECT_SELF),
TRUE,OBJECT_TYPE_DOOR);
float fMeTrapDist = GetDistanceBetween(oTarget,OBJECT_SELF);
while (GetIsObjectValid(oView)) {
fMeDoorDist = GetDistanceBetween(oView,OBJECT_SELF);
//SpeakString("Trap3 : "+FloatToString(fMeTrapDist)+" "+FloatToString(fMeDoorDist));
if (fMeDoorDist < fMeTrapDist && !GetIsTrapped(oView))
if (GetIsDoorActionPossible(oView,DOOR_ACTION_OPEN) ||
GetIsDoorActionPossible(oView,DOOR_ACTION_UNLOCK)) {
float fAngle = bkGetCosAngleBetween(oView,oTarget);
//SpeakString("Angle: "+FloatToString(fAngle));
if (fAngle > 0.5) {
// if (d10() > 7)
// SpeakString("There's something fishy near that door...");
return TRUE;
}
}
oView = GetNextObjectInShape(SHAPE_SPHERE,40.0,
GetLocation(OBJECT_SELF),
TRUE, OBJECT_TYPE_DOOR);
}
//SpeakString("No matches found");
return FALSE;
}
//void main() {}