diff --git a/src/entity/item/mod.rs b/src/entity/item/mod.rs index e3cbd09..303edf4 100644 --- a/src/entity/item/mod.rs +++ b/src/entity/item/mod.rs @@ -40,6 +40,17 @@ pub enum ItemLocation { */ } +#[derive(Debug, PartialEq)] +pub struct Meseta(pub u32); + +impl Meseta { + pub fn as_bytes(&self) -> [u8; 16] { + let mut result = [0; 16]; + result[12..16].copy_from_slice(&u32::to_le_bytes(self.0)); + result + } +} + #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum ItemType { diff --git a/src/ship/drops/box_drop_table.rs b/src/ship/drops/box_drop_table.rs index d6101d8..f78acdb 100644 --- a/src/ship/drops/box_drop_table.rs +++ b/src/ship/drops/box_drop_table.rs @@ -10,7 +10,7 @@ use crate::entity::item::tool::{Tool, ToolType}; use crate::entity::character::SectionID; use crate::ship::monster::MonsterType; use crate::ship::room::{Difficulty, Episode}; -use crate::ship::map::MapVariantType; +use crate::ship::map::MapArea; use crate::ship::drops::{ItemDropType, load_data_file}; use crate::ship::map::{MapObject, MapObjectType, FixedBoxDropType}; use crate::ship::drops::rare_drop_table::{RareDropTable, RareDropItem}; @@ -49,8 +49,8 @@ struct BoxDropRates { } impl BoxDropRates { - fn rates_by_area(&self, map_area: &MapVariantType) -> &BoxDropRate { - match map_area.area_value().unwrap() { + 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, @@ -122,8 +122,8 @@ impl BoxRareRates { } } - fn rates_by_area(&self, map_area: &MapVariantType) -> &Vec { - match map_area.area_value().unwrap() { + 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, @@ -171,7 +171,7 @@ impl BoxDropTable { } - fn rare_drop(&self, map_area: &MapVariantType, rng: &mut R) -> Option { + 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(); @@ -184,7 +184,7 @@ impl BoxDropTable { }).nth(0) } - fn random_box_drop(&self, map_area: &MapVariantType, rng: &mut R) -> Option { + 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, @@ -202,7 +202,7 @@ impl BoxDropTable { }) } - fn fixed_box_drop(&self, fixed_drop: FixedBoxDropType, map_area: &MapVariantType, rng: &mut R) -> Option { + 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? @@ -216,7 +216,7 @@ impl BoxDropTable { } } - pub fn get_drop(&self, map_area: &MapVariantType, object: &MapObject, rng: &mut R) -> Option { + 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 => { @@ -243,6 +243,6 @@ mod test { let bdt = BoxDropTable::new(Episode::One, Difficulty::Ultimate, SectionID::Skyly); - println!("{:?}", bdt.get_drop(&MapVariantType::Forest1, &MapObject {object: MapObjectType::Box}, &mut rng)); + println!("{:?}", bdt.get_drop(&MapArea::Forest1, &MapObject {object: MapObjectType::Box}, &mut rng)); } } diff --git a/src/ship/drops/generic_armor.rs b/src/ship/drops/generic_armor.rs index ec1b174..c9f6468 100644 --- a/src/ship/drops/generic_armor.rs +++ b/src/ship/drops/generic_armor.rs @@ -5,7 +5,7 @@ use rand::distributions::{WeightedIndex, Distribution}; use crate::entity::item::armor::{ArmorType, Armor}; use crate::ship::room::{Difficulty, Episode}; -use crate::ship::map::MapVariantType; +use crate::ship::map::MapArea; use crate::entity::character::SectionID; use crate::ship::drops::{ItemDropType, load_data_file}; use crate::ship::item_stats::{armor_stats, ArmorStats}; @@ -45,11 +45,11 @@ impl GenericArmorTable { gat } - fn armor_type(&self, area_map: &MapVariantType, rng: &mut R) -> ArmorType { + fn armor_type(&self, area_map: &MapArea, rng: &mut R) -> ArmorType { let rank_weights = WeightedIndex::new(&[self.rank_rates.rank0, self.rank_rates.rank1, self.rank_rates.rank2, self.rank_rates.rank3, self.rank_rates.rank4]).unwrap(); let rank = rank_weights.sample(rng) as i32; - let armor_level = std::cmp::max(0i32, self.armor_set as i32 - 3i32 + rank + area_map.area_value().unwrap_or(0) as i32); + let armor_level = std::cmp::max(0i32, self.armor_set as i32 - 3i32 + rank + area_map.drop_area_value().unwrap_or(0) as i32); match armor_level { 0x00 => ArmorType::Frame, 0x01 => ArmorType::Armor, @@ -79,7 +79,7 @@ impl GenericArmorTable { } } - pub fn slots(&self, area_map: &MapVariantType, rng: &mut R) -> usize { + pub fn slots(&self, area_map: &MapArea, rng: &mut R) -> usize { let slot_weights = WeightedIndex::new(&[self.slot_rates.slot0, self.slot_rates.slot1, self.slot_rates.slot2, self.slot_rates.slot3, self.slot_rates.slot4]).unwrap(); slot_weights.sample(rng) @@ -95,7 +95,7 @@ impl GenericArmorTable { rng.gen_range(0, stats.evp_modifier) } - pub fn get_drop(&self, area_map: &MapVariantType, rng: &mut R) -> Option { + pub fn get_drop(&self, area_map: &MapArea, rng: &mut R) -> Option { let armor_type = self.armor_type(area_map, rng); let slots = self.slots(area_map, rng); let dfp_modifier = self.dfp_modifier(&armor_type, rng); @@ -120,25 +120,25 @@ mod test { let mut rng = rand_chacha::ChaCha20Rng::from_seed([23;32]); let gat = GenericArmorTable::new(Episode::One, Difficulty::Ultimate, SectionID::Skyly); - assert!(gat.get_drop(&MapVariantType::Mines1, &mut rng) == Some(ItemDropType::Armor(Armor { + assert!(gat.get_drop(&MapArea::Mines1, &mut rng) == Some(ItemDropType::Armor(Armor { armor: ArmorType::GeneralArmor, dfp: 0, evp: 0, slots: 1, }))); - assert!(gat.get_drop(&MapVariantType::Caves3, &mut rng) == Some(ItemDropType::Armor(Armor { + assert!(gat.get_drop(&MapArea::Caves3, &mut rng) == Some(ItemDropType::Armor(Armor { armor: ArmorType::AbsorbArmor, dfp: 1, evp: 1, slots: 1, }))); - assert!(gat.get_drop(&MapVariantType::Forest2, &mut rng) == Some(ItemDropType::Armor(Armor { + assert!(gat.get_drop(&MapArea::Forest2, &mut rng) == Some(ItemDropType::Armor(Armor { armor: ArmorType::HyperFrame, dfp: 0, evp: 0, slots: 0, }))); - assert!(gat.get_drop(&MapVariantType::DarkFalz, &mut rng) == Some(ItemDropType::Armor(Armor { + assert!(gat.get_drop(&MapArea::DarkFalz, &mut rng) == Some(ItemDropType::Armor(Armor { armor: ArmorType::ImperialArmor, dfp: 2, evp: 1, diff --git a/src/ship/drops/generic_shield.rs b/src/ship/drops/generic_shield.rs index 725ff66..d0edfd9 100644 --- a/src/ship/drops/generic_shield.rs +++ b/src/ship/drops/generic_shield.rs @@ -5,7 +5,7 @@ use rand::distributions::{WeightedIndex, Distribution}; use crate::entity::item::shield::{ShieldType, Shield}; use crate::ship::room::{Difficulty, Episode}; -use crate::ship::map::MapVariantType; +use crate::ship::map::MapArea; use crate::entity::character::SectionID; use crate::ship::drops::{ItemDropType, load_data_file}; use crate::ship::item_stats::{shield_stats, ShieldStats}; @@ -35,11 +35,11 @@ impl GenericShieldTable { gst } - fn shield_type(&self, area_map: &MapVariantType, rng: &mut R) -> ShieldType { + fn shield_type(&self, area_map: &MapArea, rng: &mut R) -> ShieldType { let rank_weights = WeightedIndex::new(&[self.rank_rates.rank0, self.rank_rates.rank1, self.rank_rates.rank2, self.rank_rates.rank3, self.rank_rates.rank4]).unwrap(); let rank = rank_weights.sample(rng) as i32; - let shield_level = std::cmp::max(0i32, self.shield_set as i32 - 3i32 + rank + area_map.area_value().unwrap_or(0) as i32); + let shield_level = std::cmp::max(0i32, self.shield_set as i32 - 3i32 + rank + area_map.drop_area_value().unwrap_or(0) as i32); match shield_level { 0x00 => ShieldType::Barrier, 0x01 => ShieldType::Shield, @@ -76,7 +76,7 @@ impl GenericShieldTable { rng.gen_range(0, stats.evp_modifier) } - pub fn get_drop(&self, area_map: &MapVariantType, rng: &mut R) -> Option { + pub fn get_drop(&self, area_map: &MapArea, rng: &mut R) -> Option { let shield_type = self.shield_type(area_map, rng); let dfp_modifier = self.dfp_modifier(&shield_type, rng); let evp_modifier = self.dfp_modifier(&shield_type, rng); @@ -100,22 +100,22 @@ mod test { let gst = GenericShieldTable::new(Episode::One, Difficulty::Ultimate, SectionID::Skyly); - assert!(gst.get_drop(&MapVariantType::Forest1, &mut rng) == Some(ItemDropType::Shield(Shield { + assert!(gst.get_drop(&MapArea::Forest1, &mut rng) == Some(ItemDropType::Shield(Shield { shield: ShieldType::FreezeBarrier, dfp: 4, evp: 1, }))); - assert!(gst.get_drop(&MapVariantType::Caves3, &mut rng) == Some(ItemDropType::Shield(Shield { + assert!(gst.get_drop(&MapArea::Caves3, &mut rng) == Some(ItemDropType::Shield(Shield { shield: ShieldType::PsychicBarrier, dfp: 3, evp: 2, }))); - assert!(gst.get_drop(&MapVariantType::Mines2, &mut rng) == Some(ItemDropType::Shield(Shield { + assert!(gst.get_drop(&MapArea::Mines2, &mut rng) == Some(ItemDropType::Shield(Shield { shield: ShieldType::ImperialBarrier, dfp: 0, evp: 4, }))); - assert!(gst.get_drop(&MapVariantType::DarkFalz, &mut rng) == Some(ItemDropType::Shield(Shield { + assert!(gst.get_drop(&MapArea::DarkFalz, &mut rng) == Some(ItemDropType::Shield(Shield { shield: ShieldType::DivinityBarrier, dfp: 1, evp: 0, diff --git a/src/ship/drops/generic_unit.rs b/src/ship/drops/generic_unit.rs index 7e2224e..1f432f8 100644 --- a/src/ship/drops/generic_unit.rs +++ b/src/ship/drops/generic_unit.rs @@ -6,7 +6,7 @@ use rand::seq::IteratorRandom; use crate::entity::item::unit::{UnitType, Unit, UnitModifier}; use crate::ship::room::{Difficulty, Episode}; -use crate::ship::map::MapVariantType; +use crate::ship::map::MapArea; use crate::entity::character::SectionID; use crate::ship::drops::{ItemDropType, load_data_file}; use crate::ship::item_stats::{unit_stats, UnitStats}; @@ -28,8 +28,8 @@ struct UnitLevels { } impl UnitLevels { - fn level_by_area(&self, map_area: &MapVariantType) -> u32 { - match map_area.area_value().unwrap() { + fn level_by_area(&self, map_area: &MapArea) -> u32 { + match map_area.drop_area_value().unwrap() { 0 => self.area1, 1 => self.area2, 2 => self.area3, @@ -60,7 +60,7 @@ impl GenericUnitTable { } } - fn unit_type_and_modifier(&self, area_map: &MapVariantType, rng: &mut R) -> Option<(UnitType, Option)> { + fn unit_type_and_modifier(&self, area_map: &MapArea, rng: &mut R) -> Option<(UnitType, Option)> { let level = self.unit_levels.level_by_area(area_map) as i32; if level == 0 { return None; @@ -83,7 +83,7 @@ impl GenericUnitTable { Some(units.choose(rng).unwrap()) } - pub fn get_drop(&self, area_map: &MapVariantType, rng: &mut R) -> Option { + pub fn get_drop(&self, area_map: &MapArea, rng: &mut R) -> Option { let unit_type_modifier = self.unit_type_and_modifier(area_map, rng); unit_type_modifier.map(|(unit_type, unit_modifier)| { @@ -104,14 +104,14 @@ mod test { let mut rng = rand_chacha::ChaCha20Rng::from_seed([23;32]); let gut = GenericUnitTable::new(Episode::One, Difficulty::Normal, SectionID::Skyly); - assert!(gut.get_drop(&MapVariantType::Forest1, &mut rng) == None); + assert!(gut.get_drop(&MapArea::Forest1, &mut rng) == None); let gut = GenericUnitTable::new(Episode::One, Difficulty::Hard, SectionID::Skyly); - let unit_tests = vec![(MapVariantType::Forest1, UnitType::ResistFreeze, Some(UnitModifier::PlusPlus)), - (MapVariantType::Caves3, UnitType::GeneralTp, None), - (MapVariantType::Mines2, UnitType::ResistEvil, Some(UnitModifier::PlusPlus)), - (MapVariantType::DarkFalz, UnitType::DragonHp, Some(UnitModifier::Minus))]; + let unit_tests = vec![(MapArea::Forest1, UnitType::ResistFreeze, Some(UnitModifier::PlusPlus)), + (MapArea::Caves3, UnitType::GeneralTp, None), + (MapArea::Mines2, UnitType::ResistEvil, Some(UnitModifier::PlusPlus)), + (MapArea::DarkFalz, UnitType::DragonHp, Some(UnitModifier::Minus))]; for (area, unit, umod) in unit_tests { assert!(gut.get_drop(&area, &mut rng) == Some(ItemDropType::Unit(Unit { unit: unit, diff --git a/src/ship/drops/generic_weapon.rs b/src/ship/drops/generic_weapon.rs index d34f484..761d329 100644 --- a/src/ship/drops/generic_weapon.rs +++ b/src/ship/drops/generic_weapon.rs @@ -8,7 +8,7 @@ use rand::seq::SliceRandom; 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::ship::map::MapArea; use crate::entity::character::SectionID; use crate::ship::drops::{ItemDropType, load_data_file}; @@ -95,8 +95,8 @@ struct AttributeRates { } impl AttributeRates { - fn get_by_area(&self, map_area: &MapVariantType) -> AttributeRate { - match map_area.area_value().unwrap() { + fn get_by_area(&self, map_area: &MapArea) -> AttributeRate { + match map_area.drop_area_value().unwrap() { 0 => self.area1, 1 => self.area2, 2 => self.area3, @@ -179,8 +179,8 @@ struct AreaPercentPatterns { } impl AreaPercentPatterns { - fn get_by_area(&self, map_area: &MapVariantType) -> AttributePercentPattern { - match map_area.area_value().unwrap() { + fn get_by_area(&self, map_area: &MapArea) -> AttributePercentPattern { + match map_area.drop_area_value().unwrap() { 0 => self.area1, 1 => self.area2, 2 => self.area3, @@ -301,13 +301,13 @@ impl AttributeTable { }).0 } - fn generate_attributes(&self, map_area: &MapVariantType, rng: &mut R) -> [Option; 3] { + fn generate_attributes(&self, map_area: &MapArea, 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); self.attributes(&percent_pattern, &attribute_rate, rng) } - pub fn generate_rare_attributes(&self, map_area: &MapVariantType, rng: &mut R) -> [Option; 3] { + pub fn generate_rare_attributes(&self, map_area: &MapArea, rng: &mut R) -> [Option; 3] { let percent_pattern = AttributePercentPattern { attribute1: Some(PercentPatternType::Pattern6), attribute2: Some(PercentPatternType::Pattern6), @@ -345,8 +345,8 @@ impl 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() { + fn rate_by_area(&self, map_area: &MapArea) -> SpecialRate { + match map_area.drop_area_value().unwrap() { 0 => self.area1, 1 => self.area2, 2 => self.area3, @@ -376,7 +376,7 @@ impl SpecialRates { *specials.choose(rng).unwrap() } - fn get_special(&self, map_area: &MapVariantType, rng: &mut R) -> Option { + fn get_special(&self, map_area: &MapArea, 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)) @@ -422,16 +422,16 @@ impl GenericWeaponTable { } } - fn area_rank(&self, ratio: &WeaponRatio, map_area: &MapVariantType) -> (u32, u32) { + fn area_rank(&self, ratio: &WeaponRatio, map_area: &MapArea) -> (u32, u32) { if ratio.rank >= 0 { - (map_area.area_value().unwrap_or(0), ratio.rank as u32) + (map_area.drop_area_value().unwrap_or(0), ratio.rank as u32) } else { - ((map_area.area_value().unwrap_or(0) as i32 + ratio.rank) as u32, 0) + ((map_area.drop_area_value().unwrap_or(0) as i32 + ratio.rank) as u32, 0) } } - fn get_possible_weapon_types(&self, map_area: &MapVariantType) -> BTreeMap { + fn get_possible_weapon_types(&self, map_area: &MapArea) -> BTreeMap { let mut valid_weapons = BTreeMap::new(); let item_rate_pairs = vec![ @@ -450,7 +450,7 @@ impl GenericWeaponTable { ]; 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 { + if ratio.rate > 0 && map_area.drop_area_value().map(|k| k as i32 + ratio.rank).unwrap_or(-1) >= 0 { valid_weapons.insert(item_type, ratio); } } @@ -458,7 +458,7 @@ impl GenericWeaponTable { valid_weapons } - fn weapon_type(&self, possible_weapon_types: &BTreeMap, map_area: &MapVariantType, rng: &mut R) -> WeaponDropType { + fn weapon_type(&self, possible_weapon_types: &BTreeMap, map_area: &MapArea, rng: &mut R) -> WeaponDropType { let mut weapon_rates = possible_weapon_types.iter() .map(|(weapon, stat)| { (weapon, stat.rate) @@ -468,7 +468,7 @@ impl GenericWeaponTable { } - fn weapon_rank(&self, ratio: &WeaponRatio, map_area: &MapVariantType) -> u32 { + fn weapon_rank(&self, ratio: &WeaponRatio, map_area: &MapArea) -> u32 { let (area, rank) = self.area_rank(ratio, map_area); let weapon_rank = (rank + area / ratio.inc); @@ -480,7 +480,7 @@ impl GenericWeaponTable { weapons[std::cmp::min(weapons.len() - 1, weapon_rank as usize)] } - fn get_grind(&self, ratio: &WeaponRatio, map_area: &MapVariantType, rng: &mut R) -> usize { + fn get_grind(&self, ratio: &WeaponRatio, map_area: &MapArea, rng: &mut R) -> usize { let (area, _) = self.area_rank(ratio, map_area); let pattern = std::cmp::min(area % ratio.inc, 3); @@ -489,7 +489,7 @@ impl GenericWeaponTable { grind_choice.sample(rng) } - pub fn get_drop(&self, map_area: &MapVariantType, rng: &mut R) -> Option { + pub fn get_drop(&self, map_area: &MapArea, 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(); @@ -519,7 +519,7 @@ mod test { 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(ItemDropType::Weapon(Weapon { + assert!(gwt.get_drop(&MapArea::Forest1, &mut rng) == Some(ItemDropType::Weapon(Weapon { weapon: WeaponType::Cane, special: None, grind: 0, @@ -528,7 +528,7 @@ mod test { }))); let gwt = GenericWeaponTable::new(Episode::One, Difficulty::Hard, SectionID::Skyly); - assert!(gwt.get_drop(&MapVariantType::Caves2, &mut rng) == Some(ItemDropType::Weapon(Weapon { + assert!(gwt.get_drop(&MapArea::Caves2, &mut rng) == Some(ItemDropType::Weapon(Weapon { weapon: WeaponType::Sniper, special: None, grind: 2, @@ -537,7 +537,7 @@ mod test { }))); let gwt = GenericWeaponTable::new(Episode::One, Difficulty::VeryHard, SectionID::Skyly); - assert!(gwt.get_drop(&MapVariantType::Mines1, &mut rng) == Some(ItemDropType::Weapon(Weapon { + assert!(gwt.get_drop(&MapArea::Mines1, &mut rng) == Some(ItemDropType::Weapon(Weapon { weapon: WeaponType::Club, special: Some(WeaponSpecial::Berserk), grind: 0, @@ -546,7 +546,7 @@ mod test { }))); let gwt = GenericWeaponTable::new(Episode::One, Difficulty::Ultimate, SectionID::Skyly); - assert!(gwt.get_drop(&MapVariantType::DarkFalz, &mut rng) == Some(ItemDropType::Weapon(Weapon { + assert!(gwt.get_drop(&MapArea::DarkFalz, &mut rng) == Some(ItemDropType::Weapon(Weapon { weapon: WeaponType::Vulcan, special: None, grind: 0, diff --git a/src/ship/drops/mod.rs b/src/ship/drops/mod.rs index 32a98b7..9887e7e 100644 --- a/src/ship/drops/mod.rs +++ b/src/ship/drops/mod.rs @@ -17,7 +17,7 @@ use rand::{Rng, SeedableRng}; use crate::ship::monster::MonsterType; use crate::ship::room::{Difficulty, Episode}; -use crate::ship::map::MapVariantType; +use crate::ship::map::MapArea; use crate::entity::character::SectionID; use crate::ship::drops::generic_weapon::GenericWeaponTable; use crate::ship::drops::generic_armor::GenericArmorTable; @@ -98,7 +98,7 @@ impl ItemDrop { -struct DropTable { +pub struct DropTable { monster_stats: HashMap, rare_table: RareDropTable, weapon_table: GenericWeaponTable, @@ -112,7 +112,7 @@ struct DropTable { impl DropTable { - fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> DropTable { + pub 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()); @@ -140,7 +140,7 @@ impl DropTable { Some(ItemDropType::Meseta(self.rng.gen_range(monster.min_meseta, monster.max_meseta))) } - fn generate_typed_drop(&mut self, map_area: &MapVariantType, monster: &MonsterDropStats) -> Option { + fn generate_typed_drop(&mut self, map_area: &MapArea, monster: &MonsterDropStats) -> Option { match monster.drop_type { MonsterDropType::Weapon => self.weapon_table.get_drop(map_area, &mut self.rng), MonsterDropType::Armor => self.armor_table.get_drop(map_area, &mut self.rng), @@ -150,7 +150,7 @@ impl DropTable { } } - fn get_drop(&mut self, map_area: &MapVariantType, monster: &MonsterType) -> Option { + pub fn get_drop(&mut self, map_area: &MapArea, monster: &MonsterType) -> Option { let monster_stat = *self.monster_stats.get(monster)?; let drop_anything = self.rng.gen_range(0, 100); @@ -178,7 +178,7 @@ impl DropTable { } } - pub fn get_box_drop(&mut self, map_area: &MapVariantType, object: &MapObject) -> Option { + pub fn get_box_drop(&mut self, map_area: &MapArea, object: &MapObject) -> Option { self.box_table.get_drop(map_area, object, &mut self.rng) } } diff --git a/src/ship/drops/rare_drop_table.rs b/src/ship/drops/rare_drop_table.rs index 286e197..04fa477 100644 --- a/src/ship/drops/rare_drop_table.rs +++ b/src/ship/drops/rare_drop_table.rs @@ -10,7 +10,7 @@ use crate::entity::item::mag::{Mag, MagType}; use crate::entity::character::SectionID; use crate::ship::monster::MonsterType; use crate::ship::room::{Difficulty, Episode}; -use crate::ship::map::MapVariantType; +use crate::ship::map::MapArea; use crate::ship::drops::{ItemDropType, load_data_file}; use crate::ship::drops::generic_weapon::AttributeTable; use crate::ship::drops::generic_armor::GenericArmorTable; @@ -95,7 +95,7 @@ impl RareDropTable { } } - pub fn apply_item_stats(&self, map_area: &MapVariantType, item: RareDropItem, rng: &mut R) -> ItemDropType { + pub fn apply_item_stats(&self, map_area: &MapArea, item: RareDropItem, rng: &mut R) -> ItemDropType { match item { RareDropItem::Weapon(weapon) => { ItemDropType::Weapon(Weapon { @@ -148,7 +148,7 @@ impl RareDropTable { } } - pub fn get_drop(&self, map_area: &MapVariantType, monster: &MonsterType, rng: &mut R) -> Option { + pub fn get_drop(&self, map_area: &MapArea, monster: &MonsterType, rng: &mut R) -> Option { self.rates.get(monster) .and_then(|drop_rates| { drop_rates.iter() diff --git a/src/ship/drops/tech_table.rs b/src/ship/drops/tech_table.rs index 5a9efd5..370f9c5 100644 --- a/src/ship/drops/tech_table.rs +++ b/src/ship/drops/tech_table.rs @@ -6,7 +6,7 @@ use rand::distributions::{WeightedIndex, Distribution}; use crate::entity::item::tech::{Technique, TechniqueDisk}; use crate::ship::room::{Difficulty, Episode}; -use crate::ship::map::MapVariantType; +use crate::ship::map::MapArea; use crate::entity::character::SectionID; use crate::ship::drops::{ItemDropType, load_data_file}; @@ -65,8 +65,8 @@ impl TechniqueRates { } impl TechniqueRates { - fn get_by_area<'a>(&'a self, map_area: &MapVariantType) -> &'a BTreeMap { - match map_area.area_value().unwrap() { + fn get_by_area<'a>(&'a self, map_area: &MapArea) -> &'a BTreeMap { + match map_area.drop_area_value().unwrap() { 0 => &self.area1, 1 => &self.area2, 2 => &self.area3, @@ -95,7 +95,7 @@ impl TechniqueTable { } - pub fn get_drop(&self, map_area: &MapVariantType, rng: &mut R) -> Option { + pub fn get_drop(&self, map_area: &MapArea, rng: &mut R) -> Option { let mut tech_rates = self.rates.get_by_area(map_area).iter(); let tech_weights = WeightedIndex::new(tech_rates.clone().map(|(_, stat)| stat.rate)).unwrap(); @@ -118,10 +118,10 @@ mod test { let mut rng = rand_chacha::ChaCha20Rng::from_seed([23;32]); let tt = TechniqueTable::new(Episode::One, Difficulty::Ultimate, SectionID::Skyly); - let tech_tests = vec![(MapVariantType::Forest1, Technique::Resta, 13), - (MapVariantType::Caves3, Technique::Foie, 24), - (MapVariantType::Mines2, Technique::Gibarta, 20), - (MapVariantType::DarkFalz, Technique::Razonde, 22)]; + let tech_tests = vec![(MapArea::Forest1, Technique::Resta, 13), + (MapArea::Caves3, Technique::Foie, 24), + (MapArea::Mines2, Technique::Gibarta, 20), + (MapArea::DarkFalz, Technique::Razonde, 22)]; for (area, tech, level) in tech_tests { assert!(tt.get_drop(&area, &mut rng) == Some(ItemDropType::TechniqueDisk( diff --git a/src/ship/drops/tool_table.rs b/src/ship/drops/tool_table.rs index 04c73ea..d20d9da 100644 --- a/src/ship/drops/tool_table.rs +++ b/src/ship/drops/tool_table.rs @@ -6,7 +6,7 @@ use rand::distributions::{WeightedIndex, Distribution}; use crate::entity::item::tool::{Tool, ToolType}; use crate::ship::room::{Difficulty, Episode}; -use crate::ship::map::MapVariantType; +use crate::ship::map::MapArea; use crate::entity::character::SectionID; use crate::ship::drops::{ItemDropType, load_data_file}; use crate::ship::drops::tech_table::TechniqueTable; @@ -93,8 +93,8 @@ impl ToolRates { } impl ToolRates { - fn get_by_area<'a>(&'a self, map_area: &MapVariantType) -> &'a BTreeMap { - match map_area.area_value().unwrap() { + fn get_by_area<'a>(&'a self, map_area: &MapArea) -> &'a BTreeMap { + match map_area.drop_area_value().unwrap() { 0 => &self.area1, 1 => &self.area2, 2 => &self.area3, @@ -124,7 +124,7 @@ impl ToolTable { } } - pub fn get_drop(&self, map_area: &MapVariantType, rng: &mut R) -> Option { + pub fn get_drop(&self, map_area: &MapArea, rng: &mut R) -> Option { let tool_rates = self.rates.get_by_area(map_area).iter(); let tool_weights = WeightedIndex::new(tool_rates.clone().map(|(_, weights)| weights)).unwrap(); diff --git a/src/ship/items.rs b/src/ship/items.rs index 9d6be6e..9dcd068 100644 --- a/src/ship/items.rs +++ b/src/ship/items.rs @@ -11,12 +11,14 @@ use crate::entity::item::shield::Shield; use crate::entity::item::unit::Unit; use crate::entity::item::tool::Tool; use crate::entity::item::mag::Mag; +use crate::entity::item::Meseta; #[derive(Debug, PartialEq)] -pub enum StackedItem { +pub enum ItemInstance { Individual(ItemEntity), Stacked(Vec), + Meseta(Meseta), } #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] @@ -26,13 +28,13 @@ pub struct ActiveItemId(u32); #[derive(Debug)] pub struct ActiveItem { id: ActiveItemId, - item: StackedItem, + item: ItemInstance, } impl ActiveItem { pub fn as_client_bytes(&self) -> [u8; 16] { match &self.item { - StackedItem::Individual(i) => { + ItemInstance::Individual(i) => { match &i.item { ItemDetail::Weapon(w) => w.as_bytes(), ItemDetail::Armor(a) => a.as_bytes(), @@ -43,12 +45,15 @@ impl ActiveItem { ItemDetail::Mag(m) => m.as_bytes(), } }, - StackedItem::Stacked(i) => { + ItemInstance::Stacked(i) => { let len = i.len(); match &i[0].item { ItemDetail::Tool(t) => t.as_stacked_bytes(len), _ => panic!(), } + }, + ItemInstance::Meseta(m) => { + m.as_bytes() } } } @@ -68,12 +73,12 @@ impl ActiveInventory { // does this do anything? inventory[index].equipped = match item.item { - StackedItem::Individual(ItemEntity {location: ItemLocation::Inventory{ equipped: true, ..}, ..}) => 1, + ItemInstance::Individual(ItemEntity {location: ItemLocation::Inventory{ equipped: true, ..}, ..}) => 1, _ => 0, }; // because this actually equips the item inventory[index].flags |= match item.item { - StackedItem::Individual(ItemEntity {location: ItemLocation::Inventory{ equipped: true, ..}, ..}) => 8, + ItemInstance::Individual(ItemEntity {location: ItemLocation::Inventory{ equipped: true, ..}, ..}) => 8, _ => 0, }; inventory @@ -85,24 +90,25 @@ impl ActiveInventory { } } -fn inventory_item_index(item: &StackedItem) -> usize { +fn inventory_item_index(item: &ItemInstance) -> usize { match item { - StackedItem::Individual(i) => { + ItemInstance::Individual(i) => { match i.location { ItemLocation::Inventory{index: index, ..} => index, _ => panic!() } }, - StackedItem::Stacked(i) => { + ItemInstance::Stacked(i) => { match i[0].location { ItemLocation::Inventory{index: index, ..} => index, _ => panic!() } - } + }, + _ => panic!(), } } -fn stack_items(items: Vec) -> Vec { +fn stack_items(items: Vec) -> Vec { let mut stacks = HashMap::new(); for item in items { @@ -113,11 +119,11 @@ fn stack_items(items: Vec) -> Vec { .map(|(itype, items)| { match items[0].item.is_stackable() { true => { - vec![StackedItem::Stacked(items)] + vec![ItemInstance::Stacked(items)] }, false => { items.into_iter().map(|i| { - StackedItem::Individual(i) + ItemInstance::Individual(i) }).collect() } } @@ -141,7 +147,7 @@ impl ActiveItemDatabase { } } - fn activate_item(&mut self, item: StackedItem) -> ActiveItem { + fn activate_item(&mut self, item: ItemInstance) -> ActiveItem { self.id += 1; ActiveItem { id: ActiveItemId(self.id), @@ -294,23 +300,23 @@ mod test { assert!(stacked.len() == 5); assert!(stacked.iter().filter(|k| { - **k == StackedItem::Individual(item6.clone()) + **k == ItemInstance::Individual(item6.clone()) }).count() == 1); assert!(stacked.iter().filter(|k| { - **k == StackedItem::Individual(item3.clone()) + **k == ItemInstance::Individual(item3.clone()) }).count() == 1); assert!(stacked.iter().filter(|k| { - **k == StackedItem::Individual(item1.clone()) + **k == ItemInstance::Individual(item1.clone()) }).count() == 1); assert!(stacked.iter().filter(|k| { - **k == StackedItem::Stacked(vec![item2.clone(), item4.clone(), item5.clone()]) + **k == ItemInstance::Stacked(vec![item2.clone(), item4.clone(), item5.clone()]) }).count() == 1); assert!(stacked.iter().filter(|k| { - **k == StackedItem::Stacked(vec![item7.clone(), item8.clone(), item9.clone()]) + **k == ItemInstance::Stacked(vec![item7.clone(), item8.clone(), item9.clone()]) }).count() == 1); } } diff --git a/src/ship/location.rs b/src/ship/location.rs index 666b797..95e4134 100644 --- a/src/ship/location.rs +++ b/src/ship/location.rs @@ -1,305 +1,519 @@ +use std::collections::HashMap; use std::sync::{Arc, RwLock}; +use std::convert::Into; use std::time::SystemTime; use crate::common::serverstate::ClientId; -// TODO: room passwords? -// TODO: remove clients from areas (or upon insert, remove that id from anywhere else) pub const MAX_ROOMS: usize = 128; -#[derive(Copy, Clone)] -pub struct AreaClient { - client_id: ClientId, - time_join: SystemTime, -} - -#[derive(Copy, Clone)] -pub struct InnerClientArea { - clients: [Option; N], +pub enum AreaType { + Room, + Lobby, } -impl InnerClientArea<{N}> { - pub fn new() -> InnerClientArea<{N}> { - let mut clients: [std::mem::MaybeUninit>; N] = unsafe { - std::mem::MaybeUninit::uninit().assume_init() - }; - for i in clients.iter_mut() { - i.write(None); - } - - InnerClientArea { - clients: unsafe { (&clients as *const _ as *const [Option; N]).read()} - } - } +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct LobbyId(pub usize); +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct RoomId(pub usize); - fn add(&mut self, id: ClientId) -> Option { - for (i, client) in self.clients.iter_mut().enumerate() { - if client.is_none() { - *client = Some(AreaClient{ - client_id: id, - time_join: SystemTime::now(), - }); - return Some(i); - } - } - return None; - } - fn remove(&mut self, id: ClientId) -> bool { - for areaclient in self.clients.iter_mut() { - if let Some(client) = *areaclient { - if client.client_id == id { - *areaclient = None; - return true; - } - } - } - false - } - - fn contains(&self, id: ClientId) -> bool { - self.clients.iter() - .filter(|k| k.is_some()) - .map(|k| k.unwrap() ) - .fold(false, |acc, k| { - if acc { - acc - } - else if k.client_id == id { - true - } - else { - false - } - }) +impl LobbyId { + pub fn id(&self) -> u8 { + self.0 as u8 } } -pub struct LobbyId(pub usize); -pub struct RoomId(pub usize); -pub type Lobby = InnerClientArea<12>; -pub type Room = InnerClientArea<4>; - -trait ClientArea { - fn clients(&self) -> std::slice::Iter<'_, Option>; - fn remove(&mut self, id: ClientId) -> bool; +#[derive(Debug, PartialEq)] +pub enum CreateRoomError { + NoOpenSlots, + ClientInAreaAlready, + JoinError, } +#[derive(Debug, PartialEq)] +pub enum JoinRoomError { + RoomDoesNotExist, + RoomFull, + ClientInAreaAlready, +} -impl ClientArea for Lobby { - fn clients(& self) -> std::slice::Iter<'_, Option> { - self.clients.iter() - } - - fn remove(&mut self, id: ClientId) -> bool { - self.remove(id) - } +#[derive(Debug, PartialEq)] +pub enum JoinLobbyError { + LobbyDoesNotExist, + LobbyFull, + ClientInAreaAlready, } -impl<'a> ClientArea for Room { - fn clients(& self) -> std::slice::Iter<'_, Option> { - self.clients.iter() - } +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct LocalClientId(usize); - fn remove(&mut self, id: ClientId) -> bool { - self.remove(id) +impl LocalClientId { + pub fn id(&self) -> u8 { + self.0 as u8 } } -pub enum AreaType { - Lobby, - Room, +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct AreaClient { + pub client: ClientId, + pub local_client: LocalClientId, + time_join: SystemTime, } -#[derive(Debug)] -pub struct ClientAtLocation { - pub client_id: ClientId, - pub index: usize, +#[derive(Debug, Copy, Clone, PartialEq)] +struct Lobby([Option; 12]); +#[derive(Debug, Copy, Clone, PartialEq)] +struct Room([Option; 4]); + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum RoomLobby { + Room(RoomId), + Lobby(LobbyId), } -pub struct Area { - pub area_type: AreaType, - area: Arc>, - index: usize, +#[derive(Debug, PartialEq)] +pub enum GetAreaError { + InvalidClient, } -impl Area { - fn new(area_type: AreaType, area: Arc>, index: usize) -> Area { - Area { - area_type: area_type, - area: area, - index: index, - } - } - pub fn clients(&self) -> Vec { - self.area.read().unwrap().clients() - .enumerate() - .filter(|(_i, k)| k.is_some()) - .map(|(i, k)| (i, k.unwrap()) ) - .map(|(i, k)| ClientAtLocation { - client_id: k.client_id, - index: i - }).collect() - } - // TODO: Result in cases where no one is in the area? - pub fn leader(&self) -> ClientAtLocation { - self.area.read().unwrap().clients() - .enumerate() - .filter(|(_i, k)| k.is_some()) - .map(|(i, k)| (i, k.unwrap()) ) - .fold((ClientAtLocation { - client_id: ClientId(0), - index: 0 - }, SystemTime::UNIX_EPOCH), - |(acc, time), (i, k)| { - if time > k.time_join { - (ClientAtLocation { - client_id: k.client_id, - index: i, - }, k.time_join) - } - else { - (acc, time) - } - }).0 - } - - pub fn remove(&mut self, id: ClientId) -> bool { - self.area.write().unwrap().remove(id) - } - - pub fn id(&self) -> usize { - self.index - } +#[derive(Debug, PartialEq)] +pub enum ClientRemovalError { + ClientNotInArea, + InvalidArea, } -#[derive(Debug)] -pub enum CreateRoomError { - NoOpenSlots, - ClientInAreaAlready, +#[derive(Debug, PartialEq)] +pub enum GetClientsError { + InvalidClient, + InvalidArea, } -#[derive(Debug)] -pub enum JoinRoomError { - RoomDoesNotExist, - RoomFull, - ClientInAreaAlready, +#[derive(Debug, PartialEq)] +pub enum GetNeighborError { + InvalidClient, + InvalidArea, } -#[derive(Debug)] -pub enum JoinLobbyError { - LobbyDoesNotExist, - LobbyFull, - ClientInAreaAlready, +#[derive(Debug, PartialEq)] +pub enum GetLeaderError { + InvalidClient, + InvalidArea, + NoClientInArea, } pub struct ClientLocation { - lobbies: [Arc>; 15], - rooms: [Option>>; MAX_ROOMS], + lobbies: [Lobby; 15], + rooms: [Option; MAX_ROOMS], + client_location: HashMap, } impl ClientLocation { pub fn new() -> ClientLocation { ClientLocation { - //lobbies: [Arc::new(RwLock::new(Lobby::new())); 15], - lobbies: crate::init_array!(Arc>, 15, Arc::new(RwLock::new(Lobby::new()))), + lobbies: [Lobby([None; 12]); 15], rooms: [None; MAX_ROOMS], + client_location: HashMap::new(), } } - fn err_if_client_is_in_area(&mut self, id: ClientId, err: E) -> Result<(), E> { - let in_lobby = self.lobbies.iter() - .any(|k| k.read().unwrap().contains(id)); - let in_room = self.rooms.iter() - .filter(|k| k.is_some()) - .map(|k| k.as_ref().unwrap()) - .any(|k| k.read().unwrap().contains(id)); - - if in_lobby || in_room { - Err(err) - } - else { - Ok(()) - } + pub fn add_client_to_lobby(&mut self, id: ClientId, lobby: LobbyId) -> Result<(), JoinLobbyError> { + let l = self.lobbies.get_mut(lobby.0).ok_or(JoinLobbyError::LobbyDoesNotExist)?; + let (index, empty_slot) = l.0.iter_mut() + .enumerate() + .filter(|(_, k)| k.is_none()) + .nth(0) + .ok_or(JoinLobbyError::LobbyFull)?; + *empty_slot = Some(AreaClient { + client: id, + local_client: LocalClientId(index), + time_join: SystemTime::now(), + }); + self.remove_client_from_area(id); + self.client_location.insert(id, RoomLobby::Lobby(lobby)); + Ok(()) } - pub fn add_to_lobby(&mut self, id: ClientId, lobby: LobbyId) -> Result { - self.err_if_client_is_in_area(id, JoinLobbyError::ClientInAreaAlready)?; - self.lobbies.get_mut(lobby.0) - .ok_or(JoinLobbyError::LobbyDoesNotExist)? - .write().unwrap() - .add(id) - .ok_or(JoinLobbyError::LobbyFull) + + pub fn add_client_to_next_available_lobby(&mut self, id: ClientId, lobby: LobbyId) -> Result { + let l = (0..15) + .map(|lobby_index| { + let new_lobby = LobbyId((lobby.0 + lobby_index) % 15); + (new_lobby, self.add_client_to_lobby(id, new_lobby)) + }) + .filter(|(_, lobby_option)| { + lobby_option.is_ok() + }) + .nth(0) + .ok_or(JoinLobbyError::LobbyFull)?; + + Ok(l.0) } - pub fn new_room(&mut self, id: ClientId) -> Result { - let (room_id, empty_room) = self.rooms.iter_mut() + pub fn create_new_room(&mut self, id: ClientId) -> Result { + let (index, empty_slot) = self.rooms.iter_mut() .enumerate() - .filter(|(_, k)| k.is_none()) + .filter(|(_, r)| r.is_none()) .nth(0) .ok_or(CreateRoomError::NoOpenSlots)?; + *empty_slot = Some(Room([None; 4])); + self.add_client_to_room(id, RoomId(index)).map_err(|err| CreateRoomError::JoinError)?; - let mut new_room = Room::new(); - new_room.add(id); - *empty_room = Some(Arc::new(RwLock::new(new_room))); - self.remove_from_location(id); - - Ok(RoomId(room_id)) + Ok(RoomId(index)) } - pub fn add_to_room(&mut self, id: ClientId, room: RoomId) -> Result { - self.err_if_client_is_in_area(id, JoinRoomError::ClientInAreaAlready)?; - self.rooms.get_mut(room.0) + pub fn add_client_to_room(&mut self, id: ClientId, room: RoomId) -> Result<(), JoinRoomError> { + let r = self.rooms.get_mut(room.0) .ok_or(JoinRoomError::RoomDoesNotExist)? .as_mut() - .ok_or(JoinRoomError::RoomDoesNotExist)? - .write().unwrap() - .add(id) - .ok_or(JoinRoomError::RoomFull) + .ok_or(JoinRoomError::RoomDoesNotExist)?; + let (index, empty_slot) = r.0.iter_mut() + .enumerate() + .filter(|(_, k)| k.is_none()) + .nth(0) + .ok_or(JoinRoomError::RoomFull)?; + *empty_slot = Some(AreaClient { + client: id, + local_client: LocalClientId(index), + time_join: SystemTime::now(), + }); + self.remove_client_from_area(id); + self.client_location.insert(id, RoomLobby::Room(room)); + Ok(()) + } + + + pub fn get_all_clients_by_client(&self, id: ClientId) -> Result, GetNeighborError> { + let area = self.client_location.get(&id).ok_or(GetNeighborError::InvalidClient)?; + match area { + RoomLobby::Room(room) => { + Ok(self.get_clients_in_room(*room).map_err(|_| GetNeighborError::InvalidArea)? + .into_iter() + .collect()) + }, + RoomLobby::Lobby(lobby) => { + Ok(self.get_clients_in_lobby(*lobby).map_err(|_| GetNeighborError::InvalidArea)? + .into_iter() + .collect()) + } + } } - pub fn get_area_by_user(&mut self, id: ClientId) -> Area { - for (i, lobby) in self.lobbies.iter().enumerate() { - if lobby.read().unwrap().contains(id) { - return Area::new(AreaType::Lobby, lobby.clone(), i); + pub fn get_client_neighbors(&self, id: ClientId) -> Result, GetNeighborError> { + let area = self.client_location.get(&id).ok_or(GetNeighborError::InvalidClient)?; + match area { + RoomLobby::Room(room) => { + Ok(self.get_clients_in_room(*room).map_err(|_| GetNeighborError::InvalidArea)? + .into_iter() + .filter(|c| c.client != id) + .collect()) + }, + RoomLobby::Lobby(lobby) => { + Ok(self.get_clients_in_lobby(*lobby).map_err(|_| GetNeighborError::InvalidArea)? + .into_iter() + .filter(|c| c.client != id) + .collect()) } } + } + + pub fn get_room_leader(&self, room: RoomId) -> Result { + let mut r = self.rooms[room.0] + .as_ref() + .ok_or(GetLeaderError::InvalidArea)? + .0.iter().flat_map(|k| k) + .collect::>(); + r.sort_by_key(|k| k.time_join); + let c = r.get(0).ok_or(GetLeaderError::NoClientInArea)?; + Ok(**c) + } - for (i, room) in self.rooms.iter().enumerate() { - if let Some(room) = room { - if room.read().unwrap().contains(id){ - return Area::new(AreaType::Room, room.clone(), i); - } + pub fn get_lobby_leader(&self, lobby: LobbyId) -> Result { + let mut l = self.lobbies[lobby.0] + .0.iter().flat_map(|k| k) + .collect::>(); + l.sort_by_key(|k| k.time_join); + let c = l.get(0).ok_or(GetLeaderError::NoClientInArea)?; + Ok(**c) + } + + pub fn get_area_leader(&self, roomlobby: RoomLobby) -> Result { + match roomlobby { + RoomLobby::Room(room) => { + self.get_room_leader(room) + }, + RoomLobby::Lobby(lobby) => { + self.get_lobby_leader(lobby) } } + } + + pub fn get_leader_by_client(&self, id: ClientId) -> Result { + let area = self.client_location.get(&id).ok_or(GetLeaderError::InvalidClient)?; + match area { + RoomLobby::Room(room) => { + self.get_room_leader(*room) + }, + RoomLobby::Lobby(lobby) => { + self.get_lobby_leader(*lobby) + } + } + } + + pub fn get_clients_in_lobby(&self, lobby: LobbyId) -> Result, GetClientsError> { + Ok(self.lobbies.get(lobby.0).ok_or(GetClientsError::InvalidArea)?.0 + .iter() + .filter_map(|client| { + client.map(|c| { + c + }) + }).collect()) + } + + pub fn get_clients_in_room(&self, room: RoomId) -> Result, GetClientsError> { + Ok(self.rooms.get(room.0) + .ok_or(GetClientsError::InvalidArea)? + .ok_or(GetClientsError::InvalidArea)?.0 + .iter() + .filter_map(|client| { + client.map(|c| { + c + }) + }).collect()) + } + + pub fn get_local_client(&self, id: ClientId) -> Result { + let area = self.client_location.get(&id).ok_or(GetClientsError::InvalidClient)?; + match area { + RoomLobby::Room(room) => { + self.get_clients_in_room(*room).map_err(|_| GetClientsError::InvalidArea)? + .into_iter() + .filter(|c| c.client == id) + .nth(0) + .ok_or(GetClientsError::InvalidClient) + }, + RoomLobby::Lobby(lobby) => { + self.get_clients_in_lobby(*lobby).map_err(|_| GetClientsError::InvalidArea)? + .into_iter() + .filter(|c| c.client == id) + .nth(0) + .ok_or(GetClientsError::InvalidClient) + } + } + } + + pub fn get_area(&self, id: ClientId) -> Result { + self.client_location.get(&id) + .ok_or(GetAreaError::InvalidClient) + .map(Clone::clone) + } + + pub fn remove_client_from_area(&mut self, id: ClientId) -> Result<(), ClientRemovalError> { + let area = self.client_location.get_mut(&id).ok_or(ClientRemovalError::ClientNotInArea)?; + let client_list = match area { + RoomLobby::Room(room) => { + self.rooms[room.0].as_mut().map_or(None, |r| { + Some(r.0.iter_mut()) + }) + }, + RoomLobby::Lobby(lobby) => { + Some(self.lobbies[lobby.0].0.iter_mut()) + } + }; + + client_list + .ok_or(ClientRemovalError::InvalidArea)? + .filter(|client| { + client.map_or(false, |c| { + c.client == id + }) + }) + .for_each(|client| { + *client = None + }); + Ok(()) + } +} + + + + + + - panic!("client is not in a room/lobby") + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_add_client_to_lobby() { + let mut cl = ClientLocation::new(); + cl.add_client_to_lobby(ClientId(12), LobbyId(0)); + cl.add_client_to_lobby(ClientId(13), LobbyId(1)); + cl.add_client_to_lobby(ClientId(14), LobbyId(0)); + + assert!(cl.get_clients_in_lobby(LobbyId(0)).into_iter().flatten().map(|c| (c.client, c.local_client)).collect::>() == vec![ + (ClientId(12), LocalClientId(0)), + (ClientId(14), LocalClientId(1)), + ]); } - pub fn remove_from_location(&mut self, id: ClientId) { - let in_lobby = self.lobbies.iter_mut() - .map(|lobby| lobby.write().unwrap().remove(id)) - .any(|k| k); + #[test] + fn test_add_client_to_full_lobby() { + let mut cl = ClientLocation::new(); + (0..12).for_each(|i| { + cl.add_client_to_lobby(ClientId(i), LobbyId(0)); + }); + assert!(cl.add_client_to_lobby(ClientId(99), LobbyId(0)) == Err(JoinLobbyError::LobbyFull)); + } - if in_lobby { - return; + #[test] + fn test_add_client_to_next_available_lobby() { + let mut cl = ClientLocation::new(); + (1..4).for_each(|lobby| { + (0..12).for_each(|i| { + cl.add_client_to_lobby(ClientId(lobby*12+i), LobbyId(lobby)); + }); + }); + assert!(cl.add_client_to_next_available_lobby(ClientId(99), LobbyId(1)) == Ok(LobbyId(4))); + } + + #[test] + fn test_add_to_lobby_when_all_are_full() { + let mut cl = ClientLocation::new(); + (0..15).for_each(|lobby| { + (0..12).for_each(|i| { + cl.add_client_to_lobby(ClientId(lobby*12+i), LobbyId(lobby)); + }); + }); + assert!(cl.add_client_to_next_available_lobby(ClientId(99), LobbyId(1)) == Err(JoinLobbyError::LobbyFull)); + } + + #[test] + fn test_new_room() { + let mut cl = ClientLocation::new(); + assert!(cl.create_new_room(ClientId(12)) == Ok(RoomId(0))); + } + + #[test] + fn test_add_client_to_room() { + let mut cl = ClientLocation::new(); + let room = cl.create_new_room(ClientId(12)).unwrap(); + assert!(cl.add_client_to_room(ClientId(234), room) == Ok(())); + assert!(cl.get_clients_in_room(room).unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::>() == vec![ + (ClientId(12), LocalClientId(0)), + (ClientId(234), LocalClientId(1)), + ]); + } + + #[test] + fn test_no_new_room_slots() { + let mut cl = ClientLocation::new(); + for i in 0..128 { + cl.create_new_room(ClientId(i)); } + assert!(cl.create_new_room(ClientId(234)) == Err(CreateRoomError::NoOpenSlots)); + } + + #[test] + fn test_joining_full_room() { + let mut cl = ClientLocation::new(); + let room = cl.create_new_room(ClientId(0)).unwrap(); + assert!(cl.add_client_to_room(ClientId(1), room) == Ok(())); + assert!(cl.add_client_to_room(ClientId(2), room) == Ok(())); + assert!(cl.add_client_to_room(ClientId(3), room) == Ok(())); + assert!(cl.add_client_to_room(ClientId(234), room) == Err(JoinRoomError::RoomFull)); + } + + #[test] + fn test_adding_client_to_room_removes_from_lobby() { + let mut cl = ClientLocation::new(); + cl.add_client_to_lobby(ClientId(93), LobbyId(0)); + cl.add_client_to_lobby(ClientId(23), LobbyId(0)); + cl.add_client_to_lobby(ClientId(51), LobbyId(0)); + cl.add_client_to_lobby(ClientId(12), LobbyId(0)); + + let room = cl.create_new_room(ClientId(51)).unwrap(); + assert!(cl.add_client_to_room(ClientId(93), room) == Ok(())); + assert!(cl.get_clients_in_lobby(LobbyId(0)).unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::>() == vec![ + (ClientId(23), LocalClientId(1)), + (ClientId(12), LocalClientId(3)), + ]); + assert!(cl.get_clients_in_room(room).unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::>() == vec![ + (ClientId(51), LocalClientId(0)), + (ClientId(93), LocalClientId(1)), + ]); + } + + #[test] + fn test_getting_neighbors() { + let mut cl = ClientLocation::new(); + cl.add_client_to_lobby(ClientId(93), LobbyId(0)); + cl.add_client_to_lobby(ClientId(23), LobbyId(0)); + cl.add_client_to_lobby(ClientId(51), LobbyId(0)); + cl.add_client_to_lobby(ClientId(12), LobbyId(0)); + + assert!(cl.get_client_neighbors(ClientId(23)).unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::>() == vec![ + (ClientId(93), LocalClientId(0)), + (ClientId(51), LocalClientId(2)), + (ClientId(12), LocalClientId(3)), + ]); + } + + #[test] + fn test_failing_to_join_lobby_does_not_remove_from_current_area() { + let mut cl = ClientLocation::new(); + (0..12).for_each(|i| { + cl.add_client_to_lobby(ClientId(i), LobbyId(0)); + }); + cl.add_client_to_lobby(ClientId(99), LobbyId(1)); + cl.add_client_to_lobby(ClientId(99), LobbyId(0)); + assert!(cl.get_clients_in_lobby(LobbyId(0)).unwrap().len() == 12); + assert!(cl.get_clients_in_lobby(LobbyId(1)).unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::>() == vec![ + (ClientId(99), LocalClientId(0)), + ]); + } + + #[test] + fn test_get_leader() { + let mut cl = ClientLocation::new(); + cl.add_client_to_lobby(ClientId(93), LobbyId(0)); + cl.add_client_to_lobby(ClientId(23), LobbyId(0)); + cl.add_client_to_lobby(ClientId(51), LobbyId(0)); + cl.add_client_to_lobby(ClientId(12), LobbyId(0)); + + assert!(cl.get_leader_by_client(ClientId(51)).map(|c| (c.client, c.local_client)) == Ok((ClientId(93), LocalClientId(0)))); + } + + #[test] + fn test_remove_client_from_room() { + let mut cl = ClientLocation::new(); + let room = cl.create_new_room(ClientId(51)).unwrap(); + cl.add_client_to_room(ClientId(93), room); + cl.add_client_to_room(ClientId(23), room); + cl.remove_client_from_area(ClientId(51)); + cl.add_client_to_room(ClientId(12), room); + + assert!(cl.get_clients_in_room(room).unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::>() == vec![ + (ClientId(12), LocalClientId(0)), + (ClientId(93), LocalClientId(1)), + (ClientId(23), LocalClientId(2)), + ]); + } - self.rooms.iter_mut() - .filter(|lobby| lobby.is_some()) - .map(|lobby| lobby.as_ref().unwrap()) - .map(|lobby| lobby.write().unwrap().remove(id)) - .any(|k| k); - } - - pub fn get_client_count_in_room(&self, room_id: RoomId) -> u8 { - self.rooms[room_id.0].as_ref() - .unwrap() - .read() - .unwrap() - .clients() - .filter(|k| k.is_some()) - .count() as u8 + #[test] + fn test_leader_changes_on_leader_leaving() { + let mut cl = ClientLocation::new(); + let room = cl.create_new_room(ClientId(51)).unwrap(); + cl.add_client_to_room(ClientId(93), room); + cl.add_client_to_room(ClientId(23), room); + cl.remove_client_from_area(ClientId(51)); + cl.add_client_to_room(ClientId(12), room); + cl.remove_client_from_area(ClientId(23)); + cl.add_client_to_room(ClientId(99), room); + + assert!(cl.get_leader_by_client(ClientId(12)).map(|c| (c.client, c.local_client)) == Ok((ClientId(93), LocalClientId(1)))); } } diff --git a/src/ship/map.rs b/src/ship/map.rs index 7133aa7..afd4802 100644 --- a/src/ship/map.rs +++ b/src/ship/map.rs @@ -340,9 +340,8 @@ enum MapVariantMode { Offline, } -// TODO: rename this since apparently I'm going to be using it a lot #[derive(Debug)] -pub enum MapVariantType { +pub enum MapArea { Pioneer2Ep1, Forest1, Forest2, @@ -360,23 +359,48 @@ pub enum MapVariantType { DarkFalz, } -impl MapVariantType { - pub fn area_value(&self) -> Option { +pub enum MapAreaError { + UnknownMapArea(u32), +} + +impl MapArea { + pub fn from_value(episode: Episode, area: u32) -> Result { + match (episode, area) { + (Episode::One, 0) => Ok(MapArea::Pioneer2Ep1), + (Episode::One, 1) => Ok(MapArea::Forest1), + (Episode::One, 2) => Ok(MapArea::Forest2), + (Episode::One, 3) => Ok(MapArea::Caves1), + (Episode::One, 4) => Ok(MapArea::Caves2), + (Episode::One, 5) => Ok(MapArea::Caves3), + (Episode::One, 6) => Ok(MapArea::Mines1), + (Episode::One, 7) => Ok(MapArea::Mines2), + (Episode::One, 8) => Ok(MapArea::Ruins1), + (Episode::One, 9) => Ok(MapArea::Ruins2), + (Episode::One, 10) => Ok(MapArea::Ruins3), + (Episode::One, 11) => Ok(MapArea::Dragon), + (Episode::One, 12) => Ok(MapArea::DeRolLe), + (Episode::One, 13) => Ok(MapArea::VolOpt), + (Episode::One, 14) => Ok(MapArea::DarkFalz), + _ => Err(MapAreaError::UnknownMapArea(area)) + } + } + + pub fn drop_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), + MapArea::Forest1 => Some(0), + MapArea::Forest2 => Some(1), + MapArea::Caves1 => Some(2), + MapArea::Caves2 => Some(3), + MapArea::Caves3 => Some(4), + MapArea::Mines1 => Some(5), + MapArea::Mines2 => Some(6), + MapArea::Ruins1 => Some(7), + MapArea::Ruins2 => Some(8), + MapArea::Ruins3 => Some(9), + MapArea::Dragon => Some(2), + MapArea::DeRolLe => Some(5), + MapArea::VolOpt => Some(7), + MapArea::DarkFalz => Some(9), _ => None } } @@ -385,31 +409,31 @@ impl MapVariantType { #[derive(Debug)] struct MapVariant { - map: MapVariantType, + map: MapArea, mode: MapVariantMode, major: u8, minor: u8, } impl MapVariant { - fn new(map: MapVariantType, mode: MapVariantMode) -> MapVariant { + fn new(map: MapArea, mode: MapVariantMode) -> MapVariant { let major = match map { - MapVariantType::Pioneer2Ep1 => 0, - MapVariantType::Forest1 | MapVariantType::Forest2 => 0, - MapVariantType::Caves1 | MapVariantType::Caves2 | MapVariantType::Caves3 => rand::thread_rng().gen_range(0, 3), - MapVariantType::Mines1 | MapVariantType::Mines2 => rand::thread_rng().gen_range(0, 3), - MapVariantType::Ruins1 | MapVariantType::Ruins2 | MapVariantType::Ruins3 => rand::thread_rng().gen_range(0, 3), - MapVariantType::Dragon | MapVariantType::DeRolLe | MapVariantType::VolOpt | MapVariantType::DarkFalz => 0, + MapArea::Pioneer2Ep1 => 0, + MapArea::Forest1 | MapArea::Forest2 => 0, + MapArea::Caves1 | MapArea::Caves2 | MapArea::Caves3 => rand::thread_rng().gen_range(0, 3), + MapArea::Mines1 | MapArea::Mines2 => rand::thread_rng().gen_range(0, 3), + MapArea::Ruins1 | MapArea::Ruins2 | MapArea::Ruins3 => rand::thread_rng().gen_range(0, 3), + MapArea::Dragon | MapArea::DeRolLe | MapArea::VolOpt | MapArea::DarkFalz => 0, }; let minor = match map { - MapVariantType::Pioneer2Ep1 => 0, - MapVariantType::Forest1 => rand::thread_rng().gen_range(0, 5), - MapVariantType::Forest2 => rand::thread_rng().gen_range(0, 5), - MapVariantType::Caves1 | MapVariantType::Caves2 | MapVariantType::Caves3 => rand::thread_rng().gen_range(0, 2), - MapVariantType::Mines1 | MapVariantType::Mines2 => rand::thread_rng().gen_range(0, 2), - MapVariantType::Ruins1 | MapVariantType::Ruins2 | MapVariantType::Ruins3 => rand::thread_rng().gen_range(0, 2), - MapVariantType::Dragon | MapVariantType::DeRolLe | MapVariantType::VolOpt | MapVariantType::DarkFalz => 0, + MapArea::Pioneer2Ep1 => 0, + MapArea::Forest1 => rand::thread_rng().gen_range(0, 5), + MapArea::Forest2 => rand::thread_rng().gen_range(0, 5), + MapArea::Caves1 | MapArea::Caves2 | MapArea::Caves3 => rand::thread_rng().gen_range(0, 2), + MapArea::Mines1 | MapArea::Mines2 => rand::thread_rng().gen_range(0, 2), + MapArea::Ruins1 | MapArea::Ruins2 | MapArea::Ruins3 => rand::thread_rng().gen_range(0, 2), + MapArea::Dragon | MapArea::DeRolLe | MapArea::VolOpt | MapArea::DarkFalz => 0, }; MapVariant { @@ -422,41 +446,41 @@ impl MapVariant { // TODO: rename to npc_file fn dat_file(&self) -> String { match self.map { - MapVariantType::Pioneer2Ep1 => "data/maps/map_city00_00e.dat".into(), - MapVariantType::Forest1 => format!("data/maps/map_forest01_0{}e.dat", self.minor), - MapVariantType::Forest2 => format!("data/maps/map_forest02_0{}e.dat", self.minor), - MapVariantType::Caves1 => format!("data/maps/map_cave01_0{}_0{}e.dat", self.major, self.minor), - MapVariantType::Caves2 => format!("data/maps/map_cave02_0{}_0{}e.dat", self.major, self.minor), - MapVariantType::Caves3 => format!("data/maps/map_cave03_0{}_0{}e.dat", self.major, self.minor), - MapVariantType::Mines1 => format!("data/maps/map_machine01_0{}_0{}e.dat", self.major, self.minor), - MapVariantType::Mines2 => format!("data/maps/map_machine02_0{}_0{}e.dat", self.major, self.minor), - MapVariantType::Ruins1 => format!("data/maps/map_ancient01_0{}_0{}e.dat", self.major, self.minor), - MapVariantType::Ruins2 => format!("data/maps/map_ancient02_0{}_0{}e.dat", self.major, self.minor), - MapVariantType::Ruins3 => format!("data/maps/map_ancient03_0{}_0{}e.dat", self.major, self.minor), - MapVariantType::Dragon => "data/maps/map_boss01e.dat".into(), - MapVariantType::DeRolLe => "data/maps/map_boss02e.dat".into(), - MapVariantType::VolOpt => "data/maps/map_boss03e.dat".into(), - MapVariantType::DarkFalz => "data/maps/map_boss04e.dat".into(), + MapArea::Pioneer2Ep1 => "data/maps/map_city00_00e.dat".into(), + MapArea::Forest1 => format!("data/maps/map_forest01_0{}e.dat", self.minor), + MapArea::Forest2 => format!("data/maps/map_forest02_0{}e.dat", self.minor), + MapArea::Caves1 => format!("data/maps/map_cave01_0{}_0{}e.dat", self.major, self.minor), + MapArea::Caves2 => format!("data/maps/map_cave02_0{}_0{}e.dat", self.major, self.minor), + MapArea::Caves3 => format!("data/maps/map_cave03_0{}_0{}e.dat", self.major, self.minor), + MapArea::Mines1 => format!("data/maps/map_machine01_0{}_0{}e.dat", self.major, self.minor), + MapArea::Mines2 => format!("data/maps/map_machine02_0{}_0{}e.dat", self.major, self.minor), + MapArea::Ruins1 => format!("data/maps/map_ancient01_0{}_0{}e.dat", self.major, self.minor), + MapArea::Ruins2 => format!("data/maps/map_ancient02_0{}_0{}e.dat", self.major, self.minor), + MapArea::Ruins3 => format!("data/maps/map_ancient03_0{}_0{}e.dat", self.major, self.minor), + MapArea::Dragon => "data/maps/map_boss01e.dat".into(), + MapArea::DeRolLe => "data/maps/map_boss02e.dat".into(), + MapArea::VolOpt => "data/maps/map_boss03e.dat".into(), + MapArea::DarkFalz => "data/maps/map_boss04e.dat".into(), } } fn obj_file(&self) -> String { match self.map { - MapVariantType::Pioneer2Ep1 => "data/maps/map_city00_00e.dat".into(), - MapVariantType::Forest1 => format!("data/maps/map_forest01_0{}o.dat", self.minor), - MapVariantType::Forest2 => format!("data/maps/map_forest02_0{}o.dat", self.minor), - MapVariantType::Caves1 => format!("data/maps/map_cave01_0{}_0{}o.dat", self.major, self.minor), - MapVariantType::Caves2 => format!("data/maps/map_cave02_0{}_0{}o.dat", self.major, self.minor), - MapVariantType::Caves3 => format!("data/maps/map_cave03_0{}_0{}o.dat", self.major, self.minor), - MapVariantType::Mines1 => format!("data/maps/map_machine01_0{}_0{}o.dat", self.major, self.minor), - MapVariantType::Mines2 => format!("data/maps/map_machine02_0{}_0{}o.dat", self.major, self.minor), - MapVariantType::Ruins1 => format!("data/maps/map_ancient01_0{}_0{}o.dat", self.major, self.minor), - MapVariantType::Ruins2 => format!("data/maps/map_ancient02_0{}_0{}o.dat", self.major, self.minor), - MapVariantType::Ruins3 => format!("data/maps/map_ancient03_0{}_0{}o.dat", self.major, self.minor), - MapVariantType::Dragon => "data/maps/map_boss01o.dat".into(), - MapVariantType::DeRolLe => "data/maps/map_boss02o.dat".into(), - MapVariantType::VolOpt => "data/maps/map_boss03o.dat".into(), - MapVariantType::DarkFalz => "data/maps/map_boss04o.dat".into(), + MapArea::Pioneer2Ep1 => "data/maps/map_city00_00e.dat".into(), + MapArea::Forest1 => format!("data/maps/map_forest01_0{}o.dat", self.minor), + MapArea::Forest2 => format!("data/maps/map_forest02_0{}o.dat", self.minor), + MapArea::Caves1 => format!("data/maps/map_cave01_0{}_0{}o.dat", self.major, self.minor), + MapArea::Caves2 => format!("data/maps/map_cave02_0{}_0{}o.dat", self.major, self.minor), + MapArea::Caves3 => format!("data/maps/map_cave03_0{}_0{}o.dat", self.major, self.minor), + MapArea::Mines1 => format!("data/maps/map_machine01_0{}_0{}o.dat", self.major, self.minor), + MapArea::Mines2 => format!("data/maps/map_machine02_0{}_0{}o.dat", self.major, self.minor), + MapArea::Ruins1 => format!("data/maps/map_ancient01_0{}_0{}o.dat", self.major, self.minor), + MapArea::Ruins2 => format!("data/maps/map_ancient02_0{}_0{}o.dat", self.major, self.minor), + MapArea::Ruins3 => format!("data/maps/map_ancient03_0{}_0{}o.dat", self.major, self.minor), + MapArea::Dragon => "data/maps/map_boss01o.dat".into(), + MapArea::DeRolLe => "data/maps/map_boss02o.dat".into(), + MapArea::VolOpt => "data/maps/map_boss03o.dat".into(), + MapArea::DarkFalz => "data/maps/map_boss04o.dat".into(), } } @@ -583,21 +607,21 @@ impl Maps { pub fn new(episode: Episode) -> Maps { let map_variants = match episode { Episode::One => { - [MapVariant::new(MapVariantType::Pioneer2Ep1, MapVariantMode::Online), - MapVariant::new(MapVariantType::Forest1, MapVariantMode::Online), - MapVariant::new(MapVariantType::Forest2, MapVariantMode::Online), - MapVariant::new(MapVariantType::Caves1, MapVariantMode::Online), - MapVariant::new(MapVariantType::Caves2, MapVariantMode::Online), - MapVariant::new(MapVariantType::Caves3, MapVariantMode::Online), - MapVariant::new(MapVariantType::Mines1, MapVariantMode::Online), - MapVariant::new(MapVariantType::Mines2, MapVariantMode::Online), - MapVariant::new(MapVariantType::Ruins1, MapVariantMode::Online), - MapVariant::new(MapVariantType::Ruins2, MapVariantMode::Online), - MapVariant::new(MapVariantType::Ruins3, MapVariantMode::Online), - MapVariant::new(MapVariantType::Dragon, MapVariantMode::Online), - MapVariant::new(MapVariantType::DeRolLe, MapVariantMode::Online), - MapVariant::new(MapVariantType::VolOpt, MapVariantMode::Online), - MapVariant::new(MapVariantType::DarkFalz, MapVariantMode::Online), + [MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Online), + MapVariant::new(MapArea::Forest1, MapVariantMode::Online), + MapVariant::new(MapArea::Forest2, MapVariantMode::Online), + MapVariant::new(MapArea::Caves1, MapVariantMode::Online), + MapVariant::new(MapArea::Caves2, MapVariantMode::Online), + MapVariant::new(MapArea::Caves3, MapVariantMode::Online), + MapVariant::new(MapArea::Mines1, MapVariantMode::Online), + MapVariant::new(MapArea::Mines2, MapVariantMode::Online), + MapVariant::new(MapArea::Ruins1, MapVariantMode::Online), + MapVariant::new(MapArea::Ruins2, MapVariantMode::Online), + MapVariant::new(MapArea::Ruins3, MapVariantMode::Online), + MapVariant::new(MapArea::Dragon, MapVariantMode::Online), + MapVariant::new(MapArea::DeRolLe, MapVariantMode::Online), + MapVariant::new(MapArea::VolOpt, MapVariantMode::Online), + MapVariant::new(MapArea::DarkFalz, MapVariantMode::Online), ] }, _ => panic!() diff --git a/src/ship/room.rs b/src/ship/room.rs index 08e49ad..2695362 100644 --- a/src/ship/room.rs +++ b/src/ship/room.rs @@ -1,6 +1,8 @@ use std::convert::{From, Into, TryFrom, TryInto}; use crate::ship::map::Maps; +use crate::ship::drops::DropTable; +use crate::entity::character::SectionID; #[derive(Debug)] pub enum RoomCreationError { @@ -166,7 +168,7 @@ impl RoomState { difficulty + 0x22 } - pub fn from_create_room(create_room: &libpso::packet::ship::CreateRoom) -> Result { + pub fn from_create_room(create_room: &libpso::packet::ship::CreateRoom, section_id: SectionID) -> Result { if [create_room.battle, create_room.challenge, create_room.single_player].iter().sum::() > 1 { return Err(RoomCreationError::InvalidMode) } diff --git a/src/ship/ship.rs b/src/ship/ship.rs index aa34738..77acc92 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -21,7 +21,7 @@ use crate::entity::account::{UserAccountEntity, UserSettingsEntity, USERFLAG_NEW use crate::entity::character::CharacterEntity; use crate::entity::item::{ItemLocation, ItemEntity}; use crate::login::login::get_login_status; -use crate::ship::location::{ClientLocation, LobbyId, RoomId, AreaType, MAX_ROOMS}; +use crate::ship::location::{ClientLocation, LobbyId, RoomId, RoomLobby, MAX_ROOMS}; use crate::ship::character::{CharacterBytesBuilder, FullCharacterBytesBuilder}; use crate::ship::items; use crate::ship::room; @@ -33,6 +33,8 @@ pub enum ShipError { ClientNotFound(ClientId), NoCharacterInSlot(ClientId, u32), InvalidSlot(ClientId, u32), + TooManyClients, + ClientError, } #[derive(Debug)] @@ -216,14 +218,11 @@ impl ShipServerState { } fn send_player_to_lobby(&mut self, id: ClientId, _pkt: &CharData) -> Result, ShipError> { - self.client_location.add_to_lobby(id, LobbyId(0)).unwrap(); - - let lobby = self.client_location.get_area_by_user(id); - let clients = lobby.clients(); - println!("clients in lobby: {:?}", clients); + let lobby = self.client_location.add_client_to_next_available_lobby(id, LobbyId(0)).map_err(|_| ShipError::TooManyClients)?; + let clients = self.client_location.get_clients_in_lobby(lobby).map_err(|_| ShipError::ClientError)?; let playerinfo = clients.iter() .map(|room_client| { - let client = self.clients.get(&room_client.client_id).ok_or(ShipError::ClientNotFound(id)).unwrap(); + let client = self.clients.get(&room_client.client).ok_or(ShipError::ClientNotFound(id)).unwrap(); let (level, stats) = self.level_table.get_stats_from_exp(client.character.char_class, client.character.exp); let c = CharacterBytesBuilder::new() .character(&client.character) @@ -235,7 +234,7 @@ impl ShipServerState { tag: 0x100, guildcard: client.user.id.0, _unknown1: [0; 5], - client_id: room_client.index as u32, + client_id: room_client.local_client.id() as u32, name: c.name, _unknown2: 2, }, @@ -249,28 +248,21 @@ impl ShipServerState { character: c, } }); - let client_id = clients.iter() - .fold(0, |acc, k| { - if k.client_id == id { - k.index - } - else { - acc - } - }); + let area_client = self.client_location.get_local_client(id).map_err(|_| ShipError::ClientError)?; + let leader = self.client_location.get_lobby_leader(lobby).map_err(|_| ShipError::ClientError)?; let join_lobby = JoinLobby { - client: client_id as u8, - leader: lobby.leader().index as u8, + client: area_client.local_client.id(), + leader: leader.local_client.id(), one: 1, - lobby: lobby.id() as u8, + lobby: lobby.id(), block: 1, event: 0, padding: 0, playerinfo: playerinfo.collect(), }; - let client = self.clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id)).unwrap(); + let client = self.clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; let (level, stats) = self.level_table.get_stats_from_exp(client.character.char_class, client.character.exp); let c = CharacterBytesBuilder::new() .character(&client.character) @@ -279,10 +271,10 @@ impl ShipServerState { .build(); let addto = AddToLobby { flag: 1, - client: client_id as u8, - leader: lobby.leader().index as u8, + client: area_client.local_client.id(), + leader: leader.local_client.id(), one: 1, - lobby: lobby.id() as u8, + lobby: lobby.id(), block: 1, event: 0, padding: 0, @@ -291,7 +283,7 @@ impl ShipServerState { tag: 0x100, guildcard: client.user.id.0, _unknown1: [0; 5], - client_id: client_id as u32, + client_id: area_client.local_client.id() as u32, name: c.name, _unknown2: 2, }, @@ -306,26 +298,40 @@ impl ShipServerState { }, }; - let mut v = Vec::new(); - v.push((id, SendShipPacket::JoinLobby(join_lobby))); - for client in clients { - if client.client_id != id { - v.push((client.client_id, SendShipPacket::AddToLobby(addto.clone())) - )} - } - - Ok(v) + let neighbors = self.client_location.get_client_neighbors(id).unwrap(); + Ok(vec![(id, SendShipPacket::JoinLobby(join_lobby))] + .into_iter() + .chain(neighbors.into_iter() + .map(|c| (c.client, SendShipPacket::AddToLobby(addto.clone())))).collect()) } fn message(&mut self, id: ClientId, msg: &Message) -> Box + Send> { + match &msg.msg { + GameMessage::RequestExp(killmonster) => { + match self.client_location.get_area(id).unwrap() { + RoomLobby::Room(room) => { + let r = self.rooms[room.0].as_ref().unwrap(); + warn!("killed a {:?}", r.maps.enemy_by_id(killmonster.enemy_id as usize).monster); + }, + _ => {} + } + }, + _ => {}, + } + let cmsg = msg.clone(); - Box::new(self.client_location.get_area_by_user(id).clients().iter() - .filter(|client| client.client_id != id) + Box::new(self.client_location.get_client_neighbors(id).unwrap().into_iter() + .filter(move |client| client.client != id) .map(move |client| { - (client.client_id, SendShipPacket::Message(cmsg.clone())) - }).collect::>().into_iter()) + (client.client, SendShipPacket::Message(cmsg.clone())) + })) } + /*fn generate_item_drop(&mut self, id: ClientId, monster: MonsterType) -> Option { + let room = self.rooms[self.client_location.get_area_by_user(id).index]; + let item_drop = room.drop_table.get_drop() + }*/ + fn direct_message(&mut self, id: ClientId, msg: &DirectMessage) -> Box + Send> { let cmsg = msg.clone(); let client = self.clients.get_mut(&id).unwrap(); @@ -346,50 +352,60 @@ impl ShipServerState { class: client.character.char_class.into(), }), }; - Box::new(self.client_location.get_area_by_user(id).clients().iter() - .filter(|client| client.index == cmsg.flag as usize) - .map(|client| { - (client.client_id, SendShipPacket::DirectMessage(out_msg.clone())) - }).collect::>().into_iter()) + let msg_flag = cmsg.flag as u8; + Box::new(self.client_location.get_all_clients_by_client(id).unwrap().into_iter() + .filter(move |client| client.local_client.id() == msg_flag) + .map(move |client| { + (client.client, SendShipPacket::DirectMessage(out_msg.clone())) + })) }, + /*GameMessage::RequestItem(req_item) => { + let item = self.generate_item_drop(id); + Box::new(vec![(id, SendShipPacket::Message(Message::new(GameMessage::ItemDrop(ItemDrop { + client: req_item.client, + target: req_item.target, + area: req_item.area, + variety: 0, + unknown: 0, + x: req_item.x, + z: req_item.z, + unknown2: 0, + item_bytes: item[0..12].try_into().unwrap(), + item_id: 0, + item_bytes2: item[12..16].try_into().unwrap(), + unknown3: 0, + }))))].into_iter()) + },*/ _ => { - Box::new(self.client_location.get_area_by_user(id).clients().iter() - .filter(|client| client.index == cmsg.flag as usize) - .map(|client| { - (client.client_id, SendShipPacket::DirectMessage(cmsg.clone())) - }).collect::>().into_iter()) + let msg_flag = cmsg.flag as u8; + Box::new(self.client_location.get_all_clients_by_client(id).unwrap().into_iter() + .filter(move |client| client.local_client.id() == msg_flag) + .map(move |client| { + (client.client, SendShipPacket::DirectMessage(cmsg.clone())) + })) }, } - } fn player_chat(&mut self, id: ClientId, msg: &PlayerChat) -> Result + Send>, ShipError> { let client = self.clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; let cmsg = PlayerChat::new(client.user.id.0, msg.message.clone()); - Ok(Box::new(self.client_location.get_area_by_user(id).clients().iter() + Ok(Box::new(self.client_location.get_all_clients_by_client(id).unwrap().into_iter() .map(move |client| { - (client.client_id, SendShipPacket::PlayerChat(cmsg.clone())) - }).collect::>().into_iter())) + (client.client, SendShipPacket::PlayerChat(cmsg.clone())) + }))) } fn create_room(&mut self, id: ClientId, create_room: &CreateRoom) -> Box + Send> { - let area = self.client_location.get_area_by_user(id); - let area_client = area.clients().into_iter().filter(|client| { - client.client_id == id - }).next().unwrap(); - let other_clients = area.clients().into_iter() - .filter(move |c| { - c.client_id != id - }); - let room_id = match self.client_location.new_room(id) { - Ok(room_id) => room_id, - Err(err) => return Box::new(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new(format!("could not create room: {:?}", err))))].into_iter()) - }; - - let room = room::RoomState::from_create_room(create_room).unwrap(); + let area = self.client_location.get_area(id).unwrap(); + let area_client = self.client_location.get_local_client(id).unwrap(); + let neighbors = self.client_location.get_client_neighbors(id).unwrap(); + let room_id = self.client_location.create_new_room(id).unwrap(); let client = self.clients.get_mut(&id).unwrap();//.ok_or(ShipError::ClientNotFound(id)).unwrap(); + let room = room::RoomState::from_create_room(create_room, client.character.section_id).unwrap(); + let players = [PlayerHeader { tag: 0x00010000, guildcard: client.user.id.0, @@ -419,16 +435,22 @@ impl ShipServerState { }; self.rooms[room_id.0] = Some(room); - let leader = area.leader(); - Box::new(vec![(id, SendShipPacket::JoinRoom(join_room))].into_iter().chain(other_clients.map(move |c| { - (c.client_id, SendShipPacket::LeaveLobby(LeaveLobby::new(area_client.index as u8, leader.index as u8))) - }))) + let leader = self.client_location.get_area_leader(area).unwrap(); + Box::new(vec![(id, SendShipPacket::JoinRoom(join_room))] + .into_iter() + .chain(neighbors + .into_iter() + .map(move |c| { + (c.client, SendShipPacket::LeaveLobby(LeaveLobby::new(area_client.local_client.id(), leader.local_client.id()))) + }))) } fn room_name_request(&mut self, id: ClientId) -> Box + Send> { - let area = self.client_location.get_area_by_user(id); - let room_state = self.rooms[area.id()].as_ref().unwrap(); - Box::new(vec![(id, SendShipPacket::RoomNameResponse(RoomNameResponse {name: room_state.name.clone()}))].into_iter()) + let area = self.client_location.get_area(id).unwrap(); + match area { + RoomLobby::Room(room) => Box::new(vec![(id, SendShipPacket::RoomNameResponse(RoomNameResponse {name: self.rooms[room.0].as_ref().unwrap().name.clone()}))].into_iter()), + RoomLobby::Lobby(_) => panic!() + } } fn update_config(&mut self, id: ClientId, update_config: &UpdateConfig) -> Box + Send> { @@ -439,21 +461,17 @@ impl ShipServerState { } fn request_infoboard(&mut self, id: ClientId, request_infoboard: &ViewInfoboardRequest) -> Box + Send> { - let lobby = self.client_location.get_area_by_user(id); - let clients = lobby.clients(); - let r = clients - .iter() - .filter(|c| c.client_id != id) + let clients = self.client_location.get_client_neighbors(id).unwrap(); + let r = clients.iter() .filter_map(|c| { - self.clients.get(&c.client_id) + self.clients.get(&c.client) }) - .map(|c| { + .map(|client| { InfoboardResponse { - name: libpso::utf8_to_utf16_array!(c.character.name, 16), - message: c.character.info_board.as_bytes(), + name: libpso::utf8_to_utf16_array!(client.character.name, 16), + message: client.character.info_board.as_bytes(), } - }) - .collect(); + }).collect(); Box::new(vec![(id, SendShipPacket::ViewInfoboardResponse(ViewInfoboardResponse {response: r}))].into_iter()) } @@ -473,7 +491,7 @@ impl ShipServerState { menu_id: ROOM_MENU_ID, item_id: i as u32, difficulty: room.get_difficulty_for_room_list(), - players: self.client_location.get_client_count_in_room(RoomId(i)), // TODO + players: self.client_location.get_clients_in_room(RoomId(i)).unwrap().len() as u8, name: libpso::utf8_to_utf16_array!(room.name, 16), episode: room.get_episode_for_room_list(), flags: room.get_flags_for_room_list(), @@ -566,25 +584,24 @@ impl ServerState for ShipServerState { } fn on_disconnect(&mut self, id: ClientId) -> Vec<(ClientId, SendShipPacket)> { - let mut area = self.client_location.get_area_by_user(id); - let client = area.clients().into_iter().filter(|client| { - client.client_id == id - //}).collect::>()[0]; - }).next().unwrap(); - let other_clients = area.clients().into_iter().filter(|client| { - client.client_id != id - }); - //self.client_location.remove_from_location(id); - area.remove(id); - let leader = area.leader(); - - let pkt = match area.area_type { - AreaType::Lobby => SendShipPacket::LeaveLobby(LeaveLobby::new(client.index as u8, leader.index as u8)), - AreaType::Room => SendShipPacket::LeaveRoom(LeaveRoom::new(client.index as u8, leader.index as u8)), + let client = self.client_location.get_local_client(id).unwrap(); + let neighbors = self.client_location.get_client_neighbors(id).unwrap(); + + let pkt = match self.client_location.get_area(id).unwrap() { + RoomLobby::Room(room) => { + let leader = self.client_location.get_room_leader(room).unwrap(); + SendShipPacket::LeaveRoom(LeaveRoom::new(client.local_client.id(), leader.local_client.id())) + }, + RoomLobby::Lobby(lobby) => { + let leader = self.client_location.get_lobby_leader(lobby).unwrap(); + SendShipPacket::LeaveLobby(LeaveLobby::new(client.local_client.id(), leader.local_client.id())) + } }; - other_clients.map(|client| { - (client.client_id, pkt.clone()) + self.client_location.remove_client_from_area(id); + + neighbors.into_iter().map(|n| { + (n.client, pkt.clone()) }).collect() } }