MMD_PRC8/_module/nss/0i_actions.nss
Jaysyn904 d77404f157 2025/07/15 Update
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.
2025-07-15 22:19:46 -04:00

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();
}