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


package engine.gameManager;

import engine.InterestManagement.InterestManager;
import engine.InterestManagement.WorldGrid;
import engine.job.JobContainer;
import engine.job.JobScheduler;
import engine.jobs.UpgradeBuildingJob;
import engine.loot.WorkOrder;
import engine.math.Bounds;
import engine.math.Vector3fImmutable;
import engine.mbEnums;
import engine.mbEnums.BuildingGroup;
import engine.mbEnums.GameObjectType;
import engine.net.client.msg.ErrorPopupMsg;
import engine.objects.*;
import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;

public enum BuildingManager {

    BUILDINGMANAGER;

    public static HashMap<Integer, ArrayList<BuildingLocation>> _stuckLocations = new HashMap<>();
    public static HashMap<Integer, ArrayList<BuildingLocation>> _slotLocations = new HashMap<>();

    public static HashMap<Integer, ConcurrentHashMap<Integer, BuildingFriends>> _buildingFriends = new HashMap<>();
    public static HashMap<Integer, ConcurrentHashMap<Integer, Condemned>> _buildingCondemned = new HashMap<>();
    public static HashMap<Integer, ArrayList<Vector3fImmutable>> _buildingPatrolPoints = new HashMap<>();

    public static int getAvailableSlot(Building building) {

        ArrayList<BuildingLocation> slotLocations = _slotLocations.get(building.meshUUID);

        // Some meshes might not have slot locations assigned.

        if (slotLocations == null || slotLocations.isEmpty())
            return -1;

        int numOfSlots = _slotLocations.get(building.meshUUID).size();

        for (int i = 1; i <= numOfSlots; i++) {

            if (!building.getHirelings().containsValue(i))
                return i;
        }
        return -1;
    }

    public static int getLastAvailableSlot(Building building) {

        ArrayList<BuildingLocation> slotLocations = _slotLocations.get(building.meshUUID);

        // Some meshes might not have slot locations assigned.

        if (slotLocations == null || slotLocations.isEmpty())
            return -1;

        int numOfSlots = _slotLocations.get(building.meshUUID).size();

        for (int i = numOfSlots; i > 0; i--)
            if (!building.getHirelings().containsValue(i))
                return i;

        return -1;
    }

    public static BuildingLocation getSlotLocation(Building building, int slot) {

        BuildingLocation buildingLocation = new BuildingLocation();

        if (slot == -1)
            return buildingLocation;

        buildingLocation = _slotLocations.get(building.meshUUID).get(slot - 1); // array index

        if (buildingLocation == null)
            Logger.error("Invalid slot for building: " + building.getObjectUUID());

        return buildingLocation;
    }

    public static boolean playerCanManage(PlayerCharacter player, Building building) {

        if (player == null)
            return false;

        if (building == null)
            return false;

        //cannot access destroyed buildings
        if (building.getRank() == -1)
            return false;

        //admin characters can always access buildings
        if (player.isCSR())
            return true;

        //owner can always access their own building
        if (IsOwner(building, player))
            return true;

        //check for default IC access if building belongs to same guild
        if(player.guild.equals(building.getGuild())) {
            if (building.getBlueprint() != null && building.getBlueprint().getBuildingGroup() != null) {
                switch (building.getBlueprint().getBuildingGroup()) {
                    case TOL:
                    case BARRACK:
                    case SPIRE:
                    case SHRINE:
                    case BANESTONE:
                    case MINE:
                    case WAREHOUSE:
                    case BULWARK:
                    case SIEGETENT:
                        if (GuildStatusController.isInnerCouncil(player.getGuildStatus()))
                            return true;
                        if (GuildStatusController.isGuildLeader(player.getGuildStatus()))
                            return true;
                        break;
                }
            }
        }

        //check against friends list entries if any present
        if (building.getFriends() != null) {

            //check individuals
            if (building.getFriends().get(player.getObjectUUID()) != null)
                return true;

            if (building.getFriends().get(player.guild.objectUUID) != null) {

                //check friend type for guild related access
                switch (building.getFriends().get(player.guild.objectUUID).friendType) {
                    case 8: //full member
                        if (GuildStatusController.isFullMember(player.getGuildStatus()))
                            return true;
                        break;
                    case 9: //Inner Council
                        if (GuildStatusController.isInnerCouncil(player.getGuildStatus()))
                            return true;
                        if (GuildStatusController.isGuildLeader(player.getGuildStatus()))
                            return true;
                        break;

                }
            }
        }
        //did not meet access grant criteria, deny access
        return false;
    }

