package cyano.basemetals.init; import cyano.basemetals.items.ItemMetalCrackHammer; import cyano.basemetals.items.ItemMetalIngot; import cyano.basemetals.material.MetalMaterial; import cyano.basemetals.util.VillagerTradeHelper; import net.minecraft.entity.passive.EntityVillager.ITradeList; import net.minecraft.entity.passive.EntityVillager.ListEnchantedItemForEmeralds; import net.minecraft.entity.passive.EntityVillager.PriceInfo; import net.minecraft.item.*; import net.minecraft.village.MerchantRecipe; import net.minecraft.village.MerchantRecipeList; import net.minecraftforge.fml.common.FMLLog; import org.apache.logging.log4j.Level; import java.util.*; public abstract class VillagerTrades{ public static final int TRADES_PER_LEVEL = 4; private static boolean initDone = false; public static void init(){ if(initDone)return; cyano.basemetals.init.Materials.init(); cyano.basemetals.init.Items.init(); // Minecraft stores trades in a 4D array: // [Profession ID][Sub-profession ID][villager level - 1][trades] final int size = cyano.basemetals.init.Materials.getAllMetals().size(); final Map<MetalMaterial, List<Item>> allArmors = new HashMap<>(size); final Map<MetalMaterial, Item> allHammers = new HashMap<>(size); final Map<MetalMaterial, Item> allSwords = new HashMap<>(size); final Map<MetalMaterial, Item> allHoes = new HashMap<>(size); final Map<MetalMaterial, Item> allAxes = new HashMap<>(size); final Map<MetalMaterial, Item> allPickAxes = new HashMap<>(size); final Map<MetalMaterial, Item> allShovels = new HashMap<>(size); final Map<MetalMaterial, Item> allIngots = new HashMap<>(size); final Map<Item,Integer> tradeLevelMap = new HashMap<>(); cyano.basemetals.init.Items.getItemsByMetal().entrySet().stream() .forEach((Map.Entry<MetalMaterial,List<Item>> e)->{ final MetalMaterial m = e.getKey(); if(m == null) return; for(Item i : e.getValue()){ if(i instanceof ItemArmor){allArmors.computeIfAbsent(m, (MetalMaterial g)->new ArrayList<>()).add(i); continue;} if(i instanceof ItemMetalCrackHammer){allHammers.put(m,i); continue;} if(i instanceof ItemSword){allSwords.put(m,i); continue;} if(i instanceof ItemHoe){allHoes.put(m,i); continue;} if(i instanceof ItemAxe){allAxes.put(m,i); continue;} if(i instanceof ItemPickaxe){allPickAxes.put(m,i); continue;} if(i instanceof ItemSpade){allShovels.put(m,i); continue;} if(i instanceof ItemMetalIngot){allIngots.put(m,i); continue;} } } ); Map<Integer,List<ITradeList>> tradesTable = new HashMap<>(); // integer is used as byte data: (unused) (profession) (career) (level) for(MetalMaterial m : cyano.basemetals.init.Materials.getAllMetals()){ float value = m.hardness + m.strength + m.magicAffinity + m.getToolHarvestLevel(); if(m.isRare) continue; // for reference, iron has a value of 21.5, gold would be 14, copper is 14, and diamond is 30 int emeraldPurch = emeraldPurchaseValue(value); int emeraldSale = emeraldSaleValue(value); int tradeLevel = tradeLevel(value); if(emeraldPurch > 64 || emeraldSale > 64) continue; // too expensive int armorsmith = (3 << 16) | (1 << 8) | (tradeLevel); int weaponsmith = (3 << 16) | (2 << 8) | (tradeLevel); int toolsmith = (3 << 16) | (3 << 8) | (tradeLevel); if(allIngots.containsKey(m)){ ITradeList[] ingotTrades = makeTradePalette( makePurchasePalette(emeraldPurch, 12, allIngots.get(m)), makeSalePalette(emeraldSale, 12, allIngots.get(m)) ); tradesTable.computeIfAbsent(armorsmith,(Integer key)->new ArrayList<>()) .addAll(Arrays.asList(ingotTrades)); tradesTable.computeIfAbsent(weaponsmith,(Integer key)->new ArrayList<>()) .addAll(Arrays.asList(ingotTrades)); tradesTable.computeIfAbsent(toolsmith,(Integer key)->new ArrayList<>()) .addAll(Arrays.asList(ingotTrades)); } if(allHammers.containsKey(m) && allPickAxes.containsKey(m) && allAxes.containsKey(m) && allShovels.containsKey(m) && allHoes.containsKey(m)){ tradesTable.computeIfAbsent(toolsmith,(Integer key)->new ArrayList<>()) .addAll(Arrays.asList( makeTradePalette( makePurchasePalette(emeraldPurch, 1, allPickAxes.get(m), allAxes.get(m), allShovels.get(m), allHoes.get(m))) )); tradesTable.computeIfAbsent((3 << 16) | (3 << 8) | (tradeLevel+1),(Integer key)->new ArrayList<>()) .addAll(Arrays.asList(makeTradePalette( makePurchasePalette(emeraldPurch, 1, allHammers.get(m))) )); } if(allSwords.containsKey(m)){ tradesTable.computeIfAbsent(weaponsmith,(Integer key)->new ArrayList<>()) .addAll(Arrays.asList( makeTradePalette( makePurchasePalette(emeraldPurch + (int)(m.getBaseAttackDamage() / 2)-1, 1, allSwords.get(m))) )); } if(allArmors.containsKey(m)){ tradesTable.computeIfAbsent(armorsmith,(Integer key)->new ArrayList<>()) .addAll(Arrays.asList( makeTradePalette( makePurchasePalette(emeraldPurch + (int)(m.hardness / 2), 1, allArmors.get(m).toArray(new Item[0])) ))); } if(m.magicAffinity > 5){ if(allHammers.containsKey(m)) tradesTable.computeIfAbsent((3 << 16) | (3 << 8) | (tradeLevel+2),(Integer key)->new ArrayList<>()) .addAll(Arrays.asList( new ListEnchantedItemForEmeralds(allHammers.get(m), new PriceInfo(emeraldPurch+7, emeraldPurch+12)))); if(allPickAxes.containsKey(m)) tradesTable.computeIfAbsent((3 << 16) | (3 << 8) | (tradeLevel+1),(Integer key)->new ArrayList<>()) .addAll(Arrays.asList( new ListEnchantedItemForEmeralds(allPickAxes.get(m), new PriceInfo(emeraldPurch+7, emeraldPurch+12)))); if(allArmors.containsKey(m)) { for(int i = 0; i < allArmors.get(m).size(); i++) tradesTable.computeIfAbsent((3 << 16) | (1 << 8) | (tradeLevel+1),(Integer key)->new ArrayList<>()) .addAll(Arrays.asList( new ListEnchantedItemForEmeralds(allArmors.get(m).get(i), new PriceInfo(emeraldPurch+7 + (int)(m.hardness / 2), emeraldPurch+12 + (int)(m.hardness / 2))))); } if(allSwords.containsKey(m)) tradesTable.computeIfAbsent((3 << 16) | (2 << 8) | (tradeLevel+1),(Integer key)->new ArrayList<>()) .addAll(Arrays.asList( new ListEnchantedItemForEmeralds(allSwords.get(m), new PriceInfo(emeraldPurch+7 + (int)(m.getBaseAttackDamage() / 2)-1, emeraldPurch+12 + (int)(m.getBaseAttackDamage() / 2)-1)))); } } tradesTable.computeIfAbsent((3 << 16) | (1 << 8) | (1),(Integer key)->new ArrayList<>()) .addAll(Arrays.asList( makePurchasePalette(1,10,Items.carbon_powder))); tradesTable.computeIfAbsent((3 << 16) | (2 << 8) | (1),(Integer key)->new ArrayList<>()) .addAll(Arrays.asList( makePurchasePalette(1,10,Items.carbon_powder))); tradesTable.computeIfAbsent((3 << 16) | (3 << 8) | (1),(Integer key)->new ArrayList<>()) .addAll(Arrays.asList( makePurchasePalette(1,10,Items.carbon_powder))); for(Integer k : tradesTable.keySet()){ List<ITradeList> trades = tradesTable.get(k); int profession = (k >> 16) & 0xFF; int career = (k >> 8) & 0xFF; int level = k & 0xFF; try { VillagerTradeHelper.insertTrades(profession, career, level, new MultiTradeGenerator( TRADES_PER_LEVEL, trades )); } catch (NoSuchFieldException | IllegalAccessException ex) { FMLLog.log(Level.ERROR,ex,"Java Reflection Exception"); } } initDone = true; } private static int emeraldPurchaseValue(float value){ return Math.max(1, (int)(value * 0.2F)); } private static int emeraldSaleValue(float value){ return Math.max(1, emeraldPurchaseValue(value) / 3); } private static int tradeLevel(float value){ return Math.max(1, Math.min(4,(int)(value * 0.1F))); } private static int fluctuation(int baseValue){ if(baseValue <= 1) return 0; return Math.max(2, baseValue / 4); } private static ITradeList[] makePurchasePalette(int emeraldPrice, int stackSize, Item... items){ ITradeList[] trades = new ITradeList[items.length]; for(int i = 0; i < items.length; i++){ Item item = items[i]; trades[i] = new SimpleTrade( new ItemStack(net.minecraft.init.Items.EMERALD,emeraldPrice,0), fluctuation(emeraldPrice), (ItemStack)null, 0, new ItemStack(item,stackSize,0), 0); } return trades; } private static ITradeList[] makeSalePalette(int emeraldValue, int stackSize, Item... items){ ITradeList[] trades = new ITradeList[items.length]; for(int i = 0; i < items.length; i++){ Item item = items[i]; trades[i] = new SimpleTrade( new ItemStack(item,stackSize,0), fluctuation(stackSize), (ItemStack)null, 0, new ItemStack(net.minecraft.init.Items.EMERALD,emeraldValue,0), 0); } return trades; } private static ITradeList[] makeTradePalette(ITradeList[]... list){ if(list.length == 1) return list[0]; int totalsize = 0; for(ITradeList[] e : list){ totalsize += e.length; } ITradeList[] concat = new ITradeList[totalsize]; int index = 0; int element = 0; while(index < totalsize){ System.arraycopy(list[element], 0, concat, index, list[element].length); index += list[element].length; element++; } return concat; } /** * This ITradeList object holds a list of ITradeLists and picks a few at random to place in a merchant's trade menu. */ public static class MultiTradeGenerator implements ITradeList{ private final int numberOfTrades; private final ITradeList[] trades; /** * Creates an ITradeList instanec that randomly adds multiple trades at a time * @param tradeCount Number of trades to add to the merchant's trade menu * @param tradePalette The trades to randomly choose from */ public MultiTradeGenerator(int tradeCount, List<ITradeList> tradePalette){ this.numberOfTrades = Math.min(tradeCount,tradePalette.size()); trades = tradePalette.toArray(new ITradeList[tradePalette.size()]); } /** * Invoked when the merchant generates its trade menu * @param recipeList existing trade menu * @param random a psuedorandom number generator instance */ @Override public void modifyMerchantRecipeList(MerchantRecipeList recipeList, Random random) { for(int n = 0; n < numberOfTrades; n++){ trades[random.nextInt(trades.length)].modifyMerchantRecipeList(recipeList,random); } } /** * For debugging purposes only * @return String representastion */ @Override public String toString(){ return MultiTradeGenerator.class.getSimpleName()+": "+numberOfTrades+" trades chosen from "+ Arrays.toString(trades); } } /** * A simple, easy to use ITradeList class that holds a single trade recipe */ public static class SimpleTrade implements ITradeList{ private final ItemStack input1; private final int maxInputMarkup1; private final ItemStack input2; private final int maxInputMarkup2; private final ItemStack output; private final int maxOutputMarkup; private final int maxTrades; private final int maxTradeVariation; @Override public String toString(){ return input1+" + "+input2+" => "+output; } /** * Full constructor for making a trade recipe * @param in1 Item for the left purchase price trade slot * @param variation1 range of variation in quantity of <code>in1</code> * @param in2 Item for the right purchase price trade slot. Can be <code>null</code> (and usually is) * @param variation2 range of variation in quantity of <code>in2</code> * @param out The item to be purchased (trade recipe output slot) * @param variationOut range of variation in quantity of <code>out</code> * @param numberTrades Max number of trades before this recipe is invalidated (-1 for infinite trading) * @param tradeNumberVariation range of variation in value of <code>numberTrades</code> (-1 to disable) */ public SimpleTrade(ItemStack in1, int variation1, ItemStack in2, int variation2, ItemStack out, int variationOut, int numberTrades, int tradeNumberVariation){ this.input1 = in1; this.maxInputMarkup1 = variation1; this.input2 = in2; this.maxInputMarkup2 = variation2; this.output = out; this.maxOutputMarkup = variationOut; this.maxTrades = numberTrades; this.maxTradeVariation = tradeNumberVariation; } /** * Constructor for making a simple two-for-one trade recipe with price variation * @param in1 Item for the left purchase price trade slot * @param v1 range of variation in quantity of <code>in1</code> * @param in2 Item for the right purchase price trade slot. Can be <code>null</code> (and usually is) * @param v2 range of variation in quantity of <code>in2</code> * @param out The item to be purchased (trade recipe output slot) * @param vout range of variation in quantity of <code>out</code> */ public SimpleTrade(ItemStack in1, int v1, ItemStack in2, int v2, ItemStack out, int vout){ this(in1,v1,in2,v2,out,vout,-1,-1); } /** * Constructor for making a simple one-for-one trade with price variation * @param in1 Item for the left purchase price trade slot * @param v1 range of variation in quantity of <code>in1</code> * @param out The item to be purchased (trade recipe output slot) * @param vout range of variation in quantity of <code>out</code> */ public SimpleTrade(ItemStack in1, int v1, ItemStack out, int vout){ this(in1,v1,null,0,out,vout,-1,-1); } /** * Constructor for making a simple one-for-one trade * @param in1 Item for the left purchase price trade slot * @param out The item to be purchased (trade recipe output slot) */ public SimpleTrade(ItemStack in1, ItemStack out){ this(in1,0,null,0,out,0,-1,-1); } /** * Invoked when the merchant generates its trade menu * @param recipeList existing trade menu * @param random a psuedorandom number generator instance */ @Override public void modifyMerchantRecipeList(MerchantRecipeList recipeList, Random random) { int numTrades = -1; if(maxTrades > 0){ if(maxTradeVariation > 0){ numTrades = Math.max(1,maxTrades + random.nextInt(maxTradeVariation) - maxTradeVariation / 2); } else { numTrades = maxTrades; } } ItemStack in1 = input1.copy(); if(maxInputMarkup1 > 0) in1.stackSize = in1.stackSize + random.nextInt(maxInputMarkup1); ItemStack in2 = null; if(input2 != null && input2.getItem() != null){ in2 = input2.copy(); if(maxInputMarkup2 > 0) in2.stackSize = in2.stackSize + random.nextInt(maxInputMarkup2); } ItemStack out = output.copy(); if(maxOutputMarkup > 0) out.stackSize = out.stackSize + random.nextInt(maxOutputMarkup); if(numTrades > 0){ recipeList.add(new MerchantRecipe(in1,in2,out,0,numTrades)); }else{ recipeList.add(new MerchantRecipe(in1,in2,out)); } } } }