package com.magmaguy.elitemobs.mobconstructor;

import com.magmaguy.elitemobs.ChatColorConverter;
import com.magmaguy.elitemobs.EntityTracker;
import com.magmaguy.elitemobs.config.ConfigValues;
import com.magmaguy.elitemobs.config.DefaultConfig;
import com.magmaguy.elitemobs.config.MobCombatSettingsConfig;
import com.magmaguy.elitemobs.items.MobTierFinder;
import com.magmaguy.elitemobs.mobconstructor.mobdata.aggressivemobs.EliteMobProperties;
import com.magmaguy.elitemobs.mobpowers.ElitePower;
import com.magmaguy.elitemobs.mobpowers.majorpowers.MajorPower;
import com.magmaguy.elitemobs.mobpowers.minorpowers.MinorPower;
import com.magmaguy.elitemobs.mobspawning.NaturalMobSpawnEventHandler;
import com.magmaguy.elitemobs.powerstances.MajorPowerPowerStance;
import com.magmaguy.elitemobs.powerstances.MinorPowerPowerStance;
import com.magmaguy.elitemobs.utils.VersionChecker;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.attribute.Attribute;
import org.bukkit.entity.*;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.inventory.ItemStack;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.concurrent.ThreadLocalRandom;

public class EliteMobEntity {

    /*
    Note that a lot of values here are defined by EliteMobProperties.java
     */
    private LivingEntity eliteMob;
    private int eliteMobLevel;
    private double eliteMobTier;
    private double maxHealth;
    private String name;
    /*
    Store all powers in one set, makes no sense to access it in individual sets.
    The reason they are split up in the first place is to add them in a certain ratio
    Once added, they can just be stored in a pool
     */
    private HashSet<ElitePower> powers = new HashSet<>();
    private int minorPowerCount = 0;
    private int majorPowerCount = 0;
    private boolean hasMinorVisualEffect = false;
    private boolean hasMajorVisualEffect = false;
    private boolean hasVisualEffectObfuscated = true;
    private boolean isNaturalEntity;
    /*
    This just defines default behavior
     */
    private boolean hasStacking = true;
    private boolean hasCustomArmor = false;
    private boolean hasCustomName = false;
    private boolean hasCustomPowers = false;
    private boolean hasFarAwayUnload = true;
    private boolean hasCustomHealth = false;
    private boolean hasNormalLoot = true;
    private CreatureSpawnEvent.SpawnReason spawnReason;

    /**
     * This is the generic constructor used in most instances of natural elite mob generation
     *
     * @param livingEntity  Minecraft entity associated to this elite mob
     * @param eliteMobLevel Level of the mob, can be modified during runtime. Dynamically assigned.
     */
    public EliteMobEntity(LivingEntity livingEntity, int eliteMobLevel, CreatureSpawnEvent.SpawnReason spawnReason) {

        /*
        Register living entity to keep track of which entity this object is tied to
         */
        this.eliteMob = livingEntity;
        /*
        Register level, this is variable as per stacking rules
         */
        setEliteMobLevel(eliteMobLevel);
        eliteMobTier = MobTierFinder.findMobTier(eliteMobLevel);
        /*
        Sets the spawn reason
         */
        setSpawnReason(spawnReason);
        /*
        Start tracking the entity
         */
        if (!EntityTracker.registerEliteMob(this)) return;
        /*
        Get correct instance of plugin data, necessary for settings names and health among other things
         */
        EliteMobProperties eliteMobProperties = EliteMobProperties.getPluginData(livingEntity);
        /*
        Handle name, variable as per stacking rules
         */
        setCustomName(eliteMobProperties);
        /*
        Handle health, max is variable as per stacking rules
        Currently #setHealth() resets the health back to maximum
         */
        setMaxHealth(eliteMobProperties);
        setHealth();
        /*
        Set the armor
         */
        setArmor();
        /*
        Register whether or not the elite mob is natural
         */
        this.isNaturalEntity = EntityTracker.isNaturalEntity(livingEntity);
        /*
        Set the power list
         */
        randomizePowers(eliteMobProperties);

        eliteMob.setCanPickupItems(false);

    }