    public static synchronized boolean lootBuilding(PlayerCharacter player, Building building) {

        if (building == null)
            return false;

        if (player == null)
            return false;

        if (building.getRank() != -1)
            return false;

        if (building.getBlueprintUUID() == 0)
            return false;

        switch (building.getBlueprint().getBuildingGroup()) {
            case SHRINE:
                Shrine shrine = Shrine.shrinesByBuildingUUID.get(building.getObjectUUID());
                if (shrine == null)
                    return false;

                int amount = shrine.getFavors();
                //no more favors too loot!
                if (amount == 0) {

                    try {
                        ErrorPopupMsg.sendErrorPopup(player, 166);
                    } catch (Exception e) {

                    }
                    return false;
                }

                ItemTemplate template = ItemTemplate.templates.get(1705032); // Elan Stone

                if (!player.charItemManager.hasRoomInventory(template.item_wt))
                    return false;

                if (!ItemManager.MakeItemForPlayer(template.template_id, player, amount))
                    return false;

                shrine.setFavors(0);
                break;
            case WAREHOUSE:

                City city = building.getCity();

                if (city == null)
                    return true;

                Warehouse warehouse = city.warehouse;

                if (warehouse == null)
                    return false;

                for (mbEnums.ResourceType resourceType : EnumSet.allOf(mbEnums.ResourceType.class)) {

                    if (!player.charItemManager.hasRoomInventory(resourceType.template.item_wt)) {
                        ChatManager.chatSystemInfo(player, "You can not carry any more of that item.");
                        return false;
                    }

                    if (warehouse.resources.get(resourceType) == null)
                        continue;

                    int resourceAmount = warehouse.resources.get(resourceType);

                    if (resourceAmount <= 0)
                        continue;

                    if (Warehouse.loot(warehouse, player, resourceType, resourceAmount, true)) {
                        ChatManager.chatInfoInfo(player, "You have looted " + resourceAmount + ' ' + resourceType.name());
                    }

                }
                break;

        }
        //Everything was looted, Maybe we should
        return true;
    }

    //This method restarts an upgrade timer when a building is loaded from the database.
    // Submit upgrade job for this building based upon it's current upgradeDateTime

