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

package engine.gameManager;

import engine.Enum.*;
import engine.InterestManagement.Terrain;
import engine.InterestManagement.WorldGrid;
import engine.db.handlers.dbEffectsBaseHandler;
import engine.db.handlers.dbPowerHandler;
import engine.db.handlers.dbSkillReqHandler;
import engine.job.AbstractJob;
import engine.job.AbstractScheduleJob;
import engine.job.JobContainer;
import engine.job.JobScheduler;
import engine.jobs.*;
import engine.math.Vector3fImmutable;
import engine.net.ByteBufferWriter;
import engine.net.Dispatch;
import engine.net.DispatchMessage;
import engine.net.client.ClientConnection;
import engine.net.client.msg.*;
import engine.objects.*;
import engine.powers.*;
import engine.powers.poweractions.AbstractPowerAction;
import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;

import static engine.math.FastMath.sqr;

public enum PowersManager {

    POWERMANAGER;

    public static HashMap<String, PowersBase> powersBaseByIDString = new HashMap<>();
    public static HashMap<Integer, PowersBase> powersBaseByToken = new HashMap<>();
    public static HashMap<String, EffectsBase> effectsBaseByIDString = new HashMap<>();
    public static HashMap<Integer, EffectsBase> effectsBaseByToken = new HashMap<>();
    public static HashMap<String, AbstractPowerAction> powerActionsByIDString = new HashMap<>();
    public static HashMap<Integer, AbstractPowerAction> powerActionsByID = new HashMap<>();
    public static HashMap<String, Integer> ActionTokenByIDString = new HashMap<>();
    public static HashMap<String, Integer> AnimationOverrides = new HashMap<>();
    public static HashMap<Integer, ArrayList<RunePowerEntry>> _allRunePowers;
    public static HashMap<Integer, ArrayList<RuneSkillAdjustEntry>> _allRuneSkillAdjusts;
    private static JobScheduler js;

    public static void initPowersManager(boolean fullPowersLoad) {

        if (fullPowersLoad)
            PowersManager.InitializePowers();
        else
            PowersManager.InitializeLoginPowers();

        PowersManager.js = JobScheduler.getInstance();

    }

    public static PowersBase getPowerByToken(int token) {
        return PowersManager.powersBaseByToken.get(token);
    }

    public static PowersBase getPowerByIDString(String IDString) {
        return PowersManager.powersBaseByIDString.get(IDString);
    }

    public static EffectsBase getEffectByIDString(String IDString) {
        return PowersManager.effectsBaseByIDString.get(IDString);
    }

    public static AbstractPowerAction getPowerActionByIDString(String IDString) {
        return PowersManager.powerActionsByIDString.get(IDString);
    }

    public static EffectsBase getEffectByToken(int token) {
        return PowersManager.effectsBaseByToken.get(token);
    }

    // This pre-loads only PowersBase for login
    public static void InitializeLoginPowers() {

        // get all PowersBase
        ArrayList<PowersBase> pbList = dbSkillReqHandler.getAllPowersBase();

        for (PowersBase pb : pbList) {
            if (pb.getToken() != 0)
                PowersManager.powersBaseByToken.put(pb.getToken(), pb);
        }
    }

    public static ArrayList<RunePowerEntry> getPowersForRune(int rune_id) {

        ArrayList<RunePowerEntry> powerEntries = PowersManager._allRunePowers.get(rune_id);

        if (powerEntries == null)
            powerEntries = new ArrayList<>();

        return powerEntries;
    }

    // This pre-loads all powers and effects
    public static void InitializePowers() {

        // Add EffectsBase
        ArrayList<EffectsBase> ebList = dbEffectsBaseHandler.getAllEffectsBase();

        for (EffectsBase eb : ebList) {
            PowersManager.effectsBaseByToken.put(eb.getToken(), eb);
            PowersManager.effectsBaseByIDString.put(eb.getIDString(), eb);

        }

        // Add Fail Conditions
        EffectsBase.getFailConditions(PowersManager.effectsBaseByIDString);

        // Add Modifiers to Effects
        dbEffectsBaseHandler.cacheAllEffectModifiers();

        // Add Source Types to Effects
        dbPowerHandler.addAllSourceTypes();
        dbPowerHandler.addAllAnimationOverrides();

        // Add PowerActions
        AbstractPowerAction.getAllPowerActions(PowersManager.powerActionsByIDString, PowersManager.powerActionsByID, PowersManager.effectsBaseByIDString);

        // Load valid Item Flags
        //	AbstractPowerAction.loadValidItemFlags(PowersManager.powerActionsByIDString);

        // get all PowersBase
        ArrayList<PowersBase> pbList = dbSkillReqHandler.getAllPowersBase();
        for (PowersBase pb : pbList) {
            if (pb.getToken() != 0) {
                PowersManager.powersBaseByIDString.put(pb.getIDString(), pb);
                PowersManager.powersBaseByToken.put(pb.getToken(), pb);
            }
        }

        // Add Power Prereqs
        PowerPrereq.getAllPowerPrereqs(PowersManager.powersBaseByIDString);
        // Add Fail Conditions
        dbSkillReqHandler.getFailConditions(PowersManager.powersBaseByIDString);
        // Add Actions Base
        ActionsBase.getActionsBase(PowersManager.powersBaseByIDString,
                PowersManager.powerActionsByIDString);

    }

    public static EffectsBase setEffectToken(int ID, int token) {
        for (EffectsBase eb : PowersManager.effectsBaseByIDString.values()) {
            if (eb.getUUID() == ID) {
                eb.setToken(token);
                return eb;
            }
        }
        return null;
    }

