소스 검색

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 <daniel@shedaniel.me>

* Clean up TestTrades and add licenses

Signed-off-by: shedaniel <daniel@shedaniel.me>

* [ciskip] Reintroduce lower bound validation for level

Co-authored-by: Juuxel <6596629+Juuxel@users.noreply.github.com>
Co-authored-by: shedaniel <daniel@shedaniel.me>
Co-authored-by: Max <maxh2709@gmail.com>
canitzp 4 년 전
부모
커밋
2ff5dd500d

+ 73 - 0
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.
+ * <p>
+ * 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);
+    }
+}

+ 61 - 0
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();
+    }
+    
+}

+ 36 - 0
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));
+    }
+}

+ 75 - 0
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<VillagerProfession, Int2ObjectMap<List<VillagerTrades.ItemListing>>> TRADES_TO_ADD = new HashMap<>();
+    private static final List<VillagerTrades.ItemListing> WANDERER_TRADER_TRADES_GENERIC = new ArrayList<>();
+    private static final List<VillagerTrades.ItemListing> WANDERER_TRADER_TRADES_RARE = new ArrayList<>();
+    
+    private static void registerVillagerTrade0(VillagerProfession profession, int level, VillagerTrades.ItemListing... trades) {
+        Int2ObjectMap<List<VillagerTrades.ItemListing>> tradesForProfession = TRADES_TO_ADD.computeIfAbsent(profession, $ -> new Int2ObjectOpenHashMap<>());
+        List<VillagerTrades.ItemListing> 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<List<VillagerTrades.ItemListing>> trades = TRADES_TO_ADD.get(event.getType());
+        
+        if (trades != null) {
+            for (Int2ObjectMap.Entry<List<VillagerTrades.ItemListing>> 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);
+        }
+    }
+}

+ 2 - 0
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();
     }

+ 42 - 0
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};
+    }
+}