    /**
     * This is the generic constructor used in most instances of natural elite mob generation
     *
     * @param livingEntity  Minecraft entity associated to this elite mob
     * @param eliteMobLevel Level of the mob, can be modified during runtime. Dynamically assigned.
     */
    public EliteMobEntity(LivingEntity livingEntity, int eliteMobLevel, double currentHealthPercent, CreatureSpawnEvent.SpawnReason spawnReason) {

        /*
        Register living entity to keep track of which entity this object is tied to
         */
        this.eliteMob = livingEntity;
        /*
        Register level, this is variable as per stacking rules
         */
        setEliteMobLevel(eliteMobLevel);
        eliteMobTier = MobTierFinder.findMobTier(eliteMobLevel);
        /*
        Sets the spawn reason
         */
        setSpawnReason(spawnReason);
        /*
        Start tracking the entity
         */
        if (!EntityTracker.registerEliteMob(this)) return;
        /*
        Get correct instance of plugin data, necessary for settings names and health among other things
         */
        EliteMobProperties eliteMobProperties = EliteMobProperties.getPluginData(livingEntity);
        /*
        Handle name, variable as per stacking rules
         */
        setCustomName(eliteMobProperties);
        /*
        Handle health, max is variable as per stacking rules
        Currently #setHealth() resets the health back to maximum
         */
        setMaxHealth(eliteMobProperties);
        eliteMob.setHealth(maxHealth * currentHealthPercent);
        /*
        Set the armor
         */
        setArmor();
        /*
        Register whether or not the elite mob is natural
         */
        this.isNaturalEntity = EntityTracker.isNaturalEntity(livingEntity);
        /*
        Set the power list
         */
        randomizePowers(eliteMobProperties);

        eliteMob.setCanPickupItems(false);

    }


    /**
     * Spawning method for boss mobs.
     * Assumes custom powers and custom names.
     *
     * @param entityType    type of mob that this entity is slated to become
     * @param location      location at which the elite mob will spawn
     * @param eliteMobLevel boss mob level, should be automatically generated based on the highest player tier online
     * @param name          the name for this boss mob, overrides the usual elite mob name format
     * @see BossMobEntity
     */
    public EliteMobEntity(EntityType entityType, Location location, int eliteMobLevel, String name, CreatureSpawnEvent.SpawnReason spawnReason) {

        /*
        Register living entity to keep track of which entity this object is tied to
         */
        this.eliteMob = spawnBossMobLivingEntity(entityType, location);
        /*
        Register level, this is variable as per stacking rules
         */
        setEliteMobLevel(eliteMobLevel);
        eliteMobTier = MobTierFinder.findMobTier(eliteMobLevel);
        /*
        Sets the spawn reason
         */
        setSpawnReason(spawnReason);
        /*
        Start tracking the entity
         */
        if (!EntityTracker.registerEliteMob(this)) return;
        /*
        Get correct instance of plugin data, necessary for settings names and health among other things
         */
        EliteMobProperties eliteMobProperties = EliteMobProperties.getPluginData(entityType);
        /*
        Handle name, variable as per stacking rules
         */
        setCustomName(name);
        /*
        Handle health, max is variable as per stacking rules
        Currently #setHealth() resets the health back to maximum
         */
        setMaxHealth(eliteMobProperties);
        setHealth();
        /*
        Register whether or not the elite mob is natural
         */
        this.isNaturalEntity = EntityTracker.isNaturalEntity(this.eliteMob);
        /*
        These have custom powers
         */
        this.hasCustomPowers = true;
        /*
        Start tracking the entity
         */
//        EntityTracker.registerEliteMob(this);

        eliteMob.setCanPickupItems(false);

        this.setHasStacking(false);
        this.setHasCustomArmor(true);

    }

