diff --git a/src/bin/main.rs b/src/bin/main.rs index d24a0ac..662d86c 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -60,7 +60,6 @@ fn main() { username: if i == 0 { "hi".to_string() } else { format!("hi{}", i+1) }, password: bcrypt::hash("qwer", 5).unwrap(), guildcard: i + 1, - team_id: None, banned_until: None, muted_until: None, flags: 0, diff --git a/src/common/interserver.rs b/src/common/interserver.rs index cef0fd4..60a9700 100644 --- a/src/common/interserver.rs +++ b/src/common/interserver.rs @@ -31,6 +31,7 @@ pub enum LoginMessage { ships: Vec, }, RequestUsers, + CreatedTeam(UserAccountId, TeamEntityId), } #[derive(Debug, Serialize, Deserialize)] @@ -45,6 +46,7 @@ pub enum ShipMessage { RequestShipList, AddUser(UserAccountId), RemoveUser(UserAccountId), + CreateTeam(UserAccountId, String), } pub enum InterserverMessage { diff --git a/src/entity/account.rs b/src/entity/account.rs index 8a4acf6..5882244 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 super::team::TeamEntityId; const GUILDCARD_OFFSET: u32 = 2300000; @@ -20,12 +21,11 @@ pub struct NewUserAccountEntity { pub email: String, pub username: String, pub password: String, - pub guildcard: u32, - pub team_id: Option, + pub guildcard: u32, // TODO: remove this, guildcard is based on account id pub banned_until: Option>, pub muted_until: Option>, pub flags: u32, - pub activated: bool + pub activated: bool, } impl Default for NewUserAccountEntity { @@ -35,7 +35,6 @@ impl Default for NewUserAccountEntity { username: "".into(), password: "".into(), guildcard: 0xFFFFFFFF, - team_id: None, banned_until: None, muted_until: None, flags: 0, @@ -50,7 +49,7 @@ pub struct UserAccountEntity { pub username: String, pub password: String, pub guildcard: u32, - pub team_id: Option, + pub team_id: Option, pub banned_until: Option>, pub muted_until: Option>, pub created_at: chrono::DateTime, diff --git a/src/entity/gateway/entitygateway.rs b/src/entity/gateway/entitygateway.rs index 75518e1..61535da 100644 --- a/src/entity/gateway/entitygateway.rs +++ b/src/entity/gateway/entitygateway.rs @@ -5,6 +5,7 @@ use futures::Future; use crate::entity::account::*; use crate::entity::character::*; use crate::entity::item::*; +use crate::entity::team::*; // TODO: better granularity? @@ -13,6 +14,7 @@ use crate::entity::item::*; #[error("")] pub enum GatewayError { Error, + NotFound, PgError(#[from] sqlx::Error) } @@ -148,6 +150,26 @@ pub trait EntityGateway: Send + Sync { async fn set_character_playtime(&mut self, _char_id: &CharacterEntityId, _playtime: u32) -> Result<(), GatewayError> { unimplemented!(); } + + async fn create_team(&mut self, _team_entity: NewTeamEntity) -> Result { + unimplemented!(); + } + + async fn get_team(&mut self, _team_entity: &TeamEntityId) -> Result { + unimplemented!(); + } + + async fn add_user_to_team(&mut self, _user: &UserAccountId, _team: &TeamEntityId) -> Result<(), GatewayError> { + unimplemented!(); + } + + async fn remove_user_from_team(&mut self, _user: &UserAccountId) -> Result<(), GatewayError> { + unimplemented!(); + } + + async fn team_point_item(&mut self, _user: &UserAccountId, _team: &TeamEntityId) -> Result<(), GatewayError> { + unimplemented!(); + } } diff --git a/src/entity/gateway/inmemory.rs b/src/entity/gateway/inmemory.rs index a813a93..11f8f0b 100644 --- a/src/entity/gateway/inmemory.rs +++ b/src/entity/gateway/inmemory.rs @@ -4,6 +4,7 @@ use futures::Future; use crate::entity::account::*; use crate::entity::character::*; +use crate::entity::team::*; use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError}; use crate::entity::item::*; @@ -223,6 +224,7 @@ pub struct InMemoryGateway { mag_modifiers: Arc>>>, weapon_modifiers: Arc>>>, trades: Arc>>, + teams: Arc>>, } impl Default for InMemoryGateway { @@ -240,6 +242,7 @@ impl Default for InMemoryGateway { mag_modifiers: Arc::new(Mutex::new(BTreeMap::new())), weapon_modifiers: Arc::new(Mutex::new(BTreeMap::new())), trades: Arc::new(Mutex::new(Vec::new())), + teams: Arc::new(Mutex::new(BTreeMap::new())), } } } @@ -323,6 +326,7 @@ impl EntityGateway for InMemoryGateway { let mag_modifiers = self.mag_modifiers.lock().await.clone(); let weapon_modifiers = self.weapon_modifiers.lock().await.clone(); let trades = self.trades.lock().await.clone(); + let teams = self.teams.lock().await.clone(); let working_gateway = InMemoryGateway { users: Arc::new(Mutex::new(users)), @@ -337,6 +341,7 @@ impl EntityGateway for InMemoryGateway { mag_modifiers: Arc::new(Mutex::new(mag_modifiers)), weapon_modifiers: Arc::new(Mutex::new(weapon_modifiers)), trades: Arc::new(Mutex::new(trades)), + teams: Arc::new(Mutex::new(teams)), }; let transaction = InMemoryGatewayTransaction { @@ -361,7 +366,7 @@ impl EntityGateway for InMemoryGateway { username: user.username, password: user.password, guildcard: user.guildcard, - team_id: user.team_id, + team_id: None, banned_until: user.banned_until, muted_until: user.muted_until, created_at: chrono::Utc::now(), @@ -627,4 +632,63 @@ impl EntityGateway for InMemoryGateway { Err(GatewayError::Error) } } + + async fn create_team(&mut self, team_entity: NewTeamEntity) -> Result { + let new_team = { + let mut teams = self.teams.lock().await; + let id = teams + .iter() + .fold(0, |sum, (i, _)| std::cmp::max(sum, i.0)) + + 1; + + let new_team = TeamEntity { + id: TeamEntityId(id), + owner: team_entity.created_by, + name: team_entity.name, + flag: [0; 2048], + }; + + teams.insert(new_team.id, new_team.clone()); + new_team + }; + + self.add_user_to_team(&team_entity.created_by, &new_team.id).await.unwrap(); + Ok(new_team) + } + + async fn get_team(&mut self, team_id: &TeamEntityId) -> Result { + let teams = self.teams.lock().await; + if let Some(team) = teams.get(team_id) { + Ok(team.clone()) + } + else { + Err(GatewayError::Error) + } + } + + async fn add_user_to_team(&mut self, user_id: &UserAccountId, team: &TeamEntityId) -> Result<(), GatewayError> { + let mut users = self.users.lock().await; + if let Some(user) = users.get_mut(user_id) { + user.team_id = Some(*team); + Ok(()) + } + else { + Err(GatewayError::Error) + } + } + + async fn remove_user_from_team(&mut self, user_id: &UserAccountId) -> Result<(), GatewayError> { + let mut users = self.users.lock().await; + if let Some(user) = users.get_mut(user_id) { + user.team_id = None; + Ok(()) + } + else { + Err(GatewayError::Error) + } + } + + async fn team_point_item(&mut self, _user: &UserAccountId, _team: &TeamEntityId) -> Result<(), GatewayError> { + unimplemented!(); + } } diff --git a/src/entity/mod.rs b/src/entity/mod.rs index ba0b6e4..1e0cc92 100644 --- a/src/entity/mod.rs +++ b/src/entity/mod.rs @@ -2,3 +2,4 @@ pub mod gateway; pub mod account; pub mod character; pub mod item; +pub mod team; diff --git a/src/login/character.rs b/src/login/character.rs index de00ae0..7eb443a 100644 --- a/src/login/character.rs +++ b/src/login/character.rs @@ -12,27 +12,27 @@ use libpso::packet::login::*; use libpso::packet::ship::{MenuDetail, SmallLeftDialog}; use libpso::{PacketParseError, PSOPacket}; use libpso::crypto::bb::PSOBBCipher; -use crate::entity::item; use libpso::character::character; use crate::common::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY}; use crate::common::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId}; -use crate::common::interserver::{ServerId, InterserverActor, LoginMessage, ShipMessage, Ship}; +use crate::common::interserver::{ServerId, InterserverActor, LoginMessage, ShipMessage, Ship, AuthToken, InterserverMessage}; use crate::common::leveltable::LEVEL_TABLE; use libpso::{utf8_to_array, utf8_to_utf16_array}; use crate::entity::gateway::{EntityGateway, GatewayError}; use crate::entity::account::{UserAccountId, UserAccountEntity, NewUserSettingsEntity, USERFLAG_NEWCHAR, USERFLAG_DRESSINGROOM}; +use crate::entity::item; use crate::entity::item::{NewItemEntity, ItemDetail, ItemNote, InventoryItemEntity, InventoryEntity, BankEntity, BankName, EquippedEntity, Meseta}; use crate::entity::item::weapon::Weapon; use crate::entity::item::armor::Armor; use crate::entity::item::tech::Technique; use crate::entity::item::tool::Tool; use crate::entity::item::mag::Mag; +use crate::entity::team::{TeamEntityId, NewTeamEntity}; use crate::entity::character::{CharacterEntity, NewCharacterEntity, CharacterClass, TechLevel}; use crate::login::login::{get_login_status}; -use crate::common::interserver::AuthToken; pub const CHARACTER_PORT: u16 = 12001; pub const SHIP_MENU_ID: u32 = 1; @@ -343,7 +343,7 @@ impl CharacterServerState { let mut response = LoginResponse::by_status(AccountStatus::Ok, Session::new()); response.guildcard = user.guildcard; - response.team_id = user.team_id.map_or(0, |ti| ti); + response.team_id = user.team_id.map_or(0, |ti| ti.0); let mut client = self.clients.write().await; let client = client.get_mut(&id).ok_or_else(|| CharacterError::ClientNotFound(id))?; @@ -427,7 +427,7 @@ impl CharacterServerState { client.session.action = SessionAction::SelectCharacter; client.session.character_slot = select.slot as u8; Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_char_select(user.guildcard, - user.team_id.unwrap_or(1), + user.team_id.unwrap_or(TeamEntityId(1)).0, // TODO: why is this 1? client.session)), SendCharacterPacket::CharAck(CharAck { slot: select.slot, @@ -520,7 +520,7 @@ impl CharacterServerState { user.flags = 0; self.entity_gateway.save_user(user).await.unwrap(); Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_char_select(user.guildcard, - user.team_id.unwrap_or(1), + user.team_id.unwrap_or(TeamEntityId(1)).0, // TODO: why is this 1? client.session)), SendCharacterPacket::CharAck(CharAck { slot: preview.slot, @@ -723,6 +723,15 @@ impl InterserverActor for CharacterServerState { ShipMessage::SendMail{..} => { Ok(Vec::new()) }, + ShipMessage::CreateTeam(user_id, team_name) => { + let team = self.entity_gateway.create_team(NewTeamEntity { + created_by: user_id, + name: team_name.clone(), + }).await.unwrap(); // TODO: unwrap + self.entity_gateway.add_user_to_team(&user_id, &team.id).await.unwrap(); // TODO: unwrap + + Ok(vec![InterserverMessage::Server(id, LoginMessage::CreatedTeam(user_id, team.id))]) + }, } } diff --git a/src/ship/client.rs b/src/ship/client.rs index 795c880..6c5f9e1 100644 --- a/src/ship/client.rs +++ b/src/ship/client.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use async_std::sync::{Arc, RwLock, RwLockReadGuard}; use futures::future::BoxFuture; +use futures::stream::{FuturesOrdered, Stream, StreamExt}; use libpso::packet::ship::*; use libpso::packet::login::Session; @@ -16,7 +17,6 @@ use crate::ship::items; use crate::ship::map::MapArea; use crate::ship::shops::{WeaponShopItem, ToolShopItem, ArmorShopItem}; - #[derive(Clone, Default)] pub struct Clients(Arc>>>); @@ -101,6 +101,34 @@ impl Clients { Ok(func(&mut client).await) } + + pub async fn with_match<'a, P, F, T>(&'a self, pred: P, func: F) -> Result + where + P: Fn(&ClientState) -> bool, + F: for<'b> FnOnce(ClientId, &'b ClientState) -> BoxFuture<'b, T> + Send + 'a, + { + for (id, client) in self.0.read().await.iter() { + let client = client.read().await; + if pred(&*client) { + return Ok(func(*id, &*client).await) + } + } + Err(ShipError::ClientNotFound(ClientId(0xFFFFFFFF))) + } + + pub async fn with_match_mut<'a, P, F, T>(&'a self, pred: P, func: F) -> Result + where + P: Fn(&ClientState) -> bool, + F: for<'b> FnOnce(ClientId, &'b mut ClientState) -> BoxFuture<'b, T> + Send + 'a, + { + for (id, client) in self.0.read().await.iter() { + let mut client = client.write().await; + if pred(&*client) { + return Ok(func(*id, &mut *client).await) + } + } + Err(ShipError::ClientNotFound(ClientId(0xFFFFFFFF))) + } } diff --git a/src/ship/mod.rs b/src/ship/mod.rs index 3bc573b..ec5e689 100644 --- a/src/ship/mod.rs +++ b/src/ship/mod.rs @@ -13,3 +13,4 @@ pub mod packet; pub mod quests; pub mod shops; pub mod trade; +pub mod teams; diff --git a/src/ship/packet/builder/mod.rs b/src/ship/packet/builder/mod.rs index 5e17e00..6b08b4f 100644 --- a/src/ship/packet/builder/mod.rs +++ b/src/ship/packet/builder/mod.rs @@ -3,6 +3,7 @@ pub mod message; pub mod room; pub mod quest; pub mod ship; +pub mod team; use libpso::character::character::Inventory; use libpso::packet::ship::{PlayerHeader, PlayerInfo}; diff --git a/src/ship/packet/handler/auth.rs b/src/ship/packet/handler/auth.rs index 4e096bd..09fadaf 100644 --- a/src/ship/packet/handler/auth.rs +++ b/src/ship/packet/handler/auth.rs @@ -1,3 +1,4 @@ +use async_std::sync::{Arc, RwLock}; use libpso::packet::login::{Login, LoginResponse, AccountStatus, Session}; use libpso::packet::ship::*; use crate::common::serverstate::ClientId; @@ -24,7 +25,7 @@ where Ok(user) => { let mut response = LoginResponse::by_status(AccountStatus::Ok, Session::new()); response.guildcard = user.id.0; - response.team_id = user.team_id.map_or(31, |ti| ti); + response.team_id = user.team_id.map_or(31, |ti| ti.0); // TODO: why is this 31? let characters = entity_gateway.get_characters_by_user(&user).await?; let character = characters .get(pkt.session.character_slot as usize) diff --git a/src/ship/packet/handler/mod.rs b/src/ship/packet/handler/mod.rs index c921d28..e949bad 100644 --- a/src/ship/packet/handler/mod.rs +++ b/src/ship/packet/handler/mod.rs @@ -7,4 +7,5 @@ pub mod room; pub mod settings; pub mod quest; pub mod ship; +pub mod teams; pub mod trade; diff --git a/src/ship/ship.rs b/src/ship/ship.rs index 544c5c8..28aded0 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -34,6 +34,7 @@ use crate::ship::map::{MapsError, MapAreaError}; use crate::ship::packet::handler; use crate::ship::shops::{WeaponShop, ToolShop, ArmorShop}; use crate::ship::trade::TradeState; +use crate::ship::teams::Teams; // TODO: remove once stuff settles down pub use crate::ship::client::*; @@ -160,6 +161,8 @@ pub enum ShipError { RoomCreationError(#[from] room::RoomCreationError), #[error("channel send error {0}")] SendError(#[from] async_std::channel::SendError), + #[error("team {0}")] + TeamError(#[from] crate::ship::teams::TeamError), } impl> From for ShipError { @@ -204,6 +207,7 @@ pub enum RecvShipPacket { KeyboardConfig(KeyboardConfig), GamepadConfig(GamepadConfig), UpdateConfig(UpdateConfig), + CreateTeam(CreateTeam), } impl RecvServerPacket for RecvShipPacket { @@ -247,6 +251,7 @@ impl RecvServerPacket for RecvShipPacket { 0x4ED => Ok(RecvShipPacket::KeyboardConfig(KeyboardConfig::from_bytes(data)?)), 0x5ED => Ok(RecvShipPacket::GamepadConfig(GamepadConfig::from_bytes(data)?)), 0x7ED => Ok(RecvShipPacket::UpdateConfig(UpdateConfig::from_bytes(data)?)), + 0x1EA => Ok(RecvShipPacket::CreateTeam(CreateTeam::from_bytes(data)?)), _ => Err(PacketParseError::WrongPacketForServerType(u16::from_le_bytes([data[2], data[3]]), data.to_vec())) } } @@ -267,6 +272,7 @@ pub enum SendShipPacket { PlayerChat(PlayerChat), SmallDialog(SmallDialog), SmallLeftDialog(SmallLeftDialog), + LargeDialog(LargeDialog), JoinRoom(JoinRoom), AddToRoom(AddToRoom), LeaveLobby(LeaveLobby), @@ -292,6 +298,10 @@ pub enum SendShipPacket { CancelTrade(CancelTrade), TradeSuccessful(TradeSuccessful), LobbyEvent(LobbyEvent), + //TeamActionResponse(TeamActionResponse), + CreateTeamResponse(CreateTeamResponse), + ClientTeamStateChanged(ClientTeamStateChanged), + TeamInfo(Box), } impl SendServerPacket for SendShipPacket { @@ -310,6 +320,7 @@ impl SendServerPacket for SendShipPacket { SendShipPacket::PlayerChat(pkt) => pkt.as_bytes(), SendShipPacket::SmallDialog(pkt) => pkt.as_bytes(), SendShipPacket::SmallLeftDialog(pkt) => pkt.as_bytes(), + SendShipPacket::LargeDialog(pkt) => pkt.as_bytes(), SendShipPacket::JoinRoom(pkt) => pkt.as_bytes(), SendShipPacket::AddToRoom(pkt) => pkt.as_bytes(), SendShipPacket::LeaveLobby(pkt) => pkt.as_bytes(), @@ -335,6 +346,9 @@ impl SendServerPacket for SendShipPacket { SendShipPacket::CancelTrade(pkt) => pkt.as_bytes(), SendShipPacket::TradeSuccessful(pkt) => pkt.as_bytes(), SendShipPacket::LobbyEvent(pkt) => pkt.as_bytes(), + SendShipPacket::CreateTeamResponse(pkt) => pkt.as_bytes(), + SendShipPacket::ClientTeamStateChanged(pkt) => pkt.as_bytes(), + SendShipPacket::TeamInfo(pkt) => pkt.as_bytes(), } } } @@ -437,9 +451,11 @@ impl ShipServerStateBuilder { pub fn build(self) -> ShipServerState { let blocks = std::iter::repeat_with(Block::default).take(self.num_blocks).collect(); // Block doesn't have a Clone impl which limits the easy ways to init this + let entity_gateway = self.entity_gateway.unwrap(); + let clients = Clients::default(); ShipServerState { - entity_gateway: self.entity_gateway.unwrap(), - clients: Clients::default(), + entity_gateway: entity_gateway.clone(), + clients: clients.clone(), name: self.name.unwrap_or_else(|| "NAMENOTSET".into()), item_state: items::state::ItemState::default(), ip: self.ip.unwrap_or_else(|| Ipv4Addr::new(127,0,0,1)), @@ -447,6 +463,7 @@ impl ShipServerStateBuilder { shops: ItemShops::default(), blocks: Blocks(blocks), event: self.event.unwrap_or(ShipEvent::None), + teams: Teams::new(entity_gateway, clients), auth_token: self.auth_token.unwrap_or_else(|| AuthToken("".into())), ship_list: Arc::new(RwLock::new(Vec::new())), @@ -487,6 +504,7 @@ pub struct ShipServerState { shops: ItemShops, pub blocks: Blocks, event: ShipEvent, + teams: Teams, ip: Ipv4Addr, port: u16, @@ -803,6 +821,9 @@ impl ServerState for ShipServerState { RecvShipPacket::GamepadConfig(gamepad_config) => { handler::settings::gamepad_config(id, gamepad_config, &self.clients, &mut self.entity_gateway).await? }, + RecvShipPacket::CreateTeam(create_team) => { + handler::teams::create_team(id, create_team, &mut self.entity_gateway, &self.clients, &mut self.shipgate_sender).await? + }, }) } @@ -884,7 +905,43 @@ impl InterserverActor for ShipServerState { */ // TODO Ok(Vec::new()) - } + }, + LoginMessage::CreatedTeam(user_id, team_id) => { + let client_id = self.clients.with_match_mut(|c| c.user.id == user_id, |client_id, client| Box::pin(async move { + client.user.team_id = Some(team_id); + client_id + })).await?; + let team_pkts = self.clients.with(client_id, |client| { + let mut teams = self.teams.clone(); + Box::pin(async move { + let team_pkts = teams.get_team(client_id).await + .and_then(|team| { + let team = team.ok_or_else(|| super::teams::TeamError::ClientHasNoTeam(client_id))?; + Ok(( + crate::ship::packet::builder::team::team_info(client_id, client, &team), + crate::ship::packet::builder::team::client_team_state_changed(client_id, client, &team), + )) + }); + team_pkts + }) + }).await?; + + match team_pkts { + Ok((team_info, client_team_state_changed)) => { + Ok(vec![ + InterserverMessage::Client(client_id, SendShipPacket::CreateTeamResponse(CreateTeamResponse::success())), + InterserverMessage::Client(client_id, SendShipPacket::TeamInfo(Box::new(team_info))), // TODO: send to neighbors + InterserverMessage::Client(client_id, SendShipPacket::ClientTeamStateChanged(client_team_state_changed)), + ]) + }, + Err(err) => { + Ok(vec![ + InterserverMessage::Client(client_id, SendShipPacket::CreateTeamResponse(CreateTeamResponse::failure())), + //InterserverMessage::Client(client_id, SendShipPacket::LargeDialog(LargeDialog::new(format!("failed to create team: {:?}", err)))), + ]) + } + } + }, } } @@ -893,8 +950,6 @@ impl InterserverActor for ShipServerState { } async fn set_sender(&mut self, _server_id: ServerId, sender: channel::Sender) { - dbg!("setting sender!"); - //self.shipgate_sender = Arc::new(Some(sender)); *self.shipgate_sender.write().await = Some(sender); } }