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

package engine.gameManager;

import engine.Enum.*;
import engine.InterestManagement.HeightMap;
import engine.InterestManagement.InterestManager;
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.powers.poweractions.TrackPowerAction;
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<MobPowerEntry>> AllMobPowers;
    private static JobScheduler js;

    private PowersManager() {

    }

    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);
        }
    }

    // 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) {

        PlayerCharacter pc = SessionManager.getPlayerCharacter(origin);

        if(!pc.isFlying() && powersBaseByToken.get(msg.getPowerUsedID()) != null && powersBaseByToken.get(msg.getPowerUsedID()).isSpell) //cant be sitting if flying
            CombatManager.toggleSit(false,origin);

        if(pc.isMoving())
            pc.stopMovement(pc.getMovementLoc());

        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
            if (pc != null) {
                sendPowerMsg(pc, 2, msg);
                if (pc.isCasting()) {
                    pc.update(false);
                }

                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)) {
            if(pb.token == -1994153779)
                InterestManager.setObjectDirty(caster);
        }
    }

    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(msg.getPowerUsedID() == 430628895) {

            boolean failed = false;// group teleport
            City city = ZoneManager.getCityAtLocation(playerCharacter.loc);
            if (city == null) {
                failed = true;
            }else{
                Bane bane = city.getBane();
                if (bane == null) {
                    failed = true;
                }else{
                    if(!bane.getSiegePhase().equals(SiegePhase.WAR)){
                        failed = true;
                    }
                }
            }
            if(failed){
                //check to see if we are at an active mine
                Zone zone = ZoneManager.findSmallestZone(playerCharacter.loc);
                if(zone != null){
                    Mine mine = null;
                    for(Building building : zone.zoneBuildingSet){
                        if(building.getBlueprint().getBuildingGroup().equals(BuildingGroup.MINE)){
                            mine = Mine.getMineFromTower(building.getObjectUUID());
                        }
                    }
                    if(mine != null){
                        failed = !mine.isActive;
                    }
                }
            }

            if(failed)
                return 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;
        }

        //check for movement buffs while flying
        if(playerCharacter.isFlying()) {
            switch(pb.token){
                case 429005674:
                case 429505739:
                case 431054700:
                case 428005600:
                case 431610080:
                case 427935608:
                case 427857146:
                case 427988218:
                case 431854842:
                case 421074170:
                case 429611355:
                case 428955899:
                case 1794395699:
                case 429428796:
                case 1514898036:
                    ChatManager.chatSystemInfo(playerCharacter, "You Cannot Fly While Having A MovementBuff");
                    //resync stamina
                    playerCharacter.setStamina(playerCharacter.getStamina(), playerCharacter);

                    // Update all surrounding clients.
                    TargetedActionMsg cmm = new TargetedActionMsg(playerCharacter);
                    DispatchMessage.dispatchMsgToInterestArea(playerCharacter, cmm, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
                    return true;
            }
        }

        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);
        }

        //double stack point values for some useless disc spells
        switch(pb.token){
            case 429420458: // BH eyes
            case 429601664: // huntsman skin the beast
                msg.setNumTrains(msg.getNumTrains() * 2);
                break;
        }

        // 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(pb.token != 429396028) {

                    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;
                }else{
                    pb.isSpell = false;
                }




                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()) {

                int slot = pp.mainHand() ? MBServerStatics.SLOT_MAINHAND : MBServerStatics.SLOT_OFFHAND;

                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(false);
            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
        if(pb.token != 429396028 && pb.breaksForm) {
            playerCharacter.cancelOnCast();
        }

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

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

        // set player is casting for regens


        if (time > 100) {
            playerCharacter.update(false);
            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
        if(pb.breaksForm)
            caster.cancelOnCast();

        // update castSpell (use spell) fail condition if spell
        if (pb.isSpell() && pb.breaksForm)
            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(msg.getPowerUsedID() == 429005674){ //bard sprint
            //use sprint instead of ballad of beregund the bold
            //applyPower(playerCharacter,playerCharacter,playerCharacter.loc,429611355,msg.getNumTrains(),false);
            msg.setPowerUsedID(429611355);
        }
        if(msg.getPowerUsedID() == 429494441) {//wildkins chase
            playerCharacter.removeEffectBySource(EffectSourceType.Root,40,true);
            playerCharacter.removeEffectBySource(EffectSourceType.Snare,40,true);
        }
        if (playerCharacter.isCasting()) {
            playerCharacter.update(false);
            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(pb.token != 429396028) {

                    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;
        }

        playerCharacter.setHateValue(pb.getHateValue(trains));

        //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;
                if (!pb.isHarmful())
                    Mob.HandleAssistedAggro(playerCharacter, pcTarget);
            }

            // 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.getRace() != null)
                raceID = pc.getRace().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 summon(SendSummonsRequestMsg msg, ClientConnection origin) {
        PlayerCharacter pc = SessionManager.getPlayerCharacter(
                origin);
        if (pc == null)
            return;

        PlayerCharacter target = SessionManager
                .getPlayerCharacterByLowerCaseName(msg.getTargetName());
        if (target == null || target.equals(pc)) {

            if (target == null) // Player not found. Send not found message
                ChatManager.chatInfoError(pc,
                        "Cannot find that player to summon.");
            else if (target.isCombat())
                ChatManager.chatInfoError(pc,
                        "Cannot summon player in combat.");
            // else trying to summon self, just fail

            // recycle summon
            sendRecyclePower(msg.getPowerToken(), origin);

            // TODO: client already subtracted 200 mana.. need to correct it
            // end cast
            PerformActionMsg pam = new PerformActionMsg(msg.getPowerToken(),
                    msg.getTrains(), msg.getSourceType(), msg.getSourceID(), 0,
                    0, 0f, 0f, 0f, 1, 0);
            sendPowerMsg(pc, 2, pam);

            return;
        }

        PerformActionMsg pam = new PerformActionMsg(msg.getPowerToken(), msg
                .getTrains(), msg.getSourceType(), msg.getSourceID(), target
                .getObjectType().ordinal(), target.getObjectUUID(), 0f, 0f, 0f, 1, 0);

        // Client removes 200 mana on summon use.. so don't send message to self
        target.addSummoner(pc.getObjectUUID(), System.currentTimeMillis() + MBServerStatics.FOURTYFIVE_SECONDS);
        usePower(pam, origin, false);
    }

    public static void recvSummon(RecvSummonsRequestMsg 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.getBaseClass();
                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

                boolean enemiesNear = false;
                for(AbstractWorldObject awo : WorldGrid.getObjectsInRangePartial(pc.loc,MBServerStatics.CHARACTER_LOAD_RANGE, MBServerStatics.MASK_PLAYER)){
                    PlayerCharacter playerCharacter = (PlayerCharacter)awo;
                    if(!playerCharacter.guild.getNation().equals(pc.guild.getNation())){
                        enemiesNear = true;
                    }
                }

                if(enemiesNear && !pc.isInSafeZone())
                    duration += 60000;

                // 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 trackWindow(TrackWindowMsg msg, ClientConnection origin) {

        PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter(
                origin);

        if (playerCharacter == null)
            return;

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

        // get track power used
        PowersBase pb = PowersManager.powersBaseByToken.get(msg.getPowerToken());

        if (pb == null || !pb.isTrack())
            return;

        //check track threshold timer to prevent spam
        long currentTime = System.currentTimeMillis();
        long timestamp = playerCharacter.getTimeStamp("trackWindow");
        long dif = currentTime - timestamp;
        if (dif < MBServerStatics.TRACK_WINDOW_THRESHOLD)
            return;
        playerCharacter.setTimeStamp("trackWindow", currentTime);

        ArrayList<ActionsBase> ablist = pb.getActions();
        if (ablist == null)
            return;

        TrackPowerAction tpa = null;
        for (ActionsBase ab : ablist) {
            AbstractPowerAction apa = ab.getPowerAction();
            if (apa != null && apa instanceof TrackPowerAction)
                tpa = (TrackPowerAction) apa;
        }
        if (tpa == null)
            return;

        // Check powers for normal users
        if (playerCharacter.getPowers() == null || !playerCharacter.getPowers().containsKey(msg.getPowerToken()))
            if (!playerCharacter.isCSR())
                if (!MBServerStatics.POWERS_DEBUG) {
                    //  ChatManager.chatSayInfo(pc, "You may not cast that spell!");
                    //  this.logEXPLOIT("usePowerA(): Cheat attempted? '" + msg.getPowerToken() + "' was not associated with " + pc.getName());
                    return;
                }

        // Get search mask for track
        int mask = 0;
        if (pb.targetPlayer())
            if (tpa.trackVampire()) // track vampires
                mask = MBServerStatics.MASK_PLAYER | MBServerStatics.MASK_UNDEAD;
            else
                // track all players
                mask = MBServerStatics.MASK_PLAYER;
        else if (pb.targetCorpse()) // track corpses
            mask = MBServerStatics.MASK_CORPSE;
        else if (tpa.trackNPC()) // Track NPCs
            mask = MBServerStatics.MASK_NPC;
        else if (tpa.trackUndead()) // Track Undead
            mask = MBServerStatics.MASK_MOB | MBServerStatics.MASK_UNDEAD;
        else
            // Track All
            mask = MBServerStatics.MASK_MOB | MBServerStatics.MASK_NPC;

        // Find characters in range
        HashSet<AbstractWorldObject> allTargets;
        allTargets = WorldGrid.getObjectsInRangeContains(playerCharacter.getLoc(),
                pb.getRange(), mask);

        //remove anyone who can't be tracked
        Iterator<AbstractWorldObject> it = allTargets.iterator();
        while (it.hasNext()) {
            AbstractWorldObject awo = it.next();
            if (awo == null)
                continue;
            else if (!awo.isAlive())
                it.remove();
            else if (awo.getObjectType().equals(GameObjectType.PlayerCharacter)) {
                PlayerBonuses bonus = ((PlayerCharacter) awo).getBonuses();
                if (bonus != null && bonus.getBool(ModType.CannotTrack, SourceType.None))
                    it.remove();
            }
        }

        // get max charcters for window
        int maxTargets = 20;
        PromotionClass promo = playerCharacter.getPromotionClass();
        if (promo != null) {
            int tableID = promo.getObjectUUID();
            if (tableID == 2512 || tableID == 2514 || tableID == 2515)
                maxTargets = 40;
        }

        // create list of characters
        HashSet<AbstractCharacter> trackChars;
        switch(msg.getPowerToken()){
            case 431511776:
            case 429578587:
            case 429503360:
                trackChars = getTrackList(playerCharacter);
                break;
            default:
                trackChars = RangeBasedAwo.getTrackList(allTargets, playerCharacter, maxTargets);
                break;
        }

        TrackWindowMsg trackWindowMsg = new TrackWindowMsg(msg);

        // send track window
        trackWindowMsg.setSource(playerCharacter);
        trackWindowMsg.setCharacters(trackChars);

        Dispatch dispatch = Dispatch.borrow(playerCharacter, trackWindowMsg);
        DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);

    }

    public static HashSet<AbstractCharacter> getTrackList(PlayerCharacter tracker){
        HashSet<AbstractCharacter> list = new HashSet<AbstractCharacter>();
        HashSet<AbstractWorldObject> shortList = WorldGrid.getObjectsInRangePartial(tracker.loc,MBServerStatics.CHARACTER_LOAD_RANGE, MBServerStatics.MASK_PLAYER);
        HashSet<AbstractWorldObject> fullList = WorldGrid.getObjectsInRangePartial(tracker.loc,1408, MBServerStatics.MASK_PLAYER);
        ArrayList<Guild> guildsPresent = new ArrayList<>();
        for(AbstractWorldObject awo : shortList){
            PlayerCharacter pc = (PlayerCharacter)awo;
            if(!guildsPresent.contains(pc.guild.getNation())){
                guildsPresent.add(pc.guild.getNation());
            }
        }
        for(AbstractWorldObject awo : fullList){
            if(awo.equals(tracker))
                continue;
            PlayerCharacter pc = (PlayerCharacter)awo;
            if(!pc.isAlive())
                continue;
            if(guildsPresent.contains(pc.guild.getNation()))
                list.add(pc);
        }

        return list;
    }

    private 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.getCharItemManager();
                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(HeightMap.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(HeightMap.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(false);
        }
        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

        //if (pc.getDebug(16)) {
        //    String smsg = "ATR: " + atr + ", Defense: " + defense;
         //   ChatManager.chatSystemInfo(pc, smsg);
        //}

        //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

        if (CombatManager.LandHit((int)atr,(int)defense)) {
            // 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.getItemBase().getType().equals(ItemType.ARMOR))
                return true;
            else if (pb.targetJewelry() && item.getItemBase().getType().equals(ItemType.JEWELRY))
                return true;
            else
                return pb.targetWeapon() && item.getItemBase().getType().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) {
        if(ac.getObjectType().equals(GameObjectType.PlayerCharacter)){
            //PlayerCharacter.GroundPlayer((PlayerCharacter)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);

                }
            }
        }
    }

    public static boolean breakForm(int token) {
        switch (token) {
            case 429505865:
            case 429407561:
            case 429492073:
            case 429644123:
            case 429393769:
            case 429545819:
            case 429426537:
            case 429590377:
            case 429508425:
            case 429541193:
            case 429573961:
            case 427924330:
            case 429402918:
            case 429545688:
            case 429005674:
            case 429637823:
            case 429590426:
            case 428066972:
            case 429441862:
            case 431611756:
            case 431578988:
            case 429502506:
            case 429398191:
            case 429447384:
            case 428892191:
            case 431579167:
            case 430977067:
            case 429409100:
            case 429441868:
            case 429594877:
            case 427908971:
            case 683741153:
            case 429770569:
            case 429452379:
            case 429605055:
            case 429086971:
            case 429443230:
            case 429505400:
            case 429492122:
            case 429643992:
            case 550062236:
            case 429498252:
            case 429611224:
            case 429441834:
            case 428918940:
            case 429633739:
            case 429633579:
            case 429568043:
            case 429048646:
            case 428392639:
            case 428425407:
            case 429054168:
            case 429021400:
            case 428955864:
            case 429119704:
            case 428890328:
            case 428923096:
            case 429218008:
            case 429086936:
            case 428988632:
            case 428688204:
            case 429514603:
            case 428924959:
            case 429393818:
            case 429720966:
            case 428982463:
            case 427933887:
            case 429572287:
            case 429501222:
            case 430694431:
            case 429436131:
            case 430006124:
            case 429611355:
            case 428005600:
            case 427935608:
            case 428949695:
            case 427988218:
            case 429414616:
            case 429496495:
            case 429428796:
            case 563795754:
            case 428988217:
            case 429432716:
            case 428955899:
            case 429393286:
            case 550062220:
            case 429495557:
            case 429401278:
            case 428377478:
            case 429409094:
            case 428191947:
            case 429434474:
            case 429403363:
            case 429512920:
            case 429419611:
            case 429645676:
            case 429602895:
            case 429605071:
            case 429592428:
            case 429500010:
            case 429406602:
            case 429426586:
            case 429633898:
            case 550062212:
            case 429994027:
            case 430813227:
            case 429928491:
            case 430026795:
            case 429517915:
            case 431854842:
            case 429767544:
            case 429502507:
                return false;
        }
        return true;
    }
}



