// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
//      Magicbane Emulator Project © 2013 - 2022
//                www.magicbane.com


package engine.ai;


import engine.Enum;
import engine.Enum.DispatchChannel;
import engine.Enum.GameObjectType;
import engine.InterestManagement.WorldGrid;
import engine.ai.utilities.CombatUtilities;
import engine.ai.utilities.MovementUtilities;
import engine.gameManager.*;
import engine.math.Vector3fImmutable;
import engine.net.DispatchMessage;
import engine.net.client.msg.PerformActionMsg;
import engine.net.client.msg.PowerProjectileMsg;
import engine.net.client.msg.UpdateStateMsg;
import engine.objects.*;
import engine.powers.ActionsBase;
import engine.powers.PowersBase;
import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger;

import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;

import static engine.math.FastMath.sqr;
import static java.lang.Math.sqrt;

public class MobileFSM {

    public enum STATE {
        Disabled,
        Respawn,
        Idle,
        Awake,
        Aggro,
        Patrol,
        Help,
        Attack,
        Home,
        Dead,
        Recalling,
        Retaliate,
        Chase
    }

    public static void run(Mob mob) {
        if (mob == null) {
            return;
        }
        STATE state = mob.getState();
        switch (state) {
            case Idle:
                if (mob.isAlive())
                    mob.updateLocation();
                if (mob.isPlayerGuard()) {
                    guardAwake(mob);
                    break;
                }
                idle(mob);
                break;
            case Awake:
                if (mob.isAlive())
                    mob.updateLocation();

                if (mob.isPlayerGuard())
                    guardAwake(mob);
                else if (mob.isSiege() == false) {
                    if (mob.isPet())
                        petAwake(mob);
                    else if (mob.isGuard())
                        awakeNPCguard(mob);
                    else
                        awake(mob);
                }
                break;
            case Aggro:
                if (mob.isAlive())
                    mob.updateLocation();

                if (mob.isPlayerGuard())
                    guardAggro(mob, mob.getAggroTargetID());
                else
                    aggro(mob, mob.getAggroTargetID());
                break;
            case Patrol:

                if (mob.isAlive())
                    mob.updateLocation();

                if (mob.isPlayerGuard())
                    guardPatrol(mob);
                else
                    patrol(mob);
                break;
            case Attack:
                if (mob.isAlive())
                    mob.updateLocation();


                if (!mob.isCombat()) {
                    mob.setCombat(true);
                    UpdateStateMsg rwss = new UpdateStateMsg();
                    rwss.setPlayer(mob);
                    DispatchMessage.sendToAllInRange(mob, rwss);
                }
                if (mob.isPlayerGuard())
                    guardAttack(mob);
                else if (mob.isPet() || mob.isSiege())
                    petAttack(mob);
                else if (mob.isGuard())
                    guardAttackMob(mob);
                else
                    mobAttack(mob);
                break;
            case Home:
                if (mob.isPlayerGuard())
                    guardHome(mob, mob.isWalkingHome());
                else
                    home(mob, mob.isWalkingHome());
                break;
            case Dead:
                dead(mob);
                break;
            case Respawn:
                respawn(mob);
                break;
            case Recalling:
                recalling(mob);
                break;
            case Retaliate:
                retaliate(mob);
                break;
            case Chase:
                handleMobChase(mob);
                break;
        }
    }

    public static boolean setAwake(Mob aiAgent, boolean force) {
        if (force) {
            aiAgent.setState(STATE.Awake);
            return true;
        }
        if (aiAgent.getState() == STATE.Idle) {
            aiAgent.setState(STATE.Awake);
            return true;
        }
        return false;
    }

