include library.instance; include library.utils; const int MOVEMODE_INVALID = 0; const int MOVEMODE_FOLLOW = 1; const int MOVEMODE_RANDOM = 2; const int MOVEMODE_PREPARING_TO_MOVE = 3; const int MOVEMODE_PATHING_HOME = 4; const int MOVEMODE_PREPARING_TO_STAND = 5; const int MOVEMODE_MOVING_TO_HEAL = 7; const int MOVEMODE_IDLE = 9; const int MOVEMODE_FLEE = 10; const int MOVEMODE_FOLLOW_FORMATION = 11; const int MOVEMODE_MOVING_TO_SEE = 12; const int MOVEMODE_MOVING_TO_DEATHBLOW = 13; const int MOVEMODE_SWARM = 14; const int MOVEMOVE_EVADE = 15; ///////////////////////////////////////////////////////////////////////// // // MOVEMODE_INVALID // ///////////////////////////////////////////////////////////////////////// void aiClearMoveMode() { const obj_id self = getSelf(); //debugSpeakMsg(self, "Clear Movement."); deltadictionary dict = self.getScriptVars(); dict.put("ai.combat.moveMode", MOVEMODE_INVALID); } ///////////////////////////////////////////////////////////////////////// // // MOVEMODE_IDLE // ///////////////////////////////////////////////////////////////////////// void aiIdle() { if (aiIsIdle()) { return; } const obj_id self = getSelf(); // stop whatever the ai was doing // NOTE: Currently only doing this for patrols. suspendMovement() will work for all movement types, but there // may need to be extra code (setting/clearing scriptvars, for instance) written that I'm not aware of. SAJ if ( aiGetMovementState(self) == MOVEMENT_PATROL && !hasSuspendedMovement(self) ) { suspendMovement(self); } deltadictionary dict = self.getScriptVars(); dict.put("ai.combat.moveMode", MOVEMODE_IDLE); stop(self); } boolean aiIsIdle() { const obj_id self = getSelf(); const deltadictionary dict = self.getScriptVars(); return (dict.getInt("ai.combat.moveMode") == MOVEMODE_IDLE); } ///////////////////////////////////////////////////////////////////////// // // MOVEMODE_MOVING_TO_SEE // ///////////////////////////////////////////////////////////////////////// void aiMoveToSee(obj_id target) { if (aiIsMovingToSee(target)) { return; } const obj_id self = getSelf(); // stop whatever the ai was doing (see aiIdle() above) if ( aiGetMovementState(self) == MOVEMENT_PATROL && !hasSuspendedMovement(self) ) { suspendMovement(self); } deltadictionary dict = self.getScriptVars(); dict.put("ai.combat.moveMode", MOVEMODE_MOVING_TO_SEE); dict.put("ai.combat.moveToSee.target", target); pathTo(self, getLocation(target)); } boolean aiIsMovingToSee(obj_id target) { if(aiIsMovingToSee() && aiGetMoveToSeeTarget() == target) { return true; } return false; } boolean aiIsMovingToSee() { const obj_id self = getSelf(); const deltadictionary dict = self.getScriptVars(); return (dict.getInt("ai.combat.moveMode") == MOVEMODE_MOVING_TO_SEE); } obj_id aiGetMoveToSeeTarget() { const obj_id self = getSelf(); const deltadictionary dict = self.getScriptVars(); return dict.getObjId("ai.combat.moveToSee.target"); } ///////////////////////////////////////////////////////////////////////// // // MOVEMODE_MOVING_TO_DEATHBLOW // ///////////////////////////////////////////////////////////////////////// void aiMoveToDeathBlow(obj_id target) { if ( aiIsMovingToDeathBlow(target) || !isIncapacitated(target)) { return; } const obj_id self = getSelf(); // stop whatever the ai was doing (see aiIdle() above) if ( aiGetMovementState(self) == MOVEMENT_PATROL && !hasSuspendedMovement(self) ) { suspendMovement(self); } LOGC(aiLoggingEnabled(self) && !isIncapacitated(target), "debug_ai", "ai_combat_movement::aiMoveToDeathBlow() self(" + self + ":" + getName(self) + ") target(" + target + ") Trying to death blow a non-incapacitated target."); deltadictionary dict = self.getScriptVars(); dict.put("ai.combat.moveMode", MOVEMODE_MOVING_TO_DEATHBLOW); dict.put("ai.combat.moveToDeathBlow.target", target); pathTo(self, getLocation(target)); } boolean aiIsMovingToDeathBlow(obj_id target) { if ( (aiIsMovingToDeathBlow()) && (aiGetMoveToDeathBlowTarget() == target)) { return true; } return false; } boolean aiIsMovingToDeathBlow() { const obj_id self = getSelf(); const deltadictionary dict = self.getScriptVars(); return (dict.getInt("ai.combat.moveMode") == MOVEMODE_MOVING_TO_DEATHBLOW); } obj_id aiGetMoveToDeathBlowTarget() { const obj_id self = getSelf(); const deltadictionary dict = self.getScriptVars(); return dict.getObjId("ai.combat.moveToDeathBlow.target"); } ///////////////////////////////////////////////////////////////////////// // // MOVEMODE_FOLLOW // ///////////////////////////////////////////////////////////////////////// void aiFollow(obj_id target, float minDistance, float maxDistance) { if (aiIsFollowing(target, minDistance, maxDistance)) { return; } const obj_id self = getSelf(); // stop whatever the ai was doing (see aiIdle() above) if ( aiGetMovementState(self) == MOVEMENT_PATROL && !hasSuspendedMovement(self) ) { suspendMovement(self); } deltadictionary dict = self.getScriptVars(); dict.put("ai.combat.moveMode", MOVEMODE_FOLLOW); dict.put("ai.combat.follow.target", target); dict.put("ai.combat.follow.minDistance", minDistance); dict.put("ai.combat.follow.maxDistance", maxDistance); follow(self, target, minDistance, maxDistance); } void aiSwarm(obj_id target, float minDistance, float maxDistance) { if (aiIsSwarming(target, minDistance, maxDistance)) return; const obj_id self = getSelf(); // stop whatever the ai was doing (see aiIdle() above) if ( aiGetMovementState(self) == MOVEMENT_PATROL && !hasSuspendedMovement(self) ) { suspendMovement(self); } deltadictionary dict = self.getScriptVars(); dict.put("ai.combat.moveMode", MOVEMODE_SWARM); dict.put("ai.combat.follow.target", target); dict.put("ai.combat.follow.minDistance", minDistance); dict.put("ai.combat.follow.maxDistance", maxDistance); swarm(self,target ); } boolean aiIsFollowing(obj_id target, float minDistance, float maxDistance) { const obj_id self = getSelf(); const deltadictionary dict = self.getScriptVars(); if ( (dict.getInt("ai.combat.moveMode") == MOVEMODE_FOLLOW) && (dict.getObjId("ai.combat.follow.target") == target) && (dict.getFloat("ai.combat.follow.minDistance") == minDistance) && (dict.getFloat("ai.combat.follow.maxDistance") == maxDistance)) { return true; } return false; } boolean aiIsSwarming(obj_id target, float minDistance, float maxDistance) { const obj_id self = getSelf(); const deltadictionary dict = self.getScriptVars(); if ( (dict.getInt("ai.combat.moveMode") == MOVEMODE_SWARM) && (dict.getObjId("ai.combat.follow.target") == target) && (dict.getFloat("ai.combat.follow.minDistance") == minDistance) && (dict.getFloat("ai.combat.follow.maxDistance") == maxDistance)) { return true; } return false; } ///////////////////////////////////////////////////////////////////////// // // MOVEMODE_EVADE // ///////////////////////////////////////////////////////////////////////// float getActualEvadeChance(obj_id ai, float min, float max) { float rslt = max; float difference = max - min; float maxHealth = (float)getMaxHealth(ai); float curHealth = (float)getHealth(ai); rslt -= (difference * (curHealth / maxHealth)); return rslt; } // // Runs the AI to some location around the target, given the distances // @chance is a number between 1 and 0. if >=1 is passed in, the ai // will attempt an evasion attempt 100% of the time (not recommended as // it will make AI run around like Benny Hill during combat since the // movement code (including this function) is triggered by the server // every frame (up to 4 times a second) // boolean aiEvade(obj_id target, obj_id ai, range_info aiWeaponRanges, float minChance, float maxChance) { obj_id primaryWeapon = aiGetPrimaryWeapon(ai); obj_id secondaryWeapon = aiGetSecondaryWeapon(ai); float primaryRange = 0; float secondaryRange = 0; obj_id master = getMaster(ai); // Pets do not evade. if(isIdValid(master)) { return false; } // Am I in a heroic instance? Do not evade! if(hasScript(ai, "systems.dungeon_sequencer.ai_controller")) { return false; } if(isIdValid(primaryWeapon) && exists(primaryWeapon)) { const range_info primaryWeaponRange = aiGetWeaponRangeInfo(primaryWeapon); primaryRange = primaryWeaponRange.maxRange; } if(isIdValid(secondaryWeapon) && exists(secondaryWeapon)) { const range_info secondaryWeaponRange = aiGetWeaponRangeInfo(secondaryWeapon); secondaryRange = secondaryWeaponRange.maxRange; } if(primaryRange <= 10f && secondaryRange <= 10f) { return false; // Melee doesn't evade. } const int now = getGameTime(); const int then = utils.getIntScriptVar(ai, "aiIsEvading"); if(then != 0 && (now - then) < 15) { return false; } float minDistance = 0; // min distance to run away float maxDistance = 1f; // max distance to run away if(getTopMostContainer(ai) == ai) // outside with a gun { minDistance = 15f; maxDistance = 20f; } else // inside with a gun { minDistance = 4f; maxDistance = 10f; } if(isPlayer(target)) { float mod = 0; range_info playerRange = getWeaponRangeInfo(getCurrentWeapon(target)); if(playerRange != null && playerRange.maxRange < 10) { if(minChance == 1f) { mod = 0.985f; // when fighting a player with a melee weapon, less chance to evade, even if player is too close } else { mod = 0.9f; } minDistance = 5f; if(rand(1, 100) < 30) // n% chance to run out of player's range { maxDistance = playerRange.maxRange + 5; } else { maxDistance = playerRange.maxRange - 1; } if(minDistance > maxDistance) { minDistance = maxDistance; } } maxChance -= (maxChance * mod); if(minChance > maxChance) { minChance = maxChance; } //debugSpeakMsg(ai, "MinEvade=" + minChance + " MaxEvade=" + maxChance); } float chance = getActualEvadeChance(ai, minChance, maxChance); if(chance < 1f && rand(0.0f, 1.0f) > chance) { return false; } // Get valid away location gets a location from current location to a radius of minDistance to maxDistance. // If the location can not be seen by the target, then it returns null. // The away location is not away from the target. It is a random circular location from its current location. location evasionTargetLoc = getValidAwayLocation(target, minDistance, maxDistance); if(evasionTargetLoc == null || getLocation(ai).cell != evasionTargetLoc.cell) { return false; } // If the ai cannot see the location, then return false. The target has already checked its visibility. if(!canSee(ai, evasionTargetLoc)) { return false; } utils.setScriptVar(ai, "aiIsEvading", getGameTime()); pathTo(ai, evasionTargetLoc); faceTo(ai, target); return true; } // // this function will fail frequently (return null), but its designed to be called potentially every // frame; so frames where failures are returned are just discarded // location getValidAwayLocation(obj_id mob, float minDistance, float maxDistance) { location from = getLocation(mob); //debugSpeakMsg(mob, "" + minDistance + "/" + maxDistance); location evasionTarget = utils.getRandomAwayLocation(from, minDistance, maxDistance); evasionTarget.y += 0.5f; // have to do this because canSee will fail due to ground geometry collision otherwise if(!canSee(mob, evasionTarget)) { //playClientEffectLoc(target, "appearance/" + "pt_fireworks_01.prt", evasionTarget, 0.0f); //debugSpeakMsg(mob, "Can't see evasion target. Not Evading"); return null; } evasionTarget.y -= 0.5f; return evasionTarget; } ///////////////////////////////////////////////////////////////////////// // // MOVEMODE_FLEE // ///////////////////////////////////////////////////////////////////////// void aiFlee(obj_id target, float distance) { if (aiIsFleeing(target, distance)) { return; } const obj_id self = getSelf(); // stop whatever the ai was doing (see aiIdle() above) if ( aiGetMovementState(self) == MOVEMENT_PATROL && !hasSuspendedMovement(self) ) { suspendMovement(self); } deltadictionary dict = self.getScriptVars(); dict.put("ai.combat.moveMode", MOVEMODE_FLEE); dict.put("ai.combat.flee.target", target); dict.put("ai.combat.flee.distance", distance); flee(self, target, distance, distance); } boolean aiIsFleeing(obj_id ai) { const deltadictionary dict = ai.getScriptVars(); return dict.getInt("ai.combat.moveMode") == MOVEMODE_FLEE; } boolean aiIsFleeing(obj_id target, float distance) { const obj_id self = getSelf(); const deltadictionary dict = self.getScriptVars(); if ( (dict.getInt("ai.combat.moveMode") == MOVEMODE_FLEE) && (dict.getObjId("ai.combat.flee.target") == target) && (dict.getFloat("ai.combat.flee.distance") == distance)) { return true; } return false; } //Additional Move Modes boolean isAiImmobile(obj_id subject) { return hasObjVar(subject, "isImmobile") ? getBooleanObjVar(subject, "isImmobile") : false; }