1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
use rand::rngs::ThreadRng; use rand::Rng; /// An item that can be part of a drop table. These are Minecraft items. /// This list is incomplete, since it only contains the items involved in piglin barters from 1.16.1 and blaze rods. #[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Hash)] pub enum Item { None, Book, IronBoots, Potion, SplashPotion, IronNugget, Quartz, GlowstoneDust, MagmaCream, EnderPearl, String, FireCharge, Gravel, Leather, MetherBrick, Obsidian, CryingObsidian, SoulSand, BlazeRod, } /// The configuration for a drop, but not the drop itself. #[derive(Debug, Serialize, Deserialize, Clone)] pub struct DropConfig { pub item: Item, pub weight: u32, pub min_count: u32, pub max_count: u32, } impl DropConfig { /// Creates a drop config. /// ``` /// # use mc_sim::drop::*; /// // Create a drop config for an ender pearl. /// let drop_config = DropConfig::new(Item::EnderPearl, 20, 4, 8); /// # assert_eq!(Item::EnderPearl, drop_config.item); /// # assert_eq!(20, drop_config.weight); /// # assert_eq!(4, drop_config.min_count); /// # assert_eq!(8, drop_config.max_count); /// ``` pub fn new(item: Item, weight: u32, min_count: u32, max_count: u32) -> Self { Self { item, weight, min_count, max_count, } } } /// An item drop. The roll is the exact roll that was made that selected this item from the drop list. #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Drop { pub roll: u32, pub item: Item, pub count: u32, } /// An item drop simulator. Uses a drop list and uniform random number generation to select drops. /// This is based on the decompiled minecraft code and I believe it is an accurate representation of that logic. /// Some features of that code have been removed, as they don't play a part in bartering or blaze drops. #[derive(Debug)] pub struct DropSim { rng: ThreadRng, drop_list: Vec<DropConfig>, max_roll: u32, } impl DropSim { /// Creates a drop simulator. pub fn new(drop_list: Vec<DropConfig>) -> Self { let max_roll = drop_list.iter().fold(0, |sum, drop| sum + drop.weight); Self { rng: rand::thread_rng(), drop_list, max_roll, } } /// Gets an item drop using the drop list. /// ``` /// # use mc_sim::drop::*; /// // Create a drop list that has a 2:1 chance to be gravel over ender pearls /// // and drops 4 to 8 (inclusive) ender pearls. /// let drop_list = vec![ /// DropConfig::new(Item::Gravel, 20, 8, 32), /// DropConfig::new(Item::EnderPearl, 10, 4, 8), /// ]; /// /// // Create a drop simulator for that drop list. /// let mut drop_sim = DropSim::new(drop_list); /// /// // Get 1000 item drops. /// let drops: Vec<Drop> = (0..1000).map(|_| drop_sim.get_drop()).collect(); /// # for drop in drops { /// # match drop.item { /// # Item::EnderPearl => { /// # assert!(drop.roll >= 21); /// # assert!(drop.roll <= 30); /// # assert!(drop.count >= 4); /// # assert!(drop.count <= 8); /// # }, /// # Item::Gravel => { /// # assert!(drop.roll <= 20); /// # assert!(drop.count >= 8); /// # assert!(drop.count <= 32); /// # }, /// # _ => assert!(false) /// # }; /// # } /// ``` pub fn get_drop(&mut self) -> Drop { let roll: u32 = self.rng.gen_range(0..self.max_roll); let mut weight_remaining: i32 = roll as i32; let (_, item, count) = self .drop_list .iter() .find(|drop| { weight_remaining -= drop.weight as i32; weight_remaining <= 0 }) .map(|drop| { ( weight_remaining, drop.item, drop.min_count..(drop.max_count + 1), ) }) .unwrap(); Drop { roll, item, count: self.rng.gen_range(count), } } }