Package net.aufdemrand.denizen.objects

Source Code of net.aufdemrand.denizen.objects.dPlayer

package net.aufdemrand.denizen.objects;

import net.aufdemrand.denizen.flags.FlagManager;
import net.aufdemrand.denizen.objects.properties.Property;
import net.aufdemrand.denizen.objects.properties.PropertyParser;
import net.aufdemrand.denizen.scripts.commands.core.FailCommand;
import net.aufdemrand.denizen.scripts.commands.core.FinishCommand;
import net.aufdemrand.denizen.tags.Attribute;
import net.aufdemrand.denizen.tags.core.PlayerTags;
import net.aufdemrand.denizen.utilities.DenizenAPI;
import net.aufdemrand.denizen.utilities.debugging.dB;
import net.aufdemrand.denizen.utilities.depends.Depends;
import net.aufdemrand.denizen.utilities.nbt.ImprovedOfflinePlayer;
import net.aufdemrand.denizen.utilities.packets.BossHealthBar;
import net.aufdemrand.denizen.utilities.packets.EntityEquipment;
import net.aufdemrand.denizen.utilities.packets.ItemChangeMessage;
import net.aufdemrand.denizen.utilities.packets.PlayerBars;
import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.npc.NPC;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.CraftingInventory;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.map.MapView;
import org.bukkit.util.BlockIterator;

import java.util.*;
import java.util.regex.Pattern;

public class dPlayer implements dObject, Adjustable {


    /////////////////////
    //   STATIC METHODS
    /////////////////


    public static dPlayer mirrorBukkitPlayer(OfflinePlayer player) {
        if (player == null) return null;
        else return new dPlayer(player);
    }

    static Map<String, UUID> playerNames = new HashMap<String, UUID>();

    /**
     * Notes that the player exists, for easy dPlayer valueOf handling.
     */
    public static void notePlayer(OfflinePlayer player) {
        if (player.getName() == null) {
            dB.echoError("Null player " + player.toString());
            return;
        }
        if (!playerNames.containsKey(player.getName().toLowerCase())) {
            playerNames.put(player.getName().toLowerCase(), player.getUniqueId());
        }
    }

    public static boolean isNoted(OfflinePlayer player) {
        return playerNames.containsValue(player.getUniqueId());
    }

    public static Map<String, UUID> getAllPlayers() {
        return playerNames;
    }


    /////////////////////
    //   OBJECT FETCHER
    /////////////////

    // <--[language]
    // @name p@
    // @group Object Fetcher System
    // @description
    // p@ refers to the 'object identifier' of a dPlayer. The 'p@' is notation for Denizen's Object
    // Fetcher. The only valid constructor for a dPlayer is the name of the player the object should be
    // associated with. For example, to reference the player named 'mythan', use p@mythan. Player names
    // are case insensitive.
    // -->

    @Fetchable("p")
    public static dPlayer valueOf(String string) {
        return valueOfInternal(string, true);
    }


    static dPlayer valueOfInternal(String string, boolean announce) {
        if (string == null) return null;

        string = string.replace("p@", "").replace("P@", "");

        // Match as a UUID

        if (string.indexOf('-') >= 0) {
            try {
                UUID uuid = UUID.fromString(string);
                if (uuid != null) {
                    OfflinePlayer player = Bukkit.getOfflinePlayer(uuid);
                    if (player != null) {
                        return new dPlayer(player);
                    }
                }
            }
            catch (IllegalArgumentException e) {
                // Nothing
            }
        }

        // Match as a player name
        if (playerNames.containsKey(string.toLowerCase())) {
            OfflinePlayer player = Bukkit.getOfflinePlayer(playerNames.get(string.toLowerCase()));
            return new dPlayer(player);
        }

        if (announce)
            dB.echoError("Invalid Player! '" + string + "' could not be found.");

        return null;
    }


    public static boolean matches(String arg) {
        // If passed null, of course it doesn't match!
        if (arg == null) return false;

        // If passed a identified object that starts with 'p@', return true
        // even if the player doesn't technically exist.
        if (arg.toLowerCase().startsWith("p@")) return true;

        arg = arg.replace("p@", "").replace("P@", "");
        if (arg.indexOf('-') >= 0) {
            try {
                UUID uuid = UUID.fromString(arg);
                if (uuid != null) {
                    OfflinePlayer player = Bukkit.getOfflinePlayer(uuid);
                    if (player != null) {
                        return true;
                    }
                }
            }
            catch (IllegalArgumentException e) {
                // Nothing
            }
        }
        return false;
    }

    public static boolean playerNameIsValid(String name) {
        return playerNames.containsKey(name.toLowerCase());
    }


    /////////////////////
    //   CONSTRUCTORS
    /////////////////

    public dPlayer(OfflinePlayer player) {
        offlinePlayer = player;
    }

    public dPlayer(UUID uuid) {
        offlinePlayer = Bukkit.getOfflinePlayer(uuid);
    }

    public dPlayer(Player player) {
        this((OfflinePlayer)player);
        if (Depends.citizens != null && CitizensAPI.getNPCRegistry().isNPC(player))
            NPCPlayer = player;
    }


    /////////////////////
    //   INSTANCE FIELDS/METHODS
    /////////////////

    OfflinePlayer offlinePlayer = null;

    Player NPCPlayer = null;

    public boolean isValid() {
        return getPlayerEntity() != null || getOfflinePlayer() != null;
    }

    public Player getPlayerEntity() {
        if (NPCPlayer != null) return NPCPlayer;
        if (offlinePlayer == null) return null;
        return Bukkit.getPlayer(offlinePlayer.getUniqueId());
    }

    public OfflinePlayer getOfflinePlayer() {
        return offlinePlayer;
    }

    public ImprovedOfflinePlayer getNBTEditor() {
        return new ImprovedOfflinePlayer(getOfflinePlayer());
    }

    public dEntity getDenizenEntity() {
        return new dEntity(getPlayerEntity());
    }

    public dNPC getSelectedNPC() {
        if (Depends.citizens != null) {
            NPC npc = CitizensAPI.getDefaultNPCSelector().getSelected(getPlayerEntity());
            if (npc != null)
                return dNPC.mirrorCitizensNPC(npc);
        }
        return null;
    }

    public String getName() {
        if (offlinePlayer == null)
            return null;
        return offlinePlayer.getName();
    }

    public String getSaveName() {
        if (offlinePlayer == null)
            return "00.UNKNOWN";
        String baseID = offlinePlayer.getUniqueId().toString().toUpperCase().replace("-", "");
        return baseID.substring(0, 2) + "." + baseID;
    }

    public dLocation getLocation() {
        if (isOnline()) return new dLocation(getPlayerEntity().getLocation());
        else return new dLocation(getNBTEditor().getLocation());
    }

    public int getRemainingAir() {
        if (isOnline())
            return getPlayerEntity().getRemainingAir();
        else
            return getNBTEditor().getRemainingAir();
    }

    public int getMaximumAir() {
        if (isOnline())
            return getPlayerEntity().getMaximumAir();
        else
            return 300;
    }

    public dLocation getEyeLocation() {
        if (isOnline()) return new dLocation(getPlayerEntity().getEyeLocation());
        else return null;
    }

    public PlayerInventory getBukkitInventory() {
        if (isOnline()) return getPlayerEntity().getInventory();
        else return getNBTEditor().getInventory();
    }

    public dInventory getInventory() {
        if (isOnline()) return new dInventory(getPlayerEntity().getInventory());
        else return new dInventory(getNBTEditor());
    }

    public CraftingInventory getBukkitWorkbench() {
        if (isOnline()) {
            if (getPlayerEntity().getOpenInventory().getType() == InventoryType.WORKBENCH
                    || getPlayerEntity().getOpenInventory().getType() == InventoryType.CRAFTING)
                return (CraftingInventory) getPlayerEntity().getOpenInventory().getTopInventory();
        }
        return null;
    }

    public dInventory getWorkbench() {
        if (isOnline()) {
            CraftingInventory workbench = getBukkitWorkbench();
            if (workbench != null)
                return new dInventory(workbench, getPlayerEntity());
        }
        return null;
    }

    public Inventory getBukkitEnderChest() {
        if (isOnline()) return getPlayerEntity().getEnderChest();
        else return getNBTEditor().getEnderChest();
    }

    public dInventory getEnderChest() {
        if (isOnline()) return new dInventory(getPlayerEntity().getEnderChest(), getPlayerEntity());
        else return new dInventory(getNBTEditor(), true);
    }

    public World getWorld() {
        if (isOnline()) return getPlayerEntity().getWorld();
        else return null;
    }

