Feature/villager trades (#122)

* Add modify and removing for villager trades

- Add mixin for villager trades
- Add methods to register modify and removing
- Implement base for VillagerMixin to provide additional villager data
- Basic Access & Mixin change
- Add AT and AW
- Add overriding for max offers a villager or the wanderer can have

* Add rare check for wandering trader

* Remove todo comment

* rename some methods

* Solve reviews for #122

Move non api stuff into TradeRegistryData
Rename fields in MerchantOfferAccess
Move trade stuff into internal package
Mark internal trade classes as ApiStatus.Internal

* Minor refactors (discussed on Discord)

* Add doc for AbstractVillagerMixin

* Reformat code

* Update gradle.properties

Co-authored-by: Max <maxh2709@gmail.com>
This commit is contained in:
lythowastaken
2021-09-17 16:36:12 +02:00
committed by GitHub
parent 41345275cf
commit 0fcbf40c7f
18 changed files with 799 additions and 4 deletions

View File

@@ -0,0 +1,102 @@
/*
* 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.mixin;
import com.google.common.base.MoreObjects;
import me.shedaniel.architectury.registry.trade.TradeRegistry;
import me.shedaniel.architectury.registry.trade.impl.OfferMixingContext;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.npc.AbstractVillager;
import net.minecraft.world.entity.npc.VillagerTrades;
import net.minecraft.world.item.trading.MerchantOffer;
import net.minecraft.world.item.trading.MerchantOffers;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.Iterator;
import java.util.Set;
/**
* {@link AbstractVillager#addOffersFromItemListings(MerchantOffers, VillagerTrades.ItemListing[], int)} creates
* a {@link Set} with x random integer from {@link VillagerTrades.ItemListing} array indexes to iterate through.
* <p>
* If we use {@link TradeRegistry} to remove one offer from a villager
* we will end up with just x-1 offers but we still want to have x offers (as long there are enough) for a villager if
* there are still {@link VillagerTrades.ItemListing} left.
* <p>
* To solve this we override the iterator with our own iterator which iterate through all indexes.
* As soon {@link OfferMixingContext#maxOffers} offers are created we skip the remaining elements in the iterator {@link OfferMixingContext#skipIteratorIfMaxOffersReached()}.
*/
@Mixin(AbstractVillager.class)
public abstract class AbstractVillagerMixin extends Entity {
public AbstractVillagerMixin(EntityType<?> entityType, Level level) {
super(entityType, level);
}
@Unique
private final ThreadLocal<OfferMixingContext> offerContext = new ThreadLocal<>();
@Redirect(
method = "addOffersFromItemListings(Lnet/minecraft/world/item/trading/MerchantOffers;[Lnet/minecraft/world/entity/npc/VillagerTrades$ItemListing;I)V",
at = @At(value = "INVOKE", target = "Ljava/util/Set;iterator()Ljava/util/Iterator;")
)
public Iterator<Integer> overrideIterator(Set<Integer> set, MerchantOffers offers, VillagerTrades.ItemListing[] itemListings, int maxOffers) {
OfferMixingContext context = new OfferMixingContext(MoreObjects.firstNonNull(architectury$getMaxOfferOverride(), maxOffers), itemListings, random);
offerContext.set(context);
return context.getIterator();
}
@ModifyVariable(
method = "addOffersFromItemListings(Lnet/minecraft/world/item/trading/MerchantOffers;[Lnet/minecraft/world/entity/npc/VillagerTrades$ItemListing;I)V",
at = @At(value = "STORE"),
ordinal = 0
)
public MerchantOffer handleOffer(MerchantOffer offer) {
OfferMixingContext context = offerContext.get();
if (offer == null || context.getMaxOffers() == 0) {
context.skipIteratorIfMaxOffersReached();
return null;
}
MerchantOffer handledOffer = architectury$handleOffer(offer);
if (handledOffer != null) {
context.skipIteratorIfMaxOffersReached();
}
return handledOffer;
}
public MerchantOffer architectury$handleOffer(MerchantOffer offer) {
return offer;
}
@Nullable
public Integer architectury$getMaxOfferOverride() {
return null;
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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.mixin;
import me.shedaniel.architectury.registry.trade.VillagerTradeOfferContext;
import me.shedaniel.architectury.registry.trade.impl.TradeRegistryData;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.npc.Villager;
import net.minecraft.world.entity.npc.VillagerData;
import net.minecraft.world.item.trading.MerchantOffer;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(Villager.class)
public abstract class VillagerMixin extends AbstractVillagerMixin {
public VillagerMixin(EntityType<?> entityType, Level level) {
super(entityType, level);
}
@Shadow
public abstract VillagerData getVillagerData();
@Override
public MerchantOffer architectury$handleOffer(MerchantOffer offer) {
VillagerData vd = getVillagerData();
VillagerTradeOfferContext context = new VillagerTradeOfferContext(vd, offer, this, random);
boolean removeResult = TradeRegistryData.invokeVillagerOfferRemoving(context);
if (removeResult) {
return null;
}
TradeRegistryData.invokeVillagerOfferModify(context);
return offer;
}
@Override
@Nullable
public Integer architectury$getMaxOfferOverride() {
return TradeRegistryData.getVillagerMaxOffers(getVillagerData().getProfession(), getVillagerData().getLevel());
}
}

View File

@@ -0,0 +1,89 @@
/*
* 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.mixin;
import me.shedaniel.architectury.registry.trade.WanderingTraderOfferContext;
import me.shedaniel.architectury.registry.trade.impl.TradeRegistryData;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.npc.VillagerTrades;
import net.minecraft.world.entity.npc.WanderingTrader;
import net.minecraft.world.item.trading.MerchantOffer;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
@Mixin(WanderingTrader.class)
public abstract class WanderingTraderMixin extends AbstractVillagerMixin {
public WanderingTraderMixin(EntityType<?> entityType, Level level) {
super(entityType, level);
}
@Unique
private final ThreadLocal<VillagerTrades.ItemListing> vanillaSelectedItemListing = new ThreadLocal<>();
@ModifyVariable(
method = "updateTrades()V",
at = @At(value = "INVOKE_ASSIGN"),
ordinal = 0
)
public VillagerTrades.ItemListing storeItemListing(VillagerTrades.ItemListing itemListing) {
vanillaSelectedItemListing.set(itemListing);
return itemListing;
}
@ModifyVariable(
method = "updateTrades()V",
at = @At(value = "INVOKE_ASSIGN"),
ordinal = 0
)
public MerchantOffer handleSecondListingOffer(MerchantOffer offer) {
if (offer == null) {
return null;
}
return invokeWanderingTraderEvents(offer, true);
}
@Override
public MerchantOffer architectury$handleOffer(MerchantOffer offer) {
return invokeWanderingTraderEvents(offer, false);
}
@Nullable
private MerchantOffer invokeWanderingTraderEvents(MerchantOffer offer, boolean rare) {
WanderingTraderOfferContext context = new WanderingTraderOfferContext(offer, rare, this, random);
boolean removeResult = TradeRegistryData.invokeWanderingTraderOfferRemoving(context);
if (removeResult) {
return null;
}
TradeRegistryData.invokeWanderingTraderOfferModify(context);
return offer;
}
@Override
@Nullable
public Integer architectury$getMaxOfferOverride() {
return TradeRegistryData.getWanderingTraderMaxOffers();
}
}

View File

@@ -0,0 +1,83 @@
/*
* 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.item.ItemStack;
import net.minecraft.world.item.trading.MerchantOffer;
public class MerchantOfferAccess {
private final MerchantOffer offer;
MerchantOfferAccess(MerchantOffer offer) {
this.offer = offer;
}
public ItemStack getCostA() {
return offer.getBaseCostA();
}
public void setCostA(ItemStack itemStack) {
offer.baseCostA = itemStack.copy();
}
public ItemStack getCostB() {
return offer.getCostB();
}
public void setCostB(ItemStack itemStack) {
offer.costB = itemStack.copy();
}
public ItemStack getResult() {
return offer.getResult();
}
public void setResult(ItemStack itemStack) {
offer.result = itemStack.copy();
}
public int getMaxUses() {
return offer.getMaxUses();
}
public void setMaxUses(int maxUses) {
offer.maxUses = maxUses;
}
public float getPriceMultiplier() {
return offer.getPriceMultiplier();
}
public void setPriceMultiplier(float priceMultiplier) {
offer.priceMultiplier = priceMultiplier;
}
public int getXp() {
return offer.getXp();
}
public void setXp(int xp) {
offer.xp = xp;
}
public MerchantOffer getOffer() {
return offer;
}
}

View File

@@ -50,7 +50,7 @@ public class SimpleTrade implements VillagerTrades.ItemListing {
* 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 secondaryPrice An 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.

View File

@@ -0,0 +1,49 @@
/*
* 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.item.trading.MerchantOffer;
import java.util.Random;
public abstract class TradeOfferContext {
private final MerchantOfferAccess offer;
private final Entity entity;
private final Random random;
public TradeOfferContext(MerchantOffer offer, Entity entity, Random random) {
this.offer = new MerchantOfferAccess(offer);
this.entity = entity;
this.random = random;
}
public MerchantOfferAccess getOffer() {
return offer;
}
public Entity getEntity() {
return entity;
}
public Random getRandom() {
return random;
}
}

View File

@@ -20,9 +20,16 @@
package me.shedaniel.architectury.registry.trade;
import dev.architectury.injectables.annotations.ExpectPlatform;
import me.shedaniel.architectury.registry.trade.impl.TradeRegistryData;
import net.minecraft.world.entity.npc.VillagerProfession;
import net.minecraft.world.entity.npc.VillagerTrades;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
public class TradeRegistry {
private TradeRegistry() {
}
@@ -47,6 +54,71 @@ public class TradeRegistry {
throw new AssertionError();
}
/**
* Override the max possible offers a villager can have by its profession and level.
*
* @param profession The Profession of the villager.
* @param level The level of the villager. Vanilla range is 1 to 5, however mods may extend that upper limit further.
* @param maxOffers Max possible offers a villager can have.
*/
public static void setVillagerMaxOffers(VillagerProfession profession, int level, int maxOffers) {
if (level < 1) {
throw new IllegalArgumentException("Villager Trade level has to be at least 1!");
}
if (maxOffers < 0) {
throw new IllegalArgumentException("Villager's max offers has to be at least 0!");
}
Map<Integer, Integer> map = TradeRegistryData.VILLAGER_MAX_OFFER_OVERRIDES.computeIfAbsent(profession, k -> new HashMap<>());
map.put(level, maxOffers);
}
/**
* Register a callback which provide {@link VillagerTradeOfferContext} to modify the given offer from a villager.
* The callback gets called when {@link net.minecraft.world.entity.npc.Villager} generates their offer list.
*
* @param callback The callback to handle modification for the given offer context.
*/
public static void modifyVillagerOffers(Consumer<VillagerTradeOfferContext> callback) {
Objects.requireNonNull(callback);
TradeRegistryData.VILLAGER_MODIFY_HANDLERS.add(callback);
}
/**
* Register a filter which provide {@link VillagerTradeOfferContext} to test the given offer from a villager.
* The filter gets called when {@link net.minecraft.world.entity.npc.Villager} generates their offer list.
*
* @param filter The filter to test if an offer should be removed. Returning true means the offer will be removed.
*/
public static void removeVillagerOffers(Predicate<VillagerTradeOfferContext> filter) {
Objects.requireNonNull(filter);
TradeRegistryData.VILLAGER_REMOVE_HANDLERS.add(filter);
}
/**
* Register a callback which provide {@link WanderingTraderOfferContext} to modify the given offer from the wandering trader.
* The callback gets called when {@link net.minecraft.world.entity.npc.WanderingTrader} generates their offer list.
*
* @param callback The callback to handle modification for the given offer context.
*/
public static void modifyWanderingTraderOffers(Consumer<WanderingTraderOfferContext> callback) {
Objects.requireNonNull(callback);
TradeRegistryData.WANDERING_TRADER_MODIFY_HANDLERS.add(callback);
}
/**
* Register a filter which provide {@link WanderingTraderOfferContext} to test the given offer from the wandering trader.
* The filter gets called when {@link net.minecraft.world.entity.npc.WanderingTrader} generates their offer list.
*
* @param filter The filter to test if an offer should be removed. Returning true means the offer will be removed.
*/
public static void removeWanderingTraderOffers(Predicate<WanderingTraderOfferContext> filter) {
Objects.requireNonNull(filter);
TradeRegistryData.WANDERING_TRADER_REMOVE_HANDLERS.add(filter);
}
/**
* 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.
@@ -59,4 +131,16 @@ public class TradeRegistry {
throw new AssertionError();
}
/**
* Override the max possible offers the wandering trader can have. This does not affect the rare trade.
*
* @param maxOffers Max possible offers a villager can have.
*/
public static void setWanderingTraderMaxOffers(int maxOffers) {
if (maxOffers < 0) {
throw new IllegalArgumentException("Wandering trader's max offers has to be at least 0!");
}
TradeRegistryData.wanderingTraderMaxOfferOverride = maxOffers;
}
}

View File

@@ -0,0 +1,56 @@
/*
* 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.VillagerData;
import net.minecraft.world.entity.npc.VillagerProfession;
import net.minecraft.world.entity.npc.VillagerType;
import net.minecraft.world.item.trading.MerchantOffer;
import org.jetbrains.annotations.ApiStatus;
import java.util.Random;
public class VillagerTradeOfferContext extends TradeOfferContext {
private final VillagerProfession profession;
private final int level;
private final VillagerType type;
@ApiStatus.Internal
public VillagerTradeOfferContext(VillagerData vd, MerchantOffer offer, Entity entity, Random random) {
super(offer, entity, random);
this.profession = vd.getProfession();
this.level = vd.getLevel();
this.type = vd.getType();
}
public VillagerProfession getProfession() {
return profession;
}
public int getLevel() {
return level;
}
public VillagerType getType() {
return type;
}
}

View File

@@ -0,0 +1,21 @@
package me.shedaniel.architectury.registry.trade;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.trading.MerchantOffer;
import org.jetbrains.annotations.ApiStatus;
import java.util.Random;
public class WanderingTraderOfferContext extends TradeOfferContext {
private final boolean rare;
@ApiStatus.Internal
public WanderingTraderOfferContext(MerchantOffer offer, boolean rare, Entity entity, Random random) {
super(offer, entity, random);
this.rare = rare;
}
public boolean isRare() {
return rare;
}
}

View File

@@ -0,0 +1,76 @@
/*
* 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.impl;
import net.minecraft.world.entity.npc.VillagerTrades;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import java.util.*;
@ApiStatus.Internal
public class OfferMixingContext {
private int currentIndex;
private final int maxOffers;
private final Iterator<Integer> iterator;
private final VillagerTrades.ItemListing[] itemListings;
private final Random random;
public OfferMixingContext(int maxOffers, VillagerTrades.ItemListing[] itemListings, Random random) {
this.currentIndex = 0;
this.maxOffers = Math.min(maxOffers, itemListings.length);
this.itemListings = itemListings;
this.random = random;
List<Integer> shuffled = createShuffledIndexList();
this.iterator = shuffled.iterator();
}
public void skipIteratorIfMaxOffersReached() {
currentIndex++;
if (currentIndex >= getMaxOffers()) {
skip();
}
}
@NotNull
public Iterator<Integer> getIterator() {
return iterator;
}
private void skip() {
iterator.forEachRemaining(($) -> {
});
}
@NotNull
private List<Integer> createShuffledIndexList() {
List<Integer> shuffledListings = new ArrayList<>();
for (int i = 0; i < itemListings.length; i++) {
shuffledListings.add(i);
}
Collections.shuffle(shuffledListings, random);
return shuffledListings;
}
public int getMaxOffers() {
return maxOffers;
}
}

View File

@@ -0,0 +1,63 @@
package me.shedaniel.architectury.registry.trade.impl;
import me.shedaniel.architectury.registry.trade.VillagerTradeOfferContext;
import me.shedaniel.architectury.registry.trade.WanderingTraderOfferContext;
import net.minecraft.world.entity.npc.VillagerProfession;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Predicate;
@ApiStatus.Internal
public class TradeRegistryData {
public static final List<Consumer<VillagerTradeOfferContext>> VILLAGER_MODIFY_HANDLERS = new ArrayList<>();
public static final List<Predicate<VillagerTradeOfferContext>> VILLAGER_REMOVE_HANDLERS = new ArrayList<>();
public static final List<Consumer<WanderingTraderOfferContext>> WANDERING_TRADER_MODIFY_HANDLERS = new ArrayList<>();
public static final List<Predicate<WanderingTraderOfferContext>> WANDERING_TRADER_REMOVE_HANDLERS = new ArrayList<>();
public static final Map<VillagerProfession, Map<Integer, Integer>> VILLAGER_MAX_OFFER_OVERRIDES = new HashMap<>();
public static Integer wanderingTraderMaxOfferOverride = null;
/**
* @param profession The Profession of the villager.
* @param level The level the villager needs. Vanilla range is 1 to 5, however mods may extend that upper limit further.
* @return Max offers for the villager. Returning null means no override exists
*/
@Nullable
public static Integer getVillagerMaxOffers(VillagerProfession profession, int level) {
if (!VILLAGER_MAX_OFFER_OVERRIDES.containsKey(profession)) {
return null;
}
return VILLAGER_MAX_OFFER_OVERRIDES.get(profession).get(level);
}
/**
* @return Max offers for the wandering trader. Returning null means no override exists
*/
@Nullable
public static Integer getWanderingTraderMaxOffers() {
return wanderingTraderMaxOfferOverride;
}
public static boolean invokeVillagerOfferRemoving(VillagerTradeOfferContext ctx) {
return VILLAGER_REMOVE_HANDLERS.stream().anyMatch(predicate -> predicate.test(ctx));
}
public static void invokeVillagerOfferModify(VillagerTradeOfferContext ctx) {
VILLAGER_MODIFY_HANDLERS.forEach(consumer -> consumer.accept(ctx));
}
public static boolean invokeWanderingTraderOfferRemoving(WanderingTraderOfferContext ctx) {
return WANDERING_TRADER_REMOVE_HANDLERS.stream().anyMatch(predicate -> predicate.test(ctx));
}
public static void invokeWanderingTraderOfferModify(WanderingTraderOfferContext ctx) {
WANDERING_TRADER_MODIFY_HANDLERS.forEach(consumer -> consumer.accept(ctx));
}
}

View File

@@ -8,7 +8,10 @@
"mixins": [
"BlockLandingInvoker",
"FluidTagsAccessor",
"MixinLightningBolt"
"MixinLightningBolt",
"AbstractVillagerMixin",
"VillagerMixin",
"WanderingTraderMixin"
],
"injectors": {
"maxShiftBy": 5,

View File

@@ -46,5 +46,18 @@ accessible field net/minecraft/world/item/ShovelItem FLATTENABLES Ljava/util/Map
mutable field net/minecraft/world/item/ShovelItem FLATTENABLES Ljava/util/Map;
accessible field net/minecraft/world/item/HoeItem TILLABLES Ljava/util/Map;
mutable field net/minecraft/world/item/HoeItem TILLABLES Ljava/util/Map;
accessible field net/minecraft/world/item/trading/MerchantOffer baseCostA Lnet/minecraft/world/item/ItemStack;
mutable field net/minecraft/world/item/trading/MerchantOffer baseCostA Lnet/minecraft/world/item/ItemStack;
accessible field net/minecraft/world/item/trading/MerchantOffer costB Lnet/minecraft/world/item/ItemStack;
mutable field net/minecraft/world/item/trading/MerchantOffer costB Lnet/minecraft/world/item/ItemStack;
accessible field net/minecraft/world/item/trading/MerchantOffer result Lnet/minecraft/world/item/ItemStack;
mutable field net/minecraft/world/item/trading/MerchantOffer result Lnet/minecraft/world/item/ItemStack;
accessible field net/minecraft/world/item/trading/MerchantOffer maxUses I
mutable field net/minecraft/world/item/trading/MerchantOffer maxUses I
accessible field net/minecraft/world/item/trading/MerchantOffer priceMultiplier F
accessible field net/minecraft/world/item/trading/MerchantOffer xp I
accessible method net/minecraft/client/renderer/item/ItemProperties registerGeneric (Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/renderer/item/ItemPropertyFunction;)Lnet/minecraft/client/renderer/item/ItemPropertyFunction;
accessible method net/minecraft/client/renderer/item/ItemProperties register (Lnet/minecraft/world/item/Item;Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/renderer/item/ItemPropertyFunction;)V