From 4c8dc589eb1a5a94207f363fbaad29e90854bcdf Mon Sep 17 00:00:00 2001 From: besiece Date: Sun, 8 Nov 2026 14:32:57 +0100 Subject: [PATCH] refactor: simplify wandering trader offers and add individual spawn chances --- .../gameplay/wanderingTrader/TraderOffer.java | 32 +++++++++++++++++++ .../WanderingTraderSpawnListener.java | 2 +- .../WanderingTraderTrades.java | 23 +++++-------- 3 files changed, 41 insertions(+), 16 deletions(-) create mode 100644 craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/wanderingTrader/TraderOffer.java diff --git a/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/wanderingTrader/TraderOffer.java b/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/wanderingTrader/TraderOffer.java new file mode 100644 index 0000000..9edb031 --- /dev/null +++ b/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/wanderingTrader/TraderOffer.java @@ -0,0 +1,32 @@ +package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.wanderingTrader; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; + +import java.util.Objects; +import java.util.function.Supplier; + +record TraderOffer(Supplier resultFactory, int emeraldPrice, int maxUses, double chance) { + TraderOffer { + Objects.requireNonNull(resultFactory, "resultFactory"); + if(emeraldPrice < 1 || emeraldPrice > 64) + throw new IllegalArgumentException("emeraldPrice must be between 1 and 64"); + if(maxUses < 1) + throw new IllegalArgumentException("maxUses must be positive"); + if(!Double.isFinite(chance) || chance < 0 || chance > 1) + throw new IllegalArgumentException("chance must be finite and between 0 and 1"); + } + + static TraderOffer offer(Material material, int emeraldPrice, int maxUses, double chance) { + Objects.requireNonNull(material, "material"); + return new TraderOffer(() -> new ItemStack(material), emeraldPrice, maxUses, chance); + } + + MerchantRecipe createRecipe() { + ItemStack result = Objects.requireNonNull(this.resultFactory.get(), "resultFactory result").clone(); + MerchantRecipe recipe = new MerchantRecipe(result, 0, this.maxUses, false, 0, 0f); + recipe.addIngredient(new ItemStack(Material.EMERALD, this.emeraldPrice)); + return recipe; + } +} diff --git a/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/wanderingTrader/WanderingTraderSpawnListener.java b/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/wanderingTrader/WanderingTraderSpawnListener.java index 71702a6..20d31d0 100644 --- a/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/wanderingTrader/WanderingTraderSpawnListener.java +++ b/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/wanderingTrader/WanderingTraderSpawnListener.java @@ -13,7 +13,7 @@ class WanderingTraderSpawnListener extends ApplianceListener this.getAppliance().addTrades(trader)); } } diff --git a/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/wanderingTrader/WanderingTraderTrades.java b/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/wanderingTrader/WanderingTraderTrades.java index ca46b0e..21bb1dc 100644 --- a/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/wanderingTrader/WanderingTraderTrades.java +++ b/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/wanderingTrader/WanderingTraderTrades.java @@ -6,37 +6,30 @@ import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.entity.WanderingTrader; import org.bukkit.event.Listener; -import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.MerchantRecipe; import org.bukkit.persistence.PersistentDataType; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; -import java.util.function.Supplier; +import java.util.concurrent.ThreadLocalRandom; public class WanderingTraderTrades extends Appliance { - private static final int HEART_OF_THE_SEA_PRICE = 32; - private static final int HEART_OF_THE_SEA_MAX_USES = 1; - private final NamespacedKey appliedKey = new NamespacedKey(Main.instance(), "wandering_trader_trades_applied"); - private final List> additionalTrades = List.of(WanderingTraderTrades::heartOfTheSeaTrade); - - private static MerchantRecipe heartOfTheSeaTrade() { - MerchantRecipe recipe = new MerchantRecipe( - new ItemStack(Material.HEART_OF_THE_SEA), 0, HEART_OF_THE_SEA_MAX_USES, false, 0, 0f - ); - recipe.addIngredient(new ItemStack(Material.EMERALD, HEART_OF_THE_SEA_PRICE)); - return recipe; - } + private final List additionalTrades = List.of( + TraderOffer.offer(Material.HEART_OF_THE_SEA, 32, 1, 0.5) + ); void addTrades(WanderingTrader trader) { if(!trader.isValid() || trader.isDead()) return; if(trader.getPersistentDataContainer().has(this.appliedKey, PersistentDataType.BYTE)) return; List recipes = new ArrayList<>(trader.getRecipes()); - this.additionalTrades.forEach(factory -> recipes.add(factory.get())); + this.additionalTrades.forEach(offer -> { + if(ThreadLocalRandom.current().nextDouble() < offer.chance()) recipes.add(offer.createRecipe()); + }); trader.setRecipes(recipes); + // Mark this trader as having had custom trades applied. trader.getPersistentDataContainer().set(this.appliedKey, PersistentDataType.BYTE, (byte) 1); }