    /**
     * Constructor for Elite Mobs spawned via command
     *
     * @param entityType    Type of entity to be spawned
     * @param location      Location at which the entity will spawn
     * @param eliteMobLevel Level of the Elite Mob
     * @param mobPowers     HashSet of ElitePower that the entity will have (can be empty)
     */
    public EliteMobEntity(EntityType entityType, Location location, int eliteMobLevel, HashSet<ElitePower> mobPowers, CreatureSpawnEvent.SpawnReason spawnReason) {

        /*
        Register living entity to keep track of which entity this object is tied to
         */
        this.eliteMob = spawnBossMobLivingEntity(entityType, location);
        /*
        Register level, this is variable as per stacking rules
         */
        setEliteMobLevel(eliteMobLevel);
        eliteMobTier = MobTierFinder.findMobTier(eliteMobLevel);
        /*
        Sets the spawn reason
         */
        setSpawnReason(spawnReason);
        /*
        Start tracking the entity
         */
        if (!EntityTracker.registerEliteMob(this)) return;
        /*
        Get correct instance of plugin data, necessary for settings names and health among other things
         */
        EliteMobProperties eliteMobProperties = EliteMobProperties.getPluginData(entityType);
        /*
        Handle name, variable as per stacking rules
         */
        setCustomName(eliteMobProperties);
        /*
        Handle health, max is variable as per stacking rules
        Currently #setHealth() resets the health back to maximum
         */
        setMaxHealth(eliteMobProperties);
        setHealth();
        /*
        Set the armor
         */
        setArmor();
        /*
        Register whether or not the elite mob is natural
        All mobs spawned via commands are considered natural
         */
        this.isNaturalEntity = true;
        /*
        Set the power list
         */
        if (!mobPowers.isEmpty()) {
            this.powers = mobPowers;
            for (ElitePower elitePower : powers) {
                elitePower.applyPowers(eliteMob);
                if (elitePower instanceof MajorPower)
                    this.majorPowerCount++;
                if (elitePower instanceof MinorPower)
                    this.minorPowerCount++;
            }
            MinorPowerPowerStance minorPowerPowerStance = new MinorPowerPowerStance(this);
            MajorPowerPowerStance majorPowerPowerStance = new MajorPowerPowerStance(this);
        } else {
            randomizePowers(eliteMobProperties);
        }

        eliteMob.setCanPickupItems(false);

    }

    /**
     * This avoids accidentally assigning an elite mob to an entity spawned specifically to be a boss mob or reinforcement
     */
    private static LivingEntity spawnBossMobLivingEntity(EntityType entityType, Location location) {
        NaturalMobSpawnEventHandler.setIgnoreMob(true);
        return (LivingEntity) location.getWorld().spawnEntity(location, entityType);
    }

    /**
     * Sets the display name to be used by this Elite Mob
     *
     * @param eliteMobProperties EliteMobProperties from where the display name will be obtained
     */
    private void setCustomName(EliteMobProperties eliteMobProperties) {
        this.name = ChatColorConverter.convert(
                eliteMobProperties.getName().replace(
                        "$level", eliteMobLevel + ""));
        eliteMob.setCustomName(this.name);
        if (ConfigValues.defaultConfig.getBoolean(DefaultConfig.ALWAYS_SHOW_NAMETAGS))
            eliteMob.setCustomNameVisible(true);
    }

    /**
     * Sets the display name to be used by this Elite Mob
     *
     * @param name String which defines the display name
     */
    private void setCustomName(String name) {
        this.name = ChatColorConverter.convert(name);
        this.getLivingEntity().setCustomName(this.name);
        this.hasCustomName = true;
        if (ConfigValues.defaultConfig.getBoolean(DefaultConfig.ALWAYS_SHOW_NAMETAGS))
            eliteMob.setCustomNameVisible(true);
    }

    /**
     * Sets the level of the Elite Mob. Values below 1 default to 1
     *
     * @param newLevel Level of the Elite Mob
     */
    private void setEliteMobLevel(int newLevel) {
        if (newLevel < 1)
            newLevel = 1;
        this.eliteMobLevel = newLevel;
    }

    /**
     * Sets the max health of the Elite Mob. This is calculated based on the Elite Mob level. Maxes out at 2000 due to
     * Minecraft restrictions
     *
     * @param eliteMobProperties EliteMobProperties from where the default max health of the mob will be obtained
     */
    private void setMaxHealth(EliteMobProperties eliteMobProperties) {
        double defaultMaxHealth = eliteMobProperties.getDefaultMaxHealth();
        this.maxHealth = (eliteMobLevel * CombatSystem.PER_LEVEL_POWER_INCREASE * defaultMaxHealth + defaultMaxHealth);
        eliteMob.getAttribute(Attribute.GENERIC_MAX_HEALTH).setBaseValue(maxHealth);
    }

