|
|
@ -6,6 +6,7 @@ use rand::Rng; |
|
|
|
use crc::{crc32, Hasher32};
|
|
|
|
|
|
|
|
use libpso::packet::login::*;
|
|
|
|
use libpso::packet::ship::{MenuDetail, SmallLeftDialog};
|
|
|
|
use libpso::{PacketParseError, PSOPacket};
|
|
|
|
use libpso::crypto::bb::PSOBBCipher;
|
|
|
|
use crate::entity::item;
|
|
|
@ -18,7 +19,7 @@ use crate::common::leveltable::CharacterLevelTable; |
|
|
|
use libpso::{utf8_to_array, utf8_to_utf16_array};
|
|
|
|
|
|
|
|
use crate::entity::gateway::{EntityGateway, GatewayError};
|
|
|
|
use crate::entity::account::{UserAccountEntity, NewUserSettingsEntity, USERFLAG_NEWCHAR, USERFLAG_DRESSINGROOM};
|
|
|
|
use crate::entity::account::{UserAccountId, UserAccountEntity, NewUserSettingsEntity, USERFLAG_NEWCHAR, USERFLAG_DRESSINGROOM};
|
|
|
|
use crate::entity::item::{NewItemEntity, ItemDetail, ItemLocation, InventoryItemEntity, InventoryEntity, BankEntity, BankName, EquippedEntity};
|
|
|
|
use crate::entity::item::weapon::Weapon;
|
|
|
|
use crate::entity::item::armor::Armor;
|
|
|
@ -57,6 +58,7 @@ pub enum RecvCharacterPacket { |
|
|
|
CharacterPreview(CharacterPreview),
|
|
|
|
SetFlag(SetFlag),
|
|
|
|
MenuSelect(MenuSelect),
|
|
|
|
MenuDetail(MenuDetail),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl RecvServerPacket for RecvCharacterPacket {
|
|
|
@ -73,6 +75,7 @@ impl RecvServerPacket for RecvCharacterPacket { |
|
|
|
0xE5 => Ok(RecvCharacterPacket::CharacterPreview(CharacterPreview::from_bytes(data)?)),
|
|
|
|
0xEC => Ok(RecvCharacterPacket::SetFlag(SetFlag::from_bytes(data)?)),
|
|
|
|
0x10 => Ok(RecvCharacterPacket::MenuSelect(MenuSelect::from_bytes(data)?)),
|
|
|
|
0x09 => Ok(RecvCharacterPacket::MenuDetail(MenuDetail::from_bytes(data)?)),
|
|
|
|
_ => Err(PacketParseError::WrongPacketForServerType(u16::from_le_bytes([data[2], data[3]]), data.to_vec()))
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -94,6 +97,7 @@ pub enum SendCharacterPacket { |
|
|
|
Timestamp(Timestamp),
|
|
|
|
ShipList(ShipList),
|
|
|
|
RedirectClient(RedirectClient),
|
|
|
|
SmallLeftDialog(SmallLeftDialog),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SendServerPacket for SendCharacterPacket {
|
|
|
@ -112,6 +116,7 @@ impl SendServerPacket for SendCharacterPacket { |
|
|
|
SendCharacterPacket::Timestamp(pkt) => pkt.as_bytes(),
|
|
|
|
SendCharacterPacket::ShipList(pkt) => pkt.as_bytes(),
|
|
|
|
SendCharacterPacket::RedirectClient(pkt) => pkt.as_bytes(),
|
|
|
|
SendCharacterPacket::SmallLeftDialog(pkt) => pkt.as_bytes(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -168,6 +173,11 @@ impl ClientState { |
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
struct ConnectedClient {
|
|
|
|
ship_id: Option<ServerId>,
|
|
|
|
expires: Option<chrono::DateTime<chrono::Utc>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct CharacterServerState<EG: EntityGateway> {
|
|
|
|
entity_gateway: EG,
|
|
|
@ -177,6 +187,8 @@ pub struct CharacterServerState<EG: EntityGateway> { |
|
|
|
ships: BTreeMap<ServerId, Ship>,
|
|
|
|
level_table: CharacterLevelTable,
|
|
|
|
auth_token: AuthToken,
|
|
|
|
|
|
|
|
connected_clients: BTreeMap<UserAccountId, ConnectedClient>,
|
|
|
|
authenticated_ships: BTreeSet<ServerId>,
|
|
|
|
ship_sender: BTreeMap<ServerId, Box<dyn Fn(LoginMessage) + Send>>,
|
|
|
|
}
|
|
|
@ -298,6 +310,7 @@ impl<EG: EntityGateway> CharacterServerState<EG> { |
|
|
|
auth_token: auth_token,
|
|
|
|
authenticated_ships: BTreeSet::new(),
|
|
|
|
ship_sender: BTreeMap::new(),
|
|
|
|
connected_clients: BTreeMap::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
@ -306,23 +319,35 @@ impl<EG: EntityGateway> CharacterServerState<EG> { |
|
|
|
}
|
|
|
|
|
|
|
|
async fn validate_login(&mut self, id: ClientId, pkt: &Login) -> Result<Vec<SendCharacterPacket>, anyhow::Error> {
|
|
|
|
let client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?;
|
|
|
|
Ok(match get_login_status(&self.entity_gateway, pkt).await.and_then(check_if_already_online) {
|
|
|
|
Ok(mut user) => {
|
|
|
|
user.at_character = true;
|
|
|
|
self.entity_gateway.save_user(&user).await?;
|
|
|
|
match get_login_status(&self.entity_gateway, pkt).await {
|
|
|
|
Ok(user) => {
|
|
|
|
if let Some(connected_client) = self.connected_clients.get(&user.id) {
|
|
|
|
if let Some(expires) = connected_client.expires {
|
|
|
|
if expires < chrono::Utc::now() {
|
|
|
|
return Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(AccountStatus::AlreadyOnline, Session::new()))]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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) as u32;
|
|
|
|
|
|
|
|
let client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?;
|
|
|
|
|
|
|
|
self.connected_clients.insert(user.id.clone(), ConnectedClient {
|
|
|
|
ship_id: None,
|
|
|
|
expires: Some(chrono::Utc::now() + chrono::Duration::minutes(1)),
|
|
|
|
});
|
|
|
|
|
|
|
|
client.user = Some(user);
|
|
|
|
client.session = pkt.session;
|
|
|
|
vec![SendCharacterPacket::LoginResponse(response)]
|
|
|
|
Ok(vec![SendCharacterPacket::LoginResponse(response)])
|
|
|
|
},
|
|
|
|
Err(err) => {
|
|
|
|
vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(err, Session::new()))]
|
|
|
|
Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(err, Session::new()))])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
fn send_ship_list(&mut self, _id: ClientId, _pkt: &Login) -> Result<Vec<SendCharacterPacket>, anyhow::Error> {
|
|
|
@ -493,6 +518,16 @@ impl<EG: EntityGateway> CharacterServerState<EG> { |
|
|
|
.ok_or(CharacterError::InvalidMenuSelection(menuselect.menu, menuselect.item))?;
|
|
|
|
Ok(vec![SendCharacterPacket::RedirectClient(RedirectClient::new(u32::from_le_bytes(ship.ip.octets()), ship.port))])
|
|
|
|
}
|
|
|
|
|
|
|
|
fn ship_detail(&mut self, menudetail: &MenuDetail) -> Result<Vec<SendCharacterPacket>, anyhow::Error> {
|
|
|
|
let players = self.connected_clients.iter()
|
|
|
|
.filter(|(_, client)| {
|
|
|
|
client.ship_id == Some(ServerId(menudetail.item as usize))
|
|
|
|
})
|
|
|
|
.count();
|
|
|
|
let ship_details = format!("players: {}\nrooms: {}", players, 0);
|
|
|
|
Ok(vec![SendCharacterPacket::SmallLeftDialog(SmallLeftDialog::new(ship_details))])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[async_trait::async_trait]
|
|
|
@ -557,6 +592,12 @@ impl<EG: EntityGateway> ServerState for CharacterServerState<EG> { |
|
|
|
},
|
|
|
|
RecvCharacterPacket::MenuSelect(menuselect) => {
|
|
|
|
Box::new(self.select_ship(menuselect)?.into_iter().map(move |pkt| (id, pkt)))
|
|
|
|
},
|
|
|
|
RecvCharacterPacket::MenuDetail(menudetail) => {
|
|
|
|
match menudetail.menu {
|
|
|
|
SHIP_MENU_ID => Box::new(self.ship_detail(menudetail)?.into_iter().map(move |pkt| (id, pkt))),
|
|
|
|
_ => Box::new(Vec::new().into_iter())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
@ -588,20 +629,59 @@ impl<EG: EntityGateway> InterserverActor for CharacterServerState<EG> { |
|
|
|
if self.auth_token == auth_token {
|
|
|
|
self.authenticated_ships.insert(id);
|
|
|
|
}
|
|
|
|
Ok(Vec::new())
|
|
|
|
},
|
|
|
|
ShipMessage::NewShip(new_ship) => {
|
|
|
|
if self.authenticated_ships.contains(&id) {
|
|
|
|
self.ships.insert(id, new_ship);
|
|
|
|
}
|
|
|
|
Ok(Vec::new())
|
|
|
|
},
|
|
|
|
_ => {}
|
|
|
|
ShipMessage::AddUser(new_user) => {
|
|
|
|
if self.authenticated_ships.contains(&id) {
|
|
|
|
self.connected_clients.insert(new_user, ConnectedClient {
|
|
|
|
ship_id: Some(id),
|
|
|
|
expires: None,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
Ok(Vec::new())
|
|
|
|
},
|
|
|
|
ShipMessage::RemoveUser(new_user) => {
|
|
|
|
if self.authenticated_ships.contains(&id) {
|
|
|
|
self.connected_clients.remove(&new_user);
|
|
|
|
}
|
|
|
|
Ok(Vec::new())
|
|
|
|
},
|
|
|
|
ShipMessage::RequestShipList => {
|
|
|
|
if self.authenticated_ships.contains(&id) {
|
|
|
|
Ok(vec![(id, LoginMessage::ShipList {
|
|
|
|
ships: self.ships
|
|
|
|
.iter()
|
|
|
|
.map(|(_, ship)| {
|
|
|
|
ship
|
|
|
|
})
|
|
|
|
.cloned()
|
|
|
|
.collect()
|
|
|
|
})])
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
Ok(Vec::new())
|
|
|
|
}
|
|
|
|
},
|
|
|
|
ShipMessage::SendMail{..} => {
|
|
|
|
Ok(Vec::new())
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn on_disconnect(&mut self, id: ServerId) -> Vec<(ServerId, Self::SendMessage)> {
|
|
|
|
self.ships.remove(&id);
|
|
|
|
self.ship_sender.remove(&id);
|
|
|
|
self.connected_clients = self.connected_clients.clone().into_iter()
|
|
|
|
.filter(|(_, client)| {
|
|
|
|
client.ship_id != Some(id)
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
Vec::new()
|
|
|
|
}
|
|
|
|
}
|
|
|
|