Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TradeOfferHelper: support rebalance experiment #3306

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import java.util.List;
import java.util.function.Consumer;

import org.apache.commons.lang3.tuple.Pair;

import net.minecraft.village.TradeOffers;
import net.minecraft.village.VillagerProfession;

Expand All @@ -29,11 +31,25 @@
*/
public final class TradeOfferHelper {
/**
* Registers trade offer factories for use by villagers.
* Registers trade offer factories for use by villagers. This registers the same trades regardless of
* whether the rebalanced trade experiment is enabled.
*
* @param profession the villager profession to assign the trades to
* @param level the profession level the villager must be to offer the trades
* @param factories a consumer to provide the factories
* @deprecated Use {@link #registerVillagerOffers(VillagerProfession, int, VillagerTradeRegistrationCallback)} instead.
*/
public static void registerVillagerOffers(VillagerProfession profession, int level, Consumer<List<TradeOffers.Factory>> factories) {
TradeOfferInternals.registerVillagerOffers(profession, level, (trades, rebalanced) -> factories.accept(trades));
}

/**
* Registers trade offer factories for use by villagers. This allows mods to register different
* trades depending on whether the trades are for the rebalanced trade experiment.
*
* <p>Below is an example, of registering a trade offer factory to be added a blacksmith with a profession level of 3:
* <blockquote><pre>
* TradeOfferHelper.registerVillagerOffers(VillagerProfession.BLACKSMITH, 3, factories -> {
* TradeOfferHelper.registerVillagerOffers(VillagerProfession.BLACKSMITH, 3, (factories, rebalanced) -> {
* factories.add(new CustomTradeFactory(...);
* });
* </pre></blockquote>
Expand All @@ -42,20 +58,38 @@ public final class TradeOfferHelper {
* @param level the profession level the villager must be to offer the trades
* @param factories a consumer to provide the factories
*/
public static void registerVillagerOffers(VillagerProfession profession, int level, Consumer<List<TradeOffers.Factory>> factories) {
public static void registerVillagerOffers(VillagerProfession profession, int level, VillagerTradeRegistrationCallback factories) {
TradeOfferInternals.registerVillagerOffers(profession, level, factories);
}

/**
* Registers trade offer factories for use by wandering trades.
* If the rebalanced trade experiment is enabled, {@code level} is ignored,
* and a fixed number of randomly chosen trades registered by this method will always appear.
* This number is currently 25%; this is subject to change.
*
* @param level the level the trades
* @param level the level of the trades
* @param factory a consumer to provide the factories
* @deprecated Use {@link #registerWanderingTraderOffers(int, WanderingTraderTradeRegistrationCallback)} instead.
* Given the inherent design incompatibility that needs to be addressed by mod developers, this is deprecated for removal.
*/
@Deprecated(forRemoval = true)
public static void registerWanderingTraderOffers(int level, Consumer<List<TradeOffers.Factory>> factory) {
TradeOfferInternals.registerWanderingTraderOffers(level, factory);
}

/**
* Registers trade offer factories for use by wandering trades.
* If the rebalanced trade experiment is enabled, {@code level} is ignored.
* If the experiment is not enabled, the weight is ignored.
*
* @param level the level of the trades
* @param factory a consumer to provide the factories
*/
public static void registerWanderingTraderOffers(int level, WanderingTraderTradeRegistrationCallback factory) {
TradeOfferInternals.registerWanderingTraderOffers(level, factory);
}

/**
* @deprecated This never did anything useful.
*/
Expand All @@ -66,4 +100,27 @@ public static void refreshOffers() {

private TradeOfferHelper() {
}

@FunctionalInterface
public interface VillagerTradeRegistrationCallback {
/**
* Callback to register villager trades.
* @param trades the list to add trades to
* @param rebalanced whether the trades are for the rebalanced trade experiment
*/
void onRegister(List<TradeOffers.Factory> trades, boolean rebalanced);
}

@FunctionalInterface
public interface WanderingTraderTradeRegistrationCallback {
/**
* Callback to register weighted wandering trader trades.
*
* <p>A trade offer pool entry is an array of trades, and the number of rolls from the pool.
* If the number of rolls is equal to or above the size of the array, all trades are included.
* @param trades the list to add trade offer pool entries to
* @param rebalanced whether the trades are for the rebalanced trade experiment
*/
void onRegister(List<Pair<TradeOffers.Factory[], Integer>> trades, boolean rebalanced);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,26 @@
package net.fabricmc.fabric.impl.object.builder;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.LoggerFactory;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.minecraft.util.math.MathHelper;
import net.minecraft.village.TradeOffers;
import net.minecraft.village.VillagerProfession;

import net.fabricmc.fabric.api.object.builder.v1.trade.TradeOfferHelper;

public final class TradeOfferInternals {
private static final Logger LOGGER = LoggerFactory.getLogger("fabric-object-builder-api-v1");

Expand All @@ -38,19 +45,63 @@ private TradeOfferInternals() {

// synchronized guards against concurrent modifications - Vanilla does not mutate the underlying arrays (as of 1.16),
// so reads will be fine without locking.
public static synchronized void registerVillagerOffers(VillagerProfession profession, int level, Consumer<List<TradeOffers.Factory>> factory) {
public static synchronized void registerVillagerOffers(VillagerProfession profession, int level, TradeOfferHelper.VillagerTradeRegistrationCallback factory) {
Objects.requireNonNull(profession, "VillagerProfession may not be null.");
registerOffers(TradeOffers.PROFESSION_TO_LEVELED_TRADE.computeIfAbsent(profession, key -> new Int2ObjectOpenHashMap<>()), level, factory);

// Make the map modifiable
if (!(TradeOffers.REBALANCED_PROFESSION_TO_LEVELED_TRADE instanceof HashMap)) {
TradeOffers.REBALANCED_PROFESSION_TO_LEVELED_TRADE = new HashMap<>(TradeOffers.REBALANCED_PROFESSION_TO_LEVELED_TRADE);
}

registerOffers(TradeOffers.REBALANCED_PROFESSION_TO_LEVELED_TRADE.computeIfAbsent(profession, key -> {
// Absence of the trade entry in rebalanced map means "check normal trade map".
// If we just add an empty map, vanilla trades would not be available if rebalanced trade is used.
// Copy the vanilla map here instead. Successive calls modify the copy only.
final Int2ObjectMap<TradeOffers.Factory[]> vanillaMap = TradeOffers.PROFESSION_TO_LEVELED_TRADE.get(profession);

if (vanillaMap != null) {
return new Int2ObjectOpenHashMap<>(vanillaMap);
}

// Custom profession; vanilla trades unavailable, so just make a new map.
return new Int2ObjectOpenHashMap<>();
}), level, factory::onRegister, true); // casting functional interface
// This must be done AFTER the rebalanced trade map is changed, to avoid double registration
// for the first call (by copying the already-registered map).
registerOffers(TradeOffers.PROFESSION_TO_LEVELED_TRADE.computeIfAbsent(profession, key -> new Int2ObjectOpenHashMap<>()), level, factory::onRegister, false);
}

public static synchronized void registerWanderingTraderOffers(int level, Consumer<List<TradeOffers.Factory>> factory) {
registerOffers(TradeOffers.WANDERING_TRADER_TRADES, level, factory);
registerOffers(TradeOffers.WANDERING_TRADER_TRADES, level, (trades, rebalanced) -> factory.accept(trades), false); // rebalanced arg unused

// Rebalanced wandering trader offers are not leveled.
registerRebalancedWanderingTraderOffers(poolList -> {
final List<TradeOffers.Factory> list = new ArrayList<>();
factory.accept(list);
// The likely intent of the mod is to offer some of the entries, but not all.
// This was previously done by entirely random offer; now that fixed-count pool is
// used, if we add elements of the list one by one, they would all show up.
// Offer 25% (arbitrary number chosen by apple502j) of the registered trades at a time.
// Wandering traders are not Amazon.
poolList.add(Pair.of(list.toArray(TradeOffers.Factory[]::new), MathHelper.ceil(list.size() / 4.0)));
});
}

public static synchronized void registerWanderingTraderOffers(int level, TradeOfferHelper.WanderingTraderTradeRegistrationCallback callback) {
registerOffers(TradeOffers.WANDERING_TRADER_TRADES, level, (list, rebalanced) -> {
List<Pair<TradeOffers.Factory[], Integer>> trades = new ArrayList<>();
callback.onRegister(trades, false);
trades.forEach(trade -> list.addAll(Arrays.asList(trade.getLeft())));
}, false); // rebalanced arg unused

// Rebalanced wandering trader offers are not leveled.
registerRebalancedWanderingTraderOffers(poolList -> callback.onRegister(poolList, true));
}

// Shared code to register offers for both villagers and wandering traders.
private static void registerOffers(Int2ObjectMap<TradeOffers.Factory[]> leveledTradeMap, int level, Consumer<List<TradeOffers.Factory>> factory) {
// Shared code to register offers for both villagers and non-rebalanced wandering traders.
private static void registerOffers(Int2ObjectMap<TradeOffers.Factory[]> leveledTradeMap, int level, BiConsumer<List<TradeOffers.Factory>, Boolean> factory, boolean rebalanced) {
final List<TradeOffers.Factory> list = new ArrayList<>();
factory.accept(list);
factory.accept(list, rebalanced);

final TradeOffers.Factory[] originalEntries = leveledTradeMap.computeIfAbsent(level, key -> new TradeOffers.Factory[0]);
final TradeOffers.Factory[] addedEntries = list.toArray(new TradeOffers.Factory[0]);
Expand All @@ -59,6 +110,15 @@ private static void registerOffers(Int2ObjectMap<TradeOffers.Factory[]> leveledT
leveledTradeMap.put(level, allEntries);
}

private static void registerRebalancedWanderingTraderOffers(Consumer<List<Pair<TradeOffers.Factory[], Integer>>> factory) {
// Make the list modifiable
if (!(TradeOffers.REBALANCED_WANDERING_TRADER_TRADES instanceof ArrayList)) {
TradeOffers.REBALANCED_WANDERING_TRADER_TRADES = new ArrayList<>(TradeOffers.REBALANCED_WANDERING_TRADER_TRADES);
}

factory.accept(TradeOffers.REBALANCED_WANDERING_TRADER_TRADES);
}

public static void printRefreshOffersWarning() {
Throwable loggingThrowable = new Throwable();
LOGGER.warn("TradeOfferHelper#refreshOffers does not do anything, yet it was called! Stack trace:", loggingThrowable);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ accessible method net/minecraft/world/poi/PointOfInterestTypes register

extendable class net/minecraft/block/entity/BlockEntityType$BlockEntityFactory
accessible class net/minecraft/village/TradeOffers$TypeAwareBuyForOneEmeraldFactory
mutable field net/minecraft/village/TradeOffers REBALANCED_PROFESSION_TO_LEVELED_TRADE Ljava/util/Map;
mutable field net/minecraft/village/TradeOffers REBALANCED_WANDERING_TRADER_TRADES Ljava/util/List;

accessible method net/minecraft/entity/SpawnRestriction register (Lnet/minecraft/entity/EntityType;Lnet/minecraft/entity/SpawnRestriction$Location;Lnet/minecraft/world/Heightmap$Type;Lnet/minecraft/entity/SpawnRestriction$SpawnPredicate;)V

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import static net.minecraft.server.command.CommandManager.literal;

import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import org.apache.commons.lang3.tuple.Pair;

import net.minecraft.entity.Entity;
import net.minecraft.entity.passive.WanderingTraderEntity;
Expand All @@ -40,12 +41,30 @@
public class VillagerTypeTest1 implements ModInitializer {
@Override
public void onInitialize() {
TradeOfferHelper.registerVillagerOffers(VillagerProfession.ARMORER, 1, factories -> {
factories.add(new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.GOLD_INGOT, 3), new ItemStack(Items.NETHERITE_SCRAP, 4), new ItemStack(Items.NETHERITE_INGOT), 2, 6, 0.15F)));
TradeOfferHelper.registerVillagerOffers(VillagerProfession.ARMORER, 1, (factories, rebalanced) -> {
if (rebalanced) {
factories.add(new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.GOLD_INGOT, 48), new ItemStack(Items.NETHERITE_SCRAP, 64), new ItemStack(Items.NETHERITE_INGOT, 16), 1, 6, 0.15F)));
} else {
factories.add(new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.GOLD_INGOT, 3), new ItemStack(Items.NETHERITE_SCRAP, 4), new ItemStack(Items.NETHERITE_INGOT), 2, 6, 0.15F)));
}
});

TradeOfferHelper.registerWanderingTraderOffers(1, factories -> {
factories.add(new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.GOLD_INGOT, 3), new ItemStack(Items.NETHERITE_SCRAP, 4), new ItemStack(Items.NETHERITE_INGOT), 2, 6, 0.35F)));
TradeOfferHelper.registerWanderingTraderOffers(1, (factories, rebalanced) -> {
if (rebalanced) {
factories.add(Pair.of(
new TradeOffers.Factory[]{
new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.GOLD_INGOT, 48), new ItemStack(Items.NETHERITE_SCRAP, 64), new ItemStack(Items.NETHERITE_INGOT, 16), 1, 6, 0.35F))
},
1
));
} else {
factories.add(Pair.of(
new TradeOffers.Factory[]{
new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.GOLD_INGOT, 3), new ItemStack(Items.NETHERITE_SCRAP, 4), new ItemStack(Items.NETHERITE_INGOT), 2, 6, 0.35F))
},
1
));
}
});

CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@

package net.fabricmc.fabric.test.object.builder;

import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.registry.Registries;
import net.minecraft.village.TradeOffer;
import net.minecraft.village.VillagerProfession;

Expand All @@ -26,9 +28,11 @@

/*
* Second entrypoint to validate class loading does not break this.
* This is used to test deprecated methods, some of which have their own code path.
*/
public class VillagerTypeTest2 implements ModInitializer {
@Override
@SuppressWarnings("deprecation")
public void onInitialize() {
TradeOfferHelper.registerVillagerOffers(VillagerProfession.ARMORER, 1, factories -> {
factories.add(new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.DIAMOND, 5), new ItemStack(Items.NETHERITE_INGOT), 3, 4, 0.15F)));
Expand All @@ -48,5 +52,12 @@ public void onInitialize() {
TradeOfferHelper.registerVillagerOffers(VillagerProfession.ARMORER, 1, factories -> {
factories.add(new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.DIAMOND, 10), new ItemStack(Items.CHAINMAIL_LEGGINGS), 3, 4, 0.15F)));
});
TradeOfferHelper.registerWanderingTraderOffers(1, factory -> {
for (Item item : Registries.ITEM) {
if (item.getFoodComponent() != null) {
factory.add(new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.NETHERITE_INGOT, 1), new ItemStack(item, 1), 3, 4, 0.15F)));
}
}
});
}
}