    /**
     * Sets the health of the Elite Mob. This is the same value as the max health. Caps out at 2000.
     */
    private void setHealth() {
        eliteMob.setHealth(maxHealth = (maxHealth > 2000) ? 2000 : maxHealth);
    }

    /**
     * Sets the health of the Elite Mob. This is a percentage of the maximum health.
     *
     * @param healthPercentage Percentage of the maximum health to be set
     */
    private void setHealth(double healthPercentage) {
        eliteMob.setHealth(this.maxHealth * healthPercentage);
    }


    /**
     * Sets the armor of the EliteMob. The equipment progresses with every passing Elite Mob tier and dynamically adjusts
     * itself to the maximum tier set.
     */
    private void setArmor() {

        if (VersionChecker.currentVersionIsUnder(12, 2)) return;
        if (!ConfigValues.mobCombatSettingsConfig.getBoolean(MobCombatSettingsConfig.ELITE_ARMOR)) return;

        eliteMob.getEquipment().setItemInMainHandDropChance(0);
        eliteMob.getEquipment().setHelmetDropChance(0);
        eliteMob.getEquipment().setChestplateDropChance(0);
        eliteMob.getEquipment().setLeggingsDropChance(0);
        eliteMob.getEquipment().setBootsDropChance(0);

        if (hasCustomArmor) return;

        if (!(eliteMob instanceof Zombie || eliteMob instanceof PigZombie ||
                eliteMob instanceof Skeleton || eliteMob instanceof WitherSkeleton)) return;

        eliteMob.getEquipment().setBoots(new ItemStack(Material.AIR));
        eliteMob.getEquipment().setLeggings(new ItemStack(Material.AIR));
        eliteMob.getEquipment().setChestplate(new ItemStack(Material.AIR));
        eliteMob.getEquipment().setHelmet(new ItemStack(Material.AIR));

        if (eliteMobLevel >= 12)
            if (ConfigValues.mobCombatSettingsConfig.getBoolean(MobCombatSettingsConfig.ELITE_HELMETS))
                eliteMob.getEquipment().setHelmet(new ItemStack(Material.LEATHER_HELMET));

        if (eliteMobLevel >= 14)
            eliteMob.getEquipment().setBoots(new ItemStack(Material.LEATHER_BOOTS));

        if (eliteMobLevel >= 16)
            eliteMob.getEquipment().setLeggings(new ItemStack(Material.LEATHER_LEGGINGS));

        if (eliteMobLevel >= 18)
            eliteMob.getEquipment().setChestplate(new ItemStack(Material.LEATHER_CHESTPLATE));

        if (eliteMobLevel >= 20)
            if (ConfigValues.mobCombatSettingsConfig.getBoolean(MobCombatSettingsConfig.ELITE_HELMETS))
                eliteMob.getEquipment().setHelmet(new ItemStack(Material.CHAINMAIL_HELMET));

        if (eliteMobLevel >= 22)
            eliteMob.getEquipment().setBoots(new ItemStack(Material.CHAINMAIL_BOOTS));

        if (eliteMobLevel >= 24)
            eliteMob.getEquipment().setLeggings(new ItemStack(Material.CHAINMAIL_LEGGINGS));

        if (eliteMobLevel >= 26)
            eliteMob.getEquipment().setChestplate(new ItemStack(Material.CHAINMAIL_CHESTPLATE));

        if (eliteMobLevel >= 28)
            if (ConfigValues.mobCombatSettingsConfig.getBoolean(MobCombatSettingsConfig.ELITE_HELMETS))
                eliteMob.getEquipment().setHelmet(new ItemStack(Material.IRON_HELMET));

        if (eliteMobLevel >= 30)
            eliteMob.getEquipment().setBoots(new ItemStack(Material.IRON_BOOTS));

        if (eliteMobLevel >= 32)
            eliteMob.getEquipment().setLeggings(new ItemStack(Material.IRON_LEGGINGS));

        if (eliteMobLevel >= 34)
            eliteMob.getEquipment().setChestplate(new ItemStack(Material.IRON_CHESTPLATE));

        if (eliteMobLevel >= 36)
            eliteMob.getEquipment().setBoots(new ItemStack(Material.DIAMOND_BOOTS));

        if (eliteMobLevel >= 38)
            if (ConfigValues.mobCombatSettingsConfig.getBoolean(MobCombatSettingsConfig.ELITE_HELMETS))
                eliteMob.getEquipment().setHelmet(new ItemStack(Material.DIAMOND_HELMET));

        if (eliteMobLevel >= 40)
            eliteMob.getEquipment().setLeggings(new ItemStack(Material.DIAMOND_LEGGINGS));

        if (eliteMobLevel >= 42)
            eliteMob.getEquipment().setChestplate(new ItemStack(Material.DIAMOND_CHESTPLATE));

    }

