diff --git a/Cargo.toml b/Cargo.toml index cb3168f..09bf365 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,8 @@ path = "src/main.rs" libpso = { git = "http://git.sharnoth.com/jake/libpso" } async-std = { version = "1.4.0", features = ["unstable"] } futures = "0.3.1" -rand = "0.6.5" +rand = "0.7.3" +rand_chacha = "0.2.2" mio = "0.6" mio-extras = "2.0.5" crc = "^1.0.0" @@ -31,7 +32,10 @@ chrono = "*" serde = "*" serde_json = "*" ron = "*" +toml = "*" log = "*" fern = { version = "0.5", features = ["colored"] } byteorder = "1" +enum-utils = "0.1.2" +derive_more = { version = "0.99.3", features = ["display"]} diff --git a/src/entity/character.rs b/src/entity/character.rs index a1cf2b3..e4282ce 100644 --- a/src/entity/character.rs +++ b/src/entity/character.rs @@ -62,7 +62,7 @@ impl Into for CharacterClass { -#[derive(Copy, Clone, Hash, PartialEq, Eq)] +#[derive(Copy, Clone, Hash, PartialEq, Eq, derive_more::Display)] pub enum SectionID { Viridia, Greenill, diff --git a/src/entity/item/armor.rs b/src/entity/item/armor.rs index 3e2a235..f467ab0 100644 --- a/src/entity/item/armor.rs +++ b/src/entity/item/armor.rs @@ -1,10 +1,11 @@ use std::convert::{TryFrom, Into}; +use serde::{Serialize, Deserialize}; pub enum ArmorTypeError { UnknownArmor(String) } -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] pub enum ArmorType { Frame, Armor, diff --git a/src/entity/item/mod.rs b/src/entity/item/mod.rs index 5e56f7b..6c3ca05 100644 --- a/src/entity/item/mod.rs +++ b/src/entity/item/mod.rs @@ -43,6 +43,7 @@ pub enum ItemLocation { #[derive(Clone, Debug, PartialEq)] pub struct Weapon { pub equipped: bool, + pub tekked: bool, pub weapon: weapon::Weapon, } diff --git a/src/entity/item/weapon.rs b/src/entity/item/weapon.rs index e7f121c..0dd211a 100644 --- a/src/entity/item/weapon.rs +++ b/src/entity/item/weapon.rs @@ -1,7 +1,8 @@ use std::convert::{TryFrom, Into}; +use serde::{Serialize, Deserialize}; -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)] pub enum Attribute { Native, ABeast, @@ -12,8 +13,8 @@ pub enum Attribute { #[derive(Debug, Copy, Clone, PartialEq)] pub struct WeaponAttribute { - attr: Attribute, - value: u8, + pub attr: Attribute, + pub value: i8, } @@ -65,7 +66,7 @@ pub enum WeaponTypeError { UnknownWeapon(String) } -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] pub enum WeaponType { Saber, Brand, diff --git a/src/login/character.rs b/src/login/character.rs index 6c52bd1..e1bcecc 100644 --- a/src/login/character.rs +++ b/src/login/character.rs @@ -204,6 +204,7 @@ fn new_character(entity_gateway: &mut EG, user: &UserAccount, ItemDetail::Weapon( Weapon { equipped: true, + tekked: true, weapon: item::weapon::Weapon { weapon: new_weapon, grind: 0, diff --git a/src/main.rs b/src/main.rs index 810463c..c35631b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -86,6 +86,7 @@ fn main() { item::ItemDetail::Weapon( item::Weapon { equipped: true, + tekked: true, weapon: item::weapon::Weapon { weapon: item::weapon::WeaponType::Handgun, grind: 5, diff --git a/src/ship/drops/drop_table.rs b/src/ship/drops/drop_table.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/ship/drops/generic_armor.rs b/src/ship/drops/generic_armor.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/ship/drops/generic_shield.rs b/src/ship/drops/generic_shield.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/ship/drops/generic_unit.rs b/src/ship/drops/generic_unit.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/ship/drops/generic_weapon.rs b/src/ship/drops/generic_weapon.rs new file mode 100644 index 0000000..42db571 --- /dev/null +++ b/src/ship/drops/generic_weapon.rs @@ -0,0 +1,579 @@ +use std::collections::{HashMap, BTreeMap}; +use std::path::PathBuf; +use std::fs::File; +use std::io::Read; +use serde::{Serialize, Deserialize}; +use rand::{Rng, SeedableRng}; +use rand::distributions::{WeightedIndex, Distribution}; +use rand::seq::SliceRandom; + +use crate::entity::item::{ItemDetail, Weapon as WeaponDetail}; +use crate::entity::item::weapon::{Weapon, WeaponType, Attribute, WeaponAttribute, WeaponSpecial}; +use crate::ship::monster::MonsterType; +use crate::ship::room::{Difficulty, Episode}; +use crate::ship::map::MapVariantType; +use crate::entity::character::SectionID; + + +fn data_file_path(episode: Episode, difficulty: Difficulty, section_id: SectionID, filename: &str) -> PathBuf { + let mut path = PathBuf::from("data/drops/"); + path.push(episode.to_string()); + path.push(difficulty.to_string().to_lowercase()); + path.push(section_id.to_string().to_lowercase()); + path.push(filename); + path +} + +fn load_data_file(episode: Episode, difficulty: Difficulty, section_id: SectionID, filename: &str) -> T { + let path = data_file_path(episode, difficulty, section_id, filename); + let mut f = File::open(path).unwrap(); + let mut s = String::new(); + f.read_to_string(&mut s); + + toml::from_str::(s.as_str()).unwrap() +} + +#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Copy, Clone, Ord, PartialOrd)] +pub enum WeaponDropType { + Saber, + Sword, + Dagger, + Partisan, + Slicer, + Handgun, + Rifle, + Mechgun, + Shot, + Cane, + Rod, + Wand +} + +#[derive(Debug, Serialize, Deserialize, Default, Copy, Clone)] +pub struct WeaponRatio { + rate: u32, + rank: i32, + inc: u32, +} + + +#[derive(Debug, Serialize, Deserialize, Default, Clone)] +pub struct WeaponRatios { + saber: WeaponRatio, + sword: WeaponRatio, + dagger: WeaponRatio, + partisan: WeaponRatio, + slicer: WeaponRatio, + handgun: WeaponRatio, + rifle: WeaponRatio, + mechgun: WeaponRatio, + shot: WeaponRatio, + cane: WeaponRatio, + rod: WeaponRatio, + wand: WeaponRatio, +} + +impl WeaponRatios { + fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> WeaponRatios { + load_data_file(episode, difficulty, section_id, "weapon_rate.toml") + } +} + +#[derive(Debug, Serialize, Deserialize)] +struct GrindRates { + grind_rate: [[u32; 9]; 4] +} + +impl GrindRates { + fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> GrindRates { + load_data_file(episode, difficulty, section_id, "grind_rate.toml") + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, Copy)] +struct AttributeRate { + none: u32, + native: u32, + abeast: u32, + machine: u32, + dark: u32, + hit: u32, +} + +#[derive(Debug, Serialize, Deserialize)] +struct AttributeRates { + area1: AttributeRate, + area2: AttributeRate, + area3: AttributeRate, + area4: AttributeRate, + area5: AttributeRate, + area6: AttributeRate, + area7: AttributeRate, + area8: AttributeRate, + area9: AttributeRate, + area10: AttributeRate, +} + +impl AttributeRates { + fn get_by_area(&self, map_area: &MapVariantType) -> AttributeRate { + match map_area.area_value().unwrap() { + 0 => self.area1, + 1 => self.area2, + 2 => self.area3, + 3 => self.area4, + 4 => self.area5, + 5 => self.area6, + 6 => self.area7, + 7 => self.area8, + 8 => self.area9, + _ => self.area10, + } + } +} + +#[derive(Debug, Serialize, Deserialize, Copy, Clone)] +struct PercentRate { + p5: u32, + p10: u32, + p15: u32, + p20: u32, + p25: u32, + p30: u32, + p35: u32, + p40: u32, + p45: u32, + p50: u32, + p55: u32, + p60: u32, + p65: u32, + p70: u32, + p75: u32, + p80: u32, + p85: u32, + p90: u32, +} + +impl PercentRate { + fn as_array(&self) -> [u32; 18] { + [self.p5, self.p10, self.p15, self.p20, self.p25, self.p30, self.p35, self.p40, self.p45, + self.p50, self.p55, self.p60, self.p65, self.p70, self.p75, self.p80, self.p85, self.p90] + } +} + + +#[derive(Debug, Serialize, Deserialize, Copy, Clone)] +enum PercentPatternType { + #[serde(rename = "pattern1")] + Pattern1, + #[serde(rename = "pattern2")] + Pattern2, + #[serde(rename = "pattern3")] + Pattern3, + #[serde(rename = "pattern4")] + Pattern4, + #[serde(rename = "pattern5")] + Pattern5, + #[serde(rename = "pattern6")] + Pattern6, +} + +#[derive(Debug, Serialize, Deserialize, Copy, Clone)] +struct AttributePercentPattern { + attribute1: Option, + attribute2: Option, + attribute3: Option, +} + +#[derive(Debug, Serialize, Deserialize, Copy, Clone)] +struct AreaPercentPatterns { + area1: AttributePercentPattern, + area2: AttributePercentPattern, + area3: AttributePercentPattern, + area4: AttributePercentPattern, + area5: AttributePercentPattern, + area6: AttributePercentPattern, + area7: AttributePercentPattern, + area8: AttributePercentPattern, + area9: AttributePercentPattern, + area10: AttributePercentPattern, +} + +impl AreaPercentPatterns { + fn get_by_area(&self, map_area: &MapVariantType) -> AttributePercentPattern { + match map_area.area_value().unwrap() { + 0 => self.area1, + 1 => self.area2, + 2 => self.area3, + 3 => self.area4, + 4 => self.area5, + 5 => self.area6, + 6 => self.area7, + 7 => self.area8, + 8 => self.area9, + _ => self.area10, + } + } +} + + +#[derive(Debug, Serialize, Deserialize, Copy, Clone)] +struct PercentRatePatterns { + pattern1: PercentRate, + pattern2: PercentRate, + pattern3: PercentRate, + pattern4: PercentRate, + pattern5: PercentRate, + pattern6: PercentRate, +} + +impl PercentRatePatterns { + fn get_by_pattern(&self, pattern: &PercentPatternType) -> PercentRate { + match pattern { + PercentPatternType::Pattern1 => self.pattern1, + PercentPatternType::Pattern2 => self.pattern2, + PercentPatternType::Pattern3 => self.pattern3, + PercentPatternType::Pattern4 => self.pattern4, + PercentPatternType::Pattern5 => self.pattern5, + PercentPatternType::Pattern6 => self.pattern6 + } + } +} + +struct AttributeTable { + attribute_rates: AttributeRates, + percent_rates: PercentRatePatterns, + area_percent_patterns: AreaPercentPatterns, +} + + +impl AttributeTable { + fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> AttributeTable { + // TODO: new these + let attribute_rates: AttributeRates = load_data_file(episode, difficulty, section_id, "attribute_rate.toml"); + let percent_rates: PercentRatePatterns = load_data_file(episode, difficulty, section_id, "percent_rate.toml"); + let area_percent_patterns: AreaPercentPatterns = load_data_file(episode, difficulty, section_id, "area_percent_pattern.toml"); + + AttributeTable { + attribute_rates: attribute_rates, + percent_rates: percent_rates, + area_percent_patterns: area_percent_patterns, + } + } + + + fn generate_attribute(&self, pattern: &PercentPatternType, rates: &AttributeRate, rng: &mut R) -> Option { + let attribute_weights = WeightedIndex::new(&[rates.none, rates.native, rates.abeast, rates.machine, rates.dark, rates.hit]).unwrap(); + let attr = match attribute_weights.sample(rng) { + 0 => return None, + 1 => Attribute::Native, + 2 => Attribute::ABeast, + 3 => Attribute::Machine, + 4 => Attribute::Dark, + 5 => Attribute::Hit, + _ => panic!() + }; + + let percents = self.percent_rates.get_by_pattern(pattern); + + let value_weights = WeightedIndex::new(&percents.as_array()).unwrap(); + let value = value_weights.sample(rng); + let percent = ((value + 1) * 5) as i8; + + Some(WeaponAttribute { + attr: attr, + value: percent + }) + } + + fn generate_attributes(&self, map_area: &MapVariantType, rng: &mut R) -> [Option; 3] { + let percent_pattern = self.area_percent_patterns.get_by_area(map_area); + let attribute_rate = self.attribute_rates.get_by_area(map_area); + + let mut percents = vec![ + percent_pattern.attribute1.and_then(|pattern_type| { + self.generate_attribute(&pattern_type, &attribute_rate, rng) + }), + percent_pattern.attribute2.and_then(|pattern_type| { + self.generate_attribute(&pattern_type, &attribute_rate, rng) + }), + percent_pattern.attribute3.and_then(|pattern_type| { + self.generate_attribute(&pattern_type, &attribute_rate, rng) + }), + ]; + percents.sort_by_key(|p| { + match p { + Some(a) => Some(a.attr), + None => None, + } + }); + percents.dedup_by_key(|p| { + match p { + Some(a) => Some(a.attr), + None => None, + } + }); + percents.iter() + .fold(([None; 3], 0), |(mut acc, index), p| { // one day I'll be able to collece into an array + if let Some(_) = p { + acc[index] = *p; + (acc, index + 1) + } + else { + (acc, index) + } + }).0 + } +} + + +#[derive(Debug, Serialize, Deserialize, Copy, Clone)] +struct SpecialRate { + rank: u32, + rate: u32, +} + + +#[derive(Debug, Serialize, Deserialize, Copy, Clone)] +struct SpecialRates { + area1: SpecialRate, + area2: SpecialRate, + area3: SpecialRate, + area4: SpecialRate, + area5: SpecialRate, + area6: SpecialRate, + area7: SpecialRate, + area8: SpecialRate, + area9: SpecialRate, + area10: SpecialRate, +} + +impl SpecialRates { + fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> SpecialRates { + load_data_file(episode, difficulty, section_id, "weapon_special_rate.toml") + } + + fn rate_by_area(&self, map_area: &MapVariantType) -> SpecialRate { + match map_area.area_value().unwrap() { + 0 => self.area1, + 1 => self.area2, + 2 => self.area3, + 3 => self.area4, + 4 => self.area5, + 5 => self.area6, + 6 => self.area7, + 7=> self.area8, + 8 => self.area9, + _ => self.area10, + } + } + + fn random_special_by_rank(&self, rank: u32, rng: &mut R) -> WeaponSpecial { + let specials = match rank { + 1 => vec![WeaponSpecial::Draw, WeaponSpecial::Heart, WeaponSpecial::Ice, WeaponSpecial::Bind, WeaponSpecial::Heat, WeaponSpecial::Shock, + WeaponSpecial::Dim, WeaponSpecial::Panic], + 2 => vec![WeaponSpecial::Drain, WeaponSpecial::Mind, WeaponSpecial::Frost, WeaponSpecial::Hold, WeaponSpecial::Fire, WeaponSpecial::Thunder, + WeaponSpecial::Shadow, WeaponSpecial::Riot, WeaponSpecial::Masters, WeaponSpecial::Charge], + 3 => vec![WeaponSpecial::Fill, WeaponSpecial::Soul, WeaponSpecial::Freeze, WeaponSpecial::Seize, WeaponSpecial::Flame, WeaponSpecial::Storm, + WeaponSpecial::Dark, WeaponSpecial::Havoc, WeaponSpecial::Lords, WeaponSpecial::Charge, WeaponSpecial::Spirit, WeaponSpecial::Devils], + 4 => vec![WeaponSpecial::Gush, WeaponSpecial::Geist, WeaponSpecial::Blizzard, WeaponSpecial::Arrest, WeaponSpecial::Burning, WeaponSpecial::Tempest, + WeaponSpecial::Hell, WeaponSpecial::Chaos, WeaponSpecial::Kings, WeaponSpecial::Charge, WeaponSpecial::Berserk, WeaponSpecial::Demons], + _ => panic!(), + }; + + *specials.choose(rng).unwrap() + } + + fn get_special(&self, map_area: &MapVariantType, rng: &mut R) -> Option { + let rate = self.rate_by_area(map_area); + if rng.gen_range(0, 100) < rate.rate { + Some(self.random_special_by_rank(rate.rank, rng)) + } + else { + None + } + } +} + + +pub struct GenericWeaponTable { + rank_table: HashMap>, + weapon_ratio: WeaponRatios, + grind_rates: GrindRates, + attribute_table: AttributeTable, + special_table: SpecialRates, +} + + +impl GenericWeaponTable { + pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> GenericWeaponTable { + let mut rank_table = HashMap::new(); + rank_table.insert(WeaponDropType::Saber, vec![WeaponType::Saber, WeaponType::Brand, WeaponType::Buster, WeaponType::Pallasch, WeaponType::Gladius]); + rank_table.insert(WeaponDropType::Sword, vec![WeaponType::Sword, WeaponType::Gigush, WeaponType::Breaker, WeaponType::Claymore, WeaponType::Calibur]); + rank_table.insert(WeaponDropType::Dagger, vec![WeaponType::Dagger, WeaponType::Knife, WeaponType::Blade, WeaponType::Edge, WeaponType::Ripper]); + rank_table.insert(WeaponDropType::Partisan, vec![WeaponType::Partisan, WeaponType::Halbert, WeaponType::Glaive, WeaponType::Berdys, WeaponType::Gungnir]); + rank_table.insert(WeaponDropType::Slicer, vec![WeaponType::Slicer, WeaponType::Spinner, WeaponType::Cutter, WeaponType::Sawcer, WeaponType::Diska]); + rank_table.insert(WeaponDropType::Handgun, vec![WeaponType::Handgun, WeaponType::Autogun, WeaponType::Lockgun, WeaponType::Railgun, WeaponType::Raygun]); + rank_table.insert(WeaponDropType::Rifle, vec![WeaponType::Rifle, WeaponType::Sniper, WeaponType::Blaster, WeaponType::Beam, WeaponType::Laser]); + rank_table.insert(WeaponDropType::Mechgun, vec![WeaponType::Mechgun, WeaponType::Assault, WeaponType::Repeater, WeaponType::Gatling, WeaponType::Vulcan]); + rank_table.insert(WeaponDropType::Shot, vec![WeaponType::Shot, WeaponType::Spread, WeaponType::Cannon, WeaponType::Launcher, WeaponType::Arms]); + rank_table.insert(WeaponDropType::Cane, vec![WeaponType::Cane, WeaponType::Stick, WeaponType::Mace, WeaponType::Club]); + rank_table.insert(WeaponDropType::Rod, vec![WeaponType::Rod, WeaponType::Pole, WeaponType::Pillar, WeaponType::Striker]); + rank_table.insert(WeaponDropType::Wand, vec![WeaponType::Wand, WeaponType::Staff, WeaponType::Baton, WeaponType::Scepter]); + + GenericWeaponTable { + rank_table: rank_table, + weapon_ratio: WeaponRatios::new(episode, difficulty, section_id), + grind_rates: GrindRates::new(episode, difficulty, section_id), + attribute_table: AttributeTable::new(episode, difficulty, section_id), + special_table: SpecialRates::new(episode, difficulty, section_id), + } + } + + fn area_rank(&self, ratio: &WeaponRatio, map_area: &MapVariantType) -> (u32, u32) { + if ratio.rank >= 0 { + (map_area.area_value().unwrap_or(0), ratio.rank as u32) + } + else { + ((map_area.area_value().unwrap_or(0) as i32 + ratio.rank) as u32, 0) + } + } + + fn get_possible_weapon_types(&self, map_area: &MapVariantType) -> BTreeMap { + let mut valid_weapons = BTreeMap::new(); + + let item_rate_pairs = vec![ + (WeaponDropType::Saber, self.weapon_ratio.saber), + (WeaponDropType::Sword, self.weapon_ratio.sword), + (WeaponDropType::Dagger, self.weapon_ratio.dagger), + (WeaponDropType::Partisan, self.weapon_ratio.partisan), + (WeaponDropType::Slicer, self.weapon_ratio.slicer), + (WeaponDropType::Handgun, self.weapon_ratio.handgun), + (WeaponDropType::Rifle, self.weapon_ratio.rifle), + (WeaponDropType::Mechgun, self.weapon_ratio.mechgun), + (WeaponDropType::Shot, self.weapon_ratio.shot), + (WeaponDropType::Cane, self.weapon_ratio.cane), + (WeaponDropType::Rod, self.weapon_ratio.rod), + (WeaponDropType::Wand, self.weapon_ratio.wand), + ]; + + for (item_type, ratio) in item_rate_pairs.into_iter() { + if ratio.rate > 0 && map_area.area_value().map(|k| k as i32 + ratio.rank).unwrap_or(-1) >= 0 { + valid_weapons.insert(item_type, ratio); + } + } + + valid_weapons + } + + fn weapon_type(&self, possible_weapon_types: &BTreeMap, map_area: &MapVariantType, rng: &mut R) -> WeaponDropType { + let mut weapon_rates = possible_weapon_types.iter() + .map(|(weapon, stat)| { + (weapon, stat.rate) + }); + let weapon_choice = WeightedIndex::new(weapon_rates.clone().map(|wr| wr.1)).unwrap(); + *weapon_rates.nth(weapon_choice.sample(rng)).unwrap().0 + } + + + fn weapon_rank(&self, ratio: &WeaponRatio, map_area: &MapVariantType) -> u32 { + let (area, rank) = self.area_rank(ratio, map_area); + let weapon_rank = (rank + area / ratio.inc); + + std::cmp::max(weapon_rank, 0) + } + + fn actual_weapon(&self, weapon_type: &WeaponDropType, weapon_rank: u32) -> WeaponType { + let weapons = self.rank_table.get(weapon_type).unwrap(); + weapons[std::cmp::min(weapons.len() - 1, weapon_rank as usize)] + } + + fn get_grind(&self, ratio: &WeaponRatio, map_area: &MapVariantType, rng: &mut R) -> usize { + let (area, _) = self.area_rank(ratio, map_area); + let pattern = std::cmp::min(area % ratio.inc, 3); + + let weights = self.grind_rates.grind_rate[pattern as usize]; + let grind_choice = WeightedIndex::new(&weights).unwrap(); + grind_choice.sample(rng) + } + + pub fn get_drop(&self, map_area: &MapVariantType, rng: &mut R) -> Option { + let possible_weapon_types = self.get_possible_weapon_types(map_area); + let weapon_type = self.weapon_type(&possible_weapon_types, map_area, rng); + let ratio = possible_weapon_types.get(&weapon_type).unwrap(); + let weapon_rank = self.weapon_rank(ratio, map_area); + let weapon_grind = self.get_grind(ratio, map_area, rng); + let weapon_attributes = self.attribute_table.generate_attributes(map_area, rng); + let weapon_special = self.special_table.get_special(map_area, rng); + let actual_weapon = self.actual_weapon(&weapon_type, weapon_rank); + + Some(ItemDetail::Weapon(WeaponDetail { + equipped: false, + tekked: weapon_special.is_none(), + weapon: Weapon { + weapon: actual_weapon, + special: weapon_special, + grind: weapon_grind as u8, + attrs: weapon_attributes, + } + })) + } +} + + + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn test_weapon_generation() { + let mut rng = rand_chacha::ChaCha20Rng::from_seed([23;32]); + + let gwt = GenericWeaponTable::new(Episode::One, Difficulty::Normal, SectionID::Skyly); + assert!(gwt.get_drop(&MapVariantType::Forest1, &mut rng) == Some(ItemDetail::Weapon(WeaponDetail { + equipped: false, + tekked: true, + weapon: Weapon { + weapon: WeaponType::Cane, + special: None, + grind: 0, + attrs: [None, None, None] + } + }))); + + let gwt = GenericWeaponTable::new(Episode::One, Difficulty::Hard, SectionID::Skyly); + assert!(gwt.get_drop(&MapVariantType::Caves2, &mut rng) == Some(ItemDetail::Weapon(WeaponDetail { + equipped: false, + tekked: true, + weapon: Weapon { + weapon: WeaponType::Sniper, + special: None, + grind: 2, + attrs: [None, None, None] + } + }))); + + let gwt = GenericWeaponTable::new(Episode::One, Difficulty::VeryHard, SectionID::Skyly); + assert!(gwt.get_drop(&MapVariantType::Mines1, &mut rng) == Some(ItemDetail::Weapon(WeaponDetail { + equipped: false, + tekked: false, + weapon: Weapon { + weapon: WeaponType::Club, + special: Some(WeaponSpecial::Berserk), + grind: 0, + attrs: [None, None, None] + } + }))); + + let gwt = GenericWeaponTable::new(Episode::One, Difficulty::Ultimate, SectionID::Skyly); + assert!(gwt.get_drop(&MapVariantType::DarkFalz, &mut rng) == Some(ItemDetail::Weapon(WeaponDetail { + equipped: false, + tekked: true, + weapon: Weapon { + weapon: WeaponType::Vulcan, + special: None, + grind: 0, + attrs: [Some(WeaponAttribute {attr: Attribute::ABeast, value: 30}), Some(WeaponAttribute {attr: Attribute::Dark, value: 30}), None] + } + }))); + } +} diff --git a/src/ship/drops/mod.rs b/src/ship/drops/mod.rs new file mode 100644 index 0000000..5a571a8 --- /dev/null +++ b/src/ship/drops/mod.rs @@ -0,0 +1,190 @@ +mod drop_table; +mod rare_drop_table; +mod generic_weapon; +mod generic_armor; +mod generic_shield; + + +use std::collections::HashMap; +use std::fs::File; +use std::path::PathBuf; +use std::io::Read; +use serde::{Serialize, Deserialize}; +use rand::{Rng, SeedableRng}; +use rand::distributions::{WeightedIndex, Distribution}; + +use crate::ship::monster::MonsterType; +use crate::ship::room::{Difficulty, Episode}; +use crate::entity::item::ItemDetail; +use crate::entity::item::weapon::{WeaponType, Attribute, WeaponAttribute, WeaponSpecial}; +use crate::entity::item::armor::ArmorType; +use crate::entity::item::shield::ShieldType; +use crate::entity::item::unit::UnitType; +use crate::entity::item::tool::ToolType; +use crate::ship::map::MapVariantType; +use crate::entity::character::SectionID; +pub use crate::ship::drops::generic_weapon::*; + + +#[derive(Debug, Serialize, Deserialize, Copy, Clone)] +pub enum MonsterDropType { + #[serde(rename = "weapon")] + Weapon, + #[serde(rename = "armor")] + Armor, + #[serde(rename = "shield")] + Shield, + #[serde(rename = "unit")] + Unit, + #[serde(rename = "none")] + None, +} + +#[derive(Debug, Serialize, Deserialize, Copy, Clone)] +pub struct MonsterDropStats { + pub dar: u32, + pub drop_type: MonsterDropType, + pub min_meseta: u32, + pub max_meseta: u32, +} + +enum RareDropItem { + Weapon(WeaponType), + Armor(ArmorType), + Shield(ShieldType), + Unit(UnitType), + Tool(ToolType), +} + + +struct RareDrop { + rate: f32, + item: RareDropItem +} + + + +#[derive(Debug, Serialize, Deserialize)] +pub struct RareDropConfigEntity { + pub rate: f32, + pub item: String, +} + + + + +/*#[derive(Serialize, Deserialize)] +pub struct MonsterDar(pub HashMap); + +/*impl MonsterDar { + fn from_f + + +}*/*/ + + +struct RareDropTable { + +} + +impl RareDropTable { + fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> RareDropTable { + RareDropTable { + + } + } + + fn get_drop(&self, monster: &MonsterType) -> Option { + None + } +} + + +struct DropTable { + rare_table: RareDropTable, + monster_stats: HashMap, + weapon_table: GenericWeaponTable, + rng: R, +} + + +impl DropTable { + fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> DropTable { + let mut path = PathBuf::from("data/drops/"); + path.push(episode.to_string()); + path.push(difficulty.to_string().to_lowercase()); + path.push(section_id.to_string().to_lowercase()); + path.push("grind_rate.toml"); + let mut f = File::open(path).unwrap(); + let mut s = String::new(); + f.read_to_string(&mut s); + let monster_stats = toml::from_str(&s).unwrap(); + + DropTable { + rare_table: RareDropTable::new(episode, difficulty, section_id), + monster_stats: monster_stats, + weapon_table: GenericWeaponTable::new(episode, difficulty, section_id), + rng: R::from_entropy(), + } + } + + fn generate_meseta(&self, monster: &MonsterDropStats) -> Option { + None + } + + fn generate_tool(&self, map_area: &MapVariantType, monster: &MonsterDropStats) -> Option { + None + } + + fn generate_armor(&self) -> Option { + None + } + + fn generate_shield(&self) -> Option { + None + } + + fn generate_unit(&self) -> Option { + None + } + + fn generate_typed_drop(&mut self, map_area: &MapVariantType, monster: &MonsterDropStats) -> Option { + match monster.drop_type { + MonsterDropType::Weapon => self.weapon_table.get_drop(map_area, &mut self.rng), + MonsterDropType::Armor => self.generate_armor(), + MonsterDropType::Shield => self.generate_shield(), + MonsterDropType::Unit => self.generate_unit(), + MonsterDropType::None => None, + } + } + + + fn get_drop(&mut self, map_area: &MapVariantType, monster: &MonsterType) -> Option { + //let mut rng = rand::thread_rng(); + let monster_stat = *self.monster_stats.get(monster)?; + + let drop_anything = self.rng.gen_range(0, 100); + if drop_anything > monster_stat.dar { + return None; + } + + if let Some(item) = self.rare_table.get_drop(&monster) { + return Some(item); + } + + let drop_type = self.rng.gen_range(0, 3); + + match drop_type { + 0 => { + self.generate_meseta(&monster_stat) + }, + 1 => { + self.generate_tool(map_area, &monster_stat) + }, + 2 => { + self.generate_typed_drop(map_area, &monster_stat) + }, + _ => panic!() + } + } +} diff --git a/src/ship/drops/rare_drop_table.rs b/src/ship/drops/rare_drop_table.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/ship/items.rs b/src/ship/items.rs index 0959d1a..b5467ef 100644 --- a/src/ship/items.rs +++ b/src/ship/items.rs @@ -239,6 +239,7 @@ mod test { }, item: ItemDetail::Weapon(Weapon { equipped: false, + tekked: true, weapon: item::weapon::Weapon { weapon: item::weapon::WeaponType::Saber, grind: 0, @@ -265,6 +266,7 @@ mod test { }, item: ItemDetail::Weapon(Weapon { equipped: false, + tekked: true, weapon: item::weapon::Weapon { weapon: item::weapon::WeaponType::Handgun, grind: 12, @@ -301,6 +303,7 @@ mod test { }, item: ItemDetail::Weapon(Weapon { equipped: false, + tekked: true, weapon: item::weapon::Weapon { weapon: item::weapon::WeaponType::Handgun, grind: 12, diff --git a/src/ship/map.rs b/src/ship/map.rs index dce0903..8da7789 100644 --- a/src/ship/map.rs +++ b/src/ship/map.rs @@ -190,8 +190,9 @@ enum MapVariantMode { Offline, } +// TODO: rename this since apparently I'm going to be using it a lot #[derive(Debug)] -enum MapVariantType { +pub enum MapVariantType { Pioneer2Ep1, Forest1, Forest2, @@ -209,6 +210,29 @@ enum MapVariantType { DarkFalz, } +impl MapVariantType { + pub fn area_value(&self) -> Option { + match self { + MapVariantType::Forest1 => Some(0), + MapVariantType::Forest2 => Some(1), + MapVariantType::Caves1 => Some(2), + MapVariantType::Caves2 => Some(3), + MapVariantType::Caves3 => Some(4), + MapVariantType::Mines1 => Some(5), + MapVariantType::Mines2 => Some(6), + MapVariantType::Ruins1 => Some(7), + MapVariantType::Ruins2 => Some(8), + MapVariantType::Ruins3 => Some(9), + MapVariantType::Dragon => Some(2), + MapVariantType::DeRolLe => Some(5), + MapVariantType::VolOpt => Some(7), + MapVariantType::DarkFalz => Some(9), + _ => None + } + } +} + + #[derive(Debug)] struct MapVariant { map: MapVariantType, diff --git a/src/ship/mod.rs b/src/ship/mod.rs index 62dbf73..47662f2 100644 --- a/src/ship/mod.rs +++ b/src/ship/mod.rs @@ -5,3 +5,5 @@ pub mod room; pub mod items; pub mod map; pub mod monster; + +pub mod drops; diff --git a/src/ship/monster.rs b/src/ship/monster.rs index aaaef41..93270ee 100644 --- a/src/ship/monster.rs +++ b/src/ship/monster.rs @@ -1,4 +1,5 @@ use std::convert::TryFrom; +use serde::{Serialize, Deserialize}; #[derive(Debug)] @@ -7,12 +8,13 @@ pub enum MonsterParseError { } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Serialize, Deserialize, Copy, Clone, Hash, Eq, PartialEq, enum_utils::FromStr, derive_more::Display)] pub enum MonsterType { Hildebear, SandRappy, RagRappy, Monest, + Mothmant, SavageWolf, BarbarousWolf, Booma, @@ -108,25 +110,4 @@ pub enum MonsterType { GoranDetonator, SaintMillion, Shambertin, - - Mothmant, -} - - -impl TryFrom<&str> for MonsterType { - type Error = MonsterParseError; - - fn try_from(monster: &str) -> Result { - match monster { - "Booma" | "Bartle" => Ok(MonsterType::Booma), - "Gobooma" | "Barble" => Ok(MonsterType::Gobooma), - "Gigobooma" | "Tollaw" => Ok(MonsterType::Gigobooma), - // etc... - _ => Err(MonsterParseError::UnknownMonster(monster.to_owned())) - } - } - - - - } diff --git a/src/ship/room.rs b/src/ship/room.rs index c69c0b6..825aec6 100644 --- a/src/ship/room.rs +++ b/src/ship/room.rs @@ -10,10 +10,13 @@ pub enum RoomCreationError { } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, derive_more::Display)] pub enum Episode { + #[display(fmt="ep1")] One, + #[display(fmt="ep2")] Two, + #[display(fmt="ep4")] Four, } @@ -40,7 +43,7 @@ impl Into for Episode { } } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, derive_more::Display)] pub enum Difficulty { Normal, Hard,