    public void decrementStatistic(Statistic statistic, int amount) {
        if (isOnline()) getPlayerEntity().decrementStatistic(statistic, amount);
        else {}// TODO: write to JSON?
    }

    public void decrementStatistic(Statistic statistic, EntityType entity, int amount) {
        if (isOnline() && statistic.getType() == Statistic.Type.ENTITY)
            getPlayerEntity().decrementStatistic(statistic, entity, amount);
        else {}// TODO: write to JSON?
    }

    public void decrementStatistic(Statistic statistic, Material material, int amount) {
        if (isOnline() && (statistic.getType() == Statistic.Type.BLOCK
                || statistic.getType() == Statistic.Type.ITEM))
            getPlayerEntity().decrementStatistic(statistic, material, amount);
        else {}// TODO: write to JSON?
    }

    public void incrementStatistic(Statistic statistic, int amount) {
        if (isOnline()) getPlayerEntity().incrementStatistic(statistic, amount);
        else {}// TODO: write to JSON?
    }

    public void incrementStatistic(Statistic statistic, EntityType entity, int amount) {
        if (isOnline() && statistic.getType() == Statistic.Type.ENTITY)
            getPlayerEntity().incrementStatistic(statistic, entity, amount);
        else {}// TODO: write to JSON?
    }

    public void incrementStatistic(Statistic statistic, Material material, int amount) {
        if (isOnline() && (statistic.getType() == Statistic.Type.BLOCK
                || statistic.getType() == Statistic.Type.ITEM))
            getPlayerEntity().incrementStatistic(statistic, material, amount);
        else {}// TODO: write to JSON?
    }

    public void setStatistic(Statistic statistic, int amount) {
        if (isOnline()) getPlayerEntity().setStatistic(statistic, amount);
        else {}// TODO: write to JSON?
    }

    public void setStatistic(Statistic statistic, EntityType entity, int amount) {
        if (isOnline() && statistic.getType() == Statistic.Type.ENTITY)
            getPlayerEntity().setStatistic(statistic, entity, amount);
        else {}// TODO: write to JSON?
    }

    public void setStatistic(Statistic statistic, Material material, int amount) {
        if (isOnline() && (statistic.getType() == Statistic.Type.BLOCK
                || statistic.getType() == Statistic.Type.ITEM))
            getPlayerEntity().setStatistic(statistic, material, amount);
        else {}// TODO: write to JSON?
    }

    public boolean isOnline() {
        return getPlayerEntity() != null;
    }

    public void setBedSpawnLocation(Location location) {
        if (isOnline())
            getPlayerEntity().setBedSpawnLocation(location);
        else
            getNBTEditor().setBedSpawnLocation(location, getNBTEditor().isSpawnForced());
    }

    public void setLocation(Location location) {
        if (isOnline())
            getPlayerEntity().teleport(location);
        else
            getNBTEditor().setLocation(location);
    }

    public void setMaximumAir(int air) {
        if (isOnline())
            getPlayerEntity().setMaximumAir(air);
        else
            dB.echoError("Cannot set the maximum air of an offline player!");
    }

    public void setRemainingAir(int air) {
        if (isOnline())
            getPlayerEntity().setRemainingAir(air);
        else
            getNBTEditor().setRemainingAir(air);
    }

    public void setLevel(int level) {
        if (isOnline())
            getPlayerEntity().setLevel(level);
        else
            getNBTEditor().setLevel(level);
    }

    public void setFlySpeed(float speed) {
        if (isOnline())
            getPlayerEntity().setFlySpeed(speed);
        else
            getNBTEditor().setFlySpeed(speed);
    }

    public void setGameMode(GameMode mode) {
        if (isOnline())
            getPlayerEntity().setGameMode(mode);
        else
            getNBTEditor().setGameMode(mode);
    }


    /////////////////////
    //   dObject Methods
    /////////////////

    private String prefix = "Player";

    @Override
    public String getPrefix() {
        return prefix;
    }

    @Override
    public dPlayer setPrefix(String prefix) {
        this.prefix = prefix;
        return this;
    }

    @Override
    public String debug() {
        return (prefix + "='<A>" + identifySimple() + "<G>'  ");
    }

    @Override
    public boolean isUnique() {
        return true;
    }

    @Override
    public String getObjectType() {
        return "Player";
    }

    @Override
    public String identify() {
        return "p@" + offlinePlayer.getUniqueId().toString();
    }

    @Override
    public String identifySimple() {
        return "p@" + offlinePlayer.getName();
    }

    @Override
    public String toString() {
        return identify();
    }


