/*/////////////////////// [Include - Other AI Functions] /////////////////////// Filename: J_INC_Other_AI ///////////////////////// [Include - Other AI Functions] /////////////////////// This contains fuctions and calls for these scripts: nw_c2_default2 - Percieve nw_c2_default3 - On Combat round End (For DetermineCombatRound() only) nw_c2_default4 - Conversation (shout) nw_c2_default5 - Phisical attacked nw_c2_default6 - Damaged nw_c2_default8 - Disturbed nw_c2_defaultb - Spell cast at Ones that don't use this use different or no includes. HOPEFULLY it will make them faster, if they don't run combat. They use Execute Script to initiate combat. (With the override ones initiating the override version, the normal initiateing the normal). ///////////////////////// [History] //////////////////////////////////////////// 1.3 - Added to speed up compilings and gather non-combat, or other workings in one place. 1.4 - TO DO: - ///////////////////////// [Workings] /////////////////////////////////////////// This is included in other AI files. They then use these functions in them scripts. ///////////////////////// [Arguments] ////////////////////////////////////////// Arguments: N/A ///////////////////////// [Include - Other AI Functions] /////////////////////*/ // All constants. #include "J_INC_CONSTANTS" // Responds to it (like makinging the callers attacker thier target) // Called in OnConversation, and thats it. Use "ShouterFriend" To stop repeated GetIsFriend calls. void RespondToShout(object oShouter, int nShoutIndex); // Gets any possible target which is attacking oShouter (and isn't an ally) // or who oShouter is attacking. oShouter should be a ally. object GetIntruderFromShout(object oShouter); // Shouts, or really brings all people in 60.0M(by default) to the "shouter" void ShoutBossShout(object oEnemy); // This sets a morale penalty, to the exsisting one, if there is one. // It will reduce itself after fDuration (or if we die, ETC, it is deleted). // It is deleted at the end of combat as well. void SetMoralePenalty(int nPenalty, float fDuration = 0.0); // Removes nPenalty amount if it can. void RemoveMoralePenalty(int nPenalty); // At 5+ intelligence, we fire off any dispells at oPlaceables location void SearchDispells(object oPlaceable); // This MAY make us set a local timer to turn off hiding. // Turn of hiding, a timer to activate Hiding in the main file. This is // done in each of the events, with the opposition checking seen/heard. void TurnOffHiding(object oIntruder); // Used when we percieve a new enemy and are not in combat. Hides the creature // appropriatly with spawn settings and ability. // - At least it will clear all actions if it doesn't set hiding on void HideOrClear(); // This MIGHT move to oEnemy // - Checks special actions, such as fleeing, and may run instead! void ActionMoveToEnemy(object oEnemy); // Returns TRUE if we have under 0 morale, set to flee. // - They then run! (Badly) int PerceptionFleeFrom(object oEnemy); // This wrappers commonly used code for a "Call to arms" type response. // * We know of no enemy, so we will move to oAlly, who either called to // us, or, well, we know of. // * Calls out AI_SHOUT_CALL_TO_ARMS too. void CallToArmsResponse(object oAlly); // This wrappers commonly used code for a "I was attacked" type response. // * We know there will be an enemy - or should be - and if we find one to attack // (using GetIntruderFromShout()) - we attack it (and call another I was attacked) // else, this will run CallToArmsResponse(oAlly); // * Calls out AI_SHOUT_I_WAS_ATTACKED, or AI_SHOUT_CALL_TO_ARMS too. void IWasAttackedResponse(object oAlly); /*:://///////////////////////////////////////////// //:: Name: ShoutBossShout //:://///////////////////////////////////////////// This is used in the OnPercieve, and if we are set to, we will "shout" and bring lots of allies a running //:://///////////////////////////////////////////*/ void ShoutBossShout(object oEnemy) { // 1.4 - Added a 5 minute cooldown timer for this. Thusly, if the boss lingers, // so will the big shout they do. if(GetSpawnInCondition(AI_FLAG_OTHER_COMBAT_BOSS_MONSTER_SHOUT, AI_OTHER_COMBAT_MASTER) && !GetLocalTimer(AI_TIMER_BOSS_SHOUT_COOLDOWN)) { // Get the range (and default to 60.0 M) float fRange = IntToFloat(GetBoundriedAIInteger(AI_BOSS_MONSTER_SHOUT_RANGE, 60, 370)); // We loop through nearest not-seen, not-heard allies and get them // to attack the person. int nCnt = 1; // Not seen, not heard... object oAlly = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_FRIEND, OBJECT_SELF, nCnt, CREATURE_TYPE_IS_ALIVE, TRUE, CREATURE_TYPE_PERCEPTION, PERCEPTION_NOT_SEEN_AND_NOT_HEARD); // Get who thier target is. object oThierTarget; while(GetIsObjectValid(oAlly) && GetDistanceToObject(oAlly) <= fRange) { oThierTarget = GetLocalObject(oAlly, AI_TO_ATTACK); // If they are not attacking the enemy, we assing them to attack. if(oThierTarget != oEnemy) { // Can't be in combat. if(!GetIsInCombat(oAlly)) { // Set them to move to this SetLocalObject(oAlly, AI_TO_ATTACK, oEnemy); // Make them attack the person SetLocalObject(oAlly, AI_TEMP_SET_TARGET, oEnemy); ExecuteScript(COMBAT_FILE, oAlly); } } nCnt++; oAlly = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_FRIEND, OBJECT_SELF, nCnt, CREATURE_TYPE_IS_ALIVE, TRUE, CREATURE_TYPE_PERCEPTION, PERCEPTION_NOT_SEEN_AND_NOT_HEARD); } // * Don't speak when dead. 1.4 change (an obvious one to make) if(CanSpeak()) { // Speak a string associated with this action being carried out SpeakArrayString(AI_TALK_ON_LEADER_BOSS_SHOUT); } // Remove it for 5 minutes. SetLocalTimer(AI_TIMER_BOSS_SHOUT_COOLDOWN, 300.0); } } // This MAY make us set a local timer to turn off hiding. // Turn of hiding, a timer to activate Hiding in the main file. This is // done in each of the events, with the opposition checking seen/heard. void TurnOffHiding(object oIntruder) { if(!GetLocalTimer(AI_TIMER_TURN_OFF_HIDE) && // Are we actually seen/heard or is it just an AOE? (GetObjectSeen(OBJECT_SELF, oIntruder) || GetObjectHeard(OBJECT_SELF, oIntruder))) { SetLocalTimer(AI_TIMER_TURN_OFF_HIDE, 18.0); } } // Used when we percieve a new enemy and are not in combat. Hides the creature // appropriatly with spawn settings and ability. // - At least it will clear all actions if it doesn't set hiding on void HideOrClear() { // Spawn in conditions for it if(!GetSpawnInCondition(AI_FLAG_OTHER_COMBAT_NO_HIDING, AI_OTHER_COMBAT_MASTER) && GetActionMode(OBJECT_SELF, ACTION_MODE_STEALTH) == FALSE) { // Need skill or force on int nRank = GetSkillRank(SKILL_HIDE); if((nRank - 4 >= GetHitDice(OBJECT_SELF) && nRank >= 7) || GetSpawnInCondition(AI_FLAG_OTHER_COMBAT_FORCE_HIDING, AI_OTHER_COMBAT_MASTER)) { // Use hide ClearAllActions(TRUE); SetActionMode(OBJECT_SELF, ACTION_MODE_STEALTH, TRUE); // Stop return; } } // Else clear all actions normally. ClearAllActions(); } /*:://///////////////////////////////////////////// //:: Respond To Shouts //:: Copyright (c) 2001 Bioware Corp. //:://///////////////////////////////////////////// Useage: //NOTE ABOUT BLOCKERS int NW_GENERIC_SHOUT_BLOCKER = 2; It should be noted that the Generic Script for On Dialogue attempts to get a local set on the shouter by itself. This object represents the LastOpenedBy object. It is this object that becomes the oIntruder within this function. //NOTE ABOUT INTRUDERS These are the enemy that attacked the shouter. //NOTE ABOUT EVERYTHING ELSE I_WAS_ATTACKED = 1; If not in combat, attack the attackee of the shouter. Basically the best way to get people to come and help us, if we know of an attacker! * Call this after we call DetermineCombatRound() to make sure that any responses know of the attackers. It doesn't matter in actual fact, but useful anyway. CALL_TO_ARMS = 3; If not in combat, determine combat round. By default, it should check any allies it can see/hear for thier targets and help them too. * Better if we do not know of a target (and thusly our allies wouldn't know of them as well) so the allies will move to us. HELP_MY_FRIEND = 4; This is a runner thing. Said when the runner sees the target to run to. Gets a local location, and sets off people to run to it. If no valid area for the location, no moving :-P We also shout this if we are fleeing. It will set the person to buff too. LEADER_FLEE_NOW = 5 We flee to a pre-set object or follow the leader (who should be fleeing). LEADER_ATTACK_TARGET = 6 We attack the intruder next round, by setting it as a local object to override other choices. I_WAS_KILLED = 7 If lots are killed in one go - ouch! morale penalty each time someone dies. I_WAS_OPENED = 8 Chests/Doors which say this get the AI onto the tails of those who opened it, OR they get searched! :-) //:://///////////////////////////////////////////// // Modified almost completely: Jasperre //:://///////////////////////////////////////////*/ // Gets any possible target which is attacking oShouter (and isn't an ally) // or who oShouter is attacking. oShouter should be a ally. object GetIntruderFromShout(object oShouter) { // First, get who they specifically want to attack (IE: Input target the shout // is usually for) object oIntruder = GetLocalObject(oShouter, AI_OBJECT + AI_ATTACK_SPECIFIC_OBJECT); if(GetIgnoreNoFriend(oIntruder) || (!GetObjectSeen(oShouter) && !GetObjectHeard(oShouter))) { // Or, we look for the last melee target (which, at least, will be set) oIntruder = GetLocalObject(oShouter, AI_OBJECT + AI_LAST_MELEE_TARGET); if(GetIgnoreNoFriend(oIntruder) || (!GetObjectSeen(oShouter) && !GetObjectHeard(oShouter))) { // Current actual attack target of the shouter oIntruder = GetAttackTarget(oShouter); if(GetIgnoreNoFriend(oIntruder) || (!GetObjectSeen(oShouter) && !GetObjectHeard(oShouter))) { // Last hostile actor of the shouter oIntruder = GetLastHostileActor(oShouter); if(GetIgnoreNoFriend(oIntruder) || (!GetObjectSeen(oShouter) && !GetObjectHeard(oShouter))) { return OBJECT_INVALID; } } } } return oIntruder; } // Responds to it (like makinging the callers attacker thier target) // Called in OnConversation, and thats it. Use "ShouterFriend" To stop repeated GetIsFriend calls. void RespondToShout(object oShouter, int nShoutIndex) { // We use oIntruder to set who to attack. object oIntruder; // Check nShoutIndex against known constants switch(nShoutIndex) { // Note: Not checked in sequential order (especially as they are constants). // Instead, it is "Ones which if we are in combat, we still check" first. // Attack a specific object which the leader shouted about. case AI_SHOUT_LEADER_ATTACK_TARGET_CONSTANT: { // If a leader, we set it as a local object, nothing more if(GetSpawnInCondition(AI_FLAG_OTHER_COMBAT_GROUP_LEADER, AI_OTHER_COMBAT_MASTER, oShouter)) { // 70. "[Shout] Reacting To Shout. [ShoutNo.] " + IntToString(iInput) + " [Shouter] " + GetName(oInput) DebugActionSpeakByInt(70, oShouter, nShoutIndex); oIntruder = GetLocalObject(oShouter, AI_ATTACK_SPECIFIC_OBJECT); if(GetObjectSeen(oIntruder)) { // Set local object to use in next DetermineCombatRound. // We do not interrupt current acition (EG: Life saving stoneskins!) to re-direct. SetAIObject(AI_ATTACK_SPECIFIC_OBJECT, oIntruder); // 6 second delay. SetLocalTimer(AI_TIMER_SHOUT_IGNORE_ANYTHING_SAID, 6.0); } } return; } break; // Leader flee now - mass retreat to those who hear it. case AI_SHOUT_LEADER_FLEE_NOW_CONSTANT: { // If a leader, we set it as a local object, nothing more if(GetSpawnInCondition(AI_FLAG_OTHER_COMBAT_GROUP_LEADER, AI_OTHER_COMBAT_MASTER, oShouter)) { // Get who we are going to run too oIntruder = GetLocalObject(oShouter, AI_FLEE_TO); // RUN! If intruder set is over 5.0M or no valid intruder ClearAllActions(); // 70. "[Shout] Reacting To Shout. [ShoutNo.] " + IntToString(iInput) + " [Shouter] " + GetName(oInput) DebugActionSpeakByInt(70, oShouter, nShoutIndex); // Set to run SetCurrentAction(AI_SPECIAL_ACTIONS_FLEE); // Turn on fleeing visual effect ApplyFleeingVisual(); // Ignore talk for 12 seconds SetLocalTimer(AI_TIMER_SHOUT_IGNORE_ANYTHING_SAID, 12.0); // If valid, we run to the intruder if(GetIsObjectValid(oIntruder)) { SetAIObject(AI_FLEE_TO, oIntruder); ActionMoveToObject(oIntruder); } else // Else, we will just follow our leader! { SetAIObject(AI_FLEE_TO, oShouter); ActionForceFollowObject(oShouter, 3.0); } } return; } break; // All others (IE: We need to not be in combat for these) // Anything that requires "DetermineCombatRound()" is here. // If the shout is number 8, it is "I was opened" and so can only be a // placeable or door. case AI_SHOUT_I_WAS_OPENED_CONSTANT: { // If we are already attacking, we ignore this shout. if(CannotPerformCombatRound()) return; // We need somewhat complexe here - to get thier opener. int nType = GetObjectType(oShouter); // Check object type. If not a placeable nor door - stop script. if(nType == OBJECT_TYPE_PLACEABLE || nType == OBJECT_TYPE_DOOR) { // 70. "[Shout] Reacting To Shout. [ShoutNo.] " + IntToString(iInput) + " [Shouter] " + GetName(oInput) DebugActionSpeakByInt(70, oShouter, nShoutIndex); // Now, we assign the placeable/door to set thier opener. // We do this by just executing a script that does it. ExecuteScript(FILE_SET_OPENER, oShouter); // We can immediantly get this would-be attacker! oIntruder = GetLocalObject(oShouter, AI_PLACEABLE_LAST_OPENED_BY); if(GetIsObjectValid(oIntruder)) { // Attack ClearAllActions(); DetermineCombatRound(oShouter); } else { // Move to the object who shouted in detect mode SetActionMode(OBJECT_SELF, ACTION_MODE_DETECT, TRUE); ActionMoveToObject(oShouter, TRUE); } } return; } break; // Call to arms requires nothing special. It is only called if // There is no target the shouter has to attack specifically, rather then // "I_WAS_ATTACKED" which would have. case AI_SHOUT_CALL_TO_ARMS_CONSTANT: { // If we are already attacking, we ignore this shout. if(CannotPerformCombatRound()) return; // Ignore for 6 seconds SetLocalTimer(AI_TIMER_SHOUT_IGNORE_ANYTHING_SAID, 6.0); // 70. "[Shout] Reacting To Shout. [ShoutNo.] " + IntToString(iInput) + " [Shouter] " + GetName(oInput) DebugActionSpeakByInt(70, oShouter, nShoutIndex); // do standard Call to Arms response - IE: Move to oShouter CallToArmsResponse(oShouter); return; } break; // "Help my friend" is when a runner is running off (sorta fleeing) to // get help. This will move to the location set on them to reinforce. case AI_SHOUT_HELP_MY_FRIEND_CONSTANT: { // If we are already attacking, we ignore this shout. if(CannotPerformCombatRound()) return; // Ignore things for 6 seconds SetLocalTimer(AI_TIMER_SHOUT_IGNORE_ANYTHING_SAID, 6.0); // We move to where the runner/shouter wants us. location lMoveTo = GetLocalLocation(oShouter, AI_LOCATION + AI_HELP_MY_FRIEND_LOCATION); // 70. "[Shout] Reacting To Shout. [ShoutNo.] " + IntToString(iInput) + " [Shouter] " + GetName(oInput) DebugActionSpeakByInt(70, oShouter, nShoutIndex); // If the location is valid if(GetIsObjectValid(GetAreaFromLocation(lMoveTo))) { // New special action, but one that is overrided by combat SetCurrentAction(AI_SPECIAL_ACTIONS_MOVE_TO_COMBAT); SetAIObject(AI_MOVE_TO_COMBAT_OBJECT, oShouter); SetAILocation(AI_MOVE_TO_COMBAT_LOCATION, lMoveTo); // Move to the location of the fight, attack. ClearAllActions(); // Move to the fights location ActionMoveToLocation(lMoveTo, TRUE); // When we see someone fighting, we'll DCR return; } else { // Else, if we do not know of the friends attackers, or the location // they are at, we will follow them without casting any preperation // spells. ClearAllActions(); ActionForceFollowObject(oShouter, 3.0); // When we see an enemy, we'll attack! return; } return; } break; // "I was attacked" is called when a creature is hurt or sees an enemy, // and starts to attack them. This means they know who the enemy is - // and thusly we can get it from them (Ususally GetLastHostileActor() // "I was killed" is the same, but applies a morale penalty too case AI_SHOUT_I_WAS_ATTACKED_CONSTANT: case AI_SHOUT_I_WAS_KILLED_CONSTANT: { // If it was "I was killed", we apply a short morale penatly // Penalty is "Hit dice / 4 + 1" (so always 1 minimum) for 18 seconds. if(nShoutIndex == AI_SHOUT_I_WAS_KILLED_CONSTANT) { SetMoralePenalty(GetHitDice(oShouter)/4 + 1, 18.0); } // If we are already attacking, we ignore this shout. if(CannotPerformCombatRound()) return; // 70. "[Shout] Reacting To Shout. [ShoutNo.] " + IntToString(iInput) + " [Shouter] " + GetName(oInput) DebugActionSpeakByInt(70, oShouter, nShoutIndex); // Ignore for 6 seconds SetLocalTimer(AI_TIMER_SHOUT_IGNORE_ANYTHING_SAID, 6.0); // Respond to oShouter's request for help - find thier target, and // attack! IWasAttackedResponse(oShouter); return; } break; } } // At 5+ intelligence, we fire off any dispells at oPlaceables location void SearchDispells(object oPlaceable) { // No dispelling at low intelligence. if(GetBoundriedAIInteger(AI_INTELLIGENCE) < 5) return; location lPlace = GetLocation(oPlaceable); // Move closer if not seen. if(!GetObjectSeen(oPlaceable)) { // Move nearer - 6 M is out of the dispell range ActionMoveToObject(oPlaceable, TRUE, 6.0); } // Dispell if we have any - at the location of oPlaceable. if(GetHasSpell(SPELL_LESSER_DISPEL)) { ActionCastSpellAtLocation(SPELL_LESSER_DISPEL, lPlace); } else if(GetHasSpell(SPELL_DISPEL_MAGIC)) { ActionCastSpellAtLocation(SPELL_DISPEL_MAGIC, lPlace); } else if(GetHasSpell(SPELL_GREATER_DISPELLING)) { ActionCastSpellAtLocation(SPELL_GREATER_DISPELLING, lPlace); } else if(GetHasSpell(SPELL_MORDENKAINENS_DISJUNCTION)) { ActionCastSpellAtLocation(SPELL_MORDENKAINENS_DISJUNCTION, lPlace); } } // This sets a morale penalty, to the exsisting one, if there is one. // It will reduce itself (by the penalty) after fDuration (or if we die, ETC, it is deleted). // It is deleted at the end of combat as well. void SetMoralePenalty(int nPenalty, float fDuration = 0.0) { int nNewPenalty = GetAIInteger(AI_MORALE_PENALTY) + nPenalty; SetAIInteger(AI_MORALE_PENALTY, nNewPenalty); DelayCommand(fDuration, RemoveMoralePenalty(nPenalty)); } // Removes nPenalty amount if it can. void RemoveMoralePenalty(int nPenalty) { int nNewPenalty = GetAIInteger(AI_MORALE_PENALTY) - nPenalty; if(nNewPenalty > 0 && !GetIsDead(OBJECT_SELF)) { SetAIInteger(AI_MORALE_PENALTY, nNewPenalty); } else { DeleteAIInteger(AI_MORALE_PENALTY); } } // This MIGHT move to oEnemy // - Checks special actions, such as fleeing, and may run instead! void ActionMoveToEnemy(object oEnemy) { // Make sure that we are not fleeing badly (-1 morale from all enemies) if(GetIsEnemy(oEnemy)) { // -1 morale, flee if(PerceptionFleeFrom(oEnemy)) return; } if(GetIsPerformingSpecialAction()) { // Stop if we have an action we don't want to override return; } // End default is move to the enemy ClearAllActions(); ActionMoveToObject(oEnemy, TRUE); // combat round to heal/search/whatever if(!GetFactionEqual(oEnemy)) { ActionDoCommand(DetermineCombatRound(oEnemy)); } } // Returns TRUE if we have under 0 morale, set to flee. // - They then run! (Badly) int PerceptionFleeFrom(object oEnemy) { object oRunTarget = oEnemy; if(GetAIInteger(AI_INTELLIGENCE) < FALSE) { // Valid run from target if(!GetIsObjectValid(oRunTarget)) { oRunTarget = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, 1, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN, CREATURE_TYPE_IS_ALIVE, TRUE); if(!GetIsObjectValid(oRunTarget)) { oRunTarget = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, 1, CREATURE_TYPE_PERCEPTION, PERCEPTION_HEARD, CREATURE_TYPE_IS_ALIVE, TRUE); if(!GetIsObjectValid(oRunTarget)) { oRunTarget = GetLastHostileActor(); if(!GetIsObjectValid(oRunTarget) || GetIsDead(oRunTarget)) { // Stop - nothing to flee from! return FALSE; } } } } // Run from enemy ClearAllActions(); ActionMoveAwayFromObject(oRunTarget, TRUE, 50.0); return TRUE; } // 0 or more morale. return FALSE; } // This wrappers commonly used code for a "Call to arms" type response. // * We know of no enemy, so we will move to oAlly, who either called to // us, or, well, we know of. // * Calls out AI_SHOUT_CALL_TO_ARMS too. void CallToArmsResponse(object oAlly) { // Shout to allies to attack, or be prepared. AISpeakString(AI_SHOUT_CALL_TO_ARMS); // If we are over 2 meters away from oShouter, we move to them using // the special action if(GetDistanceToObject(oAlly) > 2.0 || !GetObjectSeen(oAlly)) { // New special action, but one that is overrided by combat location lAlly = GetLocation(oAlly); SetCurrentAction(AI_SPECIAL_ACTIONS_MOVE_TO_COMBAT); SetAIObject(AI_MOVE_TO_COMBAT_OBJECT, oAlly); SetAILocation(AI_MOVE_TO_COMBAT_LOCATION, lAlly); // Move to the location of the fight, attack. ClearAllActions(); // Move to the fights location ActionMoveToLocation(lAlly, TRUE); // When we see someone fighting, we'll DCR return; } else { // Determine it anyway - we will search around oShouter // if nothing is found...but we are near to the shouter DetermineCombatRound(oAlly); return; } } // This wrappers commonly used code for a "I was attacked" type response. // * We know there will be an enemy - or should be - and if we find one to attack // (using GetIntruderFromShout()) - we attack it (and call another I was attacked) // else, this will run CallToArmsResponse(oAlly); // * Calls out AI_SHOUT_I_WAS_ATTACKED, or AI_SHOUT_CALL_TO_ARMS too. void IWasAttackedResponse(object oAlly) { // Get the indruder. This is either who oShouter is currently attacking, // or the last attacker of them. object oIntruder = GetIntruderFromShout(oAlly); // If valid, of course attack! if(GetIsObjectValid(oIntruder)) { // 1.4 Note: // * It used to check "Are they seen". Basically, this is redudant // with the checks used in DetermineCombatRound(). It will do the // searching using oIntruder whatever. // Stop, and attack ClearAllActions(); DetermineCombatRound(oIntruder); // Shout I was attacked - we've set our intruder now AISpeakString(AI_SHOUT_I_WAS_ATTACKED); return; } // If invalid, we act as if it was "Call to arms" type thing. // Call to arms is better to use normally, of course. else { // Shout to allies to attack, or be prepared. AISpeakString(AI_SHOUT_CALL_TO_ARMS); // We see if they are attacking anything: oIntruder = GetAttackTarget(oAlly); if(!GetIsObjectValid(oIntruder)) { oIntruder = GetLocalObject(oAlly, AI_OBJECT + AI_LAST_MELEE_TARGET); } // If valid, we will move to a point bisecting the intruder and oAlly, or // move to oAlly. Should get interrupted once we see the attack target. // * NEED TO TEST if(GetIsObjectValid(oIntruder)) { // New special action, but one that is overrided by combat vector vTarget = GetPosition(oIntruder); vector vSource = GetPosition(OBJECT_SELF); vector vDirection = vTarget - vSource; float fDistance = VectorMagnitude(vDirection) / 2.0; vector vPoint = VectorNormalize(vDirection) * fDistance + vSource; location lTarget = Location(GetArea(OBJECT_SELF), vPoint, DIRECTION_NORTH); SetCurrentAction(AI_SPECIAL_ACTIONS_MOVE_TO_COMBAT); SetAIObject(AI_MOVE_TO_COMBAT_OBJECT, oAlly); SetAILocation(AI_MOVE_TO_COMBAT_LOCATION, lTarget); // Move to the location of the fight, attack. ClearAllActions(); // Move to the fights location ActionMoveToLocation(lTarget, TRUE); // When we see someone fighting, we'll DCR return; } // If we are over 2 meters away from oShouter, we move to them using // the special action else if(GetDistanceToObject(oAlly) > 2.0 || !GetObjectSeen(oAlly)) { // New special action, but one that is overrided by combat location lAlly = GetLocation(oAlly); SetCurrentAction(AI_SPECIAL_ACTIONS_MOVE_TO_COMBAT); SetAIObject(AI_MOVE_TO_COMBAT_OBJECT, oAlly); SetAILocation(AI_MOVE_TO_COMBAT_LOCATION, lAlly); // Move to the location of the fight, attack. ClearAllActions(); // Move to the fights location ActionMoveToLocation(lAlly, TRUE); // When we see someone fighting, we'll DCR return; } else { // Determine it anyway - we will search around oShouter // if nothing is found...but we are near to the shouter DetermineCombatRound(oAlly); return; } } } // Debug: To compile this script full, uncomment all of the below. /* - Add two "/"'s at the start of this line void main() { return; } //*/