diff --git a/Cargo.toml b/Cargo.toml index da84f18..1537cfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,4 +28,5 @@ derive_more = { version = "0.99.3", features = ["display"]} thiserror = "1.0.15" ages-prs = "0.1" async-trait = "0.1.31" +lazy_static = "1.4.0" diff --git a/data/item_stats/mag_stats.toml b/data/item_stats/mag_stats.toml index dae1905..04e93e1 100644 --- a/data/item_stats/mag_stats.toml +++ b/data/item_stats/mag_stats.toml @@ -6,96 +6,124 @@ feed_table = 1 [Mitra] feed_table = 3 +photon_blast = "Pilla" [Surya] feed_table = 3 +photon_blast = "Leilla" [Vayu] feed_table = 4 +photon_blast = "MyllaYoulla" [Varaha] feed_table = 4 +photon_blast = "Leilla" [Kama] feed_table = 4 +photon_blast = "Pilla" [Ushasu] feed_table = 4 +photon_blast = "Leilla" [Apsaras] feed_table = 4 +photon_blast = "Estlla" [Kumara] feed_table = 4 +photon_blast = "Leilla" [Kaitabha] feed_table = 4 +photon_blast = "MyllaYoulla" [Tapas] feed_table = 3 +photon_blast = "MyllaYoulla" [Bhirava] feed_table = 4 +photon_blast = "Pilla" [Kalki] feed_table = 1 +photon_blast = "Estlla" [Rudra] feed_table = 2 +photon_blast = "Leilla" [Marutah] feed_table = 2 +photon_blast = "Pilla" [Yaksa] feed_table = 5 +photon_blast = "Leilla" [Sita] feed_table = 5 +photon_blast = "Pilla" [Garuda] feed_table = 5 +photon_blast = "Pilla" [Nandin] feed_table = 5 +photon_blast = "Estlla" [Ashvinau] feed_table = 2 +photon_blast = "Pilla" [Ribhava] feed_table = 5 [Soma] feed_table = 5 +photon_blast = "Estlla" [Ila] feed_table = 5 +photon_blast = "MyllaYoulla" [Durga] feed_table = 5 +photon_blast = "Estlla" [Vritra] feed_table = 1 +photon_blast = "Golla" [Namuci] feed_table = 2 +photon_blast = "MyllaYoulla" [Sumba] feed_table = 2 +photon_blast = "Leilla" [Naga] feed_table = 6 +photon_blast = "MyllaYoulla" [Pitri] feed_table = 7 [Kabanda] feed_table = 6 +photon_blast = "MyllaYoulla" [Ravana] feed_table = 6 [Marica] feed_table = 6 +photon_blast = "Pilla" [Soniti] feed_table = 7 @@ -105,15 +133,19 @@ feed_table = 7 [Andhaka] feed_table = 6 +photon_blast = "Estlla" [Bana] feed_table = 6 +photon_blast = "Estlla" [Naraka] feed_table = 6 +photon_blast = "Leilla" [Madhu] feed_table = 6 +photon_blast = "MyllaYoulla" [Churel] feed_table = 7 diff --git a/src/entity/character.rs b/src/entity/character.rs index fc87569..4fc6383 100644 --- a/src/entity/character.rs +++ b/src/entity/character.rs @@ -7,7 +7,7 @@ use libpso::character::character::{DEFAULT_PALETTE_CONFIG, DEFAULT_TECH_MENU}; use crate::entity::item::tech::Technique; use crate::entity::account::UserAccountId; -#[derive(Copy, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub enum CharacterClass { HUmar, HUnewearl, @@ -64,7 +64,7 @@ impl Into for CharacterClass { } -#[derive(Copy, Clone, Hash, PartialEq, Eq, derive_more::Display)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, derive_more::Display)] pub enum SectionID { Viridia, Greenill, diff --git a/src/entity/gateway/entitygateway.rs b/src/entity/gateway/entitygateway.rs index c6324e1..9cf4d4d 100644 --- a/src/entity/gateway/entitygateway.rs +++ b/src/entity/gateway/entitygateway.rs @@ -60,6 +60,18 @@ pub trait EntityGateway: Send + Sync + Clone { unimplemented!(); } + async fn feed_mag(&mut self, _mag_item_id: &ItemEntityId, _tool_item_id: &ItemEntityId) { + unimplemented!(); + } + + async fn change_mag_owner(&mut self, _mag_item_id: &ItemEntityId, _character: &CharacterEntity) { + unimplemented!(); + } + + async fn use_mag_cell(&mut self, _mag_item_id: &ItemEntityId, _mag_cell_id: &ItemEntityId) { + unimplemented!(); + } + async fn get_items_by_character(&self, _char: &CharacterEntity) -> Vec { unimplemented!(); } diff --git a/src/entity/gateway/inmemory.rs b/src/entity/gateway/inmemory.rs index 5959a39..5620cb6 100644 --- a/src/entity/gateway/inmemory.rs +++ b/src/entity/gateway/inmemory.rs @@ -1,4 +1,5 @@ use std::collections::BTreeMap; +use std::convert::TryInto; use crate::entity::account::*; use crate::entity::character::*; @@ -13,6 +14,7 @@ pub struct InMemoryGateway { user_settings: Arc>>, characters: Arc>>, items: Arc>>, + mag_modifiers: Arc>>>, } impl InMemoryGateway { @@ -22,6 +24,7 @@ impl InMemoryGateway { user_settings: Arc::new(Mutex::new(BTreeMap::new())), characters: Arc::new(Mutex::new(BTreeMap::new())), items: Arc::new(Mutex::new(BTreeMap::new())), + mag_modifiers: Arc::new(Mutex::new(BTreeMap::new())), } } } @@ -165,6 +168,29 @@ impl EntityGateway for InMemoryGateway { }); } + async fn feed_mag(&mut self, mag_item_id: &ItemEntityId, tool_item_id: &ItemEntityId) { + self.mag_modifiers.lock().unwrap() + .entry(*mag_item_id) + .or_insert(Vec::new()) + .push(mag::MagModifier::FeedMag { + food: *tool_item_id + }); + } + + async fn change_mag_owner(&mut self, mag_item_id: &ItemEntityId, character: &CharacterEntity) { + self.mag_modifiers.lock().unwrap() + .entry(*mag_item_id) + .or_insert(Vec::new()) + .push(mag::MagModifier::OwnerChange(character.char_class, character.section_id)); + } + + async fn use_mag_cell(&mut self, mag_item_id: &ItemEntityId, mag_cell_id: &ItemEntityId) { + self.mag_modifiers.lock().unwrap() + .entry(*mag_item_id) + .or_insert(Vec::new()) + .push(mag::MagModifier::MagCell(mag_cell_id.clone())); + } + async fn get_items_by_character(&self, character: &CharacterEntity) -> Vec { let items = self.items.lock().unwrap(); items @@ -179,6 +205,41 @@ impl EntityGateway for InMemoryGateway { .map(|(_, k)| { k.clone() }) + .map(|mut item| { + item.item = match item.item { + ItemDetail::Mag(mut mag) => { + self.mag_modifiers.lock().unwrap().get(&item.id).map(|mag_modifiers| { + for mag_modifier in mag_modifiers.iter() { + match mag_modifier { + mag::MagModifier::FeedMag {food} => { + items.get(&food).map(|mag_feed| { + match mag_feed.item { + ItemDetail::Tool(mag_feed) => mag.feed(mag_feed.tool), + _ => {} + } + }); + }, + mag::MagModifier::OwnerChange(class, section_id) => { + mag.change_owner(*class, *section_id) + }, + mag::MagModifier::MagCell(mag_cell_id) => { + items.get(&mag_cell_id).map(|mag_cell| { + match mag_cell.item { + ItemDetail::Tool(mag_cell) => mag.apply_mag_cell(mag_cell.tool.try_into().unwrap()), + _ => {} + } + }); + }, + _ => {} + } + } + }); + ItemDetail::Mag(mag) + } + _ => item.item + }; + item + }) .collect() } } diff --git a/src/entity/item/mag.rs b/src/entity/item/mag.rs index 130bd2b..80a95b7 100644 --- a/src/entity/item/mag.rs +++ b/src/entity/item/mag.rs @@ -1,5 +1,62 @@ +use std::collections::HashMap; use serde::{Serialize, Deserialize}; +use crate::entity::item::tool::ToolType; +use crate::entity::character::{CharacterClass, SectionID}; use crate::entity::item::ItemEntityId; +use std::io::Read; + +use std::cmp::Ordering::{Less, Greater, Equal}; + +#[derive(Debug, Deserialize)] +struct MagStats { + feed_table: usize, + photon_blast: Option +} + +#[derive(Debug, Deserialize)] +struct MagFeedTable { + def: i16, + pow: i16, + dex: i16, + mnd: i16, + iq: i8, + syn: i8, +} + +lazy_static::lazy_static! { + static ref MAG_STATS: HashMap = { + let mut f = std::fs::File::open("data/item_stats/mag_stats.toml").unwrap(); + let mut s = String::new(); + f.read_to_string(&mut s).unwrap(); + + let mag_stats: HashMap = toml::from_str(&s).unwrap(); + mag_stats.into_iter() + .map(|(name, stats)| { + (name.parse().unwrap(), stats) + }) + .collect::>() + }; + + static ref MAG_FEEDING_TABLES: Vec> = { + let mut f = std::fs::File::open("data/item_stats/mag_feed_table.toml").unwrap(); + let mut s = String::new(); + f.read_to_string(&mut s).unwrap(); + + let mut feed: HashMap>> = toml::from_str(&s).unwrap(); + let feed = feed.remove("feedtable".into()).unwrap(); + feed.into_iter() + .map(|table| { + table.into_iter() + .map(|(tool, stats)| { + (tool.parse().unwrap(), stats) + }) + .collect() + }) + .collect::>>() + }; +} + + #[derive(Debug, Copy, Clone)] pub enum ItemParseError { @@ -250,6 +307,206 @@ impl MagType { _ => Err(ItemParseError::InvalidMagType), } } + + pub fn can_evolve(&self) -> bool { + match self { + MagType::Mag => true, + MagType::Varuna => true, + MagType::Mitra => true, + MagType::Surya => true, + MagType::Vayu => true, + MagType::Varaha => true, + MagType::Kama => true, + MagType::Ushasu => true, + MagType::Apsaras => true, + MagType::Kumara => true, + MagType::Kaitabha => true, + MagType::Tapas => true, + MagType::Bhirava => true, + MagType::Kalki => true, + MagType::Rudra => true, + MagType::Marutah => true, + MagType::Yaksa => true, + MagType::Sita => true, + MagType::Garuda => true, + MagType::Nandin => true, + MagType::Ashvinau => true, + MagType::Ribhava => true, + MagType::Soma => true, + MagType::Ila => true, + MagType::Durga => true, + MagType::Vritra => true, + MagType::Namuci => true, + MagType::Sumba => true, + MagType::Naga => true, + MagType::Pitri => true, + MagType::Kabanda => true, + MagType::Ravana => true, + MagType::Marica => true, + MagType::Soniti => true, + MagType::Preta => true, + MagType::Andhaka => true, + MagType::Bana => true, + MagType::Naraka => true, + MagType::Madhu => true, + MagType::Churel => false, + MagType::Robochao => false, + MagType::OpaOpa => false, + MagType::Pian => false, + MagType::Chao => false, + MagType::ChuChu => false, + MagType::KapuKapu => false, + MagType::AngelsWing => false, + MagType::DevilsWing => false, + MagType::Elenor => false, + MagType::MarkIII => false, + MagType::MasterSystem => false, + MagType::Genesis => false, + MagType::SegaSaturn => false, + MagType::Dreamcast => false, + MagType::Hamburger => false, + MagType::PanzersTail => false, + MagType::DevilsTail => false, + MagType::Deva => false, + MagType::Rati => false, + MagType::Savitri => false, + MagType::Rukmin => false, + MagType::Pushan => false, + MagType::Diwari => false, + MagType::Sato => false, + MagType::Bhima => false, + MagType::Nidra => false, + MagType::GeungSi => false, + MagType::Tellusis => false, + MagType::StrikerUnit => false, + MagType::Pioneer => false, + MagType::Puyo => false, + MagType::Moro => false, + MagType::Rappy => false, + MagType::Yahoo => false, + MagType::GaelGiel => false, + MagType::Agastya => false, + } + } +} + +pub enum MagCell { + CellOfMag502, + CellOfMag213, + PartsOfRobochao, + HeartOfOpaOpa, + HeartOfPian, + HeartOfChao, + HeartOfAngel, + HeartOfDevil, + KitOfHamburger, + PanthersSpirit, + KitOfMark3, + KitOfMasterSystem, + KitOfGenesis, + KitOfSegaSaturn, + KitOfDreamcast, + Tablet, + DragonScale, + HeavenStrikerCoat, + PioneerParts, + AmitiesMemo, + HeartOfMorolian, + RappysBeak, + YahoosEngine, + DPhotonCore, + LibertaKit, +} + +impl std::convert::TryFrom for MagCell { + type Error = (); + + fn try_from(tool: ToolType) -> Result { + match tool { + ToolType::CellOfMag502 => Ok(MagCell::CellOfMag502), + ToolType::CellOfMag213 => Ok(MagCell::CellOfMag213), + ToolType::PartsOfRobochao => Ok(MagCell::PartsOfRobochao), + ToolType::HeartOfOpaOpa => Ok(MagCell::HeartOfOpaOpa), + ToolType::HeartOfPian => Ok(MagCell::HeartOfPian), + ToolType::HeartOfChao => Ok(MagCell::HeartOfChao), + ToolType::HeartOfAngel => Ok(MagCell::HeartOfAngel), + ToolType::HeartOfDevil => Ok(MagCell::HeartOfDevil), + ToolType::KitOfHamburger => Ok(MagCell::KitOfHamburger), + ToolType::PanthersSpirit => Ok(MagCell::PanthersSpirit), + ToolType::KitOfMark3 => Ok(MagCell::KitOfMark3), + ToolType::KitOfMasterSystem => Ok(MagCell::KitOfMasterSystem), + ToolType::KitOfGenesis => Ok(MagCell::KitOfGenesis), + ToolType::KitOfSegaSaturn => Ok(MagCell::KitOfSegaSaturn), + ToolType::KitOfDreamcast => Ok(MagCell::KitOfDreamcast), + ToolType::Tablet => Ok(MagCell::Tablet), + ToolType::DragonScale => Ok(MagCell::DragonScale), + ToolType::HeavenStrikerCoat => Ok(MagCell::HeavenStrikerCoat), + ToolType::PioneerParts => Ok(MagCell::PioneerParts), + ToolType::AmitiesMemo => Ok(MagCell::AmitiesMemo), + ToolType::HeartOfMorolian => Ok(MagCell::HeartOfMorolian), + ToolType::RappysBeak => Ok(MagCell::RappysBeak), + ToolType::YahoosEngine => Ok(MagCell::YahoosEngine), + ToolType::DPhotonCore => Ok(MagCell::DPhotonCore), + ToolType::LibertaKit => Ok(MagCell::LibertaKit), + _ => Err(()), + } + } +} + +enum MagAttribute { + //Def, + Pow, + Dex, + Mind, +} + +// one day I hope to be cool enough to figure how to enforce that each magattribute in sequence must be unique +// (to not need the _ in the match) +enum MagAttributeOrdering { + Sequence(MagAttribute, MagAttribute, MagAttribute), + Primary(MagAttribute), + MultiPrimary +} + +impl MagAttributeOrdering { + fn new(pow: u16, dex: u16, mnd: u16) -> MagAttributeOrdering { + let primary = if pow > dex && pow > mnd { + MagAttribute::Pow + } + else if dex > pow && dex > mnd{ + MagAttribute::Dex + } + else if mnd > pow && mnd > dex { + MagAttribute::Mind + } + else { + return MagAttributeOrdering::MultiPrimary + }; + + match primary { + MagAttribute::Pow => { + match dex.cmp(&mnd) { + Greater => MagAttributeOrdering::Sequence(primary, MagAttribute::Dex, MagAttribute::Mind), + Equal => MagAttributeOrdering::Primary(primary), + Less => MagAttributeOrdering::Sequence(primary, MagAttribute::Mind, MagAttribute::Dex), + } + }, + MagAttribute::Dex => { + match pow.cmp(&mnd) { + Greater => MagAttributeOrdering::Sequence(primary, MagAttribute::Pow, MagAttribute::Mind), + Equal => MagAttributeOrdering::Primary(primary), + Less => MagAttributeOrdering::Sequence(primary, MagAttribute::Mind, MagAttribute::Pow), + } + }, + MagAttribute::Mind => { + match pow.cmp(&dex) { + Greater => MagAttributeOrdering::Sequence(primary, MagAttribute::Pow, MagAttribute::Dex), + Equal => MagAttributeOrdering::Primary(primary), + Less => MagAttributeOrdering::Sequence(primary, MagAttribute::Dex, MagAttribute::Pow), + } + }, + } + } } #[derive(Debug, Clone, PartialEq)] @@ -257,11 +514,12 @@ pub enum MagModifier { FeedMag{ food: ItemEntityId, }, - BankMag, + BankMag, // when putting a mag in the bank it truncates the values which has applications when raising degenerate mags MagCell(ItemEntityId), + OwnerChange(CharacterClass, SectionID) } -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Deserialize, enum_utils::FromStr)] pub enum PhotonBlast { Farlla, Estlla, @@ -274,19 +532,38 @@ pub enum PhotonBlast { #[derive(Debug, Clone, PartialEq)] pub struct Mag { pub mag: MagType, - pub def: u16, - pub pow: u16, - pub dex: u16, - pub mnd: u16, + def: u16, + pow: u16, + dex: u16, + mnd: u16, pub synchro: u8, - pub iq: u8, - pub photon_blast: [Option; 3], + iq: u8, + photon_blast: [Option; 3], pub color: u8, - pub modifiers: Vec, + //modifiers: Vec, + pub class: CharacterClass, + pub id: SectionID, } impl Mag { + pub fn baby_mag(skin: u16) -> Mag { + Mag { + mag: MagType::Mag, + def: 500, + pow: 0, + dex: 0, + mnd: 0, + synchro: 20, + iq: 0, + photon_blast: [None; 3], + color: (skin % 18) as u8, + //modifiers: Vec::new(), + class: CharacterClass::HUmar, + id: SectionID::Viridia, + } + } + pub fn as_bytes(&self) -> [u8; 16] { let mut result = [0; 16]; result[0..3].copy_from_slice(&self.mag.value()); @@ -355,7 +632,7 @@ impl Mag { } pub fn from_bytes(data: [u8; 16]) -> Result { - let m = MagType::parse_type([data[0], data[1], data[2]]); + let m = MagType::parse_type([data[0], data[1], data[2]]); if m.is_ok() { let mut def = u16::from_le_bytes([data[4], data[5]]); let mut pow = u16::from_le_bytes([data[6], data[7]]); @@ -382,11 +659,516 @@ impl Mag { iq: iq, photon_blast: [None, None, None], // TODO: actually get PBs from bytes color: data[15] % 18, - modifiers: Vec::new(), + //modifiers: Vec::new(), + class: CharacterClass::HUmar, + id: SectionID::Viridia, }) } else { Err(ItemParseError::InvalidMagBytes) // TODO: error handling if wrong bytes are given } } + + pub fn def(&self) -> u16 { + self.def/100 + } + + pub fn pow(&self) -> u16 { + self.pow/100 + } + + pub fn dex(&self) -> u16 { + self.dex/100 + } + + pub fn mind(&self) -> u16 { + self.mnd/100 + } + + pub fn level(&self) -> u16 { + self.def() + self.pow() + self.dex() + self.mind() + } + + fn change_mag_type(&mut self, previous_level: u16) { + if !self.mag.can_evolve() { + return + } + + if self.level() >= 10 && previous_level < 10 { + match self.class { + CharacterClass::HUmar | CharacterClass::HUnewearl | CharacterClass::HUcast | CharacterClass::HUcaseal => { + self.mag = MagType::Varuna + }, + CharacterClass::RAmar | CharacterClass::RAmarl | CharacterClass::RAcast | CharacterClass::RAcaseal => { + self.mag = MagType::Kalki + }, + CharacterClass::FOmar | CharacterClass::FOmarl | CharacterClass::FOnewm | CharacterClass::FOnewearl => { + self.mag = MagType::Vritra + }, + } + } + + if self.level() >= 35 && previous_level < 35 { + match self.mag { + MagType::Varuna => { + if self.pow > self.dex && self.pow > self.mnd { + self.mag = MagType::Rudra + } + else if self.dex > self.pow && self.dex > self.mnd { + self.mag = MagType::Marutah + } + else if self.mnd > self.pow && self.mnd > self.dex { + self.mag = MagType::Vayu + } + else { + self.mag = MagType::Rudra + } + }, + MagType::Kalki => { + if self.pow > self.dex && self.pow > self.mnd { + self.mag = MagType::Surya + } + else if self.dex > self.pow && self.dex > self.mnd { + self.mag = MagType::Mitra + } + else if self.mnd > self.pow && self.mnd > self.dex { + self.mag = MagType::Tapas + } + else { + self.mag = MagType::Mitra + } + }, + MagType::Vritra => { + if self.pow > self.dex && self.pow > self.mnd { + self.mag = MagType::Sumba + } + else if self.dex > self.pow && self.dex > self.mnd { + self.mag = MagType::Ashvinau + } + else if self.mnd > self.pow && self.mnd > self.dex { + self.mag = MagType::Namuci + } + else { + self.mag = MagType::Namuci + } + }, + _ => unreachable!(), + } + } + + if self.level() >= 50 && self.level() % 5 == 0 { + let mag_attr_ordering = MagAttributeOrdering::new(self.pow, self.dex, self.mnd); + self.mag = match self.id { + SectionID::Viridia | SectionID::Skyly | SectionID::Purplenum | SectionID::Redria | SectionID::Yellowboze => { + match self.class { + CharacterClass::HUmar | CharacterClass::HUnewearl | CharacterClass::HUcast | CharacterClass::HUcaseal => { + match mag_attr_ordering { + MagAttributeOrdering::Primary(MagAttribute::Pow) => MagType::Varaha, + MagAttributeOrdering::Primary(MagAttribute::Dex) => MagType::Nandin, + MagAttributeOrdering::Primary(MagAttribute::Mind) => MagType::Kabanda, + MagAttributeOrdering::Sequence(MagAttribute::Pow, MagAttribute::Dex, MagAttribute::Mind) => MagType::Varaha, + MagAttributeOrdering::Sequence(MagAttribute::Pow, MagAttribute::Mind, MagAttribute::Dex) => MagType::Bhirava, + MagAttributeOrdering::Sequence(MagAttribute::Dex, MagAttribute::Pow, MagAttribute::Mind) => MagType::Ila, + MagAttributeOrdering::Sequence(MagAttribute::Dex, MagAttribute::Mind, MagAttribute::Pow) => MagType::Nandin, + MagAttributeOrdering::Sequence(MagAttribute::Mind, MagAttribute::Pow, MagAttribute::Dex) => MagType::Kabanda, + MagAttributeOrdering::Sequence(MagAttribute::Mind, MagAttribute::Dex, MagAttribute::Pow) => MagType::Ushasu, + MagAttributeOrdering::MultiPrimary => { + if self.dex >= self.mnd { + MagType::Varaha + } + else { + MagType::Bhirava + } + }, + _ => unreachable!() + } + }, + CharacterClass::RAmar | CharacterClass::RAmarl | CharacterClass::RAcast | CharacterClass::RAcaseal => { + match mag_attr_ordering { + MagAttributeOrdering::Primary(MagAttribute::Pow) => MagType::Kama, + MagAttributeOrdering::Primary(MagAttribute::Dex) => MagType::Kama, + MagAttributeOrdering::Primary(MagAttribute::Mind) => MagType::Varaha, + MagAttributeOrdering::Sequence(MagAttribute::Pow, MagAttribute::Dex, MagAttribute::Mind) => MagType::Kama, + MagAttributeOrdering::Sequence(MagAttribute::Pow, MagAttribute::Mind, MagAttribute::Dex) => MagType::Bhirava, + MagAttributeOrdering::Sequence(MagAttribute::Dex, MagAttribute::Pow, MagAttribute::Mind) => MagType::Bhirava, + MagAttributeOrdering::Sequence(MagAttribute::Dex, MagAttribute::Mind, MagAttribute::Pow) => MagType::Kama, + MagAttributeOrdering::Sequence(MagAttribute::Mind, MagAttribute::Pow, MagAttribute::Dex) => MagType::Varaha, + MagAttributeOrdering::Sequence(MagAttribute::Mind, MagAttribute::Dex, MagAttribute::Pow) => MagType::Apsaras, + MagAttributeOrdering::MultiPrimary => { + if self.mnd >= self.pow { + MagType::Kama + } + else { + MagType::Bhirava + } + } + _ => unreachable!() + } + }, + CharacterClass::FOmar | CharacterClass::FOmarl | CharacterClass::FOnewm | CharacterClass::FOnewearl => { + match (mag_attr_ordering, self.def() >= 45) { + (MagAttributeOrdering::Primary(MagAttribute::Pow), true) => MagType::Andhaka, + (MagAttributeOrdering::Primary(MagAttribute::Dex), true) => MagType::Bana, + (MagAttributeOrdering::Primary(MagAttribute::Mind), true) => MagType::Bana, + (MagAttributeOrdering::Sequence(MagAttribute::Pow, MagAttribute::Dex, MagAttribute::Mind), true) => MagType::Andhaka, + (MagAttributeOrdering::Sequence(MagAttribute::Pow, MagAttribute::Mind, MagAttribute::Dex), true) => MagType::Andhaka, + (MagAttributeOrdering::Sequence(MagAttribute::Dex, MagAttribute::Pow, MagAttribute::Mind), true) => MagType::Bana, + (MagAttributeOrdering::Sequence(MagAttribute::Dex, MagAttribute::Mind, MagAttribute::Pow), true) => MagType::Bana, + (MagAttributeOrdering::Sequence(MagAttribute::Mind, MagAttribute::Pow, MagAttribute::Dex), true) => MagType::Bana, + (MagAttributeOrdering::Sequence(MagAttribute::Mind, MagAttribute::Dex, MagAttribute::Pow), true) => MagType::Bana, + (MagAttributeOrdering::MultiPrimary, true) => MagType::Bana, + (MagAttributeOrdering::Primary(MagAttribute::Pow), false) => MagType::Naraka, + (MagAttributeOrdering::Primary(MagAttribute::Dex), false) => MagType::Sita, + (MagAttributeOrdering::Primary(MagAttribute::Mind), false) => MagType::Naga, + (MagAttributeOrdering::Sequence(MagAttribute::Pow, MagAttribute::Dex, MagAttribute::Mind), false) => MagType::Naraka, + (MagAttributeOrdering::Sequence(MagAttribute::Pow, MagAttribute::Mind, MagAttribute::Dex), false) => MagType::Ravana, + (MagAttributeOrdering::Sequence(MagAttribute::Dex, MagAttribute::Pow, MagAttribute::Mind), false) => MagType::Ribhava, + (MagAttributeOrdering::Sequence(MagAttribute::Dex, MagAttribute::Mind, MagAttribute::Pow), false) => MagType::Sita, + (MagAttributeOrdering::Sequence(MagAttribute::Mind, MagAttribute::Pow, MagAttribute::Dex), false) => MagType::Naga, + (MagAttributeOrdering::Sequence(MagAttribute::Mind, MagAttribute::Dex, MagAttribute::Pow), false) => MagType::Kabanda, + (MagAttributeOrdering::MultiPrimary, false) => { + if self.pow >= self.dex { + MagType::Naga + } + else { + MagType::Kabanda + } + } + _ => unreachable!() + } + }, + } + }, + SectionID::Greenill | SectionID::Bluefull | SectionID::Pinkal | SectionID::Oran | SectionID::Whitill => { + match self.class { + CharacterClass::HUmar | CharacterClass::HUnewearl | CharacterClass::HUcast | CharacterClass::HUcaseal => { + match mag_attr_ordering { + MagAttributeOrdering::Primary(MagAttribute::Pow) => MagType::Kama, + MagAttributeOrdering::Primary(MagAttribute::Dex) => MagType::Yaksa, + MagAttributeOrdering::Primary(MagAttribute::Mind) => MagType::Bana, + MagAttributeOrdering::Sequence(MagAttribute::Pow, MagAttribute::Dex, MagAttribute::Mind) => MagType::Kama, + MagAttributeOrdering::Sequence(MagAttribute::Pow, MagAttribute::Mind, MagAttribute::Dex) => MagType::Apsaras, + MagAttributeOrdering::Sequence(MagAttribute::Dex, MagAttribute::Pow, MagAttribute::Mind) => MagType::Garuda, + MagAttributeOrdering::Sequence(MagAttribute::Dex, MagAttribute::Mind, MagAttribute::Pow) => MagType::Yaksa, + MagAttributeOrdering::Sequence(MagAttribute::Mind, MagAttribute::Pow, MagAttribute::Dex) => MagType::Bana, + MagAttributeOrdering::Sequence(MagAttribute::Mind, MagAttribute::Dex, MagAttribute::Pow) => MagType::Soma, + MagAttributeOrdering::MultiPrimary => { + if self.dex >= self.mnd { + MagType::Kama + } + else { + MagType::Apsaras + } + } + _ => unreachable!() + } + }, + CharacterClass::RAmar | CharacterClass::RAmarl | CharacterClass::RAcast | CharacterClass::RAcaseal => { + match mag_attr_ordering { + MagAttributeOrdering::Primary(MagAttribute::Pow) => MagType::Madhu, + MagAttributeOrdering::Primary(MagAttribute::Dex) => MagType::Varaha, + MagAttributeOrdering::Primary(MagAttribute::Mind) => MagType::Kabanda, + MagAttributeOrdering::Sequence(MagAttribute::Pow, MagAttribute::Dex, MagAttribute::Mind) => MagType::Madhu, + MagAttributeOrdering::Sequence(MagAttribute::Pow, MagAttribute::Mind, MagAttribute::Dex) => MagType::Kaitabha, + MagAttributeOrdering::Sequence(MagAttribute::Dex, MagAttribute::Pow, MagAttribute::Mind) => MagType::Kaitabha, + MagAttributeOrdering::Sequence(MagAttribute::Dex, MagAttribute::Mind, MagAttribute::Pow) => MagType::Varaha, + MagAttributeOrdering::Sequence(MagAttribute::Mind, MagAttribute::Pow, MagAttribute::Dex) => MagType::Kabanda, + MagAttributeOrdering::Sequence(MagAttribute::Mind, MagAttribute::Dex, MagAttribute::Pow) => MagType::Durga, + MagAttributeOrdering::MultiPrimary => { + if self.pow > self.mnd { + MagType::Kaitabha + } + else { + MagType::Varaha + } + } + _ => unreachable!() + } + }, + CharacterClass::FOmar | CharacterClass::FOmarl | CharacterClass::FOnewm | CharacterClass::FOnewearl => { + match (mag_attr_ordering, self.def() >= 45) { + (MagAttributeOrdering::Primary(MagAttribute::Pow), true) => MagType::Andhaka, + (MagAttributeOrdering::Primary(MagAttribute::Dex), true) => MagType::Bana, + (MagAttributeOrdering::Primary(MagAttribute::Mind), true) => MagType::Bana, + (MagAttributeOrdering::Sequence(MagAttribute::Pow, MagAttribute::Dex, MagAttribute::Mind), true) => MagType::Andhaka, + (MagAttributeOrdering::Sequence(MagAttribute::Pow, MagAttribute::Mind, MagAttribute::Dex), true) => MagType::Andhaka, + (MagAttributeOrdering::Sequence(MagAttribute::Dex, MagAttribute::Pow, MagAttribute::Mind), true) => MagType::Bana, + (MagAttributeOrdering::Sequence(MagAttribute::Dex, MagAttribute::Mind, MagAttribute::Pow), true) => MagType::Bana, + (MagAttributeOrdering::Sequence(MagAttribute::Mind, MagAttribute::Pow, MagAttribute::Dex), true) => MagType::Bana, + (MagAttributeOrdering::Sequence(MagAttribute::Mind, MagAttribute::Dex, MagAttribute::Pow), true) => MagType::Bana, + (MagAttributeOrdering::MultiPrimary, true) => MagType::Bana, + (MagAttributeOrdering::Primary(MagAttribute::Pow), false) => MagType::Marica, + (MagAttributeOrdering::Primary(MagAttribute::Dex), false) => MagType::Bhirava, + (MagAttributeOrdering::Primary(MagAttribute::Mind), false) => MagType::Kumara, + (MagAttributeOrdering::Sequence(MagAttribute::Pow, MagAttribute::Dex, MagAttribute::Mind), false) => MagType::Marica, + (MagAttributeOrdering::Sequence(MagAttribute::Pow, MagAttribute::Mind, MagAttribute::Dex), false) => MagType::Naga, + (MagAttributeOrdering::Sequence(MagAttribute::Dex, MagAttribute::Pow, MagAttribute::Mind), false) => MagType::Garuda, + (MagAttributeOrdering::Sequence(MagAttribute::Dex, MagAttribute::Mind, MagAttribute::Pow), false) => MagType::Bhirava, + (MagAttributeOrdering::Sequence(MagAttribute::Mind, MagAttribute::Pow, MagAttribute::Dex), false) => MagType::Kumara, + (MagAttributeOrdering::Sequence(MagAttribute::Mind, MagAttribute::Dex, MagAttribute::Pow), false) => MagType::Ila, + (MagAttributeOrdering::MultiPrimary, false) => { + if self.pow >= self.dex { + MagType::Kumara + } + else { + MagType::Ila + } + } + _ => unreachable!() + } + }, + } + } + }; + } + + if self.level() >= 100 && self.level() % 5 == 0 { + match self.id { + SectionID::Skyly | SectionID::Pinkal | SectionID::Yellowboze => { + if self.def() + self.pow() == self.dex() + self.mind() { + self.mag = match self.class { + CharacterClass::HUmar | CharacterClass::HUcast => { + MagType::Rati + }, + CharacterClass::HUnewearl | CharacterClass::HUcaseal => { + MagType::Savitri + }, + CharacterClass::RAmar | CharacterClass::RAcast => { + MagType::Pushan + }, + CharacterClass::RAmarl | CharacterClass::RAcaseal => { + MagType::Diwari + }, + CharacterClass::FOmar | CharacterClass::FOnewm => { + MagType::Nidra + }, + CharacterClass::FOmarl | CharacterClass::FOnewearl => { + MagType::Bhima + }, + } + } + }, + SectionID::Viridia | SectionID::Bluefull | SectionID::Redria | SectionID::Whitill => { + if self.def() + self.dex() == self.pow() + self.mind() { + self.mag = match self.class { + CharacterClass::HUmar | CharacterClass::HUcast => { + MagType::Deva + }, + CharacterClass::HUnewearl | CharacterClass::HUcaseal => { + MagType::Savitri + }, + CharacterClass::RAmar | CharacterClass::RAcast => { + MagType::Pushan + }, + CharacterClass::RAmarl | CharacterClass::RAcaseal => { + MagType::Rukmin + }, + CharacterClass::FOmar | CharacterClass::FOnewm => { + MagType::Nidra + }, + CharacterClass::FOmarl | CharacterClass::FOnewearl => { + MagType::Sato // best mag + }, + } + } + }, + SectionID::Greenill | SectionID::Purplenum | SectionID::Oran => { + if self.def() + self.mind() == self.pow() + self.dex() { + self.mag = match self.class { + CharacterClass::HUmar | CharacterClass::HUcast => { + MagType::Rati + }, + CharacterClass::HUnewearl | CharacterClass::HUcaseal => { + MagType::Savitri + }, + CharacterClass::RAmar | CharacterClass::RAcast => { + MagType::Pushan + }, + CharacterClass::RAmarl | CharacterClass::RAcaseal => { + MagType::Rukmin + }, + CharacterClass::FOmar | CharacterClass::FOnewm => { + MagType::Nidra + }, + CharacterClass::FOmarl | CharacterClass::FOnewearl => { + MagType::Bhima + }, + } + } + } + } + } + } + + pub fn assign_photon_blast(&mut self) { + MAG_STATS.get(&self.mag).map(|stats| { + stats.photon_blast.map(|photon_blast| { + if !self.photon_blast.contains(&Some(photon_blast)) { + self.photon_blast.iter_mut().find(|k| k.is_none()).map(|pb_slot| { + *pb_slot = Some(photon_blast) + }); + } + }) + }); + } + + pub fn feed(&mut self, tool: ToolType) { + let previous_level = self.level(); + MAG_STATS.get(&self.mag).map(|stats| { + MAG_FEEDING_TABLES.get(stats.feed_table).map(|feeding_table| { + feeding_table.get(&tool).map(|feed_stats| { + self.def = std::cmp::max(std::cmp::max((self.def as i16) + feed_stats.def, 0) as u16, self.def()*100); + self.pow = std::cmp::max(std::cmp::max((self.pow as i16) + feed_stats.pow, 0) as u16, self.pow()*100); + self.dex = std::cmp::max(std::cmp::max((self.dex as i16) + feed_stats.dex, 0) as u16, self.dex()*100); + self.mnd = std::cmp::max(std::cmp::max((self.mnd as i16) + feed_stats.mnd, 0) as u16, self.mind()*100); + self.iq = std::cmp::min(((self.iq as i16) + feed_stats.iq as i16) as u8, 200); + self.synchro = std::cmp::min(((self.synchro as i8) + feed_stats.syn) as u8, 120); + }) + }) + }); + //if previous_level != self.level() { + self.change_mag_type(previous_level); + self.assign_photon_blast(); + //} + } + + pub fn change_owner(&mut self, class: CharacterClass, id: SectionID) { + self.class = class; + self.id = id; + } + + pub fn bank(&mut self) { + // what is the truncation logic anyway + } + + // TODO: this needs more checks on validity + pub fn apply_mag_cell(&mut self, mag_cell: MagCell) { + self.mag = match mag_cell { + MagCell::CellOfMag502 => { + match self.id { + SectionID::Viridia | SectionID::Skyly | SectionID::Purplenum | SectionID::Redria | SectionID::Yellowboze => { + MagType::Soniti + }, + SectionID::Greenill | SectionID::Bluefull | SectionID::Pinkal | SectionID::Oran | SectionID::Whitill => { + MagType::Pitri + } + } + } + MagCell::CellOfMag213 => { + match self.id { + SectionID::Viridia | SectionID::Skyly | SectionID::Purplenum | SectionID::Redria | SectionID::Yellowboze => { + MagType::Churel + }, + SectionID::Greenill | SectionID::Bluefull | SectionID::Pinkal | SectionID::Oran | SectionID::Whitill => { + MagType::Preta + } + } + }, + MagCell::PartsOfRobochao => MagType::Robochao, + MagCell::HeartOfOpaOpa => MagType::OpaOpa, + MagCell::HeartOfPian => MagType::Pian, + MagCell::HeartOfChao => MagType::Chao, + MagCell::HeartOfAngel => MagType::AngelsWing, + MagCell::HeartOfDevil => if self.mag == MagType::DevilsWing { + MagType::DevilsTail + } + else { + MagType::DevilsWing + }, + MagCell::KitOfHamburger => MagType::Hamburger, + MagCell::PanthersSpirit => MagType::PanzersTail, + MagCell::KitOfMark3 => MagType::MarkIII, + MagCell::KitOfMasterSystem => MagType::MasterSystem, + MagCell::KitOfGenesis => MagType::Genesis, + MagCell::KitOfSegaSaturn => MagType::SegaSaturn, + MagCell::KitOfDreamcast => MagType::Dreamcast, + MagCell::Tablet => MagType::GeungSi, + MagCell::DragonScale => MagType::Tellusis, + MagCell::HeavenStrikerCoat => MagType::StrikerUnit, + MagCell::PioneerParts => MagType::Pioneer, + MagCell::AmitiesMemo => MagType::Puyo, + MagCell::HeartOfMorolian => MagType::Moro, + MagCell::RappysBeak => MagType::Rappy, + MagCell::YahoosEngine => MagType::Yahoo, + MagCell::DPhotonCore => MagType::GaelGiel, + MagCell::LibertaKit => MagType::Agastya, + } + } } + + +#[cfg(test)] +mod test { + use super::*; + use std::io::Read; + + #[test] + fn test_load_mag_stats() { + let mut f = std::fs::File::open("data/item_stats/mag_stats.toml").unwrap(); + let mut s = String::new(); + f.read_to_string(&mut s).unwrap(); + + let mags: HashMap = toml::from_str(&s).unwrap(); + let _mags = mags.into_iter() + .map(|(name, stats)| { + (name.parse().unwrap(), stats) + }) + .collect::>(); + } + + #[test] + fn test_load_mag_feed_table() { + let mut f = std::fs::File::open("data/item_stats/mag_feed_table.toml").unwrap(); + let mut s = String::new(); + f.read_to_string(&mut s).unwrap(); + + let mut feed: HashMap>> = toml::from_str(&s).unwrap(); + let feed = feed.remove("feedtable".into()).unwrap(); + let _feed = feed.into_iter() + .map(|table| { + table.into_iter() + .map(|(tool, stats)| { + (tool.parse().unwrap(), stats) + }) + .collect() + }) + .collect::>>(); + } + + #[test] + fn test_raise_a_sato() { + let mut mag = Mag::baby_mag(0); + mag.change_owner(CharacterClass::RAcaseal, SectionID::Whitill); + for _ in 0..137 { + mag.feed(ToolType::Antidote); + } + for _ in 0..75 { + mag.feed(ToolType::Antiparalysis); + } + mag.change_owner(CharacterClass::FOmarl, SectionID::Whitill); + for _ in 0..51 { + mag.feed(ToolType::Antiparalysis); + } + for _ in 0..284 { + mag.feed(ToolType::Dimate); + } + assert!(mag == Mag { + mag: MagType::Sato, + def: 507, + pow: 5019, + dex: 4505, + mnd: 0, + synchro: 120, + iq: 200, + photon_blast: [Some(PhotonBlast::Estlla), Some(PhotonBlast::Pilla), Some(PhotonBlast::MyllaYoulla)], + color: 0, + class: CharacterClass::FOmarl, + id: SectionID::Whitill, + }); + } + + #[test] + fn test_mag_does_not_level_down() { + } +} + diff --git a/src/entity/item/mod.rs b/src/entity/item/mod.rs index b65bebb..a594d3a 100644 --- a/src/entity/item/mod.rs +++ b/src/entity/item/mod.rs @@ -44,6 +44,9 @@ pub enum ItemLocation { z: f32, }, Consumed, + FedToMag { + mag: ItemEntityId, + } /*Destroyed { // marks an item that has been consumed in some way }, diff --git a/src/entity/item/tool.rs b/src/entity/item/tool.rs index de92170..5b95dde 100644 --- a/src/entity/item/tool.rs +++ b/src/entity/item/tool.rs @@ -251,6 +251,42 @@ impl ToolType { } } + pub fn is_mag_cell(&self) -> bool { + match self { + ToolType::CellOfMag502 => true, + ToolType::CellOfMag213 => true, + ToolType::PartsOfRobochao => true, + ToolType::HeartOfOpaOpa => true, + ToolType::HeartOfPian => true, + ToolType::HeartOfChao => true, + ToolType::HeartOfAngel => true, + ToolType::HeartOfDevil => true, + ToolType::KitOfHamburger => true, + ToolType::PanthersSpirit => true, + ToolType::KitOfMark3 => true, + ToolType::KitOfMasterSystem => true, + ToolType::KitOfGenesis => true, + ToolType::KitOfSegaSaturn => true, + ToolType::KitOfDreamcast => true, + ToolType::Tablet => true, + ToolType::DragonScale => true, + ToolType::HeavenStrikerCoat => true, + ToolType::PioneerParts => true, + ToolType::AmitiesMemo => true, + ToolType::HeartOfMorolian => true, + ToolType::RappysBeak => true, + ToolType::YahoosEngine => true, + ToolType::DPhotonCore => true, + ToolType::LibertaKit => true, + ToolType::CellOfMag0503 => true, + ToolType::CellOfMag0504 => true, + ToolType::CellOfMag0505 => true, + ToolType::CellOfMag0506 => true, + ToolType::CellOfMag0507 => true, + _ => false, + } + } + pub fn value(&self) -> [u8; 3] { match self { ToolType::Monomate => [0x03, 0x00, 0x00], diff --git a/src/lib.rs b/src/lib.rs index 309b324..abd1d14 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,8 @@ #![feature(maybe_uninit_extra)] #![feature(const_in_array_repeat_expressions)] #![feature(drain_filter)] +#![feature(or_patterns)] + diff --git a/src/login/character.rs b/src/login/character.rs index cade993..997e46d 100644 --- a/src/login/character.rs +++ b/src/login/character.rs @@ -224,21 +224,11 @@ async fn new_character(entity_gateway: &mut EG, user: &UserAc equipped: true, }}).await; + let mut mag = Mag::baby_mag(character.appearance.skin); + mag.change_owner(character.char_class, character.section_id); entity_gateway.create_item( NewItemEntity { - item: ItemDetail::Mag( - Mag { - mag: MagType::Mag, - def: 500, - pow: 0, - dex: 0, - mnd: 0, - synchro: 20, - iq: 0, - photon_blast: [None; 3], - color: (character.appearance.skin % 18) as u8, - modifiers: Vec::new(), - }), + item: ItemDetail::Mag(mag), location: ItemLocation::Inventory { character_id: character.id, slot: 2, diff --git a/src/ship/drops/rare_drop_table.rs b/src/ship/drops/rare_drop_table.rs index dc3dd47..fae0175 100644 --- a/src/ship/drops/rare_drop_table.rs +++ b/src/ship/drops/rare_drop_table.rs @@ -136,18 +136,7 @@ impl RareDropTable { }) }, RareDropItem::Mag(mag) => { - ItemDropType::Mag(Mag { - mag: mag, - def: 500, - pow: 0, - dex: 0, - mnd: 0, - iq: 0, - synchro: 20, - photon_blast: [None; 3], - color: rng.gen_range(0, 18), - modifiers: Vec::new(), - }) + ItemDropType::Mag(Mag::baby_mag(rng.gen_range(0, 18))) } } } diff --git a/src/ship/items/inventory.rs b/src/ship/items/inventory.rs index 94d634e..89d1c9e 100644 --- a/src/ship/items/inventory.rs +++ b/src/ship/items/inventory.rs @@ -3,6 +3,7 @@ use thiserror::Error; use libpso::character::character;//::InventoryItem; use crate::entity::item::{ItemEntityId, ItemDetail, ItemType}; use crate::entity::item::tool::Tool; +use crate::entity::item::mag::Mag; use crate::ship::items::{ClientItemId, BankItem, BankItemHandle}; use crate::ship::items::floor::{IndividualFloorItem, StackedFloorItem}; @@ -20,6 +21,22 @@ pub struct IndividualInventoryItem { pub equipped: bool, } +impl IndividualInventoryItem { + pub fn mag(&self) -> Option<&Mag> { + match self.item { + ItemDetail::Mag(ref mag) => Some(mag), + _ => None + } + } + + pub fn mag_mut(&mut self) -> Option<&mut Mag> { + match self.item { + ItemDetail::Mag(ref mut mag) => Some(mag), + _ => None + } + } +} + #[derive(Debug, Clone)] pub struct StackedInventoryItem { pub entity_ids: Vec, @@ -57,6 +74,17 @@ pub enum InventoryItemAddToError { } impl InventoryItem { + pub fn entity_ids(&self) -> Vec { + match self { + InventoryItem::Individual(individual_inventory_item) => { + vec![individual_inventory_item.entity_id] + }, + InventoryItem::Stacked(stacked_inventory_item) => { + stacked_inventory_item.entity_ids.clone() + } + } + } + pub fn item_id(&self) -> ClientItemId { match self { InventoryItem::Individual(individual_inventory_item) => { @@ -179,6 +207,13 @@ impl InventoryItem { } Ok(()) } + + pub fn individual(&mut self) -> Option<&mut IndividualInventoryItem> { + match self { + InventoryItem::Individual(ref mut individual_inventory_item) => Some(individual_inventory_item), + _ => None + } + } } @@ -365,6 +400,24 @@ impl CharacterInventory { }) } + pub fn get_equipped_mag_handle<'a>(&'a mut self) -> Option> { + let (slot, _) = self.items.iter() + .enumerate() + .filter(|(_, item)| { + if let InventoryItem::Individual(individual_inventory_item) = item { + if let ItemDetail::Mag(_) = &individual_inventory_item.item { + return individual_inventory_item.equipped + } + } + false + }) + .nth(0)?; + Some(InventoryItemHandle { + inventory: self, + slot: slot, + }) + } + pub fn get_item_by_id(&self, item_id: ClientItemId) -> Option<&InventoryItem> { self.items.iter() .filter(|item| { diff --git a/src/ship/items/manager.rs b/src/ship/items/manager.rs index 69cd03b..27ab699 100644 --- a/src/ship/items/manager.rs +++ b/src/ship/items/manager.rs @@ -5,7 +5,7 @@ use crate::entity::gateway::EntityGateway; use crate::entity::character::{CharacterEntity, CharacterEntityId}; use crate::entity::item::{ItemDetail, ItemLocation, BankName}; use crate::entity::item::{Meseta, NewItemEntity}; -use crate::entity::item::tool::Tool; +use crate::entity::item::tool::{Tool, ToolType}; use crate::ship::map::MapArea; use crate::ship::ship::ItemDropLocation; use crate::ship::drops::{ItemDrop, ItemDropType}; @@ -14,6 +14,7 @@ use crate::ship::location::{AreaClient, RoomId}; use crate::ship::items::bank::*; use crate::ship::items::floor::*; use crate::ship::items::inventory::*; +use crate::ship::items::use_tool; pub enum TriggerCreateItem { @@ -36,6 +37,8 @@ pub enum ItemManagerError { NotEnoughTools(Tool, usize, usize), // have, expected InventoryItemConsumeError(#[from] InventoryItemConsumeError), BankFull, + WrongItemType(ClientItemId), + UseItemError(#[from] use_tool::UseItemError), } pub struct ItemManager { @@ -260,6 +263,9 @@ impl ItemManager { equipped: false, } ).await; + if let Some(_) = new_inventory_item.mag() { + entity_gateway.change_mag_owner(&new_inventory_item.entity_id, character).await; + } }, None => { return Err(ItemManagerError::CouldNotAddToInventory(item_id)); @@ -508,7 +514,7 @@ impl ItemManager { character: &CharacterEntity, item_id: ClientItemId, amount: usize) - -> Result { + -> Result { let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; let used_item = inventory.get_item_handle_by_id(item_id).ok_or(ItemManagerError::NoSuchItemId(item_id))?; let consumed_item = used_item.consume(amount)?; @@ -518,7 +524,7 @@ impl ItemManager { ItemLocation::Consumed).await; } - Ok(consumed_item.item()) + Ok(consumed_item) } pub async fn player_deposits_item(&mut self, @@ -595,4 +601,155 @@ impl ItemManager { Ok(inventory_item.0) } + + pub async fn player_feeds_mag_item(&mut self, + entity_gateway: &mut EG, + character: &CharacterEntity, + mag_id: ClientItemId, + tool_id: ClientItemId) + -> Result<(), ItemManagerError> { + let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; + let consumed_tool = { + let item_to_feed = inventory.get_item_handle_by_id(tool_id).ok_or(ItemManagerError::NoSuchItemId(tool_id))?; + item_to_feed.consume(1)? + }; + let mut mag_handle = inventory.get_item_handle_by_id(mag_id).ok_or(ItemManagerError::NoSuchItemId(mag_id))?; + + let individual_item = mag_handle.item_mut() + .ok_or(ItemManagerError::NoSuchItemId(mag_id))? + .individual() + .ok_or(ItemManagerError::WrongItemType(mag_id))?; + let mag = individual_item + .mag_mut() + .ok_or(ItemManagerError::WrongItemType(mag_id))?; + + let consumed_tool_type = match &consumed_tool { + ConsumedItem::Stacked(stacked_consumed_item) => stacked_consumed_item.tool.tool, + _ => return Err(ItemManagerError::WrongItemType(tool_id)) + }; + mag.feed(consumed_tool_type); + + for entity_id in consumed_tool.entity_ids() { + entity_gateway.feed_mag(&individual_item.entity_id, &entity_id).await; + entity_gateway.change_item_location(&entity_id, ItemLocation::FedToMag { + mag: individual_item.entity_id, + }).await; + } + + Ok(()) + } + + + pub async fn use_item(&mut self, + used_item: ConsumedItem, + entity_gateway: &mut EG, + character: &mut CharacterEntity) -> Result<(), ItemManagerError> { + let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; + match &used_item.item() { + ItemDetail::Weapon(_w) => { + // something like when items are used to combine/transform them? + //_ => {} + }, + ItemDetail::Tool(t) => { + match t.tool { + ToolType::PowerMaterial => { + use_tool::power_material(entity_gateway, character).await; + }, + ToolType::MindMaterial => { + use_tool::mind_material(entity_gateway, character).await; + }, + ToolType::EvadeMaterial => { + use_tool::evade_material(entity_gateway, character).await; + }, + ToolType::DefMaterial => { + use_tool::def_material(entity_gateway, character).await; + }, + ToolType::LuckMaterial => { + use_tool::luck_material(entity_gateway, character).await; + }, + ToolType::HpMaterial => { + use_tool::hp_material(entity_gateway, character).await; + }, + ToolType::TpMaterial => { + use_tool::tp_material(entity_gateway, character).await; + }, + ToolType::CellOfMag502 => { + use_tool::cell_of_mag_502(entity_gateway, &used_item, inventory).await?; + }, + ToolType::CellOfMag213 => { + use_tool::cell_of_mag_213(entity_gateway, &used_item, inventory).await?; + }, + ToolType::PartsOfRobochao => { + use_tool::parts_of_robochao(entity_gateway, &used_item, inventory).await?; + }, + ToolType::HeartOfOpaOpa => { + use_tool::heart_of_opaopa(entity_gateway, &used_item, inventory).await?; + }, + ToolType::HeartOfPian => { + use_tool::heart_of_pian(entity_gateway, &used_item, inventory).await?; + }, + ToolType::HeartOfChao=> { + use_tool::heart_of_chao(entity_gateway, &used_item, inventory).await?; + }, + ToolType::HeartOfAngel => { + use_tool::heart_of_angel(entity_gateway, &used_item, inventory).await?; + }, + ToolType::KitOfHamburger => { + use_tool::kit_of_hamburger(entity_gateway, &used_item, inventory).await?; + }, + ToolType::PanthersSpirit => { + use_tool::panthers_spirit(entity_gateway, &used_item, inventory).await?; + }, + ToolType::KitOfMark3 => { + use_tool::kit_of_mark3(entity_gateway, &used_item, inventory).await?; + }, + ToolType::KitOfMasterSystem=> { + use_tool::kit_of_master_system(entity_gateway, &used_item, inventory).await?; + }, + ToolType::KitOfGenesis => { + use_tool::kit_of_genesis(entity_gateway, &used_item, inventory).await?; + }, + ToolType::KitOfSegaSaturn => { + use_tool::kit_of_sega_saturn(entity_gateway, &used_item, inventory).await?; + }, + ToolType::KitOfDreamcast => { + use_tool::kit_of_dreamcast(entity_gateway, &used_item, inventory).await?; + }, + ToolType::Tablet => { + use_tool::tablet(entity_gateway, &used_item, inventory).await?; + }, + ToolType::DragonScale => { + use_tool::dragon_scale(entity_gateway, &used_item, inventory).await?; + }, + ToolType::HeavenStrikerCoat => { + use_tool::heaven_striker_coat(entity_gateway, &used_item, inventory).await?; + }, + ToolType::PioneerParts => { + use_tool::pioneer_parts(entity_gateway, &used_item, inventory).await?; + }, + ToolType::AmitiesMemo => { + use_tool::amities_memo(entity_gateway, &used_item, inventory).await?; + }, + ToolType::HeartOfMorolian => { + use_tool::heart_of_morolian(entity_gateway, &used_item, inventory).await?; + }, + ToolType::RappysBeak => { + use_tool::rappys_beak(entity_gateway, &used_item, inventory).await?; + }, + ToolType::YahoosEngine => { + use_tool::yahoos_engine(entity_gateway, &used_item, inventory).await?; + }, + ToolType::DPhotonCore => { + use_tool::d_photon_core(entity_gateway, &used_item, inventory).await?; + }, + ToolType::LibertaKit => { + use_tool::liberta_kit(entity_gateway, &used_item, inventory).await?; + }, + _ => {} + } + } + _ => {} + } + Ok(()) + } } diff --git a/src/ship/items/mod.rs b/src/ship/items/mod.rs index 08150f2..3268b04 100644 --- a/src/ship/items/mod.rs +++ b/src/ship/items/mod.rs @@ -2,6 +2,7 @@ mod bank; mod floor; mod inventory; mod manager; +pub mod use_tool; #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct ClientItemId(pub u32); diff --git a/src/ship/items/use_tool.rs b/src/ship/items/use_tool.rs new file mode 100644 index 0000000..a3afa10 --- /dev/null +++ b/src/ship/items/use_tool.rs @@ -0,0 +1,175 @@ +use thiserror::Error; +use crate::entity::gateway::EntityGateway; +use crate::entity::character::CharacterEntity; +use crate::entity::item::{ItemEntityId, ItemDetail}; +use crate::entity::item::tool::ToolType; +use crate::entity::item::mag::MagCell; +use crate::ship::items::{ItemManager, ClientItemId, CharacterInventory, ConsumedItem}; + + + + + +#[derive(Error, Debug)] +#[error("")] +pub enum UseItemError { + NoCharacter, + ItemNotEquipped, + InvalidItem, +} + + + + + +//pub fn use_tool() + +pub async fn power_material(entity_gateway: &mut EG, character: &mut CharacterEntity) { + character.materials.power += 1; + entity_gateway.save_character(character).await; +} + +pub async fn mind_material(entity_gateway: &mut EG, character: &mut CharacterEntity) { + character.materials.mind += 1; + entity_gateway.save_character(character).await; +} + +pub async fn evade_material(entity_gateway: &mut EG, character: &mut CharacterEntity) { + character.materials.evade += 1; + entity_gateway.save_character(character).await; +} + +pub async fn def_material(entity_gateway: &mut EG, character: &mut CharacterEntity) { + character.materials.def += 1; + entity_gateway.save_character(character).await; +} + +pub async fn luck_material(entity_gateway: &mut EG, character: &mut CharacterEntity) { + character.materials.luck += 1; + entity_gateway.save_character(character).await; +} + +pub async fn hp_material(entity_gateway: &mut EG, character: &mut CharacterEntity) { + character.materials.hp += 1; + entity_gateway.save_character(character).await; +} + +pub async fn tp_material(entity_gateway: &mut EG, character: &mut CharacterEntity) { + character.materials.tp += 1; + entity_gateway.save_character(character).await; +} + +async fn mag_cell(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory, mag_cell_type: MagCell) -> Result<(), UseItemError> { + let mut mag_handle = inventory.get_equipped_mag_handle().ok_or(UseItemError::ItemNotEquipped)?; + let mag_item = mag_handle.item_mut() + .ok_or(UseItemError::InvalidItem)?; + let actual_mag = mag_item + .individual() + .ok_or(UseItemError::InvalidItem)? + .mag_mut() + .ok_or(UseItemError::InvalidItem)?; + actual_mag.apply_mag_cell(mag_cell_type); + for mag_entity_id in mag_item.entity_ids() { + for cell_entity_id in used_cell.entity_ids() { + entity_gateway.use_mag_cell(&mag_entity_id, &cell_entity_id).await; + } + } + + Ok(()) +} + +pub async fn cell_of_mag_502(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::CellOfMag502).await +} + +pub async fn cell_of_mag_213(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::CellOfMag213).await +} + +pub async fn parts_of_robochao(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::PartsOfRobochao).await +} + +pub async fn heart_of_opaopa(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfOpaOpa).await +} + +pub async fn heart_of_pian(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfPian).await +} + +pub async fn heart_of_chao(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfChao).await +} + +pub async fn heart_of_angel(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfAngel).await +} + +pub async fn kit_of_hamburger(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfHamburger).await +} + +pub async fn panthers_spirit(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::PanthersSpirit).await +} + +pub async fn kit_of_mark3(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfMark3).await +} + +pub async fn kit_of_master_system(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfMasterSystem).await +} + +pub async fn kit_of_genesis(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfGenesis).await +} + +pub async fn kit_of_sega_saturn(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfSegaSaturn).await +} + +pub async fn kit_of_dreamcast(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfDreamcast).await +} + +pub async fn tablet(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::Tablet).await +} + +pub async fn dragon_scale(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::DragonScale).await +} + +pub async fn heaven_striker_coat(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::HeavenStrikerCoat).await +} + +pub async fn pioneer_parts(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::PioneerParts).await +} + +pub async fn amities_memo(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::AmitiesMemo).await +} + +pub async fn heart_of_morolian(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfMorolian).await +} + +pub async fn rappys_beak(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::RappysBeak).await +} + +pub async fn yahoos_engine(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::YahoosEngine).await +} + +pub async fn d_photon_core(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::DPhotonCore).await +} + +pub async fn liberta_kit(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::LibertaKit).await +} diff --git a/src/ship/packet/handler/message.rs b/src/ship/packet/handler/message.rs index 098f262..0307993 100644 --- a/src/ship/packet/handler/message.rs +++ b/src/ship/packet/handler/message.rs @@ -263,50 +263,9 @@ where EG: EntityGateway { let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; - let item_used_type = item_manager.player_consumes_tool(entity_gateway, &client.character, ClientItemId(player_use_tool.item_id), 1).await?; - match item_used_type { - ItemDetail::Weapon(_w) => { - // something like when items are used to combine/transform them? - //_ => {} - }, - ItemDetail::Tool(t) => { - match t.tool { - ToolType::PowerMaterial => { - client.character.materials.power += 1; - entity_gateway.save_character(&client.character).await; - }, - ToolType::MindMaterial => { - client.character.materials.mind += 1; - entity_gateway.save_character(&client.character).await; - }, - ToolType::EvadeMaterial => { - client.character.materials.evade += 1; - entity_gateway.save_character(&client.character).await; - }, - ToolType::DefMaterial => { - client.character.materials.def += 1; - entity_gateway.save_character(&client.character).await; - }, - ToolType::LuckMaterial => { - client.character.materials.luck += 1; - entity_gateway.save_character(&client.character).await; - }, - ToolType::HpMaterial => { - client.character.materials.hp += 1; - entity_gateway.save_character(&client.character).await; - }, - ToolType::TpMaterial => { - client.character.materials.tp += 1; - entity_gateway.save_character(&client.character).await; - }, - _ => {} - } - } - _ => {} - } - + item_manager.use_item(item_used_type, entity_gateway, &mut client.character).await?; Ok(Box::new(None.into_iter())) } @@ -326,4 +285,25 @@ EG: EntityGateway } else { Err(ShipError::NotEnoughMeseta(id, client.character.meseta)) } -} \ No newline at end of file +} + + +pub async fn player_feed_mag(id: ClientId, + mag_feed: &PlayerFeedMag, + entity_gateway: &mut EG, + client_location: &ClientLocation, + clients: &Clients, + item_manager: &mut ItemManager) + -> Result + Send>, ShipError> +where + EG: EntityGateway +{ + let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?; + item_manager.player_feeds_mag_item(entity_gateway, &client.character, ClientItemId(mag_feed.mag_id), ClientItemId(mag_feed.item_id)).await?; + + let mag_feed = mag_feed.clone(); + Ok(Box::new(client_location.get_client_neighbors(id).unwrap().into_iter() + .map(move |client| { + (client.client, SendShipPacket::Message(Message::new(GameMessage::PlayerFeedMag(mag_feed.clone())))) + }))) +} diff --git a/src/ship/ship.rs b/src/ship/ship.rs index 766dfcb..643d662 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -348,6 +348,9 @@ impl ShipServerState { GameMessage::PlayerUsedMedicalCenter(player_used_medical_center) => { handler::message::player_used_medical_center(id, &player_used_medical_center, &mut self.entity_gateway, &mut self.clients).await }, + GameMessage::PlayerFeedMag(player_feed_mag) => { + handler::message::player_feed_mag(id, &player_feed_mag, &mut self.entity_gateway, &mut self.client_location, &mut self.clients, &mut self.item_manager).await + }, _ => { let cmsg = msg.clone(); Ok(Box::new(self.client_location.get_client_neighbors(id).unwrap().into_iter() diff --git a/tests/test_mags.rs b/tests/test_mags.rs new file mode 100644 index 0000000..aa2e299 --- /dev/null +++ b/tests/test_mags.rs @@ -0,0 +1,210 @@ +use elseware::common::serverstate::{ClientId, ServerState}; +use elseware::entity::gateway::{EntityGateway, InMemoryGateway}; +use elseware::entity::item; +use elseware::ship::ship::{ShipServerState, RecvShipPacket, SendShipPacket}; +use elseware::entity::character::{CharacterClass, SectionID}; + +use libpso::packet::ship::*; +use libpso::packet::messages::*; + +#[path = "common.rs"] +mod common; +use common::*; + +#[async_std::test] +async fn test_mag_feed() { + let mut entity_gateway = InMemoryGateway::new(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Mag( + item::mag::Mag::baby_mag(0) + ), + location: item::ItemLocation::Inventory { + character_id: char1.id, + slot: 0, + equipped: true, + } + }).await; + for _ in 0..7 { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monomate, + } + ), + location: item::ItemLocation::Inventory { + character_id: char1.id, + slot: 1, + equipped: false, + } + }).await; + } + + let mut ship = ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build(); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + join_lobby(&mut ship, ClientId(1)).await; + create_room(&mut ship, ClientId(1), "room", "").await; + + for _ in 0..7 { + ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerFeedMag(PlayerFeedMag { + client: 0, + target: 0, + mag_id: 0x10000, + item_id: 0x10001, + })))).await.unwrap().for_each(drop); + } + + let p1_items = entity_gateway.get_items_by_character(&char1).await; + let mag = p1_items.get(0).unwrap(); + match &mag.item { + item::ItemDetail::Mag(mag) => { + assert!(mag.level() == 7); + assert!(mag.def() == 5); + assert!(mag.pow() == 2); + assert!(mag.dex() == 0); + assert!(mag.mind() == 0); + } + _ => panic!() + } +} + +#[async_std::test] +async fn test_mag_change_owner() { + let mut entity_gateway = InMemoryGateway::new(); + + let (_user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, mut char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + char1.char_class = CharacterClass::RAmarl; + char1.section_id = SectionID::Redria; + entity_gateway.save_character(&char1).await; + char2.char_class = CharacterClass::FOmarl; + char2.section_id = SectionID::Whitill; + entity_gateway.save_character(&char2).await; + + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Mag( + item::mag::Mag::baby_mag(0) + ), + location: item::ItemLocation::Inventory { + character_id: char1.id, + slot: 0, + equipped: true, + } + }).await; + + let mut ship = ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build(); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerDropItem(PlayerDropItem { + client: 0, + target: 0, + unknown1: 0, + map_area: 0, + item_id: 0x10000, + x: 0.0, + y: 0.0, + z: 0.0, + })))).await.unwrap().for_each(drop); + + ship.handle(ClientId(2), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::PickupItem(PickupItem { + client: 0, + target: 0, + item_id: 0x10000, + map_area: 0, + unknown: [0; 3] + })))).await.unwrap().for_each(drop); + + let p2_items = entity_gateway.get_items_by_character(&char2).await; + let mag = p2_items.get(0).unwrap(); + match &mag.item { + item::ItemDetail::Mag(mag) => { + assert!(mag.class == CharacterClass::FOmarl); + assert!(mag.id == SectionID::Whitill); + }, + _ => panic!() + } +} + + +#[async_std::test] +async fn test_mag_cell() { + let mut entity_gateway = InMemoryGateway::new(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + + let mag = entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Mag( + item::mag::Mag::baby_mag(0) + ), + location: item::ItemLocation::Inventory { + character_id: char1.id, + slot: 0, + equipped: true, + } + }).await.unwrap(); + + for _ in 0..1000 { + let fed_tool = entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool ( + item::tool::Tool { + tool: item::tool::ToolType::Monomate, + } + ), + location: item::ItemLocation::FedToMag { + mag: mag.id, + } + }).await.unwrap(); + entity_gateway.feed_mag(&mag.id, &fed_tool.id).await; + } + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::CellOfMag502, + } + ), + location: item::ItemLocation::Inventory { + character_id: char1.id, + slot: 1, + equipped: false, + } + }).await; + + let mut ship = ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build(); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + join_lobby(&mut ship, ClientId(1)).await; + create_room(&mut ship, ClientId(1), "room", "").await; + + ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem { + client: 0, + target: 0, + item_id: 0x10001, + })))).await.unwrap().for_each(drop); + + let p1_items = entity_gateway.get_items_by_character(&char1).await; + let mag = p1_items.get(0).unwrap(); + match &mag.item { + item::ItemDetail::Mag(mag) => { + assert!(mag.mag == item::mag::MagType::Soniti); + } + _ => panic!() + } +}