    @Override
    public String getAttribute(Attribute attribute) {
        if (attribute == null)
            return "null";

        if (offlinePlayer == null)
            return null;

        /////////////////////
        //   OFFLINE ATTRIBUTES
        /////////////////

        // Defined in dEntity
        if (attribute.startsWith("is_player")) {
            return Element.TRUE.getAttribute(attribute.fulfill(1));
        }

        /////////////////////
        //   DEBUG ATTRIBUTES
        /////////////////

        // <--[tag]
        // @attribute <p@player.debug.log>
        // @returns Element(Boolean)
        // @description
        // Debugs the player in the log and returns true.
        // Works with offline players.
        // -->
        if (attribute.startsWith("debug.log")) {
            dB.log(debug());
            return new Element(Boolean.TRUE.toString())
                    .getAttribute(attribute.fulfill(2));
        }

        // <--[tag]
        // @attribute <p@player.debug.no_color>
        // @returns Element
        // @description
        // Returns the player's debug with no color.
        // Works with offline players.
        // -->
        if (attribute.startsWith("debug.no_color")) {
            return new Element(ChatColor.stripColor(debug()))
                    .getAttribute(attribute.fulfill(2));
        }

        // <--[tag]
        // @attribute <p@player.debug>
        // @returns Element
        // @description
        // Returns the player's debug.
        // Works with offline players.
        // -->
        if (attribute.startsWith("debug")) {
            return new Element(debug())
                    .getAttribute(attribute.fulfill(1));
        }

        // <--[tag]
        // @attribute <p@player.prefix>
        // @returns Element
        // @description
        // Returns the dObject's prefix.
        // Works with offline players.
        // -->
        if (attribute.startsWith("prefix"))
            return new Element(prefix)
                    .getAttribute(attribute.fulfill(1));


        /////////////////////
        //   DENIZEN ATTRIBUTES
        /////////////////

        // <--[tag]
        // @attribute <p@player.chat_history_list>
        // @returns dList
        // @description
        // Returns a list of the last 10 things the player has said, less
        // if the player hasn't said all that much.
        // Works with offline players.
        // -->
        if (attribute.startsWith("chat_history_list"))
            return new dList(PlayerTags.playerChatHistory.get(getName())) // TODO: UUID?
                    .getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.chat_history[#]>
        // @returns Element
        // @description
        // returns the last thing the player said.
        // If a number is specified, returns an earlier thing the player said.
        // Works with offline players.
        // -->
        if (attribute.startsWith("chat_history")) {
            int x = 1;
            if (attribute.hasContext(1) && aH.matchesInteger(attribute.getContext(1)))
                x = attribute.getIntContext(1);
            // No playerchathistory? Return null.
            if (!PlayerTags.playerChatHistory.containsKey(getName())) // TODO: UUID?
                return null;
            else
                return new Element(PlayerTags.playerChatHistory.get(getName()).get(x - 1)) // TODO: UUID?
                        .getAttribute(attribute.fulfill(1));
        }

        // <--[tag]
        // @attribute <p@player.flag[<flag_name>]>
        // @returns Flag dList
        // @description
        // returns the specified flag from the player.
        // Works with offline players.
        // -->
        if (attribute.startsWith("flag")) {
            String flag_name;
            if (attribute.hasContext(1)) flag_name = attribute.getContext(1);
            else return null;
            if (attribute.getAttribute(2).equalsIgnoreCase("is_expired")
                    || attribute.startsWith("isexpired"))
                return new Element(!FlagManager.playerHasFlag(this, flag_name))
                        .getAttribute(attribute.fulfill(2));
            if (attribute.getAttribute(2).equalsIgnoreCase("size") && !FlagManager.playerHasFlag(this, flag_name))
                return new Element(0).getAttribute(attribute.fulfill(2));
            if (FlagManager.playerHasFlag(this, flag_name))
                return new dList(DenizenAPI.getCurrentInstance().flagManager()
                        .getPlayerFlag(this, flag_name))
                        .getAttribute(attribute.fulfill(1));
            return new Element(identify()).getAttribute(attribute);
        }

        // <--[tag]
        // @attribute <p@player.has_flag[<flag_name>]>
        // @returns Element(Boolean)
        // @description
        // returns true if the Player has the specified flag, otherwise returns false.
        // Works with offline players.
        // -->
        if (attribute.startsWith("has_flag")) {
            String flag_name;
            if (attribute.hasContext(1)) flag_name = attribute.getContext(1);
            else return null;
            return new Element(FlagManager.playerHasFlag(this, flag_name)).getAttribute(attribute.fulfill(1));
        }

        // <--[tag]
        // @attribute <p@player.list_flags[(regex:)<search>]>
        // @returns dList
        // @description
        // Returns a list of a player's flag names, with an optional search for
        // names containing a certain pattern.
        // Works with offline players.
        // -->
        if (attribute.startsWith("list_flags")) {
            dList allFlags = new dList(DenizenAPI.getCurrentInstance().flagManager().listPlayerFlags(this));
            dList searchFlags = null;
            if (!allFlags.isEmpty() && attribute.hasContext(1)) {
                searchFlags = new dList();
                String search = attribute.getContext(1).toLowerCase();
                if (search.startsWith("regex:")) {
                    try {
                        Pattern pattern = Pattern.compile(search.substring(6));
                        for (String flag : allFlags)
                            if (pattern.matcher(flag).matches())
                                searchFlags.add(flag);
                    } catch (Exception e) {
                        dB.echoError(e);
                    }
                }
                else {
                    for (String flag : allFlags)
                        if (flag.toLowerCase().contains(search))
                            searchFlags.add(flag);
                }
            }
            return searchFlags == null ? allFlags.getAttribute(attribute.fulfill(1))
                    : searchFlags.getAttribute(attribute.fulfill(1));
        }


        if (attribute.startsWith("current_step")) {
            String outcome = "null";
            if (attribute.hasContext(1)) {
                try {
                    outcome = DenizenAPI.getCurrentInstance().getSaves().getString("Players." + getName() + ".Scripts."
                            + dScript.valueOf(attribute.getContext(1)).getName() + ".Current Step");
                } catch (Exception e) {
                    outcome = "null";
                }
            }
            return new Element(outcome).getAttribute(attribute.fulfill(1));
        }


        /////////////////////
        //   ECONOMY ATTRIBUTES
        /////////////////

        // <--[tag]
        // @attribute <p@player.money>
        // @returns Element(Decimal)
        // @description
        // returns the amount of money the player has with the registered Economy system.
        // May work offline depending on economy plugin.
        // -->

        if (attribute.startsWith("money")) {
            if(Depends.economy != null) {

                // <--[tag]
                // @attribute <p@player.money.currency_singular>
                // @returns Element
                // @description
                // returns the name of a single piece of currency - EG: Dollar
                // (Only if supported by the registered Economy system.)
                // -->
                if (attribute.startsWith("money.currency_singular"))
                    return new Element(Depends.economy.currencyNameSingular())
                            .getAttribute(attribute.fulfill(2));

                // <--[tag]
                // @attribute <p@player.money.currency>
                // @returns Element
                // @description
                // returns the name of multiple pieces of currency - EG: Dollars
                // (Only if supported by the registered Economy system.)
                // -->
                if (attribute.startsWith("money.currency"))
                    return new Element(Depends.economy.currencyNamePlural())
                            .getAttribute(attribute.fulfill(2));

                return new Element(Depends.economy.getBalance(getName())) // TODO: Vault UUID support?
                        .getAttribute(attribute.fulfill(1));

            } else {
                if (!attribute.hasAlternative())
                    dB.echoError("No economy loaded! Have you installed Vault and a compatible economy plugin?");
                return null;
            }
        }


        /////////////////////
        //   ENTITY LIST ATTRIBUTES
        /////////////////

        // <--[tag]
        // @attribute <p@player.target[(<entity>|...)]>
        // @returns dEntity
        // @description
        // Returns the entity that the player is looking at, within a maximum range of 50 blocks,
        // or null if the player is not looking at an entity.
        // Optionally, specify a list of entities, entity types, or 'npc' to only count those targets.
        // -->

        if (attribute.startsWith("target")) {
            int range = 50;
            int attribs = 1;

            // <--[tag]
            // @attribute <p@player.target[(<entity>|...)].within[(<#>)]>
            // @returns dEntity
            // @description
            // Returns the entity that the player is looking at within the specified range limit,
            // or null if the player is not looking at an entity.
            // Optionally, specify a list of entities, entity types, or 'npc' to only count those targets.
            // -->
            if (attribute.getAttribute(2).startsWith("within") &&
                    attribute.hasContext(2) &&
                    aH.matchesInteger(attribute.getContext(2))) {
                attribs = 2;
                range = attribute.getIntContext(2);
            }

            List<Entity> entities = getPlayerEntity().getNearbyEntities(range, range, range);
            ArrayList<LivingEntity> possibleTargets = new ArrayList<LivingEntity>();
            for (Entity entity : entities) {
                if (entity instanceof LivingEntity) {

                    // if we have a context for entity types, check the entity
                    if (attribute.hasContext(1)) {
                        String context = attribute.getContext(1);
                        if (context.toLowerCase().startsWith("li@"))
                            context = context.substring(3);
                        for (String ent: context.split("\\|")) {
                            boolean valid = false;

                            if (ent.equalsIgnoreCase("npc") && Depends.citizens != null
                                    && CitizensAPI.getNPCRegistry().isNPC(entity)) {
                                valid = true;
                            }
                            else if (dEntity.matches(ent)) {
                                // only accept generic entities that are not NPCs
                                if (dEntity.valueOf(ent).isGeneric()) {
                                    if (Depends.citizens == null || !CitizensAPI.getNPCRegistry().isNPC(entity)) {
                                        valid = true;
                                    }
                                }
                                else {
                                    valid = true;
                                }
                            }
                            if (valid) possibleTargets.add((LivingEntity) entity);
                        }
                    } else { // no entity type specified
                        possibleTargets.add((LivingEntity) entity);
                        entity.getType();
                    }
                }
            }

            // Find the valid target
            BlockIterator bi;
            try {
                bi = new BlockIterator(getPlayerEntity(), range);
            }
            catch (IllegalStateException e) {
                return null;
            }
            Block b;
            Location l;
            int bx, by, bz;
            double ex, ey, ez;

            // Loop through player's line of sight
            while (bi.hasNext()) {
                b = bi.next();
                bx = b.getX();
                by = b.getY();
                bz = b.getZ();

                if (b.getType() != Material.AIR) {
                    // Line of sight is broken
                    break;
                }
                else {
                    // Check for entities near this block in the line of sight
                    for (LivingEntity possibleTarget : possibleTargets) {
                        l = possibleTarget.getLocation();
                        ex = l.getX();
                        ey = l.getY();
                        ez = l.getZ();

                        if ((bx - .50 <= ex && ex <= bx + 1.50) &&
                                (bz - .50 <= ez && ez <= bz + 1.50) &&
                                (by - 1 <= ey && ey <= by + 2.5)) {
                            // Entity is close enough, so return it
                            return new dEntity(possibleTarget).getAttribute(attribute.fulfill(attribs));
                        }
                    }
                }
            }
            return null;
        }

        // <--[tag]
        // @attribute <p@player.list>
        // @returns dList(dPlayer)
        // @description
        // Returns all players that have ever played on the server, online or not.
        // ** NOTE: This tag is old. Please instead use <server.list_players> **
        // -->
        if (attribute.startsWith("list")) {
            List<String> players = new ArrayList<String>();

            // <--[tag]
            // @attribute <p@player.list.online>
            // @returns dList(dPlayer)
            // @description
            // Returns all online players.
            // **NOTE: This will only work if there is a player attached to the current script.
            // If you need it anywhere else, use <server.list_online_players>**
            // -->
            if (attribute.startsWith("list.online")) {
                for(Player player : Bukkit.getOnlinePlayers())
                    players.add(player.getName());
                return new dList(players).getAttribute(attribute.fulfill(2));
            }

            // <--[tag]
            // @attribute <p@player.list.offline>
            // @returns dList(dPlayer)
            // @description
            // Returns all players that have ever played on the server, but are not currently online.
            // ** NOTE: This tag is old. Please instead use <server.list_offline_players> **
            // -->
            else if (attribute.startsWith("list.offline")) {
                for(OfflinePlayer player : Bukkit.getOfflinePlayers()) {
                    if (!player.isOnline())
                        players.add("p@" + player.getUniqueId().toString());
                }
                return new dList(players).getAttribute(attribute.fulfill(2));
            }
            else {
                for(OfflinePlayer player : Bukkit.getOfflinePlayers())
                    players.add("p@" + player.getUniqueId().toString());
                return new dList(players).getAttribute(attribute.fulfill(1));
            }
        }


        /////////////////////
        //   IDENTIFICATION ATTRIBUTES
        /////////////////

        if (attribute.startsWith("name") && !isOnline())
            // This can be parsed later with more detail if the player is online, so only check for offline.
            return new Element(getName()).getAttribute(attribute.fulfill(1));

        else if (attribute.startsWith("uuid") && !isOnline())
            // This can be parsed later with more detail if the player is online, so only check for offline.
            return new Element(offlinePlayer.getUniqueId().toString()).getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.save_name>
        // @returns Element
        // @description
        // returns the ID used to save the player in Denizen's saves.yml file.
        // Works with offline players.
        // -->
        if (attribute.startsWith("save_name"))
            return new Element(getSaveName()).getAttribute(attribute.fulfill(1));


        /////////////////////
        //   LOCATION ATTRIBUTES
        /////////////////

        // <--[tag]
        // @attribute <p@player.bed_spawn>
        // @returns dLocation
        // @description
        // Returns the location of the player's bed spawn location, 'null' if
        // it doesn't exist.
        // Works with offline players.
        // -->
        if (attribute.startsWith("bed_spawn"))
            return new dLocation(getOfflinePlayer().getBedSpawnLocation())
                    .getAttribute(attribute.fulfill(2));

        // If online, let dEntity handle location tags since there are more options
        // for online Players

        if (attribute.startsWith("location") && !isOnline()) {
            return getLocation().getAttribute(attribute.fulfill(1));
        }


        /////////////////////
        //   STATE ATTRIBUTES
        /////////////////

        // <--[tag]
        // @attribute <p@player.first_played>
        // @returns Duration
        // @description
        // returns the millisecond time of when the player first logged on to this server.
        // Works with offline players.
        // -->
        if (attribute.startsWith("first_played")) {
            attribute = attribute.fulfill(1);
            if (attribute.startsWith("milliseconds") || attribute.startsWith("in_milliseconds"))
                return new Element(getOfflinePlayer().getFirstPlayed())
                        .getAttribute(attribute.fulfill(1));
            else
                return new Duration(getOfflinePlayer().getFirstPlayed() / 50)
                        .getAttribute(attribute);
        }

        // <--[tag]
        // @attribute <p@player.has_played_before>
        // @returns Element(Boolean)
        // @description
        // returns whether the player has played before.
        // Works with offline players.
        // Note: This will just always return true.
        // -->
        if (attribute.startsWith("has_played_before"))
            return new Element(true)
                    .getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.health.is_scaled>
        // @returns Element(Boolean)
        // @description
        // returns whether the player's health bar is currently being scaled.
        // -->
        if (attribute.startsWith("health.is_scaled"))
            return new Element(getPlayerEntity().isHealthScaled())
                    .getAttribute(attribute.fulfill(2));

        // <--[tag]
        // @attribute <p@player.health.scale>
        // @returns Element(Decimal)
        // @description
        // returns the current scale for the player's health bar
        // -->
        if (attribute.startsWith("health.scale"))
            return new Element(getPlayerEntity().getHealthScale())
                    .getAttribute(attribute.fulfill(2));

        // Handle dEntity oxygen tags here to allow getting them when the player is offline
        if (attribute.startsWith("oxygen.max"))
            return new Duration((long) getMaximumAir()).getAttribute(attribute.fulfill(1));

        if (attribute.startsWith("oxygen"))
            return new Duration((long) getRemainingAir()).getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.is_banned>
        // @returns Element(Boolean)
        // @description
        // returns whether the player is banned.
        // -->
        if (attribute.startsWith("is_banned"))
            return new Element(getOfflinePlayer().isBanned())
                    .getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.is_online>
        // @returns Element(Boolean)
        // @description
        // returns whether the player is currently online.
        // Works with offline players (returns false in that case).
        // -->
        if (attribute.startsWith("is_online"))
            return new Element(isOnline()).getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.is_op>
        // @returns Element(Boolean)
        // @description
        // returns whether the player is a full server operator.
        // Works with offline players.
        // -->
        if (attribute.startsWith("is_op"))
            return new Element(getOfflinePlayer().isOp())
                    .getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.is_whitelisted>
        // @returns Element(Boolean)
        // @description
        // returns whether the player is whitelisted.
        // Works with offline players.
        // -->
        if (attribute.startsWith("is_whitelisted"))
            return new Element(getOfflinePlayer().isWhitelisted())
                    .getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.last_played>
        // @returns Duration
        // @description
        // returns the millisecond time of when the player was last seen.
        // Works with offline players.
        // -->
        if (attribute.startsWith("last_played")) {
            attribute = attribute.fulfill(1);
            if (attribute.startsWith("milliseconds") || attribute.startsWith("in_milliseconds"))
                return new Element(getOfflinePlayer().getLastPlayed())
                        .getAttribute(attribute.fulfill(1));
            else
                return new Duration(getOfflinePlayer().getLastPlayed() / 50)
                        .getAttribute(attribute);
        }

        // <--[tag]
        // @attribute <p@player.groups>
        // @returns dList
        // @description
        // returns a list of all groups the player is in.
        // May work with offline players, depending on permission plugin.
        // -->
        if (attribute.startsWith("groups")) {
            if (Depends.permissions == null) {
                if (!attribute.hasAlternative())
                    dB.echoError("No permission system loaded! Have you installed Vault and a compatible permissions plugin?");
                return null;
            }
            dList list = new dList();
            for (String group: Depends.permissions.getGroups()) {
                if (Depends.permissions.playerInGroup(null, offlinePlayer, group)) {
                    list.add(group);
                }
            }
            return list.getAttribute(attribute.fulfill(1));
        }

        // <--[tag]
        // @attribute <p@player.in_group[<group_name>]>
        // @returns Element(Boolean)
        // @description
        // returns whether the player is in the specified group (requires the player to be online)
        // -->
        if (attribute.startsWith("in_group")) {
            if (Depends.permissions == null) {
                if (!attribute.hasAlternative())
                    dB.echoError("No permission system loaded! Have you installed Vault and a compatible permissions plugin?");
                return null;
            }

            String group = attribute.getContext(1);

            // <--[tag]
            // @attribute <p@player.in_group[<group_name>].global>
            // @returns Element(Boolean)
            // @description
            // returns whether the player has the group with no regard to the
            // player's current world.
            // (Works with offline players)
            // (Note: This may or may not be functional with your permissions system.)
            // -->

            // Non-world specific permission
            if (attribute.getAttribute(2).startsWith("global"))
                return new Element(Depends.permissions.playerInGroup((World) null, getName(), group)) // TODO: Vault UUID support?
                        .getAttribute(attribute.fulfill(2));

                // <--[tag]
                // @attribute <p@player.in_group[<group_name>].world>
                // @returns Element(Boolean)
                // @description
                // returns whether the player has the group in regards to the
                // player's current world.
                // (Works with offline players)
                // (Note: This may or may not be functional with your permissions system.)
                // -->

                // Permission in certain world
            else if (attribute.getAttribute(2).startsWith("world"))
                return new Element(Depends.permissions.playerInGroup(attribute.getContext(2), getName(), group)) // TODO: Vault UUID support?
                        .getAttribute(attribute.fulfill(2));

            // Permission in current world
            else if (isOnline())
                return new Element(Depends.permissions.playerInGroup(getPlayerEntity(), group))
                        .getAttribute(attribute.fulfill(1));
        }

        // <--[tag]
        // @attribute <p@player.has_permission[permission.node]>
        // @returns Element(Boolean)
        // @description
        // returns whether the player has the specified node.
        // (Requires the player to be online)
        // -->
        if (attribute.startsWith("permission")
                || attribute.startsWith("has_permission")) {

            String permission = attribute.getContext(1);

            if (Depends.permissions == null) {
                if (!attribute.hasAlternative())
                    dB.echoError("No permission system loaded! Have you installed Vault and a compatible permissions plugin?");
                return null;
            }

            // <--[tag]
            // @attribute <p@player.has_permission[permission.node].global>
            // @returns Element(Boolean)
            // @description
            // returns whether the player has the specified node, regardless of world.
            // (Works with offline players)
            // (Note: this may or may not be functional with your permissions system.)
            // -->

            // Non-world specific permission
            if (attribute.getAttribute(2).startsWith("global"))
                return new Element(Depends.permissions.has((World) null, getName(), permission)) // TODO: Vault UUID support?
                        .getAttribute(attribute.fulfill(2));

                // <--[tag]
                // @attribute <p@player.has_permission[permission.node].world>
                // @returns Element(Boolean)
                // @description
                // returns whether the player has the specified node in regards to the
                // player's current world.
                // (Works with offline players)
                // (Note: This may or may not be functional with your permissions system.)
                // -->

                // Permission in certain world
            else if (attribute.getAttribute(2).startsWith("world"))
                return new Element(Depends.permissions.has(attribute.getContext(2), getName(), permission)) // TODO: Vault UUID support?
                        .getAttribute(attribute.fulfill(2));

            // Permission in current world
            else if (isOnline())
                return new Element(getPlayerEntity().hasPermission(permission))
                        .getAttribute(attribute.fulfill(1));
        }

        /////////////////////
        //   INVENTORY ATTRIBUTES
        /////////////////

        // <--[tag]
        // @attribute <p@player.inventory>
        // @returns dInventory
        // @description
        // returns a dInventory of the player's current inventory.
        // Works with offline players.
        // -->
        if (attribute.startsWith("inventory")) {
            return getInventory().getAttribute(attribute.fulfill(1));
        }

        // <--[tag]
        // @attribute <p@player.enderchest>
        // @returns dInventory
        // @description
        // Gets the player's enderchest inventory.
        // Works with offline players.
        // -->
        if (attribute.startsWith("enderchest"))
            return getEnderChest().getAttribute(attribute.fulfill(1));


        /////////////////////
        //   ONLINE ATTRIBUTES
        /////////////////

        // Player is required to be online after this point...
        if (!isOnline()) return new Element(identify()).getAttribute(attribute);

        // <--[tag]
        // @attribute <p@player.open_inventory>
        // @returns dInventory
        // @description
        // Gets the inventory the player currently has open. If the player has no open
        // inventory, this returns the player's inventory.
        // -->
        if (attribute.startsWith("open_inventory"))
            return new dInventory(getPlayerEntity().getOpenInventory().getTopInventory())
                    .getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.item_on_cursor>
        // @returns dItem
        // @description
        // returns a dItem that the player's cursor is on, if any. This includes
        // chest interfaces, inventories, and hotbars, etc.
        // -->
        if (attribute.startsWith("item_on_cursor"))
            return new dItem(getPlayerEntity().getItemOnCursor())
                    .getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.item_in_hand.slot>
        // @returns Element(Number)
        // @description
        // returns the slot location of the player's selected item.
        // -->
        if (attribute.startsWith("item_in_hand.slot")) {
            return new Element(getPlayerEntity().getInventory().getHeldItemSlot() + 1)
                    .getAttribute(attribute.fulfill(2));
        }


        /////////////////////
        //   CITIZENS ATTRIBUTES
        /////////////////

        // <--[tag]
        // @attribute <p@player.selected_npc>
        // @returns dNPC
        // @description
        // returns the dNPC that the player currently has selected with
        // '/npc select', null if no player selected.
        // -->
        if (attribute.startsWith("selected_npc")) {
            if (getPlayerEntity().hasMetadata("selected"))
                return getSelectedNPC()
                        .getAttribute(attribute.fulfill(1));
            else return null;
        }


        /////////////////////
        //   CONVERSION ATTRIBUTES
        /////////////////

        // <--[tag]
        // @attribute <p@player.entity>
        // @returns dEntity
        // @description
        // returns the dEntity object of the player.
        // (Note: This should never actually be needed. <p@player> is considered a valid dEntity by script commands.)
        // -->
        if (attribute.startsWith("entity") && !attribute.startsWith("entity_"))
            return new dEntity(getPlayerEntity())
                    .getAttribute(attribute.fulfill(1));


        /////////////////////
        //   IDENTIFICATION ATTRIBUTES
        /////////////////

        // <--[tag]
        // @attribute <p@player.ip>
        // @returns Element
        // @description
        // returns the player's IP address host name.
        // -->
        if (attribute.startsWith("ip") ||
                attribute.startsWith("host_name")) {
            attribute = attribute.fulfill(1);
            // <--[tag]
            // @attribute <p@player.ip.address>
            // @returns Element
            // @description
            // returns the player's IP address.
            // -->
            if (attribute.startsWith("address"))
                return new Element(getPlayerEntity().getAddress().toString())
                        .getAttribute(attribute.fulfill(1));

            return new Element(getPlayerEntity().getAddress().getHostName())
                    .getAttribute(attribute);
        }

        // <--[tag]
        // @attribute <p@player.name.display>
        // @returns Element
        // @description
        // returns the display name of the player, which may contain
        // prefixes and suffixes, colors, etc.
        // -->
        if (attribute.startsWith("name.display"))
            return new Element(getPlayerEntity().getDisplayName())
                    .getAttribute(attribute.fulfill(2));

        // <--[tag]
        // @attribute <p@player.name.list>
        // @returns Element
        // @description
        // returns the name of the player as shown in the player list.
        // -->
        if (attribute.startsWith("name.list"))
            return new Element(getPlayerEntity().getPlayerListName())
                    .getAttribute(attribute.fulfill(2));

        // <--[tag]
        // @attribute <p@player.name>
        // @returns Element
        // @description
        // returns the name of the player.
        // -->
        if (attribute.startsWith("name"))
            return new Element(getName()).getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.has_finished[<script>]>
        // @returns Element(Boolean)
        // @description
        // returns whether the player has finished the specified script.
        // -->
        if (attribute.startsWith("has_finished")) {
            dScript script = dScript.valueOf(attribute.getContext(1));
            if (script == null) return Element.FALSE.getAttribute(attribute.fulfill(1));

            return new Element(FinishCommand.getScriptCompletes(getName(), script.getName()) > 0)
                    .getAttribute(attribute.fulfill(1));
        }

        // <--[tag]
        // @attribute <p@player.has_failed[<script>]>
        // @returns Element(Boolean)
        // @description
        // returns whether the player has failed the specified script.
        // -->
        if (attribute.startsWith("has_failed")) {
            dScript script = dScript.valueOf(attribute.getContext(1));
            if (script == null) return Element.FALSE.getAttribute(attribute.fulfill(1));

            return new Element(FailCommand.getScriptFails(getName(), script.getName()) > 0)
                    .getAttribute(attribute.fulfill(1));
        }

        /////////////////////
        //   LOCATION ATTRIBUTES
        /////////////////

        // <--[tag]
        // @attribute <p@player.compass.target>
        // @returns dLocation
        // @description
        // returns the location of the player's compass target.
        // -->
        if (attribute.startsWith("compass_target"))
            return new dLocation(getPlayerEntity().getCompassTarget())
                    .getAttribute(attribute.fulfill(2));


        /////////////////////
        //   STATE ATTRIBUTES
        /////////////////

        // <--[tag]
        // @attribute <p@player.allowed_flight>
        // @returns Element(Boolean)
        // @description
        // returns whether the player is allowed to fly.
        // @mechanism dPlayer.can_fly
        // -->
        if (attribute.startsWith("allowed_flight"))
            return new Element(getPlayerEntity().getAllowFlight())
                    .getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.fly_speed>
        // @returns Element(Decimal)
        // @description
        // returns the speed the player can fly at.
        // -->
        if (attribute.startsWith("fly_speed"))
            return new Element(getPlayerEntity().getFlySpeed())
                    .getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.food_level.formatted>
        // @returns Element
        // @description
        // returns a 'formatted' value of the player's current food level.
        // May be 'starving', 'famished', 'parched, 'hungry' or 'healthy'.
        // -->
        if (attribute.startsWith("food_level.formatted")) {
            double maxHunger = getPlayerEntity().getMaxHealth();
            if (attribute.hasContext(2))
                maxHunger = attribute.getIntContext(2);
            if (getPlayerEntity().getFoodLevel() / maxHunger < .10)
                return new Element("starving").getAttribute(attribute.fulfill(2));
            else if (getPlayerEntity().getFoodLevel() / maxHunger < .40)
                return new Element("famished").getAttribute(attribute.fulfill(2));
            else if (getPlayerEntity().getFoodLevel() / maxHunger < .75)
                return new Element("parched").getAttribute(attribute.fulfill(2));
            else if (getPlayerEntity().getFoodLevel() / maxHunger < 1)
                return new Element("hungry").getAttribute(attribute.fulfill(2));

            else return new Element("healthy").getAttribute(attribute.fulfill(2));
        }

        // <--[tag]
        // @attribute <p@player.saturation>
        // @returns Element(Decimal)
        // @description
        // returns the current saturation of the player.
        // -->
        if (attribute.startsWith("saturation"))
            return new Element(getPlayerEntity().getSaturation())
                    .getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.food_level>
        // @returns Element(Number)
        // @description
        // returns the current food level of the player.
        // -->
        if (attribute.startsWith("food_level"))
            return new Element(getPlayerEntity().getFoodLevel())
                    .getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.oxygen.max>
        // @returns Element(Number)
        // @description
        // returns how much air the player can have.
        // -->
        if (attribute.startsWith("oxygen.max"))
            return new Element(getPlayerEntity().getMaximumAir())
                    .getAttribute(attribute.fulfill(2));

        // <--[tag]
        // @attribute <p@player.oxygen>
        // @returns Element(Number)
        // @description
        // returns how much air the player has.
        // -->
        if (attribute.startsWith("oxygen"))
            return new Element(getPlayerEntity().getRemainingAir())
                    .getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.gamemode>
        // @returns Element
        // @description
        // returns the name of the gamemode the player is currently set to.
        // -->
        if (attribute.startsWith("gamemode")) {
            attribute = attribute.fulfill(1);
            // <--[tag]
            // @attribute <p@player.gamemode.id>
            // @returns Element(Number)
            // @description
            // returns the gamemode ID of the player. 0 = survival, 1 = creative, 2 = adventure
            // -->
            if (attribute.startsWith("id"))
                return new Element(getPlayerEntity().getGameMode().getValue())
                        .getAttribute(attribute.fulfill(1));
            return new Element(getPlayerEntity().getGameMode().name())
                    .getAttribute(attribute);
        }

        // <--[tag]
        // @attribute <p@player.is_blocking>
        // @returns Element(Boolean)
        // @description
        // returns whether the player is currently blocking.
        // -->
        if (attribute.startsWith("is_blocking"))
            return new Element(getPlayerEntity().isBlocking())
                    .getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.is_flying>
        // @returns Element(Boolean)
        // @description
        // returns whether the player is currently flying.
        // -->
        if (attribute.startsWith("is_flying"))
            return new Element(getPlayerEntity().isFlying())
                    .getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.is_sleeping>
        // @returns Element(Boolean)
        // @description
        // returns whether the player is currently sleeping.
        // -->
        if (attribute.startsWith("is_sleeping"))
            return new Element(getPlayerEntity().isSleeping())
                    .getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.is_sneaking>
        // @returns Element(Boolean)
        // @description
        // returns whether the player is currently sneaking.
        // -->
        if (attribute.startsWith("is_sneaking"))
            return new Element(getPlayerEntity().isSneaking())
                    .getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.is_sprinting>
        // @returns Element(Boolean)
        // @description
        // returns whether the player is currently sprinting.
        // -->
        if (attribute.startsWith("is_sprinting"))
            return new Element(getPlayerEntity().isSprinting())
                    .getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.statistic[<statistic>]>
        // @returns Element(Number)
        // @description
        // Returns the player's current value for the specified statistic.
        // -->
        if (attribute.startsWith("statistic")) {
            Statistic statistic = Statistic.valueOf(attribute.getContext(1).toUpperCase());

            // <--[tag]
            // @attribute <p@player.statistic[<statistic>].qualifier[<material>/<entity>]>
            // @returns Element(Number)
            // @description
            // Returns the player's current value for the specified statistic, with the
            // specified qualifier, which can be either an entity or material.
            // -->
            if (attribute.getAttribute(2).startsWith("qualifier")) {
                if (statistic == null) return null;
                dObject obj = ObjectFetcher.pickObjectFor(attribute.getContext(2));
                if (obj instanceof dMaterial)
                    return new Element(getPlayerEntity().getStatistic(statistic, ((dMaterial) obj).getMaterial()))
                            .getAttribute(attribute.fulfill(2));
                else if (obj instanceof dEntity)
                    return new Element(getPlayerEntity().getStatistic(statistic, ((dEntity) obj).getEntityType()))
                            .getAttribute(attribute.fulfill(2));
                else
                    return null;
            }

            if (statistic == null) return null;
            return new Element(getPlayerEntity().getStatistic(statistic)).getAttribute(attribute.fulfill(1));
        }

        // <--[tag]
        // @attribute <p@player.time_asleep>
        // @returns Duration
        // @description
        // returns the time the player has been asleep.
        // -->
        if (attribute.startsWith("time_asleep"))
            return new Duration(getPlayerEntity().getSleepTicks() / 20)
                    .getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.time>
        // @returns Duration
        // @description
        // returns the time the player is currently experiencing. This time could differ from
        // the time that the rest of the world is currently experiencing if a 'time' or 'freeze_time'
        // mechanism is being used on the player.
        // -->
        if (attribute.startsWith("time"))
            return new Element(getPlayerEntity().getPlayerTime())
                    .getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.walk_speed>
        // @returns Element(Decimal)
        // @description
        // returns the speed the player can walk at.
        // -->
        if (attribute.startsWith("walk_speed"))
            return new Element(getPlayerEntity().getWalkSpeed())
                    .getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.weather>
        // @returns Element
        // @description
        // returns the type of weather the player is experiencing. This will be different
        // from the weather currently in the world that the player is residing in if
        // the weather is currently being forced onto the player.
        // Returns null if the player does not currently have any forced weather.
        // -->
        if (attribute.startsWith("weather")) {
            if (getPlayerEntity().getPlayerWeather() != null)
                return new Element(getPlayerEntity().getPlayerWeather().name())
                        .getAttribute(attribute.fulfill(1));
            else
                return null;
        }

        // <--[tag]
        // @attribute <p@player.xp.level>
        // @returns Element(Number)
        // @description
        // returns the number of XP levels the player has.
        // -->
        if (attribute.startsWith("xp.level"))
            return new Element(getPlayerEntity().getLevel())
                    .getAttribute(attribute.fulfill(2));

        // <--[tag]
        // @attribute <p@player.xp.to_next_level>
        // @returns Element(Number)
        // @description
        // returns the amount of XP needed to get to the next level.
        // -->
        if (attribute.startsWith("xp.to_next_level"))
            return new Element(getPlayerEntity().getExpToLevel())
                    .getAttribute(attribute.fulfill(2));

        // <--[tag]
        // @attribute <p@player.xp.total>
        // @returns Element(Number)
        // @description
        // returns the total amount of experience points.
        // -->
        if (attribute.startsWith("xp.total"))
            return new Element(getPlayerEntity().getTotalExperience())
                    .getAttribute(attribute.fulfill(2));

        // <--[tag]
        // @attribute <p@player.xp>
        // @returns Element(Decimal)
        // @description
        // returns the percentage of experience points to the next level.
        // -->
        if (attribute.startsWith("xp"))
            return new Element(getPlayerEntity().getExp() * 100)
                    .getAttribute(attribute.fulfill(1));

        // <--[tag]
        // @attribute <p@player.type>
        // @returns Element
        // @description
        // Always returns 'Player' for dPlayer objects. All objects fetchable by the Object Fetcher will return the
        // type of object that is fulfilling this attribute.
        // -->
        if (attribute.startsWith("type")) {
            return new Element("Player").getAttribute(attribute.fulfill(1));
        }

        // Iterate through this object's properties' attributes
        for (Property property : PropertyParser.getProperties(this)) {
            String returned = property.getAttribute(attribute);
            if (returned != null) return returned;
        }

        return new dEntity(getPlayerEntity()).getAttribute(attribute);
    }


    public void applyProperty(Mechanism mechanism) {
        dB.echoError("Cannot apply properties to a player!");
    }

    @Override
    public void adjust(Mechanism mechanism) {

        Element value = mechanism.getValue();

        // <--[mechanism]
        // @object dPlayer
        // @name level
        // @input Element(Number)
        // @description
        // Sets the level on the player. Does not affect the current progression
        // of experience towards next level.
        // @tags
        // <player.xp.level>
        // -->
        if (mechanism.matches("level") && mechanism.requireInteger()) {
            setLevel(value.asInt());
        }

        // <--[mechanism]
        // @object dPlayer
        // @name item_slot
        // @input Element(Number)
        // @description
        // Sets the inventory slot that the player has selected.
        // @tags
        // <player.item_in_hand.slot>
        // -->
        if (mechanism.matches("item_slot") && mechanism.requireInteger()) {
            getPlayerEntity().getInventory().setHeldItemSlot(mechanism.getValue().asInt() - 1);
        }

        // <--[mechanism]
        // @object dPlayer
        // @name award_achievement
        // @input Element
        // @description
        // Awards an achievement to the player. Valid achievements:
        // ACQUIRE_IRON, BAKE_CAKE, BOOKCASE, BREED_COW, BREW_POTION, BUILD_BETTER_PICKAXE,
        // BUILD_FURNACE, BUILD_HOE, BUILD_PICKAXE, BUILD_SWORD, BUILD_WORKBENCH, COOK_FISH,
        // DIAMONDS_TO_YOU, ENCHANTMENTS, END_PORTAL, EXPLORE_ALL_BIOMES, FLY_PIG, FULL_BEACON,
        // GET_BLAZE_ROD, GET_DIAMONDS, GHAST_RETURN, KILL_COW, KILL_ENEMY, KILL_WITHER,
        // MAKE_BREAD, MINE_WOOD, NETHER_PORTAL, ON_A_RAIL, OPEN_INVENTORY, OVERKILL,
        // SNIPE_SKELETON, SPAWN_WITHER, THE_END
        // @tags
        // None
        // -->
        // TODO: Player achievement/statistics tags.
        if (mechanism.matches("award_achievement")&& mechanism.requireEnum(false, Achievement.values())) {
            getPlayerEntity().awardAchievement(Achievement.valueOf(value.asString().toUpperCase()));
        }

        // <--[mechanism]
        // @object dPlayer
        // @name health_scale
        // @input Element(Decimal)
        // @description
        // Sets the 'health scale' on the Player. Each heart equals '2'. The standard health scale is
        // 20, so for example, indicating a value of 40 will display double the amount of hearts
        // standard.
        // Player relogging will reset this mechanism.
        // @tags
        // <player.health.scale>
        // -->
        if (mechanism.matches("health_scale") && mechanism.requireDouble()) {
            getPlayerEntity().setHealthScale(value.asDouble());
        }

        // <--[mechanism]
        // @object dPlayer
        // @name scale_health
        // @input Element(Boolean)
        // @description
        // Enables or disables the health scale value. Disabling will result in the standard
        // amount of hearts being shown.
        // @tags
        // <player.health.is_scaled>
        // -->
        if (mechanism.matches("scale_health") && mechanism.requireBoolean()) {
            getPlayerEntity().setHealthScaled(value.asBoolean());
        }

        // <--[mechanism]
        // @object dPlayer
        // @name resource_pack
        // @input Element
        // @description
        // Sets the current resource pack by specifying a valid URL to a resource pack.
        // @tags
        // None
        // -->
        if (mechanism.matches("resource_pack") || mechanism.matches("texture_pack")) {
            getPlayerEntity().setResourcePack(value.asString());
        }

        // <--[mechanism]
        // @object dPlayer
        // @name saturation
        // @input Element(Decimal)
        // @description
        // Sets the current food saturation level of a player.
        // @tags
        // <player.saturation>
        // -->
        if (mechanism.matches("saturation") && mechanism.requireFloat()) {
            getPlayerEntity().setSaturation(value.asFloat());
        }

        // <--[mechanism]
        // @object dPlayer
        // @name send_map
        // @input Element(Number)
        // @description
        // Forces a player to receive the entirety of the specified map ID instantly.
        // @tags
        // None
        // -->
        if (mechanism.matches("send_map") && mechanism.requireInteger()) {
            MapView map = Bukkit.getServer().getMap((short) value.asInt());
            if (map != null)
                getPlayerEntity().sendMap(map);
            else
                dB.echoError("No map found for ID " + value.asInt() + "!");
        }

        // <--[mechanism]
        // @object dPlayer
        // @name food_level
        // @input Element(Number)
        // @description
        // Sets the current food level of a player. Typically, '20' is full.
        // @tags
        // <player.food_level>
        // -->
        if (mechanism.matches("food_level") && mechanism.requireInteger()) {
            getPlayerEntity().setFoodLevel(value.asInt());
        }

        // <--[mechanism]
        // @object dPlayer
        // @name bed_spawn_location
        // @input dLocation
        // @description
        // Sets the bed location that the player respawns at.
        // @tags
        // <player.bed_spawn>
        // -->
        if (mechanism.matches("bed_spawn_location") && mechanism.requireObject(dLocation.class)) {
            setBedSpawnLocation(value.asType(dLocation.class));
        }

        // <--[mechanism]
        // @object dPlayer
        // @name can_fly
        // @input Element(Boolean)
        // @description
        // Sets whether the player is allowed to fly.
        // @tags
        // <player.allowed_flight>
        // -->
        if (mechanism.matches("can_fly") && mechanism.requireBoolean()) {
            getPlayerEntity().setAllowFlight(value.asBoolean());
        }

        // <--[mechanism]
        // @object dPlayer
        // @name fly_speed
        // @input Element(Decimal)
        // @description
        // Sets the fly speed of the player. Valid range is 0.0 to 1.0
        // @tags
        // <player.fly_speed>
        // -->
        if (mechanism.matches("fly_speed") && mechanism.requireFloat()) {
            setFlySpeed(value.asFloat());
        }

        // <--[mechanism]
        // @object dPlayer
        // @name gamemode
        // @input Element
        // @description
        // Sets the game mode of the player.
        // Valid gamemodes are survival, creative, and adventure.
        // @tags
        // <p@player.gamemode>
        // <p@player.gamemode.id>
        // -->
        if (mechanism.matches("gamemode") && mechanism.requireEnum(false, GameMode.values())) {
            setGameMode(GameMode.valueOf(value.asString().toUpperCase()));
        }

        // <--[mechanism]
        // @object dPlayer
        // @name kick
        // @input Element
        // @description
        // Kicks the player, with the specified message.
        // @tags
        // None
        // -->
        if (mechanism.matches("kick")) {
            getPlayerEntity().kickPlayer(mechanism.getValue().asString());
        }

        // <--[mechanism]
        // @object dPlayer
        // @name weather
        // @input Element
        // @description
        // Sets the weather condition for the player. This does NOT affect the weather
        // in the world, and will block any world weather changes until the 'reset_weather'
        // mechanism is used. Valid weather: CLEAR, DOWNFALL
        // @tags
        // <player.weather>
        // -->
        if (mechanism.matches("weather") && mechanism.requireEnum(false, WeatherType.values())) {
            getPlayerEntity().setPlayerWeather(WeatherType.valueOf(value.asString().toUpperCase()));
        }

        // <--[mechanism]
        // @object dPlayer
        // @name reset_weather
        // @input None
        // @description
        // Resets the weather on the Player to the conditions currently taking place in the Player's
        // current world.
        // @tags
        // <player.weather>
        // -->
        if (mechanism.matches("reset_weather")) {
            getPlayerEntity().resetPlayerWeather();
        }

        // <--[mechanism]
        // @object dPlayer
        // @name player_list_name
        // @input Element
        // @description
        // Sets the entry that is shown in the 'player list' that is shown when pressing tab.
        // @tags
        // <player.name.list>
        // -->
        if (mechanism.matches("player_list_name")) {
            getPlayerEntity().setPlayerListName(value.asString());
        }

        // <--[mechanism]
        // @object dPlayer
        // @name display_name
        // @input Element
        // @description
        // Sets the name displayed for the player when chatting.
        // @tags
        // <player.name.display>
        // -->
        if (mechanism.matches("display_name")) {
            getPlayerEntity().setDisplayName(value.asString());
            return;
        }

        // <--[mechanism]
        // @object dPlayer
        // @name show_workbench
        // @input dLocation
        // @description
        // Shows the player a workbench GUI corresponding to a given location.
        // @tags
        // None
        // -->
        if (mechanism.matches("show_workbench") && mechanism.requireObject(dLocation.class)) {
            getPlayerEntity().openWorkbench(mechanism.getValue().asType(dLocation.class), true);
            return;
        }

        // <--[mechanism]
        // @object dPlayer
        // @name location
        // @input dLocation
        // @description
        // If the player is online, teleports the player to a given location.
        // Otherwise, sets the player's next spawn location.
        // @tags
        // <player.location>
        // -->
        if (mechanism.matches("location") && mechanism.requireObject(dLocation.class)) {
            setLocation(value.asType(dLocation.class));
        }

        // <--[mechanism]
        // @object dPlayer
        // @name time
        // @input Element(Number)
        // @description
        // Sets the time of day the Player is currently experiencing. Setting this will cause the
        // player to have a different time than other Players in the world are experiencing though
        // time will continue to progress. Using the 'reset_time' mechanism, or relogging your player
        // will reset this mechanism to match the world's current time. Valid range is 0-28000
        // @tags
        // <player.time>
        // -->
        if (mechanism.matches("time") && mechanism.requireInteger()) {
            getPlayerEntity().setPlayerTime(value.asInt(), true);
        }

        // <--[mechanism]
        // @object dPlayer
        // @name freeze_time
        // @input Element(Number)
        // @description
        // Sets the time of day the Player is currently experiencing and freezes it there. Note:
        // there is a small 'twitch effect' when looking at the sky when time is frozen.
        // Setting this will cause the player to have a different time than other Players in
        // the world are experiencing. Using the 'reset_time' mechanism, or relogging your player
        // will reset this mechanism to match the world's current time. Valid range is 0-28000
        // @tags
        // <player.time>
        // -->
        if (mechanism.matches("freeze_time")) {
            if (mechanism.requireInteger("Invalid integer specified. Assuming current world time."))
                getPlayerEntity().setPlayerTime(value.asInt(), false);
            else
                getPlayerEntity().setPlayerTime(getPlayerEntity().getWorld().getTime(), false);
        }

        // <--[mechanism]
        // @object dPlayer
        // @name reset_time
        // @input None
        // @description
        // Resets any altered time that has been applied to this player. Using this will make
        // the Player's time match the world's current time.
        // @tags
        // <player.time>
        // -->
        if (mechanism.matches("reset_time")) {
            getPlayerEntity().resetPlayerTime();
        }

        // <--[mechanism]
        // @object dPlayer
        // @name walk_speed
        // @input Element(Decimal)
        // @description
        // Sets the walk speed of the player. The standard value is '0.2'. Valid range is 0.0 to 1.0
        // @tags
        // <player.walk_speed>
        // -->
        if (mechanism.matches("walk_speed") && mechanism.requireFloat()) {
            getPlayerEntity().setWalkSpeed(value.asFloat());
        }

        // <--[mechanism]
        // @object dPlayer
        // @name show_entity
        // @input dEntity
        // @description
        // Shows the player an entity. (Must be a player or player NPC).
        // -->
        if (mechanism.matches("show_entity") && mechanism.requireObject(dEntity.class)) {
            getPlayerEntity().showPlayer((Player)value.asType(dEntity.class).getLivingEntity());
        }

        // <--[mechanism]
        // @object dPlayer
        // @name hide_entity
        // @input dEntity
        // @description
        // Hides an entity from the player. (Must be a player or player NPC).
        // -->
        if (mechanism.matches("hide_entity") && mechanism.requireObject(dEntity.class)) {
            getPlayerEntity().hidePlayer((Player)value.asType(dEntity.class).getLivingEntity());
        }

        // <--[mechanism]
        // @object dPlayer
        // @name show_boss_bar
        // @input (Element(Number)|)Element
        // @description
        // Shows the player a boss health bar with the specified text as a name.
        // Use with no input value to remove the bar.
        // Optionally, precede the text with a number indicating the health value. EG:
        // - adjust <player> show_boss_bar:Hello
        // - adjust <player> show_boss_bar:100|Hello
        // @tags
        // None
        // -->
        if (mechanism.matches("show_boss_bar")) {
            if (value.asString().length() > 0) {
                String[] split = value.asString().split("[\\|" + dList.internal_escape + "]", 2);
                if (split.length == 2 && new Element(split[0]).isInt()) {
                    BossHealthBar.displayTextBar(split[1], getPlayerEntity(), new Element(split[0]).asInt());
                }
                else {
                    BossHealthBar.displayTextBar(value.asString(), getPlayerEntity(), 200);
                }
            }
            else {
                BossHealthBar.removeTextBar(getPlayerEntity());
            }
        }

        // <--[mechanism]
        // @object dPlayer
        // @name fake_experience
        // @input Element(Decimal)(|Element(Number))
        // @description
        // Shows the player a fake experience bar, with a number between 0.0 and 1.0
        // to specify how far along the bar is.
        // Use with no input value to reset to the player's normal experience.
        // Optionally, you can specify a fake experience level.
        // - adjust <player> fake_experience:0.5|5
        // @tags
        // None
        // -->
        if (mechanism.matches("fake_experience")) {
            if (value.asString().length() > 0) {
                String[] split = value.asString().split("[\\|" + dList.internal_escape + "]", 2);
                if (split.length > 0 && new Element(split[0]).isFloat()) {
                    if (split.length > 1 && new Element(split[1]).isInt()) {
                        PlayerBars.showExperience(getPlayerEntity(),
                                new Element(split[0]).asFloat(), new Element(split[1]).asInt());
                    }
                    else
                        PlayerBars.showExperience(getPlayerEntity(),
                                new Element(split[0]).asFloat(), getPlayerEntity().getLevel());
                }
                else {
                    dB.echoError("'" + split[0] + "' is not a valid decimal number!");
                }
            }
            else {
                PlayerBars.resetExperience(getPlayerEntity());
            }
        }

        // <--[mechanism]
        // @object dPlayer
        // @name fake_health
        // @input Element(Decimal)(|Element(Number)(|Element(Decimal)))
        // @description
        // Shows the player a fake health bar, with a number between 0 and 20,
        // where 1 is half of a heart.
        // Use with no input value to reset to the player's normal health.
        // Optionally, you can specify a fake food level, between 0 and 20.
        // You can also optionally specify a food saturation level between 0 and 10.
        // - adjust <player> fake_health:1
        // - adjust <player> fake_health:10|15
        // - adjust <player> fake_health:<player.health>|3|0
        // @tags
        // None
        // -->
        if (mechanism.matches("fake_health")) {
            if (value.asString().length() > 0) {
                String[] split = value.asString().split("[\\|" + dList.internal_escape + "]", 3);
                if (split.length > 0 && new Element(split[0]).isFloat()) {
                    if (split.length > 1 && new Element(split[1]).isInt()) {
                        if (split.length > 2 && new Element(split[2]).isFloat())
                            PlayerBars.showHealth(getPlayerEntity(), new Element(split[0]).asFloat(),
                                    new Element(split[1]).asInt(), new Element(split[2]).asFloat());
                        else
                            PlayerBars.showHealth(getPlayerEntity(), new Element(split[0]).asFloat(),
                                    new Element(split[1]).asInt(), getPlayerEntity().getSaturation());
                    }
                    else {
                        PlayerBars.showHealth(getPlayerEntity(), new Element(split[0]).asFloat(),
                                getPlayerEntity().getFoodLevel(), getPlayerEntity().getSaturation());
                    }
                }
                else {
                    dB.echoError("'" + split[0] + "' is not a valid decimal number!");
                }
            }
            else {
                PlayerBars.resetHealth(getPlayerEntity());
            }
        }

        // <--[mechanism]
        // @object dPlayer
        // @name fake_equipment
        // @input dEntity(|Element|dItem)
        // @description
        // Shows the player fake equipment on the specified living entity, which has
        // no real non-visual effects, in the form Entity|Slot|Item, where the slot
        // can be one of the following: HAND, BOOTS, LEGS, CHEST, HEAD
        // Optionally, exclude the slot and item to stop showing the fake equipment,
        // if any, on the specified entity.
        // - adjust <player> fake_equipment:e@123|chest|i@diamond_chestplate
        // - adjust <player> fake_equipment:<player>|head|i@jack_o_lantern
        // -->
        if (mechanism.matches("fake_equipment")) {
            if (value.asString().length() > 0) {
                String[] split = value.asString().split("[\\|" + dList.internal_escape + "]", 3);
                if (split.length > 0 && new Element(split[0]).matchesType(dEntity.class)) {
                    if (split.length > 1 && new Element(split[1]).matchesEnum(EntityEquipment.EquipmentSlots.values())) {
                        if (split.length > 2 && new Element(split[2]).matchesType(dItem.class)) {
                            EntityEquipment.showEquipment(getPlayerEntity(),
                                    new Element(split[0]).asType(dEntity.class).getLivingEntity(),
                                    EntityEquipment.EquipmentSlots.valueOf(new Element(split[1]).asString().toUpperCase()),
                                    new Element(split[2]).asType(dItem.class).getItemStack());
                        }
                        else if (split.length > 2) {
                            dB.echoError("'" + split[2] + "' is not a valid dItem!");
                        }
                    }
                    else if (split.length > 1) {
                        dB.echoError("'" + split[1] + "' is not a valid slot; must be HAND, BOOTS, LEGS, CHEST, or HEAD!");
                    }
                    else {
                        EntityEquipment.resetEquipment(getPlayerEntity(),
                                new Element(split[0]).asType(dEntity.class).getLivingEntity());
                    }
                }
                else {
                    dB.echoError("'" + split[0] + "' is not a valid dEntity!");
                }
            }
        }

        // <--[mechanism]
        // @object dPlayer
        // @name item_message
        // @input Element
        // @description
        // Shows the player an item message as if the item they are carrying had
        // changed names to the specified Element.
        // -->
        if (mechanism.matches("item_message")) {
            ItemChangeMessage.sendMessage(getPlayerEntity(), value.asString());
        }

        // Iterate through this object's properties' mechanisms
        for (Property property : PropertyParser.getProperties(this)) {
            property.adjust(mechanism);
            if (mechanism.fulfilled())
                break;
        }

        // Pass along to dEntity mechanism handler if not already handled.
        if (!mechanism.fulfilled()) {
            Adjustable entity = new dEntity(getPlayerEntity());
            entity.adjust(mechanism);
        }

    }
}
TOP

Related Classes of net.aufdemrand.denizen.objects.dPlayer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.