    public static boolean setAggro(Mob aiAgent, int targetID) {
        if (aiAgent.getState() != STATE.Dead) {
            aiAgent.setNoAggro(false);
            aiAgent.setAggroTargetID(targetID);
            aiAgent.setState(STATE.Aggro);
            return true;
        }
        return false;
    }
    private static void idle(Mob mob) {

        if (mob.getLoc().distanceSquared2D(mob.getBindLoc()) > sqr(2000)) {

            mob.setWalkingHome(false);
            mob.setState(STATE.Home);
        }
    }
    private static void awake(Mob aiAgent) {
        if (!aiAgent.isAlive()) {
            aiAgent.setState(STATE.Dead);
            return;
        }

        if (aiAgent.getLoc().distanceSquared2D(aiAgent.getBindLoc()) > sqr(2000)) {
            aiAgent.setWalkingHome(false);
            aiAgent.setState(STATE.Home);
            return;
        }
        //Don't attempt to aggro if No aggro is on and aiAgent is not home yet.
        if (aiAgent.isNoAggro() && aiAgent.isMoving()) {
            return;
        }

        //Mob stopped Moving let's turn aggro back on.
        if (aiAgent.isNoAggro()) {
            aiAgent.setNoAggro(false);
        }
        //no players currently have this mob loaded. return to IDLE.
        if (aiAgent.getPlayerAgroMap().isEmpty()) {
            aiAgent.setState(STATE.Idle);
            return;
        }


        //currently npc guards wont patrol or aggro
        if (aiAgent.isGuard()) {
            return;
        }

        //Get the Map for Players that loaded this mob.

        ConcurrentHashMap<Integer, Boolean> loadedPlayers = aiAgent.getPlayerAgroMap();


        if (!Enum.MobFlagType.AGGRESSIVE.elementOf(aiAgent.getMobBase().getFlags()) && aiAgent.getCombatTarget() == null) {
            //attempt to patrol even if aiAgent isn't aggresive;

            int patrolRandom = ThreadLocalRandom.current().nextInt(1000);
            if (patrolRandom <= MBServerStatics.AI_PATROL_DIVISOR) {
                aiAgent.setState(STATE.Patrol);
            }
            return;
        }
        //aiAgent finished moving home, set aggro on.

        for (Entry playerEntry : loadedPlayers.entrySet()) {
            int playerID = (int) playerEntry.getKey();
            PlayerCharacter loadedPlayer = PlayerCharacter.getFromCache(playerID);

            //Player is null, let's remove them from the list.
            if (loadedPlayer == null) {
                //     Logger.error("MobileFSM", "Player with UID " + playerID + " returned null in mob.getPlayerAgroMap()");
                loadedPlayers.remove(playerID);
                continue;
            }
            //Player is Dead, Mob no longer needs to attempt to aggro. Remove them from aggro map.
            if (!loadedPlayer.isAlive()) {
                loadedPlayers.remove(playerID);
                continue;
            }
            //Can't see target, skip aggro.
            if (!aiAgent.canSee(loadedPlayer)) {
                continue;
            }

            // No aggro for this race type
            if (loadedPlayer.getRace().getRaceType().getAggroType().elementOf(aiAgent.getMobBase().getNoAggro()))
                continue;


            if (MovementUtilities.inRangeToAggro(aiAgent, loadedPlayer)) {
                aiAgent.setAggroTargetID(playerID);
                aiAgent.setState(STATE.Aggro);
                return;
            }


        }

        int patrolRandom = ThreadLocalRandom.current().nextInt(1000);
        if (patrolRandom <= MBServerStatics.AI_PATROL_DIVISOR) {
            aiAgent.setState(STATE.Patrol);
        }

    }
    private static void guardAttackMob(Mob aiAgent) {
        if (!aiAgent.isAlive()) {
            aiAgent.setState(STATE.Dead);
            return;
        }

        AbstractGameObject target = aiAgent.getCombatTarget();
        if (target == null) {
            aiAgent.setState(STATE.Awake);
            return;
        }

        if (target.getObjectType().equals(GameObjectType.Mob) == false) {
            aiAgent.setState(STATE.Awake);
            return;
        }

        if (target.equals(aiAgent)) {
            aiAgent.setState(STATE.Awake);
            return;
        }

        Mob mob = (Mob) target;

        if (!mob.isAlive() || mob.getState() == STATE.Dead) {
            aiAgent.setCombatTarget(null);
            aiAgent.setState(STATE.Awake);
            return;
        }

        if (CombatUtilities.inRangeToAttack(aiAgent, mob)) {
            //not time to attack yet.
            if (System.currentTimeMillis() < aiAgent.getLastAttackTime()) {
                return;
            }

            if (!CombatUtilities.RunAIRandom())
                return;

            if (aiAgent.getRange() >= 30 && aiAgent.isMoving())
                return;
            //no weapons, defualt mob attack speed 3 seconds.
            ItemBase mainHand = aiAgent.getWeaponItemBase(true);
            ItemBase offHand = aiAgent.getWeaponItemBase(false);
            if (mainHand == null && offHand == null) {
                CombatUtilities.combatCycle(aiAgent, mob, true, null);
                int delay = 3000;
                if (aiAgent.isSiege())
                    delay = 11000;
                aiAgent.setLastAttackTime(System.currentTimeMillis() + delay);

            } else
                //TODO set offhand attack time.
                if (aiAgent.getWeaponItemBase(true) != null) {
                    int attackDelay = 3000;
                    if (aiAgent.isSiege())
                        attackDelay = 11000;
                    CombatUtilities.combatCycle(aiAgent, mob, true, aiAgent.getWeaponItemBase(true));
                    aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);
                } else if (aiAgent.getWeaponItemBase(false) != null) {
                    int attackDelay = (int) (aiAgent.getSpeedHandTwo() * 100);
                    if (aiAgent.isSiege())
                        attackDelay = 3000;
                    CombatUtilities.combatCycle(aiAgent, mob, false, aiAgent.getWeaponItemBase(false));
                    aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);
                }
            return;

        }
        if (!MovementUtilities.updateMovementToCharacter(aiAgent, mob))
            return;

        if (!MovementUtilities.canMove(aiAgent))
            return;

        double WeaponRange = aiAgent.getEquip().get(0).getItemBase().getRange();
        if (CombatUtilities.inRange2D(aiAgent, mob, WeaponRange))
            return;


        aiAgent.destination = MovementUtilities.GetDestinationToCharacter(aiAgent, mob);

        MovementUtilities.moveToLocation(aiAgent, aiAgent.destination, aiAgent.getRange());
    }
    private static void awakeNPCguard(Mob aiAgent) {
        if (!aiAgent.isAlive()) {
            aiAgent.setState(STATE.Dead);
            return;
        }

        // Player guards are bound to their city zone
        // and recall when leaving it.

        if (aiAgent.getLoc().distanceSquared2D(aiAgent.getBindLoc()) > sqr(2000)) {
            aiAgent.setWalkingHome(false);
            aiAgent.setState(STATE.Home);
            return;
        }

        //Don't attempt to aggro if No aggro is on and aiAgent is not home yet.
        //no players currently have this mob loaded. return to IDLE.
        //currently npc guards wont patrol or aggro
        //Get the Map for Players that loaded this mob.

        HashSet<AbstractWorldObject> awoList = WorldGrid.getObjectsInRangePartial(aiAgent, 100, MBServerStatics.MASK_MOB);

        for (AbstractWorldObject awoMob : awoList) {

            //dont scan self.
            if (aiAgent.equals(awoMob))
                continue;

            Mob mob = (Mob) awoMob;
            //dont attack other guards
            if (mob.isGuard())
                continue;
            if (aiAgent.getLoc().distanceSquared2D(mob.getLoc()) > sqr(50))
                continue;
            aiAgent.setCombatTarget(mob);
            aiAgent.setState(STATE.Attack);
        }
    }
    private static void petAwake(Mob aiAgent) {

        if (!aiAgent.isAlive()) {
            aiAgent.setState(STATE.Dead);
            return;
        }

        PlayerCharacter petOwner = aiAgent.getOwner();

        if (petOwner == null)
            return;

        //lets make mobs ai less twitchy, Don't call another movement until mob reaches it's destination.
        if (aiAgent.isMoving())
            return;

        if (!MovementUtilities.canMove(aiAgent))
            return;

        if (petOwner.getLoc().distanceSquared2D(aiAgent.getLoc()) > MBServerStatics.AI_RECALL_RANGE * MBServerStatics.AI_RECALL_RANGE) {
            aiAgent.teleport(petOwner.getLoc());
            return;
        }

        if (petOwner.getLoc().distanceSquared2D(aiAgent.getLoc()) > 30 * 30) {
            if (aiAgent.isMoving())
                return;

            if (!MovementUtilities.canMove(aiAgent))
                return;
            if (aiAgent.getLoc().distanceSquared2D(petOwner.getLoc()) < aiAgent.getRange() * aiAgent.getRange())
                return;

            MovementUtilities.moveToLocation(aiAgent, petOwner.getLoc(), aiAgent.getRange());
        }
    }
    private static void aggro(Mob aiAgent, int targetID) {

        if (!aiAgent.isAlive()) {
            aiAgent.setState(STATE.Dead);
            return;
        }

        if (aiAgent.getLoc().distanceSquared2D(aiAgent.getBindLoc()) > sqr(2000)) {
            aiAgent.setWalkingHome(false);
            aiAgent.setState(STATE.Home);
            return;
        }

        if (!aiAgent.isCombat()) {
            aiAgent.setCombat(true);
            UpdateStateMsg rwss = new UpdateStateMsg();
            rwss.setPlayer(aiAgent);
            DispatchMessage.sendToAllInRange(aiAgent, rwss);
        }

        //a player got in aggro range. Move to player until in range of attack.
        PlayerCharacter aggroTarget = PlayerCharacter.getFromCache(targetID);

        if (aggroTarget == null) {
            // Logger.error("MobileFSM.aggro", "aggro target with UUID " + targetID + " returned null");
            aiAgent.getPlayerAgroMap().remove(targetID);
            aiAgent.setAggroTargetID(0);
            aiAgent.setState(STATE.Patrol);
            return;
        }
        if (!aiAgent.canSee(aggroTarget)) {
            aiAgent.setCombatTarget(null);
            targetID = 0;
            aiAgent.setState(STATE.Patrol);
            return;
        }

        if (!aggroTarget.isActive()) {
            aiAgent.setCombatTarget(null);
            targetID = 0;
            aiAgent.setState(STATE.Patrol);
            return;
        }
        aiAgent.setCombatTarget(aggroTarget);
        if (canCast(aiAgent) == true) {
            if (MobCast(aiAgent) == false) {
                attack(aiAgent, targetID);
            }
        } else if (CombatUtilities.inRange2D(aiAgent, aggroTarget, aiAgent.getRange())) {
            aiAgent.setState(STATE.Attack);
            attack(aiAgent, targetID);
            return;
        }

        if (!MovementUtilities.inRangeDropAggro(aiAgent, aggroTarget)) {
            aiAgent.setAggroTargetID(0);
            aiAgent.setCombatTarget(null);
            MovementUtilities.moveToLocation(aiAgent, aiAgent.getTrueBindLoc(), 0);
            aiAgent.setState(STATE.Awake);
            return;
        }

        if (!MovementUtilities.inRangeOfBindLocation(aiAgent)) {
            aiAgent.setCombatTarget(null);
            aiAgent.setAggroTargetID(0);
            aiAgent.setState(STATE.Home);
            return;
        }

        //use this so mobs dont continue to try to move if they are underneath a flying target. only use 2D range check.
        if (CombatUtilities.inRangeToAttack2D(aiAgent, aggroTarget))
            return;

        if (!MovementUtilities.updateMovementToCharacter(aiAgent, aggroTarget))
            return;

        if (!MovementUtilities.canMove(aiAgent))
            return;

        if (aiAgent.getLoc().distanceSquared2D(aggroTarget.getLoc()) < aiAgent.getRange() * aiAgent.getRange())
            return;

        aiAgent.destination = MovementUtilities.GetDestinationToCharacter(aiAgent, aggroTarget);
        MovementUtilities.moveToLocation(aiAgent, aiAgent.destination, aiAgent.getRange());

    }
    private static void petAttack(Mob aiAgent) {

        if (!aiAgent.isAlive()) {
            aiAgent.setState(STATE.Dead);
            return;
        }

        AbstractGameObject target = aiAgent.getCombatTarget();

        if (target == null) {
            aiAgent.setState(STATE.Awake);
            return;
        }

        switch (target.getObjectType()) {

            case PlayerCharacter:

                PlayerCharacter player = (PlayerCharacter) target;

                if (!player.isActive()) {
                    aiAgent.setCombatTarget(null);
                    aiAgent.setState(STATE.Awake);
                    return;
                }

                if (player.inSafeZone()) {
                    aiAgent.setCombatTarget(null);
                    aiAgent.setState(STATE.Awake);
                    return;
                }

                handlePlayerAttackForPet(aiAgent, player);

                break;
            case Building:
                Building building = (Building) target;
                petHandleBuildingAttack(aiAgent, building);
                break;
            case Mob:
                Mob mob = (Mob) target;
                handleMobAttackForPet(aiAgent, mob);
                break;
        }
    }
    private static void mobAttack(Mob aiAgent) {

        if (!aiAgent.isAlive()) {
            aiAgent.setState(STATE.Dead);
            return;
        }

        if (aiAgent.getLoc().distanceSquared2D(aiAgent.getBindLoc()) > sqr(2000)) {

            aiAgent.setWalkingHome(false);
            aiAgent.setState(STATE.Home);
            return;
        }

        AbstractGameObject target = aiAgent.getCombatTarget();

        if (target == null) {
            aiAgent.setState(STATE.Patrol);
            return;
        }

        switch (target.getObjectType()) {

            case PlayerCharacter:

                PlayerCharacter player = (PlayerCharacter) target;

                if (!player.isActive()) {
                    aiAgent.setCombatTarget(null);
                    aiAgent.setState(STATE.Patrol);
                    return;
                }

                if (aiAgent.isNecroPet() && player.inSafeZone()) {
                    aiAgent.setCombatTarget(null);
                    aiAgent.setState(STATE.Idle);
                    return;
                }
                if (canCast(aiAgent) == true) {
                    if (MobCast(aiAgent) == false) {
                        handlePlayerAttackForMob(aiAgent, player);
                    }
                } else {
                    handlePlayerAttackForMob(aiAgent, player);
                }
                break;
            case Building:
                Building building = (Building) target;
                petHandleBuildingAttack(aiAgent, building);
                break;
            case Mob:
                Mob mob = (Mob) target;
                handleMobAttackForMob(aiAgent, mob);
        }
    }
    private static void petHandleBuildingAttack(Mob aiAgent, Building building) {

        int buildingHitBox = (int) CombatManager.calcHitBox(building);

        if (building.getRank() == -1) {
            aiAgent.setCombatTarget(null);
            aiAgent.setState(STATE.Awake);
            return;
        }

        if (!building.isVulnerable()) {
            aiAgent.setCombatTarget(null);
            aiAgent.setState(STATE.Awake);
            return;
        }

        if (BuildingManager.getBuildingFromCache(building.getObjectUUID()) == null) {
            aiAgent.setCombatTarget(null);
            aiAgent.setState(STATE.Awake);
            return;
        }

        if (building.getParentZone() != null && building.getParentZone().isPlayerCity()) {

            for (Mob mob : building.getParentZone().zoneMobSet) {

                if (!mob.isPlayerGuard())
                    continue;

                if (mob.getCombatTarget() != null)
                    continue;

                if (mob.getGuild() != null && building.getGuild() != null)
                    if (!Guild.sameGuild(mob.getGuild().getNation(), building.getGuild().getNation()))
                        continue;

                mob.setCombatTarget(aiAgent);
                mob.setState(STATE.Attack);
            }
        }

        if (CombatUtilities.inRangeToAttack(aiAgent, building)) {
            //not time to attack yet.

            if (!CombatUtilities.RunAIRandom())
                return;

            if (System.currentTimeMillis() < aiAgent.getLastAttackTime())
                return;

            if (aiAgent.getRange() >= 30 && aiAgent.isMoving())
                return;

            //reset attack animation
            if (aiAgent.isSiege())
                MovementManager.sendRWSSMsg(aiAgent);

            //			Fire siege balls
            //			 TODO: Fix animations not following stone

            //no weapons, defualt mob attack speed 3 seconds.
            ItemBase mainHand = aiAgent.getWeaponItemBase(true);
            ItemBase offHand = aiAgent.getWeaponItemBase(false);

            if (mainHand == null && offHand == null) {

                CombatUtilities.combatCycle(aiAgent, building, true, null);
                int delay = 3000;

                if (aiAgent.isSiege())
                    delay = 15000;

                aiAgent.setLastAttackTime(System.currentTimeMillis() + delay);
            } else
                //TODO set offhand attack time.
                if (aiAgent.getWeaponItemBase(true) != null) {

                    int attackDelay = 3000;

                    if (aiAgent.isSiege())
                        attackDelay = 15000;

                    CombatUtilities.combatCycle(aiAgent, building, true, aiAgent.getWeaponItemBase(true));
                    aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);

                } else if (aiAgent.getWeaponItemBase(false) != null) {

                    int attackDelay = 3000;

                    if (aiAgent.isSiege())
                        attackDelay = 15000;

                    CombatUtilities.combatCycle(aiAgent, building, false, aiAgent.getWeaponItemBase(false));
                    aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);
                }

            if (aiAgent.isSiege()) {
                PowerProjectileMsg ppm = new PowerProjectileMsg(aiAgent, building);
                ppm.setRange(50);
                DispatchMessage.dispatchMsgToInterestArea(aiAgent, ppm, DispatchChannel.SECONDARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
            }
            return;
        }

        //Outside of attack Range, Move to players predicted loc.

        if (!aiAgent.isMoving())
            if (MovementUtilities.canMove(aiAgent))
                MovementUtilities.moveToLocation(aiAgent, building.getLoc(), aiAgent.getRange() + buildingHitBox);
    }
    private static void handlePlayerAttackForPet(Mob aiAgent, PlayerCharacter player) {

        if (aiAgent.getMobBase().getSeeInvis() < player.getHidden()) {
            aiAgent.setCombatTarget(null);
            aiAgent.setState(STATE.Awake);
            return;
        }

        if (!player.isAlive()) {
            aiAgent.setCombatTarget(null);
            aiAgent.setState(STATE.Awake);
            return;
        }

        if (CombatUtilities.inRangeToAttack(aiAgent, player)) {
            //not time to attack yet.
            if (System.currentTimeMillis() < aiAgent.getLastAttackTime())
                return;

            if (!CombatUtilities.RunAIRandom())
                return;

            if (aiAgent.getRange() >= 30 && aiAgent.isMoving())
                return;
            // add timer for last attack.
            //player.setTimeStamp("LastCombatPlayer", System.currentTimeMillis());
            //no weapons, defualt mob attack speed 3 seconds.
            ItemBase mainHand = aiAgent.getWeaponItemBase(true);
            ItemBase offHand = aiAgent.getWeaponItemBase(false);

            if (mainHand == null && offHand == null) {

                CombatUtilities.combatCycle(aiAgent, player, true, null);

                int delay = 3000;

                if (aiAgent.isSiege())
                    delay = 11000;

                aiAgent.setLastAttackTime(System.currentTimeMillis() + delay);
            }
            //TODO set offhand attack time.

            if (aiAgent.getWeaponItemBase(true) != null) {

                int attackDelay = 3000;

                if (aiAgent.isSiege())
                    attackDelay = 11000;

                CombatUtilities.combatCycle(aiAgent, player, true, aiAgent.getWeaponItemBase(true));
                aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);

            } else if (aiAgent.getWeaponItemBase(false) != null) {

                int attackDelay = (int) (aiAgent.getSpeedHandTwo() * 100);

                if (aiAgent.isSiege())
                    attackDelay = 3000;

                CombatUtilities.combatCycle(aiAgent, player, false, aiAgent.getWeaponItemBase(false));
                aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);
            }
            return;
        }

        if (!MovementUtilities.updateMovementToCharacter(aiAgent, player))
            return;

        //out of range to attack move
        if (!MovementUtilities.canMove(aiAgent))
            return;

        aiAgent.destination = MovementUtilities.GetDestinationToCharacter(aiAgent, player);
        MovementUtilities.moveToLocation(aiAgent, aiAgent.destination, aiAgent.getRange());
    }
    private static void handlePlayerAttackForMob(Mob aiAgent, PlayerCharacter player) {

        if (aiAgent.getMobBase().getSeeInvis() < player.getHidden()) {
            aiAgent.setCombatTarget(null);
            aiAgent.setState(STATE.Awake);
            return;
        }

        if (!player.isAlive()) {
            aiAgent.setCombatTarget(null);
            aiAgent.setState(STATE.Awake);
            return;
        }

        if (aiAgent.getMobBase().getFlags().contains(Enum.MobFlagType.CALLSFORHELP)) {
            MobCallForHelp(aiAgent);
        }
        if (!MovementUtilities.inRangeOfBindLocation(aiAgent)) {
            aiAgent.setCombatTarget(null);
            aiAgent.setAggroTargetID(0);
            aiAgent.setWalkingHome(false);
            aiAgent.setState(STATE.Home);
            return;
        }
        if (!MovementUtilities.inRangeDropAggro(aiAgent, player)) {
            aiAgent.setAggroTargetID(0);
            aiAgent.setCombatTarget(null);
            MovementUtilities.moveToLocation(aiAgent, aiAgent.getTrueBindLoc(), 0);
            aiAgent.setState(STATE.Awake);
            return;
        }
        if (CombatUtilities.inRange2D(aiAgent, player, aiAgent.getRange())) {

            //no weapons, defualt mob attack speed 3 seconds.

            if (System.currentTimeMillis() < aiAgent.getLastAttackTime())
                return;

            //if (!CombatUtilities.RunAIRandom())
            //    return;

            // ranged mobs cant attack while running. skip until they finally stop.
            //if (aiAgent.getRange() >= 30 && aiAgent.isMoving())
            if(aiAgent.isMoving())
                return;

            // add timer for last attack.
            //	player.setTimeStamp("LastCombatPlayer", System.currentTimeMillis());
            ItemBase mainHand = aiAgent.getWeaponItemBase(true);
            ItemBase offHand = aiAgent.getWeaponItemBase(false);

            if (mainHand == null && offHand == null) {

                CombatUtilities.combatCycle(aiAgent, player, true, null);
                int delay = 3000;

                if (aiAgent.isSiege())
                    delay = 11000;

                aiAgent.setLastAttackTime(System.currentTimeMillis() + delay);

            } else
                //TODO set offhand attack time.
                if (aiAgent.getWeaponItemBase(true) != null) {

                    int delay = 3000;

                    if (aiAgent.isSiege())
                        delay = 11000;

                    CombatUtilities.combatCycle(aiAgent, player, true, aiAgent.getWeaponItemBase(true));
                    aiAgent.setLastAttackTime(System.currentTimeMillis() + delay);
                } else if (aiAgent.getWeaponItemBase(false) != null) {

                    int attackDelay = 3000;

                    if (aiAgent.isSiege())
                        attackDelay = 11000;
                    if (aiAgent.getMobBase().getFlags().contains(Enum.MobFlagType.CALLSFORHELP)) {
                        MobCallForHelp(aiAgent);
                    }
                    CombatUtilities.combatCycle(aiAgent, player, false, aiAgent.getWeaponItemBase(false));
                    aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);
                }
            return;
        }

        if (!MovementUtilities.updateMovementToCharacter(aiAgent, player))
            return;

        if (!MovementUtilities.canMove(aiAgent))
            return;

        //this stops mobs from attempting to move while they are underneath a player.
        if (CombatUtilities.inRangeToAttack2D(aiAgent, player))
            return;
        //set mob to pursue target
        aiAgent.setState(MobileFSM.STATE.Chase);
    }
    private static void handleMobAttackForPet(Mob aiAgent, Mob mob) {

        if (!mob.isAlive()) {
            aiAgent.setCombatTarget(null);
            aiAgent.setState(STATE.Awake);
            return;
        }

        if (CombatUtilities.inRangeToAttack(aiAgent, mob)) {
            //not time to attack yet.
            if (System.currentTimeMillis() < aiAgent.getLastAttackTime())
                return;

            if (!CombatUtilities.RunAIRandom())
                return;

            if (aiAgent.getRange() >= 30 && aiAgent.isMoving())
                return;

            //no weapons, defualt mob attack speed 3 seconds.
            ItemBase mainHand = aiAgent.getWeaponItemBase(true);
            ItemBase offHand = aiAgent.getWeaponItemBase(false);

            if (mainHand == null && offHand == null) {

                CombatUtilities.combatCycle(aiAgent, mob, true, null);

                int delay = 3000;

                if (aiAgent.isSiege())
                    delay = 11000;

                aiAgent.setLastAttackTime(System.currentTimeMillis() + delay);

            } else
                //TODO set offhand attack time.
                if (aiAgent.getWeaponItemBase(true) != null) {

                    int attackDelay = 3000;

                    if (aiAgent.isSiege())
                        attackDelay = 11000;

                    CombatUtilities.combatCycle(aiAgent, mob, true, aiAgent.getWeaponItemBase(true));
                    aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);

                } else if (aiAgent.getWeaponItemBase(false) != null) {

                    int attackDelay = (int) (aiAgent.getSpeedHandTwo() * 100);

                    if (aiAgent.isSiege())
                        attackDelay = 3000;

                    CombatUtilities.combatCycle(aiAgent, mob, false, aiAgent.getWeaponItemBase(false));
                    aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);
                }
            return;
        }

        if (!MovementUtilities.updateMovementToCharacter(aiAgent, mob))
            return;

        if (!MovementUtilities.canMove(aiAgent))
            return;

        if (CombatUtilities.inRangeToAttack2D(aiAgent, mob))
            return;

        aiAgent.destination = MovementUtilities.GetDestinationToCharacter(aiAgent, mob);
        MovementUtilities.moveToLocation(aiAgent, aiAgent.destination, aiAgent.getRange());
    }
    private static void handleMobAttackForMob(Mob aiAgent, Mob mob) {


        if (!mob.isAlive()) {
            aiAgent.setCombatTarget(null);
            aiAgent.setState(STATE.Awake);
            return;
        }

        if (CombatUtilities.inRangeToAttack(aiAgent, mob)) {
            //not time to attack yet.
            if (System.currentTimeMillis() < aiAgent.getLastAttackTime()) {
                return;
            }

            if (!CombatUtilities.RunAIRandom())
                return;

            if (aiAgent.getRange() >= 30 && aiAgent.isMoving())
                return;
            //no weapons, defualt mob attack speed 3 seconds.
            ItemBase mainHand = aiAgent.getWeaponItemBase(true);
            ItemBase offHand = aiAgent.getWeaponItemBase(false);

            if (mainHand == null && offHand == null) {

                CombatUtilities.combatCycle(aiAgent, mob, true, null);
                int delay = 3000;

                if (aiAgent.isSiege())
                    delay = 11000;

                aiAgent.setLastAttackTime(System.currentTimeMillis() + delay);
            } else
                //TODO set offhand attack time.
                if (aiAgent.getWeaponItemBase(true) != null) {

                    int attackDelay = 3000;

                    if (aiAgent.isSiege())
                        attackDelay = 11000;

                    CombatUtilities.combatCycle(aiAgent, mob, true, aiAgent.getWeaponItemBase(true));
                    aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);

                } else if (aiAgent.getWeaponItemBase(false) != null) {

                    int attackDelay = 3000;

                    if (aiAgent.isSiege())
                        attackDelay = 11000;

                    CombatUtilities.combatCycle(aiAgent, mob, false, aiAgent.getWeaponItemBase(false));
                    aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);
                }
            return;
        }

        //use this so mobs dont continue to try to move if they are underneath a flying target. only use 2D range check.
        if (CombatUtilities.inRangeToAttack2D(aiAgent, mob))
            return;

        if (!MovementUtilities.updateMovementToCharacter(aiAgent, mob))
            return;

        //out of range to attack move
        if (!MovementUtilities.canMove(aiAgent))
            return;

        aiAgent.destination = MovementUtilities.GetDestinationToCharacter(aiAgent, mob);
        MovementUtilities.moveToLocation(aiAgent, aiAgent.destination, aiAgent.getRange());
    }
    private static void attack(Mob aiAgent, int targetID) {

        //in range to attack, start attacking now!
        if (!aiAgent.isAlive()) {
            aiAgent.setState(STATE.Dead);
            return;
        }

        PlayerCharacter aggroTarget = PlayerCharacter.getFromCache(targetID);

        if (aggroTarget == null) {
            //  Logger.error("MobileFSM.aggro", "aggro target with UUID " + targetID + " returned null");
            aiAgent.getPlayerAgroMap().remove(targetID);
            aiAgent.setAggroTargetID(0);
            aiAgent.setState(STATE.Patrol);
            return;
        }

        if (aiAgent.getMobBase().getSeeInvis() < aggroTarget.getHidden()) {
            aiAgent.setAggroTargetID(0);
            aiAgent.setCombatTarget(null);
            aiAgent.setState(STATE.Patrol);
            return;
        }

        if (!aggroTarget.isAlive()) {
            aiAgent.setAggroTargetID(0);
            aiAgent.setCombatTarget(null);
            aiAgent.setState(STATE.Patrol);
            return;
        }

        if (!MovementUtilities.inRangeOfBindLocation(aiAgent)) {
            aiAgent.setCombatTarget(null);
            aiAgent.setAggroTargetID(0);
            aiAgent.setWalkingHome(false);
            aiAgent.setState(STATE.Home);
            return;
        }

        if (!MovementUtilities.inRangeDropAggro(aiAgent, aggroTarget)) {
            aiAgent.setAggroTargetID(0);
            aiAgent.setCombatTarget(null);
            MovementUtilities.moveToLocation(aiAgent, aiAgent.getTrueBindLoc(), 0);
            aiAgent.setState(STATE.Awake);
            return;
        }


        if (CombatUtilities.inRangeToAttack(aiAgent, aggroTarget)) {

            if (aiAgent.getCombatTarget() == null)
                aiAgent.setCombatTarget(aggroTarget);

            if (!CombatUtilities.RunAIRandom())
                return;

            //not time to attack yet.
            if (System.currentTimeMillis() < aiAgent.getLastAttackTime())
                return;

            if (aiAgent.getRange() >= 30 && aiAgent.isMoving())
                return;

            //no weapons, defualt mob attack speed 3 seconds.
            ItemBase mainHand = aiAgent.getWeaponItemBase(true);
            ItemBase offHand = aiAgent.getWeaponItemBase(false);

            if (mainHand == null && offHand == null) {
                CombatUtilities.combatCycle(aiAgent, aggroTarget, true, null);
                aiAgent.setLastAttackTime(System.currentTimeMillis() + 3000);
            } else
                //TODO set offhand attack time.
                if (aiAgent.getWeaponItemBase(true) != null) {

                    int attackDelay = 3000;

                    CombatUtilities.combatCycle(aiAgent, aggroTarget, true, aiAgent.getWeaponItemBase(true));
                    aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);
                } else if (aiAgent.getWeaponItemBase(false) != null) {

                    int attackDelay = 3000;

                    CombatUtilities.combatCycle(aiAgent, aggroTarget, false, aiAgent.getWeaponItemBase(false));
                    aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);
                }
            return;
        }

        //use this so mobs dont continue to try to move if they are underneath a flying target. only use 2D range check.
        if (CombatUtilities.inRangeToAttack2D(aiAgent, aggroTarget))
            return;

        if (!MovementUtilities.canMove(aiAgent))
            return;

        if (!MovementUtilities.updateMovementToCharacter(aiAgent, aggroTarget))
            return;

        aiAgent.destination = MovementUtilities.GetDestinationToCharacter(aiAgent, aggroTarget);
        MovementUtilities.moveToLocation(aiAgent, aiAgent.destination, aiAgent.getRange());
    }
    private static void home(Mob aiAgent, boolean walk) {

        //recall home.
        MovementManager.translocate(aiAgent, aiAgent.getBindLoc(), null);
        aiAgent.setAggroTargetID(0);
        aiAgent.setCombatTarget(null);
        aiAgent.setState(STATE.Awake);
    }
    private static void recalling(Mob aiAgent) {
        //recall home.
        if (aiAgent.getLoc() == aiAgent.getBindLoc())
            aiAgent.setState(STATE.Awake);

        if (aiAgent.getLoc().distanceSquared2D(aiAgent.getBindLoc()) > sqr(2000)) {

            aiAgent.setWalkingHome(false);
            aiAgent.setState(STATE.Home);
        }
    }
    private static void patrol(Mob aiAgent) {

        MobBase mobbase = aiAgent.getMobBase();

        if (mobbase != null && (Enum.MobFlagType.SENTINEL.elementOf(mobbase.getFlags()) || !Enum.MobFlagType.CANROAM.elementOf(mobbase.getFlags()))) {
            aiAgent.setState(STATE.Awake);
            return;
        }

        if (MovementUtilities.canMove(aiAgent) && !aiAgent.isMoving()) {

            float patrolRadius = aiAgent.getSpawnRadius();

            if (patrolRadius > 256)
                patrolRadius = 256;

            if (patrolRadius < 60)
                patrolRadius = 60;

            MovementUtilities.aiMove(aiAgent, Vector3fImmutable.getRandomPointInCircle(aiAgent.getBindLoc(), patrolRadius), true);
        }
        aiAgent.setState(STATE.Awake);
    }
    private static void dead(Mob aiAgent) {
        //Despawn Timer with Loot currently in inventory.
        if (aiAgent.getCharItemManager().getInventoryCount() > 0) {
            if (System.currentTimeMillis() > aiAgent.getDeathTime() + MBServerStatics.DESPAWN_TIMER_WITH_LOOT) {
                aiAgent.despawn();
                //update time of death after mob despawns so respawn time happens after mob despawns.
                aiAgent.setDeathTime(System.currentTimeMillis());
                aiAgent.setState(STATE.Respawn);
            }

            //No items in inventory.
        } else {
            //Mob's Loot has been looted.
            if (aiAgent.isHasLoot()) {
                if (System.currentTimeMillis() > aiAgent.getDeathTime() + MBServerStatics.DESPAWN_TIMER_ONCE_LOOTED) {
                    aiAgent.despawn();
                    //update time of death after mob despawns so respawn time happens after mob despawns.
                    aiAgent.setDeathTime(System.currentTimeMillis());
                    aiAgent.setState(STATE.Respawn);
                }
                //Mob never had Loot.
            } else {
                if (System.currentTimeMillis() > aiAgent.getDeathTime() + MBServerStatics.DESPAWN_TIMER) {
                    aiAgent.despawn();
                    //update time of death after mob despawns so respawn time happens after mob despawns.
                    aiAgent.setDeathTime(System.currentTimeMillis());
                    aiAgent.setState(STATE.Respawn);
                }
            }
        }
    }
    private static void guardAwake(Mob aiAgent) {

        if (!aiAgent.isAlive()) {
            aiAgent.setState(STATE.Dead);
            return;
        }

        if (aiAgent.getLoc().distanceSquared2D(aiAgent.getBindLoc()) > sqr(2000)) {

            aiAgent.setWalkingHome(false);
            aiAgent.setState(STATE.Home);
            return;
        }

        //Don't attempt to aggro if No aggro is on and aiAgent is not home yet.

        //Mob stopped Moving let's turn aggro back on.
        if (aiAgent.isNoAggro())
            aiAgent.setNoAggro(false);

        // do nothing if no players are around.
        if (aiAgent.getPlayerAgroMap().isEmpty())
            return;

        //Get the Map for Players that loaded this mob.

        ConcurrentHashMap<Integer, Boolean> loadedPlayers = aiAgent.getPlayerAgroMap();

        //no players currently have this mob loaded. return to IDLE.
        //aiAgent finished moving home, set aggro on.

        for (Entry playerEntry : loadedPlayers.entrySet()) {

            int playerID = (int) playerEntry.getKey();

            PlayerCharacter loadedPlayer = PlayerCharacter.getFromCache(playerID);

            //Player is null, let's remove them from the list.
            if (loadedPlayer == null) {
                //     Logger.error("MobileFSM", "Player with UID " + playerID + " returned null in mob.getPlayerAgroMap()");
                loadedPlayers.remove(playerID);
                continue;
            }

            //Player is Dead, Mob no longer needs to attempt to aggro. Remove them from aggro map.
            if (!loadedPlayer.isAlive()) {
                loadedPlayers.remove(playerID);
                continue;
            }

            //Can't see target, skip aggro.
            if (!aiAgent.canSee(loadedPlayer)) {
                continue;
            }

            //Guard aggro check

            boolean aggro = false;
            Zone cityZone = aiAgent.getParentZone();

            if (cityZone != null) {
                City city = City.GetCityFromCache(cityZone.getPlayerCityUUID());
                if (city != null) {

                    Building tol = city.getTOL();

                    if (tol != null) {
                        if (tol.reverseKOS) {

                            aggro = true;

                            for (Condemned condemned : tol.getCondemned().values()) {
                                switch (condemned.getFriendType()) {
                                    case Condemned.NATION:
                                        if (loadedPlayer.getGuild() != null && loadedPlayer.getGuild().getNation() != null)
                                            if (loadedPlayer.getGuild().getNation().getObjectUUID() == condemned.getGuildUID())
                                                if (condemned.isActive())
                                                    aggro = false;
                                        break;
                                    case Condemned.GUILD:
                                        if (loadedPlayer.getGuild() != null)
                                            if (loadedPlayer.getGuild().getObjectUUID() == condemned.getGuildUID())
                                                if (condemned.isActive())
                                                    aggro = false;
                                        break;
                                    case Condemned.INDIVIDUAL:
                                        if (loadedPlayer.getObjectUUID() == condemned.getPlayerUID())
                                            if (condemned.isActive())
                                                aggro = false;
                                        break;
                                }
                            }
                        } else {
                            aggro = false;

                            for (Condemned condemned : tol.getCondemned().values()) {
                                switch (condemned.getFriendType()) {
                                    case Condemned.NATION:
                                        if (loadedPlayer.getGuild() != null && loadedPlayer.getGuild().getNation() != null)
                                            if (loadedPlayer.getGuild().getNation().getObjectUUID() == condemned.getGuildUID())
                                                if (condemned.isActive())
                                                    aggro = true;
                                        break;
                                    case Condemned.GUILD:
                                        if (loadedPlayer.getGuild() != null)
                                            if (loadedPlayer.getGuild().getObjectUUID() == condemned.getGuildUID())
                                                if (condemned.isActive())
                                                    aggro = true;
                                        break;
                                    case Condemned.INDIVIDUAL:
                                        if (loadedPlayer.getObjectUUID() == condemned.getPlayerUID())
                                            if (condemned.isActive())
                                                aggro = true;
                                        break;
                                }
                            }
                        }
                    }
                }

                if (loadedPlayer.getGuild() != null && loadedPlayer.getGuild().getNation() != null && city.getGuild() != null)
                    if (Guild.sameGuild(loadedPlayer.getGuild().getNation(), city.getGuild().getNation()))
                        aggro = false;

            }

            //lets make sure we dont aggro players in the nation.

            if (aggro) {
                if (CombatUtilities.inRangeToAttack(aiAgent, loadedPlayer)) {
                    aiAgent.setAggroTargetID(playerID);
                    aiAgent.setState(STATE.Aggro);
                    return;
                }

                if (MovementUtilities.inRangeToAggro(aiAgent, loadedPlayer)) {
                    aiAgent.setAggroTargetID(playerID);
                    aiAgent.setState(STATE.Aggro);
                    return;
                }
            }
        }

        //attempt to patrol even if aiAgent isn't aggresive;
        if (aiAgent.isMoving() == false)
            aiAgent.setState(STATE.Patrol);
    }
    private static void guardAggro(Mob aiAgent, int targetID) {

        if (!aiAgent.isAlive()) {
            aiAgent.setState(STATE.Dead);
            return;
        }

        if (!aiAgent.isCombat()) {
            aiAgent.setCombat(true);
            UpdateStateMsg rwss = new UpdateStateMsg();
            rwss.setPlayer(aiAgent);
            DispatchMessage.sendToAllInRange(aiAgent, rwss);
        }

        //a player got in aggro range. Move to player until in range of attack.
        PlayerCharacter aggroTarget = PlayerCharacter.getFromCache(targetID);

        if (aggroTarget == null) {
            aiAgent.setState(STATE.Patrol);
            return;
        }

        if (!aiAgent.canSee(aggroTarget)) {
            aiAgent.setCombatTarget(null);
            targetID = 0;
            aiAgent.setState(STATE.Patrol);
            return;
        }

        if (!aggroTarget.isActive()) {
            aiAgent.setCombatTarget(null);
            targetID = 0;
            aiAgent.setState(STATE.Patrol);
            return;
        }
        if (CombatUtilities.inRangeToAttack(aiAgent, aggroTarget)) {
            aiAgent.setCombatTarget(aggroTarget);
            aiAgent.setState(STATE.Attack);
            guardAttack(aiAgent);
            return;
        }

        //use this so mobs dont continue to try to move if they are underneath a flying target. only use 2D range check.
        if (CombatUtilities.inRangeToAttack2D(aiAgent, aggroTarget))
            return;


        if (!MovementUtilities.canMove(aiAgent))
            return;

        if (!MovementUtilities.inRangeDropAggro(aiAgent, aggroTarget)) {
            aiAgent.setAggroTargetID(0);
            aiAgent.setCombatTarget(null);
            MovementUtilities.moveToLocation(aiAgent, aiAgent.getTrueBindLoc(), 0);
            aiAgent.setState(STATE.Awake);
            return;
        }

        if (!MovementUtilities.inRangeOfBindLocation(aiAgent)) {
            aiAgent.setCombatTarget(null);
            aiAgent.setAggroTargetID(0);
            aiAgent.setWalkingHome(false);
            aiAgent.setState(STATE.Home);
            return;
        }

        if (!MovementUtilities.updateMovementToCharacter(aiAgent, aggroTarget))
            return;

        //Outside of attack Range, Move to players predicted loc.

        if (aiAgent.getLoc().distanceSquared2D(aggroTarget.getLoc()) < aiAgent.getRange() * aiAgent.getRange())
            return;
        aiAgent.destination = MovementUtilities.GetDestinationToCharacter(aiAgent, aggroTarget);
        MovementUtilities.moveToLocation(aiAgent, aiAgent.destination, aiAgent.getRange());

    }
    private static void guardPatrol(Mob aiAgent) {

        if (aiAgent.getPlayerAgroMap().isEmpty()) {
            aiAgent.setState(STATE.Awake);
            return;
        }

        if (aiAgent.isCombat() && aiAgent.getCombatTarget() == null) {
            aiAgent.setCombat(false);
            UpdateStateMsg rwss = new UpdateStateMsg();
            rwss.setPlayer(aiAgent);
            DispatchMessage.sendToAllInRange(aiAgent, rwss);
        }

        if (aiAgent.getNpcOwner() == null) {

            if (!aiAgent.isWalk() || (aiAgent.isCombat() && aiAgent.getCombatTarget() == null)) {
                aiAgent.setWalkMode(true);
                aiAgent.setCombat(false);
                UpdateStateMsg rwss = new UpdateStateMsg();
                rwss.setPlayer(aiAgent);
                DispatchMessage.sendToAllInRange(aiAgent, rwss);
            }

            if (aiAgent.isMoving()) {
                aiAgent.setState(STATE.Awake);
                return;
            }

            Building barrack = aiAgent.getBuilding();

            if (barrack == null) {
                aiAgent.setState(STATE.Awake);
                return;
            }

            int patrolRandom = ThreadLocalRandom.current().nextInt(1000);

            if (patrolRandom <= 10) {
                int buildingHitBox = (int) CombatManager.calcHitBox(barrack);
                if (MovementUtilities.canMove(aiAgent)) {
                    MovementUtilities.aiMove(aiAgent, MovementUtilities.randomPatrolLocation(aiAgent, aiAgent.getBindLoc(), buildingHitBox * 2), true);
                }
            }

            aiAgent.setState(STATE.Awake);
            return;

        }

        if (!aiAgent.isWalk() || (aiAgent.isCombat() && aiAgent.getCombatTarget() == null)) {
            aiAgent.setWalkMode(true);
            aiAgent.setCombat(false);
            UpdateStateMsg rwss = new UpdateStateMsg();
            rwss.setPlayer(aiAgent);
            DispatchMessage.sendToAllInRange(aiAgent, rwss);

        }

        Building barrack = ((Mob) aiAgent.getNpcOwner()).getBuilding();

        if (barrack == null) {
            aiAgent.setState(STATE.Awake);
            return;
        }

        if (barrack.getPatrolPoints() == null) {
            aiAgent.setState(STATE.Awake);
            return;
        }

        if (barrack.getPatrolPoints().isEmpty()) {
            aiAgent.setState(STATE.Awake);
            return;
        }

        if (aiAgent.isMoving()) {
            aiAgent.setState(STATE.Awake);
            return;
        }

        int patrolRandom = ThreadLocalRandom.current().nextInt(1000);

        if (patrolRandom <= 10) {
            if (aiAgent.getPatrolPointIndex() < barrack.getPatrolPoints().size()) {
                Vector3fImmutable patrolLoc = barrack.getPatrolPoints().get(aiAgent.getPatrolPointIndex());
                aiAgent.setPatrolPointIndex(aiAgent.getPatrolPointIndex() + 1);
                if (aiAgent.getPatrolPointIndex() == barrack.getPatrolPoints().size())
                    aiAgent.setPatrolPointIndex(0);

                if (patrolLoc != null) {
                    if (MovementUtilities.canMove(aiAgent)) {
                        MovementUtilities.aiMove(aiAgent, patrolLoc, true);
                        aiAgent.setState(STATE.Awake);
                    }
                }
            }
        }
        aiAgent.setState(STATE.Awake);
    }
    private static void guardAttack(Mob aiAgent) {

        if (!aiAgent.isAlive()) {
            aiAgent.setState(STATE.Dead);
            return;
        }

        AbstractGameObject target = aiAgent.getCombatTarget();

        if (target == null) {
            aiAgent.setState(STATE.Patrol);
            return;
        }

        switch (target.getObjectType()) {
            case PlayerCharacter:

                PlayerCharacter player = (PlayerCharacter) target;

                if (!player.isActive()) {
                    aiAgent.setCombatTarget(null);
                    aiAgent.setState(STATE.Patrol);
                    return;
                }

                if (aiAgent.isNecroPet() && player.inSafeZone()) {
                    aiAgent.setCombatTarget(null);
                    aiAgent.setState(STATE.Idle);
                    return;
                }
                if (canCast(aiAgent) == true) {
                    if (MobCast(aiAgent) == false) {
                        handlePlayerAttackForMob(aiAgent, player);
                    }
                } else {
                    handlePlayerAttackForMob(aiAgent, player);
                }
                break;
            case Building:
                Logger.info("PLAYER GUARD ATTEMPTING TO ATTACK BUILDING IN " + aiAgent.getParentZone().getName());
                aiAgent.setState(STATE.Awake);
                break;
            case Mob:
                Mob mob = (Mob) target;
                handleMobAttackForMob(aiAgent, mob);
        }
    }
    private static void guardHome(Mob aiAgent, boolean walk) {

        //recall home.
        PowersBase recall = PowersManager.getPowerByToken(-1994153779);
        PowersManager.useMobPower(aiAgent, aiAgent, recall, 40);

        aiAgent.setAggroTargetID(0);
        aiAgent.setCombatTarget(null);
        aiAgent.setState(STATE.Awake);
    }
    private static void respawn(Mob aiAgent) {

        if (!aiAgent.canRespawn())
            return;

        long spawnTime = aiAgent.getSpawnTime();

        if (aiAgent.isPlayerGuard() && aiAgent.getNpcOwner() != null && !aiAgent.getNpcOwner().isAlive())
            return;

        if (System.currentTimeMillis() > aiAgent.getDeathTime() + spawnTime) {
            aiAgent.respawn();
            aiAgent.setState(STATE.Idle);
        }
    }
    private static void retaliate(Mob aiAgent) {

        if (aiAgent.getCombatTarget() == null)
            aiAgent.setState(STATE.Awake);

        //out of range to attack move
        if (!MovementUtilities.canMove(aiAgent)) {
            aiAgent.setState(STATE.Attack);
            return;
        }

        aiAgent.setState(STATE.Attack);

        //lets make mobs ai less twitchy, Don't call another movement until mob reaches it's destination.
        if (aiAgent.isMoving())
            return;

        MovementUtilities.moveToLocation(aiAgent, aiAgent.getCombatTarget().getLoc(), aiAgent.getRange());
    }
    public static boolean canCast(Mob mob) {
        if(mob == null){
            return false;
        }
        if(mob.mobPowers.isEmpty() == true){
            return false;
        }
        if(mob.nextCastTime == 0){
            mob.nextCastTime = System.currentTimeMillis();
        }
        if (mob.nextCastTime > System.currentTimeMillis()) {
            return false;
        }
        return true;
    }
    public static boolean MobCast(Mob mob) {

        // Method picks a random spell from a mobile's list of powers
        // and casts it on the player.  Validation (including empty lists)
        // if done previously in canCast();

        ArrayList<Integer> powerTokens;
        ArrayList<Integer> purgeTokens;
        PlayerCharacter target = (PlayerCharacter) mob.getCombatTarget();

        if (mob.getMobBase().getFlags().contains(Enum.MobFlagType.CALLSFORHELP))
            MobCallForHelp(mob);

        // Generate a list of tokens from the mob powers for this mobile.

        powerTokens = new ArrayList<>(mob.mobPowers.keySet());
        purgeTokens = new ArrayList<>();

        // If player has this effect on them already then remove the token
        // from our list of mob powers

        for (int powerToken : powerTokens){

            PowersBase pwr= PowersManager.getPowerByToken(powerToken);

            for(ActionsBase act : pwr.getActions()){

                String des = act.stackType;

                if (target.getEffects() != null && target.getEffects().containsKey(des))
                    purgeTokens.add(powerToken);
            }
        }

        powerTokens.removeAll(purgeTokens);

        // Sanity check

        if (powerTokens.isEmpty())
            return false;

        // Pick random spell from our list of powers

        int powerToken = powerTokens.get(ThreadLocalRandom.current().nextInt(powerTokens.size()));
        int powerRank = mob.mobPowers.get(powerToken);
        PowersBase mobPower = PowersManager.getPowerByToken(powerToken);

        // Cast the spell

        if (CombatUtilities.inRange2D(mob, mob.getCombatTarget(), mobPower.getRange())) {
            PowersManager.useMobPower(mob, (AbstractCharacter) mob.getCombatTarget(), mobPower, powerRank);
            PerformActionMsg msg;

            if (mobPower.isHarmful() == false || mobPower.targetSelf == true)
                msg = PowersManager.createPowerMsg(mobPower, powerRank, mob, mob);
            else
                msg = PowersManager.createPowerMsg(mobPower, powerRank, mob, target);

            msg.setUnknown04(2);
            PowersManager.finishUseMobPower(msg, mob, 0, 0);

            //default minimum seconds between cast = 10

            long coolDown = mobPower.getCooldown();

            if (coolDown < 10000)
                mob.nextCastTime = System.currentTimeMillis() + 10000 + coolDown;
            else
                mob.nextCastTime = System.currentTimeMillis() + coolDown;

            return true;
        }

     return false;
                }

    public static void MobCallForHelp(Mob mob) {
        if(mob.nextCallForHelp == 0){
            mob.nextCallForHelp = System.currentTimeMillis();
        }
        if(mob.nextCallForHelp < System.currentTimeMillis()){
            return;
        }
        Zone mobCamp = mob.getParentZone();
        for (Mob mob1 : mobCamp.zoneMobSet) {
            if (mob1.getMobBase().getFlags().contains(Enum.MobFlagType.RESPONDSTOCALLSFORHELP)) {
                if (mob1.getState() == STATE.Awake) {
                    if (CombatUtilities.inRange2D(mob, mob1, mob.getAggroRange()) == true) {
                        MovementUtilities.moveToLocation(mob1, mob.getLoc(), 0);
                    }
                }
            }
        }
        //wait 60 seconds to call for help again
        mob.nextCallForHelp = System.currentTimeMillis() + 60000;
    }
    public static void handleMobChase(Mob mob){
        if (!MovementUtilities.inRangeOfBindLocation(mob)) {
            mob.setCombatTarget(null);
            mob.setAggroTargetID(0);
            mob.setWalkingHome(false);
            mob.setState(STATE.Home);
            return;
        }
        mob.updateMovementState();
        mob.updateLocation();
        if(CombatUtilities.inRange2D(mob,mob.getCombatTarget(), mob.getRange()) == true) {
            MovementUtilities.moveToLocation(mob, mob.getLoc(), 0);
            mob.setState(STATE.Attack);
        }
        else {//if (mob.isMoving() == false){
            if(mob.getRange() > 15) {
                mob.destination = mob.getCombatTarget().getLoc();
                MovementUtilities.moveToLocation(mob, mob.destination, 0);
            } else{
                mob.destination = MovementUtilities.GetDestinationToCharacter(mob, (AbstractCharacter) mob.getCombatTarget());
                MovementUtilities.moveToLocation(mob, mob.destination, mob.getRange());
            }

        }
    }
}