package net.aufdemrand.denizen.objects; import net.aufdemrand.denizen.nms.NMSHandler; import net.aufdemrand.denizen.utilities.DenizenAPI; import net.aufdemrand.denizen.utilities.blocks.FakeBlock; import net.aufdemrand.denizen.utilities.debugging.dB; import net.aufdemrand.denizencore.objects.*; import net.aufdemrand.denizencore.objects.properties.Property; import net.aufdemrand.denizencore.objects.properties.PropertyParser; import net.aufdemrand.denizencore.tags.Attribute; import net.aufdemrand.denizencore.tags.TagContext; import net.aufdemrand.denizencore.utilities.CoreUtilities; import org.bukkit.Chunk; import org.bukkit.ChunkSnapshot; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; import org.bukkit.scheduler.BukkitRunnable; import java.util.*; public class dChunk implements dObject, Adjustable { ////////////////// // OBJECT FETCHER //////////////// public static dChunk valueOf(String string) { return valueOf(string, null); } /** * Gets a Chunk Object from a string form of x,z,world. * This is not to be confused with the 'x,y,z,world' of a * location, which is a finer grain of unit in a dWorlds. * * @param string the string or dScript argument String * @return a dChunk, or null if incorrectly formatted */ @Fetchable("ch") public static dChunk valueOf(String string, TagContext context) { if (string == null) { return null; } string = CoreUtilities.toLowerCase(string).replace("ch@", ""); //////// // Match location formats // Get a location to fetch its chunk, return if null String[] parts = string.split(","); if (parts.length == 3) { try { return new dChunk(dWorld.valueOf(parts[2], context).getWorld() .getChunkAt(Integer.valueOf(parts[0]), Integer.valueOf(parts[1]))); } catch (Exception e) { if (context == null || context.debug) { dB.log("Minor: valueOf dChunk returning null: " + "ch@" + string); } return null; } } else { if (context == null || context.debug) { dB.log("Minor: valueOf dChunk unable to handle malformed format: " + "ch@" + string); } } return null; } public static boolean matches(String string) { if (CoreUtilities.toLowerCase(string).startsWith("ch@")) { return true; } else { return false; } } Chunk chunk = null; /** * dChunk can be constructed with a Chunk * * @param chunk The chunk to use. */ public dChunk(Chunk chunk) { this.chunk = chunk; } /** * dChunk can be constructed with a Location (or dLocation) * * @param location The location of the chunk. */ public dChunk(Location location) { this(location.getChunk()); } public dLocation getCenter() { return new dLocation(getWorld(), getX() * 16 + 8, 128, getZ() * 16 + 8); } public int getX() { return chunk.getX(); } public int getZ() { return chunk.getZ(); } public World getWorld() { return chunk.getWorld(); } public ChunkSnapshot getSnapshot() { return chunk.getChunkSnapshot(); } public int[] getHeightMap() { return NMSHandler.getInstance().getChunkHelper().getHeightMap(chunk); } String prefix = "Chunk"; @Override public String getObjectType() { return "Chunk"; } @Override public String getPrefix() { return prefix; } @Override public dChunk setPrefix(String prefix) { this.prefix = prefix; return this; } @Override public String debug() { return ("<G>" + prefix + "='<Y>" + identify() + "<G>' "); } @Override public boolean isUnique() { return true; } @Override public String identify() { return "ch@" + getX() + ',' + getZ() + ',' + getWorld().getName(); } @Override public String identifySimple() { return identify(); } @Override public String toString() { return identify(); } public static void registerTags() { // <--[tag] // @attribute <ch@chunk.add[<#>,<#>]> // @returns dChunk // @description // Returns the chunk with the specified coordinates added to it. // --> registerTag("add", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { if (!attribute.hasContext(1)) { dB.echoError("The tag ch@chunk.add[<#>,<#>] must have a value."); return null; } List<String> coords = CoreUtilities.split(attribute.getContext(1), ','); if (coords.size() < 2) { dB.echoError("The tag ch@chunk.add[<#>,<#>] requires two values!"); return null; } double x = aH.getDoubleFrom(coords.get(0)) * 16; double z = aH.getDoubleFrom(coords.get(1)) * 16; return new dChunk(((dChunk) object).getCenter().clone().add(x, 0, z).getChunk()) .getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <ch@chunk.sub[<#>,<#>]> // @returns dChunk // @description // Returns the chunk with the specified coordinates subtracted from it. // --> registerTag("sub", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { if (!attribute.hasContext(1)) { dB.echoError("The tag ch@chunk.add[<#>,<#>] must have a value."); return null; } List<String> coords = CoreUtilities.split(attribute.getContext(1), ','); if (coords.size() < 2) { dB.echoError("The tag ch@chunk.sub[<#>,<#>] requires two values!"); return null; } double x = aH.getDoubleFrom(coords.get(0)) * 16; double z = aH.getDoubleFrom(coords.get(1)) * 16; return new dChunk(((dChunk) object).getCenter().clone().subtract(x, 0, z).getChunk()) .getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <ch@chunk.is_loaded> // @returns Element(Boolean) // @description // Returns true if the chunk is currently loaded into memory. // --> registerTag("is_loaded", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { return new Element(((dChunk) object).chunk.isLoaded()).getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <ch@chunk.x> // @returns Element(Number) // @description // Returns the x coordinate of the chunk. // --> registerTag("x", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { return new Element(((dChunk) object).chunk.getX()).getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <ch@chunk.z> // @returns Element(Number) // @description // Returns the z coordinate of the chunk. // --> registerTag("z", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { return new Element(((dChunk) object).chunk.getZ()).getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <ch@chunk.world> // @returns dWorld // @description // Returns the world associated with the chunk. // --> registerTag("world", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { return dWorld.mirrorBukkitWorld(((dChunk) object).chunk.getWorld()).getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <ch@chunk.cuboid> // @returns dCuboid // @description // Returns a cuboid of this chunk. // --> registerTag("cuboid", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { dChunk chunk = (dChunk) object; return new dCuboid(new Location(chunk.getWorld(), chunk.getX() * 16, 0, chunk.getZ() * 16), new Location(chunk.getWorld(), chunk.getX() * 16 + 15, 255, chunk.getZ() * 16 + 15)) .getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <ch@chunk.entities> // @returns dList(dEntity) // @description // Returns a list of entities in the chunk. // --> registerTag("entities", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { dList entities = new dList(); for (Entity ent : ((dChunk) object).chunk.getEntities()) { entities.add(new dEntity(ent).identify()); } return entities.getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <ch@chunk.living_entities> // @returns dList(dEntity) // @description // Returns a list of living entities in the chunk. This includes Players, mobs, NPCs, etc., but excludes // dropped items, experience orbs, etc. // --> registerTag("living_entities", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { dList entities = new dList(); for (Entity ent : ((dChunk) object).chunk.getEntities()) { if (ent instanceof LivingEntity) { entities.add(new dEntity(ent).identify()); } } return entities.getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <ch@chunk.players> // @returns dList(dPlayer) // @description // Returns a list of players in the chunk. // --> registerTag("players", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { dList entities = new dList(); for (Entity ent : ((dChunk) object).chunk.getEntities()) { if (dEntity.isPlayer(ent)) { entities.add(new dEntity(ent).identify()); } } return entities.getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <ch@chunk.height_map> // @returns dList(Element) // @description // Returns a list of the height of each block in the chunk. // --> registerTag("height_map", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { int[] heightMap = ((dChunk) object).getHeightMap(); List<String> height_map = new ArrayList<String>(heightMap.length); for (int i : heightMap) { height_map.add(String.valueOf(i)); } return new dList(height_map).getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <ch@chunk.average_height> // @returns Element(Decimal) // @description // Returns the average height of the blocks in the chunk. // --> registerTag("average_height", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { int sum = 0; int[] heightMap = ((dChunk) object).getHeightMap(); for (int i : heightMap) { sum += i; } return new Element(((double) sum) / heightMap.length).getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <ch@chunk.is_flat[#]> // @returns Element(Boolean) // @description // scans the heights of the blocks to check variance between them. If no number is supplied, is_flat will return // true if all the blocks are less than 2 blocks apart in height. Specifying a number will modify the number // criteria for determining if it is flat. // --> registerTag("is_flat", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { int tolerance = attribute.hasContext(1) && aH.matchesInteger(attribute.getContext(1)) ? Integer.valueOf(attribute.getContext(1)) : 2; int[] heightMap = ((dChunk) object).getHeightMap(); int x = heightMap[0]; for (int i : heightMap) { if (Math.abs(x - i) > tolerance) { return Element.FALSE.getAttribute(attribute.fulfill(1)); } } return Element.TRUE.getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <ch@chunk.surface_blocks> // @returns dList(dLocation) // @description // Returns a list of the highest non-air surface blocks in the chunk. // --> registerTag("surface_blocks", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { dList surface_blocks = new dList(); for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { surface_blocks.add(new dLocation(((dChunk) object).chunk.getBlock(x, ((dChunk) object) .getSnapshot().getHighestBlockYAt(x, z) - 1, z).getLocation()).identify()); } } return surface_blocks.getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <ch@chunk.spawn_slimes> // @returns dList(dLocation) // @description // Returns whether the chunk is a specially located 'slime spawner' chunk. // --> registerTag("spawn_slimes", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { dChunk chunk = (dChunk) object; Random random = new Random(chunk.getWorld().getSeed() + chunk.getX() * chunk.getX() * 4987142 + chunk.getX() * 5947611 + chunk.getZ() * chunk.getZ() * 4392871L + chunk.getZ() * 389711 ^ 0x3AD8025F); return new Element(random.nextInt(10) == 0).getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <ch@chunk.type> // @returns Element // @description // Always returns 'Chunk' for dChunk objects. All objects fetchable by the Object Fetcher will return the // type of object that is fulfilling this attribute. // --> registerTag("type", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { return new Element("Chunk").getAttribute(attribute.fulfill(1)); } }); } public static HashMap<String, TagRunnable> registeredTags = new HashMap<String, TagRunnable>(); public static void registerTag(String name, TagRunnable runnable) { if (runnable.name == null) { runnable.name = name; } registeredTags.put(name, runnable); } @Override public String getAttribute(Attribute attribute) { if (attribute == null) { return null; } // TODO: Scrap getAttribute, make this functionality a core system String attrLow = CoreUtilities.toLowerCase(attribute.getAttributeWithoutContext(1)); TagRunnable tr = registeredTags.get(attrLow); if (tr != null) { if (!tr.name.equals(attrLow)) { net.aufdemrand.denizencore.utilities.debugging.dB.echoError(attribute.getScriptEntry() != null ? attribute.getScriptEntry().getResidingQueue() : null, "Using deprecated form of tag '" + tr.name + "': '" + attrLow + "'."); } return tr.run(attribute, this); } // Iterate through this object's properties' attributes for (Property property : PropertyParser.getProperties(this)) { String returned = property.getAttribute(attribute); if (returned != null) { return returned; } } return new Element(identify()).getAttribute(attribute); } public void applyProperty(Mechanism mechanism) { dB.echoError("Cannot apply properties to a chunk!"); } @Override public void adjust(Mechanism mechanism) { // <--[mechanism] // @object dChunk // @name unload // @input None // @description // Removes a chunk from memory. // @tags // <chunk.is_loaded> // --> if (mechanism.matches("unload")) { chunk.unload(true); } // <--[mechanism] // @object dChunk // @name unload_safely // @input None // @description // Removes a chunk from memory in a safe manner. // @tags // <chunk.is_loaded> // --> if (mechanism.matches("unload_safely")) { chunk.unload(true, true); } // <--[mechanism] // @object dChunk // @name unload_without_saving // @input None // @description // Removes a chunk from memory without saving any recent changes. // @tags // <chunk.is_loaded> // --> if (mechanism.matches("unload_without_saving")) { chunk.unload(false); } // <--[mechanism] // @object dChunk // @name load // @input None // @description // Loads a chunk into memory. // @tags // <chunk.is_loaded> // --> if (mechanism.matches("load")) { chunk.load(true); } // <--[mechanism] // @object dChunk // @name regenerate // @input None // @description // Causes the chunk to be entirely deleted and reformed from the world's seed. // @tags // None // --> if (mechanism.matches("regenerate")) { getWorld().regenerateChunk(getX(), getZ()); } // <--[mechanism] // @object dChunk // @name refresh_chunk // @input None // @description // Refreshes the chunk, sending any changed properties to players. // @tags // None // --> if (mechanism.matches("refresh_chunk")) { final int chunkX = getX(); final int chunkZ = getZ(); getWorld().refreshChunk(chunkX, chunkZ); new BukkitRunnable() { @Override public void run() { for (Map<dLocation, FakeBlock> blocks : FakeBlock.getBlocks().values()) { for (Map.Entry<dLocation, FakeBlock> locBlock : blocks.entrySet()) { dLocation location = locBlock.getKey(); if (Math.floor(location.getX() / 16) == chunkX && Math.floor(location.getZ() / 16) == chunkZ) { locBlock.getValue().updateBlock(); } } } } }.runTaskLater(DenizenAPI.getCurrentInstance(), 2); } if (!mechanism.fulfilled()) { mechanism.reportInvalid(); } // Iterate through this object's properties' mechanisms for (Property property : PropertyParser.getProperties(this)) { property.adjust(mechanism); if (mechanism.fulfilled()) { break; } } } }