diff --git a/src/main/java/com/volmit/iris/Iris.java b/src/main/java/com/volmit/iris/Iris.java index f5ff4df3b..d571b5e59 100644 --- a/src/main/java/com/volmit/iris/Iris.java +++ b/src/main/java/com/volmit/iris/Iris.java @@ -55,8 +55,7 @@ import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.Queue; import com.volmit.iris.util.scheduling.ShurikenQueue; import io.papermc.lib.PaperLib; -import org.bukkit.Bukkit; -import org.bukkit.World; +import org.bukkit.*; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -69,7 +68,7 @@ import org.bukkit.plugin.Plugin; import java.io.*; import java.lang.annotation.Annotation; import java.net.URL; -import java.util.Date; +import java.util.*; @SuppressWarnings("CanBeFinal") public class Iris extends VolmitPlugin implements Listener { @@ -140,6 +139,8 @@ public class Iris extends VolmitPlugin implements Listener { configWatcher = new FileWatcher(getDataFile("settings.json")); getServer().getPluginManager().registerEvents(new CommandLocate(), this); getServer().getPluginManager().registerEvents(new WandManager(), this); + getServer().getPluginManager().registerEvents(new DolphinManager(), this); + getServer().getPluginManager().registerEvents(new VillagerManager(), this); super.onEnable(); Bukkit.getPluginManager().registerEvents(this, this); J.s(this::lateBind); diff --git a/src/main/java/com/volmit/iris/core/DolphinManager.java b/src/main/java/com/volmit/iris/core/DolphinManager.java new file mode 100644 index 000000000..3a2041946 --- /dev/null +++ b/src/main/java/com/volmit/iris/core/DolphinManager.java @@ -0,0 +1,28 @@ +package com.volmit.iris.core; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.tools.IrisToolbelt; +import org.bukkit.Material; +import org.bukkit.entity.EntityType; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerInteractEntityEvent; + +public class DolphinManager implements Listener { + + /** + * Prevents dolphins from being fed, to locate a treasure map. + * Note: This results in odd dolphin behaviour, but it's the best we can do. + */ + @EventHandler + public void on(PlayerInteractEntityEvent event){ + if (!IrisToolbelt.isIrisWorld(event.getPlayer().getWorld())){ + return; + } + + Material hand = event.getPlayer().getInventory().getItem(event.getHand()).getType(); + if (event.getRightClicked().getType().equals(EntityType.DOLPHIN) && (hand.equals(Material.TROPICAL_FISH) || hand.equals(Material.PUFFERFISH) || hand.equals(Material.COD) || hand.equals(Material.SALMON))){ + event.setCancelled(true); + } + } +} diff --git a/src/main/java/com/volmit/iris/core/VillagerManager.java b/src/main/java/com/volmit/iris/core/VillagerManager.java new file mode 100644 index 000000000..c7bd8bfda --- /dev/null +++ b/src/main/java/com/volmit/iris/core/VillagerManager.java @@ -0,0 +1,45 @@ +package com.volmit.iris.core; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.tools.IrisToolbelt; +import com.volmit.iris.engine.object.villager.IrisVillagerOverride; +import com.volmit.iris.engine.object.villager.IrisVillagerTrade; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.VillagerAcquireTradeEvent; + +public class VillagerManager implements Listener { + /** + * Replace or disable villager trade add event to prevent explorer map + */ + @EventHandler + public void on(VillagerAcquireTradeEvent event){ + if (!IrisToolbelt.isIrisWorld((event.getEntity().getWorld()))){ + return; + } + + // Iris.info("Trade event: type " + event.getRecipe().getResult().getType() + " / meta " + event.getRecipe().getResult().getItemMeta() + " / data " + event.getRecipe().getResult().getData()); + if (!event.getRecipe().getResult().getType().equals(Material.FILLED_MAP)) { + return; + } + + IrisVillagerOverride override = IrisToolbelt.access(event.getEntity().getWorld()).getCompound().getRootDimension().getPatchCartographers(); + + if (override.isDisableTrade()){ + event.setCancelled(true); + Iris.debug("Cancelled cartographer trade @ " + event.getEntity().getLocation()); + return; + } + + if (override.getValidItems() == null){ + event.setCancelled(true); + Iris.debug("Cancelled cartographer trade because no override items are valid @ " + event.getEntity().getLocation()); + return; + } + + IrisVillagerTrade trade = override.getValidItems().getRandom(); + event.setRecipe(trade.convert()); + Iris.debug("Overrode cartographer trade with: " + trade + " to prevent allowing cartography map trades"); + } +} diff --git a/src/main/java/com/volmit/iris/core/project/SchemaBuilder.java b/src/main/java/com/volmit/iris/core/project/SchemaBuilder.java index b279ed987..838560c11 100644 --- a/src/main/java/com/volmit/iris/core/project/SchemaBuilder.java +++ b/src/main/java/com/volmit/iris/core/project/SchemaBuilder.java @@ -28,6 +28,9 @@ import com.volmit.iris.util.data.B; import com.volmit.iris.util.json.JSONArray; import com.volmit.iris.util.json.JSONObject; import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.material.MaterialData; import org.bukkit.potion.PotionEffectType; import java.awt.*; @@ -529,7 +532,7 @@ public class SchemaBuilder { } private String getType(Class c) { - if (c.equals(int.class) || c.equals(Integer.class) || c.equals(long.class)) { + if (c.equals(int.class) || c.equals(Integer.class) || c.equals(long.class) || c.equals(byte.class)) { return "integer"; } @@ -549,7 +552,7 @@ public class SchemaBuilder { return "array"; } - if (c.equals(KMap.class)) { + if (c.equals(KMap.class) || c.equals(ItemStack.class) || c.equals(ItemMeta.class) || c.equals(MaterialData.class)) { return "object"; } @@ -561,10 +564,16 @@ public class SchemaBuilder { } private String getFieldDescription(Field r) { + if (r.isAnnotationPresent(Desc.class)) { return r.getDeclaredAnnotation(Desc.class).value(); } + // suppress warnings on bukkit classes + if (r.getDeclaringClass().getName().startsWith("org.bukkit.")){ + return "Bukkit package classes and enums have no descriptions"; + } + warnings.addIfMissing("Missing @Desc on field " + r.getName() + " (" + r.getType() + ") in " + r.getDeclaringClass().getCanonicalName()); return "No Field Description"; } diff --git a/src/main/java/com/volmit/iris/engine/object/dimensional/IrisDimension.java b/src/main/java/com/volmit/iris/engine/object/dimensional/IrisDimension.java index c78be70e0..34b8ac4e9 100644 --- a/src/main/java/com/volmit/iris/engine/object/dimensional/IrisDimension.java +++ b/src/main/java/com/volmit/iris/engine/object/dimensional/IrisDimension.java @@ -34,6 +34,7 @@ import com.volmit.iris.engine.object.carve.IrisCaveFluid; import com.volmit.iris.engine.object.carve.IrisCaveLayer; import com.volmit.iris.engine.object.carve.IrisCaverns; import com.volmit.iris.engine.object.deposits.IrisDepositGenerator; +import com.volmit.iris.engine.object.villager.IrisVillagerOverride; import com.volmit.iris.engine.object.feature.IrisFeaturePositional; import com.volmit.iris.engine.object.feature.IrisFeaturePotential; import com.volmit.iris.engine.object.jigsaw.IrisJigsawStructure; @@ -330,6 +331,9 @@ public class IrisDimension extends IrisRegistrant { @Desc("Define biome mutations for this dimension") private KList mutations = new KList<>(); + @Desc("Cartographer map trade overrides") + private IrisVillagerOverride patchCartographers = new IrisVillagerOverride().setDisableTrade(false); + private final transient AtomicCache parallaxSize = new AtomicCache<>(); private final transient AtomicCache rockLayerGenerator = new AtomicCache<>(); private final transient AtomicCache fluidLayerGenerator = new AtomicCache<>(); diff --git a/src/main/java/com/volmit/iris/engine/object/villager/IrisVillagerOverride.java b/src/main/java/com/volmit/iris/engine/object/villager/IrisVillagerOverride.java new file mode 100644 index 000000000..2f2a2c6c4 --- /dev/null +++ b/src/main/java/com/volmit/iris/engine/object/villager/IrisVillagerOverride.java @@ -0,0 +1,52 @@ +package com.volmit.iris.engine.object.villager; + +import com.volmit.iris.engine.object.annotations.ArrayType; +import com.volmit.iris.engine.object.annotations.DependsOn; +import com.volmit.iris.engine.object.annotations.Desc; +import com.volmit.iris.engine.object.annotations.Required; +import com.volmit.iris.util.collection.KList; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor + +@Desc("Override cartographer map trades with others or disable the trade altogether") +@Data +@EqualsAndHashCode(callSuper = false) +public class IrisVillagerOverride { + @Desc(""" + Disable the trade altogether. + If a cartographer villager gets a new explorer map trade: + If this is enabled -> the trade is removed + If this is disabled -> the trade is replaced with the "override" setting below + Default is true, so if you omit this, trades will be removed.""") + private boolean disableTrade = true; + + @DependsOn("disableTrade") + @Required + @Desc(""" + The items to override the cartographer trade with. + By default, this is: + 3 emeralds + 3 glass blocks -> 1 spyglass. + Can trade 3 to 5 times""") + @ArrayType(min = 1, type = IrisVillagerTrade.class) + private KList items = new KList<>(new IrisVillagerTrade() + .setIngredient1(new ItemStack(Material.EMERALD, 3)) + .setIngredient2(new ItemStack(Material.GLASS, 3)) + .setResult(new ItemStack(Material.SPYGLASS)) + .setMinTrades(3) + .setMaxTrades(5)); + + public KList getValidItems(){ + KList valid = new KList<>(); + getItems().stream().filter(IrisVillagerTrade::isValidItems).forEach(valid::add); + return valid.size() == 0 ? null : valid; + } +} diff --git a/src/main/java/com/volmit/iris/engine/object/villager/IrisVillagerTrade.java b/src/main/java/com/volmit/iris/engine/object/villager/IrisVillagerTrade.java new file mode 100644 index 000000000..e11356d0b --- /dev/null +++ b/src/main/java/com/volmit/iris/engine/object/villager/IrisVillagerTrade.java @@ -0,0 +1,130 @@ +package com.volmit.iris.engine.object.villager; + + +import com.volmit.iris.Iris; +import com.volmit.iris.engine.object.annotations.*; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.math.M; +import com.volmit.iris.util.math.RNG; +import com.volmit.iris.util.scheduling.S; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; + +import java.util.List; + + +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor + +@SuppressWarnings("BooleanMethodIsAlwaysInverted") +@Desc("Represents a villager trade.") +@Data +@EqualsAndHashCode(callSuper = false) +public class IrisVillagerTrade { + + @Required + @RegistryListItemType + @Desc("The first, required, ingredient for the trade.\nNote: this MUST be an item, and may not be a non-obtainable block!") + private ItemStack ingredient1; + + @RegistryListItemType + @Desc("The second, optional, ingredient for the trade.\nNote: this MUST be an item, and may not be a non-obtainable block!") + private ItemStack ingredient2 = null; + + @Required + @RegistryListItemType + @Desc("The result of the trade.\nNote: this MUST be an item, and may not be a non-obtainable block!") + private ItemStack result; + + @Desc("The min amount of times this trade can be done. Default 3") + @MinNumber(1) + @MaxNumber(64) + private int minTrades = 3; + + @Desc("The max amount of times this trade can be done. Default 5") + @MinNumber(1) + @MaxNumber(64) + private int maxTrades = 5; + + /** + * @return true if:
+ * ingredient 1 & result are non-null,
+ * mintrades > 0, maxtrades > 0, maxtrades > mintrades, and
+ * ingredient 1, (if defined ingredient 2) and the result are valid items + */ + public boolean isValidItems(){ + KList warnings = new KList<>(); + if (ingredient1 == null) { + warnings.add("Ingredient 1 is null"); + } + + if (result == null) { + warnings.add("Result is null"); + } + + if (minTrades <= 0) { + warnings.add("Negative minimal trades"); + } + + if (maxTrades <= 0) { + warnings.add("Negative maximal trades"); + } + + if (minTrades > maxTrades) { + warnings.add("More minimal than maximal trades"); + } + + if (ingredient1 != null && !ingredient1.getType().isItem()){ + warnings.add("Ingredient 1 is not an item"); + } + + if (ingredient2 != null && !ingredient2.getType().isItem()){ + warnings.add("Ingredient 2 is not an item"); + } + + if (result != null && !result.getType().isItem()){ + warnings.add("Result is not an item"); + } + + if (warnings.isEmpty()) { + return true; + } else { + Iris.warn("Faulty item in cartographer item overrides: " + this); + warnings.forEach(w -> Iris.warn(" " + w)); + return false; + } + } + + /** + * Get the ingredients + * @return The list of 1 or 2 ingredients (depending on if ing2 is null) + */ + public List getIngredients() { + if (!isValidItems()){ + return null; + } + return ingredient2 == null ? new KList<>(ingredient1) : new KList<>(ingredient1, ingredient2); + } + + /** + * @return the amount of trades (RNG.r.i(min, max)) + */ + public int getAmount() { + return RNG.r.i(minTrades, maxTrades); + } + + /** + * @return the trade as a merchant recipe + */ + public MerchantRecipe convert(){ + MerchantRecipe recipe = new MerchantRecipe(getResult(), getAmount()); + recipe.setIngredients(getIngredients()); + return recipe; + } +}