From b2ab2172640d1a147c83cc9e1e47925ab6de48fb Mon Sep 17 00:00:00 2001 From: Robert Lang Date: Thu, 7 May 2026 18:26:16 +0200 Subject: [PATCH 1/8] Trades can now have an optional chance parameter, that gives a percentage for how likely a villager would be able to adopt a new trade. --- .../crafttweaker/merchant/MerchantTrade.java | 41 +++++++++++++++++-- .../merchant/MerchantTradeHandler.java | 26 +++++++++++- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantTrade.java b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantTrade.java index c97b331..d78773b 100644 --- a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantTrade.java +++ b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantTrade.java @@ -2,15 +2,25 @@ import net.minecraft.entity.passive.EntityVillager; import net.minecraft.item.ItemStack; +import net.minecraft.util.math.MathHelper; import net.minecraft.village.MerchantRecipe; import net.minecraftforge.fml.common.registry.VillagerRegistry; + +import java.util.Random; + public class MerchantTrade { private VillagerRegistry.VillagerProfession profession; private VillagerRegistry.VillagerCareer career; private MerchantRecipe recipe; private int level; + private float chance; + + public MerchantTrade(VillagerRegistry.VillagerProfession profession, VillagerRegistry.VillagerCareer career, ItemStack buy1, ItemStack buy2, + ItemStack sell, int level, float chance) { + this(profession, career, new MerchantRecipe(buy1, buy2, sell), level, chance); + } public MerchantTrade(VillagerRegistry.VillagerProfession profession, VillagerRegistry.VillagerCareer career, ItemStack buy1, ItemStack buy2, ItemStack sell, int level) { @@ -22,6 +32,15 @@ public MerchantTrade(VillagerRegistry.VillagerProfession profession, VillagerReg this.career = career; this.recipe = recipe; this.level = level; + this.chance = 100.0f; + } + + public MerchantTrade(VillagerRegistry.VillagerProfession profession, VillagerRegistry.VillagerCareer career, MerchantRecipe recipe, int level, float chance) { + this.profession = profession; + this.career = career; + this.recipe = recipe; + this.level = level; + this.chance = chance; } public VillagerRegistry.VillagerProfession getProfession() { @@ -32,8 +51,17 @@ public VillagerRegistry.VillagerCareer getCareer() { return career; } - public MerchantRecipe getRecipe() { - return recipe; + public MerchantRecipe getRecipe(Random random) { + if (random == null) { + return recipe; + } else { + float randomValue = MathHelper.nextFloat(random, 0.0f, 100.0f); + if (randomValue < this.chance) { + return recipe; + } else { + return null; + } + } } public int getLevel() { @@ -42,6 +70,13 @@ public int getLevel() { public void register() { profession.getCareer(VillagerHelper.getVillagerCareers(profession).indexOf(career)) - .addTrade(getLevel(), (EntityVillager.ITradeList) (merchant, recipeList, random) -> recipeList.add(getRecipe())); + .addTrade(getLevel(), (EntityVillager.ITradeList) (merchant, recipeList, random) -> { + if(recipe != null) { + MerchantRecipe recipeChance = getRecipe(random); + if (recipeChance != null) { + recipeList.add(recipeChance); + } + } + }); } } diff --git a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantTradeHandler.java b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantTradeHandler.java index a048813..3688052 100644 --- a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantTradeHandler.java +++ b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantTradeHandler.java @@ -36,6 +36,28 @@ public static void addTrade(String profession, String career, IItemStack buy1, I )); } + @ZenMethod + public static void addTradeChance(String profession, String career, IItemStack buy1, IItemStack buy2, IItemStack sell, int level, float chance) { + Preconditions.checkNotNull(profession); + Preconditions.checkArgument(VillagerHelper.getProfession(profession).isPresent()); + VillagerRegistry.VillagerProfession p1 = VillagerHelper.getProfession(profession).get(); + Preconditions.checkNotNull(career); + Preconditions.checkArgument(VillagerHelper.getCareer(p1, career).isPresent()); + Preconditions.checkNotNull(buy1); + Preconditions.checkNotNull(sell); + Preconditions.checkArgument(level > 0); + Preconditions.checkArgument(chance > 0.0f); + Preconditions.checkArgument(chance <= 100.0f); + CraftTweakerAPI.apply(new MerchantTradeHandler.Add( + new MerchantTrade(p1, VillagerHelper.getCareer(p1, career).get(), toStack(buy1), toStack(buy2), toStack(sell), level, chance) + )); + } + + @ZenMethod + public static void addTradeChance(String profession, String career, IItemStack buy1, IItemStack sell, int level, float chance) { + addTradeChance(profession, career, buy1, null, sell, level, chance); + } + @ZenMethod public static void addTrade(String profession, String career, IItemStack buy1, IItemStack sell, int level) { addTrade(profession, career, buy1, null, sell, level); @@ -57,7 +79,7 @@ public void apply() { this.successful.add(trade); trade.register(); } else { - LogHelper.logError(String.format("Error adding %s Recipe for %s", this.name, this.getRecipeInfo(trade))); + LogHelper.logError(String.format("Error adding %s Recipe for %s", this.name, this.getRecipeInfo())); } } else { LogHelper.logError(String.format("Error adding %s Recipe: null object", this.name)); @@ -68,7 +90,7 @@ public void apply() { @Override public String getRecipeInfo(MerchantTrade trade) { - return LogHelper.getStackDescription(trade.getRecipe()); + return LogHelper.getStackDescription(trade.getRecipe(null)); } } From b086009df1f59290e5c6848f59f88119ba8c6f87 Mon Sep 17 00:00:00 2001 From: Robert Lang Date: Fri, 8 May 2026 05:04:25 +0200 Subject: [PATCH 2/8] Careers and Professions are retrieved from Registry directly using Reflection instead of via temporary tables. --- .../merchant/MerchantCommand.java | 6 +-- .../crafttweaker/merchant/MerchantTrade.java | 23 ++++---- .../crafttweaker/merchant/VillagerHelper.java | 53 +++++++++++++------ 3 files changed, 55 insertions(+), 27 deletions(-) diff --git a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantCommand.java b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantCommand.java index a0c845b..bae372b 100644 --- a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantCommand.java +++ b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantCommand.java @@ -54,11 +54,11 @@ public void executeCommand(MinecraftServer server, ICommandSender sender, String if (profession == null) { VillagerHelper.getVillagerProfessions().forEach(p -> { builder.append(p.getRegistryName()).appendNewLine(); - VillagerHelper.getVillagerCareers(p).forEach(c -> builder.append(" - ").append(c.getName()).appendNewLine()); + VillagerHelper.getProfessionCareers(p).forEach(c -> builder.append(" - ").append(c.getName()).appendNewLine()); }); } else { builder.append(profession.getRegistryName()).appendNewLine(); - VillagerHelper.getVillagerCareers(profession).forEach(c -> builder.append(" - ").append(c.getName()).appendNewLine()); + VillagerHelper.getProfessionCareers(profession).forEach(c -> builder.append(" - ").append(c.getName()).appendNewLine()); } CraftTweakerAPI.logCommand(builder.build()); sender.sendMessage(new TextComponentString("List generated; see crafttweaker.log in your minecraft dir.")); @@ -81,7 +81,7 @@ public void executeCommand(MinecraftServer server, ICommandSender sender, String } if (profession == null) { VillagerHelper.getVillagerProfessions().forEach(p -> { - VillagerHelper.getVillagerCareers(p).forEach(c -> { + VillagerHelper.getProfessionCareers(p).forEach(c -> { // TODO: Get the Merchant Recipes ¯\_(ツ)_/¯ }); }); diff --git a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantTrade.java b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantTrade.java index d78773b..98d7493 100644 --- a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantTrade.java +++ b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantTrade.java @@ -1,5 +1,6 @@ package rocks.gameonthe.rockytweaks.crafttweaker.merchant; +import com.blamejared.mtlib.helpers.LogHelper; import net.minecraft.entity.passive.EntityVillager; import net.minecraft.item.ItemStack; import net.minecraft.util.math.MathHelper; @@ -69,14 +70,18 @@ public int getLevel() { } public void register() { - profession.getCareer(VillagerHelper.getVillagerCareers(profession).indexOf(career)) - .addTrade(getLevel(), (EntityVillager.ITradeList) (merchant, recipeList, random) -> { - if(recipe != null) { - MerchantRecipe recipeChance = getRecipe(random); - if (recipeChance != null) { - recipeList.add(recipeChance); - } - } - }); + try { + VillagerHelper.getCareerRef(VillagerHelper.getProfessionName(profession).toString(), career.getName()) + .addTrade(getLevel(), (EntityVillager.ITradeList) (merchant, recipeList, random) -> { + if(recipe != null) { + MerchantRecipe recipeChance = getRecipe(random); + if (recipeChance != null) { + recipeList.add(recipeChance); + } + } + }); + } catch (NullPointerException e) { + LogHelper.logError(String.format("Got invalid career: %s", e.getMessage())); + } } } diff --git a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/VillagerHelper.java b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/VillagerHelper.java index 690fada..473f796 100644 --- a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/VillagerHelper.java +++ b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/VillagerHelper.java @@ -3,9 +3,10 @@ import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; -import java.util.Collection; -import java.util.List; -import java.util.Optional; +import java.lang.reflect.Field; +import java.util.*; + +import net.minecraft.util.ResourceLocation; import net.minecraft.village.MerchantRecipe; import net.minecraftforge.fml.common.registry.ForgeRegistries; import net.minecraftforge.fml.common.registry.VillagerRegistry; @@ -20,17 +21,6 @@ public static Collection getVillagerProfessions() { return ForgeRegistries.VILLAGER_PROFESSIONS.getValuesCollection(); } - public static List getVillagerCareers(VillagerRegistry.VillagerProfession profession) { - List careers = Lists.newArrayList(profession.getCareer(0)); - int i = 0; - do { - i++; - careers.add(profession.getCareer(i)); - } while (!careers.get(0).equals(careers.get(i))); - careers.remove(profession.getCareer(i)); - return careers; - } - public static Optional getProfession(String profession) { for (VillagerRegistry.VillagerProfession p : getVillagerProfessions()) { if (p.getRegistryName() != null @@ -43,7 +33,40 @@ public static Optional getProfession(String } public static Optional getCareer(VillagerRegistry.VillagerProfession profession, String career) { - return getVillagerCareers(profession).stream().filter(c -> c.getName().equalsIgnoreCase(career)).findAny(); + return getProfessionCareers(profession).stream().filter(c -> c.getName().equalsIgnoreCase(career)).findAny(); + } + + public static ResourceLocation getProfessionName(VillagerRegistry.VillagerProfession profession) { + try { + Field namefield = profession.getClass().getDeclaredField("name"); + namefield.setAccessible(true); + return (ResourceLocation) namefield.get(profession); + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + return new ResourceLocation(""); + } + } + + public static List getProfessionCareers(VillagerRegistry.VillagerProfession profession) { + try { + Field namefield = profession.getClass().getDeclaredField("careers"); + namefield.setAccessible(true); + return (List) namefield.get(profession); + } catch (Exception e) { + return new ArrayList<>(); + } + } + + public static VillagerRegistry.VillagerCareer getCareerRef(String professionName, String careerName) { + Optional> firstEntry = getVillagerProfessions().stream().filter(profession -> { + String name = getProfessionName(profession).toString(); + return professionName.equals(name); + }).map(profession -> getProfessionCareers(profession).stream().filter(career -> careerName.equals(career.getName())).findFirst()).findFirst(); + if (firstEntry.isPresent()) { + Optional innerEntry = firstEntry.get(); + return innerEntry.orElse(null); + } else { + return null; + } } public static Multimap getMerchantRecipes(VillagerRegistry.VillagerCareer career) { From 40dbb0cd4f931f05b9abb2fe06c91a5b8910b8a8 Mon Sep 17 00:00:00 2001 From: Robert Lang Date: Sun, 10 May 2026 14:37:23 +0200 Subject: [PATCH 3/8] Command now supports trades. MerchantTrade now creates a MerchantTradeItem instead of a lambda. Added two ZenMethods: clearTrades(profession, career), removeTradeItem(profession, career, itemstack). --- .../merchant/MerchantCommand.java | 113 ++++++++++++- .../crafttweaker/merchant/MerchantTrade.java | 36 +---- .../merchant/MerchantTradeHandler.java | 153 +++++++++++++++++- .../merchant/MerchantTradeItem.java | 43 +++++ .../crafttweaker/merchant/VillagerHelper.java | 15 ++ 5 files changed, 324 insertions(+), 36 deletions(-) create mode 100644 src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantTradeItem.java diff --git a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantCommand.java b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantCommand.java index bae372b..5e971fe 100644 --- a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantCommand.java +++ b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantCommand.java @@ -4,18 +4,24 @@ import com.google.common.collect.Lists; import crafttweaker.CraftTweakerAPI; import crafttweaker.mc1120.commands.CraftTweakerCommand; + +import java.lang.reflect.Field; import java.util.List; import javax.annotation.Nullable; import net.minecraft.command.ICommandSender; +import net.minecraft.entity.passive.EntityVillager; import net.minecraft.server.MinecraftServer; import net.minecraft.util.math.BlockPos; import net.minecraft.util.text.TextComponentString; +import net.minecraft.world.storage.MapDecoration; import net.minecraftforge.fml.common.registry.VillagerRegistry; import org.apache.commons.lang3.text.StrBuilder; +import static rocks.gameonthe.rockytweaks.crafttweaker.merchant.VillagerHelper.TREASURE_MAP_TRADE; + public class MerchantCommand extends CraftTweakerCommand { - private final List arguments = Lists.newArrayList("professions", "careers"); // TODO: Add "trades" + private final List arguments = Lists.newArrayList("professions", "careers", "trades"); public MerchantCommand() { super("merchant"); @@ -79,18 +85,115 @@ public void executeCommand(MinecraftServer server, ICommandSender sender, String sender.sendMessage(new TextComponentString("Invalid career.")); } } - if (profession == null) { - VillagerHelper.getVillagerProfessions().forEach(p -> { - VillagerHelper.getProfessionCareers(p).forEach(c -> { - // TODO: Get the Merchant Recipes ¯\_(ツ)_/¯ + } + if (profession == null) { + VillagerHelper.getVillagerProfessions().forEach(p -> { + builder.append(p.getRegistryName()).appendNewLine(); + VillagerHelper.getProfessionCareers(p).forEach(c -> { + builder.append(" - ").append(c.getName()).appendNewLine(); + + int idx = 1; + for (List levelTrades : VillagerHelper.getCareerTrades(c)) { + if (!levelTrades.isEmpty()) builder.append(" - Level ").append(idx++).appendNewLine(); + + levelTrades.forEach(tradeItem -> { + builder.append(" * ").append(buildTradeString(tradeItem)).appendNewLine(); + }); + } + }); + }); + } else if (career == null) { + builder.append(profession.getRegistryName()).appendNewLine(); + VillagerHelper.getProfessionCareers(profession).forEach(c -> { + builder.append(" - ").append(c.getName()).appendNewLine(); + + int idx = 1; + for (List levelTrades : VillagerHelper.getCareerTrades(c)) { + if (!levelTrades.isEmpty()) builder.append(" - Level ").append(idx++).appendNewLine(); + + levelTrades.forEach(tradeItem -> { + builder.append(" * ").append(buildTradeString(tradeItem)).appendNewLine(); }); + } + }); + } else { + builder.append(profession.getRegistryName()).appendNewLine(); + builder.append(" - ").append(career.getName()).appendNewLine(); + + int idx = 1; + for (List levelTrades : VillagerHelper.getCareerTrades(career)) { + if (!levelTrades.isEmpty()) builder.append(" - Level ").append(idx++).appendNewLine(); + + levelTrades.forEach(tradeItem -> { + builder.append(" * ").append(buildTradeString(tradeItem)).appendNewLine(); }); } } CraftTweakerAPI.logCommand(builder.build()); + sender.sendMessage(new TextComponentString("List generated; see crafttweaker.log in your minecraft dir.")); } else { sender.sendMessage(new TextComponentString("I can't even... (╯°□°)╯︵ ┻━┻")); } } } + + private String buildTradeString(EntityVillager.ITradeList tradeItem) { + if(tradeItem instanceof EntityVillager.EmeraldForItems) { + EntityVillager.EmeraldForItems specialized = (EntityVillager.EmeraldForItems) tradeItem; + return "[EmeraldForItems] buy: " + specialized.buyingItem.getRegistryName().toString() + + ", emeralds: " + specialized.price.getFirst() + "/" + specialized.price.getSecond(); + } + + if(tradeItem instanceof MerchantTradeItem) { + MerchantTradeItem specialized = (MerchantTradeItem) tradeItem; + return "[MerchantTradeItem] chance: " + specialized.chance + + ", buy1: " + specialized.recipe.getItemToBuy().serializeNBT().toString() + + ", buy2: " + (specialized.recipe.hasSecondItemToBuy()?specialized.recipe.getSecondItemToBuy().serializeNBT().toString():"null") + + ", sell: " + specialized.recipe.getItemToSell().serializeNBT().toString(); + } + + if(tradeItem instanceof EntityVillager.ItemAndEmeraldToItem) { + EntityVillager.ItemAndEmeraldToItem specialized = (EntityVillager.ItemAndEmeraldToItem) tradeItem; + return "[ItemAndEmeraldToItem] buy: " + specialized.buyingItemStack.serializeNBT().toString() + + ", buyEmeralds: " + specialized.buyingPriceInfo.getFirst() + "/" + specialized.buyingPriceInfo.getSecond() + + ", sell: " + specialized.sellingItemstack.serializeNBT().toString() + + ", sellEmeralds: " + specialized.sellingPriceInfo.getFirst() + "/" + specialized.sellingPriceInfo.getSecond(); + } + + if(tradeItem instanceof EntityVillager.ListEnchantedBookForEmeralds) { + return "[ListEnchantedBookForEmeralds] fixedBuy: book + emeralds" + + ", fixedSell: enchanted_book + enchantment?"; + } + + if(tradeItem instanceof EntityVillager.ListEnchantedItemForEmeralds) { + EntityVillager.ListEnchantedItemForEmeralds specialized = (EntityVillager.ListEnchantedItemForEmeralds) tradeItem; + return "[ListEnchantedItemForEmeralds] emeralds: " + specialized.priceInfo.getFirst() + "/" + specialized.priceInfo.getSecond() + + ", sell: " + specialized.enchantedItemStack.serializeNBT().toString() + " + enchantment?"; + } + + if(tradeItem instanceof EntityVillager.ListItemForEmeralds) { + EntityVillager.ListItemForEmeralds specialized = (EntityVillager.ListItemForEmeralds) tradeItem; + return "[ListItemForEmeralds] buy: " + specialized.itemToBuy.serializeNBT().toString() + + ", emeralds: " + specialized.priceInfo.getFirst() + "/" + specialized.priceInfo.getSecond(); + } + + if(TREASURE_MAP_TRADE.isInstance(tradeItem)) { + try { + Field value = tradeItem.getClass().getDeclaredField("value"); + value.setAccessible(true); + EntityVillager.PriceInfo price = (EntityVillager.PriceInfo) value.get(tradeItem); + + Field destinationType = tradeItem.getClass().getDeclaredField("destinationType"); + destinationType.setAccessible(true); + MapDecoration.Type mapType = (MapDecoration.Type) destinationType.get(tradeItem); + return "[TreasureMapForEmeralds] emeralds: " + price.getFirst() + "/" + price.getSecond() + + ", fixedSell: minecraft:compass" + + ", buyMapFilled: " + mapType.name(); + } catch (Exception e) { + return "[TreasureMapForEmeralds] Malformed???"; + } + } + + return "[Unknown Type] ???"; + } } diff --git a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantTrade.java b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantTrade.java index 98d7493..50fb6b0 100644 --- a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantTrade.java +++ b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantTrade.java @@ -14,7 +14,7 @@ public class MerchantTrade { private VillagerRegistry.VillagerProfession profession; private VillagerRegistry.VillagerCareer career; - private MerchantRecipe recipe; + public final MerchantRecipe recipe; private int level; private float chance; @@ -44,27 +44,6 @@ public MerchantTrade(VillagerRegistry.VillagerProfession profession, VillagerReg this.chance = chance; } - public VillagerRegistry.VillagerProfession getProfession() { - return profession; - } - - public VillagerRegistry.VillagerCareer getCareer() { - return career; - } - - public MerchantRecipe getRecipe(Random random) { - if (random == null) { - return recipe; - } else { - float randomValue = MathHelper.nextFloat(random, 0.0f, 100.0f); - if (randomValue < this.chance) { - return recipe; - } else { - return null; - } - } - } - public int getLevel() { return level; } @@ -72,16 +51,13 @@ public int getLevel() { public void register() { try { VillagerHelper.getCareerRef(VillagerHelper.getProfessionName(profession).toString(), career.getName()) - .addTrade(getLevel(), (EntityVillager.ITradeList) (merchant, recipeList, random) -> { - if(recipe != null) { - MerchantRecipe recipeChance = getRecipe(random); - if (recipeChance != null) { - recipeList.add(recipeChance); - } - } - }); + .addTrade(getLevel(), toTradeItem()); } catch (NullPointerException e) { LogHelper.logError(String.format("Got invalid career: %s", e.getMessage())); } } + + private EntityVillager.ITradeList toTradeItem() { + return (EntityVillager.ITradeList) new MerchantTradeItem(this.recipe, this.chance); + } } diff --git a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantTradeHandler.java b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantTradeHandler.java index 3688052..b89fb69 100644 --- a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantTradeHandler.java +++ b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantTradeHandler.java @@ -1,6 +1,7 @@ package rocks.gameonthe.rockytweaks.crafttweaker.merchant; import static com.blamejared.mtlib.helpers.InputHelper.toStack; +import static rocks.gameonthe.rockytweaks.crafttweaker.merchant.VillagerHelper.TREASURE_MAP_TRADE; import com.blamejared.mtlib.helpers.LogHelper; import com.blamejared.mtlib.utils.BaseListAddition; @@ -9,7 +10,15 @@ import crafttweaker.CraftTweakerAPI; import crafttweaker.annotations.ZenRegister; import crafttweaker.api.item.IItemStack; + +import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import net.minecraft.entity.passive.EntityVillager; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; import net.minecraftforge.fml.common.registry.VillagerRegistry; import stanhebben.zenscript.annotations.ZenClass; import stanhebben.zenscript.annotations.ZenMethod; @@ -63,6 +72,148 @@ public static void addTrade(String profession, String career, IItemStack buy1, I addTrade(profession, career, buy1, null, sell, level); } + @ZenMethod + public static void clearTrades(String profession, String career) { + Preconditions.checkNotNull(profession); + + Optional professionOption = VillagerHelper.getProfession(profession); + Preconditions.checkArgument(professionOption.isPresent()); + VillagerRegistry.VillagerProfession professionObject = professionOption.get(); + + Preconditions.checkArgument(VillagerHelper.getCareer(professionObject, career).isPresent()); + Optional careerOption = VillagerHelper.getCareer(professionObject, career); + Preconditions.checkArgument(careerOption.isPresent()); + VillagerRegistry.VillagerCareer careerObject = careerOption.get(); + + List> allTrades = VillagerHelper.getCareerTrades(careerObject); + + if (allTrades != null) { + for (List tradeLevel : allTrades) { + if (tradeLevel != null) { + tradeLevel.clear(); + } + } + } + } + + @ZenMethod + public static void removeTradeItem(String profession, String career, IItemStack target ) { + Preconditions.checkNotNull(profession); + + Optional professionOption = VillagerHelper.getProfession(profession); + Preconditions.checkArgument(professionOption.isPresent()); + VillagerRegistry.VillagerProfession professionObject = professionOption.get(); + + Preconditions.checkArgument(VillagerHelper.getCareer(professionObject, career).isPresent()); + Optional careerOption = VillagerHelper.getCareer(professionObject, career); + Preconditions.checkArgument(careerOption.isPresent()); + VillagerRegistry.VillagerCareer careerObject = careerOption.get(); + + List> allTrades = VillagerHelper.getCareerTrades(careerObject); + + Preconditions.checkNotNull(target); + + String targetId = target.getDefinition().getId(); + if (allTrades != null) { + for (List tradeLevel : allTrades) { + if (tradeLevel != null) { + + List removeList = new ArrayList(); + + for (EntityVillager.ITradeList trade : tradeLevel) { + + // Emerald for Items + if (trade instanceof EntityVillager.EmeraldForItems) { + if ( ((EntityVillager.EmeraldForItems) trade).buyingItem.equals(target) ) { + removeList.add(trade); + } + + if ("minecraft:emerald".equals(targetId)) removeList.add(trade); + } + + // Our Trade Item, so we can remove it again + if (trade instanceof MerchantTradeItem) { + if ( compareStack(target, ((MerchantTradeItem) trade).recipe.getItemToBuy()) ) { + removeList.add(trade); + } + if ( compareStack(target, ((MerchantTradeItem) trade).recipe.getSecondItemToBuy()) ) { + removeList.add(trade); + } + if ( compareStack(target, ((MerchantTradeItem) trade).recipe.getItemToSell()) ) { + removeList.add(trade); + } + } + + // Item and Emerald To Item + if (trade instanceof EntityVillager.ItemAndEmeraldToItem) { + if ( compareStack(target, ((EntityVillager.ItemAndEmeraldToItem)trade).buyingItemStack) ) { + removeList.add(trade); + } + if ( compareStack(target, ((EntityVillager.ItemAndEmeraldToItem)trade).sellingItemstack) ) { + removeList.add(trade); + } + + if ("minecraft:emerald".equals(targetId)) removeList.add(trade); + } + + // List Enchanted Book For Emeralds + if (trade instanceof EntityVillager.ListEnchantedBookForEmeralds) { + if ("minecraft:book".equals(targetId) + || "minecraft:emerald".equals(targetId) + || "minecraft:enchanted_book".equals(targetId)) { + removeList.add(trade); + } + } + + // List Enchanted Item For Emeralds + if (trade instanceof EntityVillager.ListEnchantedItemForEmeralds) { + if ( compareStack(target, ((EntityVillager.ListEnchantedItemForEmeralds)trade).enchantedItemStack) ) { + removeList.add(trade); + } + + if ("minecraft:emerald".equals(targetId)) removeList.add(trade); + } + + // List Item For Emeralds + if (trade instanceof EntityVillager.ListItemForEmeralds) { + + if ( compareStack(target, ((EntityVillager.ListItemForEmeralds)trade).itemToBuy) ) { + removeList.add(trade); + } + + if ("minecraft:emerald".equals(targetId)) removeList.add(trade); + } + + if(TREASURE_MAP_TRADE.isInstance(trade)) { + if ("minecraft:map_filled".equals(targetId) + || "minecraft:emerald".equals(targetId) + || "minecraft:compass".equals(targetId)) { + removeList.add(trade); + } + } + } + + tradeLevel.removeAll(removeList); + } + } + } + } + + private static boolean compareStack(IItemStack itemStack, Object other) { + if (other instanceof Item) { + Item otherItem = (Item) other; + return itemStack.getDefinition().getId().equals(otherItem.getRegistryName().toString()); + } + + if (other instanceof ItemStack) { + ItemStack otherStack = (ItemStack) other; + return itemStack.getMetadata() == otherStack.getMetadata() + && itemStack.getDefinition().getId().equals(otherStack.getItem().getRegistryName().toString()); + } + + return false; + } + private static class Add extends BaseListAddition { public Add(MerchantTrade recipe) { @@ -90,7 +241,7 @@ public void apply() { @Override public String getRecipeInfo(MerchantTrade trade) { - return LogHelper.getStackDescription(trade.getRecipe(null)); + return LogHelper.getStackDescription(trade.recipe); } } diff --git a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantTradeItem.java b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantTradeItem.java new file mode 100644 index 0000000..df797ee --- /dev/null +++ b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantTradeItem.java @@ -0,0 +1,43 @@ +package rocks.gameonthe.rockytweaks.crafttweaker.merchant; + +import net.minecraft.entity.IMerchant; +import net.minecraft.entity.passive.EntityVillager; +import net.minecraft.util.math.MathHelper; +import net.minecraft.village.MerchantRecipe; +import net.minecraft.village.MerchantRecipeList; + +import java.util.Random; + +public class MerchantTradeItem implements EntityVillager.ITradeList { + + public final MerchantRecipe recipe; + public final float chance; + + public MerchantTradeItem(MerchantRecipe recipe, float chance) { + this.recipe = recipe; + this.chance = chance; + } + + public MerchantRecipe getRecipe(Random random) { + if (random == null) { + return recipe; + } else { + float randomValue = MathHelper.nextFloat(random, 0.0f, 100.0f); + if (randomValue < this.chance) { + return recipe; + } else { + return null; + } + } + } + + @Override + public void addMerchantRecipe(IMerchant merchant, MerchantRecipeList recipeList, Random random) { + if(recipe != null) { + MerchantRecipe recipeChance = getRecipe(random); + if (recipeChance != null) { + recipeList.add(recipeChance); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/VillagerHelper.java b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/VillagerHelper.java index 473f796..505516d 100644 --- a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/VillagerHelper.java +++ b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/VillagerHelper.java @@ -6,6 +6,7 @@ import java.lang.reflect.Field; import java.util.*; +import net.minecraft.entity.passive.EntityVillager; import net.minecraft.util.ResourceLocation; import net.minecraft.village.MerchantRecipe; import net.minecraftforge.fml.common.registry.ForgeRegistries; @@ -17,6 +18,10 @@ public final class VillagerHelper { private VillagerHelper(){ } + public static Class TREASURE_MAP_TRADE = Arrays.stream(EntityVillager.class.getDeclaredClasses()).filter(clazz -> + "net.minecraft.entity.passive.EntityVillager.TreasureMapForEmeralds".equals(clazz.getCanonicalName()) + ).findFirst().get(); + public static Collection getVillagerProfessions() { return ForgeRegistries.VILLAGER_PROFESSIONS.getValuesCollection(); } @@ -56,6 +61,16 @@ public static List getProfessionCareers(Village } } + public static List> getCareerTrades(VillagerRegistry.VillagerCareer career) { + try { + Field namefield = career.getClass().getDeclaredField("trades"); + namefield.setAccessible(true); + return (List>) namefield.get(career); + } catch (Exception e) { + return new ArrayList<>(); + } + } + public static VillagerRegistry.VillagerCareer getCareerRef(String professionName, String careerName) { Optional> firstEntry = getVillagerProfessions().stream().filter(profession -> { String name = getProfessionName(profession).toString(); From 4abf0b2951b988f9320d52109e2c5b2562aac289 Mon Sep 17 00:00:00 2001 From: Robert Lang Date: Sun, 10 May 2026 14:44:51 +0200 Subject: [PATCH 4/8] Bumped version based on amount of changes + added author --- gradle.properties | 2 +- src/main/resources/mcmod.info | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index ec22cd4..9a080d7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ # This is required to provide enough memory for the Minecraft decompilation process. org.gradle.jvmargs=-Xmx4G name=RockyTweaks -mod_version=0.5.0 +mod_version=0.6.0 mc_version=1.12.2 mcp_mappings=stable_39 forge_version=14.23.5.2768 diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info index c470f9a..8bab715 100644 --- a/src/main/resources/mcmod.info +++ b/src/main/resources/mcmod.info @@ -8,7 +8,7 @@ "url": "", "updateUrl": "", "authorList": [ - "Mohron" + "Mohron", "BIOCHEMIST" ], "credits": "", "logoFile": "", From bb1f3662aef042824af1f811b018455ceff5acae Mon Sep 17 00:00:00 2001 From: Robert Lang Date: Tue, 12 May 2026 05:05:43 +0200 Subject: [PATCH 5/8] Added change to unknown trade types listing (now gives Canonical class name) --- .../rockytweaks/crafttweaker/merchant/MerchantCommand.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantCommand.java b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantCommand.java index 5e971fe..720dbb0 100644 --- a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantCommand.java +++ b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantCommand.java @@ -195,5 +195,6 @@ private String buildTradeString(EntityVillager.ITradeList tradeItem) { } return "[Unknown Type] ???"; + return "[Unknown Type] " + tradeItem.getClass().getCanonicalName(); } } From 87430fd47819390092a9fb46409ec3fe44bd2e26 Mon Sep 17 00:00:00 2001 From: Robert Lang Date: Tue, 12 May 2026 05:52:25 +0200 Subject: [PATCH 6/8] Added change to unknown trade types listing (now gives Canonical class name) --- .../rockytweaks/crafttweaker/merchant/MerchantCommand.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantCommand.java b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantCommand.java index 720dbb0..b71f139 100644 --- a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantCommand.java +++ b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantCommand.java @@ -195,6 +195,7 @@ private String buildTradeString(EntityVillager.ITradeList tradeItem) { } return "[Unknown Type] ???"; + return "[Unknown Type] " + tradeItem.getClass().getCanonicalName(); } } From 5acb0d0ee8fe9fdbc29de9103150a86cbd859b9c Mon Sep 17 00:00:00 2001 From: Biochemic Date: Tue, 12 May 2026 05:53:39 +0200 Subject: [PATCH 7/8] Update MerchantCommand.java Fixed wrong return that snuck past Version control --- .../rockytweaks/crafttweaker/merchant/MerchantCommand.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantCommand.java b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantCommand.java index b71f139..714dc2a 100644 --- a/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantCommand.java +++ b/src/main/java/rocks/gameonthe/rockytweaks/crafttweaker/merchant/MerchantCommand.java @@ -194,8 +194,6 @@ private String buildTradeString(EntityVillager.ITradeList tradeItem) { } } - return "[Unknown Type] ???"; - return "[Unknown Type] " + tradeItem.getClass().getCanonicalName(); } } From 754bd654e5f8c8c8dbdf6f972974344ed30bfc15 Mon Sep 17 00:00:00 2001 From: Robert Lang Date: Tue, 12 May 2026 05:59:41 +0200 Subject: [PATCH 8/8] Bump Version to resolve merge conflict --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 9a080d7..170e20b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ # This is required to provide enough memory for the Minecraft decompilation process. org.gradle.jvmargs=-Xmx4G name=RockyTweaks -mod_version=0.6.0 +mod_version=0.7.0 mc_version=1.12.2 mcp_mappings=stable_39 forge_version=14.23.5.2768