diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/EntityTag.java b/plugin/src/main/java/com/denizenscript/denizen/objects/EntityTag.java index 7ad96b6837..dd825ddc63 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/EntityTag.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/EntityTag.java @@ -22,6 +22,7 @@ import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.objects.core.ListTag; import com.denizenscript.denizencore.objects.core.ScriptTag; +import com.denizenscript.denizencore.tags.ObjectTagProcessor; import com.denizenscript.denizencore.utilities.debugging.Debug; import com.denizenscript.denizencore.objects.properties.PropertyParser; import com.denizenscript.denizencore.scripts.ScriptRegistry; @@ -1330,18 +1331,6 @@ public boolean matchesEntity(String ent) { @Override public String getAttribute(Attribute attribute) { - if (attribute == null) { - return null; - } - - if (entity == null && entity_type == null) { - if (npc != null) { - return new ElementTag(identify()).getAttribute(attribute); - } - Debug.echoError("dEntity has returned null."); - return null; - } - // <--[tag] // @attribute // @returns ElementTag @@ -1349,9 +1338,12 @@ public String getAttribute(Attribute attribute) { // Always returns 'Entity' for EntityTag objects. All objects fetchable by the Object Fetcher will return the // type of object that is fulfilling this attribute. // --> - if (attribute.startsWith("type")) { - return new ElementTag("Entity").getAttribute(attribute.fulfill(1)); - } + registerTag("type", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag("Entity").getObjectAttribute(attribute.fulfill(1)); + } + }); ///////////////////// // UNSPAWNED ATTRIBUTES @@ -1364,9 +1356,12 @@ public String getAttribute(Attribute attribute) { // @description // Returns the type of the entity. // --> - if (attribute.startsWith("entity_type")) { - return new ElementTag(entity_type.getName()).getAttribute(attribute.fulfill(1)); - } + registerTag("entity_type", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(entity_type.getName()).getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1375,10 +1370,13 @@ public String getAttribute(Attribute attribute) { // @description // Returns whether the entity is spawned. // --> - if (attribute.startsWith("is_spawned")) { - return new ElementTag(isSpawned()) - .getAttribute(attribute.fulfill(1)); - } + registerTag("is_spawned", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(isSpawned()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1387,10 +1385,13 @@ public String getAttribute(Attribute attribute) { // @description // Returns the entity's temporary server entity ID. // --> - if (attribute.startsWith("eid")) { - return new ElementTag(entity.getEntityId()) - .getAttribute(attribute.fulfill(1)); - } + registerTag("eid", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(entity.getEntityId()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1400,10 +1401,13 @@ public String getAttribute(Attribute attribute) { // Returns the permanent unique ID of the entity. // Works with offline players. // --> - if (attribute.startsWith("uuid")) { - return new ElementTag(getUUID().toString()) - .getAttribute(attribute.fulfill(1)); - } + registerTag("uuid", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getUUID().toString()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1412,13 +1416,16 @@ public String getAttribute(Attribute attribute) { // @description // Returns the name of the entity script that spawned this entity, if any. // --> - if (attribute.startsWith("scriptname")) { - if (entityScript == null) { - return null; + registerTag("scriptname", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (entityScript == null) { + return null; + } + return new ElementTag(entityScript) + .getObjectAttribute(attribute.fulfill(1)); } - return new ElementTag(entityScript) - .getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute ]> @@ -1426,20 +1433,23 @@ public String getAttribute(Attribute attribute) { // @description // Returns true if the entity has the specified flag, otherwise returns false. // --> - if (attribute.startsWith("has_flag")) { - String flag_name; - if (attribute.hasContext(1)) { - flag_name = attribute.getContext(1); - } - else { - return null; - } - if (isPlayer() || isCitizensNPC()) { - Debug.echoError("Reading flag for PLAYER or NPC as if it were an ENTITY!"); - return null; + registerSpawnedOnlyTag("has_flag", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + String flag_name; + if (attribute.hasContext(1)) { + flag_name = attribute.getContext(1); + } + else { + return null; + } + if (isPlayer() || isCitizensNPC()) { + Debug.echoError("Reading flag for PLAYER or NPC as if it were an ENTITY!"); + return null; + } + return new ElementTag(FlagManager.entityHasFlag((EntityTag) object, flag_name)).getObjectAttribute(attribute.fulfill(1)); } - return new ElementTag(FlagManager.entityHasFlag(this, flag_name)).getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute ]> @@ -1447,32 +1457,35 @@ public String getAttribute(Attribute attribute) { // @description // Returns the specified flag from the entity. // --> - if (attribute.startsWith("flag")) { - String flag_name; - if (attribute.hasContext(1)) { - flag_name = attribute.getContext(1); - } - else { - return null; - } - if (isPlayer() || isCitizensNPC()) { - Debug.echoError("Reading flag for PLAYER or NPC as if it were an ENTITY!"); - return null; - } - if (attribute.getAttribute(2).equalsIgnoreCase("is_expired") - || attribute.startsWith("isexpired")) { - return new ElementTag(!FlagManager.entityHasFlag(this, flag_name)) - .getAttribute(attribute.fulfill(2)); - } - if (attribute.getAttribute(2).equalsIgnoreCase("size") && !FlagManager.entityHasFlag(this, flag_name)) { - return new ElementTag(0).getAttribute(attribute.fulfill(2)); - } - if (FlagManager.entityHasFlag(this, flag_name)) { - FlagManager.Flag flag = DenizenAPI.getCurrentInstance().flagManager().getEntityFlag(this, flag_name); - return new ListTag(flag.toString(), true, flag.values()).getAttribute(attribute.fulfill(1)); + registerSpawnedOnlyTag("flag", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + String flag_name; + if (attribute.hasContext(1)) { + flag_name = attribute.getContext(1); + } + else { + return null; + } + if (isPlayer() || isCitizensNPC()) { + Debug.echoError("Reading flag for PLAYER or NPC as if it were an ENTITY!"); + return null; + } + if (attribute.getAttribute(2).equalsIgnoreCase("is_expired") + || attribute.startsWith("isexpired")) { + return new ElementTag(!FlagManager.entityHasFlag((EntityTag) object, flag_name)) + .getObjectAttribute(attribute.fulfill(2)); + } + if (attribute.getAttribute(2).equalsIgnoreCase("size") && !FlagManager.entityHasFlag((EntityTag) object, flag_name)) { + return new ElementTag(0).getObjectAttribute(attribute.fulfill(2)); + } + if (FlagManager.entityHasFlag((EntityTag) object, flag_name)) { + FlagManager.Flag flag = DenizenAPI.getCurrentInstance().flagManager().getEntityFlag((EntityTag) object, flag_name); + return new ListTag(flag.toString(), true, flag.values()).getObjectAttribute(attribute.fulfill(1)); + } + return new ElementTag(identify()).getObjectAttribute(attribute); } - return new ElementTag(identify()).getAttribute(attribute); - } + }); // <--[tag] // @attribute ]> @@ -1481,47 +1494,44 @@ public String getAttribute(Attribute attribute) { // Returns a list of an entity's flag names, with an optional search for // names containing a certain pattern. // --> - if (attribute.startsWith("list_flags")) { - ListTag allFlags = new ListTag(DenizenAPI.getCurrentInstance().flagManager().listEntityFlags(this)); - ListTag searchFlags = null; - if (!allFlags.isEmpty() && attribute.hasContext(1)) { - searchFlags = new ListTag(); - String search = attribute.getContext(1); - if (search.startsWith("regex:")) { - try { - Pattern pattern = Pattern.compile(search.substring(6), Pattern.CASE_INSENSITIVE); + registerSpawnedOnlyTag("list_flags", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + ListTag allFlags = new ListTag(DenizenAPI.getCurrentInstance().flagManager().listEntityFlags((EntityTag) object)); + ListTag searchFlags = null; + if (!allFlags.isEmpty() && attribute.hasContext(1)) { + searchFlags = new ListTag(); + String search = attribute.getContext(1); + if (search.startsWith("regex:")) { + try { + Pattern pattern = Pattern.compile(search.substring(6), Pattern.CASE_INSENSITIVE); + for (String flag : allFlags) { + if (pattern.matcher(flag).matches()) { + searchFlags.add(flag); + } + } + } + catch (Exception e) { + Debug.echoError(e); + } + } + else { + search = CoreUtilities.toLowerCase(search); for (String flag : allFlags) { - if (pattern.matcher(flag).matches()) { + if (CoreUtilities.toLowerCase(flag).contains(search)) { searchFlags.add(flag); } } } - catch (Exception e) { - Debug.echoError(e); - } + DenizenAPI.getCurrentInstance().flagManager().shrinkEntityFlags((EntityTag) object, searchFlags); } else { - search = CoreUtilities.toLowerCase(search); - for (String flag : allFlags) { - if (CoreUtilities.toLowerCase(flag).contains(search)) { - searchFlags.add(flag); - } - } + DenizenAPI.getCurrentInstance().flagManager().shrinkEntityFlags((EntityTag) object, allFlags); } - DenizenAPI.getCurrentInstance().flagManager().shrinkEntityFlags(this, searchFlags); + return searchFlags == null ? allFlags.getObjectAttribute(attribute.fulfill(1)) + : searchFlags.getObjectAttribute(attribute.fulfill(1)); } - else { - DenizenAPI.getCurrentInstance().flagManager().shrinkEntityFlags(this, allFlags); - } - return searchFlags == null ? allFlags.getAttribute(attribute.fulfill(1)) - : searchFlags.getAttribute(attribute.fulfill(1)); - } - - if (entity == null) { - return new ElementTag(identify()).getAttribute(attribute); - } - // Only spawned entities past this point! - + }); ///////////////////// // IDENTIFICATION ATTRIBUTES @@ -1535,16 +1545,19 @@ public String getAttribute(Attribute attribute) { // If the entity has a script ID, returns the ScriptTag of that ID. // Otherwise, returns the name of the entity type. // --> - if (attribute.startsWith("custom_id")) { - if (CustomNBT.hasCustomNBT(getLivingEntity(), "denizen-script-id")) { - return new ScriptTag(CustomNBT.getCustomNBT(getLivingEntity(), "denizen-script-id")) - .getAttribute(attribute.fulfill(1)); - } - else { - return new ElementTag(entity.getType().name()) - .getAttribute(attribute.fulfill(1)); + registerSpawnedOnlyTag("custom_id", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (CustomNBT.hasCustomNBT(getLivingEntity(), "denizen-script-id")) { + return new ScriptTag(CustomNBT.getCustomNBT(getLivingEntity(), "denizen-script-id")) + .getObjectAttribute(attribute.fulfill(1)); + } + else { + return new ElementTag(entity.getType().name()) + .getObjectAttribute(attribute.fulfill(1)); + } } - } + }); // <--[tag] // @attribute @@ -1555,9 +1568,12 @@ public String getAttribute(Attribute attribute) { // This can be a player name, an NPC name, a custom_name, or the entity type. // Works with offline players. // --> - if (attribute.startsWith("name")) { - return new ElementTag(getName()).getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("name", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getName()).getObjectAttribute(attribute.fulfill(1)); + } + }); ///////////////////// @@ -1572,16 +1588,20 @@ public String getAttribute(Attribute attribute) { // @description // If the entity is a horse or pig, returns the saddle as a ItemTag, or air if none. // --> - if (attribute.startsWith("saddle")) { - if (getLivingEntity().getType() == EntityType.HORSE) { - return new ItemTag(((Horse) getLivingEntity()).getInventory().getSaddle()) - .getAttribute(attribute.fulfill(1)); - } - else if (getLivingEntity().getType() == EntityType.PIG) { - return new ItemTag(((Pig) getLivingEntity()).hasSaddle() ? Material.SADDLE : Material.AIR) - .getAttribute(attribute.fulfill(1)); + registerSpawnedOnlyTag("saddle", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (getLivingEntity().getType() == EntityType.HORSE) { + return new ItemTag(((Horse) getLivingEntity()).getInventory().getSaddle()) + .getObjectAttribute(attribute.fulfill(1)); + } + else if (getLivingEntity().getType() == EntityType.PIG) { + return new ItemTag(((Pig) getLivingEntity()).hasSaddle() ? Material.SADDLE : Material.AIR) + .getObjectAttribute(attribute.fulfill(1)); + } + return null; } - } + }); // <--[tag] // @attribute @@ -1590,12 +1610,17 @@ else if (getLivingEntity().getType() == EntityType.PIG) { // @description // If the entity is a horse, returns the item equipped as the horses armor, or air if none. // --> - if (attribute.startsWith("horse_armor") || attribute.startsWith("horse_armour")) { - if (getLivingEntity().getType() == EntityType.HORSE) { - return new ItemTag(((Horse) getLivingEntity()).getInventory().getArmor()) - .getAttribute(attribute.fulfill(1)); + registerTag("horse_armor", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (getBukkitEntityType() == EntityType.HORSE) { + return new ItemTag(((Horse) getLivingEntity()).getInventory().getArmor()) + .getObjectAttribute(attribute.fulfill(1)); + } + return null; } - } + }); + registerSpawnedOnlyTag("horse_armour", tagProcessor.registeredObjectTags.get("horse_armor")); // <--[tag] // @attribute @@ -1604,16 +1629,20 @@ else if (getLivingEntity().getType() == EntityType.PIG) { // @description // If the entity s a pig or horse, returns whether it has a saddle equipped. // --> - if (attribute.startsWith("has_saddle")) { - if (getLivingEntity().getType() == EntityType.HORSE) { - return new ElementTag(((Horse) getLivingEntity()).getInventory().getSaddle().getType() == Material.SADDLE) - .getAttribute(attribute.fulfill(1)); - } - else if (getLivingEntity().getType() == EntityType.PIG) { - return new ElementTag(((Pig) getLivingEntity()).hasSaddle()) - .getAttribute(attribute.fulfill(1)); + registerSpawnedOnlyTag("has_saddle", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (getBukkitEntityType() == EntityType.HORSE) { + return new ElementTag(((Horse) getLivingEntity()).getInventory().getSaddle().getType() == Material.SADDLE) + .getObjectAttribute(attribute.fulfill(1)); + } + else if (getBukkitEntityType() == EntityType.PIG) { + return new ElementTag(((Pig) getLivingEntity()).hasSaddle()) + .getObjectAttribute(attribute.fulfill(1)); + } + return null; } - } + }); // <--[tag] // @attribute @@ -1622,11 +1651,14 @@ else if (getLivingEntity().getType() == EntityType.PIG) { // @description // Returns the item the entity is holding, or air if none. // --> - if (attribute.startsWith("item_in_hand") || - attribute.startsWith("iteminhand")) { - return new ItemTag(NMSHandler.getEntityHelper().getItemInHand(getLivingEntity())) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("item_in_hand", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ItemTag(getLivingEntity().getEquipment().getItemInMainHand()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); + registerSpawnedOnlyTag("iteminhand", tagProcessor.registeredObjectTags.get("item_in_hand")); // <--[tag] // @attribute @@ -1635,11 +1667,14 @@ else if (getLivingEntity().getType() == EntityType.PIG) { // @description // Returns the item the entity is holding in their off hand, or air if none. // --> - if (attribute.startsWith("item_in_offhand") || - attribute.startsWith("iteminoffhand")) { - return new ItemTag(NMSHandler.getEntityHelper().getItemInOffHand(getLivingEntity())) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("item_in_offhand", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ItemTag(getLivingEntity().getEquipment().getItemInOffHand()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); + registerSpawnedOnlyTag("iteminoffhand", tagProcessor.registeredObjectTags.get("item_in_offhand")); // <--[tag] // @attribute @@ -1647,11 +1682,15 @@ else if (getLivingEntity().getType() == EntityType.PIG) { // @description // Returns whether the villager entity is trading. // --> - if (attribute.startsWith("is_trading")) { - if (entity instanceof Merchant) { - return new ElementTag(((Merchant) entity).isTrading()).getAttribute(attribute.fulfill(1)); + registerSpawnedOnlyTag("is_trading", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (entity instanceof Merchant) { + return new ElementTag(((Merchant) entity).isTrading()).getObjectAttribute(attribute.fulfill(1)); + } + return null; } - } + }); // <--[tag] // @attribute @@ -1659,12 +1698,16 @@ else if (getLivingEntity().getType() == EntityType.PIG) { // @description // Returns the player who is trading with the villager entity, or null if it is not trading. // --> - if (attribute.startsWith("trading_with")) { - if (entity instanceof Merchant - && ((Merchant) entity).getTrader() != null) { - return new EntityTag(((Merchant) entity).getTrader()).getAttribute(attribute.fulfill(1)); + registerSpawnedOnlyTag("trading_with", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (entity instanceof Merchant + && ((Merchant) entity).getTrader() != null) { + return new EntityTag(((Merchant) entity).getTrader()).getObjectAttribute(attribute.fulfill(1)); + } + return null; } - } + }); ///////////////////// @@ -1679,30 +1722,34 @@ else if (getLivingEntity().getType() == EntityType.PIG) { // Returns a 2D location indicating where on the map the entity's looking at. // Each coordinate is in the range of 0 to 128. // --> - if (attribute.startsWith("map_trace")) { - EntityHelper.MapTraceResult mtr = NMSHandler.getEntityHelper().mapTrace(getLivingEntity(), 200); - if (mtr != null) { - double x = 0; - double y = 0; - double basex = mtr.hitLocation.getX() - Math.floor(mtr.hitLocation.getX()); - double basey = mtr.hitLocation.getY() - Math.floor(mtr.hitLocation.getY()); - double basez = mtr.hitLocation.getZ() - Math.floor(mtr.hitLocation.getZ()); - if (mtr.angle == BlockFace.NORTH) { - x = 128f - (basex * 128f); - } - else if (mtr.angle == BlockFace.SOUTH) { - x = basex * 128f; - } - else if (mtr.angle == BlockFace.WEST) { - x = basez * 128f; - } - else if (mtr.angle == BlockFace.EAST) { - x = 128f - (basez * 128f); + registerSpawnedOnlyTag("map_trace", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + EntityHelper.MapTraceResult mtr = NMSHandler.getEntityHelper().mapTrace(getLivingEntity(), 200); + if (mtr != null) { + double x = 0; + double y = 0; + double basex = mtr.hitLocation.getX() - Math.floor(mtr.hitLocation.getX()); + double basey = mtr.hitLocation.getY() - Math.floor(mtr.hitLocation.getY()); + double basez = mtr.hitLocation.getZ() - Math.floor(mtr.hitLocation.getZ()); + if (mtr.angle == BlockFace.NORTH) { + x = 128f - (basex * 128f); + } + else if (mtr.angle == BlockFace.SOUTH) { + x = basex * 128f; + } + else if (mtr.angle == BlockFace.WEST) { + x = basez * 128f; + } + else if (mtr.angle == BlockFace.EAST) { + x = 128f - (basez * 128f); + } + y = 128f - (basey * 128f); + return new LocationTag(null, Math.round(x), Math.round(y)).getObjectAttribute(attribute.fulfill(1)); } - y = 128f - (basey * 128f); - return new LocationTag(null, Math.round(x), Math.round(y)).getAttribute(attribute.fulfill(1)); + return null; } - } + }); // <--[tag] // @attribute ]> @@ -1711,14 +1758,18 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns whether the entity can see the specified other entity (has an uninterrupted line-of-sight). // --> - if (attribute.startsWith("can_see")) { - if (isLivingEntity() && attribute.hasContext(1) && EntityTag.matches(attribute.getContext(1))) { - EntityTag toEntity = EntityTag.valueOf(attribute.getContext(1)); - if (toEntity != null && toEntity.isSpawned()) { - return new ElementTag(getLivingEntity().hasLineOfSight(toEntity.getBukkitEntity())).getAttribute(attribute.fulfill(1)); + registerSpawnedOnlyTag("can_see", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (isLivingEntity() && attribute.hasContext(1) && EntityTag.matches(attribute.getContext(1))) { + EntityTag toEntity = EntityTag.valueOf(attribute.getContext(1)); + if (toEntity != null && toEntity.isSpawned()) { + return new ElementTag(getLivingEntity().hasLineOfSight(toEntity.getBukkitEntity())).getObjectAttribute(attribute.fulfill(1)); + } } + return null; } - } + }); // <--[tag] // @attribute @@ -1727,10 +1778,13 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns the location of the entity's eyes. // --> - if (attribute.startsWith("eye_location")) { - return new LocationTag(getEyeLocation()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("eye_location", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new LocationTag(getEyeLocation()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1739,12 +1793,16 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns the height of the entity's eyes above its location. // --> - if (attribute.startsWith("eye_height")) { - if (isLivingEntity()) { - return new ElementTag(getLivingEntity().getEyeHeight()) - .getAttribute(attribute.fulfill(1)); + registerSpawnedOnlyTag("eye_height", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (isLivingEntity()) { + return new ElementTag(getLivingEntity().getEyeHeight()) + .getObjectAttribute(attribute.fulfill(1)); + } + return null; } - } + }); // <--[tag] // @attribute ]> @@ -1764,23 +1822,26 @@ else if (mtr.angle == BlockFace.EAST) { // ignored material. // Optionally, specify a maximum range to find the location from. // --> - if (attribute.startsWith("location.cursor_on")) { - int range = attribute.getIntContext(2); - if (range < 1) { - range = 50; - } - Set set = new HashSet<>(); - set.add(Material.AIR); - attribute = attribute.fulfill(2); - if (attribute.startsWith("ignore") && attribute.hasContext(1)) { - List ignoreList = ListTag.valueOf(attribute.getContext(1)).filter(MaterialTag.class, attribute.context); - for (MaterialTag material : ignoreList) { - set.add(material.getMaterial()); + registerSpawnedOnlyTag("location.cursor_on", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + int range = attribute.getIntContext(2); + if (range < 1) { + range = 50; } - attribute = attribute.fulfill(1); + Set set = new HashSet<>(); + set.add(Material.AIR); + attribute = attribute.fulfill(2); + if (attribute.startsWith("ignore") && attribute.hasContext(1)) { + List ignoreList = ListTag.valueOf(attribute.getContext(1)).filter(MaterialTag.class, attribute.context); + for (MaterialTag material : ignoreList) { + set.add(material.getMaterial()); + } + attribute = attribute.fulfill(1); + } + return new LocationTag(getTargetBlockSafe(set, range)).getObjectAttribute(attribute); } - return new LocationTag(getTargetBlockSafe(set, range)).getAttribute(attribute); - } + }); // <--[tag] // @attribute @@ -1790,10 +1851,13 @@ else if (mtr.angle == BlockFace.EAST) { // Returns the location of what the entity is standing on. // Works with offline players. // --> - if (attribute.startsWith("location.standing_on")) { - return new LocationTag(entity.getLocation().clone().add(0, -0.5f, 0)) - .getAttribute(attribute.fulfill(2)); - } + registerSpawnedOnlyTag("location.standing_on", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new LocationTag(entity.getLocation().clone().add(0, -0.5f, 0)) + .getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -1803,10 +1867,13 @@ else if (mtr.angle == BlockFace.EAST) { // Returns the location of the entity. // Works with offline players. // --> - if (attribute.startsWith("location")) { - return new LocationTag(entity.getLocation()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("location", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new LocationTag(entity.getLocation()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1815,10 +1882,13 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns the entity's body yaw (separate from head yaw). // --> - if (attribute.startsWith("body_yaw")) { - return new ElementTag(NMSHandler.getEntityHelper().getBaseYaw(entity)) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("body_yaw", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(NMSHandler.getEntityHelper().getBaseYaw(entity)) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1828,10 +1898,13 @@ else if (mtr.angle == BlockFace.EAST) { // Returns the movement velocity of the entity. // Note: Does not accurately calculate player clientside movement velocity. // --> - if (attribute.startsWith("velocity")) { - return new LocationTag(entity.getVelocity().toLocation(entity.getWorld())) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("velocity", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new LocationTag(entity.getVelocity().toLocation(entity.getWorld())) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1840,10 +1913,13 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns the world the entity is in. Works with offline players. // --> - if (attribute.startsWith("world")) { - return new WorldTag(entity.getWorld()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("world", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new WorldTag(entity.getWorld()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); ///////////////////// @@ -1857,12 +1933,16 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns whether the entity can pick up items. // --> - if (attribute.startsWith("can_pickup_items")) { - if (isLivingEntity()) { - return new ElementTag(getLivingEntity().getCanPickupItems()) - .getAttribute(attribute.fulfill(1)); + registerSpawnedOnlyTag("can_pickup_items", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (isLivingEntity()) { + return new ElementTag(getLivingEntity().getCanPickupItems()) + .getObjectAttribute(attribute.fulfill(1)); + } + return null; } - } + }); // <--[tag] // @attribute @@ -1871,10 +1951,16 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns the material of a fallingblock-type entity. // --> - if (attribute.startsWith("fallingblock_material") && entity instanceof FallingBlock) { - return new MaterialTag(NMSHandler.getEntityHelper().getBlockDataFor((FallingBlock) entity)) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("fallingblock_material", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!(entity instanceof FallingBlock)) { + return null; + } + return new MaterialTag(NMSHandler.getEntityHelper().getBlockDataFor((FallingBlock) entity)) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1883,10 +1969,13 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns how far the entity has fallen. // --> - if (attribute.startsWith("fall_distance")) { - return new ElementTag(entity.getFallDistance()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("fall_distance", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(entity.getFallDistance()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1895,10 +1984,13 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns the duration for which the entity will remain on fire // --> - if (attribute.startsWith("fire_time")) { - return new DurationTag(entity.getFireTicks() / 20) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("fire_time", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new DurationTag(entity.getFireTicks() / 20) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1907,9 +1999,12 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns whether the entity is currently ablaze or not. // --> - if (attribute.startsWith("on_fire")) { - return new ElementTag(entity.getFireTicks() > 0).getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("on_fire", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(entity.getFireTicks() > 0).getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1918,12 +2013,17 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns the leash holder of entity. // --> - if (attribute.startsWith("leash_holder") || attribute.startsWith("get_leash_holder")) { - if (isLivingEntity() && getLivingEntity().isLeashed()) { - return new EntityTag(getLivingEntity().getLeashHolder()) - .getAttribute(attribute.fulfill(1)); + registerSpawnedOnlyTag("leash_holder", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (isLivingEntity() && getLivingEntity().isLeashed()) { + return new EntityTag(getLivingEntity().getLeashHolder()) + .getObjectAttribute(attribute.fulfill(1)); + } + return null; } - } + }); + registerSpawnedOnlyTag("get_leash_holder", tagProcessor.registeredObjectTags.get("leash_holder")); // <--[tag] // @attribute @@ -1932,13 +2032,17 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns a list of the entity's passengers, if any. // --> - if (attribute.startsWith("passengers") || attribute.startsWith("get_passengers")) { - ArrayList passengers = new ArrayList<>(); - for (Entity ent : entity.getPassengers()) { - passengers.add(new EntityTag(ent)); + registerSpawnedOnlyTag("passengers", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + ArrayList passengers = new ArrayList<>(); + for (Entity ent : entity.getPassengers()) { + passengers.add(new EntityTag(ent)); + } + return new ListTag(passengers).getObjectAttribute(attribute.fulfill(1)); } - return new ListTag(passengers).getAttribute(attribute.fulfill(1)); - } + }); + registerSpawnedOnlyTag("get_passengers", tagProcessor.registeredObjectTags.get("passengers")); // <--[tag] // @attribute @@ -1947,12 +2051,17 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns the entity's passenger, if any. // --> - if (attribute.startsWith("passenger") || attribute.startsWith("get_passenger")) { - if (!entity.isEmpty()) { - return new EntityTag(entity.getPassenger()) - .getAttribute(attribute.fulfill(1)); + registerSpawnedOnlyTag("passenger", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!entity.isEmpty()) { + return new EntityTag(entity.getPassenger()) + .getObjectAttribute(attribute.fulfill(1)); + } + return null; } - } + }); + registerSpawnedOnlyTag("get_passenger", tagProcessor.registeredObjectTags.get("passenger")); // <--[tag] // @attribute @@ -1962,12 +2071,13 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns the entity's shooter, if any. // --> - if (attribute.startsWith("shooter") || - attribute.startsWith("get_shooter")) { - if (isProjectile() && hasShooter()) { - return getShooter().getAttribute(attribute.fulfill(1)); + registerSpawnedOnlyTag("shooter", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return getShooter().getObjectAttribute(attribute.fulfill(1)); } - } + }); + registerSpawnedOnlyTag("get_shooter", tagProcessor.registeredObjectTags.get("shooter")); // <--[tag] // @attribute @@ -1978,11 +2088,16 @@ else if (mtr.angle == BlockFace.EAST) { // NOTE: The returned entity will not be spawned within the world, // so most operations are invalid unless the entity is first spawned in. // --> - if (getLivingEntity() instanceof HumanEntity - && attribute.startsWith("left_shoulder")) { - return new EntityTag(((HumanEntity) getLivingEntity()).getShoulderEntityLeft()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("left_shoulder", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!(getLivingEntity() instanceof HumanEntity)) { + return null; + } + return new EntityTag(((HumanEntity) getLivingEntity()).getShoulderEntityLeft()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1993,11 +2108,16 @@ else if (mtr.angle == BlockFace.EAST) { // NOTE: The returned entity will not be spawned within the world, // so most operations are invalid unless the entity is first spawned in. // --> - if (getLivingEntity() instanceof HumanEntity - && attribute.startsWith("right_shoulder")) { - return new EntityTag(((HumanEntity) getLivingEntity()).getShoulderEntityRight()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("right_shoulder", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!(getLivingEntity() instanceof HumanEntity)) { + return null; + } + return new EntityTag(((HumanEntity) getLivingEntity()).getShoulderEntityRight()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2006,12 +2126,17 @@ else if (mtr.angle == BlockFace.EAST) { // @description // If the entity is in a vehicle, returns the vehicle as a EntityTag. // --> - if (attribute.startsWith("vehicle") || attribute.startsWith("get_vehicle")) { - if (entity.isInsideVehicle()) { - return new EntityTag(entity.getVehicle()) - .getAttribute(attribute.fulfill(1)); + registerSpawnedOnlyTag("vehicle", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (entity.isInsideVehicle()) { + return new EntityTag(entity.getVehicle()) + .getObjectAttribute(attribute.fulfill(1)); + } + return null; } - } + }); + registerSpawnedOnlyTag("get_vehicle", tagProcessor.registeredObjectTags.get("vehicle")); // <--[tag] // @attribute @@ -2020,10 +2145,13 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns whether the animal entity is capable of mating with another of its kind. // --> - if (attribute.startsWith("can_breed")) { - return new ElementTag(((Ageable) getLivingEntity()).canBreed()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("can_breed", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(((Ageable) getLivingEntity()).canBreed()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2032,10 +2160,14 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns whether the animal entity is trying to with another of its kind. // --> - if (attribute.startsWith("breeding") || attribute.startsWith("is_breeding")) { - return new ElementTag(NMSHandler.getEntityHelper().isBreeding((Animals) getLivingEntity())) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("breeding", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(NMSHandler.getEntityHelper().isBreeding((Animals) getLivingEntity())) + .getObjectAttribute(attribute.fulfill(1)); + } + }); + registerSpawnedOnlyTag("is_breeding", tagProcessor.registeredObjectTags.get("breeding")); // <--[tag] // @attribute @@ -2044,10 +2176,13 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns whether the entity has a passenger. // --> - if (attribute.startsWith("has_passenger")) { - return new ElementTag(!entity.isEmpty()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("has_passenger", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(!entity.isEmpty()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2056,10 +2191,14 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns whether the entity does not have a passenger. // --> - if (attribute.startsWith("empty") || attribute.startsWith("is_empty")) { - return new ElementTag(entity.isEmpty()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("is_empty", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(entity.isEmpty()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); + registerSpawnedOnlyTag("is_empty", tagProcessor.registeredObjectTags.get("empty")); // <--[tag] // @attribute @@ -2068,10 +2207,14 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns whether the entity is inside a vehicle. // --> - if (attribute.startsWith("inside_vehicle") || attribute.startsWith("is_inside_vehicle")) { - return new ElementTag(entity.isInsideVehicle()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("is_inside_vehicle", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(entity.isInsideVehicle()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); + registerSpawnedOnlyTag("inside_vehicle", tagProcessor.registeredObjectTags.get("is_inside_vehicle")); // <--[tag] // @attribute @@ -2080,10 +2223,14 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns whether the entity is leashed. // --> - if (attribute.startsWith("leashed") || attribute.startsWith("is_leashed")) { - return new ElementTag(isLivingEntity() && getLivingEntity().isLeashed()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("is_leashed", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(isLivingEntity() && getLivingEntity().isLeashed()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); + registerSpawnedOnlyTag("leashed", tagProcessor.registeredObjectTags.get("is_leashed")); // <--[tag] // @attribute @@ -2092,10 +2239,16 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns whether a sheep is sheared. // --> - if (attribute.startsWith("is_sheared") && getBukkitEntity() instanceof Sheep) { - return new ElementTag(((Sheep) getBukkitEntity()).isSheared()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("is_sheared", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!(getBukkitEntity() instanceof Sheep)) { + return null; + } + return new ElementTag(((Sheep) getBukkitEntity()).isSheared()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2104,10 +2257,14 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns whether the entity is supported by a block. // --> - if (attribute.startsWith("on_ground") || attribute.startsWith("is_on_ground")) { - return new ElementTag(entity.isOnGround()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("is_on_ground", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(entity.isOnGround()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); + registerSpawnedOnlyTag("on_ground", tagProcessor.registeredObjectTags.get("is_on_ground")); // <--[tag] // @attribute @@ -2116,10 +2273,14 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns whether the entity will not be removed completely when far away from players. // --> - if (attribute.startsWith("persistent") || attribute.startsWith("is_persistent")) { - return new ElementTag(isLivingEntity() && !getLivingEntity().getRemoveWhenFarAway()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("is_persistent", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(isLivingEntity() && !getLivingEntity().getRemoveWhenFarAway()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); + registerSpawnedOnlyTag("is_persistent", tagProcessor.registeredObjectTags.get("persistent")); // <--[tag] // @attribute @@ -2129,10 +2290,13 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns whether the entity is collidable. // --> - if (attribute.startsWith("is_collidable")) { - return new ElementTag(getLivingEntity().isCollidable()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("is_collidable", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getLivingEntity().isCollidable()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2141,10 +2305,13 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns the player that last killed the entity. // --> - if (attribute.startsWith("killer")) { - return getPlayerFrom(getLivingEntity().getKiller()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("killer", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return getPlayerFrom(getLivingEntity().getKiller()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2153,10 +2320,13 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns the amount of the last damage taken by the entity. // --> - if (attribute.startsWith("last_damage.amount")) { - return new ElementTag(getLivingEntity().getLastDamage()) - .getAttribute(attribute.fulfill(2)); - } + registerSpawnedOnlyTag("last_damage.amount", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getLivingEntity().getLastDamage()) + .getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -2165,11 +2335,16 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns the cause of the last damage taken by the entity. // --> - if (attribute.startsWith("last_damage.cause") - && entity.getLastDamageCause() != null) { - return new ElementTag(entity.getLastDamageCause().getCause().name()) - .getAttribute(attribute.fulfill(2)); - } + registerSpawnedOnlyTag("last_damage.cause", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (entity.getLastDamageCause() == null) { + return null; + } + return new ElementTag(entity.getLastDamageCause().getCause().name()) + .getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -2179,10 +2354,13 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns the duration of the last damage taken by the entity. // --> - if (attribute.startsWith("last_damage.duration")) { - return new DurationTag((long) getLivingEntity().getNoDamageTicks()) - .getAttribute(attribute.fulfill(2)); - } + registerSpawnedOnlyTag("last_damage.duration", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new DurationTag((long) getLivingEntity().getNoDamageTicks()) + .getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -2192,10 +2370,13 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns the maximum duration of the last damage taken by the entity. // --> - if (attribute.startsWith("last_damage.max_duration")) { - return new DurationTag((long) getLivingEntity().getMaximumNoDamageTicks()) - .getAttribute(attribute.fulfill(2)); - } + registerSpawnedOnlyTag("last_damage.max_duration", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new DurationTag((long) getLivingEntity().getMaximumNoDamageTicks()) + .getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -2205,10 +2386,13 @@ else if (mtr.angle == BlockFace.EAST) { // Returns the maximum duration of oxygen the entity can have. // Works with offline players. // --> - if (attribute.startsWith("oxygen.max")) { - return new DurationTag((long) getLivingEntity().getMaximumAir()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("oxygen.max", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new DurationTag((long) getLivingEntity().getMaximumAir()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2218,10 +2402,13 @@ else if (mtr.angle == BlockFace.EAST) { // Returns the duration of oxygen the entity has left. // Works with offline players. // --> - if (attribute.startsWith("oxygen")) { - return new DurationTag((long) getLivingEntity().getRemainingAir()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("oxygen", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new DurationTag((long) getLivingEntity().getRemainingAir()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2230,10 +2417,13 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns whether the entity despawns when away from players. // --> - if (attribute.startsWith("remove_when_far")) { - return new ElementTag(getLivingEntity().getRemoveWhenFarAway()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("remove_when_far", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getLivingEntity().getRemoveWhenFarAway()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2243,15 +2433,18 @@ else if (mtr.angle == BlockFace.EAST) { // Returns the target entity of the creature, if any. // Note: use for NPC's. // --> - if (attribute.startsWith("target")) { - if (getBukkitEntity() instanceof Creature) { - Entity target = ((Creature) getLivingEntity()).getTarget(); - if (target != null) { - return new EntityTag(target).getAttribute(attribute.fulfill(1)); + registerSpawnedOnlyTag("target", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (getBukkitEntity() instanceof Creature) { + Entity target = ((Creature) getLivingEntity()).getTarget(); + if (target != null) { + return new EntityTag(target).getObjectAttribute(attribute.fulfill(1)); + } } + return null; } - return null; - } + }); // <--[tag] // @attribute @@ -2260,10 +2453,13 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns how long the entity has lived. // --> - if (attribute.startsWith("time_lived")) { - return new DurationTag(entity.getTicksLived() / 20) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("time_lived", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new DurationTag(entity.getTicksLived() / 20) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2272,10 +2468,16 @@ else if (mtr.angle == BlockFace.EAST) { // @description // Returns how long before the item-type entity can be picked up by a player. // --> - if ((attribute.startsWith("pickup_delay") || attribute.startsWith("pickupdelay")) - && getBukkitEntity() instanceof Item) { - return new DurationTag(((Item) getBukkitEntity()).getPickupDelay() * 20).getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("pickup_delay", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!(getBukkitEntity() instanceof Item)) { + return null; + } + return new DurationTag(((Item) getBukkitEntity()).getPickupDelay() * 20).getObjectAttribute(attribute.fulfill(1)); + } + }); + registerSpawnedOnlyTag("pickupdelay", tagProcessor.registeredObjectTags.get("pickup_delay")); // <--[tag] // @attribute @@ -2284,12 +2486,15 @@ && getBukkitEntity() instanceof Item) { // @description // Returns whether or not the arrow/trident entity is in a block. // --> - if (attribute.startsWith("is_in_block")) { - if (getBukkitEntity() instanceof Arrow) { - return new ElementTag(((Arrow) getBukkitEntity()).isInBlock()).getAttribute(attribute.fulfill(1)); + registerSpawnedOnlyTag("is_in_block", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (getBukkitEntity() instanceof Arrow) { + return new ElementTag(((Arrow) getBukkitEntity()).isInBlock()).getObjectAttribute(attribute.fulfill(1)); + } + return null; } - return null; - } + }); // <--[tag] // @attribute @@ -2298,20 +2503,23 @@ && getBukkitEntity() instanceof Item) { // @description // Returns the location of the block that the arrow/trident or hanging entity is attached to. // --> - if (attribute.startsWith("attached_block")) { - if (getBukkitEntity() instanceof Arrow) { - Block attachedBlock = ((Arrow) getBukkitEntity()).getAttachedBlock(); - if (attachedBlock != null) { - return new LocationTag(attachedBlock.getLocation()).getAttribute(attribute.fulfill(1)); + registerSpawnedOnlyTag("attached_block", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (getBukkitEntity() instanceof Arrow) { + Block attachedBlock = ((Arrow) getBukkitEntity()).getAttachedBlock(); + if (attachedBlock != null) { + return new LocationTag(attachedBlock.getLocation()).getObjectAttribute(attribute.fulfill(1)); + } } + else if (getBukkitEntity() instanceof Hanging) { + Vector dir = ((Hanging) getBukkitEntity()).getAttachedFace().getDirection(); + return new LocationTag(getLocation().clone().add(dir.multiply(0.5))).getBlockLocation() + .getObjectAttribute(attribute.fulfill(1)); + } + return null; } - else if (getBukkitEntity() instanceof Hanging) { - Vector dir = ((Hanging) getBukkitEntity()).getAttachedFace().getDirection(); - return new LocationTag(getLocation().clone().add(dir.multiply(0.5))).getBlockLocation() - .getAttribute(attribute.fulfill(1)); - } - return null; - } + }); // <--[tag] // @attribute @@ -2321,10 +2529,13 @@ else if (getBukkitEntity() instanceof Hanging) { // @description // Returns whether this entity is gliding. // --> - if (attribute.startsWith("gliding")) { - return new ElementTag(getLivingEntity().isGliding()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("gliding", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getLivingEntity().isGliding()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2334,9 +2545,14 @@ else if (getBukkitEntity() instanceof Hanging) { // @description // Returns whether this entity is swimming. // --> - if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_13) && attribute.startsWith("swimming")) { - return new ElementTag(getLivingEntity().isSwimming()) - .getAttribute(attribute.fulfill(1)); + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_13)) { + registerSpawnedOnlyTag("swimming", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getLivingEntity().isSwimming()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); } // <--[tag] @@ -2347,10 +2563,13 @@ else if (getBukkitEntity() instanceof Hanging) { // @description // Returns whether this entity is glowing. // --> - if (attribute.startsWith("glowing")) { - return new ElementTag(getBukkitEntity().isGlowing()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("glowing", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getBukkitEntity().isGlowing()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); ///////////////////// // TYPE ATTRIBUTES @@ -2364,10 +2583,13 @@ else if (getBukkitEntity() instanceof Hanging) { // Returns whether the entity is a living-type entity (eg a cow or a player or anything else that lives, as specifically opposed to non-living entities like paintings, etc). // Not to be confused with the idea of being alive - see <@link tag EntityTag.is_spawned>. // --> - if (attribute.startsWith("is_living")) { - return new ElementTag(isLivingEntity()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("is_living", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(isLivingEntity()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2376,10 +2598,13 @@ else if (getBukkitEntity() instanceof Hanging) { // @description // Returns whether the entity is a hostile monster. // --> - if (attribute.startsWith("is_monster")) { - return new ElementTag(getBukkitEntity() instanceof Monster) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("is_monster", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getBukkitEntity() instanceof Monster) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2388,10 +2613,13 @@ else if (getBukkitEntity() instanceof Hanging) { // @description // Returns whether the entity is a mob (Not a player or NPC). // --> - if (attribute.startsWith("is_mob")) { - return new ElementTag(!isPlayer() && !isNPC() && isLivingEntity()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("is_mob", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(!isPlayer() && !isNPC() && isLivingEntity()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2400,10 +2628,13 @@ else if (getBukkitEntity() instanceof Hanging) { // @description // Returns whether the entity is a Citizens NPC. // --> - if (attribute.startsWith("is_npc")) { - return new ElementTag(isCitizensNPC()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("is_npc", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(isCitizensNPC()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2413,10 +2644,13 @@ else if (getBukkitEntity() instanceof Hanging) { // Returns whether the entity is a player. // Works with offline players. // --> - if (attribute.startsWith("is_player")) { - return new ElementTag(isPlayer()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("is_player", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(isPlayer()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2425,10 +2659,13 @@ else if (getBukkitEntity() instanceof Hanging) { // @description // Returns whether the entity is a projectile. // --> - if (attribute.startsWith("is_projectile")) { - return new ElementTag(isProjectile()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("is_projectile", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(isProjectile()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); ///////////////////// // PROPERTY ATTRIBUTES @@ -2444,10 +2681,14 @@ else if (getBukkitEntity() instanceof Hanging) { // <@link mechanism EntityTag.tame>, <@link mechanism EntityTag.owner>, // <@link tag EntityTag.is_tamed>, and <@link tag EntityTag.owner> // --> - if (attribute.startsWith("tameable") || attribute.startsWith("is_tameable")) { - return new ElementTag(EntityTame.describes(this)) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("tameable", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(EntityTame.describes(object)) + .getObjectAttribute(attribute.fulfill(1)); + } + }); + registerSpawnedOnlyTag("is_tameable", tagProcessor.registeredObjectTags.get("tameable")); // <--[tag] // @attribute @@ -2460,10 +2701,14 @@ else if (getBukkitEntity() instanceof Hanging) { // <@link tag EntityTag.is_baby>, <@link tag EntityTag.age>, // and <@link tag EntityTag.is_age_locked> // --> - if (attribute.startsWith("ageable") || attribute.startsWith("is_ageable")) { - return new ElementTag(EntityAge.describes(this)) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("ageable", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(EntityAge.describes(object)) + .getObjectAttribute(attribute.fulfill(1)); + } + }); + registerSpawnedOnlyTag("is_ageable", tagProcessor.registeredObjectTags.get("ageable")); // <--[tag] // @attribute @@ -2474,10 +2719,14 @@ else if (getBukkitEntity() instanceof Hanging) { // If this returns true, it will enable access to: // <@link mechanism EntityTag.color> and <@link tag EntityTag.color> // --> - if (attribute.startsWith("colorable") || attribute.startsWith("is_colorable")) { - return new ElementTag(EntityColor.describes(this)) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("colorable", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(EntityColor.describes(object)) + .getObjectAttribute(attribute.fulfill(1)); + } + }); + registerSpawnedOnlyTag("is_colorable", tagProcessor.registeredObjectTags.get("colorable")); // <--[tag] // @attribute @@ -2486,10 +2735,16 @@ else if (getBukkitEntity() instanceof Hanging) { // @description // Returns the experience value of this experience orb entity. // --> - if (attribute.startsWith("experience") && getBukkitEntity() instanceof ExperienceOrb) { - return new ElementTag(((ExperienceOrb) getBukkitEntity()).getExperience()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("experience", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!(getBukkitEntity() instanceof ExperienceOrb)) { + return null; + } + return new ElementTag(((ExperienceOrb) getBukkitEntity()).getExperience()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2498,10 +2753,16 @@ else if (getBukkitEntity() instanceof Hanging) { // @description // Returns the number of ticks until the explosion of the primed TNT. // --> - if (attribute.startsWith("fuse_ticks") && getBukkitEntity() instanceof TNTPrimed) { - return new ElementTag(((TNTPrimed) getBukkitEntity()).getFuseTicks()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("fuse_ticks", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!(getBukkitEntity() instanceof TNTPrimed)) { + return null; + } + return new ElementTag(((TNTPrimed) getBukkitEntity()).getFuseTicks()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2511,10 +2772,16 @@ else if (getBukkitEntity() instanceof Hanging) { // Returns the phase an EnderDragon is currently in. // Valid phases: <@link url https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/EnderDragon.Phase.html> // --> - if (attribute.startsWith("dragon_phase") && getBukkitEntity() instanceof EnderDragon) { - return new ElementTag(((EnderDragon) getLivingEntity()).getPhase().name()) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("dragon_phase", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!(getBukkitEntity() instanceof EnderDragon)) { + return null; + } + return new ElementTag(((EnderDragon) getLivingEntity()).getPhase().name()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2523,19 +2790,43 @@ else if (getBukkitEntity() instanceof Hanging) { // @description // Returns the entity's full description, including all properties. // --> - if (attribute.startsWith("describe")) { - String escript = getEntityScript(); - return new ElementTag("e@" + (escript != null && escript.length() > 0 ? escript : getEntityType().getLowercaseName()) - + PropertyParser.getPropertiesString(this)) - .getAttribute(attribute.fulfill(1)); - } + registerSpawnedOnlyTag("describe", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + String escript = getEntityScript(); + return new ElementTag("e@" + (escript != null && escript.length() > 0 ? escript : getEntityType().getLowercaseName()) + + PropertyParser.getPropertiesString((EntityTag) object)) + .getObjectAttribute(attribute.fulfill(1)); + } + }); + } - String returned = CoreUtilities.autoPropertyTag(this, attribute); - if (returned != null) { - return returned; - } + public static ObjectTagProcessor tagProcessor = new ObjectTagProcessor(); + + public static void registerSpawnedOnlyTag(String name, TagRunnable.ObjectForm runnable) { + TagRunnable.ObjectForm newRunnable = new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!((EntityTag) object).isSpawned()) { + if (!attribute.hasAlternative()) { + com.denizenscript.denizen.utilities.debugging.Debug.echoError("Entity is not spawned, but tag '" + attribute.getAttributeWithoutContext(1) + "' requires the entity be spawned, for entity: " + object.debuggable()); + } + return null; + } + return runnable.run(attribute, object); + } + }; + newRunnable.name = runnable.name; + registerTag(name, newRunnable); + } + + public static void registerTag(String name, TagRunnable.ObjectForm runnable) { + tagProcessor.registerTag(name, runnable); + } - return new ElementTag(identify()).getAttribute(attribute); + @Override + public ObjectTag getObjectAttribute(Attribute attribute) { + return tagProcessor.getObjectAttribute(this, attribute); } private ArrayList mechanisms = new ArrayList<>(); diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/InventoryTag.java b/plugin/src/main/java/com/denizenscript/denizen/objects/InventoryTag.java index 4e7bd1f4b8..15c9270d1e 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/InventoryTag.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/InventoryTag.java @@ -20,6 +20,7 @@ import com.denizenscript.denizencore.objects.properties.PropertyParser; import com.denizenscript.denizencore.scripts.ScriptRegistry; import com.denizenscript.denizencore.tags.Attribute; +import com.denizenscript.denizencore.tags.ObjectTagProcessor; import com.denizenscript.denizencore.tags.TagContext; import com.denizenscript.denizencore.utilities.CoreUtilities; import net.citizensnpcs.api.CitizensAPI; @@ -1281,23 +1282,26 @@ public String getAttribute(Attribute attribute) { // @description // Returns the number of empty slots in an inventory. // --> - if (attribute.startsWith("empty_slots")) { - InventoryTag dummyInv; - if (inventory.getType() == InventoryType.PLAYER) { - dummyInv = new InventoryTag(Bukkit.createInventory(null, InventoryType.CHEST)); - ItemStack[] contents = getStorageContents(); - dummyInv.setSize(contents.length); - if (contents.length != dummyInv.getSize()) { - contents = Arrays.copyOf(contents, dummyInv.getSize()); + registerTag("empty_slots", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + InventoryTag dummyInv; + if (inventory.getType() == InventoryType.PLAYER) { + dummyInv = new InventoryTag(Bukkit.createInventory(null, InventoryType.CHEST)); + ItemStack[] contents = getStorageContents(); + dummyInv.setSize(contents.length); + if (contents.length != dummyInv.getSize()) { + contents = Arrays.copyOf(contents, dummyInv.getSize()); + } + dummyInv.setContents(contents); } - dummyInv.setContents(contents); - } - else { - dummyInv = new InventoryTag(inventory); + else { + dummyInv = new InventoryTag(inventory); + } + int full = dummyInv.count(null, true); + return new ElementTag(dummyInv.getSize() - full).getObjectAttribute(attribute.fulfill(1)); } - int full = dummyInv.count(null, true); - return new ElementTag(dummyInv.getSize() - full).getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute |...]> @@ -1305,66 +1309,72 @@ public String getAttribute(Attribute attribute) { // @description // Returns whether the inventory can fit an item. // --> - if (attribute.startsWith("can_fit") && attribute.hasContext(1)) { - List items = ListTag.valueOf(attribute.getContext(1)).filter(ItemTag.class, attribute.getScriptEntry()); - if (items == null || items.isEmpty()) { - return null; - } - int attribs = 1; - - InventoryType type = inventory.getType(); - InventoryTag dummyInv = new InventoryTag(Bukkit.createInventory(null, type == InventoryType.PLAYER ? InventoryType.CHEST : type, NMSHandler.getInstance().getTitle(inventory))); - ItemStack[] contents = getStorageContents(); - if (dummyInv.getInventoryType() == InventoryType.CHEST) { - dummyInv.setSize(contents.length); - } - if (contents.length != dummyInv.getSize()) { - contents = Arrays.copyOf(contents, dummyInv.getSize()); - } - dummyInv.setContents(contents); - - // <--[tag] - // @attribute ].count> - // @returns ElementTag(Number) - // @description - // Returns the total count of how many times an item can fit into an inventory. - // --> - if (attribute.getAttribute(2).startsWith("count")) { - attribs = 2; - ItemStack toAdd = items.get(0).getItemStack().clone(); - int totalCount = 64 * 64 * 4; // Technically nothing stops us from ridiculous numbers in an ItemStack amount. - toAdd.setAmount(totalCount); - List leftovers = dummyInv.addWithLeftovers(0, true, toAdd); - int result = 0; - if (leftovers.size() > 0) { - result += leftovers.get(0).getAmount(); - } - return new ElementTag(totalCount - result).getAttribute(attribute.fulfill(attribs)); - } - - // <--[tag] - // @attribute ].quantity[<#>]> - // @returns ElementTag(Boolean) - // @description - // Returns whether the inventory can fit a certain quantity of an item. - // --> - if ((attribute.getAttribute(2).startsWith("quantity") || attribute.getAttribute(2).startsWith("qty")) && - attribute.hasContext(2) && - ArgumentHelper.matchesInteger(attribute.getContext(2))) { - int qty = attribute.getIntContext(2); - attribs = 2; - items.get(0).setAmount(qty); - } - - // NOTE: Could just also convert items to an array and pass it all in at once... - for (ItemTag itm : items) { - List leftovers = dummyInv.addWithLeftovers(0, true, itm.getItemStack()); - if (!leftovers.isEmpty()) { - return new ElementTag(false).getAttribute(attribute.fulfill(attribs)); - } - } - return new ElementTag(true).getAttribute(attribute.fulfill(attribs)); - } + registerTag("can_fit", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; + } + List items = ListTag.valueOf(attribute.getContext(1)).filter(ItemTag.class, attribute.getScriptEntry()); + if (items == null || items.isEmpty()) { + return null; + } + int attribs = 1; + + InventoryType type = inventory.getType(); + InventoryTag dummyInv = new InventoryTag(Bukkit.createInventory(null, type == InventoryType.PLAYER ? InventoryType.CHEST : type, NMSHandler.getInstance().getTitle(inventory))); + ItemStack[] contents = getStorageContents(); + if (dummyInv.getInventoryType() == InventoryType.CHEST) { + dummyInv.setSize(contents.length); + } + if (contents.length != dummyInv.getSize()) { + contents = Arrays.copyOf(contents, dummyInv.getSize()); + } + dummyInv.setContents(contents); + + // <--[tag] + // @attribute ].count> + // @returns ElementTag(Number) + // @description + // Returns the total count of how many times an item can fit into an inventory. + // --> + if (attribute.getAttribute(2).startsWith("count")) { + attribs = 2; + ItemStack toAdd = items.get(0).getItemStack().clone(); + int totalCount = 64 * 64 * 4; // Technically nothing stops us from ridiculous numbers in an ItemStack amount. + toAdd.setAmount(totalCount); + List leftovers = dummyInv.addWithLeftovers(0, true, toAdd); + int result = 0; + if (leftovers.size() > 0) { + result += leftovers.get(0).getAmount(); + } + return new ElementTag(totalCount - result).getObjectAttribute(attribute.fulfill(attribs)); + } + + // <--[tag] + // @attribute ].quantity[<#>]> + // @returns ElementTag(Boolean) + // @description + // Returns whether the inventory can fit a certain quantity of an item. + // --> + if ((attribute.getAttribute(2).startsWith("quantity") || attribute.getAttribute(2).startsWith("qty")) && + attribute.hasContext(2) && + ArgumentHelper.matchesInteger(attribute.getContext(2))) { + int qty = attribute.getIntContext(2); + attribs = 2; + items.get(0).setAmount(qty); + } + + // NOTE: Could just also convert items to an array and pass it all in at once... + for (ItemTag itm : items) { + List leftovers = dummyInv.addWithLeftovers(0, true, itm.getItemStack()); + if (!leftovers.isEmpty()) { + return new ElementTag(false).getObjectAttribute(attribute.fulfill(attribs)); + } + } + return new ElementTag(true).getObjectAttribute(attribute.fulfill(attribs)); + } + }); // <--[tag] // @attribute ]> @@ -1372,36 +1382,41 @@ public String getAttribute(Attribute attribute) { // @description // Returns the InventoryTag with an item added. // --> - if (attribute.startsWith("include") && attribute.hasContext(1) - && ItemTag.matches(attribute.getContext(1))) { - ItemTag item = ItemTag.valueOf(attribute.getContext(1), attribute.context); - if (item == null) { - return null; - } - int attribs = 1; - int qty = 1; - - InventoryTag dummyInv = new InventoryTag(Bukkit.createInventory(null, inventory.getType(), NMSHandler.getInstance().getTitle(inventory))); - if (inventory.getType() == InventoryType.CHEST) { - dummyInv.setSize(inventory.getSize()); - } - dummyInv.setContents(getContents()); + registerTag("include", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1) || !ItemTag.matches(attribute.getContext(1))) { + return null; + } + ItemTag item = ItemTag.valueOf(attribute.getContext(1), attribute.context); + if (item == null) { + return null; + } + int attribs = 1; + int qty = 1; - // <--[tag] - // @attribute ].quantity[<#>]> - // @returns InventoryTag - // @description - // Returns the InventoryTag with a certain quantity of an item added. - // --> - if ((attribute.getAttribute(2).startsWith("quantity") || attribute.getAttribute(2).startsWith("qty")) && - attribute.hasContext(2) && ArgumentHelper.matchesInteger(attribute.getContext(2))) { - qty = attribute.getIntContext(2); - attribs = 2; + InventoryTag dummyInv = new InventoryTag(Bukkit.createInventory(null, inventory.getType(), NMSHandler.getInstance().getTitle(inventory))); + if (inventory.getType() == InventoryType.CHEST) { + dummyInv.setSize(inventory.getSize()); + } + dummyInv.setContents(getContents()); + + // <--[tag] + // @attribute ].quantity[<#>]> + // @returns InventoryTag + // @description + // Returns the InventoryTag with a certain quantity of an item added. + // --> + if ((attribute.getAttribute(2).startsWith("quantity") || attribute.getAttribute(2).startsWith("qty")) && + attribute.hasContext(2) && ArgumentHelper.matchesInteger(attribute.getContext(2))) { + qty = attribute.getIntContext(2); + attribs = 2; + } + item.setAmount(qty); + dummyInv.add(0, item.getItemStack()); + return dummyInv.getObjectAttribute(attribute.fulfill(attribs)); } - item.setAmount(qty); - dummyInv.add(0, item.getItemStack()); - return dummyInv.getAttribute(attribute.fulfill(attribs)); - } + }); // <--[tag] // @attribute @@ -1409,16 +1424,19 @@ public String getAttribute(Attribute attribute) { // @description // Returns whether the inventory is empty. // --> - if (attribute.startsWith("is_empty")) { - boolean empty = true; - for (ItemStack item : getStorageContents()) { - if (item != null && item.getType() != Material.AIR) { - empty = false; - break; + registerTag("is_empty", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + boolean empty = true; + for (ItemStack item : getStorageContents()) { + if (item != null && item.getType() != Material.AIR) { + empty = false; + break; + } } + return new ElementTag(empty).getObjectAttribute(attribute.fulfill(1)); } - return new ElementTag(empty).getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute @@ -1426,309 +1444,364 @@ public String getAttribute(Attribute attribute) { // @description // Returns whether the inventory is completely full. // --> - if (attribute.startsWith("is_full")) { - boolean full = true; - - for (ItemStack item : getStorageContents()) { - if ((item == null) || - (item.getType() == Material.AIR) || - (item.getAmount() < item.getMaxStackSize())) { - full = false; - break; + registerTag("is_full", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + boolean full = true; + + for (ItemStack item : getStorageContents()) { + if ((item == null) || + (item.getType() == Material.AIR) || + (item.getAmount() < item.getMaxStackSize())) { + full = false; + break; + } } + return new ElementTag(full).getObjectAttribute(attribute.fulfill(1)); } - return new ElementTag(full).getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] - // @attribute ]> + // @attribute |...]> // @returns ElementTag(Boolean) // @description - // Returns whether the inventory contains an item with the specified display - // name. Use 'strict:' in front of the search element to ensure the display - // name is EXACTLY the search element, otherwise the searching will only - // check if the search element is contained in the display name. + // Returns whether the inventory contains all of the specified items. // --> - if (attribute.startsWith("contains.display") && attribute.hasContext(2)) { - String search_string = attribute.getContext(2); - boolean strict = false; - if (CoreUtilities.toLowerCase(search_string).startsWith("strict:") && search_string.length() > 7) { - strict = true; - search_string = search_string.substring(7); - } - if (search_string.length() == 0) { - return null; - } - int qty = 1; - int attribs = 2; - - // <--[tag] - // @attribute ].quantity[<#>]> - // @returns ElementTag(Boolean) - // @description - // Returns whether the inventory contains a certain quantity of an item with the - // specified display name. Use 'strict:' in front of the search element to ensure - // the display name is EXACTLY the search element, otherwise the searching will only - // check if the search element is contained in the display name. - // --> - if ((attribute.getAttribute(3).startsWith("quantity") || attribute.getAttribute(3).startsWith("qty")) && - attribute.hasContext(3) && - ArgumentHelper.matchesInteger(attribute.getContext(3))) { - qty = attribute.getIntContext(3); - attribs = 3; - } - - int found_items = 0; - - if (strict) { - for (ItemStack item : getContents()) { - if (item != null && item.getType() == Material.WRITTEN_BOOK - && ((BookMeta) item.getItemMeta()).getTitle().equalsIgnoreCase(search_string)) { - found_items += item.getAmount(); - if (found_items >= qty) { - break; - } + registerTag("contains", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + // <--[tag] + // @attribute ]> + // @returns ElementTag(Boolean) + // @description + // Returns whether the inventory contains an item with the specified display + // name. Use 'strict:' in front of the search element to ensure the display + // name is EXACTLY the search element, otherwise the searching will only + // check if the search element is contained in the display name. + // --> + if (attribute.getAttributeWithoutContext(2).equals("display")) { + if (!attribute.hasContext(2)) { + return null; } - else if (item != null && item.hasItemMeta() && item.getItemMeta().hasDisplayName() && - item.getItemMeta().getDisplayName().equalsIgnoreCase(search_string)) { - found_items += item.getAmount(); - if (found_items >= qty) { - break; - } + String search_string = attribute.getContext(2); + boolean strict = false; + if (CoreUtilities.toLowerCase(search_string).startsWith("strict:") && search_string.length() > 7) { + strict = true; + search_string = search_string.substring(7); } - } - } - else { - for (ItemStack item : getContents()) { - if (item != null && item.getType() == Material.WRITTEN_BOOK - && CoreUtilities.toLowerCase(((BookMeta) item.getItemMeta()).getTitle()) - .contains(CoreUtilities.toLowerCase(search_string))) { - found_items += item.getAmount(); - if (found_items >= qty) { - break; + if (search_string.length() == 0) { + return null; + } + int qty = 1; + int attribs = 2; + + // <--[tag] + // @attribute ].quantity[<#>]> + // @returns ElementTag(Boolean) + // @description + // Returns whether the inventory contains a certain quantity of an item with the + // specified display name. Use 'strict:' in front of the search element to ensure + // the display name is EXACTLY the search element, otherwise the searching will only + // check if the search element is contained in the display name. + // --> + if ((attribute.getAttribute(3).startsWith("quantity") || attribute.getAttribute(3).startsWith("qty")) && + attribute.hasContext(3) && + ArgumentHelper.matchesInteger(attribute.getContext(3))) { + qty = attribute.getIntContext(3); + attribs = 3; + } + + int found_items = 0; + + if (strict) { + for (ItemStack item : getContents()) { + if (item != null && item.getType() == Material.WRITTEN_BOOK + && ((BookMeta) item.getItemMeta()).getTitle().equalsIgnoreCase(search_string)) { + found_items += item.getAmount(); + if (found_items >= qty) { + break; + } + } + else if (item != null && item.hasItemMeta() && item.getItemMeta().hasDisplayName() && + item.getItemMeta().getDisplayName().equalsIgnoreCase(search_string)) { + found_items += item.getAmount(); + if (found_items >= qty) { + break; + } + } } } - else if (item != null && item.hasItemMeta() && item.getItemMeta().hasDisplayName() && - CoreUtilities.toLowerCase(item.getItemMeta().getDisplayName()) + else { + for (ItemStack item : getContents()) { + if (item != null && item.getType() == Material.WRITTEN_BOOK + && CoreUtilities.toLowerCase(((BookMeta) item.getItemMeta()).getTitle()) .contains(CoreUtilities.toLowerCase(search_string))) { - found_items += item.getAmount(); - if (found_items >= qty) { - break; + found_items += item.getAmount(); + if (found_items >= qty) { + break; + } + } + else if (item != null && item.hasItemMeta() && item.getItemMeta().hasDisplayName() && + CoreUtilities.toLowerCase(item.getItemMeta().getDisplayName()) + .contains(CoreUtilities.toLowerCase(search_string))) { + found_items += item.getAmount(); + if (found_items >= qty) { + break; + } + } } } + + return new ElementTag(found_items >= qty).getObjectAttribute(attribute.fulfill(attribs)); } - } + // <--[tag] + // @attribute |...]> + // @returns ElementTag(Boolean) + // @description + // Returns whether the inventory contains an item with the specified lore. + // Use 'strict:' in front of the search elements to ensure all lore lines + // are EXACTLY the search elements, otherwise the searching will only + // check if the search elements are contained in the lore. + // --> + if (attribute.getAttributeWithoutContext(2).equals("lore")) { + if (!attribute.hasContext(2)) { + return null; + } + String search_string = attribute.getContext(2); + boolean strict = false; + if (CoreUtilities.toLowerCase(search_string).startsWith("strict:")) { + strict = true; + search_string = search_string.substring(7); + } + if (search_string.length() == 0) { + return null; + } + ListTag lore = ListTag.valueOf(search_string); + int qty = 1; + int attribs = 2; + + // <--[tag] + // @attribute |...].quantity[<#>]> + // @returns ElementTag(Boolean) + // @description + // Returns whether the inventory contains a certain quantity of an item + // with the specified lore. Use 'strict:' in front of the search elements + // to ensure all lore lines are EXACTLY the search elements, otherwise the + // searching will only check if the search elements are contained in the lore. + // --> + if ((attribute.getAttribute(3).startsWith("quantity") || attribute.getAttribute(3).startsWith("qty")) && + attribute.hasContext(3) && + ArgumentHelper.matchesInteger(attribute.getContext(3))) { + qty = attribute.getIntContext(3); + attribs = 3; + } - return new ElementTag(found_items >= qty).getAttribute(attribute.fulfill(attribs)); - } + int found_items = 0; - // <--[tag] - // @attribute |...]> - // @returns ElementTag(Boolean) - // @description - // Returns whether the inventory contains an item with the specified lore. - // Use 'strict:' in front of the search elements to ensure all lore lines - // are EXACTLY the search elements, otherwise the searching will only - // check if the search elements are contained in the lore. - // --> - if (attribute.startsWith("contains.lore") && attribute.hasContext(2)) { - String search_string = attribute.getContext(2); - boolean strict = false; - if (CoreUtilities.toLowerCase(search_string).startsWith("strict:")) { - strict = true; - search_string = search_string.substring(7); - } - if (search_string.length() == 0) { - return null; - } - ListTag lore = ListTag.valueOf(search_string); - int qty = 1; - int attribs = 2; - - // <--[tag] - // @attribute |...].quantity[<#>]> - // @returns ElementTag(Boolean) - // @description - // Returns whether the inventory contains a certain quantity of an item - // with the specified lore. Use 'strict:' in front of the search elements - // to ensure all lore lines are EXACTLY the search elements, otherwise the - // searching will only check if the search elements are contained in the lore. - // --> - if ((attribute.getAttribute(3).startsWith("quantity") || attribute.getAttribute(3).startsWith("qty")) && - attribute.hasContext(3) && - ArgumentHelper.matchesInteger(attribute.getContext(3))) { - qty = attribute.getIntContext(3); - attribs = 3; - } - - int found_items = 0; - - if (strict) { - strict_items: - for (ItemStack item : getContents()) { - if (item != null && item.hasItemMeta() && item.getItemMeta().hasLore()) { - List item_lore = item.getItemMeta().getLore(); - if (lore.size() != item_lore.size()) { - continue; + if (strict) { + strict_items: + for (ItemStack item : getContents()) { + if (item != null && item.hasItemMeta() && item.getItemMeta().hasLore()) { + List item_lore = item.getItemMeta().getLore(); + if (lore.size() != item_lore.size()) { + continue; + } + for (int i = 0; i < item_lore.size(); i++) { + if (lore.get(i).equalsIgnoreCase(item_lore.get(i))) { + if (i == lore.size()) { + found_items += item.getAmount(); + if (found_items >= qty) { + break strict_items; + } + } + } + else { + continue strict_items; + } + } + } } - for (int i = 0; i < item_lore.size(); i++) { - if (lore.get(i).equalsIgnoreCase(item_lore.get(i))) { - if (i == lore.size()) { + } + else { + for (ItemStack item : getContents()) { + if (item != null && item.hasItemMeta() && item.getItemMeta().hasLore()) { + List item_lore = item.getItemMeta().getLore(); + int loreCount = 0; + lines: + for (String line : lore) { + for (String item_line : item_lore) { + if (CoreUtilities.toLowerCase(item_line).contains(CoreUtilities.toLowerCase(line))) { + loreCount++; + continue lines; + } + } + } + if (loreCount == lore.size()) { found_items += item.getAmount(); if (found_items >= qty) { - break strict_items; + break; } } } - else { - continue strict_items; - } } } + + return new ElementTag(found_items >= qty).getObjectAttribute(attribute.fulfill(attribs)); } - } - else { - for (ItemStack item : getContents()) { - if (item != null && item.hasItemMeta() && item.getItemMeta().hasLore()) { - List item_lore = item.getItemMeta().getLore(); - int loreCount = 0; - lines: - for (String line : lore) { - for (String item_line : item_lore) { - if (CoreUtilities.toLowerCase(item_line).contains(CoreUtilities.toLowerCase(line))) { - loreCount++; - continue lines; - } - } - } - if (loreCount == lore.size()) { + // <--[tag] + // @attribute ]> + // @returns ElementTag(Boolean) + // @description + // Returns whether the inventory contains an item with the specified scriptname. + // --> + if (attribute.getAttributeWithoutContext(2).equals("scriptname")) { + if (!attribute.hasContext(2)) { + return null; + } + String scrName = attribute.getContext(2); + int qty = 1; + int attribs = 2; + + // <--[tag] + // @attribute ].quantity[<#>]> + // @returns ElementTag(Boolean) + // @description + // Returns whether the inventory contains a certain quantity of an item with the specified scriptname. + // --> + if ((attribute.getAttribute(3).startsWith("quantity") || attribute.getAttribute(3).startsWith("qty")) && + attribute.hasContext(3) && + ArgumentHelper.matchesInteger(attribute.getContext(3))) { + qty = attribute.getIntContext(3); + attribs = 3; + } + + int found_items = 0; + + for (ItemStack item : getContents()) { + if (item != null && scrName.equalsIgnoreCase(new ItemTag(item).getScriptName())) { found_items += item.getAmount(); if (found_items >= qty) { break; } } } + + return new ElementTag(found_items >= qty).getObjectAttribute(attribute.fulfill(attribs)); } - } + // <--[tag] + // @attribute ]> + // @returns ElementTag(Boolean) + // @description + // Returns whether the inventory contains an item with the specified key. + // --> + if (attribute.getAttributeWithoutContext(2).equals("nbt")) { + if (!attribute.hasContext(2)) { + return null; + } + String keyName = attribute.getContext(2); + int qty = 1; + int attribs = 2; + + // <--[tag] + // @attribute ].quantity[<#>]> + // @returns ElementTag(Boolean) + // @description + // Returns whether the inventory contains a certain quantity of an item with the specified key. + // --> + if ((attribute.getAttribute(3).startsWith("quantity") || attribute.getAttribute(3).startsWith("qty")) && + attribute.hasContext(3) && + ArgumentHelper.matchesInteger(attribute.getContext(3))) { + qty = attribute.getIntContext(3); + attribs = 3; + } - return new ElementTag(found_items >= qty).getAttribute(attribute.fulfill(attribs)); - } + int found_items = 0; - // <--[tag] - // @attribute ]> - // @returns ElementTag(Boolean) - // @description - // Returns whether the inventory contains an item with the specified scriptname. - // --> - if (attribute.startsWith("contains.scriptname") && attribute.hasContext(2)) { - String scrName = attribute.getContext(2); - int qty = 1; - int attribs = 2; - - // <--[tag] - // @attribute ].quantity[<#>]> - // @returns ElementTag(Boolean) - // @description - // Returns whether the inventory contains a certain quantity of an item with the specified scriptname. - // --> - if ((attribute.getAttribute(3).startsWith("quantity") || attribute.getAttribute(3).startsWith("qty")) && - attribute.hasContext(3) && - ArgumentHelper.matchesInteger(attribute.getContext(3))) { - qty = attribute.getIntContext(3); - attribs = 3; - } - - int found_items = 0; - - for (ItemStack item : getContents()) { - if (item != null && scrName.equalsIgnoreCase(new ItemTag(item).getScriptName())) { - found_items += item.getAmount(); - if (found_items >= qty) { - break; + for (ItemStack item : getContents()) { + if (CustomNBT.hasCustomNBT(item, keyName, CustomNBT.KEY_DENIZEN)) { + found_items += item.getAmount(); + if (found_items >= qty) { + break; + } + } } + + return new ElementTag(found_items >= qty).getObjectAttribute(attribute.fulfill(attribs)); } - } + // <--[tag] + // @attribute ]> + // @returns ElementTag(Boolean) + // @description + // Returns whether the inventory contains an item with the specified material. + // --> + if (attribute.getAttributeWithoutContext(2).equals("material")) { + if (!attribute.hasContext(2) || !MaterialTag.matches(attribute.getContext(2))) { + return null; + } + MaterialTag material = MaterialTag.valueOf(attribute.getContext(2)); + int qty = 1; + int attribs = 2; + + // <--[tag] + // @attribute ].quantity[<#>]> + // @returns ElementTag(Boolean) + // @description + // Returns whether the inventory contains a certain quantity of an item with the + // specified material. + // --> + if ((attribute.getAttribute(3).startsWith("quantity") || attribute.getAttribute(3).startsWith("qty")) && + attribute.hasContext(3) && + ArgumentHelper.matchesInteger(attribute.getContext(3))) { + qty = attribute.getIntContext(3); + attribs = 3; + } - return new ElementTag(found_items >= qty).getAttribute(attribute.fulfill(attribs)); - } + int found_items = 0; - // <--[tag] - // @attribute ]> - // @returns ElementTag(Boolean) - // @description - // Returns whether the inventory contains an item with the specified key. - // --> - if (attribute.startsWith("contains.nbt") && attribute.hasContext(2)) { - String keyName = attribute.getContext(2); - int qty = 1; - int attribs = 2; - - // <--[tag] - // @attribute ].quantity[<#>]> - // @returns ElementTag(Boolean) - // @description - // Returns whether the inventory contains a certain quantity of an item with the specified key. - // --> - if ((attribute.getAttribute(3).startsWith("quantity") || attribute.getAttribute(3).startsWith("qty")) && - attribute.hasContext(3) && - ArgumentHelper.matchesInteger(attribute.getContext(3))) { - qty = attribute.getIntContext(3); - attribs = 3; - } - - int found_items = 0; - - for (ItemStack item : getContents()) { - if (CustomNBT.hasCustomNBT(item, keyName, CustomNBT.KEY_DENIZEN)) { - found_items += item.getAmount(); - if (found_items >= qty) { - break; + for (ItemStack item : getContents()) { + if (item != null && item.getType() == material.getMaterial()) { + found_items += item.getAmount(); + if (found_items >= qty) { + break; + } + } } - } - } - - return new ElementTag(found_items >= qty).getAttribute(attribute.fulfill(attribs)); - } - // <--[tag] - // @attribute ]> - // @returns ElementTag(Boolean) - // @description - // Returns whether the inventory contains an item with the specified material. - // --> - if (attribute.startsWith("contains.material") && attribute.hasContext(2) && - MaterialTag.matches(attribute.getContext(2))) { - MaterialTag material = MaterialTag.valueOf(attribute.getContext(2)); - int qty = 1; - int attribs = 2; - - // <--[tag] - // @attribute ].quantity[<#>]> - // @returns ElementTag(Boolean) - // @description - // Returns whether the inventory contains a certain quantity of an item with the - // specified material. - // --> - if ((attribute.getAttribute(3).startsWith("quantity") || attribute.getAttribute(3).startsWith("qty")) && - attribute.hasContext(3) && - ArgumentHelper.matchesInteger(attribute.getContext(3))) { - qty = attribute.getIntContext(3); - attribs = 3; - } - - int found_items = 0; - - for (ItemStack item : getContents()) { - if (item != null && item.getType() == material.getMaterial()) { - found_items += item.getAmount(); - if (found_items >= qty) { - break; + return new ElementTag(found_items >= qty).getObjectAttribute(attribute.fulfill(attribs)); + } + if (!attribute.hasContext(1)) { + return null; + } + ListTag list = ListTag.valueOf(attribute.getContext(1)); + if (list.isEmpty()) { + return null; + } + int qty = 1; + int attribs = 1; + + // <--[tag] + // @attribute |...].quantity[<#>]> + // @returns ElementTag(Boolean) + // @description + // Returns whether the inventory contains a certain quantity of all of the specified items. + // --> + if ((attribute.getAttribute(2).startsWith("quantity") || attribute.getAttribute(2).startsWith("qty")) + && attribute.hasContext(2) && ArgumentHelper.matchesInteger(attribute.getContext(2))) { + qty = attribute.getIntContext(2); + attribs = 2; + } + List contains = list.filter(ItemTag.class, attribute.getScriptEntry()); + if (contains.size() == list.size()) { + for (ItemTag item : contains) { + if (!containsItem(item, qty)) { + return new ElementTag(false).getObjectAttribute(attribute.fulfill(attribs)); + } } + return new ElementTag(true).getObjectAttribute(attribute.fulfill(attribs)); } + return new ElementTag(false).getObjectAttribute(attribute.fulfill(attribs)); } - - return new ElementTag(found_items >= qty).getAttribute(attribute.fulfill(attribs)); - } + }); // <--[tag] // @attribute |...]> @@ -1736,72 +1809,41 @@ else if (item != null && item.hasItemMeta() && item.getItemMeta().hasDisplayName // @description // Returns whether the inventory contains any of the specified items. // --> - if (attribute.startsWith("contains_any") && attribute.hasContext(1)) { - ListTag list = ListTag.valueOf(attribute.getContext(1)); - if (list.isEmpty()) { - return null; - } - int qty = 1; - int attribs = 1; - - // <--[tag] - // @attribute |...].quantity[<#>]> - // @returns ElementTag(Boolean) - // @description - // Returns whether the inventory contains a certain quantity of any of the specified items. - // --> - if ((attribute.getAttribute(2).startsWith("quantity") || attribute.getAttribute(2).startsWith("qty")) - && attribute.hasContext(2) && ArgumentHelper.matchesInteger(attribute.getContext(2))) { - qty = attribute.getIntContext(2); - attribs = 2; - } - List contains = list.filter(ItemTag.class, attribute.getScriptEntry()); - if (!contains.isEmpty()) { - for (ItemTag item : contains) { - if (containsItem(item, qty)) { - return new ElementTag(true).getAttribute(attribute.fulfill(attribs)); - } + registerTag("contains_any", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; } - } - return new ElementTag(false).getAttribute(attribute.fulfill(attribs)); - } - - // <--[tag] - // @attribute |...]> - // @returns ElementTag(Boolean) - // @description - // Returns whether the inventory contains all of the specified items. - // --> - if (attribute.startsWith("contains") && attribute.hasContext(1)) { - ListTag list = ListTag.valueOf(attribute.getContext(1)); - if (list.isEmpty()) { - return null; - } - int qty = 1; - int attribs = 1; - - // <--[tag] - // @attribute |...].quantity[<#>]> - // @returns ElementTag(Boolean) - // @description - // Returns whether the inventory contains a certain quantity of all of the specified items. - // --> - if ((attribute.getAttribute(2).startsWith("quantity") || attribute.getAttribute(2).startsWith("qty")) - && attribute.hasContext(2) && ArgumentHelper.matchesInteger(attribute.getContext(2))) { - qty = attribute.getIntContext(2); - attribs = 2; - } - List contains = list.filter(ItemTag.class, attribute.getScriptEntry()); - if (contains.size() == list.size()) { - for (ItemTag item : contains) { - if (!containsItem(item, qty)) { - return new ElementTag(false).getAttribute(attribute.fulfill(attribs)); + ListTag list = ListTag.valueOf(attribute.getContext(1)); + if (list.isEmpty()) { + return null; + } + int qty = 1; + int attribs = 1; + + // <--[tag] + // @attribute |...].quantity[<#>]> + // @returns ElementTag(Boolean) + // @description + // Returns whether the inventory contains a certain quantity of any of the specified items. + // --> + if ((attribute.getAttribute(2).startsWith("quantity") || attribute.getAttribute(2).startsWith("qty")) + && attribute.hasContext(2) && ArgumentHelper.matchesInteger(attribute.getContext(2))) { + qty = attribute.getIntContext(2); + attribs = 2; + } + List contains = list.filter(ItemTag.class, attribute.getScriptEntry()); + if (!contains.isEmpty()) { + for (ItemTag item : contains) { + if (containsItem(item, qty)) { + return new ElementTag(true).getObjectAttribute(attribute.fulfill(attribs)); + } } } - return new ElementTag(true).getAttribute(attribute.fulfill(attribs)); + return new ElementTag(false).getObjectAttribute(attribute.fulfill(attribs)); } - return new ElementTag(false).getAttribute(attribute.fulfill(attribs)); - } + }); // <--[tag] // @attribute @@ -1810,114 +1852,120 @@ else if (item != null && item.hasItemMeta() && item.getItemMeta().hasDisplayName // Returns the location of the first empty slot. // Returns -1 if the inventory is full. // --> - if (attribute.startsWith("first_empty")) { - int val = firstEmpty(0); - return new ElementTag(val >= 0 ? (val + 1) : -1).getAttribute(attribute.fulfill(1)); - } - - // <--[tag] - // @attribute ]> - // @returns ElementTag(Number) - // @description - // Returns the location of the first slot that contains the material. - // Returns -1 if there's no match. - // --> - if (attribute.startsWith("find.material") - && attribute.hasContext(2) - && MaterialTag.matches(attribute.getContext(2))) { - MaterialTag material = MaterialTag.valueOf(attribute.getContext(2)); - if (material == null) { - return null; + registerTag("first_empty", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + int val = firstEmpty(0); + return new ElementTag(val >= 0 ? (val + 1) : -1).getObjectAttribute(attribute.fulfill(1)); } - int slot = -1; - for (int i = 0; i < inventory.getSize(); i++) { - if (inventory.getItem(i) != null && inventory.getItem(i).getType() == material.getMaterial()) { - slot = i + 1; - break; - } - } - return new ElementTag(slot).getAttribute(attribute.fulfill(2)); - } + }); - // <--[tag] - // @attribute ]> - // @returns ElementTag(Number) - // @description - // Returns the location of the first slot that contains the item - // with the specified script name. - // Returns -1 if there's no match. - // --> - if (attribute.startsWith("find.scriptname") - && attribute.hasContext(2) - && ItemTag.matches(attribute.getContext(2))) { - String scrname = ItemTag.valueOf(attribute.getContext(2), attribute.context).getScriptName(); - if (scrname == null) { - return null; - } - int slot = -1; - for (int i = 0; i < inventory.getSize(); i++) { - if (inventory.getItem(i) != null - && scrname.equalsIgnoreCase(new ItemTag(inventory.getItem(i)).getScriptName())) { - slot = i + 1; - break; - } - } - return new ElementTag(slot).getAttribute(attribute.fulfill(2)); - } // <--[tag] - // @attribute ]> + // @attribute ]> // @returns ElementTag(Number) // @description // Returns the location of the first slot that contains the item. // Returns -1 if there's no match. - // Will match item script to item script, even if one is edited. // --> - if (attribute.startsWith("find_imperfect") - && attribute.hasContext(1) - && ItemTag.matches(attribute.getContext(1))) { - ItemTag item = ItemTag.valueOf(attribute.getContext(1), attribute.context); - item.setAmount(1); - int slot = -1; - for (int i = 0; i < inventory.getSize(); i++) { - if (inventory.getItem(i) != null) { - ItemTag compare_to = new ItemTag(inventory.getItem(i).clone()); - compare_to.setAmount(1); - if (item.identify().equalsIgnoreCase(compare_to.identify()) - || item.getScriptName().equalsIgnoreCase(compare_to.getScriptName())) { - slot = i + 1; - break; + registerTag("find", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + // <--[tag] + // @attribute ]> + // @returns ElementTag(Number) + // @description + // Returns the location of the first slot that contains the material. + // Returns -1 if there's no match. + // --> + if (attribute.getAttributeWithoutContext(2).equals("material")) { + MaterialTag material = MaterialTag.valueOf(attribute.getContext(2)); + if (material == null) { + return null; + } + int slot = -1; + for (int i = 0; i < inventory.getSize(); i++) { + if (inventory.getItem(i) != null && inventory.getItem(i).getType() == material.getMaterial()) { + slot = i + 1; + break; + } + } + return new ElementTag(slot).getObjectAttribute(attribute.fulfill(2)); + } + // <--[tag] + // @attribute ]> + // @returns ElementTag(Number) + // @description + // Returns the location of the first slot that contains the item + // with the specified script name. + // Returns -1 if there's no match. + // --> + if (attribute.getAttributeWithoutContext(2).equals("scriptname")) { + String scrname = ItemTag.valueOf(attribute.getContext(2), attribute.context).getScriptName(); + if (scrname == null) { + return null; + } + int slot = -1; + for (int i = 0; i < inventory.getSize(); i++) { + if (inventory.getItem(i) != null + && scrname.equalsIgnoreCase(new ItemTag(inventory.getItem(i)).getScriptName())) { + slot = i + 1; + break; + } + } + return new ElementTag(slot).getObjectAttribute(attribute.fulfill(2)); + } + if (!attribute.hasContext(1) || !ItemTag.matches(attribute.getContext(1))) { + return null; + } + ItemTag item = ItemTag.valueOf(attribute.getContext(1), attribute.context); + item.setAmount(1); + int slot = -1; + for (int i = 0; i < inventory.getSize(); i++) { + if (inventory.getItem(i) != null) { + ItemTag compare_to = new ItemTag(inventory.getItem(i).clone()); + compare_to.setAmount(1); + if (item.getFullString().equalsIgnoreCase(compare_to.getFullString())) { + slot = i + 1; + break; + } } } + return new ElementTag(slot).getObjectAttribute(attribute.fulfill(1)); } - return new ElementTag(slot).getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] - // @attribute ]> + // @attribute ]> // @returns ElementTag(Number) // @description // Returns the location of the first slot that contains the item. // Returns -1 if there's no match. + // Will match item script to item script, even if one is edited. // --> - if (attribute.startsWith("find") - && attribute.hasContext(1) - && ItemTag.matches(attribute.getContext(1))) { - ItemTag item = ItemTag.valueOf(attribute.getContext(1), attribute.context); - item.setAmount(1); - int slot = -1; - for (int i = 0; i < inventory.getSize(); i++) { - if (inventory.getItem(i) != null) { - ItemTag compare_to = new ItemTag(inventory.getItem(i).clone()); - compare_to.setAmount(1); - if (item.getFullString().equalsIgnoreCase(compare_to.getFullString())) { - slot = i + 1; - break; + registerTag("find_imperfect", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1) || !ItemTag.matches(attribute.getContext(1))) { + return null; + } + ItemTag item = ItemTag.valueOf(attribute.getContext(1), attribute.context); + item.setAmount(1); + int slot = -1; + for (int i = 0; i < inventory.getSize(); i++) { + if (inventory.getItem(i) != null) { + ItemTag compare_to = new ItemTag(inventory.getItem(i).clone()); + compare_to.setAmount(1); + if (item.identify().equalsIgnoreCase(compare_to.identify()) + || item.getScriptName().equalsIgnoreCase(compare_to.getScriptName())) { + slot = i + 1; + break; + } } } + return new ElementTag(slot).getObjectAttribute(attribute.fulfill(1)); } - return new ElementTag(slot).getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute @@ -1925,9 +1973,12 @@ else if (item != null && item.hasItemMeta() && item.getItemMeta().hasDisplayName // @description // Returns Denizen's type ID for this inventory. (player, location, etc.) // --> - if (attribute.startsWith("id_type")) { - return new ElementTag(idType).getAttribute(attribute.fulfill(1)); - } + registerTag("id_type", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(idType).getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1936,13 +1987,16 @@ else if (item != null && item.hasItemMeta() && item.getItemMeta().hasDisplayName // Gets the name of a Notable InventoryTag. If the inventory isn't noted, // this is null. // --> - if (attribute.startsWith("notable_name")) { - String notname = NotableManager.getSavedId(this); - if (notname == null) { - return null; + registerTag("notable_name", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + String notname = NotableManager.getSavedId((InventoryTag) object); + if (notname == null) { + return null; + } + return new ElementTag(notname).getObjectAttribute(attribute.fulfill(1)); } - return new ElementTag(notname).getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute @@ -1950,13 +2004,16 @@ else if (item != null && item.hasItemMeta() && item.getItemMeta().hasDisplayName // @description // Returns the location of this inventory's holder. // --> - if (attribute.startsWith("location")) { - LocationTag location = getLocation(); - if (location == null) { - return null; + registerTag("location", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + LocationTag location = getLocation(); + if (location == null) { + return null; + } + return location.getObjectAttribute(attribute.fulfill(1)); } - return location.getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute ]> @@ -1964,10 +2021,16 @@ else if (item != null && item.hasItemMeta() && item.getItemMeta().hasDisplayName // @description // Returns the combined quantity of itemstacks that have the specified script name. // --> - if (attribute.startsWith("quantity.scriptname") && attribute.hasContext(2)) { - return new ElementTag(countByScriptName(attribute.getContext(2))) - .getAttribute(attribute.fulfill(2)); - } + registerTag("quantity.scriptname", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(2)) { + return null; + } + return new ElementTag(countByScriptName(attribute.getContext(2))) + .getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute ]> @@ -1975,11 +2038,16 @@ else if (item != null && item.hasItemMeta() && item.getItemMeta().hasDisplayName // @description // Returns the combined quantity of itemstacks that have the specified material. // --> - if (attribute.startsWith("quantity.material") - && attribute.hasContext(2) && MaterialTag.matches(attribute.getContext(2))) { - return new ElementTag(countByMaterial(MaterialTag.valueOf(attribute.getContext(2)).getMaterial())) - .getAttribute(attribute.fulfill(2)); - } + registerTag("quantity.material", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(2) && MaterialTag.matches(attribute.getContext(2))) { + return null; + } + return new ElementTag(countByMaterial(MaterialTag.valueOf(attribute.getContext(2)).getMaterial())) + .getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute ]> @@ -1989,17 +2057,21 @@ else if (item != null && item.hasItemMeta() && item.getItemMeta().hasDisplayName // one is specified, or the combined quantity of all itemstacks // if one is not. // --> - if (attribute.startsWith("quantity") || attribute.startsWith("qty")) { - if (attribute.hasContext(1) && ItemTag.matches(attribute.getContext(1))) { - return new ElementTag(count // TODO: Handle no-script-entry cases - (ItemTag.valueOf(attribute.getContext(1), attribute.context).getItemStack(), false)) - .getAttribute(attribute.fulfill(1)); - } - else { - return new ElementTag(count(null, false)) - .getAttribute(attribute.fulfill(1)); + registerTag("quantity", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (attribute.hasContext(1) && ItemTag.matches(attribute.getContext(1))) { + return new ElementTag(count // TODO: Handle no-script-entry cases + (ItemTag.valueOf(attribute.getContext(1), attribute.context).getItemStack(), false)) + .getObjectAttribute(attribute.fulfill(1)); + } + else { + return new ElementTag(count(null, false)) + .getObjectAttribute(attribute.fulfill(1)); + } } - } + }); + registerTag("qty", tagProcessor.registeredObjectTags.get("quantity")); // <--[tag] // @attribute ]> @@ -2008,17 +2080,20 @@ else if (item != null && item.hasItemMeta() && item.getItemMeta().hasDisplayName // Returns the number of itemstacks that match an item if one is // specified, or the number of all itemstacks if one is not. // --> - if (attribute.startsWith("stacks")) { - if (attribute.hasContext(1) && ItemTag.matches(attribute.getContext(1))) { - return new ElementTag(count // TODO: Handle no-script-entry cases - (ItemTag.valueOf(attribute.getContext(1), attribute.context).getItemStack(), true)) - .getAttribute(attribute.fulfill(1)); - } - else { - return new ElementTag(count(null, true)) - .getAttribute(attribute.fulfill(1)); + registerTag("stacks", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (attribute.hasContext(1) && ItemTag.matches(attribute.getContext(1))) { + return new ElementTag(count // TODO: Handle no-script-entry cases + (ItemTag.valueOf(attribute.getContext(1), attribute.context).getItemStack(), true)) + .getObjectAttribute(attribute.fulfill(1)); + } + else { + return new ElementTag(count(null, true)) + .getObjectAttribute(attribute.fulfill(1)); + } } - } + }); // <--[tag] // @attribute |...]> @@ -2027,40 +2102,45 @@ else if (item != null && item.hasItemMeta() && item.getItemMeta().hasDisplayName // If one slot is specified, returns the item in the specified slot. // If more than what slot is specified, returns a list of the item in each given slot. // --> - if (attribute.startsWith("slot") - && attribute.hasContext(1)) { - ListTag slots = ListTag.getListFor(attribute.getContextObject(1)); - if (slots.size() == 0) { - if (!attribute.hasAlternative()) { - Debug.echoError("Cannot get a list of zero slots."); - } - return null; - } - else if (slots.size() == 1) { - int slot = SlotHelper.nameToIndex(attribute.getContext(1)); - if (slot < 0) { - slot = 0; + registerTag("slot", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; } - else if (slot > getInventory().getSize() - 1) { - slot = getInventory().getSize() - 1; + ListTag slots = ListTag.getListFor(attribute.getContextObject(1)); + if (slots.size() == 0) { + if (!attribute.hasAlternative()) { + Debug.echoError("Cannot get a list of zero slots."); + } + return null; } - return new ItemTag(getInventory().getItem(slot)).getAttribute(attribute.fulfill(1)); - } - else { - ListTag result = new ListTag(); - for (String slotText : slots) { - int slot = SlotHelper.nameToIndex(slotText); + else if (slots.size() == 1) { + int slot = SlotHelper.nameToIndex(attribute.getContext(1)); if (slot < 0) { slot = 0; } else if (slot > getInventory().getSize() - 1) { slot = getInventory().getSize() - 1; } - result.addObject(new ItemTag(getInventory().getItem(slot))); + return new ItemTag(getInventory().getItem(slot)).getObjectAttribute(attribute.fulfill(1)); + } + else { + ListTag result = new ListTag(); + for (String slotText : slots) { + int slot = SlotHelper.nameToIndex(slotText); + if (slot < 0) { + slot = 0; + } + else if (slot > getInventory().getSize() - 1) { + slot = getInventory().getSize() - 1; + } + result.addObject(new ItemTag(getInventory().getItem(slot))); + } + return result.getObjectAttribute(attribute.fulfill(1)); } - return result.getAttribute(attribute.fulfill(1)); } - } + }); // <--[tag] // @attribute @@ -2068,10 +2148,13 @@ else if (slot > getInventory().getSize() - 1) { // @description // Returns the type of the inventory (e.g. "PLAYER", "CRAFTING", "HORSE"). // --> - if (attribute.startsWith("inventory_type")) { - return new ElementTag(inventory instanceof HorseInventory ? "HORSE" : getInventory().getType().name()) - .getAttribute(attribute.fulfill(1)); - } + registerTag("inventory_type", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(inventory instanceof HorseInventory ? "HORSE" : getInventory().getType().name()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2079,13 +2162,16 @@ else if (slot > getInventory().getSize() - 1) { // @description // Returns the equipment of an inventory. // --> - if (attribute.startsWith("equipment")) { - ListTag equipment = getEquipment(); - if (equipment == null) { - return null; + registerTag("equipment", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + ListTag equipment = getEquipment(); + if (equipment == null) { + return null; + } + return equipment.getObjectAttribute(attribute.fulfill(1)); } - return equipment.getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute @@ -2093,18 +2179,24 @@ else if (slot > getInventory().getSize() - 1) { // @description // Returns the ItemTags currently in a crafting inventory's matrix. // --> - if (attribute.startsWith("matrix") && inventory instanceof CraftingInventory) { - ListTag recipeList = new ListTag(); - for (ItemStack item : ((CraftingInventory) inventory).getMatrix()) { - if (item != null) { - recipeList.add(new ItemTag(item).identify()); + registerTag("matrix", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!(inventory instanceof CraftingInventory)) { + return null; } - else { - recipeList.add(new ItemTag(Material.AIR).identify()); + ListTag recipeList = new ListTag(); + for (ItemStack item : ((CraftingInventory) inventory).getMatrix()) { + if (item != null) { + recipeList.add(new ItemTag(item).identify()); + } + else { + recipeList.add(new ItemTag(Material.AIR).identify()); + } } + return recipeList.getObjectAttribute(attribute.fulfill(1)); } - return recipeList.getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute @@ -2112,13 +2204,19 @@ else if (slot > getInventory().getSize() - 1) { // @description // Returns the ItemTag currently in the result section of a crafting inventory. // --> - if (attribute.startsWith("result") && inventory instanceof CraftingInventory) { - ItemStack result = ((CraftingInventory) inventory).getResult(); - if (result == null) { - return null; + registerTag("result", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!(inventory instanceof CraftingInventory)) { + return null; + } + ItemStack result = ((CraftingInventory) inventory).getResult(); + if (result == null) { + return null; + } + return new ItemTag(result).getObjectAttribute(attribute.fulfill(1)); } - return new ItemTag(result).getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute @@ -2127,9 +2225,15 @@ else if (slot > getInventory().getSize() - 1) { // @description // Returns the current repair cost on an anvil. // --> - if (attribute.startsWith("anvil_repair_cost") && inventory instanceof AnvilInventory) { - return new ElementTag(((AnvilInventory) inventory).getRepairCost()).getAttribute(attribute.fulfill(1)); - } + registerTag("anvil_repair_cost", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!(inventory instanceof AnvilInventory)) { + return null; + } + return new ElementTag(((AnvilInventory) inventory).getRepairCost()).getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2138,9 +2242,15 @@ else if (slot > getInventory().getSize() - 1) { // @description // Returns the maximum repair cost on an anvil. // --> - if (attribute.startsWith("anvil_max_repair_cost") && inventory instanceof AnvilInventory) { - return new ElementTag(((AnvilInventory) inventory).getMaximumRepairCost()).getAttribute(attribute.fulfill(1)); - } + registerTag("anvil_max_repair_cost", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!(inventory instanceof AnvilInventory)) { + return null; + } + return new ElementTag(((AnvilInventory) inventory).getMaximumRepairCost()).getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2148,9 +2258,15 @@ else if (slot > getInventory().getSize() - 1) { // @description // Returns the current entered renaming text on an anvil. // --> - if (attribute.startsWith("anvil_rename_text") && inventory instanceof AnvilInventory) { - return new ElementTag(((AnvilInventory) inventory).getRenameText()).getAttribute(attribute.fulfill(1)); - } + registerTag("anvil_rename_text", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!(inventory instanceof AnvilInventory)) { + return null; + } + return new ElementTag(((AnvilInventory) inventory).getRenameText()).getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2159,16 +2275,23 @@ else if (slot > getInventory().getSize() - 1) { // Always returns 'Inventory' for InventoryTag objects. All objects fetchable by the Object Fetcher will return the // type of object that is fulfilling this attribute. // --> - if (attribute.startsWith("type")) { - return new ElementTag("Inventory").getAttribute(attribute.fulfill(1)); - } + registerTag("type", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag("Inventory").getObjectAttribute(attribute.fulfill(1)); + } + }); + } - String returned = CoreUtilities.autoPropertyTag(this, attribute); - if (returned != null) { - return returned; - } + public static ObjectTagProcessor tagProcessor = new ObjectTagProcessor(); + + public static void registerTag(String name, TagRunnable.ObjectForm runnable) { + tagProcessor.registerTag(name, runnable); + } - return new ElementTag(identify()).getAttribute(attribute); + @Override + public ObjectTag getObjectAttribute(Attribute attribute) { + return tagProcessor.getObjectAttribute(this, attribute); } private ArrayList mechanisms = new ArrayList<>(); diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/LocationTag.java b/plugin/src/main/java/com/denizenscript/denizen/objects/LocationTag.java index 91e2096330..cb38aeecab 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/LocationTag.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/LocationTag.java @@ -24,6 +24,7 @@ import com.denizenscript.denizencore.objects.notable.Notable; import com.denizenscript.denizencore.objects.notable.Note; import com.denizenscript.denizencore.tags.Attribute; +import com.denizenscript.denizencore.tags.ObjectTagProcessor; import com.denizenscript.denizencore.tags.TagContext; import com.denizenscript.denizencore.tags.core.EscapeTagBase; import com.denizenscript.denizencore.utilities.CoreUtilities; @@ -771,13 +772,17 @@ public String getAttribute(Attribute attribute) { // This can return for example "1,0,0" to mean the block is facing towards the positive X axis. // You can use ]> to get the block directly in front of this block (based on its facing direction). // --> - if (attribute.matches("block_facing")) { - Vector facing = DirectionalBlocksHelper.getFacing(getBlockForTag(attribute)); - if (facing != null) { - return new LocationTag(getWorld(), facing.getX(), facing.getY(), facing.getZ()) - .getAttribute(attribute.fulfill(1)); + registerTag("block_facing", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + Vector facing = DirectionalBlocksHelper.getFacing(getBlockForTag(attribute)); + if (facing != null) { + return new LocationTag(getWorld(), facing.getX(), facing.getY(), facing.getZ()) + .getObjectAttribute(attribute.fulfill(1)); + } + return null; } - } + }); // <--[tag] // @attribute )]> @@ -786,10 +791,13 @@ public String getAttribute(Attribute attribute) { // Returns the location above this location. Optionally specify a number of blocks to go up. // This just moves straight along the Y axis, equivalent to <@link tag LocationTag.add> with input 0,1,0 (or the input value instead of '1'). // --> - if (attribute.startsWith("above")) { - return new LocationTag(this.clone().add(0, attribute.hasContext(1) ? attribute.getDoubleContext(1) : 1, 0)) - .getAttribute(attribute.fulfill(1)); - } + registerTag("above", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new LocationTag(((LocationTag) object).clone().add(0, attribute.hasContext(1) ? attribute.getDoubleContext(1) : 1, 0)) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute )]> @@ -798,10 +806,13 @@ public String getAttribute(Attribute attribute) { // Returns the location below this location. Optionally specify a number of blocks to go down. // This just moves straight along the Y axis, equivalent to <@link tag LocationTag.sub> with input 0,1,0 (or the input value instead of '1'). // --> - if (attribute.startsWith("below")) { - return new LocationTag(this.clone().subtract(0, attribute.hasContext(1) ? attribute.getDoubleContext(1) : 1, 0)) - .getAttribute(attribute.fulfill(1)); - } + registerTag("below", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new LocationTag(((LocationTag) object).clone().subtract(0, attribute.hasContext(1) ? attribute.getDoubleContext(1) : 1, 0)) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute )]> @@ -809,13 +820,16 @@ public String getAttribute(Attribute attribute) { // @description // Returns the location in front of this location based on yaw but not pitch. Optionally specify a number of blocks to go forward. // --> - if (attribute.startsWith("forward_flat")) { - Location loc = this.clone(); - loc.setPitch(0); - Vector vector = loc.getDirection().multiply(attribute.hasContext(1) ? attribute.getDoubleContext(1) : 1); - return new LocationTag(this.clone().add(vector)) - .getAttribute(attribute.fulfill(1)); - } + registerTag("forward_flat", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + Location loc = ((LocationTag) object).clone(); + loc.setPitch(0); + Vector vector = loc.getDirection().multiply(attribute.hasContext(1) ? attribute.getDoubleContext(1) : 1); + return new LocationTag(((LocationTag) object).clone().add(vector)) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute )]> @@ -824,13 +838,16 @@ public String getAttribute(Attribute attribute) { // Returns the location behind this location based on yaw but not pitch. Optionally specify a number of blocks to go backward. // This is equivalent to <@link tag LocationTag.forward_flat> in the opposite direction. // --> - if (attribute.startsWith("backward_flat")) { - Location loc = this.clone(); - loc.setPitch(0); - Vector vector = loc.getDirection().multiply(attribute.hasContext(1) ? attribute.getDoubleContext(1) : 1); - return new LocationTag(this.clone().subtract(vector)) - .getAttribute(attribute.fulfill(1)); - } + registerTag("backward_flat", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + Location loc = ((LocationTag) object).clone(); + loc.setPitch(0); + Vector vector = loc.getDirection().multiply(attribute.hasContext(1) ? attribute.getDoubleContext(1) : 1); + return new LocationTag(((LocationTag) object).clone().subtract(vector)) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute )]> @@ -838,11 +855,14 @@ public String getAttribute(Attribute attribute) { // @description // Returns the location in front of this location based on pitch and yaw. Optionally specify a number of blocks to go forward. // --> - if (attribute.startsWith("forward")) { - Vector vector = this.getDirection().multiply(attribute.hasContext(1) ? attribute.getDoubleContext(1) : 1); - return new LocationTag(this.clone().add(vector)) - .getAttribute(attribute.fulfill(1)); - } + registerTag("forward", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + Vector vector = ((LocationTag) object).getDirection().multiply(attribute.hasContext(1) ? attribute.getDoubleContext(1) : 1); + return new LocationTag(((LocationTag) object).clone().add(vector)) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute )]> @@ -851,11 +871,14 @@ public String getAttribute(Attribute attribute) { // Returns the location behind this location based on pitch and yaw. Optionally specify a number of blocks to go backward. // This is equivalent to <@link tag LocationTag.forward> in the opposite direction. // --> - if (attribute.startsWith("backward")) { - Vector vector = this.getDirection().multiply(attribute.hasContext(1) ? attribute.getDoubleContext(1) : 1); - return new LocationTag(this.clone().subtract(vector)) - .getAttribute(attribute.fulfill(1)); - } + registerTag("backward", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + Vector vector = ((LocationTag) object).getDirection().multiply(attribute.hasContext(1) ? attribute.getDoubleContext(1) : 1); + return new LocationTag(((LocationTag) object).clone().subtract(vector)) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute )]> @@ -864,13 +887,16 @@ public String getAttribute(Attribute attribute) { // Returns the location to the left of this location based on pitch and yaw. Optionally specify a number of blocks to go left. // This is equivalent to <@link tag LocationTag.forward> with a +90 degree rotation to the yaw and the pitch set to 0. // --> - if (attribute.startsWith("left")) { - Location loc = this.clone(); - loc.setPitch(0); - Vector vector = loc.getDirection().rotateAroundY(Math.PI / 2).multiply(attribute.hasContext(1) ? attribute.getDoubleContext(1) : 1); - return new LocationTag(this.clone().add(vector)) - .getAttribute(attribute.fulfill(1)); - } + registerTag("left", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + Location loc = ((LocationTag) object).clone(); + loc.setPitch(0); + Vector vector = loc.getDirection().rotateAroundY(Math.PI / 2).multiply(attribute.hasContext(1) ? attribute.getDoubleContext(1) : 1); + return new LocationTag(((LocationTag) object).clone().add(vector)) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute )]> @@ -879,13 +905,16 @@ public String getAttribute(Attribute attribute) { // Returns the location to the right of this location based on pitch and yaw. Optionally specify a number of blocks to go right. // This is equivalent to <@link tag LocationTag.forward> with a -90 degree rotation to the yaw and the pitch set to 0. // --> - if (attribute.startsWith("right")) { - Location loc = this.clone(); - loc.setPitch(0); - Vector vector = loc.getDirection().rotateAroundY(Math.PI / 2).multiply(attribute.hasContext(1) ? attribute.getDoubleContext(1) : 1); - return new LocationTag(this.clone().subtract(vector)) - .getAttribute(attribute.fulfill(1)); - } + registerTag("right", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + Location loc = ((LocationTag) object).clone(); + loc.setPitch(0); + Vector vector = loc.getDirection().rotateAroundY(Math.PI / 2).multiply(attribute.hasContext(1) ? attribute.getDoubleContext(1) : 1); + return new LocationTag(((LocationTag) object).clone().subtract(vector)) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute )]> @@ -894,13 +923,16 @@ public String getAttribute(Attribute attribute) { // Returns the location above this location based on pitch and yaw. Optionally specify a number of blocks to go up. // This is equivalent to <@link tag LocationTag.forward> with a +90 degree rotation to the pitch. // --> - if (attribute.startsWith("up")) { - Location loc = this.clone(); - loc.setPitch(loc.getPitch() - 90); - Vector vector = loc.getDirection().multiply(attribute.hasContext(1) ? attribute.getDoubleContext(1) : 1); - return new LocationTag(this.clone().add(vector)) - .getAttribute(attribute.fulfill(1)); - } + registerTag("up", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + Location loc = ((LocationTag) object).clone(); + loc.setPitch(loc.getPitch() - 90); + Vector vector = loc.getDirection().multiply(attribute.hasContext(1) ? attribute.getDoubleContext(1) : 1); + return new LocationTag(((LocationTag) object).clone().add(vector)) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute )]> @@ -909,13 +941,16 @@ public String getAttribute(Attribute attribute) { // Returns the location below this location based on pitch and yaw. Optionally specify a number of blocks to go down. // This is equivalent to <@link tag LocationTag.forward> with a -90 degree rotation to the pitch. // --> - if (attribute.startsWith("down")) { - Location loc = this.clone(); - loc.setPitch(loc.getPitch() - 90); - Vector vector = loc.getDirection().multiply(attribute.hasContext(1) ? attribute.getDoubleContext(1) : 1); - return new LocationTag(this.clone().subtract(vector)) - .getAttribute(attribute.fulfill(1)); - } + registerTag("down", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + Location loc = ((LocationTag) object).clone(); + loc.setPitch(loc.getPitch() - 90); + Vector vector = loc.getDirection().multiply(attribute.hasContext(1) ? attribute.getDoubleContext(1) : 1); + return new LocationTag(((LocationTag) object).clone().subtract(vector)) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute ]> @@ -924,22 +959,28 @@ public String getAttribute(Attribute attribute) { // Returns the location relative to this location. Input is a vector location of the form left,up,forward. // For example, input -1,1,1 will return a location 1 block to the right, 1 block up, and 1 block forward. // --> - if (attribute.startsWith("relative") && attribute.hasContext(1)) { - LocationTag offsetLoc = LocationTag.valueOf(attribute.getContext(1)); - if (offsetLoc == null) { - return null; - } + registerTag("relative", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; + } + LocationTag offsetLoc = LocationTag.valueOf(attribute.getContext(1)); + if (offsetLoc == null) { + return null; + } - Location loc = this.clone(); - Vector offset = loc.getDirection().multiply(offsetLoc.getZ()); - loc.setPitch(loc.getPitch() - 90); - offset = offset.add(loc.getDirection().multiply(offsetLoc.getY())); - loc.setPitch(0); - offset = offset.add(loc.getDirection().rotateAroundY(Math.PI / 2).multiply(offsetLoc.getX())); + Location loc = ((LocationTag) object).clone(); + Vector offset = loc.getDirection().multiply(offsetLoc.getZ()); + loc.setPitch(loc.getPitch() - 90); + offset = offset.add(loc.getDirection().multiply(offsetLoc.getY())); + loc.setPitch(0); + offset = offset.add(loc.getDirection().rotateAroundY(Math.PI / 2).multiply(offsetLoc.getX())); - return new LocationTag(this.clone().add(offset)) - .getAttribute(attribute.fulfill(1)); - } + return new LocationTag(((LocationTag) object).clone().add(offset)) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -948,10 +989,13 @@ public String getAttribute(Attribute attribute) { // Returns the location of the block this location is on, // i.e. returns a location without decimals or direction. // --> - if (attribute.matches("block")) { - return new LocationTag(getWorld(), getBlockX(), getBlockY(), getBlockZ()) - .getAttribute(attribute.fulfill(1)); - } + registerTag("block", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new LocationTag(getWorld(), getBlockX(), getBlockY(), getBlockZ()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -959,10 +1003,13 @@ public String getAttribute(Attribute attribute) { // @description // Returns the location at the center of the block this location is on. // --> - if (attribute.startsWith("center")) { - return new LocationTag(getWorld(), getBlockX() + 0.5, getBlockY() + 0.5, getBlockZ() + 0.5) - .getAttribute(attribute.fulfill(1)); - } + registerTag("center", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new LocationTag(getWorld(), getBlockX() + 0.5, getBlockY() + 0.5, getBlockZ() + 0.5) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -970,10 +1017,13 @@ public String getAttribute(Attribute attribute) { // @description // Returns the location of the highest solid block at the location. // --> - if (attribute.startsWith("highest")) { - return new LocationTag(getHighestBlockForTag(attribute).add(0, -1, 0)) - .getAttribute(attribute.fulfill(1)); - } + registerTag("highest", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new LocationTag(getHighestBlockForTag(attribute).add(0, -1, 0)) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -983,13 +1033,16 @@ public String getAttribute(Attribute attribute) { // For the list of possible colors, see <@link url http://bit.ly/1dydq12>. // As of 1.13+, this tag is no longer relevant. // --> - if (attribute.startsWith("base_color")) { - if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_13)) { - Debug.echoError("Base_Color tag no longer relevant: banner types are now distinct materials."); + registerTag("base_color", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_13)) { + Debug.echoError("Base_Color tag no longer relevant: banner types are now distinct materials."); + } + DyeColor color = ((Banner) getBlockStateForTag(attribute)).getBaseColor(); + return new ElementTag(color != null ? color.name() : "BLACK").getObjectAttribute(attribute.fulfill(1)); } - DyeColor color = ((Banner) getBlockStateForTag(attribute)).getBaseColor(); - return new ElementTag(color != null ? color.name() : "BLACK").getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute @@ -997,9 +1050,12 @@ public String getAttribute(Attribute attribute) { // @description // Returns whether the block at the location has an inventory. // --> - if (attribute.startsWith("has_inventory")) { - return new ElementTag(getBlockStateForTag(attribute) instanceof InventoryHolder).getAttribute(attribute.fulfill(1)); - } + registerTag("has_inventory", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getBlockStateForTag(attribute) instanceof InventoryHolder).getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1008,13 +1064,16 @@ public String getAttribute(Attribute attribute) { // Returns the InventoryTag of the block at the location. If the // block is not a container, returns null. // --> - if (attribute.startsWith("inventory")) { - if (!isChunkLoadedSafe()) { - return null; + registerTag("inventory", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!isChunkLoadedSafe()) { + return null; + } + ObjectTag obj = ElementTag.handleNull(identify() + ".inventory", getInventory(), "dInventory", attribute.hasAlternative()); + return obj == null ? null : obj.getObjectAttribute(attribute.fulfill(1)); } - ObjectTag obj = ElementTag.handleNull(identify() + ".inventory", getInventory(), "dInventory", attribute.hasAlternative()); - return obj == null ? null : obj.getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute @@ -1022,13 +1081,16 @@ public String getAttribute(Attribute attribute) { // @description // Returns the material of the block at the location. // --> - if (attribute.startsWith("material")) { - Block block = getBlockForTag(attribute); - if (block == null) { - return null; + registerTag("material", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + Block block = getBlockForTag(attribute); + if (block == null) { + return null; + } + return new MaterialTag(block).getObjectAttribute(attribute.fulfill(1)); } - return new MaterialTag(block).getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute @@ -1040,13 +1102,16 @@ public String getAttribute(Attribute attribute) { // For the list of possible colors, see <@link url http://bit.ly/1dydq12>. // For the list of possible patterns, see <@link url http://bit.ly/1MqRn7T>. // --> - if (attribute.startsWith("patterns")) { - ListTag list = new ListTag(); - for (org.bukkit.block.banner.Pattern pattern : ((Banner) getBlockStateForTag(attribute)).getPatterns()) { - list.add(pattern.getColor().name() + "/" + pattern.getPattern().name()); + registerTag("patterns", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + ListTag list = new ListTag(); + for (org.bukkit.block.banner.Pattern pattern : ((Banner) getBlockStateForTag(attribute)).getPatterns()) { + list.add(pattern.getColor().name() + "/" + pattern.getPattern().name()); + } + return list.getObjectAttribute(attribute.fulfill(1)); } - return list.getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute @@ -1055,10 +1120,13 @@ public String getAttribute(Attribute attribute) { // Gets the rotation of the head at this location. Can be 1-16. // @mechanism LocationTag.head_rotation // --> - if (attribute.startsWith("head_rotation")) { - return new ElementTag(getSkullRotation(((Skull) getBlockStateForTag(attribute)).getRotation()) + 1) - .getAttribute(attribute.fulfill(1)); - } + registerTag("head_rotation", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getSkullRotation(((Skull) getBlockStateForTag(attribute)).getRotation()) + 1) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1068,10 +1136,13 @@ public String getAttribute(Attribute attribute) { // (For buttons, levers, etc.) // To change this, see <@link command Switch> // --> - if (attribute.startsWith("switched")) { - return new ElementTag(SwitchCommand.switchState(getBlockForTag(attribute))) - .getAttribute(attribute.fulfill(1)); - } + registerTag("switched", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(SwitchCommand.switchState(getBlockForTag(attribute))) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1080,15 +1151,18 @@ public String getAttribute(Attribute attribute) { // @description // Returns a list of lines on a sign. // --> - if (attribute.startsWith("sign_contents")) { - if (getBlockStateForTag(attribute) instanceof Sign) { - return new ListTag(Arrays.asList(((Sign) getBlockStateForTag(attribute)).getLines())) - .getAttribute(attribute.fulfill(1)); - } - else { - return null; + registerTag("sign_contents", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (getBlockStateForTag(attribute) instanceof Sign) { + return new ListTag(Arrays.asList(((Sign) getBlockStateForTag(attribute)).getLines())) + .getObjectAttribute(attribute.fulfill(1)); + } + else { + return null; + } } - } + }); // <--[tag] // @attribute @@ -1097,15 +1171,18 @@ public String getAttribute(Attribute attribute) { // @description // Returns the type of entity spawned by a mob spawner. // --> - if (attribute.startsWith("spawner_type")) { - if (getBlockStateForTag(attribute) instanceof CreatureSpawner) { - return new EntityTag(DenizenEntityType.getByName(((CreatureSpawner) getBlockStateForTag(attribute)) - .getSpawnedType().name())).getAttribute(attribute.fulfill(1)); - } - else { - return null; + registerTag("spawner_type", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (getBlockStateForTag(attribute) instanceof CreatureSpawner) { + return new EntityTag(DenizenEntityType.getByName(((CreatureSpawner) getBlockStateForTag(attribute)) + .getSpawnedType().name())).getObjectAttribute(attribute.fulfill(1)); + } + else { + return null; + } } - } + }); // <--[tag] // @attribute @@ -1114,11 +1191,17 @@ public String getAttribute(Attribute attribute) { // @description // Returns the password to a locked container. // --> - if (attribute.startsWith("lock") && getBlockStateForTag(attribute) instanceof Lockable) { - Lockable lock = (Lockable) getBlockStateForTag(attribute); - return new ElementTag(lock.isLocked() ? lock.getLock() : null) - .getAttribute(attribute.fulfill(1)); - } + registerTag("lock", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!(getBlockStateForTag(attribute) instanceof Lockable)) { + return null; + } + Lockable lock = (Lockable) getBlockStateForTag(attribute); + return new ElementTag(lock.isLocked() ? lock.getLock() : null) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1127,10 +1210,16 @@ public String getAttribute(Attribute attribute) { // @description // Returns whether the container is locked. // --> - if (attribute.startsWith("is_locked") && getBlockStateForTag(attribute) instanceof Lockable) { - return new ElementTag(((Lockable) getBlockStateForTag(attribute)).isLocked()) - .getAttribute(attribute.fulfill(1)); - } + registerTag("is_locked", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!(getBlockStateForTag(attribute) instanceof Lockable)) { + return null; + } + return new ElementTag(((Lockable) getBlockStateForTag(attribute)).isLocked()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1139,10 +1228,13 @@ public String getAttribute(Attribute attribute) { // @description // Returns whether the container is lockable. // --> - if (attribute.startsWith("is_lockable")) { - return new ElementTag(getBlockStateForTag(attribute) instanceof Lockable) - .getAttribute(attribute.fulfill(1)); - } + registerTag("is_lockable", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getBlockStateForTag(attribute) instanceof Lockable) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute )]> @@ -1151,17 +1243,20 @@ public String getAttribute(Attribute attribute) { // Returns what items the block at the location would drop if broken naturally. // Optionally specifier a breaker item. // --> - if (attribute.startsWith("drops")) { - ItemStack inputItem = null; - if (attribute.hasContext(1)) { - inputItem = ItemTag.valueOf(attribute.getContext(1), attribute.context).getItemStack(); - } - ListTag list = new ListTag(); - for (ItemStack it : getDropsForTag(attribute, inputItem)) { - list.add(new ItemTag(it).identify()); + registerTag("drops", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + ItemStack inputItem = null; + if (attribute.hasContext(1)) { + inputItem = ItemTag.valueOf(attribute.getContext(1), attribute.context).getItemStack(); + } + ListTag list = new ListTag(); + for (ItemStack it : getDropsForTag(attribute, inputItem)) { + list.add(new ItemTag(it).identify()); + } + return list.getObjectAttribute(attribute.fulfill(1)); } - return list.getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute @@ -1170,17 +1265,20 @@ public String getAttribute(Attribute attribute) { // Returns the flower pot contents at the location. // NOTE: Replaced by materials (such as POTTED_CACTUS) in 1.13 and above. // --> - if (attribute.startsWith("flowerpot_contents")) { - if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_13)) { - Debug.echoError("As of Minecraft version 1.13 potted flowers each have their own material, such as POTTED_CACTUS."); - } - else if (getBlockTypeForTag(attribute) == Material.FLOWER_POT) { - MaterialData contents = NMSHandler.getBlockHelper().getFlowerpotContents(getBlockForTag(attribute)); - return OldMaterialsHelper.getMaterialFrom(contents.getItemType(), contents.getData()) - .getAttribute(attribute.fulfill(1)); + registerTag("flowerpot_contents", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_13)) { + Debug.echoError("As of Minecraft version 1.13 potted flowers each have their own material, such as POTTED_CACTUS."); + } + else if (getBlockTypeForTag(attribute) == Material.FLOWER_POT) { + MaterialData contents = NMSHandler.getBlockHelper().getFlowerpotContents(getBlockForTag(attribute)); + return OldMaterialsHelper.getMaterialFrom(contents.getItemType(), contents.getData()) + .getObjectAttribute(attribute.fulfill(1)); + } + return null; } - return null; - } + }); // <--[tag] @@ -1189,13 +1287,17 @@ else if (getBlockTypeForTag(attribute) == Material.FLOWER_POT) { // @description // Returns the type of the skull. // --> - if (attribute.startsWith("skull_type")) { - BlockState blockState = getBlockStateForTag(attribute); - if (blockState instanceof Skull) { - String t = ((Skull) blockState).getSkullType().name(); - return new ElementTag(t).getAttribute(attribute.fulfill(1)); + registerTag("skull_type", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + BlockState blockState = getBlockStateForTag(attribute); + if (blockState instanceof Skull) { + String t = ((Skull) blockState).getSkullType().name(); + return new ElementTag(t).getObjectAttribute(attribute.fulfill(1)); + } + return null; } - } + }); // <--[tag] // @attribute @@ -1204,20 +1306,24 @@ else if (getBlockTypeForTag(attribute) == Material.FLOWER_POT) { // @description // Returns the name of the skin the skull is displaying. // --> - if (attribute.startsWith("skull_name")) { - BlockState blockState = getBlockStateForTag(attribute); - if (blockState instanceof Skull) { - PlayerProfile profile = NMSHandler.getBlockHelper().getPlayerProfile((Skull) blockState); - if (profile == null) { - return null; - } - String n = profile.getName(); - if (n == null) { - n = ((Skull) blockState).getOwningPlayer().getName(); + registerTag("skull_name", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + BlockState blockState = getBlockStateForTag(attribute); + if (blockState instanceof Skull) { + PlayerProfile profile = NMSHandler.getBlockHelper().getPlayerProfile((Skull) blockState); + if (profile == null) { + return null; + } + String n = profile.getName(); + if (n == null) { + n = ((Skull) blockState).getOwningPlayer().getName(); + } + return new ElementTag(n).getObjectAttribute(attribute.fulfill(1)); } - return new ElementTag(n).getAttribute(attribute.fulfill(1)); + return null; } - } + }); // <--[tag] // @attribute @@ -1226,36 +1332,39 @@ else if (getBlockTypeForTag(attribute) == Material.FLOWER_POT) { // @description // Returns the skin the skull is displaying - just the name or UUID as text, not a player object. // --> - if (attribute.startsWith("skull_skin")) { - BlockState blockState = getBlockStateForTag(attribute); - if (blockState instanceof Skull) { - PlayerProfile profile = NMSHandler.getBlockHelper().getPlayerProfile((Skull) blockState); - if (profile == null) { - return null; + registerTag("skull_skin", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + BlockState blockState = getBlockStateForTag(attribute); + if (blockState instanceof Skull) { + PlayerProfile profile = NMSHandler.getBlockHelper().getPlayerProfile((Skull) blockState); + if (profile == null) { + return null; + } + String name = profile.getName(); + UUID uuid = profile.getUniqueId(); + String texture = profile.getTexture(); + attribute = attribute.fulfill(1); + // <--[tag] + // @attribute + // @returns ElementTag|Element + // @mechanism LocationTag.skull_skin + // @description + // Returns the skin the skull item is displaying - just the name or UUID as text, not a player object, + // along with the permanently cached texture property. + // --> + if (attribute.startsWith("full")) { + return new ElementTag((uuid != null ? uuid : name) + + (texture != null ? "|" + texture : "")) + .getObjectAttribute(attribute.fulfill(1)); + } + return new ElementTag(uuid != null ? uuid.toString() : name).getObjectAttribute(attribute); } - String name = profile.getName(); - UUID uuid = profile.getUniqueId(); - String texture = profile.getTexture(); - attribute = attribute.fulfill(1); - // <--[tag] - // @attribute - // @returns ElementTag|Element - // @mechanism LocationTag.skull_skin - // @description - // Returns the skin the skull item is displaying - just the name or UUID as text, not a player object, - // along with the permanently cached texture property. - // --> - if (attribute.startsWith("full")) { - return new ElementTag((uuid != null ? uuid : name) - + (texture != null ? "|" + texture : "")) - .getAttribute(attribute.fulfill(1)); + else { + return null; } - return new ElementTag(uuid != null ? uuid.toString() : name).getAttribute(attribute); - } - else { - return null; } - } + }); // <--[tag] // @attribute @@ -1265,12 +1374,15 @@ else if (getBlockTypeForTag(attribute) == Material.FLOWER_POT) { // In the format: X 'x', Y 'y', Z 'z', in world 'world' // For example, X '1', Y '2', Z '3', in world 'world_nether' // --> - if (attribute.startsWith("simple.formatted")) { - return new ElementTag("X '" + getBlockX() - + "', Y '" + getBlockY() - + "', Z '" + getBlockZ() - + "', in world '" + getWorldName() + "'").getAttribute(attribute.fulfill(2)); - } + registerTag("simple.formatted", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag("X '" + getBlockX() + + "', Y '" + getBlockY() + + "', Z '" + getBlockZ() + + "', in world '" + getWorldName() + "'").getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -1280,16 +1392,19 @@ else if (getBlockTypeForTag(attribute) == Material.FLOWER_POT) { // In the format: x,y,z,world // For example: 1,2,3,world_nether // --> - if (attribute.startsWith("simple")) { - if (getWorldName() == null) { - return new ElementTag(getBlockX() + "," + getBlockY() + "," + getBlockZ()) - .getAttribute(attribute.fulfill(1)); - } - else { - return new ElementTag(getBlockX() + "," + getBlockY() + "," + getBlockZ() - + "," + getWorldName()).getAttribute(attribute.fulfill(1)); + registerTag("simple", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (getWorldName() == null) { + return new ElementTag(getBlockX() + "," + getBlockY() + "," + getBlockZ()) + .getObjectAttribute(attribute.fulfill(1)); + } + else { + return new ElementTag(getBlockX() + "," + getBlockY() + "," + getBlockZ() + + "," + getWorldName()).getObjectAttribute(attribute.fulfill(1)); + } } - } + }); ///////////////////// @@ -1303,23 +1418,26 @@ else if (getBlockTypeForTag(attribute) == Material.FLOWER_POT) { // Returns the exact impact normal at the location this location is pointing at. // Optionally, specify a maximum range to find the location from (defaults to 200). // --> - if (attribute.startsWith("precise_impact_normal")) { - int range = attribute.getIntContext(1); - if (range < 1) { - range = 200; - } - double xzLen = Math.cos((getPitch() % 360) * (Math.PI / 180)); - double nx = xzLen * Math.sin(-getYaw() * (Math.PI / 180)); - double ny = Math.sin(getPitch() * (Math.PI / 180)); - double nz = xzLen * Math.cos(getYaw() * (Math.PI / 180)); - Location location = NMSHandler.getEntityHelper().getImpactNormal(this, new Vector(nx, -ny, nz), range); - if (location != null) { - return new LocationTag(location).getAttribute(attribute.fulfill(1)); - } - else { - return null; + registerTag("precise_impact_normal", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + int range = attribute.getIntContext(1); + if (range < 1) { + range = 200; + } + double xzLen = Math.cos((getPitch() % 360) * (Math.PI / 180)); + double nx = xzLen * Math.sin(-getYaw() * (Math.PI / 180)); + double ny = Math.sin(getPitch() * (Math.PI / 180)); + double nz = xzLen * Math.cos(getYaw() * (Math.PI / 180)); + Location location = NMSHandler.getEntityHelper().getImpactNormal((LocationTag) object, new Vector(nx, -ny, nz), range); + if (location != null) { + return new LocationTag(location).getObjectAttribute(attribute.fulfill(1)); + } + else { + return null; + } } - } + }); // <--[tag] // @attribute ]> @@ -1328,23 +1446,26 @@ else if (getBlockTypeForTag(attribute) == Material.FLOWER_POT) { // Returns the block location this location is pointing at. // Optionally, specify a maximum range to find the location from (defaults to 200). // --> - if (attribute.startsWith("precise_cursor_on_block")) { - int range = attribute.getIntContext(1); - if (range < 1) { - range = 200; - } - double xzLen = Math.cos((getPitch() % 360) * (Math.PI / 180)); - double nx = xzLen * Math.sin(-getYaw() * (Math.PI / 180)); - double ny = Math.sin(getPitch() * (Math.PI / 180)); - double nz = xzLen * Math.cos(getYaw() * (Math.PI / 180)); - Location location = NMSHandler.getEntityHelper().rayTraceBlock(this, new Vector(nx, -ny, nz), range); - if (location != null) { - return new LocationTag(location).getBlockLocation().getAttribute(attribute.fulfill(1)); - } - else { - return null; + registerTag("precise_cursor_on_block", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + int range = attribute.getIntContext(1); + if (range < 1) { + range = 200; + } + double xzLen = Math.cos((getPitch() % 360) * (Math.PI / 180)); + double nx = xzLen * Math.sin(-getYaw() * (Math.PI / 180)); + double ny = Math.sin(getPitch() * (Math.PI / 180)); + double nz = xzLen * Math.cos(getYaw() * (Math.PI / 180)); + Location location = NMSHandler.getEntityHelper().rayTraceBlock((LocationTag) object, new Vector(nx, -ny, nz), range); + if (location != null) { + return new LocationTag(location).getBlockLocation().getObjectAttribute(attribute.fulfill(1)); + } + else { + return null; + } } - } + }); // <--[tag] // @attribute ]> @@ -1353,23 +1474,26 @@ else if (getBlockTypeForTag(attribute) == Material.FLOWER_POT) { // Returns the exact location this location is pointing at. // Optionally, specify a maximum range to find the location from (defaults to 200). // --> - if (attribute.startsWith("precise_cursor_on")) { - int range = attribute.getIntContext(1); - if (range < 1) { - range = 200; - } - double xzLen = Math.cos((getPitch() % 360) * (Math.PI / 180)); - double nx = xzLen * Math.sin(-getYaw() * (Math.PI / 180)); - double ny = Math.sin(getPitch() * (Math.PI / 180)); - double nz = xzLen * Math.cos(getYaw() * (Math.PI / 180)); - Location location = NMSHandler.getEntityHelper().rayTrace(this, new Vector(nx, -ny, nz), range); - if (location != null) { - return new LocationTag(location).getAttribute(attribute.fulfill(1)); - } - else { - return null; + registerTag("precise_cursor_on", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + int range = attribute.getIntContext(1); + if (range < 1) { + range = 200; + } + double xzLen = Math.cos((getPitch() % 360) * (Math.PI / 180)); + double nx = xzLen * Math.sin(-getYaw() * (Math.PI / 180)); + double ny = Math.sin(getPitch() * (Math.PI / 180)); + double nz = xzLen * Math.cos(getYaw() * (Math.PI / 180)); + Location location = NMSHandler.getEntityHelper().rayTrace((LocationTag) object, new Vector(nx, -ny, nz), range); + if (location != null) { + return new LocationTag(location).getObjectAttribute(attribute.fulfill(1)); + } + else { + return null; + } } - } + }); // <--[tag] // @attribute ]> @@ -1377,32 +1501,35 @@ else if (getBlockTypeForTag(attribute) == Material.FLOWER_POT) { // @description // Finds all locations between this location and another, separated by 1 block-width each. // --> - if (attribute.startsWith("points_between")) { - LocationTag target = LocationTag.valueOf(attribute.getContext(1)); - if (target == null) { - return null; - } - attribute = attribute.fulfill(1); - // <--[tag] - // @attribute ].distance[<#.#>]> - // @returns ListTag(LocationTag) - // @description - // Finds all locations between this location and another, separated by the specified distance each. - // --> - double rad = 1d; - if (attribute.startsWith("distance")) { - rad = attribute.getDoubleContext(1); + registerTag("points_between", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + LocationTag target = LocationTag.valueOf(attribute.getContext(1)); + if (target == null) { + return null; + } attribute = attribute.fulfill(1); + // <--[tag] + // @attribute ].distance[<#.#>]> + // @returns ListTag(LocationTag) + // @description + // Finds all locations between this location and another, separated by the specified distance each. + // --> + double rad = 1d; + if (attribute.startsWith("distance")) { + rad = attribute.getDoubleContext(1); + attribute = attribute.fulfill(1); + } + ListTag list = new ListTag(); + org.bukkit.util.Vector rel = target.toVector().subtract(((LocationTag) object).toVector()); + double len = rel.length(); + rel = rel.multiply(1d / len); + for (double i = 0d; i <= len; i += rad) { + list.add(new LocationTag(((LocationTag) object).clone().add(rel.clone().multiply(i))).identify()); + } + return list.getObjectAttribute(attribute); } - ListTag list = new ListTag(); - org.bukkit.util.Vector rel = target.toVector().subtract(this.toVector()); - double len = rel.length(); - rel = rel.multiply(1d / len); - for (double i = 0d; i <= len; i += rad) { - list.add(new LocationTag(this.clone().add(rel.clone().multiply(i))).identify()); - } - return list.getAttribute(attribute); - } + }); // <--[tag] // @attribute ]> @@ -1413,24 +1540,27 @@ else if (getBlockTypeForTag(attribute) == Material.FLOWER_POT) { // For example a location at 0,0,0 facing straight up // will include 0,1,0 0,2,0 and so on. // --> - if (attribute.startsWith("facing_blocks")) { - int range = attribute.getIntContext(1); - if (range < 1) { - range = 100; - } - ListTag list = new ListTag(); - try { - NMSHandler.getChunkHelper().changeChunkServerThread(getWorld()); - BlockIterator iterator = new BlockIterator(this, 0, range); - while (iterator.hasNext()) { - list.add(new LocationTag(iterator.next().getLocation()).identify()); + registerTag("facing_blocks", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + int range = attribute.getIntContext(1); + if (range < 1) { + range = 100; } + ListTag list = new ListTag(); + try { + NMSHandler.getChunkHelper().changeChunkServerThread(getWorld()); + BlockIterator iterator = new BlockIterator((LocationTag) object, 0, range); + while (iterator.hasNext()) { + list.add(new LocationTag(iterator.next().getLocation()).identify()); + } + } + finally { + NMSHandler.getChunkHelper().restoreServerThread(getWorld()); + } + return list.getObjectAttribute(attribute.fulfill(1)); } - finally { - NMSHandler.getChunkHelper().restoreServerThread(getWorld()); - } - return list.getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute ]> @@ -1439,19 +1569,26 @@ else if (getBlockTypeForTag(attribute) == Material.FLOWER_POT) { // Returns whether the specified location is within this location's // line of sight. // --> - if (attribute.startsWith("line_of_sight") && attribute.hasContext(1)) { - LocationTag location = LocationTag.valueOf(attribute.getContext(1)); - if (location != null) { - try { - NMSHandler.getChunkHelper().changeChunkServerThread(getWorld()); - return new ElementTag(NMSHandler.getEntityHelper().canTrace(getWorld(), toVector(), location.toVector())) - .getAttribute(attribute.fulfill(1)); + registerTag("line_of_sight", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; } - finally { - NMSHandler.getChunkHelper().restoreServerThread(getWorld()); + LocationTag location = LocationTag.valueOf(attribute.getContext(1)); + if (location != null) { + try { + NMSHandler.getChunkHelper().changeChunkServerThread(getWorld()); + return new ElementTag(NMSHandler.getEntityHelper().canTrace(getWorld(), toVector(), location.toVector())) + .getObjectAttribute(attribute.fulfill(1)); + } + finally { + NMSHandler.getChunkHelper().restoreServerThread(getWorld()); + } } + return null; } - } + }); // <--[tag] // @attribute @@ -1459,13 +1596,16 @@ else if (getBlockTypeForTag(attribute) == Material.FLOWER_POT) { // @description // Returns the location's direction as a one-length vector. // --> - if (attribute.startsWith("direction.vector")) { - double xzLen = Math.cos((getPitch() % 360) * (Math.PI / 180)); - double nx = xzLen * Math.sin(-getYaw() * (Math.PI / 180)); - double ny = Math.sin(getPitch() * (Math.PI / 180)); - double nz = xzLen * Math.cos(getYaw() * (Math.PI / 180)); - return new LocationTag(getWorld(), nx, -ny, nz).getAttribute(attribute.fulfill(2)); - } + registerTag("direction.vector", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + double xzLen = Math.cos((getPitch() % 360) * (Math.PI / 180)); + double nx = xzLen * Math.sin(-getYaw() * (Math.PI / 180)); + double ny = Math.sin(getPitch() * (Math.PI / 180)); + double nz = xzLen * Math.cos(getYaw() * (Math.PI / 180)); + return new LocationTag(getWorld(), nx, -ny, nz).getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute ]> @@ -1475,39 +1615,42 @@ else if (getBlockTypeForTag(attribute) == Material.FLOWER_POT) { // If no second location is specified, returns the direction of the location. // Example returns include "north", "southwest", ... // --> - if (attribute.startsWith("direction")) { - // Get the cardinal direction from this location to another - if (attribute.hasContext(1) && LocationTag.matches(attribute.getContext(1))) { - // Subtract this location's vector from the other location's vector, - // not the other way around - LocationTag target = LocationTag.valueOf(attribute.getContext(1)); - attribute = attribute.fulfill(1); - EntityHelper entityHelper = NMSHandler.getEntityHelper(); - // <--[tag] - // @attribute ].yaw> - // @returns ElementTag(Decimal) - // @description - // Returns the yaw direction between two locations. - // --> - if (attribute.startsWith("yaw")) { - return new ElementTag(entityHelper.normalizeYaw(entityHelper.getYaw - (target.toVector().subtract(this.toVector()) - .normalize()))) - .getAttribute(attribute.fulfill(1)); + registerTag("direction", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + // Get the cardinal direction from this location to another + if (attribute.hasContext(1) && LocationTag.matches(attribute.getContext(1))) { + // Subtract this location's vector from the other location's vector, + // not the other way around + LocationTag target = LocationTag.valueOf(attribute.getContext(1)); + attribute = attribute.fulfill(1); + EntityHelper entityHelper = NMSHandler.getEntityHelper(); + // <--[tag] + // @attribute ].yaw> + // @returns ElementTag(Decimal) + // @description + // Returns the yaw direction between two locations. + // --> + if (attribute.startsWith("yaw")) { + return new ElementTag(entityHelper.normalizeYaw(entityHelper.getYaw + (target.toVector().subtract(((LocationTag) object).toVector()) + .normalize()))) + .getObjectAttribute(attribute.fulfill(1)); + } + else { + return new ElementTag(entityHelper.getCardinal(entityHelper.getYaw + (target.toVector().subtract(((LocationTag) object).toVector()) + .normalize()))) + .getObjectAttribute(attribute); + } } + // Get a cardinal direction from this location's yaw else { - return new ElementTag(entityHelper.getCardinal(entityHelper.getYaw - (target.toVector().subtract(this.toVector()) - .normalize()))) - .getAttribute(attribute); + return new ElementTag(NMSHandler.getEntityHelper().getCardinal(getYaw())) + .getObjectAttribute(attribute.fulfill(1)); } } - // Get a cardinal direction from this location's yaw - else { - return new ElementTag(NMSHandler.getEntityHelper().getCardinal(getYaw())) - .getAttribute(attribute.fulfill(1)); - } - } + }); // <--[tag] // @attribute ]> @@ -1516,12 +1659,17 @@ else if (getBlockTypeForTag(attribute) == Material.FLOWER_POT) { // Returns a location containing a yaw/pitch that point from the current location // to the target location. // --> - if (attribute.startsWith("face") - && attribute.hasContext(1)) { - Location two = LocationTag.valueOf(attribute.getContext(1)); - return new LocationTag(NMSHandler.getEntityHelper().faceLocation(this, two)) - .getAttribute(attribute.fulfill(1)); - } + registerTag("face", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; + } + Location two = LocationTag.valueOf(attribute.getContext(1)); + return new LocationTag(NMSHandler.getEntityHelper().faceLocation((LocationTag) object, two)) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute /]> @@ -1530,62 +1678,66 @@ else if (getBlockTypeForTag(attribute) == Material.FLOWER_POT) { // Returns whether the location's yaw is facing another // entity or location. // --> - if (attribute.startsWith("facing")) { - if (attribute.hasContext(1)) { + registerTag("facing", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (attribute.hasContext(1)) { - // The default number of degrees if there is no degrees attribute - int degrees = 45; + // The default number of degrees if there is no degrees attribute + int degrees = 45; - // The attribute to fulfill from - int attributePos = 1; + // The attribute to fulfill from + int attributePos = 1; - // <--[tag] - // @attribute /].degrees[<#>(,<#>)]> - // @returns ElementTag(Boolean) - // @description - // Returns whether the location's yaw is facing another - // entity or location, within a specified degree range. - // Optionally specify a pitch limit as well. - // --> - if (attribute.getAttribute(2).startsWith("degrees") && - attribute.hasContext(2)) { - String context = attribute.getContext(2); - if (context.contains(",")) { - String yaw = context.substring(0, context.indexOf(',')); - String pitch = context.substring(context.indexOf(',') + 1); - degrees = ArgumentHelper.getIntegerFrom(yaw); - int pitchDegrees = ArgumentHelper.getIntegerFrom(pitch); - if (LocationTag.matches(attribute.getContext(1))) { - return new ElementTag(NMSHandler.getEntityHelper().isFacingLocation - (this, LocationTag.valueOf(attribute.getContext(1)), degrees, pitchDegrees)) - .getAttribute(attribute.fulfill(attributePos)); + // <--[tag] + // @attribute /].degrees[<#>(,<#>)]> + // @returns ElementTag(Boolean) + // @description + // Returns whether the location's yaw is facing another + // entity or location, within a specified degree range. + // Optionally specify a pitch limit as well. + // --> + if (attribute.getAttribute(2).startsWith("degrees") && + attribute.hasContext(2)) { + String context = attribute.getContext(2); + if (context.contains(",")) { + String yaw = context.substring(0, context.indexOf(',')); + String pitch = context.substring(context.indexOf(',') + 1); + degrees = ArgumentHelper.getIntegerFrom(yaw); + int pitchDegrees = ArgumentHelper.getIntegerFrom(pitch); + if (LocationTag.matches(attribute.getContext(1))) { + return new ElementTag(NMSHandler.getEntityHelper().isFacingLocation + ((LocationTag) object, LocationTag.valueOf(attribute.getContext(1)), degrees, pitchDegrees)) + .getObjectAttribute(attribute.fulfill(attributePos)); + } + else if (EntityTag.matches(attribute.getContext(1))) { + return new ElementTag(NMSHandler.getEntityHelper().isFacingLocation + ((LocationTag) object, EntityTag.valueOf(attribute.getContext(1)) + .getBukkitEntity().getLocation(), degrees, pitchDegrees)) + .getObjectAttribute(attribute.fulfill(attributePos)); + } } - else if (EntityTag.matches(attribute.getContext(1))) { - return new ElementTag(NMSHandler.getEntityHelper().isFacingLocation - (this, EntityTag.valueOf(attribute.getContext(1)) - .getBukkitEntity().getLocation(), degrees, pitchDegrees)) - .getAttribute(attribute.fulfill(attributePos)); + else { + degrees = attribute.getIntContext(2); + attributePos++; } } - else { - degrees = attribute.getIntContext(2); - attributePos++; - } - } - if (LocationTag.matches(attribute.getContext(1))) { - return new ElementTag(NMSHandler.getEntityHelper().isFacingLocation - (this, LocationTag.valueOf(attribute.getContext(1)), degrees)) - .getAttribute(attribute.fulfill(attributePos)); - } - else if (EntityTag.matches(attribute.getContext(1))) { - return new ElementTag(NMSHandler.getEntityHelper().isFacingLocation - (this, EntityTag.valueOf(attribute.getContext(1)) - .getBukkitEntity().getLocation(), degrees)) - .getAttribute(attribute.fulfill(attributePos)); + if (LocationTag.matches(attribute.getContext(1))) { + return new ElementTag(NMSHandler.getEntityHelper().isFacingLocation + ((LocationTag) object, LocationTag.valueOf(attribute.getContext(1)), degrees)) + .getObjectAttribute(attribute.fulfill(attributePos)); + } + else if (EntityTag.matches(attribute.getContext(1))) { + return new ElementTag(NMSHandler.getEntityHelper().isFacingLocation + ((LocationTag) object, EntityTag.valueOf(attribute.getContext(1)) + .getBukkitEntity().getLocation(), degrees)) + .getObjectAttribute(attribute.fulfill(attributePos)); + } } + return null; } - } + }); // <--[tag] // @attribute @@ -1593,9 +1745,12 @@ else if (EntityTag.matches(attribute.getContext(1))) { // @description // Returns the pitch of the object at the location. // --> - if (attribute.startsWith("pitch")) { - return new ElementTag(getPitch()).getAttribute(attribute.fulfill(1)); - } + registerTag("pitch", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getPitch()).getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute /,]> @@ -1603,27 +1758,30 @@ else if (EntityTag.matches(attribute.getContext(1))) { // @description // Returns the location with pitch and yaw. // --> - if (attribute.startsWith("with_pose")) { - String context = attribute.getContext(1); - float pitch = 0f; - float yaw = 0f; - if (EntityTag.matches(context)) { - EntityTag ent = EntityTag.valueOf(context); - if (ent.isSpawned()) { - pitch = ent.getBukkitEntity().getLocation().getPitch(); - yaw = ent.getBukkitEntity().getLocation().getYaw(); + registerTag("with_pose", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + String context = attribute.getContext(1); + float pitch = 0f; + float yaw = 0f; + if (EntityTag.matches(context)) { + EntityTag ent = EntityTag.valueOf(context); + if (ent.isSpawned()) { + pitch = ent.getBukkitEntity().getLocation().getPitch(); + yaw = ent.getBukkitEntity().getLocation().getYaw(); + } } + else if (context.split(",").length == 2) { + String[] split = context.split(","); + pitch = Float.parseFloat(split[0]); + yaw = Float.parseFloat(split[1]); + } + LocationTag loc = LocationTag.valueOf(identify()); + loc.setPitch(pitch); + loc.setYaw(yaw); + return loc.getObjectAttribute(attribute.fulfill(1)); } - else if (context.split(",").length == 2) { - String[] split = context.split(","); - pitch = Float.parseFloat(split[0]); - yaw = Float.parseFloat(split[1]); - } - LocationTag loc = LocationTag.valueOf(identify()); - loc.setPitch(pitch); - loc.setYaw(yaw); - return loc.getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute @@ -1631,29 +1789,32 @@ else if (context.split(",").length == 2) { // @description // Returns the yaw as 'North', 'South', 'East', or 'West'. // --> - if (attribute.startsWith("yaw.simple")) { - float yaw = NMSHandler.getEntityHelper().normalizeYaw(getYaw()); - if (yaw < 45) { - return new ElementTag("South") - .getAttribute(attribute.fulfill(2)); - } - else if (yaw < 135) { - return new ElementTag("West") - .getAttribute(attribute.fulfill(2)); - } - else if (yaw < 225) { - return new ElementTag("North") - .getAttribute(attribute.fulfill(2)); - } - else if (yaw < 315) { - return new ElementTag("East") - .getAttribute(attribute.fulfill(2)); - } - else { - return new ElementTag("South") - .getAttribute(attribute.fulfill(2)); + registerTag("yaw.simple", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + float yaw = NMSHandler.getEntityHelper().normalizeYaw(getYaw()); + if (yaw < 45) { + return new ElementTag("South") + .getObjectAttribute(attribute.fulfill(2)); + } + else if (yaw < 135) { + return new ElementTag("West") + .getObjectAttribute(attribute.fulfill(2)); + } + else if (yaw < 225) { + return new ElementTag("North") + .getObjectAttribute(attribute.fulfill(2)); + } + else if (yaw < 315) { + return new ElementTag("East") + .getObjectAttribute(attribute.fulfill(2)); + } + else { + return new ElementTag("South") + .getObjectAttribute(attribute.fulfill(2)); + } } - } + }); // <--[tag] // @attribute @@ -1661,10 +1822,13 @@ else if (yaw < 315) { // @description // Returns the raw yaw of the object at the location. // --> - if (attribute.startsWith("yaw.raw")) { - return new ElementTag(getYaw()) - .getAttribute(attribute.fulfill(2)); - } + registerTag("yaw.raw", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getYaw()) + .getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -1672,10 +1836,13 @@ else if (yaw < 315) { // @description // Returns the normalized yaw of the object at the location. // --> - if (attribute.startsWith("yaw")) { - return new ElementTag(NMSHandler.getEntityHelper().normalizeYaw(getYaw())) - .getAttribute(attribute.fulfill(1)); - } + registerTag("yaw", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(NMSHandler.getEntityHelper().normalizeYaw(getYaw())) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute ]> @@ -1683,17 +1850,23 @@ else if (yaw < 315) { // @description // Returns the location rotated around the x axis by a specified angle in radians. // --> - if (attribute.startsWith("rotate_around_x") && attribute.hasContext(1)) { - double angle = attribute.getDoubleContext(1); - double cos = Math.cos(angle); - double sin = Math.sin(angle); - double y = (getY() * cos) - (getZ() * sin); - double z = (getY() * sin) + (getZ() * cos); - Location location = clone(); - location.setY(y); - location.setZ(z); - return new LocationTag(location).getAttribute(attribute.fulfill(1)); - } + registerTag("rotate_around_x", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; + } + double angle = attribute.getDoubleContext(1); + double cos = Math.cos(angle); + double sin = Math.sin(angle); + double y = (getY() * cos) - (getZ() * sin); + double z = (getY() * sin) + (getZ() * cos); + Location location = ((LocationTag) object).clone(); + location.setY(y); + location.setZ(z); + return new LocationTag(location).getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute ]> @@ -1701,17 +1874,23 @@ else if (yaw < 315) { // @description // Returns the location rotated around the y axis by a specified angle in radians. // --> - if (attribute.startsWith("rotate_around_y") && attribute.hasContext(1)) { - double angle = attribute.getDoubleContext(1); - double cos = Math.cos(angle); - double sin = Math.sin(angle); - double x = (getX() * cos) + (getZ() * sin); - double z = (getX() * -sin) + (getZ() * cos); - Location location = clone(); - location.setX(x); - location.setZ(z); - return new LocationTag(location).getAttribute(attribute.fulfill(1)); - } + registerTag("rotate_around_y", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; + } + double angle = attribute.getDoubleContext(1); + double cos = Math.cos(angle); + double sin = Math.sin(angle); + double x = (getX() * cos) + (getZ() * sin); + double z = (getX() * -sin) + (getZ() * cos); + Location location = ((LocationTag) object).clone(); + location.setX(x); + location.setZ(z); + return new LocationTag(location).getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute ]> @@ -1719,295 +1898,305 @@ else if (yaw < 315) { // @description // Returns the location rotated around the z axis by a specified angle in radians. // --> - if (attribute.startsWith("rotate_around_z") && attribute.hasContext(1)) { - double angle = attribute.getDoubleContext(1); - double cos = Math.cos(angle); - double sin = Math.sin(angle); - double x = (getX() * cos) - (getY() * sin); - double y = (getZ() * sin) + (getY() * cos); - Location location = clone(); - location.setX(x); - location.setY(y); - return new LocationTag(location).getAttribute(attribute.fulfill(1)); - } + registerTag("rotate_around_z", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; + } + double angle = attribute.getDoubleContext(1); + double cos = Math.cos(angle); + double sin = Math.sin(angle); + double x = (getX() * cos) - (getY() * sin); + double y = (getZ() * sin) + (getY() * cos); + Location location = ((LocationTag) object).clone(); + location.setX(x); + location.setY(y); + return new LocationTag(location).getObjectAttribute(attribute.fulfill(1)); + } + }); ///////////////////// // ENTITY AND BLOCK LIST ATTRIBUTES ///////////////// - if (attribute.matches("find") || attribute.startsWith("nearest")) { - attribute.fulfill(1); - - // <--[tag] - // @attribute |...].within[<#>]> - // @returns ListTag - // @description - // Returns a list of matching blocks within a radius. - // Note: current implementation measures the center of nearby block's distance from the exact given location. - // --> - if (attribute.startsWith("blocks") - && attribute.getAttribute(2).startsWith("within") - && attribute.hasContext(2)) { - ArrayList found = new ArrayList<>(); - int radius = ArgumentHelper.matchesInteger(attribute.getContext(2)) ? attribute.getIntContext(2) : 10; - List materials = new ArrayList<>(); - if (attribute.hasContext(1)) { - materials = ListTag.valueOf(attribute.getContext(1)).filter(MaterialTag.class, attribute.context); - } - // Avoid NPE from invalid materials - if (materials == null) { - return null; - } - int max = Settings.blockTagsMaxBlocks(); - int index = 0; - - attribute.fulfill(2); - Location tstart = getBlockForTag(attribute).getLocation(); - double tstartY = tstart.getY(); + registerTag("find", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + attribute.fulfill(1); - fullloop: - for (int x = -(radius); x <= radius; x++) { - for (int y = -(radius); y <= radius; y++) { - double newY = y + tstartY; - if (newY < 0 || newY > 255) { - continue; - } - for (int z = -(radius); z <= radius; z++) { - index++; - if (index > max) { - break fullloop; + // <--[tag] + // @attribute |...].within[<#>]> + // @returns ListTag + // @description + // Returns a list of matching blocks within a radius. + // Note: current implementation measures the center of nearby block's distance from the exact given location. + // --> + if (attribute.startsWith("blocks") + && attribute.getAttribute(2).startsWith("within") + && attribute.hasContext(2)) { + ArrayList found = new ArrayList<>(); + int radius = ArgumentHelper.matchesInteger(attribute.getContext(2)) ? attribute.getIntContext(2) : 10; + List materials = new ArrayList<>(); + if (attribute.hasContext(1)) { + materials = ListTag.valueOf(attribute.getContext(1)).filter(MaterialTag.class, attribute.context); + } + // Avoid NPE from invalid materials + if (materials == null) { + return null; + } + int max = Settings.blockTagsMaxBlocks(); + int index = 0; + + attribute.fulfill(2); + Location tstart = getBlockForTag(attribute).getLocation(); + double tstartY = tstart.getY(); + + fullloop: + for (int x = -(radius); x <= radius; x++) { + for (int y = -(radius); y <= radius; y++) { + double newY = y + tstartY; + if (newY < 0 || newY > 255) { + continue; } - if (Utilities.checkLocation(this, tstart.clone().add(x + 0.5, y + 0.5, z + 0.5), radius)) { - if (!materials.isEmpty()) { - for (MaterialTag material : materials) { - if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_12) && material.hasData() && material.getData() != 0) { - BlockState bs = new LocationTag(tstart.clone().add(x, y, z)).getBlockStateForTag(attribute); - if (bs != null && material.matchesMaterialData(bs.getData())) { + for (int z = -(radius); z <= radius; z++) { + index++; + if (index > max) { + break fullloop; + } + if (Utilities.checkLocation((LocationTag) object, tstart.clone().add(x + 0.5, y + 0.5, z + 0.5), radius)) { + if (!materials.isEmpty()) { + for (MaterialTag material : materials) { + if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_12) && material.hasData() && material.getData() != 0) { + BlockState bs = new LocationTag(tstart.clone().add(x, y, z)).getBlockStateForTag(attribute); + if (bs != null && material.matchesMaterialData(bs.getData())) { + found.add(new LocationTag(tstart.clone().add(x, y, z))); + } + } + else if (material.getMaterial() == new LocationTag(tstart.clone().add(x, y, z)).getBlockTypeForTag(attribute)) { found.add(new LocationTag(tstart.clone().add(x, y, z))); } } - else if (material.getMaterial() == new LocationTag(tstart.clone().add(x, y, z)).getBlockTypeForTag(attribute)) { - found.add(new LocationTag(tstart.clone().add(x, y, z))); - } } - } - else { - found.add(new LocationTag(tstart.clone().add(x, y, z))); + else { + found.add(new LocationTag(tstart.clone().add(x, y, z))); + } } } } } - } - Collections.sort(found, new Comparator() { - @Override - public int compare(LocationTag loc1, LocationTag loc2) { - return LocationTag.this.compare(loc1, loc2); - } - }); - - return new ListTag(found).getAttribute(attribute); - } - - // <--[tag] - // @attribute |...].within[<#.#>]> - // @returns ListTag - // @description - // Returns a list of matching surface blocks within a radius. - // --> - else if (attribute.startsWith("surface_blocks") - && attribute.getAttribute(2).startsWith("within") - && attribute.hasContext(2)) { - ArrayList found = new ArrayList<>(); - double radius = ArgumentHelper.matchesDouble(attribute.getContext(2)) ? attribute.getDoubleContext(2) : 10; - List materials = new ArrayList<>(); - if (attribute.hasContext(1)) { - materials = ListTag.valueOf(attribute.getContext(1)).filter(MaterialTag.class, attribute.context); - } - // Avoid NPE from invalid materials - if (materials == null) { - return null; - } - int max = Settings.blockTagsMaxBlocks(); - int index = 0; + Collections.sort(found, new Comparator() { + @Override + public int compare(LocationTag loc1, LocationTag loc2) { + return ((LocationTag) object).compare(loc1, loc2); + } + }); - attribute.fulfill(2); - Location blockLoc = getBlockLocation(); - Location loc = blockLoc.clone().add(0.5f, 0.5f, 0.5f); + return new ListTag(found).getObjectAttribute(attribute); + } - fullloop: - for (double x = -(radius); x <= radius; x++) { - for (double y = -(radius); y <= radius; y++) { - for (double z = -(radius); z <= radius; z++) { - index++; - if (index > max) { - break fullloop; - } - if (Utilities.checkLocation(loc, blockLoc.clone().add(x + 0.5, y + 0.5, z + 0.5), radius)) { - LocationTag l = new LocationTag(blockLoc.clone().add(x, y, z)); - if (!materials.isEmpty()) { - for (MaterialTag material : materials) { - if (material.matchesBlock(l.getBlockForTag(attribute))) { - if (new LocationTag(l.clone().add(0, 1, 0)).getBlockTypeForTag(attribute) == Material.AIR - && new LocationTag(l.clone().add(0, 2, 0)).getBlockTypeForTag(attribute) == Material.AIR - && l.getBlockTypeForTag(attribute) != Material.AIR) { - found.add(new LocationTag(blockLoc.clone().add(x + 0.5, y, z + 0.5))); + // <--[tag] + // @attribute |...].within[<#.#>]> + // @returns ListTag + // @description + // Returns a list of matching surface blocks within a radius. + // --> + else if (attribute.startsWith("surface_blocks") + && attribute.getAttribute(2).startsWith("within") + && attribute.hasContext(2)) { + ArrayList found = new ArrayList<>(); + double radius = ArgumentHelper.matchesDouble(attribute.getContext(2)) ? attribute.getDoubleContext(2) : 10; + List materials = new ArrayList<>(); + if (attribute.hasContext(1)) { + materials = ListTag.valueOf(attribute.getContext(1)).filter(MaterialTag.class, attribute.context); + } + // Avoid NPE from invalid materials + if (materials == null) { + return null; + } + int max = Settings.blockTagsMaxBlocks(); + int index = 0; + + attribute.fulfill(2); + Location blockLoc = getBlockLocation(); + Location loc = blockLoc.clone().add(0.5f, 0.5f, 0.5f); + + fullloop: + for (double x = -(radius); x <= radius; x++) { + for (double y = -(radius); y <= radius; y++) { + for (double z = -(radius); z <= radius; z++) { + index++; + if (index > max) { + break fullloop; + } + if (Utilities.checkLocation(loc, blockLoc.clone().add(x + 0.5, y + 0.5, z + 0.5), radius)) { + LocationTag l = new LocationTag(blockLoc.clone().add(x, y, z)); + if (!materials.isEmpty()) { + for (MaterialTag material : materials) { + if (material.matchesBlock(l.getBlockForTag(attribute))) { + if (new LocationTag(l.clone().add(0, 1, 0)).getBlockTypeForTag(attribute) == Material.AIR + && new LocationTag(l.clone().add(0, 2, 0)).getBlockTypeForTag(attribute) == Material.AIR + && l.getBlockTypeForTag(attribute) != Material.AIR) { + found.add(new LocationTag(blockLoc.clone().add(x + 0.5, y, z + 0.5))); + } } } } - } - else { - if (new LocationTag(l.clone().add(0, 1, 0)).getBlockTypeForTag(attribute) == Material.AIR - && new LocationTag(l.clone().add(0, 2, 0)).getBlockTypeForTag(attribute) == Material.AIR - && l.getBlockTypeForTag(attribute) != Material.AIR) { - found.add(new LocationTag(blockLoc.clone().add(x + 0.5, y, z + 0.5))); + else { + if (new LocationTag(l.clone().add(0, 1, 0)).getBlockTypeForTag(attribute) == Material.AIR + && new LocationTag(l.clone().add(0, 2, 0)).getBlockTypeForTag(attribute) == Material.AIR + && l.getBlockTypeForTag(attribute) != Material.AIR) { + found.add(new LocationTag(blockLoc.clone().add(x + 0.5, y, z + 0.5))); + } } } } } } + + Collections.sort(found, new Comparator() { + @Override + public int compare(LocationTag loc1, LocationTag loc2) { + return ((LocationTag) object).compare(loc1, loc2); + } + }); + + return new ListTag(found).getObjectAttribute(attribute); } - Collections.sort(found, new Comparator() { - @Override - public int compare(LocationTag loc1, LocationTag loc2) { - return LocationTag.this.compare(loc1, loc2); - } - }); - - return new ListTag(found).getAttribute(attribute); - } - - // <--[tag] - // @attribute ]> - // @returns ListTag - // @description - // Returns a list of players within a radius. - // --> - else if (attribute.startsWith("players") - && attribute.getAttribute(2).startsWith("within") - && attribute.hasContext(2)) { - ArrayList found = new ArrayList<>(); - double radius = ArgumentHelper.matchesDouble(attribute.getContext(2)) ? attribute.getDoubleContext(2) : 10; - attribute.fulfill(2); - for (Player player : Bukkit.getOnlinePlayers()) { - if (!player.isDead() && Utilities.checkLocation(this, player.getLocation(), radius)) { - found.add(new PlayerTag(player)); + // <--[tag] + // @attribute ]> + // @returns ListTag + // @description + // Returns a list of players within a radius. + // --> + else if (attribute.startsWith("players") + && attribute.getAttribute(2).startsWith("within") + && attribute.hasContext(2)) { + ArrayList found = new ArrayList<>(); + double radius = ArgumentHelper.matchesDouble(attribute.getContext(2)) ? attribute.getDoubleContext(2) : 10; + attribute.fulfill(2); + for (Player player : Bukkit.getOnlinePlayers()) { + if (!player.isDead() && Utilities.checkLocation((LocationTag) object, player.getLocation(), radius)) { + found.add(new PlayerTag(player)); + } } + + Collections.sort(found, new Comparator() { + @Override + public int compare(PlayerTag pl1, PlayerTag pl2) { + return ((LocationTag) object).compare(pl1.getLocation(), pl2.getLocation()); + } + }); + + return new ListTag(found).getObjectAttribute(attribute); } - Collections.sort(found, new Comparator() { - @Override - public int compare(PlayerTag pl1, PlayerTag pl2) { - return LocationTag.this.compare(pl1.getLocation(), pl2.getLocation()); - } - }); - - return new ListTag(found).getAttribute(attribute); - } - - // <--[tag] - // @attribute ]> - // @returns ListTag - // @description - // Returns a list of NPCs within a radius. - // --> - else if (attribute.startsWith("npcs") - && attribute.getAttribute(2).startsWith("within") - && attribute.hasContext(2)) { - ArrayList found = new ArrayList<>(); - double radius = ArgumentHelper.matchesDouble(attribute.getContext(2)) ? attribute.getDoubleContext(2) : 10; - attribute.fulfill(2); - for (NPC npc : CitizensAPI.getNPCRegistry()) { - if (npc.isSpawned() && Utilities.checkLocation(getBlockForTag(attribute).getLocation(), npc.getStoredLocation(), radius)) { - found.add(new NPCTag(npc)); + // <--[tag] + // @attribute ]> + // @returns ListTag + // @description + // Returns a list of NPCs within a radius. + // --> + else if (attribute.startsWith("npcs") + && attribute.getAttribute(2).startsWith("within") + && attribute.hasContext(2)) { + ArrayList found = new ArrayList<>(); + double radius = ArgumentHelper.matchesDouble(attribute.getContext(2)) ? attribute.getDoubleContext(2) : 10; + attribute.fulfill(2); + for (NPC npc : CitizensAPI.getNPCRegistry()) { + if (npc.isSpawned() && Utilities.checkLocation(getBlockForTag(attribute).getLocation(), npc.getStoredLocation(), radius)) { + found.add(new NPCTag(npc)); + } } + + Collections.sort(found, new Comparator() { + @Override + public int compare(NPCTag npc1, NPCTag npc2) { + return ((LocationTag) object).compare(npc1.getLocation(), npc2.getLocation()); + } + }); + + return new ListTag(found).getObjectAttribute(attribute); } - Collections.sort(found, new Comparator() { - @Override - public int compare(NPCTag npc1, NPCTag npc2) { - return LocationTag.this.compare(npc1.getLocation(), npc2.getLocation()); + // <--[tag] + // @attribute |...].within[<#.#>]> + // @returns ListTag + // @description + // Returns a list of entities within a radius, with an optional search parameter + // for the entity type. + // --> + else if (attribute.startsWith("entities") + && attribute.getAttribute(2).startsWith("within") + && attribute.hasContext(2)) { + ListTag ent_list = new ListTag(); + if (attribute.hasContext(1)) { + ent_list = ListTag.valueOf(attribute.getContext(1)); } - }); - - return new ListTag(found).getAttribute(attribute); - } - - // <--[tag] - // @attribute |...].within[<#.#>]> - // @returns ListTag - // @description - // Returns a list of entities within a radius, with an optional search parameter - // for the entity type. - // --> - else if (attribute.startsWith("entities") - && attribute.getAttribute(2).startsWith("within") - && attribute.hasContext(2)) { - ListTag ent_list = new ListTag(); - if (attribute.hasContext(1)) { - ent_list = ListTag.valueOf(attribute.getContext(1)); - } - ArrayList found = new ArrayList<>(); - double radius = ArgumentHelper.matchesDouble(attribute.getContext(2)) ? attribute.getDoubleContext(2) : 10; - attribute.fulfill(2); - for (Entity entity : new WorldTag(getWorld()).getEntitiesForTag()) { - if (Utilities.checkLocation(this, entity.getLocation(), radius)) { - EntityTag current = new EntityTag(entity); - if (!ent_list.isEmpty()) { - for (String ent : ent_list) { - if (current.comparedTo(ent)) { - found.add(current); - break; + ArrayList found = new ArrayList<>(); + double radius = ArgumentHelper.matchesDouble(attribute.getContext(2)) ? attribute.getDoubleContext(2) : 10; + attribute.fulfill(2); + for (Entity entity : new WorldTag(getWorld()).getEntitiesForTag()) { + if (Utilities.checkLocation((LocationTag) object, entity.getLocation(), radius)) { + EntityTag current = new EntityTag(entity); + if (!ent_list.isEmpty()) { + for (String ent : ent_list) { + if (current.comparedTo(ent)) { + found.add(current); + break; + } } } - } - else { - found.add(current); + else { + found.add(current); + } } } - } - Collections.sort(found, new Comparator() { - @Override - public int compare(EntityTag ent1, EntityTag ent2) { - return LocationTag.this.compare(ent1.getLocation(), ent2.getLocation()); - } - }); - - return new ListTag(found).getAttribute(attribute); - } - - // <--[tag] - // @attribute ]> - // @returns ListTag - // @description - // Returns a list of living entities within a radius. - // --> - else if (attribute.startsWith("living_entities") - && attribute.getAttribute(2).startsWith("within") - && attribute.hasContext(2)) { - ArrayList found = new ArrayList<>(); - double radius = ArgumentHelper.matchesDouble(attribute.getContext(2)) ? attribute.getDoubleContext(2) : 10; - attribute.fulfill(2); - for (Entity entity : new WorldTag(getWorld()).getEntitiesForTag()) { - if (entity instanceof LivingEntity - && Utilities.checkLocation(this, entity.getLocation(), radius)) { - found.add(new EntityTag(entity)); - } + Collections.sort(found, new Comparator() { + @Override + public int compare(EntityTag ent1, EntityTag ent2) { + return ((LocationTag) object).compare(ent1.getLocation(), ent2.getLocation()); + } + }); + + return new ListTag(found).getObjectAttribute(attribute); } - Collections.sort(found, new Comparator() { - @Override - public int compare(EntityTag ent1, EntityTag ent2) { - return LocationTag.this.compare(ent1.getLocation(), ent2.getLocation()); + // <--[tag] + // @attribute ]> + // @returns ListTag + // @description + // Returns a list of living entities within a radius. + // --> + else if (attribute.startsWith("living_entities") + && attribute.getAttribute(2).startsWith("within") + && attribute.hasContext(2)) { + ArrayList found = new ArrayList<>(); + double radius = ArgumentHelper.matchesDouble(attribute.getContext(2)) ? attribute.getDoubleContext(2) : 10; + attribute.fulfill(2); + for (Entity entity : new WorldTag(getWorld()).getEntitiesForTag()) { + if (entity instanceof LivingEntity + && Utilities.checkLocation((LocationTag) object, entity.getLocation(), radius)) { + found.add(new EntityTag(entity)); + } } - }); - return new ListTag(found).getAttribute(attribute); + Collections.sort(found, new Comparator() { + @Override + public int compare(EntityTag ent1, EntityTag ent2) { + return ((LocationTag) object).compare(ent1.getLocation(), ent2.getLocation()); + } + }); + + return new ListTag(found).getObjectAttribute(attribute); + } + return null; } - } + }); // <--[tag] // @attribute ]> @@ -2016,19 +2205,24 @@ public int compare(EntityTag ent1, EntityTag ent2) { // Returns a full list of points along the path from this location to the given location. // Uses a max range of 100 blocks from the start. // --> - if (attribute.startsWith("find_path") - && attribute.hasContext(1)) { - LocationTag two = LocationTag.valueOf(attribute.getContext(1)); - if (two == null) { - return null; - } - List locs = PathFinder.getPath(this, two); - ListTag list = new ListTag(); - for (LocationTag loc : locs) { - list.add(loc.identify()); + registerTag("find_path", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; + } + LocationTag two = LocationTag.valueOf(attribute.getContext(1)); + if (two == null) { + return null; + } + List locs = PathFinder.getPath((LocationTag) object, two); + ListTag list = new ListTag(); + for (LocationTag loc : locs) { + list.add(loc.identify()); + } + return list.getObjectAttribute(attribute.fulfill(1)); } - return list.getAttribute(attribute.fulfill(1)); - } + }); ///////////////////// @@ -2043,9 +2237,12 @@ public int compare(EntityTag ent1, EntityTag ent2) { // In the format: x.x:y.y:z.z:world // For example: 1.0:2.0:3.0:world_nether // --> - if (attribute.startsWith("formatted.citizens")) { - return new ElementTag(getX() + ":" + getY() + ":" + getZ() + ":" + getWorldName()).getAttribute(attribute.fulfill(2)); - } + registerTag("formatted.citizens", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getX() + ":" + getY() + ":" + getZ() + ":" + getWorldName()).getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -2055,12 +2252,15 @@ public int compare(EntityTag ent1, EntityTag ent2) { // In the format: X 'x.x', Y 'y.y', Z 'z.z', in world 'world' // For example: X '1.0', Y '2.0', Z '3.0', in world 'world_nether' // --> - if (attribute.startsWith("formatted")) { - return new ElementTag("X '" + getX() - + "', Y '" + getY() - + "', Z '" + getZ() - + "', in world '" + getWorldName() + "'").getAttribute(attribute.fulfill(1)); - } + registerTag("formatted", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag("X '" + getX() + + "', Y '" + getY() + + "', Z '" + getZ() + + "', in world '" + getWorldName() + "'").getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2068,10 +2268,13 @@ public int compare(EntityTag ent1, EntityTag ent2) { // @description // Returns the chunk that this location belongs to. // --> - if (attribute.startsWith("chunk") || - attribute.startsWith("get_chunk")) { - return new ChunkTag(this).getAttribute(attribute.fulfill(1)); - } + registerTag("chunk", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ChunkTag((LocationTag) object).getObjectAttribute(attribute.fulfill(1)); + } + }); + registerTag("get_chunk", tagProcessor.registeredObjectTags.get("chunk")); // <--[tag] // @attribute @@ -2080,11 +2283,14 @@ public int compare(EntityTag ent1, EntityTag ent2) { // Returns the raw representation of this location, // ignoring any notables it might match. // --> - if (attribute.startsWith("raw")) { - LocationTag rawLocation = new LocationTag(this); - rawLocation.setRaw(true); - return rawLocation.getAttribute(attribute.fulfill(1)); - } + registerTag("raw", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + LocationTag rawLocation = new LocationTag((LocationTag) object); + rawLocation.setRaw(true); + return rawLocation.getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2092,10 +2298,13 @@ public int compare(EntityTag ent1, EntityTag ent2) { // @description // Returns the world that the location is in. // --> - if (attribute.startsWith("world")) { - return WorldTag.mirrorBukkitWorld(getWorld()) - .getAttribute(attribute.fulfill(1)); - } + registerTag("world", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return WorldTag.mirrorBukkitWorld(getWorld()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2103,9 +2312,12 @@ public int compare(EntityTag ent1, EntityTag ent2) { // @description // Returns the X coordinate of the location. // --> - if (attribute.startsWith("x")) { - return new ElementTag(getX()).getAttribute(attribute.fulfill(1)); - } + registerTag("x", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getX()).getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2113,9 +2325,12 @@ public int compare(EntityTag ent1, EntityTag ent2) { // @description // Returns the Y coordinate of the location. // --> - if (attribute.startsWith("y")) { - return new ElementTag(getY()).getAttribute(attribute.fulfill(1)); - } + registerTag("y", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getY()).getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2123,9 +2338,12 @@ public int compare(EntityTag ent1, EntityTag ent2) { // @description // Returns the Z coordinate of the location. // --> - if (attribute.startsWith("z")) { - return new ElementTag(getZ()).getAttribute(attribute.fulfill(1)); - } + registerTag("z", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getZ()).getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute ]> @@ -2133,11 +2351,17 @@ public int compare(EntityTag ent1, EntityTag ent2) { // @description // Returns a copy of the location with a changed X value. // --> - if (attribute.matches("with_x") && attribute.hasContext(1)) { - LocationTag output = clone(); - output.setX(attribute.getDoubleContext(1)); - return output.getAttribute(attribute.fulfill(1)); - } + registerTag("with_x", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; + } + LocationTag output = ((LocationTag) object).clone(); + output.setX(attribute.getDoubleContext(1)); + return output.getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute ]> @@ -2145,11 +2369,17 @@ public int compare(EntityTag ent1, EntityTag ent2) { // @description // Returns a copy of the location with a changed Y value. // --> - if (attribute.matches("with_y") && attribute.hasContext(1)) { - LocationTag output = clone(); - output.setY(attribute.getDoubleContext(1)); - return output.getAttribute(attribute.fulfill(1)); - } + registerTag("with_y", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; + } + LocationTag output = ((LocationTag) object).clone(); + output.setY(attribute.getDoubleContext(1)); + return output.getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute ]> @@ -2157,11 +2387,17 @@ public int compare(EntityTag ent1, EntityTag ent2) { // @description // Returns a copy of the location with a changed Z value. // --> - if (attribute.matches("with_z") && attribute.hasContext(1)) { - LocationTag output = clone(); - output.setZ(attribute.getDoubleContext(1)); - return output.getAttribute(attribute.fulfill(1)); - } + registerTag("with_z", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; + } + LocationTag output = ((LocationTag) object).clone(); + output.setZ(attribute.getDoubleContext(1)); + return output.getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute ]> @@ -2169,11 +2405,17 @@ public int compare(EntityTag ent1, EntityTag ent2) { // @description // Returns a copy of the location with a changed yaw value. // --> - if (attribute.matches("with_yaw") && attribute.hasContext(1)) { - LocationTag output = clone(); - output.setYaw((float) attribute.getDoubleContext(1)); - return output.getAttribute(attribute.fulfill(1)); - } + registerTag("with_yaw", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; + } + LocationTag output = ((LocationTag) object).clone(); + output.setYaw((float) attribute.getDoubleContext(1)); + return output.getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute ]> @@ -2181,11 +2423,17 @@ public int compare(EntityTag ent1, EntityTag ent2) { // @description // Returns a copy of the location with a changed pitch value. // --> - if (attribute.matches("with_pitch") && attribute.hasContext(1)) { - LocationTag output = clone(); - output.setPitch((float) attribute.getDoubleContext(1)); - return output.getAttribute(attribute.fulfill(1)); - } + registerTag("with_pitch", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; + } + LocationTag output = ((LocationTag) object).clone(); + output.setPitch((float) attribute.getDoubleContext(1)); + return output.getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute ]> @@ -2193,12 +2441,18 @@ public int compare(EntityTag ent1, EntityTag ent2) { // @description // Returns a copy of the location with a changed world value. // --> - if (attribute.matches("with_world") && attribute.hasContext(1)) { - LocationTag output = clone(); - WorldTag world = WorldTag.valueOf(attribute.getContext(1)); - output.setWorld(world.getWorld()); - return output.getAttribute(attribute.fulfill(1)); - } + registerTag("with_world", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; + } + LocationTag output = ((LocationTag) object).clone(); + WorldTag world = WorldTag.valueOf(attribute.getContext(1)); + output.setWorld(world.getWorld()); + return output.getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2207,13 +2461,16 @@ public int compare(EntityTag ent1, EntityTag ent2) { // Gets the name of a Notable LocationTag. If the location isn't noted, // this is null. // --> - if (attribute.startsWith("notable_name")) { - String notname = NotableManager.getSavedId(this); - if (notname == null) { - return null; + registerTag("notable_name", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + String notname = NotableManager.getSavedId(((LocationTag) object)); + if (notname == null) { + return null; + } + return new ElementTag(notname).getObjectAttribute(attribute.fulfill(1)); } - return new ElementTag(notname).getAttribute(attribute.fulfill(1)); - } + }); ///////////////////// @@ -2226,23 +2483,29 @@ public int compare(EntityTag ent1, EntityTag ent2) { // @description // Returns the location with the specified coordinates added to it. // --> - if (attribute.startsWith("add") - && attribute.hasContext(1)) { - String[] ints = attribute.getContext(1).replace("l@", "").split(",", 4); // TODO: Just LocationTag.valueOf? - if (ints.length >= 3) { - if ((ArgumentHelper.matchesDouble(ints[0]) || ArgumentHelper.matchesInteger(ints[0])) - && (ArgumentHelper.matchesDouble(ints[1]) || ArgumentHelper.matchesInteger(ints[1])) - && (ArgumentHelper.matchesDouble(ints[2]) || ArgumentHelper.matchesInteger(ints[2]))) { - return new LocationTag(this.clone().add(Double.valueOf(ints[0]), - Double.valueOf(ints[1]), - Double.valueOf(ints[2]))).getAttribute(attribute.fulfill(1)); + registerTag("add", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; } + String[] ints = attribute.getContext(1).replace("l@", "").split(",", 4); // TODO: Just LocationTag.valueOf? + if (ints.length >= 3) { + if ((ArgumentHelper.matchesDouble(ints[0]) || ArgumentHelper.matchesInteger(ints[0])) + && (ArgumentHelper.matchesDouble(ints[1]) || ArgumentHelper.matchesInteger(ints[1])) + && (ArgumentHelper.matchesDouble(ints[2]) || ArgumentHelper.matchesInteger(ints[2]))) { + return new LocationTag(((LocationTag) object).clone().add(Double.valueOf(ints[0]), + Double.valueOf(ints[1]), + Double.valueOf(ints[2]))).getObjectAttribute(attribute.fulfill(1)); + } + } + else if (LocationTag.matches(attribute.getContext(1))) { + return new LocationTag(((LocationTag) object).clone().add(LocationTag.valueOf(attribute.getContext(1)))) + .getObjectAttribute(attribute.fulfill(1)); + } + return null; } - else if (LocationTag.matches(attribute.getContext(1))) { - return new LocationTag(this.clone().add(LocationTag.valueOf(attribute.getContext(1)))) - .getAttribute(attribute.fulfill(1)); - } - } + }); // <--[tag] // @attribute ]> @@ -2250,23 +2513,29 @@ else if (LocationTag.matches(attribute.getContext(1))) { // @description // Returns the location with the specified coordinates subtracted from it. // --> - if (attribute.startsWith("sub") - && attribute.hasContext(1)) { - String[] ints = attribute.getContext(1).replace("l@", "").split(",", 4); // TODO: Just LocationTag.valueOf? - if (ints.length == 3 || ints.length == 4) { - if ((ArgumentHelper.matchesDouble(ints[0]) || ArgumentHelper.matchesInteger(ints[0])) - && (ArgumentHelper.matchesDouble(ints[1]) || ArgumentHelper.matchesInteger(ints[1])) - && (ArgumentHelper.matchesDouble(ints[2]) || ArgumentHelper.matchesInteger(ints[2]))) { - return new LocationTag(this.clone().subtract(Double.valueOf(ints[0]), - Double.valueOf(ints[1]), - Double.valueOf(ints[2]))).getAttribute(attribute.fulfill(1)); + registerTag("sub", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; } + String[] ints = attribute.getContext(1).replace("l@", "").split(",", 4); // TODO: Just LocationTag.valueOf? + if (ints.length == 3 || ints.length == 4) { + if ((ArgumentHelper.matchesDouble(ints[0]) || ArgumentHelper.matchesInteger(ints[0])) + && (ArgumentHelper.matchesDouble(ints[1]) || ArgumentHelper.matchesInteger(ints[1])) + && (ArgumentHelper.matchesDouble(ints[2]) || ArgumentHelper.matchesInteger(ints[2]))) { + return new LocationTag(((LocationTag) object).clone().subtract(Double.valueOf(ints[0]), + Double.valueOf(ints[1]), + Double.valueOf(ints[2]))).getObjectAttribute(attribute.fulfill(1)); + } + } + else if (LocationTag.matches(attribute.getContext(1))) { + return new LocationTag(((LocationTag) object).clone().subtract(LocationTag.valueOf(attribute.getContext(1)))) + .getObjectAttribute(attribute.fulfill(1)); + } + return null; } - else if (LocationTag.matches(attribute.getContext(1))) { - return new LocationTag(this.clone().subtract(LocationTag.valueOf(attribute.getContext(1)))) - .getAttribute(attribute.fulfill(1)); - } - } + }); // <--[tag] // @attribute ]> @@ -2274,11 +2543,16 @@ else if (LocationTag.matches(attribute.getContext(1))) { // @description // Returns the location multiplied by the specified length. // --> - if (attribute.startsWith("mul") && - attribute.hasContext(1)) { - return new LocationTag(this.clone().multiply(Double.parseDouble(attribute.getContext(1)))) - .getAttribute(attribute.fulfill(1)); - } + registerTag("mul", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; + } + return new LocationTag(((LocationTag) object).clone().multiply(Double.parseDouble(attribute.getContext(1)))) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute ]> @@ -2286,11 +2560,16 @@ else if (LocationTag.matches(attribute.getContext(1))) { // @description // Returns the location divided by the specified length. // --> - if (attribute.startsWith("div") && - attribute.hasContext(1)) { - return new LocationTag(this.clone().multiply(1D / Double.parseDouble(attribute.getContext(1)))) - .getAttribute(attribute.fulfill(1)); - } + registerTag("div", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; + } + return new LocationTag(((LocationTag) object).clone().multiply(1D / Double.parseDouble(attribute.getContext(1)))) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2298,16 +2577,17 @@ else if (LocationTag.matches(attribute.getContext(1))) { // @description // Returns a 1-length vector in the same direction as this vector location. // --> - if (attribute.startsWith("normalize")) { - double len = Math.sqrt(Math.pow(getX(), 2) + Math.pow(getY(), 2) + Math.pow(getZ(), 2)); - if (len == 0) { - return this.getAttribute(attribute.fulfill(1)); - } - else { - return new LocationTag(this.clone().multiply(1D / len)) - .getAttribute(attribute.fulfill(1)); + registerTag("normalize", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + double len = Math.sqrt(Math.pow(getX(), 2) + Math.pow(getY(), 2) + Math.pow(getZ(), 2)); + if (len == 0) { + len = 1; + } + return new LocationTag(((LocationTag) object).clone().multiply(1D / len)) + .getObjectAttribute(attribute.fulfill(1)); } - } + }); // <--[tag] // @attribute @@ -2315,10 +2595,13 @@ else if (LocationTag.matches(attribute.getContext(1))) { // @description // Returns the 3D length of the vector/location. // --> - if (attribute.startsWith("vector_length")) { - return new ElementTag(Math.sqrt(Math.pow(getX(), 2) + Math.pow(getY(), 2) + Math.pow(getZ(), 2))) - .getAttribute(attribute.fulfill(1)); - } + registerTag("vector_length", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(Math.sqrt(Math.pow(getX(), 2) + Math.pow(getY(), 2) + Math.pow(getZ(), 2))) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2330,13 +2613,17 @@ else if (LocationTag.matches(attribute.getContext(1))) { // WEST_NORTH_WEST, NORTH_NORTH_WEST, NORTH_NORTH_EAST, EAST_NORTH_EAST, EAST_SOUTH_EAST, // SOUTH_SOUTH_EAST, SOUTH_SOUTH_WEST, WEST_SOUTH_WEST, SELF // --> - if (attribute.startsWith("vector_to_face")) { - BlockFace face = Utilities.faceFor(toVector()); - if (face != null) { - return new ElementTag(face.name()) - .getAttribute(attribute.fulfill(1)); + registerTag("vector_to_face", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + BlockFace face = Utilities.faceFor(toVector()); + if (face != null) { + return new ElementTag(face.name()) + .getObjectAttribute(attribute.fulfill(1)); + } + return null; } - } + }); // <--[tag] // @attribute ]> @@ -2344,20 +2631,26 @@ else if (LocationTag.matches(attribute.getContext(1))) { // @description // Returns the distance between 2 locations, squared. // --> - if (attribute.startsWith("distance_squared") - && attribute.hasContext(1)) { - if (LocationTag.matches(attribute.getContext(1))) { - LocationTag toLocation = LocationTag.valueOf(attribute.getContext(1)); - if (!getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { - if (!attribute.hasAlternative()) { - Debug.echoError("Can't measure distance between two different worlds!"); - } + registerTag("distance_squared", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { return null; } - return new ElementTag(this.distanceSquared(toLocation)) - .getAttribute(attribute.fulfill(1)); + if (LocationTag.matches(attribute.getContext(1))) { + LocationTag toLocation = LocationTag.valueOf(attribute.getContext(1)); + if (!getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { + if (!attribute.hasAlternative()) { + Debug.echoError("Can't measure distance between two different worlds!"); + } + return null; + } + return new ElementTag(((LocationTag) object).distanceSquared(toLocation)) + .getObjectAttribute(attribute.fulfill(1)); + } + return null; } - } + }); // <--[tag] // @attribute ]> @@ -2365,75 +2658,81 @@ else if (LocationTag.matches(attribute.getContext(1))) { // @description // Returns the distance between 2 locations. // --> - if (attribute.startsWith("distance") - && attribute.hasContext(1)) { - if (LocationTag.matches(attribute.getContext(1))) { - LocationTag toLocation = LocationTag.valueOf(attribute.getContext(1)); - - // <--[tag] - // @attribute ].horizontal> - // @returns ElementTag(Decimal) - // @description - // Returns the horizontal distance between 2 locations. - // --> - if (attribute.getAttribute(2).startsWith("horizontal")) { + registerTag("distance", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; + } + if (LocationTag.matches(attribute.getContext(1))) { + LocationTag toLocation = LocationTag.valueOf(attribute.getContext(1)); // <--[tag] - // @attribute ].horizontal.multiworld> + // @attribute ].horizontal> // @returns ElementTag(Decimal) // @description - // Returns the horizontal distance between 2 multiworld locations. + // Returns the horizontal distance between 2 locations. // --> - if (attribute.getAttribute(3).startsWith("multiworld")) { - return new ElementTag(Math.sqrt( - Math.pow(this.getX() - toLocation.getX(), 2) + - Math.pow(this.getZ() - toLocation.getZ(), 2))) - .getAttribute(attribute.fulfill(3)); - } - else if (this.getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { - return new ElementTag(Math.sqrt( - Math.pow(this.getX() - toLocation.getX(), 2) + - Math.pow(this.getZ() - toLocation.getZ(), 2))) - .getAttribute(attribute.fulfill(2)); + if (attribute.getAttribute(2).startsWith("horizontal")) { + + // <--[tag] + // @attribute ].horizontal.multiworld> + // @returns ElementTag(Decimal) + // @description + // Returns the horizontal distance between 2 multiworld locations. + // --> + if (attribute.getAttribute(3).startsWith("multiworld")) { + return new ElementTag(Math.sqrt( + Math.pow(((LocationTag) object).getX() - toLocation.getX(), 2) + + Math.pow(((LocationTag) object).getZ() - toLocation.getZ(), 2))) + .getObjectAttribute(attribute.fulfill(3)); + } + else if (((LocationTag) object).getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { + return new ElementTag(Math.sqrt( + Math.pow(((LocationTag) object).getX() - toLocation.getX(), 2) + + Math.pow(((LocationTag) object).getZ() - toLocation.getZ(), 2))) + .getObjectAttribute(attribute.fulfill(2)); + } } - } - - // <--[tag] - // @attribute ].vertical> - // @returns ElementTag(Decimal) - // @description - // Returns the vertical distance between 2 locations. - // --> - else if (attribute.getAttribute(2).startsWith("vertical")) { // <--[tag] - // @attribute ].vertical.multiworld> + // @attribute ].vertical> // @returns ElementTag(Decimal) // @description - // Returns the vertical distance between 2 multiworld locations. + // Returns the vertical distance between 2 locations. // --> - if (attribute.getAttribute(3).startsWith("multiworld")) { - return new ElementTag(Math.abs(this.getY() - toLocation.getY())) - .getAttribute(attribute.fulfill(3)); - } - else if (this.getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { - return new ElementTag(Math.abs(this.getY() - toLocation.getY())) - .getAttribute(attribute.fulfill(2)); + else if (attribute.getAttribute(2).startsWith("vertical")) { + + // <--[tag] + // @attribute ].vertical.multiworld> + // @returns ElementTag(Decimal) + // @description + // Returns the vertical distance between 2 multiworld locations. + // --> + if (attribute.getAttribute(3).startsWith("multiworld")) { + return new ElementTag(Math.abs(((LocationTag) object).getY() - toLocation.getY())) + .getObjectAttribute(attribute.fulfill(3)); + } + else if (((LocationTag) object).getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { + return new ElementTag(Math.abs(((LocationTag) object).getY() - toLocation.getY())) + .getObjectAttribute(attribute.fulfill(2)); + } } - } - if (!getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { - if (!attribute.hasAlternative()) { - Debug.echoError("Can't measure distance between two different worlds!"); + if (!getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { + if (!attribute.hasAlternative()) { + Debug.echoError("Can't measure distance between two different worlds!"); + } + return null; + } + else { + return new ElementTag(((LocationTag) object).distance(toLocation)) + .getObjectAttribute(attribute.fulfill(1)); } - return null; - } - else { - return new ElementTag(this.distance(toLocation)) - .getAttribute(attribute.fulfill(1)); } + return null; } - } + }); // <--[tag] // @attribute @@ -2441,10 +2740,13 @@ else if (this.getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { // @description // Returns whether the location is within the world border. // --> - if (attribute.startsWith("is_within_border")) { - return new ElementTag(getWorld().getWorldBorder().isInside(this)) - .getAttribute(attribute.fulfill(1)); - } + registerTag("is_within_border", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getWorld().getWorldBorder().isInside((LocationTag) object)) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute /]> @@ -2452,23 +2754,29 @@ else if (this.getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { // @description // Returns whether the location is within the cuboid or ellipsoid. // --> - if (attribute.startsWith("is_within") - && attribute.hasContext(1)) { - if (EllipsoidTag.matches(attribute.getContext(1))) { - EllipsoidTag ellipsoid = EllipsoidTag.valueOf(attribute.getContext(1)); - if (ellipsoid != null) { - return new ElementTag(ellipsoid.contains(this)) - .getAttribute(attribute.fulfill(1)); + registerTag("is_within", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; } - } - else { - CuboidTag cuboid = CuboidTag.valueOf(attribute.getContext(1)); - if (cuboid != null) { - return new ElementTag(cuboid.isInsideCuboid(this)) - .getAttribute(attribute.fulfill(1)); + if (EllipsoidTag.matches(attribute.getContext(1))) { + EllipsoidTag ellipsoid = EllipsoidTag.valueOf(attribute.getContext(1)); + if (ellipsoid != null) { + return new ElementTag(ellipsoid.contains((LocationTag) object)) + .getObjectAttribute(attribute.fulfill(1)); + } + } + else { + CuboidTag cuboid = CuboidTag.valueOf(attribute.getContext(1)); + if (cuboid != null) { + return new ElementTag(cuboid.isInsideCuboid((LocationTag) object)) + .getObjectAttribute(attribute.fulfill(1)); + } } + return null; } - } + }); ///////////////////// @@ -2481,10 +2789,13 @@ else if (this.getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { // @description // Returns the formatted biome name at the location. // --> - if (attribute.startsWith("biome.formatted")) { - return new ElementTag(CoreUtilities.toLowerCase(getBiomeForTag(attribute).name()).replace('_', ' ')) - .getAttribute(attribute.fulfill(2)); - } + registerTag("biome.formatted", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(CoreUtilities.toLowerCase(getBiomeForTag(attribute).name()).replace('_', ' ')) + .getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -2493,10 +2804,13 @@ else if (this.getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { // @description // Returns the biome at the location. // --> - if (attribute.startsWith("biome")) { - return new BiomeTag(getBiomeForTag(attribute)) - .getAttribute(attribute.fulfill(1)); - } + registerTag("biome", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new BiomeTag(getBiomeForTag(attribute)) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2504,14 +2818,17 @@ else if (this.getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { // @description // Returns a ListTag of all notable CuboidTags that include this location. // --> - if (attribute.startsWith("cuboids")) { - List cuboids = CuboidTag.getNotableCuboidsContaining(this); - ListTag cuboid_list = new ListTag(); - for (CuboidTag cuboid : cuboids) { - cuboid_list.add(cuboid.identify()); + registerTag("cuboids", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + List cuboids = CuboidTag.getNotableCuboidsContaining((LocationTag) object); + ListTag cuboid_list = new ListTag(); + for (CuboidTag cuboid : cuboids) { + cuboid_list.add(cuboid.identify()); + } + return cuboid_list.getObjectAttribute(attribute.fulfill(1)); } - return cuboid_list.getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute @@ -2519,14 +2836,17 @@ else if (this.getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { // @description // Returns a ListTag of all notable EllipsoidTags that include this location. // --> - if (attribute.startsWith("ellipsoids")) { - List ellipsoids = EllipsoidTag.getNotableEllipsoidsContaining(this); - ListTag ellipsoid_list = new ListTag(); - for (EllipsoidTag ellipsoid : ellipsoids) { - ellipsoid_list.add(ellipsoid.identify()); + registerTag("ellipsoids", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + List ellipsoids = EllipsoidTag.getNotableEllipsoidsContaining((LocationTag) object); + ListTag ellipsoid_list = new ListTag(); + for (EllipsoidTag ellipsoid : ellipsoids) { + ellipsoid_list.add(ellipsoid.identify()); + } + return ellipsoid_list.getObjectAttribute(attribute.fulfill(1)); } - return ellipsoid_list.getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute @@ -2534,63 +2854,22 @@ else if (this.getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { // @description // Returns whether the block at the location is a liquid. // --> - if (attribute.startsWith("is_liquid")) { - Block b = getBlockForTag(attribute); - if (b != null) { - try { - NMSHandler.getChunkHelper().changeChunkServerThread(getWorld()); - return new ElementTag(b.isLiquid()).getAttribute(attribute.fulfill(1)); - } - finally { - NMSHandler.getChunkHelper().restoreServerThread(getWorld()); - } - } - } - - - // <--[tag] - // @attribute - // @returns ElementTag(Number) - // @description - // Returns the amount of light from light blocks that is - // on the location. - // --> - if (attribute.startsWith("light.from_blocks") || - attribute.startsWith("light.blocks")) { - Block b = getBlockForTag(attribute); - if (b != null) { - try { - NMSHandler.getChunkHelper().changeChunkServerThread(getWorld()); - return new ElementTag(getBlockForTag(attribute).getLightFromBlocks()) - .getAttribute(attribute.fulfill(2)); - } - finally { - NMSHandler.getChunkHelper().restoreServerThread(getWorld()); + registerTag("is_liquid", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + Block b = getBlockForTag(attribute); + if (b != null) { + try { + NMSHandler.getChunkHelper().changeChunkServerThread(getWorld()); + return new ElementTag(b.isLiquid()).getObjectAttribute(attribute.fulfill(1)); + } + finally { + NMSHandler.getChunkHelper().restoreServerThread(getWorld()); + } } + return null; } - } - - // <--[tag] - // @attribute - // @returns ElementTag(Number) - // @description - // Returns the amount of light from the sky that is - // on the location. - // --> - if (attribute.startsWith("light.from_sky") || - attribute.startsWith("light.sky")) { - Block b = getBlockForTag(attribute); - if (b != null) { - try { - NMSHandler.getChunkHelper().changeChunkServerThread(getWorld()); - return new ElementTag(getBlockForTag(attribute).getLightFromSky()) - .getAttribute(attribute.fulfill(2)); - } - finally { - NMSHandler.getChunkHelper().restoreServerThread(getWorld()); - } - } - } + }); // <--[tag] // @attribute @@ -2598,19 +2877,45 @@ else if (this.getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { // @description // Returns the total amount of light on the location. // --> - if (attribute.startsWith("light")) { - Block b = getBlockForTag(attribute); - if (b != null) { - try { - NMSHandler.getChunkHelper().changeChunkServerThread(getWorld()); - return new ElementTag(getBlockForTag(attribute).getLightLevel()) - .getAttribute(attribute.fulfill(1)); - } - finally { - NMSHandler.getChunkHelper().restoreServerThread(getWorld()); + registerTag("light", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + Block b = getBlockForTag(attribute); + if (b != null) { + try { + NMSHandler.getChunkHelper().changeChunkServerThread(getWorld()); + // <--[tag] + // @attribute + // @returns ElementTag(Number) + // @description + // Returns the amount of light from light blocks that is + // on the location. + // --> + if (attribute.getAttributeWithoutContext(2).equals("blocks")) { + return new ElementTag(getBlockForTag(attribute).getLightFromBlocks()) + .getObjectAttribute(attribute.fulfill(2)); + } + // <--[tag] + // @attribute + // @returns ElementTag(Number) + // @description + // Returns the amount of light from the sky that is + // on the location. + // --> + if (attribute.getAttributeWithoutContext(2).equals("sky")) { + return new ElementTag(getBlockForTag(attribute).getLightFromSky()) + .getObjectAttribute(attribute.fulfill(2)); + } + return new ElementTag(getBlockForTag(attribute).getLightLevel()) + .getObjectAttribute(attribute.fulfill(1)); + } + finally { + NMSHandler.getChunkHelper().restoreServerThread(getWorld()); + } } + return null; } - } + }); // <--[tag] // @attribute @@ -2618,19 +2923,23 @@ else if (this.getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { // @description // Returns the current redstone power level of a block. // --> - if (attribute.startsWith("power")) { - Block b = getBlockForTag(attribute); - if (b != null) { - try { - NMSHandler.getChunkHelper().changeChunkServerThread(getWorld()); - return new ElementTag(getBlockForTag(attribute).getBlockPower()) - .getAttribute(attribute.fulfill(1)); - } - finally { - NMSHandler.getChunkHelper().restoreServerThread(getWorld()); + registerTag("power", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + Block b = getBlockForTag(attribute); + if (b != null) { + try { + NMSHandler.getChunkHelper().changeChunkServerThread(getWorld()); + return new ElementTag(getBlockForTag(attribute).getBlockPower()) + .getObjectAttribute(attribute.fulfill(1)); + } + finally { + NMSHandler.getChunkHelper().restoreServerThread(getWorld()); + } } + return null; } - } + }); // <--[tag] // @attribute @@ -2640,14 +2949,18 @@ else if (this.getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { // Returns a number of how many blocks away from a connected tree leaves are. // Defaults to 7 if not connected to a tree. // --> - if (attribute.startsWith("tree_distance")) { - MaterialTag material = new MaterialTag(getBlockForTag(attribute)); - if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_13) - && MaterialLeaves.describes(material)) { - return new ElementTag(MaterialLeaves.getFrom(material).getDistance()) - .getAttribute(attribute.fulfill(1)); + registerTag("tree_distance", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + MaterialTag material = new MaterialTag(getBlockForTag(attribute)); + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_13) + && MaterialLeaves.describes(material)) { + return new ElementTag(MaterialLeaves.getFrom(material).getDistance()) + .getObjectAttribute(attribute.fulfill(1)); + } + return null; } - } + }); // <--[tag] // @attribute @@ -2656,9 +2969,12 @@ else if (this.getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { // Always returns 'Location' for LocationTag objects. All objects fetchable by the Object Fetcher will return the // type of object that is fulfilling this attribute. // --> - if (attribute.startsWith("type")) { - return new ElementTag("Location").getAttribute(attribute.fulfill(1)); - } + registerTag("type", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag("Location").getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2667,10 +2983,16 @@ else if (this.getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { // @description // Returns the name a command block is set to. // --> - if (attribute.startsWith("command_block_name") && getBlockStateForTag(attribute) instanceof CommandBlock) { - return new ElementTag(((CommandBlock) getBlockStateForTag(attribute)).getName()) - .getAttribute(attribute.fulfill(1)); - } + registerTag("command_block_name", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!(getBlockStateForTag(attribute) instanceof CommandBlock)) { + return null; + } + return new ElementTag(((CommandBlock) getBlockStateForTag(attribute)).getName()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2679,10 +3001,16 @@ else if (this.getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { // @description // Returns the command a command block is set to. // --> - if (attribute.startsWith("command_block") && getBlockStateForTag(attribute) instanceof CommandBlock) { - return new ElementTag(((CommandBlock) getBlockStateForTag(attribute)).getCommand()) - .getAttribute(attribute.fulfill(1)); - } + registerTag("command_block", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!(getBlockStateForTag(attribute) instanceof CommandBlock)) { + return null; + } + return new ElementTag(((CommandBlock) getBlockStateForTag(attribute)).getCommand()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2691,10 +3019,13 @@ else if (this.getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { // @description // Returns the burn time a furnace has left. // --> - if (attribute.startsWith("furnace_burn_time")) { - return new ElementTag(((Furnace) getBlockStateForTag(attribute)).getBurnTime()) - .getAttribute(attribute.fulfill(1)); - } + registerTag("furnace_burn_time", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(((Furnace) getBlockStateForTag(attribute)).getBurnTime()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2703,10 +3034,13 @@ else if (this.getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { // @description // Returns the cook time a furnace has left. // --> - if (attribute.startsWith("furnace_cook_time")) { - return new ElementTag(((Furnace) getBlockStateForTag(attribute)).getCookTime()) - .getAttribute(attribute.fulfill(1)); - } + registerTag("furnace_cook_time", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(((Furnace) getBlockStateForTag(attribute)).getCookTime()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2715,24 +3049,28 @@ else if (this.getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { // Returns the block this block is attached to. // (Only if it is a lever or button!) // --> - if (attribute.startsWith("attached_to")) { - BlockFace face = BlockFace.SELF; - MaterialTag material = new MaterialTag(getBlockForTag(attribute)); - if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_13) - && MaterialSwitchFace.describes(material)) { - face = MaterialSwitchFace.getFrom(material).getAttachedTo(); - } - else { - MaterialData data = getBlockStateForTag(attribute).getData(); - if (data instanceof Attachable) { - face = ((Attachable) data).getAttachedFace(); + registerTag("attached_to", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + BlockFace face = BlockFace.SELF; + MaterialTag material = new MaterialTag(getBlockForTag(attribute)); + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_13) + && MaterialSwitchFace.describes(material)) { + face = MaterialSwitchFace.getFrom(material).getAttachedTo(); } + else { + MaterialData data = getBlockStateForTag(attribute).getData(); + if (data instanceof Attachable) { + face = ((Attachable) data).getAttachedFace(); + } + } + if (face != BlockFace.SELF) { + return new LocationTag(getBlockForTag(attribute).getRelative(face).getLocation()) + .getObjectAttribute(attribute.fulfill(1)); + } + return null; } - if (face != BlockFace.SELF) { - return new LocationTag(getBlockForTag(attribute).getRelative(face).getLocation()) - .getAttribute(attribute.fulfill(1)); - } - } + }); // <--[tag] // @attribute @@ -2741,68 +3079,71 @@ else if (this.getWorldName().equalsIgnoreCase(toLocation.getWorldName())) { // If the location is part of a double-block structure // (double chests, doors, beds, etc), returns the location of the other block in the double-block structure. // --> - if (attribute.startsWith("other_block")) { - BlockState state = getBlockStateForTag(attribute); - if (state instanceof Chest - && NMSHandler.getVersion().isAtLeast(NMSVersion.v1_13)) { - Vector direction = DirectionalBlocksHelper.getFacing(getBlockForTag(attribute)); - if (DirectionalBlocksHelper.isLeftHalf(getBlockForTag(attribute))) { - direction = new Vector(-direction.getZ(), 0, direction.getX()); - } - else if (DirectionalBlocksHelper.isRightHalf(getBlockForTag(attribute))) { - direction = new Vector(direction.getZ(), 0, -direction.getX()); - } - else { - if (!attribute.hasAlternative()) { - Debug.echoError("Block is a single-block chest."); + registerTag("other_block", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + BlockState state = getBlockStateForTag(attribute); + if (state instanceof Chest + && NMSHandler.getVersion().isAtLeast(NMSVersion.v1_13)) { + Vector direction = DirectionalBlocksHelper.getFacing(getBlockForTag(attribute)); + if (DirectionalBlocksHelper.isLeftHalf(getBlockForTag(attribute))) { + direction = new Vector(-direction.getZ(), 0, direction.getX()); } - return null; - } - return new LocationTag(this.clone().add(direction)).getAttribute(attribute.fulfill(1)); - } - else if (state instanceof Chest) { - // There is no remotely sane API for this. - InventoryHolder holder = ((Chest) state).getBlockInventory().getHolder(); - if (holder instanceof DoubleChest) { - Location left = ((DoubleChest) holder).getLeftSide().getInventory().getLocation(); - Location right = ((DoubleChest) holder).getRightSide().getInventory().getLocation(); - if (left.getBlockX() == getBlockX() && left.getBlockY() == getBlockY() && left.getBlockZ() == getBlockZ()) { - return new LocationTag(right).getAttribute(attribute.fulfill(1)); + else if (DirectionalBlocksHelper.isRightHalf(getBlockForTag(attribute))) { + direction = new Vector(direction.getZ(), 0, -direction.getX()); } else { - return new LocationTag(left).getAttribute(attribute.fulfill(1)); + if (!attribute.hasAlternative()) { + Debug.echoError("Block is a single-block chest."); + } + return null; + } + return new LocationTag(((LocationTag) object).clone().add(direction)).getObjectAttribute(attribute.fulfill(1)); + } + else if (state instanceof Chest) { + // There is no remotely sane API for this. + InventoryHolder holder = ((Chest) state).getBlockInventory().getHolder(); + if (holder instanceof DoubleChest) { + Location left = ((DoubleChest) holder).getLeftSide().getInventory().getLocation(); + Location right = ((DoubleChest) holder).getRightSide().getInventory().getLocation(); + if (left.getBlockX() == getBlockX() && left.getBlockY() == getBlockY() && left.getBlockZ() == getBlockZ()) { + return new LocationTag(right).getObjectAttribute(attribute.fulfill(1)); + } + else { + return new LocationTag(left).getObjectAttribute(attribute.fulfill(1)); + } } } - } - else if (state instanceof Bed - && NMSHandler.getVersion().isAtLeast(NMSVersion.v1_13)) { - // There's no pre-1.13 API for this *at all*, and the new API isn't very sane, but can be used. - boolean isTop = DirectionalBlocksHelper.isTopHalf(getBlockForTag(attribute)); - BlockFace direction = DirectionalBlocksHelper.getFace(getBlockForTag(attribute)); - if (!isTop) { - direction = direction.getOppositeFace(); + else if (state instanceof Bed + && NMSHandler.getVersion().isAtLeast(NMSVersion.v1_13)) { + // There's no pre-1.13 API for this *at all*, and the new API isn't very sane, but can be used. + boolean isTop = DirectionalBlocksHelper.isTopHalf(getBlockForTag(attribute)); + BlockFace direction = DirectionalBlocksHelper.getFace(getBlockForTag(attribute)); + if (!isTop) { + direction = direction.getOppositeFace(); + } + return new LocationTag(((LocationTag) object).clone().add(direction.getDirection())).getObjectAttribute(attribute.fulfill(1)); } - return new LocationTag(this.clone().add(direction.getDirection())).getAttribute(attribute.fulfill(1)); - } - else if (state.getData() instanceof Door) { - if (((Door) state.getData()).isTopHalf()) { - return new LocationTag(this.clone().subtract(0, 1, 0)).getAttribute(attribute.fulfill(1)); + else if (state.getData() instanceof Door) { + if (((Door) state.getData()).isTopHalf()) { + return new LocationTag(((LocationTag) object).clone().subtract(0, 1, 0)).getObjectAttribute(attribute.fulfill(1)); + } + else { + return new LocationTag(((LocationTag) object).clone().add(0, 1, 0)).getObjectAttribute(attribute.fulfill(1)); + } } else { - return new LocationTag(this.clone().add(0, 1, 0)).getAttribute(attribute.fulfill(1)); + if (!attribute.hasAlternative()) { + Debug.echoError("Block of type " + getBlockTypeForTag(attribute).name() + " isn't supported by other_block."); + } + return null; } - } - else { if (!attribute.hasAlternative()) { - Debug.echoError("Block of type " + getBlockTypeForTag(attribute).name() + " isn't supported by other_block."); + Debug.echoError("Block of type " + getBlockTypeForTag(attribute).name() + " doesn't have an other block."); } return null; } - if (!attribute.hasAlternative()) { - Debug.echoError("Block of type " + getBlockTypeForTag(attribute).name() + " doesn't have an other block."); - } - return null; - } + }); // <--[tag] // @attribute @@ -2812,19 +3153,27 @@ else if (state.getData() instanceof Door) { // Returns the custom name of this block. // Only works for nameable blocks, such as chests and dispensers. // --> - if (attribute.startsWith("custom_name")) { - if (getBlockStateForTag(attribute) instanceof Nameable) { - return new ElementTag(((Nameable) getBlockStateForTag(attribute)).getCustomName()) - .getAttribute(attribute.fulfill(1)); + registerTag("custom_name", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (getBlockStateForTag(attribute) instanceof Nameable) { + return new ElementTag(((Nameable) getBlockStateForTag(attribute)).getCustomName()) + .getObjectAttribute(attribute.fulfill(1)); + } + return null; } - } + }); + } - String returned = CoreUtilities.autoPropertyTag(this, attribute); - if (returned != null) { - return returned; - } + public static ObjectTagProcessor tagProcessor = new ObjectTagProcessor(); - return new ElementTag(identify()).getAttribute(attribute); + public static void registerTag(String name, TagRunnable.ObjectForm runnable) { + tagProcessor.registerTag(name, runnable); + } + + @Override + public ObjectTag getObjectAttribute(Attribute attribute) { + return tagProcessor.getObjectAttribute(this, attribute); } public void applyProperty(Mechanism mechanism) { diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/NPCTag.java b/plugin/src/main/java/com/denizenscript/denizen/objects/NPCTag.java index 41172ad1df..b2d9efd13b 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/NPCTag.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/NPCTag.java @@ -6,6 +6,7 @@ import com.denizenscript.denizen.scripts.containers.core.InteractScriptHelper; import com.denizenscript.denizen.scripts.triggers.AbstractTrigger; import com.denizenscript.denizen.utilities.DenizenAPI; +import com.denizenscript.denizencore.tags.ObjectTagProcessor; import com.denizenscript.denizencore.utilities.debugging.Debug; import com.denizenscript.denizencore.objects.*; import com.denizenscript.denizen.flags.FlagManager; @@ -453,20 +454,32 @@ public String getAttribute(Attribute attribute) { } // Defined in EntityTag - if (attribute.startsWith("is_npc")) { - return new ElementTag(true).getAttribute(attribute.fulfill(1)); - } + registerTag("is_npc", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(true).getObjectAttribute(attribute.fulfill(1)); + } + }); // Defined in EntityTag - if (attribute.startsWith("location") && !isSpawned()) { - return getLocation().getAttribute(attribute.fulfill(1)); - } + registerTag("location", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!!isSpawned()) { + return null; + } + return getLocation().getObjectAttribute(attribute.fulfill(1)); + } + }); // Defined in EntityTag - if (attribute.startsWith("eye_location")) { - return getEyeLocation().getAttribute(attribute.fulfill(1)); - } + registerTag("eye_location", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return getEyeLocation().getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -474,11 +487,14 @@ public String getAttribute(Attribute attribute) { // @description // Returns true if the NPC has a nickname. // --> - if (attribute.startsWith("has_nickname")) { - NPC citizen = getCitizen(); - return new ElementTag(citizen.hasTrait(NicknameTrait.class) && citizen.getTrait(NicknameTrait.class).hasNickname()) - .getAttribute(attribute.fulfill(1)); - } + registerTag("has_nickname", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + NPC citizen = getCitizen(); + return new ElementTag(citizen.hasTrait(NicknameTrait.class) && citizen.getTrait(NicknameTrait.class).hasNickname()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -486,10 +502,13 @@ public String getAttribute(Attribute attribute) { // @description // Returns the NPC's display name. // --> - if (attribute.startsWith("name.nickname")) { - return new ElementTag(getCitizen().hasTrait(NicknameTrait.class) ? getCitizen().getTrait(NicknameTrait.class) - .getNickname() : getName()).getAttribute(attribute.fulfill(2)); - } + registerTag("name.nickname", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getCitizen().hasTrait(NicknameTrait.class) ? getCitizen().getTrait(NicknameTrait.class) + .getNickname() : getName()).getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -497,10 +516,13 @@ public String getAttribute(Attribute attribute) { // @description // Returns the name of the NPC. // --> - if (attribute.startsWith("name")) { - return new ElementTag(getName()) - .getAttribute(attribute.fulfill(1)); - } + registerTag("name", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getName()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -508,13 +530,16 @@ public String getAttribute(Attribute attribute) { // @description // Returns a list of all of the NPC's traits. // --> - if (attribute.startsWith("list_traits")) { - List list = new ArrayList<>(); - for (Trait trait : getCitizen().getTraits()) { - list.add(trait.getName()); + registerTag("list_traits", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + List list = new ArrayList<>(); + for (Trait trait : getCitizen().getTraits()) { + list.add(trait.getName()); + } + return new ListTag(list).getObjectAttribute(attribute.fulfill(1)); } - return new ListTag(list).getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute ]> @@ -522,15 +547,19 @@ public String getAttribute(Attribute attribute) { // @description // Returns whether the NPC has a specified trait. // --> - if (attribute.startsWith("has_trait")) { - if (attribute.hasContext(1)) { - Class trait = CitizensAPI.getTraitFactory().getTraitClass(attribute.getContext(1)); - if (trait != null) { - return new ElementTag(getCitizen().hasTrait(trait)) - .getAttribute(attribute.fulfill(1)); + registerTag("has_trait", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (attribute.hasContext(1)) { + Class trait = CitizensAPI.getTraitFactory().getTraitClass(attribute.getContext(1)); + if (trait != null) { + return new ElementTag(getCitizen().hasTrait(trait)) + .getObjectAttribute(attribute.fulfill(1)); + } } + return null; } - } + }); // <--[tag] // @attribute @@ -538,9 +567,13 @@ public String getAttribute(Attribute attribute) { // @description // Returns whether the NPC is pushable. // --> - if (attribute.startsWith("pushable") || attribute.startsWith("is_pushable")) { - return new ElementTag(getPushableTrait().isPushable()).getAttribute(attribute.fulfill(1)); - } + registerTag("pushable", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getPushableTrait().isPushable()).getObjectAttribute(attribute.fulfill(1)); + } + }); + registerTag("is_pushable", tagProcessor.registeredObjectTags.get("pushable")); // <--[tag] // @attribute ]> @@ -548,30 +581,21 @@ public String getAttribute(Attribute attribute) { // @description // Returns whether the NPC has a specified trigger. // --> - if (attribute.startsWith("has_trigger") - && attribute.hasContext(1)) { - if (!getCitizen().hasTrait(TriggerTrait.class)) { - return new ElementTag(false).getAttribute(attribute.fulfill(1)); + registerTag("has_trigger", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; + } + if (!getCitizen().hasTrait(TriggerTrait.class)) { + return new ElementTag(false).getObjectAttribute(attribute.fulfill(1)); + } + TriggerTrait trait = getCitizen().getTrait(TriggerTrait.class); + return new ElementTag(trait.hasTrigger(attribute.getContext(1))) + .getObjectAttribute(attribute.fulfill(1)); } - TriggerTrait trait = getCitizen().getTrait(TriggerTrait.class); - return new ElementTag(trait.hasTrigger(attribute.getContext(1))) - .getAttribute(attribute.fulfill(1)); - } + }); - // <--[tag] - // @attribute - // @returns ListTag - // @description - // Returns a list of anchor names currently assigned to the NPC. - // --> - if (attribute.startsWith("anchor.list") - || attribute.startsWith("anchors.list")) { - List list = new ArrayList<>(); - for (Anchor anchor : getCitizen().getTrait(Anchors.class).getAnchors()) { - list.add(anchor.getName()); - } - return new ListTag(list).getAttribute(attribute.fulfill(2)); - } // <--[tag] // @attribute @@ -579,10 +603,13 @@ public String getAttribute(Attribute attribute) { // @description // Returns whether the NPC has anchors assigned. // --> - if (attribute.startsWith("has_anchors")) { - return (new ElementTag(getCitizen().getTrait(Anchors.class).getAnchors().size() > 0)) - .getAttribute(attribute.fulfill(1)); - } + registerTag("has_anchors", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return (new ElementTag(getCitizen().getTrait(Anchors.class).getAnchors().size() > 0)) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute ]> @@ -590,14 +617,32 @@ public String getAttribute(Attribute attribute) { // @description // Returns the location associated with the specified anchor, or null if it doesn't exist. // --> - if (attribute.startsWith("anchor")) { - if (attribute.hasContext(1) - && getCitizen().getTrait(Anchors.class).getAnchor(attribute.getContext(1)) != null) { - return new LocationTag(getCitizen().getTrait(Anchors.class) - .getAnchor(attribute.getContext(1)).getLocation()) - .getAttribute(attribute.fulfill(1)); + registerTag("anchor", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (attribute.hasContext(1) + && getCitizen().getTrait(Anchors.class).getAnchor(attribute.getContext(1)) != null) { + return new LocationTag(getCitizen().getTrait(Anchors.class) + .getAnchor(attribute.getContext(1)).getLocation()) + .getObjectAttribute(attribute.fulfill(1)); + } + // <--[tag] + // @attribute + // @returns ListTag + // @description + // Returns a list of anchor names currently assigned to the NPC. + // --> + else if (attribute.getAttributeWithoutContext(2).equals("list")) { + List list = new ArrayList<>(); + for (Anchor anchor : getCitizen().getTrait(Anchors.class).getAnchors()) { + list.add(anchor.getName()); + } + return new ListTag(list).getObjectAttribute(attribute.fulfill(2)); + } + return null; } - } + }); + registerTag("anchors", tagProcessor.registeredObjectTags.get("anchor")); // <--[tag] // @attribute ]> @@ -605,16 +650,19 @@ && getCitizen().getTrait(Anchors.class).getAnchor(attribute.getContext(1)) != nu // @description // Returns true if the NPC has the specified flag, otherwise returns false. // --> - if (attribute.startsWith("has_flag")) { - String flag_name; - if (attribute.hasContext(1)) { - flag_name = attribute.getContext(1); - } - else { - return null; + registerTag("has_flag", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + String flag_name; + if (attribute.hasContext(1)) { + flag_name = attribute.getContext(1); + } + else { + return null; + } + return new ElementTag(FlagManager.npcHasFlag((NPCTag) object, flag_name)).getObjectAttribute(attribute.fulfill(1)); } - return new ElementTag(FlagManager.npcHasFlag(this, flag_name)).getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute ]> @@ -622,42 +670,48 @@ && getCitizen().getTrait(Anchors.class).getAnchor(attribute.getContext(1)) != nu // @description // Returns the specified flag from the NPC. // --> - if (attribute.startsWith("flag") && attribute.hasContext(1)) { - String flag_name = attribute.getContext(1); - - // <--[tag] - // @attribute ].is_expired> - // @returns ElementTag(Boolean) - // @description - // returns true if the flag is expired or does not exist, false if it is not yet expired or has no expiration. - // --> - if (attribute.getAttribute(2).equalsIgnoreCase("is_expired") - || attribute.startsWith("isexpired")) { - return new ElementTag(!FlagManager.npcHasFlag(this, flag_name)) - .getAttribute(attribute.fulfill(2)); - } - if (attribute.getAttribute(2).equalsIgnoreCase("size") && !FlagManager.npcHasFlag(this, flag_name)) { - return new ElementTag(0).getAttribute(attribute.fulfill(2)); - } - if (FlagManager.npcHasFlag(this, flag_name)) { - FlagManager.Flag flag = DenizenAPI.getCurrentInstance().flagManager() - .getNPCFlag(getId(), flag_name); + registerTag("flag", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; + } + String flag_name = attribute.getContext(1); // <--[tag] - // @attribute ].expiration> - // @returns DurationTag + // @attribute ].is_expired> + // @returns ElementTag(Boolean) // @description - // Returns a DurationTag of the time remaining on the flag, if it has an expiration. + // returns true if the flag is expired or does not exist, false if it is not yet expired or has no expiration. // --> - if (attribute.getAttribute(2).equalsIgnoreCase("expiration")) { - return flag.expiration().getAttribute(attribute.fulfill(2)); + if (attribute.getAttribute(2).equalsIgnoreCase("is_expired") + || attribute.startsWith("isexpired")) { + return new ElementTag(!FlagManager.npcHasFlag((NPCTag) object, flag_name)) + .getObjectAttribute(attribute.fulfill(2)); } + if (attribute.getAttribute(2).equalsIgnoreCase("size") && !FlagManager.npcHasFlag((NPCTag) object, flag_name)) { + return new ElementTag(0).getObjectAttribute(attribute.fulfill(2)); + } + if (FlagManager.npcHasFlag((NPCTag) object, flag_name)) { + FlagManager.Flag flag = DenizenAPI.getCurrentInstance().flagManager() + .getNPCFlag(getId(), flag_name); + + // <--[tag] + // @attribute ].expiration> + // @returns DurationTag + // @description + // Returns a DurationTag of the time remaining on the flag, if it has an expiration. + // --> + if (attribute.getAttribute(2).equalsIgnoreCase("expiration")) { + return flag.expiration().getObjectAttribute(attribute.fulfill(2)); + } - return new ListTag(flag.toString(), true, flag.values()) - .getAttribute(attribute.fulfill(1)); + return new ListTag(flag.toString(), true, flag.values()) + .getObjectAttribute(attribute.fulfill(1)); + } + return new ElementTag(identify()).getObjectAttribute(attribute); } - return new ElementTag(identify()).getAttribute(attribute); - } + }); // <--[tag] // @attribute ]> @@ -666,37 +720,40 @@ && getCitizen().getTrait(Anchors.class).getAnchor(attribute.getContext(1)) != nu // Returns a list of an NPC's flag names, with an optional search for // names containing a certain pattern. // --> - if (attribute.startsWith("list_flags")) { - ListTag allFlags = new ListTag(DenizenAPI.getCurrentInstance().flagManager().listNPCFlags(getId())); - ListTag searchFlags = null; - if (!allFlags.isEmpty() && attribute.hasContext(1)) { - searchFlags = new ListTag(); - String search = attribute.getContext(1); - if (search.startsWith("regex:")) { - try { - Pattern pattern = Pattern.compile(search.substring(6), Pattern.CASE_INSENSITIVE); - for (String flag : allFlags) { - if (pattern.matcher(flag).matches()) { - searchFlags.add(flag); + registerTag("list_flags", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + ListTag allFlags = new ListTag(DenizenAPI.getCurrentInstance().flagManager().listNPCFlags(getId())); + ListTag searchFlags = null; + if (!allFlags.isEmpty() && attribute.hasContext(1)) { + searchFlags = new ListTag(); + String search = attribute.getContext(1); + if (search.startsWith("regex:")) { + try { + Pattern pattern = Pattern.compile(search.substring(6), Pattern.CASE_INSENSITIVE); + for (String flag : allFlags) { + if (pattern.matcher(flag).matches()) { + searchFlags.add(flag); + } } } + catch (Exception e) { + Debug.echoError(e); + } } - catch (Exception e) { - Debug.echoError(e); - } - } - else { - search = CoreUtilities.toLowerCase(search); - for (String flag : allFlags) { - if (CoreUtilities.toLowerCase(flag).contains(search)) { - searchFlags.add(flag); + else { + search = CoreUtilities.toLowerCase(search); + for (String flag : allFlags) { + if (CoreUtilities.toLowerCase(flag).contains(search)) { + searchFlags.add(flag); + } } } } + return searchFlags == null ? allFlags.getObjectAttribute(attribute.fulfill(1)) + : searchFlags.getObjectAttribute(attribute.fulfill(1)); } - return searchFlags == null ? allFlags.getAttribute(attribute.fulfill(1)) - : searchFlags.getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute ]> @@ -704,18 +761,22 @@ && getCitizen().getTrait(Anchors.class).getAnchor(attribute.getContext(1)) != nu // @description // Returns the specified constant from the NPC. // --> - if (attribute.startsWith("constant")) { - if (attribute.hasContext(1)) { - if (getCitizen().hasTrait(ConstantsTrait.class) - && getCitizen().getTrait(ConstantsTrait.class).getConstant(attribute.getContext(1)) != null) { - return new ElementTag(getCitizen().getTrait(ConstantsTrait.class) - .getConstant(attribute.getContext(1))).getAttribute(attribute.fulfill(1)); - } - else { - return null; + registerTag("constant", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (attribute.hasContext(1)) { + if (getCitizen().hasTrait(ConstantsTrait.class) + && getCitizen().getTrait(ConstantsTrait.class).getConstant(attribute.getContext(1)) != null) { + return new ElementTag(getCitizen().getTrait(ConstantsTrait.class) + .getConstant(attribute.getContext(1))).getObjectAttribute(attribute.fulfill(1)); + } + else { + return null; + } } + return null; } - } + }); // <--[tag] // @attribute ]> @@ -723,15 +784,18 @@ && getCitizen().getTrait(ConstantsTrait.class).getConstant(attribute.getContext( // @description // Returns true if the NPC has the specified pose, otherwise returns false. // --> - if (attribute.startsWith("has_pose")) { - if (attribute.hasContext(1)) { - return new ElementTag(getCitizen().getTrait(Poses.class).hasPose(attribute.getContext(1))) - .getAttribute(attribute.fulfill(1)); - } - else { - return null; + registerTag("has_pose", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (attribute.hasContext(1)) { + return new ElementTag(getCitizen().getTrait(Poses.class).hasPose(attribute.getContext(1))) + .getObjectAttribute(attribute.fulfill(1)); + } + else { + return null; + } } - } + }); // <--[tag] // @attribute ]> @@ -740,16 +804,20 @@ && getCitizen().getTrait(ConstantsTrait.class).getConstant(attribute.getContext( // Returns the pose as a LocationTag with x, y, and z set to 0, and the world set to the first // possible available world Bukkit knows about. // --> - if (attribute.startsWith("pose") || attribute.startsWith("get_pose")) { - if (attribute.hasContext(1)) { - Pose pose = getCitizen().getTrait(Poses.class).getPose(attribute.getContext(1)); - return new LocationTag(org.bukkit.Bukkit.getWorlds().get(0), 0, 0, 0, pose.getYaw(), pose.getPitch()) - .getAttribute(attribute.fulfill(1)); - } - else { - return null; + registerTag("pose", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (attribute.hasContext(1)) { + Pose pose = getCitizen().getTrait(Poses.class).getPose(attribute.getContext(1)); + return new LocationTag(org.bukkit.Bukkit.getWorlds().get(0), 0, 0, 0, pose.getYaw(), pose.getPitch()) + .getObjectAttribute(attribute.fulfill(1)); + } + else { + return null; + } } - } + }); + registerTag("get_pose", tagProcessor.registeredObjectTags.get("pose")); // <--[tag] // @attribute @@ -757,11 +825,16 @@ && getCitizen().getTrait(ConstantsTrait.class).getConstant(attribute.getContext( // @description // Returns whether the NPC is currently sneaking. Only works for player-type NPCs. // --> - if (attribute.startsWith("is_sneaking") - && isSpawned() && getEntity() instanceof Player) { - return new ElementTag(((Player) getEntity()).isSneaking()) - .getAttribute(attribute.fulfill(1)); - } + registerTag("is_sneaking", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!isSpawned() && getEntity() instanceof Player) { + return null; + } + return new ElementTag(((Player) getEntity()).isSneaking()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -770,9 +843,13 @@ && isSpawned() && getEntity() instanceof Player) { // Returns whether the NPC is currently engaged. // See <@link command engage> // --> - if (attribute.startsWith("engaged") || attribute.startsWith("is_engaged")) { - return new ElementTag(isEngaged()).getAttribute(attribute.fulfill(1)); - } + registerTag("engaged", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(isEngaged()).getObjectAttribute(attribute.fulfill(1)); + } + }); + registerTag("is_engage", tagProcessor.registeredObjectTags.get("engage")); // <--[tag] // @attribute @@ -781,9 +858,13 @@ && isSpawned() && getEntity() instanceof Player) { // Returns whether the NPC is currently invulnerable. // See <@link command vulnerable> // --> - if (attribute.startsWith("invulnerable") || attribute.startsWith("vulnerable")) { - return new ElementTag(getCitizen().data().get(NPC.DEFAULT_PROTECTED_METADATA, true)).getAttribute(attribute.fulfill(1)); - } + registerTag("invulnerable", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getCitizen().data().get(NPC.DEFAULT_PROTECTED_METADATA, true)).getObjectAttribute(attribute.fulfill(1)); + } + }); + registerTag("vulnerable", tagProcessor.registeredObjectTags.get("invulnerable")); // <--[tag] // @attribute @@ -791,9 +872,12 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns the NPC's ID number. // --> - if (attribute.startsWith("id")) { - return new ElementTag(getId()).getAttribute(attribute.fulfill(1)); - } + registerTag("id", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getId()).getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -801,19 +885,22 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns the owner of the NPC as a PlayerTag if it's a player, otherwise as just the name. // --> - if (attribute.startsWith("owner")) { - String owner = getOwner(); - PlayerTag player = null; - if (!owner.equalsIgnoreCase("server")) { - player = PlayerTag.valueOfInternal(owner, false); - } - if (player != null) { - return player.getAttribute(attribute.fulfill(1)); - } - else { - return new ElementTag(owner).getAttribute(attribute.fulfill(1)); + registerTag("owner", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + String owner = getOwner(); + PlayerTag player = null; + if (!owner.equalsIgnoreCase("server")) { + player = PlayerTag.valueOfInternal(owner, false); + } + if (player != null) { + return player.getObjectAttribute(attribute.fulfill(1)); + } + else { + return new ElementTag(owner).getObjectAttribute(attribute.fulfill(1)); + } } - } + }); // <--[tag] // @attribute @@ -821,9 +908,12 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns whether the NPC has a custom skinskin. // --> - if (attribute.startsWith("has_skin")) { - return new ElementTag(getCitizen().data().has(NPC.PLAYER_SKIN_UUID_METADATA)).getAttribute(attribute.fulfill(1)); - } + registerTag("has_skin", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getCitizen().data().has(NPC.PLAYER_SKIN_UUID_METADATA)).getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -831,16 +921,20 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns the NPC's custom skin blob, if any. // --> - if (attribute.startsWith("skin_blob")) { - if (getCitizen().data().has(NPC.PLAYER_SKIN_TEXTURE_PROPERTIES_METADATA)) { - String tex = getCitizen().data().get(NPC.PLAYER_SKIN_TEXTURE_PROPERTIES_METADATA).toString(); - String sign = ""; - if (getCitizen().data().has(NPC.PLAYER_SKIN_TEXTURE_PROPERTIES_SIGN_METADATA)) { - sign = ";" + getCitizen().data().get(NPC.PLAYER_SKIN_TEXTURE_PROPERTIES_SIGN_METADATA).toString(); + registerTag("skin_blob", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (getCitizen().data().has(NPC.PLAYER_SKIN_TEXTURE_PROPERTIES_METADATA)) { + String tex = getCitizen().data().get(NPC.PLAYER_SKIN_TEXTURE_PROPERTIES_METADATA).toString(); + String sign = ""; + if (getCitizen().data().has(NPC.PLAYER_SKIN_TEXTURE_PROPERTIES_SIGN_METADATA)) { + sign = ";" + getCitizen().data().get(NPC.PLAYER_SKIN_TEXTURE_PROPERTIES_SIGN_METADATA).toString(); + } + return new ElementTag(tex + sign).getObjectAttribute(attribute.fulfill(1)); } - return new ElementTag(tex + sign).getAttribute(attribute.fulfill(1)); + return null; } - } + }); // <--[tag] // @attribute @@ -848,11 +942,15 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns the NPC's custom skin, if any. // --> - if (attribute.startsWith("skin")) { - if (getCitizen().data().has(NPC.PLAYER_SKIN_UUID_METADATA)) { - return new ElementTag(getCitizen().data().get(NPC.PLAYER_SKIN_UUID_METADATA).toString()).getAttribute(attribute.fulfill(1)); + registerTag("skin", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (getCitizen().data().has(NPC.PLAYER_SKIN_UUID_METADATA)) { + return new ElementTag(getCitizen().data().get(NPC.PLAYER_SKIN_UUID_METADATA).toString()).getObjectAttribute(attribute.fulfill(1)); + } + return null; } - } + }); // <--[tag] // @attribute @@ -860,9 +958,12 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns the InventoryTag of the NPC. // --> - if (attribute.startsWith("inventory")) { - return getDenizenInventory().getAttribute(attribute.fulfill(1)); - } + registerTag("inventory", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return getDenizenInventory().getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -870,9 +971,12 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns whether the NPC is spawned. // --> - if (attribute.startsWith("is_spawned")) { - return new ElementTag(isSpawned()).getAttribute(attribute.fulfill(1)); - } + registerTag("is_spawned", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(isSpawned()).getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -880,9 +984,12 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns whether the NPC is protected. // --> - if (attribute.startsWith("is_protected")) { - return new ElementTag(getCitizen().isProtected()).getAttribute(attribute.fulfill(1)); - } + registerTag("is_protected", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getCitizen().isProtected()).getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -890,17 +997,20 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns the NPC's "lookclose" mechanism.getValue(). // --> - if (attribute.startsWith("lookclose")) { - NPC citizen = getCitizen(); - if (citizen.hasTrait(LookClose.class)) { - // There is no method to check if the NPC has LookClose enabled... - // LookClose.toString() returns "LookClose{" + enabled + "}" - String lookclose = citizen.getTrait(LookClose.class).toString(); - lookclose = lookclose.substring(10, lookclose.length() - 1); - return new ElementTag(Boolean.valueOf(lookclose)).getAttribute(attribute.fulfill(1)); + registerTag("lookclose", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + NPC citizen = getCitizen(); + if (citizen.hasTrait(LookClose.class)) { + // There is no method to check if the NPC has LookClose enabled... + // LookClose.toString() returns "LookClose{" + enabled + "}" + String lookclose = citizen.getTrait(LookClose.class).toString(); + lookclose = lookclose.substring(10, lookclose.length() - 1); + return new ElementTag(Boolean.valueOf(lookclose)).getObjectAttribute(attribute.fulfill(1)); + } + return new ElementTag(false).getObjectAttribute(attribute.fulfill(1)); } - return new ElementTag(false).getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute @@ -908,11 +1018,14 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns the NPC's previous navigated location. // --> - if (attribute.startsWith("location.previous_location")) { - return (NPCTagBase.previousLocations.containsKey(getId()) - ? NPCTagBase.previousLocations.get(getId()).getAttribute(attribute.fulfill(2)) - : null); - } + registerTag("location.previous_location", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return (NPCTagBase.previousLocations.containsKey(getId()) + ? NPCTagBase.previousLocations.get(getId()).getObjectAttribute(attribute.fulfill(2)) + : null); + } + }); // <--[tag] // @attribute @@ -921,10 +1034,13 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns whether the NPC teleports when it is stuck. // --> - if (attribute.startsWith("teleport_on_stuck")) { - return new ElementTag(getNavigator().getDefaultParameters().stuckAction() == TeleportStuckAction.INSTANCE) - .getAttribute(attribute.fulfill(1)); - } + registerTag("teleport_on_stuck", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getNavigator().getDefaultParameters().stuckAction() == TeleportStuckAction.INSTANCE) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -932,11 +1048,14 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns true if the NPC has an assignment script. // --> - if (attribute.startsWith("has_script")) { - NPC citizen = getCitizen(); - return new ElementTag(citizen.hasTrait(AssignmentTrait.class) && citizen.getTrait(AssignmentTrait.class).hasAssignment()) - .getAttribute(attribute.fulfill(1)); - } + registerTag("has_script", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + NPC citizen = getCitizen(); + return new ElementTag(citizen.hasTrait(AssignmentTrait.class) && citizen.getTrait(AssignmentTrait.class).hasAssignment()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -944,16 +1063,19 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns the NPC's assigned script. // --> - if (attribute.startsWith("script")) { - NPC citizen = getCitizen(); - if (!citizen.hasTrait(AssignmentTrait.class) || !citizen.getTrait(AssignmentTrait.class).hasAssignment()) { - return null; - } - else { - return new ScriptTag(citizen.getTrait(AssignmentTrait.class).getAssignment().getName()) - .getAttribute(attribute.fulfill(1)); + registerTag("script", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + NPC citizen = getCitizen(); + if (!citizen.hasTrait(AssignmentTrait.class) || !citizen.getTrait(AssignmentTrait.class).hasAssignment()) { + return null; + } + else { + return new ScriptTag(citizen.getTrait(AssignmentTrait.class).getAssignment().getName()) + .getObjectAttribute(attribute.fulfill(1)); + } } - } + }); // <--[tag] // @attribute @@ -962,9 +1084,12 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns the NPC's current pathfinding distance margin. That is, how close it needs to get to its destination (in block-lengths). // --> - if (attribute.startsWith("distance_margin")) { - return new ElementTag(getNavigator().getDefaultParameters().distanceMargin()).getAttribute(attribute.fulfill(1)); - } + registerTag("distance_margin", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getNavigator().getDefaultParameters().distanceMargin()).getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -973,9 +1098,12 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns the NPC's current pathfinding distance margin. That is, how close it needs to get to individual points along its path. // --> - if (attribute.startsWith("path_distance_margin")) { - return new ElementTag(getNavigator().getDefaultParameters().pathDistanceMargin()).getAttribute(attribute.fulfill(1)); - } + registerTag("path_distance_margin", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getNavigator().getDefaultParameters().pathDistanceMargin()).getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -983,13 +1111,19 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns whether the NPC is currently navigating. // --> - if (attribute.startsWith("is_navigating")) { - return new ElementTag(getNavigator().isNavigating()).getAttribute(attribute.fulfill(1)); - } - if (attribute.startsWith("navigator.is_navigating")) { - //Deprecations.oldNPCNavigator.warn(attribute.context); - return new ElementTag(getNavigator().isNavigating()).getAttribute(attribute.fulfill(2)); - } + registerTag("is_navigating", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getNavigator().isNavigating()).getObjectAttribute(attribute.fulfill(1)); + } + }); + registerTag("navigator.is_navigating", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + //Deprecations.oldNPCNavigator.warn(attribute.context); + return new ElementTag(getNavigator().isNavigating()).getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -997,15 +1131,21 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns the current speed of the NPC. // --> - if (attribute.startsWith("speed")) { - return new ElementTag(getNavigator().getLocalParameters().speed()) - .getAttribute(attribute.fulfill(1)); - } - if (attribute.startsWith("navigator.speed")) { - //Deprecations.oldNPCNavigator.warn(attribute.context); - return new ElementTag(getNavigator().getLocalParameters().speed()) - .getAttribute(attribute.fulfill(2)); - } + registerTag("speed", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getNavigator().getLocalParameters().speed()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); + registerTag("navigator.speed", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + //Deprecations.oldNPCNavigator.warn(attribute.context); + return new ElementTag(getNavigator().getLocalParameters().speed()) + .getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -1013,15 +1153,21 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns the NPC's current maximum pathfinding range. // --> - if (attribute.startsWith("range")) { - return new ElementTag(getNavigator().getLocalParameters().range()) - .getAttribute(attribute.fulfill(1)); - } - if (attribute.startsWith("navigator.range")) { - //Deprecations.oldNPCNavigator.warn(attribute.context); - return new ElementTag(getNavigator().getLocalParameters().range()) - .getAttribute(attribute.fulfill(2)); - } + registerTag("range", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getNavigator().getLocalParameters().range()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); + registerTag("navigator.range", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + //Deprecations.oldNPCNavigator.warn(attribute.context); + return new ElementTag(getNavigator().getLocalParameters().range()) + .getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -1029,15 +1175,21 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns the NPC's current navigator attack range limit. // --> - if (attribute.startsWith("attack_range")) { - return new ElementTag(getNavigator().getLocalParameters().attackRange()) - .getAttribute(attribute.fulfill(1)); - } - if (attribute.startsWith("navigator.attack_range")) { - //Deprecations.oldNPCNavigator.warn(attribute.context); - return new ElementTag(getNavigator().getLocalParameters().attackRange()) - .getAttribute(attribute.fulfill(2)); - } + registerTag("attack_range", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getNavigator().getLocalParameters().attackRange()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); + registerTag("navigator.attack_range", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + //Deprecations.oldNPCNavigator.warn(attribute.context); + return new ElementTag(getNavigator().getLocalParameters().attackRange()) + .getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -1045,15 +1197,21 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns the NPC's current navigator attack strategy. // --> - if (attribute.startsWith("attack_strategy")) { - return new ElementTag(getNavigator().getLocalParameters().attackStrategy().toString()) - .getAttribute(attribute.fulfill(1)); - } - if (attribute.startsWith("navigator.attack_strategy")) { - //Deprecations.oldNPCNavigator.warn(attribute.context); - return new ElementTag(getNavigator().getLocalParameters().attackStrategy().toString()) - .getAttribute(attribute.fulfill(2)); - } + registerTag("attack_strategy", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getNavigator().getLocalParameters().attackStrategy().toString()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); + registerTag("navigator.attack_strategy", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + //Deprecations.oldNPCNavigator.warn(attribute.context); + return new ElementTag(getNavigator().getLocalParameters().attackStrategy().toString()) + .getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -1061,15 +1219,21 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns the NPC's current movement speed modifier (a multiplier applied over their base speed). // --> - if (attribute.startsWith("speed_modifier")) { - return new ElementTag(getNavigator().getLocalParameters().speedModifier()) - .getAttribute(attribute.fulfill(1)); - } - if (attribute.startsWith("navigator.speed_modifier")) { - //Deprecations.oldNPCNavigator.warn(attribute.context); - return new ElementTag(getNavigator().getLocalParameters().speedModifier()) - .getAttribute(attribute.fulfill(2)); - } + registerTag("speed_modifier", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getNavigator().getLocalParameters().speedModifier()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); + registerTag("navigator.speed_modifier", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + //Deprecations.oldNPCNavigator.warn(attribute.context); + return new ElementTag(getNavigator().getLocalParameters().speedModifier()) + .getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -1077,15 +1241,21 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns the NPC's base navigation speed. // --> - if (attribute.startsWith("base_speed")) { - return new ElementTag(getNavigator().getLocalParameters().baseSpeed()) - .getAttribute(attribute.fulfill(1)); - } - if (attribute.startsWith("navigator.base_speed")) { - //Deprecations.oldNPCNavigator.warn(attribute.context); - return new ElementTag(getNavigator().getLocalParameters().baseSpeed()) - .getAttribute(attribute.fulfill(2)); - } + registerTag("base_speed", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getNavigator().getLocalParameters().baseSpeed()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); + registerTag("navigator.base_speed", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + //Deprecations.oldNPCNavigator.warn(attribute.context); + return new ElementTag(getNavigator().getLocalParameters().baseSpeed()) + .getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -1093,15 +1263,21 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns whether the NPC will avoid water. // --> - if (attribute.startsWith("avoid_water")) { - return new ElementTag(getNavigator().getLocalParameters().avoidWater()) - .getAttribute(attribute.fulfill(1)); - } - if (attribute.startsWith("navigator.avoid_water")) { - //Deprecations.oldNPCNavigator.warn(attribute.context); - return new ElementTag(getNavigator().getLocalParameters().avoidWater()) - .getAttribute(attribute.fulfill(2)); - } + registerTag("avoid_water", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getNavigator().getLocalParameters().avoidWater()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); + registerTag("navigator.avoid_water", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + //Deprecations.oldNPCNavigator.warn(attribute.context); + return new ElementTag(getNavigator().getLocalParameters().avoidWater()) + .getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -1109,19 +1285,25 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns the location the NPC is currently navigating towards (if any). // --> - if (attribute.startsWith("target_location")) { - if (getNavigator().getTargetAsLocation() == null) { - return null; + registerTag("target_location", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (getNavigator().getTargetAsLocation() == null) { + return null; + } + return new LocationTag(getNavigator().getTargetAsLocation()).getObjectAttribute(attribute.fulfill(1)); } - return new LocationTag(getNavigator().getTargetAsLocation()).getAttribute(attribute.fulfill(1)); - } - if (attribute.startsWith("navigator.target_location")) { - //Deprecations.oldNPCNavigator.warn(attribute.context); - if (getNavigator().getTargetAsLocation() == null) { - return null; + }); + registerTag("navigator.target_location", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + //Deprecations.oldNPCNavigator.warn(attribute.context); + if (getNavigator().getTargetAsLocation() == null) { + return null; + } + return new LocationTag(getNavigator().getTargetAsLocation()).getObjectAttribute(attribute.fulfill(2)); } - return new LocationTag(getNavigator().getTargetAsLocation()).getAttribute(attribute.fulfill(2)); - } + }); // <--[tag] // @attribute @@ -1129,15 +1311,21 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns whether the NPC is in combat. // --> - if (attribute.startsWith("is_fighting")) { - return new ElementTag(getNavigator().getEntityTarget() != null && getNavigator().getEntityTarget().isAggressive()) - .getAttribute(attribute.fulfill(1)); - } - if (attribute.startsWith("navigator.is_fighting")) { - //Deprecations.oldNPCNavigator.warn(attribute.context); - return new ElementTag(getNavigator().getEntityTarget() != null && getNavigator().getEntityTarget().isAggressive()) - .getAttribute(attribute.fulfill(2)); - } + registerTag("is_fighting", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getNavigator().getEntityTarget() != null && getNavigator().getEntityTarget().isAggressive()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); + registerTag("navigator.is_fighting", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + //Deprecations.oldNPCNavigator.warn(attribute.context); + return new ElementTag(getNavigator().getEntityTarget() != null && getNavigator().getEntityTarget().isAggressive()) + .getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -1145,21 +1333,27 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns the entity type of the NPC's current navigation target (if any). // --> - if (attribute.startsWith("target_type")) { - if (getNavigator().getTargetType() == null) { - return null; + registerTag("target_type", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (getNavigator().getTargetType() == null) { + return null; + } + return new ElementTag(getNavigator().getTargetType().toString()) + .getObjectAttribute(attribute.fulfill(1)); } - return new ElementTag(getNavigator().getTargetType().toString()) - .getAttribute(attribute.fulfill(1)); - } - if (attribute.startsWith("navigator.target_type")) { - //Deprecations.oldNPCNavigator.warn(attribute.context); - if (getNavigator().getTargetType() == null) { - return null; + }); + registerTag("navigator.target_type", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + //Deprecations.oldNPCNavigator.warn(attribute.context); + if (getNavigator().getTargetType() == null) { + return null; + } + return new ElementTag(getNavigator().getTargetType().toString()) + .getObjectAttribute(attribute.fulfill(2)); } - return new ElementTag(getNavigator().getTargetType().toString()) - .getAttribute(attribute.fulfill(2)); - } + }); // <--[tag] // @attribute @@ -1167,19 +1361,25 @@ && isSpawned() && getEntity() instanceof Player) { // @description // Returns the entity being targeted by the NPC's current navigation (if any). // --> - if (attribute.startsWith("target_entity")) { - if (getNavigator().getEntityTarget() == null || getNavigator().getEntityTarget().getTarget() == null) { - return null; + registerTag("target_entity", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (getNavigator().getEntityTarget() == null || getNavigator().getEntityTarget().getTarget() == null) { + return null; + } + return new EntityTag(getNavigator().getEntityTarget().getTarget()).getObjectAttribute(attribute.fulfill(1)); } - return new EntityTag(getNavigator().getEntityTarget().getTarget()).getAttribute(attribute.fulfill(1)); - } - if (attribute.startsWith("navigator.target_entity")) { - //Deprecations.oldNPCNavigator.warn(attribute.context); - if (getNavigator().getEntityTarget() == null || getNavigator().getEntityTarget().getTarget() == null) { - return null; + }); + registerTag("navigator.target_entity", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + //Deprecations.oldNPCNavigator.warn(attribute.context); + if (getNavigator().getEntityTarget() == null || getNavigator().getEntityTarget().getTarget() == null) { + return null; + } + return new EntityTag(getNavigator().getEntityTarget().getTarget()).getObjectAttribute(attribute.fulfill(2)); } - return new EntityTag(getNavigator().getEntityTarget().getTarget()).getAttribute(attribute.fulfill(2)); - } + }); // <--[tag] // @attribute @@ -1188,19 +1388,31 @@ && isSpawned() && getEntity() instanceof Player) { // Always returns 'NPC' for NPCTag objects. All objects fetchable by the Object Fetcher will return the // type of object that is fulfilling this attribute. // --> - if (attribute.startsWith("type")) { - return new ElementTag("NPC").getAttribute(attribute.fulfill(1)); - } + registerTag("type", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag("NPC").getObjectAttribute(attribute.fulfill(1)); + } + }); + } - String returned = CoreUtilities.autoPropertyTag(this, attribute); - if (returned != null) { - return returned; - } + public static ObjectTagProcessor tagProcessor = new ObjectTagProcessor(); - return (getEntity() != null - ? new EntityTag(this).getAttribute(attribute) - : new ElementTag(identify()).getAttribute(attribute)); + public static void registerTag(String name, TagRunnable.ObjectForm runnable) { + tagProcessor.registerTag(name, runnable); + } + + @Override + public ObjectTag getObjectAttribute(Attribute attribute) { + return tagProcessor.getObjectAttribute(this, attribute); + } + @Override + public ObjectTag getNextObjectTypeDown() { + if (getEntity() != null) { + return new EntityTag(this); + } + return new ElementTag(identify()); } public void applyProperty(Mechanism mechanism) { diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/PlayerTag.java b/plugin/src/main/java/com/denizenscript/denizen/objects/PlayerTag.java index b258370f19..8f5cbd0356 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/PlayerTag.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/PlayerTag.java @@ -20,6 +20,7 @@ import com.denizenscript.denizencore.objects.core.ListTag; import com.denizenscript.denizencore.objects.core.ScriptTag; import com.denizenscript.denizencore.tags.Attribute; +import com.denizenscript.denizencore.tags.ObjectTagProcessor; import com.denizenscript.denizencore.tags.TagContext; import com.denizenscript.denizencore.utilities.CoreUtilities; import com.denizenscript.denizencore.utilities.Deprecations; @@ -652,9 +653,12 @@ public String getAttribute(Attribute attribute) { ///////////////// // Defined in EntityTag - if (attribute.startsWith("is_player")) { - return new ElementTag(true).getAttribute(attribute.fulfill(1)); - } + registerTag("is_player", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(true).getObjectAttribute(attribute.fulfill(1)); + } + }); ///////////////////// // DENIZEN ATTRIBUTES @@ -668,10 +672,13 @@ public String getAttribute(Attribute attribute) { // if the player hasn't said all that much. // Works with offline players. // --> - if (attribute.startsWith("chat_history_list")) { - return new ListTag(PlayerTagBase.playerChatHistory.get(getPlayerEntity().getUniqueId())) - .getAttribute(attribute.fulfill(1)); - } + registerTag("chat_history_list", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ListTag(PlayerTagBase.playerChatHistory.get(getPlayerEntity().getUniqueId())) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -681,22 +688,25 @@ public String getAttribute(Attribute attribute) { // If a number is specified, returns an earlier thing the player said. // Works with offline players. // --> - if (attribute.startsWith("chat_history")) { - int x = 1; - if (attribute.hasContext(1) && ArgumentHelper.matchesInteger(attribute.getContext(1))) { - x = attribute.getIntContext(1); - } - // No playerchathistory? Return null. - if (!PlayerTagBase.playerChatHistory.containsKey(getPlayerEntity().getUniqueId())) { - return null; - } - List messages = PlayerTagBase.playerChatHistory.get(getPlayerEntity().getUniqueId()); - if (messages.size() < x || x < 1) { - return null; + registerTag("chat_history", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + int x = 1; + if (attribute.hasContext(1) && ArgumentHelper.matchesInteger(attribute.getContext(1))) { + x = attribute.getIntContext(1); + } + // No playerchathistory? Return null. + if (!PlayerTagBase.playerChatHistory.containsKey(getPlayerEntity().getUniqueId())) { + return null; + } + List messages = PlayerTagBase.playerChatHistory.get(getPlayerEntity().getUniqueId()); + if (messages.size() < x || x < 1) { + return null; + } + return new ElementTag(messages.get(x - 1)) + .getObjectAttribute(attribute.fulfill(1)); } - return new ElementTag(messages.get(x - 1)) - .getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute ]> @@ -705,44 +715,50 @@ public String getAttribute(Attribute attribute) { // Returns the specified flag from the player. // Works with offline players. // --> - if (attribute.startsWith("flag") && attribute.hasContext(1)) { - String flag_name = attribute.getContext(1); - - // <--[tag] - // @attribute ].is_expired> - // @returns ElementTag(Boolean) - // @description - // returns true if the flag is expired or does not exist, false if it is not yet expired or has no expiration. - // Works with offline players. - // --> - if (attribute.getAttribute(2).equalsIgnoreCase("is_expired") - || attribute.startsWith("isexpired")) { - return new ElementTag(!FlagManager.playerHasFlag(this, flag_name)) - .getAttribute(attribute.fulfill(2)); - } - if (attribute.getAttribute(2).equalsIgnoreCase("size") && !FlagManager.playerHasFlag(this, flag_name)) { - return new ElementTag(0).getAttribute(attribute.fulfill(2)); - } - if (FlagManager.playerHasFlag(this, flag_name)) { - FlagManager.Flag flag = DenizenAPI.getCurrentInstance().flagManager() - .getPlayerFlag(this, flag_name); + registerTag("flag", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; + } + String flag_name = attribute.getContext(1); // <--[tag] - // @attribute ].expiration> - // @returns DurationTag + // @attribute ].is_expired> + // @returns ElementTag(Boolean) // @description - // Returns a DurationTag of the time remaining on the flag, if it has an expiration. + // returns true if the flag is expired or does not exist, false if it is not yet expired or has no expiration. // Works with offline players. // --> - if (attribute.getAttribute(2).equalsIgnoreCase("expiration")) { - return flag.expiration().getAttribute(attribute.fulfill(2)); + if (attribute.getAttribute(2).equalsIgnoreCase("is_expired") + || attribute.startsWith("isexpired")) { + return new ElementTag(!FlagManager.playerHasFlag((PlayerTag) object, flag_name)) + .getObjectAttribute(attribute.fulfill(2)); + } + if (attribute.getAttribute(2).equalsIgnoreCase("size") && !FlagManager.playerHasFlag((PlayerTag) object, flag_name)) { + return new ElementTag(0).getObjectAttribute(attribute.fulfill(2)); } + if (FlagManager.playerHasFlag((PlayerTag) object, flag_name)) { + FlagManager.Flag flag = DenizenAPI.getCurrentInstance().flagManager() + .getPlayerFlag((PlayerTag) object, flag_name); + + // <--[tag] + // @attribute ].expiration> + // @returns DurationTag + // @description + // Returns a DurationTag of the time remaining on the flag, if it has an expiration. + // Works with offline players. + // --> + if (attribute.getAttribute(2).equalsIgnoreCase("expiration")) { + return flag.expiration().getObjectAttribute(attribute.fulfill(2)); + } - return new ListTag(flag.toString(), true, flag.values()) - .getAttribute(attribute.fulfill(1)); + return new ListTag(flag.toString(), true, flag.values()) + .getObjectAttribute(attribute.fulfill(1)); + } + return new ElementTag(identify()).getObjectAttribute(attribute); } - return new ElementTag(identify()).getAttribute(attribute); - } + }); // <--[tag] // @attribute ]> @@ -751,10 +767,16 @@ public String getAttribute(Attribute attribute) { // Returns true if the Player has the specified flag, otherwise returns false. // Works with offline players. // --> - if (attribute.startsWith("has_flag") && attribute.hasContext(1)) { - String flag_name = attribute.getContext(1); - return new ElementTag(FlagManager.playerHasFlag(this, flag_name)).getAttribute(attribute.fulfill(1)); - } + registerTag("has_flag", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; + } + String flag_name = attribute.getContext(1); + return new ElementTag(FlagManager.playerHasFlag((PlayerTag) object, flag_name)).getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute ]> @@ -764,56 +786,62 @@ public String getAttribute(Attribute attribute) { // names containing a certain pattern. // Works with offline players. // --> - if (attribute.startsWith("list_flags")) { - ListTag allFlags = new ListTag(DenizenAPI.getCurrentInstance().flagManager().listPlayerFlags(this)); - ListTag searchFlags = null; - if (!allFlags.isEmpty() && attribute.hasContext(1)) { - searchFlags = new ListTag(); - String search = attribute.getContext(1); - if (search.startsWith("regex:")) { - try { - Pattern pattern = Pattern.compile(search.substring(6), Pattern.CASE_INSENSITIVE); + registerTag("list_flags", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + ListTag allFlags = new ListTag(DenizenAPI.getCurrentInstance().flagManager().listPlayerFlags((PlayerTag) object)); + ListTag searchFlags = null; + if (!allFlags.isEmpty() && attribute.hasContext(1)) { + searchFlags = new ListTag(); + String search = attribute.getContext(1); + if (search.startsWith("regex:")) { + try { + Pattern pattern = Pattern.compile(search.substring(6), Pattern.CASE_INSENSITIVE); + for (String flag : allFlags) { + if (pattern.matcher(flag).matches()) { + searchFlags.add(flag); + } + } + } + catch (Exception e) { + Debug.echoError(e); + } + } + else { + search = CoreUtilities.toLowerCase(search); for (String flag : allFlags) { - if (pattern.matcher(flag).matches()) { + if (CoreUtilities.toLowerCase(flag).contains(search)) { searchFlags.add(flag); } } } - catch (Exception e) { - Debug.echoError(e); - } + DenizenAPI.getCurrentInstance().flagManager().shrinkPlayerFlags((PlayerTag) object, searchFlags); } else { - search = CoreUtilities.toLowerCase(search); - for (String flag : allFlags) { - if (CoreUtilities.toLowerCase(flag).contains(search)) { - searchFlags.add(flag); - } - } + DenizenAPI.getCurrentInstance().flagManager().shrinkPlayerFlags((PlayerTag) object, allFlags); } - DenizenAPI.getCurrentInstance().flagManager().shrinkPlayerFlags(this, searchFlags); - } - else { - DenizenAPI.getCurrentInstance().flagManager().shrinkPlayerFlags(this, allFlags); + return searchFlags == null ? allFlags.getObjectAttribute(attribute.fulfill(1)) + : searchFlags.getObjectAttribute(attribute.fulfill(1)); } - return searchFlags == null ? allFlags.getAttribute(attribute.fulfill(1)) - : searchFlags.getAttribute(attribute.fulfill(1)); - } + }); - if (attribute.startsWith("current_step")) { - String outcome = "null"; - if (attribute.hasContext(1)) { - try { - outcome = DenizenAPI.getCurrentInstance().getSaves().getString("Players." + getName() + ".Scripts." - + ScriptTag.valueOf(attribute.getContext(1)).getName() + ".Current Step"); - } - catch (Exception e) { - outcome = "null"; + registerTag("current_step", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + String outcome = "null"; + if (attribute.hasContext(1)) { + try { + outcome = DenizenAPI.getCurrentInstance().getSaves().getString("Players." + getName() + ".Scripts." + + ScriptTag.valueOf(attribute.getContext(1)).getName() + ".Current Step"); + } + catch (Exception e) { + outcome = "null"; + } } + return new ElementTag(outcome).getObjectAttribute(attribute.fulfill(1)); } - return new ElementTag(outcome).getAttribute(attribute.fulfill(1)); - } + }); ///////////////////// @@ -830,44 +858,47 @@ public String getAttribute(Attribute attribute) { // @mechanism PlayerTag.money // --> - if (attribute.startsWith("money")) { - if (Depends.economy == null) { - if (!attribute.hasAlternative()) { - Debug.echoError("No economy loaded! Have you installed Vault and a compatible economy plugin?"); + registerTag("money", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag 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 null; - } - attribute = attribute.fulfill(1); + attribute = attribute.fulfill(1); - // <--[tag] - // @attribute - // @returns ElementTag - // @plugin Vault - // @description - // Returns the formatted form of the player's money balance in the registered Economy system. - // --> - if (attribute.startsWith("formatted")) { - return new ElementTag(Depends.economy.format(Depends.economy.getBalance(getOfflinePlayer()))) - .getAttribute(attribute.fulfill(1)); - } + // <--[tag] + // @attribute + // @returns ElementTag + // @plugin Vault + // @description + // Returns the formatted form of the player's money balance in the registered Economy system. + // --> + if (attribute.startsWith("formatted")) { + return new ElementTag(Depends.economy.format(Depends.economy.getBalance(getOfflinePlayer()))) + .getObjectAttribute(attribute.fulfill(1)); + } - if (attribute.startsWith("currency_singular")) { - Deprecations.oldEconomyTags.warn(attribute.getScriptEntry()); - return new ElementTag(Depends.economy.currencyNameSingular()) - .getAttribute(attribute.fulfill(1)); - } + if (attribute.startsWith("currency_singular")) { + Deprecations.oldEconomyTags.warn(attribute.getScriptEntry()); + return new ElementTag(Depends.economy.currencyNameSingular()) + .getObjectAttribute(attribute.fulfill(1)); + } - if (attribute.startsWith("currency")) { - Deprecations.oldEconomyTags.warn(attribute.getScriptEntry()); - return new ElementTag(Depends.economy.currencyNamePlural()) - .getAttribute(attribute.fulfill(1)); - } + if (attribute.startsWith("currency")) { + Deprecations.oldEconomyTags.warn(attribute.getScriptEntry()); + return new ElementTag(Depends.economy.currencyNamePlural()) + .getObjectAttribute(attribute.fulfill(1)); + } - return new ElementTag(Depends.economy.getBalance(getOfflinePlayer())) - .getAttribute(attribute); + return new ElementTag(Depends.economy.getBalance(getOfflinePlayer())) + .getObjectAttribute(attribute); - } + } + }); ///////////////////// @@ -883,172 +914,170 @@ public String getAttribute(Attribute attribute) { // Optionally, specify a list of entities, entity types, or 'npc' to only count those targets. // --> - if (attribute.startsWith("target")) { - int range = 50; - int attribs = 1; + registerTag("target", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + int range = 50; + int attribs = 1; - // <--[tag] - // @attribute |...)].within[(<#>)]> - // @returns EntityTag - // @description - // Returns the living entity that the player is looking at within the specified range limit, - // or null if the player is not looking at an entity. - // Optionally, specify a list of entities, entity types, or 'npc' to only count those targets. - // --> - if (attribute.getAttribute(2).startsWith("within") && - attribute.hasContext(2) && - ArgumentHelper.matchesInteger(attribute.getContext(2))) { - attribs = 2; - range = attribute.getIntContext(2); - } - - List entities = getPlayerEntity().getNearbyEntities(range, range, range); - ArrayList possibleTargets = new ArrayList<>(); - if (!attribute.hasContext(1)) { - for (Entity entity : entities) { - if (entity instanceof LivingEntity) { - possibleTargets.add((LivingEntity) entity); + // <--[tag] + // @attribute |...)].within[(<#>)]> + // @returns EntityTag + // @description + // Returns the living entity that the player is looking at within the specified range limit, + // or null if the player is not looking at an entity. + // Optionally, specify a list of entities, entity types, or 'npc' to only count those targets. + // --> + if (attribute.getAttribute(2).startsWith("within") && + attribute.hasContext(2) && + ArgumentHelper.matchesInteger(attribute.getContext(2))) { + attribs = 2; + range = attribute.getIntContext(2); + } + + List entities = getPlayerEntity().getNearbyEntities(range, range, range); + ArrayList possibleTargets = new ArrayList<>(); + if (!attribute.hasContext(1)) { + for (Entity entity : entities) { + if (entity instanceof LivingEntity) { + possibleTargets.add((LivingEntity) entity); + } } } - } - else { - ListTag list = ListTag.getListFor(attribute.getContextObject(1)); - for (Entity entity : entities) { - if (entity instanceof LivingEntity) { - for (ObjectTag obj : list.objectForms) { - boolean valid = false; - EntityTag filterEntity = null; - if (obj instanceof EntityTag) { - filterEntity = (EntityTag) obj; - } - else if (CoreUtilities.toLowerCase(obj.toString()).equals("npc")) { - valid = EntityTag.isCitizensNPC(entity); - } - else { - filterEntity = EntityTag.getEntityFor(obj, attribute.context); - if (filterEntity == null) { - Debug.echoError("Trying to filter 'player.target[...]' tag with invalid input: " + obj.toString()); - continue; + else { + ListTag list = ListTag.getListFor(attribute.getContextObject(1)); + for (Entity entity : entities) { + if (entity instanceof LivingEntity) { + for (ObjectTag obj : list.objectForms) { + boolean valid = false; + EntityTag filterEntity = null; + if (obj instanceof EntityTag) { + filterEntity = (EntityTag) obj; } - } - if (!valid && filterEntity != null) { - if (filterEntity.isGeneric()) { - valid = filterEntity.getBukkitEntityType().equals(entity.getType()); + else if (CoreUtilities.toLowerCase(obj.toString()).equals("npc")) { + valid = EntityTag.isCitizensNPC(entity); } else { - valid = filterEntity.getUUID().equals(entity.getUniqueId()); + filterEntity = EntityTag.getEntityFor(obj, attribute.context); + if (filterEntity == null) { + Debug.echoError("Trying to filter 'player.target[...]' tag with invalid input: " + obj.toString()); + continue; + } + } + if (!valid && filterEntity != null) { + if (filterEntity.isGeneric()) { + valid = filterEntity.getBukkitEntityType().equals(entity.getType()); + } + else { + valid = filterEntity.getUUID().equals(entity.getUniqueId()); + } + } + if (valid) { + possibleTargets.add((LivingEntity) entity); + break; } - } - if (valid) { - possibleTargets.add((LivingEntity) entity); - break; } } } } - } - try { - NMSHandler.getChunkHelper().changeChunkServerThread(getWorld()); - // Find the valid target - BlockIterator bi; try { - bi = new BlockIterator(getPlayerEntity(), range); - } - catch (IllegalStateException e) { - return null; - } - Block b; - Location l; - int bx, by, bz; - double ex, ey, ez; - - // Loop through player's line of sight - while (bi.hasNext()) { - b = bi.next(); - bx = b.getX(); - by = b.getY(); - bz = b.getZ(); - - if (b.getType().isSolid()) { - // Line of sight is broken - break; + NMSHandler.getChunkHelper().changeChunkServerThread(getWorld()); + // Find the valid target + BlockIterator bi; + try { + bi = new BlockIterator(getPlayerEntity(), range); } - else { - // Check for entities near this block in the line of sight - for (LivingEntity possibleTarget : possibleTargets) { - l = possibleTarget.getLocation(); - ex = l.getX(); - ey = l.getY(); - ez = l.getZ(); - - if ((bx - .50 <= ex && ex <= bx + 1.50) && - (bz - .50 <= ez && ez <= bz + 1.50) && - (by - 1 <= ey && ey <= by + 2.5)) { - // Entity is close enough, so return it - return new EntityTag(possibleTarget).getDenizenObject().getAttribute(attribute.fulfill(attribs)); + catch (IllegalStateException e) { + return null; + } + Block b; + Location l; + int bx, by, bz; + double ex, ey, ez; + + // Loop through player's line of sight + while (bi.hasNext()) { + b = bi.next(); + bx = b.getX(); + by = b.getY(); + bz = b.getZ(); + + if (b.getType().isSolid()) { + // Line of sight is broken + break; + } + else { + // Check for entities near this block in the line of sight + for (LivingEntity possibleTarget : possibleTargets) { + l = possibleTarget.getLocation(); + ex = l.getX(); + ey = l.getY(); + ez = l.getZ(); + + if ((bx - .50 <= ex && ex <= bx + 1.50) && + (bz - .50 <= ez && ez <= bz + 1.50) && + (by - 1 <= ey && ey <= by + 2.5)) { + // Entity is close enough, so return it + return new EntityTag(possibleTarget).getDenizenObject().getObjectAttribute(attribute.fulfill(attribs)); + } } } } } + finally { + NMSHandler.getChunkHelper().restoreServerThread(getWorld()); + } + return null; } - finally { - NMSHandler.getChunkHelper().restoreServerThread(getWorld()); - } - return null; - } + }); // workaround for - if (attribute.startsWith("list_effects")) { - ListTag effects = new ListTag(); - for (PotionEffect effect : getPlayerEntity().getActivePotionEffects()) { - effects.add(effect.getType().getName() + "," + effect.getAmplifier() + "," + effect.getDuration() + "t"); + registerTag("list_effects", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + ListTag effects = new ListTag(); + for (PotionEffect effect : getPlayerEntity().getActivePotionEffects()) { + effects.add(effect.getType().getName() + "," + effect.getAmplifier() + "," + effect.getDuration() + "t"); + } + return effects.getObjectAttribute(attribute.fulfill(1)); } - return effects.getAttribute(attribute.fulfill(1)); - } + }); - if (attribute.startsWith("list")) { - Debug.echoError("DO NOT USE PLAYER.LIST AS A TAG, please use and related tags!"); - List players = new ArrayList<>(); + registerTag("list", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + Debug.echoError("DO NOT USE PLAYER.LIST AS A TAG, please use and related tags!"); + List players = new ArrayList<>(); - if (attribute.startsWith("list.online")) { - for (Player player : Bukkit.getOnlinePlayers()) { - players.add(player.getName()); + if (attribute.startsWith("list.online")) { + for (Player player : Bukkit.getOnlinePlayers()) { + players.add(player.getName()); + } + return new ListTag(players).getObjectAttribute(attribute.fulfill(2)); } - return new ListTag(players).getAttribute(attribute.fulfill(2)); - } - else if (attribute.startsWith("list.offline")) { - for (OfflinePlayer player : Bukkit.getOfflinePlayers()) { - if (!player.isOnline()) { - players.add("p@" + player.getUniqueId().toString()); + else if (attribute.startsWith("list.offline")) { + for (OfflinePlayer player : Bukkit.getOfflinePlayers()) { + if (!player.isOnline()) { + players.add("p@" + player.getUniqueId().toString()); + } } + return new ListTag(players).getObjectAttribute(attribute.fulfill(2)); } - return new ListTag(players).getAttribute(attribute.fulfill(2)); - } - else { - for (OfflinePlayer player : Bukkit.getOfflinePlayers()) { - players.add("p@" + player.getUniqueId().toString()); + else { + for (OfflinePlayer player : Bukkit.getOfflinePlayers()) { + players.add("p@" + player.getUniqueId().toString()); + } + return new ListTag(players).getObjectAttribute(attribute.fulfill(1)); } - return new ListTag(players).getAttribute(attribute.fulfill(1)); } - } + }); ///////////////////// // IDENTIFICATION ATTRIBUTES ///////////////// - if (attribute.startsWith("name") && !isOnline()) - // This can be parsed later with more detail if the player is online, so only check for offline. - { - return new ElementTag(getName()).getAttribute(attribute.fulfill(1)); - } - else if (attribute.startsWith("uuid") && !isOnline()) - // This can be parsed later with more detail if the player is online, so only check for offline. - { - return new ElementTag(offlinePlayer.getUniqueId().toString()).getAttribute(attribute.fulfill(1)); - } - // <--[tag] // @attribute // @returns ElementTag @@ -1056,9 +1085,12 @@ else if (attribute.startsWith("uuid") && !isOnline()) // Always returns 'Player' for PlayerTag objects. All objects fetchable by the Object Fetcher will return the // type of object that is fulfilling this attribute. // --> - if (attribute.startsWith("type")) { - return new ElementTag("Player").getAttribute(attribute.fulfill(1)); - } + registerTag("type", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag("Player").getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1067,9 +1099,12 @@ else if (attribute.startsWith("uuid") && !isOnline()) // Returns the ID used to save the player in Denizen's saves.yml file. // Works with offline players. // --> - if (attribute.startsWith("save_name")) { - return new ElementTag(getSaveName()).getAttribute(attribute.fulfill(1)); - } + registerTag("save_name", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getSaveName()).getObjectAttribute(attribute.fulfill(1)); + } + }); ///////////////////// @@ -1085,30 +1120,45 @@ else if (attribute.startsWith("uuid") && !isOnline()) // Works with offline players. // @mechanism PlayerTag.bed_spawn_location // --> - if (attribute.startsWith("bed_spawn")) { - try { - NMSHandler.getChunkHelper().changeChunkServerThread(getWorld()); - if (getOfflinePlayer().getBedSpawnLocation() == null) { - return null; + registerTag("bed_spawn", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + try { + NMSHandler.getChunkHelper().changeChunkServerThread(getWorld()); + if (getOfflinePlayer().getBedSpawnLocation() == null) { + return null; + } + return new LocationTag(getOfflinePlayer().getBedSpawnLocation()) + .getObjectAttribute(attribute.fulfill(1)); + } + finally { + NMSHandler.getChunkHelper().restoreServerThread(getWorld()); } - return new LocationTag(getOfflinePlayer().getBedSpawnLocation()) - .getAttribute(attribute.fulfill(1)); - } - finally { - NMSHandler.getChunkHelper().restoreServerThread(getWorld()); } - } + }); // If online, let EntityTag handle location tags since there are more options // for online Players - if (attribute.startsWith("location") && !isOnline()) { - return getLocation().getAttribute(attribute.fulfill(1)); - } + registerTag("location", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!!isOnline()) { + return null; + } + return getLocation().getObjectAttribute(attribute.fulfill(1)); + } + }); - if (attribute.startsWith("world") && !isOnline()) { - return new WorldTag(getWorld()).getAttribute(attribute.fulfill(1)); - } + registerTag("world", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!!isOnline()) { + return null; + } + return new WorldTag(getWorld()).getObjectAttribute(attribute.fulfill(1)); + } + }); ///////////////////// @@ -1121,13 +1171,17 @@ else if (attribute.startsWith("uuid") && !isOnline()) // @description // Returns the cooldown duration remaining on player's material. // --> - if (attribute.startsWith("item_cooldown")) { - MaterialTag mat = new ElementTag(attribute.getContext(1)).asType(MaterialTag.class, attribute.context); - if (mat != null) { - return new DurationTag((long) getPlayerEntity().getCooldown(mat.getMaterial())) - .getAttribute(attribute.fulfill(1)); + registerTag("item_cooldown", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + MaterialTag mat = new ElementTag(attribute.getContext(1)).asType(MaterialTag.class, attribute.context); + if (mat != null) { + return new DurationTag((long) getPlayerEntity().getCooldown(mat.getMaterial())) + .getObjectAttribute(attribute.fulfill(1)); + } + return null; } - } + }); // <--[tag] // @attribute @@ -1136,15 +1190,18 @@ else if (attribute.startsWith("uuid") && !isOnline()) // Returns the millisecond time of when the player first logged on to this server. // Works with offline players. // --> - if (attribute.startsWith("first_played")) { - attribute = attribute.fulfill(1); - if (attribute.startsWith("milliseconds") || attribute.startsWith("in_milliseconds")) { - return new ElementTag(getOfflinePlayer().getFirstPlayed()) - .getAttribute(attribute.fulfill(1)); + registerTag("first_played", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + attribute = attribute.fulfill(1); + if (attribute.startsWith("milliseconds") || attribute.startsWith("in_milliseconds")) { + return new ElementTag(getOfflinePlayer().getFirstPlayed()) + .getObjectAttribute(attribute.fulfill(1)); + } + return new DurationTag(getOfflinePlayer().getFirstPlayed() / 50) + .getObjectAttribute(attribute); } - return new DurationTag(getOfflinePlayer().getFirstPlayed() / 50) - .getAttribute(attribute); - } + }); // <--[tag] // @attribute @@ -1154,10 +1211,13 @@ else if (attribute.startsWith("uuid") && !isOnline()) // Works with offline players. // Note: This will just always return true. // --> - if (attribute.startsWith("has_played_before")) { - return new ElementTag(true) - .getAttribute(attribute.fulfill(1)); - } + registerTag("has_played_before", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(true) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1166,10 +1226,13 @@ else if (attribute.startsWith("uuid") && !isOnline()) // Returns the player's absorption health. // @mechanism PlayerTag.absorption_health // --> - if (attribute.startsWith("absorption_health")) { - return new ElementTag(NMSHandler.getPlayerHelper().getAbsorption(getPlayerEntity())) - .getAttribute(attribute.fulfill(1)); - } + registerTag("absorption_health", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(NMSHandler.getPlayerHelper().getAbsorption(getPlayerEntity())) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1177,10 +1240,13 @@ else if (attribute.startsWith("uuid") && !isOnline()) // @description // Returns whether the player's health bar is currently being scaled. // --> - if (attribute.startsWith("health.is_scaled")) { - return new ElementTag(getPlayerEntity().isHealthScaled()) - .getAttribute(attribute.fulfill(2)); - } + registerTag("health.is_scaled", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getPlayerEntity().isHealthScaled()) + .getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -1188,10 +1254,13 @@ else if (attribute.startsWith("uuid") && !isOnline()) // @description // Returns the current scale for the player's health bar // --> - if (attribute.startsWith("health.scale")) { - return new ElementTag(getPlayerEntity().getHealthScale()) - .getAttribute(attribute.fulfill(2)); - } + registerTag("health.scale", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getPlayerEntity().getHealthScale()) + .getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -1199,41 +1268,48 @@ else if (attribute.startsWith("uuid") && !isOnline()) // @description // Returns how fast the food level drops (exhaustion). // --> - if (attribute.startsWith("exhaustion")) { - return new ElementTag(getPlayerEntity().getExhaustion()) - .getAttribute(attribute.fulfill(1)); - } + registerTag("exhaustion", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getPlayerEntity().getExhaustion()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // Handle EntityTag oxygen tags here to allow getting them when the player is offline - if (attribute.startsWith("oxygen.max")) { - return new DurationTag((long) getMaximumAir()).getAttribute(attribute.fulfill(2)); - } - - if (attribute.startsWith("oxygen")) { - return new DurationTag((long) getRemainingAir()).getAttribute(attribute.fulfill(1)); - } + registerTag("oxygen", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + attribute = attribute.fulfill(1); + if (attribute.startsWith("max")) { + return new DurationTag((long) getMaximumAir()).getObjectAttribute(attribute.fulfill(1)); + } + return new DurationTag((long) getRemainingAir()).getObjectAttribute(attribute); + } + }); // Same with health tags - if (attribute.startsWith("health.formatted")) { - return EntityHealth.getHealthFormatted(new EntityTag(getPlayerEntity()), attribute).toString(); - } - - if (attribute.startsWith("health.percentage")) { - double maxHealth = getPlayerEntity().getMaxHealth(); - if (attribute.hasContext(2)) { - maxHealth = attribute.getIntContext(2); + registerTag("health", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + attribute = attribute.fulfill(1); + if (attribute.startsWith("formatted")) { + return EntityHealth.getHealthFormatted(new EntityTag(getPlayerEntity()), attribute.fulfill(1)); + } + if (attribute.startsWith("percentage")) { + double maxHealth = getPlayerEntity().getMaxHealth(); + if (attribute.hasContext(1)) { + maxHealth = attribute.getIntContext(1); + } + return new ElementTag((getPlayerEntity().getHealth() / maxHealth) * 100) + .getObjectAttribute(attribute.fulfill(1)); + } + if (attribute.startsWith("max")) { + return new ElementTag(getMaxHealth()).getObjectAttribute(attribute.fulfill(1)); + } + return new ElementTag(getHealth()).getObjectAttribute(attribute); } - return new ElementTag((getPlayerEntity().getHealth() / maxHealth) * 100) - .getAttribute(attribute.fulfill(2)); - } - - if (attribute.startsWith("health.max")) { - return new ElementTag(getMaxHealth()).getAttribute(attribute.fulfill(2)); - } - - if (attribute.matches("health")) { - return new ElementTag(getHealth()).getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute @@ -1241,16 +1317,19 @@ else if (attribute.startsWith("uuid") && !isOnline()) // @description // Returns whether the player is banned. // --> - if (attribute.startsWith("is_banned")) { - BanEntry ban = Bukkit.getBanList(BanList.Type.NAME).getBanEntry(getName()); - if (ban == null) { - return new ElementTag(false).getAttribute(attribute.fulfill(1)); - } - else if (ban.getExpiration() == null) { - return new ElementTag(true).getAttribute(attribute.fulfill(1)); + registerTag("is_banned", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + BanEntry ban = Bukkit.getBanList(BanList.Type.NAME).getBanEntry(getName()); + if (ban == null) { + return new ElementTag(false).getObjectAttribute(attribute.fulfill(1)); + } + else if (ban.getExpiration() == null) { + return new ElementTag(true).getObjectAttribute(attribute.fulfill(1)); + } + return new ElementTag(ban.getExpiration().after(new Date())).getObjectAttribute(attribute.fulfill(1)); } - return new ElementTag(ban.getExpiration().after(new Date())).getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute @@ -1259,9 +1338,12 @@ else if (ban.getExpiration() == null) { // Returns whether the player is currently online. // Works with offline players (returns false in that case). // --> - if (attribute.startsWith("is_online")) { - return new ElementTag(isOnline()).getAttribute(attribute.fulfill(1)); - } + registerTag("is_online", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(isOnline()).getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1271,10 +1353,13 @@ else if (ban.getExpiration() == null) { // Works with offline players. // @mechanism PlayerTag.is_op // --> - if (attribute.startsWith("is_op")) { - return new ElementTag(getOfflinePlayer().isOp()) - .getAttribute(attribute.fulfill(1)); - } + registerTag("is_op", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getOfflinePlayer().isOp()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1284,10 +1369,13 @@ else if (ban.getExpiration() == null) { // Works with offline players. // @mechanism PlayerTag.is_whitelisted // --> - if (attribute.startsWith("is_whitelisted")) { - return new ElementTag(getOfflinePlayer().isWhitelisted()) - .getAttribute(attribute.fulfill(1)); - } + registerTag("is_whitelisted", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getOfflinePlayer().isWhitelisted()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1296,23 +1384,26 @@ else if (ban.getExpiration() == null) { // Returns the datestamp of when the player was last seen in duration. // Works with offline players. // --> - if (attribute.startsWith("last_played")) { - attribute = attribute.fulfill(1); - if (attribute.startsWith("milliseconds") || attribute.startsWith("in_milliseconds")) { + registerTag("last_played", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + attribute = attribute.fulfill(1); + if (attribute.startsWith("milliseconds") || attribute.startsWith("in_milliseconds")) { + if (isOnline()) { + return new ElementTag(System.currentTimeMillis()) + .getObjectAttribute(attribute.fulfill(1)); + } + return new ElementTag(getOfflinePlayer().getLastPlayed()) + .getObjectAttribute(attribute.fulfill(1)); + } if (isOnline()) { - return new ElementTag(System.currentTimeMillis()) - .getAttribute(attribute.fulfill(1)); + return new DurationTag(System.currentTimeMillis() / 50) + .getObjectAttribute(attribute); } - return new ElementTag(getOfflinePlayer().getLastPlayed()) - .getAttribute(attribute.fulfill(1)); - } - if (isOnline()) { - return new DurationTag(System.currentTimeMillis() / 50) - .getAttribute(attribute); + return new DurationTag(getOfflinePlayer().getLastPlayed() / 50) + .getObjectAttribute(attribute); } - return new DurationTag(getOfflinePlayer().getLastPlayed() / 50) - .getAttribute(attribute); - } + }); // <--[tag] // @attribute @@ -1321,77 +1412,83 @@ else if (ban.getExpiration() == null) { // Returns a list of all groups the player is in. // May work with offline players, depending on permission plugin. // --> - if (attribute.startsWith("groups")) { - if (Depends.permissions == null) { - if (!attribute.hasAlternative()) { - Debug.echoError("No permission system loaded! Have you installed Vault and a compatible permissions plugin?"); + registerTag("groups", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag 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; } - return null; - } - ListTag list = new ListTag(); - // TODO: optionally specify world - for (String group : Depends.permissions.getGroups()) { - if (Depends.permissions.playerInGroup(null, offlinePlayer, group)) { - list.add(group); + ListTag list = new ListTag(); + // TODO: optionally specify world + for (String group : Depends.permissions.getGroups()) { + if (Depends.permissions.playerInGroup(null, offlinePlayer, group)) { + list.add(group); + } } + return list.getObjectAttribute(attribute.fulfill(1)); } - return list.getAttribute(attribute.fulfill(1)); - } + }); - if (attribute.startsWith("ban_info")) { - attribute.fulfill(1); - BanEntry ban = Bukkit.getBanList(BanList.Type.NAME).getBanEntry(getName()); - if (ban == null || (ban.getExpiration() != null && ban.getExpiration().before(new Date()))) { - return null; - } + registerTag("ban_info", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + attribute.fulfill(1); + BanEntry ban = Bukkit.getBanList(BanList.Type.NAME).getBanEntry(getName()); + if (ban == null || (ban.getExpiration() != null && ban.getExpiration().before(new Date()))) { + return null; + } - // <--[tag] - // @attribute - // @returns DurationTag - // @description - // Returns the expiration of the player's ban, if they are banned. - // Potentially can be null. - // --> - if (attribute.startsWith("expiration") && ban.getExpiration() != null) { - return new DurationTag(ban.getExpiration().getTime() / 50) - .getAttribute(attribute.fulfill(1)); - } + // <--[tag] + // @attribute + // @returns DurationTag + // @description + // Returns the expiration of the player's ban, if they are banned. + // Potentially can be null. + // --> + if (attribute.startsWith("expiration") && ban.getExpiration() != null) { + return new DurationTag(ban.getExpiration().getTime() / 50) + .getObjectAttribute(attribute.fulfill(1)); + } - // <--[tag] - // @attribute - // @returns ElementTag - // @description - // Returns the reason for the player's ban, if they are banned. - // --> - else if (attribute.startsWith("reason")) { - return new ElementTag(ban.getReason()) - .getAttribute(attribute.fulfill(1)); - } + // <--[tag] + // @attribute + // @returns ElementTag + // @description + // Returns the reason for the player's ban, if they are banned. + // --> + else if (attribute.startsWith("reason")) { + return new ElementTag(ban.getReason()) + .getObjectAttribute(attribute.fulfill(1)); + } - // <--[tag] - // @attribute - // @returns DurationTag - // @description - // Returns when the player's ban was created, if they are banned. - // --> - else if (attribute.startsWith("created")) { - return new DurationTag(ban.getCreated().getTime() / 50) - .getAttribute(attribute.fulfill(1)); - } + // <--[tag] + // @attribute + // @returns DurationTag + // @description + // Returns when the player's ban was created, if they are banned. + // --> + else if (attribute.startsWith("created")) { + return new DurationTag(ban.getCreated().getTime() / 50) + .getObjectAttribute(attribute.fulfill(1)); + } - // <--[tag] - // @attribute - // @returns ElementTag - // @description - // Returns the source of the player's ban, if they are banned. - // --> - else if (attribute.startsWith("source")) { - return new ElementTag(ban.getSource()) - .getAttribute(attribute.fulfill(1)); - } + // <--[tag] + // @attribute + // @returns ElementTag + // @description + // Returns the source of the player's ban, if they are banned. + // --> + else if (attribute.startsWith("source")) { + return new ElementTag(ban.getSource()) + .getObjectAttribute(attribute.fulfill(1)); + } - return null; - } + return null; + } + }); // <--[tag] // @attribute ]> @@ -1401,53 +1498,57 @@ else if (attribute.startsWith("source")) { // This requires an online player - if the player may be offline, consider using // <@link tag PlayerTag.in_group[group_name].global>. // --> - if (attribute.startsWith("in_group")) { - if (Depends.permissions == null) { - if (!attribute.hasAlternative()) { - Debug.echoError("No permission system loaded! Have you installed Vault and a compatible permissions plugin?"); + registerTag("in_group", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag 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; } - return null; - } - String group = attribute.getContext(1); + String group = attribute.getContext(1); - // <--[tag] - // @attribute ].global> - // @returns ElementTag(Boolean) - // @description - // Returns whether the player has the group with no regard to the - // player's current world. - // (Works with offline players) - // (Note: This may or may not be functional with your permissions system.) - // --> + // <--[tag] + // @attribute ].global> + // @returns ElementTag(Boolean) + // @description + // Returns whether the player has the group with no regard to the + // player's current world. + // (Works with offline players) + // (Note: This may or may not be functional with your permissions system.) + // --> - // Non-world specific permission - if (attribute.getAttribute(2).startsWith("global")) { - return new ElementTag(Depends.permissions.playerInGroup((World) null, getName(), group)) // TODO: Vault UUID support? - .getAttribute(attribute.fulfill(2)); - } + // Non-world specific permission + if (attribute.getAttribute(2).startsWith("global")) { + return new ElementTag(Depends.permissions.playerInGroup((World) null, getName(), group)) // TODO: Vault UUID support? + .getObjectAttribute(attribute.fulfill(2)); + } - // <--[tag] - // @attribute ].world[]> - // @returns ElementTag(Boolean) - // @description - // Returns whether the player has the group in regards to a specific world. - // (Works with offline players) - // (Note: This may or may not be functional with your permissions system.) - // --> + // <--[tag] + // @attribute ].world[]> + // @returns ElementTag(Boolean) + // @description + // Returns whether the player has the group in regards to a specific world. + // (Works with offline players) + // (Note: This may or may not be functional with your permissions system.) + // --> - // Permission in certain world - else if (attribute.getAttribute(2).startsWith("world")) { - return new ElementTag(Depends.permissions.playerInGroup(attribute.getContext(2), getName(), group)) // TODO: Vault UUID support? - .getAttribute(attribute.fulfill(2)); - } + // Permission in certain world + else if (attribute.getAttribute(2).startsWith("world")) { + return new ElementTag(Depends.permissions.playerInGroup(attribute.getContext(2), getName(), group)) // TODO: Vault UUID support? + .getObjectAttribute(attribute.fulfill(2)); + } - // Permission in current world - else if (isOnline()) { - return new ElementTag(Depends.permissions.playerInGroup(getPlayerEntity(), group)) - .getAttribute(attribute.fulfill(1)); + // Permission in current world + else if (isOnline()) { + return new ElementTag(Depends.permissions.playerInGroup(getPlayerEntity(), group)) + .getObjectAttribute(attribute.fulfill(1)); + } + return null; } - } + }); // <--[tag] // @attribute @@ -1456,62 +1557,115 @@ else if (isOnline()) { // Returns whether the player has the specified node. // (Requires the player to be online) // --> - if (attribute.startsWith("permission") - || attribute.startsWith("has_permission")) { + registerTag("has_permission", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + String permission = attribute.getContext(1); - String permission = attribute.getContext(1); - - // <--[tag] - // @attribute - // @returns ElementTag(Boolean) - // @description - // Returns whether the player has the specified node, regardless of world. - // (Works with offline players) - // (Note: this may or may not be functional with your permissions system.) - // --> + // <--[tag] + // @attribute + // @returns ElementTag(Boolean) + // @description + // Returns whether the player has the specified node, regardless of world. + // (Works with offline players) + // (Note: this may or may not be functional with your permissions system.) + // --> - // Non-world specific permission - if (attribute.getAttribute(2).startsWith("global")) { - if (Depends.permissions == null) { - if (!attribute.hasAlternative()) { - Debug.echoError("No permission system loaded! Have you installed Vault and a compatible permissions plugin?"); + // Non-world specific permission + if (attribute.getAttribute(2).startsWith("global")) { + if (Depends.permissions == null) { + if (!attribute.hasAlternative()) { + Debug.echoError("No permission system loaded! Have you installed Vault and a compatible permissions plugin?"); + } + return null; } - return null; - } - return new ElementTag(Depends.permissions.has((World) null, getName(), permission)) // TODO: Vault UUID support? - .getAttribute(attribute.fulfill(2)); - } + return new ElementTag(Depends.permissions.has((World) null, getName(), permission)) // TODO: Vault UUID support? + .getObjectAttribute(attribute.fulfill(2)); + } - // <--[tag] - // @attribute ]> - // @returns ElementTag(Boolean) - // @description - // Returns whether the player has the specified node in regards to the - // specified world. - // (Works with offline players) - // (Note: This may or may not be functional with your permissions system.) - // --> + // <--[tag] + // @attribute ]> + // @returns ElementTag(Boolean) + // @description + // Returns whether the player has the specified node in regards to the + // specified world. + // (Works with offline players) + // (Note: This may or may not be functional with your permissions system.) + // --> - // Permission in certain world - else if (attribute.getAttribute(2).startsWith("world")) { - if (Depends.permissions == null) { - if (!attribute.hasAlternative()) { - Debug.echoError("No permission system loaded! Have you installed Vault and a compatible permissions plugin?"); + // Permission in certain world + else if (attribute.getAttribute(2).startsWith("world")) { + if (Depends.permissions == null) { + if (!attribute.hasAlternative()) { + Debug.echoError("No permission system loaded! Have you installed Vault and a compatible permissions plugin?"); + } + return null; } - return null; + + return new ElementTag(Depends.permissions.has(attribute.getContext(2), getName(), permission)) // TODO: Vault UUID support? + .getObjectAttribute(attribute.fulfill(2)); } - return new ElementTag(Depends.permissions.has(attribute.getContext(2), getName(), permission)) // TODO: Vault UUID support? - .getAttribute(attribute.fulfill(2)); + // Permission in current world + else if (isOnline()) { + return new ElementTag(getPlayerEntity().hasPermission(permission)) + .getObjectAttribute(attribute.fulfill(1)); + } + return null; } + }); + registerTag("permission", tagProcessor.registeredObjectTags.get("has_permission")); - // Permission in current world - else if (isOnline()) { - return new ElementTag(getPlayerEntity().hasPermission(permission)) - .getAttribute(attribute.fulfill(1)); + // <--[tag] + // @attribute + // @returns ElementTag + // @description + // Returns the UUID of the player. + // Works with offline players. + // --> + registerTag("uuid", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getOfflinePlayer().getUniqueId().toString()).getObjectAttribute(attribute.fulfill(1)); } - } + }); + + // <--[tag] + // @attribute + // @returns ElementTag + // @description + // Returns the name of the player. + // Works with offline players. + // --> + registerTag("name", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + attribute = attribute.fulfill(1); + // <--[tag] + // @attribute + // @returns ElementTag + // @description + // Returns the name of the player as shown in the player list. + // --> + if (attribute.startsWith("list") && isOnline()) { + return new ElementTag(getPlayerEntity().getPlayerListName()) + .getObjectAttribute(attribute.fulfill(1)); + } + // <--[tag] + // @attribute + // @returns ElementTag + // @description + // Returns the display name of the player, which may contain + // prefixes and suffixes, colors, etc. + // --> + if (attribute.startsWith("display") && isOnline()) { + return new ElementTag(getPlayerEntity().getDisplayName()) + .getObjectAttribute(attribute.fulfill(1)); + } + return new ElementTag(getName()).getObjectAttribute(attribute); + } + }); ///////////////////// // INVENTORY ATTRIBUTES @@ -1524,9 +1678,12 @@ else if (isOnline()) { // Returns a InventoryTag of the player's current inventory. // Works with offline players. // --> - if (attribute.startsWith("inventory")) { - return getInventory().getAttribute(attribute.fulfill(1)); - } + registerTag("inventory", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return getInventory().getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1535,26 +1692,18 @@ else if (isOnline()) { // Gets the player's enderchest inventory. // Works with offline players. // --> - if (attribute.startsWith("enderchest")) { - return getEnderChest().getAttribute(attribute.fulfill(1)); - } + registerTag("enderchest", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return getEnderChest().getObjectAttribute(attribute.fulfill(1)); + } + }); ///////////////////// // ONLINE ATTRIBUTES ///////////////// - // Player is required to be online after this point... - if (!isOnline()) { - - String returned = CoreUtilities.autoPropertyTag(this, attribute); - if (returned != null) { - return returned; - } - - return new ElementTag(identify()).getAttribute(attribute); - } - // <--[tag] // @attribute // @returns InventoryTag @@ -1562,10 +1711,13 @@ else if (isOnline()) { // Gets the inventory the player currently has open. If the player has no open // inventory, this returns the player's inventory. // --> - if (attribute.startsWith("open_inventory")) { - return InventoryTag.mirrorBukkitInventory(getPlayerEntity().getOpenInventory().getTopInventory()) - .getAttribute(attribute.fulfill(1)); - } + registerOnlineOnlyTag("open_inventory", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return InventoryTag.mirrorBukkitInventory(getPlayerEntity().getOpenInventory().getTopInventory()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1573,12 +1725,16 @@ else if (isOnline()) { // @description // Returns the index of the trade the player is currently viewing, if any. // --> - if (attribute.startsWith("selected_trade_index")) { - if (getPlayerEntity().getOpenInventory().getTopInventory() instanceof MerchantInventory) { - return new ElementTag(((MerchantInventory) getPlayerEntity().getOpenInventory().getTopInventory()) - .getSelectedRecipeIndex() + 1).getAttribute(attribute.fulfill(1)); + registerOnlineOnlyTag("selected_trade_index", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (getPlayerEntity().getOpenInventory().getTopInventory() instanceof MerchantInventory) { + return new ElementTag(((MerchantInventory) getPlayerEntity().getOpenInventory().getTopInventory()) + .getSelectedRecipeIndex() + 1).getObjectAttribute(attribute.fulfill(1)); + } + return null; } - } + }); // This is almost completely broke and only works if the player has placed items in the trade slots. // [tag] @@ -1588,13 +1744,16 @@ else if (isOnline()) { // Returns the trade the player is currently viewing, if any. // /* - if (attribute.startsWith("selected_trade")) { - Inventory playerInventory = getPlayerEntity().getOpenInventory().getTopInventory(); + registerOnlineOnlyTag("selected_trade", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + Inventory playerInventory = getPlayerEntity().getOpenInventory().getTopInventory(); if (playerInventory instanceof MerchantInventory && ((MerchantInventory) playerInventory).getSelectedRecipe() != null) { - return new TradeTag(((MerchantInventory) playerInventory).getSelectedRecipe()).getAttribute(attribute.fulfill(1)); + return new TradeTag(((MerchantInventory) playerInventory).getSelectedRecipe()).getObjectAttribute(attribute.fulfill(1)); } - } + } + }); */ // <--[tag] @@ -1604,10 +1763,13 @@ else if (isOnline()) { // Returns the item on the player's cursor, if any. This includes // chest interfaces, inventories, and hotbars, etc. // --> - if (attribute.startsWith("item_on_cursor")) { - return new ItemTag(getPlayerEntity().getItemOnCursor()) - .getAttribute(attribute.fulfill(1)); - } + registerOnlineOnlyTag("item_on_cursor", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ItemTag(getPlayerEntity().getItemOnCursor()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1615,10 +1777,13 @@ else if (isOnline()) { // @description // Returns the slot location of the player's selected item. // --> - if (attribute.startsWith("item_in_hand.slot")) { - return new ElementTag(getPlayerEntity().getInventory().getHeldItemSlot() + 1) - .getAttribute(attribute.fulfill(2)); - } + registerOnlineOnlyTag("item_in_hand.slot", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getPlayerEntity().getInventory().getHeldItemSlot() + 1) + .getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -1626,13 +1791,16 @@ else if (isOnline()) { // @description // Returns the current lines set on the player's Sidebar via the Sidebar command. // --> - if (attribute.startsWith("sidebar.lines")) { - Sidebar sidebar = SidebarCommand.getSidebar(this); - if (sidebar == null) { - return null; + registerOnlineOnlyTag("sidebar.lines", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + Sidebar sidebar = SidebarCommand.getSidebar((PlayerTag) object); + if (sidebar == null) { + return null; + } + return new ListTag(sidebar.getLinesText()).getObjectAttribute(attribute.fulfill(2)); } - return new ListTag(sidebar.getLinesText()).getAttribute(attribute.fulfill(2)); - } + }); // <--[tag] // @attribute @@ -1640,13 +1808,16 @@ else if (isOnline()) { // @description // Returns the current title set on the player's Sidebar via the Sidebar command. // --> - if (attribute.startsWith("sidebar.title")) { - Sidebar sidebar = SidebarCommand.getSidebar(this); - if (sidebar == null) { - return null; + registerOnlineOnlyTag("sidebar.title", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + Sidebar sidebar = SidebarCommand.getSidebar((PlayerTag) object); + if (sidebar == null) { + return null; + } + return new ElementTag(sidebar.getTitle()).getObjectAttribute(attribute.fulfill(2)); } - return new ElementTag(sidebar.getTitle()).getAttribute(attribute.fulfill(2)); - } + }); // <--[tag] // @attribute @@ -1655,17 +1826,20 @@ else if (isOnline()) { // Returns the current scores set on the player's Sidebar via the Sidebar command, // in the same order as <@link tag PlayerTag.sidebar.lines>. // --> - if (attribute.startsWith("sidebar.scores")) { - Sidebar sidebar = SidebarCommand.getSidebar(this); - if (sidebar == null) { - return null; - } - ListTag scores = new ListTag(); - for (int score : sidebar.getScores()) { - scores.add(String.valueOf(score)); + registerOnlineOnlyTag("sidebar.scores", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + Sidebar sidebar = SidebarCommand.getSidebar((PlayerTag) object); + if (sidebar == null) { + return null; + } + ListTag scores = new ListTag(); + for (int score : sidebar.getScores()) { + scores.add(String.valueOf(score)); + } + return scores.getObjectAttribute(attribute.fulfill(2)); } - return scores.getAttribute(attribute.fulfill(2)); - } + }); // <--[tag] // @attribute @@ -1674,58 +1848,64 @@ else if (isOnline()) { // Returns the player's current skin blob. // @mechanism PlayerTag.skin_blob // --> - if (attribute.startsWith("skin_blob")) { - return new ElementTag(NMSHandler.getInstance().getProfileEditor().getPlayerSkinBlob(getPlayerEntity())) - .getAttribute(attribute.fulfill(1)); - } + registerOnlineOnlyTag("skin_blob", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(NMSHandler.getInstance().getProfileEditor().getPlayerSkinBlob(getPlayerEntity())) + .getObjectAttribute(attribute.fulfill(1)); + } + }); - if (attribute.startsWith("attack_cooldown")) { - attribute.fulfill(1); + registerOnlineOnlyTag("attack_cooldown", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + attribute.fulfill(1); - // <--[tag] - // @attribute - // @returns DurationTag - // @description - // Returns the amount of time that passed since the start of the attack cooldown. - // --> - if (attribute.startsWith("duration")) { - return new DurationTag((long) NMSHandler.getPlayerHelper() - .ticksPassedDuringCooldown(getPlayerEntity())).getAttribute(attribute.fulfill(1)); - } + // <--[tag] + // @attribute + // @returns DurationTag + // @description + // Returns the amount of time that passed since the start of the attack cooldown. + // --> + if (attribute.startsWith("duration")) { + return new DurationTag((long) NMSHandler.getPlayerHelper() + .ticksPassedDuringCooldown(getPlayerEntity())).getObjectAttribute(attribute.fulfill(1)); + } - // <--[tag] - // @attribute - // @returns DurationTag - // @description - // Returns the maximum amount of time that can pass before the player's main hand has returned - // to its original place after the cooldown has ended. - // NOTE: This is slightly inaccurate and may not necessarily match with the actual attack - // cooldown progress. - // --> - else if (attribute.startsWith("max_duration")) { - return new DurationTag((long) NMSHandler.getPlayerHelper() - .getMaxAttackCooldownTicks(getPlayerEntity())).getAttribute(attribute.fulfill(1)); - } + // <--[tag] + // @attribute + // @returns DurationTag + // @description + // Returns the maximum amount of time that can pass before the player's main hand has returned + // to its original place after the cooldown has ended. + // NOTE: This is slightly inaccurate and may not necessarily match with the actual attack + // cooldown progress. + // --> + else if (attribute.startsWith("max_duration")) { + return new DurationTag((long) NMSHandler.getPlayerHelper() + .getMaxAttackCooldownTicks(getPlayerEntity())).getObjectAttribute(attribute.fulfill(1)); + } - // <--[tag] - // @attribute - // @returns ElementTag(Decimal) - // @description - // Returns the progress of the attack cooldown. 0 means that the attack cooldown has just - // started, while 100 means that the attack cooldown has finished. - // NOTE: This may not match exactly with the clientside attack cooldown indicator. - // --> - else if (attribute.startsWith("percent")) { - return new ElementTag(NMSHandler.getPlayerHelper() - .getAttackCooldownPercent(getPlayerEntity()) * 100).getAttribute(attribute.fulfill(1)); - } + // <--[tag] + // @attribute + // @returns ElementTag(Decimal) + // @description + // Returns the progress of the attack cooldown. 0 means that the attack cooldown has just + // started, while 100 means that the attack cooldown has finished. + // NOTE: This may not match exactly with the clientside attack cooldown indicator. + // --> + else if (attribute.startsWith("percent")) { + return new ElementTag(NMSHandler.getPlayerHelper() + .getAttackCooldownPercent(getPlayerEntity()) * 100).getObjectAttribute(attribute.fulfill(1)); + } - Debug.echoError("The tag 'player.attack_cooldown...' must be followed by a sub-tag."); + Debug.echoError("The tag 'player.attack_cooldown...' must be followed by a sub-tag."); - return null; - } + return null; + } + }); ///////////////////// @@ -1739,11 +1919,15 @@ else if (attribute.startsWith("percent")) { // Returns the NPCTag that the player currently has selected with // '/npc select', null if no player selected. // --> - if (attribute.startsWith("selected_npc")) { - if (getPlayerEntity().hasMetadata("selected")) { - return getSelectedNPC().getAttribute(attribute.fulfill(1)); + registerOnlineOnlyTag("selected_npc", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (getPlayerEntity().hasMetadata("selected")) { + return getSelectedNPC().getObjectAttribute(attribute.fulfill(1)); + } + return null; } - } + }); ///////////////////// @@ -1757,10 +1941,13 @@ else if (attribute.startsWith("percent")) { // Returns the EntityTag object of the player. // (Note: This should never actually be needed. is considered a valid EntityTag by script commands.) // --> - if (attribute.startsWith("entity") && !attribute.startsWith("entity_")) { - return new EntityTag(getPlayerEntity()) - .getAttribute(attribute.fulfill(1)); - } + registerOnlineOnlyTag("entity", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new EntityTag(getPlayerEntity()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); ///////////////////// @@ -1773,56 +1960,36 @@ else if (attribute.startsWith("percent")) { // @description // Returns the player's IP address host name. // --> - if (attribute.startsWith("ip") || - attribute.startsWith("host_name")) { - attribute = attribute.fulfill(1); - // <--[tag] - // @attribute - // @returns ElementTag - // @description - // Returns the player's IP address. - // --> - if (attribute.startsWith("address_only")) { - return new ElementTag(getPlayerEntity().getAddress().toString()) - .getAttribute(attribute.fulfill(1)); - } - String host = getPlayerEntity().getAddress().getHostName(); - // <--[tag] - // @attribute - // @returns ElementTag - // @description - // Returns the player's IP address. - // --> - if (attribute.startsWith("address")) { - return new ElementTag(getPlayerEntity().getAddress().toString()) - .getAttribute(attribute.fulfill(1)); + registerOnlineOnlyTag("ip", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + attribute = attribute.fulfill(1); + // <--[tag] + // @attribute + // @returns ElementTag + // @description + // Returns the player's IP address. + // --> + if (attribute.startsWith("address_only")) { + return new ElementTag(getPlayerEntity().getAddress().toString()) + .getObjectAttribute(attribute.fulfill(1)); + } + String host = getPlayerEntity().getAddress().getHostName(); + // <--[tag] + // @attribute + // @returns ElementTag + // @description + // Returns the player's IP address. + // --> + if (attribute.startsWith("address")) { + return new ElementTag(getPlayerEntity().getAddress().toString()) + .getObjectAttribute(attribute.fulfill(1)); + } + return new ElementTag(host) + .getObjectAttribute(attribute); } - return new ElementTag(host) - .getAttribute(attribute); - } - - // <--[tag] - // @attribute - // @returns ElementTag - // @description - // Returns the display name of the player, which may contain - // prefixes and suffixes, colors, etc. - // --> - if (attribute.startsWith("name.display")) { - return new ElementTag(getPlayerEntity().getDisplayName()) - .getAttribute(attribute.fulfill(2)); - } - - // <--[tag] - // @attribute - // @returns ElementTag - // @description - // Returns the name of the player as shown in the player list. - // --> - if (attribute.startsWith("name.list")) { - return new ElementTag(getPlayerEntity().getPlayerListName()) - .getAttribute(attribute.fulfill(2)); - } + }); + registerOnlineOnlyTag("host_name", tagProcessor.registeredObjectTags.get("ip")); // <--[tag] // @attribute @@ -1830,20 +1997,13 @@ else if (attribute.startsWith("percent")) { // @description // Returns the displayed text in the nameplate of the player. // --> - if (attribute.startsWith("nameplate")) { - return new ElementTag(NMSHandler.getInstance().getProfileEditor().getPlayerName(getPlayerEntity())) - .getAttribute(attribute.fulfill(1)); - } - - // <--[tag] - // @attribute - // @returns ElementTag - // @description - // Returns the name of the player. - // --> - if (attribute.startsWith("name")) { - return new ElementTag(getName()).getAttribute(attribute.fulfill(1)); - } + registerOnlineOnlyTag("nameplate", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(NMSHandler.getInstance().getProfileEditor().getPlayerName(getPlayerEntity())) + .getObjectAttribute(attribute.fulfill(1)); + } + }); ///////////////////// // LOCATION ATTRIBUTES @@ -1855,12 +2015,16 @@ else if (attribute.startsWith("percent")) { // @description // Returns the location of the player's compass target. // --> - if (attribute.startsWith("compass_target")) { - Location target = getPlayerEntity().getCompassTarget(); - if (target != null) { - return new LocationTag(target).getAttribute(attribute.fulfill(1)); + registerOnlineOnlyTag("compass_target", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + Location target = getPlayerEntity().getCompassTarget(); + if (target != null) { + return new LocationTag(target).getObjectAttribute(attribute.fulfill(1)); + } + return null; } - } + }); // <--[tag] // @attribute ]> @@ -1868,13 +2032,19 @@ else if (attribute.startsWith("percent")) { // @description // Returns whether the player has the chunk loaded on their client. // --> - if (attribute.startsWith("chunk_loaded") && attribute.hasContext(1)) { - ChunkTag chunk = ChunkTag.valueOf(attribute.getContext(1)); - if (chunk == null) { - return null; + registerOnlineOnlyTag("chunk_loaded", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; + } + ChunkTag chunk = ChunkTag.valueOf(attribute.getContext(1)); + if (chunk == null) { + return null; + } + return new ElementTag(chunk.isLoadedSafe() && hasChunkLoaded(chunk.getChunkForTag(attribute))).getObjectAttribute(attribute.fulfill(1)); } - return new ElementTag(chunk.isLoadedSafe() && hasChunkLoaded(chunk.getChunkForTag(attribute))).getAttribute(attribute.fulfill(1)); - } + }); ///////////////////// @@ -1888,10 +2058,14 @@ else if (attribute.startsWith("percent")) { // Returns whether the player is allowed to fly. // @mechanism PlayerTag.can_fly // --> - if (attribute.startsWith("can_fly") || attribute.startsWith("allowed_flight")) { - return new ElementTag(getPlayerEntity().getAllowFlight()) - .getAttribute(attribute.fulfill(1)); - } + registerOnlineOnlyTag("can_fly", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getPlayerEntity().getAllowFlight()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); + registerOnlineOnlyTag("allowed_flight", tagProcessor.registeredObjectTags.get("can_fly")); // <--[tag] // @attribute @@ -1899,10 +2073,13 @@ else if (attribute.startsWith("percent")) { // @description // Returns the speed the player can fly at. // --> - if (attribute.startsWith("fly_speed")) { - return new ElementTag(getPlayerEntity().getFlySpeed()) - .getAttribute(attribute.fulfill(1)); - } + registerOnlineOnlyTag("fly_speed", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getPlayerEntity().getFlySpeed()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1911,28 +2088,31 @@ else if (attribute.startsWith("percent")) { // Returns a 'formatted' value of the player's current food level. // May be 'starving', 'famished', 'parched, 'hungry' or 'healthy'. // --> - if (attribute.startsWith("food_level.formatted")) { - double maxHunger = getPlayerEntity().getMaxHealth(); - if (attribute.hasContext(2)) { - maxHunger = attribute.getIntContext(2); - } - int foodLevel = getFoodLevel(); - if (foodLevel / maxHunger < .10) { - return new ElementTag("starving").getAttribute(attribute.fulfill(2)); - } - else if (foodLevel / maxHunger < .40) { - return new ElementTag("famished").getAttribute(attribute.fulfill(2)); - } - else if (foodLevel / maxHunger < .75) { - return new ElementTag("parched").getAttribute(attribute.fulfill(2)); - } - else if (foodLevel / maxHunger < 1) { - return new ElementTag("hungry").getAttribute(attribute.fulfill(2)); - } - else { - return new ElementTag("healthy").getAttribute(attribute.fulfill(2)); + registerOnlineOnlyTag("food_level.formatted", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + double maxHunger = getPlayerEntity().getMaxHealth(); + if (attribute.hasContext(2)) { + maxHunger = attribute.getIntContext(2); + } + int foodLevel = getFoodLevel(); + if (foodLevel / maxHunger < .10) { + return new ElementTag("starving").getObjectAttribute(attribute.fulfill(2)); + } + else if (foodLevel / maxHunger < .40) { + return new ElementTag("famished").getObjectAttribute(attribute.fulfill(2)); + } + else if (foodLevel / maxHunger < .75) { + return new ElementTag("parched").getObjectAttribute(attribute.fulfill(2)); + } + else if (foodLevel / maxHunger < 1) { + return new ElementTag("hungry").getObjectAttribute(attribute.fulfill(2)); + } + else { + return new ElementTag("healthy").getObjectAttribute(attribute.fulfill(2)); + } } - } + }); // <--[tag] // @attribute @@ -1940,10 +2120,13 @@ else if (foodLevel / maxHunger < 1) { // @description // Returns the current saturation of the player. // --> - if (attribute.startsWith("saturation")) { - return new ElementTag(getPlayerEntity().getSaturation()) - .getAttribute(attribute.fulfill(1)); - } + registerOnlineOnlyTag("saturation", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getPlayerEntity().getSaturation()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1951,10 +2134,13 @@ else if (foodLevel / maxHunger < 1) { // @description // Returns the current food level of the player. // --> - if (attribute.startsWith("food_level")) { - return new ElementTag(getFoodLevel()) - .getAttribute(attribute.fulfill(1)); - } + registerOnlineOnlyTag("food_level", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getFoodLevel()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1962,21 +2148,24 @@ else if (foodLevel / maxHunger < 1) { // @description // Returns the name of the gamemode the player is currently set to. // --> - if (attribute.startsWith("gamemode")) { - attribute = attribute.fulfill(1); - // <--[tag] - // @attribute - // @returns ElementTag(Number) - // @description - // Returns the gamemode ID of the player. 0 = survival, 1 = creative, 2 = adventure, 3 = spectator - // --> - if (attribute.startsWith("id")) { - return new ElementTag(getPlayerEntity().getGameMode().getValue()) - .getAttribute(attribute.fulfill(1)); + registerOnlineOnlyTag("gamemode", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + attribute = attribute.fulfill(1); + // <--[tag] + // @attribute + // @returns ElementTag(Number) + // @description + // Returns the gamemode ID of the player. 0 = survival, 1 = creative, 2 = adventure, 3 = spectator + // --> + if (attribute.startsWith("id")) { + return new ElementTag(getPlayerEntity().getGameMode().getValue()) + .getObjectAttribute(attribute.fulfill(1)); + } + return new ElementTag(getPlayerEntity().getGameMode().name()) + .getObjectAttribute(attribute); } - return new ElementTag(getPlayerEntity().getGameMode().name()) - .getAttribute(attribute); - } + }); // <--[tag] // @attribute @@ -1984,10 +2173,13 @@ else if (foodLevel / maxHunger < 1) { // @description // Returns whether the player is currently blocking. // --> - if (attribute.startsWith("is_blocking")) { - return new ElementTag(getPlayerEntity().isBlocking()) - .getAttribute(attribute.fulfill(1)); - } + registerOnlineOnlyTag("is_blocking", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getPlayerEntity().isBlocking()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -1995,10 +2187,13 @@ else if (foodLevel / maxHunger < 1) { // @description // Returns the player's current ping. // --> - if (attribute.startsWith("ping")) { - return new ElementTag(NMSHandler.getPlayerHelper().getPing(getPlayerEntity())) - .getAttribute(attribute.fulfill(1)); - } + registerOnlineOnlyTag("ping", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(NMSHandler.getPlayerHelper().getPing(getPlayerEntity())) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2006,10 +2201,13 @@ else if (foodLevel / maxHunger < 1) { // @description // Returns whether the player is currently flying. // --> - if (attribute.startsWith("is_flying")) { - return new ElementTag(getPlayerEntity().isFlying()) - .getAttribute(attribute.fulfill(1)); - } + registerOnlineOnlyTag("is_flying", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getPlayerEntity().isFlying()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2017,10 +2215,13 @@ else if (foodLevel / maxHunger < 1) { // @description // Returns whether the player is currently sleeping. // --> - if (attribute.startsWith("is_sleeping")) { - return new ElementTag(getPlayerEntity().isSleeping()) - .getAttribute(attribute.fulfill(1)); - } + registerOnlineOnlyTag("is_sleeping", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getPlayerEntity().isSleeping()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2028,10 +2229,13 @@ else if (foodLevel / maxHunger < 1) { // @description // Returns whether the player is currently sneaking. // --> - if (attribute.startsWith("is_sneaking")) { - return new ElementTag(getPlayerEntity().isSneaking()) - .getAttribute(attribute.fulfill(1)); - } + registerOnlineOnlyTag("is_sneaking", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getPlayerEntity().isSneaking()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2039,10 +2243,13 @@ else if (foodLevel / maxHunger < 1) { // @description // Returns whether the player is currently sprinting. // --> - if (attribute.startsWith("is_sprinting")) { - return new ElementTag(getPlayerEntity().isSprinting()) - .getAttribute(attribute.fulfill(1)); - } + registerOnlineOnlyTag("is_sprinting", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getPlayerEntity().isSprinting()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute ]> @@ -2050,17 +2257,23 @@ else if (foodLevel / maxHunger < 1) { // @description // Returns whether the player has completed the specified advancement. // --> - if (attribute.startsWith("has_advancement") && attribute.hasContext(1)) { - Advancement adv = AdvancementHelper.getAdvancement(attribute.getContext(1)); - if (adv == null) { - if (!attribute.hasAlternative()) { - Debug.echoError("Advancement '" + attribute.getContext(1) + "' does not exist."); + registerOnlineOnlyTag("has_advancement", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; } - return null; + Advancement adv = AdvancementHelper.getAdvancement(attribute.getContext(1)); + if (adv == null) { + if (!attribute.hasAlternative()) { + Debug.echoError("Advancement '" + attribute.getContext(1) + "' does not exist."); + } + return null; + } + AdvancementProgress progress = getPlayerEntity().getAdvancementProgress(adv); + return new ElementTag(progress.isDone()).getObjectAttribute(attribute.fulfill(1)); } - AdvancementProgress progress = getPlayerEntity().getAdvancementProgress(adv); - return new ElementTag(progress.isDone()).getAttribute(attribute.fulfill(1)); - } + }); // <--[tag] // @attribute @@ -2068,15 +2281,18 @@ else if (foodLevel / maxHunger < 1) { // @description // Returns a list of the names of all advancements the player has completed. // --> - if (attribute.startsWith("list_advancements")) { - ListTag list = new ListTag(); - Bukkit.advancementIterator().forEachRemaining((adv) -> { - if (getPlayerEntity().getAdvancementProgress(adv).isDone()) { - list.add(adv.getKey().toString()); - } - }); - return list.getAttribute(attribute.fulfill(1)); - } + registerOnlineOnlyTag("list_advancements", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + ListTag list = new ListTag(); + Bukkit.advancementIterator().forEachRemaining((adv) -> { + if (getPlayerEntity().getAdvancementProgress(adv).isDone()) { + list.add(adv.getKey().toString()); + } + }); + return list.getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute ]> @@ -2085,48 +2301,54 @@ else if (foodLevel / maxHunger < 1) { // Returns the player's current value for the specified statistic. // Valid statistics: <@link url https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Statistic.html> // --> - if (attribute.startsWith("statistic") && attribute.hasContext(1)) { - Statistic statistic = Statistic.valueOf(attribute.getContext(1).toUpperCase()); - if (statistic == null) { - return null; - } + registerOnlineOnlyTag("statistic", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!attribute.hasContext(1)) { + return null; + } + Statistic statistic = Statistic.valueOf(attribute.getContext(1).toUpperCase()); + if (statistic == null) { + return null; + } - // <--[tag] - // @attribute ].qualifier[/]> - // @returns ElementTag(Number) - // @description - // Returns the player's current value for the specified statistic, with the - // specified qualifier, which can be either an entity or material. - // Valid statistics: <@link url https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Statistic.html> - // --> - if (attribute.getAttribute(2).startsWith("qualifier")) { - ObjectTag obj = ObjectFetcher.pickObjectFor(attribute.getContext(2), attribute.context); - try { - if (obj instanceof MaterialTag) { - return new ElementTag(getPlayerEntity().getStatistic(statistic, ((MaterialTag) obj).getMaterial())) - .getAttribute(attribute.fulfill(2)); - } - else if (obj instanceof EntityTag) { - return new ElementTag(getPlayerEntity().getStatistic(statistic, ((EntityTag) obj).getBukkitEntityType())) - .getAttribute(attribute.fulfill(2)); + // <--[tag] + // @attribute ].qualifier[/]> + // @returns ElementTag(Number) + // @description + // Returns the player's current value for the specified statistic, with the + // specified qualifier, which can be either an entity or material. + // Valid statistics: <@link url https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Statistic.html> + // --> + if (attribute.getAttribute(2).startsWith("qualifier")) { + ObjectTag obj = ObjectFetcher.pickObjectFor(attribute.getContext(2), attribute.context); + try { + if (obj instanceof MaterialTag) { + return new ElementTag(getPlayerEntity().getStatistic(statistic, ((MaterialTag) obj).getMaterial())) + .getObjectAttribute(attribute.fulfill(2)); + } + else if (obj instanceof EntityTag) { + return new ElementTag(getPlayerEntity().getStatistic(statistic, ((EntityTag) obj).getBukkitEntityType())) + .getObjectAttribute(attribute.fulfill(2)); + } + else { + return null; + } } - else { + catch (Exception e) { + Debug.echoError("Invalid statistic: " + statistic + " for this player!"); return null; } } + try { + return new ElementTag(getPlayerEntity().getStatistic(statistic)).getObjectAttribute(attribute.fulfill(1)); + } catch (Exception e) { Debug.echoError("Invalid statistic: " + statistic + " for this player!"); return null; } } - try { - return new ElementTag(getPlayerEntity().getStatistic(statistic)).getAttribute(attribute.fulfill(1)); - } - catch (Exception e) { - Debug.echoError("Invalid statistic: " + statistic + " for this player!"); - return null; - } - } + }); // <--[tag] // @attribute @@ -2134,10 +2356,13 @@ else if (obj instanceof EntityTag) { // @description // Returns the time the player has been asleep. // --> - if (attribute.startsWith("time_asleep")) { - return new DurationTag(getPlayerEntity().getSleepTicks() / 20) - .getAttribute(attribute.fulfill(1)); - } + registerOnlineOnlyTag("time_asleep", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new DurationTag(getPlayerEntity().getSleepTicks() / 20) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2147,10 +2372,13 @@ else if (obj instanceof EntityTag) { // the time that the rest of the world is currently experiencing if a 'time' or 'freeze_time' // mechanism is being used on the player. // --> - if (attribute.startsWith("time")) { - return new ElementTag(getPlayerEntity().getPlayerTime()) - .getAttribute(attribute.fulfill(1)); - } + registerOnlineOnlyTag("time", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getPlayerEntity().getPlayerTime()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2158,10 +2386,13 @@ else if (obj instanceof EntityTag) { // @description // Returns the speed the player can walk at. // --> - if (attribute.startsWith("walk_speed")) { - return new ElementTag(getPlayerEntity().getWalkSpeed()) - .getAttribute(attribute.fulfill(1)); - } + registerOnlineOnlyTag("walk_speed", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getPlayerEntity().getWalkSpeed()) + .getObjectAttribute(attribute.fulfill(1)); + } + }); // <--[tag] // @attribute @@ -2173,15 +2404,18 @@ else if (obj instanceof EntityTag) { // the weather is currently being forced onto the player. // Returns null if the player does not currently have any forced weather. // --> - if (attribute.startsWith("weather")) { - if (getPlayerEntity().getPlayerWeather() != null) { - return new ElementTag(getPlayerEntity().getPlayerWeather().name()) - .getAttribute(attribute.fulfill(1)); - } - else { - return null; + registerOnlineOnlyTag("weather", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (getPlayerEntity().getPlayerWeather() != null) { + return new ElementTag(getPlayerEntity().getPlayerWeather().name()) + .getObjectAttribute(attribute.fulfill(1)); + } + else { + return null; + } } - } + }); // <--[tag] // @attribute @@ -2189,10 +2423,13 @@ else if (obj instanceof EntityTag) { // @description // Returns the number of XP levels the player has. // --> - if (attribute.startsWith("xp.level")) { - return new ElementTag(getPlayerEntity().getLevel()) - .getAttribute(attribute.fulfill(2)); - } + registerOnlineOnlyTag("xp.level", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getPlayerEntity().getLevel()) + .getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -2200,10 +2437,13 @@ else if (obj instanceof EntityTag) { // @description // Returns the amount of XP needed to get to the next level. // --> - if (attribute.startsWith("xp.to_next_level")) { - return new ElementTag(getPlayerEntity().getExpToLevel()) - .getAttribute(attribute.fulfill(2)); - } + registerOnlineOnlyTag("xp.to_next_level", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getPlayerEntity().getExpToLevel()) + .getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -2211,10 +2451,13 @@ else if (obj instanceof EntityTag) { // @description // Returns the total amount of experience points. // --> - if (attribute.startsWith("xp.total")) { - return new ElementTag(getPlayerEntity().getTotalExperience()) - .getAttribute(attribute.fulfill(2)); - } + registerOnlineOnlyTag("xp.total", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getPlayerEntity().getTotalExperience()) + .getObjectAttribute(attribute.fulfill(2)); + } + }); // <--[tag] // @attribute @@ -2222,58 +2465,104 @@ else if (obj instanceof EntityTag) { // @description // Returns the percentage of experience points to the next level. // --> - if (attribute.startsWith("xp")) { - return new ElementTag(getPlayerEntity().getExp() * 100) - .getAttribute(attribute.fulfill(1)); - } - - if (Depends.chat != null) { + registerOnlineOnlyTag("xp", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + return new ElementTag(getPlayerEntity().getExp() * 100) + .getObjectAttribute(attribute.fulfill(1)); + } + }); - // <--[tag] - // @attribute - // @returns ElementTag - // @plugin Vault - // @description - // Returns the player's chat prefix. - // NOTE: May work with offline players. - // Requires a Vault-compatible chat plugin. - // @mechanism PlayerTag.chat_prefix - // --> - if (attribute.startsWith("chat_prefix")) { + // <--[tag] + // @attribute + // @returns ElementTag + // @plugin Vault + // @description + // Returns the player's chat prefix. + // NOTE: May work with offline players. + // Requires a Vault-compatible chat plugin. + // @mechanism PlayerTag.chat_prefix + // --> + registerTag("chat_prefix", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag 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(getWorld().getName(), getOfflinePlayer()); if (prefix == null) { return null; } - return new ElementTag(prefix).getAttribute(attribute.fulfill(1)); + return new ElementTag(prefix).getObjectAttribute(attribute.fulfill(1)); } + }); - // <--[tag] - // @attribute - // @returns ElementTag - // @plugin Vault - // @description - // Returns the player's chat suffix. - // NOTE: May work with offline players. - // Requires a Vault-compatible chat plugin. - // @mechanism PlayerTag.chat_suffix - // --> - else if (attribute.startsWith("chat_suffix")) { + // <--[tag] + // @attribute + // @returns ElementTag + // @plugin Vault + // @description + // Returns the player's chat suffix. + // NOTE: May work with offline players. + // Requires a Vault-compatible chat plugin. + // @mechanism PlayerTag.chat_suffix + // --> + registerTag("chat_suffix", new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag 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(getWorld().getName(), getOfflinePlayer()); if (suffix == null) { return null; } - return new ElementTag(suffix).getAttribute(attribute.fulfill(1)); + return new ElementTag(suffix).getObjectAttribute(attribute.fulfill(1)); } - } + }); + } - String returned = CoreUtilities.autoPropertyTag(this, attribute); - if (returned != null) { - return returned; - } + public static ObjectTagProcessor tagProcessor = new ObjectTagProcessor(); + + public static void registerOnlineOnlyTag(String name, TagRunnable.ObjectForm runnable) { + TagRunnable.ObjectForm newRunnable = new TagRunnable.ObjectForm() { + @Override + public ObjectTag run(Attribute attribute, ObjectTag object) { + if (!((PlayerTag) object).isOnline()) { + if (!attribute.hasAlternative()) { + Debug.echoError("Player is not online, but tag '" + attribute.getAttributeWithoutContext(1) + "' requires the player be online, for player: " + object.debuggable()); + } + return null; + } + return runnable.run(attribute, object); + } + }; + newRunnable.name = runnable.name; + registerTag(name, newRunnable); + } + + public static void registerTag(String name, TagRunnable.ObjectForm runnable) { + tagProcessor.registerTag(name, runnable); + } - return new EntityTag(getPlayerEntity()).getAttribute(attribute); + @Override + public ObjectTag getObjectAttribute(Attribute attribute) { + return tagProcessor.getObjectAttribute(this, attribute); } + @Override + public ObjectTag getNextObjectTypeDown() { + if (isOnline()) { + return new EntityTag(getPlayerEntity()); + } + return new ElementTag(identify()); + } public void applyProperty(Mechanism mechanism) { Debug.echoError("Cannot apply properties to a player!");