diff --git a/src/main/java/com/volmit/iris/Iris.java b/src/main/java/com/volmit/iris/Iris.java index c72a7ef21..a29197e41 100644 --- a/src/main/java/com/volmit/iris/Iris.java +++ b/src/main/java/com/volmit/iris/Iris.java @@ -127,7 +127,7 @@ public class Iris extends VolmitPlugin implements Listener { J.a(this::splash, 20); J.ar(this::checkConfigHotload, 60); J.sr(this::tickQueue, 0); - J.a(this::setupPapi); + J.s(this::setupPapi); } private void setupPapi() { diff --git a/src/main/java/com/volmit/iris/core/WandManager.java b/src/main/java/com/volmit/iris/core/WandManager.java index dbcdfb43a..de1bebe95 100644 --- a/src/main/java/com/volmit/iris/core/WandManager.java +++ b/src/main/java/com/volmit/iris/core/WandManager.java @@ -34,6 +34,7 @@ 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.Inventory; import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; @@ -41,6 +42,7 @@ import org.bukkit.util.BlockVector; import org.bukkit.util.Vector; import java.awt.Color; +import java.util.ArrayList; import java.util.Objects; public class WandManager implements Listener { @@ -70,10 +72,20 @@ public class WandManager implements Listener { } } + /** + * Draw the outline of a selected region + * @param d The cuboid + * @param p The player to show it to + */ public void draw(Cuboid d, Player p) { draw(new Location[]{d.getLowerNE(), d.getUpperSW()}, p); } + /** + * Draw the outline of a selected region + * @param d A pair of locations + * @param p The player to show them to + */ 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); @@ -146,7 +158,7 @@ public class WandManager implements Listener { @EventHandler public void on(PlayerInteractEvent e) { try { - if (isWand(e.getPlayer())) { + if (isHoldingWand(e.getPlayer())) { if (e.getAction().equals(Action.LEFT_CLICK_BLOCK)) { e.setCancelled(true); e.getPlayer().getInventory().setItemInMainHand(update(true, Objects.requireNonNull(e.getClickedBlock()).getLocation(), e.getPlayer().getInventory().getItemInMainHand())); @@ -160,7 +172,7 @@ public class WandManager implements Listener { } } - if (isDust(e.getPlayer())) { + if (isHoldingDust(e.getPlayer())) { if (e.getAction().equals(Action.RIGHT_CLICK_BLOCK)) { e.setCancelled(true); e.getPlayer().playSound(Objects.requireNonNull(e.getClickedBlock()).getLocation(), Sound.ENTITY_ENDER_EYE_DEATH, 2f, 1.97f); @@ -177,6 +189,11 @@ public class WandManager implements Listener { s.place(at); } + /** + * Creates an Iris Object from the 2 coordinates selected with a wand + * @param wand The wand itemstack + * @return The new object + */ public static IrisObject createSchematic(ItemStack wand) { if (!isWand(wand)) { return null; @@ -204,6 +221,11 @@ public class WandManager implements Listener { return null; } + /** + * Converts a user friendly location string to an actual Location + * @param s The string + * @return The location + */ public static Location stringToLocation(String s) { try { String[] f = s.split("\\Q in \\E"); @@ -215,18 +237,31 @@ public class WandManager implements Listener { } } - public static String locationToString(Location s) { - if (s == null) { + /** + * Get a user friendly string of a location + * @param loc The location + * @return The string + */ + public static String locationToString(Location loc) { + if (loc == null) { return "<#>"; } - return s.getBlockX() + "," + s.getBlockY() + "," + s.getBlockZ() + " in " + s.getWorld().getName(); + return loc.getBlockX() + "," + loc.getBlockY() + "," + loc.getBlockZ() + " in " + loc.getWorld().getName(); } + /** + * Create a new blank Iris wand + * @return The wand itemstack + */ public static ItemStack createWand() { return createWand(null, null); } + /** + * Create a new dust itemstack + * @return The stack + */ public static ItemStack createDust() { ItemStack is = new ItemStack(Material.GLOWSTONE_DUST); is.addUnsafeEnchantment(Enchantment.ARROW_INFINITE, 1); @@ -240,15 +275,32 @@ public class WandManager implements Listener { return is; } - public boolean isDust(Player p) { + /** + * Is the player holding Dust? + * @param p The player + * @return True if they are + */ + public boolean isHoldingDust(Player p) { ItemStack is = p.getInventory().getItemInMainHand(); return is != null && isDust(is); } + /** + * Is the itemstack passed Iris dust? + * @param is The itemstack + * @return True if it is + */ public boolean isDust(ItemStack is) { - return is.equals(dust); + return is.isSimilar(dust); } + /** + * Update the location on an Iris wand + * @param left True for first location, false for second + * @param a The location + * @param item The wand + * @return The updated wand + */ public ItemStack update(boolean left, Location a, ItemStack item) { if (!isWand(item)) { return item; @@ -264,6 +316,35 @@ public class WandManager implements Listener { return createWand(left ? a : other, left ? other : a); } + /** + * Finds an existing wand in a users inventory + * @param inventory The inventory to search + * @return The slot number the wand is in. Or -1 if none are found + */ + public static int findWand(Inventory inventory) { + ItemStack wand = createWand(); //Create blank wand + ItemMeta meta = wand.getItemMeta(); + meta.setLore(new ArrayList<>()); //We are resetting the lore as the lore differs between wands + wand.setItemMeta(meta); + + for (int s = 0; s < inventory.getSize(); s++) { + ItemStack stack = inventory.getItem(s); + if (stack == null) continue; + meta = stack.getItemMeta(); + meta.setLore(new ArrayList<>()); //Reset the lore on this too so we can compare them + stack.setItemMeta(meta); //We dont need to clone the item as items from .get are cloned + + if (wand.isSimilar(stack)) return s; //If the name, material and NBT is the same + } + return -1; + } + + /** + * Creates an Iris wand. The locations should be the currently selected locations, or null + * @param a Location A + * @param b Location B + * @return A new wand + */ public static ItemStack createWand(Location a, Location b) { ItemStack is = new ItemStack(Material.BLAZE_ROD); is.addUnsafeEnchantment(Enchantment.ARROW_INFINITE, 1); @@ -277,16 +358,31 @@ public class WandManager implements Listener { return is; } + /** + * Get a pair of locations that are selected in an Iris wand + * @param is The wand item + * @return An array with the 2 locations + */ 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(Player p) { + /** + * Is a player holding an Iris wand + * @param p The player + * @return True if they are + */ + public static boolean isHoldingWand(Player p) { ItemStack is = p.getInventory().getItemInMainHand(); return is != null && isWand(is); } + /** + * Is the itemstack passed an Iris wand + * @param is The itemstack + * @return True if it is + */ public static boolean isWand(ItemStack is) { ItemStack wand = createWand(); if (is.getItemMeta() == null) return false; diff --git a/src/main/java/com/volmit/iris/core/command/object/CommandIrisObject.java b/src/main/java/com/volmit/iris/core/command/object/CommandIrisObject.java index 58750306f..11a4cdaff 100644 --- a/src/main/java/com/volmit/iris/core/command/object/CommandIrisObject.java +++ b/src/main/java/com/volmit/iris/core/command/object/CommandIrisObject.java @@ -59,6 +59,12 @@ public class CommandIrisObject extends MortarCommand { @Command private CommandIrisObjectPaste paste; + @Command + private CommandIrisObjectUndo undo; + + @Command + private CommandIrisObjectAnalyze analyze; + public CommandIrisObject() { super("object", "iob", "o", "obj"); requiresPermission(Iris.perm); diff --git a/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectAnalyze.java b/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectAnalyze.java new file mode 100644 index 000000000..8199dfd5c --- /dev/null +++ b/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectAnalyze.java @@ -0,0 +1,151 @@ +package com.volmit.iris.core.command.object; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.core.ProjectManager; +import com.volmit.iris.core.project.loader.IrisData; +import com.volmit.iris.core.tools.IrisWorlds; +import com.volmit.iris.engine.object.IrisObject; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.plugin.MortarCommand; +import com.volmit.iris.util.plugin.VolmitSender; +import com.volmit.iris.util.scheduling.J; +import com.volmit.iris.util.scheduling.Queue; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Player; + +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; + +public class CommandIrisObjectAnalyze extends MortarCommand { + + public CommandIrisObjectAnalyze() { + super("check", "c", "analyze"); + requiresPermission(Iris.perm); + setCategory("Object"); + setDescription("Check an object's composition"); + } + + @Override + public void addTabOptions(VolmitSender sender, String[] args, KList list) { + if ((args.length == 0 || args.length == 1) && sender.isPlayer() && IrisWorlds.isIrisWorld(sender.player().getWorld())) { + IrisData data = IrisWorlds.access(sender.player().getWorld()).getData(); + if (data == null) { + sender.sendMessage("Tab complete options only work for objects while in an Iris world."); + } else if (args.length == 0) { + list.add(data.getObjectLoader().getPossibleKeys()); + } else { + list.add(data.getObjectLoader().getPossibleKeys(args[0])); + } + } + } + + @Override + protected String getArgsUsage() { + return "[name]"; + } + + @Override + public boolean handle(VolmitSender sender, String[] args) { + if (!IrisSettings.get().isStudio()) { + sender.sendMessage("To use Iris Studio Objects, please enable studio in Iris/settings.json"); + return true; + } + + if (!sender.isPlayer()) { + sender.sendMessage("Only players can spawn objects with this command"); + return true; + } + + if (args.length == 0) { + sender.sendMessage("Please specify the name of of the object want to paste"); + return true; + } + + Player p = sender.player(); + + J.a(() -> { + IrisObject obj = IrisData.loadAnyObject(args[0]); + + if (obj == null || obj.getLoadFile() == null) { + sender.sendMessage("Can't find " + args[0] + " in the " + ProjectManager.WORKSPACE_NAME + " folder"); + return; + } + + sender.sendMessage("Object Size: " + obj.getW() + " * " + obj.getH() + " * " + obj.getD() + ""); + sender.sendMessage("Blocks Used: " + NumberFormat.getIntegerInstance().format(obj.getBlocks().size())); + + Queue queue = obj.getBlocks().enqueueValues(); + Map> unsorted = new HashMap<>(); + Map amounts = new HashMap<>(); + Map materials = new HashMap<>(); + while (queue.hasNext()) { + BlockData block = queue.next(); + + //unsorted.put(block.getMaterial(), block); + + if (!amounts.containsKey(block)) { + amounts.put(block, 1); + + + } else + amounts.put(block, amounts.get(block) + 1); + + if (!materials.containsKey(block.getMaterial())) { + materials.put(block.getMaterial(), 1); + unsorted.put(block.getMaterial(), new HashSet<>()); + unsorted.get(block.getMaterial()).add(block); + } else { + materials.put(block.getMaterial(), materials.get(block.getMaterial()) + 1); + unsorted.get(block.getMaterial()).add(block); + } + + } + + List sortedMatsList = amounts.keySet().stream().map(BlockData::getMaterial) + .sorted().collect(Collectors.toList()); + Set sortedMats = new TreeSet<>(Comparator.comparingInt(materials::get).reversed()); + sortedMats.addAll(sortedMatsList); + sender.sendMessage("== Blocks in object =="); + + int n = 0; + for (Material mat : sortedMats) { + int amount = materials.get(mat); + List set = new ArrayList<>(unsorted.get(mat)); + set.sort(Comparator.comparingInt(amounts::get).reversed()); + BlockData data = set.get(0); + int dataAmount = amounts.get(data); + + String string = " - " + mat.toString() + "*" + amount; + if (data.getAsString(true).contains("[")) { + string = string + " --> [" + data.getAsString(true).split("\\[")[1] + .replaceAll("true", ChatColor.GREEN + "true" + ChatColor.GRAY) + .replaceAll("false", ChatColor.RED + "false" + ChatColor.GRAY)+ "*" + dataAmount; + } + + sender.sendMessage(string); + + n++; + + if (n >= 10) { + sender.sendMessage(" + " + (sortedMats.size() - n) + " other block types"); + return; + } + } + }); + + return true; + } + + +} diff --git a/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectContract.java b/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectContract.java index 70d876d8f..c719db546 100644 --- a/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectContract.java +++ b/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectContract.java @@ -58,7 +58,7 @@ public class CommandIrisObjectContract extends MortarCommand { Player p = sender.player(); - if (!WandManager.isWand(p)) { + if (!WandManager.isHoldingWand(p)) { sender.sendMessage("Ready your Wand."); return true; } diff --git a/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectExpand.java b/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectExpand.java index 232283154..613899e19 100644 --- a/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectExpand.java +++ b/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectExpand.java @@ -58,7 +58,7 @@ public class CommandIrisObjectExpand extends MortarCommand { Player p = sender.player(); - if (!WandManager.isWand(p)) { + if (!WandManager.isHoldingWand(p)) { sender.sendMessage("Ready your Wand."); return true; } diff --git a/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectP1.java b/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectP1.java index a84c73acd..6b07100a0 100644 --- a/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectP1.java +++ b/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectP1.java @@ -57,7 +57,7 @@ public class CommandIrisObjectP1 extends MortarCommand { Player p = sender.player(); - if (!WandManager.isWand(p)) { + if (!WandManager.isHoldingWand(p)) { sender.sendMessage("Ready your Wand."); return true; } diff --git a/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectP2.java b/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectP2.java index f3f3c183a..7e5605d94 100644 --- a/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectP2.java +++ b/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectP2.java @@ -57,7 +57,7 @@ public class CommandIrisObjectP2 extends MortarCommand { Player p = sender.player(); - if (!WandManager.isWand(p)) { + if (!WandManager.isHoldingWand(p)) { sender.sendMessage("Ready your Wand."); return true; } diff --git a/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectPaste.java b/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectPaste.java index ee5460ac1..bb60a5e5d 100644 --- a/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectPaste.java +++ b/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectPaste.java @@ -24,16 +24,37 @@ import com.volmit.iris.core.ProjectManager; import com.volmit.iris.core.WandManager; import com.volmit.iris.core.project.loader.IrisData; import com.volmit.iris.core.tools.IrisWorlds; +import com.volmit.iris.engine.object.IrisAxisRotationClamp; import com.volmit.iris.engine.object.IrisObject; +import com.volmit.iris.engine.object.IrisObjectPlacement; +import com.volmit.iris.engine.object.IrisObjectPlacementScaleInterpolator; +import com.volmit.iris.engine.object.IrisObjectRotation; +import com.volmit.iris.engine.object.IrisObjectScale; +import com.volmit.iris.engine.object.common.IObjectPlacer; +import com.volmit.iris.engine.object.tile.TileData; import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.plugin.MortarCommand; import com.volmit.iris.util.plugin.VolmitSender; +import org.bukkit.HeightMap; import org.bukkit.Location; +import org.bukkit.Material; import org.bukkit.Sound; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.TileState; +import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + public class CommandIrisObjectPaste extends MortarCommand { + public CommandIrisObjectPaste() { super("paste", "pasta", "place", "p"); requiresPermission(Iris.perm); @@ -82,27 +103,114 @@ public class CommandIrisObjectPaste extends MortarCommand { } boolean intoWand = false; + int rotate = 0; + double scale = 1; + IrisObjectPlacementScaleInterpolator interpolator = IrisObjectPlacementScaleInterpolator.NONE; - for (String i : args) { - if (i.equalsIgnoreCase("-edit")) { + for (int i = 0; i < args.length; i++) { + String str = args[i]; + if (str.equalsIgnoreCase("-edit") || str.equalsIgnoreCase("-e")) { intoWand = true; - break; + } else if (str.equalsIgnoreCase("-r") || str.equalsIgnoreCase("-rotate")) { + if (i + 1 >= args.length) { + sender.sendMessage("No rotation parameter provided! Usage is -rotate "); + return true; + } + try { + rotate = Integer.parseInt(args[i + 1]); + } catch (NumberFormatException e) { + sender.sendMessage("\"" + args[i + 1] + "\" is not a number!"); + return true; + } + } else if (str.equalsIgnoreCase("-s") || str.equalsIgnoreCase("-scale")) { + if (i + 1 >= args.length) { + sender.sendMessage("No scale parameter provided! Usage is -scale [method=linear|cubic|hermite|none]"); + return true; + } + try { + scale = Double.parseDouble(args[i + 1]); + + int max = 10; + + if (obj.getBlocks().size() > 30_000) { + max = 5; + } + if (obj.getBlocks().size() > 60_000) { + max = 3; + } + if (obj.getBlocks().size() > 90_000) { + max = 2; + } + + if (scale > max) { + sender.sendMessage("Due to size restrictions, the object will only be scaled to " + max + "x size"); + scale = max; + } + + if (i + 2 >= args.length) { + continue; //Dont parse the method and keep it at none + } + String intpol = args[i + 2]; + if (intpol.toLowerCase().startsWith("method=")) intpol = intpol.split("=", 2)[1]; + if (intpol.toLowerCase().startsWith("tri")) intpol = intpol.substring(3); + + interpolator = IrisObjectPlacementScaleInterpolator.valueOf("TRI" + intpol.toUpperCase()); + } catch (NumberFormatException e) { + sender.sendMessage("\"" + args[i + 1] + "\" is not a decimal number!"); + return true; + } catch (IllegalArgumentException e) { + sender.sendMessage("\"" + args[i + 2] + "\" is not a valid interpolator method! Must be LINEAR, CUBIC, HERMITE or NONE!"); + return true; + } + } else if (str.startsWith("-")) { + sender.sendMessage("Unknown flag \"" + args[i + 1] + "\" provided! Valid flags are -edit, -rotate and -scale"); + return true; } } + IrisObjectPlacement placement = new IrisObjectPlacement(); + if (rotate != 0) { + IrisObjectRotation rot = IrisObjectRotation.of(0, rotate, 0); + placement.setRotation(rot); + } + if (scale != 1) { + obj = obj.scaled(scale, interpolator); + } + ItemStack wand = sender.player().getInventory().getItemInMainHand(); Iris.debug("Loaded object for placement: " + "objects/" + args[0] + ".iob"); sender.player().getWorld().playSound(sender.player().getLocation(), Sound.BLOCK_ENCHANTMENT_TABLE_USE, 1f, 1.5f); - Location block = sender.player().getTargetBlock(null, 256).getLocation().clone().add(0, 1, 0); - WandManager.pasteSchematic(obj, block); + Set skipBlocks = Set.of(Material.GRASS, Material.SNOW, Material.VINE, Material.TORCH, Material.DEAD_BUSH, + Material.POPPY, Material.DANDELION); - if (intoWand && WandManager.isWand(wand)) { - wand = WandManager.createWand(block.clone().subtract(obj.getCenter()).add(obj.getW() - 1, obj.getH(), obj.getD() - 1), block.clone().subtract(obj.getCenter())); - p.getInventory().setItemInMainHand(wand); - sender.sendMessage("Updated wand for " + "objects/" + args[0] + ".iob"); + Location block = sender.player().getTargetBlock(skipBlocks, 256).getLocation().clone().add(0, 1, 0); + + //WandManager.pasteSchematic(obj, block); + + Map futureChanges = new HashMap<>(); + obj.place(block.getBlockX(), block.getBlockY() + (int)obj.getCenter().getY(), block.getBlockZ(), createPlacer(sender.player(), block.getWorld(), futureChanges), placement, new RNG(), null); + CommandIrisObjectUndo.addChanges(sender.player(), futureChanges); + + if (intoWand) { + ItemStack newWand = WandManager.createWand(block.clone().subtract(obj.getCenter()).add(obj.getW() - 1, + obj.getH() + obj.getCenter().clone().getY() - 1, obj.getD() - 1), block.clone().subtract(obj.getCenter().clone().setY(0))); + if (WandManager.isWand(wand)) { + wand = newWand; + p.getInventory().setItemInMainHand(wand); + sender.sendMessage("Updated wand for " + "objects/" + args[0] + ".iob"); + } else { + int slot = WandManager.findWand(sender.player().getInventory()); + if (slot == -1) { + p.getInventory().addItem(newWand); + sender.sendMessage("Given new wand for " + "objects/" + args[0] + ".iob"); + } else { + sender.player().getInventory().setItem(slot, newWand); + sender.sendMessage("Updated wand for " + "objects/" + args[0] + ".iob"); + } + } } else { sender.sendMessage("Placed " + "objects/" + args[0] + ".iob"); } @@ -113,6 +221,70 @@ public class CommandIrisObjectPaste extends MortarCommand { @Override protected String getArgsUsage() { - return "[name] [-edit]"; + return "[name] [-edit] [-rotate [angle]] [-scale [num] [method]]"; + } + + public static IObjectPlacer createPlacer(Player player, World world, Map futureBlockChanges) { + + return new IObjectPlacer() { + @Override + public int getHighest(int x, int z, IrisData data) { + return world.getHighestBlockYAt(x, z); + } + + @Override + public int getHighest(int x, int z, IrisData data, boolean ignoreFluid) { + return world.getHighestBlockYAt(x, z, ignoreFluid ? HeightMap.OCEAN_FLOOR: HeightMap.MOTION_BLOCKING); + } + + @Override + public void set(int x, int y, int z, BlockData d) { + Block block = world.getBlockAt(x, y, z); + + //Prevent blocks being set in or bellow bedrock + if (y <= world.getMinHeight() || block.getType() == Material.BEDROCK) return; + + futureBlockChanges.put(block, block.getBlockData()); + + block.setBlockData(d); + } + + @Override + public BlockData get(int x, int y, int z) { + return world.getBlockAt(x, y, z).getBlockData(); + } + + @Override + public boolean isPreventingDecay() { + return false; + } + + @Override + public boolean isSolid(int x, int y, int z) { + return world.getBlockAt(x, y, z).getType().isSolid(); + } + + @Override + public boolean isUnderwater(int x, int z) { + return false; + } + + @Override + public int getFluidHeight() { + return 63; + } + + @Override + public boolean isDebugSmartBore() { + return false; + } + + @Override + public void setTile(int xx, int yy, int zz, TileData tile) { + BlockState state = world.getBlockAt(xx, yy, zz).getState(); + tile.toBukkitTry(state); + state.update(); + } + }; } } diff --git a/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectShift.java b/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectShift.java index 5a31120fd..05bc6f508 100644 --- a/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectShift.java +++ b/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectShift.java @@ -58,7 +58,7 @@ public class CommandIrisObjectShift extends MortarCommand { Player p = sender.player(); - if (!WandManager.isWand(p)) { + if (!WandManager.isHoldingWand(p)) { sender.sendMessage("Ready your Wand."); return true; } diff --git a/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectUndo.java b/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectUndo.java new file mode 100644 index 000000000..6fe187a4b --- /dev/null +++ b/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectUndo.java @@ -0,0 +1,168 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2021 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.core.command.object; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.plugin.MortarCommand; +import com.volmit.iris.util.plugin.VolmitSender; +import com.volmit.iris.util.scheduling.J; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Player; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.UUID; + +public class CommandIrisObjectUndo extends MortarCommand { + + private static Map>> undos = new HashMap<>(); + + public CommandIrisObjectUndo() { + super("undo", "u", "revert"); + requiresPermission(Iris.perm); + setCategory("Object"); + setDescription("Undo an object paste "); + } + + @Override + public void addTabOptions(VolmitSender sender, String[] args, KList list) { + + } + + @Override + public boolean handle(VolmitSender sender, String[] args) { + if (!IrisSettings.get().isStudio()) { + sender.sendMessage("To use Iris Studio Objects, please enable studio in Iris/settings.json"); + return true; + } + + UUID player = null; + int amount = 1; + + for (int i = 0; i < args.length; i++) { + String str = args[i]; + if (str.equalsIgnoreCase("-u") || str.equalsIgnoreCase("-user") + || str.equalsIgnoreCase("-p") || str.equalsIgnoreCase("-player")) { + if (i + 1 >= args.length) { + sender.sendMessage("No user parameter provided! Usage is -user "); + return true; + } + + OfflinePlayer p = Bukkit.getOfflinePlayer(args[i + 1]); + if (!p.hasPlayedBefore()) { + sender.sendMessage("\"" + args[i + 1] + "\" is not a player that has played before!"); + return true; + } + player = p.getUniqueId(); + } else if (str.equalsIgnoreCase("-n") || str.equalsIgnoreCase("-number")) { + if (i + 1 >= args.length) { + sender.sendMessage("No number parameter provided! Usage is -number "); + return true; + } + try { + amount = Integer.parseInt(args[i + 1]); + } catch (NumberFormatException e) { + sender.sendMessage("\"" + args[i + 1] + "\" is not a number!"); + return true; + } + } else if (str.startsWith("-")) { + sender.sendMessage("Unknown flag \"" + args[i + 1] + "\" provided! Valid flags are -number and -user"); + return true; + } + } + + if (!sender.isPlayer() && player == null) { + sender.sendMessage("Please specify a player to revert!"); + return true; + } else if (sender.isPlayer()) { + player = sender.player().getUniqueId(); + } + + if (amount < 0) { + sender.sendMessage("Please specify an amount greater than 0!"); + return true; + } + + if (!undos.containsKey(player) || undos.get(player).size() == 0) { + sender.sendMessage("No pastes to undo"); + return true; + } + + int actualReverts = Math.min(undos.get(player).size(), amount); + revertChanges(player, amount); + sender.sendMessage("Reverted " + actualReverts + " pastes!"); + + return true; + } + + @Override + protected String getArgsUsage() { + return "[-number [num]] [-user [username]]"; + } + + public static void addChanges(Player player, Map oldBlocks) { + if (!undos.containsKey(player.getUniqueId())) { + undos.put(player.getUniqueId(), new ArrayDeque<>()); + } + + undos.get(player.getUniqueId()).add(oldBlocks); + } + + public static void revertChanges(UUID player, int amount) { + loopChange(player, amount); + } + + private static void loopChange(UUID uuid, int amount) { + Deque> queue = undos.get(uuid); + if (queue != null && queue.size() > 0) { + revert(queue.pollLast()); + if (amount > 1) { + J.s(() -> loopChange(uuid, amount - 1), 2); + } + } + } + + /** + * Reverts all the block changes provided, 200 blocks per tick + * @param blocks The blocks to remove + */ + private static void revert(Map blocks) { + int amount = 0; + Iterator> it = blocks.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = it.next(); + BlockData data = entry.getValue(); + entry.getKey().setBlockData(data, false); + it.remove(); + + amount++; + + if (amount > 200) { + J.s(() -> revert(blocks), 1); + } + } + } +} diff --git a/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectXAY.java b/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectXAY.java index 7a4d41a0f..24acc4abc 100644 --- a/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectXAY.java +++ b/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectXAY.java @@ -60,7 +60,7 @@ public class CommandIrisObjectXAY extends MortarCommand { Player p = sender.player(); - if (!WandManager.isWand(p)) { + if (!WandManager.isHoldingWand(p)) { sender.sendMessage("Ready your Wand."); return true; } diff --git a/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectXPY.java b/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectXPY.java index caed13059..b142e9649 100644 --- a/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectXPY.java +++ b/src/main/java/com/volmit/iris/core/command/object/CommandIrisObjectXPY.java @@ -59,7 +59,7 @@ public class CommandIrisObjectXPY extends MortarCommand { Player p = sender.player(); - if (!WandManager.isWand(p)) { + if (!WandManager.isHoldingWand(p)) { sender.sendMessage("Ready your Wand."); return true; } diff --git a/src/main/java/com/volmit/iris/core/command/world/CommandIrisCreate.java b/src/main/java/com/volmit/iris/core/command/world/CommandIrisCreate.java index 2bf1f3fe2..424aa3545 100644 --- a/src/main/java/com/volmit/iris/core/command/world/CommandIrisCreate.java +++ b/src/main/java/com/volmit/iris/core/command/world/CommandIrisCreate.java @@ -131,6 +131,12 @@ public class CommandIrisCreate extends MortarCommand { pregen.set(i.startsWith("pregen=") ? getVal(i.split("\\Q=\\E")[1]) : pregen.get()); } + if (worldName.equalsIgnoreCase("iris")) { + sender.sendMessage("You cannot use the world name \"iris\" for creating worlds as Iris uses this directory for studio worlds."); + sender.sendMessage("May we suggest the name \"IrisWorld\" instead?"); + return true; + } + Iris.linkMultiverseCore.assignWorldType(worldName, type); final AtomicReference world = new AtomicReference<>(); IrisDimension dim; diff --git a/src/main/java/com/volmit/iris/engine/data/B.java b/src/main/java/com/volmit/iris/engine/data/B.java index c141a5751..9ef93defb 100644 --- a/src/main/java/com/volmit/iris/engine/data/B.java +++ b/src/main/java/com/volmit/iris/engine/data/B.java @@ -27,6 +27,11 @@ import org.bukkit.Material; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.type.Leaves; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + public class B { private static final Material AIR_MATERIAL = Material.AIR; private static final BlockData AIR = AIR_MATERIAL.createBlockData(); @@ -157,26 +162,67 @@ public class B { blockDataCache.put(ix, bx); return bx; - } catch (Throwable e) { - Iris.reportError(e); + } catch (Exception e) { + //Iris.reportError(e); + Iris.debug("Failed to load block \"" + ix + "\""); + String block = ix.contains(":") ? ix.split(":")[1].toLowerCase() : ix.toLowerCase(); + String state = block.contains("[") ? block.split("\\[")[1].split("\\]")[0] : ""; + Map stateMap = new HashMap<>(); + if (!state.equals("")) { + Arrays.stream(state.split(",")).forEach(s -> { + stateMap.put(s.split("=")[0], s.split("=")[1]); + }); + } + block = block.split("\\[")[0]; + + switch (block) { + case "cauldron" -> block = "water_cauldron"; //Would fail to load if it has a level parameter + case "grass_path" -> block = "dirt_path"; + case "concrete" -> block = "white_concrete"; + case "wool" -> block = "white_wool"; + case "beetroots" -> { + if (stateMap.containsKey("age")) { + String updated = stateMap.get("age"); + switch (updated) { + case "7" -> updated = "3"; + case "3", "4", "5" -> updated = "2"; + case "1", "2" -> updated = "1"; + } + stateMap.put("age", updated); + } + } + } + + Map newStates = new HashMap<>(); + for (String key : stateMap.keySet()) { //Iterate through every state and check if its valid + try { + String newState = block + "[" + key + "=" + stateMap.get(key) + "]"; + Bukkit.createBlockData(newState); + + //If we get to here, the state is okay so we can use it + newStates.put(key, stateMap.get(key)); + + } catch (IllegalArgumentException ignored) { } + } + + //Combine all the "good" states again + state = newStates.entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()).collect(Collectors.joining(",")); + if (!state.equals("")) state = "[" + state + "]"; + String newBlock = block + state; + Iris.debug("Converting " + ix + " to " + newBlock); + + try { + BlockData bd = Bukkit.createBlockData(newBlock); + blockDataCache.put(ix, bd); + return bd; + } catch (Throwable e1) { + Iris.reportError(e1); + } + + nullBlockDataCache.add(ix); + return null; } - - String i = ix.toUpperCase().trim(); - i = i.equals("GRASS_PATH") ? "DIRT_PATH" : i; - i = i.equals("WOOL") ? "WHITE_WOOL" : i; - i = i.equals("CONCRETE") ? "WHITE_CONCRETE" : i; - - try { - BlockData bd = Material.valueOf(i).createBlockData(); - blockDataCache.put(ix, bd); - } catch (Throwable e) { - Iris.reportError(e); - - } - - nullBlockDataCache.add(ix); - return null; } private static BlockData parseBlockData(String ix) { diff --git a/src/main/java/com/volmit/iris/engine/object/IrisEntity.java b/src/main/java/com/volmit/iris/engine/object/IrisEntity.java index eaec31d86..c48d22db5 100644 --- a/src/main/java/com/volmit/iris/engine/object/IrisEntity.java +++ b/src/main/java/com/volmit/iris/engine/object/IrisEntity.java @@ -136,6 +136,12 @@ public class IrisEntity extends IrisRegistrant { @Desc("The this entity is ageable, set it's baby status") private boolean baby = false; + @Desc("If the entity should never be culled. Useful for Jigsaws") + private boolean keepEntity = false; + + @Desc("The surface type to spawn this mob on") + private IrisSurface surface = IrisSurface.LAND; + public Entity spawn(Engine gen, Location at) { return spawn(gen, at, new RNG(at.hashCode())); } @@ -154,10 +160,14 @@ public class IrisEntity extends IrisRegistrant { e.setGravity(isGravity()); e.setInvulnerable(isInvulnerable()); e.setSilent(isSilent()); + e.setPersistent(isKeepEntity()); int gg = 0; for (IrisEntity i : passengers) { - e.addPassenger(i.spawn(gen, at, rng.nextParallelRNG(234858 + gg++))); + Entity passenger = i.spawn(gen, at, rng.nextParallelRNG(234858 + gg++)); + if (!Bukkit.isPrimaryThread()) { + J.s(() -> e.addPassenger(passenger)); + } } if (e instanceof Attributable) { diff --git a/src/main/java/com/volmit/iris/engine/object/IrisEntitySpawn.java b/src/main/java/com/volmit/iris/engine/object/IrisEntitySpawn.java index 6314f50a5..cf2e3b38d 100644 --- a/src/main/java/com/volmit/iris/engine/object/IrisEntitySpawn.java +++ b/src/main/java/com/volmit/iris/engine/object/IrisEntitySpawn.java @@ -38,7 +38,9 @@ import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import org.bukkit.Chunk; +import org.bukkit.HeightMap; import org.bukkit.Location; +import org.bukkit.block.Block; import org.bukkit.entity.Entity; @Accessors(chain = true) @@ -105,8 +107,8 @@ public class IrisEntitySpawn implements IRare { }; if (l != null) { - spawn100(gen, l); - s++; + if (spawn100(gen, l) != null) + s++; } } } @@ -132,8 +134,11 @@ public class IrisEntitySpawn implements IRare { private Entity spawn100(Engine g, Location at) { try { - Location l = at.clone().add(0.5, 1, 0.5); - Entity e = getRealEntity(g).spawn(g, l, rng.aquire(() -> new RNG(g.getTarget().getWorld().seed() + 4))); + IrisEntity irisEntity = getRealEntity(g); + + if (!irisEntity.getSurface().matches(at.clone().subtract(0, 1, 0).getBlock().getState())) return null; //Make sure it can spawn on the block + + Entity e = irisEntity.spawn(g, at.add(0.5, 0, 0.5), rng.aquire(() -> new RNG(g.getTarget().getWorld().seed() + 4))); if (e != null) { Iris.debug("Spawned " + C.DARK_AQUA + "Entity<" + getEntity() + "> " + C.GREEN + e.getType() + C.LIGHT_PURPLE + " @ " + C.GRAY + e.getLocation().getX() + ", " + e.getLocation().getY() + ", " + e.getLocation().getZ()); } diff --git a/src/main/java/com/volmit/iris/engine/object/IrisObject.java b/src/main/java/com/volmit/iris/engine/object/IrisObject.java index c6686580c..ee623875f 100644 --- a/src/main/java/com/volmit/iris/engine/object/IrisObject.java +++ b/src/main/java/com/volmit/iris/engine/object/IrisObject.java @@ -787,6 +787,8 @@ public class IrisObject extends IrisRegistrant { IrisPosition l1 = getAABB().max(); IrisPosition l2 = getAABB().min(); + @SuppressWarnings({"unchecked", "rawtypes"}) HashMap placeBlock = new HashMap(); + Vector center = getCenter(); if (getH() == 2) { center = center.setY(center.getBlockY() + 0.5); @@ -797,14 +799,13 @@ public class IrisObject extends IrisRegistrant { if (getD() == 2) { center = center.setZ(center.getBlockZ() + 0.5); } - @SuppressWarnings({"unchecked", "rawtypes"}) HashMap placeBlock = new HashMap(); IrisObject oo = new IrisObject((int) Math.ceil((w * scale) + (scale * 2)), (int) Math.ceil((h * scale) + (scale * 2)), (int) Math.ceil((d * scale) + (scale * 2))); for (Map.Entry entry : blocks.entrySet()) { BlockData bd = entry.getValue(); placeBlock.put(entry.getKey().clone().add(HALF).subtract(center) - .multiply(scale).toBlockVector(), bd); + .multiply(scale).add(sm1).toBlockVector(), bd); } for (Map.Entry entry : placeBlock.entrySet()) { diff --git a/src/main/java/com/volmit/iris/engine/object/IrisSurface.java b/src/main/java/com/volmit/iris/engine/object/IrisSurface.java new file mode 100644 index 000000000..27c2bb523 --- /dev/null +++ b/src/main/java/com/volmit/iris/engine/object/IrisSurface.java @@ -0,0 +1,50 @@ +package com.volmit.iris.engine.object; + +import com.volmit.iris.Iris; +import com.volmit.iris.engine.object.annotations.Desc; +import org.bukkit.Material; +import org.bukkit.block.BlockState; +import org.bukkit.block.data.Waterlogged; + +@Desc("The type of surface entities should spawn on") +public enum IrisSurface { + + @Desc("Land surfaces") + LAND, + + @Desc("Any surfaces animals can spawn on, such as dirt, grass and podzol") + ANIMAL, + + @Desc("Within the water") + WATER, + + @Desc("On land or on water") + OVERWORLD, + + @Desc("Within lava") + LAVA; + + /** + * Check if this Iris surface matches the blockstate provided + * @param state The blockstate + * @return True if it matches + */ + public boolean matches(BlockState state) { + Material type = state.getType(); + if (type.isSolid()) { + return this == LAND || this == OVERWORLD || (this == ANIMAL + && (type == Material.GRASS_BLOCK || type == Material.DIRT + || type == Material.DIRT_PATH || type == Material.COARSE_DIRT + || type == Material.ROOTED_DIRT || type == Material.PODZOL + || type == Material.MYCELIUM || type == Material.SNOW_BLOCK)); + } + if (type == Material.LAVA) return this == LAVA; + if (type == Material.WATER || type == Material.SEAGRASS + || type == Material.TALL_SEAGRASS || type == Material.KELP_PLANT + || type == Material.KELP || + (state instanceof Waterlogged && ((Waterlogged) state).isWaterlogged())) + return this == WATER || this == OVERWORLD; + + return false; + } +}