diff --git a/Cargo.lock b/Cargo.lock index c6e1df1..682d18a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1001,7 +1001,6 @@ checksum = "739e9d7726dc32173fed2d69d17eef3c54682169e4e20ff1d0a45dcd37063cef" [[package]] name = "libpso" version = "0.1.0" -source = "git+http://git.sharnoth.com/jake/libpso#892d2ed220369f0ff7b7530fa734e722c2b21c2c" dependencies = [ "chrono", "psopacket", @@ -1401,7 +1400,6 @@ dependencies = [ [[package]] name = "psopacket" version = "1.0.0" -source = "git+http://git.sharnoth.com/jake/libpso#892d2ed220369f0ff7b7530fa734e722c2b21c2c" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 582634f..8f6b805 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Jake Probst "] edition = "2018" [dependencies] -libpso = { git = "http://git.sharnoth.com/jake/libpso" } +libpso = { git = "http://git.sharnoth.com/jake/libpso", branch = "sendgc" } async-std = { version = "1.9.0", features = ["unstable", "attributes"] } futures = "0.3.5" rand = "0.7.3" diff --git a/src/entity/account.rs b/src/entity/account.rs index 3958412..8247352 100644 --- a/src/entity/account.rs +++ b/src/entity/account.rs @@ -1,6 +1,7 @@ use serde::{Serialize, Deserialize}; use libpso::character::settings; use libpso::character::guildcard; +use libpso::packet::ship::{GuildcardAccept}; pub const USERFLAG_NEWCHAR: u32 = 0x00000001; pub const USERFLAG_DRESSINGROOM: u32 = 0x00000002; @@ -9,9 +10,13 @@ pub const USERFLAG_DRESSINGROOM: u32 = 0x00000002; pub struct UserAccountId(pub u32); #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct UserSettingsId(pub u32); -#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct GuildCardDataId(pub u32); +#[derive(Debug)] +pub enum GuildcardError { + GuildcardAlreadyFriend(UserAccountId), + GuildcardAlreadyBlocked(UserAccountId), + GuildcardListFull, +} #[derive(Clone, Debug)] pub struct NewUserAccountEntity { @@ -124,20 +129,26 @@ impl NewGuildCardDataEntity { } } -// TODO: implement this properly -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct GuildCardDataEntity { - pub id: GuildCardDataId, pub user_id: UserAccountId, - pub guildcard: guildcard::GuildCardData, + pub guildcard_data: Box, } impl GuildCardDataEntity { pub fn new(user_id: UserAccountId) -> GuildCardDataEntity { GuildCardDataEntity { - id: GuildCardDataId(0), user_id, - guildcard: guildcard::GuildCardData::default(), + guildcard_data: Box::new(guildcard::GuildCardData::default()), } } + + pub fn add_friend(&mut self, new_friend: &GuildcardAccept) -> Result<(), GuildcardError> { + let next_open_spot = self.guildcard_data.friends + .iter() + .position(|&g| g.id == 0) + .ok_or(GuildcardError::GuildcardListFull)?; + self.guildcard_data.friends[next_open_spot] = guildcard::GuildCard::from(new_friend); + Ok(()) // TODO: implement a real error + } } diff --git a/src/entity/gateway/entitygateway.rs b/src/entity/gateway/entitygateway.rs index fd8b1aa..cd4d699 100644 --- a/src/entity/gateway/entitygateway.rs +++ b/src/entity/gateway/entitygateway.rs @@ -61,6 +61,10 @@ pub trait EntityGateway: Send + Sync + Clone { unimplemented!(); } + async fn set_guild_card(&mut self, _id: UserAccountId, _gc_data: GuildCardDataEntity) -> Result<(), GatewayError> { + unimplemented!(); + } + async fn create_item(&mut self, _item: NewItemEntity) -> Result { unimplemented!(); } diff --git a/src/entity/gateway/inmemory.rs b/src/entity/gateway/inmemory.rs index 3d37761..0af09bb 100644 --- a/src/entity/gateway/inmemory.rs +++ b/src/entity/gateway/inmemory.rs @@ -21,6 +21,7 @@ pub struct InMemoryGateway { equips: Arc>>, mag_modifiers: Arc>>>, weapon_modifiers: Arc>>>, + guildcard_entities: Arc>>, } impl Default for InMemoryGateway { @@ -37,6 +38,7 @@ impl Default for InMemoryGateway { equips: Arc::new(Mutex::new(BTreeMap::new())), mag_modifiers: Arc::new(Mutex::new(BTreeMap::new())), weapon_modifiers: Arc::new(Mutex::new(BTreeMap::new())), + guildcard_entities: Arc::new(Mutex::new(BTreeMap::new())), } } } @@ -101,6 +103,7 @@ impl InMemoryGateway { impl EntityGateway for InMemoryGateway { async fn create_user(&mut self, user: NewUserAccountEntity) -> Result { let mut users = self.users.lock().unwrap(); + let mut guildcards = self.guildcard_entities.lock().unwrap(); let id = users .iter() .fold(0, |sum, (i, _)| std::cmp::max(sum, i.0)) @@ -109,7 +112,7 @@ impl EntityGateway for InMemoryGateway { id: UserAccountId(id), username: user.username, password: user.password, - guildcard: user.guildcard, + guildcard: id, team_id: user.team_id, banned_until: user.banned_until, muted_until: user.muted_until, @@ -120,7 +123,11 @@ impl EntityGateway for InMemoryGateway { at_character: false, at_ship: false, }; + + let guildcard = GuildCardDataEntity::new(UserAccountId(id)); // TODO: NewGuildcardDataEntity ? users.insert(user.id, user.clone()); + guildcards.insert(user.id, guildcard); + Ok(user) } @@ -213,8 +220,14 @@ impl EntityGateway for InMemoryGateway { Ok(()) } + // TODO: ok_or a real error ? async fn get_guild_card_data_by_user(&self, user: &UserAccountEntity) -> Result { - Ok(GuildCardDataEntity::new(user.id)) + let guildcards = self.guildcard_entities.lock().unwrap(); + guildcards + .iter() + .find(|(_, g)| g.user_id == user.id) + .map(|(_, g)| g.clone()) + .ok_or(GatewayError::Error) } async fn create_item(&mut self, item: NewItemEntity) -> Result { @@ -347,4 +360,10 @@ impl EntityGateway for InMemoryGateway { Err(GatewayError::Error) } } + + async fn set_guild_card(&mut self, id: UserAccountId, gc_data: GuildCardDataEntity) -> Result<(), GatewayError> { + let mut guildcard = self.guildcard_entities.lock().unwrap(); + guildcard.insert(id, gc_data); + Ok(()) + } } diff --git a/src/entity/gateway/postgres/postgres.rs b/src/entity/gateway/postgres/postgres.rs index bbf116a..516088c 100644 --- a/src/entity/gateway/postgres/postgres.rs +++ b/src/entity/gateway/postgres/postgres.rs @@ -278,9 +278,8 @@ impl EntityGateway for PostgresGateway { async fn get_guild_card_data_by_user(&self, user: &UserAccountEntity) -> Result { Ok(GuildCardDataEntity { - id: GuildCardDataId(0), user_id: user.id, - guildcard: guildcard::GuildCardData::default(), + guildcard_data: Box::new(guildcard::GuildCardData::default()), }) } diff --git a/src/login/character.rs b/src/login/character.rs index 69d43e7..610318c 100644 --- a/src/login/character.rs +++ b/src/login/character.rs @@ -441,12 +441,10 @@ impl CharacterServerState { async fn guildcard_data_header(&mut self, id: ClientId) -> Result, anyhow::Error> { let client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?; let guildcard_data = self.entity_gateway.get_guild_card_data_by_user(client.user.as_ref().unwrap()).await.map_err(|_| CharacterError::CouldNotLoadGuildcard)?; - - let bytes = guildcard_data.guildcard.as_bytes(); + let bytes = guildcard_data.guildcard_data.as_bytes(); let mut crc = crc32::Digest::new(crc32::IEEE); crc.write(&bytes[..]); client.guildcard_data_buffer = Some(bytes.to_vec()); - Ok(vec![SendCharacterPacket::GuildcardDataHeader(GuildcardDataHeader::new(bytes.len(), crc.sum32()))]) } diff --git a/src/ship/packet/handler/communication.rs b/src/ship/packet/handler/communication.rs index 087913b..13aa276 100644 --- a/src/ship/packet/handler/communication.rs +++ b/src/ship/packet/handler/communication.rs @@ -45,3 +45,17 @@ pub async fn write_infoboard(id: ClientId, entity_gateway.save_character(&client.character).await.unwrap(); Box::new(None.into_iter()) } + +// TODO: return Result> so ship can do await? and catch errors? +pub async fn accept_guildcard(id: ClientId, + accepted_card: &GuildcardAccept, + clients: &mut Clients, + entity_gateway: &mut EG) + -> Box + Send> { + let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id)).unwrap(); + + let mut gc_data = entity_gateway.get_guild_card_data_by_user(&client.user).await.unwrap(); + gc_data.add_friend(accepted_card).unwrap(); + entity_gateway.set_guild_card(client.user.id, gc_data).await.unwrap(); + Box::new(None.into_iter()) // TODO: does the server need to return anything to any client? everything seems to work fine like this... +} \ No newline at end of file diff --git a/src/ship/ship.rs b/src/ship/ship.rs index ae0fd08..5baf3c4 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -114,6 +114,7 @@ pub enum RecvShipPacket { RequestShipBlockList(RequestShipBlockList), ItemsToTrade(ItemsToTrade), TradeConfirmed(TradeConfirmed), + GuildcardAccept(GuildcardAccept), } impl RecvServerPacket for RecvShipPacket { @@ -155,6 +156,7 @@ impl RecvServerPacket for RecvShipPacket { 0xD2 => Ok(RecvShipPacket::TradeConfirmed(TradeConfirmed::from_bytes(data)?)), 0xE7 => Ok(RecvShipPacket::FullCharacterData(Box::new(FullCharacterData::from_bytes(data)?))), 0x1ED => Ok(RecvShipPacket::SaveOptions(SaveOptions::from_bytes(data)?)), + 0x4E8 => Ok(RecvShipPacket::GuildcardAccept(GuildcardAccept::from_bytes(data)?)), _ => Err(PacketParseError::WrongPacketForServerType(u16::from_le_bytes([data[2], data[3]]), data.to_vec())) } } @@ -743,6 +745,9 @@ impl ServerState for ShipServerState { let block = self.blocks.with_client(id, &self.clients)?; handler::trade::trade_confirmed(id, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager, &mut self.trades).await? }, + RecvShipPacket::GuildcardAccept(guildcard_accept) => { + handler::communication::accept_guildcard(id, guildcard_accept, &mut self.clients, &mut self.entity_gateway).await + }, }) } diff --git a/tests/test_communication.rs b/tests/test_communication.rs new file mode 100644 index 0000000..2e7e63b --- /dev/null +++ b/tests/test_communication.rs @@ -0,0 +1,55 @@ +use elseware::common::serverstate::{ClientId, ServerState}; +use elseware::entity::gateway::{EntityGateway, InMemoryGateway}; +use elseware::ship::ship::{ShipServerState, RecvShipPacket}; +use libpso::packet::ship::*; + +#[path = "common.rs"] +mod common; +use common::*; + +#[async_std::test] +async fn test_guildcard_add_friend() { + let mut entity_gateway = InMemoryGateway::default(); + let (user1, _char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + join_lobby(&mut ship, ClientId(1)).await; + + // Accept friend request from "Test Char 2" + ship.handle(ClientId(1), &RecvShipPacket::GuildcardAccept(GuildcardAccept { + id: 2, + name: [84, 101, 115, 116, 32, 67, 104, 97, 114, 32, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + team: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + desc: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + one: 1, + language: 0, + section_id: 0, + class: 0, + })).await.unwrap().for_each(drop); + + let friendlist = entity_gateway.get_guild_card_data_by_user(&user1).await.unwrap(); + + assert!(friendlist.guildcard_data.friends[0].name == [84, 101, 115, 116, 32, 67, 104, 97, 114, 32, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); +} + + +/* +TODO: actually write these tests at some point. also add a test for transmute/repr(C)? + +#[async_std::test] +async fn test_guildcard_block_rival() {} + +#[async_std::test] +async fn test_guildcard_write_comment() {} + +#[async_std::test] +async fn test_player_chat() {} + +#[async_std::test] +async fn test_update_infoboard() {} + +*/ \ No newline at end of file