#![allow(dead_code, unused_must_use)] use std::net::Ipv4Addr; use std::collections::HashMap; use async_std::channel; use async_std::sync::{Arc, Mutex, RwLock}; use rand::Rng; use thiserror::Error; use libpso::packet::ship::*; use libpso::packet::login::{RedirectClient, Login, LoginResponse, ShipList}; use libpso::packet::messages::*; use libpso::{PacketParseError, PSOPacket}; use libpso::crypto::bb::PSOBBCipher; use libpso::packet::ship::{BLOCK_MENU_ID, ROOM_MENU_ID}; use crate::common::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY}; use crate::common::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId}; use crate::common::interserver::{AuthToken, Ship, ServerId, InterserverActor, LoginMessage, ShipMessage}; use crate::login::character::SHIP_MENU_ID; use crate::entity::gateway::{EntityGateway, GatewayError}; use crate::entity::character::SectionID; use crate::entity::room::RoomNote; use crate::ship::location::{ClientLocation, RoomLobby, ClientLocationError, RoomId}; use crate::ship::drops::DropTable; use crate::ship::items; use crate::ship::room; use crate::ship::map::{Maps, MapsError, MapAreaError, generate_free_roam_maps}; use crate::ship::packet::handler; use crate::ship::shops::{WeaponShop, ToolShop, ArmorShop}; use crate::ship::trade::TradeState; use crate::ship::chatcommand; // TODO: remove once stuff settles down pub use crate::ship::client::*; pub const SHIP_PORT: u16 = 23423; pub const QUEST_CATEGORY_MENU_ID: u32 = 0xA2; pub const QUEST_SELECT_MENU_ID: u32 = 0xA3; #[derive(Clone, Copy)] pub enum ShipEvent { None, Christmas, Valentines, Easter, Halloween, Sonic, NewYear, Summer, White, Wedding, Fall, Spring, Summer2, Spring2, } impl From for u32 { fn from(other: ShipEvent) -> u32 { u16::from(other) as u32 } } impl From for u16 { fn from(other: ShipEvent) -> u16 { u8::from(other) as u16 } } impl From for u8 { fn from(other: ShipEvent) -> u8 { match other { ShipEvent::None => 0, ShipEvent::Christmas => 1, ShipEvent::Valentines => 3, ShipEvent::Easter => 4, ShipEvent::Halloween => 5, ShipEvent::Sonic => 6, ShipEvent::NewYear => 7, ShipEvent::Summer => 8, ShipEvent::White => 9, ShipEvent::Wedding => 10, ShipEvent::Fall => 11, ShipEvent::Spring => 12, ShipEvent::Summer2 => 13, ShipEvent::Spring2 => 14, } } } #[derive(Error, Debug)] pub enum ShipError { #[error("client not found {0}")] ClientNotFound(ClientId), #[error("no character in slot {0} {1}")] NoCharacterInSlot(ClientId, u32), #[error("invalid slot {0} {1}")] InvalidSlot(ClientId, u32), #[error("too many clients")] TooManyClients, #[error("client location {0}")] ClientLocationError(ClientLocationError), #[error("maps error {0}")] MapsError(#[from] MapsError), #[error("map area error {0}")] MapAreaError(#[from] MapAreaError), #[error("invalid room {0}")] InvalidRoom(u32), #[error("monster already droppped item {0} {1}")] MonsterAlreadyDroppedItem(ClientId, u16), #[error("slice error {0}")] SliceError(#[from] std::array::TryFromSliceError), #[error("item error")] ItemError, // TODO: refine this #[error("pick up invalid item id {0}")] PickUpInvalidItemId(u32), #[error("drop invalid item id {0}")] DropInvalidItemId(u32), #[error("item state error {0}")] ItemStateError(#[from] items::state::ItemStateError), #[error("item drop location not set")] ItemDropLocationNotSet, #[error("box already dropped item {0} {1}")] BoxAlreadyDroppedItem(ClientId, u16), #[error("invalid quest category {0}")] InvalidQuestCategory(u16), #[error("invalid quest {0}")] InvalidQuest(u16), #[error("invalid quest filename {0}")] InvalidQuestFilename(String), #[error("io error {0}")] IoError(#[from] std::io::Error), #[error("not enough meseta {0} {1}")] NotEnoughMeseta(ClientId, u32), #[error("shop error")] ShopError, #[error("gateway error {0}")] GatewayError(#[from] GatewayError), #[error("unknown monster {0}")] UnknownMonster(crate::ship::monster::MonsterType), #[error("invalid ship {0}")] InvalidShip(usize), #[error("invalid block {0}")] InvalidBlock(usize), #[error("invalid item {0}")] InvalidItem(items::ClientItemId), #[error("trade error {0}")] TradeError(#[from] crate::ship::packet::handler::trade::TradeError), #[error("trade state error {0}")] TradeStateError(#[from] crate::ship::trade::TradeStateError), #[error("message error {0}")] MessageError(#[from] crate::ship::packet::handler::direct_message::MessageError), #[error("room creation error {0}")] RoomCreationError(#[from] room::RoomCreationError), #[error("channel send error {0}")] SendError(#[from] async_std::channel::SendError), } /* impl> From for ShipError { fn from(other: I) -> ShipError { ShipError::ClientLocationError(other.into()) } } */ #[derive(Debug)] pub enum RecvShipPacket { Login(Login), MenuSelect(MenuSelect), RoomPasswordReq(RoomPasswordReq), CharData(CharData), Message(Message), DirectMessage(DirectMessage), PlayerChat(PlayerChat), CreateRoom(CreateRoom), RoomNameRequest(RoomNameRequest), ViewInfoboardRequest(ViewInfoboardRequest), WriteInfoboard(WriteInfoboard), RoomListRequest(RoomListRequest), Like62ButCooler(Like62ButCooler), ClientCharacterData(ClientCharacterData), DoneBursting(DoneBursting), DoneBursting2(DoneBursting2), LobbySelect(LobbySelect), RequestQuestList(RequestQuestList), MenuDetail(MenuDetail), QuestDetailRequest(QuestDetailRequest), QuestMenuSelect(QuestMenuSelect), QuestFileRequest(QuestFileRequest), QuestChunkAck(QuestChunkAck), DoneLoadingQuest(DoneLoadingQuest), FullCharacterData(Box), SaveOptions(SaveOptions), RequestShipList(RequestShipList), RequestShipBlockList(RequestShipBlockList), ItemsToTrade(ItemsToTrade), TradeConfirmed(TradeConfirmed), KeyboardConfig(KeyboardConfig), GamepadConfig(GamepadConfig), UpdateConfig(UpdateConfig), UpdateTechMenu(UpdateTechMenu), } impl RecvServerPacket for RecvShipPacket { fn from_bytes(data: &[u8]) -> Result { match u16::from_le_bytes([data[2], data[3]]) { 0x93 => Ok(RecvShipPacket::Login(Login::from_bytes(data)?)), 0x09 => match data[8] as u32 { QUEST_SELECT_MENU_ID => Ok(RecvShipPacket::QuestDetailRequest(QuestDetailRequest::from_bytes(data)?)), _ => Ok(RecvShipPacket::MenuDetail(MenuDetail::from_bytes(data)?)), } 0x10 => match (data[0], data[8] as u32) { (16, QUEST_SELECT_MENU_ID) => Ok(RecvShipPacket::QuestMenuSelect(QuestMenuSelect::from_bytes(data)?)), (16, _) => Ok(RecvShipPacket::MenuSelect(MenuSelect::from_bytes(data)?)), (48, _) => Ok(RecvShipPacket::RoomPasswordReq(RoomPasswordReq::from_bytes(data)?)), _ => Err(PacketParseError::WrongPacketForServerType(u16::from_le_bytes([data[2], data[3]]), data.to_vec())), }, 0x13 => Ok(RecvShipPacket::QuestChunkAck(QuestChunkAck::from_bytes(data)?)), 0x44 => Ok(RecvShipPacket::QuestFileRequest(QuestFileRequest::from_bytes(data)?)), 0x61 => Ok(RecvShipPacket::CharData(CharData::from_bytes(data)?)), 0x60 => Ok(RecvShipPacket::Message(Message::from_bytes(data)?)), 0x62 => Ok(RecvShipPacket::DirectMessage(DirectMessage::from_bytes(data)?)), 0x06 => Ok(RecvShipPacket::PlayerChat(PlayerChat::from_bytes(data)?)), 0xC1 => Ok(RecvShipPacket::CreateRoom(CreateRoom::from_bytes(data)?)), 0x8A => Ok(RecvShipPacket::RoomNameRequest(RoomNameRequest::from_bytes(data)?)), 0xD8 => Ok(RecvShipPacket::ViewInfoboardRequest(ViewInfoboardRequest::from_bytes(data)?)), 0xD9 => Ok(RecvShipPacket::WriteInfoboard(WriteInfoboard::from_bytes(data)?)), 0x08 => Ok(RecvShipPacket::RoomListRequest(RoomListRequest::from_bytes(data)?)), 0x6D => Ok(RecvShipPacket::Like62ButCooler(Like62ButCooler::from_bytes(data)?)), 0x98 => Ok(RecvShipPacket::ClientCharacterData(ClientCharacterData::from_bytes(data)?)), 0x6F => Ok(RecvShipPacket::DoneBursting(DoneBursting::from_bytes(data)?)), 0x16F => Ok(RecvShipPacket::DoneBursting2(DoneBursting2::from_bytes(data)?)), 0x84 => Ok(RecvShipPacket::LobbySelect(LobbySelect::from_bytes(data)?)), 0xA0 => Ok(RecvShipPacket::RequestShipList(RequestShipList::from_bytes(data)?)), 0xA1 => Ok(RecvShipPacket::RequestShipBlockList(RequestShipBlockList::from_bytes(data)?)), 0xA2 => Ok(RecvShipPacket::RequestQuestList(RequestQuestList::from_bytes(data)?)), 0xAC => Ok(RecvShipPacket::DoneLoadingQuest(DoneLoadingQuest::from_bytes(data)?)), 0xD0 => Ok(RecvShipPacket::ItemsToTrade(ItemsToTrade::from_bytes(data)?)), 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)?)), 0x4ED => Ok(RecvShipPacket::KeyboardConfig(KeyboardConfig::from_bytes(data)?)), 0x5ED => Ok(RecvShipPacket::GamepadConfig(GamepadConfig::from_bytes(data)?)), 0x6ED => Ok(RecvShipPacket::UpdateTechMenu(UpdateTechMenu::from_bytes(data)?)), 0x7ED => Ok(RecvShipPacket::UpdateConfig(UpdateConfig::from_bytes(data)?)), _ => Err(PacketParseError::WrongPacketForServerType(u16::from_le_bytes([data[2], data[3]]), data.to_vec())) } } } #[derive(Debug, Clone, PartialEq)] pub enum SendShipPacket { ShipWelcome(ShipWelcome), LoginResponse(LoginResponse), ShipList(ShipList), ShipBlockList(ShipBlockList), FullCharacter(Box), CharDataRequest(CharDataRequest), JoinLobby(JoinLobby), AddToLobby(AddToLobby), Message(Message), DirectMessage(DirectMessage), PlayerChat(PlayerChat), SmallDialog(SmallDialog), SmallLeftDialog(SmallLeftDialog), JoinRoom(JoinRoom), AddToRoom(AddToRoom), LeaveLobby(LeaveLobby), LeaveRoom(LeaveRoom), RoomNameResponse(RoomNameResponse), ViewInfoboardResponse(ViewInfoboardResponse), RoomListResponse(RoomListResponse), Like62ButCooler(Like62ButCooler), BurstDone72(BurstDone72), DoneBursting(DoneBursting), DoneBursting2(DoneBursting2), LobbyList(LobbyList), QuestCategoryList(QuestCategoryList), QuestOptionList(QuestOptionList), QuestDetail(QuestDetail), QuestHeader(QuestHeader), QuestChunk(QuestChunk), DoneLoadingQuest(DoneLoadingQuest), BankItemList(BankItemList), RedirectClient(RedirectClient), RareMonsterList(RareMonsterList), AcknowledgeTrade(AcknowledgeTrade), CancelTrade(CancelTrade), TradeSuccessful(TradeSuccessful), LobbyEvent(LobbyEvent), LargeDialog(LargeDialog), } impl SendServerPacket for SendShipPacket { fn as_bytes(&self) -> Vec { match self { SendShipPacket::ShipWelcome(pkt) => pkt.as_bytes(), SendShipPacket::LoginResponse(pkt) => pkt.as_bytes(), SendShipPacket::ShipList(pkt) => pkt.as_bytes(), SendShipPacket::ShipBlockList(pkt) => pkt.as_bytes(), SendShipPacket::FullCharacter(pkt) => pkt.as_bytes(), SendShipPacket::CharDataRequest(pkt) => pkt.as_bytes(), SendShipPacket::JoinLobby(pkt) => pkt.as_bytes(), SendShipPacket::AddToLobby(pkt) => pkt.as_bytes(), SendShipPacket::Message(pkt) => pkt.as_bytes(), SendShipPacket::DirectMessage(pkt) => pkt.as_bytes(), SendShipPacket::PlayerChat(pkt) => pkt.as_bytes(), SendShipPacket::SmallDialog(pkt) => pkt.as_bytes(), SendShipPacket::SmallLeftDialog(pkt) => pkt.as_bytes(), SendShipPacket::JoinRoom(pkt) => pkt.as_bytes(), SendShipPacket::AddToRoom(pkt) => pkt.as_bytes(), SendShipPacket::LeaveLobby(pkt) => pkt.as_bytes(), SendShipPacket::LeaveRoom(pkt) => pkt.as_bytes(), SendShipPacket::RoomNameResponse(pkt) => pkt.as_bytes(), SendShipPacket::ViewInfoboardResponse(pkt) => pkt.as_bytes(), SendShipPacket::RoomListResponse(pkt) => pkt.as_bytes(), SendShipPacket::Like62ButCooler(pkt) => pkt.as_bytes(), SendShipPacket::BurstDone72(pkt) => pkt.as_bytes(), SendShipPacket::DoneBursting(pkt) => pkt.as_bytes(), SendShipPacket::DoneBursting2(pkt) => pkt.as_bytes(), SendShipPacket::LobbyList(pkt) => pkt.as_bytes(), SendShipPacket::QuestCategoryList(pkt) => pkt.as_bytes(), SendShipPacket::QuestOptionList(pkt) => pkt.as_bytes(), SendShipPacket::QuestDetail(pkt) => pkt.as_bytes(), SendShipPacket::QuestHeader(pkt) => pkt.as_bytes(), SendShipPacket::QuestChunk(pkt) => pkt.as_bytes(), SendShipPacket::DoneLoadingQuest(pkt) => pkt.as_bytes(), SendShipPacket::BankItemList(pkt) => pkt.as_bytes(), SendShipPacket::RedirectClient(pkt) => pkt.as_bytes(), SendShipPacket::RareMonsterList(pkt) => pkt.as_bytes(), SendShipPacket::AcknowledgeTrade(pkt) => pkt.as_bytes(), SendShipPacket::CancelTrade(pkt) => pkt.as_bytes(), SendShipPacket::TradeSuccessful(pkt) => pkt.as_bytes(), SendShipPacket::LobbyEvent(pkt) => pkt.as_bytes(), SendShipPacket::LargeDialog(pkt) => pkt.as_bytes(), } } } #[derive(Clone)] pub struct ItemShops { pub weapon_shop: HashMap<(room::Difficulty, SectionID), Arc>>>, pub tool_shop: Arc>>, pub armor_shop: Arc>>, } impl Default for ItemShops { fn default() -> ItemShops { let difficulty = [room::Difficulty::Normal, room::Difficulty::Hard, room::Difficulty::VeryHard, room::Difficulty::Ultimate]; let section_id = [SectionID::Viridia, SectionID::Greenill, SectionID::Skyly, SectionID::Bluefull, SectionID::Purplenum, SectionID::Pinkal, SectionID::Redria, SectionID::Oran, SectionID::Yellowboze, SectionID::Whitill]; let mut weapon_shop = HashMap::new(); for d in difficulty.iter() { for id in section_id.iter() { weapon_shop.insert((*d, *id), Arc::new(Mutex::new(WeaponShop::new(*d, *id)))); } } ItemShops { weapon_shop, tool_shop: Arc::new(Mutex::new(ToolShop::default())), armor_shop: Arc::new(Mutex::new(ArmorShop::default())), } } } pub struct ShipServerStateBuilder { entity_gateway: Option, name: Option, ip: Option, port: Option, auth_token: Option, event: Option, map_builder: Option Maps + Send + Sync>>, drop_table_builder: Option DropTable + Send + Sync>>, num_blocks: usize, } impl Default for ShipServerStateBuilder { fn default() -> ShipServerStateBuilder { ShipServerStateBuilder { entity_gateway: None, name: None, ip: None, port: None, auth_token: None, event: None, map_builder: None, drop_table_builder: None, num_blocks: 2, } } } impl ShipServerStateBuilder { #[must_use] pub fn gateway(mut self, entity_gateway: EG) -> ShipServerStateBuilder { self.entity_gateway = Some(entity_gateway); self } #[must_use] pub fn name(mut self, name: String) -> ShipServerStateBuilder { self.name = Some(name); self } #[must_use] pub fn ip(mut self, ip: Ipv4Addr) -> ShipServerStateBuilder { self.ip = Some(ip); self } #[must_use] pub fn port(mut self, port: u16) -> ShipServerStateBuilder { self.port = Some(port); self } #[must_use] pub fn auth_token(mut self, auth_token: AuthToken) -> ShipServerStateBuilder { self.auth_token = Some(auth_token); self } #[must_use] pub fn event(mut self, event: ShipEvent) -> ShipServerStateBuilder { self.event = Some(event); self } #[must_use] pub fn map_builder(mut self, map_builder: Box Maps + Send + Sync>) -> ShipServerStateBuilder { self.map_builder = Some(map_builder); self } #[must_use] pub fn drop_table_builder(mut self, drop_table_builder: Box DropTable + Send + Sync>) -> ShipServerStateBuilder { self.drop_table_builder = Some(drop_table_builder); self } #[must_use] pub fn blocks(mut self, num_blocks: usize) -> ShipServerStateBuilder { self.num_blocks = num_blocks; self } 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 ShipServerState { entity_gateway: self.entity_gateway.unwrap(), clients: Clients::default(), 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)), port: self.port.unwrap_or(SHIP_PORT), shops: ItemShops::default(), blocks: Blocks(blocks), event: self.event.unwrap_or(ShipEvent::None), map_builder: Arc::new(self.map_builder.unwrap_or(Box::new(generate_free_roam_maps))), drop_table_builder: Arc::new(self.drop_table_builder.unwrap_or(Box::new(DropTable::new))), auth_token: self.auth_token.unwrap_or_else(|| AuthToken("".into())), ship_list: Arc::new(RwLock::new(Vec::new())), shipgate_sender: None, trades: Default::default(), } } } #[derive(Clone, Default)] pub struct Block { client_location: ClientLocation, pub rooms: room::Rooms, } #[derive(Clone)] pub struct Blocks(pub Vec); impl Blocks { async fn get_from_client(&mut self, id: ClientId, clients: &Clients) -> Result<&mut Block, anyhow::Error> { let block = clients.with(id, |client| Box::pin(async move { client.block })).await?; self.0 .get_mut(block) .ok_or_else(|| ShipError::InvalidBlock(block).into()) } } #[derive(Clone)] pub struct ShipServerState { pub(crate) entity_gateway: EG, pub clients: Clients, name: String, pub(crate) item_state: items::state::ItemState, shops: ItemShops, pub blocks: Blocks, event: ShipEvent, ip: Ipv4Addr, port: u16, auth_token: AuthToken, ship_list: Arc>>, shipgate_sender: Option>, trades: TradeState, map_builder: Arc Maps + Send + Sync>>, drop_table_builder: Arc DropTable + Send + Sync>>, } impl ShipServerState { pub fn builder() -> ShipServerStateBuilder { ShipServerStateBuilder::default() } async fn message(&mut self, id: ClientId, msg: Message) -> Result, anyhow::Error> { Ok(match msg.msg { GameMessage::RequestExp(request_exp) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::message::request_exp(id, request_exp, &mut self.entity_gateway, &block.client_location, &self.clients, &block.rooms).await? }, GameMessage::PlayerDropItem(player_drop_item) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::message::player_drop_item(id, player_drop_item, &mut self.entity_gateway, &block.client_location, &self.clients, &block.rooms, &mut self.item_state).await? }, GameMessage::DropCoordinates(drop_coordinates) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::message::drop_coordinates(id, drop_coordinates, &block.client_location, &self.clients, &block.rooms).await? }, GameMessage::PlayerNoLongerHasItem(no_longer_has_item) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::message::no_longer_has_item(id, no_longer_has_item, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_state).await? }, GameMessage::PlayerChangedMap(_) | GameMessage::PlayerChangedMap2(_) | GameMessage::TellOtherPlayerMyLocation(_) | GameMessage::PlayerWarpingToFloor(_) | GameMessage::PlayerTeleported(_) | GameMessage::PlayerStopped(_) | GameMessage::PlayerLoadedIn(_) | GameMessage::PlayerWalking(_) | GameMessage::PlayerRunning(_) | GameMessage::PlayerWarped(_) | GameMessage::PlayerChangedFloor(_) | GameMessage::InitializeSpeechNpc(_) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::message::update_player_position(id, msg, &self.clients, &block.client_location, &block.rooms).await? }, GameMessage::ChargeAttack(charge_attack) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::message::charge_attack(id, charge_attack, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_state).await? }, GameMessage::PlayerUseItem(player_use_item) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::message::player_uses_item(id, player_use_item, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_state).await? }, GameMessage::PlayerUsedMedicalCenter(player_used_medical_center) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::message::player_used_medical_center(id, player_used_medical_center, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_state).await? }, GameMessage::PlayerFeedMag(player_feed_mag) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::message::player_feed_mag(id, player_feed_mag, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_state).await? }, GameMessage::PlayerEquipItem(player_equip_item) => { handler::message::player_equips_item(id, player_equip_item, &mut self.entity_gateway, &self.clients, &mut self.item_state).await? }, GameMessage::PlayerUnequipItem(player_unequip_item) => { handler::message::player_unequips_item(id, player_unequip_item, &mut self.entity_gateway, &self.clients, &mut self.item_state).await? }, GameMessage::SortItems(sort_items) => { handler::message::player_sorts_items(id, sort_items, &mut self.entity_gateway, &self.clients, &mut self.item_state).await? }, GameMessage::PlayerSoldItem(player_sold_item) => { handler::message::player_sells_item(id, player_sold_item, &mut self.entity_gateway, &self.clients, &mut self.item_state).await? }, GameMessage::FloorItemLimitItemDeletion(floor_item_limit_delete) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::message::floor_item_limit_deletion(id, floor_item_limit_delete, &mut self.entity_gateway, &block.client_location, &self.clients, &block.rooms, &mut self.item_state).await? }, _ => { let cmsg = msg.clone(); let block = self.blocks.get_from_client(id, &self.clients).await?; block.client_location.get_client_neighbors(id).await.unwrap().into_iter() .map(move |client| { (client.client, SendShipPacket::Message(cmsg.clone())) }) .collect() }, }) } async fn direct_message(&mut self, id: ClientId, msg: DirectMessage) -> Result, anyhow::Error> { let target = msg.flag; let block = self.blocks.get_from_client(id, &self.clients).await?; Ok(match msg.msg { GameMessage::GuildcardSend(guildcard_send) => { handler::direct_message::guildcard_send(id, guildcard_send, target, &block.client_location, &self.clients).await? }, GameMessage::RequestItem(request_item) => { handler::direct_message::request_item(id, request_item, &mut self.entity_gateway, &block.client_location, &self.clients, &block.rooms, &mut self.item_state).await? }, GameMessage::PickupItem(pickup_item) => { handler::direct_message::pickup_item(id, pickup_item, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_state).await? }, GameMessage::BoxDropRequest(box_drop_request) => { handler::direct_message::request_box_item(id, box_drop_request, &mut self.entity_gateway, &block.client_location, &self.clients, &block.rooms, &mut self.item_state).await? }, GameMessage::BankRequest(_bank_request) => { handler::direct_message::send_bank_list(id, &self.clients, &mut self.item_state).await? }, GameMessage::BankInteraction(bank_interaction) => { handler::direct_message::bank_interaction(id, bank_interaction, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_state).await? }, GameMessage::ShopRequest(shop_request) => { handler::direct_message::shop_request(id, shop_request, &block.client_location, &self.clients, &block.rooms, &self.shops).await? }, GameMessage::BuyItem(buy_item) => { handler::direct_message::buy_item(id, buy_item, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_state).await? }, GameMessage::TekRequest(tek_request) => { handler::direct_message::request_tek_item(id, tek_request, &mut self.entity_gateway, &self.clients, &mut self.item_state).await? }, GameMessage::TekAccept(tek_accept) => { handler::direct_message::accept_tek_item(id, tek_accept, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_state).await? }, GameMessage::TradeRequest(trade_request) => { handler::trade::trade_request(id, trade_request, target, &block.client_location, &self.clients, &mut self.item_state, &mut self.trades).await? }, _ => { let cmsg = msg.clone(); block.client_location.get_all_clients_by_client(id).await.unwrap().into_iter() .filter(move |client| client.local_client.id() == target as u8) .map(move |client| { (client.client, SendShipPacket::DirectMessage(cmsg.clone())) }) .collect() }, }) } } #[async_trait::async_trait] impl ServerState for ShipServerState { type SendPacket = SendShipPacket; type RecvPacket = RecvShipPacket; type Cipher = PSOBBCipher; type PacketError = anyhow::Error; async fn on_connect(&mut self, _id: ClientId) -> Result>, anyhow::Error> { let mut rng = rand::thread_rng(); let mut server_key = [0u8; 48]; let mut client_key = [0u8; 48]; rng.fill(&mut server_key[..]); rng.fill(&mut client_key[..]); Ok(vec![OnConnect::Packet(SendShipPacket::ShipWelcome(ShipWelcome::new(server_key, client_key))), OnConnect::Cipher(PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, client_key), PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, server_key)) ]) } async fn handle(&mut self, id: ClientId, pkt: RecvShipPacket) -> Result, anyhow::Error> { if let Ok((char_id, char_playtime)) = self.clients.with_mut(id, |client| Box::pin(async move { client.update_playtime(); (client.character.id, client.character.playtime) })).await { self.entity_gateway.set_character_playtime(&char_id, char_playtime).await?; } Ok(match pkt { RecvShipPacket::Login(login) => { handler::auth::validate_login(id, login, &mut self.entity_gateway, &mut self.clients, &mut self.item_state, &self.shipgate_sender, &self.name, self.blocks.0.len()) .await? .into_iter() .map(move |pkt| (id, pkt)) .collect() }, RecvShipPacket::QuestDetailRequest(questdetailrequest) => { let block = self.blocks.get_from_client(id, &self.clients).await?; match questdetailrequest.menu { QUEST_SELECT_MENU_ID => handler::quest::quest_detail(id, questdetailrequest, &block.client_location, &block.rooms).await?, _ => unreachable!(), } }, RecvShipPacket::MenuSelect(menuselect) => { let block = self.blocks.get_from_client(id, &self.clients).await?; match menuselect.menu { SHIP_MENU_ID => { let leave_lobby = handler::lobby::remove_from_lobby(id, &mut block.client_location).await.into_iter().flatten(); let select_ship = handler::ship::selected_ship(id, menuselect, &self.ship_list).await?; leave_lobby.chain(select_ship).collect() } BLOCK_MENU_ID => { let leave_lobby = handler::lobby::remove_from_lobby(id, &mut block.client_location).await.into_iter().flatten(); let select_block = handler::lobby::block_selected(id, menuselect, &self.clients, &self.item_state).await?.into_iter(); leave_lobby.chain(select_block).collect() } ROOM_MENU_ID => handler::room::join_room(id, menuselect, &mut self.entity_gateway, &mut block.client_location, &self.clients, &mut self.item_state, &block.rooms, self.event).await?, QUEST_CATEGORY_MENU_ID => handler::quest::select_quest_category(id, menuselect, &block.client_location, &block.rooms).await?, _ => unreachable!(), } }, RecvShipPacket::QuestMenuSelect(questmenuselect) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::quest::player_chose_quest(id, questmenuselect, &self.clients, &block.client_location, &block.rooms, self.event).await? }, RecvShipPacket::MenuDetail(menudetail) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::lobby::get_room_tab_info(id, menudetail, &mut block.client_location, &self.clients).await? }, RecvShipPacket::RoomPasswordReq(room_password_req) => { let block = self.blocks.get_from_client(id, &self.clients).await?; let room_password = block.rooms.with(RoomId(room_password_req.item as usize), |room| Box::pin(async move { room.password })).await?; if room_password_req.password == room_password { let menuselect = MenuSelect { menu: room_password_req.menu, item: room_password_req.item, }; handler::room::join_room(id, menuselect, &mut self.entity_gateway, &mut block.client_location, &self.clients, &mut self.item_state, &block.rooms, self.event).await? } else { vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("Incorrect password".into())))] } }, RecvShipPacket::CharData(chardata) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::lobby::send_player_to_lobby(id, chardata, &mut block.client_location, &self.clients, &self.item_state, self.event).await? }, RecvShipPacket::Message(msg) => { self.message(id, msg).await? }, RecvShipPacket::DirectMessage(msg) => { self.direct_message(id, msg).await? }, RecvShipPacket::PlayerChat(msg) => { match chatcommand::handle_chat_command(id, msg.clone(), self).await { Some(ccmd) => { match ccmd { Ok(pkts) => pkts, Err(msg) => vec![(id, SendShipPacket::LargeDialog(LargeDialog::new(msg)))] } }, None => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::communication::player_chat(id, msg, &block.client_location, &self.clients).await? } } }, RecvShipPacket::CreateRoom(create_room) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::room::create_room(id, create_room, &mut self.entity_gateway, &mut block.client_location, &self.clients, &mut self.item_state, &block.rooms, self.map_builder.clone(), self.drop_table_builder.clone(), self.event).await? }, RecvShipPacket::RoomNameRequest(_req) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::room::room_name_request(id, &block.client_location, &block.rooms).await? }, RecvShipPacket::UpdateTechMenu(pkt) => { handler::settings::update_tech_menu(id, pkt, &self.clients, &mut self.entity_gateway).await? }, RecvShipPacket::UpdateConfig(pkt) => { handler::settings::update_config(id, pkt, &self.clients, &mut self.entity_gateway).await? }, RecvShipPacket::ViewInfoboardRequest(_pkt) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::communication::request_infoboard(id, &block.client_location, &self.clients).await? }, RecvShipPacket::WriteInfoboard(pkt) => { handler::communication::write_infoboard(id, pkt, &self.clients, &mut self.entity_gateway).await? }, RecvShipPacket::RoomListRequest(_req) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::room::request_room_list(id, &block.client_location, &block.rooms).await }, RecvShipPacket::Like62ButCooler(cool62) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::room::cool_62(id, cool62, &block.client_location).await? }, RecvShipPacket::ClientCharacterData(_) => { // TOOD: validate this in some way? Vec::new() }, RecvShipPacket::DoneBursting(_) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::room::done_bursting(id, &block.client_location, &block.rooms).await? }, RecvShipPacket::DoneBursting2(_) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::room::done_bursting(id, &block.client_location, &block.rooms).await? }, RecvShipPacket::LobbySelect(pkt) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::lobby::change_lobby(id, pkt.lobby, &mut block.client_location, &self.clients, &mut self.item_state, &block.rooms, &mut self.entity_gateway, self.event).await? }, RecvShipPacket::RequestQuestList(rql) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::quest::send_quest_category_list(id, rql, &block.client_location, &block.rooms).await? }, RecvShipPacket::QuestFileRequest(quest_file_request) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::quest::quest_file_request(id, quest_file_request, &block.client_location, &mut block.rooms).await? }, RecvShipPacket::QuestChunkAck(quest_chunk_ack) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::quest::quest_chunk_ack(id, quest_chunk_ack, &block.client_location, &block.rooms).await? }, RecvShipPacket::DoneLoadingQuest(_) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::quest::done_loading_quest(id, &self.clients, &block.client_location).await? }, RecvShipPacket::FullCharacterData(_full_character_data) => { Vec::new() }, RecvShipPacket::SaveOptions(save_options) => { handler::settings::save_options(id, save_options, &self.clients, &mut self.entity_gateway).await? }, RecvShipPacket::RequestShipList(_) => { handler::ship::ship_list(id, &self.ship_list).await }, RecvShipPacket::RequestShipBlockList(_) => { handler::ship::block_list(id, &self.name, self.blocks.0.len()) }, RecvShipPacket::ItemsToTrade(items_to_trade) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::trade::items_to_trade(id, items_to_trade, &block.client_location, &self.clients, &mut self.item_state, &mut self.trades).await? }, RecvShipPacket::TradeConfirmed(_) => { let block = self.blocks.get_from_client(id, &self.clients).await?; handler::trade::trade_confirmed(id, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_state, &mut self.trades).await? }, RecvShipPacket::KeyboardConfig(keyboard_config) => { handler::settings::keyboard_config(id, keyboard_config, &self.clients, &mut self.entity_gateway).await? }, RecvShipPacket::GamepadConfig(gamepad_config) => { handler::settings::gamepad_config(id, gamepad_config, &self.clients, &mut self.entity_gateway).await? }, }) } async fn on_disconnect(&mut self, id: ClientId) -> Result, anyhow::Error> { let block = self.blocks.get_from_client(id, &self.clients).await?; let area_client = block.client_location.get_local_client(id).await?; let neighbors = block.client_location.get_client_neighbors(id).await?; let pkt = match block.client_location.get_area(id).await? { RoomLobby::Room(room) => { let character_id = self.clients.with(id, |client| Box::pin(async { client.character.id })).await?; block.rooms.with(room, |room| { let mut entity_gateway = self.entity_gateway.clone(); Box::pin(async move { entity_gateway.add_room_note(room.room_id, RoomNote::PlayerJoin { character_id, }).await })}).await; if neighbors.is_empty() { block.rooms.remove(room).await; } let leader = block.client_location.get_room_leader(room).await?; SendShipPacket::LeaveRoom(LeaveRoom::new(area_client.local_client.id(), leader.local_client.id())) }, RoomLobby::Lobby(lobby) => { let leader = block.client_location.get_lobby_leader(lobby).await?; SendShipPacket::LeaveLobby(LeaveLobby::new(area_client.local_client.id(), leader.local_client.id())) } }; if let Some(mut client) = self.clients.remove(&id).await { client.user.at_ship = false; self.entity_gateway.save_user(&client.user).await; if let Some(shipgate_sender) = self.shipgate_sender.as_ref() { shipgate_sender.send(ShipMessage::RemoveUser(client.user.id)).await; } self.item_state.remove_character_from_room(&client.character).await } block.client_location.remove_client_from_area(id).await?; Ok(neighbors.into_iter().map(|n| { (n.client, pkt.clone()) }).collect()) } } #[async_trait::async_trait] impl InterserverActor for ShipServerState { type SendMessage = ShipMessage; type RecvMessage = LoginMessage; type Error = (); async fn on_connect(&mut self, id: ServerId) -> Vec<(ServerId, Self::SendMessage)> { vec![ (id, ShipMessage::Authenticate(self.auth_token.clone())), (id, ShipMessage::NewShip(Ship { name: self.name.clone(), ip: self.ip, port: self.port, block_count: 2, })), (id, ShipMessage::RequestShipList) ] } async fn on_action(&mut self, _id: ServerId, msg: Self::RecvMessage) -> Result, Self::Error> { match msg { LoginMessage::SendMail{..} => { Ok(Vec::new()) }, LoginMessage::ShipList{mut ships} => { let mut ship_list = self.ship_list .write() .await; ship_list.clear(); ship_list.append(&mut ships); Ok(Vec::new()) }, LoginMessage::RequestUsers => { /* Ok(self.clients.iter() .map(|(_, client)| { (id, ShipMessage::AddUser(client.user.id)) }) .collect()) */ // TODO Ok(Vec::new()) } } } async fn on_disconnect(&mut self, _id: ServerId) -> Vec<(ServerId, Self::SendMessage)> { Vec::new() } async fn set_sender(&mut self, _server_id: ServerId, sender: channel::Sender) { self.shipgate_sender = Some(sender); } }