From b257bfeae1446ed2439023dc8f379bff39291ce8 Mon Sep 17 00:00:00 2001 From: vE5li Date: Mon, 29 Apr 2024 18:08:52 +0200 Subject: [PATCH] Add back friend list packet handling --- .../elements/miscellanious/chat/builder.rs | 1 - .../elements/miscellanious/chat/mod.rs | 3 +- korangar/src/interface/theme/mod.rs | 7 + korangar/src/main.rs | 163 +++++---- korangar_interface/src/theme.rs | 1 + .../examples/ollama-chat-bot.rs | 51 +-- korangar_networking/src/lib.rs | 334 +++++++++++++----- ragnarok_networking/src/lib.rs | 15 +- 8 files changed, 367 insertions(+), 208 deletions(-) diff --git a/korangar/src/interface/elements/miscellanious/chat/builder.rs b/korangar/src/interface/elements/miscellanious/chat/builder.rs index fad2a1034..150388b29 100644 --- a/korangar/src/interface/elements/miscellanious/chat/builder.rs +++ b/korangar/src/interface/elements/miscellanious/chat/builder.rs @@ -51,7 +51,6 @@ impl ChatBuilder>, Rc>> { Chat { messages, font_loader, - stamp: true, state: Default::default(), } } diff --git a/korangar/src/interface/elements/miscellanious/chat/mod.rs b/korangar/src/interface/elements/miscellanious/chat/mod.rs index bde956823..7f8d4cf01 100644 --- a/korangar/src/interface/elements/miscellanious/chat/mod.rs +++ b/korangar/src/interface/elements/miscellanious/chat/mod.rs @@ -22,8 +22,6 @@ use crate::ChatMessage; pub struct Chat { messages: PlainRemote>, font_loader: Rc>, - // TODO: make this Remote - stamp: bool, state: ElementState, } @@ -111,6 +109,7 @@ impl Element for Chat { korangar_networking::MessageColor::Broadcast => theme.chat.broadcast_color.get(), korangar_networking::MessageColor::Server => theme.chat.server_color.get(), korangar_networking::MessageColor::Error => theme.chat.broadcast_color.get(), + korangar_networking::MessageColor::Information => theme.chat.information_color.get(), }; // NOTE: Dividing by the scaling is done to counteract the scaling being applied diff --git a/korangar/src/interface/theme/mod.rs b/korangar/src/interface/theme/mod.rs index ce4ae12a2..df6ded721 100644 --- a/korangar/src/interface/theme/mod.rs +++ b/korangar/src/interface/theme/mod.rs @@ -853,6 +853,7 @@ pub struct ChatTheme { pub broadcast_color: Mutable, pub server_color: Mutable, pub error_color: Mutable, + pub information_color: Mutable, } impl ThemeDefault for ChatTheme { @@ -863,6 +864,7 @@ impl ThemeDefault for ChatTheme { broadcast_color: Mutable::new(Color::rgb_u8(210, 210, 210)), server_color: Mutable::new(Color::rgb_u8(255, 255, 210)), error_color: Mutable::new(Color::rgb_u8(255, 150, 150)), + information_color: Mutable::new(Color::rgb_u8(200, 255, 200)), } } } @@ -875,6 +877,7 @@ impl ThemeDefault for ChatTheme { broadcast_color: Mutable::new(Color::rgb_u8(210, 210, 210)), server_color: Mutable::new(Color::rgb_u8(255, 255, 210)), error_color: Mutable::new(Color::rgb_u8(255, 150, 150)), + information_color: Mutable::new(Color::rgb_u8(200, 255, 200)), } } } @@ -899,6 +902,10 @@ impl korangar_interface::theme::ChatTheme for ChatTheme { fn error_color(&self) -> Color { self.error_color.get() } + + fn information_color(&self) -> Color { + self.information_color.get() + } } #[derive(Serialize, Deserialize, PrototypeElement)] diff --git a/korangar/src/main.rs b/korangar/src/main.rs index c05c4ef96..3bbdc8c7e 100644 --- a/korangar/src/main.rs +++ b/korangar/src/main.rs @@ -26,7 +26,7 @@ mod inventory; mod loaders; mod world; -use std::cell::RefCell; +use std::cell::{RefCell, UnsafeCell}; use std::io::Cursor; use std::net::{IpAddr, SocketAddr}; use std::rc::Rc; @@ -43,10 +43,11 @@ use korangar_debug::profile_block; #[cfg(feature = "debug")] use korangar_debug::profiling::Profiler; use korangar_interface::application::{Application, FocusState, FontSizeTrait, FontSizeTraitExt, PositionTraitExt}; -use korangar_interface::state::{PlainTrackedState, Remote, RemoteClone, TrackedState, TrackedStateVec}; +use korangar_interface::elements::WeakElementCell; +use korangar_interface::state::{PlainTrackedState, Remote, RemoteClone, TrackedState, TrackedStateExt, TrackedStateTake, TrackedStateVec}; use korangar_interface::Interface; use korangar_networking::{LoginServerLoginData, MessageColor, NetworkEvent, NetworkingSystem}; -use ragnarok_networking::{SkillId, SkillType, TilePosition, UnitId, WorldPosition}; +use ragnarok_networking::{CharacterId, Friend, SkillId, SkillType, TilePosition, UnitId, WorldPosition}; use vulkano::device::{Device, DeviceCreateInfo, QueueCreateInfo}; #[cfg(feature = "debug")] use vulkano::instance::debug::{ @@ -71,8 +72,8 @@ use crate::interface::layout::{ScreenPosition, ScreenSize}; use crate::interface::resource::{ItemSource, Move, SkillSource}; use crate::interface::windows::{ AudioSettingsWindow, CharacterCreationWindow, CharacterOverviewWindow, CharacterSelectionWindow, ChatWindow, DialogWindow, - EquipmentWindow, ErrorWindow, FriendRequestWindow, GraphicsSettingsWindow, HotbarWindow, InventoryWindow, LoginWindow, MenuWindow, - SelectServerWindow, SkillTreeWindow, + EquipmentWindow, ErrorWindow, FriendRequestWindow, FriendsWindow, GraphicsSettingsWindow, HotbarWindow, InventoryWindow, LoginWindow, + MenuWindow, SelectServerWindow, SkillTreeWindow, }; #[cfg(feature = "debug")] use crate::interface::windows::{CommandsWindow, MapsWindow, ProfilerWindow, RenderSettingsWindow, TimeWindow}; @@ -392,8 +393,12 @@ fn main() { let client_info = load_client_info(&mut game_file_loader); let mut networking_system = NetworkingSystem::::new(); + + let mut friend_list: PlainTrackedState>>)>> = + PlainTrackedState::default(); let mut saved_login_data: Option = None; let mut saved_characters: PlainTrackedState> = PlainTrackedState::default(); + let mut currently_deleting: Option = None; let mut saved_player_name = String::new(); let mut move_request: PlainTrackedState> = PlainTrackedState::default(); let mut saved_slot_count = 0; @@ -527,8 +532,7 @@ fn main() { if let Some(PickerTarget::Entity(entity_id)) = mouse_target { if let Some(entity) = entities.iter_mut().find(|entity| entity.get_entity_id() == entity_id) { if entity.are_details_unavailable() { - // FIX: Uncomment - // networking_system.request_entity_details(entity_id); + networking_system.entity_details(entity_id); entity.set_details_requested(); } @@ -557,8 +561,8 @@ fn main() { interface.close_window_with_class(&mut focus_state, LoginWindow::WINDOW_CLASS); interface.open_window(&application, &mut focus_state, &SelectServerWindow::new(character_servers)); } - NetworkEvent::LoginServerConnectionFailed { reason } => { - interface.open_window(&application, &mut focus_state, &ErrorWindow::new(reason.to_owned())) + NetworkEvent::LoginServerConnectionFailed { message, .. } => { + interface.open_window(&application, &mut focus_state, &ErrorWindow::new(message.to_owned())) } NetworkEvent::LoginServerDisconnected => { println!("Login server disconnected!!"); @@ -570,12 +574,38 @@ fn main() { networking_system.request_character_list(); }, - NetworkEvent::CharacterServerConnectionFailed { reason } => { - interface.open_window(&application, &mut focus_state, &ErrorWindow::new(reason.to_owned())) + NetworkEvent::CharacterServerConnectionFailed { message, .. } => { + interface.open_window(&application, &mut focus_state, &ErrorWindow::new(message.to_owned())) }, NetworkEvent::CharacterServerDisconnected => { println!("Character server disconnected!!"); }, + NetworkEvent::MapServerDisconnected => { + println!("Map server disconnected!!"); + + entities.clear(); + particle_holder.clear(); + effect_holder.clear(); + + map = map_loader + .get( + DEFAULT_MAP.to_string(), + &mut game_file_loader, + &mut buffer_allocator, + &mut model_loader, + &mut texture_loader, + ) + .expect("failed to load initial map"); + + interface.close_all_windows_except(&mut focus_state); + + let character_selection_window = CharacterSelectionWindow::new(saved_characters.new_remote(), move_request.new_remote(), saved_slot_count); + interface.open_window(&application, &mut focus_state, &character_selection_window); + + start_camera.set_focus_point(cgmath::Point3::new(600.0, 0.0, 240.0)); + directional_shadow_camera.set_focus_point(cgmath::Point3::new(600.0, 0.0, 240.0)); + + }, NetworkEvent::AccountId(account_id) => { println!("Got account id: {:?}", account_id); }, @@ -590,8 +620,17 @@ fn main() { interface.close_window_with_class(&mut focus_state, SelectServerWindow::WINDOW_CLASS); interface.open_window(&application, &mut focus_state, &character_selection_window); } - NetworkEvent::CharacterSelectionFailed { reason } => { - interface.open_window(&application, &mut focus_state, &ErrorWindow::new(reason.to_owned())) + NetworkEvent::CharacterSelectionFailed { message, .. } => { + interface.open_window(&application, &mut focus_state, &ErrorWindow::new(message.to_owned())) + } + NetworkEvent::CharacterDeleted => { + let character_id = currently_deleting.take().unwrap(); + + saved_characters.retain(|character| character.character_id != character_id); + }, + NetworkEvent::CharacterDeletionFailed { message, .. } => { + currently_deleting = None; + interface.open_window(&application, &mut focus_state, &ErrorWindow::new(message.to_owned())) } NetworkEvent::CharacterSelected { login_data, map_name } => { let saved_login_data = saved_login_data.as_ref().unwrap(); @@ -650,10 +689,22 @@ fn main() { mouse_cursor.set_start_time(client_tick); game_timer.set_client_tick(client_tick); }, + NetworkEvent::CharacterCreated { character_information } => { + saved_characters.push(character_information); + + interface.close_window_with_class(&mut focus_state, CharacterCreationWindow::WINDOW_CLASS); + }, + NetworkEvent::CharacterCreationFailed { message, .. } => { + interface.open_window(&application, &mut focus_state, &ErrorWindow::new(message.to_owned())); + }, + NetworkEvent::CharacterSlotSwitched => {}, + NetworkEvent::CharacterSlotSwitchFailed => { + interface.open_window(&application, &mut focus_state, &ErrorWindow::new("Failed to switch character slots".to_owned())); + }, NetworkEvent::AddEntity(entity_appeared_data) => { // Sometimes (like after a job change) the server will tell the client // that a new entity appeared, even though it was already on screen. So - // to prevent this we remove the old entity. + // to prevent the entity existing twice we remove the old one. entities.retain(|entity| entity.get_entity_id() != entity_appeared_data.entity_id); let npc = Npc::new( @@ -803,34 +854,17 @@ fn main() { entity.set_job(job_id as usize); entity.reload_sprite(&mut game_file_loader, &mut sprite_loader, &mut action_loader, &script_loader); } - NetworkEvent::Disconnect => { - // FIX: Uncomment - // networking_system.disconnect_from_map_server(); - entities.clear(); - particle_holder.clear(); - effect_holder.clear(); - - map = map_loader - .get( - DEFAULT_MAP.to_string(), - &mut game_file_loader, - &mut buffer_allocator, - &mut model_loader, - &mut texture_loader, - ) - .expect("failed to load initial map"); - - interface.close_all_windows_except(&mut focus_state); - - // FIX: Uncomment - // let character_selection_window = networking_system.character_selection_window(); - // interface.open_window(&application, &mut focus_state, &character_selection_window); - - start_camera.set_focus_point(cgmath::Point3::new(600.0, 0.0, 240.0)); - directional_shadow_camera.set_focus_point(cgmath::Point3::new(600.0, 0.0, 240.0)); + NetworkEvent::LoggedOut => { + networking_system.disconnect_from_map_server(); } - NetworkEvent::FriendRequest(friend) => { - interface.open_window(&application, &mut focus_state, &FriendRequestWindow::new(friend)) + NetworkEvent::FriendRequest { requestee } => { + interface.open_window(&application, &mut focus_state, &FriendRequestWindow::new(requestee)) + } + NetworkEvent::FriendRemoved { account_id, character_id } => { + friend_list.retain(|(friend, _)| !(friend.account_id == account_id && friend.character_id == character_id)); + } + NetworkEvent::FriendAdded { friend } => { + friend_list.push((friend, UnsafeCell::new(None))); } NetworkEvent::VisualEffect(path, entity_id) => { let effect = effect_loader.get(path, &mut game_file_loader, &mut texture_loader).unwrap(); @@ -897,6 +931,11 @@ fn main() { NetworkEvent::RemoveSkillUnit(entity_id) => { effect_holder.remove_unit(entity_id); } + NetworkEvent::SetFriendList { friends } => { + friend_list.mutate(|friend_list| { + *friend_list = friends.into_iter().map(|friend| (friend, UnsafeCell::new(None))).collect(); + }); + } } } @@ -974,7 +1013,7 @@ fn main() { ), UserEvent::OpenAudioSettingsWindow => interface.open_window(&application, &mut focus_state, &AudioSettingsWindow), UserEvent::OpenFriendsWindow => { - // interface.open_window(&application, &mut focus_state, &networking_system.friends_window()) + interface.open_window(&application, &mut focus_state, &FriendsWindow::new(friend_list.new_remote())); } UserEvent::ToggleShowInterface => show_interface = !show_interface, UserEvent::SetThemeFile { theme_file, theme_kind } => application.set_theme_file(theme_file, theme_kind), @@ -984,37 +1023,21 @@ fn main() { UserEvent::OpenCharacterCreationWindow(character_slot) => { interface.open_window(&application, &mut focus_state, &CharacterCreationWindow::new(character_slot)) } - UserEvent::CreateCharacter(character_slot, name) => { - // match networking_system.create_character(character_slot, name) { - // Ok(..) => interface.close_window_with_class(&mut focus_state, CharacterCreationWindow::WINDOW_CLASS), - // Err(message) => interface.open_window(&application, &mut focus_state, &ErrorWindow::new(message)), - // } - } + UserEvent::CreateCharacter(character_slot, name) => networking_system.create_character(character_slot, name), UserEvent::DeleteCharacter(character_id) => { - // handle_result( - // &application, - // &mut interface, - // &mut focus_state, - // networking_system.delete_character(character_id), - // ) - }, - UserEvent::RequestSwitchCharacterSlot(origin_slot) => { - // networking_system.request_switch_character_slot(origin_slot) - }, - UserEvent::CancelSwitchCharacterSlot => { - // networking_system.cancel_switch_character_slot() + if currently_deleting.is_none() { + currently_deleting = Some(character_id); + networking_system.delete_character(character_id); + } }, + UserEvent::RequestSwitchCharacterSlot(origin_slot) => move_request.set(Some(origin_slot)), + UserEvent::CancelSwitchCharacterSlot => move_request.set(None), UserEvent::SwitchCharacterSlot(destination_slot) => { - // handle_result( - // &application, - // &mut interface, - // &mut focus_state, - // networking_system.switch_character_slot(destination_slot), - // ) + networking_system.switch_character_slot(move_request.take().unwrap(), destination_slot); }, UserEvent::RequestPlayerMove(destination) => { if !entities.is_empty() { - networking_system.request_player_move(WorldPosition::new(destination.x, destination.y)) + networking_system.player_move(WorldPosition::new(destination.x, destination.y)) } } UserEvent::RequestPlayerInteract(entity_id) => { @@ -1023,14 +1046,14 @@ fn main() { if let Some(entity) = entity { match entity.get_entity_type() { EntityType::Npc => networking_system.start_dialog(entity_id), - EntityType::Monster => networking_system.request_player_attack(entity_id), - EntityType::Warp => networking_system.request_player_move({let position = entity.get_grid_position(); WorldPosition { x: position.x, y: position.y }}), + EntityType::Monster => networking_system.player_attack(entity_id), + EntityType::Warp => networking_system.player_move({let position = entity.get_grid_position(); WorldPosition { x: position.x, y: position.y }}), _ => {} // TODO: add other interactions } } } UserEvent::RequestWarpToMap(map_name, position) => { - networking_system.request_warp_to_map(map_name, position) + networking_system.warp_to_map(map_name, position) }, UserEvent::SendMessage(message) => { networking_system.send_chat_message(&saved_player_name, &message); diff --git a/korangar_interface/src/theme.rs b/korangar_interface/src/theme.rs index 63a6bf24f..2e934e9b6 100644 --- a/korangar_interface/src/theme.rs +++ b/korangar_interface/src/theme.rs @@ -139,6 +139,7 @@ where fn broadcast_color(&self) -> App::Color; fn server_color(&self) -> App::Color; fn error_color(&self) -> App::Color; + fn information_color(&self) -> App::Color; } pub trait CursorTheme diff --git a/korangar_networking/examples/ollama-chat-bot.rs b/korangar_networking/examples/ollama-chat-bot.rs index 3610f2c4d..b2433a956 100644 --- a/korangar_networking/examples/ollama-chat-bot.rs +++ b/korangar_networking/examples/ollama-chat-bot.rs @@ -1,5 +1,4 @@ use std::collections::HashMap; -use std::io::Write; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::thread::sleep; use std::time::Duration; @@ -68,6 +67,7 @@ async fn main() { const OLLAMA_MODEL: &str = "llama2:13b"; const USERNAME: &str = "chatbot"; const PASSWORD: &str = "chatbot"; + const CHARACTER_NAME: &str = "ChatbotBoy"; // Create the networking system and HTTP client. let mut networking_system = NetworkingSystem::<()>::new(); @@ -76,7 +76,6 @@ async fn main() { // Persistent data. let mut saved_login_data = None; let mut message_history = MessageHistory { hash_map: HashMap::new() }; - let mut character_name = String::new(); // Kick of the bot by connecting to the login server. networking_system.connect_to_login_server(SOCKET_ADDR, USERNAME.to_owned(), PASSWORD.to_owned()); @@ -93,8 +92,8 @@ async fn main() { networking_system.connect_to_character_server(&login_data, character_servers[0].clone()); saved_login_data = Some(login_data); } - NetworkEvent::LoginServerConnectionFailed { reason } => { - panic!("Failed to connect to login server: {}", reason); + NetworkEvent::LoginServerConnectionFailed { message, .. } => { + panic!("Failed to connect to login server: {}", message); } NetworkEvent::LoginServerDisconnected => { panic!("Login server disconnected"); @@ -104,40 +103,26 @@ async fn main() { networking_system.request_character_list(); } - NetworkEvent::CharacterServerConnectionFailed { reason } => { - panic!("Failed to connect to character server: {}", reason); + NetworkEvent::CharacterServerConnectionFailed { message, .. } => { + panic!("Failed to connect to character server: {}", message); } NetworkEvent::CharacterServerDisconnected => { panic!("Character server disconnected"); } - NetworkEvent::CharacterSelectionFailed { reason } => { - panic!("Failed to select character: {}", reason); + NetworkEvent::CharacterSelectionFailed { message, .. } => { + panic!("Failed to select character: {}", message); + } + NetworkEvent::MapServerDisconnected => { + panic!("Map server disconnected"); } NetworkEvent::CharacterInformation { characters } => { - for character in &characters { - println!( - "[{}] On slot {}: {}", - "Characters".magenta(), - character.character_number.green(), - character.name.magenta() - ); - } - - print!("[{}] Character to use: ", "Characters".magenta()); - std::io::stdout().flush().unwrap(); - - let mut buffer = String::new(); - let stdin = std::io::stdin(); - stdin.read_line(&mut buffer).expect("Failed to receive input"); - - let character_slot = buffer.trim().parse::().expect("Failed to parse slot"); - let character_name = characters - .into_iter() - .find(|character| character.character_number as usize == character_slot) - .expect("No player in that slot") - .name; + let character_slot = characters + .iter() + .find(|character| character.name == CHARACTER_NAME) + .expect(&format!("Character with name \"{}\" not found for this user", CHARACTER_NAME)) + .character_number as usize; - print!("[{}] Using character: {}", "Characters".magenta(), character_name.green()); + println!("[{}] Using character in slot: {}", "Setup".green(), character_slot.green()); networking_system.select_character(character_slot) } @@ -147,7 +132,7 @@ async fn main() { networking_system.map_loaded(); } NetworkEvent::ChatMessage { text, .. } => { - if text.starts_with(&character_name) { + if text.starts_with(CHARACTER_NAME) { continue; } @@ -189,7 +174,7 @@ async fn main() { content: response.content.to_owned(), }); - networking_system.send_chat_message(&character_name, &response.content); + networking_system.send_chat_message(CHARACTER_NAME, &response.content); } } _ => {} diff --git a/korangar_networking/src/lib.rs b/korangar_networking/src/lib.rs index f520e09df..15c6e6cda 100644 --- a/korangar_networking/src/lib.rs +++ b/korangar_networking/src/lib.rs @@ -16,6 +16,7 @@ pub enum MessageColor { Broadcast, Server, Error, + Information, } #[derive(Debug, Clone, Copy)] @@ -26,6 +27,28 @@ pub struct LoginServerLoginData { pub sex: Sex, } +#[derive(Debug, Clone, Copy)] +pub enum UnifiedLoginFailedReason { + ServerClosed, + AlreadyLoggedIn, + AlreadyOnline, + UnregisteredId, + IncorrectPassword, + IdExpired, + RejectedFromServer, + BlockedByGMTeam, + GameOutdated, + LoginProhibitedUntil, + ServerFull, + CompanyAccountLimitReached, +} + +#[derive(Debug, Clone, Copy)] +pub enum UnifiedCharacterSelectionFailedReason { + RejectedFromServer, + MapServerUnavailable, +} + #[derive(Debug, Clone, Copy)] pub struct CharacterServerLoginData { pub server_ip: IpAddr, @@ -120,14 +143,16 @@ pub enum NetworkEvent { login_data: LoginServerLoginData, }, LoginServerConnectionFailed { - reason: &'static str, + reason: UnifiedLoginFailedReason, + message: &'static str, }, LoginServerDisconnected, CharacterServerConnected { normal_slot_count: usize, }, CharacterServerConnectionFailed { - reason: &'static str, + reason: LoginFailedReason, + message: &'static str, }, CharacterServerDisconnected, AccountId(AccountId), @@ -139,8 +164,22 @@ pub enum NetworkEvent { map_name: String, }, CharacterSelectionFailed { - reason: &'static str, + reason: UnifiedCharacterSelectionFailedReason, + message: &'static str, + }, + CharacterCreated { + character_information: CharacterInformation, + }, + CharacterCreationFailed { + reason: CharacterCreationFailedReason, + message: &'static str, }, + CharacterDeleted, + CharacterDeletionFailed { + reason: CharacterDeletionFailedReason, + message: &'static str, + }, + MapServerDisconnected, /// Add an entity to the list of entities that the client is aware of. AddEntity(EntityData), /// Remove an entity from the list of entities that the client is aware of @@ -161,6 +200,8 @@ pub enum NetworkEvent { text: String, color: MessageColor, }, + CharacterSlotSwitched, + CharacterSlotSwitchFailed, /// Update entity details. Mostly received when the client sends /// [RequestDetailsPacket] after the player hovered an entity. UpdateEntityDetails(EntityId, String), @@ -183,11 +224,23 @@ pub enum NetworkEvent { }, ChangeJob(AccountId, u32), SetPlayerPosition(WorldPosition), - Disconnect, - FriendRequest(Friend), + LoggedOut, + FriendRequest { + requestee: Friend, + }, VisualEffect(&'static str, EntityId), AddSkillUnit(EntityId, UnitId, TilePosition), RemoveSkillUnit(EntityId), + SetFriendList { + friends: Vec, + }, + FriendAdded { + friend: Friend, + }, + FriendRemoved { + account_id: AccountId, + character_id: CharacterId, + }, } /// New-type so we can implement some `From` traits. This will help when @@ -222,24 +275,50 @@ impl From<(NetworkEvent, NetworkEvent)> for NetworkEventList { } } -enum ConnectCommand { - LoginServer { +enum ServerConnectCommand { + Login { address: SocketAddr, action_receiver: Receiver>, event_sender: Sender, }, - CharacterServer { + Character { address: SocketAddr, action_receiver: Receiver>, event_sender: Sender, }, - MapServer { + Map { address: SocketAddr, action_receiver: Receiver>, event_sender: Sender, }, } +trait DisconnectedEvent { + fn network_event() -> NetworkEvent; +} + +struct LoginServerDisconnectEvent; +struct CharacterServerDisconnectEvent; +struct MapServerDisconnectEvent; + +impl DisconnectedEvent for LoginServerDisconnectEvent { + fn network_event() -> NetworkEvent { + NetworkEvent::LoginServerDisconnected + } +} + +impl DisconnectedEvent for CharacterServerDisconnectEvent { + fn network_event() -> NetworkEvent { + NetworkEvent::CharacterServerDisconnected + } +} + +impl DisconnectedEvent for MapServerDisconnectEvent { + fn network_event() -> NetworkEvent { + NetworkEvent::MapServerDisconnected + } +} + #[derive(Debug)] pub enum NetworkTaskError { FailedToConnect, @@ -251,6 +330,7 @@ enum ServerConnection { action_sender: Sender>, event_receiver: Receiver, }, + ClosingManually, Disconnected, } @@ -261,7 +341,7 @@ impl ServerConnection { } pub struct NetworkingSystem { - command_sender: Sender, + command_sender: Sender, login_server_connection: ServerConnection, character_server_connection: ServerConnection, map_server_connection: ServerConnection, @@ -284,8 +364,8 @@ where } } - fn spawn_networking_thread() -> Sender { - let (command_sender, mut command_receiver) = tokio::sync::mpsc::channel::(20); + fn spawn_networking_thread() -> Sender { + let (command_sender, mut command_receiver) = tokio::sync::mpsc::channel::(20); std::thread::spawn(move || { println!("Networking thread started"); @@ -302,7 +382,7 @@ where local_set.block_on(&runtime, async { while let Some(command) = command_receiver.recv().await { match command { - ConnectCommand::LoginServer { + ServerConnectCommand::Login { address, action_receiver, event_sender, @@ -327,7 +407,7 @@ where login_server_task_handle = Some(handle); } - ConnectCommand::CharacterServer { + ServerConnectCommand::Character { address, action_receiver, event_sender, @@ -352,7 +432,7 @@ where character_server_task_handle = Some(handle); } - ConnectCommand::MapServer { + ServerConnectCommand::Map { address, action_receiver, event_sender, @@ -385,13 +465,15 @@ where command_sender } - fn handle_connection(connection: &mut ServerConnection, events: &mut Vec) { - if let ServerConnection::Connected { - action_sender, - mut event_receiver, - } = connection.take() - { - loop { + fn handle_connection(connection: &mut ServerConnection, events: &mut Vec) + where + Event: DisconnectedEvent, + { + match connection.take() { + ServerConnection::Connected { + action_sender, + mut event_receiver, + } => loop { match event_receiver.try_recv() { Ok(login_event) => { events.push(login_event); @@ -404,12 +486,17 @@ where break; } Err(..) => { - events.push(NetworkEvent::LoginServerDisconnected); + events.push(Event::network_event()); *connection = ServerConnection::Disconnected; break; } } + }, + ServerConnection::ClosingManually => { + events.push(Event::network_event()); + *connection = ServerConnection::Disconnected; } + _ => (), }; } @@ -523,7 +610,7 @@ where // FIX: Don't unwrap. self.command_sender - .try_send(ConnectCommand::LoginServer { + .try_send(ServerConnectCommand::Login { address, action_receiver, event_sender, @@ -548,7 +635,7 @@ where // FIX: Don't unwrap. self.command_sender - .try_send(ConnectCommand::CharacterServer { + .try_send(ServerConnectCommand::Character { address, action_receiver, event_sender, @@ -582,7 +669,7 @@ where // FIX: Don't unwrap. self.command_sender - .try_send(ConnectCommand::MapServer { + .try_send(ServerConnectCommand::Map { address, action_receiver, event_sender, @@ -605,12 +692,16 @@ where }; } + pub fn disconnect_from_map_server(&mut self) { + self.map_server_connection = ServerConnection::ClosingManually; + } + pub fn get_events(&mut self) -> Vec { let mut events = Vec::new(); - Self::handle_connection(&mut self.login_server_connection, &mut events); - Self::handle_connection(&mut self.character_server_connection, &mut events); - Self::handle_connection(&mut self.map_server_connection, &mut events); + Self::handle_connection::(&mut self.login_server_connection, &mut events); + Self::handle_connection::(&mut self.character_server_connection, &mut events); + Self::handle_connection::(&mut self.map_server_connection, &mut events); events } @@ -663,45 +754,44 @@ where fn create_login_server_packet_handler() -> Result, DuplicateHandlerError> { let mut packet_handler = PacketHandler::::default(); - packet_handler.register( - |packet: LoginServerLoginSuccessPacket| match packet.character_server_information.is_empty() { - true => NetworkEvent::LoginServerConnectionFailed { - reason: "No character server available", - }, - false => NetworkEvent::LoginServerConnected { - character_servers: packet.character_server_information, - login_data: LoginServerLoginData { - account_id: packet.account_id, - login_id1: packet.login_id1, - login_id2: packet.login_id2, - sex: packet.sex, - }, - }, + packet_handler.register(|packet: LoginServerLoginSuccessPacket| NetworkEvent::LoginServerConnected { + character_servers: packet.character_server_information, + login_data: LoginServerLoginData { + account_id: packet.account_id, + login_id1: packet.login_id1, + login_id2: packet.login_id2, + sex: packet.sex, }, - )?; + })?; packet_handler.register(|packet: LoginFailedPacket| { - let reason = match packet.reason { - LoginFailedReason::ServerClosed => "Server closed", - LoginFailedReason::AlreadyLoggedIn => "Someone has already logged in with this id", - LoginFailedReason::AlreadyOnline => "Already online", + let (reason, message) = match packet.reason { + LoginFailedReason::ServerClosed => (UnifiedLoginFailedReason::ServerClosed, "Server closed"), + LoginFailedReason::AlreadyLoggedIn => ( + UnifiedLoginFailedReason::AlreadyLoggedIn, + "Someone has already logged in with this id", + ), + LoginFailedReason::AlreadyOnline => (UnifiedLoginFailedReason::AlreadyOnline, "Already online"), }; - NetworkEvent::LoginServerConnectionFailed { reason } + NetworkEvent::LoginServerConnectionFailed { reason, message } })?; packet_handler.register(|packet: LoginFailedPacket2| { - let reason = match packet.reason { - LoginFailedReason2::UnregisteredId => "Unregistered id", - LoginFailedReason2::IncorrectPassword => "Incorrect password", - LoginFailedReason2::IdExpired => "Id has expired", - LoginFailedReason2::RejectedFromServer => "Rejected from server", - LoginFailedReason2::BlockedByGMTeam => "Blocked by gm team", - LoginFailedReason2::GameOutdated => "Game outdated", - LoginFailedReason2::LoginProhibitedUntil => "Login prohibited until", - LoginFailedReason2::ServerFull => "Server is full", - LoginFailedReason2::CompanyAccountLimitReached => "Company account limit reached", + let (reason, message) = match packet.reason { + LoginFailedReason2::UnregisteredId => (UnifiedLoginFailedReason::UnregisteredId, "Unregistered id"), + LoginFailedReason2::IncorrectPassword => (UnifiedLoginFailedReason::IncorrectPassword, "Incorrect password"), + LoginFailedReason2::IdExpired => (UnifiedLoginFailedReason::IdExpired, "Id has expired"), + LoginFailedReason2::RejectedFromServer => (UnifiedLoginFailedReason::RejectedFromServer, "Rejected from server"), + LoginFailedReason2::BlockedByGMTeam => (UnifiedLoginFailedReason::BlockedByGMTeam, "Blocked by gm team"), + LoginFailedReason2::GameOutdated => (UnifiedLoginFailedReason::GameOutdated, "Game outdated"), + LoginFailedReason2::LoginProhibitedUntil => (UnifiedLoginFailedReason::LoginProhibitedUntil, "Login prohibited until"), + LoginFailedReason2::ServerFull => (UnifiedLoginFailedReason::ServerFull, "Server is full"), + LoginFailedReason2::CompanyAccountLimitReached => ( + UnifiedLoginFailedReason::CompanyAccountLimitReached, + "Company account limit reached", + ), }; - NetworkEvent::LoginServerConnectionFailed { reason } + NetworkEvent::LoginServerConnectionFailed { reason, message } })?; Ok(packet_handler) @@ -711,13 +801,14 @@ where let mut packet_handler = PacketHandler::::default(); packet_handler.register(|packet: LoginFailedPacket| { - let reason = match packet.reason { + let reason = packet.reason; + let message = match reason { LoginFailedReason::ServerClosed => "Server closed", LoginFailedReason::AlreadyLoggedIn => "Someone has already logged in with this id", LoginFailedReason::AlreadyOnline => "Already online", }; - NetworkEvent::CharacterServerConnectionFailed { reason } + NetworkEvent::CharacterServerConnectionFailed { reason, message } })?; packet_handler.register( |packet: CharacterServerLoginSuccessPacket| NetworkEvent::CharacterServerConnected { @@ -740,14 +831,48 @@ where NetworkEvent::CharacterSelected { login_data, map_name } })?; packet_handler.register(|packet: CharacterSelectionFailedPacket| { - let reason = match packet.reason { - CharacterSelectionFailedReason::RejectedFromServer => "Rejected from server", + let (reason, message) = match packet.reason { + CharacterSelectionFailedReason::RejectedFromServer => ( + UnifiedCharacterSelectionFailedReason::RejectedFromServer, + "Rejected from server", + ), + }; + + NetworkEvent::CharacterSelectionFailed { reason, message } + })?; + packet_handler.register(|_: MapServerUnavailablePacket| { + let reason = UnifiedCharacterSelectionFailedReason::MapServerUnavailable; + let message = "Map server currently unavailable"; + + NetworkEvent::CharacterSelectionFailed { reason, message } + })?; + packet_handler.register(|packet: CreateCharacterSuccessPacket| NetworkEvent::CharacterCreated { + character_information: packet.character_information, + })?; + packet_handler.register(|packet: CharacterCreationFailedPacket| { + let reason = packet.reason; + let message = match reason { + CharacterCreationFailedReason::CharacterNameAlreadyUsed => "Character name is already used", + CharacterCreationFailedReason::NotOldEnough => "You are not old enough to create a character", + CharacterCreationFailedReason::NotAllowedToUseSlot => "You are not allowed to use this character slot", + CharacterCreationFailedReason::CharacterCerationFailed => "Character creation failed", }; - NetworkEvent::CharacterSelectionFailed { reason } + NetworkEvent::CharacterCreationFailed { reason, message } + })?; + packet_handler.register(|_: CharacterDeletionSuccessPacket| NetworkEvent::CharacterDeleted)?; + packet_handler.register(|packet: CharacterDeletionFailedPacket| { + let reason = packet.reason; + let message = match reason { + CharacterDeletionFailedReason::NotAllowed => "You are not allowed to delete this character", + CharacterDeletionFailedReason::CharacterNotFound => "Character was not found", + CharacterDeletionFailedReason::NotEligible => "Character is not eligible for deletion", + }; + NetworkEvent::CharacterDeletionFailed { reason, message } })?; - packet_handler.register(|_: MapServerUnavailablePacket| NetworkEvent::CharacterSelectionFailed { - reason: "Map server currently unavailable", + packet_handler.register(|packet: SwitchCharacterSlotResponsePacket| match packet.status { + SwitchCharacterSlotResponseStatus::Success => NetworkEvent::CharacterSlotSwitched, + SwitchCharacterSlotResponseStatus::Error => NetworkEvent::CharacterSlotSwitchFailed, })?; Ok(packet_handler) @@ -931,14 +1056,14 @@ where ) })?; packet_handler.register(|packet: RestartResponsePacket| match packet.result { - RestartResponseStatus::Ok => NetworkEvent::Disconnect, + RestartResponseStatus::Ok => NetworkEvent::LoggedOut, RestartResponseStatus::Nothing => NetworkEvent::ChatMessage { text: "Failed to log out.".to_string(), color: MessageColor::Error, }, })?; packet_handler.register(|packet: DisconnectResponsePacket| match packet.result { - DisconnectResponseStatus::Ok => NetworkEvent::Disconnect, + DisconnectResponseStatus::Ok => NetworkEvent::LoggedOut, DisconnectResponseStatus::Wait10Seconds => NetworkEvent::ChatMessage { text: "Please wait 10 seconds before trying to log out.".to_string(), color: MessageColor::Error, @@ -950,26 +1075,33 @@ where .register(|packet: NotifySkillUnitPacket| NetworkEvent::AddSkillUnit(packet.entity_id, packet.unit_id, packet.position))?; packet_handler.register(|packet: SkillUnitDisappearPacket| NetworkEvent::RemoveSkillUnit(packet.entity_id))?; packet_handler.register_noop::()?; - packet_handler.register(|packet: FriendListPacket| { - // NetworkEvent::SetFriendsList(friends) - None - })?; + packet_handler.register(|packet: FriendListPacket| NetworkEvent::SetFriendList { friends: packet.friends })?; packet_handler.register_noop::()?; - packet_handler.register(|packet: FriendRequestPacket| { - // NetworkEvent::ReceivedFriendRequest(packet.friend) - None + packet_handler.register(|packet: FriendRequestPacket| NetworkEvent::FriendRequest { + requestee: packet.requestee, })?; packet_handler.register(|packet: FriendRequestResultPacket| { - // let color = Color::rgb_u8(220, 200, 30).unwrap(); - // let chat_message = ChatMessage::new(packet.into_message(), - // color).unwrap(); - // (NetworkEvent::ChatMessage(chat_message), - // NetworkEvent::FriendRequestAccepted(packet.friend)) - None + let text = match packet.result { + FriendRequestResult::Accepted => format!("You have become friends with {}.", packet.friend.name), + FriendRequestResult::Rejected => format!("{} does not want to be friends with you.", packet.friend.name), + FriendRequestResult::OwnFriendListFull => "Your Friend List is full.".to_owned(), + FriendRequestResult::OtherFriendListFull => format!("{}'s Friend List is full.", packet.friend.name), + }; + + let mut events = vec![NetworkEvent::ChatMessage { + text, + color: MessageColor::Information, + }]; + + if matches!(packet.result, FriendRequestResult::Accepted) { + events.push(NetworkEvent::FriendAdded { friend: packet.friend }); + } + + events })?; - packet_handler.register(|packet: NotifyFriendRemovedPacket| { - // NetworkEvent::FriendRemoved(packet.account_id, packet.character_id)) - None + packet_handler.register(|packet: NotifyFriendRemovedPacket| NetworkEvent::FriendRemoved { + account_id: packet.account_id, + character_id: packet.character_id, })?; packet_handler.register_noop::()?; packet_handler.register_noop::()?; @@ -1001,20 +1133,20 @@ where self.map_server_packet(RestartPacket::new(RestartType::Disconnect)).unwrap(); } - pub fn request_player_move(&mut self, position: WorldPosition) { + pub fn player_move(&mut self, position: WorldPosition) { // FIX: Don't unwrap self.map_server_packet(RequestPlayerMovePacket::new(position)).unwrap(); } - pub fn request_warp_to_map(&mut self, map_name: String, position: TilePosition) { + pub fn warp_to_map(&mut self, map_name: String, position: TilePosition) { self.map_server_packet(RequestWarpToMapPacket::new(map_name, position)).unwrap(); } - pub fn request_entity_details(&mut self, entity_id: EntityId) { + pub fn entity_details(&mut self, entity_id: EntityId) { self.map_server_packet(RequestDetailsPacket::new(entity_id)).unwrap(); } - pub fn request_player_attack(&mut self, entity_id: EntityId) { + pub fn player_attack(&mut self, entity_id: EntityId) { self.map_server_packet(RequestActionPacket::new(entity_id, Action::Attack)).unwrap(); } @@ -1097,6 +1229,30 @@ where )) .unwrap(); } + + pub fn create_character(&mut self, slot: usize, name: String) { + let hair_color = 0; + let hair_style = 0; + let start_job = 0; + let sex = Sex::Male; + + self.character_server_packet(CreateCharacterPacket::new( + name, slot as u8, hair_color, hair_style, start_job, sex, + )) + .unwrap(); + } + + pub fn delete_character(&mut self, character_id: CharacterId) { + let email = "a@a.com".to_string(); + + self.character_server_packet(DeleteCharacterPacket::new(character_id, email)) + .unwrap(); + } + + pub fn switch_character_slot(&mut self, origin_slot: usize, destination_slot: usize) { + self.character_server_packet(SwitchCharacterSlotPacket::new(origin_slot as u16, destination_slot as u16)) + .unwrap(); + } } #[cfg(test)] diff --git a/ragnarok_networking/src/lib.rs b/ragnarok_networking/src/lib.rs index f119bf2d2..cf5d745a0 100644 --- a/ragnarok_networking/src/lib.rs +++ b/ragnarok_networking/src/lib.rs @@ -2079,6 +2079,7 @@ pub enum EquipPosition { ShadowLeftRightAccessory, } +// TODO: Move this out of ragnarok_packets. impl EquipPosition { pub fn display_name(&self) -> &'static str { match self { @@ -2559,7 +2560,7 @@ pub struct FriendOnlineStatusPacket { #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0207)] pub struct FriendRequestPacket { - pub friend: Friend, + pub requestee: Friend, } #[derive(Debug, Clone, ByteConvertable)] @@ -2597,18 +2598,6 @@ pub struct FriendRequestResultPacket { pub friend: Friend, } -impl FriendRequestResultPacket { - pub fn into_message(&self) -> String { - // Messages taken from rAthena - match self.result { - FriendRequestResult::Accepted => format!("You have become friends with {}.", self.friend.name), - FriendRequestResult::Rejected => format!("{} does not want to be friends with you.", self.friend.name), - FriendRequestResult::OwnFriendListFull => "Your Friend List is full.".to_owned(), - FriendRequestResult::OtherFriendListFull => format!("{}'s Friend List is full.", self.friend.name), - } - } -} - #[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x02C6)]