#![allow(dead_code, unused_variables)] use rand::{Rng}; use rand::distributions::{WeightedIndex, Distribution}; use serde::{Serialize, Deserialize}; use entity::character::SectionID; use maps::room::{Difficulty, Episode}; use maps::area::MapArea; use crate::{ItemDropType, load_data_file}; use maps::object::{MapObject, MapObjectType, FixedBoxDropType}; use crate::rare_drop_table::{RareDropTable, RareDropItem}; use crate::generic_weapon::GenericWeaponTable; use crate::generic_armor::GenericArmorTable; use crate::generic_shield::GenericShieldTable; use crate::generic_unit::GenericUnitTable; use crate::tool_table::ToolTable; #[derive(Debug, Serialize, Deserialize)] struct BoxDropRate { weapon_rate: u32, armor_rate: u32, shield_rate: u32, unit_rate: u32, tool_rate: u32, meseta_rate: u32, nothing_rate: u32, min_meseta: u32, max_meseta: u32, } #[derive(Debug, Serialize, Deserialize)] struct BoxDropRates { area1: BoxDropRate, area2: BoxDropRate, area3: BoxDropRate, area4: BoxDropRate, area5: BoxDropRate, area6: BoxDropRate, area7: BoxDropRate, area8: BoxDropRate, area9: BoxDropRate, area10: BoxDropRate, } impl BoxDropRates { fn rates_by_area(&self, map_area: &MapArea) -> &BoxDropRate { match map_area.drop_area_value().unwrap() { 0 => &self.area1, 1 => &self.area2, 2 => &self.area3, 3 => &self.area1, 4 => &self.area1, 5 => &self.area6, 6 => &self.area7, 7 => &self.area8, 8 => &self.area9, 9 => &self.area10, _ => panic!() } } } #[derive(Debug, Serialize, Deserialize)] struct BoxRareRateRaw { item: String, rate: f32, } #[derive(Debug, Serialize, Deserialize)] struct BoxRareRatesRaw { area1: Vec, area2: Vec, area3: Vec, area4: Vec, area5: Vec, area6: Vec, area7: Vec, area8: Vec, area9: Vec, area10: Vec, } struct BoxRareRate { item: RareDropItem, rate: f32, } struct BoxRareRates { area1: Vec, area2: Vec, area3: Vec, area4: Vec, area5: Vec, area6: Vec, area7: Vec, area8: Vec, area9: Vec, area10: Vec, } impl BoxRareRates { fn new(raw: BoxRareRatesRaw) -> BoxRareRates { BoxRareRates { area1: raw.area1.into_iter().map(|i| { BoxRareRate {item: RareDropItem::from_string(i.item), rate: i.rate} }).collect(), area2: raw.area2.into_iter().map(|i| { BoxRareRate {item: RareDropItem::from_string(i.item), rate: i.rate} }).collect(), area3: raw.area3.into_iter().map(|i| { BoxRareRate {item: RareDropItem::from_string(i.item), rate: i.rate} }).collect(), area4: raw.area4.into_iter().map(|i| { BoxRareRate {item: RareDropItem::from_string(i.item), rate: i.rate} }).collect(), area5: raw.area5.into_iter().map(|i| { BoxRareRate {item: RareDropItem::from_string(i.item), rate: i.rate} }).collect(), area6: raw.area6.into_iter().map(|i| { BoxRareRate {item: RareDropItem::from_string(i.item), rate: i.rate} }).collect(), area7: raw.area7.into_iter().map(|i| { BoxRareRate {item: RareDropItem::from_string(i.item), rate: i.rate} }).collect(), area8: raw.area8.into_iter().map(|i| { BoxRareRate {item: RareDropItem::from_string(i.item), rate: i.rate} }).collect(), area9: raw.area9.into_iter().map(|i| { BoxRareRate {item: RareDropItem::from_string(i.item), rate: i.rate} }).collect(), area10: raw.area10.into_iter().map(|i| { BoxRareRate {item: RareDropItem::from_string(i.item), rate: i.rate} }).collect(), } } fn rates_by_area(&self, map_area: &MapArea) -> &Vec { match map_area.drop_area_value().unwrap() { 0 => &self.area1, 1 => &self.area2, 2 => &self.area3, 3 => &self.area1, 4 => &self.area1, 5 => &self.area6, 6 => &self.area7, 7 => &self.area8, 8 => &self.area9, 9 => &self.area10, _ => panic!() } } } pub struct BoxDropTable { box_rates: BoxDropRates, rare_rates: BoxRareRates, rare_stats: RareDropTable, weapon_table: GenericWeaponTable, armor_table: GenericArmorTable, shield_table: GenericShieldTable, unit_table: GenericUnitTable, tool_table: ToolTable, } impl BoxDropTable { pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> BoxDropTable { let rates = load_data_file(episode, difficulty, section_id, "box_rare_rate.toml"); BoxDropTable { box_rates: load_data_file(episode, difficulty, section_id, "box_drop_rate.toml"), rare_rates: BoxRareRates::new(rates), rare_stats: RareDropTable::new(episode, difficulty, section_id), weapon_table: GenericWeaponTable::new(episode, difficulty, section_id), armor_table: GenericArmorTable::new(episode, difficulty, section_id), shield_table: GenericShieldTable::new(episode, difficulty, section_id), unit_table: GenericUnitTable::new(episode, difficulty, section_id), tool_table: ToolTable::new(episode, difficulty, section_id), } } fn rare_drop(&self, map_area: &MapArea, rng: &mut R) -> Option { self.rare_rates.rates_by_area(map_area).iter() .filter_map(|rate| { let rand: f32 = rng.gen(); if rand < rate.rate { Some(self.rare_stats.apply_item_stats(map_area, rate.item, rng)) } else { None } }).next() } fn random_box_drop(&self, map_area: &MapArea, rng: &mut R) -> Option { self.rare_drop(map_area, rng).or_else(|| { let rate = self.box_rates.rates_by_area(map_area); let type_weights = WeightedIndex::new([rate.weapon_rate, rate.armor_rate, rate.shield_rate, rate.unit_rate, rate.tool_rate, rate.meseta_rate, rate.nothing_rate]).unwrap(); let btype = type_weights.sample(rng); match btype { 0 => self.weapon_table.get_drop(map_area, rng), 1 => self.armor_table.get_drop(map_area, rng), 2 => self.shield_table.get_drop(map_area, rng), 3 => self.unit_table.get_drop(map_area, rng), 4 => self.tool_table.get_drop(map_area, rng), 5 => Some(ItemDropType::Meseta(rng.gen_range(rate.min_meseta, rate.max_meseta))), _ => None, } }) } fn fixed_box_drop(&self, fixed_drop: FixedBoxDropType, map_area: &MapArea, rng: &mut R) -> Option { match fixed_drop { FixedBoxDropType::Weapon => self.weapon_table.get_drop(map_area, rng), FixedBoxDropType::Armor => self.armor_table.get_drop(map_area, rng), // TODO: should this drop shield? FixedBoxDropType::Tool => self.tool_table.get_drop(map_area, rng), FixedBoxDropType::Meseta => { let rate = self.box_rates.rates_by_area(map_area); Some(ItemDropType::Meseta(rng.gen_range(rate.min_meseta, rate.max_meseta))) }, FixedBoxDropType::Random => self.random_box_drop(map_area, rng), FixedBoxDropType::Specific(value) => { let mut buf: [u8; 16] = [0; 16]; buf[0..4].copy_from_slice(&u32::to_be_bytes(value)); ItemDropType::parse_item_from_bytes(buf) }, } } pub fn get_drop(&self, map_area: &MapArea, object: &MapObject, rng: &mut R) -> Option { match object.object { MapObjectType::Box | MapObjectType::EnemyBox | MapObjectType::RuinsBox| MapObjectType::RuinsEnemyBox | MapObjectType::CcaBox => { self.random_box_drop(map_area, rng) }, MapObjectType::FixedBox(f) | MapObjectType::EnemyFixedBox(f) | MapObjectType::RuinsFixedBox(f) | MapObjectType::RuinsEnemyFixedBox(f) | MapObjectType::CcaFixedBox(f) => { self.fixed_box_drop(f, map_area, rng) }, _ => None, } } } #[cfg(test)] mod test { use super::*; use rand::{SeedableRng}; #[test] fn test_box_drops() { let mut rng = rand_chacha::ChaCha20Rng::from_seed([23;32]); let bdt = BoxDropTable::new(Episode::One, Difficulty::Ultimate, SectionID::Skyly); println!("{:?}", bdt.get_drop(&MapArea::Forest1, &MapObject {object: MapObjectType::Box, map: MapArea::Forest1, dropped_item: false}, &mut rng)); } }