From 2ff5dd500d3739d4af5c2f693d452d8289cb988b Mon Sep 17 00:00:00 2001 From: canitzp <12819060+canitzp@users.noreply.github.com> Date: Fri, 14 May 2021 23:59:58 +0200 Subject: [PATCH] Add an easy way to register Villager and Wandering Trader trades (#84) * Add TradeRegistry to add Trades a bit more easily. Uses the VillagerTradesEvent on forge * Added TradeRegistry#registerTradeForWanderer which uses the WandererTraderEvent on forge * Added javadoc * Use Fabric own Trade implementation and fixed Test Mod * Update common/src/main/java/me/shedaniel/architectury/registry/TradeRegistry.java Co-authored-by: Juuxel <6596629+Juuxel@users.noreply.github.com> * Update common/src/main/java/me/shedaniel/architectury/registry/TradeRegistry.java Co-authored-by: Juuxel <6596629+Juuxel@users.noreply.github.com> * Update common/src/main/java/me/shedaniel/architectury/registry/TradeRegistry.java Co-authored-by: Juuxel <6596629+Juuxel@users.noreply.github.com> * Update common/src/main/java/me/shedaniel/architectury/registry/TradeRegistry.java Co-authored-by: Juuxel <6596629+Juuxel@users.noreply.github.com> * Added javadoc for SimpleTrade * Use two lists instead of Int2ObjectMap * Use "registerTradeForWanderingTrader" instead of "registerTradeForWanderer" for better clarification * Use IllegalArgumentException instead of RuntimeException * Remove level limit (Mods may be able to remove this restriction in VillagerData#canLevelUp), Clean up forge's implementation Signed-off-by: shedaniel * Clean up TestTrades and add licenses Signed-off-by: shedaniel * [ciskip] Reintroduce lower bound validation for level Co-authored-by: Juuxel <6596629+Juuxel@users.noreply.github.com> Co-authored-by: shedaniel Co-authored-by: Max --- .../registry/trade/SimpleTrade.java | 73 ++++++++++++++++++ .../registry/trade/TradeRegistry.java | 61 +++++++++++++++ .../trade/fabric/TradeRegistryImpl.java | 36 +++++++++ .../trade/forge/TradeRegistryImpl.java | 75 +++++++++++++++++++ .../shedaniel/architectury/test/TestMod.java | 2 + .../architectury/test/trade/TestTrades.java | 42 +++++++++++ 6 files changed, 289 insertions(+) create mode 100644 common/src/main/java/me/shedaniel/architectury/registry/trade/SimpleTrade.java create mode 100644 common/src/main/java/me/shedaniel/architectury/registry/trade/TradeRegistry.java create mode 100644 fabric/src/main/java/me/shedaniel/architectury/registry/trade/fabric/TradeRegistryImpl.java create mode 100644 forge/src/main/java/me/shedaniel/architectury/registry/trade/forge/TradeRegistryImpl.java create mode 100644 testmod-common/src/main/java/me/shedaniel/architectury/test/trade/TestTrades.java diff --git a/common/src/main/java/me/shedaniel/architectury/registry/trade/SimpleTrade.java b/common/src/main/java/me/shedaniel/architectury/registry/trade/SimpleTrade.java new file mode 100644 index 00000000..4a532bf9 --- /dev/null +++ b/common/src/main/java/me/shedaniel/architectury/registry/trade/SimpleTrade.java @@ -0,0 +1,73 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.registry.trade; + +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.npc.VillagerTrades; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.trading.MerchantOffer; +import org.jetbrains.annotations.Nullable; + +import java.util.Random; + +/** + * This class is the easiest implementation of a trade object. + * All trades added by vanilla do have custom classes like {@link VillagerTrades.EmeraldForItems}, but they aren't accessible. + *

+ * Instead of widening the access of those classes or recreating them, this class was added to serve a basic trading implementation. + * To register a trade, just call + * {@link TradeRegistry#registerVillagerTrade(net.minecraft.world.entity.npc.VillagerProfession, int, VillagerTrades.ItemListing...)} + * or + * {@link TradeRegistry#registerTradeForWanderingTrader(boolean, VillagerTrades.ItemListing...)}. + */ +public class SimpleTrade implements VillagerTrades.ItemListing { + private final ItemStack primaryPrice; + private final ItemStack secondaryPrice; + private final ItemStack sale; + private final int maxTrades; + private final int experiencePoints; + private final float priceMultiplier; + + /** + * Constructor for creating the trade. + * You can take a look at all the values the vanilla game uses right here {@link VillagerTrades#TRADES}. + * + * @param primaryPrice The first price a player has to pay to get the 'sale' stack. + * @param secondaryPrice A optional, secondary price to pay as well as the primary one. If not needed just use {@link ItemStack#EMPTY}. + * @param sale The ItemStack which a player can purchase in exchange for the two prices. + * @param maxTrades The amount of trades one villager or wanderer can do. When the amount is surpassed, the trade can't be purchased anymore. + * @param experiencePoints How much experience points does the player get, when trading. Vanilla uses between 2 and 30 for this. + * @param priceMultiplier How much should the price rise, after the trade is used. It is added to the stack size of the primary price. Vanilla uses between 0.05 and 0.2. + */ + public SimpleTrade(ItemStack primaryPrice, ItemStack secondaryPrice, ItemStack sale, int maxTrades, int experiencePoints, float priceMultiplier) { + this.primaryPrice = primaryPrice; + this.secondaryPrice = secondaryPrice; + this.sale = sale; + this.maxTrades = maxTrades; + this.experiencePoints = experiencePoints; + this.priceMultiplier = priceMultiplier; + } + + @Nullable + @Override + public MerchantOffer getOffer(Entity entity, Random random) { + return new MerchantOffer(this.primaryPrice, this.secondaryPrice, this.sale, this.maxTrades, this.experiencePoints, this.priceMultiplier); + } +} diff --git a/common/src/main/java/me/shedaniel/architectury/registry/trade/TradeRegistry.java b/common/src/main/java/me/shedaniel/architectury/registry/trade/TradeRegistry.java new file mode 100644 index 00000000..d26d41d2 --- /dev/null +++ b/common/src/main/java/me/shedaniel/architectury/registry/trade/TradeRegistry.java @@ -0,0 +1,61 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.registry.trade; + +import me.shedaniel.architectury.annotations.ExpectPlatform; +import net.minecraft.world.entity.npc.VillagerProfession; +import net.minecraft.world.entity.npc.VillagerTrades; + +public class TradeRegistry { + private TradeRegistry() {} + + /** + * Register a trade ({@link VillagerTrades.ItemListing}) for a villager by its profession and level. + * When the mod loader is Forge, the {@code VillagerTradesEvent} event is used. + * + * @param profession The Profession the villager needs to have this trade. + * @param level The level the villager needs. Vanilla range is 1 to 5, however mods may extend that upper limit further. + * @param trades The trades to add to this profession at the specified level. + */ + public static void registerVillagerTrade(VillagerProfession profession, int level, VillagerTrades.ItemListing... trades) { + if (level < 1) { + throw new IllegalArgumentException("Villager Trade level has to be at least 1!"); + } + registerVillagerTrade0(profession, level, trades); + } + + @ExpectPlatform + private static void registerVillagerTrade0(VillagerProfession profession, int level, VillagerTrades.ItemListing... trades) { + throw new AssertionError(); + } + + /** + * Register a trade ({@link VillagerTrades.ItemListing}) to a wandering trader by its rarity. + * When the mod loader is Forge, the {@code WandererTradesEvent} event is used. + * + * @param rare Whether this trade is "rare". Rare trades have a five times lower chance of being used. + * @param trades The trades to add to the wandering trader. + */ + @ExpectPlatform + public static void registerTradeForWanderingTrader(boolean rare, VillagerTrades.ItemListing... trades) { + throw new AssertionError(); + } + +} diff --git a/fabric/src/main/java/me/shedaniel/architectury/registry/trade/fabric/TradeRegistryImpl.java b/fabric/src/main/java/me/shedaniel/architectury/registry/trade/fabric/TradeRegistryImpl.java new file mode 100644 index 00000000..a897e2b4 --- /dev/null +++ b/fabric/src/main/java/me/shedaniel/architectury/registry/trade/fabric/TradeRegistryImpl.java @@ -0,0 +1,36 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.registry.trade.fabric; + +import net.fabricmc.fabric.api.object.builder.v1.trade.TradeOfferHelper; +import net.minecraft.world.entity.npc.VillagerProfession; +import net.minecraft.world.entity.npc.VillagerTrades; + +import java.util.Collections; + +public class TradeRegistryImpl { + private static void registerVillagerTrade0(VillagerProfession profession, int level, VillagerTrades.ItemListing... trades) { + TradeOfferHelper.registerVillagerOffers(profession, level, allTradesList -> Collections.addAll(allTradesList, trades)); + } + + public static void registerTradeForWanderingTrader(boolean rare, VillagerTrades.ItemListing... trades) { + TradeOfferHelper.registerWanderingTraderOffers(rare ? 2 : 1, allTradesList -> Collections.addAll(allTradesList, trades)); + } +} diff --git a/forge/src/main/java/me/shedaniel/architectury/registry/trade/forge/TradeRegistryImpl.java b/forge/src/main/java/me/shedaniel/architectury/registry/trade/forge/TradeRegistryImpl.java new file mode 100644 index 00000000..fcecfab2 --- /dev/null +++ b/forge/src/main/java/me/shedaniel/architectury/registry/trade/forge/TradeRegistryImpl.java @@ -0,0 +1,75 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.registry.trade.forge; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import me.shedaniel.architectury.forge.ArchitecturyForge; +import net.minecraft.core.NonNullList; +import net.minecraft.world.entity.npc.VillagerProfession; +import net.minecraft.world.entity.npc.VillagerTrades; +import net.minecraftforge.event.village.VillagerTradesEvent; +import net.minecraftforge.event.village.WandererTradesEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +import java.util.*; + +@Mod.EventBusSubscriber(modid = ArchitecturyForge.MOD_ID) +public class TradeRegistryImpl { + private static final Map>> TRADES_TO_ADD = new HashMap<>(); + private static final List WANDERER_TRADER_TRADES_GENERIC = new ArrayList<>(); + private static final List WANDERER_TRADER_TRADES_RARE = new ArrayList<>(); + + private static void registerVillagerTrade0(VillagerProfession profession, int level, VillagerTrades.ItemListing... trades) { + Int2ObjectMap> tradesForProfession = TRADES_TO_ADD.computeIfAbsent(profession, $ -> new Int2ObjectOpenHashMap<>()); + List tradesForLevel = tradesForProfession.computeIfAbsent(level, $ -> new ArrayList<>()); + Collections.addAll(tradesForLevel, trades); + } + + public static void registerTradeForWanderingTrader(boolean rare, VillagerTrades.ItemListing... trades) { + if (rare) { + Collections.addAll(WANDERER_TRADER_TRADES_RARE, trades); + } else { + Collections.addAll(WANDERER_TRADER_TRADES_GENERIC, trades); + } + } + + @SubscribeEvent + public static void onTradeRegistering(VillagerTradesEvent event) { + Int2ObjectMap> trades = TRADES_TO_ADD.get(event.getType()); + + if (trades != null) { + for (Int2ObjectMap.Entry> entry : trades.int2ObjectEntrySet()) { + event.getTrades().computeIfAbsent(entry.getIntKey(), $ -> NonNullList.create()).addAll(entry.getValue()); + } + } + } + + @SubscribeEvent + public static void onWanderingTradeRegistering(WandererTradesEvent event) { + if (!WANDERER_TRADER_TRADES_GENERIC.isEmpty()) { + event.getGenericTrades().addAll(WANDERER_TRADER_TRADES_GENERIC); + } + if (!WANDERER_TRADER_TRADES_RARE.isEmpty()) { + event.getRareTrades().addAll(WANDERER_TRADER_TRADES_RARE); + } + } +} diff --git a/testmod-common/src/main/java/me/shedaniel/architectury/test/TestMod.java b/testmod-common/src/main/java/me/shedaniel/architectury/test/TestMod.java index e1e87e5b..1193c342 100644 --- a/testmod-common/src/main/java/me/shedaniel/architectury/test/TestMod.java +++ b/testmod-common/src/main/java/me/shedaniel/architectury/test/TestMod.java @@ -28,6 +28,7 @@ import me.shedaniel.architectury.test.gamerule.TestGameRules; import me.shedaniel.architectury.test.registry.TestRegistries; import me.shedaniel.architectury.test.registry.client.TestKeybinds; import me.shedaniel.architectury.test.tags.TestTags; +import me.shedaniel.architectury.test.trade.TestTrades; import me.shedaniel.architectury.utils.Env; import me.shedaniel.architectury.utils.EnvExecutor; @@ -40,6 +41,7 @@ public class TestMod { TestRegistries.initialize(); TestGameRules.init(); TestTags.initialize(); + TestTrades.init(); if (Platform.getEnvironment() == Env.CLIENT) TestKeybinds.initialize(); } diff --git a/testmod-common/src/main/java/me/shedaniel/architectury/test/trade/TestTrades.java b/testmod-common/src/main/java/me/shedaniel/architectury/test/trade/TestTrades.java new file mode 100644 index 00000000..2db6ed33 --- /dev/null +++ b/testmod-common/src/main/java/me/shedaniel/architectury/test/trade/TestTrades.java @@ -0,0 +1,42 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.test.trade; + +import me.shedaniel.architectury.registry.trade.TradeRegistry; +import me.shedaniel.architectury.registry.trade.SimpleTrade; +import net.minecraft.core.Registry; +import net.minecraft.world.entity.npc.VillagerProfession; +import net.minecraft.world.entity.npc.VillagerTrades; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; + +public class TestTrades { + public static void init() { + for (VillagerProfession villagerProfession : Registry.VILLAGER_PROFESSION) { + TradeRegistry.registerVillagerTrade(villagerProfession, 1, TestTrades.createTrades()); + } + TradeRegistry.registerTradeForWanderingTrader(false, TestTrades.createTrades()); + } + + private static VillagerTrades.ItemListing[] createTrades() { + SimpleTrade trade = new SimpleTrade(Items.APPLE.getDefaultInstance(), ItemStack.EMPTY, Items.ACACIA_BOAT.getDefaultInstance(), 1, 0, 1.0F); + return new VillagerTrades.ItemListing[]{trade}; + } +}