Search in sources :

Example 1 with AdvancementProgress

use of org.bukkit.advancement.AdvancementProgress in project Denizen-For-Bukkit by DenizenScript.

the class PlayerTag method adjust.

@Override
public void adjust(Mechanism mechanism) {
    // -->
    if (mechanism.matches("noclip") && mechanism.hasValue()) {
        if (mechanism.getValue().asBoolean()) {
            DenizenPacketHandler.forceNoclip.add(getUUID());
        } else {
            DenizenPacketHandler.forceNoclip.remove(getUUID());
        }
    }
    // -->
    if (mechanism.matches("respawn")) {
        NMSHandler.getPacketHelper().respawn(getPlayerEntity());
    }
    // -->
    if (mechanism.matches("vision")) {
        if (mechanism.hasValue() && mechanism.requireEnum(EntityType.class)) {
            NMSHandler.getPacketHelper().setVision(getPlayerEntity(), EntityType.valueOf(mechanism.getValue().asString().toUpperCase()));
        } else {
            NMSHandler.getPacketHelper().forceSpectate(getPlayerEntity(), getPlayerEntity());
        }
    }
    // -->
    if (mechanism.matches("level") && mechanism.requireInteger()) {
        setLevel(mechanism.getValue().asInt());
    }
    // -->
    if (mechanism.matches("item_slot") && mechanism.requireInteger()) {
        if (isOnline()) {
            getPlayerEntity().getInventory().setHeldItemSlot(mechanism.getValue().asInt() - 1);
        } else {
            getNBTEditor().setItemInHand(mechanism.getValue().asInt() - 1);
        }
    }
    // -->
    if (mechanism.matches("window_property")) {
        String[] split = mechanism.getValue().asString().split(",", 2);
        if (split.length != 2) {
            Debug.echoError("Invalid input! Must be in the form PROPERTY,VALUE");
        } else {
            try {
                getPlayerEntity().setWindowProperty(InventoryView.Property.valueOf(split[0].toUpperCase()), Integer.parseInt(split[1]));
            } catch (NumberFormatException e) {
                Debug.echoError("Input value must be a number!");
            } catch (IllegalArgumentException e) {
                Debug.echoError("Must specify a valid window property!");
            }
        }
    }
    // -->
    if (mechanism.matches("item_on_cursor") && mechanism.requireObject(ItemTag.class)) {
        getPlayerEntity().setItemOnCursor(mechanism.valueAsType(ItemTag.class).getItemStack());
    }
    // -->
    if (mechanism.matches("award_advancement")) {
        Advancement adv = AdvancementHelper.getAdvancement(mechanism.getValue().asString());
        if (adv == null) {
            if (mechanism.shouldDebug()) {
                Debug.echoError("Advancement '" + mechanism.getValue().asString() + "' does not exist.");
            }
            return;
        }
        AdvancementProgress prog = getPlayerEntity().getAdvancementProgress(adv);
        for (String criteria : prog.getRemainingCriteria()) {
            prog.awardCriteria(criteria);
        }
    }
    // -->
    if (mechanism.matches("revoke_advancement")) {
        Advancement adv = AdvancementHelper.getAdvancement(mechanism.getValue().asString());
        if (adv == null) {
            if (mechanism.shouldDebug()) {
                Debug.echoError("Advancement '" + mechanism.getValue().asString() + "' does not exist.");
            }
            return;
        }
        AdvancementProgress prog = getPlayerEntity().getAdvancementProgress(adv);
        for (String criteria : prog.getAwardedCriteria()) {
            prog.revokeCriteria(criteria);
        }
    }
    // -->
    if (mechanism.matches("fake_absorption_health") && mechanism.requireFloat()) {
        NMSHandler.getPacketHelper().setFakeAbsorption(getPlayerEntity(), mechanism.getValue().asFloat());
    }
    // -->
    if (mechanism.matches("health_scale") && mechanism.requireDouble()) {
        getPlayerEntity().setHealthScale(mechanism.getValue().asDouble());
    }
    // -->
    if (mechanism.matches("scale_health") && mechanism.requireBoolean()) {
        getPlayerEntity().setHealthScaled(mechanism.getValue().asBoolean());
    }
    // Allow offline editing of health values
    if (mechanism.matches("max_health") && mechanism.requireDouble()) {
        setMaxHealth(mechanism.getValue().asDouble());
    }
    if (mechanism.matches("health") && mechanism.requireDouble()) {
        setHealth(mechanism.getValue().asDouble());
    }
    // -->
    if (mechanism.matches("resource_pack") || mechanism.matches("texture_pack")) {
        Deprecations.playerResourcePackMech.warn(mechanism.context);
        String pack = mechanism.getValue().asString();
        int pipe = pack.indexOf('|');
        if (pipe > 0) {
            String hash = pack.substring(pipe + 1);
            pack = pack.substring(0, pipe);
            if (hash.length() != 40) {
                Debug.echoError("Invalid resource_pack hash. Should be 40 characters of hexadecimal data.");
                return;
            }
            byte[] hashData = new byte[20];
            for (int i = 0; i < 20; i++) {
                hashData[i] = (byte) Integer.parseInt(hash.substring(i * 2, i * 2 + 2), 16);
            }
            getPlayerEntity().setResourcePack(pack, hashData);
        } else {
            getPlayerEntity().setResourcePack(pack);
        }
    }
    // -->
    if (mechanism.matches("saturation") && mechanism.requireFloat()) {
        if (isOnline()) {
            getPlayerEntity().setSaturation(mechanism.getValue().asFloat());
        } else {
            getNBTEditor().setSaturation(mechanism.getValue().asFloat());
        }
    }
    // -->
    if (mechanism.matches("send_map") && mechanism.requireInteger()) {
        MapView map = Bukkit.getServer().getMap((short) mechanism.getValue().asInt());
        if (map != null) {
            getPlayerEntity().sendMap(map);
        } else {
            Debug.echoError("No map found for ID " + mechanism.getValue().asInt() + "!");
        }
    }
    // -->
    if (mechanism.matches("food_level") && mechanism.requireInteger()) {
        setFoodLevel(mechanism.getValue().asInt());
    }
    // -->
    if (mechanism.matches("bed_spawn_location") && mechanism.requireObject(LocationTag.class)) {
        setBedSpawnLocation(mechanism.valueAsType(LocationTag.class));
    }
    // -->
    if (mechanism.matches("spawn_forced") && mechanism.requireBoolean()) {
        if (isOnline()) {
            NMSHandler.getPlayerHelper().setSpawnForced(getPlayerEntity(), mechanism.getValue().asBoolean());
        } else {
            ImprovedOfflinePlayer editor = getNBTEditor();
            editor.setBedSpawnLocation(editor.getBedSpawnLocation(), mechanism.getValue().asBoolean());
        }
    }
    // -->
    if (mechanism.matches("can_fly") && mechanism.requireBoolean()) {
        if (isOnline()) {
            getPlayerEntity().setAllowFlight(mechanism.getValue().asBoolean());
        } else {
            getNBTEditor().setAllowFlight(mechanism.getValue().asBoolean());
        }
    }
    // -->
    if (mechanism.matches("fly_speed") && mechanism.requireFloat()) {
        setFlySpeed(mechanism.getValue().asFloat());
    }
    // -->
    if (mechanism.matches("flying") && mechanism.requireBoolean()) {
        getPlayerEntity().setFlying(mechanism.getValue().asBoolean());
    }
    // -->
    if (mechanism.matches("sprinting") && mechanism.requireBoolean()) {
        getPlayerEntity().setSprinting(mechanism.getValue().asBoolean());
    }
    // -->
    if (mechanism.matches("gamemode") && mechanism.requireEnum(GameMode.class)) {
        setGameMode(GameMode.valueOf(mechanism.getValue().asString().toUpperCase()));
    }
    if (mechanism.matches("kick")) {
        Deprecations.oldKickMech.warn(mechanism.context);
        getPlayerEntity().kickPlayer(mechanism.getValue().asString());
    }
    if (mechanism.matches("weather") && mechanism.requireEnum(WeatherType.class)) {
        Deprecations.oldWeatherMech.warn(mechanism.context);
        getPlayerEntity().setPlayerWeather(WeatherType.valueOf(mechanism.getValue().asString().toUpperCase()));
    }
    if (mechanism.matches("reset_weather")) {
        Deprecations.oldWeatherMech.warn(mechanism.context);
        getPlayerEntity().resetPlayerWeather();
    }
    // -->
    if (mechanism.matches("player_list_name")) {
        AdvancedTextImpl.instance.setPlayerListName(getPlayerEntity(), mechanism.getValue().asString());
    }
    // -->
    if (mechanism.matches("display_name")) {
        getPlayerEntity().setDisplayName(mechanism.getValue().asString());
        return;
    }
    // -->
    if (mechanism.matches("show_workbench") && mechanism.requireObject(LocationTag.class)) {
        getPlayerEntity().openWorkbench(mechanism.valueAsType(LocationTag.class), true);
        return;
    }
    // -->
    if (mechanism.matches("location") && mechanism.requireObject(LocationTag.class)) {
        setLocation(mechanism.valueAsType(LocationTag.class));
    }
    if (mechanism.matches("time") && mechanism.requireInteger()) {
        Deprecations.oldTimeMech.warn(mechanism.context);
        getPlayerEntity().setPlayerTime(mechanism.getValue().asInt(), true);
    }
    if (mechanism.matches("freeze_time")) {
        Deprecations.oldTimeMech.warn(mechanism.context);
        if (mechanism.requireInteger("Invalid integer specified. Assuming current world time.")) {
            getPlayerEntity().setPlayerTime(mechanism.getValue().asInt(), false);
        } else {
            getPlayerEntity().setPlayerTime(getPlayerEntity().getWorld().getTime(), false);
        }
    }
    if (mechanism.matches("reset_time")) {
        Deprecations.oldTimeMech.warn(mechanism.context);
        getPlayerEntity().resetPlayerTime();
    }
    // -->
    if (mechanism.matches("walk_speed") && mechanism.requireFloat()) {
        if (isOnline()) {
            getPlayerEntity().setWalkSpeed(mechanism.getValue().asFloat());
        } else {
            getNBTEditor().setWalkSpeed(mechanism.getValue().asFloat());
        }
    }
    // -->
    if (mechanism.matches("exhaustion") && mechanism.requireFloat()) {
        if (isOnline()) {
            getPlayerEntity().setExhaustion(mechanism.getValue().asFloat());
        } else {
            getNBTEditor().setExhaustion(mechanism.getValue().asFloat());
        }
    }
    // -->
    if (mechanism.matches("show_entity") && mechanism.requireObject(EntityTag.class)) {
        HideEntitiesHelper.unhideEntity(getPlayerEntity(), mechanism.valueAsType(EntityTag.class).getBukkitEntity());
    }
    // -->
    if (mechanism.matches("hide_entity")) {
        if (!mechanism.getValue().asString().isEmpty()) {
            ListTag split = mechanism.valueAsType(ListTag.class);
            if (split.size() > 0 && new ElementTag(split.get(0)).matchesType(EntityTag.class)) {
                EntityTag entity = EntityTag.valueOf(split.get(0), mechanism.context);
                if (!entity.isSpawnedOrValidForTag()) {
                    Debug.echoError("Can't hide the unspawned entity '" + split.get(0) + "'!");
                } else {
                    HideEntitiesHelper.hideEntity(getPlayerEntity(), entity.getBukkitEntity());
                }
            } else {
                Debug.echoError("'" + split.get(0) + "' is not a valid entity!");
            }
        } else {
            Debug.echoError("Must specify an entity to hide!");
        }
    }
    // -->
    if (mechanism.matches("hide_entities") && mechanism.hasValue()) {
        HideEntitiesHelper.PlayerHideMap map = HideEntitiesHelper.getPlayerMapFor(getUUID());
        String hideMe = mechanism.getValue().asString();
        map.matchersHidden.add(hideMe);
        if (isOnline()) {
            for (Entity ent : getPlayerEntity().getWorld().getEntities()) {
                if (BukkitScriptEvent.tryEntity(new EntityTag(ent), hideMe) && map.shouldHide(ent)) {
                    NMSHandler.getEntityHelper().sendHidePacket(getPlayerEntity(), ent);
                }
            }
        }
    }
    // -->
    if (mechanism.matches("unhide_entities") && mechanism.hasValue()) {
        HideEntitiesHelper.PlayerHideMap map = HideEntitiesHelper.getPlayerMapFor(getUUID());
        String unhideMe = mechanism.getValue().asString();
        map.matchersHidden.remove(unhideMe);
        if (map.matchersHidden.isEmpty() && map.entitiesHidden.isEmpty() && map.overridinglyShow.isEmpty()) {
            HideEntitiesHelper.playerHides.remove(getUUID());
        }
        if (isOnline()) {
            for (Entity ent : getPlayerEntity().getWorld().getEntities()) {
                if (BukkitScriptEvent.tryEntity(new EntityTag(ent), unhideMe) && !map.shouldHide(ent)) {
                    NMSHandler.getEntityHelper().sendShowPacket(getPlayerEntity(), ent);
                }
            }
        }
    }
    if (mechanism.matches("show_boss_bar")) {
        Deprecations.oldBossBarMech.warn(mechanism.context);
        if (!mechanism.getValue().asString().isEmpty()) {
            String[] split = mechanism.getValue().asString().split("\\|", 2);
            if (split.length == 2 && new ElementTag(split[0]).isDouble()) {
                BossBarHelper.showSimpleBossBar(getPlayerEntity(), split[1], new ElementTag(split[0]).asDouble() * (1.0 / 200.0));
            } else {
                BossBarHelper.showSimpleBossBar(getPlayerEntity(), split[0], 1.0);
            }
        } else {
            BossBarHelper.removeSimpleBossBar(getPlayerEntity());
        }
    }
    // -->
    if (mechanism.matches("fake_experience")) {
        if (!mechanism.getValue().asString().isEmpty()) {
            String[] split = mechanism.getValue().asString().split("\\|", 2);
            if (split.length > 0 && new ElementTag(split[0]).isFloat()) {
                if (split.length > 1 && new ElementTag(split[1]).isInt()) {
                    NMSHandler.getPacketHelper().showExperience(getPlayerEntity(), new ElementTag(split[0]).asFloat(), new ElementTag(split[1]).asInt());
                } else {
                    NMSHandler.getPacketHelper().showExperience(getPlayerEntity(), new ElementTag(split[0]).asFloat(), getPlayerEntity().getLevel());
                }
            } else {
                Debug.echoError("'" + split[0] + "' is not a valid decimal number!");
            }
        } else {
            NMSHandler.getPacketHelper().resetExperience(getPlayerEntity());
        }
    }
    // -->
    if (mechanism.matches("fake_health")) {
        if (!mechanism.getValue().asString().isEmpty()) {
            String[] split = mechanism.getValue().asString().split("\\|", 3);
            if (split.length > 0 && new ElementTag(split[0]).isFloat()) {
                if (split.length > 1 && new ElementTag(split[1]).isInt()) {
                    if (split.length > 2 && new ElementTag(split[2]).isFloat()) {
                        NMSHandler.getPacketHelper().showHealth(getPlayerEntity(), new ElementTag(split[0]).asFloat(), new ElementTag(split[1]).asInt(), new ElementTag(split[2]).asFloat());
                    } else {
                        NMSHandler.getPacketHelper().showHealth(getPlayerEntity(), new ElementTag(split[0]).asFloat(), new ElementTag(split[1]).asInt(), getPlayerEntity().getSaturation());
                    }
                } else {
                    NMSHandler.getPacketHelper().showHealth(getPlayerEntity(), new ElementTag(split[0]).asFloat(), getPlayerEntity().getFoodLevel(), getPlayerEntity().getSaturation());
                }
            } else {
                Debug.echoError("'" + split[0] + "' is not a valid decimal number!");
            }
        } else {
            NMSHandler.getPacketHelper().resetHealth(getPlayerEntity());
        }
    }
    // -->
    if (mechanism.matches("fake_mount_health")) {
        if (!isOnline() || !getPlayerEntity().isInsideVehicle()) {
            mechanism.echoError("Cannot run fake_mount_health - player is offline or unmounted.");
            return;
        }
        Entity vehicle = getPlayerEntity().getVehicle();
        if (!(vehicle instanceof LivingEntity)) {
            mechanism.echoError("Cannot run fake_mount_health - vehicle is not a living entity.");
            return;
        }
        LivingEntity liveVehicle = (LivingEntity) vehicle;
        double current, maximum;
        if (mechanism.hasValue()) {
            ListTag input = mechanism.valueAsType(ListTag.class);
            if (input.size() != 2) {
                mechanism.echoError("Cannot run fake_mount_health - improper input.");
                return;
            }
            current = new ElementTag(input.get(0)).asDouble();
            maximum = new ElementTag(input.get(1)).asDouble();
        } else {
            current = liveVehicle.getHealth();
            maximum = liveVehicle.getMaxHealth();
        }
        NMSHandler.getPacketHelper().showMobHealth(getPlayerEntity(), liveVehicle, current, maximum);
    }
    // -->
    if (mechanism.matches("fake_entity_health") && mechanism.requireObject(MapTag.class)) {
        if (!isOnline()) {
            mechanism.echoError("Cannot run fake_entity_health - player is offline.");
            return;
        }
        MapTag map = mechanism.valueAsType(MapTag.class);
        ObjectTag entityObject = map.getObject("entity");
        ObjectTag healthObject = map.getObject("health");
        ObjectTag maxObject = map.getObject("max");
        if (entityObject == null || healthObject == null) {
            mechanism.echoError("Cannot run fake_entity_health - input map is missing keys.");
            return;
        }
        EntityTag entity = entityObject.asType(EntityTag.class, mechanism.context);
        double health = new ElementTag(healthObject.toString()).asDouble();
        if (entity == null || !entity.isLivingEntity()) {
            mechanism.echoError("Cannot run fake_entity_health - entity is invalid or not living.");
            return;
        }
        double max = maxObject == null ? entity.getLivingEntity().getMaxHealth() : maxObject.asElement().asDouble();
        NMSHandler.getPacketHelper().showMobHealth(getPlayerEntity(), entity.getLivingEntity(), health, max);
    }
    // -->
    if (mechanism.matches("fake_equipment")) {
        if (!mechanism.getValue().asString().isEmpty()) {
            String[] split = mechanism.getValue().asString().split("\\|", 3);
            if (split.length > 0 && new ElementTag(split[0]).matchesType(EntityTag.class)) {
                String slot = split.length > 1 ? split[1].toUpperCase() : null;
                if (split.length > 1 && (new ElementTag(slot).matchesEnum(EquipmentSlot.class) || slot.equals("MAIN_HAND") || slot.equals("BOOTS"))) {
                    if (split.length > 2 && new ElementTag(split[2]).matchesType(ItemTag.class)) {
                        if (slot.equals("MAIN_HAND")) {
                            slot = "HAND";
                        } else if (slot.equals("BOOTS")) {
                            slot = "FEET";
                        }
                        NMSHandler.getPacketHelper().showEquipment(getPlayerEntity(), new ElementTag(split[0]).asType(EntityTag.class, mechanism.context).getLivingEntity(), EquipmentSlot.valueOf(slot), new ElementTag(split[2]).asType(ItemTag.class, mechanism.context).getItemStack());
                    } else if (split.length > 2) {
                        Debug.echoError("'" + split[2] + "' is not a valid ItemTag!");
                    }
                } else if (split.length > 1) {
                    Debug.echoError("'" + split[1] + "' is not a valid slot; must be HAND, OFF_HAND, BOOTS, LEGS, CHEST, or HEAD!");
                } else {
                    NMSHandler.getPacketHelper().resetEquipment(getPlayerEntity(), new ElementTag(split[0]).asType(EntityTag.class, mechanism.context).getLivingEntity());
                }
            } else {
                Debug.echoError("'" + split[0] + "' is not a valid EntityTag!");
            }
        }
    }
    // -->
    if (mechanism.matches("fov_multiplier")) {
        if (mechanism.hasValue() && mechanism.requireFloat()) {
            NMSHandler.getPacketHelper().setFieldOfView(getPlayerEntity(), mechanism.getValue().asFloat());
        } else {
            NMSHandler.getPacketHelper().setFieldOfView(getPlayerEntity(), Float.NaN);
        }
    }
    if (mechanism.matches("item_message")) {
        Deprecations.itemMessage.warn(mechanism.context);
        ItemChangeMessage.sendMessage(getPlayerEntity(), mechanism.getValue().asString());
    }
    // -->
    if (mechanism.matches("show_endcredits")) {
        NMSHandler.getPlayerHelper().showEndCredits(getPlayerEntity());
    }
    // -->
    if (mechanism.matches("show_demo")) {
        if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_18)) {
            getPlayerEntity().showDemoScreen();
        } else {
            NMSHandler.getPacketHelper().showDemoScreen(getPlayerEntity());
        }
    }
    // -->
    if (mechanism.matches("spectator_target")) {
        if (mechanism.hasValue()) {
            getPlayerEntity().setGameMode(GameMode.SPECTATOR);
            getPlayerEntity().setSpectatorTarget(mechanism.valueAsType(EntityTag.class).getBukkitEntity());
        } else if (getPlayerEntity().getGameMode() == GameMode.SPECTATOR) {
            getPlayerEntity().setSpectatorTarget(null);
        }
    }
    // -->
    if (mechanism.matches("spectate") && mechanism.requireObject(EntityTag.class)) {
        NMSHandler.getPacketHelper().forceSpectate(getPlayerEntity(), mechanism.valueAsType(EntityTag.class).getBukkitEntity());
    }
    // -->
    if (mechanism.matches("open_book")) {
        NMSHandler.getPacketHelper().openBook(getPlayerEntity(), EquipmentSlot.HAND);
    }
    // -->
    if (mechanism.matches("open_offhand_book")) {
        NMSHandler.getPacketHelper().openBook(getPlayerEntity(), EquipmentSlot.OFF_HAND);
    }
    // -->
    if (mechanism.matches("show_book") && mechanism.requireObject(ItemTag.class)) {
        ItemTag book = mechanism.valueAsType(ItemTag.class);
        if (!(book.getItemMeta() instanceof BookMeta)) {
            Debug.echoError("show_book mechanism must have a book as input.");
            return;
        }
        NMSHandler.getPacketHelper().showEquipment(getPlayerEntity(), getPlayerEntity(), EquipmentSlot.OFF_HAND, book.getItemStack());
        NMSHandler.getPacketHelper().openBook(getPlayerEntity(), EquipmentSlot.OFF_HAND);
        NMSHandler.getPacketHelper().showEquipment(getPlayerEntity(), getPlayerEntity(), EquipmentSlot.OFF_HAND, getPlayerEntity().getEquipment().getItemInOffHand());
    }
    // -->
    if (mechanism.matches("resend_recipes")) {
        NMSHandler.getPlayerHelper().resendRecipeDetails(getPlayerEntity());
        NMSHandler.getPlayerHelper().resendDiscoveredRecipes(getPlayerEntity());
    }
    // -->
    if (mechanism.matches("resend_discovered_recipes")) {
        NMSHandler.getPlayerHelper().resendDiscoveredRecipes(getPlayerEntity());
    }
    // -->
    if (mechanism.matches("quietly_discover_recipe")) {
        for (String keyText : mechanism.valueAsType(ListTag.class)) {
            NamespacedKey key = Utilities.parseNamespacedKey(keyText);
            NMSHandler.getPlayerHelper().quietlyAddRecipe(getPlayerEntity(), key);
        }
    }
    // -->
    if (mechanism.matches("discover_recipe")) {
        List<NamespacedKey> keys = new ArrayList<>();
        for (String key : mechanism.valueAsType(ListTag.class)) {
            keys.add(Utilities.parseNamespacedKey(key));
        }
        getPlayerEntity().discoverRecipes(keys);
    }
    // -->
    if (mechanism.matches("forget_recipe")) {
        List<NamespacedKey> keys = new ArrayList<>();
        for (String key : mechanism.valueAsType(ListTag.class)) {
            keys.add(Utilities.parseNamespacedKey(key));
        }
        getPlayerEntity().undiscoverRecipes(keys);
    }
    // -->
    if (mechanism.matches("edit_sign")) {
        if (!NMSHandler.getPacketHelper().showSignEditor(getPlayerEntity(), mechanism.hasValue() ? mechanism.valueAsType(LocationTag.class) : null)) {
            Debug.echoError("Can't edit non-sign materials!");
        }
    }
    // -->
    if (mechanism.matches("tab_list_info")) {
        if (!mechanism.getValue().asString().isEmpty()) {
            String[] split = mechanism.getValue().asString().split("\\|", 2);
            if (split.length > 0) {
                String header = split[0];
                String footer = "";
                if (split.length > 1) {
                    footer = split[1];
                }
                NMSHandler.getPacketHelper().showTabListHeaderFooter(getPlayerEntity(), header, footer);
            } else {
                Debug.echoError("Must specify a header and footer to show!");
            }
        } else {
            NMSHandler.getPacketHelper().resetTabListHeaderFooter(getPlayerEntity());
        }
    }
    // -->
    if (mechanism.matches("sign_update")) {
        if (!mechanism.getValue().asString().isEmpty()) {
            String[] split = mechanism.getValue().asString().split("\\|", 2);
            if (LocationTag.matches(split[0]) && split.length > 1) {
                ListTag lines = ListTag.valueOf(split[1], mechanism.context);
                LocationTag location = LocationTag.valueOf(split[0], mechanism.context);
                AdvancedTextImpl.instance.sendSignUpdate(getPlayerEntity(), location, lines.toArray(new String[4]));
            } else {
                Debug.echoError("Must specify a valid location and at least one sign line!");
            }
        } else {
            Debug.echoError("Must specify a valid location and at least one sign line!");
        }
    }
    // -->
    if (mechanism.matches("banner_update")) {
        if (mechanism.getValue().asString().length() > 0) {
            String[] split = mechanism.getValue().asString().split("\\|");
            List<org.bukkit.block.banner.Pattern> patterns = new ArrayList<>();
            if (LocationTag.matches(split[0]) && split.length > 1) {
                List<String> splitList;
                for (int i = 1; i < split.length; i++) {
                    String string = split[i];
                    if (i == 1 && !string.contains("/")) {
                        // Comapt with old input format that had base_color
                        continue;
                    }
                    try {
                        splitList = CoreUtilities.split(string, '/', 2);
                        patterns.add(new org.bukkit.block.banner.Pattern(DyeColor.valueOf(splitList.get(0).toUpperCase()), PatternType.valueOf(splitList.get(1).toUpperCase())));
                    } catch (Exception e) {
                        Debug.echoError("Could not apply pattern to banner: " + string);
                    }
                }
                LocationTag location = LocationTag.valueOf(split[0], mechanism.context);
                NMSHandler.getPacketHelper().showBannerUpdate(getPlayerEntity(), location, DyeColor.WHITE, patterns);
            } else {
                Debug.echoError("Must specify a valid location and pattern list!");
            }
        }
    }
    // -->
    if (mechanism.matches("stop_sound")) {
        SoundCategory category = null;
        String key = null;
        if (mechanism.hasValue()) {
            try {
                if (mechanism.getValue().matchesEnum(SoundCategory.class)) {
                    category = SoundCategory.valueOf(mechanism.getValue().asString().toUpperCase());
                } else {
                    key = mechanism.getValue().asString();
                }
            } catch (Exception e) {
            }
        } else {
            category = SoundCategory.MASTER;
        }
        NMSHandler.getPlayerHelper().stopSound(getPlayerEntity(), key, category);
    }
    if (mechanism.matches("action_bar")) {
        Deprecations.playerActionBarMech.warn(mechanism.context);
        getPlayerEntity().spigot().sendMessage(ChatMessageType.ACTION_BAR, FormattedTextHelper.parse(mechanism.getValue().asString(), ChatColor.WHITE));
    }
    // -->
    if (mechanism.matches("update_advancements")) {
        NMSHandler.getAdvancementHelper().update(getPlayerEntity());
    }
    // -->
    if (mechanism.matches("name")) {
        String name = mechanism.getValue().asString();
        if (name.length() > 16) {
            Debug.echoError("Must specify a name with no more than 16 characters.");
        } else {
            NMSHandler.getInstance().getProfileEditor().setPlayerName(getPlayerEntity(), mechanism.getValue().asString());
        }
    }
    // -->
    if (mechanism.matches("skin")) {
        String name = mechanism.getValue().asString();
        if (name.length() > 16) {
            Debug.echoError("Must specify a name with no more than 16 characters.");
        } else {
            NMSHandler.getInstance().getProfileEditor().setPlayerSkin(getPlayerEntity(), mechanism.getValue().asString());
        }
    }
    // -->
    if (mechanism.matches("skin_blob")) {
        NMSHandler.getInstance().getProfileEditor().setPlayerSkinBlob(getPlayerEntity(), mechanism.getValue().asString());
    }
    // -->
    if (mechanism.matches("is_whitelisted") && mechanism.requireBoolean()) {
        getPlayerEntity().setWhitelisted(mechanism.getValue().asBoolean());
    }
    // -->
    if (mechanism.matches("is_op") && mechanism.requireBoolean()) {
        getOfflinePlayer().setOp(mechanism.getValue().asBoolean());
    }
    if (mechanism.matches("money") && mechanism.requireDouble() && Depends.economy != null) {
        Deprecations.oldMoneyMech.warn(mechanism.context);
        double bal = Depends.economy.getBalance(getOfflinePlayer());
        double goal = mechanism.getValue().asDouble();
        if (goal > bal) {
            Depends.economy.depositPlayer(getOfflinePlayer(), goal - bal);
        } else if (bal > goal) {
            Depends.economy.withdrawPlayer(getOfflinePlayer(), bal - goal);
        }
    }
    // -->
    if (mechanism.matches("chat_prefix")) {
        if (Depends.chat == null) {
            Debug.echoError("Chat_Prefix mechanism invalid: No linked Chat plugin.");
            return;
        }
        Depends.chat.setPlayerPrefix(getPlayerEntity(), mechanism.getValue().asString());
    }
    // -->
    if (mechanism.matches("chat_suffix")) {
        if (Depends.chat == null) {
            Debug.echoError("Chat_Suffix mechanism invalid: No linked Chat plugin.");
            return;
        }
        Depends.chat.setPlayerSuffix(getPlayerEntity(), mechanism.getValue().asString());
    }
    // -->
    if (mechanism.matches("selected_npc") && Depends.citizens != null && mechanism.requireObject(NPCTag.class)) {
        ((NPCSelector) CitizensAPI.getDefaultNPCSelector()).select(getPlayerEntity(), mechanism.valueAsType(NPCTag.class).getCitizen());
    }
    // -->
    if (mechanism.matches("hide_particles")) {
        if (!mechanism.hasValue()) {
            HideParticles.hidden.remove(getUUID());
        } else {
            NetworkInterceptHelper.enable();
            HashSet<Particle> particles = HideParticles.hidden.computeIfAbsent(getUUID(), k -> new HashSet<>());
            Particle particle = Particle.valueOf(mechanism.getValue().asString().toUpperCase());
            particles.add(particle);
        }
    }
    // -->
    if (mechanism.matches("send_to") && mechanism.hasValue()) {
        if (!isOnline()) {
            Debug.echoError("Cannot use send_to on offline player.");
            return;
        }
        Depends.bungeeSendPlayer(getPlayerEntity(), mechanism.getValue().asString());
    }
    // -->
    if (mechanism.matches("send_server_brand") && mechanism.hasValue()) {
        if (!isOnline()) {
            Debug.echoError("Cannot use send_server_brand on offline player.");
            return;
        }
        NMSHandler.getPacketHelper().sendBrand(getPlayerEntity(), mechanism.getValue().asString());
    }
    CoreUtilities.autoPropertyMechanism(this, mechanism);
    // Pass along to EntityTag mechanism handler if not already handled.
    if (!mechanism.fulfilled()) {
        if (isOnline()) {
            new EntityTag(getPlayerEntity()).adjust(mechanism);
        } else {
            if (mechanism.matches("show_to_players")) {
                HideEntitiesHelper.removeHide(null, getUUID());
            }
            if (mechanism.matches("hide_from_players")) {
                HideEntitiesHelper.addHide(null, getUUID());
            }
        }
    }
}
Also used : FakeEntity(com.denizenscript.denizen.utilities.entity.FakeEntity) org.bukkit(org.bukkit) ImprovedOfflinePlayer(com.denizenscript.denizen.nms.abstracts.ImprovedOfflinePlayer) HideEntitiesHelper(com.denizenscript.denizen.utilities.entity.HideEntitiesHelper) MapView(org.bukkit.map.MapView) AdvancementProgress(org.bukkit.advancement.AdvancementProgress) NPCSelector(net.citizensnpcs.npc.NPCSelector) BookMeta(org.bukkit.inventory.meta.BookMeta) Advancement(org.bukkit.advancement.Advancement)

