Hooks NeoForge's VillagerTradesEvent + WandererTradesEvent at data-pack reload time and wraps every existing ItemListing so the resulting MerchantOffer has its emerald-cost-or-result items swapped to Numismatics coin equivalents: 1..7 emeralds -> spurs (1:1) 8..63 emeralds -> bevels (1 bevel = 8 spurs) 64+ emeralds -> cogs (1 cog = 64 spurs) Lives in bnstoolkit instead of a third-party mod because the obvious choice — Modrinth's 'numismatics-villager-currency' — requires NeoForge 21.1.230+ and we're on 21.1.228. The two-page Java wrapper here is simpler than the alternatives (writing every trade override JSON via 'data-trades') AND doesn't push us into a runtime upgrade. Includes wanderer trades. Modded villager professions are picked up automatically since we just wrap the event's trade list as-is. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -19,5 +19,5 @@ loader_version_range=[1,)
|
||||
mod_id=bnstoolkit
|
||||
mod_name=Brass and Sigil Toolkit
|
||||
mod_license=All Rights Reserved
|
||||
mod_version=0.2.0
|
||||
mod_version=0.3.0
|
||||
mod_group_id=uk.sijbers.bnstoolkit
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package uk.sijbers.bnstoolkit.economy;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.npc.VillagerTrades;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.item.trading.ItemCost;
|
||||
import net.minecraft.world.item.trading.MerchantOffer;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
import net.neoforged.fml.common.EventBusSubscriber;
|
||||
import net.neoforged.neoforge.event.village.VillagerTradesEvent;
|
||||
import net.neoforged.neoforge.event.village.WandererTradesEvent;
|
||||
import uk.sijbers.bnstoolkit.BnsToolkit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Server-side villager-trade rebalance.
|
||||
*
|
||||
* Hooks {@link VillagerTradesEvent} and {@link WandererTradesEvent} at
|
||||
* data-pack-reload time. For every existing trade listing, swaps any
|
||||
* emerald cost/result for the Numismatics equivalent (1 spur = 1
|
||||
* emerald), so the entire vanilla and modded villager trade table
|
||||
* routes through the BNS economy without us having to enumerate every
|
||||
* profession + level by hand.
|
||||
*
|
||||
* Counts >= 8 are upgraded to higher denominations so inventory doesn't
|
||||
* fill up with raw spurs:
|
||||
* 1..7 spurs (1 each)
|
||||
* 8..63 -> bevels (1 bevel = 8 spurs)
|
||||
* 64.. -> cogs (1 cog = 64 spurs)
|
||||
*
|
||||
* Why this lives in bnstoolkit rather than a third-party mod:
|
||||
* - The two existing options on Modrinth (numismatics-villager-currency
|
||||
* and data-trades) either require a NeoForge bump (1.1.0 wants
|
||||
* 21.1.230+, we're on 228) or need hand-written JSON for every
|
||||
* trade. Reusing the event we already need for HUD pushes is
|
||||
* simpler and avoids touching the live NeoForge install.
|
||||
*/
|
||||
@EventBusSubscriber(modid = BnsToolkit.MOD_ID)
|
||||
public final class VillagerTradeRebalance {
|
||||
private VillagerTradeRebalance() {}
|
||||
|
||||
private static final ResourceLocation SPUR_ID = ResourceLocation.parse("numismatics:spur");
|
||||
private static final ResourceLocation BEVEL_ID = ResourceLocation.parse("numismatics:bevel");
|
||||
private static final ResourceLocation COG_ID = ResourceLocation.parse("numismatics:cog");
|
||||
private static final ResourceLocation EMERALD_ID = ResourceLocation.parse("minecraft:emerald");
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onVillagerTrades(VillagerTradesEvent event) {
|
||||
Int2ObjectMap<List<VillagerTrades.ItemListing>> trades = event.getTrades();
|
||||
for (Int2ObjectMap.Entry<List<VillagerTrades.ItemListing>> e : trades.int2ObjectEntrySet()) {
|
||||
List<VillagerTrades.ItemListing> orig = e.getValue();
|
||||
List<VillagerTrades.ItemListing> wrapped = new ArrayList<>(orig.size());
|
||||
for (VillagerTrades.ItemListing inner : orig) wrapped.add(new SpurTradeWrapper(inner));
|
||||
e.setValue(wrapped);
|
||||
}
|
||||
BnsToolkit.LOG.info("[bns/trades] rebalanced trades for profession {}", event.getType().name());
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onWandererTrades(WandererTradesEvent event) {
|
||||
rewrapList(event.getGenericTrades());
|
||||
rewrapList(event.getRareTrades());
|
||||
BnsToolkit.LOG.info("[bns/trades] rebalanced wanderer trades");
|
||||
}
|
||||
|
||||
private static void rewrapList(List<VillagerTrades.ItemListing> list) {
|
||||
List<VillagerTrades.ItemListing> copy = new ArrayList<>(list);
|
||||
list.clear();
|
||||
for (VillagerTrades.ItemListing inner : copy) list.add(new SpurTradeWrapper(inner));
|
||||
}
|
||||
|
||||
/** Per-listing wrapper that rewrites the offer's emerald slots on each call. */
|
||||
private record SpurTradeWrapper(VillagerTrades.ItemListing inner) implements VillagerTrades.ItemListing {
|
||||
@Override
|
||||
public MerchantOffer getOffer(net.minecraft.world.entity.Entity entity, net.minecraft.util.RandomSource rand) {
|
||||
MerchantOffer base = inner.getOffer(entity, rand);
|
||||
if (base == null) return null;
|
||||
ItemCost costA = base.getItemCostA();
|
||||
Optional<ItemCost> costB = base.getItemCostB();
|
||||
ItemStack result = base.getResult();
|
||||
|
||||
ItemCost newCostA = swapIfEmerald(costA);
|
||||
Optional<ItemCost> newCostB = costB.map(VillagerTradeRebalance::swapIfEmerald);
|
||||
ItemStack newResult = swapIfEmerald(result);
|
||||
|
||||
return new MerchantOffer(newCostA, newCostB, newResult,
|
||||
base.getUses(), base.getMaxUses(), base.getXp(),
|
||||
base.getPriceMultiplier(), base.getDemand());
|
||||
}
|
||||
}
|
||||
|
||||
private static ItemCost swapIfEmerald(ItemCost cost) {
|
||||
if (cost == null) return null;
|
||||
ItemStack peek = cost.itemStack();
|
||||
ResourceLocation id = BuiltInRegistries.ITEM.getKey(peek.getItem());
|
||||
if (!EMERALD_ID.equals(id)) return cost;
|
||||
int count = peek.getCount();
|
||||
CoinSwap swap = pickDenom(count);
|
||||
Item coinItem = BuiltInRegistries.ITEM.get(swap.itemId);
|
||||
if (coinItem == Items.AIR) return cost; // mod missing — leave vanilla cost
|
||||
return new ItemCost(coinItem, swap.count);
|
||||
}
|
||||
|
||||
private static ItemStack swapIfEmerald(ItemStack stack) {
|
||||
if (stack == null || stack.isEmpty()) return stack;
|
||||
ResourceLocation id = BuiltInRegistries.ITEM.getKey(stack.getItem());
|
||||
if (!EMERALD_ID.equals(id)) return stack;
|
||||
int count = stack.getCount();
|
||||
CoinSwap swap = pickDenom(count);
|
||||
Item coinItem = BuiltInRegistries.ITEM.get(swap.itemId);
|
||||
if (coinItem == Items.AIR) return stack;
|
||||
return new ItemStack(coinItem, swap.count);
|
||||
}
|
||||
|
||||
private record CoinSwap(ResourceLocation itemId, int count) {}
|
||||
|
||||
private static CoinSwap pickDenom(int emeralds) {
|
||||
if (emeralds >= 64) return new CoinSwap(COG_ID, Math.max(1, emeralds / 64));
|
||||
if (emeralds >= 8) return new CoinSwap(BEVEL_ID, Math.max(1, emeralds / 8));
|
||||
return new CoinSwap(SPUR_ID, Math.max(1, emeralds));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user