    /**
     * Randomizes the powers that the EliteMob has. Determines which powers will be picked by randomizing the powers
     * from pools associated to the mob type and conditioned by config settings
     *
     * @param eliteMobProperties Properties of the Elite Mob Type
     */
    private void randomizePowers(EliteMobProperties eliteMobProperties) {

        if (hasCustomPowers) return;
        if (eliteMobTier < 1) return;

        int availableDefensivePowers = 0;
        int availableOffensivePowers = 0;
        int availableMiscellaneousPowers = 0;
        int availableMajorPowers = 0;

        if (eliteMobTier >= 1) availableDefensivePowers = 1;
        if (eliteMobTier >= 2) availableOffensivePowers = 1;
        if (eliteMobTier >= 3) availableMiscellaneousPowers = 1;
        if (eliteMobTier >= 4) availableMajorPowers = 1;
        if (eliteMobTier >= 5) availableDefensivePowers = 2;
        if (eliteMobTier >= 6) availableOffensivePowers = 2;
        if (eliteMobTier >= 7) availableMiscellaneousPowers = 2;
        if (eliteMobTier >= 8) availableMajorPowers = 2;

        //apply defensive powers
        applyPowers((HashSet<ElitePower>) eliteMobProperties.getValidDefensivePowers().clone(), availableDefensivePowers);

        //apply offensive powers
        applyPowers((HashSet<ElitePower>) eliteMobProperties.getValidOffensivePowers().clone(), availableOffensivePowers);

        //apply miscellaneous powers
        applyPowers((HashSet<ElitePower>) eliteMobProperties.getValidMiscellaneousPowers().clone(), availableMiscellaneousPowers);

        //apply major powers
        applyPowers((HashSet<ElitePower>) eliteMobProperties.getValidMajorPowers().clone(), availableMajorPowers);

        MinorPowerPowerStance minorPowerStanceMath = new MinorPowerPowerStance(this);
        MajorPowerPowerStance majorPowerPowerStance = new MajorPowerPowerStance(this);

    }

    /**
     * Applies the power to the Elite Mob based on a HashSet of powers
     *
     * @param elitePowers          ElitePower HashSet from which the powers will be randomized
     * @param availablePowerAmount Amount of powers to pick
     */
    private void applyPowers(HashSet<ElitePower> elitePowers, int availablePowerAmount) {

        if (availablePowerAmount < 1) return;

        ArrayList<ElitePower> localPowers = new ArrayList<>(elitePowers);

        for (ElitePower mobPower : this.powers)
            localPowers.remove(mobPower);

        for (int i = 0; i < availablePowerAmount; i++)
            if (localPowers.size() < 1)
                break;
            else {
                ElitePower selectedPower = localPowers.get(ThreadLocalRandom.current().nextInt(localPowers.size()));
                this.powers.add(selectedPower);
                selectedPower.applyPowers(this.eliteMob);
                localPowers.remove(selectedPower);
                if (selectedPower instanceof MajorPower)
                    this.majorPowerCount++;
                if (selectedPower instanceof MinorPower)
                    this.minorPowerCount++;
            }

    }

    /**
     * Applies a HashSet of ElitePower to an Elite Mob
     *
     * @param elitePowers HashSet of Elite Powers to be applied
     */
    public void setCustomPowers(HashSet<ElitePower> elitePowers) {

        this.powers = elitePowers;
        for (ElitePower elitePower : elitePowers) {
            elitePower.applyPowers(this.eliteMob);
            if (elitePower instanceof MinorPower)
                this.minorPowerCount++;
            if (elitePower instanceof MajorPower)
                this.majorPowerCount++;
        }

        if (this.minorPowerCount > 0) {
            MinorPowerPowerStance minorPowerStanceMath = new MinorPowerPowerStance(this);
        }

        if (this.majorPowerCount > 0) {
            MajorPowerPowerStance majorPowerPowerStance = new MajorPowerPowerStance(this);
        }

    }

