Merge pull request 'misc refactorings to make things easier' (#100) from refactor_some_things into master
This commit is contained in:
commit
806aab7d21
@ -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 {
|
||||
|
@ -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<BoxRareRate> {
|
||||
match map_area.area_value().unwrap() {
|
||||
fn rates_by_area(&self, map_area: &MapArea) -> &Vec<BoxRareRate> {
|
||||
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<R: Rng>(&self, map_area: &MapVariantType, rng: &mut R) -> Option<ItemDropType> {
|
||||
fn rare_drop<R: Rng>(&self, map_area: &MapArea, rng: &mut R) -> Option<ItemDropType> {
|
||||
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<R: Rng>(&self, map_area: &MapVariantType, rng: &mut R) -> Option<ItemDropType> {
|
||||
fn random_box_drop<R: Rng>(&self, map_area: &MapArea, rng: &mut R) -> Option<ItemDropType> {
|
||||
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<R: Rng>(&self, fixed_drop: FixedBoxDropType, map_area: &MapVariantType, rng: &mut R) -> Option<ItemDropType> {
|
||||
fn fixed_box_drop<R: Rng>(&self, fixed_drop: FixedBoxDropType, map_area: &MapArea, rng: &mut R) -> Option<ItemDropType> {
|
||||
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<R: Rng>(&self, map_area: &MapVariantType, object: &MapObject, rng: &mut R) -> Option<ItemDropType> {
|
||||
pub fn get_drop<R: Rng>(&self, map_area: &MapArea, object: &MapObject, rng: &mut R) -> Option<ItemDropType> {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
@ -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<R: Rng>(&self, area_map: &MapVariantType, rng: &mut R) -> ArmorType {
|
||||
fn armor_type<R: Rng>(&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<R: Rng>(&self, area_map: &MapVariantType, rng: &mut R) -> usize {
|
||||
pub fn slots<R: Rng>(&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<R: Rng>(&self, area_map: &MapVariantType, rng: &mut R) -> Option<ItemDropType> {
|
||||
pub fn get_drop<R: Rng>(&self, area_map: &MapArea, rng: &mut R) -> Option<ItemDropType> {
|
||||
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,
|
||||
|
@ -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<R: Rng>(&self, area_map: &MapVariantType, rng: &mut R) -> ShieldType {
|
||||
fn shield_type<R: Rng>(&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<R: Rng>(&self, area_map: &MapVariantType, rng: &mut R) -> Option<ItemDropType> {
|
||||
pub fn get_drop<R: Rng>(&self, area_map: &MapArea, rng: &mut R) -> Option<ItemDropType> {
|
||||
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,
|
||||
|
@ -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<R: Rng>(&self, area_map: &MapVariantType, rng: &mut R) -> Option<(UnitType, Option<UnitModifier>)> {
|
||||
fn unit_type_and_modifier<R: Rng>(&self, area_map: &MapArea, rng: &mut R) -> Option<(UnitType, Option<UnitModifier>)> {
|
||||
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<R: Rng>(&self, area_map: &MapVariantType, rng: &mut R) -> Option<ItemDropType> {
|
||||
pub fn get_drop<R: Rng>(&self, area_map: &MapArea, rng: &mut R) -> Option<ItemDropType> {
|
||||
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,
|
||||
|
@ -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<R: Rng>(&self, map_area: &MapVariantType, rng: &mut R) -> [Option<WeaponAttribute>; 3] {
|
||||
fn generate_attributes<R: Rng>(&self, map_area: &MapArea, rng: &mut R) -> [Option<WeaponAttribute>; 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<R: Rng>(&self, map_area: &MapVariantType, rng: &mut R) -> [Option<WeaponAttribute>; 3] {
|
||||
pub fn generate_rare_attributes<R: Rng>(&self, map_area: &MapArea, rng: &mut R) -> [Option<WeaponAttribute>; 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<R: Rng>(&self, map_area: &MapVariantType, rng: &mut R) -> Option<WeaponSpecial> {
|
||||
fn get_special<R: Rng>(&self, map_area: &MapArea, rng: &mut R) -> Option<WeaponSpecial> {
|
||||
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<WeaponDropType, WeaponRatio> {
|
||||
fn get_possible_weapon_types(&self, map_area: &MapArea) -> BTreeMap<WeaponDropType, WeaponRatio> {
|
||||
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<R: Rng>(&self, possible_weapon_types: &BTreeMap<WeaponDropType, WeaponRatio>, map_area: &MapVariantType, rng: &mut R) -> WeaponDropType {
|
||||
fn weapon_type<R: Rng>(&self, possible_weapon_types: &BTreeMap<WeaponDropType, WeaponRatio>, 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<R: Rng>(&self, ratio: &WeaponRatio, map_area: &MapVariantType, rng: &mut R) -> usize {
|
||||
fn get_grind<R: Rng>(&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<R: Rng>(&self, map_area: &MapVariantType, rng: &mut R) -> Option<ItemDropType> {
|
||||
pub fn get_drop<R: Rng>(&self, map_area: &MapArea, rng: &mut R) -> Option<ItemDropType> {
|
||||
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,
|
||||
|
@ -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<R: Rng + SeedableRng> {
|
||||
pub struct DropTable<R: Rng + SeedableRng> {
|
||||
monster_stats: HashMap<MonsterType, MonsterDropStats>,
|
||||
rare_table: RareDropTable,
|
||||
weapon_table: GenericWeaponTable,
|
||||
@ -112,7 +112,7 @@ struct DropTable<R: Rng + SeedableRng> {
|
||||
|
||||
|
||||
impl<R: Rng + SeedableRng> DropTable<R> {
|
||||
fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> DropTable<R> {
|
||||
pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> DropTable<R> {
|
||||
let mut path = PathBuf::from("data/drops/");
|
||||
path.push(episode.to_string());
|
||||
path.push(difficulty.to_string().to_lowercase());
|
||||
@ -140,7 +140,7 @@ impl<R: Rng + SeedableRng> DropTable<R> {
|
||||
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<ItemDropType> {
|
||||
fn generate_typed_drop(&mut self, map_area: &MapArea, monster: &MonsterDropStats) -> Option<ItemDropType> {
|
||||
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<R: Rng + SeedableRng> DropTable<R> {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_drop(&mut self, map_area: &MapVariantType, monster: &MonsterType) -> Option<ItemDropType> {
|
||||
pub fn get_drop(&mut self, map_area: &MapArea, monster: &MonsterType) -> Option<ItemDropType> {
|
||||
let monster_stat = *self.monster_stats.get(monster)?;
|
||||
|
||||
let drop_anything = self.rng.gen_range(0, 100);
|
||||
@ -178,7 +178,7 @@ impl<R: Rng + SeedableRng> DropTable<R> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_box_drop(&mut self, map_area: &MapVariantType, object: &MapObject) -> Option<ItemDropType> {
|
||||
pub fn get_box_drop(&mut self, map_area: &MapArea, object: &MapObject) -> Option<ItemDropType> {
|
||||
self.box_table.get_drop(map_area, object, &mut self.rng)
|
||||
}
|
||||
}
|
||||
|
@ -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<R: Rng>(&self, map_area: &MapVariantType, item: RareDropItem, rng: &mut R) -> ItemDropType {
|
||||
pub fn apply_item_stats<R: Rng>(&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<R: Rng>(&self, map_area: &MapVariantType, monster: &MonsterType, rng: &mut R) -> Option<ItemDropType> {
|
||||
pub fn get_drop<R: Rng>(&self, map_area: &MapArea, monster: &MonsterType, rng: &mut R) -> Option<ItemDropType> {
|
||||
self.rates.get(monster)
|
||||
.and_then(|drop_rates| {
|
||||
drop_rates.iter()
|
||||
|
@ -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<Technique, TechniqueRateStat> {
|
||||
match map_area.area_value().unwrap() {
|
||||
fn get_by_area<'a>(&'a self, map_area: &MapArea) -> &'a BTreeMap<Technique, TechniqueRateStat> {
|
||||
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<R: Rng>(&self, map_area: &MapVariantType, rng: &mut R) -> Option<ItemDropType> {
|
||||
pub fn get_drop<R: Rng>(&self, map_area: &MapArea, rng: &mut R) -> Option<ItemDropType> {
|
||||
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(
|
||||
|
@ -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<ToolRateType, u32> {
|
||||
match map_area.area_value().unwrap() {
|
||||
fn get_by_area<'a>(&'a self, map_area: &MapArea) -> &'a BTreeMap<ToolRateType, u32> {
|
||||
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<R: Rng>(&self, map_area: &MapVariantType, rng: &mut R) -> Option<ItemDropType> {
|
||||
pub fn get_drop<R: Rng>(&self, map_area: &MapArea, rng: &mut R) -> Option<ItemDropType> {
|
||||
let tool_rates = self.rates.get_by_area(map_area).iter();
|
||||
let tool_weights = WeightedIndex::new(tool_rates.clone().map(|(_, weights)| weights)).unwrap();
|
||||
|
||||
|
@ -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<ItemEntity>),
|
||||
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<ItemEntity>) -> Vec<StackedItem> {
|
||||
fn stack_items(items: Vec<ItemEntity>) -> Vec<ItemInstance> {
|
||||
let mut stacks = HashMap::new();
|
||||
|
||||
for item in items {
|
||||
@ -113,11 +119,11 @@ fn stack_items(items: Vec<ItemEntity>) -> Vec<StackedItem> {
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
pub enum AreaType {
|
||||
Room,
|
||||
Lobby,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct InnerClientArea<const N: usize> {
|
||||
clients: [Option<AreaClient>; N],
|
||||
}
|
||||
|
||||
impl<const N: usize> InnerClientArea<{N}> {
|
||||
pub fn new() -> InnerClientArea<{N}> {
|
||||
let mut clients: [std::mem::MaybeUninit<Option<AreaClient>>; 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<AreaClient>; N]).read()}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn add(&mut self, id: ClientId) -> Option<usize> {
|
||||
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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct LobbyId(pub usize);
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct RoomId(pub usize);
|
||||
|
||||
pub type Lobby = InnerClientArea<12>;
|
||||
pub type Room = InnerClientArea<4>;
|
||||
|
||||
trait ClientArea {
|
||||
fn clients(&self) -> std::slice::Iter<'_, Option<AreaClient>>;
|
||||
fn remove(&mut self, id: ClientId) -> bool;
|
||||
}
|
||||
|
||||
|
||||
impl ClientArea for Lobby {
|
||||
fn clients(& self) -> std::slice::Iter<'_, Option<AreaClient>> {
|
||||
self.clients.iter()
|
||||
}
|
||||
|
||||
fn remove(&mut self, id: ClientId) -> bool {
|
||||
self.remove(id)
|
||||
impl LobbyId {
|
||||
pub fn id(&self) -> u8 {
|
||||
self.0 as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ClientArea for Room {
|
||||
fn clients(& self) -> std::slice::Iter<'_, Option<AreaClient>> {
|
||||
self.clients.iter()
|
||||
}
|
||||
|
||||
fn remove(&mut self, id: ClientId) -> bool {
|
||||
self.remove(id)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum AreaType {
|
||||
Lobby,
|
||||
Room,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ClientAtLocation {
|
||||
pub client_id: ClientId,
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
pub struct Area {
|
||||
pub area_type: AreaType,
|
||||
area: Arc<RwLock<dyn ClientArea>>,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl Area {
|
||||
fn new(area_type: AreaType, area: Arc<RwLock<dyn ClientArea>>, index: usize) -> Area {
|
||||
Area {
|
||||
area_type: area_type,
|
||||
area: area,
|
||||
index: index,
|
||||
}
|
||||
}
|
||||
pub fn clients(&self) -> Vec<ClientAtLocation> {
|
||||
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)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum CreateRoomError {
|
||||
NoOpenSlots,
|
||||
ClientInAreaAlready,
|
||||
JoinError,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum JoinRoomError {
|
||||
RoomDoesNotExist,
|
||||
RoomFull,
|
||||
ClientInAreaAlready,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum JoinLobbyError {
|
||||
LobbyDoesNotExist,
|
||||
LobbyFull,
|
||||
ClientInAreaAlready,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct LocalClientId(usize);
|
||||
|
||||
impl LocalClientId {
|
||||
pub fn id(&self) -> u8 {
|
||||
self.0 as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct AreaClient {
|
||||
pub client: ClientId,
|
||||
pub local_client: LocalClientId,
|
||||
time_join: SystemTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
struct Lobby([Option<AreaClient>; 12]);
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
struct Room([Option<AreaClient>; 4]);
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum RoomLobby {
|
||||
Room(RoomId),
|
||||
Lobby(LobbyId),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum GetAreaError {
|
||||
InvalidClient,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ClientRemovalError {
|
||||
ClientNotInArea,
|
||||
InvalidArea,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum GetClientsError {
|
||||
InvalidClient,
|
||||
InvalidArea,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum GetNeighborError {
|
||||
InvalidClient,
|
||||
InvalidArea,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum GetLeaderError {
|
||||
InvalidClient,
|
||||
InvalidArea,
|
||||
NoClientInArea,
|
||||
}
|
||||
|
||||
pub struct ClientLocation {
|
||||
lobbies: [Arc<RwLock<Lobby>>; 15],
|
||||
rooms: [Option<Arc<RwLock<Room>>>; MAX_ROOMS],
|
||||
lobbies: [Lobby; 15],
|
||||
rooms: [Option<Room>; MAX_ROOMS],
|
||||
client_location: HashMap<ClientId, RoomLobby>,
|
||||
}
|
||||
|
||||
impl ClientLocation {
|
||||
pub fn new() -> ClientLocation {
|
||||
ClientLocation {
|
||||
//lobbies: [Arc::new(RwLock::new(Lobby::new())); 15],
|
||||
lobbies: crate::init_array!(Arc<RwLock<Lobby>>, 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<E>(&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_to_lobby(&mut self, id: ClientId, lobby: LobbyId) -> Result<usize, JoinLobbyError> {
|
||||
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 new_room(&mut self, id: ClientId) -> Result<RoomId, CreateRoomError> {
|
||||
let (room_id, empty_room) = self.rooms.iter_mut()
|
||||
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(CreateRoomError::NoOpenSlots)?;
|
||||
|
||||
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_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_room(&mut self, id: ClientId, room: RoomId) -> Result<usize, JoinRoomError> {
|
||||
self.err_if_client_is_in_area(id, JoinRoomError::ClientInAreaAlready)?;
|
||||
self.rooms.get_mut(room.0)
|
||||
|
||||
pub fn add_client_to_next_available_lobby(&mut self, id: ClientId, lobby: LobbyId) -> Result<LobbyId, JoinLobbyError> {
|
||||
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 create_new_room(&mut self, id: ClientId) -> Result<RoomId, CreateRoomError> {
|
||||
let (index, empty_slot) = self.rooms.iter_mut()
|
||||
.enumerate()
|
||||
.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)?;
|
||||
|
||||
Ok(RoomId(index))
|
||||
}
|
||||
|
||||
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_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_all_clients_by_client(&self, id: ClientId) -> Result<Vec<AreaClient>, 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_client_neighbors(&self, id: ClientId) -> Result<Vec<AreaClient>, 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())
|
||||
}
|
||||
}
|
||||
|
||||
panic!("client is not in a room/lobby")
|
||||
}
|
||||
|
||||
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);
|
||||
pub fn get_room_leader(&self, room: RoomId) -> Result<AreaClient, GetLeaderError> {
|
||||
let mut r = self.rooms[room.0]
|
||||
.as_ref()
|
||||
.ok_or(GetLeaderError::InvalidArea)?
|
||||
.0.iter().flat_map(|k| k)
|
||||
.collect::<Vec<_>>();
|
||||
r.sort_by_key(|k| k.time_join);
|
||||
let c = r.get(0).ok_or(GetLeaderError::NoClientInArea)?;
|
||||
Ok(**c)
|
||||
}
|
||||
|
||||
if in_lobby {
|
||||
return;
|
||||
pub fn get_lobby_leader(&self, lobby: LobbyId) -> Result<AreaClient, GetLeaderError> {
|
||||
let mut l = self.lobbies[lobby.0]
|
||||
.0.iter().flat_map(|k| k)
|
||||
.collect::<Vec<_>>();
|
||||
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<AreaClient, GetLeaderError> {
|
||||
match roomlobby {
|
||||
RoomLobby::Room(room) => {
|
||||
self.get_room_leader(room)
|
||||
},
|
||||
RoomLobby::Lobby(lobby) => {
|
||||
self.get_lobby_leader(lobby)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
pub fn get_leader_by_client(&self, id: ClientId) -> Result<AreaClient, GetLeaderError> {
|
||||
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<Vec<AreaClient>, 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<Vec<AreaClient>, 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<AreaClient, GetClientsError> {
|
||||
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<RoomLobby, GetAreaError> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#[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<_>>() == vec![
|
||||
(ClientId(12), LocalClientId(0)),
|
||||
(ClientId(14), LocalClientId(1)),
|
||||
]);
|
||||
}
|
||||
|
||||
#[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));
|
||||
}
|
||||
|
||||
#[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<_>>() == 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<_>>() == 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<_>>() == 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<_>>() == 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<_>>() == 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<_>>() == vec![
|
||||
(ClientId(12), LocalClientId(0)),
|
||||
(ClientId(93), LocalClientId(1)),
|
||||
(ClientId(23), LocalClientId(2)),
|
||||
]);
|
||||
}
|
||||
|
||||
#[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))));
|
||||
}
|
||||
}
|
||||
|
180
src/ship/map.rs
180
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<u32> {
|
||||
pub enum MapAreaError {
|
||||
UnknownMapArea(u32),
|
||||
}
|
||||
|
||||
impl MapArea {
|
||||
pub fn from_value(episode: Episode, area: u32) -> Result<MapArea, MapAreaError> {
|
||||
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<u32> {
|
||||
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!()
|
||||
|
@ -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<RoomState, RoomCreationError> {
|
||||
pub fn from_create_room(create_room: &libpso::packet::ship::CreateRoom, section_id: SectionID) -> Result<RoomState, RoomCreationError> {
|
||||
if [create_room.battle, create_room.challenge, create_room.single_player].iter().sum::<u8>() > 1 {
|
||||
return Err(RoomCreationError::InvalidMode)
|
||||
}
|
||||
|
219
src/ship/ship.rs
219
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<EG: EntityGateway> ShipServerState<EG> {
|
||||
}
|
||||
|
||||
fn send_player_to_lobby(&mut self, id: ClientId, _pkt: &CharData) -> Result<Vec<(ClientId, SendShipPacket)>, 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<EG: EntityGateway> ShipServerState<EG> {
|
||||
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<EG: EntityGateway> ShipServerState<EG> {
|
||||
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<EG: EntityGateway> ShipServerState<EG> {
|
||||
.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<EG: EntityGateway> ShipServerState<EG> {
|
||||
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<EG: EntityGateway> ShipServerState<EG> {
|
||||
},
|
||||
};
|
||||
|
||||
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<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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::<Vec<_>>().into_iter())
|
||||
(client.client, SendShipPacket::Message(cmsg.clone()))
|
||||
}))
|
||||
}
|
||||
|
||||
/*fn generate_item_drop(&mut self, id: ClientId, monster: MonsterType) -> Option<ActiveItem> {
|
||||
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<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send> {
|
||||
let cmsg = msg.clone();
|
||||
let client = self.clients.get_mut(&id).unwrap();
|
||||
@ -346,50 +352,60 @@ impl<EG: EntityGateway> ShipServerState<EG> {
|
||||
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::<Vec<_>>().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::<Vec<_>>().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<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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::<Vec<_>>().into_iter()))
|
||||
(client.client, SendShipPacket::PlayerChat(cmsg.clone()))
|
||||
})))
|
||||
}
|
||||
|
||||
fn create_room(&mut self, id: ClientId, create_room: &CreateRoom) -> Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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<EG: EntityGateway> ShipServerState<EG> {
|
||||
};
|
||||
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<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send> {
|
||||
@ -439,21 +461,17 @@ impl<EG: EntityGateway> ShipServerState<EG> {
|
||||
}
|
||||
|
||||
fn request_infoboard(&mut self, id: ClientId, request_infoboard: &ViewInfoboardRequest) -> Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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<EG: EntityGateway> ShipServerState<EG> {
|
||||
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<EG: EntityGateway> ServerState for ShipServerState<EG> {
|
||||
}
|
||||
|
||||
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::<Vec<_>>()[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 client = self.client_location.get_local_client(id).unwrap();
|
||||
let neighbors = self.client_location.get_client_neighbors(id).unwrap();
|
||||
|
||||
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 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()
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user