diff --git a/src/main/java/ninja/bytecode/iris/Iris.java b/src/main/java/ninja/bytecode/iris/Iris.java index ab17e7db6..d88af5fbf 100644 --- a/src/main/java/ninja/bytecode/iris/Iris.java +++ b/src/main/java/ninja/bytecode/iris/Iris.java @@ -1,25 +1,32 @@ package ninja.bytecode.iris; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.UUID; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.GameMode; import org.bukkit.Location; +import org.bukkit.Material; import org.bukkit.World; import org.bukkit.WorldCreator; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; import org.bukkit.generator.ChunkGenerator; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; import ninja.bytecode.iris.generator.IrisGenerator; import ninja.bytecode.iris.object.IrisBiome; import ninja.bytecode.iris.object.IrisDimension; +import ninja.bytecode.iris.object.IrisObject; import ninja.bytecode.iris.util.BiomeResult; import ninja.bytecode.iris.util.BoardManager; import ninja.bytecode.iris.util.BoardProvider; @@ -28,6 +35,7 @@ import ninja.bytecode.iris.util.CNG; import ninja.bytecode.iris.util.GroupedExecutor; import ninja.bytecode.iris.util.IO; import ninja.bytecode.iris.util.ScoreDirection; +import ninja.bytecode.iris.wand.WandController; import ninja.bytecode.shuriken.collections.KList; import ninja.bytecode.shuriken.execution.J; import ninja.bytecode.shuriken.format.Form; @@ -39,8 +47,9 @@ public class Iris extends JavaPlugin implements BoardProvider public static KList executors = new KList<>(); public static Iris instance; public static IrisDataManager data; - private static String last = ""; public static IrisHotloadManager hotloader; + public static WandController wand; + private static String last = ""; private BoardManager manager; private RollingSequence hits = new RollingSequence(20); @@ -54,6 +63,7 @@ public class Iris extends JavaPlugin implements BoardProvider instance = this; hotloader = new IrisHotloadManager(); data = new IrisDataManager(getDataFolder()); + wand = new WandController(); manager = new BoardManager(this, BoardSettings.builder().boardProvider(this).scoreDirection(ScoreDirection.UP).build()); } @@ -83,6 +93,9 @@ public class Iris extends JavaPlugin implements BoardProvider lines.add(ChatColor.GREEN + "Loss" + ChatColor.GRAY + ": " + ChatColor.BOLD + "" + ChatColor.GRAY + Form.duration(g.getMetrics().getLoss().getAverage(), 4) + ""); lines.add(ChatColor.GREEN + "Generators" + ChatColor.GRAY + ": " + Form.f(CNG.creates)); lines.add(ChatColor.GREEN + "Noise" + ChatColor.GRAY + ": " + Form.f((int) hits.getAverage())); + lines.add(ChatColor.GREEN + "Parallax Regions" + ChatColor.GRAY + ": " + Form.f((int) g.getParallaxMap().getLoadedRegions().size())); + lines.add(ChatColor.GREEN + "Parallax Chunks" + ChatColor.GRAY + ": " + Form.f((int) g.getParallaxMap().getLoadedChunks().size())); + lines.add(ChatColor.GREEN + "Sliver Buffer" + ChatColor.GRAY + ": " + Form.f((int) g.getSliverBuffer())); if(er != null && b != null) { @@ -110,6 +123,8 @@ public class Iris extends JavaPlugin implements BoardProvider executors.clear(); manager.onDisable(); + Bukkit.getScheduler().cancelTasks(this); + HandlerList.unregisterAll((Plugin) this); } @Override @@ -120,10 +135,79 @@ public class Iris extends JavaPlugin implements BoardProvider if(args.length == 0) { imsg(sender, "/iris dev - Create a new dev world"); + imsg(sender, "/iris wand - Get a wand"); } if(args.length >= 1) { + if(args[0].equalsIgnoreCase("wand")) + { + ((Player) sender).getInventory().addItem(WandController.createWand()); + } + + if(args[0].equalsIgnoreCase("save") && args.length >= 2) + { + ItemStack wand = ((Player) sender).getInventory().getItemInMainHand(); + IrisObject o = WandController.createSchematic(wand); + try + { + o.write(new File(getDataFolder(), "objects/" + args[1] + ".iob")); + imsg(sender, "Saved " + "objects/" + args[1] + ".iob"); + } + + catch(IOException e) + { + imsg(sender, "Failed to save " + "objects/" + args[1] + ".iob"); + + e.printStackTrace(); + } + } + + if(args[0].equalsIgnoreCase("load") && args.length >= 2) + { + File file = new File(getDataFolder(), "objects/" + args[1] + ".iob"); + boolean intoWand = false; + + for(String i : args) + { + if(i.equalsIgnoreCase("-edit")) + { + intoWand = true; + } + } + + if(!file.exists()) + { + imsg(sender, "Can't find " + "objects/" + args[1] + ".iob"); + } + + ItemStack wand = ((Player) sender).getInventory().getItemInMainHand(); + IrisObject o = new IrisObject(0, 0, 0); + + try + { + o.read(new File(getDataFolder(), "objects/" + args[1] + ".iob")); + imsg(sender, "Loaded " + "objects/" + args[1] + ".iob"); + Location block = ((Player) sender).getTargetBlock((Set) null, 256).getLocation().clone().add(0, 1, 0); + + if(intoWand && WandController.isWand(wand)) + { + wand = WandController.createWand(block.clone().subtract(o.getCenter()).add(o.getW() - 1, o.getH(), o.getD() - 1), block.clone().subtract(o.getCenter())); + ((Player) sender).getInventory().setItemInMainHand(wand); + imsg(sender, "Updated wand for " + "objects/" + args[1] + ".iob"); + } + + WandController.pasteSchematic(o, block); + imsg(sender, "Placed " + "objects/" + args[1] + ".iob"); + } + + catch(IOException e) + { + imsg(sender, "Failed to load " + "objects/" + args[1] + ".iob"); + e.printStackTrace(); + } + } + if(args[0].equalsIgnoreCase("dev")) { String dim = "Overworld"; diff --git a/src/main/java/ninja/bytecode/iris/IrisDataManager.java b/src/main/java/ninja/bytecode/iris/IrisDataManager.java index c49328cd0..8d5221436 100644 --- a/src/main/java/ninja/bytecode/iris/IrisDataManager.java +++ b/src/main/java/ninja/bytecode/iris/IrisDataManager.java @@ -10,6 +10,7 @@ import ninja.bytecode.iris.object.IrisBiome; import ninja.bytecode.iris.object.IrisDimension; import ninja.bytecode.iris.object.IrisRegion; import ninja.bytecode.iris.util.IO; +import ninja.bytecode.iris.util.ObjectResourceLoader; import ninja.bytecode.iris.util.ResourceLoader; @Data @@ -17,10 +18,10 @@ public class IrisDataManager { private File dataFolder; private File packs; - private ResourceLoader biomeLoader; private ResourceLoader regionLoader; private ResourceLoader dimensionLoader; + private ObjectResourceLoader objectLoader; public void hotloaded() { @@ -28,6 +29,7 @@ public class IrisDataManager this.regionLoader = new ResourceLoader<>(packs, "regions", "Region", IrisRegion.class); this.biomeLoader = new ResourceLoader<>(packs, "biomes", "Biome", IrisBiome.class); this.dimensionLoader = new ResourceLoader<>(packs, "dimensions", "Dimension", IrisDimension.class); + this.objectLoader = new ObjectResourceLoader(packs, "objects", "Object"); writeExamples(); } diff --git a/src/main/java/ninja/bytecode/iris/generator/ContextualChunkGenerator.java b/src/main/java/ninja/bytecode/iris/generator/ContextualChunkGenerator.java index ed27df212..bc3a4bd89 100644 --- a/src/main/java/ninja/bytecode/iris/generator/ContextualChunkGenerator.java +++ b/src/main/java/ninja/bytecode/iris/generator/ContextualChunkGenerator.java @@ -250,29 +250,33 @@ public abstract class ContextualChunkGenerator extends ChunkGenerator implements CNG.hits = 0; Iris.instance.hit(hits); metrics.getLoss().put(sx.getMilliseconds() - s.getMilliseconds()); - return c; } catch(Throwable e) { - failing = true; - Iris.error("ERROR! Failed to generate chunk! Iris has entered a failed state!"); - - for(Player i : world.getPlayers()) - { - Iris.instance.imsg(i, ChatColor.DARK_RED + "Iris Generator has entered a failed state!"); - Iris.instance.imsg(i, ChatColor.RED + "- Check the console for the error."); - Iris.instance.imsg(i, ChatColor.RED + "- Then simply run /iris dev"); - } - - L.ex(e); - onFailure(e); + fail(e); } return generateChunkDataFailure(world, no, x, z, biomeGrid); } + protected void fail(Throwable e) + { + failing = true; + Iris.error("ERROR! Failed to generate chunk! Iris has entered a failed state!"); + + for(Player i : world.getPlayers()) + { + Iris.instance.imsg(i, ChatColor.DARK_RED + "Iris Generator has entered a failed state!"); + Iris.instance.imsg(i, ChatColor.RED + "- Check the console for the error."); + Iris.instance.imsg(i, ChatColor.RED + "- Then simply run /iris dev"); + } + + L.ex(e); + onFailure(e); + } + @Override public List getDefaultPopulators(World world) { diff --git a/src/main/java/ninja/bytecode/iris/generator/IrisGenerator.java b/src/main/java/ninja/bytecode/iris/generator/IrisGenerator.java index c5e6d32b8..7532639cd 100644 --- a/src/main/java/ninja/bytecode/iris/generator/IrisGenerator.java +++ b/src/main/java/ninja/bytecode/iris/generator/IrisGenerator.java @@ -5,6 +5,7 @@ import org.bukkit.entity.Player; import lombok.Data; import lombok.EqualsAndHashCode; +import ninja.bytecode.iris.Iris; import ninja.bytecode.iris.IrisContext; import ninja.bytecode.iris.object.IrisRegion; import ninja.bytecode.iris.util.BiomeResult; @@ -39,13 +40,14 @@ public class IrisGenerator extends ParallaxChunkGenerator implements IrisContext @Override protected void onTick(int ticks) { - + super.onTick(ticks); } @Override protected void onClose() { - + super.onClose(); + Iris.info("Closing Iris Dimension " + getWorld().getName()); } @Override diff --git a/src/main/java/ninja/bytecode/iris/generator/ParallaxChunkGenerator.java b/src/main/java/ninja/bytecode/iris/generator/ParallaxChunkGenerator.java index 5702635fd..b17e955ed 100644 --- a/src/main/java/ninja/bytecode/iris/generator/ParallaxChunkGenerator.java +++ b/src/main/java/ninja/bytecode/iris/generator/ParallaxChunkGenerator.java @@ -1,22 +1,172 @@ package ninja.bytecode.iris.generator; +import java.io.IOException; + +import org.bukkit.World; +import org.bukkit.block.data.BlockData; + import lombok.Data; import lombok.EqualsAndHashCode; +import ninja.bytecode.iris.Iris; +import ninja.bytecode.iris.object.IrisBiome; +import ninja.bytecode.iris.object.IrisObjectPlacement; +import ninja.bytecode.iris.object.atomics.AtomicSliver; +import ninja.bytecode.iris.object.atomics.AtomicSliverMap; +import ninja.bytecode.iris.object.atomics.AtomicWorldData; +import ninja.bytecode.iris.util.BiomeMap; +import ninja.bytecode.iris.util.ChunkPosition; import ninja.bytecode.iris.util.HeightMap; +import ninja.bytecode.iris.util.IObjectPlacer; import ninja.bytecode.iris.util.RNG; +import ninja.bytecode.shuriken.collections.KMap; @Data @EqualsAndHashCode(callSuper = false) -public abstract class ParallaxChunkGenerator extends TerrainChunkGenerator +public abstract class ParallaxChunkGenerator extends TerrainChunkGenerator implements IObjectPlacer { + private KMap sliverCache; + protected AtomicWorldData parallaxMap; + private int sliverBuffer = 0; + public ParallaxChunkGenerator(String dimensionName, int threads) { super(dimensionName, threads); + sliverCache = new KMap<>(); } - - @Override - protected void onPostGenerate(RNG random, int x, int z, ChunkData data, BiomeGrid grid, HeightMap height) + + public void onInit(World world, RNG rng) { - + super.onInit(world, rng); + parallaxMap = new AtomicWorldData(world); + } + + protected void onClose() + { + super.onClose(); + + try + { + parallaxMap.unloadAll(true); + } + + catch(IOException e) + { + e.printStackTrace(); + } + } + + @Override + public int getHighest(int x, int z) + { + return sampleSliver(x, z).getHighestBlock(); + } + + @Override + public void set(int x, int y, int z, BlockData d) + { + getParallaxSliver(x, z).set(y, d); + } + + @Override + public BlockData get(int x, int y, int z) + { + BlockData b = sampleSliver(x, z).getBlock().get(y); + return b == null ? AIR : b; + } + + public AtomicSliver getParallaxSliver(int wx, int wz) + { + return getParallaxChunk(wx >> 4, wz >> 4).getSliver(wx & 15, wz & 15); + } + + public boolean hasParallaxChunk(int x, int z) + { + try + { + return getParallaxMap().hasChunk(x, z); + } + + catch(IOException e) + { + fail(e); + } + + return false; + } + + public AtomicSliverMap getParallaxChunk(int x, int z) + { + try + { + return getParallaxMap().loadChunk(x, z); + } + + catch(IOException e) + { + fail(e); + } + + return new AtomicSliverMap(); + } + + @Override + protected void onPostGenerate(RNG random, int x, int z, ChunkData data, BiomeGrid grid, HeightMap height, BiomeMap biomeMap) + { + onGenerateParallax(random, x, z); + getParallaxChunk(x, z).inject(data); + sliverBuffer = sliverCache.size(); + sliverCache.clear(); + } + + protected void onGenerateParallax(RNG random, int x, int z) + { + ChunkPosition pos = Iris.data.getObjectLoader().getParallaxSize(); + + for(int i = x - pos.getX() / 2; i <= x + pos.getX() / 2; i++) + { + for(int j = z - pos.getZ() / 2; j <= z + pos.getZ() / 2; j++) + { + IrisBiome b = sampleBiome((i * 16) + 7, (j * 16) + 7).getBiome(); + int g = 1; + + for(IrisObjectPlacement k : b.getObjects()) + { + placeObject(k, i, j, random.nextParallelRNG((i * 30) + (j * 30) + g++)); + } + } + } + } + + @Override + protected void onTick(int ticks) + { + if(ticks % 100 == 0) + { + parallaxMap.clean(); + } + } + + protected void placeObject(IrisObjectPlacement o, int x, int z, RNG rng) + { + for(int i = 0; i < o.getTriesForChunk(rng); i++) + { + o.getSchematic(rng).place((x * 16) * rng.nextInt(16), (z * 16) + rng.nextInt(16), this); + } + } + + public AtomicSliver sampleSliver(int x, int z) + { + ChunkPosition key = new ChunkPosition(x, z); + + if(sliverCache.containsKey(key)) + { + return sliverCache.get(key); + } + + AtomicSliver s = new AtomicSliver(x & 15, z & 15); + onGenerateColumn(x >> 4, z >> 4, x, z, x & 15, z & 15, s, null); + sliverCache.put(key, s); + + return s; } } diff --git a/src/main/java/ninja/bytecode/iris/generator/ParallelChunkGenerator.java b/src/main/java/ninja/bytecode/iris/generator/ParallelChunkGenerator.java index 0e265986f..d0cca8b24 100644 --- a/src/main/java/ninja/bytecode/iris/generator/ParallelChunkGenerator.java +++ b/src/main/java/ninja/bytecode/iris/generator/ParallelChunkGenerator.java @@ -7,6 +7,7 @@ import lombok.EqualsAndHashCode; import ninja.bytecode.iris.Iris; import ninja.bytecode.iris.object.atomics.AtomicSliver; import ninja.bytecode.iris.object.atomics.AtomicSliverMap; +import ninja.bytecode.iris.util.BiomeMap; import ninja.bytecode.iris.util.GroupedExecutor; import ninja.bytecode.iris.util.HeightMap; import ninja.bytecode.iris.util.RNG; @@ -37,11 +38,11 @@ public abstract class ParallelChunkGenerator extends BiomeChunkGenerator } } - protected abstract void onGenerateColumn(int cx, int cz, int wx, int wz, int x, int z, AtomicSliver sliver); + protected abstract void onGenerateColumn(int cx, int cz, int wx, int wz, int x, int z, AtomicSliver sliver, BiomeMap biomeMap); protected abstract int onSampleColumnHeight(int cx, int cz, int wx, int wz, int x, int z); - protected abstract void onPostGenerate(RNG random, int x, int z, ChunkData data, BiomeGrid grid, HeightMap height); + protected abstract void onPostGenerate(RNG random, int x, int z, ChunkData data, BiomeGrid grid, HeightMap height, BiomeMap biomeMap); protected int sampleHeight(int x, int z) { @@ -53,6 +54,7 @@ public abstract class ParallelChunkGenerator extends BiomeChunkGenerator AtomicSliverMap map = new AtomicSliverMap(); HeightMap height = new HeightMap(); String key = "c" + x + "," + z; + BiomeMap biomeMap = new BiomeMap(); int ii, jj; for(ii = 0; ii < 16; ii++) @@ -68,14 +70,19 @@ public abstract class ParallelChunkGenerator extends BiomeChunkGenerator tx.queue(key, () -> { - onGenerateColumn(x, z, wx, wz, i, j, sliver); + onGenerateColumn(x, z, wx, wz, i, j, sliver, biomeMap); }); } } tx.waitFor(key); map.write(data, grid, height); - onPostGenerate(random, x, z, data, grid, height); + onPostGenerate(random, x, z, data, grid, height, biomeMap); + } + + protected void onClose() + { + tx.close(); } public void onInit(World world, RNG rng) @@ -87,6 +94,6 @@ public abstract class ParallelChunkGenerator extends BiomeChunkGenerator @Override public boolean isParallelCapable() { - return true; + return false; } } diff --git a/src/main/java/ninja/bytecode/iris/generator/TerrainChunkGenerator.java b/src/main/java/ninja/bytecode/iris/generator/TerrainChunkGenerator.java index 8c6ccbcc5..91dab992f 100644 --- a/src/main/java/ninja/bytecode/iris/generator/TerrainChunkGenerator.java +++ b/src/main/java/ninja/bytecode/iris/generator/TerrainChunkGenerator.java @@ -9,6 +9,7 @@ import lombok.EqualsAndHashCode; import ninja.bytecode.iris.object.IrisBiome; import ninja.bytecode.iris.object.IrisRegion; import ninja.bytecode.iris.object.atomics.AtomicSliver; +import ninja.bytecode.iris.util.BiomeMap; import ninja.bytecode.iris.util.BiomeResult; import ninja.bytecode.iris.util.CNG; import ninja.bytecode.iris.util.RNG; @@ -18,6 +19,7 @@ import ninja.bytecode.shuriken.collections.KList; @EqualsAndHashCode(callSuper = false) public abstract class TerrainChunkGenerator extends ParallelChunkGenerator { + protected static final BlockData AIR = Material.AIR.createBlockData(); protected static final BlockData STONE = Material.STONE.createBlockData(); protected static final BlockData WATER = Material.WATER.createBlockData(); protected CNG terrainNoise; @@ -34,7 +36,7 @@ public abstract class TerrainChunkGenerator extends ParallelChunkGenerator } @Override - protected void onGenerateColumn(int cx, int cz, int rx, int rz, int x, int z, AtomicSliver sliver) + protected void onGenerateColumn(int cx, int cz, int rx, int rz, int x, int z, AtomicSliver sliver, BiomeMap biomeMap) { BlockData block; int fluidHeight = getDimension().getFluidHeight(); @@ -64,7 +66,12 @@ public abstract class TerrainChunkGenerator extends ParallelChunkGenerator for(int k = Math.max(height, fluidHeight); k >= 0; k--) { boolean underwater = k > height && k <= fluidHeight; - sliver.set(k, biome.getDerivative()); + + if(biomeMap != null) + { + sliver.set(k, biome.getDerivative()); + biomeMap.setBiome(x, z, biome); + } if(underwater) { @@ -76,7 +83,6 @@ public abstract class TerrainChunkGenerator extends ParallelChunkGenerator block = layers.hasIndex(depth) ? layers.get(depth) : STONE; depth++; } - sliver.set(k, block); } } diff --git a/src/main/java/ninja/bytecode/iris/object/Blueprint.java b/src/main/java/ninja/bytecode/iris/object/Blueprint.java new file mode 100644 index 000000000..00ee59e13 --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/object/Blueprint.java @@ -0,0 +1,6 @@ +package ninja.bytecode.iris.object; + +public class Blueprint +{ + +} diff --git a/src/main/java/ninja/bytecode/iris/object/IrisBiome.java b/src/main/java/ninja/bytecode/iris/object/IrisBiome.java index cb901ad2c..c674a133a 100644 --- a/src/main/java/ninja/bytecode/iris/object/IrisBiome.java +++ b/src/main/java/ninja/bytecode/iris/object/IrisBiome.java @@ -25,7 +25,7 @@ public class IrisBiome extends IrisRegisteredObject private KList children = new KList<>(); private KList layers = new KList().qadd(new IrisBiomePaletteLayer()); private KList decorators = new KList(); - + private KList objects = new KList(); private transient ReentrantLock lock = new ReentrantLock(); private transient CellGenerator childrenCell; private transient InferredType inferredType; diff --git a/src/main/java/ninja/bytecode/iris/object/IrisObject.java b/src/main/java/ninja/bytecode/iris/object/IrisObject.java index ab1262ca0..064abed65 100644 --- a/src/main/java/ninja/bytecode/iris/object/IrisObject.java +++ b/src/main/java/ninja/bytecode/iris/object/IrisObject.java @@ -1,31 +1,97 @@ package ninja.bytecode.iris.object; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.bukkit.Location; import org.bukkit.block.data.BlockData; import org.bukkit.util.BlockVector; +import lombok.Data; +import lombok.EqualsAndHashCode; +import ninja.bytecode.iris.util.BlockDataTools; +import ninja.bytecode.iris.util.IObjectPlacer; import ninja.bytecode.shuriken.collections.KMap; -import ninja.bytecode.shuriken.collections.KSet; -public class IrisObject +@Data +@EqualsAndHashCode(callSuper = false) +public class IrisObject extends IrisRegisteredObject { - private String name; private KMap blocks; - private KSet mount; private int w; private int d; private int h; private transient BlockVector center; - public IrisObject(String name, int w, int h, int d) + public IrisObject(int w, int h, int d) { blocks = new KMap<>(); - mount = new KSet<>(); this.w = w; this.h = h; this.d = d; - this.name = name; center = new BlockVector(w / 2, h / 2, d / 2); } + + public static BlockVector sampleSize(File file) throws IOException + { + FileInputStream in = new FileInputStream(file); + DataInputStream din = new DataInputStream(in); + BlockVector bv = new BlockVector(din.readInt(), din.readInt(), din.readInt()); + din.close(); + return bv; + } + + public void read(InputStream in) throws IOException + { + DataInputStream din = new DataInputStream(in); + this.w = din.readInt(); + this.h = din.readInt(); + this.d = din.readInt(); + center = new BlockVector(w / 2, h / 2, d / 2); + int s = din.readInt(); + + for(int i = 0; i < s; i++) + { + blocks.put(new BlockVector(din.readShort(), din.readShort(), din.readShort()), BlockDataTools.getBlockData(din.readUTF())); + } + } + + public void read(File file) throws IOException + { + FileInputStream fin = new FileInputStream(file); + read(fin); + fin.close(); + } + + public void write(File file) throws IOException + { + file.getParentFile().mkdirs(); + FileOutputStream out = new FileOutputStream(file); + write(out); + out.close(); + } + + public void write(OutputStream o) throws IOException + { + DataOutputStream dos = new DataOutputStream(o); + dos.writeInt(w); + dos.writeInt(h); + dos.writeInt(d); + dos.writeInt(blocks.size()); + for(BlockVector i : blocks.k()) + { + dos.writeShort(i.getBlockX()); + dos.writeShort(i.getBlockY()); + dos.writeShort(i.getBlockZ()); + dos.writeUTF(blocks.get(i).getAsString(true)); + } + } public void setUnsigned(int x, int y, int z, BlockData block) { @@ -33,17 +99,35 @@ public class IrisObject { throw new RuntimeException(x + " " + y + " " + z + " exceeds limit of " + w + " " + h + " " + d); } - + BlockVector v = new BlockVector(x, y, z).subtract(center).toBlockVector(); - + if(block == null) { blocks.remove(v); } - + else { blocks.put(v, block); } } + + public void place(int x, int z, IObjectPlacer placer) + { + int y = placer.getHighest(x, z) + getCenter().getBlockY(); + + for(BlockVector i : blocks.k()) + { + placer.set(x + i.getBlockX(), y + i.getBlockY(), z + i.getBlockZ(), blocks.get(i)); + } + } + + public void place(Location at) + { + for(BlockVector i : blocks.k()) + { + at.clone().add(0, getCenter().getY(), 0).add(i).getBlock().setBlockData(blocks.get(i), false); + } + } } diff --git a/src/main/java/ninja/bytecode/iris/object/IrisObjectPlacement.java b/src/main/java/ninja/bytecode/iris/object/IrisObjectPlacement.java new file mode 100644 index 000000000..c8a71bc24 --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/object/IrisObjectPlacement.java @@ -0,0 +1,42 @@ +package ninja.bytecode.iris.object; + +import ninja.bytecode.iris.Iris; +import ninja.bytecode.iris.util.RNG; +import ninja.bytecode.shuriken.collections.KList; + +public class IrisObjectPlacement +{ + private KList place = new KList<>(); + private double chance = 1; + private int density = 1; + + public IrisObjectPlacement() + { + + } + + public IrisObject getSchematic(RNG random) + { + if(place.isEmpty()) + { + return null; + } + + return Iris.data.getObjectLoader().load(place.get(random.nextInt(place.size()))); + } + + public int getTriesForChunk(RNG random) + { + if(chance <= 0) + { + return 0; + } + + if(chance >= 1 || random.nextDouble() < chance) + { + return density; + } + + return 0; + } +} diff --git a/src/main/java/ninja/bytecode/iris/object/atomics/AtomicRegionData.java b/src/main/java/ninja/bytecode/iris/object/atomics/AtomicRegionData.java index 6db485f91..22bf5e558 100644 --- a/src/main/java/ninja/bytecode/iris/object/atomics/AtomicRegionData.java +++ b/src/main/java/ninja/bytecode/iris/object/atomics/AtomicRegionData.java @@ -53,6 +53,11 @@ public class AtomicRegionData public void set(int rx, int rz, AtomicSliverMap data) throws IOException { + if(data == null) + { + return; + } + ByteArrayOutputStream boas = new ByteArrayOutputStream(); data.write(boas); tag.put(rx + "." + rz, new ByteArrayTag(rx + "." + rz, boas.toByteArray())); @@ -60,12 +65,13 @@ public class AtomicRegionData public AtomicSliverMap get(int rx, int rz) throws IOException { + AtomicSliverMap data = new AtomicSliverMap(); + if(!contains(rx, rz)) { - return null; + return data; } - AtomicSliverMap data = new AtomicSliverMap(); ByteArrayTag btag = (ByteArrayTag) tag.get(rx + "." + rz); ByteArrayInputStream in = new ByteArrayInputStream(btag.getValue()); data.read(in); diff --git a/src/main/java/ninja/bytecode/iris/object/atomics/AtomicSliver.java b/src/main/java/ninja/bytecode/iris/object/atomics/AtomicSliver.java index 979f4ab0c..6917f3187 100644 --- a/src/main/java/ninja/bytecode/iris/object/atomics/AtomicSliver.java +++ b/src/main/java/ninja/bytecode/iris/object/atomics/AtomicSliver.java @@ -12,13 +12,14 @@ import org.bukkit.generator.ChunkGenerator.ChunkData; import lombok.Data; import ninja.bytecode.iris.util.BlockDataTools; import ninja.bytecode.iris.util.HeightMap; +import ninja.bytecode.shuriken.collections.KMap; @Data public class AtomicSliver { private static final BlockData AIR = BlockDataTools.getBlockData("AIR"); - private BlockData[] block; - private Biome[] biome; + private KMap block; + private KMap biome; private int highestBlock = 0; private int highestBiome = 0; private int x; @@ -28,19 +29,19 @@ public class AtomicSliver { this.x = x; this.z = z; - this.block = new BlockData[256]; - this.biome = new Biome[256]; + this.block = new KMap<>(); + this.biome = new KMap<>(); } public void set(int h, BlockData d) { - block[h] = d; + block.put(h, d); highestBlock = h > highestBlock ? h : highestBlock; } public void set(int h, Biome d) { - biome[h] = d; + biome.put(h, d); highestBiome = h > highestBiome ? h : highestBiome; } @@ -48,14 +49,14 @@ public class AtomicSliver { for(int i = 0; i <= highestBlock; i++) { - if(block[i] == null) + if(block.get(i) == null) { d.setBlock(x, i, z, AIR); } else { - d.setBlock(x, i, z, block[i]); + d.setBlock(x, i, z, block.get(i)); } } } @@ -64,7 +65,10 @@ public class AtomicSliver { for(int i = 0; i <= highestBiome; i++) { - d.setBiome(x, i, z, biome[i]); + if(biome.get(i) != null) + { + d.setBiome(x, i, z, biome.get(i)); + } } } @@ -75,11 +79,11 @@ public class AtomicSliver public void read(DataInputStream din) throws IOException { - this.block = new BlockData[256]; + this.block = new KMap(); int h = din.readByte() - Byte.MIN_VALUE; for(int i = 0; i <= h; i++) { - block[i] = BlockDataTools.getBlockData(din.readUTF()); + block.put(i, BlockDataTools.getBlockData(din.readUTF())); } } @@ -89,7 +93,8 @@ public class AtomicSliver for(int i = 0; i <= highestBlock; i++) { - dos.writeUTF(block[i].getAsString(true)); + BlockData dat = block.get(i); + dos.writeUTF((dat == null ? AIR : dat).getAsString(true)); } } @@ -97,15 +102,27 @@ public class AtomicSliver { for(int i = 0; i < 256; i++) { - if(block[i] == null || block[i].equals(AIR)) + if(block.get(i) == null || block.get(i).equals(AIR)) { - BlockData b = atomicSliver.block[i]; + BlockData b = atomicSliver.block.get(i); if(b == null || b.equals(AIR)) { continue; } - block[i] = b; + block.put(i, b); + } + } + } + + public void inject(ChunkData currentData) + { + for(int i = 0; i < getHighestBlock(); i++) + { + if(block.get(i) != null && !block.get(i).equals(AIR)) + { + BlockData b = block.get(i); + currentData.setBlock(x, i, z, b); } } } diff --git a/src/main/java/ninja/bytecode/iris/object/atomics/AtomicSliverMap.java b/src/main/java/ninja/bytecode/iris/object/atomics/AtomicSliverMap.java index eccfee7d2..df6d96fe9 100644 --- a/src/main/java/ninja/bytecode/iris/object/atomics/AtomicSliverMap.java +++ b/src/main/java/ninja/bytecode/iris/object/atomics/AtomicSliverMap.java @@ -73,4 +73,12 @@ public class AtomicSliverMap } } } + + public void inject(ChunkData currentData) + { + for(AtomicSliver i : slivers) + { + i.inject(currentData); + } + } } diff --git a/src/main/java/ninja/bytecode/iris/object/atomics/AtomicWorldData.java b/src/main/java/ninja/bytecode/iris/object/atomics/AtomicWorldData.java index cc3dfceee..615d93c0e 100644 --- a/src/main/java/ninja/bytecode/iris/object/atomics/AtomicWorldData.java +++ b/src/main/java/ninja/bytecode/iris/object/atomics/AtomicWorldData.java @@ -7,29 +7,36 @@ import java.io.IOException; import org.bukkit.World; +import ninja.bytecode.iris.Iris; import ninja.bytecode.iris.util.ChunkPosition; -import ninja.bytecode.shuriken.collections.KList; import ninja.bytecode.shuriken.collections.KMap; +import ninja.bytecode.shuriken.math.M; public class AtomicWorldData { private World world; + private KMap loadedChunks; private KMap loadedSections; + private KMap lastRegion; public AtomicWorldData(World world) { this.world = world; loadedSections = new KMap<>(); + loadedChunks = new KMap<>(); + lastRegion = new KMap<>(); getSubregionFolder().mkdirs(); } - public KList getLoadedRegions() + public KMap getLoadedRegions() { - return loadedSections.k(); + return loadedSections; } public AtomicRegionData getSubregion(int x, int z) throws IOException { + lastRegion.put(new ChunkPosition(x, z), M.ms()); + if(!isSectionLoaded(x, z)) { loadedSections.put(new ChunkPosition(x, z), loadSection(x, z)); @@ -42,6 +49,8 @@ public class AtomicWorldData public void saveAll() throws IOException { + saveChunks(); + for(ChunkPosition i : loadedSections.keySet()) { saveSection(i); @@ -50,10 +59,16 @@ public class AtomicWorldData public void unloadAll(boolean save) throws IOException { + saveChunks(); + for(ChunkPosition i : loadedSections.keySet()) { unloadSection(i, save); } + + loadedSections.clear(); + loadedChunks.clear(); + lastRegion.clear(); } public void deleteSection(int x, int z) throws IOException @@ -105,6 +120,7 @@ public class AtomicWorldData return false; } + saveChunks(s); AtomicRegionData data = loadedSections.get(s); FileOutputStream fos = new FileOutputStream(getSubregionFile(s.getX(), s.getZ())); data.write(fos); @@ -112,29 +128,84 @@ public class AtomicWorldData return true; } + public void saveChunks() throws IOException + { + for(ChunkPosition i : loadedChunks.k()) + { + saveChunk(i); + } + } + + public void saveChunks(ChunkPosition reg) throws IOException + { + for(ChunkPosition i : loadedChunks.k()) + { + int x = i.getX(); + int z = i.getZ(); + + if(x >> 5 == reg.getX() && z >> 5 == reg.getZ()) + { + saveChunk(i); + } + } + } + + public void saveChunk(ChunkPosition i) throws IOException + { + int x = i.getX(); + int z = i.getZ(); + AtomicRegionData dat = loadSection(x >> 5, z >> 5); + dat.set(x & 31, z & 31, loadedChunks.get(i)); + loadedChunks.remove(i); + } + public AtomicSliverMap loadChunk(int x, int z) throws IOException { - return loadSection(x >> 5, z >> 5).get(x & 31, z & 31); + ChunkPosition pos = new ChunkPosition(x, z); + + if(loadedChunks.containsKey(pos)) + { + return loadedChunks.get(pos); + } + + AtomicRegionData dat = loadSection(x >> 5, z >> 5); + AtomicSliverMap m = dat.get(x & 31, z & 31); + loadedChunks.put(pos, m); + + Iris.info("Loaded chunk: sections: " + loadedSections.size()); + + return m; + } + + public boolean hasChunk(int x, int z) throws IOException + { + return loadSection(x >> 5, z >> 5).contains(x & 31, z & 31); } public AtomicRegionData loadSection(int x, int z) throws IOException { + ChunkPosition pos = new ChunkPosition(x, z); + lastRegion.put(pos, M.ms()); + if(isSectionLoaded(x, z)) { - return loadedSections.get(new ChunkPosition(x, z)); + return loadedSections.get(pos); } File file = getSubregionFile(x, z); if(!file.exists()) { - return createSection(x, z); + AtomicRegionData dat = createSection(x, z); + loadedSections.put(pos, dat); + return dat; } FileInputStream fin = new FileInputStream(file); AtomicRegionData data = new AtomicRegionData(world); data.read(fin); fin.close(); + loadedSections.put(pos, data); return data; } @@ -160,4 +231,30 @@ public class AtomicWorldData { return new File(world.getWorldFolder(), "subregion"); } + + public KMap getLoadedChunks() + { + return loadedChunks; + } + + public void clean() + { + for(ChunkPosition i : lastRegion.k()) + { + if(M.ms() - lastRegion.get(i) > 3000) + { + lastRegion.remove(i); + + try + { + unloadSection(i, true); + } + + catch(IOException e) + { + e.printStackTrace(); + } + } + } + } } diff --git a/src/main/java/ninja/bytecode/iris/util/Axis.java b/src/main/java/ninja/bytecode/iris/util/Axis.java new file mode 100644 index 000000000..1e7e7291f --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/util/Axis.java @@ -0,0 +1,31 @@ +package ninja.bytecode.iris.util; + +import org.bukkit.util.Vector; + +public enum Axis +{ + X(1, 0, 0), + Y(0, 1, 0), + Z(0, 0, 1); + + private int x; + private int y; + private int z; + + private Axis(int x, int y, int z) + { + this.x = x; + this.y = y; + this.z = z; + } + + public Vector positive() + { + return new Vector(x, y, z); + } + + public Vector negative() + { + return VectorMath.reverse(positive()); + } +} diff --git a/src/main/java/ninja/bytecode/iris/util/BiomeMap.java b/src/main/java/ninja/bytecode/iris/util/BiomeMap.java new file mode 100644 index 000000000..bd915a268 --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/util/BiomeMap.java @@ -0,0 +1,23 @@ +package ninja.bytecode.iris.util; + +import ninja.bytecode.iris.object.IrisBiome; + +public class BiomeMap +{ + private final IrisBiome[] height; + + public BiomeMap() + { + height = new IrisBiome[256]; + } + + public void setBiome(int x, int z, IrisBiome h) + { + height[x * 16 + z] = h; + } + + public IrisBiome getBiome(int x, int z) + { + return height[x * 16 + z]; + } +} diff --git a/src/main/java/ninja/bytecode/iris/util/CDou.java b/src/main/java/ninja/bytecode/iris/util/CDou.java new file mode 100644 index 000000000..c4c099d0f --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/util/CDou.java @@ -0,0 +1,49 @@ +package ninja.bytecode.iris.util; + +public class CDou +{ + private double number; + private double max; + + public CDou(double max) + { + number = 0; + this.max = max; + } + + public CDou set(double n) + { + number = n; + circ(); + return this; + } + + public CDou add(double a) + { + number += a; + circ(); + return this; + } + + public CDou sub(double a) + { + number -= a; + circ(); + return this; + } + + public double get() + { + return number; + } + + public void circ() + { + if(number < 0) + { + number = max - (Math.abs(number) > max ? max : Math.abs(number)); + } + + number = number % (max); + } +} diff --git a/src/main/java/ninja/bytecode/iris/util/Cuboid.java b/src/main/java/ninja/bytecode/iris/util/Cuboid.java new file mode 100644 index 000000000..c0e99ad54 --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/util/Cuboid.java @@ -0,0 +1,884 @@ +package ninja.bytecode.iris.util; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.entity.Entity; + +import ninja.bytecode.shuriken.collections.KList; + +/** + * Cuboids + * + * @author cyberpwn + */ +public class Cuboid implements Iterable, Cloneable, ConfigurationSerializable +{ + protected final String worldName; + protected int x1, y1, z1; + protected int x2, y2, z2; + + /** + * Construct a Cuboid given two Location objects which represent any two corners + * of the Cuboid. + * + * @param l1 + * one of the corners + * @param l2 + * the other corner + */ + public Cuboid(Location l1, Location l2) + { + if(!l1.getWorld().equals(l2.getWorld())) + { + throw new IllegalArgumentException("locations must be on the same world"); + } + + worldName = l1.getWorld().getName(); + x1 = Math.min(l1.getBlockX(), l2.getBlockX()); + y1 = Math.min(l1.getBlockY(), l2.getBlockY()); + z1 = Math.min(l1.getBlockZ(), l2.getBlockZ()); + x2 = Math.max(l1.getBlockX(), l2.getBlockX()); + y2 = Math.max(l1.getBlockY(), l2.getBlockY()); + z2 = Math.max(l1.getBlockZ(), l2.getBlockZ()); + } + + public KList getEntities() + { + KList en = new KList(); + + for(Chunk i : getChunks()) + { + for(Entity j : i.getEntities()) + { + if(contains(j.getLocation())) + { + en.add(j); + } + } + } + + return en; + } + + /** + * Set the locations + * + * @param l1 + * a + * @param l2 + * b + */ + public void set(Location l1, Location l2) + { + x1 = Math.min(l1.getBlockX(), l2.getBlockX()); + y1 = Math.min(l1.getBlockY(), l2.getBlockY()); + z1 = Math.min(l1.getBlockZ(), l2.getBlockZ()); + x2 = Math.max(l1.getBlockX(), l2.getBlockX()); + y2 = Math.max(l1.getBlockY(), l2.getBlockY()); + z2 = Math.max(l1.getBlockZ(), l2.getBlockZ()); + } + + /** + * Construct a one-block Cuboid at the given Location of the Cuboid. + * + * @param l1 + * location of the Cuboid + */ + public Cuboid(Location l1) + { + this(l1, l1); + } + + /** + * Copy constructor. + * + * @param other + * the Cuboid to copy + */ + public Cuboid(Cuboid other) + { + this(other.getWorld().getName(), other.x1, other.y1, other.z1, other.x2, other.y2, other.z2); + } + + /** + * Construct a Cuboid in the given World and xyz co-ordinates + * + * @param world + * the Cuboid's world + * @param x1 + * X co-ordinate of corner 1 + * @param y1 + * Y co-ordinate of corner 1 + * @param z1 + * Z co-ordinate of corner 1 + * @param x2 + * X co-ordinate of corner 2 + * @param y2 + * Y co-ordinate of corner 2 + * @param z2 + * Z co-ordinate of corner 2 + */ + public Cuboid(World world, int x1, int y1, int z1, int x2, int y2, int z2) + { + this.worldName = world.getName(); + this.x1 = Math.min(x1, x2); + this.x2 = Math.max(x1, x2); + this.y1 = Math.min(y1, y2); + this.y2 = Math.max(y1, y2); + this.z1 = Math.min(z1, z2); + this.z2 = Math.max(z1, z2); + } + + /** + * Construct a Cuboid in the given world name and xyz co-ordinates. + * + * @param worldName + * the Cuboid's world name + * @param x1 + * X co-ordinate of corner 1 + * @param y1 + * Y co-ordinate of corner 1 + * @param z1 + * Z co-ordinate of corner 1 + * @param x2 + * X co-ordinate of corner 2 + * @param y2 + * Y co-ordinate of corner 2 + * @param z2 + * Z co-ordinate of corner 2 + */ + private Cuboid(String worldName, int x1, int y1, int z1, int x2, int y2, int z2) + { + this.worldName = worldName; + this.x1 = Math.min(x1, x2); + this.x2 = Math.max(x1, x2); + this.y1 = Math.min(y1, y2); + this.y2 = Math.max(y1, y2); + this.z1 = Math.min(z1, z2); + this.z2 = Math.max(z1, z2); + } + + public Cuboid(Map map) + { + worldName = (String) map.get("worldName"); + x1 = (Integer) map.get("x1"); + x2 = (Integer) map.get("x2"); + y1 = (Integer) map.get("y1"); + y2 = (Integer) map.get("y2"); + z1 = (Integer) map.get("z1"); + z2 = (Integer) map.get("z2"); + } + + @Override + public Map serialize() + { + Map map = new HashMap(); + map.put("worldName", worldName); + map.put("x1", x1); + map.put("y1", y1); + map.put("z1", z1); + map.put("x2", x2); + map.put("y2", y2); + map.put("z2", z2); + return map; + } + + public Cuboid flatten(int level) + { + return new Cuboid(getWorld(), x1, level, z1, x2, level, z2); + } + + /** + * Get the Location of the lower northeast corner of the Cuboid (minimum XYZ + * co-ordinates). + * + * @return Location of the lower northeast corner + */ + public Location getLowerNE() + { + return new Location(getWorld(), x1, y1, z1); + } + + /** + * Get the Location of the upper southwest corner of the Cuboid (maximum XYZ + * co-ordinates). + * + * @return Location of the upper southwest corner + */ + public Location getUpperSW() + { + return new Location(getWorld(), x2, y2, z2); + } + + /** + * Get the the centre of the Cuboid + * + * @return Location at the centre of the Cuboid + */ + public Location getCenter() + { + int x1 = getUpperX() + 1; + int y1 = getUpperY() + 1; + int z1 = getUpperZ() + 1; + return new Location(getWorld(), getLowerX() + (x1 - getLowerX()) / 2.0, getLowerY() + (y1 - getLowerY()) / 2.0, getLowerZ() + (z1 - getLowerZ()) / 2.0); + } + + /** + * Get the Cuboid's world. + * + * @return the World object representing this Cuboid's world + * @throws IllegalStateException + * if the world is not loaded + */ + public World getWorld() + { + World world = Bukkit.getWorld(worldName); + if(world == null) + { + throw new IllegalStateException("world '" + worldName + "' is not loaded"); + } + return world; + } + + /** + * Get the size of this Cuboid along the X axis + * + * @return Size of Cuboid along the X axis + */ + public int getSizeX() + { + return (x2 - x1) + 1; + } + + /** + * Get the size of this Cuboid along the Y axis + * + * @return Size of Cuboid along the Y axis + */ + public int getSizeY() + { + return (y2 - y1) + 1; + } + + /** + * Get the size of this Cuboid along the Z axis + * + * @return Size of Cuboid along the Z axis + */ + public int getSizeZ() + { + return (z2 - z1) + 1; + } + + /** + * Get the cuboid dimensions + * + * @return the dimensions + */ + public Dimension getDimension() + { + return new Dimension(getSizeX(), getSizeY(), getSizeZ()); + } + + /** + * Get the minimum X co-ordinate of this Cuboid + * + * @return the minimum X co-ordinate + */ + public int getLowerX() + { + return x1; + } + + /** + * Get the minimum Y co-ordinate of this Cuboid + * + * @return the minimum Y co-ordinate + */ + public int getLowerY() + { + return y1; + } + + /** + * Get the minimum Z co-ordinate of this Cuboid + * + * @return the minimum Z co-ordinate + */ + public int getLowerZ() + { + return z1; + } + + /** + * Get the maximum X co-ordinate of this Cuboid + * + * @return the maximum X co-ordinate + */ + public int getUpperX() + { + return x2; + } + + /** + * Get the maximum Y co-ordinate of this Cuboid + * + * @return the maximum Y co-ordinate + */ + public int getUpperY() + { + return y2; + } + + /** + * Get the maximum Z co-ordinate of this Cuboid + * + * @return the maximum Z co-ordinate + */ + public int getUpperZ() + { + return z2; + } + + /** + * Get the Blocks at the eight corners of the Cuboid. + * + * @return array of Block objects representing the Cuboid corners + */ + public Block[] corners() + { + Block[] res = new Block[8]; + World w = getWorld(); + res[0] = w.getBlockAt(x1, y1, z1); + res[1] = w.getBlockAt(x1, y1, z2); + res[2] = w.getBlockAt(x1, y2, z1); + res[3] = w.getBlockAt(x1, y2, z2); + res[4] = w.getBlockAt(x2, y1, z1); + res[5] = w.getBlockAt(x2, y1, z2); + res[6] = w.getBlockAt(x2, y2, z1); + res[7] = w.getBlockAt(x2, y2, z2); + return res; + } + + /** + * Expand the Cuboid in the given direction by the given amount. Negative + * amounts will shrink the Cuboid in the given direction. Shrinking a cuboid's + * face past the opposite face is not an error and will return a valid Cuboid. + * + * @param dir + * the direction in which to expand + * @param amount + * the number of blocks by which to expand + * @return a new Cuboid expanded by the given direction and amount + */ + public Cuboid expand(CuboidDirection dir, int amount) + { + switch(dir) + { + case North: + return new Cuboid(worldName, x1 - amount, y1, z1, x2, y2, z2); + case South: + return new Cuboid(worldName, x1, y1, z1, x2 + amount, y2, z2); + case East: + return new Cuboid(worldName, x1, y1, z1 - amount, x2, y2, z2); + case West: + return new Cuboid(worldName, x1, y1, z1, x2, y2, z2 + amount); + case Down: + return new Cuboid(worldName, x1, y1 - amount, z1, x2, y2, z2); + case Up: + return new Cuboid(worldName, x1, y1, z1, x2, y2 + amount, z2); + default: + throw new IllegalArgumentException("invalid direction " + dir); + } + } + + public Cuboid expand(Direction dir, int amount) + { + int ax = dir.toVector().getBlockX() == 1 ? amount : 0; + int sx = dir.toVector().getBlockX() == -1 ? -amount : 0; + int ay = dir.toVector().getBlockY() == 1 ? amount : 0; + int sy = dir.toVector().getBlockY() == -1 ? -amount : 0; + int az = dir.toVector().getBlockZ() == 1 ? amount : 0; + int sz = dir.toVector().getBlockZ() == -1 ? -amount : 0; + return new Cuboid(worldName, x1 + sx, y1 + sy, z1 + sz, x2 + ax, y2 + ay, z2 + az); + } + + /** + * Shift the Cuboid in the given direction by the given amount. + * + * @param dir + * the direction in which to shift + * @param amount + * the number of blocks by which to shift + * @return a new Cuboid shifted by the given direction and amount + */ + public Cuboid shift(CuboidDirection dir, int amount) + { + return expand(dir, amount).expand(dir.opposite(), -amount); + } + + /** + * Outset (grow) the Cuboid in the given direction by the given amount. + * + * @param dir + * the direction in which to outset (must be Horizontal, Vertical, or + * Both) + * @param amount + * the number of blocks by which to outset + * @return a new Cuboid outset by the given direction and amount + */ + public Cuboid outset(CuboidDirection dir, int amount) + { + Cuboid c; + switch(dir) + { + case Horizontal: + c = expand(CuboidDirection.North, amount).expand(CuboidDirection.South, amount).expand(CuboidDirection.East, amount).expand(CuboidDirection.West, amount); + break; + case Vertical: + c = expand(CuboidDirection.Down, amount).expand(CuboidDirection.Up, amount); + break; + case Both: + c = outset(CuboidDirection.Horizontal, amount).outset(CuboidDirection.Vertical, amount); + break; + default: + throw new IllegalArgumentException("invalid direction " + dir); + } + return c; + } + + /** + * Inset (shrink) the Cuboid in the given direction by the given amount. + * Equivalent to calling outset() with a negative amount. + * + * @param dir + * the direction in which to inset (must be Horizontal, Vertical, or + * Both) + * @param amount + * the number of blocks by which to inset + * @return a new Cuboid inset by the given direction and amount + */ + public Cuboid inset(CuboidDirection dir, int amount) + { + return outset(dir, -amount); + } + + /** + * Return true if the point at (x,y,z) is contained within this Cuboid. + * + * @param x + * the X co-ordinate + * @param y + * the Y co-ordinate + * @param z + * the Z co-ordinate + * @return true if the given point is within this Cuboid, false otherwise + */ + public boolean contains(int x, int y, int z) + { + return x >= x1 && x <= x2 && y >= y1 && y <= y2 && z >= z1 && z <= z2; + } + + /** + * Check if the given Block is contained within this Cuboid. + * + * @param b + * the Block to check for + * @return true if the Block is within this Cuboid, false otherwise + */ + public boolean contains(Block b) + { + return contains(b.getLocation()); + } + + /** + * Check if the given Location is contained within this Cuboid. + * + * @param l + * the Location to check for + * @return true if the Location is within this Cuboid, false otherwise + */ + public boolean contains(Location l) + { + return worldName.equals(l.getWorld().getName()) && contains(l.getBlockX(), l.getBlockY(), l.getBlockZ()); + } + + /** + * Get the volume of this Cuboid. + * + * @return the Cuboid volume, in blocks + */ + public int volume() + { + return getSizeX() * getSizeY() * getSizeZ(); + } + + /** + * Get the average light level of all empty (air) blocks in the Cuboid. Returns + * 0 if there are no empty blocks. + * + * @return the average light level of this Cuboid + */ + public byte averageLightLevel() + { + long total = 0; + int n = 0; + for(Block b : this) + { + if(b.isEmpty()) + { + total += b.getLightLevel(); + ++n; + } + } + return n > 0 ? (byte) (total / n) : 0; + } + + /** + * Contract the Cuboid, returning a Cuboid with any air around the edges + * removed, just large enough to include all non-air blocks. + * + * @return a new Cuboid with no external air blocks + */ + public Cuboid contract() + { + return this.contract(CuboidDirection.Down).contract(CuboidDirection.South).contract(CuboidDirection.East).contract(CuboidDirection.Up).contract(CuboidDirection.North).contract(CuboidDirection.West); + } + + /** + * Contract the Cuboid in the given direction, returning a new Cuboid which has + * no exterior empty space. E.g. a direction of Down will push the top face + * downwards as much as possible. + * + * @param dir + * the direction in which to contract + * @return a new Cuboid contracted in the given direction + */ + public Cuboid contract(CuboidDirection dir) + { + Cuboid face = getFace(dir.opposite()); + switch(dir) + { + case Down: + while(face.containsOnly(Material.AIR) && face.getLowerY() > this.getLowerY()) + { + face = face.shift(CuboidDirection.Down, 1); + } + return new Cuboid(worldName, x1, y1, z1, x2, face.getUpperY(), z2); + case Up: + while(face.containsOnly(Material.AIR) && face.getUpperY() < this.getUpperY()) + { + face = face.shift(CuboidDirection.Up, 1); + } + return new Cuboid(worldName, x1, face.getLowerY(), z1, x2, y2, z2); + case North: + while(face.containsOnly(Material.AIR) && face.getLowerX() > this.getLowerX()) + { + face = face.shift(CuboidDirection.North, 1); + } + return new Cuboid(worldName, x1, y1, z1, face.getUpperX(), y2, z2); + case South: + while(face.containsOnly(Material.AIR) && face.getUpperX() < this.getUpperX()) + { + face = face.shift(CuboidDirection.South, 1); + } + return new Cuboid(worldName, face.getLowerX(), y1, z1, x2, y2, z2); + case East: + while(face.containsOnly(Material.AIR) && face.getLowerZ() > this.getLowerZ()) + { + face = face.shift(CuboidDirection.East, 1); + } + return new Cuboid(worldName, x1, y1, z1, x2, y2, face.getUpperZ()); + case West: + while(face.containsOnly(Material.AIR) && face.getUpperZ() < this.getUpperZ()) + { + face = face.shift(CuboidDirection.West, 1); + } + return new Cuboid(worldName, x1, y1, face.getLowerZ(), x2, y2, z2); + default: + throw new IllegalArgumentException("Invalid direction " + dir); + } + } + + /** + * Get the Cuboid representing the face of this Cuboid. The resulting Cuboid + * will be one block thick in the axis perpendicular to the requested face. + * + * @param dir + * which face of the Cuboid to get + * @return the Cuboid representing this Cuboid's requested face + */ + public Cuboid getFace(CuboidDirection dir) + { + switch(dir) + { + case Down: + return new Cuboid(worldName, x1, y1, z1, x2, y1, z2); + case Up: + return new Cuboid(worldName, x1, y2, z1, x2, y2, z2); + case North: + return new Cuboid(worldName, x1, y1, z1, x1, y2, z2); + case South: + return new Cuboid(worldName, x2, y1, z1, x2, y2, z2); + case East: + return new Cuboid(worldName, x1, y1, z1, x2, y2, z1); + case West: + return new Cuboid(worldName, x1, y1, z2, x2, y2, z2); + default: + throw new IllegalArgumentException("Invalid direction " + dir); + } + } + + /** + * Check if the Cuboid contains only blocks of the given type + * + * @param material + * the material to check for + * @return true if this Cuboid contains only blocks of the given type + */ + public boolean containsOnly(Material material) + { + for(Block b : this) + { + if(b.getType() != material) + { + return false; + } + } + return true; + } + + /** + * Get the Cuboid big enough to hold both this Cuboid and the given one. + * + * @param other + * the other Cuboid to include + * @return a new Cuboid large enough to hold this Cuboid and the given Cuboid + */ + public Cuboid getBoundingCuboid(Cuboid other) + { + if(other == null) + { + return this; + } + + int xMin = Math.min(getLowerX(), other.getLowerX()); + int yMin = Math.min(getLowerY(), other.getLowerY()); + int zMin = Math.min(getLowerZ(), other.getLowerZ()); + int xMax = Math.max(getUpperX(), other.getUpperX()); + int yMax = Math.max(getUpperY(), other.getUpperY()); + int zMax = Math.max(getUpperZ(), other.getUpperZ()); + + return new Cuboid(worldName, xMin, yMin, zMin, xMax, yMax, zMax); + } + + /** + * Get a block relative to the lower NE point of the Cuboid. + * + * @param x + * the X co-ordinate + * @param y + * the Y co-ordinate + * @param z + * the Z co-ordinate + * @return the block at the given position + */ + public Block getRelativeBlock(int x, int y, int z) + { + return getWorld().getBlockAt(x1 + x, y1 + y, z1 + z); + } + + /** + * Get a block relative to the lower NE point of the Cuboid in the given World. + * This version of getRelativeBlock() should be used if being called many times, + * to avoid excessive calls to getWorld(). + * + * @param w + * the World + * @param x + * the X co-ordinate + * @param y + * the Y co-ordinate + * @param z + * the Z co-ordinate + * @return the block at the given position + */ + public Block getRelativeBlock(World w, int x, int y, int z) + { + return w.getBlockAt(x1 + x, y1 + y, z1 + z); + } + + /** + * Get a list of the chunks which are fully or partially contained in this + * cuboid. + * + * @return a list of Chunk objects + */ + public List getChunks() + { + List res = new ArrayList(); + + World w = getWorld(); + int x1 = getLowerX() & ~0xf; + int x2 = getUpperX() & ~0xf; + int z1 = getLowerZ() & ~0xf; + int z2 = getUpperZ() & ~0xf; + for(int x = x1; x <= x2; x += 16) + { + for(int z = z1; z <= z2; z += 16) + { + res.add(w.getChunkAt(x >> 4, z >> 4)); + } + } + return res; + } + + /** + * Set all the blocks within the Cuboid to the given MaterialData, using a + * MassBlockUpdate object for fast updates. + * + * @param mat + * the MaterialData to set + * @param mbu + * the MassBlockUpdate object + */ + + /** + * Reset the light level of all blocks within this Cuboid. + */ + + /* + * (non-Javadoc) + * + * @see java.lang.Iterable#iterator() + */ + @Override + public Iterator iterator() + { + return new CuboidIterator(getWorld(), x1, y1, z1, x2, y2, z2); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#clone() + */ + @Override + public Cuboid clone() throws CloneNotSupportedException + { + return new Cuboid(this); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + return "Cuboid: " + worldName + "," + x1 + "," + y1 + "," + z1 + "=>" + x2 + "," + y2 + "," + z2; + } + + public class CuboidIterator implements Iterator + { + private World w; + private int baseX, baseY, baseZ; + private int x, y, z; + private int sizeX, sizeY, sizeZ; + + public CuboidIterator(World w, int x1, int y1, int z1, int x2, int y2, int z2) + { + this.w = w; + baseX = x1; + baseY = y1; + baseZ = z1; + sizeX = Math.abs(x2 - x1) + 1; + sizeY = Math.abs(y2 - y1) + 1; + sizeZ = Math.abs(z2 - z1) + 1; + x = y = z = 0; + } + + @Override + public boolean hasNext() + { + return x < sizeX && y < sizeY && z < sizeZ; + } + + @Override + public Block next() + { + Block b = w.getBlockAt(baseX + x, baseY + y, baseZ + z); + if(++x >= sizeX) + { + x = 0; + if(++y >= sizeY) + { + y = 0; + ++z; + } + } + return b; + } + + @Override + public void remove() + { + // nop + } + } + + public enum CuboidDirection + { + + North, + East, + South, + West, + Up, + Down, + Horizontal, + Vertical, + Both, + Unknown; + + public CuboidDirection opposite() + { + switch(this) + { + case North: + return South; + case East: + return West; + case South: + return North; + case West: + return East; + case Horizontal: + return Vertical; + case Vertical: + return Horizontal; + case Up: + return Down; + case Down: + return Up; + case Both: + return Both; + default: + return Unknown; + } + } + } + +} \ No newline at end of file diff --git a/src/main/java/ninja/bytecode/iris/util/CuboidException.java b/src/main/java/ninja/bytecode/iris/util/CuboidException.java new file mode 100644 index 000000000..212f9f79c --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/util/CuboidException.java @@ -0,0 +1,16 @@ +package ninja.bytecode.iris.util; + +/** + * Represents a cuboid exception + * + * @author cyberpwn + */ +public class CuboidException extends Exception +{ + public CuboidException(String string) + { + super(string); + } + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/ninja/bytecode/iris/util/DOP.java b/src/main/java/ninja/bytecode/iris/util/DOP.java new file mode 100644 index 000000000..b36611738 --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/util/DOP.java @@ -0,0 +1,20 @@ +package ninja.bytecode.iris.util; + +import org.bukkit.util.Vector; + +public abstract class DOP +{ + private String type; + + public DOP(String type) + { + this.type = type; + } + + public abstract Vector op(Vector v); + + public String getType() + { + return type; + } +} diff --git a/src/main/java/ninja/bytecode/iris/util/Dimension.java b/src/main/java/ninja/bytecode/iris/util/Dimension.java new file mode 100644 index 000000000..c9b8a0db6 --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/util/Dimension.java @@ -0,0 +1,86 @@ +package ninja.bytecode.iris.util; + +/** + * Dimensions + * + * @author cyberpwn + */ +public class Dimension +{ + private final int width; + private final int height; + private final int depth; + + /** + * Make a dimension + * + * @param width + * width of this (X) + * @param height + * the height (Y) + * @param depth + * the depth (Z) + */ + public Dimension(int width, int height, int depth) + { + this.width = width; + this.height = height; + this.depth = depth; + } + + /** + * Make a dimension + * + * @param width + * width of this (X) + * @param height + * the height (Y) + */ + public Dimension(int width, int height) + { + this.width = width; + this.height = height; + this.depth = 0; + } + + /** + * Get the direction of the flat part of this dimension (null if no thin + * face) + * + * @return the direction of the flat pane or null + */ + public DimensionFace getPane() + { + if(width == 1) + { + return DimensionFace.X; + } + + if(height == 1) + { + return DimensionFace.Y; + } + + if(depth == 1) + { + return DimensionFace.Z; + } + + return null; + } + + public int getWidth() + { + return width; + } + + public int getHeight() + { + return height; + } + + public int getDepth() + { + return depth; + } +} \ No newline at end of file diff --git a/src/main/java/ninja/bytecode/iris/util/DimensionFace.java b/src/main/java/ninja/bytecode/iris/util/DimensionFace.java new file mode 100644 index 000000000..0db5b6b07 --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/util/DimensionFace.java @@ -0,0 +1,24 @@ +package ninja.bytecode.iris.util; + +/** + * Represents a dimension (coordinates not worlds) + * + * @author cyberpwn + */ +public enum DimensionFace +{ + /** + * The X dimension (width) + */ + X, + + /** + * The Y dimension (height) + */ + Y, + + /** + * The Z dimension (depth) + */ + Z +} \ No newline at end of file diff --git a/src/main/java/ninja/bytecode/iris/util/Direction.java b/src/main/java/ninja/bytecode/iris/util/Direction.java new file mode 100644 index 000000000..1a8388575 --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/util/Direction.java @@ -0,0 +1,535 @@ + +package ninja.bytecode.iris.util; + +import org.bukkit.Axis; +import org.bukkit.block.BlockFace; +import org.bukkit.util.Vector; + +import ninja.bytecode.iris.util.Cuboid.CuboidDirection; +import ninja.bytecode.shuriken.collections.KList; +import ninja.bytecode.shuriken.collections.KMap; + +/** + * Directions + * + * @author cyberpwn + */ +public enum Direction +{ + U(0, 1, 0, CuboidDirection.Up), + D(0, -1, 0, CuboidDirection.Down), + N(0, 0, -1, CuboidDirection.North), + S(0, 0, 1, CuboidDirection.South), + E(1, 0, 0, CuboidDirection.East), + W(-1, 0, 0, CuboidDirection.West); + + private static KMap, DOP> permute = null; + + private int x; + private int y; + private int z; + private CuboidDirection f; + + public static Direction getDirection(BlockFace f) + { + switch(f) + { + case DOWN: + return D; + case EAST: + return E; + case EAST_NORTH_EAST: + return E; + case EAST_SOUTH_EAST: + return E; + case NORTH: + return N; + case NORTH_EAST: + return N; + case NORTH_NORTH_EAST: + return N; + case NORTH_NORTH_WEST: + return N; + case NORTH_WEST: + return N; + case SELF: + return U; + case SOUTH: + return S; + case SOUTH_EAST: + return S; + case SOUTH_SOUTH_EAST: + return S; + case SOUTH_SOUTH_WEST: + return S; + case SOUTH_WEST: + return S; + case UP: + return U; + case WEST: + return W; + case WEST_NORTH_WEST: + return W; + case WEST_SOUTH_WEST: + return W; + } + + return D; + } + + @Override + public String toString() + { + switch(this) + { + case D: + return "Down"; + case E: + return "East"; + case N: + return "North"; + case S: + return "South"; + case U: + return "Up"; + case W: + return "West"; + } + + return "?"; + } + + public boolean isVertical() + { + return equals(D) || equals(U); + } + + public static Direction closest(Vector v) + { + double m = Double.MAX_VALUE; + Direction s = null; + + for(Direction i : values()) + { + Vector x = i.toVector(); + double g = x.dot(v); + + if(g < m) + { + m = g; + s = i; + } + } + + return s; + } + + public static Direction closest(Vector v, Direction... d) + { + double m = Double.MAX_VALUE; + Direction s = null; + + for(Direction i : d) + { + Vector x = i.toVector(); + double g = x.distance(v); + + if(g < m) + { + m = g; + s = i; + } + } + + return s; + } + + public static Direction closest(Vector v, KList d) + { + double m = Double.MAX_VALUE; + Direction s = null; + + for(Direction i : d) + { + Vector x = i.toVector(); + double g = x.distance(v); + + if(g < m) + { + m = g; + s = i; + } + } + + return s; + } + + public Vector toVector() + { + return new Vector(x, y, z); + } + + public boolean isCrooked(Direction to) + { + if(equals(to.reverse())) + { + return false; + } + + if(equals(to)) + { + return false; + } + + return true; + } + + private Direction(int x, int y, int z, CuboidDirection f) + { + this.x = x; + this.y = y; + this.z = z; + this.f = f; + } + + public Vector angle(Vector initial, Direction d) + { + calculatePermutations(); + + for(GBiset i : permute.keySet()) + { + if(i.getA().equals(this) && i.getB().equals(d)) + { + return permute.get(i).op(initial); + } + } + + return initial; + } + + public Direction reverse() + { + switch(this) + { + case D: + return U; + case E: + return W; + case N: + return S; + case S: + return N; + case U: + return D; + case W: + return E; + default: + break; + } + + return null; + } + + public int x() + { + return x; + } + + public int y() + { + return y; + } + + public int z() + { + return z; + } + + public CuboidDirection f() + { + return f; + } + + public static KList news() + { + return new KList().add(N, E, W, S); + } + + public static Direction getDirection(Vector v) + { + Vector k = VectorMath.triNormalize(v.clone().normalize()); + + for(Direction i : udnews()) + { + if(i.x == k.getBlockX() && i.y == k.getBlockY() && i.z == k.getBlockZ()) + { + return i; + } + } + + return Direction.N; + } + + public static KList udnews() + { + return new KList().add(U, D, N, E, W, S); + } + + /** + * Get the directional value from the given byte from common directional blocks + * (MUST BE BETWEEN 0 and 5 INCLUSIVE) + * + * @param b + * the byte + * @return the direction or null if the byte is outside of the inclusive range + * 0-5 + */ + public static Direction fromByte(byte b) + { + if(b > 5 || b < 0) + { + return null; + } + + if(b == 0) + { + return D; + } + + else if(b == 1) + { + return U; + } + + else if(b == 2) + { + return N; + } + + else if(b == 3) + { + return S; + } + + else if(b == 4) + { + return W; + } + + else + { + return E; + } + } + + /** + * Get the byte value represented in some directional blocks + * + * @return the byte value + */ + public byte byteValue() + { + switch(this) + { + case D: + return 0; + case E: + return 5; + case N: + return 2; + case S: + return 3; + case U: + return 1; + case W: + return 4; + default: + break; + } + + return -1; + } + + public static void calculatePermutations() + { + if(permute != null) + { + return; + } + + permute = new KMap, DOP>(); + + for(Direction i : udnews()) + { + for(Direction j : udnews()) + { + GBiset b = new GBiset(i, j); + + if(i.equals(j)) + { + permute.put(b, new DOP("DIRECT") + { + @Override + public Vector op(Vector v) + { + return v; + } + }); + } + + else if(i.reverse().equals(j)) + { + if(i.isVertical()) + { + permute.put(b, new DOP("R180CCZ") + { + @Override + public Vector op(Vector v) + { + return VectorMath.rotate90CCZ(VectorMath.rotate90CCZ(v)); + } + }); + } + + else + { + permute.put(b, new DOP("R180CCY") + { + @Override + public Vector op(Vector v) + { + return VectorMath.rotate90CCY(VectorMath.rotate90CCY(v)); + } + }); + } + } + + else if(getDirection(VectorMath.rotate90CX(i.toVector())).equals(j)) + { + permute.put(b, new DOP("R90CX") + { + @Override + public Vector op(Vector v) + { + return VectorMath.rotate90CX(v); + } + }); + } + + else if(getDirection(VectorMath.rotate90CCX(i.toVector())).equals(j)) + { + permute.put(b, new DOP("R90CCX") + { + @Override + public Vector op(Vector v) + { + return VectorMath.rotate90CCX(v); + } + }); + } + + else if(getDirection(VectorMath.rotate90CY(i.toVector())).equals(j)) + { + permute.put(b, new DOP("R90CY") + { + @Override + public Vector op(Vector v) + { + return VectorMath.rotate90CY(v); + } + }); + } + + else if(getDirection(VectorMath.rotate90CCY(i.toVector())).equals(j)) + { + permute.put(b, new DOP("R90CCY") + { + @Override + public Vector op(Vector v) + { + return VectorMath.rotate90CCY(v); + } + }); + } + + else if(getDirection(VectorMath.rotate90CZ(i.toVector())).equals(j)) + { + permute.put(b, new DOP("R90CZ") + { + @Override + public Vector op(Vector v) + { + return VectorMath.rotate90CZ(v); + } + }); + } + + else if(getDirection(VectorMath.rotate90CCZ(i.toVector())).equals(j)) + { + permute.put(b, new DOP("R90CCZ") + { + @Override + public Vector op(Vector v) + { + return VectorMath.rotate90CCZ(v); + } + }); + } + + else + { + permute.put(b, new DOP("FAIL") + { + @Override + public Vector op(Vector v) + { + return v; + } + }); + } + } + } + } + + public BlockFace getFace() + { + switch(this) + { + case D: + return BlockFace.DOWN; + case E: + return BlockFace.EAST; + case N: + return BlockFace.NORTH; + case S: + return BlockFace.SOUTH; + case U: + return BlockFace.UP; + case W: + return BlockFace.WEST; + } + + return null; + } + + public Axis getAxis() + { + switch(this) + { + case D: + return Axis.Y; + case E: + return Axis.X; + case N: + return Axis.Z; + case S: + return Axis.Z; + case U: + return Axis.Y; + case W: + return Axis.X; + } + + return null; + } +} diff --git a/src/main/java/ninja/bytecode/iris/util/FastParticle.java b/src/main/java/ninja/bytecode/iris/util/FastParticle.java new file mode 100644 index 000000000..c7ef5bc46 --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/util/FastParticle.java @@ -0,0 +1,161 @@ +package ninja.bytecode.iris.util; + +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; + +/** + * Simple Bukkit Particles API with 1.7 to 1.13.2 support ! + *

+ * You can find the project on GitHub + * + * @author MrMicky + */ +public final class FastParticle { + + private static final ParticleSender PARTICLE_SENDER; + + static { + if (FastReflection.optionalClass("org.bukkit.Particle$DustOptions").isPresent()) { + PARTICLE_SENDER = new ParticleSender.ParticleSender1_13(); + } else if (FastReflection.optionalClass("org.bukkit.Particle").isPresent()) { + PARTICLE_SENDER = new ParticleSender.ParticleSenderImpl(); + } else { + PARTICLE_SENDER = new ParticleSenderLegacy(); + } + } + + private FastParticle() { + throw new UnsupportedOperationException(); + } + + /* + * Worlds methods + */ + public static void spawnParticle(World world, ParticleType particle, Location location, int count) { + spawnParticle(world, particle, location.getX(), location.getY(), location.getZ(), count); + } + + public static void spawnParticle(World world, ParticleType particle, double x, double y, double z, int count) { + spawnParticle(world, particle, x, y, z, count, null); + } + + public static void spawnParticle(World world, ParticleType particle, Location location, int count, T data) { + spawnParticle(world, particle, location.getX(), location.getY(), location.getZ(), count, data); + } + + public static void spawnParticle(World world, ParticleType particle, double x, double y, double z, int count, + T data) { + spawnParticle(world, particle, x, y, z, count, 0.0D, 0.0D, 0.0D, data); + } + + public static void spawnParticle(World world, ParticleType particle, Location location, int count, double offsetX, + double offsetY, double offsetZ) { + spawnParticle(world, particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ); + } + + public static void spawnParticle(World world, ParticleType particle, double x, double y, double z, int count, + double offsetX, double offsetY, double offsetZ) { + spawnParticle(world, particle, x, y, z, count, offsetX, offsetY, offsetZ, null); + } + + public static void spawnParticle(World world, ParticleType particle, Location location, int count, + double offsetX, double offsetY, double offsetZ, T data) { + spawnParticle(world, particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, + offsetZ, data); + } + + public static void spawnParticle(World world, ParticleType particle, double x, double y, double z, int count, + double offsetX, double offsetY, double offsetZ, T data) { + spawnParticle(world, particle, x, y, z, count, offsetX, offsetY, offsetZ, 1.0D, data); + } + + public static void spawnParticle(World world, ParticleType particle, Location location, int count, double offsetX, + double offsetY, double offsetZ, double extra) { + spawnParticle(world, particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, extra); + } + + public static void spawnParticle(World world, ParticleType particle, double x, double y, double z, int count, + double offsetX, double offsetY, double offsetZ, double extra) { + spawnParticle(world, particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, null); + } + + public static void spawnParticle(World world, ParticleType particle, Location location, int count, + double offsetX, double offsetY, double offsetZ, double extra, T data) { + spawnParticle(world, particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, extra, data); + } + + public static void spawnParticle(World world, ParticleType particle, double x, double y, double z, int count, + double offsetX, double offsetY, double offsetZ, double extra, T data) { + sendParticle(world, particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data); + } + + /* + * Player methods + */ + public static void spawnParticle(Player player, ParticleType particle, Location location, int count) { + spawnParticle(player, particle, location.getX(), location.getY(), location.getZ(), count); + } + + public static void spawnParticle(Player player, ParticleType particle, double x, double y, double z, int count) { + spawnParticle(player, particle, x, y, z, count, null); + } + + public static void spawnParticle(Player player, ParticleType particle, Location location, int count, T data) { + spawnParticle(player, particle, location.getX(), location.getY(), location.getZ(), count, data); + } + + public static void spawnParticle(Player player, ParticleType particle, double x, double y, double z, int count, + T data) { + spawnParticle(player, particle, x, y, z, count, 0.0D, 0.0D, 0.0D, data); + } + + public static void spawnParticle(Player player, ParticleType particle, Location location, int count, double offsetX, + double offsetY, double offsetZ) { + spawnParticle(player, particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ); + } + + public static void spawnParticle(Player player, ParticleType particle, double x, double y, double z, int count, + double offsetX, double offsetY, double offsetZ) { + spawnParticle(player, particle, x, y, z, count, offsetX, offsetY, offsetZ, null); + } + + public static void spawnParticle(Player player, ParticleType particle, Location location, int count, + double offsetX, double offsetY, double offsetZ, T data) { + spawnParticle(player, particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, data); + } + + public static void spawnParticle(Player player, ParticleType particle, double x, double y, double z, int count, + double offsetX, double offsetY, double offsetZ, T data) { + spawnParticle(player, particle, x, y, z, count, offsetX, offsetY, offsetZ, 1.0D, data); + } + + public static void spawnParticle(Player player, ParticleType particle, Location location, int count, double offsetX, + double offsetY, double offsetZ, double extra) { + spawnParticle(player, particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, extra); + } + + public static void spawnParticle(Player player, ParticleType particle, double x, double y, double z, int count, + double offsetX, double offsetY, double offsetZ, double extra) { + spawnParticle(player, particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, null); + } + + public static void spawnParticle(Player player, ParticleType particle, Location location, int count, + double offsetX, double offsetY, double offsetZ, double extra, T data) { + spawnParticle(player, particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, extra, data); + } + + public static void spawnParticle(Player player, ParticleType particle, double x, double y, double z, int count, + double offsetX, double offsetY, double offsetZ, double extra, T data) { + sendParticle(player, particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data); + } + + private static void sendParticle(Object receiver, ParticleType particle, double x, double y, double z, int count, + double offsetX, double offsetY, double offsetZ, double extra, Object data) { + if (!particle.isSupported()) { + throw new IllegalArgumentException("The particle '" + particle + "' is not compatible with your server version"); + } + + PARTICLE_SENDER.spawnParticle(receiver, particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data); + } +} diff --git a/src/main/java/ninja/bytecode/iris/util/FastReflection.java b/src/main/java/ninja/bytecode/iris/util/FastReflection.java new file mode 100644 index 000000000..c330722c7 --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/util/FastReflection.java @@ -0,0 +1,59 @@ +package ninja.bytecode.iris.util; + +import org.bukkit.Bukkit; + +import java.util.Optional; + +/** + * Small reflection class to use CraftBukkit and NMS + * + * @author MrMicky + */ +public final class FastReflection { + + public static final String OBC_PACKAGE = "org.bukkit.craftbukkit"; + public static final String NMS_PACKAGE = "net.minecraft.server"; + + public static final String VERSION = Bukkit.getServer().getClass().getPackage().getName().substring(OBC_PACKAGE.length() + 1); + + private FastReflection() { + throw new UnsupportedOperationException(); + } + + public static String nmsClassName(String className) { + return NMS_PACKAGE + '.' + VERSION + '.' + className; + } + + public static Class nmsClass(String className) throws ClassNotFoundException { + return Class.forName(nmsClassName(className)); + } + + public static Optional> nmsOptionalClass(String className) { + return optionalClass(nmsClassName(className)); + } + + public static String obcClassName(String className) { + return OBC_PACKAGE + '.' + VERSION + '.' + className; + } + + public static Class obcClass(String className) throws ClassNotFoundException { + return Class.forName(obcClassName(className)); + } + + public static Optional> obcOptionalClass(String className) { + return optionalClass(obcClassName(className)); + } + + public static Optional> optionalClass(String className) { + try { + return Optional.of(Class.forName(className)); + } catch (ClassNotFoundException e) { + return Optional.empty(); + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public static Object enumValueOf(Class enumClass, String enumName) { + return Enum.valueOf((Class) enumClass, enumName.toUpperCase()); + } +} \ No newline at end of file diff --git a/src/main/java/ninja/bytecode/iris/util/GBiset.java b/src/main/java/ninja/bytecode/iris/util/GBiset.java new file mode 100644 index 000000000..901384c1a --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/util/GBiset.java @@ -0,0 +1,77 @@ +package ninja.bytecode.iris.util; + + +import java.io.Serializable; + +/** + * A Biset + * + * @author cyberpwn + * + * @param + * the first object type + * @param + * the second object type + */ +@SuppressWarnings("hiding") +public class GBiset implements Serializable +{ + private static final long serialVersionUID = 1L; + private A a; + private B b; + + /** + * Create a new Biset + * + * @param a + * the first object + * @param b + * the second object + */ + public GBiset(A a, B b) + { + this.a = a; + this.b = b; + } + + /** + * Get the object of the type A + * + * @return the first object + */ + public A getA() + { + return a; + } + + /** + * Set the first object + * + * @param a + * the first object A + */ + public void setA(A a) + { + this.a = a; + } + + /** + * Get the second object + * + * @return the second object + */ + public B getB() + { + return b; + } + + /** + * Set the second object + * + * @param b + */ + public void setB(B b) + { + this.b = b; + } +} diff --git a/src/main/java/ninja/bytecode/iris/util/GListAdapter.java b/src/main/java/ninja/bytecode/iris/util/GListAdapter.java new file mode 100644 index 000000000..d6c0d702b --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/util/GListAdapter.java @@ -0,0 +1,51 @@ +package ninja.bytecode.iris.util; + + +import java.util.List; + +import ninja.bytecode.shuriken.collections.KList; + +/** + * Adapts a list of objects into a list of other objects + * + * @author cyberpwn + * @param + * the from object in lists (the item INSIDE the list) + * @param + * the to object in lists (the item INSIDE the list) + */ +public abstract class GListAdapter +{ + /** + * Adapts a list of FROM to a list of TO + * + * @param from + * the from list + * @return the to list + */ + public List adapt(List from) + { + List adapted = new KList(); + + for(FROM i : from) + { + TO t = onAdapt(i); + + if(t != null) + { + adapted.add(onAdapt(i)); + } + } + + return adapted; + } + + /** + * Adapts a list object FROM to TO for use with the adapt method + * + * @param from + * the from object + * @return the to object + */ + public abstract TO onAdapt(FROM from); +} diff --git a/src/main/java/ninja/bytecode/iris/util/IObjectPlacer.java b/src/main/java/ninja/bytecode/iris/util/IObjectPlacer.java new file mode 100644 index 000000000..2229cfc1b --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/util/IObjectPlacer.java @@ -0,0 +1,12 @@ +package ninja.bytecode.iris.util; + +import org.bukkit.block.data.BlockData; + +public interface IObjectPlacer +{ + public int getHighest(int x, int z); + + public void set(int x, int y, int z, BlockData d); + + public BlockData get(int x, int y, int z); +} diff --git a/src/main/java/ninja/bytecode/iris/util/ObjectResourceLoader.java b/src/main/java/ninja/bytecode/iris/util/ObjectResourceLoader.java new file mode 100644 index 000000000..dacc1855f --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/util/ObjectResourceLoader.java @@ -0,0 +1,104 @@ +package ninja.bytecode.iris.util; + +import java.io.File; + +import org.bukkit.util.BlockVector; + +import ninja.bytecode.iris.Iris; +import ninja.bytecode.iris.object.IrisObject; + +public class ObjectResourceLoader extends ResourceLoader +{ + private ChunkPosition parallaxSize; + + public ObjectResourceLoader(File root, String folderName, String resourceTypeName) + { + super(root, folderName, resourceTypeName, IrisObject.class); + } + + public ChunkPosition getParallaxSize() + { + lock.lock(); + if(parallaxSize == null) + { + int x = 0; + int z = 0; + + for(File i : getFolders()) + { + for(File j : i.listFiles()) + { + if(j.isFile() && j.getName().endsWith(".iob")) + { + try + { + BlockVector b = IrisObject.sampleSize(j); + x = b.getBlockX() > x ? b.getBlockX() : x; + z = b.getBlockZ() > z ? b.getBlockZ() : z; + } + + catch(Throwable e) + { + + } + } + } + } + + x = (Math.max(x, 16) + 16) >> 4; + z = (Math.max(z, 16) + 16) >> 4; + x = x % 2 == 0 ? x + 1 : x; + z = z % 2 == 0 ? z + 1 : z; + parallaxSize = new ChunkPosition(x, z); + Iris.info("Parallax View Distance: " + x + "x" + z); + } + + lock.unlock(); + + return parallaxSize; + } + + public IrisObject load(String name) + { + String key = name + "-" + objectClass.getCanonicalName(); + + if(loadCache.containsKey(key)) + { + IrisObject t = loadCache.get(key); + return t; + } + + lock.lock(); + for(File i : getFolders()) + { + for(File j : i.listFiles()) + { + if(j.isFile() && j.getName().endsWith(".iob") && j.getName().split("\\Q.\\E")[0].equals(name)) + { + try + { + IrisObject t = new IrisObject(0, 0, 0); + t.read(j); + loadCache.put(key, t); + Iris.hotloader.track(j); + Iris.info("Loading " + resourceTypeName + ": " + j.getPath()); + t.setLoadKey(name); + lock.unlock(); + return t; + } + + catch(Throwable e) + { + lock.unlock(); + Iris.warn("Couldn't read " + resourceTypeName + " file: " + j.getPath() + ": " + e.getMessage()); + } + } + } + } + + Iris.warn("Couldn't find " + resourceTypeName + ": " + name); + + lock.unlock(); + return null; + } +} diff --git a/src/main/java/ninja/bytecode/iris/util/ParticleSender.java b/src/main/java/ninja/bytecode/iris/util/ParticleSender.java new file mode 100644 index 000000000..1c725aa1a --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/util/ParticleSender.java @@ -0,0 +1,130 @@ +package ninja.bytecode.iris.util; + +import org.bukkit.Bukkit; +import org.bukkit.Color; +import org.bukkit.Particle; +import org.bukkit.World; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Player; +import org.bukkit.material.MaterialData; + +/** + * Particle sender using the Bukkit particles API for 1.9+ servers + * + * @author MrMicky + */ +@SuppressWarnings("deprecation") +interface ParticleSender +{ + + void spawnParticle(Object receiver, ParticleType particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, Object data); + + Object getParticle(ParticleType particle); + + boolean isValidData(Object particle, Object data); + + default double color(double color) + { + return color / 255.0; + } + + class ParticleSenderImpl implements ParticleSender + { + + @Override + public void spawnParticle(Object receiver, ParticleType particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, Object data) + { + Particle bukkitParticle = Particle.valueOf(particle.toString()); + + if(data instanceof Color) + { + if(particle.getDataType() == Color.class) + { + Color color = (Color) data; + count = 0; + offsetX = color(color.getRed()); + offsetY = color(color.getGreen()); + offsetZ = color(color.getBlue()); + extra = 1.0; + } + data = null; + } + + if(receiver instanceof World) + { + ((World) receiver).spawnParticle(bukkitParticle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data); + } + else if(receiver instanceof Player) + { + ((Player) receiver).spawnParticle(bukkitParticle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data); + } + } + + @Override + public Particle getParticle(ParticleType particle) + { + try + { + return Particle.valueOf(particle.toString()); + } + catch(IllegalArgumentException e) + { + return null; + } + } + + @Override + public boolean isValidData(Object particle, Object data) + { + return isValidDataBukkit((Particle) particle, data); + } + + public boolean isValidDataBukkit(Particle particle, Object data) + { + return particle.getDataType() == Void.class || particle.getDataType().isInstance(data); + } + } + + class ParticleSender1_13 extends ParticleSenderImpl + { + @Override + public void spawnParticle(Object receiver, ParticleType particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, Object data) + { + Particle bukkitParticle = Particle.valueOf(particle.toString()); + + if(bukkitParticle.getDataType() == Particle.DustOptions.class) + { + if(data instanceof Color) + { + data = new Particle.DustOptions((Color) data, 1); + } + else if(data == null) + { + data = new Particle.DustOptions(Color.RED, 1); + } + } + else if(bukkitParticle.getDataType() == BlockData.class && data instanceof MaterialData) + { + data = Bukkit.createBlockData(((MaterialData) data).getItemType()); + } + + super.spawnParticle(receiver, particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data); + } + + @Override + public boolean isValidDataBukkit(Particle particle, Object data) + { + if(particle.getDataType() == Particle.DustOptions.class && data instanceof Color) + { + return true; + } + + if(particle.getDataType() == BlockData.class && data instanceof MaterialData) + { + return true; + } + + return super.isValidDataBukkit(particle, data); + } + } +} diff --git a/src/main/java/ninja/bytecode/iris/util/ParticleSenderLegacy.java b/src/main/java/ninja/bytecode/iris/util/ParticleSenderLegacy.java new file mode 100644 index 000000000..02f3713f4 --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/util/ParticleSenderLegacy.java @@ -0,0 +1,166 @@ +package ninja.bytecode.iris.util; + +import org.bukkit.Color; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.material.MaterialData; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * Legacy particle sender with NMS for 1.7/1.8 servers + * + * @author MrMicky + */ +@SuppressWarnings("deprecation") +class ParticleSenderLegacy implements ParticleSender { + + private static final boolean SERVER_IS_1_8; + + private static final Constructor PACKET_PARTICLE; + private static final Class ENUM_PARTICLE; + + private static final Method WORLD_GET_HANDLE; + private static final Method WORLD_SEND_PARTICLE; + + private static final Method PLAYER_GET_HANDLE; + private static final Field PLAYER_CONNECTION; + private static final Method SEND_PACKET; + private static final int[] EMPTY = new int[0]; + + static { + ENUM_PARTICLE = FastReflection.nmsOptionalClass("EnumParticle").orElse(null); + SERVER_IS_1_8 = ENUM_PARTICLE != null; + + try { + Class packetParticleClass = FastReflection.nmsClass("PacketPlayOutWorldParticles"); + Class playerClass = FastReflection.nmsClass("EntityPlayer"); + Class playerConnectionClass = FastReflection.nmsClass("PlayerConnection"); + Class worldClass = FastReflection.nmsClass("WorldServer"); + Class entityPlayerClass = FastReflection.nmsClass("EntityPlayer"); + + Class craftPlayerClass = FastReflection.obcClass("entity.CraftPlayer"); + Class craftWorldClass = FastReflection.obcClass("CraftWorld"); + + if (SERVER_IS_1_8) { + PACKET_PARTICLE = packetParticleClass.getConstructor(ENUM_PARTICLE, boolean.class, float.class, + float.class, float.class, float.class, float.class, float.class, float.class, int.class, + int[].class); + WORLD_SEND_PARTICLE = worldClass.getDeclaredMethod("sendParticles", entityPlayerClass, ENUM_PARTICLE, + boolean.class, double.class, double.class, double.class, int.class, double.class, double.class, + double.class, double.class, int[].class); + } else { + PACKET_PARTICLE = packetParticleClass.getConstructor(String.class, float.class, float.class, float.class, + float.class, float.class, float.class, float.class, int.class); + WORLD_SEND_PARTICLE = worldClass.getDeclaredMethod("a", String.class, double.class, double.class, + double.class, int.class, double.class, double.class, double.class, double.class); + } + + WORLD_GET_HANDLE = craftWorldClass.getDeclaredMethod("getHandle"); + PLAYER_GET_HANDLE = craftPlayerClass.getDeclaredMethod("getHandle"); + PLAYER_CONNECTION = playerClass.getField("playerConnection"); + SEND_PACKET = playerConnectionClass.getMethod("sendPacket", FastReflection.nmsClass("Packet")); + } catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } + + @Override + public void spawnParticle(Object receiver, ParticleType particle, double x, double y, double z, int count, double offsetX, double offsetY, + double offsetZ, double extra, Object data) { + try { + int[] datas = toData(particle, data); + + if (data instanceof Color) { + if (particle.getDataType() == Color.class) { + Color color = (Color) data; + count = 0; + offsetX = color(color.getRed()); + offsetY = color(color.getGreen()); + offsetZ = color(color.getBlue()); + extra = 1.0; + } + } + + if (receiver instanceof World) { + Object worldServer = WORLD_GET_HANDLE.invoke(receiver); + + if (SERVER_IS_1_8) { + WORLD_SEND_PARTICLE.invoke(worldServer, null, getEnumParticle(particle), true, x, y, z, count, offsetX, offsetY, offsetZ, extra, datas); + } else { + String particleName = particle.getLegacyName() + (datas.length != 2 ? "" : "_" + datas[0] + "_" + datas[1]); + WORLD_SEND_PARTICLE.invoke(worldServer, particleName, x, y, z, count, offsetX, offsetY, offsetZ, extra); + } + } else if (receiver instanceof Player) { + Object packet; + + if (SERVER_IS_1_8) { + packet = PACKET_PARTICLE.newInstance(getEnumParticle(particle), true, (float) x, (float) y, + (float) z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count, datas); + } else { + String particleName = particle.getLegacyName() + (datas.length != 2 ? "" : "_" + datas[0] + "_" + datas[1]); + packet = PACKET_PARTICLE.newInstance(particleName, (float) x, (float) y, (float) z, + (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count); + } + + Object entityPlayer = PLAYER_GET_HANDLE.invoke(receiver); + Object playerConnection = PLAYER_CONNECTION.get(entityPlayer); + SEND_PACKET.invoke(playerConnection, packet); + } + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean isValidData(Object particle, Object data) { + return true; + } + + @Override + public Object getParticle(ParticleType particle) { + if (!SERVER_IS_1_8) { + return particle.getLegacyName(); + } + + try { + return getEnumParticle(particle); + } catch (IllegalArgumentException e) { + return null; + } + } + + private Object getEnumParticle(ParticleType particleType) { + return FastReflection.enumValueOf(ENUM_PARTICLE, particleType.toString()); + } + + private int[] toData(ParticleType particle, Object data) { + Class dataType = particle.getDataType(); + if (dataType == ItemStack.class) { + if (!(data instanceof ItemStack)) { + return SERVER_IS_1_8 ? new int[2] : new int[]{1, 0}; + } + + ItemStack itemStack = (ItemStack) data; + return new int[]{itemStack.getType().getId(), itemStack.getDurability()}; + } + + if (dataType == MaterialData.class) { + if (!(data instanceof MaterialData)) { + return SERVER_IS_1_8 ? new int[1] : new int[]{1, 0}; + } + + MaterialData materialData = (MaterialData) data; + if (SERVER_IS_1_8) { + return new int[]{materialData.getItemType().getId() + (materialData.getData() << 12)}; + } else { + return new int[]{materialData.getItemType().getId(), materialData.getData()}; + } + } + + return EMPTY; + } +} diff --git a/src/main/java/ninja/bytecode/iris/util/ParticleType.java b/src/main/java/ninja/bytecode/iris/util/ParticleType.java new file mode 100644 index 000000000..f8429c15c --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/util/ParticleType.java @@ -0,0 +1,177 @@ +package ninja.bytecode.iris.util; + +import org.bukkit.Color; +import org.bukkit.inventory.ItemStack; +import org.bukkit.material.MaterialData; + +/** + * @author MrMicky + */ +@SuppressWarnings("deprecation") +public enum ParticleType { + + // 1.7+ + EXPLOSION_NORMAL("explode", "poof"), + EXPLOSION_LARGE("largeexplode", "explosion"), + EXPLOSION_HUGE("hugeexplosion", "explosion_emitter"), + FIREWORKS_SPARK("fireworksSpark", "firework"), + WATER_BUBBLE("bubble", "bubble"), + WATER_SPLASH("splash", "splash"), + WATER_WAKE("wake", "fishing"), + SUSPENDED("suspended", "underwater"), + SUSPENDED_DEPTH("depthsuspend", "underwater"), + CRIT("crit", "crit"), + CRIT_MAGIC("magicCrit", "enchanted_hit"), + SMOKE_NORMAL("smoke", "smoke"), + SMOKE_LARGE("largesmoke", "large_smoke"), + SPELL("spell", "effect"), + SPELL_INSTANT("instantSpell", "instant_effect"), + SPELL_MOB("mobSpell", "entity_effect"), + SPELL_MOB_AMBIENT("mobSpellAmbient", "ambient_entity_effect"), + SPELL_WITCH("witchMagic", "witch"), + DRIP_WATER("dripWater", "dripping_water"), + DRIP_LAVA("dripLava", "dripping_lava"), + VILLAGER_ANGRY("angryVillager", "angry_villager"), + VILLAGER_HAPPY("happyVillager", "happy_villager"), + TOWN_AURA("townaura", "mycelium"), + NOTE("note", "note"), + PORTAL("portal", "portal"), + ENCHANTMENT_TABLE("enchantmenttable", "enchant"), + FLAME("flame", "flame"), + LAVA("lava", "lava"), + // FOOTSTEP("footstep", null), + CLOUD("cloud", "cloud"), + REDSTONE("reddust", "dust"), + SNOWBALL("snowballpoof", "item_snowball"), + SNOW_SHOVEL("snowshovel", "item_snowball"), + SLIME("slime", "item_slime"), + HEART("heart", "heart"), + ITEM_CRACK("iconcrack", "item"), + BLOCK_CRACK("blockcrack", "block"), + BLOCK_DUST("blockdust", "block"), + + // 1.8+ + BARRIER("barrier", "barrier", 8), + WATER_DROP("droplet", "rain", 8), + MOB_APPEARANCE("mobappearance", "elder_guardian", 8), + // ITEM_TAKE("take", null, 8), + + // 1.9+ + DRAGON_BREATH("dragonbreath", "dragon_breath", 9), + END_ROD("endRod", "end_rod", 9), + DAMAGE_INDICATOR("damageIndicator", "damage_indicator", 9), + SWEEP_ATTACK("sweepAttack", "sweep_attack", 9), + + // 1.10+ + FALLING_DUST("fallingdust", "falling_dust", 10), + + // 1.11+ + TOTEM("totem", "totem_of_undying", 11), + SPIT("spit", "spit", 11), + + // 1.13+ + SQUID_INK(13), + BUBBLE_POP(13), + CURRENT_DOWN(13), + BUBBLE_COLUMN_UP(13), + NAUTILUS(13), + DOLPHIN(13), + + // 1.14+ + SNEEZE(14), + CAMPFIRE_COSY_SMOKE(14), + CAMPFIRE_SIGNAL_SMOKE(14), + COMPOSTER(14), + FLASH(14), + FALLING_LAVA(14), + LANDING_LAVA(14), + FALLING_WATER(14), + + // 1.15+ + DRIPPING_HONEY(15), + FALLING_HONEY(15), + LANDING_HONEY(15), + FALLING_NECTAR(15); + + private static final int SERVER_VERSION_ID; + + static { + String ver = FastReflection.VERSION; + SERVER_VERSION_ID = ver.charAt(4) == '_' ? Character.getNumericValue(ver.charAt(3)) : Integer.parseInt(ver.substring(3, 5)); + } + + private final String legacyName; + private final String name; + private final int minimumVersion; + + // 1.7 particles + ParticleType(String legacyName, String name) { + this(legacyName, name, -1); + } + + // 1.13+ particles + ParticleType(int minimumVersion) { + this.legacyName = null; + this.name = name().toLowerCase(); + this.minimumVersion = minimumVersion; + } + + // 1.8-1.12 particles + ParticleType(String legacyName, String name, int minimumVersion) { + this.legacyName = legacyName; + this.name = name; + this.minimumVersion = minimumVersion; + } + + public boolean hasLegacyName() { + return legacyName != null; + } + + public String getLegacyName() { + if (!hasLegacyName()) { + throw new IllegalStateException("Particle " + name() + " don't have legacy name"); + } + return legacyName; + } + + public String getName() { + return name; + } + + public boolean isSupported() { + return minimumVersion <= 0 || SERVER_VERSION_ID >= minimumVersion; + } + + public Class getDataType() { + switch (this) { + case ITEM_CRACK: + return ItemStack.class; + case BLOCK_CRACK: + case BLOCK_DUST: + case FALLING_DUST: + //noinspection deprecation + return MaterialData.class; + case REDSTONE: + return Color.class; + default: + return Void.class; + } + } + + public static ParticleType getParticle(String particleName) { + try { + return ParticleType.valueOf(particleName.toUpperCase()); + } catch (IllegalArgumentException e) { + for (ParticleType particle : values()) { + if (particle.getName().equalsIgnoreCase(particleName)) { + return particle; + } + + if (particle.hasLegacyName() && particle.getLegacyName().equalsIgnoreCase(particleName)) { + return particle; + } + } + } + return null; + } +} diff --git a/src/main/java/ninja/bytecode/iris/util/ResourceLoader.java b/src/main/java/ninja/bytecode/iris/util/ResourceLoader.java index 29a55cf1e..d422e788d 100644 --- a/src/main/java/ninja/bytecode/iris/util/ResourceLoader.java +++ b/src/main/java/ninja/bytecode/iris/util/ResourceLoader.java @@ -12,13 +12,13 @@ import ninja.bytecode.shuriken.collections.KMap; public class ResourceLoader { - private File root; - private String folderName; - private String resourceTypeName; - private KMap loadCache; - private KList folderCache; - private Class objectClass; - private ReentrantLock lock; + protected File root; + protected String folderName; + protected String resourceTypeName; + protected KMap loadCache; + protected KList folderCache; + protected Class objectClass; + protected ReentrantLock lock; public ResourceLoader(File root, String folderName, String resourceTypeName, Class objectClass) { diff --git a/src/main/java/ninja/bytecode/iris/util/VectorMath.java b/src/main/java/ninja/bytecode/iris/util/VectorMath.java new file mode 100644 index 000000000..99cdf3e87 --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/util/VectorMath.java @@ -0,0 +1,755 @@ +package ninja.bytecode.iris.util; + +import org.bukkit.Axis; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; +import org.bukkit.util.Vector; + +import ninja.bytecode.shuriken.collections.KList; +import ninja.bytecode.shuriken.format.Form; + +/** + * Vector utilities + * + * @author cyberpwn + */ +public class VectorMath +{ + public static Vector scaleStatic(Axis x, Vector v, double amt) + { + switch(x) + { + case X: + return scaleX(v, amt); + case Y: + return scaleY(v, amt); + case Z: + return scaleZ(v, amt); + } + + return null; + } + + public static Vector scaleX(Vector v, double amt) + { + double x = v.getX(); + double y = v.getY(); + double z = v.getZ(); + double rx = x == 0 ? 1 : amt / x; + + return new Vector(x * rx, y * rx, z * rx); + } + + public static Vector scaleY(Vector v, double amt) + { + double x = v.getX(); + double y = v.getY(); + double z = v.getZ(); + double rx = y == 0 ? 1 : amt / y; + + return new Vector(x * rx, y * rx, z * rx); + } + + public static Vector scaleZ(Vector v, double amt) + { + double x = v.getX(); + double y = v.getY(); + double z = v.getZ(); + double rx = z == 0 ? 1 : amt / z; + + return new Vector(x * rx, y * rx, z * rx); + } + + public static Vector reverseXZ(Vector v) + { + v.setX(-v.getX()); + v.setZ(-v.getZ()); + return v; + } + + public static boolean isLookingNear(Location a, Location b, double maxOff) + { + Vector perfect = VectorMath.direction(a, b); + Vector actual = a.getDirection(); + + return perfect.distance(actual) <= maxOff; + } + + public static Vector rotate(Direction current, Direction to, Vector v) + { + if(current.equals(to)) + { + return v; + } + + else if(current.equals(to.reverse())) + { + if(current.isVertical()) + { + return new Vector(v.getX(), -v.getY(), v.getZ()); + } + + else + { + return new Vector(-v.getX(), v.getY(), -v.getZ()); + } + } + + else + { + Vector c = current.toVector().clone().add(to.toVector()); + + if(c.getX() == 0) + { + if(c.getY() != c.getZ()) + { + return rotate90CX(v); + } + + return rotate90CCX(v); + } + + else if(c.getY() == 0) + { + if(c.getX() != c.getZ()) + { + return rotate90CY(v); + } + + return rotate90CCY(v); + } + + else if(c.getZ() == 0) + { + if(c.getX() != c.getY()) + { + return rotate90CZ(v); + } + + return rotate90CCZ(v); + } + } + + return v; + } + + // Y X 0 0 + // X Z 0 0 + + // 0 X Y 0 + // 0 Z X 0 + + public static Vector rotate(Direction current, Direction to, Vector v, int w, int h, int d) + { + if(current.equals(to)) + { + return v; + } + + else if(current.equals(to.reverse())) + { + if(current.isVertical()) + { + return new Vector(v.getX(), -v.getY() + h, v.getZ()); + } + + else + { + return new Vector(-v.getX() + w, v.getY(), -v.getZ() + d); + } + } + + else + { + Vector c = current.toVector().clone().add(to.toVector()); + + if(c.getX() == 0) + { + if(c.getY() != c.getZ()) + { + return rotate90CX(v, d); + } + + return rotate90CCX(v, h); + } + + else if(c.getY() == 0) + { + if(c.getX() != c.getZ()) + { + return rotate90CY(v, d); + } + + return rotate90CCY(v, w); + } + + else if(c.getZ() == 0) + { + if(c.getX() != c.getY()) + { + return rotate90CZ(v, w); + } + + return rotate90CCZ(v, h); + } + } + + return v; + } + + public static Vector rotate90CX(Vector v) + { + return new Vector(v.getX(), -v.getZ(), v.getY()); + } + + public static Vector rotate90CCX(Vector v) + { + return new Vector(v.getX(), v.getZ(), -v.getY()); + } + + public static Vector rotate90CY(Vector v) + { + return new Vector(-v.getZ(), v.getY(), v.getX()); + } + + public static Vector rotate90CCY(Vector v) + { + return new Vector(v.getZ(), v.getY(), -v.getX()); + } + + public static Vector rotate90CZ(Vector v) + { + return new Vector(v.getY(), -v.getX(), v.getZ()); + } + + public static Vector rotate90CCZ(Vector v) + { + return new Vector(-v.getY(), v.getX(), v.getZ()); + } + + public static Vector rotate90CX(Vector v, int s) + { + return new Vector(v.getX(), -v.getZ() + s, v.getY()); + } + + public static Vector rotate90CCX(Vector v, int s) + { + return new Vector(v.getX(), v.getZ(), -v.getY() + s); + } + + public static Vector rotate90CY(Vector v, int s) + { + return new Vector(-v.getZ() + s, v.getY(), v.getX()); + } + + public static Vector rotate90CCY(Vector v, int s) + { + return new Vector(v.getZ(), v.getY(), -v.getX() + s); + } + + public static Vector rotate90CZ(Vector v, int s) + { + return new Vector(v.getY(), -v.getX() + s, v.getZ()); + } + + public static Vector rotate90CCZ(Vector v, int s) + { + return new Vector(-v.getY() + s, v.getX(), v.getZ()); + } + + public static Vector getAxis(Direction current, Direction to) + { + if(current.equals(Direction.U) || current.equals(Direction.D)) + { + if(to.equals(Direction.U) || to.equals(Direction.D)) + { + return new Vector(1, 0, 0); + } + + else + { + if(current.equals(Direction.N) || current.equals(Direction.S)) + { + return Direction.E.toVector(); + } + + else + { + return Direction.S.toVector(); + } + } + } + + return new Vector(0, 1, 0); + } + + private static double round(double value, int precision) + { + return Double.valueOf(Form.f(value, precision)); + } + + public static Vector clip(Vector v, int decimals) + { + v.setX(round(v.getX(), decimals)); + v.setY(round(v.getY(), decimals)); + v.setZ(round(v.getZ(), decimals)); + return v; + } + + public static Vector rotateVectorCC(Vector vec, Vector axis, double deg) + { + double theta = Math.toRadians(deg); + double x, y, z; + double u, v, w; + x = vec.getX(); + y = vec.getY(); + z = vec.getZ(); + u = axis.getX(); + v = axis.getY(); + w = axis.getZ(); + double xPrime = u * (u * x + v * y + w * z) * (1d - Math.cos(theta)) + x * Math.cos(theta) + (-w * y + v * z) * Math.sin(theta); + double yPrime = v * (u * x + v * y + w * z) * (1d - Math.cos(theta)) + y * Math.cos(theta) + (w * x - u * z) * Math.sin(theta); + double zPrime = w * (u * x + v * y + w * z) * (1d - Math.cos(theta)) + z * Math.cos(theta) + (-v * x + u * y) * Math.sin(theta); + + return clip(new Vector(xPrime, yPrime, zPrime), 4); + } + + /** + * Get all SIMPLE block faces from a more specific block face (SOUTH_EAST) = + * (south, east) + * + * @param f + * the block face + * @return multiple faces, or one if the face is already simple + */ + public static KList split(BlockFace f) + { + KList faces = new KList(); + + switch(f) + { + case DOWN: + faces.add(BlockFace.DOWN); + break; + case EAST: + faces.add(BlockFace.EAST); + break; + case EAST_NORTH_EAST: + faces.add(BlockFace.EAST); + faces.add(BlockFace.EAST); + faces.add(BlockFace.NORTH); + break; + case EAST_SOUTH_EAST: + faces.add(BlockFace.EAST); + faces.add(BlockFace.EAST); + faces.add(BlockFace.SOUTH); + break; + case NORTH: + faces.add(BlockFace.NORTH); + break; + case NORTH_EAST: + faces.add(BlockFace.NORTH); + faces.add(BlockFace.EAST); + break; + case NORTH_NORTH_EAST: + faces.add(BlockFace.NORTH); + faces.add(BlockFace.NORTH); + faces.add(BlockFace.EAST); + break; + case NORTH_NORTH_WEST: + faces.add(BlockFace.NORTH); + faces.add(BlockFace.NORTH); + faces.add(BlockFace.WEST); + break; + case NORTH_WEST: + faces.add(BlockFace.NORTH); + faces.add(BlockFace.WEST); + break; + case SELF: + faces.add(BlockFace.SELF); + break; + case SOUTH: + faces.add(BlockFace.SOUTH); + break; + case SOUTH_EAST: + faces.add(BlockFace.SOUTH); + faces.add(BlockFace.EAST); + break; + case SOUTH_SOUTH_EAST: + faces.add(BlockFace.SOUTH); + faces.add(BlockFace.SOUTH); + faces.add(BlockFace.EAST); + break; + case SOUTH_SOUTH_WEST: + faces.add(BlockFace.SOUTH); + faces.add(BlockFace.SOUTH); + faces.add(BlockFace.WEST); + break; + case SOUTH_WEST: + faces.add(BlockFace.SOUTH); + faces.add(BlockFace.WEST); + break; + case UP: + faces.add(BlockFace.UP); + break; + case WEST: + faces.add(BlockFace.WEST); + break; + case WEST_NORTH_WEST: + faces.add(BlockFace.WEST); + faces.add(BlockFace.WEST); + faces.add(BlockFace.NORTH); + break; + case WEST_SOUTH_WEST: + faces.add(BlockFace.WEST); + faces.add(BlockFace.WEST); + faces.add(BlockFace.SOUTH); + break; + default: + break; + } + + return faces; + } + + /** + * Get a normalized vector going from a location to another + * + * @param from + * from here + * @param to + * to here + * @return the normalized vector direction + */ + public static Vector direction(Location from, Location to) + { + return to.clone().subtract(from.clone()).toVector().normalize(); + } + + public static Vector directionNoNormal(Location from, Location to) + { + return to.clone().subtract(from.clone()).toVector(); + } + + /** + * Get the vector direction from the yaw and pitch + * + * @param yaw + * the yaw + * @param pitch + * the pitch + * @return the vector + */ + public static Vector toVector(float yaw, float pitch) + { + return new Vector(Math.cos(pitch) * Math.cos(yaw), Math.sin(pitch), Math.cos(pitch) * Math.sin(-yaw)); + } + + /** + * Add an impulse (force) to an entity + * + * @param e + * the entity + * @param v + * the vector + */ + public static void impulse(Entity e, Vector v) + { + impulse(e, v, 1.0); + } + + /** + * Add an impulse (force) on an entity + * + * @param e + * the entity + * @param v + * the vector + * @param effectiveness + * the effectiveness + */ + public static void impulse(Entity e, Vector v, double effectiveness) + { + Vector vx = e.getVelocity(); + vx.add(v.clone().multiply(effectiveness)); + e.setVelocity(vx); + } + + /** + * Reverse a direction + * + * @param v + * the direction + * @return the reversed direction + */ + public static Vector reverse(Vector v) + { + if(v.getX() != 0) + { + v.setX(-v.getX()); + } + + if(v.getY() != 0) + { + v.setY(-v.getY()); + } + + if(v.getZ() != 0) + { + v.setZ(-v.getZ()); + } + + return v; + } + + /** + * Get a speed value from a vector (velocity) + * + * @param v + * the vector + * @return the speed + */ + public static double getSpeed(Vector v) + { + Vector vi = new Vector(0, 0, 0); + Vector vt = new Vector(0, 0, 0).add(v); + + return vi.distance(vt); + } + + /** + * Shift all vectors based on the given vector + * + * @param vector + * the vector direction to shift the vectors + * @param vectors + * the vectors to be shifted + * @return the shifted vectors + */ + public static KList shift(Vector vector, KList vectors) + { + return new KList(new GListAdapter() + { + @Override + public Vector onAdapt(Vector from) + { + return from.add(vector); + } + }.adapt(vectors)); + } + + /** + * Attempt to get the blockFace for the vector (will be tri-normalized) + * + * @param v + * the vector + * @return the block face or null + */ + public static BlockFace getBlockFace(Vector v) + { + Vector p = triNormalize(v); + + for(BlockFace i : BlockFace.values()) + { + if(p.getX() == i.getModX() && p.getY() == i.getModY() && p.getZ() == i.getModZ()) + { + return i; + } + } + + for(BlockFace i : BlockFace.values()) + { + if(p.getX() == i.getModX() && p.getZ() == i.getModZ()) + { + return i; + } + } + + for(BlockFace i : BlockFace.values()) + { + if(p.getY() == i.getModY() && p.getZ() == i.getModZ()) + { + return i; + } + } + + for(BlockFace i : BlockFace.values()) + { + if(p.getX() == i.getModX() || p.getY() == i.getModY()) + { + return i; + } + } + + for(BlockFace i : BlockFace.values()) + { + if(p.getX() == i.getModX() || p.getY() == i.getModY() || p.getZ() == i.getModZ()) + { + return i; + } + } + + return null; + } + + /** + * Angle the vector in a self relative direction + * + * @param v + * the initial direction + * @param amt + * the amount to shift in the direction + * @return the shifted direction + */ + public static Vector angleLeft(Vector v, float amt) + { + Location l = new Location(Bukkit.getWorlds().get(0), 0, 0, 0); + l.setDirection(v); + float y = l.getYaw(); + float p = l.getPitch(); + CDou cy = new CDou(360); + CDou cp = new CDou(180); + cy.set(y); + cp.set(p); + cy.sub(amt); + l.setYaw((float) cy.get()); + l.setPitch((float) cp.get()); + + return l.getDirection(); + } + + /** + * Angle the vector in a self relative direction + * + * @param v + * the initial direction + * @param amt + * the amount to shift in the direction + * @return the shifted direction + */ + public static Vector angleRight(Vector v, float amt) + { + Location l = new Location(Bukkit.getWorlds().get(0), 0, 0, 0); + l.setDirection(v); + float y = l.getYaw(); + float p = l.getPitch(); + CDou cy = new CDou(360); + CDou cp = new CDou(180); + cy.set(y); + cp.set(p); + cy.add(amt); + l.setYaw((float) cy.get()); + l.setPitch((float) cp.get()); + + return l.getDirection(); + } + + /** + * Angle the vector in a self relative direction + * + * @param v + * the initial direction + * @param amt + * the amount to shift in the direction + * @return the shifted direction + */ + public static Vector angleUp(Vector v, float amt) + { + Location l = new Location(Bukkit.getWorlds().get(0), 0, 0, 0); + l.setDirection(v); + float y = l.getYaw(); + float p = l.getPitch(); + CDou cy = new CDou(360); + cy.set(y); + l.setYaw((float) cy.get()); + l.setPitch((float) Math.max(-90, p - amt)); + + return l.getDirection(); + } + + /** + * Angle the vector in a self relative direction + * + * @param v + * the initial direction + * @param amt + * the amount to shift in the direction + * @return the shifted direction + */ + public static Vector angleDown(Vector v, float amt) + { + Location l = new Location(Bukkit.getWorlds().get(0), 0, 0, 0); + l.setDirection(v); + float y = l.getYaw(); + float p = l.getPitch(); + CDou cy = new CDou(360); + cy.set(y); + l.setYaw((float) cy.get()); + l.setPitch((float) Math.min(90, p + amt)); + + return l.getDirection(); + } + + /** + * (clone) Force normalize the vector into three points, 1, 0, or -1. If the + * value is > 0.333 (1) if the value is less than -0.333 (-1) else 0 + * + * @param direction + * the direction + * @return the vector + */ + public static Vector triNormalize(Vector direction) + { + Vector v = direction.clone(); + v.normalize(); + + if(v.getX() > 0.333) + { + v.setX(1); + } + + else if(v.getX() < -0.333) + { + v.setX(-1); + } + + else + { + v.setX(0); + } + + if(v.getY() > 0.333) + { + v.setY(1); + } + + else if(v.getY() < -0.333) + { + v.setY(-1); + } + + else + { + v.setY(0); + } + + if(v.getZ() > 0.333) + { + v.setZ(1); + } + + else if(v.getZ() < -0.333) + { + v.setZ(-1); + } + + else + { + v.setZ(0); + } + + return v; + } +} diff --git a/src/main/java/ninja/bytecode/iris/wand/WandController.java b/src/main/java/ninja/bytecode/iris/wand/WandController.java new file mode 100644 index 000000000..e1e38529c --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/wand/WandController.java @@ -0,0 +1,307 @@ +package ninja.bytecode.iris.wand; + +import java.awt.Color; +import java.util.Iterator; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.util.BlockVector; +import org.bukkit.util.Vector; + +import ninja.bytecode.iris.Iris; +import ninja.bytecode.iris.object.IrisObject; +import ninja.bytecode.iris.util.Cuboid; +import ninja.bytecode.shuriken.collections.KList; +import ninja.bytecode.shuriken.math.M; + +public class WandController implements Listener +{ + public WandController() + { + Bukkit.getScheduler().scheduleSyncRepeatingTask(Iris.instance, () -> + { + for(Player i : Bukkit.getOnlinePlayers()) + { + tick(i); + } + }, 0, 0); + Bukkit.getPluginManager().registerEvents(this, Iris.instance); + } + + public void tick(Player p) + { + try + { + if(isWand(p.getInventory().getItemInMainHand())) + { + Location[] d = getCuboid(p.getInventory().getItemInMainHand()); + draw(d, p); + } + } + + catch(Throwable e) + { + + } + } + + public void draw(Location[] d, Player p) + { + Vector gx = Vector.getRandom().subtract(Vector.getRandom()).normalize().clone().multiply(0.65); + d[0].getWorld().spawnParticle(Particle.CRIT_MAGIC, d[0], 1, 0.5 + gx.getX(), 0.5 + gx.getY(), 0.5 + gx.getZ(), 0, null, false); + Vector gxx = Vector.getRandom().subtract(Vector.getRandom()).normalize().clone().multiply(0.65); + d[1].getWorld().spawnParticle(Particle.CRIT, d[1], 1, 0.5 + gxx.getX(), 0.5 + gxx.getY(), 0.5 + gxx.getZ(), 0, null, false); + + if(!d[0].getWorld().equals(d[1].getWorld())) + { + return; + } + + if(d[0].distanceSquared(d[1]) > 64 * 64) + { + return; + } + + int minx = Math.min(d[0].getBlockX(), d[1].getBlockX()); + int miny = Math.min(d[0].getBlockY(), d[1].getBlockY()); + int minz = Math.min(d[0].getBlockZ(), d[1].getBlockZ()); + int maxx = Math.max(d[0].getBlockX(), d[1].getBlockX()); + int maxy = Math.max(d[0].getBlockY(), d[1].getBlockY()); + int maxz = Math.max(d[0].getBlockZ(), d[1].getBlockZ()); + + for(double j = minx - 1; j < maxx + 1; j += 0.25) + { + for(double k = miny - 1; k < maxy + 1; k += 0.25) + { + for(double l = minz - 1; l < maxz + 1; l += 0.25) + { + if(M.r(0.2)) + { + boolean jj = j == minx || j == maxx; + boolean kk = k == miny || k == maxy; + boolean ll = l == minz || l == maxz; + double aa = j; + double bb = k; + double cc = l; + + if((jj && kk) || (jj && ll) || (ll && kk)) + { + Vector push = new Vector(0, 0, 0); + + if(j == minx) + { + push.add(new Vector(-0.55, 0, 0)); + } + + if(k == miny) + { + push.add(new Vector(0, -0.55, 0)); + } + + if(l == minz) + { + push.add(new Vector(0, 0, -0.55)); + } + + if(j == maxx) + { + push.add(new Vector(0.55, 0, 0)); + } + + if(k == maxy) + { + push.add(new Vector(0, 0.55, 0)); + } + + if(l == maxz) + { + push.add(new Vector(0, 0, 0.55)); + } + + Location lv = new Location(d[0].getWorld(), aa, bb, cc).clone().add(0.5, 0.5, 0.5).clone().add(push); + Color color = Color.getHSBColor((float) (0.5f + (Math.sin((aa + bb + cc + (p.getTicksLived() / 2)) / 20f) / 2)), 1, 1); + int r = color.getRed(); + int g = color.getGreen(); + int b = color.getBlue(); + p.spawnParticle(Particle.REDSTONE, lv.getX(), lv.getY(), lv.getZ(), 1, 0, 0, 0, 0, new Particle.DustOptions(org.bukkit.Color.fromRGB(r, g, b), 0.75f)); + } + } + } + } + } + } + + @EventHandler + public void on(PlayerInteractEvent e) + { + if(e.getHand().equals(EquipmentSlot.HAND) && isWand(e.getPlayer().getInventory().getItemInMainHand())) + { + if(e.getAction().equals(Action.LEFT_CLICK_BLOCK)) + { + e.setCancelled(true); + e.getPlayer().getInventory().setItemInMainHand(update(true, e.getClickedBlock().getLocation(), e.getPlayer().getInventory().getItemInMainHand())); + e.getPlayer().playSound(e.getClickedBlock().getLocation(), Sound.BLOCK_END_PORTAL_FRAME_FILL, 1f, 0.67f); + e.getPlayer().updateInventory(); + } + + else if(e.getAction().equals(Action.RIGHT_CLICK_BLOCK)) + { + e.setCancelled(true); + e.getPlayer().getInventory().setItemInMainHand(update(false, e.getClickedBlock().getLocation(), e.getPlayer().getInventory().getItemInMainHand())); + e.getPlayer().playSound(e.getClickedBlock().getLocation(), Sound.BLOCK_END_PORTAL_FRAME_FILL, 1f, 1.17f); + e.getPlayer().updateInventory(); + } + } + } + + public static void pasteSchematic(IrisObject s, Location at) + { + s.place(at); + } + + public static IrisObject createSchematic(ItemStack wand) + { + if(!isWand(wand)) + { + return null; + } + + try + { + Location[] f = getCuboid(wand); + Cuboid c = new Cuboid(f[0], f[1]); + IrisObject s = new IrisObject(c.getSizeX(), c.getSizeY(), c.getSizeZ()); + Iterator bb = c.iterator(); + while(bb.hasNext()) + { + Block b = bb.next(); + + if(b.getType().equals(Material.AIR)) + { + continue; + } + + BlockVector bv = b.getLocation().subtract(c.getLowerNE().toVector()).toVector().toBlockVector(); + s.setUnsigned(bv.getBlockX(), bv.getBlockY(), bv.getBlockZ(), b.getBlockData()); + } + + return s; + } + + catch(Throwable e) + { + e.printStackTrace(); + } + + return null; + } + + public static Location stringToLocation(String s) + { + try + { + String[] f = s.split("\\Q in \\E"); + String[] g = f[0].split("\\Q,\\E"); + return new Location(Bukkit.getWorld(f[1]), Integer.valueOf(g[0]), Integer.valueOf(g[1]), Integer.valueOf(g[2])); + } + + catch(Throwable e) + { + return null; + } + } + + public static String locationToString(Location s) + { + if(s == null) + { + return "<#>"; + } + + return s.getBlockX() + "," + s.getBlockY() + "," + s.getBlockZ() + " in " + s.getWorld().getName(); + } + + public static ItemStack createWand() + { + return createWand(null, null); + } + + public static ItemStack update(boolean left, Location a, ItemStack item) + { + if(!isWand(item)) + { + return item; + } + + Location[] f = getCuboid(item); + Location other = left ? f[1] : f[0]; + + if(other != null && !other.getWorld().getName().equals(a.getWorld().getName())) + { + other = null; + } + + return createWand(left ? a : other, left ? other : a); + } + + public static ItemStack createWand(Location a, Location b) + { + ItemStack is = new ItemStack(Material.BLAZE_ROD); + is.addUnsafeEnchantment(Enchantment.ARROW_INFINITE, 1); + ItemMeta im = is.getItemMeta(); + im.setDisplayName(ChatColor.BOLD + "" + ChatColor.GOLD + "Wand of Iris"); + im.setUnbreakable(true); + im.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_PLACED_ON, ItemFlag.HIDE_POTION_EFFECTS, ItemFlag.HIDE_DESTROYS, ItemFlag.HIDE_ENCHANTS); + im.setLore(new KList().add(locationToString(a), locationToString(b))); + is.setItemMeta(im); + + return is; + } + + public static boolean isWand(Player p) + { + ItemStack is = p.getInventory().getItemInMainHand(); + return !(is == null || !isWand(is)); + } + + public static Location[] getCuboid(ItemStack is) + { + ItemMeta im = is.getItemMeta(); + return new Location[] {stringToLocation(im.getLore().get(0)), stringToLocation(im.getLore().get(1))}; + } + + public static boolean isWand(ItemStack item) + { + if(!item.getType().equals(createWand().getType())) + { + return false; + } + + if(!item.getItemMeta().getEnchants().equals(createWand().getItemMeta().getEnchants())) + { + return false; + } + + if(!item.getItemMeta().getDisplayName().equals(createWand().getItemMeta().getDisplayName())) + { + return false; + } + + return true; + } +}