    public static void submitUpgradeJob(Building building) {

        if (building == null)
            return;


        if (building.getUpgradeDateTime() == null) {
            Logger.error("Attempt to submit upgrade job for non-ranking building");
            return;
        }

        // Submit upgrade job for future date or current instant

        if (building.getUpgradeDateTime().isAfter(LocalDateTime.now())) {
            JobContainer jc = JobScheduler.getInstance().scheduleJob(new UpgradeBuildingJob(building), building.getUpgradeDateTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
        } else
            JobScheduler.getInstance().scheduleJob(new UpgradeBuildingJob(building), 0);
    }

    public static void setUpgradeDateTime(Building building, LocalDateTime upgradeDateTime, int rankCost) {

        if (building == null)
            return;
        if (!DbManager.BuildingQueries.updateBuildingUpgradeTime(upgradeDateTime, building, rankCost)) {
            Logger.error("Failed to set upgradeTime for building " + building.getObjectUUID());
            return;
        }

        building.upgradeDateTime = upgradeDateTime;

    }

    // Method transfers ownership of all hirelings in a building

    public static void refreshHirelings(Building building) {

        if (building == null)
            return;

        Guild newGuild;

        if (building.getOwner() == null)
            newGuild = Guild.getErrantGuild();
        else
            newGuild = building.getOwner().getGuild();

        for (AbstractCharacter hireling : building.getHirelings().keySet()) {
            hireling.setGuild(newGuild);
            WorldGrid.updateObject(hireling);
        }

    }

    public static void removeHireling(Building building, AbstractCharacter hireling) {

        if (hireling.getObjectType().equals(GameObjectType.Mob)) {

            Mob guardCaptain = (Mob) hireling;

            // Clear minions from database if a guard captain

            if (guardCaptain.agentType.equals(mbEnums.AIAgentType.GUARDCAPTAIN))
                DbManager.MobQueries.REMOVE_ALL_MINIONS(hireling.getObjectUUID());
        }

        // Clear minions from world

        for (Integer minionUUID : hireling.minions) {
            Mob minionMob = Mob.getMob(minionUUID);
            DbManager.removeFromCache(minionMob);
            WorldGrid.RemoveWorldObject(minionMob);
            WorldGrid.unloadObject(minionMob);

            if (minionMob.parentZone != null)
                minionMob.parentZone.zoneMobSet.remove(minionMob);
        }

        // Clear all workorders for this hireling
        if (hireling.getObjectType().equals(GameObjectType.NPC)) {

            NPC hirelingNPC = (NPC) hireling;

            if (ForgeManager.vendorWorkOrderLookup.get(hirelingNPC) != null)

                for (WorkOrder workOrder : ForgeManager.vendorWorkOrderLookup.get(hirelingNPC)) {
                    workOrder.runCompleted.set(true);
                    workOrder.vendor = null;

                    // Remove any cooking items from collections
                    // to ensure we don't leak memory.

                    for (Item item : workOrder.cooking) {
                        DbManager.removeFromCache(item);
                        ForgeManager.itemWorkOrderLookup.remove(item);
                    }

                    DbManager.WarehouseQueries.DELETE_WORKORDER(workOrder);
                }

            // Finally remove the NPC from ForgeManager

            ForgeManager.vendorWorkOrderLookup.remove(hirelingNPC);
        }

        // Remove hireling from building

        building.getHirelings().remove(hireling);

        // Remove from zone mob set

        if (hireling.getObjectType().equals(GameObjectType.Mob)) {

            Mob hirelingMob = (Mob) hireling;

            if (hirelingMob.parentZone != null)
                if (hirelingMob.parentZone.zoneMobSet.contains(hirelingMob))
                    hirelingMob.parentZone.zoneMobSet.remove(hireling);
        }

        if (hireling.getObjectType().equals(GameObjectType.NPC)) {

            NPC hirelingNPC = (NPC) hireling;

            if (hirelingNPC.getParentZone() != null)
                if (hirelingNPC.getParentZone().zoneNPCSet.contains(hirelingNPC))
                    hirelingNPC.getParentZone().zoneNPCSet.remove(hireling);

        }

        // Unload hireling from world

        DbManager.removeFromCache(hireling);
        WorldGrid.RemoveWorldObject(hireling);
        WorldGrid.removeObject(hireling);

        // Delete hireling from database

        if (hireling.getObjectType().equals(GameObjectType.Mob))
            DbManager.MobQueries.DELETE_MOB((Mob) hireling);
        else
            DbManager.NPCQueries.DELETE_NPC((NPC) hireling);
    }

    public static void cleanupHirelings(Building building) {

        // Early exit:  Cannot have hirelings in a building
        // without a blueprint.

        if (building.getBlueprintUUID() == 0)
            return;

        // Remove all hirelings for destroyed buildings

        if (building.getRank() < 1) {
            for (AbstractCharacter slottedNPC : building.getHirelings().keySet())
                BuildingManager.removeHireling(building, slottedNPC);
            return;
        }

        // Delete hireling if building has deranked.

        for (AbstractCharacter hireling : building.getHirelings().keySet())
            if (building.getHirelings().get(hireling) > building.getBlueprint().getSlotsForRank(building.getRank()))
                BuildingManager.removeHireling(building, hireling);

        refreshHirelings(building);
    }

    public static Building getBuilding(int id) {

        if (id == 0)
            return null;

        Building building;

        building = (Building) DbManager.getFromCache(mbEnums.GameObjectType.Building, id);

        if (building != null)
            return building;

        return DbManager.BuildingQueries.GET_BUILDINGBYUUID(id);

    }

    public static boolean PlayerCanControlNotOwner(Building building, PlayerCharacter player) {

        if (player == null)
            return false;

        if (building == null)
            return false;

        if (building.getOwner() == null)
            return false;

        //lets pass true if player is owner anyway.

        if (building.getOwner().equals(player))
            return true;

        if (player.getGuild().isEmptyGuild())
            return false;

        if (building.getGuild().isGuildLeader(player.getObjectUUID()))
            return true;

        if (!Guild.sameGuild(building.getGuild(), player.getGuild()))
            return false;

        return GuildStatusController.isGuildLeader(player.getGuildStatus()) || GuildStatusController.isInnerCouncil(player.getGuildStatus());
    }

    public static boolean IsPlayerHostile(Building building, PlayerCharacter player) {

        if (Guild.sameNationExcludeErrant(building.getGuild(), player.getGuild()))
            return false;

        if(building.enforceKOS) {
            if (building.getCity() != null) {
                Building TOL = building.getCity().getTOL();
                if (TOL != null) {
                    building = TOL;
                }
            }
        }
        if (!building.reverseKOS) {

            Condemned condemn = building.getCondemned().get(player.getObjectUUID());

            if (condemn != null && condemn.active)
                return true;

            if (player.getGuild() != null) {

                Condemned guildCondemn = building.getCondemned().get(player.getGuild().getObjectUUID());

                if (guildCondemn != null && guildCondemn.active)
                    return true;

                Condemned nationCondemn = building.getCondemned().get(player.getGuild().getNation().getObjectUUID());
                return nationCondemn != null && nationCondemn.active && nationCondemn.friendType == Condemned.NATION;
            } else {
                //TODO ADD ERRANT KOS CHECK
            }
        } else {

            Condemned condemn = building.getCondemned().get(player.getObjectUUID());

            if (condemn != null && condemn.active)
                return false;

            if (player.getGuild() != null) {

                Condemned guildCondemn = building.getCondemned().get(player.getGuild().getObjectUUID());

                if (guildCondemn != null && guildCondemn.active)
                    return false;

                Condemned nationCondemn = building.getCondemned().get(player.getGuild().getNation().getObjectUUID());
                return nationCondemn == null || !nationCondemn.active || nationCondemn.friendType != Condemned.NATION;
            } else {
                //TODO ADD ERRANT KOS CHECK
            }
            return true;
        }

        //When we get to here, This means The building was not reverse KOS
        //and passed the hostile test.

        return false;
    }

    public static final synchronized boolean addHirelingForWorld(Building building, PlayerCharacter contractOwner, Vector3fImmutable NpcLoc, Zone zone, Contract NpcID, int rank) {

        String pirateName = NPCManager.getPirateName(NpcID.getMobbaseID());

        NPC npc;

        npc = NPC.createNPC(pirateName, NpcID.getObjectUUID(), NpcLoc, building.getGuild(), zone, (short) rank, building);

        if (npc == null)
            return false;

        npc.setObjectTypeMask(MBServerStatics.MASK_NPC);
        npc.setLoc(npc.bindLoc);
        InterestManager.setObjectDirty(npc);
        return true;

    }

    public static synchronized boolean addHireling(Building building, PlayerCharacter contractOwner, Zone zone, Contract contract, Item item) {

        int rank;

        if (building.getBlueprintUUID() == 0)
            return false;

        if (building.getBlueprint().getMaxSlots() == building.getHirelings().size())
            return false;

        String pirateName = NPCManager.getPirateName(contract.getMobbaseID());

        if ((byte) item.chargesRemaining > 0)
            rank = (byte) item.chargesRemaining * 10;
        else
            rank = 10;

        Mob mobile;
        NPC npc;

        if (NPC.ISWallArcher(contract)) {

            mobile = Mob.createMob(contract.getMobbaseID(), Vector3fImmutable.ZERO, contractOwner.getGuild(), zone, building, contract, pirateName, rank, mbEnums.AIAgentType.GUARDWALLARCHER);

            if (mobile == null)
                return false;

            // Configure AI and write new mobile to disk

            mobile.behaviourType = mbEnums.MobBehaviourType.GuardWallArcher;
            mobile = DbManager.MobQueries.PERSIST(mobile);

            // Spawn new mobile

            mobile.setLoc(mobile.getLoc());

            return true;
        }

        if (NPC.ISGuardCaptain(contract.getContractID())) {

            mobile = Mob.createMob(contract.getMobbaseID(), Vector3fImmutable.ZERO, contractOwner.getGuild(), zone, building, contract, pirateName, rank, mbEnums.AIAgentType.GUARDCAPTAIN);

            if (mobile == null)
                return false;

            // Configure AI and write new mobile to disk

            mobile.behaviourType = mbEnums.MobBehaviourType.GuardCaptain;
            mobile = DbManager.MobQueries.PERSIST(mobile);

            // Spawn new mobile

            mobile.setLoc(mobile.getLoc());

            return true;
        }

        if (contract.getContractID() == 910) {

            //guard dog

            mobile = Mob.createMob(contract.getMobbaseID(), Vector3fImmutable.ZERO, contractOwner.getGuild(), zone, building, contract, pirateName, rank, mbEnums.AIAgentType.GUARDCAPTAIN);

            if (mobile == null)
                return false;

            // Configure AI and write new mobile to disk

            mobile.behaviourType = mbEnums.MobBehaviourType.GuardCaptain;
            mobile = DbManager.MobQueries.PERSIST(mobile);

            // Spawn new mobile

            mobile.setLoc(mobile.getLoc());

            return true;
        }

        npc = NPC.createNPC(pirateName, contract.getObjectUUID(), Vector3fImmutable.ZERO, contractOwner.getGuild(), zone, (short) rank, building);

        if (npc == null)
            return false;

        npc.setObjectTypeMask(MBServerStatics.MASK_NPC);
        npc.setLoc(npc.bindLoc);
        InterestManager.setObjectDirty(npc);
        return true;
    }

    public static boolean IsWallPiece(Building building) {

        if (building.getBlueprint() == null)
            return false;

        BuildingGroup buildingGroup = building.getBlueprint().getBuildingGroup();

        switch (buildingGroup) {
            case WALLSTRAIGHT:
            case WALLCORNER:
            case SMALLGATE:
            case ARTYTOWER:
            case WALLSTRAIGHTTOWER:
            case WALLSTAIRS:
                return true;
            default:
                return false;
        }
    }

    public static Building getBuildingFromCache(int id) {
        return (Building) DbManager.getFromCache(GameObjectType.Building, id);
    }

    public static boolean IsOwner(Building building, PlayerCharacter player) {
        if (building == null || player == null)
            return false;

        if (building.getOwner() == null)
            return false;

        return building.getOwner().getObjectUUID() == player.getObjectUUID();
    }

    public static float GetMissingHealth(Building building) {
        return building.healthMax - building.getCurrentHitpoints();
    }

    public static int GetRepairCost(Building building) {
        return (int) (GetMissingHealth(building) * .10f);
    }

    public static Regions GetRegion(Building building, float x, float y, float z) {

        if (building.getBounds() == null)
            return null;

        if (building.getBounds().getRegions() == null)
            return null;

        Regions currentRegion = null;

        for (Regions region : building.getBounds().getRegions()) {
            if (region.isPointInPolygon(new Vector3fImmutable(x, y, z)))
                if (y > (region.highLerp.y - 5))
                    currentRegion = region;
        }

        return currentRegion;
    }

    public static Regions GetRegion(Building building, int room, int level, float x, float z) {

        if (building.getBounds() == null)
            return null;

        if (building.getBounds().getRegions() == null)
            return null;

        for (Regions region : building.getBounds().getRegions()) {

            if (region.getLevel() != level)
                continue;

            if (region.getRoom() != room)
                continue;

            if (region.isPointInPolygon(new Vector3fImmutable(x, 0, z)))
                return region;
        }
        return null;
    }

    public static Vector3fImmutable GetBindLocationForBuilding(Building building) {

        Vector3fImmutable bindLoc = null;

        if (building == null)
            return mbEnums.Ruins.getRandomRuin().getLocation();

        bindLoc = building.getLoc();

        float radius = Bounds.meshBoundsCache.get(building.getMeshUUID()).radius;

        if (building.getRank() == 8) {
            bindLoc = building.getStuckLocation();
            if (bindLoc != null)
                return bindLoc;
        }

        float x = bindLoc.getX();
        float z = bindLoc.getZ();
        float offset = ((ThreadLocalRandom.current().nextFloat() * 2) - 1) * radius;
        int direction = ThreadLocalRandom.current().nextInt(4);

        switch (direction) {
            case 0:
                x += radius;
                z += offset;
                break;
            case 1:
                x += offset;
                z -= radius;
                break;
            case 2:
                x -= radius;
                z += offset;
                break;
            case 3:
                x += offset;
                z += radius;
                break;
        }
        bindLoc = new Vector3fImmutable(x, bindLoc.getY(), z);

        return bindLoc;


    }

    public static void rebuildMine(Building mineBuilding) {
        setRank(mineBuilding, 1);
        mineBuilding.meshUUID = mineBuilding.getBlueprint().getMeshForRank(mineBuilding.rank);

        // New rank mean new max hit points.

        mineBuilding.healthMax = mineBuilding.getBlueprint().getMaxHealth(mineBuilding.rank);
        mineBuilding.setCurrentHitPoints(mineBuilding.healthMax);
        mineBuilding.getBounds().setBounds(mineBuilding);
    }

    public static void setRank(Building building, int rank) {

        int newMeshUUID;
        boolean success;

        // If this building has no blueprint then set rank and exit immediatly.

        if (building.blueprintUUID == 0 || building.getBlueprint() != null && building.getBlueprint().getBuildingGroup().equals(BuildingGroup.MINE)) {
            building.rank = rank;
            DbManager.BuildingQueries.CHANGE_RANK(building.getObjectUUID(), rank);
            return;
        }

        // Delete any upgrade jobs before doing anything else.  It won't quite work
        // if in a few lines we happen to delete this building.

        JobContainer jobContainer = building.getTimers().get("UPGRADE");

        if (jobContainer != null) {
            if (!JobScheduler.getInstance().cancelScheduledJob(jobContainer))
                Logger.error("failed to cancel existing upgrade job.");
        }

        // Attempt to write to database or delete the building
        // if we are destroying it.

        if (rank == -1)
            success = DbManager.BuildingQueries.DELETE_FROM_DATABASE(building);
        else
            success = DbManager.BuildingQueries.updateBuildingRank(building, rank);

        if (!success) {
            Logger.error("Error writing to database UUID: " + building.getObjectUUID());
            return;
        }

        building.isDeranking.compareAndSet(false, true);

        // Change the building's rank

        building.rank = rank;

        // New rank means new mesh
        newMeshUUID = building.getBlueprint().getMeshForRank(building.rank);

        if ((building.getBlueprint().getBuildingGroup() == BuildingGroup.TOL) && (building.rank == 8))
            newMeshUUID = Realm.getRealmMesh(building.getCity());

        building.meshUUID = newMeshUUID;

        // New rank mean new max hitpoints.

        building.healthMax = building.getBlueprint().getMaxHealth(building.rank);
        building.setCurrentHitPoints(building.healthMax);

        if (building.getUpgradeDateTime() != null)
            setUpgradeDateTime(building, null, 0);

        // If we destroyed this building make sure to turn off
        // protection

        if (building.rank == -1)
            building.protectionState = mbEnums.ProtectionState.NONE;

        // update object to clients

        building.refresh(true);

        if (building.getBounds() != null)
            building.getBounds().setBounds(building);

        // Cleanup hirelings resulting from rank change

        cleanupHirelings(building);

        building.isDeranking.compareAndSet(true, false);
    }

    public static Building getBuildingAtLocation(Vector3fImmutable loc) {

        for (AbstractWorldObject awo : WorldGrid.getObjectsInRangePartial(loc, 64, MBServerStatics.MASK_BUILDING)) {
            Building building = (Building) awo;

            if (building == null)
                continue;

            if (Bounds.collide(loc, building.getBounds()))
                return building;
        }
        return null;
    }

}