    /**
     * Returns the living EliteMob
     *
     * @return LivingEntity associated to the Elite Mob
     */
    public LivingEntity getLivingEntity() {
        return eliteMob;
    }

    /**
     * Returns the level of the Elite Mob
     *
     * @return Level of the Elite Mob
     */
    public int getLevel() {
        return eliteMobLevel;
    }

    /**
     * Checks if an EliteMobs has that MobPower
     *
     * @param mobPower MobPower to be checked
     * @return If the EliteMob has this MobPower
     */
    public boolean hasPower(ElitePower mobPower) {
        for (ElitePower elitePower : powers)
            if (elitePower.getClass().getTypeName().equals(mobPower.getClass().getTypeName()))
                return true;
        return false;
    }

    /**
     * Returns how many minor powers this Elite Mob has
     *
     * @return How many minor powers this Elite Mob has
     */
    public int getMinorPowerCount() {
        return this.minorPowerCount;
    }

    /**
     * Returns how many major powers this Elite Mob has
     *
     * @return How many major powers this Elite Mob has
     */
    public int getMajorPowerCount() {
        return this.majorPowerCount;
    }

    /**
     * Returns the MobPowers that this Elite Mob has
     *
     * @return MobPowers that this Elite Mob has
     */
    public HashSet<ElitePower> getPowers() {
        return powers;
    }

    /**
     * Returns the maximum health that this Elite Mob has
     *
     * @return Maximum health that this Elite Mob has
     */
    public double getMaxHealth() {
        return maxHealth;
    }

    /**
     * Sets whether this Elite Mob can stack with other Elite Mobs or Entities of the the same Type
     *
     * @param bool Whether the Elite Mob can stack
     */
    public void setHasStacking(boolean bool) {
        this.hasStacking = bool;
    }

    /**
     * Returns if this Elite Mob has custom armor
     *
     * @return Whether Elite Mob has custom armor
     */
    public boolean getHasCustomArmor() {
        return this.hasCustomArmor;
    }

    /**
     * Sets if this Elite Mob will wear custom armor. Will then only be applied by other methods.
     *
     * @param bool whether this Elite Mob will wear custom armor
     */
    public void setHasCustomArmor(boolean bool) {
        this.hasCustomArmor = bool;
    }

    /**
     * Returns whether the Elite Mob has custom ElitePower
     *
     * @return Whether the Elite Mob has custom ElitePower
     */
    public boolean getHasCustomPowers() {
        return this.hasCustomPowers;
    }

    /**
     * Sets if the Elite Mob will have custom ElitePower
     *
     * @param bool Whether the Elite Mob will have custom EltiePower
     */
    public void setHasCustomPowers(boolean bool) {
        this.hasCustomPowers = bool;
    }

    /**
     * Returns the name of the Elite Mob
     *
     * @return Name of the Elite Mob
     */
    public String getName() {
        return this.name;
    }

    /**
     * Returns whether the Elite Mob has minor visual effects
     *
     * @return Whether the Elite Mob has minor visual effects
     */
    public boolean hasMinorVisualEffect() {
        return this.hasMinorVisualEffect;
    }

    /**
     * Sets whether the Elite Mob has a minor visual effect
     *
     * @param bool Whether the Elite Mob has a minor visual effect
     */
    public void setHasMinorVisualEffect(boolean bool) {
        this.hasMinorVisualEffect = bool;
    }

    /**
     * Returns whether the Elite Mob has a major visual effect
     *
     * @return Whether the Elite Mob has a major visual effect
     */
    public boolean hasMajorVisualEffect() {
        return this.hasMajorVisualEffect;
    }

    /**
     * Sets whether the Elite Mob has a major visual effect
     *
     * @param bool Whether the Elite Mob has a major visual effect
     */
    public void setHasMajorVisualEffect(boolean bool) {
        this.hasMajorVisualEffect = bool;
    }

