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_id=bnstoolkit
|
||||||
mod_name=Brass and Sigil Toolkit
|
mod_name=Brass and Sigil Toolkit
|
||||||
mod_license=All Rights Reserved
|
mod_license=All Rights Reserved
|
||||||
mod_version=0.2.0
|
mod_version=0.3.0
|
||||||
mod_group_id=uk.sijbers.bnstoolkit
|
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