Example 2 with AdvancementProgress

use of org.bukkit.advancement.AdvancementProgress in project Denizen-For-Bukkit by DenizenScript.

the class PlayerTag method registerTags.

public static void registerTags() {
    AbstractFlagTracker.registerFlagHandlers(tagProcessor);
    // ///////////////////
    // OFFLINE ATTRIBUTES
    // ///////////////
    // Defined in EntityTag
    tagProcessor.registerTag(ElementTag.class, "is_player", (attribute, object) -> {
        return new ElementTag(true);
    });
    // ///////////////////
    // DENIZEN ATTRIBUTES
    // ///////////////
    // <--[tag]
    // @attribute <PlayerTag.chat_history_list>
    // @returns ListTag
    // @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.
    // -->
    tagProcessor.registerTag(ListTag.class, "chat_history_list", (attribute, object) -> {
        return new ListTag(PlayerTagBase.playerChatHistory.get(object.getUUID()), true);
    });
    // <--[tag]
    // @attribute <PlayerTag.chat_history[(<#>)]>
    // @returns ElementTag
    // @description
    // Returns the last thing the player said.
    // If a number is specified, returns an earlier thing the player said.
    // Works with offline players.
    // -->
    tagProcessor.registerTag(ElementTag.class, "chat_history", (attribute, object) -> {
        int x = 1;
        if (attribute.hasParam() && ArgumentHelper.matchesInteger(attribute.getParam())) {
            x = attribute.getIntParam();
        }
        // No playerchathistory? Return null.
        if (!PlayerTagBase.playerChatHistory.containsKey(object.getUUID())) {
            return null;
        }
        List<String> messages = PlayerTagBase.playerChatHistory.get(object.getUUID());
        if (messages.size() < x || x < 1) {
            return null;
        }
        return new ElementTag(messages.get(x - 1), true);
    });
    // ///////////////////
    // ECONOMY ATTRIBUTES
    // ///////////////
    // <--[tag]
    // @attribute <PlayerTag.formatted_money>
    // @returns ElementTag
    // @plugin Vault
    // @description
    // Returns the formatted form of the player's money balance in the registered Economy system.
    // -->
    tagProcessor.registerTag(ElementTag.class, "formatted_money", (attribute, object) -> {
        if (Depends.economy == null) {
            if (!attribute.hasAlternative()) {
                Debug.echoError("No economy loaded! Have you installed Vault and a compatible economy plugin?");
            }
            return null;
        }
        return new ElementTag(Depends.economy.format(Depends.economy.getBalance(object.getOfflinePlayer())), true);
    });
    // <--[tag]
    // @attribute <PlayerTag.money>
    // @returns ElementTag(Decimal)
    // @plugin Vault
    // @description
    // Returns the amount of money the player has with the registered Economy system.
    // May work offline depending on economy provider.
    // -->
    tagProcessor.registerTag(ElementTag.class, "money", (attribute, object) -> {
        if (Depends.economy == null) {
            if (!attribute.hasAlternative()) {
                Debug.echoError("No economy loaded! Have you installed Vault and a compatible economy plugin?");
            }
            return null;
        }
        if (attribute.startsWith("formatted", 2)) {
            attribute.fulfill(1);
            Deprecations.playerMoneyFormatTag.warn(attribute.context);
            return new ElementTag(Depends.economy.format(Depends.economy.getBalance(object.getOfflinePlayer())));
        }
        if (attribute.startsWith("currency_singular", 2)) {
            attribute.fulfill(1);
            Deprecations.oldEconomyTags.warn(attribute.context);
            return new ElementTag(Depends.economy.currencyNameSingular());
        }
        if (attribute.startsWith("currency", 2)) {
            attribute.fulfill(1);
            Deprecations.oldEconomyTags.warn(attribute.context);
            return new ElementTag(Depends.economy.currencyNamePlural());
        }
        return new ElementTag(Depends.economy.getBalance(object.getOfflinePlayer()));
    });
    // ///////////////////
    // ENTITY LIST ATTRIBUTES
    // ///////////////
    // <--[tag]
    // @attribute <PlayerTag.target[(<matcher>)]>
    // @returns EntityTag
    // @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 an entity type matcher to only count matches as possible targets.
    // -->
    registerOnlineOnlyTag(ObjectTag.class, "target", (attribute, object) -> {
        double range = 50;
        String matcher = attribute.hasParam() ? attribute.getParam() : null;
        // -->
        if (attribute.startsWith("within", 2) && attribute.hasContext(2)) {
            range = attribute.getDoubleContext(2);
            attribute.fulfill(1);
        }
        Location eyeLoc = object.getEyeLocation();
        RayTraceResult result = eyeLoc.getWorld().rayTrace(eyeLoc, eyeLoc.getDirection(), range, FluidCollisionMode.NEVER, true, 0.01, (e) -> {
            if (e.getUniqueId().equals(object.getUUID())) {
                return false;
            }
            if (matcher != null) {
                return BukkitScriptEvent.tryEntity(new EntityTag(e), matcher);
            }
            return true;
        });
        if (result == null || result.getHitEntity() == null) {
            return null;
        }
        return new EntityTag(result.getHitEntity()).getDenizenObject();
    });
    // ///////////////////
    // LOCATION ATTRIBUTES
    // ///////////////
    // <--[tag]
    // @attribute <PlayerTag.bed_spawn>
    // @returns LocationTag
    // @mechanism PlayerTag.bed_spawn_location
    // @description
    // Returns the location of the player's bed spawn location, null if
    // it doesn't exist.
    // Works with offline players.
    // -->
    tagProcessor.registerTag(LocationTag.class, "bed_spawn", (attribute, object) -> {
        try {
            NMSHandler.getChunkHelper().changeChunkServerThread(object.getWorld());
            if (object.getOfflinePlayer().getBedSpawnLocation() == null) {
                return null;
            }
            return new LocationTag(object.getOfflinePlayer().getBedSpawnLocation());
        } finally {
            NMSHandler.getChunkHelper().restoreServerThread(object.getWorld());
        }
    });
    tagProcessor.registerTag(ObjectTag.class, "location", (attribute, object) -> {
        if (object.isOnline() && !object.getPlayerEntity().isDead()) {
            return new EntityTag(object.getPlayerEntity()).doLocationTag(attribute);
        }
        return object.getLocation();
    });
    tagProcessor.registerTag(WorldTag.class, "world", (attribute, object) -> {
        return new WorldTag(object.getWorld());
    });
    // ///////////////////
    // STATE ATTRIBUTES
    // ///////////////
    // <--[tag]
    // @attribute <PlayerTag.item_cooldown[<material>]>
    // @returns DurationTag
    // @description
    // Returns the cooldown duration remaining on player's material.
    // -->
    registerOnlineOnlyTag(DurationTag.class, "item_cooldown", (attribute, object) -> {
        MaterialTag mat = new ElementTag(attribute.getParam()).asType(MaterialTag.class, attribute.context);
        if (mat != null) {
            return new DurationTag((long) object.getPlayerEntity().getCooldown(mat.getMaterial()));
        }
        return null;
    });
    // <--[tag]
    // @attribute <PlayerTag.first_played_time>
    // @returns TimeTag
    // @description
    // Returns the time of when the player first logged on to this server.
    // Works with offline players.
    // -->
    tagProcessor.registerTag(TimeTag.class, "first_played_time", (attribute, object) -> {
        return new TimeTag(object.getOfflinePlayer().getFirstPlayed());
    });
    tagProcessor.registerTag(DurationTag.class, "first_played", (attribute, object) -> {
        Deprecations.playerTimePlayedTags.warn(attribute.context);
        return new DurationTag(object.getOfflinePlayer().getFirstPlayed() / 50);
    });
    // <--[tag]
    // @attribute <PlayerTag.has_played_before>
    // @returns ElementTag(Boolean)
    // @description
    // Returns whether the player has played before.
    // Works with offline players.
    // Note: This will just always return true.
    // -->
    tagProcessor.registerTag(ElementTag.class, "has_played_before", (attribute, object) -> {
        return new ElementTag(true);
    });
    // <--[tag]
    // @attribute <PlayerTag.exhaustion>
    // @returns ElementTag(Decimal)
    // @mechanism PlayerTag.exhaustion
    // @description
    // Returns the player's exhaustion value. Exhaustion is increased in vanilla when a player sprints or jumps, and is used to reduce food saturation over time.
    // This can reach a maximum value of 40, and decreases by 4 every tick.
    // Works with offline players.
    // -->
    tagProcessor.registerTag(ElementTag.class, "exhaustion", (attribute, object) -> {
        if (object.isOnline()) {
            return new ElementTag(object.getPlayerEntity().getExhaustion());
        } else {
            return new ElementTag(object.getNBTEditor().getExhaustion());
        }
    });
    // Handle EntityTag oxygen tags here to allow getting them when the player is offline
    tagProcessor.registerTag(DurationTag.class, "max_oxygen", (attribute, object) -> {
        return new DurationTag((long) object.getMaximumAir());
    });
    tagProcessor.registerTag(DurationTag.class, "oxygen", (attribute, object) -> {
        if (attribute.startsWith("max", 2)) {
            Deprecations.entityMaxOxygenTag.warn(attribute.context);
            attribute.fulfill(1);
            return new DurationTag((long) object.getMaximumAir());
        }
        return new DurationTag((long) object.getRemainingAir());
    });
    // <--[tag]
    // @attribute <PlayerTag.health_is_scaled>
    // @returns ElementTag(Boolean)
    // @description
    // Returns whether the player's health bar is currently being scaled.
    // -->
    tagProcessor.registerTag(ElementTag.class, "health_is_scaled", (attribute, object) -> {
        return new ElementTag(object.getPlayerEntity().isHealthScaled());
    });
    // <--[tag]
    // @attribute <PlayerTag.health_scale>
    // @returns ElementTag(Decimal)
    // @mechanism PlayerTag.health_scale
    // @description
    // Returns the current scale for the player's health bar
    // -->
    tagProcessor.registerTag(ElementTag.class, "health_scale", (attribute, object) -> {
        return new ElementTag(object.getPlayerEntity().getHealthScale());
    });
    // Handle EntityTag health tags here to allow getting them when the player is offline
    tagProcessor.registerTag(ElementTag.class, "formatted_health", (attribute, object) -> {
        Double maxHealth = attribute.hasParam() ? attribute.getDoubleParam() : null;
        return EntityHealth.getHealthFormatted(new EntityTag(object.getPlayerEntity()), maxHealth);
    });
    tagProcessor.registerTag(ElementTag.class, "health_percentage", (attribute, object) -> {
        double maxHealth = object.getPlayerEntity().getMaxHealth();
        if (attribute.hasParam()) {
            maxHealth = attribute.getIntParam();
        }
        return new ElementTag((object.getPlayerEntity().getHealth() / maxHealth) * 100);
    });
    tagProcessor.registerTag(ElementTag.class, "health_max", (attribute, object) -> {
        return new ElementTag(object.getMaxHealth());
    });
    tagProcessor.registerTag(ElementTag.class, "health", (attribute, object) -> {
        if (attribute.startsWith("is_scaled", 2)) {
            attribute.fulfill(1);
            Deprecations.entityHealthTags.warn(attribute.context);
            return new ElementTag(object.getPlayerEntity().isHealthScaled());
        }
        if (attribute.startsWith("scale", 2)) {
            attribute.fulfill(1);
            Deprecations.entityHealthTags.warn(attribute.context);
            return new ElementTag(object.getPlayerEntity().getHealthScale());
        }
        if (attribute.startsWith("formatted", 2)) {
            Deprecations.entityHealthTags.warn(attribute.context);
            Double maxHealth = attribute.hasContext(2) ? attribute.getDoubleContext(2) : null;
            attribute.fulfill(1);
            return EntityHealth.getHealthFormatted(new EntityTag(object.getPlayerEntity()), maxHealth);
        }
        if (attribute.startsWith("percentage", 2)) {
            Deprecations.entityHealthTags.warn(attribute.context);
            attribute.fulfill(1);
            double maxHealth = object.getPlayerEntity().getMaxHealth();
            if (attribute.hasParam()) {
                maxHealth = attribute.getIntParam();
            }
            return new ElementTag((object.getPlayerEntity().getHealth() / maxHealth) * 100);
        }
        if (attribute.startsWith("max", 2)) {
            Deprecations.entityHealthTags.warn(attribute.context);
            attribute.fulfill(1);
            return new ElementTag(object.getMaxHealth());
        }
        return new ElementTag(object.getHealth());
    });
    // <--[tag]
    // @attribute <PlayerTag.is_banned>
    // @returns ElementTag(Boolean)
    // @description
    // Returns whether the player is banned.
    // -->
    tagProcessor.registerTag(ElementTag.class, "is_banned", (attribute, object) -> {
        BanEntry ban = Bukkit.getBanList(BanList.Type.NAME).getBanEntry(object.getName());
        if (ban == null) {
            return new ElementTag(false);
        } else if (ban.getExpiration() == null) {
            return new ElementTag(true);
        }
        return new ElementTag(ban.getExpiration().after(new Date()));
    });
    // <--[tag]
    // @attribute <PlayerTag.is_online>
    // @returns ElementTag(Boolean)
    // @description
    // Returns whether the player is currently online.
    // Works with offline players (returns false in that case).
    // -->
    tagProcessor.registerTag(ElementTag.class, "is_online", (attribute, object) -> {
        return new ElementTag(object.isOnline());
    });
    // <--[tag]
    // @attribute <PlayerTag.is_op>
    // @returns ElementTag(Boolean)
    // @mechanism PlayerTag.is_op
    // @description
    // Returns whether the player is a full server operator.
    // Works with offline players.
    // -->
    tagProcessor.registerTag(ElementTag.class, "is_op", (attribute, object) -> {
        return new ElementTag(object.getOfflinePlayer().isOp());
    });
    // <--[tag]
    // @attribute <PlayerTag.is_whitelisted>
    // @returns ElementTag(Boolean)
    // @mechanism PlayerTag.is_whitelisted
    // @description
    // Returns whether the player is whitelisted.
    // Works with offline players.
    // -->
    tagProcessor.registerTag(ElementTag.class, "is_whitelisted", (attribute, object) -> {
        return new ElementTag(object.getOfflinePlayer().isWhitelisted());
    });
    // <--[tag]
    // @attribute <PlayerTag.last_played_time>
    // @returns TimeTag
    // @description
    // Returns the time of when the player was last seen.
    // Works with offline players.
    // Not very useful for online players.
    // -->
    tagProcessor.registerTag(TimeTag.class, "last_played_time", (attribute, object) -> {
        if (object.isOnline()) {
            return TimeTag.now();
        }
        return new TimeTag(object.getOfflinePlayer().getLastPlayed());
    });
    tagProcessor.registerTag(DurationTag.class, "last_played", (attribute, object) -> {
        Deprecations.playerTimePlayedTags.warn(attribute.context);
        if (object.isOnline()) {
            return new DurationTag(System.currentTimeMillis() / 50);
        }
        return new DurationTag(object.getOfflinePlayer().getLastPlayed() / 50);
    });
    // <--[tag]
    // @attribute <PlayerTag.groups[(<world>)]>
    // @returns ListTag
    // @description
    // Returns a list of all groups the player is in.
    // May work with offline players, depending on permission plugin.
    // -->
    tagProcessor.registerTag(ListTag.class, "groups", (attribute, object) -> {
        if (Depends.permissions == null) {
            if (!attribute.hasAlternative()) {
                Debug.echoError("No permission system loaded! Have you installed Vault and a compatible permissions plugin?");
            }
            return null;
        }
        ListTag list = new ListTag();
        WorldTag world = null;
        if (attribute.hasParam()) {
            world = attribute.paramAsType(WorldTag.class);
            if (world == null) {
                Debug.echoError("Invalid world specified: " + attribute.getParam());
                return null;
            }
        }
        for (String group : Depends.permissions.getGroups()) {
            if (Depends.permissions.playerInGroup(world != null ? world.getName() : null, object.getOfflinePlayer(), group)) {
                list.addObject(new ElementTag(group, true));
            }
        }
        return list;
    });
    // <--[tag]
    // @attribute <PlayerTag.ban_expiration_time>
    // @returns TimeTag
    // @description
    // Returns the expiration of the player's ban, if they are banned.
    // Potentially can be null.
    // -->
    tagProcessor.registerTag(TimeTag.class, "ban_expiration_time", (attribute, object) -> {
        BanEntry ban = Bukkit.getBanList(BanList.Type.NAME).getBanEntry(object.getName());
        if (ban == null || ban.getExpiration() == null || (ban.getExpiration() != null && ban.getExpiration().before(new Date()))) {
            return null;
        }
        return new TimeTag(ban.getExpiration().getTime());
    });
    tagProcessor.registerTag(DurationTag.class, "ban_expiration", (attribute, object) -> {
        Deprecations.playerTimePlayedTags.warn(attribute.context);
        BanEntry ban = Bukkit.getBanList(BanList.Type.NAME).getBanEntry(object.getName());
        if (ban == null || ban.getExpiration() == null || (ban.getExpiration() != null && ban.getExpiration().before(new Date()))) {
            return null;
        }
        return new DurationTag(ban.getExpiration().getTime() / 50);
    });
    // <--[tag]
    // @attribute <PlayerTag.ban_reason>
    // @returns ElementTag
    // @description
    // Returns the reason for the player's ban, if they are banned.
    // -->
    tagProcessor.registerTag(ElementTag.class, "ban_reason", (attribute, object) -> {
        BanEntry ban = Bukkit.getBanList(BanList.Type.NAME).getBanEntry(object.getName());
        if (ban == null || (ban.getExpiration() != null && ban.getExpiration().before(new Date()))) {
            return null;
        }
        return new ElementTag(ban.getReason(), true);
    });
    // <--[tag]
    // @attribute <PlayerTag.ban_created_time>
    // @returns TimeTag
    // @description
    // Returns when the player's ban was created, if they are banned.
    // -->
    tagProcessor.registerTag(TimeTag.class, "ban_created_time", (attribute, object) -> {
        BanEntry ban = Bukkit.getBanList(BanList.Type.NAME).getBanEntry(object.getName());
        if (ban == null || (ban.getExpiration() != null && ban.getExpiration().before(new Date()))) {
            return null;
        }
        return new TimeTag(ban.getCreated().getTime());
    });
    tagProcessor.registerTag(DurationTag.class, "ban_created", (attribute, object) -> {
        Deprecations.timeTagRewrite.warn(attribute.context);
        BanEntry ban = Bukkit.getBanList(BanList.Type.NAME).getBanEntry(object.getName());
        if (ban == null || (ban.getExpiration() != null && ban.getExpiration().before(new Date()))) {
            return null;
        }
        return new DurationTag(ban.getCreated().getTime() / 50);
    });
    // <--[tag]
    // @attribute <PlayerTag.ban_source>
    // @returns ElementTag
    // @description
    // Returns the source of the player's ban, if they are banned.
    // -->
    tagProcessor.registerTag(ElementTag.class, "ban_source", (attribute, object) -> {
        BanEntry ban = Bukkit.getBanList(BanList.Type.NAME).getBanEntry(object.getName());
        if (ban == null || (ban.getExpiration() != null && ban.getExpiration().before(new Date()))) {
            return null;
        }
        return new ElementTag(ban.getSource(), true);
    });
    tagProcessor.registerTag(ObjectTag.class, "ban_info", (attribute, object) -> {
        Deprecations.playerBanInfoTags.warn(attribute.context);
        BanEntry ban = Bukkit.getBanList(BanList.Type.NAME).getBanEntry(object.getName());
        if (ban == null || (ban.getExpiration() != null && ban.getExpiration().before(new Date()))) {
            return null;
        }
        if (attribute.startsWith("expiration", 2) && ban.getExpiration() != null) {
            attribute.fulfill(1);
            return new DurationTag(ban.getExpiration().getTime() / 50);
        } else if (attribute.startsWith("reason", 2)) {
            attribute.fulfill(1);
            return new ElementTag(ban.getReason());
        } else if (attribute.startsWith("created", 2)) {
            attribute.fulfill(1);
            return new DurationTag(ban.getCreated().getTime() / 50);
        } else if (attribute.startsWith("source", 2)) {
            attribute.fulfill(1);
            return new ElementTag(ban.getSource());
        }
        return null;
    });
    // <--[tag]
    // @attribute <PlayerTag.in_group[<group_name>]>
    // @returns ElementTag(Boolean)
    // @description
    // Returns whether the player is in the specified group.
    // (May work with offline players, depending on your permissions system.)
    // -->
    tagProcessor.registerTag(ElementTag.class, "in_group", (attribute, object) -> {
        if (Depends.permissions == null) {
            if (!attribute.hasAlternative()) {
                Debug.echoError("No permission system loaded! Have you installed Vault and a compatible permissions plugin?");
            }
            return null;
        }
        String group = attribute.getParam();
        // Non-world specific permission
        if (attribute.startsWith("global", 2)) {
            attribute.fulfill(1);
            return new ElementTag(Depends.permissions.playerInGroup(null, object.getOfflinePlayer(), group));
        } else // Permission in certain world
        if (attribute.startsWith("world", 2)) {
            WorldTag world = null;
            if (attribute.hasContext(2)) {
                world = attribute.contextAsType(2, WorldTag.class);
                if (world == null) {
                    Debug.echoError("Invalid world specified: " + attribute.getContext(2));
                    return null;
                }
            }
            attribute.fulfill(1);
            return new ElementTag(Depends.permissions.playerInGroup(world != null ? world.getName() : null, object.getOfflinePlayer(), group));
        } else // Permission in current world
        if (object.isOnline()) {
            return new ElementTag(Depends.permissions.playerInGroup(object.getPlayerEntity(), group));
        } else if (Depends.permissions != null) {
            return new ElementTag(Depends.permissions.playerInGroup(null, object.getOfflinePlayer(), group));
        }
        return null;
    });
    // <--[tag]
    // @attribute <PlayerTag.has_permission[permission.node]>
    // @returns ElementTag(Boolean)
    // @description
    // Returns whether the player has the specified node.
    // (May work with offline players, depending on your permissions system.)
    // -->
    tagProcessor.registerTag(ElementTag.class, "has_permission", (attribute, object) -> {
        String permission = attribute.getParam();
        // Non-world specific permission
        if (attribute.startsWith("global", 2)) {
            if (Depends.permissions == null) {
                if (!attribute.hasAlternative()) {
                    Debug.echoError("No permission system loaded! Have you installed Vault and a compatible permissions plugin?");
                }
                return null;
            }
            attribute.fulfill(1);
            return new ElementTag(Depends.permissions.playerHas(null, object.getOfflinePlayer(), permission));
        } else // Permission in certain world
        if (attribute.startsWith("world", 2)) {
            String world = attribute.getContext(2);
            if (Depends.permissions == null) {
                if (!attribute.hasAlternative()) {
                    Debug.echoError("No permission system loaded! Have you installed Vault and a compatible permissions plugin?");
                }
                return null;
            }
            attribute.fulfill(1);
            if (world.startsWith("w@")) {
                world = world.substring(2);
            }
            return new ElementTag(Depends.permissions.playerHas(world, object.getOfflinePlayer(), permission));
        } else // Permission in current world
        if (object.isOnline()) {
            return new ElementTag(object.getPlayerEntity().hasPermission(permission));
        } else if (Depends.permissions != null) {
            return new ElementTag(Depends.permissions.playerHas(null, object.getOfflinePlayer(), permission));
        }
        return null;
    }, "permission");
    // <--[tag]
    // @attribute <PlayerTag.statistic[<statistic>]>
    // @returns ElementTag(Number)
    // @description
    // Returns the player's current value for the specified statistic.
    // Valid statistics: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Statistic.html>
    // Works with offline players.
    // -->
    tagProcessor.registerTag(ElementTag.class, "statistic", (attribute, object) -> {
        if (!attribute.hasParam()) {
            return null;
        }
        Statistic statistic;
        try {
            statistic = Statistic.valueOf(attribute.getParam().toUpperCase());
        } catch (IllegalArgumentException ex) {
            attribute.echoError("Statistic '" + attribute.getParam() + "' does not exist: " + ex.getMessage());
            return null;
        }
        // -->
        if (attribute.startsWith("qualifier", 2)) {
            ObjectTag obj = ObjectFetcher.pickObjectFor(attribute.getContext(2), attribute.context);
            attribute.fulfill(1);
            try {
                if (obj instanceof MaterialTag) {
                    return new ElementTag(object.getOfflinePlayer().getStatistic(statistic, ((MaterialTag) obj).getMaterial()));
                } else if (obj instanceof EntityTag) {
                    return new ElementTag(object.getOfflinePlayer().getStatistic(statistic, ((EntityTag) obj).getBukkitEntityType()));
                } else {
                    return null;
                }
            } catch (Exception e) {
                Debug.echoError("Invalid statistic: " + statistic + " for this player!");
                return null;
            }
        }
        try {
            return new ElementTag(object.getOfflinePlayer().getStatistic(statistic));
        } catch (Exception e) {
            Debug.echoError("Invalid statistic: " + statistic + " for this player!");
            return null;
        }
    });
    // <--[tag]
    // @attribute <PlayerTag.uuid>
    // @returns ElementTag
    // @description
    // Returns the UUID of the player.
    // Works with offline players.
    // -->
    tagProcessor.registerTag(ElementTag.class, "uuid", (attribute, object) -> {
        return new ElementTag(object.getUUID().toString());
    });
    // <--[tag]
    // @attribute <PlayerTag.list_name>
    // @returns ElementTag
    // @mechanism PlayerTag.player_list_name
    // @description
    // Returns the name of the player as shown in the player list.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "list_name", (attribute, object) -> {
        return new ElementTag(AdvancedTextImpl.instance.getPlayerListName(object.getPlayerEntity()), true);
    });
    // <--[tag]
    // @attribute <PlayerTag.display_name>
    // @returns ElementTag
    // @mechanism PlayerTag.display_name
    // @description
    // Returns the display name of the player, which may contain prefixes and suffixes, colors, etc.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "display_name", (attribute, object) -> {
        return new ElementTag(object.getPlayerEntity().getDisplayName(), true);
    });
    // Documented in EntityTag
    tagProcessor.registerTag(ElementTag.class, "name", (attribute, object) -> {
        if (attribute.startsWith("list", 2) && object.isOnline()) {
            Deprecations.playerNameTags.warn(attribute.context);
            attribute.fulfill(1);
            return new ElementTag(object.getPlayerEntity().getPlayerListName(), true);
        }
        if (attribute.startsWith("display", 2) && object.isOnline()) {
            Deprecations.playerNameTags.warn(attribute.context);
            attribute.fulfill(1);
            return new ElementTag(object.getPlayerEntity().getDisplayName(), true);
        }
        return new ElementTag(object.getName(), true);
    });
    // <--[tag]
    // @attribute <PlayerTag.client_brand>
    // @returns ElementTag
    // @description
    // Returns the brand of the client, as sent via the "minecraft:brand" packet.
    // On normal clients, will say "vanilla". On broken clients, will say "unknown". Modded clients will identify themselves (though not guaranteed!).
    // It may be ideal to change setting "Packets.Auto init" in the Denizen config to "true" to guarantee this tag functions as expected.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "client_brand", (attribute, object) -> {
        NetworkInterceptHelper.enable();
        return new ElementTag(NMSHandler.getPlayerHelper().getPlayerBrand(object.getPlayerEntity()), true);
    });
    // <--[tag]
    // @attribute <PlayerTag.locale>
    // @returns ElementTag
    // @description
    // Returns the current locale of the player.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "locale", (attribute, object) -> {
        return new ElementTag(object.getPlayerEntity().getLocale(), true);
    });
    // ///////////////////
    // INVENTORY ATTRIBUTES
    // ///////////////
    // <--[tag]
    // @attribute <PlayerTag.inventory>
    // @returns InventoryTag
    // @description
    // Returns a InventoryTag of the player's current inventory.
    // Works with offline players.
    // -->
    tagProcessor.registerTag(InventoryTag.class, "inventory", (attribute, object) -> {
        return object.getInventory();
    });
    // <--[tag]
    // @attribute <PlayerTag.enderchest>
    // @returns InventoryTag
    // @description
    // Gets the player's enderchest inventory.
    // Works with offline players.
    // -->
    tagProcessor.registerTag(InventoryTag.class, "enderchest", (attribute, object) -> {
        return object.getEnderChest();
    });
    // ///////////////////
    // ONLINE ATTRIBUTES
    // ///////////////
    // <--[tag]
    // @attribute <PlayerTag.open_inventory>
    // @returns InventoryTag
    // @description
    // Gets the inventory the player currently has open. If the player has no open
    // inventory, this returns the player's inventory.
    // -->
    registerOnlineOnlyTag(InventoryTag.class, "open_inventory", (attribute, object) -> {
        return InventoryTag.mirrorBukkitInventory(object.getPlayerEntity().getOpenInventory().getTopInventory());
    });
    // <--[tag]
    // @attribute <PlayerTag.discovered_recipes>
    // @returns ListTag
    // @description
    // Returns a list of the recipes the player has discovered, in the Namespace:Key format, for example "minecraft:gold_nugget".
    // -->
    registerOnlineOnlyTag(ListTag.class, "discovered_recipes", (attribute, object) -> {
        return new ListTag(NMSHandler.getEntityHelper().getDiscoveredRecipes(object.getPlayerEntity()));
    });
    // <--[tag]
    // @attribute <PlayerTag.selected_trade_index>
    // @returns ElementTag(Number)
    // @description
    // Returns the index of the trade the player is currently viewing, if any.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "selected_trade_index", (attribute, object) -> {
        if (object.getPlayerEntity().getOpenInventory().getTopInventory() instanceof MerchantInventory) {
            return new ElementTag(((MerchantInventory) object.getPlayerEntity().getOpenInventory().getTopInventory()).getSelectedRecipeIndex() + 1);
        }
        return null;
    });
    // [tag]
    // @attribute <PlayerTag.selected_trade>
    // @returns TradeTag
    // @description
    // Returns the trade the player is currently viewing, if any.
    // This is almost completely broke and only works if the player has placed items in the trade slots.
    // 
    registerOnlineOnlyTag(TradeTag.class, "selected_trade", (attribute, object) -> {
        Inventory playerInventory = object.getPlayerEntity().getOpenInventory().getTopInventory();
        if (playerInventory instanceof MerchantInventory && ((MerchantInventory) playerInventory).getSelectedRecipe() != null) {
            return new TradeTag(((MerchantInventory) playerInventory).getSelectedRecipe()).duplicate();
        }
        return null;
    });
    // <--[tag]
    // @attribute <PlayerTag.item_on_cursor>
    // @returns ItemTag
    // @mechanism PlayerTag.item_on_cursor
    // @description
    // Returns the item on the player's cursor, if any. This includes
    // chest interfaces, inventories, and hotbars, etc.
    // -->
    registerOnlineOnlyTag(ItemTag.class, "item_on_cursor", (attribute, object) -> {
        return new ItemTag(object.getPlayerEntity().getItemOnCursor());
    });
    // <--[tag]
    // @attribute <PlayerTag.held_item_slot>
    // @returns ElementTag(Number)
    // @description
    // Returns the slot location of the player's selected item.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "held_item_slot", (attribute, object) -> {
        return new ElementTag(object.getPlayerEntity().getInventory().getHeldItemSlot() + 1);
    });
    registerOnlineOnlyTag(ObjectTag.class, "item_in_hand", (attribute, object) -> {
        if (attribute.startsWith("slot", 2)) {
            Deprecations.playerItemInHandSlotTag.warn(attribute.context);
            attribute.fulfill(1);
            return new ElementTag(object.getPlayerEntity().getInventory().getHeldItemSlot() + 1);
        }
        return object.getHeldItem();
    });
    // <--[tag]
    // @attribute <PlayerTag.sidebar_lines>
    // @returns ListTag
    // @description
    // Returns the current lines set on the player's Sidebar via <@link command sidebar>.
    // -->
    registerOnlineOnlyTag(ListTag.class, "sidebar_lines", (attribute, object) -> {
        Sidebar sidebar = SidebarCommand.getSidebar(object);
        if (sidebar == null) {
            return null;
        }
        return new ListTag(sidebar.getLinesText(), true);
    });
    // <--[tag]
    // @attribute <PlayerTag.sidebar_title>
    // @returns ElementTag
    // @description
    // Returns the current title set on the player's Sidebar via <@link command sidebar>.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "sidebar_title", (attribute, object) -> {
        Sidebar sidebar = SidebarCommand.getSidebar(object);
        if (sidebar == null) {
            return null;
        }
        return new ElementTag(sidebar.getTitle(), true);
    });
    // <--[tag]
    // @attribute <PlayerTag.sidebar_scores>
    // @returns ListTag
    // @description
    // Returns the current scores set on the player's Sidebar via <@link command sidebar>,
    // in the same order as <@link tag PlayerTag.sidebar_lines>.
    // -->
    registerOnlineOnlyTag(ListTag.class, "sidebar_scores", (attribute, object) -> {
        Sidebar sidebar = SidebarCommand.getSidebar(object);
        if (sidebar == null) {
            return null;
        }
        ListTag scores = new ListTag();
        for (int score : sidebar.getScores()) {
            scores.add(String.valueOf(score));
        }
        return scores;
    });
    registerOnlineOnlyTag(ObjectTag.class, "sidebar", (attribute, object) -> {
        Deprecations.playerSidebarTags.warn(attribute.context);
        if (attribute.startsWith("lines", 2)) {
            attribute.fulfill(1);
            Sidebar sidebar = SidebarCommand.getSidebar(object);
            if (sidebar == null) {
                return null;
            }
            return new ListTag(sidebar.getLinesText());
        }
        if (attribute.startsWith("title", 2)) {
            attribute.fulfill(1);
            Sidebar sidebar = SidebarCommand.getSidebar(object);
            if (sidebar == null) {
                return null;
            }
            return new ElementTag(sidebar.getTitle());
        }
        if (attribute.startsWith("scores", 2)) {
            attribute.fulfill(1);
            Sidebar sidebar = SidebarCommand.getSidebar(object);
            if (sidebar == null) {
                return null;
            }
            ListTag scores = new ListTag();
            for (int score : sidebar.getScores()) {
                scores.add(String.valueOf(score));
            }
            return scores;
        }
        return null;
    });
    // <--[tag]
    // @attribute <PlayerTag.skin_blob>
    // @returns ElementTag
    // @mechanism PlayerTag.skin_blob
    // @description
    // Returns the player's current skin blob.
    // In the format: "texture;signature" (two values separated by a semicolon).
    // See also <@link language Player Entity Skins (Skin Blobs)>.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "skin_blob", (attribute, object) -> {
        return new ElementTag(NMSHandler.getInstance().getProfileEditor().getPlayerSkinBlob(object.getPlayerEntity()));
    });
    // <--[tag]
    // @attribute <PlayerTag.skull_skin>
    // @returns ElementTag
    // @description
    // Returns the player's current skin blob, formatted for input to a Player Skull item.
    // In the format: "UUID|Texture|Name" (three values separated by pipes).
    // See also <@link language Player Entity Skins (Skin Blobs)>.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "skull_skin", (attribute, object) -> {
        String skin = NMSHandler.getInstance().getProfileEditor().getPlayerSkinBlob(object.getPlayerEntity());
        if (skin == null) {
            return null;
        }
        int semicolon = skin.indexOf(';');
        return new ElementTag(object.getPlayerEntity().getUniqueId() + "|" + skin.substring(0, semicolon) + "|" + object.getName());
    });
    // <--[tag]
    // @attribute <PlayerTag.skull_item>
    // @returns ItemTag
    // @description
    // Returns a Player_Head item with the skin of the player.
    // See also <@link language Player Entity Skins (Skin Blobs)>.
    // -->
    registerOnlineOnlyTag(ItemTag.class, "skull_item", (attribute, object) -> {
        ItemStack item = new ItemStack(Material.PLAYER_HEAD);
        item = NMSHandler.getItemHelper().setSkullSkin(item, NMSHandler.getInstance().getPlayerProfile(object.getPlayerEntity()));
        return new ItemTag(item);
    });
    registerOnlineOnlyTag(ObjectTag.class, "attack_cooldown", (attribute, object) -> {
        Deprecations.playerAttackCooldownTags.warn(attribute.context);
        if (attribute.startsWith("duration", 2)) {
            attribute.fulfill(1);
            return new DurationTag((long) NMSHandler.getPlayerHelper().ticksPassedDuringCooldown(object.getPlayerEntity()));
        } else if (attribute.startsWith("max_duration", 2)) {
            attribute.fulfill(1);
            return new DurationTag((long) NMSHandler.getPlayerHelper().getMaxAttackCooldownTicks(object.getPlayerEntity()));
        } else if (attribute.startsWith("percent", 2)) {
            attribute.fulfill(1);
            return new ElementTag(NMSHandler.getPlayerHelper().getAttackCooldownPercent(object.getPlayerEntity()) * 100);
        }
        Debug.echoError("The tag 'player.attack_cooldown...' must be followed by a sub-tag.");
        return null;
    });
    // <--[tag]
    // @attribute <PlayerTag.main_hand>
    // @returns ElementTag
    // @description
    // Returns the player's main hand, either LEFT or RIGHT.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "main_hand", (attribute, object) -> {
        return new ElementTag(object.getPlayerEntity().getMainHand().toString());
    });
    // ///////////////////
    // CITIZENS ATTRIBUTES
    // ///////////////
    // <--[tag]
    // @attribute <PlayerTag.selected_npc>
    // @returns NPCTag
    // @mechanism PlayerTag.selected_npc
    // @description
    // Returns the NPCTag that the player currently has selected with '/npc select', null if no NPC selected.
    // -->
    registerOnlineOnlyTag(NPCTag.class, "selected_npc", (attribute, object) -> {
        if (object.getPlayerEntity().hasMetadata("selected")) {
            return object.getSelectedNPC();
        }
        return null;
    });
    // ///////////////////
    // CONVERSION ATTRIBUTES
    // ///////////////
    // <--[tag]
    // @attribute <PlayerTag.entity>
    // @returns EntityTag
    // @description
    // Returns the EntityTag object of the player.
    // (Note: This should never actually be needed. PlayerTags are considered valid EntityTags.)
    // -->
    registerOnlineOnlyTag(EntityTag.class, "entity", (attribute, object) -> {
        return new EntityTag(object.getPlayerEntity());
    });
    // ///////////////////
    // IDENTIFICATION ATTRIBUTES
    // ///////////////
    // <--[tag]
    // @attribute <PlayerTag.ip_address>
    // @returns ElementTag
    // @description
    // Returns the player's IP address, without port or hostname.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "ip_address", (attribute, object) -> {
        return new ElementTag(object.getPlayerEntity().getAddress().getAddress().toString());
    });
    // <--[tag]
    // @attribute <PlayerTag.ip>
    // @returns ElementTag
    // @description
    // Returns the player's IP address host name.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "ip", (attribute, object) -> {
        // -->
        if (attribute.startsWith("address_only", 2)) {
            attribute.fulfill(1);
            return new ElementTag(object.getPlayerEntity().getAddress().toString());
        }
        String host = object.getPlayerEntity().getAddress().getHostName();
        // -->
        if (attribute.startsWith("address", 2)) {
            attribute.fulfill(1);
            return new ElementTag(object.getPlayerEntity().getAddress().toString());
        }
        return new ElementTag(host);
    }, "host_name");
    // <--[tag]
    // @attribute <PlayerTag.nameplate>
    // @returns ElementTag
    // @description
    // Returns the displayed text in the nameplate of the player.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "nameplate", (attribute, object) -> {
        return new ElementTag(NMSHandler.getInstance().getProfileEditor().getPlayerName(object.getPlayerEntity()), true);
    });
    // ///////////////////
    // LOCATION ATTRIBUTES
    // ///////////////
    // <--[tag]
    // @attribute <PlayerTag.compass_target>
    // @returns LocationTag
    // @description
    // Returns the location of the player's compass target.
    // -->
    registerOnlineOnlyTag(LocationTag.class, "compass_target", (attribute, object) -> {
        Location target = object.getPlayerEntity().getCompassTarget();
        if (target != null) {
            return new LocationTag(target);
        }
        return null;
    });
    // <--[tag]
    // @attribute <PlayerTag.chunk_loaded[<chunk>]>
    // @returns ElementTag(Boolean)
    // @description
    // Returns whether the player has the chunk loaded on their client.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "chunk_loaded", (attribute, object) -> {
        if (!attribute.hasParam()) {
            return null;
        }
        ChunkTag chunk = attribute.paramAsType(ChunkTag.class);
        if (chunk == null) {
            return null;
        }
        return new ElementTag(chunk.isLoadedSafe() && object.hasChunkLoaded(chunk.getChunkForTag(attribute)));
    });
    // ///////////////////
    // STATE ATTRIBUTES
    // ///////////////
    // <--[tag]
    // @attribute <PlayerTag.can_fly>
    // @returns ElementTag(Boolean)
    // @mechanism PlayerTag.can_fly
    // @description
    // Returns whether the player is allowed to fly.
    // Works with offline players.
    // -->
    tagProcessor.registerTag(ElementTag.class, "can_fly", (attribute, object) -> {
        if (object.isOnline()) {
            return new ElementTag(object.getPlayerEntity().getAllowFlight());
        } else {
            return new ElementTag(object.getNBTEditor().getAllowFlight());
        }
    }, "allowed_flight");
    // <--[tag]
    // @attribute <PlayerTag.fly_speed>
    // @returns ElementTag(Decimal)
    // @mechanism PlayerTag.fly_speed
    // @description
    // Returns the speed the player can fly at.
    // Default value is '0.2'.
    // Works with offline players.
    // -->
    tagProcessor.registerTag(ElementTag.class, "fly_speed", (attribute, object) -> {
        if (object.isOnline()) {
            return new ElementTag(object.getPlayerEntity().getFlySpeed());
        } else {
            return new ElementTag(object.getNBTEditor().getFlySpeed());
        }
    });
    // <--[tag]
    // @attribute <PlayerTag.walk_speed>
    // @returns ElementTag(Decimal)
    // @mechanism PlayerTag.walk_speed
    // @description
    // Returns the speed the player can walk at.
    // Works with offline players.
    // -->
    tagProcessor.registerTag(ElementTag.class, "walk_speed", (attribute, object) -> {
        if (object.isOnline()) {
            return new ElementTag(object.getPlayerEntity().getWalkSpeed());
        } else {
            return new ElementTag(object.getNBTEditor().getWalkSpeed());
        }
    });
    // <--[tag]
    // @attribute <PlayerTag.saturation>
    // @returns ElementTag(Decimal)
    // @mechanism PlayerTag.saturation
    // @description
    // Returns the current food saturation of the player.
    // Works with offline players.
    // -->
    tagProcessor.registerTag(ElementTag.class, "saturation", (attribute, object) -> {
        if (object.isOnline()) {
            return new ElementTag(object.getPlayerEntity().getSaturation());
        } else {
            return new ElementTag(object.getNBTEditor().getSaturation());
        }
    });
    // <--[tag]
    // @attribute <PlayerTag.formatted_food_level[(<max>)]>
    // @returns ElementTag
    // @mechanism PlayerTag.food_level
    // @description
    // Returns a 'formatted' value of the player's current food level.
    // May be 'starving', 'famished', 'parched, 'hungry', or 'healthy'.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "formatted_food_level", (attribute, object) -> {
        double maxHunger = object.getPlayerEntity().getMaxHealth();
        if (attribute.hasParam()) {
            maxHunger = attribute.getIntParam();
        }
        attribute.fulfill(1);
        int foodLevel = object.getFoodLevel();
        if (foodLevel / maxHunger < .10) {
            return new ElementTag("starving");
        } else if (foodLevel / maxHunger < .40) {
            return new ElementTag("famished");
        } else if (foodLevel / maxHunger < .75) {
            return new ElementTag("parched");
        } else if (foodLevel / maxHunger < 1) {
            return new ElementTag("hungry");
        } else {
            return new ElementTag("healthy");
        }
    });
    // <--[tag]
    // @attribute <PlayerTag.food_level>
    // @returns ElementTag(Number)
    // @mechanism PlayerTag.food_level
    // @description
    // Returns the current food level (aka hunger) of the player.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "food_level", (attribute, object) -> {
        if (attribute.startsWith("formatted", 2)) {
            Deprecations.playerFoodLevelFormatTag.warn(attribute.context);
            double maxHunger = object.getPlayerEntity().getMaxHealth();
            if (attribute.hasContext(2)) {
                maxHunger = attribute.getIntContext(2);
            }
            attribute.fulfill(1);
            int foodLevel = object.getFoodLevel();
            if (foodLevel / maxHunger < .10) {
                return new ElementTag("starving");
            } else if (foodLevel / maxHunger < .40) {
                return new ElementTag("famished");
            } else if (foodLevel / maxHunger < .75) {
                return new ElementTag("parched");
            } else if (foodLevel / maxHunger < 1) {
                return new ElementTag("hungry");
            } else {
                return new ElementTag("healthy");
            }
        }
        return new ElementTag(object.getFoodLevel());
    });
    // <--[tag]
    // @attribute <PlayerTag.gamemode>
    // @returns ElementTag
    // @mechanism PlayerTag.gamemode
    // @description
    // Returns the name of the gamemode the player is currently set to.
    // Works with offline players.
    // -->
    tagProcessor.registerTag(ElementTag.class, "gamemode", (attribute, object) -> {
        if (object.isOnline()) {
            return new ElementTag(object.getPlayerEntity().getGameMode().name());
        }
        return new ElementTag(object.getNBTEditor().getGameMode().name());
    });
    // <--[tag]
    // @attribute <PlayerTag.is_blocking>
    // @returns ElementTag(Boolean)
    // @description
    // Returns whether the player is currently blocking.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "is_blocking", (attribute, object) -> {
        return new ElementTag(object.getPlayerEntity().isBlocking());
    });
    // <--[tag]
    // @attribute <PlayerTag.ping>
    // @returns ElementTag(Number)
    // @description
    // Returns the player's current ping.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "ping", (attribute, object) -> {
        return new ElementTag(NMSHandler.getPlayerHelper().getPing(object.getPlayerEntity()));
    });
    // <--[tag]
    // @attribute <PlayerTag.is_flying>
    // @returns ElementTag(Boolean)
    // @description
    // Returns whether the player is currently flying.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "is_flying", (attribute, object) -> {
        return new ElementTag(object.getPlayerEntity().isFlying());
    });
    // <--[tag]
    // @attribute <PlayerTag.is_sneaking>
    // @returns ElementTag(Boolean)
    // @description
    // Returns whether the player is currently sneaking.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "is_sneaking", (attribute, object) -> {
        return new ElementTag(object.getPlayerEntity().isSneaking());
    });
    // <--[tag]
    // @attribute <PlayerTag.is_sprinting>
    // @returns ElementTag(Boolean)
    // @mechanism PlayerTag.sprinting
    // @description
    // Returns whether the player is currently sprinting.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "is_sprinting", (attribute, object) -> {
        return new ElementTag(object.getPlayerEntity().isSprinting());
    });
    // <--[tag]
    // @attribute <PlayerTag.has_advancement[<advancement>]>
    // @returns ElementTag(Boolean)
    // @description
    // Returns whether the player has completed the specified advancement.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "has_advancement", (attribute, object) -> {
        if (!attribute.hasParam()) {
            return null;
        }
        Advancement adv = AdvancementHelper.getAdvancement(attribute.getParam());
        if (adv == null) {
            if (!attribute.hasAlternative()) {
                Debug.echoError("Advancement '" + attribute.getParam() + "' does not exist.");
            }
            return null;
        }
        AdvancementProgress progress = object.getPlayerEntity().getAdvancementProgress(adv);
        return new ElementTag(progress.isDone());
    });
    // <--[tag]
    // @attribute <PlayerTag.advancements>
    // @returns ListTag
    // @description
    // Returns a list of the names of all advancements the player has completed.
    // -->
    registerOnlineOnlyTag(ListTag.class, "advancements", (attribute, object) -> {
        ListTag list = new ListTag();
        Bukkit.advancementIterator().forEachRemaining((adv) -> {
            if (object.getPlayerEntity().getAdvancementProgress(adv).isDone()) {
                list.add(adv.getKey().toString());
            }
        });
        return list;
    }, "list_advancements");
    // <--[tag]
    // @attribute <PlayerTag.time_asleep>
    // @returns DurationTag
    // @description
    // Returns the time the player has been asleep.
    // -->
    registerOnlineOnlyTag(DurationTag.class, "time_asleep", (attribute, object) -> {
        return new DurationTag((long) object.getPlayerEntity().getSleepTicks());
    });
    // <--[tag]
    // @attribute <PlayerTag.time>
    // @returns ElementTag(Number)
    // @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 <@link command time> is being used on the player.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "time", (attribute, object) -> {
        return new ElementTag(object.getPlayerEntity().getPlayerTime());
    });
    // <--[tag]
    // @attribute <PlayerTag.weather>
    // @returns ElementTag
    // @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.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "weather", (attribute, object) -> {
        if (object.getPlayerEntity().getPlayerWeather() != null) {
            return new ElementTag(object.getPlayerEntity().getPlayerWeather().name());
        } else {
            return null;
        }
    });
    // <--[tag]
    // @attribute <PlayerTag.calculate_xp>
    // @returns ElementTag(Number)
    // @description
    // Returns the calculated total amount of XP the player has, based on the amount of experience needed per level, for each level the player has.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "calculate_xp", (attribute, object) -> {
        int level = object.getPlayerEntity().getLevel();
        return new ElementTag(ExperienceCommand.TOTAL_XP_FOR_LEVEL(level) + (object.getPlayerEntity().getExp() * ExperienceCommand.XP_FOR_NEXT_LEVEL(level)));
    });
    // <--[tag]
    // @attribute <PlayerTag.xp_level>
    // @returns ElementTag(Number)
    // @description
    // Returns the number of XP levels the player has.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "xp_level", (attribute, object) -> {
        return new ElementTag(object.getPlayerEntity().getLevel());
    });
    // <--[tag]
    // @attribute <PlayerTag.xp_to_next_level>
    // @returns ElementTag(Number)
    // @description
    // Returns the amount of XP the player needs to get to the next level.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "xp_to_next_level", (attribute, object) -> {
        return new ElementTag(object.getPlayerEntity().getExpToLevel());
    });
    // <--[tag]
    // @attribute <PlayerTag.xp_total>
    // @returns ElementTag(Number)
    // @description
    // Returns the total amount of experience points the player has.
    // This is how much XP the player has ever received, not a current value.
    // To get the current total, use @<link tag PlayerTag.calculate_xp>.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "xp_total", (attribute, object) -> {
        return new ElementTag(object.getPlayerEntity().getTotalExperience());
    });
    // <--[tag]
    // @attribute <PlayerTag.xp>
    // @returns ElementTag(Decimal)
    // @description
    // Returns the percentage of experience points to the next level.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "xp", (attribute, object) -> {
        if (attribute.startsWith("level", 2)) {
            Deprecations.playerXpTags.warn(attribute.context);
            attribute.fulfill(1);
            return new ElementTag(object.getPlayerEntity().getLevel());
        }
        if (attribute.startsWith("to_next_level", 2)) {
            Deprecations.playerXpTags.warn(attribute.context);
            attribute.fulfill(1);
            return new ElementTag(object.getPlayerEntity().getExpToLevel());
        }
        if (attribute.startsWith("total", 2)) {
            Deprecations.playerXpTags.warn(attribute.context);
            attribute.fulfill(1);
            return new ElementTag(object.getPlayerEntity().getTotalExperience());
        }
        return new ElementTag(object.getPlayerEntity().getExp() * 100);
    });
    // <--[tag]
    // @attribute <PlayerTag.chat_prefix>
    // @returns ElementTag
    // @plugin Vault
    // @mechanism PlayerTag.chat_prefix
    // @description
    // Returns the player's chat prefix.
    // NOTE: May work with offline players.
    // Requires a Vault-compatible chat plugin.
    // -->
    tagProcessor.registerTag(ElementTag.class, "chat_prefix", (attribute, object) -> {
        if (Depends.chat == null) {
            if (!attribute.hasAlternative()) {
                Debug.echoError("'chat_prefix' tag unavailable: Vault and a chat plugin are required.");
            }
            return null;
        }
        String prefix = Depends.chat.getPlayerPrefix(object.getWorld().getName(), object.getOfflinePlayer());
        if (prefix == null) {
            return null;
        }
        return new ElementTag(prefix, true);
    });
    // <--[tag]
    // @attribute <PlayerTag.chat_suffix>
    // @returns ElementTag
    // @plugin Vault
    // @mechanism PlayerTag.chat_suffix
    // @description
    // Returns the player's chat suffix.
    // NOTE: May work with offline players.
    // Requires a Vault-compatible chat plugin.
    // -->
    tagProcessor.registerTag(ElementTag.class, "chat_suffix", (attribute, object) -> {
        if (Depends.chat == null) {
            if (!attribute.hasAlternative()) {
                Debug.echoError("'chat_suffix' tag unavailable: Vault and a chat plugin are required.");
            }
            return null;
        }
        String suffix = Depends.chat.getPlayerSuffix(object.getWorld().getName(), object.getOfflinePlayer());
        if (suffix == null) {
            return null;
        }
        return new ElementTag(suffix, true);
    });
    // <--[tag]
    // @attribute <PlayerTag.fake_block_locations>
    // @returns ListTag(LocationTag)
    // @description
    // Returns a list of locations that the player will see a fake block at, as set by <@link command showfake> or connected commands.
    // -->
    tagProcessor.registerTag(ListTag.class, "fake_block_locations", (attribute, object) -> {
        ListTag list = new ListTag();
        FakeBlock.FakeBlockMap map = FakeBlock.blocks.get(object.getUUID());
        if (map != null) {
            for (LocationTag loc : map.byLocation.keySet()) {
                list.addObject(loc.clone());
            }
        }
        return list;
    });
    // <--[tag]
    // @attribute <PlayerTag.fake_block[<location>]>
    // @returns MaterialTag
    // @description
    // Returns the fake material that the player will see at the input location, as set by <@link command showfake> or connected commands.
    // Works best alongside <@link tag PlayerTag.fake_block_locations>.
    // Returns null if the player doesn't have a fake block at the location.
    // -->
    tagProcessor.registerTag(MaterialTag.class, "fake_block", (attribute, object) -> {
        if (!attribute.hasParam()) {
            return null;
        }
        LocationTag input = attribute.paramAsType(LocationTag.class);
        FakeBlock.FakeBlockMap map = FakeBlock.blocks.get(object.getUUID());
        if (map != null) {
            FakeBlock block = map.byLocation.get(input);
            if (block != null) {
                return block.material;
            }
        }
        return null;
    });
    // <--[tag]
    // @attribute <PlayerTag.fake_entities>
    // @returns ListTag(EntityTag)
    // @description
    // Returns a list of fake entities the player can see, as set by <@link command fakespawn>.
    // Note that these entities are not being tracked by the server, so many operations may not be possible on them.
    // -->
    tagProcessor.registerTag(ListTag.class, "fake_entities", (attribute, object) -> {
        ListTag list = new ListTag();
        FakeEntity.FakeEntityMap map = FakeEntity.playersToEntities.get(object.getUUID());
        if (map != null) {
            for (Map.Entry<Integer, FakeEntity> entry : map.byId.entrySet()) {
                list.addObject(entry.getValue().entity);
            }
        }
        return list;
    });
    // <--[tag]
    // @attribute <PlayerTag.disguise_to_self[(<player>)]>
    // @returns EntityTag
    // @group properties
    // @description
    // Returns the fake entity used to disguise the entity in the player's self-view (only relevant to players), either globally (if no context input given), or to the specified player.
    // Relates to <@link command disguise>.
    // -->
    tagProcessor.registerTag(EntityTag.class, "disguise_to_self", (attribute, object) -> {
        HashMap<UUID, DisguiseCommand.TrackedDisguise> map = DisguiseCommand.disguises.get(object.getUUID());
        if (map == null) {
            return null;
        }
        DisguiseCommand.TrackedDisguise disguise;
        if (attribute.hasParam()) {
            PlayerTag player = attribute.paramAsType(PlayerTag.class);
            if (player == null) {
                attribute.echoError("Invalid player for is_disguised tag.");
                return null;
            }
            disguise = map.get(player.getUUID());
            if (disguise == null) {
                disguise = map.get(null);
            }
        } else {
            disguise = map.get(null);
        }
        if (disguise == null) {
            return null;
        }
        if (disguise.fakeToSelf == null) {
            return null;
        }
        return disguise.fakeToSelf.entity;
    });
    // <--[tag]
    // @attribute <PlayerTag.spectator_target>
    // @returns EntityTag
    // @mechanism PlayerTag.spectator_target
    // @description
    // Returns the entity that a spectator-mode player is currently spectating, if any.
    // -->
    registerOnlineOnlyTag(ObjectTag.class, "spectator_target", (attribute, object) -> {
        if (object.getPlayerEntity().getGameMode() != GameMode.SPECTATOR) {
            return null;
        }
        Entity target = object.getPlayerEntity().getSpectatorTarget();
        if (target == null) {
            return null;
        }
        return new EntityTag(target).getDenizenObject();
    });
    // <--[tag]
    // @attribute <PlayerTag.packets_sent>
    // @returns ElementTag(Number)
    // @description
    // Returns a total count of how many network packets have been sent to this player while they have been online.
    // It may be ideal to change setting "Packets.Auto init" in the Denizen config to "true" to guarantee this tag functions as expected.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "packets_sent", (attribute, object) -> {
        NetworkInterceptHelper.enable();
        return new ElementTag(NMSHandler.getPacketHelper().getPacketStats(object.getPlayerEntity(), true));
    });
    // <--[tag]
    // @attribute <PlayerTag.packets_received>
    // @returns ElementTag(Number)
    // @description
    // Returns a total count of how many network packets have been received from this player while they have been online.
    // It may be ideal to change setting "Packets.Auto init" in the Denizen config to "true" to guarantee this tag functions as expected.
    // -->
    registerOnlineOnlyTag(ElementTag.class, "packets_received", (attribute, object) -> {
        NetworkInterceptHelper.enable();
        return new ElementTag(NMSHandler.getPacketHelper().getPacketStats(object.getPlayerEntity(), false));
    });
    // <--[tag]
    // @attribute <PlayerTag.fish_hook>
    // @returns EntityTag
    // @description
    // Returns the fishing hook a player has cast (if any).
    // -->
    registerOnlineOnlyTag(EntityTag.class, "fish_hook", (attribute, object) -> {
        FishHook hook = NMSHandler.getFishingHelper().getHookFrom(object.getPlayerEntity());
        if (hook == null) {
            return null;
        }
        return new EntityTag(hook);
    });
    // <--[tag]
    // @attribute <PlayerTag.spawn_forced>
    // @returns ElementTag(Boolean)
    // @mechanism PlayerTag.spawn_forced
    // @description
    // Returns whether the player's bed spawn location is forced (ie still valid even if a bed is missing).
    // -->
    tagProcessor.registerTag(ElementTag.class, "spawn_forced", (attribute, object) -> {
        if (object.isOnline()) {
            return new ElementTag(NMSHandler.getPlayerHelper().getSpawnForced(object.getPlayerEntity()));
        }
        return new ElementTag(object.getNBTEditor().isSpawnForced());
    });
    // <--[tag]
    // @attribute <PlayerTag.last_action_time>
    // @returns TimeTag
    // @description
    // Returns the time of the last direct input from the player. Internally used with <@link tag server.idle_timeout>.
    // -->
    registerOnlineOnlyTag(TimeTag.class, "last_action_time", (attribute, object) -> {
        // The internal time values use monotonic time - this converts to real time.
        long playerMilliTime = NMSHandler.getPlayerHelper().getLastActionTime(object.getPlayerEntity());
        long relativeMillis = System.nanoTime() / 1000000L - playerMilliTime;
        return new TimeTag(System.currentTimeMillis() - relativeMillis);
    });
    // <--[tag]
    // @attribute <PlayerTag.scoreboard_id>
    // @returns ElementTag
    // @description
    // Returns the ID of the scoreboard from <@link command scoreboard> that a player is currently viewing, if any.
    // -->
    tagProcessor.registerTag(ElementTag.class, "scoreboard_id", (attribute, object) -> {
        String id = ScoreboardHelper.viewerMap.get(object.getUUID());
        if (id == null) {
            return null;
        }
        return new ElementTag(id);
    });
    // <--[tag]
    // @attribute <PlayerTag.bossbar_ids>
    // @returns ListTag
    // @description
    // Returns a list of all bossbars from <@link command bossbar> that this player can see.
    // Does not list bossbars created by any other source.
    // -->
    registerOnlineOnlyTag(ListTag.class, "bossbar_ids", (attribute, object) -> {
        ListTag result = new ListTag();
        for (Map.Entry<String, BossBar> bar : BossBarCommand.bossBarMap.entrySet()) {
            if (bar.getValue().getPlayers().contains(object.getPlayerEntity())) {
                result.addObject(new ElementTag(bar.getKey(), true));
            }
        }
        return result;
    });
    // <--[tag]
    // @attribute <PlayerTag.tab_completions[<command>]>
    // @returns ListTag
    // @description
    // Returns a list of all tab completions for the given plaintext of a command.
    // Input is formatted equivalent to if it were typed into a chat bar, minus the '/' slash at the start.
    // Input must necessarily contain at least one space.
    // For example: "<player.tab_completions[npc ]>" will return all /NPC sub command names available to the player.
    // This is only compatible with commands registered in Spigot. Meaning in particular, vanilla commands are not recognized or supported.
    // -->
    registerOnlineOnlyTag(ListTag.class, "tab_completions", (attribute, object) -> {
        if (!attribute.hasParam()) {
            return null;
        }
        String cmdFull = attribute.getParam();
        int space = cmdFull.indexOf(' ');
        if (space == -1) {
            attribute.echoError("Invalid command input '" + cmdFull + "': must have at least one space");
            return null;
        }
        String cmdName = cmdFull.substring(0, space);
        PluginCommand actualCmd = Bukkit.getPluginCommand(cmdName);
        if (actualCmd == null) {
            attribute.echoError("Unknown command '" + cmdName + "'");
            return null;
        }
        String args = cmdFull.substring(space + 1);
        ListTag result = new ListTag();
        for (String str : actualCmd.tabComplete(object.getPlayerEntity(), cmdName, CoreUtilities.split(args, ' ').toArray(new String[0]))) {
            result.addObject(new ElementTag(str, true));
        }
        return result;
    });
}
Also used : DisguiseCommand(com.denizenscript.denizen.scripts.commands.player.DisguiseCommand) FakeEntity(com.denizenscript.denizen.utilities.entity.FakeEntity) BossBar(org.bukkit.boss.BossBar) AdvancementProgress(org.bukkit.advancement.AdvancementProgress) RayTraceResult(org.bukkit.util.RayTraceResult) Sidebar(com.denizenscript.denizen.nms.abstracts.Sidebar) FakeEntity(com.denizenscript.denizen.utilities.entity.FakeEntity) PluginCommand(org.bukkit.command.PluginCommand) FakeBlock(com.denizenscript.denizen.utilities.blocks.FakeBlock) Advancement(org.bukkit.advancement.Advancement)

Aggregations

FakeEntity (com.denizenscript.denizen.utilities.entity.FakeEntity)2 Advancement (org.bukkit.advancement.Advancement)2 AdvancementProgress (org.bukkit.advancement.AdvancementProgress)2 ImprovedOfflinePlayer (com.denizenscript.denizen.nms.abstracts.ImprovedOfflinePlayer)1 Sidebar (com.denizenscript.denizen.nms.abstracts.Sidebar)1 DisguiseCommand (com.denizenscript.denizen.scripts.commands.player.DisguiseCommand)1 FakeBlock (com.denizenscript.denizen.utilities.blocks.FakeBlock)1 HideEntitiesHelper (com.denizenscript.denizen.utilities.entity.HideEntitiesHelper)1 NPCSelector (net.citizensnpcs.npc.NPCSelector)1 org.bukkit (org.bukkit)1 BossBar (org.bukkit.boss.BossBar)1 PluginCommand (org.bukkit.command.PluginCommand)1 BookMeta (org.bukkit.inventory.meta.BookMeta)1 MapView (org.bukkit.map.MapView)1 RayTraceResult (org.bukkit.util.RayTraceResult)1