    /**
     * Returns whether the Elite Mob is a natural entity. Only natural entities can drop special plugin loot. Additionally,
     * based on the settings, only natural Elite Mobs tend to only have visual effects if they are natural.
     *
     * @return Whether the Elite Mob is a natural entity.
     */
    public boolean isNaturalEntity() {
        return this.isNaturalEntity;
    }

    /**
     * Sets whether the ELite Mob is a natural entity. Only natural entities can drop special plugin loot. Additionally,
     * based on the settings, only natural Elite Mobs tend to only have visual effects if they are natural.
     *
     * @param bool Whether the Elite Mob is a natural entity.
     */
    public void setIsNaturalEntity(Boolean bool) {
        this.isNaturalEntity = bool;
        this.hasNormalLoot = bool;
    }

    /**
     * Returns whether the Elite Mob can stack.
     *
     * @return Whether the Elite Mob can stack.
     */
    public boolean canStack() {
        return this.hasStacking;
    }

    /**
     * Sets the name of the Elite Mob.
     *
     * @param name Name of the Elite Mob.
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * Returns whether the Elite Mob will unload when far away.
     *
     * @return Whether the Elite Mob will unload when far away.
     */
    public boolean getHasFarAwayUnload() {
        return this.hasFarAwayUnload;
    }

    /**
     * Sets whether the Elite Mob will unload when far away.
     *
     * @param bool Whether the Elite Mob will unload when far away.
     */
    public void setHasFarAwayUnload(boolean bool) {
        this.hasFarAwayUnload = bool;
    }

    /**
     * Returns whether the Elite Mob can drop special loot. This only affects its eligibility and does not necessarily
     * mean it will drop special loot.
     *
     * @return Whether the ELite Mob can drop special loot.
     */
    public boolean getHasSpecialLoot() {
        return this.hasNormalLoot;
    }

    /**
     * Sets whether the ELite Mob can drop special loot. This only affects its eligibility and does not necessarily
     * mean it will drop special loot.
     *
     * @param bool Whether the Elite Mob can drop special loot.
     */
    public void setHasSpecialLoot(boolean bool) {
        this.isNaturalEntity = bool;
        this.hasNormalLoot = bool;
    }

    /**
     * Returns whether the Elite Mob has a special health value. Default values are simply multiplied from the default
     * health.
     *
     * @return Whether the Elite Mob has a special health value.
     */
    public boolean getHasCustomHealth() {
        return this.hasCustomHealth;
    }

    /**
     * Sets whether the Elite Mob has a special health value. Default health values are simply multiplied from the default
     * health.
     *
     * @param bool Whether the Elite Mob has a special health value.
     */
    public void setHasCustomHealth(boolean bool) {
        this.hasCustomHealth = bool;
    }

    /**
     * Returns whether the Elite Mob has a custom name
     *
     * @return Whether the Elite Mob has a custom name
     */
    public boolean getHasCustomName() {
        return this.hasCustomName;
    }

    /**
     * Sets whether the Elite Mob has a custom name
     *
     * @param bool Whether the Elite Mob has a custom name
     */
    public void setHasCustomName(boolean bool) {
        this.hasCustomName = bool;
    }

    /**
     * Sets whether the Elite Mob has obfuscate visual effects
     *
     * @param bool Whether the Elite Mob has visual effects
     */
    public void setHasVisualEffectObfuscated(boolean bool) {
        this.hasVisualEffectObfuscated = bool;
    }

    /**
     * Returns whether the Elite Mob has obfuscated visual effects
     *
     * @return Whether the Elite Mob has obfuscated visual effects
     */
    public boolean getHasVisualEffectObfuscated() {
        return this.hasVisualEffectObfuscated;
    }

    /**
     * Gets the spawn reason for the LivingEntity. Used for the API.
     *
     * @return Spawn reason for the LivingEntity.
     */
    public CreatureSpawnEvent.SpawnReason getSpawnReason() {
        return this.spawnReason;
    }

    /**
     * Sets the spawn reason for the Living Entity. Used for the API.
     *
     * @param spawnReason Spawn reason for the Living Entity.
     */
    public void setSpawnReason(CreatureSpawnEvent.SpawnReason spawnReason) {
        this.spawnReason = spawnReason;
    }

}