    public static void usePower(final PerformActionMsg msg, ClientConnection origin,
                                boolean sendCastToSelf) {

        if (ConfigManager.MB_RULESET.getValue() == "LORE") {
            PowersBase pb = PowersManager.powersBaseByToken.get(msg.getPowerUsedID());
            PlayerCharacter caster = origin.getPlayerCharacter();
            PlayerCharacter target = PlayerCharacter.getFromCache(msg.getTargetID());
            if (pb != null && pb.isHarmful == false) {
                if (caster.guild.equals(Guild.getErrantGuild()))
                    return;

                if (target != null && caster.guild.getGuildType().equals(target.guild.getGuildType()) == false)
                    return;
            }
        }

        if (usePowerA(msg, origin, sendCastToSelf)) {
            // Cast failed for some reason, reset timer

            RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(msg.getPowerUsedID());
            Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), recyclePowerMsg);
            DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);

            // Send Fail to cast message
            PlayerCharacter pc = SessionManager
                    .getPlayerCharacter(origin);

            if (pc != null) {
                sendPowerMsg(pc, 2, msg);
                if (pc.isCasting()) {
                    pc.update();
                }

                pc.setIsCasting(false);
            }

        }
    }

    public static void useMobPower(Mob caster, AbstractCharacter target, PowersBase pb, int rank) {

        PerformActionMsg msg = createPowerMsg(pb, rank, caster, target);
        msg.setUnknown04(1);

        if (useMobPowerA(msg, caster)) {
            //sendMobPowerMsg(caster,2,msg); //Lol wtf was i thinking sending msg's to mobs... ZZZZ
        }
    }

    public static boolean usePowerA(final PerformActionMsg msg, ClientConnection origin,
                                    boolean sendCastToSelf) {
        PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter(
                origin);
        if (playerCharacter == null)
            return false;

        boolean CSRCast = false;


        if (MBServerStatics.POWERS_DEBUG) {
            ChatManager.chatSayInfo(
                    playerCharacter,
                    "Using Power: " + Integer.toHexString(msg.getPowerUsedID())
                            + " (" + msg.getPowerUsedID() + ')');
            Logger.info("Using Power: "
                    + Integer.toHexString(msg.getPowerUsedID()) + " ("
                    + msg.getPowerUsedID() + ')');
        }

        //Sending recycle message to player if died while casting.
        if (!playerCharacter.isAlive() && msg.getPowerUsedID() != 428589216) { //succor

            RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(msg.getPowerUsedID());
            Dispatch dispatch = Dispatch.borrow(playerCharacter, recyclePowerMsg);
            DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);

            return false;
        }


        // if (!pc.getPowers().contains(msg.getPowerUsedID())) {
        // sendPowerMsg(pc, 10, msg);
        // return false;
        // }
        // verify recycle timer is not active for power, skip for a CSR
        if (playerCharacter.getRecycleTimers().containsKey(msg.getPowerUsedID()) && !playerCharacter.isCSR()) {
            // ChatManager.chatSayInfo(pc, "Recycle timer not finished!");

            Logger.warn("usePowerA(): Cheat attempted? '" + msg.getPowerUsedID() + "' recycle timer not finished " + playerCharacter.getName());
            return false;
        }

        // get power
        PowersBase pb = PowersManager.powersBaseByToken.get(msg.getPowerUsedID());
        if (pb == null) {
            ChatManager.chatSayInfo(playerCharacter,
                    "This power is not implemented yet.");

            // Logger.error("usePowerA(): Power '" +
            // msg.getPowerUsedID()
            // + "' was not found on powersBaseByToken map.");
            return true;
            // return false;
        }

        if (playerCharacter.getLastPower() != null)
            return true;

        //Check if Power Target is allowed to cast.


        // Check powers for normal users
        if (playerCharacter.getPowers() == null || !playerCharacter.getPowers().containsKey(msg.getPowerUsedID()))
            if (!playerCharacter.isCSR()) {
                if (!MBServerStatics.POWERS_DEBUG) {
                    //  ChatManager.chatSayInfo(pc, "You may not cast that spell!");

                    Logger.info("usePowerA(): Cheat attempted? '" + msg.getPowerUsedID() + "' was not associated with " + playerCharacter.getName());
                    return true;
                }
            } else
                CSRCast = true;

        // get numTrains for power
        int trains = msg.getNumTrains();

        // can't go over the max trains for the power, unless CSR
        if (trains > pb.getMaxTrains() && !playerCharacter.isCSR()) {
            trains = pb.getMaxTrains();
            msg.setNumTrains(trains);
        }

        // can't go over total trains by player
        if (playerCharacter.getPowers() != null && playerCharacter.getPowers().containsKey(msg.getPowerUsedID())) {
            CharacterPower cp = playerCharacter.getPowers().get(msg.getPowerUsedID());
            if (cp != null) {
                int tot = cp.getTotalTrains();
                if (tot == 0 && !playerCharacter.isCSR())
                    return false;
                if (trains != tot && !playerCharacter.isCSR()) {
                    trains = tot;
                    msg.setNumTrains(trains);
                }
            }
        }

        // get recycle time in ms
        int time = pb.getRecycleTime(trains);

        // verify player is in correct mode (combat/nonCombat)
        if (playerCharacter.isCombat()) {
            if (!pb.allowedInCombat())
                // ChatManager.chatPowerError(pc,
                // "This power is not allowed in combat mode.");
                return true;
        } else if (!pb.allowedOutOfCombat())
            // ChatManager.chatPowerError(pc,
            // "You must be in combat mode to use this power.");
            return true;

        // verify player is not stunned or prohibited from casting
        PlayerBonuses bonus = playerCharacter.getBonuses();
        SourceType sourceType = SourceType.GetSourceType(pb.getCategory());
        if (bonus != null && (bonus.getBool(ModType.Stunned, SourceType.NONE) || bonus.getBool(ModType.CannotCast, SourceType.NONE) || bonus.getBool(ModType.BlockedPowerType, sourceType)))
            return true;

        // if moving make sure spell valid for movement
        Vector3fImmutable endLoc = playerCharacter.getEndLoc();


        if (!pb.canCastWhileMoving())
            if (playerCharacter.isMoving()) {

                // if movement left is less then 1 seconds worth then let cast
                // go through.
                float distanceLeftSquared = endLoc.distanceSquared2D(playerCharacter.getLoc());

                if (distanceLeftSquared > sqr(playerCharacter.getSpeed()))
                    return true;
            }
        // if flying, make sure spell valid for flying.
        // if (pc.getAltitude() > 0)
        // if (!pb.canCastWhileFlying())
        // return true;

        int targetLiveCounter = -1;

        // get target based on targetType;
        if (pb.targetFromLastTarget() || pb.targetPet()) // use msg's target
            if (pb.isAOE()) {
                if (!pb.usePointBlank()) {
                    AbstractWorldObject target = getTarget(msg);


                    if (target != null && target.getObjectType() == GameObjectType.Building && !pb.targetBuilding()) {
                        PowersManager.sendPowerMsg(playerCharacter, 9, new PerformActionMsg(msg));
                        return true;
                    }

                    if (target == null) {
                        if (playerCharacter.getLoc().distanceSquared2D(msg.getTargetLoc()) > sqr(pb
                                .getRange()))
                            return true;
                    } else if (verifyInvalidRange(playerCharacter, target, pb.getRange()))
                        // pc.getLoc().distance(target.getLoc()) >
                        // pb.getRange())
                        return true;


                }
            } else {
                // get target
                AbstractWorldObject target = getTarget(msg);

                if (target == null)
                    return true;

                if (!target.isAlive() && target.getObjectType().equals(GameObjectType.Building) == false && msg.getPowerUsedID() != 428589216)
                    return true;

                float range = pb.getRange();
                // verify target is in range


                if (verifyInvalidRange(playerCharacter, target, range))
                    // (pc.getLoc().distance(target.getLoc()) > pb.getRange()) {
                    // TODO send message that target is out of range
                    return true;

                // verify target is valid type
                if (!validateTarget(target, playerCharacter, pb))
                    return true;


                if (AbstractWorldObject.IsAbstractCharacter(target))
                    targetLiveCounter = ((AbstractCharacter) target).getLiveCounter();
            }

        // verify regular player can cast spell, otherwise authenticate
        if (!pb.regularPlayerCanCast()) {
            Account a = SessionManager.getAccount(playerCharacter);
            if (a.status.equals(AccountStatus.ADMIN) == false) {
                Logger.warn("Player '" + playerCharacter.getName()
                        + "' is attempting to cast a spell outside of their own access level.");
                return true;
            }
        }

        // verify player has proper effects applied to use power
        if (pb.getEffectPrereqs().size() > 0 && playerCharacter.getEffects() != null) {

            boolean passed = false;
            for (PowerPrereq pp : pb.getEffectPrereqs()) {

                EffectsBase eb = PowersManager.getEffectByIDString(pp.getEffect());

                if (playerCharacter.containsEffect(eb.getToken())) {
                    passed = true;
                    break;
                }
            }

            if (!passed)
                return true;
        }

        //verify player has proper equipment to use power
        if (pb.getEquipPrereqs().size() > 0) {

            boolean passed = false;

            for (PowerPrereq pp : pb.getEquipPrereqs()) {

                EquipSlotType slot = pp.mainHand() ? EquipSlotType.RHELD : EquipSlotType.LHELD;

                if (playerCharacter.validEquip(slot, pp.getMessage())) {
                    passed = true; //should have item in slot
                    break;
                } else if (!pp.isRequired()) {
                    passed = true; //should not have item in slot
                    break;
                }
            }
            if (!passed)
                return true;
        }

        // TODO if target immune to powers, cancel unless aoe
        // Also make sure No.ImmuneToPowers is false for target
        // if there's a different power waiting to finish, stop here


        //get power cost and calculate any power cost modifiers
        float cost = pb.getCost(trains);

        if (playerCharacter.isCSR())
            cost = 0;

        if (bonus != null)
            cost *= (1 + bonus.getFloatPercentAll(ModType.PowerCost, SourceType.NONE));

        if (playerCharacter.getAltitude() > 0)
            cost *= 1.5f;

        // Verify player can afford to use power
        //CCR toons dont use cost

        if (!playerCharacter.isCSR()) {
            if (cost > 0)
                if ((playerCharacter.getObjectTypeMask() & MBServerStatics.MASK_UNDEAD) != 0)
                    if (playerCharacter.getHealth() <= cost)
                        return true;
                    else {
                        playerCharacter.modifyHealth(-cost, playerCharacter, true);
                        ModifyHealthMsg mhm = new ModifyHealthMsg(playerCharacter, playerCharacter, -cost,
                                0f, 0f, 0, null,
                                9999, 0);
                        mhm.setOmitFromChat(1);
                        DispatchMessage.dispatchMsgToInterestArea(playerCharacter, mhm, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
                    }
                else if (pb.useMana())
                    if (playerCharacter.getMana() < cost)
                        return true;
                    else
                        playerCharacter.modifyMana(-cost, playerCharacter, true);
                else if (pb.useStamina())
                    if (playerCharacter.getStamina() < cost)
                        return true;
                    else
                        playerCharacter.modifyStamina(-cost, playerCharacter, true);
                else if (playerCharacter.getHealth() <= cost)
                    return true;
                else
                    playerCharacter.modifyHealth(-cost, playerCharacter, true);
        }


        // Validity checks passed, move on to casting spell
        //get caster's live counter
        int casterLiveCounter = playerCharacter.getLiveCounter();

        // run recycle job for when cast is available again, don't bother adding the timer for CSRs
        if (time > 0) {
            FinishRecycleTimeJob frtj = new FinishRecycleTimeJob(playerCharacter, msg);
            playerCharacter.getRecycleTimers().put(msg.getPowerUsedID(), js.scheduleJob(frtj, time));
        } else {
            // else send recycle message to unlock power
            RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(msg.getPowerUsedID());
            Dispatch dispatch = Dispatch.borrow(playerCharacter, recyclePowerMsg);
            DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
        }

        //what the fuck?
//		// Send cast to other players
//		if ((playerCharacter.getObjectTypeMask() & MBServerStatics.MASK_UNDEAD) != 0)
//			msg.setUnknown04(2); // Vampire Race, use health?
//		else
//			msg.setUnknown04(1); // Regular Race, use mana?
        int tr = msg.getNumTrains();
        DispatchMessage.dispatchMsgToInterestArea(playerCharacter, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, sendCastToSelf, false);

        //Make new msg..
        PerformActionMsg copyMsg = new PerformActionMsg(msg);
        copyMsg.setNumTrains(tr);

        // make person casting stand up if spell (unless they're casting a chant which does not make them stand up)
        if (pb.isSpell() && !pb.isChant() && playerCharacter.isSit()) {
            playerCharacter.update();
            playerCharacter.setSit(false);
            UpdateStateMsg updateStateMsg = new UpdateStateMsg(playerCharacter);
            DispatchMessage.dispatchMsgToInterestArea(playerCharacter, updateStateMsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);

        }

        // update cast (use skill) fail condition
        playerCharacter.cancelOnCast();

        // update castSpell (use spell) fail condition if spell
        if (pb.isSpell())
            playerCharacter.cancelOnSpell();

        // get cast time in ms.
        time = pb.getCastTime(trains);

        // set player is casting for regens


        if (time > 100) {
            playerCharacter.update();
            playerCharacter.setIsCasting(true);
        }


        playerCharacter.setLastMovementState(playerCharacter.getMovementState());

        // run timer job to end cast
        if (time < 1) // run immediately
            finishUsePower(copyMsg, playerCharacter, casterLiveCounter, targetLiveCounter);
        else {
            UsePowerJob upj = new UsePowerJob(playerCharacter, copyMsg, copyMsg.getPowerUsedID(), pb, casterLiveCounter, targetLiveCounter);
            JobContainer jc = js.scheduleJob(upj, time);

            // make lastPower
            playerCharacter.setLastPower(jc);
        }

        if (CSRCast)
            Logger.info("CSR " + playerCharacter.getName() + " cast power " + msg.getPowerUsedID() + '.');

        return false;
    }

    public static void testPowers(ByteBufferWriter writer) {
        writer.putInt(powersBaseByToken.size());
        for (int token : powersBaseByToken.keySet()) {
            writer.putInt(token);
            writer.putInt(40);
        }
    }

    public static boolean useMobPowerA(PerformActionMsg msg, Mob caster) {
        if (caster == null)
            return false;


        if (!caster.isAlive() && msg.getPowerUsedID() != 428589216) //succor
            return false;

        // get power
        PowersBase pb = PowersManager.powersBaseByToken.get(msg.getPowerUsedID());
        if (pb == null)
            return true;

        // Check powers for normal users
        // get numTrains for power
        int trains = msg.getNumTrains();

        // can't go over the max trains for the power, unless CSR
        // can't go over total trains by player
        // get recycle time in ms
        int time = pb.getRecycleTime(trains);

        // verify player is in correct mode (combat/nonCombat)
        // verify player is not stunned or prohibited from casting
        PlayerBonuses bonus = caster.getBonuses();
        SourceType sourceType = SourceType.GetSourceType(pb.getCategory());
        if (bonus != null && (bonus.getBool(ModType.Stunned, SourceType.NONE) || bonus.getBool(ModType.CannotCast, SourceType.NONE) || bonus.getBool(ModType.BlockedPowerType, sourceType)))
            return true;

        // if moving make sure spell valid for movement
        // if flying, make sure spell valid for flying.
        // if (pc.getAltitude() > 0)
        // if (!pb.canCastWhileFlying())
        // return true;
        int targetLiveCounter = -1;

        // get target based on targetType;
        if (pb.targetFromLastTarget() || pb.targetPet()) // use msg's target
            if (pb.isAOE()) {
                if (!pb.usePointBlank()) {
                    AbstractWorldObject target = getTarget(msg);


                    if (target == null) {

                        if (caster.getLoc().distanceSquared2D(msg.getTargetLoc()) > sqr(pb
                                .getRange()))
                            return true;
                    } else if (verifyInvalidRange(caster, target, pb.getRange()))
                        // pc.getLoc().distance(target.getLoc()) >
                        // pb.getRange())
                        return true;
                }
            } else {
                // get target
                AbstractWorldObject target = getTarget(msg);

                if (target == null)
                    return true;

                // verify target is in range
                if (verifyInvalidRange(caster, target, pb.getRange()))
                    // (pc.getLoc().distance(target.getLoc()) > pb.getRange()) {
                    // TODO send message that target is out of range
                    return true;

                // verify target is valid type
                if (AbstractWorldObject.IsAbstractCharacter(target))
                    targetLiveCounter = ((AbstractCharacter) target).getLiveCounter();
            }

        // TODO if target immune to powers, cancel unless aoe
        // Also make sure No.ImmuneToPowers is false for target
        // if there's a different power waiting to finish, stop here
        if (caster.getLastMobPowerToken() != 0)
            return true;

        //get power cost and calculate any power cost modifiers
        // Validity checks passed, move on to casting spell
        //get caster's live counter
        int casterLiveCounter = caster.getLiveCounter();

        // run recycle job for when cast is available again, don't bother adding the timer for CSRs
        // Send cast to other players
        if (caster.getObjectTypeMask() == MBServerStatics.MASK_UNDEAD)
            msg.setUnknown05(0); // Regular Race, use mana?
        else
            msg.setUnknown05(0);

        int tr = msg.getNumTrains();

        msg.setNumTrains(9999);

        DispatchMessage.sendToAllInRange(caster, msg);
        DispatchMessage.sendToAllInRange(caster, msg);

        msg.setNumTrains(tr);

        // make person casting stand up if spell (unless they're casting a chant which does not make them stand up)
        // update cast (use skill) fail condition
        caster.cancelOnCast();

        // update castSpell (use spell) fail condition if spell
        if (pb.isSpell())
            caster.cancelOnSpell();

        // get cast time in ms.
        time = pb.getCastTime(trains);

        // set player is casting for regens
        caster.setIsCasting(true);
        caster.setLastMobPowerToken(pb.getToken());

        // run timer job to end cast
        if (time < 1 || pb.getToken() == -1994153779) {
            // run immediately
            finishUseMobPower(msg, caster, casterLiveCounter, targetLiveCounter);
            caster.setLastMobPowerToken(0);
        } else {
            caster.setLastMobPowerToken(pb.getToken());
            caster.setTimeStamp("FinishCast", System.currentTimeMillis() + (pb.getCastTime(trains)));
        }
        //			finishUseMobPower(msg, caster, casterLiveCounter, targetLiveCounter); //			UseMobPowerJob upj = new UseMobPowerJob(caster, msg, msg.getPowerUsedID(), pb, casterLiveCounter, targetLiveCounter);
        //			 JobContainer jc = js.scheduleJob(upj, time);
        //			// make lastPower


        return false;
    }

    // called when a spell finishes casting. perform actions
    public static void finishUsePower(final PerformActionMsg msg, PlayerCharacter playerCharacter, int casterLiveCounter, int targetLiveCounter) {

        PerformActionMsg performActionMsg;
        Dispatch dispatch;

        if (playerCharacter == null || msg == null)
            return;

        if (playerCharacter.isCasting()) {
            playerCharacter.update();
            playerCharacter.updateStamRegen(-100);
        }

        playerCharacter.resetLastSetLocUpdate();
        playerCharacter.setIsCasting(false);
        // can't go over total trains by player


        if (!playerCharacter.isAlive() || playerCharacter.getLiveCounter() != casterLiveCounter) {
            playerCharacter.clearLastPower();
            finishRecycleTime(msg.getPowerUsedID(), playerCharacter, true);


            // Let's do good OO.  Clone message don't modify it.

            performActionMsg = new PerformActionMsg(msg);
            performActionMsg.setNumTrains(9999);
            performActionMsg.setUnknown04(2);

            dispatch = Dispatch.borrow(playerCharacter, performActionMsg);
            DispatchMessage.dispatchMsgToInterestArea(playerCharacter, performActionMsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
            return;
        }

        // set player is not casting for regens


        // clear power.
        playerCharacter.clearLastPower();

        PowersBase pb = PowersManager.powersBaseByToken.get(msg.getPowerUsedID());

        if (pb == null) {
            Logger.error(
                    "finishUsePower(): Power '" + msg.getPowerUsedID()
                            + "' was not found on powersBaseByToken map.");
            return;
        }

        int trains = msg.getNumTrains();

        // verify player is not stunned or power type is blocked
        PlayerBonuses bonus = playerCharacter.getBonuses();

        if (bonus != null) {
            if (bonus.getBool(ModType.Stunned, SourceType.NONE))
                return;

            SourceType sourceType = SourceType.GetSourceType(pb.getCategory());
            if (bonus.getBool(ModType.BlockedPowerType, sourceType)) {
                finishRecycleTime(msg.getPowerUsedID(), playerCharacter, true);
                return;
            }
        }

        // get target loc
        Vector3fImmutable targetLoc = msg.getTargetLoc();


        if (pb.targetFromLastTarget() || pb.targetPet()) // use msg's target
            if (pb.isAOE()) {
                if (!pb.usePointBlank()) {
                    AbstractWorldObject mainTarget = getTarget(msg);

                    float speedRange = 0;
                    if (AbstractWorldObject.IsAbstractCharacter(mainTarget)) {
                        speedRange = ((AbstractCharacter) mainTarget).getSpeed() * (pb.getCastTime(trains) * .001f);
                    }

                    if (mainTarget != null && mainTarget.getObjectType() == GameObjectType.Building && !pb.targetBuilding()) {
                        PowersManager.sendPowerMsg(playerCharacter, 8, new PerformActionMsg(msg));
                        return;
                    }

                    if (mainTarget == null) {

                        if (playerCharacter.getLoc().distanceSquared2D(msg.getTargetLoc()) > sqr(pb
                                .getRange())) {
                            sendPowerMsg(playerCharacter, 8, msg);
                            return;
                        }
                    } else if (verifyInvalidRange(playerCharacter, mainTarget, speedRange + pb.getRange())) {

                        sendPowerMsg(playerCharacter, 8, msg);
                        return;
                    }
                }
            } else {

                // get target
                AbstractWorldObject mainTarget = getTarget(msg);

                if (mainTarget == null)
                    return;

                float speedRange = 0;
                if (AbstractWorldObject.IsAbstractCharacter(mainTarget)) {
                    speedRange = ((AbstractCharacter) mainTarget).getSpeed() * (pb.getCastTime(trains) * .001f);
                }
                float range = pb.getRange() + speedRange;


                if (verifyInvalidRange(playerCharacter, mainTarget, range)) {

                    sendPowerMsg(playerCharacter, 8, msg);
                    return;
                }
                // (pc.getLoc().distance(target.getLoc()) > pb.getRange()) {
                // TODO send message that target is out of range


            }

        if (targetLoc.x == 0f || targetLoc.z == 0f) {
            AbstractWorldObject tar = getTarget(msg);
            if (tar != null)
                targetLoc = tar.getLoc();
        }


        // get list of targets
        HashSet<AbstractWorldObject> allTargets = getAllTargets(
                getTarget(msg), msg.getTargetLoc(), playerCharacter, pb);

        // no targets found. send error message

        if (allTargets.size() == 0) {
            sendPowerMsg(playerCharacter, 9, msg);
            return;
        }

        //Send Cast Message.
//		PerformActionMsg castMsg = new PerformActionMsg(msg);
//		castMsg.setNumTrains(9999);
//		castMsg.setUnknown04(3);
//		DispatchMessage.dispatchMsgToInterestArea(playerCharacter, castMsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
//		
        boolean msgCasted = false;

        for (AbstractWorldObject target : allTargets) {

            if (target == null)
                continue;

            //Hacky Pyschic healing cross heal

            //make sure target hasn't respawned since we began casting
            //skip this if liveCounter = -1 (from aoe)

            if (targetLiveCounter != -1)
                if (AbstractWorldObject.IsAbstractCharacter(target))
                    if (targetLiveCounter != ((AbstractCharacter) target).getLiveCounter())
                        continue;

            if (!target.isAlive() && target.getObjectType() != GameObjectType.Building && pb.getToken() != 428589216 && pb.getToken() != 429425915)
                continue;

            //make sure mob is awake to respond.
            //if (target instanceof AbstractIntelligenceAgent)
            //((AbstractIntelligenceAgent)target).enableIntelligence();
            // If Hit roll required, test hit

            boolean miss = false;
            if (pb.requiresHitRoll() && !pb.isWeaponPower() && testAttack(playerCharacter, target, pb, msg)) {
                miss = true;
                //aggro mob even on a miss
                if (target.getObjectType() == GameObjectType.Mob) {
                    Mob mobTarget = (Mob) target;
                    if (pb.isHarmful())
                        mobTarget.handleDirectAggro(playerCharacter);
                }
                continue;
            }
            if (target.getObjectType() == GameObjectType.Mob) {
                Mob mobTarget = (Mob) target;
                if (pb.isHarmful())
                    mobTarget.handleDirectAggro(playerCharacter);
            }
            //Power is aiding a target, handle aggro if combat target is a Mob.
            if (!pb.isHarmful() && target.getObjectType() == GameObjectType.PlayerCharacter) {
                PlayerCharacter pcTarget = (PlayerCharacter) target;
            }

            // update target of used power timer

            if (pb.isHarmful())
                if (target.getObjectType().equals(GameObjectType.PlayerCharacter) && target.getObjectUUID() != playerCharacter.getObjectUUID()) {
                    ((PlayerCharacter) target).setTimeStamp("LastCombatPlayer", System.currentTimeMillis());
                    playerCharacter.setTimeStamp("LastCombatPlayer", System.currentTimeMillis());
                }


            //Player didn't miss power, send finish cast. Miss cast already sent.


            // finally Apply actions
            for (ActionsBase ab : pb.getActions()) {
                // get numTrains for power, skip action if invalid

                if (trains < ab.getMinTrains() || trains > ab.getMaxTrains())
                    continue;
                // If something blocks the action, then stop

                if (ab.blocked(target, pb, trains)) {

                    PowersManager.sendEffectMsg(playerCharacter, 5, ab, pb);
                    continue;
                }

                // TODO handle overwrite stack order here
                String stackType = ab.getStackType();
                stackType = (stackType.equals("IgnoreStack")) ? Integer.toString(ab.getUUID()) : stackType;
                //				if (!stackType.equals("IgnoreStack")) {
                if (target.getEffects().containsKey(stackType)) {
                    // remove any existing power that overrides
                    Effect ef = target.getEffects().get(stackType);
                    AbstractEffectJob effect = null;
                    if (ef != null) {
                        JobContainer jc = ef.getJobContainer();
                        if (jc != null)
                            effect = (AbstractEffectJob) jc.getJob();
                    }
                    ActionsBase overwrite = effect.getAction();

                    if (overwrite == null) {
                        Logger.error("NULL ACTION FOR POWER " + effect.getPowerToken());
                        continue;
                    }

                    if (ab.getStackOrder() < overwrite.getStackOrder())
                        continue; // not high enough to overwrite
                    else if (ab.getStackOrder() > overwrite.getStackOrder()) {
                        effect.setNoOverwrite(true);
                        removeEffect(target, overwrite, true, false);
                    } else if (ab.getStackOrder() == overwrite.getStackOrder())
                        if (ab.greaterThanEqual()
                                && (trains >= effect.getTrains())) {
                            effect.setNoOverwrite(true);
                            removeEffect(target, overwrite, true, false);
                        } else if (ab.always())
                            removeEffect(target, overwrite, true, false);
                        else if (ab.greaterThan()
                                && (trains > effect.getTrains())) {
                            effect.setNoOverwrite(true);
                            removeEffect(target, overwrite, true, false);
                        } else if (ab.greaterThan() && pb.getToken() == effect.getPowerToken())
                            removeEffect(target, overwrite, true, false);

                        else
                            continue; // trains not high enough to overwrite

                }

                //				}


                runPowerAction(playerCharacter, target, targetLoc, ab, trains, pb);
                if (!miss && !msgCasted) {
                    PerformActionMsg castMsg = new PerformActionMsg(msg);
                    castMsg.setNumTrains(9999);
                    castMsg.setUnknown04(2);
                    DispatchMessage.dispatchMsgToInterestArea(playerCharacter, castMsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
                    msgCasted = true;
                }
            }
        }

        if (!msgCasted) {
            PerformActionMsg castMsg = new PerformActionMsg(msg);
            castMsg.setNumTrains(9999);
            castMsg.setUnknown04(2);
            DispatchMessage.dispatchMsgToInterestArea(playerCharacter, castMsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
            msgCasted = true;
        }

        //Handle chant
        if (pb != null && pb.isChant())
            for (ActionsBase ab : pb.getActions()) {
                AbstractPowerAction pa = ab.getPowerAction();
                if (pa != null)
                    if (pb.getToken() != 428950414 && pb.getToken() != 428884878)
                        pa.handleChant(playerCharacter, playerCharacter, targetLoc, trains, ab, pb);
                    else if (PowersManager.getTarget(msg) != null && PowersManager.getTarget(msg).isAlive())
                        pa.handleChant(playerCharacter, PowersManager.getTarget(msg), targetLoc, trains, ab, pb);
                    else
                        pa.handleChant(playerCharacter, null, targetLoc, trains, ab, pb);
            }


        //DispatchMessage.dispatchMsgToInterestArea(playerCharacter, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);


    }

    public static void finishUseMobPower(PerformActionMsg msg, Mob caster, int casterLiveCounter, int targetLiveCounter) {

        if (caster == null || msg == null)
            return;

        if (!caster.isAlive() || caster.getLiveCounter() != casterLiveCounter)
            return;

        // set player is not casting for regens
        caster.setIsCasting(false);


        PowersBase pb = PowersManager.powersBaseByToken.get(msg.getPowerUsedID());
        // clear power.
        caster.setLastMobPowerToken(0);

        if (pb == null) {
            Logger.error(
                    "finishUsePower(): Power '" + msg.getPowerUsedID()
                            + "' was not found on powersBaseByToken map.");
            return;
        }

        int trains = msg.getNumTrains();

        // update used power timer
        // verify player is not stunned or power type is blocked
        PlayerBonuses bonus = caster.getBonuses();
        if (bonus != null) {
            if (bonus.getBool(ModType.Stunned, SourceType.NONE))
                return;
            SourceType sourceType = SourceType.GetSourceType(pb.getCategory());
            if (bonus.getBool(ModType.BlockedPowerType, sourceType))
                return;
        }

        msg.setNumTrains(9999);
        msg.setUnknown04(2);
        DispatchMessage.sendToAllInRange(caster, msg);

        // get target loc
        Vector3fImmutable targetLoc = msg.getTargetLoc();
        if (targetLoc.x == 0f || targetLoc.z == 0f) {
            AbstractWorldObject tar = getTarget(msg);
            if (tar != null)
                targetLoc = tar.getLoc();
        }

        // get list of targets
        HashSet<AbstractWorldObject> allTargets = getAllTargets(
                getTarget(msg), msg.getTargetLoc(), caster, pb);
        for (AbstractWorldObject target : allTargets) {

            if (target == null)
                continue;


            //make sure target hasn't respawned since we began casting
            //skip this if liveCounter = -1 (from aoe)
            if (targetLiveCounter != -1)
                if (AbstractWorldObject.IsAbstractCharacter(target))
                    if (targetLiveCounter != ((AbstractCharacter) target).getLiveCounter())
                        continue;

            //make sure mob is awake to respond.
            //if (target instanceof AbstractIntelligenceAgent)
            //((AbstractIntelligenceAgent)target).enableIntelligence();
            // If Hit roll required, test hit
            if (pb.requiresHitRoll() && !pb.isWeaponPower() && testAttack(caster, target, pb, msg))
                //aggro mob even on a miss
                continue;

            // update target of used power timer

            if (target.getObjectType().equals(GameObjectType.PlayerCharacter))
                ((PlayerCharacter) target).setTimeStamp("LastCombatPlayer", System.currentTimeMillis());

            // finally Apply actions
            for (ActionsBase ab : pb.getActions()) {
                // get numTrains for power, skip action if invalid

                if (trains < ab.getMinTrains() || trains > ab.getMaxTrains())
                    continue;
                // If something blocks the action, then stop

                if (ab.blocked(target, pb, trains))
                    continue;
                // TODO handle overwrite stack order here
                String stackType = ab.getStackType();
                stackType = (stackType.equals("IgnoreStack")) ? Integer.toString(ab.getUUID()) : stackType;
                //				if (!stackType.equals("IgnoreStack")) {
                if (target.getEffects().containsKey(stackType)) {
                    // remove any existing power that overrides
                    Effect ef = target.getEffects().get(stackType);
                    AbstractEffectJob effect = null;
                    if (ef != null) {
                        JobContainer jc = ef.getJobContainer();
                        if (jc != null)
                            effect = (AbstractEffectJob) jc.getJob();
                    }
                    ActionsBase overwrite = effect.getAction();

                    if (overwrite == null) {
                        Logger.error("NULL ACTION FOR EFFECT " + effect.getPowerToken());
                        continue;
                    }
                    if (ab.getStackOrder() < overwrite.getStackOrder())
                        continue; // not high enough to overwrite
                    else if (ab.getStackOrder() > overwrite.getStackOrder()) {
                        effect.setNoOverwrite(true);
                        removeEffect(target, overwrite, true, false);
                    } else if (ab.getStackOrder() == overwrite.getStackOrder())
                        if (ab.greaterThanEqual()
                                && (trains >= effect.getTrains())) {
                            effect.setNoOverwrite(true);
                            removeEffect(target, overwrite, true, false);
                        } else if (ab.always())
                            removeEffect(target, overwrite, true, false);
                        else if (ab.greaterThan()
                                && (trains > effect.getTrains())) {
                            effect.setNoOverwrite(true);
                            removeEffect(target, overwrite, true, false);
                        } else if (ab.greaterThan() && pb.getToken() == effect.getPowerToken())
                            removeEffect(target, overwrite, true, false);

                        else
                            continue; // trains not high enough to overwrite
                }

                //				}
                runPowerAction(caster, target, targetLoc, ab, trains, pb);
            }
        }

        //Handle chant
        if (pb != null && pb.isChant())
            for (ActionsBase ab : pb.getActions()) {
                AbstractPowerAction pa = ab.getPowerAction();
                if (pa != null)
                    pa.handleChant(caster, caster, targetLoc, trains, ab, pb);
            }

        // TODO echo power use to everyone else
        msg.setNumTrains(9999);
        msg.setUnknown04(2);
        DispatchMessage.sendToAllInRange(caster, msg);

    }

    // *** Refactor : Wtf is this mess?

    private static boolean validMonsterType(AbstractWorldObject target, PowersBase pb) {

        if (pb == null || target == null)
            return false;

        String mtp = pb.getMonsterTypePrereq();

        if (mtp.length() == 0)
            return true;

        if (target.getObjectType().equals(GameObjectType.PlayerCharacter)) {

            PlayerCharacter pc = (PlayerCharacter) target;
            int raceID = 0;

            if (pc.race != null)
                raceID = pc.race.getRaceRuneID();

            switch (mtp) {
                case "Shade":
                    return raceID == 2015 || raceID == 2016;
                case "Elf":
                    return raceID == 2008 || raceID == 2009;
                case "Dwarf":
                    return raceID == 2006;
                case "Aracoix":
                    return raceID == 2002 || raceID == 2003;
                case "Irekei":
                    return raceID == 2013 || raceID == 2014;
                case "Vampire":
                    return raceID == 2028 || raceID == 2029;
            }
        } else if (target.getObjectType().equals(GameObjectType.Mob)) {
            Mob mob = (Mob) target;

            if (pb.targetMob() && !(mob.agentType.equals(AIAgentType.MOBILE)) && !mob.isSiege())
                return false;
            else if (pb.targetPet() && !mob.isPet() && !mob.isSiege())
                return false;

            switch (mtp) {
                case "Animal":
                    if ((mob.getObjectTypeMask() & MBServerStatics.MASK_BEAST) == 0)
                        return false;
                    break;
                case "NPC":
                    if ((mob.getObjectTypeMask() & MBServerStatics.MASK_HUMANOID) == 0)
                        return false;
                    break;
                case "Rat":
                    if ((mob.getObjectTypeMask() & MBServerStatics.MASK_RAT) == 0)
                        return false;
                    break;
                case "Siege":
                    if (!mob.isSiege())
                        return false;
                    break;
                case "Undead":
                    if ((mob.getObjectTypeMask() & MBServerStatics.MASK_UNDEAD) == 0)
                        return false;
                    break;
            }
            return true;
        } else
            return target.getObjectType().equals(GameObjectType.Building) && mtp.equals("Siege");
        return false;
    }

    public static void recvSummon(RecvSummonsMsg msg, ClientConnection origin) {
        PlayerCharacter pc = SessionManager.getPlayerCharacter(origin);
        if (pc == null)
            return;

        PlayerCharacter source = PlayerCharacter.getFromCache(msg.getSourceID());
        if (source == null)
            return;

        long tooLate = pc.getSummoner(source.getObjectUUID());
        if (tooLate < System.currentTimeMillis()) {
            ChatManager.chatInfoError(pc, "You waited too long to " + (msg.accepted() ? "accept" : "decline") + " the summons.");
            pc.removeSummoner(source.getObjectUUID());
            return;
        }

        if (pc.getBonuses() != null && pc.getBonuses().getBool(ModType.BlockedPowerType, SourceType.SUMMON)) {
            ErrorPopupMsg.sendErrorMsg(pc, "You have been blocked from receiving summons!");
            ErrorPopupMsg.sendErrorMsg(source, "Target is blocked from receiving summons!");
            pc.removeSummoner(source.getObjectUUID());
            return;
        }
        pc.removeSummoner(source.getObjectUUID());

        // Handle Accepting or Denying a summons.
        // set timer based on summon type.
        boolean wentThrough = false;
        if (msg.accepted())
            // summons accepted, let's move the player if within time
            if (source.isAlive()) {

                //				//make sure summons handled in time
                ConcurrentHashMap<String, JobContainer> timers = source.getTimers();
                //				if (timers == null || !timers.containsKey("SummonSend")) {
                //					ChatManager.chatInfoError(pc, "You waited too long to " + (msg.accepted() ? "accept" : "decline") + " the summons.");
                //					return;
                //				}

                //				// clear last summons accept timer
                //	timers.get("SummonSend").cancelJob();
                //timers.remove("SummonSend");
                // cancel any other summons waiting
                timers = pc.getTimers();
                if (timers != null && timers.containsKey("Summon"))
                    timers.get("Summon").cancelJob();

                // get time to wait before summons goes through
                BaseClass base = source.baseClass;
                PromotionClass promo = source.getPromotionClass();
                int duration;


                //determine if in combat with another player


                //comment out this block to disable combat timer
                //				if (lastAttacked < 60000) {
                //					if (pc.inSafeZone()) //player in safe zone, no need for combat timer
                //						combat = false;
                //					else if (source.inSafeZone()) //summoner in safe zone, apply combat timer
                //						combat = true;
                //					else if ((source.getLoc().distance2D(pc.getLoc())) > 6144f)
                //						combat = true; //more than 1.5x width of zone, not tactical summons
                //				}

                if (promo != null && promo.getObjectUUID() == 2519)
                    duration = 10000; // Priest summons, 10 seconds
                else if (base != null && base.getObjectUUID() == 2501)
                    duration = 15000; // Healer Summons, 15 seconds
                else
                    duration = 45000; // Belgosh Summons, 45 seconds


                // Teleport to summoners location
                FinishSummonsJob fsj = new FinishSummonsJob(source, pc);
                JobContainer jc = JobScheduler.getInstance().scheduleJob(fsj,
                        duration);
                if (timers != null)
                    timers.put("Summon", jc);
                wentThrough = true;
            }

        // Summons failed
        if (!wentThrough)
            // summons refused. Let's be nice and reset recycle timer
            if (source != null) {

                // Send summons refused Message
                ErrorPopupMsg.sendErrorPopup(source, 29);

                // recycle summons power
                //finishRecycleTime(428523680, source, true);
            }
    }

    public static void sendRecyclePower(int token, ClientConnection origin) {
        RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(token);

        Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), recyclePowerMsg);
        DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);

    }

    public static boolean verifyInvalidRange(AbstractCharacter ac,
                                             AbstractWorldObject target, float range) {
        Vector3fImmutable sl = ac.getLoc();
        Vector3fImmutable tl = target.getLoc();
        if (target.getObjectType().equals(GameObjectType.Item)) {

            Item item = (Item) target;
            AbstractGameObject owner = item.getOwner();

            if (owner == null || owner.getObjectType().equals(GameObjectType.Account))
                return true;

            if (owner.getObjectType().equals(GameObjectType.PlayerCharacter) || owner.getObjectType().equals(GameObjectType.Mob)) {
                AbstractCharacter acOwner = (AbstractCharacter) owner;
                CharacterItemManager itemMan = acOwner.charItemManager;
                if (itemMan == null)
                    return true;
                if (itemMan.inventoryContains(item)) {
                    tl = acOwner.getLoc();
                    return !(sl.distanceSquared(tl) <= sqr(range));
                }
                return true;
            }
            return true;
        }

        range += (calcHitBox(ac) + calcHitBox(target));


        float distanceToTarget = sl.distanceSquared(tl);//distance to center of target

        return distanceToTarget > range * range;

    }

    public static float calcHitBox(AbstractWorldObject ac) {
        //TODO Figure out how Str Affects HitBox
        float hitBox = 1;
        switch (ac.getObjectType()) {
            case PlayerCharacter:
                PlayerCharacter pc = (PlayerCharacter) ac;
                if (MBServerStatics.COMBAT_TARGET_HITBOX_DEBUG)
                    Logger.info("Hit box radius for " + pc.getFirstName() + " is " + ((int) pc.statStrBase / 200f));
                hitBox = 2f + (int) ((PlayerCharacter) ac).statStrBase / 50f;
                break;

            case Mob:
                Mob mob = (Mob) ac;
                if (MBServerStatics.COMBAT_TARGET_HITBOX_DEBUG)
                    Logger.info("Hit box radius for " + mob.getFirstName()
                            + " is " + ((Mob) ac).getMobBase().getHitBoxRadius());

                hitBox = ((Mob) ac).getMobBase().getHitBoxRadius();
                break;
            case Building:
                Building building = (Building) ac;
                if (building.getBlueprint() == null)
                    return 32;
                hitBox = Math.max(building.getBlueprint().getBuildingGroup().getExtents().x,
                        building.getBlueprint().getBuildingGroup().getExtents().y);
                if (MBServerStatics.COMBAT_TARGET_HITBOX_DEBUG)
                    Logger.info("Hit box radius for " + building.getName() + " is " + hitBox);
                break;

        }
        return hitBox;
    }

    // Apply a power based on it's IDString
    public static void applyPower(AbstractCharacter ac, AbstractWorldObject target,
                                  Vector3fImmutable targetLoc, String ID, int trains, boolean fromItem) {
        if (ac == null || target == null || !ac.isAlive())
            return;
        PowersBase pb = powersBaseByIDString.get(ID);
        if (pb == null) {
            Logger.error(
                    "applyPower(): Got NULL on powersBaseByIDString table lookup for: "
                            + ID);
            return;
        }
        applyPowerA(ac, target, targetLoc, pb, trains, fromItem);
    }

    // Apply a power based on it's Token
    public static void applyPower(AbstractCharacter ac, AbstractWorldObject target,
                                  Vector3fImmutable targetLoc, int token, int trains, boolean fromItem) {
        if (ac == null || target == null)
            return;

        //Don't apply power if ac is dead, unless death shroud or safe mode
        if (!ac.isAlive())
            if (!(token == -1661758934 || token == 1672601862))
                return;

        PowersBase pb = powersBaseByToken.get(token);
        if (pb == null) {
            Logger.error(
                    "applyPower(): Got NULL on powersBaseByToken table lookup for: "
                            + token);
            return;
        }
        applyPowerA(ac, target, targetLoc, pb, trains, fromItem);
    }

    private static void applyPowerA(AbstractCharacter ac, AbstractWorldObject target,
                                    Vector3fImmutable targetLoc, PowersBase pb, int trains,
                                    boolean fromItem) {
        int time = pb.getCastTime(trains);
        if (!fromItem)
            finishApplyPowerA(ac, target, targetLoc, pb, trains, false);
        else if (time == 0)
            finishApplyPower(ac, target, targetLoc, pb, trains, ac.getLiveCounter());
        else {

            ac.setItemCasting(true);
            int tarType = (target == null) ? 0 : target.getObjectType().ordinal();
            int tarID = (target == null) ? 0 : target.getObjectUUID();

            // start the action animation
            PerformActionMsg msg = new PerformActionMsg(pb.getToken(),
                    trains, ac.getObjectType().ordinal(), ac.getObjectUUID(), tarType, tarID, 0,
                    0, 0, 1, 0);
            DispatchMessage.sendToAllInRange(target, msg);


            ConcurrentHashMap<String, JobContainer> timers = ac.getTimers();

            if (timers.containsKey(Integer.toString(pb.getToken()))) {
                JobContainer jc = timers.get(Integer.toString(pb.getToken()));
                if (jc != null)
                    jc.cancelJob();
            }

            //				// clear any other items being used
            //				JobContainer jc = ac.getLastItem();
            //				if (jc != null) {
            //					jc.cancelJob();
            //					ac.clearLastItem();
            //				}
            // run timer job to end cast
            UseItemJob uij = new UseItemJob(ac, target, pb, trains, ac.getLiveCounter());
            JobContainer jc = js.scheduleJob(uij, time);

            // make lastItem
            timers.put(Integer.toString(pb.getToken()), jc);
        }
    }

    public static void finishApplyPower(AbstractCharacter ac,
                                        AbstractWorldObject target, Vector3fImmutable targetLoc,
                                        PowersBase pb, int trains, int liveCounter) {

        if (ac != null)
            ac.setItemCasting(false);
        if (ac == null || target == null || pb == null)
            return;

        ac.clearTimer(Integer.toString(pb.getToken()));
        if (liveCounter == ac.getLiveCounter())
            finishApplyPowerA(ac, target, targetLoc, pb, trains, false);
    }

    public static void finishApplyPowerA(AbstractCharacter ac,
                                         AbstractWorldObject target, Vector3fImmutable targetLoc,
                                         PowersBase pb, int trains, boolean fromChant) {
        // finally Apply actions
        ArrayList<ActionsBase> actions = pb.getActions();
        for (ActionsBase ab : actions) {
            // get numTrains for power, skip action if invalid
            if (trains < ab.getMinTrains() || trains > ab.getMaxTrains())
                continue;
            // If something blocks the action, then stop
            if (ab.blocked(target, pb, trains))
                // sendPowerMsg(pc, 5, msg);
                continue;
            // TODO handle overwrite stack order here
            String stackType = ab.getStackType();
            stackType = (stackType.equals("IgnoreStack")) ? Integer.toString(ab.getUUID()) : stackType;
            if (target.getEffects().containsKey(stackType)) {
                // remove any existing power that overrides
                Effect ef = target.getEffects().get(stackType);
                AbstractEffectJob effect = null;
                if (ef != null) {
                    JobContainer jc = ef.getJobContainer();
                    if (jc != null)
                        effect = (AbstractEffectJob) jc.getJob();
                }
                ActionsBase overwrite = effect.getAction();
                PowersBase pbOverwrite = effect.getPower();
                if (pbOverwrite != null && pbOverwrite.equals(pb)
                        && (trains >= effect.getTrains()))
                    removeEffect(target, overwrite, true, fromChant);
                else if (ab.getStackOrder() < overwrite.getStackOrder())
                    continue; // not high enough to overwrite
                else if (ab.getStackOrder() > overwrite.getStackOrder())
                    removeEffect(target, overwrite, true, false);
                else if (ab.getStackOrder() == overwrite.getStackOrder())
                    if (ab.greaterThanEqual()
                            && (trains >= effect.getTrains()))
                        removeEffect(target, overwrite, true, false);
                    else if (ab.always())
                        removeEffect(target, overwrite, true, false);
                    else if (ab.greaterThan()
                            && (trains > effect.getTrains()))
                        removeEffect(target, overwrite, true, false);
                    else if (ab.greaterThan() && pb.getToken() == effect.getPowerToken())
                        removeEffect(target, overwrite, true, false);
                    else
                        continue; // trains not high enough to overwrite
            }
            if (fromChant)
                targetLoc = Vector3fImmutable.ZERO;
            runPowerAction(ac, target, targetLoc, ab, trains, pb);
        }

        //Handle chant
        if (pb != null && pb.isChant())
            for (ActionsBase ab : pb.getActions()) {
                AbstractPowerAction pa = ab.getPowerAction();
                if (pa != null)
                    pa.handleChant(ac, target, targetLoc, trains, ab, pb);
            }

        // for chants, only send the animation if the character is not is not moving or casting
        boolean doAnimation = true;

        if (target.getObjectType().equals(GameObjectType.PlayerCharacter)) {
            PlayerCharacter pc = (PlayerCharacter) target;
            if (pb != null && pb.isChant() && (pc.isMoving() || pc.isCasting()))
                doAnimation = false;
        }

        if (pb.getToken() == 428950414)
            doAnimation = true;

        if (doAnimation) {
            PerformActionMsg msg = new PerformActionMsg(pb.getToken(), 9999, ac
                    .getObjectType().ordinal(), ac.getObjectUUID(), target.getObjectType().ordinal(),
                    target.getObjectUUID(), 0, 0, 0, 2, 0);

            DispatchMessage.sendToAllInRange(ac, msg);

        }
    }

    public static void runPowerAction(AbstractCharacter source,
                                      AbstractWorldObject awo, Vector3fImmutable targetLoc,
                                      ActionsBase ab, int trains, PowersBase pb) {
        AbstractPowerAction pa = ab.getPowerAction();
        if (pa == null) {
            Logger.error(
                    "runPowerAction(): PowerAction not found of IDString: "
                            + ab.getEffectID());
            return;
        }
        pa.startAction(source, awo, targetLoc, trains, ab, pb);
    }

    public static void runPowerAction(AbstractCharacter source,
                                      AbstractWorldObject awo, Vector3fImmutable targetLoc,
                                      ActionsBase ab, int trains, PowersBase pb, int duration) {
        AbstractPowerAction pa = ab.getPowerAction();
        if (pa == null) {
            Logger.error(
                    "runPowerAction(): PowerAction not found of IDString: "
                            + ab.getEffectID());
            return;
        }
        pa.startAction(source, awo, targetLoc, trains, ab, pb, duration);
    }

    public static HashSet<AbstractWorldObject> getAllTargets(
            AbstractWorldObject target, Vector3fImmutable tl,
            PlayerCharacter pc, PowersBase pb) {
        HashSet<AbstractWorldObject> allTargets;
        if (pb.isAOE()) {
            Vector3fImmutable targetLoc = null;
            if (pb.usePointBlank()) {
                targetLoc = pc.getLoc();
            } else {
                if (target != null) {
                    targetLoc = target.getLoc();
                } else {
                    targetLoc = tl;
                    try {
                        targetLoc = targetLoc.setY(Terrain.getWorldHeight(targetLoc)); //on ground
                    } catch (Exception e) {
                        Logger.error(e);
                        targetLoc = tl;
                    }

                }
            }

            if (targetLoc.x == 0f || targetLoc.z == 0f)
                return new HashSet<>(); // invalid loc,
            // return
            // nothing

            //first find targets in range quickly with QTree
            if (pb.targetPlayer() && pb.targetMob())
                // Player and mobs
                allTargets = WorldGrid.getObjectsInRangePartial(
                        targetLoc, pb.getRadius(), MBServerStatics.MASK_MOBILE);
            else if (pb.targetPlayer())
                // Player only
                allTargets = WorldGrid.getObjectsInRangePartial(
                        targetLoc, pb.getRadius(), MBServerStatics.MASK_PLAYER);
            else if (pb.targetMob())
                // Mob only
                allTargets = WorldGrid.getObjectsInRangePartial(
                        targetLoc, pb.getRadius(), MBServerStatics.MASK_MOB
                                | MBServerStatics.MASK_PET);
            else if (pb.targetPet())
                //Pet only
                allTargets = WorldGrid.getObjectsInRangePartial(
                        targetLoc, pb.getRadius(), MBServerStatics.MASK_PET);
            else if (pb.targetNecroPet())
                allTargets = WorldGrid.getObjectsInRangePartialNecroPets(
                        targetLoc, pb.getRadius());
            else
                allTargets = WorldGrid.getObjectsInRangePartial(
                        targetLoc, pb.getRadius(), 0);

            // cleanup self, group and nation targets if needed
            Iterator<AbstractWorldObject> awolist = allTargets.iterator();
            while (awolist.hasNext()) {
                AbstractWorldObject awo = awolist.next();
                if (awo == null) {
                    awolist.remove(); // won't hit a null
                    continue;
                }

                //see if targets are within 3D range of each other
                Vector3fImmutable tloc = awo.getLoc();

                if (tloc.distanceSquared(targetLoc) > sqr(pb.getRadius())) {
                    awolist.remove(); // too far away
                    continue;
                }

                if (pb.isCasterFriendly() && pc.equals(awo)) {
                    awolist.remove(); // won't hit self
                    continue;
                }

                if (!awo.isAlive()) {
                    awolist.remove(); // too far away
                    continue;
                }

                if (awo.getObjectType().equals(GameObjectType.PlayerCharacter)) {

                    PlayerCharacter pcc = (PlayerCharacter) awo;

                    if (pb.isGroupFriendly() && GroupManager.getGroup(pc) != null && GroupManager.getGroup(pcc) != null)
                        if (GroupManager.getGroup(pc).equals(GroupManager.getGroup(pcc))) {
                            awolist.remove(); // Won't hit group members
                            continue;
                        }
                    if (pb.isNationFriendly() && pc.getGuild() != null &&
                            pc.getGuild().getNation() != null && pcc.getGuild() != null &&
                            pc.getGuild().getNation() != null)
                        if (pc.getGuild().getNation().equals(pcc.getGuild().getNation())) {
                            awolist.remove(); // Won't hit nation members
                            continue;
                        }

                    // Remove players for non-friendly spells in safe zone
                    if (pb.isHarmful() && (pcc.inSafeZone() || pc.inSafeZone())) {
                        awolist.remove();
                        continue;
                    }
                }
            }
            // Trim list down to max size closest targets, limited by max
            // Player/Mob amounts
            allTargets = RangeBasedAwo.getSortedList(allTargets, targetLoc, pb
                    .getMaxNumPlayerTargets(), pb.getMaxNumMobTargets());
        } else if (pb.targetGroup()) {

            if (GroupManager.getGroup(pc) != null) {
                allTargets = WorldGrid.getObjectsInRangePartial(pc
                        .getLoc(), pb.getRange(), MBServerStatics.MASK_PLAYER);
                Iterator<AbstractWorldObject> awolist = allTargets.iterator();
                while (awolist.hasNext()) {

                    AbstractWorldObject awo = awolist.next();

                    if (!(awo.getObjectType().equals(GameObjectType.PlayerCharacter))) {
                        awolist.remove(); // remove non players if there are any
                        continue;
                    }
                    PlayerCharacter pcc = (PlayerCharacter) awo;

                    if (GroupManager.getGroup(pcc) == null)
                        awolist.remove(); // remove players not in a group
                    else if (!GroupManager.getGroup(pcc).equals(GroupManager.getGroup(pc)))
                        awolist.remove(); // remove if not same group

                }
            } else {
                allTargets = new HashSet<>();
                allTargets.add(pc); // no group, use only self
            }
        } else {
            allTargets = new HashSet<>();
            if (pb.targetSelf())
                allTargets.add(pc);
            else if (pb.targetFromLastTarget())
                allTargets.add(target);
            else if (pb.targetFromNearbyMobs())
                allTargets.add(target); // need better way to do this later
            else
                // targetByName
                allTargets.add(target); // need to get name later
            // can't target self if caster friendly
            if (pb.isCasterFriendly() && allTargets.contains(pc))
                allTargets.remove(0);
        }

        Iterator<AbstractWorldObject> awolist = allTargets.iterator();
        while (awolist.hasNext()) {
            AbstractWorldObject awo = awolist.next();

            //See if target is valid type
            if (!validMonsterType(awo, pb)) {
                awolist.remove();
                continue;
            }

            if (awo != null && awo.getObjectType().equals(GameObjectType.PlayerCharacter)) {

                // Remove players who are in safe mode
                PlayerCharacter pcc = (PlayerCharacter) awo;
                PlayerBonuses bonuses = pcc.getBonuses();

                if (bonuses != null && bonuses.getBool(ModType.ImmuneToPowers, SourceType.NONE)) {
                    awolist.remove();
                    continue;
                }

                //remove if power is harmful and caster or target is in safe zone
                if (pb.isHarmful() && (pcc.inSafeZone() || pc.inSafeZone())) {
                    awolist.remove();
                    continue;
                }
            }
        }

        // verify target has proper effects applied to receive power
        if (pb.getTargetEffectPrereqs().size() > 0) {
            Iterator<AbstractWorldObject> it = allTargets.iterator();
            while (it.hasNext()) {
                boolean passed = false;
                AbstractWorldObject awo = it.next();
                if (awo.getEffects() != null) {
                    for (PowerPrereq pp : pb.getTargetEffectPrereqs()) {
                        EffectsBase eb = PowersManager.getEffectByIDString(pp.getEffect());
                        if (awo.containsEffect(eb.getToken())) {
                            passed = true;
                            break;
                        }
                    }
                    if (!passed)
                        it.remove();
                } else
                    it.remove(); //awo is missing it's effects list
            }
        }
        return allTargets;
    }

    public static HashSet<AbstractWorldObject> getAllTargets(
            AbstractWorldObject target, Vector3fImmutable tl,
            AbstractCharacter caster, PowersBase pb) {
        HashSet<AbstractWorldObject> allTargets;
        if (pb.isAOE()) {
            Vector3fImmutable targetLoc = tl;
            if (pb.usePointBlank()) {
                targetLoc = caster.getLoc();
            } else {
                if (target != null) {
                    targetLoc = target.getLoc();
                } else {
                    targetLoc = tl;
                    try {
                        targetLoc = targetLoc.setY(Terrain.getWorldHeight(targetLoc)); //on ground
                    } catch (Exception e) {
                        Logger.error(e);
                    }

                }
            }

            if (targetLoc.x == 0f || targetLoc.z == 0f)
                return new HashSet<>(); // invalid loc,

            //first find targets in range quickly with QTree
            if (pb.targetPlayer() && pb.targetMob())
                // Player and mobs
                allTargets = WorldGrid.getObjectsInRangePartial(
                        targetLoc, pb.getRadius(), MBServerStatics.MASK_MOBILE);
            else if (pb.targetPlayer())
                // Player only
                allTargets = WorldGrid.getObjectsInRangePartial(
                        targetLoc, pb.getRadius(), MBServerStatics.MASK_PLAYER);
            else if (pb.targetMob())
                // Mob only
                allTargets = WorldGrid.getObjectsInRangePartial(
                        targetLoc, pb.getRadius(), MBServerStatics.MASK_MOB
                                | MBServerStatics.MASK_PET);
            else if (pb.targetPet())
                //Pet only
                allTargets = WorldGrid.getObjectsInRangePartial(
                        targetLoc, pb.getRadius(), MBServerStatics.MASK_PET);
            else if (pb.targetNecroPet())
                allTargets = WorldGrid.getObjectsInRangePartialNecroPets(
                        targetLoc, pb.getRadius());
            else
                allTargets = WorldGrid.getObjectsInRangePartial(
                        targetLoc, pb.getRadius(), 0);

            // cleanup self, group and nation targets if needed
            Iterator<AbstractWorldObject> awolist = allTargets.iterator();
            while (awolist.hasNext()) {
                AbstractWorldObject awo = awolist.next();
                if (awo == null) {
                    awolist.remove(); // won't hit a null
                    continue;
                }

                //see if targets are within 3D range of each other
                Vector3fImmutable tloc = awo.getLoc();

                if (tloc.distanceSquared(targetLoc) > sqr(pb.getRadius())) {
                    awolist.remove(); // too far away
                    continue;
                }

                if (pb.isCasterFriendly() && caster.equals(awo)) {
                    awolist.remove(); // won't hit self
                    continue;
                }

                if (awo.getObjectType() == GameObjectType.Mob) {
                    awolist.remove(); // Won't hit other mobs.
                    continue;
                }
            }
            // Trim list down to max size closest targets, limited by max
            // Player/Mob amounts
            allTargets = RangeBasedAwo.getSortedList(allTargets, targetLoc, pb
                    .getMaxNumPlayerTargets(), pb.getMaxNumMobTargets());
        } else if (pb.targetGroup()) {
            allTargets = new HashSet<>();
            allTargets.add(caster); // no group, use only self
        } else {
            allTargets = new HashSet<>();
            if (pb.targetSelf())
                allTargets.add(caster);
            else if (pb.targetFromLastTarget())
                allTargets.add(target);
            else if (pb.targetFromNearbyMobs())
                allTargets.add(target); // need better way to do this later
            else
                // targetByName
                allTargets.add(target); // need to get name later
            // can't target self if caster friendly
            if (pb.isCasterFriendly() && allTargets.contains(caster))
                allTargets.remove(caster);
        }

        Iterator<AbstractWorldObject> awolist = allTargets.iterator();
        while (awolist.hasNext()) {
            AbstractWorldObject awo = awolist.next();

            //See if target is valid type
            if (!validMonsterType(awo, pb)) {
                awolist.remove();
                continue;
            }

            if (awo != null && awo.getObjectType().equals(GameObjectType.PlayerCharacter)) {
                // Remove players who are in safe mode
                PlayerCharacter pcc = (PlayerCharacter) awo;
                PlayerBonuses bonuses = pcc.getBonuses();
                if (bonuses != null && bonuses.getBool(ModType.ImmuneToPowers, SourceType.NONE)) {
                    awolist.remove();
                    continue;
                }
            }
        }

        // verify target has proper effects applied to receive power
        if (pb.getTargetEffectPrereqs().size() > 0) {
            Iterator<AbstractWorldObject> it = allTargets.iterator();
            while (it.hasNext()) {
                boolean passed = false;
                AbstractWorldObject awo = it.next();
                if (awo.getEffects() != null) {
                    for (PowerPrereq pp : pb.getTargetEffectPrereqs()) {
                        EffectsBase eb = PowersManager.getEffectByIDString(pp.getEffect());
                        if (awo.containsEffect(eb.getToken())) {
                            passed = true;
                            break;
                        }
                    }
                    if (!passed)
                        it.remove();
                } else
                    it.remove(); //awo is missing it's effects list
            }
        }
        return allTargets;
    }

    // removes an effect before time is finished
    public static void removeEffect(AbstractWorldObject awo, ActionsBase toRemove,
                                    boolean overwrite, boolean fromChant) {
        if (toRemove == null)
            return;

        String stackType = toRemove.getStackType();
        stackType = (stackType.equals("IgnoreStack")) ? Integer
                .toString(toRemove.getUUID()) : stackType;
        if (fromChant) {
            Effect eff = awo.getEffects().get(stackType);
            if (eff != null)
                eff.cancelJob(true);
        } else
            awo.cancelEffect(stackType, overwrite);
    }

    // removes an effect when timer finishes
    public static void finishEffectTime(AbstractWorldObject source,
                                        AbstractWorldObject awo, ActionsBase toRemove, int trains) {
        if (awo == null || toRemove == null)
            return;

        // remove effect from player
        String stackType = toRemove.getStackType();
        if (stackType.equals("IgnoreStack"))
            stackType = Integer.toString(toRemove.getUUID());
        awo.endEffect(stackType);
    }

    // removes an effect when timer is canceled
    public static void cancelEffectTime(AbstractWorldObject source,
                                        AbstractWorldObject awo, PowersBase pb, EffectsBase eb,
                                        ActionsBase toRemove, int trains, AbstractEffectJob efj) {
        if (awo == null || pb == null || eb == null || toRemove == null)
            return;
        eb.endEffect(source, awo, trains, pb, efj);
    }

    // called when cooldown ends letting player cast next spell
    public static void finishCooldownTime(PerformActionMsg msg, PlayerCharacter pc) {
        // clear spell so player can cast again
        // if (pc != null)
        // pc.clearLastPower();
    }

    // called when recycle time ends letting player cast spell again
    public static void finishRecycleTime(PerformActionMsg msg, PlayerCharacter pc,
                                         boolean canceled) {
        finishRecycleTime(msg.getPowerUsedID(), pc, canceled);
    }

    public static void finishRecycleTime(int token, PlayerCharacter pc,
                                         boolean canceled) {
        if (pc == null)
            return;

        ConcurrentHashMap<Integer, JobContainer> recycleTimers = pc
                .getRecycleTimers();
        // clear recycle time
        if (recycleTimers != null)
            if (recycleTimers.containsKey(token)) {
                if (canceled) {
                    JobContainer jc = recycleTimers.get(token);
                    if (jc != null)
                        jc.cancelJob();
                }
                recycleTimers.remove(token);
            }

        // send recycle message to unlock power

        RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(token);
        Dispatch dispatch = Dispatch.borrow(pc, recyclePowerMsg);
        DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);

    }

    // Called when a fail condition is met by player
    // such as moving, taking damage, ect.

    public static void cancelUseLastPower(PlayerCharacter pc) {

        if (pc == null)
            return;

        // set player is not casting for regens
        if (pc.isCasting()) {
            pc.update();
        }
        pc.setIsCasting(false);

        UsePowerJob lastPower = null;
        JobContainer jc = pc.getLastPower();

        if (jc != null)
            lastPower = ((UsePowerJob) jc.getJob());

        if (lastPower == null)
            return;

        // clear recycle timer
        int token = lastPower.getToken();

        if (pc.getRecycleTimers().contains(token))
            finishRecycleTime(token, pc, true);

        //			pc.getRecycleTimers().remove(token);
        // Cancel power
        js.cancelScheduledJob(lastPower);

        // clear last power
        pc.clearLastPower();

    }

    private static AbstractWorldObject getTarget(PerformActionMsg msg) {

        int type = msg.getTargetType();
        int UUID = msg.getTargetID();

        if (type == -1 || type == 0 || UUID == -1 || UUID == 0)
            return null;

        return (AbstractWorldObject) DbManager.getObject(GameObjectType.values()[type], UUID);
    }

    public static boolean testAttack(PlayerCharacter pc, AbstractWorldObject awo,
                                     PowersBase pb, PerformActionMsg msg) {
        // Get defense for target
        float atr = CharacterSkill.getATR(pc, pb.getSkillName());
        float defense;

        if (AbstractWorldObject.IsAbstractCharacter(awo)) {
            AbstractCharacter tar = (AbstractCharacter) awo;
            defense = tar.getDefenseRating();
        } else
            defense = 0f;

        // Get hit chance

        int chance;

        if (atr > defense || defense == 0)
            chance = 94;
        else {
            float dif = atr / defense;
            if (dif <= 0.8f)
                chance = 4;
            else
                chance = ((int) (450 * (dif - 0.8f)) + 4);
        }

        // calculate hit/miss
        int roll = ThreadLocalRandom.current().nextInt(100);

        boolean disable = true;
        if (roll < chance) {
            // Hit, check if dodge kicked in
            if (awo instanceof AbstractCharacter) {
                AbstractCharacter tarAc = (AbstractCharacter) awo;
                // Handle Dodge passive
                if (testPassive(pc, tarAc, "Dodge")) {
                    // Dodge fired, send dodge message
                    PerformActionMsg dodgeMsg = new PerformActionMsg(msg);
                    dodgeMsg.setTargetType(awo.getObjectType().ordinal());
                    dodgeMsg.setTargetID(awo.getObjectUUID());
                    sendPowerMsg(pc, 4, dodgeMsg);
                    return true;
                }
            }
            return false;
        } else {
            // Miss. Send miss message
            PerformActionMsg missMsg = new PerformActionMsg(msg);

            missMsg.setTargetType(awo.getObjectType().ordinal());
            missMsg.setTargetID(awo.getObjectUUID());
            sendPowerMsg(pc, 3, missMsg);
            return true;
        }
    }

    public static boolean testAttack(Mob caster, AbstractWorldObject awo,
                                     PowersBase pb, PerformActionMsg msg) {
        // Get defense for target
        float atr = 2000;
        float defense;

        if (AbstractWorldObject.IsAbstractCharacter(awo)) {
            AbstractCharacter tar = (AbstractCharacter) awo;
            defense = tar.getDefenseRating();
        } else
            defense = 0f;
        // Get hit chance

        int chance;

        if (atr > defense || defense == 0)
            chance = 94;
        else {
            float dif = atr / defense;
            if (dif <= 0.8f)
                chance = 4;
            else
                chance = ((int) (450 * (dif - 0.8f)) + 4);
        }

        // calculate hit/miss
        int roll = ThreadLocalRandom.current().nextInt(100);

        if (roll < chance) {
            // Hit, check if dodge kicked in
            if (AbstractWorldObject.IsAbstractCharacter(awo)) {
                AbstractCharacter tarAc = (AbstractCharacter) awo;
                // Handle Dodge passive
                return testPassive(caster, tarAc, "Dodge");
            }
            return false;
        } else
            return true;
    }

    public static void sendPowerMsg(PlayerCharacter playerCharacter, int type, PerformActionMsg msg) {

        if (playerCharacter == null)
            return;

        msg.setUnknown05(type);

        switch (type) {
            case 3:
            case 4:
                msg.setUnknown04(2);
                DispatchMessage.dispatchMsgToInterestArea(playerCharacter, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
                break;
            default:
                msg.setUnknown04(1);
                Dispatch dispatch = Dispatch.borrow(playerCharacter, msg);
                DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
        }
    }

    public static void sendEffectMsg(PlayerCharacter pc, int type, ActionsBase ab, PowersBase pb) {

        if (pc == null)
            return;

        try {

            EffectsBase eb = PowersManager.effectsBaseByIDString.get(ab.getEffectID());

            if (eb == null)
                return;

            ApplyEffectMsg aem = new ApplyEffectMsg(pc, pc, 0, eb.getToken(), 9, pb.getToken(), pb.getName());
            aem.setUnknown03(type);
            DispatchMessage.dispatchMsgToInterestArea(pc, aem, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);


        } catch (Exception e) {
            Logger.error(e.getMessage());
        }

    }

    public static void sendEffectMsg(PlayerCharacter pc, int type, EffectsBase eb) {

        if (pc == null)
            return;
        try {

            if (eb == null)
                return;
            ApplyEffectMsg aem = new ApplyEffectMsg(pc, pc, 0, eb.getToken(), 0, eb.getToken(), "");
            aem.setUnknown03(type);
            aem.setUnknown05(1);

            DispatchMessage.dispatchMsgToInterestArea(pc, aem, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);


        } catch (Exception e) {
            Logger.error(e.getMessage());
        }

    }

    public static void sendMobPowerMsg(Mob mob, int type, PerformActionMsg msg) {

        msg.setUnknown05(type);
        switch (type) {
            case 3:
            case 4:
                DispatchMessage.sendToAllInRange(mob, msg);

        }
    }

    private static boolean testPassive(AbstractCharacter source,
                                       AbstractCharacter target, String type) {

        float chance = target.getPassiveChance(type, source.getLevel(), false);

        if (chance == 0f)
            return false;

        // max 75% chance of passive to fire
        if (chance > 75f)
            chance = 75f;

        int roll = ThreadLocalRandom.current().nextInt(100);
        // Passive fired
        // TODO send message
        // Passive did not fire
        return roll < chance;
    }

    private static boolean validateTarget(AbstractWorldObject target,
                                          PlayerCharacter pc, PowersBase pb) {

        //group target. uses pbaoe rules
        if (pb.targetGroup())
            return true;

            // target is player
        else if ((target.getObjectTypeMask() & MBServerStatics.MASK_PLAYER) != 0) {
            if (pb.targetPlayer())
                if (pb.isGroupOnly()) { //single target group only power
                    PlayerCharacter trg = (PlayerCharacter) target;

                    if (GroupManager.getGroup(trg) != null && GroupManager.getGroup(pc) != null)
                        if (GroupManager.getGroup(trg).getObjectUUID() == GroupManager.getGroup(pc).getObjectUUID())
                            return true; // both in same group, good to go
                    return trg != null && pc.getObjectUUID() == trg.getObjectUUID();
                } else
                    return true; // can target player, good to go.
            else if (target.getObjectUUID() == pc.getObjectUUID() && pb.targetSelf())
                return true; // can target self, good to go
            else if (pb.targetCorpse()) {
                //target is dead player
                PlayerCharacter trg = (PlayerCharacter) target;
                return !trg.isAlive();
            } else {
                PlayerCharacter trg = (PlayerCharacter) target;

                if (pb.targetGroup())
                    if (GroupManager.getGroup(trg) != null && GroupManager.getGroup(pc) != null)
                        if (GroupManager.getGroup(trg).getObjectUUID() == GroupManager.getGroup(pc)
                                .getObjectUUID())
                            return true; // both in same group, good to go
                if (pb.targetGuildLeader())
                    if (pc.getGuild() != null)
                        if (pc.getGuild().getGuildLeaderUUID() == trg.getObjectUUID())
                            return true; // can hit guild leader, good to go
            }
            String outmsg = "Invalid Target";
            ChatManager.chatSystemInfo(pc, outmsg);
            return false; // can't target player, stop here
        } // target is mob
        else if ((target.getObjectTypeMask() & MBServerStatics.MASK_MOB) != 0)
            return pb.targetMob();

            // target is pet
        else if ((target.getObjectTypeMask() & MBServerStatics.MASK_PET) != 0)
            return pb.targetPet();

            // target is Building
        else if ((target.getObjectTypeMask() & MBServerStatics.MASK_BUILDING) != 0)
            return pb.targetBuilding();

        else if (target.getObjectType().equals(GameObjectType.Item)) {
            Item item = (Item) target;
            if (pb.targetItem())
                return true;
                // TODO add these checks later
            else if (pb.targetArmor() && item.template.item_type.equals(ItemType.ARMOR))
                return true;
            else if (pb.targetJewelry() && item.template.item_type.equals(ItemType.JEWELRY))
                return true;
            else
                return pb.targetWeapon() && item.template.item_type.equals(ItemType.WEAPON);
        } // How did we get here? all valid targets have been covered
        else
            return false;
    }

    /*
     * Cancel spell upon actions
     */
    public static void cancelOnAttack(AbstractCharacter ac) {
        ac.cancelTimer("Stuck");
    }

    public static void cancelOnAttackSwing(AbstractCharacter ac) {
    }

    public static void cancelOnCast(AbstractCharacter ac) {

    }

    public static void cancelOnSpell(AbstractCharacter ac) {

        PowersBase power = getLastPower(ac);

        if (power != null && power.cancelOnCastSpell())
            cancelPower(ac, false);
        ac.cancelLastChant();
    }

    public static void cancelOnEquipChange(AbstractCharacter ac) {

    }

    public static void cancelOnLogout(AbstractCharacter ac) {

    }

    public static void cancelOnMove(AbstractCharacter ac) {

        PowersBase power = getLastPower(ac);

        if (power != null && !power.canCastWhileMoving())
            cancelPower(ac, false);

        //cancel items
        cancelItems(ac, true, false);
        ac.cancelTimer("Stuck");
    }


    public static void cancelOnNewCharm(AbstractCharacter ac) {

    }

    public static void cancelOnSit(AbstractCharacter ac) {
        cancelPower(ac, false); // Always cancel casts on sit
    }

    public static void cancelOnTakeDamage(AbstractCharacter ac) {

        PowersBase power = getLastPower(ac);

        if (power != null && power.cancelOnTakeDamage())
            cancelPower(ac, true);
        cancelItems(ac, false, true);
        ac.cancelTimer("Stuck");
    }

    public static void cancelOnTerritoryClaim(AbstractCharacter ac) {

    }

    public static void cancelOnUnEquip(AbstractCharacter ac) {

    }

    public static void cancelOnStun(AbstractCharacter ac) {

    }

    private static PowersBase getLastPower(AbstractCharacter ac) {
        if (ac == null)
            return null;

        JobContainer jc = ac.getLastPower();

        if (jc == null)
            return null;

        AbstractJob aj = jc.getJob();

        if (aj == null)
            return null;

        if (aj instanceof UsePowerJob) {
            UsePowerJob upj = (UsePowerJob) aj;
            return upj.getPowersBase();
        }
        return null;
    }

    private static PowersBase getLastItem(AbstractCharacter ac) {

        if (ac == null)
            return null;

        JobContainer jc = ac.getLastItem();

        if (jc == null)
            return null;

        AbstractJob aj = jc.getJob();

        if (aj == null)
            return null;

        if (aj instanceof UseItemJob) {
            UseItemJob uij = (UseItemJob) aj;
            return uij.getPowersBase();
        }
        return null;
    }

    //cancels last casted power
    private static void cancelPower(AbstractCharacter ac, boolean cancelCastAnimation) {

        if (ac == null)
            return;

        JobContainer jc = ac.getLastPower();

        if (jc == null)
            return;

        AbstractJob aj = jc.getJob();

        if (aj == null)
            return;

        if (aj instanceof AbstractScheduleJob)
            ((AbstractScheduleJob) aj).cancelJob();

        ac.clearLastPower();

        //clear cast animation for everyone in view range
        if (aj instanceof UsePowerJob && cancelCastAnimation) {

            PerformActionMsg pam = ((UsePowerJob) aj).getMsg();

            if (pam != null) {
                pam.setNumTrains(9999);
                pam.setUnknown04(2);
                DispatchMessage.sendToAllInRange(ac, pam);
            }
        }
    }

    public static PerformActionMsg createPowerMsg(PowersBase pb, int trains, AbstractCharacter source, AbstractCharacter target) {
        return new PerformActionMsg(pb.getToken(), trains, source.getObjectType().ordinal(), source.getObjectUUID(), target.getObjectType().ordinal(), target.getObjectUUID(), target.getLoc().x, target.getLoc().y, target.getLoc().z, 0, 0);

    }

    //cancels any casts from using an item

    private static void cancelItems(AbstractCharacter ac, boolean cancelOnMove, boolean cancelOnTakeDamage) {
        JobContainer jc;
        AbstractJob aj;
        ConcurrentHashMap<String, JobContainer> timers;
        UseItemJob uij;
        PowersBase pb;
        AbstractWorldObject target;

        if (ac == null)
            return;

        timers = ac.getTimers();

        if (timers == null)
            return;

        for (String name : timers.keySet()) {

            jc = timers.get(name);

            if (jc == null)
                continue;

            aj = jc.getJob();

            if (aj != null && aj instanceof UseItemJob) {
                uij = (UseItemJob) aj;
                pb = uij.getPowersBase();

                if (pb == null)
                    continue;

                if (!pb.canCastWhileMoving() && cancelOnMove) {
                    uij.cancelJob();
                    timers.remove(name);
                    continue;
                }

                if ((pb.cancelOnTakeDamage() == false) &&
                        (cancelOnTakeDamage == false))
                    continue;

                uij.cancelJob();
                timers.remove(name);

                //clear cast animation for everyone in view range
                target = uij.getTarget();

                if (target != null) {
                    PerformActionMsg pam = new PerformActionMsg(pb.getToken(), 9999, ac
                            .getObjectType().ordinal(), ac.getObjectUUID(), target.getObjectType().ordinal(),
                            target.getObjectUUID(), 0, 0, 0, 2, 0);
                    DispatchMessage.sendToAllInRange(ac, pam);

                }
            }
        }
    }

}