390 lines
14 KiB
JavaScript
390 lines
14 KiB
JavaScript
const { $RandomSource } = require("packages/net/minecraft/util/$RandomSource");
|
|
const { $Villager } = require("packages/net/minecraft/world/entity/npc/$Villager");
|
|
|
|
// 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',
|
|
|
|
'abnormals_delight:perch_with_mushrooms',
|
|
'abnormals_delight:pike_with_beetroot',
|
|
'abnormals_delight:vension_with_bamboo_shoots',
|
|
'abnormals_delight:passion_fruit_glazed_duck',
|
|
];
|
|
|
|
// 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: ['fishing_rod'],
|
|
// various potion arrows, bows, crossbows
|
|
fletcher: ['8x arrow', '4x caverns_and_chasms:large_arrow', '16x caverns_and_chasms:blunt_arrow', '8x savage_and_ravages:mischief_arrow'],
|
|
// 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', 'iron_hoe', 'golden_shovel', 'golden_pickaxe', 'golden_hoe'],
|
|
// sword, axe
|
|
weaponsmith: ['iron_sword', 'iron_axe', 'golden_sword', 'golden_axe', 'bow', 'crossbow'],
|
|
// 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: [],
|
|
};
|
|
|
|
const enchant_consumables = {
|
|
armorer: 1,
|
|
leatherworker: 2,
|
|
toolsmith: 3,
|
|
weaponsmith: 2,
|
|
fisherman: 2,
|
|
};
|
|
|
|
// level 1 villagers buy food for 1 emerald
|
|
const food_list = [
|
|
'8x minecraft:apple',
|
|
'8x minecraft:beetroot',
|
|
'8x minecraft:melon_slice',
|
|
'8x minecraft:sweet_berries',
|
|
|
|
'8x farmersdelight:cabbage_leaf',
|
|
'8x farmersdelight:tomato',
|
|
'8x farmersdelight:fried_egg',
|
|
'8x farmersdelight:onion',
|
|
|
|
'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',
|
|
|
|
'brewinandchewin:beer',
|
|
'brewinandchewin:vodka',
|
|
'brewinandchewin:mead',
|
|
'brewinandchewin:rice_wine',
|
|
'brewinandchewin:pale_jane',
|
|
'brewinandchewin:egg_grog',
|
|
'brewinandchewin:bloody_mary',
|
|
'brewinandchewin:salty_folly',
|
|
'brewinandchewin:saccharine_rum',
|
|
];
|
|
|
|
const main_diet = {
|
|
'desert': '8x farmersdelight:milk_bottle',
|
|
'plains': '8x bread',
|
|
'savanna': '8x carrot',
|
|
'snowy': '8x pumpkin_slice',
|
|
'taiga': '8x baked_potato',
|
|
'jungle': '8x farmersdelight:cooked_rice',
|
|
'swamp': '8x mushroom_stew',
|
|
'atmospheric:scrubland': '8x atmospheric:dragon_fruit',
|
|
default: '8x bread'
|
|
}
|
|
|
|
/**
|
|
* @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<string, number>} */
|
|
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
|
|
};
|
|
const village_logs = {
|
|
'desert': 'minecraft:cactus',
|
|
'plains': 'minecraft:oak_log',
|
|
'savanna': 'minecraft:acacia_log',
|
|
'snowy': 'minecraft:spruce_log',
|
|
'taiga': 'minecraft:birch_log',
|
|
'jungle': 'minecraft:jungle_log',
|
|
'swamp': 'minecraft:dark_oak_log',
|
|
'atmospheric:scrubland': 'atmospheric:yucca_log'
|
|
};
|
|
// various saplings
|
|
const carpenter_trades = [
|
|
'minecraft:oak_sapling',
|
|
'minecraft:spruce_sapling',
|
|
'minecraft:birch_sapling',
|
|
'minecraft:acacia_sapling',
|
|
'minecraft:jungle_sapling',
|
|
'minecraft:dark_oak_sapling',
|
|
'minecraft:mangrove_sapling',
|
|
'minecraft:cherry_sapling',
|
|
'minecraft:azalea_sapling',
|
|
];
|
|
/**
|
|
* @param {Record<string, number>} 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();
|
|
|
|
// cartographers: explorer maps
|
|
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);
|
|
|
|
// carpenters: logs, saplings, planks, tree barks
|
|
event.addCustomTrade('sawmill:carpenter', 2, (offer, entity, random) => {
|
|
/** @type {$Villager} */
|
|
const villager = entity;
|
|
const villagerData = villager.getVillagerData();
|
|
const biome = villagerData.getType().toString();
|
|
const log = village_logs[biome] || 'minecraft:oak_log';
|
|
if(!village_logs[biome]) console.warn("No log for " + biome);
|
|
|
|
offer.setFirstInput('2x emerald');
|
|
offer.setOutput('16x ' + log);
|
|
offer.setMaxUses(1);
|
|
offer.setVillagerExperience(1);
|
|
offer.setPriceMultiplier(0.05);
|
|
});
|
|
event.addCustomTrade('sawmill:carpenter', 2, (offer, entity, random) => {
|
|
offer.setFirstInput('2x emerald');
|
|
offer.setOutput(roll(carpenter_trades, random));
|
|
offer.setMaxUses(1);
|
|
offer.setVillagerExperience(1);
|
|
offer.setPriceMultiplier(0.05);
|
|
});
|
|
|
|
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(0.2);
|
|
});
|
|
// 1 level 1 trade
|
|
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(0.05);
|
|
});
|
|
// 1 diet trade
|
|
event.addCustomTrade(prof, 1, (offer, entity, random) => {
|
|
const villagerData = entity.getVillagerData();
|
|
const biome = villagerData.getType().toString();
|
|
const diet = main_diet[biome] || main_diet.default;
|
|
if(!main_diet[biome]) console.log("No diet for " + biome);
|
|
offer.setFirstInput(diet);
|
|
offer.setOutput('1x emerald');
|
|
offer.setMaxUses(1);
|
|
offer.setVillagerExperience(5);
|
|
offer.setPriceMultiplier(0.05);
|
|
});
|
|
// 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(0.05);
|
|
});
|
|
// level 3
|
|
event.addCustomTrade(prof, 3, (offer, entity, random) => {
|
|
offer.setFirstInput('8x emerald');
|
|
offer.setSecondInput(roll(gems, random));
|
|
offer.setOutput(genEnchanted(['enchanted_book'], enchantments, rolls+1, random, false));
|
|
offer.setMaxUses(1);
|
|
offer.setVillagerExperience(1);
|
|
offer.setPriceMultiplier(0.2);
|
|
});
|
|
// 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(0.05);
|
|
});
|
|
}
|
|
});
|
|
});
|