Added PEPS AI. Updated module name. Set all henchmen to have a random race &/or class based name using a custom version of Markshire's Nomeclature scripts, as well as appearance. Set Constructs, Undead, Outsiders & Elementals to not require food or drink. Full compile.
2326 lines
111 KiB
Plaintext
2326 lines
111 KiB
Plaintext
/*////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Script Name: 0i_actions
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
Include scripts for action in and out of combat.
|
|
|
|
Detect Mode:
|
|
Passive(default) mode
|
|
* Trap detection radius: 5ft
|
|
* Trap detection rate: every 6 seconds
|
|
* Trap detection roll: d20 + 1/2 skill
|
|
* Spot/Listen roll: d10 + 1/2 skill
|
|
|
|
Active(Detect) mode
|
|
* Trap detection radius: 10ft
|
|
* Trap detection rate: every 3 seconds
|
|
* Trap detection roll: d20 + skill
|
|
* Spot/Listen roll: d20 + skill
|
|
|
|
Stealth checks
|
|
* Player detects stealth: 5 times per second.
|
|
* Player rolls for hide/move silently & spot/listen: every 6 seconds.
|
|
* NPC detects stealth: 4 seconds
|
|
* NPC rolls for hide/move silently & spot/listen: every 6 seconds.
|
|
|
|
Listen/Move Silently:
|
|
* Cannot detect silenced creatures.
|
|
* Cannot detect sanctuaried creatures.
|
|
* Can only detect invisible (or when your blind) creatures within max attack range.
|
|
* Listen checks are made each round for success and failur.
|
|
* Outdoors: Objects between you and the target gives a +5 DC for every 40cm of thickness.
|
|
* Indoors: No Line of sight and the target is within 40 meters gives a +2 DC.
|
|
* +10 DC in combat for the target.
|
|
* +5 DC if the target is standing still.
|
|
* -5 DC if the listener is standing still.
|
|
* +1 DC for every 3 meters of distance to the target.
|
|
* Relative size modifiers for both: Tiny +8, Small +4, Medium 0, Larget -4, Huge -8.
|
|
* Favored enemy bonuses.
|
|
|
|
Spot/Hide:
|
|
* Cannot spot invisible creatures.
|
|
* Cannot spot any creatures while blinded.
|
|
* Night time: Spotter has not light or darkvision +5 DC.
|
|
* Night time: Target has a light no them -10 DC.
|
|
* +5 DC if target is behind the spotter.
|
|
* +10 DC if the spotter are in combat.
|
|
* +5 DC if the target is standing still.
|
|
* -5 DC if the spotter is standing still.
|
|
* Relative size modifiers for both: Tiny +8, Small +4, Medium 0, Larget -4, Huge -8.
|
|
* Favored enemy bonuses.
|
|
|
|
*/////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Programmer: Philos
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#include "0i_talents"
|
|
#include "x0_inc_henai"
|
|
#include "X0_I0_ANIMS"
|
|
// Chooses an action in combat and executes it for oCreature that is an associate.
|
|
void ai_DoAssociateCombatRound(object oCreature, object oTarget = OBJECT_INVALID);
|
|
// Sets variables and states for oAssociate to start combat.
|
|
void ai_StartAssociateCombat(object oAssociate, object oTarget = OBJECT_INVALID);
|
|
// Chooses an action in combat and executes it for oCreature that is a monster.
|
|
void ai_DoMonsterCombatRound(object oCreature);
|
|
// Sets variables and states for oMonster to start combat.
|
|
void ai_StartMonsterCombat(object oMonster);
|
|
// Return the distance that is set for how close we should follow our master.
|
|
float ai_GetFollowDistance(object oCreature);
|
|
// Returns TRUE if the caller's distance is greater than fDistance from who they
|
|
// are following. Unless they are cowardly or in stand ground mode.
|
|
// This will also force the caller to move towards them.
|
|
int ai_StayClose(object oCreature);
|
|
// Returns TRUE if oCreature becomes invisible or hides.
|
|
int ai_TryToBecomeInvisible(object oCreature);
|
|
// Returns TRUE if oCreature continues to bash a door.
|
|
int ai_BashDoorCheck(object oCreature);
|
|
// Returns TRUE if we find an hidden creature within battle and do an action.
|
|
// If oCreature is too far away they will run upto 14 meters of the invisible creature.
|
|
// If oCreature is close they will attempt to cast a spell or search for them.
|
|
// bMonster needs to be set for monsters otherwise we do associate perception checks.
|
|
// fRange is how close we want to get to hidden targets.
|
|
int ai_SearchForHiddenCreature(object oCreature, int bMonster, object oHidden = OBJECT_INVALID, float fRange = 1.0);
|
|
// Returns TRUE if oCreature fails a moral check.
|
|
// We only make moral checks once we are below AI_HEALTH_WOUNDED health percent.
|
|
// If we are at AI_HEALTH_BLOODY hp percent then add + AI_MORAL_INC_DC to the Check.
|
|
int ai_MoralCheck(object oCreature);
|
|
// Returns TRUE if oCreature is in and nSpell is a dangerous Area Of Effect.
|
|
// Used in the on spell cast at scripts. [nw_c2_defaultb and nw_ch_acb].
|
|
int ai_GetInAOEReaction(object oCreature, object oCaster, int nSpell);
|
|
// Have the associate speak a random voice from VOICE_CHAT_*.
|
|
// nRoll is the number to roll. If nRoll is 0 then it will SpeakString(sVoiceChatArray);
|
|
// sVoiceChatArray is an array of VOICE_CHAT_* numbers over nRoll.
|
|
// example(4, ":3:4:8:7:") will roll a d4() picking from 3,4,8,7 of VOICE_CHAT_*.
|
|
// if nRoll is higher than the number of VOICE_CHAT_* then it will not speak.
|
|
void ai_HaveCreatureSpeak(object oCreature, int nRoll, string sVoiceChatArray, int bImportant = FALSE);
|
|
// Returns if a spell talent was used.
|
|
// This is a common set of AI scripts ran on associate spell casters.
|
|
int ai_CheckForAssociateSpellTalent(object oAssociate, int nInMelee, int nMaxLevel, int nRound = 0);
|
|
// Targets the best creature oCreature it can see.
|
|
// This checks all physcal attack talents starting with ranged attacks then melee.
|
|
// Using TALENT_CATEGORY_HARMFUL_MELEE [22] talents.
|
|
// If no talents are used it will do either a ranged attack or a melee attack.
|
|
void ai_DoPhysicalAttackOnBest(object oCreature, int nInMelee, int bAlwaysAtk = TRUE);
|
|
// Targets the nearest creature oCreature it can see.
|
|
// This checks all physcal attack talents starting with ranged attacks then melee.
|
|
// Using TALENT_CATEGORY_HARMFUL_MELEE [22] talents.
|
|
// If no talents are used it will do either a ranged attack or a melee attack.
|
|
void ai_DoPhysicalAttackOnNearest(object oCreature, int nInMelee, int bAlwaysAtk = TRUE);
|
|
// Targets the weakest creature oCreature can see.
|
|
// This checks all physcal attack talents starting with ranged attacks then melee.
|
|
// Using TALENT_CATEGORY_HARMFUL_MELEE [22] talents.
|
|
// If no talents are used it will do either a ranged attack or a melee attack.
|
|
void ai_DoPhysicalAttackOnLowestCR(object oCreature, int nInMelee, int bAlwaysAtk = TRUE);
|
|
// Returns TRUE if they equip a melee weapon, FALSE if they don't.
|
|
// This also calls for the next combat round.
|
|
int ai_InCombatEquipBestMeleeWeapon(object oCreature);
|
|
// Returns TRUE if they equip a ranged weapon, FALSE if they don't.
|
|
// This also calls for the next combat round.
|
|
int ai_InCombatEquipBestRangedWeapon(object oCreature);
|
|
// Action wrapper for ai_TryHealing.
|
|
void ai_ActionTryHealing(object oCreature, object oTarget);
|
|
// Returns TRUE if oCreature heals oTarget.
|
|
// This uses an action and must use AssignCommand or OBJECT_SELF is the caster!
|
|
int ai_TryHealing(object oCreature, object oTarget, int bForce = FALSE);
|
|
// oCreature will move into the area looking for creatures.
|
|
void ai_ScoutAhead(object oCreature);
|
|
// Have oCreature search one object, may continue from that object.
|
|
void ai_SearchObject(object oCreature, object oObject, object oMaster, int bOnce = FALSE);
|
|
// Returns TRUE if oCreature disarms oTrap.
|
|
// bForce if TRUE, oCreature will try to disarm the trap even if they have tried before.
|
|
int ai_ReactToTrap(object oCreature, object oTrap, int bForce = FALSE);
|
|
// Returns TRUE if oCreature opens oLocked object.
|
|
// This will make oCreature open oLocked either by picking or casting a spell.
|
|
// bForce if TRUE, oCreature will try to pick the lock even if they have tried before.
|
|
int ai_AttemptToByPassLock(object oCreature, object oLocked, int bForce = FALSE);
|
|
// Returns TRUE if oCreature opens oDoor.
|
|
// bForce if TRUE, oCreature will try to open the door even if they have tried before.
|
|
int ai_AttemptToOpenDoor(object oCreature, object oDoor, int bForce = FALSE);
|
|
// Action for Checking nearby objects for traps, locks and loot.
|
|
void ai_ActionCheckNearbyObjects(object oCreature);
|
|
// oCreature will check nearby objects and see what they should do based upon
|
|
// selected actions by the player.
|
|
int ai_CheckNearbyObjects(object oCreature);
|
|
// Used to determine special behaviors for oCeature.
|
|
void ai_DetermineSpecialBehavior(object oCreature);
|
|
// The target object flees to the specified way point and then destroys itself,
|
|
// to be respawned at a later point. For unkillable sign post characters
|
|
// who are not meant to fight back.
|
|
void ai_ActivateFleeToExit(object oCreature);
|
|
// Returns TRUE if oCreature should flee to an exit.
|
|
int ai_GetFleeToExit(object oCreature);
|
|
// Does random animation in a close distance for creatures.
|
|
void ai_AmbientAnimations();
|
|
|
|
void ai_DoAssociateCombatRound(object oCreature, object oTarget = OBJECT_INVALID)
|
|
{
|
|
if(ai_StayClose(oCreature)) return;
|
|
// Is the target our Player has locked in dead? If so then clear it.
|
|
if(GetIsDead(GetLocalObject(oCreature, AI_PC_LOCKED_TARGET))) DeleteLocalObject(oCreature, AI_PC_LOCKED_TARGET);
|
|
// Setup the combat state for this round of combat.
|
|
object oNearestEnemy = ai_SetCombatState(oCreature);
|
|
// If we are in standground mode we only fight if the enemy is near us.
|
|
if(ai_GetAIMode(oCreature, AI_MODE_STAND_GROUND) &&
|
|
ai_GetEnemyAttackingMe(oCreature) == OBJECT_INVALID) oNearestEnemy = OBJECT_INVALID;
|
|
// If we found an Enemy or we have a Target then continue into the combat round.
|
|
if(oNearestEnemy != OBJECT_INVALID || oTarget != OBJECT_INVALID)
|
|
{
|
|
// In combat we should stop searching.
|
|
if(GetActionMode(oCreature, ACTION_MODE_DETECT) && !GetHasFeat(FEAT_KEEN_SENSE))
|
|
{
|
|
SetActionMode(oCreature, ACTION_MODE_DETECT, FALSE);
|
|
}
|
|
ai_SetCombatRound(oCreature);
|
|
string sAI = GetLocalString(oCreature, AI_COMBAT_SCRIPT);
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "167", " AI not Coward/Peaceful: " +
|
|
IntToString(sAI != "ai_coward" && sAI != "ai_a_peaceful"));
|
|
// If we are using a normal AI script and are polymorphed we should use
|
|
// the polymorph AI script.
|
|
if(sAI != "ai_coward" && sAI != "ai_a_peaceful")
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "173", "Should we use polymorph? " +
|
|
IntToString(GetAppearanceType(oCreature) != ai_GetNormalAppearance(oCreature)));
|
|
if(AI_DEBUG)
|
|
{
|
|
if(ai_GetIsHidden(oCreature))
|
|
{
|
|
ai_Debug("0i_actions", "179", "We are hidden!" +
|
|
" Can they see us? " + IntToString(ai_GetNearestIndexThatSeesUs(oCreature)));
|
|
}
|
|
}
|
|
if(GetAppearanceType(oCreature) != ai_GetNormalAppearance(oCreature))
|
|
{
|
|
sAI = "ai_a_polymorphed";
|
|
}
|
|
else if(ai_GetIsHidden(oCreature) && !ai_GetNearestIndexThatSeesUs(oCreature)) sAI = "ai_a_invisible";
|
|
}
|
|
if(sAI == "") sAI = "ai_a_default";
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "190", "********** " + GetName (oCreature) + " **********");
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "191", "********** " + sAI + " **********");
|
|
ai_ClearCreatureActions();
|
|
if(AI_DEBUG) ai_Counter_Start();
|
|
// Execute this creatures AI routine.
|
|
ExecuteScript(sAI, oCreature);
|
|
if(AI_DEBUG) ai_Counter_End(GetName(oCreature) + " has finalized round action.");
|
|
return;
|
|
}
|
|
// We have exhausted our check for an enemy. Combat is over.
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "200", "---------- " + GetName (OBJECT_SELF) + "'s combat has ended! ----------");
|
|
ai_ClearCombatState(oCreature);
|
|
// Run the heartbeat script so we start doing our actions out of combat.
|
|
ExecuteScript("nw_ch_ac1", oCreature);
|
|
}
|
|
void ai_StartAssociateCombat(object oAssociate, object oTarget = OBJECT_INVALID)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "217", "---------- " + GetName(oAssociate) + " is starting combat! ----------");
|
|
ai_SetCreatureTalents(oAssociate, FALSE);
|
|
ai_CheckXPPartyScale(oAssociate);
|
|
ai_DoAssociateCombatRound(oAssociate, oTarget);
|
|
}
|
|
void ai_DoMonsterCombatRound(object oMonster)
|
|
{
|
|
object oNearestEnemy = ai_SetCombatState(oMonster);
|
|
if(oNearestEnemy != OBJECT_INVALID)
|
|
{
|
|
if(GetActionMode(oMonster, ACTION_MODE_DETECT) && !GetHasFeat(FEAT_KEEN_SENSE, oMonster))
|
|
SetActionMode(oMonster, ACTION_MODE_DETECT, FALSE);
|
|
ai_SetCombatRound(oMonster);
|
|
string sAI = GetLocalString(oMonster, AI_COMBAT_SCRIPT);
|
|
if(sAI != "ai_coward")
|
|
{
|
|
if(GetAppearanceType(oMonster) != ai_GetNormalAppearance(oMonster))
|
|
{
|
|
sAI = "ai_polymorphed";
|
|
}
|
|
else if(ai_GetIsHidden(oMonster) && !ai_GetNearestIndexThatSeesUs(oMonster)) sAI = "ai_invisible";
|
|
}
|
|
if(sAI == "") sAI = "ai_default";
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "230", "********** " + GetName (oMonster) + " **********");
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "231", "********** " + sAI + " **********");
|
|
// We clear actions here and setup multiple actions to the queue for oCreature.
|
|
ai_ClearCreatureActions();
|
|
ai_Counter_Start();
|
|
ExecuteScript(sAI, oMonster);
|
|
ai_Counter_End(GetName(oMonster) + " is ending round calculations.");
|
|
return;
|
|
}
|
|
// Check to see if we just didn't see the enemies.
|
|
if(GetLocalInt(oMonster, AI_ENEMY_NUMBERS) &&
|
|
ai_SearchForHiddenCreature(oMonster, TRUE)) return;
|
|
// We have exhausted our check for an enemy. Combat is over.
|
|
ai_EndCombatRound(oMonster);
|
|
ai_ClearCombatState(oMonster);
|
|
// Run the heartbeat script so we start doing our actions out of combat.
|
|
ExecuteScript("nw_c2_default1", oMonster);
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "247", GetName(oMonster) + "'s combat has ended!");
|
|
return;
|
|
}
|
|
void ai_StartMonsterCombat(object oMonster)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "264", "---------- " + GetName(oMonster) + " is starting combat! ----------");
|
|
ai_SetCreatureTalents(oMonster, TRUE);
|
|
ai_DoMonsterCombatRound(oMonster);
|
|
}
|
|
float ai_GetFollowDistance(object oCreature)
|
|
{
|
|
// Also check for size of creature and adjust based on that.
|
|
float fDistance = StringToFloat(Get2DAString("appearance", "PREFATCKDIST", GetAppearanceType(oCreature)));
|
|
return GetLocalFloat(oCreature, AI_FOLLOW_RANGE) + fDistance;
|
|
}
|
|
int ai_StayClose(object oCreature)
|
|
{
|
|
if(ai_GetIsCharacter(oCreature) ||
|
|
ai_GetAIMode(oCreature, AI_MODE_STAND_GROUND) ||
|
|
GetLocalString(oCreature, AI_COMBAT_SCRIPT) == "ai_a_peaceful" ||
|
|
GetLocalString(oCreature, AI_COMBAT_SCRIPT) == "ai_coward") return FALSE;
|
|
object oMaster = GetMaster(oCreature);
|
|
// We stay within our perception range of who we are following.
|
|
float fPerceptionDistance = GetLocalFloat(oCreature, AI_ASSOC_PERCEPTION_DISTANCE);
|
|
if(fPerceptionDistance == 0.0)
|
|
{
|
|
fPerceptionDistance = GetLocalFloat(oMaster, AI_ASSOC_PERCEPTION_DISTANCE);
|
|
if(fPerceptionDistance == 0.0) fPerceptionDistance = 20.0;
|
|
}
|
|
object oTarget = GetLocalObject(oCreature, AI_FOLLOW_TARGET);
|
|
if(oTarget == OBJECT_INVALID) oTarget = oMaster;
|
|
if(AI_DEBUG) ai_Debug("0i_associates", "214", "Distance from who we are following in combat." +
|
|
" oFollowing: " + FloatToString(GetDistanceBetween(oTarget, oCreature), 0, 2) + " fPerceptionDistance: " + FloatToString(fPerceptionDistance, 0, 2));
|
|
if(GetDistanceBetween(oTarget, oCreature) < fPerceptionDistance) return FALSE;
|
|
ai_ClearCreatureActions();
|
|
if(AI_DEBUG) ai_Debug("0i_associates", "218", "We are too far away! Move back to our master.");
|
|
ActionMoveToObject(oTarget, TRUE, ai_GetFollowDistance(oCreature));
|
|
return TRUE;
|
|
}
|
|
int ai_TryToBecomeInvisible(object oCreature)
|
|
{
|
|
// If we are invisible then we don't need to check this.
|
|
if(!ai_GetIsHidden(oCreature)) return FALSE;
|
|
// If we are not invisible lets try.
|
|
int nDarkness;
|
|
if(GetHasSpell(SPELL_DARKNESS, oCreature) && ai_GetHasEffectType(oCreature, EFFECT_TYPE_ULTRAVISION)) nDarkness = TRUE;
|
|
if(GetHasSpell(SPELL_IMPROVED_INVISIBILITY, oCreature) || GetHasSpell(SPELL_INVISIBILITY, oCreature) ||
|
|
GetHasSpell(SPELL_INVISIBILITY_SPHERE, oCreature) ||(nDarkness) ||
|
|
GetHasSpell(SPELL_SANCTUARY, oCreature) || GetHasSpell(SPELL_ETHEREALNESS, oCreature) ||
|
|
GetHasSpell(799/*SPELLABILITY_VAMPIRE_INVISIBILITY*/) ||
|
|
GetHasFeat(FEAT_HIDE_IN_PLAIN_SIGHT, oCreature) == TRUE)
|
|
{
|
|
// This bit ported directly from Jasperre
|
|
// Can anyone see me?(has spell effects of X)
|
|
// The point of this is to see if its even worthwhile to go invisbile
|
|
// or will it be immediately dispeled.
|
|
object oSeeMe = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, oCreature, 1, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN, CREATURE_TYPE_HAS_SPELL_EFFECT, SPELL_TRUE_SEEING);
|
|
if(oSeeMe == OBJECT_INVALID)
|
|
{
|
|
oSeeMe = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, oCreature, 1, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN, CREATURE_TYPE_HAS_SPELL_EFFECT, SPELL_SEE_INVISIBILITY);
|
|
}
|
|
if(oSeeMe == OBJECT_INVALID)
|
|
{
|
|
// Check non-invisibility options first. Since they can be used
|
|
// while near enemies.
|
|
if(GetHasFeat(FEAT_HIDE_IN_PLAIN_SIGHT, oCreature))
|
|
{
|
|
// Go into stealth mode
|
|
SetActionMode(oCreature, ACTION_MODE_STEALTH, TRUE);
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "207", "Using HIDE_IN_PLAIN_SIGHT!");
|
|
return TRUE;
|
|
}
|
|
if(nDarkness)
|
|
{
|
|
ai_SetLastAction(oCreature, SPELL_DARKVISION);
|
|
ActionCastSpellAtObject(SPELL_DARKVISION, oCreature);
|
|
return TRUE;
|
|
}
|
|
if(GetHasSpell(SPELL_ETHEREALNESS, oCreature))
|
|
{
|
|
ai_SetLastAction(oCreature, SPELL_ETHEREALNESS);
|
|
ActionCastSpellAtObject(SPELL_ETHEREALNESS, oCreature);
|
|
return TRUE;
|
|
}
|
|
if(GetHasSpell(SPELL_SANCTUARY, oCreature))
|
|
{
|
|
ai_SetLastAction(oCreature, SPELL_SANCTUARY);
|
|
ActionCastSpellAtObject(SPELL_SANCTUARY, oCreature);
|
|
return TRUE;
|
|
}
|
|
// Get the nearest Enemy and how close they are.
|
|
// Use this to keep invisibility from being spammed in melee.
|
|
object oEnemy = ai_GetNearestEnemy(oCreature);
|
|
if(GetDistanceBetween(oCreature, oEnemy) > AI_RANGE_MELEE)
|
|
{
|
|
if(GetHasSpell(SPELL_IMPROVED_INVISIBILITY, oCreature))
|
|
{
|
|
ai_SetLastAction(oCreature, SPELL_IMPROVED_INVISIBILITY);
|
|
ActionCastSpellAtObject(SPELL_IMPROVED_INVISIBILITY, oCreature);
|
|
return TRUE;
|
|
}
|
|
if(GetHasSpell(SPELL_INVISIBILITY, oCreature))
|
|
{
|
|
ai_SetLastAction(oCreature, SPELL_INVISIBILITY);
|
|
ActionCastSpellAtObject(SPELL_INVISIBILITY, oCreature);
|
|
return TRUE;
|
|
}
|
|
if(GetHasSpell(SPELL_INVISIBILITY_SPHERE, oCreature))
|
|
{
|
|
ai_SetLastAction(oCreature, SPELL_INVISIBILITY_SPHERE);
|
|
ActionCastSpellAtObject(SPELL_INVISIBILITY_SPHERE, oCreature);
|
|
return TRUE;
|
|
}
|
|
if(GetHasSpell(799/*SPELLABILITY_VAMPIRE_INVISIBILITY*/, oCreature))
|
|
{
|
|
ai_SetLastAction(oCreature, 799/*SPELLABILITY_VAMPIRE_INVISIBILITY*/);
|
|
ActionCastSpellAtObject(799/*SPELLABILITY_VAMPIRE_INVISIBILITY*/, oCreature);
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
int ai_SearchForHiddenCreature(object oCreature, int bMonster, object oInvisible = OBJECT_INVALID, float fRange = 1.0)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "358", GetName(oCreature) + " is searching for an invisible creature (" +
|
|
GetName(oInvisible) + ").");
|
|
if(oInvisible == OBJECT_INVALID)
|
|
{
|
|
// Have we seen anyone go invisible?
|
|
oInvisible = GetLocalObject(oCreature, AI_IS_INVISIBLE);
|
|
if(oInvisible == OBJECT_INVALID || GetIsDead(oInvisible))
|
|
{
|
|
oInvisible = ai_GetNearestEnemy(oCreature, 1, 7, PERCEPTION_HEARD_AND_NOT_SEEN);
|
|
if(oInvisible == OBJECT_INVALID) oInvisible = ai_GetNearestEnemy(oCreature);
|
|
}
|
|
}
|
|
float fPerceptionDistance, fDistance;
|
|
if(bMonster)
|
|
{
|
|
GetDistanceBetween(oCreature, oInvisible);
|
|
fPerceptionDistance = GetLocalFloat(GetModule(), AI_RULE_PERCEPTION_DISTANCE);
|
|
}
|
|
else
|
|
{
|
|
// We want to use the distance between the PC and target not us.
|
|
object oMaster = GetMaster();
|
|
if(oMaster != OBJECT_INVALID) fDistance = GetDistanceBetween(oMaster, oInvisible);
|
|
else GetDistanceBetween(oCreature, oInvisible);
|
|
fPerceptionDistance = GetLocalFloat(oCreature, AI_ASSOC_PERCEPTION_DISTANCE);
|
|
if(fPerceptionDistance == 0.0) fPerceptionDistance = 20.0;
|
|
}
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "383", "Is invisible: " + GetName(oInvisible) +
|
|
" fDistance: " + FloatToString(fDistance, 0, 2) +
|
|
" fPerceptionDistance: " + FloatToString(fPerceptionDistance, 0, 2));
|
|
// Might need to end combat at this point?
|
|
if(fDistance > fPerceptionDistance) return FALSE;
|
|
// If we are close enough then lets look for them.
|
|
if(fDistance < AI_RANGE_LONG)
|
|
{
|
|
// nHidden 1 = Invisible effects, 2 = Darkness effects, 3 = Sanctuary effects, 4 Stealth.
|
|
int nHidden = ai_GetIsHidden(oInvisible);
|
|
if(nHidden)
|
|
{
|
|
// They have a magical effect! Is there a spell we can use to see?
|
|
if(nHidden < 4)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "399", " They are using magic to hide: " +
|
|
IntToString(nHidden));
|
|
// True Seeing pierces all types of magical hiding.
|
|
if(GetHasSpell(SPELL_TRUE_SEEING, oCreature))
|
|
{
|
|
ai_SetLastAction(oCreature, SPELL_TRUE_SEEING);
|
|
ActionCastSpellAtObject(SPELL_TRUE_SEEING, oCreature);
|
|
return TRUE;
|
|
}
|
|
if(nHidden == 1 || nHidden == 3) // Invisibility or Ethereal effect.
|
|
{
|
|
if(GetHasSpell(SPELL_SEE_INVISIBILITY, oCreature))
|
|
{
|
|
ai_SetLastAction(oCreature, SPELL_SEE_INVISIBILITY);
|
|
ActionCastSpellAtObject(SPELL_SEE_INVISIBILITY, oCreature);
|
|
return TRUE;
|
|
}
|
|
if(GetHasSpell(SPELL_INVISIBILITY_PURGE, oCreature))
|
|
{
|
|
ai_SetLastAction(oCreature, SPELL_INVISIBILITY_PURGE);
|
|
ActionCastSpellAtObject(SPELL_INVISIBILITY_PURGE, oCreature);
|
|
return TRUE;
|
|
}
|
|
}
|
|
if(nHidden == 2) // Darkness spell effect.
|
|
{
|
|
if(GetHasSpell(SPELL_DARKVISION))
|
|
{
|
|
ai_SetLastAction(oCreature, SPELL_DARKVISION);
|
|
ActionCastSpellAtObject(SPELL_DARKVISION, oCreature);
|
|
return TRUE;
|
|
}
|
|
}
|
|
// To be able to attack a magically hidden foe we have to be
|
|
// with in melee attack range. Cannot hear Ethereal foes!
|
|
// We will automatically hear them once we are within range.
|
|
// We also walk so we don't give attacks of opportunity.
|
|
if(nHidden < 3)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "437", " We have no spells to counter with. Moving up to attack!");
|
|
SetLocalInt(oCreature, AI_AM_I_SEARCHING, TRUE);
|
|
ActionMoveToObject(oInvisible);
|
|
ActionDoCommand(DeleteLocalInt(oCreature, AI_AM_I_SEARCHING));
|
|
if(ai_GetIsInCombat(oCreature)) ActionDoCommand(ExecuteScript("0e_do_combat_rnd", oCreature));
|
|
return TRUE;
|
|
}
|
|
}
|
|
else // They are using stealth!
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "447", " Using Detect mode and moving up.");
|
|
SetActionMode(oCreature, ACTION_MODE_DETECT, TRUE);
|
|
SetLocalInt(oCreature, AI_AM_I_SEARCHING, TRUE);
|
|
// We use to move to the object but that is creepy!
|
|
//ActionMoveToObject(oInvisible, FALSE, fRange);
|
|
ActionMoveToLocation(GetLocation(oInvisible), FALSE);
|
|
ActionDoCommand(DeleteLocalInt(oCreature, AI_AM_I_SEARCHING));
|
|
if(ai_GetIsInCombat(oCreature)) ActionDoCommand(ExecuteScript("0e_do_combat_rnd", oCreature));
|
|
return TRUE;
|
|
}
|
|
}
|
|
else // They are not hidden, then that means we can hear them but not see them.
|
|
// Probably behind a wall or door.
|
|
{
|
|
SetLocalInt(oCreature, AI_AM_I_SEARCHING, TRUE);
|
|
// We use to move to the object but that is creepy!
|
|
//ActionMoveToObject(oInvisible, FALSE, fRange);
|
|
ActionMoveToLocation(GetLocation(oInvisible), FALSE);
|
|
ActionDoCommand(DeleteLocalInt(oCreature, AI_AM_I_SEARCHING));
|
|
if(ai_GetIsInCombat(oCreature)) ActionDoCommand(ExecuteScript("0e_do_combat_rnd", oCreature));
|
|
return TRUE;
|
|
}
|
|
}
|
|
else // We need to get closer to start looking for them.
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "469", "Moving towards invisible creature from a distance: " + GetName(oInvisible));
|
|
SetLocalInt(oCreature, AI_AM_I_SEARCHING, TRUE);
|
|
// We use to move to the object but that is creepy!
|
|
//ActionMoveToObject(oInvisible, TRUE, 14.0);
|
|
ActionMoveToLocation(GetLocation(oInvisible), FALSE);
|
|
AssignCommand(oCreature, ActionDoCommand(DeleteLocalInt(oCreature, AI_AM_I_SEARCHING)));
|
|
if(ai_GetIsInCombat(oCreature)) ActionDoCommand(ExecuteScript("0e_do_combat_rnd", oCreature));
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
int ai_MoralCheck(object oCreature)
|
|
{
|
|
// If we are immune to fear then we are immune to MoralChecks!
|
|
// Constructs and Undead are also immune to fear.
|
|
int nRaceType = GetRacialType(oCreature);
|
|
if(!GetLocalInt(GetModule(), AI_RULE_MORAL_CHECKS) || GetIsImmune(oCreature, IMMUNITY_TYPE_FEAR) ||
|
|
nRaceType == RACIAL_TYPE_UNDEAD ||
|
|
nRaceType == RACIAL_TYPE_CONSTRUCT ||
|
|
ai_GetIsCharacter(oCreature)) return FALSE;
|
|
// Moral DC is AI_WOUNDED_MORAL_DC - The number of allies.
|
|
// or AI_BLOODY_MORAL_DC - number of allies.
|
|
int nDC;
|
|
int nHpPercent = ai_GetPercHPLoss(oCreature);
|
|
object oNearestEnemy = GetLocalObject(oCreature, AI_ENEMY_NEAREST);
|
|
// We only make moral checks if we are below half hitpoints and the Difficulty should be adjusted to -10 at 0.
|
|
if(nHpPercent <= AI_HEALTH_WOUNDED)
|
|
{
|
|
// Debug code to look for multiple moral checks at once by one creature?
|
|
if(GetLocalString(GetModule(), AI_RULE_DEBUG_CREATURE) == "")
|
|
{
|
|
SetLocalString(GetModule(), AI_RULE_DEBUG_CREATURE, GetName(oCreature));
|
|
ai_Debug("0i_actions", "424", GetName(oCreature) + " starting debug mode to test Moral checks!");
|
|
}
|
|
if(nHpPercent <= AI_HEALTH_BLOODY) nDC = AI_BLOODY_MORAL_DC;
|
|
else nDC = AI_WOUNDED_MORAL_DC;
|
|
nDC = nDC - GetLocalInt(oCreature, AI_ALLY_NUMBERS);
|
|
if(nDC < 1) nDC = 1;
|
|
if(AI_DEBUG) ai_Debug("0i_talents", "367", "Moral check DC: " + IntToString(nDC) + ".");
|
|
//SendMessageToPC(GetFirstPC(), "0i_talents, 431, " + GetName(oCreature) + " Moral check DC: " + IntToString(nDC) + ".");
|
|
if(!WillSave(oCreature, nDC, SAVING_THROW_TYPE_FEAR, oNearestEnemy))
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_talents", "370", "Moral check failed, we are fleeing!");
|
|
SetLocalString(oCreature, AI_COMBAT_SCRIPT, "ai_coward");
|
|
effect eVFX = EffectVisualEffect(VFX_DUR_MIND_AFFECTING_FEAR);
|
|
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eVFX, oCreature, 6.0f);
|
|
ActionMoveAwayFromObject(oNearestEnemy, TRUE, AI_RANGE_LONG);
|
|
if(!ai_GetAIMode(oCreature, AI_MODE_DO_NOT_SPEAK))
|
|
{
|
|
int nRoll = d4();
|
|
if(nRoll == 1) PlayVoiceChat(VOICE_CHAT_FLEE, oCreature);
|
|
else if(nRoll == 2) PlayVoiceChat(VOICE_CHAT_GUARDME, oCreature);
|
|
else if(nRoll == 3) PlayVoiceChat(VOICE_CHAT_HELP, oCreature);
|
|
else if(nRoll == 4 && nHpPercent < 100) PlayVoiceChat(VOICE_CHAT_HEALME, oCreature);
|
|
}
|
|
return TRUE;
|
|
}
|
|
if(nDC >= 11 && !ai_GetAIMode(oCreature, AI_MODE_DO_NOT_SPEAK))
|
|
{
|
|
int nRoll = d6();
|
|
// Cry out when you are overwhelmed!
|
|
if(nRoll == 1) PlayVoiceChat(VOICE_CHAT_CUSS, oCreature);
|
|
else if(nRoll == 2) PlayVoiceChat(VOICE_CHAT_BADIDEA, oCreature);
|
|
else if(nRoll == 3) PlayVoiceChat(VOICE_CHAT_ENEMIES, oCreature);
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
int ai_GetInAOEReaction(object oCreature, object oCaster, int nSpell)
|
|
{
|
|
switch(nSpell)
|
|
{
|
|
case SPELL_ACID_FOG:
|
|
case SPELL_CLOUDKILL:
|
|
case SPELL_CREEPING_DOOM:
|
|
{
|
|
// Nothing but bad times with these spells.
|
|
return TRUE;
|
|
}
|
|
case SPELL_STORM_OF_VENGEANCE:
|
|
{
|
|
// This only harms our enemies!
|
|
return (oCaster != oCreature && GetIsEnemy(oCaster, oCreature));
|
|
}
|
|
// They should only flee Silence if they want to cast a spell!
|
|
//case SPELL_SILENCE:
|
|
case SPELL_BLADE_BARRIER:
|
|
case SPELL_WALL_OF_FIRE:
|
|
case SPELL_INCENDIARY_CLOUD:
|
|
{
|
|
// Check reflex feats and saves.
|
|
return (!GetHasFeat(FEAT_EVASION, oCreature) &&
|
|
!GetHasFeat(FEAT_IMPROVED_EVASION, oCreature) &&
|
|
GetReflexSavingThrow(oCreature) < 21 + d6());
|
|
}
|
|
case SPELL_STINKING_CLOUD:
|
|
{
|
|
// Do we have a high fortitude save? 20 + 5
|
|
return (GetFortitudeSavingThrow(oCreature) < 20 + d6());
|
|
}
|
|
case SPELL_GREASE:
|
|
case SPELL_ENTANGLE:
|
|
case SPELL_VINE_MINE_ENTANGLE:
|
|
case SPELL_WEB:
|
|
{
|
|
// Do we have a high reflex save? d20 + 1
|
|
return (!GetHasFeat(FEAT_WOODLAND_STRIDE, oCreature) &&
|
|
!GetLocalInt(oCreature, "X2_L_IS_INCORPOREAL") &&
|
|
GetReflexSavingThrow(oCreature) < 15 + d6());
|
|
}
|
|
case SPELL_EVARDS_BLACK_TENTACLES:
|
|
{
|
|
// Small creatures are immune and can they hit me? d20 + 8 + caster lvl(7)
|
|
return (GetCreatureSize(oCreature) > 2 &&
|
|
GetAC(oCreature) < 30 + d6());
|
|
}
|
|
case SPELL_CLOUD_OF_BEWILDERMENT:
|
|
{
|
|
// Do we have a high fortitude save? 20 + 2
|
|
return (GetFortitudeSavingThrow(oCreature) < 17 + d6());
|
|
}
|
|
case SPELL_MIND_FOG:
|
|
case SPELL_STONEHOLD:
|
|
{
|
|
// Do we have a high enough will save? 20 + 6
|
|
return (GetWillSavingThrow(oCreature) < 21 + d6());
|
|
}
|
|
case SPELL_SPIKE_GROWTH:
|
|
case SPELL_VINE_MINE_HAMPER_MOVEMENT:
|
|
{
|
|
// Do we have a high reflex save? d20 + 3
|
|
return (GetReflexSavingThrow(oCreature) < 18 + d6());
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
void ai_HaveCreatureSpeak(object oCreature, int nRoll, string sVoiceChatArray, int bImportant = FALSE)
|
|
{
|
|
if(ai_GetAIMode(oCreature, AI_MODE_DO_NOT_SPEAK) && !bImportant) return;
|
|
if(nRoll == 0)
|
|
{
|
|
// Some races shouldn't talk.
|
|
int nRacialType = GetRacialType(oCreature);
|
|
if(nRacialType == RACIAL_TYPE_ANIMAL || nRacialType == RACIAL_TYPE_BEAST ||
|
|
nRacialType == RACIAL_TYPE_MAGICAL_BEAST || nRacialType == RACIAL_TYPE_OOZE ||
|
|
nRacialType == RACIAL_TYPE_UNDEAD || nRacialType == RACIAL_TYPE_VERMIN) return;
|
|
SpeakString(sVoiceChatArray);
|
|
return;
|
|
}
|
|
nRoll = Random(nRoll);
|
|
string sVoice = ai_GetStringArray(sVoiceChatArray, nRoll);
|
|
if(sVoice != "") PlayVoiceChat(StringToInt(sVoice), oCreature);
|
|
}
|
|
int ai_CheckForAssociateSpellTalent(object oAssociate, int nInMelee, int nMaxLevel, int nRound = 0)
|
|
{
|
|
// ******************* OFFENSIVE AOE TALENTS ***********************
|
|
// Check the battlefield for a group of enemies to shoot a big spell at!
|
|
// We are checking here since these opportunities are rare and we need
|
|
// to take advantage of them as often as possible.
|
|
if(!ai_GetMagicMode(oAssociate, AI_MAGIC_DEFENSIVE_CASTING))
|
|
{
|
|
if(ai_UseCreatureTalent(oAssociate, AI_TALENT_INDISCRIMINANT_AOE, nInMelee, nMaxLevel)) return TRUE;
|
|
if(ai_UseCreatureTalent(oAssociate, AI_TALENT_DISCRIMINANT_AOE, nInMelee, nMaxLevel)) return TRUE;
|
|
}
|
|
if(ai_GetMagicMode(oAssociate, AI_MAGIC_OFFENSIVE_CASTING)) return FALSE;
|
|
// ********** PROTECTION/ENHANCEMENT/SUMMON TALENTS ************
|
|
// Does our master want to be buffed first?
|
|
object oTarget = OBJECT_INVALID;
|
|
if(ai_GetMagicMode(oAssociate, AI_MAGIC_BUFF_MASTER)) oTarget = GetMaster(oAssociate);
|
|
return ai_TryDefensiveTalents(oAssociate, nInMelee, nMaxLevel, nRound, oTarget);
|
|
}
|
|
void ai_DoPhysicalAttackOnBest(object oCreature, int nInMelee, int bAlwaysAtk = TRUE)
|
|
{
|
|
talent tUse;
|
|
object oTarget;
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "496", "Check for ranged attack on nearest enemy!");
|
|
// ************************** Ranged feat attacks **************************
|
|
if(!GetHasFeatEffect(FEAT_BARBARIAN_RAGE, oCreature) &&
|
|
!ai_GetAIMode(oCreature, AI_MODE_STOP_RANGED) &&
|
|
ai_CanIUseRangedWeapon(oCreature, nInMelee))
|
|
{
|
|
if(ai_HasRangedWeaponWithAmmo(oCreature))
|
|
{
|
|
if(ai_TryRangedSneakAttack(oCreature, nInMelee)) return;
|
|
// Lets pick off the nearest targets first.
|
|
if(!nInMelee)
|
|
{
|
|
if(ai_GetAIMode(oCreature, AI_MODE_DEFEND_MASTER)) oTarget = ai_GetLowestCRAttackerOnMaster(oCreature);
|
|
if(oTarget == OBJECT_INVALID) oTarget = ai_GetNearestFavoredEnemyTarget(oCreature);
|
|
if(oTarget == OBJECT_INVALID) oTarget = ai_GetLowestCRTarget(oCreature);
|
|
}
|
|
else
|
|
{
|
|
if(ai_GetAIMode(oCreature, AI_MODE_DEFEND_MASTER)) oTarget = ai_GetLowestCRAttackerOnMaster(oCreature);
|
|
if(oTarget == OBJECT_INVALID) oTarget = ai_GetNearestFavoredEnemyTarget(oCreature, AI_RANGE_MELEE);
|
|
if(oTarget == OBJECT_INVALID) oTarget = ai_GetLowestCRTarget(oCreature, AI_RANGE_MELEE);
|
|
}
|
|
if(oTarget != OBJECT_INVALID)
|
|
{
|
|
if(ai_TryRapidShotFeat(oCreature, oTarget, nInMelee)) return;
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "519", "Do ranged attack against nearest: " + GetName(oTarget) + "!");
|
|
ai_ActionAttack(oCreature, AI_LAST_ACTION_RANGED_ATK, oTarget, nInMelee, TRUE);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
ai_SearchForHiddenCreature(oCreature, TRUE);
|
|
return;
|
|
}
|
|
}
|
|
else if(ai_InCombatEquipBestRangedWeapon(oCreature)) return;
|
|
}
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "525", "Check for melee attack on nearest enemy!");
|
|
// ************************** Melee feat attacks *************************
|
|
if(ai_InCombatEquipBestMeleeWeapon(oCreature)) return;
|
|
if(ai_TryWhirlwindFeat(oCreature)) return;
|
|
if(ai_TrySneakAttack(oCreature, nInMelee, bAlwaysAtk)) return;
|
|
if(ai_GetAIMode(oCreature, AI_MODE_DEFEND_MASTER)) oTarget = ai_GetLowestCRAttackerOnMaster(oCreature);
|
|
if(oTarget == OBJECT_INVALID) oTarget = ai_GetNearestFavoredEnemyTarget(oCreature, AI_RANGE_PERCEPTION, bAlwaysAtk);
|
|
if(oTarget == OBJECT_INVALID) oTarget = ai_GetBestTargetForMeleeCombat(oCreature, nInMelee, bAlwaysAtk);
|
|
// If we don't find a target then we don't want to fight anyone!
|
|
if(oTarget != OBJECT_INVALID)
|
|
{
|
|
if(ai_TryMeleeTalents(oCreature, oTarget)) return;
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "536", "Do melee attack against nearest: " + GetName(oTarget) + "!");
|
|
ai_ActionAttack(oCreature, AI_LAST_ACTION_MELEE_ATK, oTarget);
|
|
}
|
|
else ai_SearchForHiddenCreature(oCreature, TRUE);
|
|
}
|
|
void ai_DoPhysicalAttackOnNearest(object oCreature, int nInMelee, int bAlwaysAtk = TRUE)
|
|
{
|
|
talent tUse;
|
|
object oTarget;
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "496", "Check for ranged attack on nearest enemy!");
|
|
// ************************** Ranged feat attacks **************************
|
|
if(!GetHasFeatEffect(FEAT_BARBARIAN_RAGE, oCreature) &&
|
|
!ai_GetAIMode(oCreature, AI_MODE_STOP_RANGED) &&
|
|
ai_CanIUseRangedWeapon(oCreature, nInMelee))
|
|
{
|
|
if(ai_HasRangedWeaponWithAmmo(oCreature))
|
|
{
|
|
if(ai_TryRangedSneakAttack(oCreature, nInMelee)) return;
|
|
// Lets pick off the nearest targets first.
|
|
if(!nInMelee)
|
|
{
|
|
if(ai_GetAIMode(oCreature, AI_MODE_DEFEND_MASTER)) oTarget = ai_GetLowestCRAttackerOnMaster(oCreature);
|
|
if(oTarget == OBJECT_INVALID) oTarget = ai_GetNearestFavoredEnemyTarget(oCreature);
|
|
if(oTarget == OBJECT_INVALID) oTarget = ai_GetNearestTarget(oCreature);
|
|
}
|
|
else
|
|
{
|
|
if(ai_GetAIMode(oCreature, AI_MODE_DEFEND_MASTER)) oTarget = ai_GetLowestCRAttackerOnMaster(oCreature);
|
|
if(oTarget == OBJECT_INVALID) oTarget = ai_GetNearestFavoredEnemyTarget(oCreature, AI_RANGE_MELEE);
|
|
if(oTarget == OBJECT_INVALID) oTarget = ai_GetNearestTarget(oCreature, AI_RANGE_MELEE);
|
|
}
|
|
if(oTarget != OBJECT_INVALID)
|
|
{
|
|
if(ai_TryRapidShotFeat(oCreature, oTarget, nInMelee)) return;
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "519", "Do ranged attack against nearest: " + GetName(oTarget) + "!");
|
|
ai_ActionAttack(oCreature, AI_LAST_ACTION_RANGED_ATK, oTarget, nInMelee, TRUE);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
ai_SearchForHiddenCreature(oCreature, TRUE);
|
|
return;
|
|
}
|
|
}
|
|
else if(ai_InCombatEquipBestRangedWeapon(oCreature)) return;
|
|
}
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "525", "Check for melee attack on nearest enemy!");
|
|
// ************************** Melee feat attacks *************************
|
|
if(ai_InCombatEquipBestMeleeWeapon(oCreature)) return;
|
|
if(ai_TryWhirlwindFeat(oCreature)) return;
|
|
if(ai_TrySneakAttack(oCreature, nInMelee, bAlwaysAtk)) return;
|
|
if(ai_GetAIMode(oCreature, AI_MODE_DEFEND_MASTER)) oTarget = ai_GetLowestCRAttackerOnMaster(oCreature);
|
|
if(oTarget == OBJECT_INVALID) oTarget = ai_GetNearestFavoredEnemyTarget(oCreature, AI_RANGE_PERCEPTION, bAlwaysAtk);
|
|
if(oTarget == OBJECT_INVALID) oTarget = ai_GetNearestTargetForMeleeCombat(oCreature, nInMelee, bAlwaysAtk);
|
|
// If we don't find a target then we don't want to fight anyone!
|
|
if(oTarget != OBJECT_INVALID)
|
|
{
|
|
if(ai_TryMeleeTalents(oCreature, oTarget)) return;
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "536", "Do melee attack against nearest: " + GetName(oTarget) + "!");
|
|
ai_ActionAttack(oCreature, AI_LAST_ACTION_MELEE_ATK, oTarget);
|
|
}
|
|
else ai_SearchForHiddenCreature(oCreature, TRUE);
|
|
}
|
|
void ai_DoPhysicalAttackOnLowestCR(object oCreature, int nInMelee, int bAlwaysAtk = TRUE)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "533", "Check for ranged attack on weakest enemy!");
|
|
object oTarget;
|
|
// ************************** Ranged feat attacks **************************
|
|
if(!GetHasFeatEffect(FEAT_BARBARIAN_RAGE, oCreature) &&
|
|
!ai_GetAIMode(oCreature, AI_MODE_STOP_RANGED) &&
|
|
ai_CanIUseRangedWeapon(oCreature, nInMelee))
|
|
{
|
|
if(ai_HasRangedWeaponWithAmmo(oCreature))
|
|
{
|
|
if(ai_TryRangedSneakAttack(oCreature, nInMelee)) return;
|
|
// Lets pick off the weaker targets.
|
|
if(!nInMelee)
|
|
{
|
|
if(ai_GetAIMode(oCreature, AI_MODE_DEFEND_MASTER)) oTarget = ai_GetLowestCRAttackerOnMaster(oCreature);
|
|
if(oTarget == OBJECT_INVALID) oTarget = ai_GetNearestFavoredEnemyTarget(oCreature);
|
|
if(oTarget == OBJECT_INVALID) oTarget = ai_GetLowestCRTarget(oCreature);
|
|
}
|
|
else
|
|
{
|
|
if(ai_GetAIMode(oCreature, AI_MODE_DEFEND_MASTER)) oTarget = ai_GetLowestCRAttackerOnMaster(oCreature);
|
|
if(oTarget == OBJECT_INVALID) oTarget = ai_GetNearestFavoredEnemyTarget(oCreature, AI_RANGE_MELEE);
|
|
if(oTarget == OBJECT_INVALID) oTarget = ai_GetLowestCRTarget(oCreature, AI_RANGE_MELEE);
|
|
}
|
|
if(oTarget != OBJECT_INVALID)
|
|
{
|
|
if(ai_TryRapidShotFeat(oCreature, oTarget, nInMelee)) return;
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "559", GetName(OBJECT_SELF) + " does ranged attack on weakest: " + GetName(oTarget) + "!");
|
|
ai_ActionAttack(oCreature, AI_LAST_ACTION_RANGED_ATK, oTarget, nInMelee, TRUE);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
ai_SearchForHiddenCreature(oCreature, FALSE);
|
|
return;
|
|
}
|
|
}
|
|
else if(ai_InCombatEquipBestRangedWeapon(oCreature)) return;
|
|
}
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "571", "Check for melee attack on weakest enemy!");
|
|
// ************************** Melee feat attacks *************************
|
|
if(ai_InCombatEquipBestMeleeWeapon(oCreature)) return;
|
|
if(ai_TrySneakAttack(oCreature, nInMelee, bAlwaysAtk)) return;
|
|
if(ai_TryWhirlwindFeat(oCreature)) return;
|
|
if(ai_GetAIMode(oCreature, AI_MODE_DEFEND_MASTER)) oTarget = ai_GetLowestCRAttackerOnMaster(oCreature);
|
|
if(oTarget == OBJECT_INVALID) oTarget = ai_GetNearestFavoredEnemyTarget(oCreature, AI_RANGE_PERCEPTION, bAlwaysAtk);
|
|
if(oTarget == OBJECT_INVALID) oTarget = ai_GetLowestCRTargetForMeleeCombat(oCreature, nInMelee, bAlwaysAtk);
|
|
if(oTarget != OBJECT_INVALID)
|
|
{
|
|
if(ai_TryMeleeTalents(oCreature, oTarget)) return;
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "577", GetName(OBJECT_SELF) + " does melee attack against weakest: " + GetName(oTarget) + "!");
|
|
ai_ActionAttack(oCreature, AI_LAST_ACTION_MELEE_ATK, oTarget);
|
|
}
|
|
else ai_SearchForHiddenCreature(oCreature, FALSE);
|
|
}
|
|
int ai_InCombatEquipBestMeleeWeapon(object oCreature)
|
|
{
|
|
if(ai_GetIsMeleeWeapon(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oCreature))) return FALSE;
|
|
if(ai_EquipBestMeleeWeapon(oCreature))
|
|
{
|
|
// We delay 1 second since ActionEquip is not an action we can check for.
|
|
// This keeps event scripts from clearing before we actually equip.
|
|
SetLocalInt(oCreature, AI_COMBAT_WAIT_IN_SECONDS, 2);
|
|
ActionDoCommand(ExecuteScript("0e_do_combat_rnd", oCreature));
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
int ai_InCombatEquipBestRangedWeapon(object oCreature)
|
|
{
|
|
if(ai_EquipBestRangedWeapon(oCreature))
|
|
{
|
|
// We delay 1 second since ActionEquip is not an action we can check for.
|
|
// This keeps event scripts from clearing before we actually equip.
|
|
SetLocalInt(oCreature, AI_COMBAT_WAIT_IN_SECONDS, 1);
|
|
ActionDoCommand(ExecuteScript("0e_do_combat_rnd", oCreature));
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
int ai_CheckItemForHealing(object oCreature, object oTarget, object oItem, int nHpLost, int bEquiped = FALSE)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "629", "Checking Item properties on " + GetName(oItem));
|
|
int nIprpSubType, nSpell, nLevel, nIPType;
|
|
itemproperty ipProp = GetFirstItemProperty(oItem);
|
|
// Lets skip this if there are no properties.
|
|
if(!GetIsItemPropertyValid(ipProp)) return FALSE;
|
|
// Check for cast spell property and add them to the talent list.
|
|
int nIndex;
|
|
ipProp = GetFirstItemProperty(oItem);
|
|
while(GetIsItemPropertyValid(ipProp))
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "639", "ItempropertyType(15): " + IntToString(GetItemPropertyType(ipProp)));
|
|
nIPType = GetItemPropertyType(ipProp);
|
|
if(nIPType == ITEM_PROPERTY_CAST_SPELL)
|
|
{
|
|
nIprpSubType = GetItemPropertySubType(ipProp);
|
|
nSpell = StringToInt(Get2DAString("iprp_spells", "SpellIndex", nIprpSubType));
|
|
if(ai_ShouldWeCastThisCureSpell(nSpell, nHpLost))
|
|
{
|
|
// We have established that we can use the item if it is equiped.
|
|
if(!bEquiped) ai_CheckIfCanUseItem(oCreature, oItem);
|
|
// Get how they use the item (charges or uses per day).
|
|
int nUses = GetItemPropertyCostTableValue(ipProp);
|
|
if(nUses > 1 && nUses < 7)
|
|
{
|
|
int nCharges = GetItemCharges(oItem);
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "654", "Item charges: " + IntToString(nCharges));
|
|
if(nUses == 6 && nCharges < 1 || nUses == 5 && nCharges < 3 ||
|
|
nUses == 4 && nCharges < 5 || nUses == 3 && nCharges < 7 ||
|
|
nUses == 2 && nCharges < 9) return FALSE;
|
|
}
|
|
else if(nUses > 7 && nUses < 13)
|
|
{
|
|
int nPerDay = GetItemPropertyUsesPerDayRemaining(oItem, ipProp);
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "662", "Item uses: " + IntToString(nPerDay));
|
|
if(nPerDay == 0) return FALSE;
|
|
}
|
|
// SubType is the ip spell index for iprp_spells.2da
|
|
nIprpSubType = GetItemPropertySubType(ipProp);
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "667", GetName(oCreature) + " is using " + GetName(oItem) + " on " + GetName(oTarget) + ".");
|
|
ActionUseItemOnObject(oItem, ipProp, oTarget, nIprpSubType);
|
|
return TRUE;
|
|
}
|
|
}
|
|
nIndex++;
|
|
ipProp = GetNextItemProperty(oItem);
|
|
}
|
|
return FALSE;
|
|
}
|
|
int ai_HealSickness(object oCreature, object oTarget, object oPC, int nSickness, int bForce = FALSE)
|
|
{
|
|
// If the player is not forcing a check.
|
|
if(!bForce)
|
|
{
|
|
// Is Casting Cure spells off?
|
|
if(ai_GetMagicMode(oCreature, AI_MAGIC_CURE_SPELLS_OFF)) return FALSE;
|
|
// Do we have no magic on?
|
|
if(ai_GetMagicMode(oCreature, AI_MAGIC_NO_MAGIC)) return FALSE;
|
|
// Should we ignore associates?
|
|
if(ai_GetAIMode(oCreature, AI_MODE_IGNORE_ASSOCIATES) &&
|
|
GetAssociateType(oTarget) > 1) return FALSE;
|
|
}
|
|
// Check for spells.
|
|
if(nSickness == AI_ALLY_IS_DISEASED)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "717", "Attempting to remove disease.");
|
|
if(ai_CheckAndCastSpell(oCreature, SPELL_REMOVE_DISEASE, 0, 0.0, oTarget)) return TRUE;
|
|
}
|
|
else if(nSickness == AI_ALLY_IS_POISONED)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "726", "Attempting to remove poison.");
|
|
if(ai_CheckAndCastSpell(oCreature, SPELL_NEUTRALIZE_POISON, 0, 0.0, oTarget)) return TRUE;
|
|
}
|
|
else if(nSickness == AI_ALLY_IS_WEAK)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "735", "Attempting to remove ability score drain.");
|
|
if(ai_CheckAndCastSpell(oCreature, SPELL_LESSER_RESTORATION, 0, 0.0, oTarget)) return TRUE;
|
|
if(ai_CheckAndCastSpell(oCreature, SPELL_RESTORATION, 0, 0.0, oTarget)) return TRUE;
|
|
if(ai_CheckAndCastSpell(oCreature, SPELL_GREATER_RESTORATION, 0, 0.0, oTarget)) return TRUE;
|
|
}
|
|
else return FALSE;
|
|
// Check for healing kits.
|
|
if(!GetLocalInt(GetModule(), AI_RULE_HEALERSKITS)) return FALSE;
|
|
int nIprpSubType, nSpell;
|
|
itemproperty ipProp;
|
|
object oItem = GetFirstItemInInventory(oCreature);
|
|
while(oItem != OBJECT_INVALID)
|
|
{
|
|
if(GetIdentified(oItem))
|
|
{
|
|
int nBaseItemType = GetBaseItemType(oItem);
|
|
if(nBaseItemType == BASE_ITEM_HEALERSKIT &&
|
|
(nSickness == AI_ALLY_IS_DISEASED ||
|
|
nSickness == AI_ALLY_IS_POISONED))
|
|
{
|
|
ipProp = GetFirstItemProperty(oItem);
|
|
while(GetIsItemPropertyValid(ipProp))
|
|
{
|
|
if(GetItemPropertyType(ipProp) == ITEM_PROPERTY_HEALERS_KIT)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "772", "Attempting to remove (" + IntToString(nSickness) + ") with a healing kit.");
|
|
if(ai_GetIsCharacter(oPC)) ai_SendMessages(GetName(oCreature) + " uses " + GetName(oItem) + " on " + GetName(oTarget) + ".", AI_COLOR_YELLOW, oPC);
|
|
ActionUseItemOnObject(oItem, ipProp, oTarget);
|
|
return TRUE;
|
|
}
|
|
ipProp = GetNextItemProperty(oItem);
|
|
}
|
|
}
|
|
else if(nBaseItemType == BASE_ITEM_POTIONS ||
|
|
nBaseItemType == BASE_ITEM_ENCHANTED_POTION ||
|
|
nBaseItemType == FEAT_BREW_POTION)
|
|
{
|
|
ipProp = GetFirstItemProperty(oItem);
|
|
while(GetIsItemPropertyValid(ipProp))
|
|
{
|
|
nIprpSubType = GetItemPropertySubType(ipProp);
|
|
nSpell = StringToInt(Get2DAString("iprp_spells", "SpellIndex", nIprpSubType));
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "789", "Checking potion, " + IntToString(nSpell));
|
|
if(nSpell == SPELL_REMOVE_DISEASE && nSickness == AI_ALLY_IS_DISEASED)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "786", "Using a potion of Remove Disease.");
|
|
ActionUseItemOnObject(oItem, ipProp, oTarget);
|
|
return TRUE;
|
|
}
|
|
if(nSpell == SPELL_NEUTRALIZE_POISON && nSickness == AI_ALLY_IS_POISONED)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "786", "Using a potion of Neturalize Poison.");
|
|
ActionUseItemOnObject(oItem, ipProp, oTarget);
|
|
return TRUE;
|
|
}
|
|
if(nSpell == SPELL_LESSER_RESTORATION && nSickness == AI_ALLY_IS_WEAK)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "781", "Using a potion of Lesser Restoration.");
|
|
ActionUseItemOnObject(oItem, ipProp, oTarget);
|
|
return TRUE;
|
|
}
|
|
if(nSpell == SPELL_RESTORATION && nSickness == AI_ALLY_IS_WEAK)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "791", "Using a potion of Restoration.");
|
|
ActionUseItemOnObject(oItem, ipProp, oTarget);
|
|
return TRUE;
|
|
}
|
|
ipProp = GetNextItemProperty(oItem);
|
|
}
|
|
}
|
|
else if(nBaseItemType == BASE_ITEM_SCROLL ||
|
|
nBaseItemType == BASE_ITEM_ENCHANTED_SCROLL ||
|
|
nBaseItemType == BASE_ITEM_SPELLSCROLL ||
|
|
nBaseItemType == BASE_ITEM_ENCHANTED_WAND ||
|
|
nBaseItemType == BASE_ITEM_MAGICWAND ||
|
|
nBaseItemType == BASE_ITEM_MAGICSTAFF)
|
|
{
|
|
if(ai_CheckIfCanUseItem(oCreature, oItem))
|
|
{
|
|
ipProp = GetFirstItemProperty(oItem);
|
|
while(GetIsItemPropertyValid(ipProp))
|
|
{
|
|
nSpell = StringToInt(Get2DAString("iprp_spells", "SpellIndex", nIprpSubType));
|
|
if(nSpell == SPELL_REMOVE_DISEASE && nSickness == AI_ALLY_IS_DISEASED)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "786", "Using a potion of Remove Disease.");
|
|
ActionUseItemOnObject(oItem, ipProp, oTarget);
|
|
return TRUE;
|
|
}
|
|
if(nSpell == SPELL_NEUTRALIZE_POISON && nSickness == AI_ALLY_IS_POISONED)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "786", "Using a potion of Neturalize Poison.");
|
|
ActionUseItemOnObject(oItem, ipProp, oTarget);
|
|
return TRUE;
|
|
}
|
|
if(nSpell == SPELL_LESSER_RESTORATION && nSickness == AI_ALLY_IS_WEAK)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "781", "Using a potion of Lesser Restoration.");
|
|
ActionUseItemOnObject(oItem, ipProp, oTarget);
|
|
return TRUE;
|
|
}
|
|
if(nSpell == SPELL_RESTORATION && nSickness == AI_ALLY_IS_WEAK)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "791", "Using a potion of Restoration.");
|
|
ActionUseItemOnObject(oItem, ipProp, oTarget);
|
|
return TRUE;
|
|
}
|
|
ipProp = GetNextItemProperty(oItem);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
oItem = GetNextItemInInventory(oCreature);
|
|
}
|
|
return FALSE;
|
|
}
|
|
int ai_UseHealingItem(object oCreature, object oTarget, object oPC)
|
|
{
|
|
if(ai_GetMagicMode(oCreature, AI_MAGIC_NO_MAGIC_ITEMS)) return FALSE;
|
|
string sSlots;
|
|
int nDamage = GetMaxHitPoints(oTarget) - GetCurrentHitPoints(oTarget);
|
|
itemproperty ipProp;
|
|
// Cycle through all the creatures equiped items.
|
|
int nSlot;
|
|
object oItem = GetItemInSlot(nSlot, oCreature);
|
|
while(nSlot < 11)
|
|
{
|
|
if(oItem != OBJECT_INVALID &&
|
|
ai_CheckItemForHealing(oCreature, oTarget, oItem, nDamage, TRUE)) return TRUE;
|
|
oItem = GetItemInSlot(++nSlot, oCreature);
|
|
}
|
|
oItem = GetFirstItemInInventory(oCreature);
|
|
while(oItem != OBJECT_INVALID)
|
|
{
|
|
if(GetIdentified(oItem))
|
|
{
|
|
// Does the item need to be equiped to use its powers?
|
|
sSlots = Get2DAString("baseitems", "EquipableSlots", GetBaseItemType(oItem));
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "696", GetName(oItem) + " requires " + Get2DAString("baseitems", "EquipableSlots", GetBaseItemType(oItem)) + " slots.");
|
|
if(sSlots == "0x00000")
|
|
{
|
|
int nBaseItemType = GetBaseItemType(oItem);
|
|
// Lets not use up our healing kits on minor damage.
|
|
if(nBaseItemType == BASE_ITEM_HEALERSKIT)
|
|
{
|
|
if(!GetLocalInt(GetModule(), AI_RULE_HEALERSKITS)) return FALSE;
|
|
ipProp = GetFirstItemProperty(oItem);
|
|
if(GetItemPropertyType(ipProp) == ITEM_PROPERTY_HEALERS_KIT)
|
|
{
|
|
if(ai_GetIsCharacter(oPC)) ai_SendMessages(GetName(oCreature) + " uses " + GetName(oItem) + " on " + GetName(oTarget) + ".", AI_COLOR_YELLOW, oPC);
|
|
ActionUseItemOnObject(oItem, ipProp, oTarget);
|
|
return TRUE;
|
|
}
|
|
}
|
|
// Do we want Player AI and Associates to use potions on others?
|
|
//else if(nBaseItemType == BASE_ITEM_ENCHANTED_POTION ||
|
|
// nBaseItemType == BASE_ITEM_POTIONS)
|
|
//{
|
|
// if(oCaster == oTarget)
|
|
// {
|
|
// if(ai_CheckItemForHealing(oCreature, oTarget, oItem, nDamage)) return TRUE;
|
|
// }
|
|
//}
|
|
else if(ai_CheckItemForHealing(oCreature, oTarget, oItem, nDamage)) return TRUE;
|
|
}
|
|
}
|
|
oItem = GetNextItemInInventory(oCreature);
|
|
}
|
|
return FALSE;
|
|
}
|
|
void ai_ActionTryHealing(object oCreature, object oTarget)
|
|
{
|
|
ai_TryHealing(oCreature, oTarget, TRUE);
|
|
}
|
|
int ai_TryHealing(object oCreature, object oTarget, int bForce = FALSE)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "733", "Try healing: oCreature: " + GetName(oCreature) +
|
|
" oTarget: " + GetName(oTarget) + " No Party Healing: " + IntToString(ai_GetAIMode(oCreature, AI_MODE_PARTY_HEALING_OFF)) +
|
|
" No Self Healing: " + IntToString(ai_GetAIMode(oCreature, AI_MODE_SELF_HEALING_OFF)) +
|
|
" AI_I_AM_BEING_HEALED: " + IntToString(GetLocalInt(oTarget, "AI_I_AM_BEING_HEALED")) +
|
|
" Undead: " + IntToString(GetRacialType(oTarget) == RACIAL_TYPE_UNDEAD));
|
|
// If the player is not forcing a check.
|
|
if(!bForce)
|
|
{
|
|
// Should we ignore associates?
|
|
if(ai_GetAIMode(oCreature, AI_MODE_IGNORE_ASSOCIATES) &&
|
|
GetAssociateType(oTarget) > 1) return FALSE;
|
|
}
|
|
// Limits the number of times a wounded creature will ask for help.
|
|
if(GetLocalInt(oCreature, "AI_WOUNDED_SHOUT_LIMIT") > 3) return FALSE;
|
|
// This keeps everyone from healing the same character in one round and over healing!
|
|
if(oCreature == oTarget) DeleteLocalInt(oTarget, "AI_I_AM_BEING_HEALED");
|
|
else if(GetLocalInt(oTarget, "AI_I_AM_BEING_HEALED")) return FALSE;
|
|
if(ai_GetAIMode(oCreature, AI_MODE_PARTY_HEALING_OFF) &&
|
|
oCreature != oTarget) return FALSE;
|
|
if(ai_GetAIMode(oCreature, AI_MODE_SELF_HEALING_OFF) &&
|
|
oCreature == oTarget) return FALSE;
|
|
// Undead don't heal so lets skip this for them, maybe later we can fix this.
|
|
if(GetRacialType(oTarget) == RACIAL_TYPE_UNDEAD) return FALSE;
|
|
int nHpLost = ai_GetPercHPLoss(oTarget);
|
|
if(bForce && nHpLost < 100) nHpLost = 0;
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "743", "nHpLost: " + IntToString(nHpLost) +
|
|
" limit: " + IntToString(ai_GetHealersHpLimit(oTarget, FALSE)));
|
|
if(nHpLost >= ai_GetHealersHpLimit(oTarget, FALSE))
|
|
{
|
|
// Check to see if we need poison, disease, or ability drain removed.
|
|
int nEffectType;
|
|
effect eEffect = GetFirstEffect(oTarget);
|
|
while(GetIsEffectValid(eEffect))
|
|
{
|
|
nEffectType = GetEffectType(eEffect);
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "1094", "Checking to cure(31/32/39) nEffectType: " + IntToString(nEffectType));
|
|
if(nEffectType == EFFECT_TYPE_DISEASE)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "1097", "I am diseased!");
|
|
if(ai_HealSickness(oCreature, oTarget, ai_GetPlayerMaster(oCreature), AI_ALLY_IS_DISEASED, bForce)) return TRUE;
|
|
if(oCreature == oTarget)
|
|
{
|
|
if(!d20()) ai_HaveCreatureSpeak(oCreature, 5, ":43:4:14:15:16:");
|
|
SpeakString(AI_I_AM_DISEASED, TALKVOLUME_SILENT_TALK);
|
|
}
|
|
}
|
|
else if(nEffectType == EFFECT_TYPE_POISON)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "1107", "I am poisoned!");
|
|
if(ai_HealSickness(oCreature, oTarget, ai_GetPlayerMaster(oCreature), AI_ALLY_IS_POISONED, bForce)) return TRUE;
|
|
if(oCreature == oTarget)
|
|
{
|
|
if(!d20()) ai_HaveCreatureSpeak(oCreature, 6, ":43:4:14:15:16:19:");
|
|
SpeakString(AI_I_AM_POISONED, TALKVOLUME_SILENT_TALK);
|
|
}
|
|
}
|
|
else if(nEffectType == EFFECT_TYPE_ABILITY_DECREASE)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "1117", "I am weak!");
|
|
if(ai_HealSickness(oCreature, oTarget, ai_GetPlayerMaster(oCreature), AI_ALLY_IS_WEAK, bForce)) return TRUE;
|
|
if(oCreature == oTarget)
|
|
{
|
|
if(!d20()) ai_HaveCreatureSpeak(oCreature, 3, ":43:4:5:");
|
|
SpeakString(AI_I_AM_WEAK, TALKVOLUME_SILENT_TALK);
|
|
}
|
|
}
|
|
eEffect = GetNextEffect(oTarget);
|
|
}
|
|
return FALSE;
|
|
}
|
|
// Do they have Lay on Hands?
|
|
if(GetHasFeat(FEAT_LAY_ON_HANDS, oCreature))
|
|
{
|
|
int nCanHeal = GetAbilityModifier(ABILITY_CHARISMA, oCreature) * ai_GetCharacterLevels(oCreature);
|
|
if(nCanHeal <= nHpLost)
|
|
{
|
|
ai_UseFeat(oCreature, FEAT_LAY_ON_HANDS, oTarget);
|
|
return TRUE;
|
|
}
|
|
}
|
|
object oMaster = ai_GetPlayerMaster(oCreature);
|
|
// Do we have no magic on?
|
|
if(!ai_GetMagicMode(oCreature, AI_MAGIC_NO_MAGIC))
|
|
{
|
|
int nClass, nPosition = 1;
|
|
string sMemorized;
|
|
while(nPosition <= AI_MAX_CLASSES_PER_CHARACTER)
|
|
{
|
|
nClass = GetClassByPosition(nPosition, oCreature);
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "753", "nClass: " + IntToString(nClass));
|
|
if(nClass == CLASS_TYPE_INVALID) break;
|
|
sMemorized = Get2DAString("classes", "MemorizesSpells", nClass);
|
|
// If Memorized column is "" then they are not a caster.
|
|
if(sMemorized != "")
|
|
{
|
|
if(sMemorized == "1")
|
|
{
|
|
if(ai_CastMemorizedHealing(oCreature, oTarget, oMaster, nClass))
|
|
{
|
|
SetLocalInt(oTarget, "AI_I_AM_BEING_HEALED", TRUE);
|
|
return TRUE;
|
|
}
|
|
}
|
|
else if(ai_CastKnownHealing(oCreature, oTarget, oMaster, nClass))
|
|
{
|
|
SetLocalInt(oTarget, "AI_I_AM_BEING_HEALED", TRUE);
|
|
return TRUE;
|
|
}
|
|
}
|
|
nPosition++;
|
|
}
|
|
}
|
|
// We have exhausted all attempts to use normal healing spells.
|
|
if(ai_UseHealingItem(oCreature, oTarget, oMaster))
|
|
{
|
|
SetLocalInt(oTarget, "AI_I_AM_BEING_HEALED", TRUE);
|
|
return TRUE;
|
|
}
|
|
// Final attempt to heal oTarget, check for Spontaneous cure spells.
|
|
if(ai_CastSpontaneousCure(oCreature, oTarget, oMaster))
|
|
{
|
|
SetLocalInt(oTarget, "AI_I_AM_BEING_HEALED", TRUE);
|
|
return TRUE;
|
|
}
|
|
// We can't heal ourselves! Can any of our allies? Lets ask.
|
|
if(oCreature == oTarget)
|
|
{
|
|
SetLocalInt(oCreature, "AI_WOUNDED_SHOUT_LIMIT", GetLocalInt(oCreature, "AI_WOUNDED_SHOUT_LIMIT") + 1);
|
|
SpeakString(AI_I_AM_WOUNDED, TALKVOLUME_SILENT_TALK);
|
|
}
|
|
return FALSE;
|
|
}
|
|
int ai_PerceiveEnemy(object oCreature, object oEnemy)
|
|
{
|
|
float fDistance = GetDistanceBetween(oCreature, oEnemy);
|
|
if(fDistance < 50.0)
|
|
{
|
|
// Game is in meters, so 1 foot = 3.333 meter
|
|
// penalty is -1 per 10' so divide it by 10 to use 0.3333f
|
|
int nDC = 10 + FloatToInt(fDistance * 0.3333f);
|
|
// Check to see if the creature is hiding and add the creatures checks.
|
|
int nEnemyMoveSilent, nEnemyHide;
|
|
if(GetStealthMode(oEnemy))
|
|
{
|
|
nEnemyMoveSilent =(d20() + GetSkillRank(SKILL_MOVE_SILENTLY, oEnemy));
|
|
nEnemyHide =(d20() + GetSkillRank(SKILL_HIDE, oEnemy));
|
|
}
|
|
if(GetIsSkillSuccessful (oCreature, SKILL_SPOT, nDC + nEnemyHide)) return TRUE;
|
|
if(GetIsSkillSuccessful (oCreature, SKILL_LISTEN, nDC + nEnemyMoveSilent)) return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
void ai_ScoutAhead(object oCreature)
|
|
{
|
|
object oPerceived;
|
|
object oEnemy = ai_GetNearestEnemy(oCreature, 1, -1, -1, -1, -1, TRUE);
|
|
// We see them so fight!
|
|
if(oEnemy != OBJECT_INVALID)
|
|
{
|
|
if(ai_PerceiveEnemy(oCreature, oEnemy))
|
|
{
|
|
if(!ai_GetAIMode(oCreature, AI_MODE_DO_NOT_SPEAK))
|
|
{
|
|
int nRoll = d10();
|
|
if(nRoll == 1) PlayVoiceChat(VOICE_CHAT_ENEMIES, oCreature);
|
|
else if(nRoll == 2) PlayVoiceChat(VOICE_CHAT_FOLLOWME, oCreature);
|
|
else if(nRoll == 3) PlayVoiceChat(VOICE_CHAT_LOOKHERE, oCreature);
|
|
}
|
|
ActionMoveToObject(oEnemy, TRUE, AI_RANGE_LONG);
|
|
return;
|
|
}
|
|
// There are enemies here so lets go to them.
|
|
else
|
|
{
|
|
if(!ai_GetAIMode(oCreature, AI_MODE_DO_NOT_SPEAK))
|
|
{
|
|
int nRoll = d3();
|
|
if(nRoll == 1) PlayVoiceChat(VOICE_CHAT_BADIDEA, oCreature);
|
|
else if(nRoll == 2) PlayVoiceChat(VOICE_CHAT_SEARCH, oCreature);
|
|
else if(nRoll == 3) PlayVoiceChat(VOICE_CHAT_FOLLOWME, oCreature);
|
|
}
|
|
ActionMoveToObject(oEnemy, TRUE, AI_RANGE_CLOSE);
|
|
}
|
|
}
|
|
// There are no more enemies, but we must look like we are patroling so
|
|
// go to encounter points.
|
|
else
|
|
{
|
|
if(!ai_GetAIMode(oCreature, AI_MODE_DO_NOT_SPEAK))
|
|
{
|
|
int nRoll = d10();
|
|
if(nRoll == 1) PlayVoiceChat(VOICE_CHAT_BADIDEA, oCreature);
|
|
else if(nRoll == 2) PlayVoiceChat(VOICE_CHAT_SEARCH, oCreature);
|
|
else if(nRoll == 3) PlayVoiceChat(VOICE_CHAT_FOLLOWME, oCreature);
|
|
}
|
|
// No enemy so lets get a spawn point!
|
|
object oSpawnPoint = GetNearestObjectByTag("ip_encounter", oCreature, d6());
|
|
ActionMoveToObject(oSpawnPoint, TRUE, AI_RANGE_CLOSE);
|
|
}
|
|
}
|
|
int ai_ShouldIPickItUp(object oCreature, object oItem)
|
|
{
|
|
int nMinGold;
|
|
if(GetResRef(oItem) == "nw_it_gold001") return TRUE;
|
|
int nBaseItem = GetBaseItemType(oItem);
|
|
if(GetPlotFlag(oItem))
|
|
{
|
|
if(ai_GetLootFilter(oCreature, AI_LOOT_PLOT)) nMinGold = GetLocalInt(oCreature, "AI_MIN_GOLD_2");
|
|
else return FALSE;
|
|
}
|
|
else if(nBaseItem == BASE_ITEM_ARMOR)
|
|
{
|
|
if (ai_GetLootFilter(oCreature, AI_LOOT_ARMOR)) nMinGold = GetLocalInt(oCreature, "AI_MIN_GOLD_3");
|
|
else return FALSE;
|
|
}
|
|
else if(nBaseItem == BASE_ITEM_BELT)
|
|
{
|
|
if(ai_GetLootFilter(oCreature, AI_LOOT_BELTS)) nMinGold = GetLocalInt(oCreature, "AI_MIN_GOLD_4");
|
|
else return FALSE;
|
|
}
|
|
else if(nBaseItem == BASE_ITEM_BOOTS)
|
|
{
|
|
if(ai_GetLootFilter(oCreature, AI_LOOT_BOOTS)) nMinGold = GetLocalInt(oCreature, "AI_MIN_GOLD_5");
|
|
else return FALSE;
|
|
}
|
|
else if(nBaseItem == BASE_ITEM_CLOAK)
|
|
{
|
|
if(ai_GetLootFilter(oCreature, AI_LOOT_CLOAKS)) nMinGold = GetLocalInt(oCreature, "AI_MIN_GOLD_6");
|
|
else return FALSE;
|
|
}
|
|
else if(nBaseItem == BASE_ITEM_GEM)
|
|
{
|
|
if(ai_GetLootFilter(oCreature, AI_LOOT_GEMS)) nMinGold = GetLocalInt(oCreature, "AI_MIN_GOLD_7");
|
|
else return FALSE;
|
|
}
|
|
else if((nBaseItem == BASE_ITEM_BRACER|| nBaseItem == BASE_ITEM_GLOVES))
|
|
{
|
|
if(ai_GetLootFilter(oCreature, AI_LOOT_GLOVES)) nMinGold = GetLocalInt(oCreature, "AI_MIN_GOLD_8");
|
|
else return FALSE;
|
|
}
|
|
else if(nBaseItem == BASE_ITEM_HELMET)
|
|
{
|
|
if(ai_GetLootFilter(oCreature, AI_LOOT_HEADGEAR)) nMinGold = GetLocalInt(oCreature, "AI_MIN_GOLD_9");
|
|
else return FALSE;
|
|
}
|
|
else if(nBaseItem == BASE_ITEM_AMULET || nBaseItem == BASE_ITEM_RING)
|
|
{
|
|
if(ai_GetLootFilter(oCreature, AI_LOOT_JEWELRY)) nMinGold = GetLocalInt(oCreature, "AI_MIN_GOLD_10");
|
|
else return FALSE;
|
|
}
|
|
else if(nBaseItem == BASE_ITEM_BLANK_POTION || nBaseItem == BASE_ITEM_POTIONS ||
|
|
nBaseItem == BASE_ITEM_ENCHANTED_POTION)
|
|
{
|
|
if(ai_GetLootFilter(oCreature, AI_LOOT_POTIONS)) nMinGold = GetLocalInt(oCreature, "AI_MIN_GOLD_12");
|
|
else return FALSE;
|
|
}
|
|
else if(nBaseItem == BASE_ITEM_BLANK_SCROLL || nBaseItem == BASE_ITEM_SCROLL ||
|
|
nBaseItem == BASE_ITEM_ENCHANTED_SCROLL || nBaseItem == BASE_ITEM_SPELLSCROLL)
|
|
{
|
|
if(ai_GetLootFilter(oCreature, AI_LOOT_SCROLLS)) nMinGold = GetLocalInt(oCreature, "AI_MIN_GOLD_13");
|
|
else return FALSE;
|
|
}
|
|
else if(nBaseItem == BASE_ITEM_BLANK_WAND || nBaseItem == BASE_ITEM_ENCHANTED_WAND ||
|
|
nBaseItem == BASE_ITEM_MAGICWAND || nBaseItem == BASE_ITEM_MAGICROD ||
|
|
nBaseItem == BASE_ITEM_MAGICSTAFF)
|
|
{
|
|
if(ai_GetLootFilter(oCreature, AI_LOOT_WANDS_RODS_STAVES)) nMinGold = GetLocalInt(oCreature, "AI_MIN_GOLD_15");
|
|
else return FALSE;
|
|
}
|
|
else if(nBaseItem == BASE_ITEM_ARROW)
|
|
{
|
|
if(ai_GetLootFilter(oCreature, AI_LOOT_ARROWS)) nMinGold = GetLocalInt(oCreature, "AI_MIN_GOLD_17");
|
|
else return FALSE;
|
|
}
|
|
else if(nBaseItem == BASE_ITEM_BOLT)
|
|
{
|
|
if(ai_GetLootFilter(oCreature, AI_LOOT_BOLTS)) nMinGold = GetLocalInt(oCreature, "AI_MIN_GOLD_18");
|
|
else return FALSE;
|
|
}
|
|
else if(nBaseItem == BASE_ITEM_BULLET)
|
|
{
|
|
if(ai_GetLootFilter(oCreature, AI_LOOT_BULLETS)) nMinGold = GetLocalInt(oCreature, "AI_MIN_GOLD_19");
|
|
else return FALSE;
|
|
}
|
|
else if(ai_GetIsWeapon(oItem))
|
|
{
|
|
if(ai_GetLootFilter(oCreature, AI_LOOT_WEAPONS)) nMinGold = GetLocalInt(oCreature, "AI_MIN_GOLD_16");
|
|
else return FALSE;
|
|
}
|
|
else if(ai_GetIsShield(oItem))
|
|
{
|
|
if(ai_GetLootFilter(oCreature, AI_LOOT_SHIELDS)) nMinGold = GetLocalInt(oCreature, "AI_MIN_GOLD_14");
|
|
else return FALSE;
|
|
}
|
|
else if(ai_GetLootFilter(oCreature, AI_LOOT_MISC)) nMinGold = GetLocalInt(oCreature, "AI_MIN_GOLD_11");
|
|
else return FALSE;
|
|
// Check if it is too heavy.
|
|
int nItemWeight = GetWeight(oItem);
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "1146", GetName(oItem) + " nItemWeight: " +
|
|
IntToString(nItemWeight) + " Max Weight: " + IntToString(GetLocalInt(oCreature, AI_MAX_LOOT_WEIGHT) * 10));
|
|
if(nItemWeight > GetLocalInt(oCreature, AI_MAX_LOOT_WEIGHT) * 10) return FALSE;
|
|
// Check if it is not valuable enough.
|
|
int bID = GetIdentified(oItem);
|
|
if(!bID) SetIdentified(oItem, TRUE);
|
|
int nItemValue = GetGoldPieceValue(oItem);
|
|
if(!bID) SetIdentified(oItem, FALSE);
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "998", GetName(oItem) + " nMinGold: " + IntToString(nMinGold) + " nItemValue: " +
|
|
IntToString(nItemValue) + " bID: " + IntToString(bID));
|
|
if(nMinGold > nItemValue) return FALSE;
|
|
return TRUE;
|
|
}
|
|
void ai_TakeItemMessage(object oCreature, object oObject, object oItem, object oMaster)
|
|
{
|
|
int bId = GetIdentified(oItem);
|
|
int nCreatureSkill = GetSkillRank(SKILL_LORE, oCreature);
|
|
int nMasterSkill = GetSkillRank(SKILL_LORE, oMaster);
|
|
if(nCreatureSkill + nMasterSkill > 0)
|
|
{
|
|
if(nCreatureSkill > nMasterSkill) ai_IdentifyItemVsKnowledge(oCreature, oItem);
|
|
else ai_IdentifyItemVsKnowledge(oMaster, oItem);
|
|
}
|
|
if(!ai_GetIsCharacter(oCreature))
|
|
{
|
|
if(GetIdentified(oItem))
|
|
{
|
|
if(bId) ai_SendMessages(GetName(oCreature) + " has found a " + GetName(oItem) + " from the " + GetName(oObject) + ".", AI_COLOR_GRAY, oMaster);
|
|
else ai_SendMessages(GetName(oCreature) + " has found and identified " + GetName(oItem) + " from the " + GetName(oObject) + ".", AI_COLOR_GREEN, oMaster);
|
|
}
|
|
else if(!ai_GetIsCharacter(oCreature))
|
|
{
|
|
string sBaseName = GetStringByStrRef(StringToInt(Get2DAString("baseitems", "name", GetBaseItemType(oItem))));
|
|
ai_SendMessages(GetName(oCreature) + " has found a " + sBaseName + " from the " + GetName(oObject) + ".", AI_COLOR_GRAY, oMaster);
|
|
}
|
|
}
|
|
else if(GetIdentified(oItem) && !bId)
|
|
{
|
|
ai_SendMessages(GetName(oCreature) + " has identified " + GetName(oItem) + " from the " + GetName(oObject) + ".", AI_COLOR_GREEN, oMaster);
|
|
}
|
|
if(GetPlotFlag(oItem))
|
|
{
|
|
if(!ai_GetAIMode(oCreature, AI_MODE_DO_NOT_SPEAK)) PlayVoiceChat(VOICE_CHAT_LOOKHERE, oCreature);
|
|
}
|
|
}
|
|
void ai_SearchObject(object oCreature, object oObject, object oMaster, int bOnce = FALSE)
|
|
{
|
|
ai_Debug("0i_actions", "966", GetName(OBJECT_SELF) + " is opening " + GetName(oObject));
|
|
string sTag = GetTag(oCreature);
|
|
AssignCommand(oObject, ActionPlayAnimation(ANIMATION_PLACEABLE_OPEN));
|
|
if(GetIsTrapped(oObject)) DoPlaceableObjectAction(oObject, PLACEABLE_ACTION_USE);
|
|
SetLocalInt(oObject, "AI_LOOTED_" + sTag, TRUE);
|
|
// Big Hack to allow NPC's to loot!
|
|
string sLootScript = GetEventScript(oObject, EVENT_SCRIPT_PLACEABLE_ON_OPEN);
|
|
//ai_Debug("0i_actions", "972", "Loot script: " + sLootScript);
|
|
if(sLootScript != "")
|
|
{
|
|
// Used in Original Campaign, and SOU for loot scripts to get treasure to work.
|
|
SetLocalObject(oObject, "AI_GET_LAST_OPENED_BY", oMaster);
|
|
ExecuteScript(sLootScript, oObject);
|
|
}
|
|
AssignCommand(oObject, ActionWait(2.0f));
|
|
AssignCommand(oObject, ActionPlayAnimation(ANIMATION_PLACEABLE_CLOSE));
|
|
int nItemType, nGold;
|
|
object oItem = GetFirstItemInInventory(oObject);
|
|
//ai_Debug("0i_actions", "983", "Found: " + GetName(oItem) + " ResRef: " + GetResRef(oItem) +
|
|
// " in " + GetName(oObject));
|
|
while(oItem != OBJECT_INVALID)
|
|
{
|
|
ai_Debug("0i_actions", "987", "Found: " + GetName(oItem) + " ResRef: " + GetResRef(oItem));
|
|
if(ai_ShouldIPickItUp(oCreature, oItem))
|
|
{
|
|
ai_Debug("0i_actions", "1002", "Taking: " + GetName(oItem));
|
|
if(GetResRef(oItem) == "nw_it_gold001")
|
|
{
|
|
if(!ai_GetIsCharacter(oCreature))
|
|
{
|
|
int nGold = GetItemStackSize(oItem);
|
|
DestroyObject(oItem);
|
|
ActionDoCommand(GiveGoldToCreature(oMaster, nGold));
|
|
ActionDoCommand(ai_SendMessages(GetName(oCreature) + " has retrieved " + IntToString(nGold) +
|
|
" gold from the " + GetName(oObject) + ".", AI_COLOR_GRAY, oMaster));
|
|
}
|
|
else AssignCommand(oCreature, ActionTakeItem(oItem, oObject));
|
|
}
|
|
// Check if they are a henchman, companions and familiars give all items to the pc.
|
|
else if(!ai_GetLootFilter(oCreature, AI_LOOT_GIVE_TO_PC) &&
|
|
GetAssociateType(oCreature) == ASSOCIATE_TYPE_HENCHMAN &&
|
|
!GetPlotFlag(oItem))
|
|
{
|
|
if(GetBaseItemFitsInInventory(GetBaseItemType(oItem), oCreature))
|
|
{
|
|
ActionDoCommand(ai_TakeItemMessage(oCreature, oObject, oItem, oMaster));
|
|
ActionTakeItem(oItem, oObject);
|
|
}
|
|
else
|
|
{
|
|
if(GetIdentified(oItem)) SpeakString("My inventory is full! I cannot pick up the " + GetName(oItem) + ".");
|
|
else
|
|
{
|
|
string sBaseName = GetStringByStrRef(StringToInt(Get2DAString("baseitems", "name", GetBaseItemType(oItem))));
|
|
SpeakString("My inventory is full! I cannot pick up the " + sBaseName + ".");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(GetBaseItemFitsInInventory(GetBaseItemType(oItem), oMaster))
|
|
{
|
|
//ai_Debug("0i_actions", "1010", "Giving to master: " + GetName(oItem));
|
|
ActionDoCommand(ai_TakeItemMessage(oCreature, oObject, oItem, oMaster));
|
|
AssignCommand(oObject, ActionGiveItem(oItem, oMaster));
|
|
}
|
|
else
|
|
{
|
|
if(GetIdentified(oItem)) SpeakString("Your inventory is full! You cannot take the " + GetName(oItem) + ".");
|
|
else
|
|
{
|
|
string sBaseName = GetStringByStrRef(StringToInt(Get2DAString("baseitems", "name", GetBaseItemType(oItem))));
|
|
SpeakString("Your inventory is full! You cannot take the " + sBaseName + ".");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
oItem = GetNextItemInInventory(oObject);
|
|
//ai_Debug("0i_actions", "1016", GetName(oItem) + " is the next item.");
|
|
}
|
|
//ai_Debug("0i_actions", "1018", "Setting object as looted. Check for a new Placeable.");
|
|
if(!bOnce) ActionDoCommand(ai_ActionCheckNearbyObjects(oCreature));
|
|
}
|
|
int ai_IsContainerLootable(object oCreature, object oObject)
|
|
{
|
|
string sTag = GetTag(oCreature);
|
|
//ai_Debug("0i_actions", "1303", GetName(oObject) + " (sTag " + GetTag(oObject) + ") " +
|
|
// "has inventory: " + IntToString(GetHasInventory(oObject)) + " Has been looted: " +
|
|
// IntToString(GetLocalInt(oObject, "AI_LOOTED_" + sTag)) + " Is Useable? " +
|
|
// IntToString(GetUseableFlag(oObject)));
|
|
if(!GetHasInventory(oObject) || !GetUseableFlag(oObject)) return FALSE;
|
|
// This associate has already looted this object, skip.
|
|
if(GetLocalInt(oObject, "AI_LOOTED_" + sTag) || ai_GetIsCharacter(oObject)) return FALSE;
|
|
return TRUE;
|
|
}
|
|
int ai_AttempToCastKnockSpell(object oCreature, object oLocked)
|
|
{
|
|
if(GetHasSpell(SPELL_KNOCK, oCreature) &&
|
|
(GetIsDoorActionPossible(oLocked, DOOR_ACTION_KNOCK) ||
|
|
GetIsPlaceableObjectActionPossible(oLocked, PLACEABLE_ACTION_KNOCK)) &&
|
|
ai_GetIsInLineOfSight(oCreature, oLocked))
|
|
{
|
|
SetLocalInt(oLocked, AI_OBJECT_IN_USE, TRUE);
|
|
DelayCommand(6.0, DeleteLocalInt(oLocked, AI_OBJECT_IN_USE));
|
|
AssignCommand(oCreature, ai_ClearCreatureActions());
|
|
AssignCommand(oCreature, ActionWait(1.0));
|
|
AssignCommand(oCreature, ActionCastSpellAtObject(SPELL_KNOCK, oLocked));
|
|
AssignCommand(oCreature, ActionWait(1.0));
|
|
AssignCommand(oCreature, ActionDoCommand(DeleteLocalInt(oLocked, AI_OBJECT_IN_USE)));
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
int ai_ReactToTrap(object oCreature, object oTrap, int bForce = FALSE)
|
|
{
|
|
int nTrapDC = GetTrapDisarmDC(oTrap);
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "1520", "Reacting to trap on " + GetName(oTrap) +
|
|
" bForce: " + IntToString(bForce) + " nTrapDC: " + IntToString(nTrapDC) +
|
|
" [AI_OBJECT_IN_USE: " + IntToString(GetLocalInt(oTrap, AI_OBJECT_IN_USE)) + "].");
|
|
if(nTrapDC == 0) return FALSE;
|
|
string sTag = GetTag(oCreature);
|
|
if(bForce || ai_GetAIMode(oCreature, AI_MODE_DISARM_TRAPS))
|
|
{
|
|
if(GetTrapDisarmable(oTrap))
|
|
{
|
|
if(GetLocalInt(oTrap, AI_OBJECT_IN_USE)) return FALSE;
|
|
// We must have ranks in disable traps to actually disable the trap!
|
|
if(GetSkillRank(SKILL_DISABLE_TRAP, oCreature, TRUE))
|
|
{
|
|
int nSkill = GetSkillRank(SKILL_DISABLE_TRAP, oCreature);
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "1534", "nSkill: " + IntToString(nSkill) +
|
|
" + 20 = " + IntToString(nSkill + 20) + " nTrapDC: " + IntToString(nTrapDC));
|
|
if(nSkill + 20 >= nTrapDC)
|
|
{
|
|
SetLocalInt(oTrap, AI_OBJECT_IN_USE, TRUE);
|
|
DelayCommand(18.0, DeleteLocalInt(oTrap, AI_OBJECT_IN_USE));
|
|
AssignCommand(oCreature, ai_ClearCreatureActions());
|
|
AssignCommand(oCreature, ActionUseSkill(SKILL_DISABLE_TRAP, oTrap, 0));
|
|
// Let them know we did it!
|
|
AssignCommand(oCreature, ActionDoCommand(ai_HaveCreatureSpeak(oCreature, 6, ":44:42:31:35:")));
|
|
AssignCommand(oCreature, ActionDoCommand(DeleteLocalInt(oTrap, AI_OBJECT_IN_USE)));
|
|
// Continue checking for traps, locks, and loot.
|
|
AssignCommand(oCreature, ActionDoCommand(ai_ActionCheckNearbyObjects(oCreature)));
|
|
return TRUE;
|
|
}
|
|
if(GetHasSpell(SPELL_FIND_TRAPS, oCreature))
|
|
{
|
|
AssignCommand(oCreature, ai_ClearCreatureActions());
|
|
AssignCommand(oCreature, ActionCastSpellAtObject(SPELL_FIND_TRAPS, oTrap));
|
|
// Continue checking for traps, locks, and loot.
|
|
AssignCommand(oCreature, ActionDoCommand(ai_ActionCheckNearbyObjects(oCreature)));
|
|
return TRUE;
|
|
}
|
|
}
|
|
if(GetLocalInt(oTrap, "AI_CANNOT_TRAP_" + sTag) && !bForce) return FALSE;
|
|
// Let them know we can't get this done!.
|
|
//StrRef(40551) "I cannot disarm this trap!"
|
|
ActionDoCommand(ai_HaveCreatureSpeak(oCreature, 0, GetStringByStrRef(40551)));
|
|
ActionDoCommand(ai_HaveCreatureSpeak(oCreature, 8, ":47:30:43:5:36:"));
|
|
SetLocalInt(oTrap, "AI_CANNOT_TRAP_" + sTag, TRUE);
|
|
return FALSE;
|
|
}
|
|
if(GetLocalInt(oTrap, "AI_SAW_TRAP_" + sTag) && !bForce) return FALSE;
|
|
// Let them know we can't get this done!.
|
|
ai_HaveCreatureSpeak(oCreature, 0, "I'm not skilled enough to disable the trap!", TRUE);
|
|
ActionDoCommand(ai_HaveCreatureSpeak(oCreature, 8, ":47:30:43:5:36:"));
|
|
SetLocalInt(oTrap, "AI_SAW_TRAP_" + sTag, TRUE);
|
|
return FALSE;
|
|
}
|
|
if(GetObjectType(oTrap) == OBJECT_TYPE_TRIGGER)
|
|
{
|
|
object oMaster = ai_GetPlayerMaster(oCreature);
|
|
if(oMaster != OBJECT_INVALID && !ai_GetIsCharacter(oCreature) &&
|
|
!ai_GetAIMode(oCreature, AI_MODE_IGNORE_TRAPS))
|
|
{
|
|
ai_SetAIMode(oCreature, AI_MODE_SCOUT_AHEAD, FALSE);
|
|
ai_SetAIMode(oCreature, AI_MODE_STAND_GROUND, TRUE);
|
|
ai_SetAIMode(oCreature, AI_MODE_FOLLOW, FALSE);
|
|
ai_SetAIMode(oCreature, AI_MODE_COMMANDED, FALSE);
|
|
int nToken = NuiFindWindow(oMaster, ai_GetAssociateType(oMaster, oCreature) + AI_WIDGET_NUI);
|
|
ai_HighlightWidgetMode(oMaster, oCreature, nToken);
|
|
aiSaveAssociateModesToDb(oMaster, oCreature);
|
|
if(ai_IsInCombatRound(oCreature)) ai_ClearCombatState(oCreature);
|
|
ai_ClearCreatureActions(TRUE);
|
|
ai_SendMessages(GetName(oCreature) + " has went into hold mode after seeing a trap!", AI_COLOR_YELLOW, oMaster);
|
|
return TRUE;
|
|
}
|
|
}
|
|
if(ai_GetAIMode(oCreature, AI_MODE_PICKUP_ITEMS))
|
|
{
|
|
if(GetLocalInt(oTrap, "AI_SAW_TRAP_" + sTag) && !bForce) return FALSE;
|
|
ActionDoCommand(ai_HaveCreatureSpeak(oCreature, 0, "This " + GetName(oTrap) + " is trapped!", TRUE));
|
|
ActionDoCommand(ai_HaveCreatureSpeak(oCreature, 8, ":47:30:43:5:36:"));
|
|
SetLocalInt(oTrap, "AI_SAW_TRAP_" + sTag, TRUE);
|
|
}
|
|
return FALSE;
|
|
}
|
|
int ai_AttemptToByPassLock(object oCreature, object oLocked, int bForce = FALSE)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "1446", "Attempting to bypass lock on " +
|
|
GetName(oLocked) + " [AI_OBJECT_IN_USE: " +
|
|
IntToString(GetLocalInt(oLocked, AI_OBJECT_IN_USE)) + "]" +
|
|
" bForce: " + IntToString(bForce));
|
|
if(GetLocalInt(oLocked, AI_OBJECT_IN_USE)) return FALSE;
|
|
string sTag = GetTag(oCreature);
|
|
// Attempt to cast knock because its always safe to cast it, even on a trapped object.
|
|
if(ai_AttempToCastKnockSpell(oLocked, oCreature)) return TRUE;
|
|
// First, let's see if we notice that it's trapped
|
|
if(GetTrapDetectedBy(oCreature, oLocked))
|
|
{
|
|
// Ick! Try and disarm the trap first
|
|
if(ai_ReactToTrap(oCreature, oLocked, bForce)) return TRUE;
|
|
}
|
|
if(GetLockKeyRequired(oLocked))
|
|
{
|
|
// We might be able to open this.
|
|
string sKeyTag = GetLockKeyTag(oLocked);
|
|
// Do we have the key?
|
|
object oKey = ai_GetCreatureHasItem(oCreature, sKeyTag, FALSE);
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "1469", "Requires a Key! sKeyTag: " +
|
|
sKeyTag + " Has key oKey: " + GetName(oKey));
|
|
if(oKey != OBJECT_INVALID)
|
|
{
|
|
int nObjectType = GetObjectType(oLocked);
|
|
if(nObjectType == OBJECT_TYPE_DOOR) return ai_AttemptToOpenDoor(oCreature, oLocked, bForce);
|
|
else if (nObjectType == OBJECT_TYPE_PLACEABLE)
|
|
{
|
|
SetLocalInt(oLocked, AI_OBJECT_IN_USE, TRUE);
|
|
DelayCommand(18.0, DeleteLocalInt(oLocked, AI_OBJECT_IN_USE));
|
|
AssignCommand(oCreature, ActionUnlockObject(oLocked));
|
|
// Let them know we did it!
|
|
ActionDoCommand(ai_HaveCreatureSpeak(oCreature, 6, ":44:42:31:35:"));
|
|
AssignCommand(oCreature, ActionDoCommand(DeleteLocalInt(oLocked, AI_OBJECT_IN_USE)));
|
|
// Continue checking for traps, locks, and loot.
|
|
AssignCommand(oCreature, ActionDoCommand(ai_ActionCheckNearbyObjects(oCreature)));
|
|
return TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(GetLocalInt(oLocked, "AI_LOCKED_" + sTag) && !bForce) return FALSE;
|
|
// Let them know we can't get this done!.
|
|
AssignCommand(oCreature, ActionDoCommand(ai_HaveCreatureSpeak(oCreature, 0, "This " + GetName(oLocked) + " is special! It requires a special key to open.")));
|
|
ActionDoCommand(ai_HaveCreatureSpeak(oCreature, 8, ":47:30:43:5:36:"));
|
|
SetLocalInt(oLocked, "AI_LOCKED_" + sTag, TRUE);
|
|
return FALSE;
|
|
}
|
|
}
|
|
if(bForce || ai_GetAIMode(oCreature, AI_MODE_PICK_LOCKS))
|
|
{
|
|
// We must have ranks in open locks to actually open the lock!
|
|
if(GetSkillRank(SKILL_OPEN_LOCK, oCreature, TRUE))
|
|
{
|
|
int nSkill = GetSkillRank(SKILL_OPEN_LOCK, oCreature);
|
|
int nLockDC = GetLockUnlockDC(oLocked);
|
|
object oPicks = ai_GetBestPicks(oCreature, nLockDC);
|
|
int nPickBonus = GetLocalInt(oPicks, "AI_BONUS");
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "1497", "I have picks: " + GetName(oPicks) +
|
|
" nSkill :" + IntToString(nSkill) + " nPickBonus: " +
|
|
IntToString(nPickBonus) + " + 20 = " +
|
|
IntToString(nSkill + nPickBonus + 20) +
|
|
" nLockDC: " + IntToString(nLockDC));
|
|
if(nSkill + 20 + nPickBonus >= nLockDC)
|
|
{
|
|
SetLocalInt(oLocked, AI_OBJECT_IN_USE, TRUE);
|
|
DelayCommand(18.0, DeleteLocalInt(oLocked, AI_OBJECT_IN_USE));
|
|
AssignCommand(oCreature, ai_ClearCreatureActions());
|
|
AssignCommand(oCreature, ActionWait(1.0));
|
|
AssignCommand(oCreature, ActionUseSkill(SKILL_OPEN_LOCK, oLocked, 0, oPicks));
|
|
AssignCommand(oCreature, ActionWait(1.0));
|
|
// Let them know we did it!
|
|
ActionDoCommand(ai_HaveCreatureSpeak(oCreature, 8, ":44:42:26:31:35:"));
|
|
AssignCommand(oCreature, ActionDoCommand(DeleteLocalInt(oLocked, AI_OBJECT_IN_USE)));
|
|
// Continue checking for traps, locks, and loot.
|
|
AssignCommand(oCreature, ActionDoCommand(ai_ActionCheckNearbyObjects(oCreature)));
|
|
return TRUE;
|
|
}
|
|
else if(!GetLocalInt(oLocked, "AI_LOCKED_" + sTag))
|
|
{
|
|
// Let them know we can't get this done!
|
|
AssignCommand(oCreature, ActionDoCommand(ai_HaveCreatureSpeak(oCreature, 0, "I'm not skilled enough to pick the lock on this " + GetName(oLocked) + "!", TRUE)));
|
|
ActionDoCommand(ai_HaveCreatureSpeak(oCreature, 8, ":47:30:43:5:36:"));
|
|
SetLocalInt(oLocked, "AI_LOCKED_" + sTag, TRUE);
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
if(bForce || ai_GetAIMode(oCreature, AI_MODE_BASH_LOCKS))
|
|
{
|
|
//AssignCommand(oCreature, ai_ClearCreatureActions());
|
|
// Check to make sure we are not using a ranged weapon.
|
|
if(!ai_GetIsRangeWeapon(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oCreature)))
|
|
{
|
|
if(ai_CheckClassType(oCreature, CLASS_TYPE_MONK)) ai_EquipBestMonkMeleeWeapon(oCreature);
|
|
else ai_EquipBestMeleeWeapon(oCreature);
|
|
AssignCommand(oCreature, ActionWait(1.0));
|
|
if(ai_TryImprovedPowerAttackFeat(oCreature, oLocked)) return TRUE;
|
|
if(ai_TryPowerAttackFeat(oCreature, oLocked)) return TRUE;
|
|
if(ai_TryFlurryOfBlowsFeat(oCreature, oLocked)) return TRUE;
|
|
AssignCommand(oCreature, ActionAttack(oLocked));
|
|
return TRUE;
|
|
}
|
|
if(GetLocalInt(oLocked, "AI_LOCKED_" + sTag) && !bForce) return FALSE;
|
|
// Let them know we can't get this done!.
|
|
AssignCommand(oCreature, ActionDoCommand(ai_HaveCreatureSpeak(oCreature, 0, "I cannot bash this " + GetName(oLocked) + " open!", TRUE)));
|
|
SetLocalInt(oLocked, "AI_LOCKED_" + sTag, TRUE);
|
|
return FALSE;
|
|
}
|
|
if(bForce || ai_GetAIMode(oCreature, AI_MODE_PICKUP_ITEMS))
|
|
{
|
|
if(GetLocalInt(oLocked, "AI_LOCKED_" + sTag) && !bForce) return FALSE;
|
|
AssignCommand(oCreature, ActionDoCommand(ai_HaveCreatureSpeak(oCreature, 0, "This " + GetName(oLocked) + " is locked!", TRUE)));
|
|
ActionDoCommand(ai_HaveCreatureSpeak(oCreature, 8, ":47:30:43:5:36:"));
|
|
SetLocalInt(oLocked, "AI_LOCKED_" + sTag, TRUE);
|
|
}
|
|
return FALSE;
|
|
}
|
|
int ai_AttemptToOpenDoor(object oCreature, object oDoor, int bForce = FALSE)
|
|
{
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "1542", "Attempting to open " +
|
|
GetName(oDoor) + " [AI_OBJECT_IN_USE: " +
|
|
IntToString(GetLocalInt(oDoor, AI_OBJECT_IN_USE)) + "] " +
|
|
" IsOpen: " + IntToString(GetIsOpen(oDoor)) +
|
|
" Plot: " + IntToString(GetPlotFlag(oDoor)) + ".");
|
|
if(!ai_GetAIMode(oCreature, AI_MODE_OPEN_DOORS) && !bForce) return FALSE;
|
|
if(GetLocalInt(oDoor, AI_OBJECT_IN_USE)) return FALSE;
|
|
if(GetIsOpen(oDoor)) return FALSE;
|
|
string sTag = GetTag(oCreature);
|
|
if(GetIsTrapped(oDoor))
|
|
{
|
|
if(GetTrapDetectedBy(oDoor, GetMaster(oCreature))) SetTrapDetectedBy(oDoor, oCreature);
|
|
if(GetTrapDetectedBy(oDoor, oCreature))
|
|
{
|
|
if(GetLocalInt(oDoor, "AI_SAW_TRAP_" + sTag)) return FALSE;
|
|
ActionDoCommand(ai_HaveCreatureSpeak(oCreature, 0, "This " + GetName(oDoor) + " is trapped!", TRUE));
|
|
ActionDoCommand(ai_HaveCreatureSpeak(oCreature, 8, ":47:30:43:5:36:"));
|
|
SetLocalInt(oDoor, "AI_SAW_TRAP_" + sTag, TRUE);
|
|
return FALSE;
|
|
}
|
|
}
|
|
if(GetLocked(oDoor))
|
|
{
|
|
if(GetLocalInt(oDoor, "AI_LOCKED_" + sTag)) return FALSE;
|
|
AssignCommand(oCreature, ActionDoCommand(ai_HaveCreatureSpeak(oCreature, 0, "This " + GetName(oDoor) + " is locked!", TRUE)));
|
|
ActionDoCommand(ai_HaveCreatureSpeak(oCreature, 8, ":47:30:43:5:36:"));
|
|
SetLocalInt(oDoor, "AI_LOCKED_" + sTag, TRUE);
|
|
return FALSE;
|
|
}
|
|
SetLocalInt(oDoor, AI_OBJECT_IN_USE, TRUE);
|
|
DelayCommand(18.0, DeleteLocalInt(oDoor, AI_OBJECT_IN_USE));
|
|
AssignCommand(oCreature, ActionOpenDoor(oDoor, TRUE));
|
|
AssignCommand(oCreature, ActionDoCommand(DeleteLocalInt(oDoor, AI_OBJECT_IN_USE)));
|
|
return TRUE;
|
|
}
|
|
void ai_ActionCheckNearbyObjects(object oCreature)
|
|
{
|
|
if(ai_GetIsBusy(oCreature)) return;
|
|
ai_CheckNearbyObjects(oCreature);
|
|
}
|
|
int ai_CheckNearbyObjects(object oCreature)
|
|
{
|
|
object oMaster = ai_GetPlayerMaster(oCreature);
|
|
location lMaster = GetLocation(oMaster);
|
|
int nObjectType, bIgnore;
|
|
int nFilter = OBJECT_TYPE_DOOR | OBJECT_TYPE_PLACEABLE | OBJECT_TYPE_TRIGGER | OBJECT_TYPE_ITEM;
|
|
float fLockRange, fDoorRange, fLootRange, fObjectDistance;
|
|
float fTrapRange = GetLocalFloat(oCreature, AI_TRAP_CHECK_RANGE);
|
|
if(ai_GetAIMode(oCreature, AI_MODE_PICK_LOCKS) ||
|
|
ai_GetAIMode(oCreature, AI_MODE_BASH_LOCKS)) fLockRange = GetLocalFloat(oCreature, AI_LOCK_CHECK_RANGE);
|
|
if(ai_GetAIMode(oCreature, AI_MODE_PICKUP_ITEMS)) fLootRange = GetLocalFloat(oCreature, AI_LOOT_CHECK_RANGE);
|
|
if(ai_GetAIMode(oCreature, AI_MODE_OPEN_DOORS)) fDoorRange = GetLocalFloat(oCreature, AI_OPEN_DOORS_RANGE);
|
|
if(AI_DEBUG && fTrapRange != 0.0) ai_Debug("0i_actions", "1579", " Checking " + FloatToString(fTrapRange, 0, 0) + " foot area for traps.");
|
|
if(AI_DEBUG && fLootRange != 0.0) ai_Debug("0i_actions", "1580", " Checking " + FloatToString(fLootRange, 0, 0) + " foot area for traps.");
|
|
if(AI_DEBUG && fLockRange != 0.0) ai_Debug("0i_actions", "1581", " Checking " + FloatToString(fLockRange, 0, 0) + " foot area for locks.");
|
|
if(AI_DEBUG && fDoorRange != 0.0) ai_Debug("0i_actions", "1582", " Checking " + FloatToString(fDoorRange, 0, 0) + " foot area for doors.");
|
|
float fLongestRange = fTrapRange;
|
|
vector vCreature = GetPositionFromLocation(GetLocation(oCreature));
|
|
if(fLongestRange < fLootRange) fLongestRange = fLootRange;
|
|
if(fLongestRange < fLockRange) fLongestRange = fLockRange;
|
|
if(fLongestRange < fDoorRange) fLongestRange = fDoorRange;
|
|
object oObject = GetFirstObjectInShape(SHAPE_SPHERE, fLongestRange, lMaster, TRUE, nFilter);
|
|
while(oObject != OBJECT_INVALID)
|
|
{
|
|
fObjectDistance = GetDistanceBetween(oMaster, oObject);
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "1651", "Checking Nearby Objects: " +
|
|
GetName(oObject) + " fDistance: " + FloatToString(fObjectDistance, 0, 2));
|
|
if(GetTrapDetectedBy(oObject, oCreature))
|
|
{
|
|
if(fTrapRange >= fObjectDistance)
|
|
{
|
|
if(ai_ReactToTrap(oCreature, oObject)) return TRUE;
|
|
}
|
|
}
|
|
if(GetLocked(oObject))
|
|
{
|
|
if(fLockRange >= fObjectDistance)
|
|
{
|
|
if(ai_AttemptToByPassLock(oCreature, oObject)) return TRUE;
|
|
}
|
|
}
|
|
nObjectType = GetObjectType(oObject);
|
|
if(fDoorRange >= fObjectDistance && nObjectType == OBJECT_TYPE_DOOR)
|
|
{
|
|
if(ai_AttemptToOpenDoor(oCreature, oObject)) return TRUE;
|
|
}
|
|
if(fLootRange >= fObjectDistance)
|
|
{
|
|
if(nObjectType == OBJECT_TYPE_PLACEABLE)
|
|
{
|
|
if(!GetLocalInt(oObject, AI_OBJECT_IN_USE) &&
|
|
ai_IsContainerLootable(oCreature, oObject))
|
|
{
|
|
if(GetLocked(oObject))
|
|
{
|
|
string sTag = GetTag(oCreature);
|
|
if(GetLocalInt(oObject, "AI_LOCKED_" + sTag)) return FALSE;
|
|
AssignCommand(oCreature, ActionDoCommand(ai_HaveCreatureSpeak(oCreature, 0, "This " + GetName(oObject) + " is locked!", TRUE)));
|
|
ActionDoCommand(ai_HaveCreatureSpeak(oCreature, 8, ":47:30:43:5:36:"));
|
|
SetLocalInt(oObject, "AI_LOCKED_" + sTag, TRUE);
|
|
return FALSE;
|
|
}
|
|
ai_ClearCreatureActions();
|
|
ActionMoveToObject(oObject, TRUE);
|
|
AssignCommand(oCreature, ActionDoCommand(ai_SearchObject(oCreature, oObject, oMaster)));
|
|
return TRUE;
|
|
}
|
|
}
|
|
else if(nObjectType == OBJECT_TYPE_ITEM)
|
|
{
|
|
if(ai_ShouldIPickItUp(oCreature, oObject))
|
|
{
|
|
ActionPickUpItem(oObject);
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
oObject = GetNextObjectInShape(SHAPE_SPHERE, fLongestRange, lMaster, TRUE, nFilter);
|
|
}
|
|
return FALSE;
|
|
}
|
|
void ai_DetermineSpecialBehavior(object oCreature)
|
|
{
|
|
object oTarget = ai_GetNearestEnemy(oCreature, 1, 7, 7, -1, -1, TRUE);
|
|
if(ai_GetBehaviorState(NW_FLAG_BEHAVIOR_OMNIVORE))
|
|
{
|
|
if(ai_GetIsInCombat(oCreature)) ai_DoMonsterCombatRound(oTarget);
|
|
// * if not attacking, then wander.
|
|
else
|
|
{
|
|
AssignCommand(oCreature, ai_ClearCreatureActions());
|
|
AssignCommand(oCreature, ActionRandomWalk());
|
|
return;
|
|
}
|
|
}
|
|
else if(ai_GetBehaviorState(NW_FLAG_BEHAVIOR_HERBIVORE))
|
|
{
|
|
if(GetIsObjectValid(ai_GetAttackedTarget(oCreature, TRUE, TRUE)))
|
|
{
|
|
if(oTarget != OBJECT_INVALID && GetDistanceBetween(oCreature, oTarget) <= 6.0)
|
|
{
|
|
if(!GetIsFriend(oTarget))
|
|
{
|
|
if(GetLevelByClass(CLASS_TYPE_DRUID, oTarget) == 0 && GetLevelByClass(CLASS_TYPE_RANGER, oTarget) == 0)
|
|
{
|
|
SetLocalString(oCreature, AI_COMBAT_SCRIPT, "ai_coward");
|
|
ActionMoveAwayFromObject(oTarget, TRUE, AI_RANGE_LONG);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if(!IsInConversation(OBJECT_SELF))
|
|
{
|
|
AssignCommand(oCreature, ai_ClearCreatureActions());
|
|
AssignCommand(oCreature, ActionRandomWalk());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
//This function is used only because ActionDoCommand can only accept void functions
|
|
void ai_CreateSignPostNPC(string sTag, location lLocal)
|
|
{
|
|
CreateObject(OBJECT_TYPE_CREATURE, sTag, lLocal);
|
|
}
|
|
void ai_ActivateFleeToExit(object oCreature)
|
|
{
|
|
//minor optimizations - only grab these variables when actually needed
|
|
//can make for larger code, but it's faster
|
|
//object oExitWay = GetWaypointByTag("EXIT_" + GetTag(OBJECT_SELF));
|
|
//location lLocal = GetLocalLocation(OBJECT_SELF, "NW_GENERIC_START_POINT");
|
|
//string sTag = GetTag(OBJECT_SELF);
|
|
int nPlot = GetLocalInt(oCreature, "NW_GENERIC_MASTER");
|
|
if(nPlot & NW_FLAG_TELEPORT_RETURN || nPlot & NW_FLAG_TELEPORT_LEAVE)
|
|
{
|
|
effect eVis = EffectVisualEffect(VFX_IMP_UNSUMMON);
|
|
ApplyEffectToObject(DURATION_TYPE_INSTANT, eVis, oCreature);
|
|
if(nPlot & NW_FLAG_TELEPORT_RETURN)
|
|
{
|
|
location lLocal = GetLocalLocation(oCreature, "NW_GENERIC_START_POINT");
|
|
string sTag = GetTag(oCreature);
|
|
DelayCommand(6.0, AssignCommand(oCreature, ActionDoCommand(ai_CreateSignPostNPC(sTag, lLocal))));
|
|
}
|
|
AssignCommand(oCreature, ActionDoCommand(DestroyObject(oCreature, 0.75)));
|
|
}
|
|
else
|
|
{
|
|
if(nPlot & NW_FLAG_ESCAPE_LEAVE)
|
|
{
|
|
object oExitWay = GetWaypointByTag("EXIT_" + GetTag(oCreature));
|
|
ActionMoveToObject(oExitWay, TRUE);
|
|
AssignCommand(oCreature, ActionDoCommand(DestroyObject(oCreature, 1.0)));
|
|
}
|
|
else if(nPlot & NW_FLAG_ESCAPE_RETURN)
|
|
{
|
|
string sTag = GetTag(oCreature);
|
|
object oExitWay = GetWaypointByTag("EXIT_" + sTag);
|
|
ActionMoveToObject(oExitWay, TRUE);
|
|
location lLocal = GetLocalLocation(oCreature, "NW_GENERIC_START_POINT");
|
|
DelayCommand(6.0, AssignCommand(oCreature, ActionDoCommand(ai_CreateSignPostNPC(sTag, lLocal))));
|
|
AssignCommand(oCreature, ActionDoCommand(DestroyObject(oCreature, 1.0)));
|
|
}
|
|
}
|
|
}
|
|
int ai_GetFleeToExit(object oCreature)
|
|
{
|
|
int nPlot = GetLocalInt(oCreature, "NW_GENERIC_MASTER");
|
|
if(nPlot & NW_FLAG_ESCAPE_RETURN) return TRUE;
|
|
else if(nPlot & NW_FLAG_ESCAPE_LEAVE) return TRUE;
|
|
else if(nPlot & NW_FLAG_TELEPORT_RETURN) return TRUE;
|
|
else if(nPlot & NW_FLAG_TELEPORT_LEAVE) return TRUE;
|
|
return FALSE;
|
|
}
|
|
void ai_ActionInitialization()
|
|
{
|
|
SetAnimationCondition(NW_ANIM_FLAG_IS_ACTIVE);
|
|
SetAnimationCondition(NW_ANIM_FLAG_INITIALIZED);
|
|
SetLocalLocation(OBJECT_SELF, "ANIM_START_LOCATION", GetLocation(OBJECT_SELF));
|
|
}
|
|
// Start interacting with a placeable object
|
|
void ai_ActionStartInteracting(object oPlaceable)
|
|
{
|
|
SetAnimationCondition(NW_ANIM_FLAG_IS_INTERACTING);
|
|
ActionMoveToObject(oPlaceable, FALSE, DISTANCE_TINY);
|
|
ActionDoCommand(SetFacingPoint(GetPosition(oPlaceable)));
|
|
SetCurrentInteractionTarget(oPlaceable);
|
|
AnimActionPlayRandomInteractAnimation(oPlaceable);
|
|
}
|
|
|
|
void ai_ActionStopInteracting()
|
|
{
|
|
AnimActionRandomMoveAway(GetCurrentInteractionTarget(), DISTANCE_LARGE);
|
|
SetCurrentInteractionTarget(OBJECT_INVALID);
|
|
SetAnimationCondition(NW_ANIM_FLAG_IS_INTERACTING, FALSE);
|
|
AnimActionTurnAround();
|
|
AnimActionPlayRandomAnimation();
|
|
}
|
|
|
|
// Start talking with a friend
|
|
void ai_ActionStartTalking(object oFriend, int nHDiff=0)
|
|
{
|
|
object oMe = OBJECT_SELF;
|
|
ActionMoveToObject(oFriend, FALSE, DISTANCE_TINY);
|
|
AnimActionPlayRandomGreeting(nHDiff);
|
|
AssignCommand(oFriend, ActionMoveToObject(oMe, FALSE, DISTANCE_TINY));
|
|
AssignCommand(oFriend, AnimActionPlayRandomGreeting(0 - nHDiff));
|
|
SetCurrentFriend(oFriend);
|
|
AssignCommand(oFriend, SetCurrentFriend(oMe));
|
|
ActionDoCommand(SetFacingPoint(GetPosition(oFriend)));
|
|
AssignCommand(oFriend, ActionDoCommand(SetFacingPoint(GetPosition(oMe))));
|
|
SetAnimationCondition(NW_ANIM_FLAG_IS_TALKING);
|
|
SetAnimationCondition(NW_ANIM_FLAG_IS_TALKING, TRUE, oFriend);
|
|
}
|
|
void ai_ActionStopTalking(object oFriend, int nHDiff=0)
|
|
{
|
|
object oMe = OBJECT_SELF;
|
|
AnimActionPlayRandomGoodbye(nHDiff);
|
|
AnimActionRandomMoveAway(oFriend, DISTANCE_LARGE);
|
|
AssignCommand(oFriend, AnimActionPlayRandomGoodbye(0 - nHDiff));
|
|
AssignCommand(oFriend, AnimActionRandomMoveAway(oMe, DISTANCE_HUGE));
|
|
SetAnimationCondition(NW_ANIM_FLAG_IS_TALKING, FALSE);
|
|
SetAnimationCondition(NW_ANIM_FLAG_IS_TALKING, FALSE, oFriend);
|
|
}
|
|
object ai_GetRandomFriend(float fMaxDistance)
|
|
{
|
|
object oCreature = OBJECT_SELF;
|
|
location lStartLocation = GetLocalLocation(oCreature, "ANIM_START_LOCATION");
|
|
object oFriend = GetNearestCreature(CREATURE_TYPE_REPUTATION,
|
|
REPUTATION_TYPE_FRIEND,
|
|
oCreature, d2(),
|
|
CREATURE_TYPE_PERCEPTION,
|
|
PERCEPTION_SEEN);
|
|
//SendMessageToPC(GetFirstPC(), " 0i_actions, 1748 oFriend: " + GetName(oFriend) +
|
|
// " AnimationCondition: " + IntToString(GetAnimationCondition(NW_ANIM_FLAG_IS_ACTIVE, oFriend)) +
|
|
// " Conversation: " + IntToString(IsInConversation(oFriend)) +
|
|
// " Combat: " + IntToString(GetIsInCombat(oFriend)) +
|
|
// " Distance: " + FloatToString(GetDistanceBetweenLocations(GetLocation(oFriend), lStartLocation), 0,0 ));
|
|
if(fMaxDistance > 20.0) fMaxDistance = 20.0;
|
|
if(GetIsObjectValid(oFriend)
|
|
&& !GetIsPC(oFriend)
|
|
&& !GetAnimationCondition(NW_ANIM_FLAG_IS_TALKING, oFriend)
|
|
&& !IsInConversation(oFriend)
|
|
&& !GetIsInCombat(oFriend)
|
|
&& GetDistanceBetweenLocations(GetLocation(oFriend), lStartLocation) <= fMaxDistance)
|
|
{
|
|
return oFriend;
|
|
}
|
|
|
|
return OBJECT_INVALID;
|
|
}
|
|
int ai_ActionFindFriend(float fMaxDistance)
|
|
{
|
|
// Try and find a friend to talk to.
|
|
object oFriend = ai_GetRandomFriend(fMaxDistance);
|
|
//SendMessageToPC(GetFirstPC(), GetName(oFriend) + " TALKING: " + IntToString(GetAnimationCondition(NW_ANIM_FLAG_IS_TALKING, oFriend)));
|
|
if(GetIsObjectValid(oFriend) && !GetAnimationCondition(NW_ANIM_FLAG_IS_TALKING, oFriend))
|
|
{
|
|
int nHDiff = GetHitDice(OBJECT_SELF) - GetHitDice(oFriend);
|
|
ai_ActionStartTalking(oFriend, nHDiff);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
object ai_GetRandomObjectbyTag(string sTag, float fMaxDistance)
|
|
{
|
|
int nIndex;
|
|
if(fMaxDistance < DISTANCE_MEDIUM) nIndex = d4();
|
|
else if (fMaxDistance < DISTANCE_HUGE) nIndex = d8();
|
|
else nIndex = d10();
|
|
location lStartLocation = GetLocalLocation(OBJECT_SELF, "ANIM_START_LOCATION");
|
|
if(fMaxDistance > 20.0) fMaxDistance = 20.0;
|
|
object oObject = GetNearestObjectToLocation(OBJECT_TYPE_PLACEABLE, lStartLocation, nIndex);
|
|
while(nIndex > 0)
|
|
{
|
|
if(GetTag(oObject) == sTag &&
|
|
GetDistanceBetweenLocations(GetLocation(oObject), lStartLocation) <= fMaxDistance) break;
|
|
oObject = GetNearestObjectToLocation(OBJECT_TYPE_PLACEABLE, lStartLocation, --nIndex);
|
|
}
|
|
if(GetIsObjectValid(oObject))
|
|
return oObject;
|
|
return OBJECT_INVALID;
|
|
}
|
|
int ai_ActionSitInChair(float fMaxDistance)
|
|
{
|
|
object oChair = GetRandomObjectByTag("Chair", fMaxDistance);
|
|
if (GetIsObjectValid(oChair) &&
|
|
!GetIsObjectValid(GetSittingCreature(oChair)))
|
|
{
|
|
ActionSit(oChair);
|
|
SetAnimationCondition(NW_ANIM_FLAG_IS_INTERACTING);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
object ai_GetRandomUseableObject(float fMaxDistance)
|
|
{
|
|
int nIndex;
|
|
if(fMaxDistance < DISTANCE_MEDIUM) nIndex = d2();
|
|
else if (fMaxDistance < DISTANCE_HUGE) nIndex = d4();
|
|
else nIndex = d6();
|
|
location lStartLocation = GetLocalLocation(OBJECT_SELF, "ANIM_START_LOCATION");
|
|
if(fMaxDistance > 20.0) fMaxDistance = 20.0;
|
|
object oObject = GetNearestObjectToLocation(OBJECT_TYPE_PLACEABLE, lStartLocation, nIndex);
|
|
while(nIndex > 0)
|
|
{
|
|
if(GetUseableFlag(oObject) && !GetLocked(oObject) &&
|
|
GetDistanceBetweenLocations(GetLocation(oObject), lStartLocation) <= fMaxDistance) break;
|
|
oObject = GetNearestObjectToLocation(OBJECT_TYPE_PLACEABLE, lStartLocation, --nIndex);
|
|
}
|
|
if(GetIsObjectValid(oObject))
|
|
return oObject;
|
|
return OBJECT_INVALID;
|
|
}
|
|
int ai_ActionFindPlaceable(float fMaxDistance)
|
|
{
|
|
object oPlaceable = ai_GetRandomUseableObject(fMaxDistance);
|
|
if(GetIsObjectValid(oPlaceable))
|
|
{
|
|
ai_ActionStartInteracting(oPlaceable);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
int ai_ActionCheckDoor(float fMaxDistance)
|
|
{
|
|
int nIndex = 1;
|
|
object oCreature = OBJECT_SELF;
|
|
location lStartLocation = GetLocalLocation(oCreature, "ANIM_START_LOCATION");
|
|
if(fMaxDistance > 20.0) fMaxDistance = 20.0;
|
|
object oDoor = GetNearestObject(OBJECT_TYPE_DOOR, oCreature);
|
|
while(oDoor != OBJECT_INVALID)
|
|
{
|
|
if(GetDistanceBetweenLocations(GetLocation(oDoor), lStartLocation) <= fMaxDistance)
|
|
{
|
|
// Make sure everyone doesn't run to close or open the same door.
|
|
if(!GetLocalInt(oDoor, "DOOR_INTERACTION"))
|
|
{
|
|
if(GetIsOpen(oDoor))
|
|
{
|
|
//SendMessageToPC(GetFirstPC(), GetName(oCreature) +
|
|
// " Closing " + GetName(oDoor) + ".");
|
|
SetLocalInt(oDoor, "DOOR_INTERACTION", TRUE);
|
|
ActionCloseDoor(oDoor);
|
|
AssignCommand(oDoor, ActionDoCommand(SetLocalInt(oDoor, "DOOR_INTERACTION", FALSE)));
|
|
return TRUE;
|
|
}
|
|
else if(GetLocalInt(GetModule(), AI_RULE_OPEN_DOORS))
|
|
{
|
|
//SendMessageToPC(GetFirstPC(), GetName(oDoor) + " Locked: " +
|
|
// IntToString(GetLocked(oDoor)) + " Trapped: " +
|
|
// IntToString(GetIsTrapped(oDoor)) +
|
|
// " Plot: " + IntToString(GetPlotFlag(oDoor)));
|
|
if(!GetLocked(oDoor) &&
|
|
!GetIsTrapped(oDoor) &&
|
|
!GetPlotFlag(oDoor))
|
|
{
|
|
//SendMessageToPC(GetFirstPC(), GetName(oCreature) +
|
|
// " Opening " + GetName(oDoor) + ".");
|
|
SetLocalInt(oDoor, "DOOR_INTERACTION", TRUE);
|
|
ActionOpenDoor(oDoor);
|
|
// If a door has been opened lets not go right behind and close for a minute.
|
|
DelayCommand(60.0, SetLocalInt(oDoor, "DOOR_INTERACTION", FALSE));
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
oDoor = GetNearestObject(OBJECT_TYPE_DOOR, oCreature, ++nIndex);
|
|
}
|
|
return FALSE;
|
|
}
|
|
int ai_ActionInteraction()
|
|
{
|
|
// If we're talking, either keep going or stop.
|
|
// Low prob of stopping, since both parties have
|
|
// a chance and conversations are cool.
|
|
if(GetAnimationCondition(NW_ANIM_FLAG_IS_TALKING))
|
|
{
|
|
object oFriend = GetCurrentFriend();
|
|
//SendMessageToPC(GetFirstPC(), GetName(OBJECT_SELF) + " Is talking to " + GetName(oFriend));
|
|
int nHDiff = GetHitDice(OBJECT_SELF) - GetHitDice(oFriend);
|
|
if(Random(100) < 20)
|
|
{
|
|
//SendMessageToPC(GetFirstPC(), GetName(OBJECT_SELF) + " I'm done talking!");
|
|
ai_ActionStopTalking(oFriend, nHDiff);
|
|
return TRUE;
|
|
}
|
|
AnimActionPlayRandomTalkAnimation(nHDiff);
|
|
return TRUE;
|
|
}
|
|
// If we're interacting with a placeable, either keep going or stop.
|
|
// High probability of stopping, since looks silly to
|
|
// constantly turn something on-and-off.
|
|
if(GetAnimationCondition(NW_ANIM_FLAG_IS_INTERACTING))
|
|
{
|
|
//SendMessageToPC(GetFirstPC(), GetName(OBJECT_SELF) + " Is interacting.");
|
|
if(Random(100) < 40)
|
|
{
|
|
//SendMessageToPC(GetFirstPC(), GetName(OBJECT_SELF) + " I'm done interacting!");
|
|
ai_ActionStopInteracting();
|
|
return TRUE;
|
|
}
|
|
AnimActionPlayRandomInteractAnimation(GetCurrentInteractionTarget());
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
location ai_GetWalkingLocation(object oSource, float fDistance)
|
|
{
|
|
location lStart = GetLocation(oSource);
|
|
// Try to move in a north/south/east/west direction that will allow better
|
|
// movement around the map!
|
|
float fFacing = GetFacing(oSource);
|
|
if(Random(100) < 25) fFacing = IntToFloat(Random(360));
|
|
float fAngle;
|
|
if(fFacing > 315.0 || fFacing < 45.0) fAngle = DIRECTION_EAST;
|
|
else if(fFacing < 135.0) fAngle = DIRECTION_NORTH;
|
|
else if(fFacing < 225.0) fAngle = DIRECTION_WEST;
|
|
else fAngle = DIRECTION_SOUTH;
|
|
fAngle += IntToFloat(Random(20) - 10);
|
|
float fOrientation = fAngle;
|
|
return GenerateNewLocationFromLocation(lStart, fDistance, fAngle, fOrientation);
|
|
}
|
|
void ai_ActionRandomWalk(float fMaxDistance)
|
|
{
|
|
// If we stay within our alloted distance then we can walk to the new location.
|
|
location lStartLocation = GetLocalLocation(OBJECT_SELF, "ANIM_START_LOCATION");
|
|
int nRandom = FloatToInt(fMaxDistance);
|
|
if(nRandom > 20) nRandom = 20;
|
|
float fRandom = IntToFloat(Random(nRandom) + 1);
|
|
location lNewLocation = ai_GetWalkingLocation(OBJECT_SELF, fRandom);
|
|
if(AI_DEBUG) ai_Debug("0i_actions", "2092", GetName(OBJECT_SELF) + " is walking " +
|
|
FloatToString(GetDistanceBetweenLocations(lNewLocation, lStartLocation), 0, 0) +
|
|
" distance of fMaxDistance: " + FloatToString(fMaxDistance, 0, 0));
|
|
ActionMoveToLocation(lNewLocation);
|
|
}
|
|
void ai_Actions()
|
|
{
|
|
float fMaxDistance = GetLocalFloat(GetModule(), AI_RULE_WANDER_DISTANCE);
|
|
// Are we interacting? If so continue else see what else there is to do.
|
|
if(ai_ActionInteraction()) return;
|
|
// If we got here, we're not busy
|
|
ClearAllActions();
|
|
// Check for chance to do an action to keep things interesting.
|
|
int nRoll = Random(100);
|
|
if(fMaxDistance < 2.0)
|
|
{
|
|
if(nRoll < 51) AnimActionPlayRandomAnimation();
|
|
return;
|
|
}
|
|
int nRace = GetRacialType(OBJECT_SELF);
|
|
if(nRace != RACIAL_TYPE_ABERRATION && nRace != RACIAL_TYPE_ANIMAL &&
|
|
nRace != RACIAL_TYPE_BEAST && nRace != RACIAL_TYPE_MAGICAL_BEAST &&
|
|
nRace != RACIAL_TYPE_OOZE && nRace != RACIAL_TYPE_VERMIN)
|
|
{
|
|
if(nRoll < 5) if(ai_ActionSitInChair(fMaxDistance)) return;
|
|
// Open or close a door
|
|
if(nRoll < 20) if(ai_ActionCheckDoor(fMaxDistance)) return;
|
|
// Fiddle with a placeable
|
|
if(nRoll < 40) if(ai_ActionFindPlaceable(fMaxDistance)) return;
|
|
// Start talking to a friend
|
|
if(nRoll < 50) if(ai_ActionFindFriend(fMaxDistance)) return;
|
|
}
|
|
// Lets walk around.
|
|
if(nRoll < 80)
|
|
{
|
|
ai_ActionRandomWalk(fMaxDistance);
|
|
return;
|
|
}
|
|
// If we find nothing interesting to do then just stay put and look interesting.
|
|
AnimActionPlayRandomAnimation();
|
|
}
|
|
int ai_CheckCurrentAction()
|
|
{
|
|
int nAction = GetCurrentAction();
|
|
if(nAction == ACTION_SIT)
|
|
{
|
|
// low prob of getting up, so we don't bop up and down constantly
|
|
if (Random(10) == 0)
|
|
{
|
|
AnimActionGetUpFromChair();
|
|
return TRUE;
|
|
}
|
|
}
|
|
else if(nAction != ACTION_INVALID)
|
|
{
|
|
// Sometimes we cannot do an action so lets break out sometimes.
|
|
if((nAction == ACTION_CLOSEDOOR ||
|
|
nAction == ACTION_OPENDOOR ||
|
|
nAction == ACTION_MOVETOPOINT) && Random(100) < 20) return FALSE;
|
|
// we're doing *something*, don't switch
|
|
//AnimDebug("performing action");
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
void ai_AmbientAnimations()
|
|
{
|
|
if(!GetAnimationCondition(NW_ANIM_FLAG_INITIALIZED)) ai_ActionInitialization();
|
|
// Check if we should turn off
|
|
if(!CheckIsAnimActive(OBJECT_SELF)) return;
|
|
// Check current actions so we don't interrupt something in progress
|
|
if(ai_CheckCurrentAction()) return;
|
|
// First check: go back to starting position and rest if we are hurt
|
|
//if(AnimActionRest()) return;
|
|
// If we get here then lets go see what we can do!
|
|
ai_Actions();
|
|
}
|