const { $RandomSource } = require("packages/net/minecraft/util/$RandomSource"); // level 5 just eat feast for 5 emeralds const feast_list = [ 'farmersdelight:rice_roll_medley_block', 'farmersdelight:shepherds_pie_block', 'farmersdelight:honey_glazed_ham_block', 'farmersdelight:stuffed_pumpkin_block', 'farmersdelight:roast_chicken_block', 'farmersdelight:pasta_with_meatballs', ]; // level 4 helps you with block collection const deco_list = [ '32x minecraft:diorite', '32x minecraft:granite', '32x minecraft:andesite', '32x minecraft:calcite', '32x minecraft:tuff', '32x minecraft:cobblestone', '32x minecraft:stone', '32x minecraft:cobbled_deepslate', '32x minecraft:deepslate', '32x minecraft:dirt', '32x minecraft:gravel', '32x minecraft:sand', '32x minecraft:mud', '32x minecraft:mud_bricks', '32x minecraft:stone_bricks', ]; // level 3 sells enchantment books for random gem const enchantment_list = { armorer: ['projectile_protection', 'blast_protection'], butcher: ['sharpness', 'sweeping'], cartographer: ['looting', 'fortune'], cleric: ['fire_aspect', 'fire_protection', 'flame'], farmer: ['efficiency', 'thorns'], fisherman: ['lure', 'luck_of_the_sea'], fletcher: ['power', 'punch', 'quick_charge', 'piercing'], leatherworker: ['feather_falling', 'depth_strider'], librarian: ['smite', 'bane_of_arthropods'], mason: ['protection', 'unbreaking'], shepherd: ['knockback', 'aqua_affinity'], toolsmith: ['silk_touch', 'fortune'], weaponsmith: ['sharpness', 'fire_aspect'], delightchef: ['flame', 'fire_aspect'], delightcook: ['flame', 'fire_aspect'], carpenter: ['knockback', 'fortune'], }; // level 2 villagers sell consumables for 2 emerald const consumables = { // chainmail / gold armor pieces armorer: [ 'chainmail_helmet', 'chainmail_chestplate', 'chainmail_leggings', 'chainmail_boots', 'golden_helmet', 'golden_chestplate', 'golden_leggings', 'golden_boots' ], // raw meat, bones, leather butcher: ['3x beef', '3x porkchop', '3x mutton', '3x environmental:vension', '4x chicken', '4x environmental:duck', '6x rabbit', '5x bone'], // explorer maps cartographer: [], // torches, saltpeter, lanterns, salt, sulfur, spider eyes, rotten flesh cleric: ['32x torch', '16x lantern', '16x spelunkery:salt', '16x spelunkery:saltpeter'], // bonemeal, seed, hoe, organic_compost farmer: ['6x bone_meal', '2x farmersdelight:organic_compost', '24x farmersdelight:straw', '18x supplementaries:flax'], // fish bones, leech, worm, fishing rod, treasure boxes fisherman: ['2x aquaculture:fish_bones', '3x aquaculture:leech', '5x aquaculture:worm', '8x aquaculture:box', '5x aquaculture:lockbox', '2x aquaculture:treasure_chest'], // various potion arrows, bows, crossbows fletcher: ['bow', 'bow', 'bow', 'crossbow', 'crowssbow'], // leather armor, bedrolls leatherworker: ['leather_helmet', 'leather_chestplate', 'leather_leggings', 'leather_boots'], // various dyes librarian: ['5x black_dye', '5x blue_dye', '5x brown_dye', '5x cyan_dye', '5x gray_dye', '5x green_dye', '5x light_blue_dye', '5x light_gray_dye', '5x lime_dye', '5x magenta_dye', '5x orange_dye', '5x pink_dye', '5x purple_dye', '5x red_dye', '5x yellow_dye', '5x white_dye'], // brick, concrete, terracotta mason: ['12x brick', '12x supplementaries:ash_brick', '12x terracotta', '24x concrete'], // wool, eggs, milk, honey, slime ball shepherd: ['3x white_wool', '8x environmental:duck_egg', '8x autumnity:turkey_egg', '4x environmental:yak_hair'], // shovel, pick toolsmith: ['iron_shovel', 'iron_pickaxe', 'golden_shovel', 'golden_pickaxe'], // sword, axe weaponsmith: ['iron_sword', 'iron_axe', 'golden_sword', 'golden_axe'], // rice, spaghetti, dough, crust delightchef: ['8x farmersdelight:rice', '10x farmersdelight:raw_pasta', '10x farmersdelight:crust', '16x sugar'], // cut raw meat delightcook: ['16x bowl', '16x glass_bottle', '1x bucket', '8x farmersdelight:minced_beef', '8x farmersdelight:bacon'], // planks carpenter: ['32x oak_planks', '32x spruce_planks', '32x birch_planks', '32x jungle_planks', '32x pine_planks', '32x bamboo_planks', '32x cherry_planks'], }; const enchant_consumables = { armorer: 1, fletcher: 1, leatherworker: 2, toolsmith: 3, weaponsmith: 2 }; // level 1 villagers buy food for 1 emerald const food_list = [ '8x minecraft:apple', '8x minecraft:beetroot', '8x minecraft:baked_potato', '8x minecraft:bread', '8x minecraft:carrot', '8x minecraft:melon_slice', '8x minecraft:sweet_berries', '8x farmersdelight:pumpkin_slice', '8x farmersdelight:cabbage_leaf', '8x farmersdelight:tomato', '8x farmersdelight:cooked_rice', '8x farmersdelight:fried_egg', '8x neapolitan:strawberries', '8x neapolitan:banana', '8x neapolitan:dried_banana', '8x neapolitan:roasted_adzuki_beans', '8x neapolitan:mint_candies', '8x atmospheric:passion_fruit', '8x atmospheric:currant', '8x atmospheric:dragon_fruit', '8x atmospheric:candied_orange_slices', '8x atmospheric:roasted_yucca_fruit', ]; /** * @param {string[]} list * @param {$RandomSource} random * @returns {string} */ function roll(list, random){ if (list == null) throw new Error("list not valid..."); return list[Math.floor(random.nextFloat() * list.length)]; } /** * @param {string[]} ids * @param {string[]} enchant_pool * @param {number} enchant_rolls * @param {$RandomSource} random * @param {boolean} damage */ function genEnchanted(ids, enchant_pool, enchant_rolls, random, damage ){ const id = roll(ids, random); const item = Item.of(id); if(damage) { const dmg = Math.floor(item.maxDamage * (0.2 + 0.1 * random.nextFloat())); item.setDamageValue(dmg); } /** @type {Record} */ const enchantments = {}; for(let i = 0; i < enchant_rolls; i ++){ const enchant = roll(enchant_pool, random); enchantments[enchant] = (enchantments[enchant] || 0) + 1; } for(const [enchant, level] of Object.entries(enchantments)){ item.enchant(enchant, level); } return item; } const gems = [ 'diamond', '2x gold_ingot', '3x caverns_and_chasms:spinel', '4x spelunkery:cinnabar', '5x lapis_lazuli', '6x amethyst_shard', ]; /** * explorer maps: * - villages: not very useful, since we're already in one. could be good for biome discovery though. * - dungeons & mineshafts: fights with chests but are optional. good candidates. * - shipwreck / ocean_ruins / pyramids / temples / pillager_outpost: optional treasures. * - woodland mansion & ocean monuments: important for exclusive loot. * - ancient city: again, exclusive loot. * - nether portal: required to get to the nether. * * it makes little sense to gate the maps, since structures can be stumbled upon anyway and are optional anyway. * only two maps are created per cartographer upgrade, and you need to move them into the wild. * so we want to tie new maps to villages, since new villages have new cartographers. * * let's do 3 trades * - a village from a different biome. * - one of woodland mansion / ocean monument / ancient city / nether portal. * - one of dungeon / mineshaft / pyramid / temple / pillager outpost. */ const treasure_structures = { '#minecraft:mineshaft': 10, '#minecraft:pillager_outpost' : 10, '#betterdungeons:better_dungeons': 5, }; const progression_structures = { '#minecraft:ruined_portal' : 15, '#minecraft:ancient_city' : 5, '#minecraft:mansion' : 5, '#betteroceanmonuments:better_ocean_monuments' : 5, }; const village_structures = { 'village_desert': 1, 'village_plains': 1, 'village_savanna': 1, 'village_snowy': 1, 'village_taiga': 1, 'mmv:village_jungle': 1, 'mmv:village_swamp': 1, "atmospheric:village_scrubland" : 1 }; /** * @param {Record} map */ function make_weighted_list(map){ const list = MoreJS.weightedList(); for(const [key, weight] of Object.entries(map)){ list.add(weight, key); } return list; } MoreJSEvents.villagerTrades((event) => { event.removeVanillaTrades(); event.removeModdedTrades(); const villages = make_weighted_list(village_structures) const villageTrade = VillagerUtils.createStructureMapTrade(['2x emerald', 'map'], villages); villageTrade.maxUses(1); event.addTrade('cartographer', 2, villageTrade); const treasures = make_weighted_list(treasure_structures); const treasureTrade = VillagerUtils.createStructureMapTrade(['3x emerald', 'map'], treasures); treasureTrade.maxUses(1); event.addTrade('cartographer', 2, treasureTrade); const progression = make_weighted_list(progression_structures); const progressionTrade = VillagerUtils.createStructureMapTrade(['5x emerald', 'map'], progression); progressionTrade.maxUses(1); event.addTrade('cartographer', 2, progressionTrade); const professions = VillagerUtils.getProfessions(); professions.forEach(prof => { const profName = prof.name(); if(profName === 'nitwit') return; const rolls = enchant_consumables[profName] || 0; const enchantments = enchantment_list[profName]; const staples = consumables[profName]; if(!enchantments) throw new Error("No enchantments for " + profName); if(!staples) throw new Error("No staples for " + profName); // 1 level 5 trade event.addCustomTrade(prof, 5, (offer, entity, random) => { offer.setFirstInput(roll(feast_list, random)); offer.setOutput('5x emerald'); offer.setMaxUses(1); offer.setVillagerExperience(1); offer.setPriceMultiplier(1); }); // 2 for the rest for(let i = 0; i < 2; i ++) { // level 4 event.addCustomTrade(prof, 4, (offer, entity, random) => { offer.setFirstInput('3x emerald'); offer.setOutput(roll(deco_list, random)); offer.setMaxUses(4); offer.setVillagerExperience(1); offer.setPriceMultiplier(1); }); // level 3 event.addCustomTrade(prof, 3, (offer, entity, random) => { offer.setFirstInput(roll(gems, random)); offer.setSecondInput('8x emerald'); offer.setOutput(genEnchanted(['enchanted_book'], enchantments, rolls+1, random, false)); offer.setMaxUses(1); offer.setVillagerExperience(1); offer.setPriceMultiplier(1); }); // level 2 if(staples.length > 0) event.addCustomTrade(prof, 2, (offer, entity, random) => { offer.setFirstInput('2x emerald'); if(rolls > 0) offer.setOutput(genEnchanted(staples, enchantments, rolls, random, true)); else offer.setOutput(roll(staples, random)); offer.setMaxUses(1); offer.setVillagerExperience(1); offer.setPriceMultiplier(1); }); // level 1 event.addCustomTrade(prof, 1, (offer, entity, random) => { offer.setFirstInput(roll(food_list, random)); offer.setOutput('1x emerald'); offer.setMaxUses(1); offer.setVillagerExperience(5); offer.setPriceMultiplier(1); }); } }); });