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