diff --git a/src/main/java/net/aufdemrand/denizen/scripts/commands/BukkitCommandRegistry.java b/src/main/java/net/aufdemrand/denizen/scripts/commands/BukkitCommandRegistry.java index 853f4a03c4..65bda5ee39 100644 --- a/src/main/java/net/aufdemrand/denizen/scripts/commands/BukkitCommandRegistry.java +++ b/src/main/java/net/aufdemrand/denizen/scripts/commands/BukkitCommandRegistry.java @@ -1487,6 +1487,32 @@ public void registerCoreMembers() { "LEASH", "leash (cancel) [|...] (holder:/)", 1); + // <--[command] + // @Name Light + // @Syntax light [] [<#>/reset] + // @Required 2 + // @Stable stable + // @Short Creates a light source at the location. + // @Author Morphan1 + // @Group world + // @Description + // This command can create and reset a light source at a specified location, regardless of the type + // of block. It will be shown to all players near the location until it is reset, with certain + // exceptions. + // @Tags + // + // + // @Usage + // Use to create a bright light at a noted location. + // - light l@MyFancyLightOfWool 15 + // @Usage + // Use to reset the brightness of the location to its original state. + // - light l@MyFancyLightOfWool reset + // --> + registerCoreMember(LightCommand.class, + "LIGHT", "light [] [<#>/reset]", 2); + + // <--[command] // @Name Listen // @Syntax listen ({new}/cancel/finish) [kill/block/item/itemdrop/travel] [] [script:] (id:) diff --git a/src/main/java/net/aufdemrand/denizen/scripts/commands/world/LightCommand.java b/src/main/java/net/aufdemrand/denizen/scripts/commands/world/LightCommand.java new file mode 100644 index 0000000000..4fa9f2159e --- /dev/null +++ b/src/main/java/net/aufdemrand/denizen/scripts/commands/world/LightCommand.java @@ -0,0 +1,56 @@ +package net.aufdemrand.denizen.scripts.commands.world; + +import net.aufdemrand.denizen.objects.dLocation; +import net.aufdemrand.denizen.utilities.blocks.BlockLight; +import net.aufdemrand.denizen.utilities.debugging.dB; +import net.aufdemrand.denizencore.exceptions.CommandExecutionException; +import net.aufdemrand.denizencore.exceptions.InvalidArgumentsException; +import net.aufdemrand.denizencore.objects.Element; +import net.aufdemrand.denizencore.objects.aH; +import net.aufdemrand.denizencore.scripts.ScriptEntry; +import net.aufdemrand.denizencore.scripts.commands.AbstractCommand; + +public class LightCommand extends AbstractCommand { + + @Override + public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException { + + for (aH.Argument arg : aH.interpret(scriptEntry.getArguments())) { + + if (!scriptEntry.hasObject("location") + && arg.matchesArgumentType(dLocation.class)) + scriptEntry.addObject("location", arg.asType(dLocation.class)); + + else if (!scriptEntry.hasObject("light") + && arg.matchesPrimitive(aH.PrimitiveType.Integer)) + scriptEntry.addObject("light", arg.asElement()); + + else if (!scriptEntry.hasObject("reset") + && arg.matches("reset")) + scriptEntry.addObject("reset", new Element(true)); + + } + + if (!scriptEntry.hasObject("location") || + (!scriptEntry.hasObject("light") && !scriptEntry.hasObject("reset"))) { + throw new InvalidArgumentsException("Must specify a valid location and light level."); + } + + scriptEntry.defaultObject("reset", new Element(false)); + } + + @Override + public void execute(ScriptEntry scriptEntry) throws CommandExecutionException { + + dLocation location = scriptEntry.getdObject("location"); + Element light = scriptEntry.getElement("light"); + Element reset = scriptEntry.getElement("reset"); + + dB.report(scriptEntry, getName(), location.debug() + (light != null ? light.debug() : "") + reset.debug()); + + if (!reset.asBoolean()) + BlockLight.createLight(location, light.asInt()); + else + BlockLight.removeLight(location); + } +} diff --git a/src/main/java/net/aufdemrand/denizen/utilities/blocks/BlockLight.java b/src/main/java/net/aufdemrand/denizen/utilities/blocks/BlockLight.java new file mode 100644 index 0000000000..85cfab0d28 --- /dev/null +++ b/src/main/java/net/aufdemrand/denizen/utilities/blocks/BlockLight.java @@ -0,0 +1,199 @@ +package net.aufdemrand.denizen.utilities.blocks; + +import net.aufdemrand.denizen.utilities.debugging.dB; +import net.minecraft.server.v1_8_R1.*; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.craftbukkit.v1_8_R1.CraftChunk; +import org.bukkit.craftbukkit.v1_8_R1.CraftWorld; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +public class BlockLight { + + private static final Method playerChunkMethod; + private static final Field dirtyCountField; + private static final Map lightsByLocation = new HashMap(); + private static final BlockFace[] adjacentFaces = new BlockFace[] { + BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST, BlockFace.UP, BlockFace.DOWN + }; + + static { + Method pcm = null; + Field dcf = null; + try { + pcm = PlayerChunkMap.class.getDeclaredMethod("a", int.class, int.class, boolean.class); + pcm.setAccessible(true); + dcf = pcm.getReturnType().getDeclaredField("dirtyCount"); + dcf.setAccessible(true); + } catch (Exception e) { + dB.echoError(e); + } + playerChunkMethod = pcm; + dirtyCountField = dcf; + for (World worlds : Bukkit.getServer().getWorlds()) { + WorldServer nmsWorld = ((CraftWorld) worlds).getHandle(); + IWorldAccess access = getIWorldAccess(worlds); + + nmsWorld.addIWorldAccess(access); + } + } + + private final CraftWorld craftWorld; + private final WorldServer worldServer; + private final CraftChunk craftChunk; + private final Block block; + private final BlockPosition position; + private final int originalLight; + private int currentLight; + + private BlockLight(Location location) { + this.craftWorld = (CraftWorld) location.getWorld(); + this.worldServer = craftWorld.getHandle(); + this.craftChunk = (CraftChunk) location.getChunk(); + this.block = location.getBlock(); + this.position = new BlockPosition(block.getX(), block.getY(), block.getZ()); + this.originalLight = block.getLightLevel(); + this.currentLight = originalLight; + lightsByLocation.put(location, this); + } + + public static BlockLight createLight(Location location, int lightLevel) { + location = location.getBlock().getLocation(); + BlockLight blockLight; + if (lightsByLocation.containsKey(location)) + removeLight(location); + blockLight = new BlockLight(location); + blockLight.update(lightLevel); + return blockLight; + } + + public static void removeLight(Location location) { + location = location.getBlock().getLocation(); + if (lightsByLocation.containsKey(location)) { + lightsByLocation.get(location).reset(); + lightsByLocation.remove(location); + } + } + + private void reset() { + this.update(originalLight); + } + + private void update(int lightLevel) { + if (this.currentLight == lightLevel) { + return; + } + else if (this.originalLight == lightLevel) { + worldServer.c(EnumSkyBlock.BLOCK, position); + } + else { + worldServer.a(EnumSkyBlock.BLOCK, position, lightLevel); + Block adjacentAir = null; + for (BlockFace face : adjacentFaces) { + if (position.getY() == 0 && face == BlockFace.DOWN) + continue; + if (position.getY() == (craftWorld.getMaxHeight() - 1) && face == BlockFace.UP) + continue; + Block possible = block.getRelative(face); + if (possible.getType() == Material.AIR) { + adjacentAir = possible; + break; + } + } + if (adjacentAir != null) { + worldServer.x(new BlockPosition(adjacentAir.getX(), adjacentAir.getY(), adjacentAir.getZ())); + } + } + int cX = craftChunk.getX(); + int cZ = craftChunk.getZ(); + for (int x = -1; x <= 1; x++) { + for (int z = -1; z <= 1; z++) { + BlockLight.setDirtyCount(getPlayerChunk(worldServer.getPlayerChunkMap(), cX + x, cZ + z)); + } + } + this.currentLight = lightLevel; + } + + private static Object getPlayerChunk(PlayerChunkMap map, int x, int z) { + try { + return playerChunkMethod.invoke(map, x, z, false); + } catch (Exception e) { + dB.echoError(e); + } + return null; + } + + private static void setDirtyCount(Object playerChunk) { + try { + int dirtyCount = dirtyCountField.getInt(playerChunk); + if (dirtyCount > 0 && dirtyCount < 64) { + dirtyCountField.set(playerChunk, 64); + } + } catch (Exception e) { + dB.echoError(e); + } + } + + private static IWorldAccess getIWorldAccess(World world) { + final PlayerChunkMap map = ((CraftWorld) world).getHandle().getPlayerChunkMap(); + return new IWorldAccess() { + @Override + public void a(BlockPosition position) { + map.flagDirty(position); + } + + @Override + public void b(BlockPosition position) { + map.flagDirty(position); + } + + @Override + public void b(int arg0, BlockPosition arg1, int arg2) { + } + + @Override + public void a(EntityHuman arg0, int arg1, BlockPosition arg2, int arg3) { + } + + @Override + public void a(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) { + } + + @Override + public void a(int arg0, BlockPosition arg1, int arg2) { + } + + @Override + public void a(String arg0, double arg1, double arg2, double arg3, float arg4, float arg5) { + } + + @Override + public void a(EntityHuman arg0, String arg1, double arg2, double arg3, double arg4, float arg5, float arg6) { + } + + @Override + public void a(int arg0, boolean arg1, double arg2, double arg3, double arg4, double arg5, double arg6, double arg7, int... arg8) { + } + + @Override + public void a(String arg0, BlockPosition arg1) { + } + + @Override + public void a(Entity arg0) { + } + + @Override + public void b(Entity arg0) { + } + }; + } +}