diff --git a/Cargo.toml b/Cargo.toml index e99d6d06f..6b2c8cf85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,9 @@ bitflags = "2.4.2" cgmath = { version = "0.18", features = ["serde"] } chrono = "0.4" derive-new = "0.6.0" -korangar_interface = { path = "korangar_interface" } korangar_debug = { path = "korangar_debug" } +korangar_interface = { path = "korangar_interface" } +korangar_networking = { path = "korangar_networking" } num = "0.4.1" ragnarok_bytes = { path = "ragnarok_bytes" } ragnarok_networking = { path = "ragnarok_networking" } diff --git a/korangar/Cargo.toml b/korangar/Cargo.toml index 63a1a9777..41f7c8977 100644 --- a/korangar/Cargo.toml +++ b/korangar/Cargo.toml @@ -12,6 +12,7 @@ collision = { git = "https://github.com/rustgd/collision-rs.git" } derive-new = { workspace = true } image = "0.24.2" korangar_interface = { workspace = true, features = ["serde", "cgmath"] } +korangar_networking = { workspace = true, features = ["debug"] } lunify = "1.1.0" mlua = { version = "0.8", features = ["lua51", "vendored"] } num = { workspace = true } diff --git a/korangar/src/main.rs b/korangar/src/main.rs index 656511d1b..e3884a8bd 100644 --- a/korangar/src/main.rs +++ b/korangar/src/main.rs @@ -44,6 +44,7 @@ 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::Interface; +use korangar_networking::NetworkingSystem2; use ragnarok_networking::{SkillId, SkillType, UnitId}; use vulkano::device::{Device, DeviceCreateInfo, QueueCreateInfo}; #[cfg(feature = "debug")] @@ -388,6 +389,9 @@ fn main() { let client_info = load_client_info(&mut game_file_loader); let mut networking_system = NetworkingSystem::new(); + let mut networking_system_2 = NetworkingSystem2::::new(); + networking_system_2.connect_to_login_server(std::net::SocketAddr::new([49, 12, 109, 207].into(), 6900), "lucas", "password"); + interface.open_window(&application, &mut focus_state, &LoginWindow::new(&client_info)); #[cfg(feature = "debug")] @@ -536,6 +540,8 @@ fn main() { for event in network_events { match event { + NetworkEvent::LoginServerDisconnected => { + }, 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 diff --git a/korangar/src/network/mod.rs b/korangar/src/network/mod.rs index 7c617a571..6cb3a9b05 100644 --- a/korangar/src/network/mod.rs +++ b/korangar/src/network/mod.rs @@ -13,15 +13,11 @@ use korangar_debug::logging::{print_debug, Colorize, Timer}; #[cfg(feature = "debug")] use korangar_debug::profiling::RingBuffer; use korangar_interface::elements::{PrototypeElement, WeakElementCell}; -use korangar_interface::state::{PlainTrackedState, TrackedState, TrackedStateClone, TrackedStateExt, TrackedStateTake, TrackedStateVec}; -use ragnarok_bytes::{ByteStream, ConversionResult, FromBytes}; +use korangar_interface::state::{ + PlainTrackedState, TrackedState, TrackedStateClone, TrackedStateExt, TrackedStateTake, TrackedStateVec, ValueState, +}; +use ragnarok_bytes::{ByteStream, ConversionError, ConversionResult, FromBytes}; use ragnarok_networking::*; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use tokio::net::ToSocketAddrs; -use tokio::runtime::Handle; -use tokio::sync::mpsc::error::TryRecvError; -use tokio::sync::mpsc::{Receiver, Sender}; -use tokio::task::JoinHandle; pub use self::login::LoginSettings; use crate::graphics::Color; @@ -34,9 +30,9 @@ use crate::interface::windows::{CharacterSelectionWindow, FriendsWindow}; use crate::loaders::{ClientInfo, ServiceId}; #[cfg(feature = "debug")] -type PacketMetadata = Vec; +pub type PacketMetadata = Vec; #[cfg(not(feature = "debug"))] -type PacketMetadata = (); +pub type PacketMetadata = (); /// Extension trait for for [`ByteStream`] for working with network packets. #[cfg(feature = "debug")] @@ -92,9 +88,7 @@ where } } -pub enum NetworkAction {} - -/// An event triggered by the map server. +/// An event triggered by the a server. pub enum NetworkEvent { LoginServerDisconnected, /// Add an entity to the list of entities that the client is aware of. @@ -297,10 +291,11 @@ struct LoginData { // } pub struct NetworkingSystem { - packet_handler: PacketHandler, PacketMetadata>, login_stream: Option, character_stream: Option, map_stream: Option, + // TODO: Make this a heapless Vec or something + map_stream_buffer: Vec, login_keep_alive_timer: NetworkTimer, character_keep_alive_timer: NetworkTimer, map_keep_alive_timer: NetworkTimer, @@ -320,10 +315,10 @@ pub struct NetworkingSystem { impl NetworkingSystem { pub fn new() -> Self { - let packet_handler = Self::create_packet_handler(); let login_stream = None; let character_stream = None; let map_stream = None; + let map_stream_buffer = Vec::new(); let login_data = None; let characters = PlainTrackedState::default(); let move_request = PlainTrackedState::default(); @@ -339,12 +334,12 @@ impl NetworkingSystem { let packet_history = PlainTrackedState::default(); Self { - packet_handler, login_stream, character_stream, slot_count, login_data, map_stream, + map_stream_buffer, characters, move_request, friend_list, @@ -359,283 +354,6 @@ impl NetworkingSystem { } } - fn create_packet_handler() -> PacketHandler, PacketMetadata> { - let mut packet_handler = PacketHandler::, PacketMetadata>::default(); - - packet_handler.register(BroadcastMessagePacket::payload_from_bytes_recorded, |packet| { - let color = Color::rgb_u8(220, 200, 30); - let chat_message = ChatMessage::new(packet.message, color); - NetworkEvent::ChatMessage(chat_message) - }); - packet_handler.register(Broadcast2MessagePacket::payload_from_bytes_recorded, |packet| { - // NOTE: Drop the alpha channel because it might be 0. - let color = Color::rgb_u8(packet.font_color.red, packet.font_color.green, packet.font_color.blue); - let chat_message = ChatMessage::new(packet.message, color); - NetworkEvent::ChatMessage(chat_message) - }); - packet_handler.register(OverheadMessagePacket::payload_from_bytes_recorded, |packet| { - let color = Color::monochrome_u8(230); - let chat_message = ChatMessage::new(packet.message, color); - NetworkEvent::ChatMessage(chat_message) - }); - packet_handler.register(ServerMessagePacket::payload_from_bytes_recorded, |packet| { - let chat_message = ChatMessage::new(packet.message, Color::monochrome_u8(255)); - NetworkEvent::ChatMessage(chat_message) - }); - packet_handler.register(EntityMessagePacket::payload_from_bytes_recorded, |packet| { - // NOTE: Drop the alpha channel because it might be 0. - let color = Color::rgb_u8(packet.color.red, packet.color.green, packet.color.blue); - let chat_message = ChatMessage::new(packet.message, color); - NetworkEvent::ChatMessage(chat_message) - }); - packet_handler.register_noop(DisplayEmotionPacket::payload_from_bytes_recorded); - packet_handler.register(EntityMovePacket::payload_from_bytes_recorded, |packet| { - let (origin, destination) = ( - Vector2::new(packet.from_to.x1, packet.from_to.y1), - Vector2::new(packet.from_to.x2, packet.from_to.y2), - ); - NetworkEvent::EntityMove(packet.entity_id, origin, destination, packet.timestamp) - }); - packet_handler.register_noop(EntityStopMovePacket::payload_from_bytes_recorded); - packet_handler.register(PlayerMovePacket::payload_from_bytes_recorded, |packet| { - let (origin, destination) = ( - Vector2::new(packet.from_to.x1, packet.from_to.y1), - Vector2::new(packet.from_to.x2, packet.from_to.y2), - ); - NetworkEvent::PlayerMove(origin, destination, packet.timestamp) - }); - packet_handler.register(ChangeMapPacket::payload_from_bytes_recorded, |packet| { - NetworkEvent::ChangeMap( - packet.map_name.replace(".gat", ""), - Vector2::new(packet.position.x as usize, packet.position.y as usize), - ) - }); - packet_handler.register(EntityAppearedPacket::payload_from_bytes_recorded, |packet| { - NetworkEvent::AddEntity(packet.into()) - }); - packet_handler.register(EntityAppeared2Packet::payload_from_bytes_recorded, |packet| { - NetworkEvent::AddEntity(packet.into()) - }); - packet_handler.register(MovingEntityAppearedPacket::payload_from_bytes_recorded, |packet| { - NetworkEvent::AddEntity(packet.into()) - }); - packet_handler.register(EntityDisappearedPacket::payload_from_bytes_recorded, |packet| { - NetworkEvent::RemoveEntity(packet.entity_id) - }); - packet_handler.register(UpdateStatusPacket::payload_from_bytes_recorded, |packet| { - NetworkEvent::UpdateStatus(packet.status_type) - }); - packet_handler.register(UpdateStatusPacket1::payload_from_bytes_recorded, |packet| { - NetworkEvent::UpdateStatus(packet.status_type) - }); - packet_handler.register(UpdateStatusPacket2::payload_from_bytes_recorded, |packet| { - NetworkEvent::UpdateStatus(packet.status_type) - }); - packet_handler.register(UpdateStatusPacket3::payload_from_bytes_recorded, |packet| { - NetworkEvent::UpdateStatus(packet.status_type) - }); - packet_handler.register_noop(UpdateAttackRangePacket::payload_from_bytes_recorded); - packet_handler.register_noop(NewMailStatusPacket::payload_from_bytes_recorded); - packet_handler.register_noop(AchievementUpdatePacket::payload_from_bytes_recorded); - packet_handler.register_noop(AchievementListPacket::payload_from_bytes_recorded); - packet_handler.register_noop(CriticalWeightUpdatePacket::payload_from_bytes_recorded); - packet_handler.register(SpriteChangePacket::payload_from_bytes_recorded, |packet| { - match packet.sprite_type == 0 { - true => vec![NetworkEvent::ChangeJob(packet.account_id, packet.value)], - false => Vec::new(), - } - }); - packet_handler.register_noop(InventoyStartPacket::payload_from_bytes_recorded); - packet_handler.register_noop(InventoyEndPacket::payload_from_bytes_recorded); - packet_handler.register_noop(RegularItemListPacket::payload_from_bytes_recorded); - packet_handler.register_noop(EquippableItemListPacket::payload_from_bytes_recorded); - packet_handler.register_noop(EquippableSwitchItemListPacket::payload_from_bytes_recorded); - packet_handler.register_noop(MapTypePacket::payload_from_bytes_recorded); - packet_handler.register(UpdateSkillTreePacket::payload_from_bytes_recorded, |packet| { - NetworkEvent::SkillTree(packet.skill_information) - }); - packet_handler.register_noop(UpdateHotkeysPacket::payload_from_bytes_recorded); - packet_handler.register_noop(InitialStatusPacket::payload_from_bytes_recorded); - packet_handler.register_noop(UpdatePartyInvitationStatePacket::payload_from_bytes_recorded); - packet_handler.register_noop(UpdateShowEquipPacket::payload_from_bytes_recorded); - packet_handler.register_noop(UpdateConfigurationPacket::payload_from_bytes_recorded); - packet_handler.register_noop(NavigateToMonsterPacket::payload_from_bytes_recorded); - packet_handler.register_noop(MarkMinimapPositionPacket::payload_from_bytes_recorded); - packet_handler.register(NextButtonPacket::payload_from_bytes_recorded, |_| NetworkEvent::AddNextButton); - packet_handler.register(CloseButtonPacket::payload_from_bytes_recorded, |_| NetworkEvent::AddCloseButton); - packet_handler.register(DialogMenuPacket::payload_from_bytes_recorded, |packet| { - let choices = packet - .message - .split(':') - .map(String::from) - .filter(|text| !text.is_empty()) - .collect(); - - NetworkEvent::AddChoiceButtons(choices) - }); - packet_handler.register_noop(DisplaySpecialEffectPacket::payload_from_bytes_recorded); - packet_handler.register_noop(DisplaySkillCooldownPacket::payload_from_bytes_recorded); - packet_handler.register_noop(DisplaySkillEffectAndDamagePacket::payload_from_bytes_recorded); - packet_handler.register(DisplaySkillEffectNoDamagePacket::payload_from_bytes_recorded, |packet| { - NetworkEvent::HealEffect(packet.destination_entity_id, packet.heal_amount as usize) - - //NetworkEvent::VisualEffect() - }); - packet_handler.register_noop(DisplayPlayerHealEffect::payload_from_bytes_recorded); - packet_handler.register_noop(StatusChangePacket::payload_from_bytes_recorded); - packet_handler.register_noop(QuestNotificationPacket1::payload_from_bytes_recorded); - packet_handler.register_noop(HuntingQuestNotificationPacket::payload_from_bytes_recorded); - packet_handler.register_noop(HuntingQuestUpdateObjectivePacket::payload_from_bytes_recorded); - packet_handler.register_noop(QuestRemovedPacket::payload_from_bytes_recorded); - packet_handler.register_noop(QuestListPacket::payload_from_bytes_recorded); - packet_handler.register(VisualEffectPacket::payload_from_bytes_recorded, |packet| { - let path = match packet.effect { - VisualEffect::BaseLevelUp => "angel.str", - VisualEffect::JobLevelUp => "joblvup.str", - VisualEffect::RefineFailure => "bs_refinefailed.str", - VisualEffect::RefineSuccess => "bs_refinesuccess.str", - VisualEffect::GameOver => "help_angel\\help_angel\\help_angel.str", - VisualEffect::PharmacySuccess => "p_success.str", - VisualEffect::PharmacyFailure => "p_failed.str", - VisualEffect::BaseLevelUpSuperNovice => "help_angel\\help_angel\\help_angel.str", - VisualEffect::JobLevelUpSuperNovice => "help_angel\\help_angel\\help_angel.str", - VisualEffect::BaseLevelUpTaekwon => "help_angel\\help_angel\\help_angel.str", - }; - - NetworkEvent::VisualEffect(path, packet.entity_id) - }); - packet_handler.register_noop(DisplayGainedExperiencePacket::payload_from_bytes_recorded); - packet_handler.register_noop(DisplayImagePacket::payload_from_bytes_recorded); - packet_handler.register_noop(StateChangePacket::payload_from_bytes_recorded); - - packet_handler.register(QuestEffectPacket::payload_from_bytes_recorded, |packet| match packet.effect { - QuestEffect::None => NetworkEvent::RemoveQuestEffect(packet.entity_id), - _ => NetworkEvent::AddQuestEffect(packet), - }); - packet_handler.register(ItemPickupPacket::payload_from_bytes_recorded, |packet| { - NetworkEvent::AddIventoryItem(packet.index, packet.item_id, packet.equip_position, EquipPosition::None) - }); - packet_handler.register_noop(RemoveItemFromInventoryPacket::payload_from_bytes_recorded); - packet_handler.register(ServerTickPacket::payload_from_bytes_recorded, |packet| { - NetworkEvent::UpdateClientTick(packet.client_tick) - }); - packet_handler.register(RequestPlayerDetailsSuccessPacket::payload_from_bytes_recorded, |packet| { - NetworkEvent::UpdateEntityDetails(EntityId(packet.character_id.0), packet.name) - }); - packet_handler.register(RequestEntityDetailsSuccessPacket::payload_from_bytes_recorded, |packet| { - NetworkEvent::UpdateEntityDetails(packet.entity_id, packet.name) - }); - packet_handler.register(UpdateEntityHealthPointsPacket::payload_from_bytes_recorded, |packet| { - NetworkEvent::UpdateEntityHealth( - packet.entity_id, - packet.health_points as usize, - packet.maximum_health_points as usize, - ) - }); - packet_handler.register_noop(RequestPlayerAttackFailedPacket::payload_from_bytes_recorded); - packet_handler.register(DamagePacket::payload_from_bytes_recorded, |packet| { - NetworkEvent::DamageEffect(packet.destination_entity_id, packet.damage_amount as usize) - }); - packet_handler.register(NpcDialogPacket::payload_from_bytes_recorded, |packet| { - NetworkEvent::OpenDialog(packet.text, packet.npc_id) - }); - packet_handler.register( - RequestEquipItemStatusPacket::payload_from_bytes_recorded, - |packet| match packet.result { - RequestEquipItemStatus::Success => { - vec![NetworkEvent::UpdateEquippedPosition { - index: packet.inventory_index, - equipped_position: packet.equipped_position, - }] - } - _ => Vec::new(), - }, - ); - packet_handler.register( - RequestUnequipItemStatusPacket::payload_from_bytes_recorded, - |packet| match packet.result { - RequestUnequipItemStatus::Success => { - vec![NetworkEvent::UpdateEquippedPosition { - index: packet.inventory_index, - equipped_position: EquipPosition::None, - }] - } - _ => Vec::new(), - }, - ); - packet_handler.register_noop(Packet8302::payload_from_bytes_recorded); - packet_handler.register_noop(Packet180b::payload_from_bytes_recorded); - packet_handler.register(MapServerLoginSuccessPacket::payload_from_bytes_recorded, |packet| { - vec![ - NetworkEvent::UpdateClientTick(packet.client_tick), - NetworkEvent::SetPlayerPosition(Vector2::new(packet.position.x, packet.position.y)), - ] - }); - packet_handler.register(RestartResponsePacket::payload_from_bytes_recorded, |packet| { - match packet.result { - RestartResponseStatus::Ok => NetworkEvent::Disconnect, - RestartResponseStatus::Nothing => { - let color = Color::rgb_u8(255, 100, 100); - let chat_message = ChatMessage::new("Failed to log out.".to_string(), color); - NetworkEvent::ChatMessage(chat_message) - } - } - }); - packet_handler.register(DisconnectResponsePacket::payload_from_bytes_recorded, |packet| { - match packet.result { - DisconnectResponseStatus::Ok => NetworkEvent::Disconnect, - DisconnectResponseStatus::Wait10Seconds => { - let color = Color::rgb_u8(255, 100, 100); - let chat_message = ChatMessage::new("Please wait 10 seconds before trying to log out.".to_string(), color); - NetworkEvent::ChatMessage(chat_message) - } - } - }); - packet_handler.register_noop(UseSkillSuccessPacket::payload_from_bytes_recorded); - packet_handler.register_noop(ToUseSkillSuccessPacket::payload_from_bytes_recorded); - packet_handler.register(NotifySkillUnitPacket::payload_from_bytes_recorded, |packet| { - NetworkEvent::AddSkillUnit( - packet.entity_id, - packet.unit_id, - Vector2::new(packet.position.x as usize, packet.position.y as usize), - ) - }); - packet_handler.register(SkillUnitDisappearPacket::payload_from_bytes_recorded, |packet| { - NetworkEvent::RemoveSkillUnit(packet.entity_id) - }); - packet_handler.register_noop(NotifyGroundSkillPacket::payload_from_bytes_recorded); - packet_handler.register(FriendListPacket::payload_from_bytes_recorded, |packet| { - // events.push(NetworkEvent::SetFriends(friends)); - vec![] - }); - packet_handler.register_noop(FriendOnlineStatusPacket::payload_from_bytes_recorded); - packet_handler.register(FriendRequestPacket::payload_from_bytes_recorded, |packet| { - // events.push(NetworkEvent::FriendRequest(packet.friend)); - vec![] - }); - packet_handler.register(FriendRequestResultPacket::payload_from_bytes_recorded, |packet| { - // let color = Color::rgb_u8(220, 200, 30); - // let chat_message = ChatMessage::new(packet.into_message(), - // color); - // events.push(NetworkEvent::ChatMessage(chat_message)); - // events.push(NetworkEvent::AcceptFriendRequest(packet.friend)); - vec![] - }); - packet_handler.register(NotifyFriendRemovedPacket::payload_from_bytes_recorded, |packet| { - // events.push(NetworkEvent::RemoveFriend(packet.account_id, - // packet.character_id)); - vec![] - }); - packet_handler.register_noop(PartyInvitePacket::payload_from_bytes_recorded); - packet_handler.register_noop(StatusChangeSequencePacket::payload_from_bytes_recorded); - packet_handler.register_noop(ReputationPacket::payload_from_bytes_recorded); - packet_handler.register_noop(ClanInfoPacket::payload_from_bytes_recorded); - packet_handler.register_noop(ClanOnlineCountPacket::payload_from_bytes_recorded); - packet_handler.register_noop(ChangeMapCellPacket::payload_from_bytes_recorded); - - packet_handler - } - pub fn log_in( &mut self, client_info: &ClientInfo, @@ -659,50 +377,55 @@ impl NetworkingSystem { self.send_packet_to_login_server(LoginServerLoginPacket::new(username.clone(), password.clone())); - let (result, packet_data) = LocalHandler::default() - .register(LoginFailedPacket::payload_from_bytes_recorded, |packet| { - Err(match packet.reason { - LoginFailedReason::ServerClosed => "server closed", - LoginFailedReason::AlreadyLoggedIn => "someone has already logged in with this id", - LoginFailedReason::AlreadyOnline => "already online", - }) - }) - .register(LoginFailedPacket2::payload_from_bytes_recorded, |packet| { - Err(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", - }) - }) - .register(LoginServerLoginSuccessPacket::payload_from_bytes_recorded, Ok) - .take(self.login_stream.as_mut().expect("not connected")); - - let login_data = result.map_err(ToOwned::to_owned)?; + let response = self.get_data_from_login_server(); + let mut byte_stream: ByteStream = ByteStream::without_metadata(&response); + + let header = u16::from_bytes(&mut byte_stream).unwrap(); + let login_server_login_success_packet = match header { + LoginFailedPacket::HEADER => { + let packet = LoginFailedPacket::payload_from_bytes_recorded(&mut byte_stream).unwrap(); + match packet.reason { + LoginFailedReason::ServerClosed => return Err("server closed".to_string()), + LoginFailedReason::AlreadyLoggedIn => return Err("someone has already logged in with this id".to_string()), + LoginFailedReason::AlreadyOnline => return Err("already online".to_string()), + } + } + LoginFailedPacket2::HEADER => { + let packet = LoginFailedPacket2::payload_from_bytes_recorded(&mut byte_stream).unwrap(); + match packet.reason { + LoginFailedReason2::UnregisteredId => return Err("unregistered id".to_string()), + LoginFailedReason2::IncorrectPassword => return Err("incorrect password".to_string()), + LoginFailedReason2::IdExpired => return Err("id has expired".to_string()), + LoginFailedReason2::RejectedFromServer => return Err("rejected from server".to_string()), + LoginFailedReason2::BlockedByGMTeam => return Err("blocked by gm team".to_string()), + LoginFailedReason2::GameOutdated => return Err("game outdated".to_string()), + LoginFailedReason2::LoginProhibitedUntil => return Err("login prohibited until".to_string()), + LoginFailedReason2::ServerFull => return Err("server is full".to_string()), + LoginFailedReason2::CompanyAccountLimitReached => return Err("company account limit reached".to_string()), + } + } + LoginServerLoginSuccessPacket::HEADER => LoginServerLoginSuccessPacket::payload_from_bytes_recorded(&mut byte_stream).unwrap(), + _ => panic!(), + }; self.login_data = Some(LoginData::new( - login_data.account_id, - login_data.login_id1, - login_data.login_id2, - login_data.sex, + login_server_login_success_packet.account_id, + login_server_login_success_packet.login_id1, + login_server_login_success_packet.login_id2, + login_server_login_success_packet.sex, )); - if login_data.character_server_information.is_empty() { + if login_server_login_success_packet.character_server_information.is_empty() { return Err("no character server available".to_string()); } #[cfg(feature = "debug")] - self.update_packet_history(packet_data); + self.update_packet_history(byte_stream.into_metadata()); #[cfg(feature = "debug")] timer.stop(); - Ok(login_data.character_server_information) + Ok(login_server_login_success_packet.character_server_information) } pub fn select_server(&mut self, character_server_information: CharacterServerInformation) -> Result<(), String> { @@ -739,34 +462,41 @@ impl NetworkingSystem { #[cfg(feature = "debug")] self.update_packet_history(byte_stream.into_metadata()); - let (result, packet_data) = LocalHandler::default() - .register(LoginFailedPacket::payload_from_bytes_recorded, |packet| { - Err(match packet.reason { - LoginFailedReason::ServerClosed => "server closed", - LoginFailedReason::AlreadyLoggedIn => "someone has already logged in with this id", - LoginFailedReason::AlreadyOnline => "already online", - }) - }) - .register(CharacterServerLoginSuccessPacket::payload_from_bytes_recorded, Ok) - .take(self.character_stream.as_mut().expect("not connected")); - - let login_success = result.map_err(ToOwned::to_owned)?; + let response = self.get_data_from_character_server(); + let mut byte_stream: ByteStream = ByteStream::without_metadata(&response); - #[cfg(feature = "debug")] - self.update_packet_history(packet_data); + let header = u16::from_bytes(&mut byte_stream).unwrap(); + let character_server_login_success_packet = match header { + LoginFailedPacket::HEADER => { + let packet = LoginFailedPacket::payload_from_bytes_recorded(&mut byte_stream).unwrap(); + match packet.reason { + LoginFailedReason::ServerClosed => return Err("server closed".to_string()), + LoginFailedReason::AlreadyLoggedIn => return Err("someone has already logged in with this id".to_string()), + LoginFailedReason::AlreadyOnline => return Err("already online".to_string()), + } + } + CharacterServerLoginSuccessPacket::HEADER => { + CharacterServerLoginSuccessPacket::payload_from_bytes_recorded(&mut byte_stream).unwrap() + } + _ => panic!(), + }; self.send_packet_to_character_server(RequestCharacterListPacket::default()); - let (character_list_packet, packet_data) = LocalHandler::default() - .register(RequestCharacterListSuccessPacket::payload_from_bytes_recorded, |packet| packet) - .take(self.character_stream.as_mut().expect("not connected")); + #[cfg(feature = "debug")] + self.update_packet_history(byte_stream.into_metadata()); - self.characters.set(character_list_packet.character_information); + let response = self.get_data_from_character_server(); + let mut byte_stream: ByteStream = ByteStream::without_metadata(&response); + + let request_character_list_success_packet = + RequestCharacterListSuccessPacket::packet_from_bytes_recorded(&mut byte_stream).unwrap(); + self.characters.set(request_character_list_success_packet.character_information); #[cfg(feature = "debug")] - self.update_packet_history(packet_data); + self.update_packet_history(byte_stream.into_metadata()); - self.slot_count = login_success.normal_slot_count as usize; + self.slot_count = character_server_login_success_packet.normal_slot_count as usize; #[cfg(feature = "debug")] timer.stop(); @@ -859,6 +589,13 @@ impl NetworkingSystem { map_stream.write_all(&packet_bytes).expect("failed to send packet to map server"); } + fn get_data_from_login_server(&mut self) -> Vec { + let mut buffer = [0; 4096]; + let login_stream = self.login_stream.as_mut().expect("no login server connection"); + let response_length = login_stream.read(&mut buffer).expect("failed to get response from login server"); + buffer[..response_length].to_vec() + } + fn get_data_from_character_server(&mut self) -> Vec { let mut buffer = [0; 4096]; let character_stream = self.character_stream.as_mut().expect("no character server connection"); @@ -868,6 +605,23 @@ impl NetworkingSystem { buffer[..response_length].to_vec() } + fn try_get_data_from_map_server(&mut self) -> Option> { + let mut buffer = [0; 8096]; + + let stream_buffer_length = self.map_stream_buffer.len(); + let map_stream = self.map_stream.as_mut()?; + let response_length = map_stream.read(&mut buffer[stream_buffer_length..]).ok()?; + + // We copy the buffered data *after* the read call, to save so unnecessary + // computation. + buffer[..stream_buffer_length].copy_from_slice(&self.map_stream_buffer); + + self.map_stream_buffer.clear(); + + let total_length = stream_buffer_length + response_length; + Some(buffer[..total_length].to_vec()) + } + pub fn keep_alive(&mut self, delta_time: f64, client_tick: ClientTick) { if self.login_keep_alive_timer.update(delta_time) && self.login_stream.is_some() { self.send_packet_to_login_server(LoginServerKeepalivePacket::default()); @@ -898,24 +652,30 @@ impl NetworkingSystem { name, slot as u8, hair_color, hair_style, start_job, sex, )); - let (result, packet_data) = LocalHandler::default() - .register(CharacterCreationFailedPacket::payload_from_bytes_recorded, |packet| { - Err(match packet.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 that character slot", - CharacterCreationFailedReason::CharacterCerationFailed => "character creation failed", - }) - }) - .register(CreateCharacterSuccessPacket::payload_from_bytes_recorded, Ok) - .take(self.character_stream.as_mut().expect("not connected")); + let response = self.get_data_from_character_server(); + let mut byte_stream: ByteStream = ByteStream::without_metadata(&response); - let create_character_success = result.map_err(ToOwned::to_owned)?; + let header = u16::from_bytes(&mut byte_stream).unwrap(); + let create_character_success_packet = match header { + CharacterCreationFailedPacket::HEADER => { + let packet = CharacterCreationFailedPacket::payload_from_bytes_recorded(&mut byte_stream).unwrap(); + match packet.reason { + CharacterCreationFailedReason::CharacterNameAlreadyUsed => return Err("character name is already used".to_string()), + CharacterCreationFailedReason::NotOldEnough => return Err("you are not old enough to create a character".to_string()), + CharacterCreationFailedReason::NotAllowedToUseSlot => { + return Err("you are not allowed to use that character slot".to_string()); + } + CharacterCreationFailedReason::CharacterCerationFailed => return Err("character creation failed".to_string()), + } + } + CreateCharacterSuccessPacket::HEADER => CreateCharacterSuccessPacket::payload_from_bytes_recorded(&mut byte_stream).unwrap(), + _ => panic!(), + }; #[cfg(feature = "debug")] - self.update_packet_history(packet_data); + self.update_packet_history(byte_stream.into_metadata()); - self.characters.push(create_character_success.character_information); + self.characters.push(create_character_success_packet.character_information); #[cfg(feature = "debug")] timer.stop(); @@ -934,21 +694,27 @@ impl NetworkingSystem { self.send_packet_to_character_server(DeleteCharacterPacket::new(character_id, email)); - let (result, packet_data) = LocalHandler::default() - .register(CharacterDeletionFailedPacket::payload_from_bytes_recorded, |packet| { - Err(match packet.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", - }) - }) - .register(CharacterDeletionSuccessPacket::payload_from_bytes_recorded, Ok) - .take(self.character_stream.as_mut().expect("not connected")); + let response = self.get_data_from_character_server(); + let mut byte_stream: ByteStream = ByteStream::without_metadata(&response); - let _ = result.map_err(ToOwned::to_owned)?; + let header = u16::from_bytes(&mut byte_stream).unwrap(); + match header { + CharacterDeletionFailedPacket::HEADER => { + let packet = CharacterDeletionFailedPacket::payload_from_bytes_recorded(&mut byte_stream).unwrap(); + match packet.reason { + CharacterDeletionFailedReason::NotAllowed => return Err("you are not allowed to delete this character".to_string()), + CharacterDeletionFailedReason::CharacterNotFound => return Err("character was not found".to_string()), + CharacterDeletionFailedReason::NotEligible => return Err("character is not eligible for deletion".to_string()), + } + } + CharacterDeletionSuccessPacket::HEADER => { + let _ = CharacterDeletionSuccessPacket::payload_from_bytes_recorded(&mut byte_stream).unwrap(); + } + _ => panic!(), + } #[cfg(feature = "debug")] - self.update_packet_history(packet_data); + self.update_packet_history(byte_stream.into_metadata()); self.characters.retain(|character| character.character_id != character_id); @@ -967,38 +733,43 @@ impl NetworkingSystem { self.send_packet_to_character_server(SelectCharacterPacket::new(slot as u8)); - let (result, packet_data) = LocalHandler::default() - .register(CharacterSelectionFailedPacket::payload_from_bytes_recorded, |packet| { - Err(match packet.reason { - CharacterSelectionFailedReason::RejectedFromServer => "rejected from server", - }) - }) - .register(LoginFailedPacket::payload_from_bytes_recorded, |packet| { - Err(match packet.reason { - LoginFailedReason::ServerClosed => "Server closed", - LoginFailedReason::AlreadyLoggedIn => "Someone has already logged in with this ID", - LoginFailedReason::AlreadyOnline => "Already online", - }) - }) - .register(MapServerUnavailablePacket::payload_from_bytes_recorded, |_| { - Err("Map server currently unavailable") - }) - .register(CharacterSelectionSuccessPacket::payload_from_bytes_recorded, Ok) - .take(self.character_stream.as_mut().expect("not connected")); - - let character_selection_success = result.map_err(ToOwned::to_owned)?; + let response = self.get_data_from_character_server(); + let mut byte_stream: ByteStream = ByteStream::without_metadata(&response); - #[cfg(feature = "debug")] - self.update_packet_history(packet_data); + let header = u16::from_bytes(&mut byte_stream).unwrap(); + let character_selection_success_packet = match header { + CharacterSelectionFailedPacket::HEADER => { + let packet = CharacterSelectionFailedPacket::payload_from_bytes_recorded(&mut byte_stream).unwrap(); + match packet.reason { + CharacterSelectionFailedReason::RejectedFromServer => return Err("rejected from server".to_string()), + } + } + LoginFailedPacket::HEADER => { + let packet = LoginFailedPacket::payload_from_bytes_recorded(&mut byte_stream).unwrap(); + match packet.reason { + LoginFailedReason::ServerClosed => return Err("Server closed".to_string()), + LoginFailedReason::AlreadyLoggedIn => return Err("Someone has already logged in with this ID".to_string()), + LoginFailedReason::AlreadyOnline => return Err("Already online".to_string()), + } + } + MapServerUnavailablePacket::HEADER => { + let _ = MapServerUnavailablePacket::payload_from_bytes_recorded(&mut byte_stream).unwrap(); + return Err("Map server currently unavailable".to_string()); + } + CharacterSelectionSuccessPacket::HEADER => { + CharacterSelectionSuccessPacket::payload_from_bytes_recorded(&mut byte_stream).unwrap() + } + _ => panic!(), + }; - let server_ip = IpAddr::V4(character_selection_success.map_server_ip.into()); - let server_port = character_selection_success.map_server_port; + let server_ip = IpAddr::V4(character_selection_success_packet.map_server_ip.into()); + let server_port = character_selection_success_packet.map_server_port; #[cfg(feature = "debug")] print_debug!( "connecting to map server at {} on port {}", server_ip.magenta(), - character_selection_success.map_server_port.magenta(), + character_selection_success_packet.map_server_port.magenta(), ); let socket_address = SocketAddr::new(server_ip, server_port); @@ -1013,12 +784,15 @@ impl NetworkingSystem { self.send_packet_to_map_server(MapServerLoginPacket::new( account_id, - character_selection_success.character_id, + character_selection_success_packet.character_id, login_data.login_id1, ClientTick(100), // TODO: what is the logic here? login_data.sex, )); + #[cfg(feature = "debug")] + self.update_packet_history(byte_stream.into_metadata()); + let character_information = self .characters .get() @@ -1035,7 +809,7 @@ impl NetworkingSystem { Ok(( account_id, character_information, - character_selection_success.map_name.replace(".gat", ""), + character_selection_success_packet.map_name.replace(".gat", ""), )) } @@ -1063,47 +837,34 @@ impl NetworkingSystem { self.send_packet_to_character_server(SwitchCharacterSlotPacket::new(origin_slot as u16, destination_slot as u16)); - let (result, packet_data) = LocalHandler::default() - .register( - SwitchCharacterSlotResponsePacket::payload_from_bytes_recorded, - |packet| match packet.status { - SwitchCharacterSlotResponseStatus::Success => Ok(()), - SwitchCharacterSlotResponseStatus::Error => Err("failed to move character to a different slot"), - }, - ) - .take(self.character_stream.as_mut().expect("not connected")); - - result.map_err(ToOwned::to_owned)?; - - #[cfg(feature = "debug")] - self.update_packet_history(packet_data); + let response = self.get_data_from_character_server(); + let mut byte_stream: ByteStream = ByteStream::without_metadata(&response); - let (_, packet_data) = LocalHandler::default() - .register(CharacterServerLoginSuccessPacket::payload_from_bytes_recorded, |packet| packet) - .take(self.character_stream.as_mut().expect("not connected")); + let switch_character_slot_response_packet = + SwitchCharacterSlotResponsePacket::packet_from_bytes_recorded(&mut byte_stream).unwrap(); - result.map_err(ToOwned::to_owned)?; + match switch_character_slot_response_packet.status { + SwitchCharacterSlotResponseStatus::Success => { + let _character_server_login_success_packet = + CharacterServerLoginSuccessPacket::packet_from_bytes_recorded(&mut byte_stream).unwrap(); + let _packet_006b = Packet6b00::packet_from_bytes_recorded(&mut byte_stream).unwrap(); - #[cfg(feature = "debug")] - self.update_packet_history(packet_data); + let character_count = self.characters.len(); + self.characters.clear(); - let (_, packet_data) = LocalHandler::default() - .register(Packet6b00::payload_from_bytes_recorded, |packet| packet) - .take(self.character_stream.as_mut().expect("not connected")); + for _index in 0..character_count { + let character_information = CharacterInformation::from_bytes(&mut byte_stream).unwrap(); + self.characters.push(character_information); + } - result.map_err(ToOwned::to_owned)?; + // packet_length and packet 0x09a0 are left unread because we + // don't need them + } + SwitchCharacterSlotResponseStatus::Error => return Err("failed to move character to a different slot".to_string()), + } #[cfg(feature = "debug")] - self.update_packet_history(packet_data); - - let character_count = self.characters.len(); - self.characters.clear(); - - // FIX: How do we just pop something from the byte stream here? - // for _index in 0..character_count { - // let character_information = CharacterInformation::from_bytes(&mut - // byte_stream).unwrap(); self.characters. - // push(character_information); } + self.update_packet_history(byte_stream.into_metadata()); self.move_request.take(); @@ -1219,283 +980,531 @@ impl NetworkingSystem { )); } - fn handle_unknown_packet(byte_stream: &mut ByteStream) { - let packet = UnknownPacket::new(byte_stream.remaining_bytes()); - byte_stream.incoming_packet(&packet); - } - #[cfg_attr(feature = "debug", korangar_debug::profile)] pub fn network_events(&mut self) -> Vec { - let (events, metadata) = self - .map_stream - .as_mut() - .map(|stream| self.packet_handler.process_one(stream, Self::handle_unknown_packet)) - .unwrap_or_default(); - - #[cfg(feature = "debug")] - self.update_packet_history(metadata); - - events - } - - #[cfg(feature = "debug")] - pub fn clear_packet_history(&mut self) { - self.packet_history.mutate(|buffer| { - buffer.clear(); - }); - } - - #[cfg(feature = "debug")] - pub fn packet_window(&self) -> PacketWindow<256> { - PacketWindow::new(self.packet_history.new_remote(), self.update_packets.clone()) - } -} - -enum NetworkTaskHandle { - FailedToConnect, - ConnectionClosed, -} - -enum ServerConnection { - Healthy { - handle: JoinHandle<()>, - action_sender: Sender>, - event_receiver: Receiver, - }, - Closing { - handle: JoinHandle<()>, - closing_since: std::time::Instant, - }, - Disconnected, -} - -impl ServerConnection { - fn take(&mut self) -> Self { - std::mem::replace(self, ServerConnection::Disconnected) - } -} + let mut events = Vec::new(); -struct NetworkingSystem2 { - runtime_handle: Handle, - login_server_connection: ServerConnection, - character_server_connection: ServerConnection, - map_server_connection: ServerConnection, -} + while let Some(data) = self.try_get_data_from_map_server() { + let mut byte_stream: ByteStream = ByteStream::without_metadata(&data); + + while !byte_stream.is_empty() { + let save_point = byte_stream.create_save_point(); + + // Packet is cut-off at the header + let Ok(header) = u16::from_bytes(&mut byte_stream) else { + byte_stream.restore_save_point(save_point); + self.map_stream_buffer = byte_stream.remaining_bytes(); + break; + }; + + match self.handle_packet(&mut byte_stream, header, &mut events) { + Ok(true) => {} + // Unknown packet + Ok(false) => { + #[cfg(feature = "debug")] + { + byte_stream.restore_save_point(save_point); + let packet = UnknownPacket::new(byte_stream.remaining_bytes()); + byte_stream.incoming_packet(&packet); + } -impl NetworkingSystem2 { - fn new() -> Self { - let runtime_handle = Self::spawn_networking_thread(); + break; + } + // Cut-off packet (probably). + Err(error) if error.is_byte_stream_too_short() => { + byte_stream.restore_save_point(save_point); + self.map_stream_buffer = byte_stream.remaining_bytes(); + break; + } + Err(error) => panic!("{:?}", error), + } + } - Self { - runtime_handle, - login_server_connection: ServerConnection::Disconnected, - character_server_connection: ServerConnection::Disconnected, - map_server_connection: ServerConnection::Disconnected, + #[cfg(feature = "debug")] + self.update_packet_history(byte_stream.into_metadata()); } - } - fn spawn_networking_thread() -> Handle { - let (handle_sender, mut handle_receiver) = tokio::sync::oneshot::channel::(); + events + } - std::thread::spawn(move || { - let runtime = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); + #[cfg_attr(feature = "debug", korangar_debug::profile)] + fn handle_packet( + &mut self, + byte_stream: &mut ByteStream, + header: u16, + events: &mut Vec, + ) -> ConversionResult { + match header { + BroadcastMessagePacket::HEADER => { + let packet = BroadcastMessagePacket::payload_from_bytes_recorded(byte_stream)?; + let color = Color::rgb_u8(220, 200, 30); + let chat_message = ChatMessage::new(packet.message, color); + events.push(NetworkEvent::ChatMessage(chat_message)); + } + Broadcast2MessagePacket::HEADER => { + let packet = Broadcast2MessagePacket::payload_from_bytes_recorded(byte_stream)?; + // NOTE: Drop the alpha channel because it might be 0. + let color = Color::rgb_u8(packet.font_color.red, packet.font_color.green, packet.font_color.blue); + let chat_message = ChatMessage::new(packet.message, color); + events.push(NetworkEvent::ChatMessage(chat_message)); + } + OverheadMessagePacket::HEADER => { + let packet = OverheadMessagePacket::payload_from_bytes_recorded(byte_stream)?; + let color = Color::monochrome_u8(230); + let chat_message = ChatMessage::new(packet.message, color); + events.push(NetworkEvent::ChatMessage(chat_message)); + } + ServerMessagePacket::HEADER => { + let packet = ServerMessagePacket::payload_from_bytes_recorded(byte_stream)?; + let chat_message = ChatMessage::new(packet.message, Color::monochrome_u8(255)); + events.push(NetworkEvent::ChatMessage(chat_message)); + } + EntityMessagePacket::HEADER => { + let packet = EntityMessagePacket::payload_from_bytes_recorded(byte_stream)?; + // NOTE: Drop the alpha channel because it might be 0. + let color = Color::rgb_u8(packet.color.red, packet.color.green, packet.color.blue); + let chat_message = ChatMessage::new(packet.message, color); + events.push(NetworkEvent::ChatMessage(chat_message)); + } + DisplayEmotionPacket::HEADER => { + let _packet = DisplayEmotionPacket::payload_from_bytes_recorded(byte_stream)?; + } + EntityMovePacket::HEADER => { + let packet = EntityMovePacket::payload_from_bytes_recorded(byte_stream)?; + let (origin, destination) = ( + Vector2::new(packet.from_to.x1, packet.from_to.y1), + Vector2::new(packet.from_to.x2, packet.from_to.y2), + ); + events.push(NetworkEvent::EntityMove( + packet.entity_id, + origin, + destination, + packet.timestamp, + )); + } + EntityStopMovePacket::HEADER => { + let _packet = EntityStopMovePacket::payload_from_bytes_recorded(byte_stream)?; + } + PlayerMovePacket::HEADER => { + let packet = PlayerMovePacket::payload_from_bytes_recorded(byte_stream)?; + let (origin, destination) = ( + Vector2::new(packet.from_to.x1, packet.from_to.y1), + Vector2::new(packet.from_to.x2, packet.from_to.y2), + ); + events.push(NetworkEvent::PlayerMove(origin, destination, packet.timestamp)); + } + ChangeMapPacket::HEADER => { + let packet = ChangeMapPacket::payload_from_bytes_recorded(byte_stream)?; + events.push(NetworkEvent::ChangeMap( + packet.map_name.replace(".gat", ""), + Vector2::new(packet.position.x as usize, packet.position.y as usize), + )); + } + EntityAppearedPacket::HEADER => { + let packet = EntityAppearedPacket::payload_from_bytes_recorded(byte_stream)?; + events.push(NetworkEvent::AddEntity(packet.into())); + } + EntityAppeared2Packet::HEADER => { + let packet = EntityAppeared2Packet::payload_from_bytes_recorded(byte_stream)?; + events.push(NetworkEvent::AddEntity(packet.into())); + } + MovingEntityAppearedPacket::HEADER => { + let packet = MovingEntityAppearedPacket::payload_from_bytes_recorded(byte_stream)?; + events.push(NetworkEvent::AddEntity(packet.into())); + } + EntityDisappearedPacket::HEADER => { + let packet = EntityDisappearedPacket::payload_from_bytes_recorded(byte_stream)?; + events.push(NetworkEvent::RemoveEntity(packet.entity_id)); + } + UpdateStatusPacket::HEADER => { + let packet = UpdateStatusPacket::payload_from_bytes_recorded(byte_stream)?; + events.push(NetworkEvent::UpdateStatus(packet.status_type)); + } + UpdateStatusPacket1::HEADER => { + let packet = UpdateStatusPacket1::payload_from_bytes_recorded(byte_stream)?; + events.push(NetworkEvent::UpdateStatus(packet.status_type)); + } + UpdateStatusPacket2::HEADER => { + let packet = UpdateStatusPacket2::payload_from_bytes_recorded(byte_stream)?; + events.push(NetworkEvent::UpdateStatus(packet.status_type)); + } + UpdateStatusPacket3::HEADER => { + let packet = UpdateStatusPacket3::payload_from_bytes_recorded(byte_stream)?; + events.push(NetworkEvent::UpdateStatus(packet.status_type)); + } + UpdateAttackRangePacket::HEADER => { + let _packet = UpdateAttackRangePacket::payload_from_bytes_recorded(byte_stream)?; + } + NewMailStatusPacket::HEADER => { + let _packet = NewMailStatusPacket::payload_from_bytes_recorded(byte_stream)?; + } + AchievementUpdatePacket::HEADER => { + let _packet = AchievementUpdatePacket::payload_from_bytes_recorded(byte_stream)?; + } + AchievementListPacket::HEADER => { + let _packet = AchievementListPacket::payload_from_bytes_recorded(byte_stream)?; + } + CriticalWeightUpdatePacket::HEADER => { + let _packet = CriticalWeightUpdatePacket::payload_from_bytes_recorded(byte_stream)?; + } + SpriteChangePacket::HEADER => { + let packet = SpriteChangePacket::payload_from_bytes_recorded(byte_stream)?; + if packet.sprite_type == 0 { + events.push(NetworkEvent::ChangeJob(packet.account_id, packet.value)); + } + } + InventoyStartPacket::HEADER => { + let _packet = InventoyStartPacket::payload_from_bytes_recorded(byte_stream)?; + let mut item_data = Vec::new(); + + // TODO: it might be better for performance and resilience to instead save a + // state in the networking system instaed of buffering *all* + // inventory packets if one of them is cut off + loop { + let header = u16::from_bytes(byte_stream)?; + + match header { + InventoyEndPacket::HEADER => { + break; + } + RegularItemListPacket::HEADER => { + let packet = RegularItemListPacket::payload_from_bytes_recorded(byte_stream)?; + for item_information in packet.item_information { + item_data.push(( + item_information.index, + item_information.item_id, + EquipPosition::None, + EquipPosition::None, + )); // TODO: Don't add that data here, only equippable items need this data. + } + } + EquippableItemListPacket::HEADER => { + let packet = EquippableItemListPacket::payload_from_bytes_recorded(byte_stream)?; + for item_information in packet.item_information { + item_data.push(( + item_information.index, + item_information.item_id, + item_information.equip_position, + item_information.equipped_position, + )); + } + } + _ => return Err(ConversionError::from_message("expected inventory packet")), + } + } - let handle = runtime.handle().clone(); - handle_sender.send(handle).unwrap(); + let _ = InventoyEndPacket::payload_from_bytes_recorded(byte_stream)?; - runtime.block_on(std::future::pending::<()>()); - }); + events.push(NetworkEvent::Inventory(item_data)); + } + EquippableSwitchItemListPacket::HEADER => { + let _packet = EquippableSwitchItemListPacket::payload_from_bytes_recorded(byte_stream)?; + } + MapTypePacket::HEADER => { + let _packet = MapTypePacket::payload_from_bytes_recorded(byte_stream)?; + } + UpdateSkillTreePacket::HEADER => { + let packet = UpdateSkillTreePacket::payload_from_bytes_recorded(byte_stream)?; + events.push(NetworkEvent::SkillTree(packet.skill_information)); + } + UpdateHotkeysPacket::HEADER => { + let _packet = UpdateHotkeysPacket::payload_from_bytes_recorded(byte_stream)?; + } + InitialStatusPacket::HEADER => { + let _packet = InitialStatusPacket::payload_from_bytes_recorded(byte_stream)?; + } + UpdatePartyInvitationStatePacket::HEADER => { + let _packet = UpdatePartyInvitationStatePacket::payload_from_bytes_recorded(byte_stream)?; + } + UpdateShowEquipPacket::HEADER => { + let _packet = UpdateShowEquipPacket::payload_from_bytes_recorded(byte_stream)?; + } + UpdateConfigurationPacket::HEADER => { + let _packet = UpdateConfigurationPacket::payload_from_bytes_recorded(byte_stream)?; + } + NavigateToMonsterPacket::HEADER => { + let _packet = NavigateToMonsterPacket::payload_from_bytes_recorded(byte_stream)?; + } + MarkMinimapPositionPacket::HEADER => { + let _packet = MarkMinimapPositionPacket::payload_from_bytes_recorded(byte_stream)?; + } + NextButtonPacket::HEADER => { + let _packet = NextButtonPacket::payload_from_bytes_recorded(byte_stream)?; + events.push(NetworkEvent::AddNextButton); + } + CloseButtonPacket::HEADER => { + let _packet = CloseButtonPacket::payload_from_bytes_recorded(byte_stream)?; + events.push(NetworkEvent::AddCloseButton); + } + DialogMenuPacket::HEADER => { + let packet = DialogMenuPacket::payload_from_bytes_recorded(byte_stream)?; + let choices = packet + .message + .split(':') + .map(String::from) + .filter(|text| !text.is_empty()) + .collect(); + + events.push(NetworkEvent::AddChoiceButtons(choices)); + } + DisplaySpecialEffectPacket::HEADER => { + let _packet = DisplaySpecialEffectPacket::payload_from_bytes_recorded(byte_stream)?; + } + DisplaySkillCooldownPacket::HEADER => { + let _packet = DisplaySkillCooldownPacket::payload_from_bytes_recorded(byte_stream)?; + } + DisplaySkillEffectAndDamagePacket::HEADER => { + let _packet = DisplaySkillEffectAndDamagePacket::payload_from_bytes_recorded(byte_stream)?; + } + DisplaySkillEffectNoDamagePacket::HEADER => { + let packet = DisplaySkillEffectNoDamagePacket::payload_from_bytes_recorded(byte_stream)?; + events.push(NetworkEvent::HealEffect( + packet.destination_entity_id, + packet.heal_amount as usize, + )); - loop { - match handle_receiver.try_recv() { - Ok(handle) => break handle, - Err(tokio::sync::oneshot::error::TryRecvError::Empty) => { - #[cfg(feature = "debug")] - print_debug!("waiting for {} to spawn", "Tokio runtime".magenta()); - std::thread::sleep(std::time::Duration::from_millis(10)); - } - Err(tokio::sync::oneshot::error::TryRecvError::Closed) => { - panic!("failed to start Tokio runtime"); - } + //events.push(NetworkEvent::VisualEffect()); + } + DisplayPlayerHealEffect::HEADER => { + let _packet = DisplayPlayerHealEffect::payload_from_bytes_recorded(byte_stream)?; + } + StatusChangePacket::HEADER => { + let _packet = StatusChangePacket::payload_from_bytes_recorded(byte_stream)?; + } + QuestNotificationPacket1::HEADER => { + let _packet = QuestNotificationPacket1::payload_from_bytes_recorded(byte_stream)?; + } + HuntingQuestNotificationPacket::HEADER => { + let _packet = HuntingQuestNotificationPacket::payload_from_bytes_recorded(byte_stream)?; + } + HuntingQuestUpdateObjectivePacket::HEADER => { + let _packet = HuntingQuestUpdateObjectivePacket::payload_from_bytes_recorded(byte_stream)?; + } + QuestRemovedPacket::HEADER => { + let _packet = QuestRemovedPacket::payload_from_bytes_recorded(byte_stream)?; + } + QuestListPacket::HEADER => { + let _packet = QuestListPacket::payload_from_bytes_recorded(byte_stream)?; + } + VisualEffectPacket::HEADER => { + let packet = VisualEffectPacket::payload_from_bytes_recorded(byte_stream)?; + let path = match packet.effect { + VisualEffect::BaseLevelUp => "angel.str", + VisualEffect::JobLevelUp => "joblvup.str", + VisualEffect::RefineFailure => "bs_refinefailed.str", + VisualEffect::RefineSuccess => "bs_refinesuccess.str", + VisualEffect::GameOver => "help_angel\\help_angel\\help_angel.str", + VisualEffect::PharmacySuccess => "p_success.str", + VisualEffect::PharmacyFailure => "p_failed.str", + VisualEffect::BaseLevelUpSuperNovice => "help_angel\\help_angel\\help_angel.str", + VisualEffect::JobLevelUpSuperNovice => "help_angel\\help_angel\\help_angel.str", + VisualEffect::BaseLevelUpTaekwon => "help_angel\\help_angel\\help_angel.str", + }; + + events.push(NetworkEvent::VisualEffect(path, packet.entity_id)); + } + DisplayGainedExperiencePacket::HEADER => { + let _packet = DisplayGainedExperiencePacket::payload_from_bytes_recorded(byte_stream)?; + } + DisplayImagePacket::HEADER => { + let _packet = DisplayImagePacket::payload_from_bytes_recorded(byte_stream)?; + } + StateChangePacket::HEADER => { + let _packet = StateChangePacket::payload_from_bytes_recorded(byte_stream)?; } - } - } - fn handle_connection(connection: &mut ServerConnection, events: &mut Vec) { - match connection.take() { - ServerConnection::Healthy { - handle, - action_sender, - event_receiver, - } => match event_receiver.try_recv() { - Ok(login_event) => { - events.push(login_event); - *connection = ServerConnection::Healthy { - handle, - action_sender, - event_receiver, - }; - } - Err(TryRecvError::Empty) => { - *connection = ServerConnection::Healthy { - handle, - action_sender, - event_receiver, - }; - } - Err(error) => { - *connection = ServerConnection::Closing { - handle, - closing_since: std::time::Instant::now(), - }; + QuestEffectPacket::HEADER => { + let packet = QuestEffectPacket::payload_from_bytes_recorded(byte_stream)?; + let event = match packet.effect { + QuestEffect::None => NetworkEvent::RemoveQuestEffect(packet.entity_id), + _ => NetworkEvent::AddQuestEffect(packet), + }; + events.push(event); + } + ItemPickupPacket::HEADER => { + let packet = ItemPickupPacket::payload_from_bytes_recorded(byte_stream)?; + events.push(NetworkEvent::AddIventoryItem( + packet.index, + packet.item_id, + packet.equip_position, + EquipPosition::None, + )); + } + RemoveItemFromInventoryPacket::HEADER => { + let _packet = RemoveItemFromInventoryPacket::payload_from_bytes_recorded(byte_stream)?; + } + ServerTickPacket::HEADER => { + let packet = ServerTickPacket::payload_from_bytes_recorded(byte_stream)?; + events.push(NetworkEvent::UpdateClientTick(packet.client_tick)); + } + RequestPlayerDetailsSuccessPacket::HEADER => { + let packet = RequestPlayerDetailsSuccessPacket::payload_from_bytes_recorded(byte_stream)?; + events.push(NetworkEvent::UpdateEntityDetails(EntityId(packet.character_id.0), packet.name)); + } + RequestEntityDetailsSuccessPacket::HEADER => { + let packet = RequestEntityDetailsSuccessPacket::payload_from_bytes_recorded(byte_stream)?; + events.push(NetworkEvent::UpdateEntityDetails(packet.entity_id, packet.name)); + } + UpdateEntityHealthPointsPacket::HEADER => { + let packet = UpdateEntityHealthPointsPacket::payload_from_bytes_recorded(byte_stream)?; + events.push(NetworkEvent::UpdateEntityHealth( + packet.entity_id, + packet.health_points as usize, + packet.maximum_health_points as usize, + )); + } + RequestPlayerAttackFailedPacket::HEADER => { + let _packet = RequestPlayerAttackFailedPacket::payload_from_bytes_recorded(byte_stream)?; + } + DamagePacket::HEADER => { + let packet = DamagePacket::payload_from_bytes_recorded(byte_stream)?; + events.push(NetworkEvent::DamageEffect( + packet.destination_entity_id, + packet.damage_amount as usize, + )); + } + NpcDialogPacket::HEADER => { + let packet = NpcDialogPacket::payload_from_bytes_recorded(byte_stream)?; + events.push(NetworkEvent::OpenDialog(packet.text, packet.npc_id)); + } + RequestEquipItemStatusPacket::HEADER => { + let packet = RequestEquipItemStatusPacket::payload_from_bytes_recorded(byte_stream)?; + if let RequestEquipItemStatus::Success = packet.result { + events.push(NetworkEvent::UpdateEquippedPosition { + index: packet.inventory_index, + equipped_position: packet.equipped_position, + }); } - }, - ServerConnection::Closing { handle, closing_since } => { - if handle.is_finished() { - events.push(NetworkEvent::LoginServerDisconnected); - *connection = ServerConnection::Disconnected; - } else if closing_since.elapsed() > std::time::Duration::from_secs(2) { - println!("Networking task not gracefully shutting down: attempting to abort it"); - - handle.abort(); - - *connection = ServerConnection::Closing { - handle, - closing_since: std::time::Instant::now(), - }; + } + RequestUnequipItemStatusPacket::HEADER => { + let packet = RequestUnequipItemStatusPacket::payload_from_bytes_recorded(byte_stream)?; + if let RequestUnequipItemStatus::Success = packet.result { + events.push(NetworkEvent::UpdateEquippedPosition { + index: packet.inventory_index, + equipped_position: EquipPosition::None, + }); } } - ServerConnection::Disconnected => {} - }; - } - - async fn handle_server_thing( - address: impl ToSocketAddrs + Send, - mut action_receiver: tokio::sync::mpsc::Receiver>, - ping_frequency: Duration, - ) -> Result<(), NetworkTaskHandle> - where - Ping: OutgoingPacket + Default, - { - let mut stream = tokio::net::TcpStream::connect(address) - .await - .map_err(|_| NetworkTaskHandle::FailedToConnect)?; - let mut packet_handler = PacketHandler::, PacketMetadata>::default(); - let mut interval = tokio::time::interval(ping_frequency); - let mut buffer = [0u8; 4096]; - - loop { - tokio::select! { - // Send a packet to the server. - action = action_receiver.recv() => { - let Some(action) = action else { - // Channel was closed by the main thread. - return Ok(()); - }; - - stream.write_all(&action).await.map_err(|_| NetworkTaskHandle::ConnectionClosed)?; + Packet8302::HEADER => { + let _packet = Packet8302::payload_from_bytes_recorded(byte_stream)?; + } + Packet180b::HEADER => { + let _packet = Packet180b::payload_from_bytes_recorded(byte_stream)?; + } + MapServerLoginSuccessPacket::HEADER => { + let packet = MapServerLoginSuccessPacket::payload_from_bytes_recorded(byte_stream)?; + events.push(NetworkEvent::UpdateClientTick(packet.client_tick)); + events.push(NetworkEvent::SetPlayerPosition(Vector2::new( + packet.position.x, + packet.position.y, + ))); + } + RestartResponsePacket::HEADER => { + let packet = RestartResponsePacket::payload_from_bytes_recorded(byte_stream)?; + match packet.result { + RestartResponseStatus::Ok => events.push(NetworkEvent::Disconnect), + RestartResponseStatus::Nothing => { + let color = Color::rgb_u8(255, 100, 100); + let chat_message = ChatMessage::new("Failed to log out.".to_string(), color); + events.push(NetworkEvent::ChatMessage(chat_message)); + } } - // Receive some packets from the server. - received_bytes = stream.read(&mut buffer) => { - let Ok(received_bytes) = received_bytes else { - // Channel was closed by the main thread. - return Err(NetworkTaskHandle::ConnectionClosed); - }; - - // FIX: Handle cut-off packets - let data = &buffer[..received_bytes]; - let mut byte_stream = ByteStream::without_metadata(&data); - let mut events = Vec::new(); - - while !byte_stream.is_empty() { - match packet_handler.process_one(&mut byte_stream) { - HandlerResult::Ok(packet_events) => events.extend(packet_events.into_iter()), - HandlerResult::UnhandledPacket => { - let packet = UnknownPacket::new(byte_stream.remaining_bytes()); - byte_stream.incoming_packet(&packet); - break; - }, - HandlerResult::PacketCutOff => { - panic!("Implement logic"); - }, - HandlerResult::InternalError(error) => panic!("Error parsing packet: {:?}", error), - } + } + DisconnectResponsePacket::HEADER => { + let packet = DisconnectResponsePacket::payload_from_bytes_recorded(byte_stream)?; + match packet.result { + DisconnectResponseStatus::Ok => events.push(NetworkEvent::Disconnect), + DisconnectResponseStatus::Wait10Seconds => { + let color = Color::rgb_u8(255, 100, 100); + let chat_message = ChatMessage::new("Please wait 10 seconds before trying to log out.".to_string(), color); + events.push(NetworkEvent::ChatMessage(chat_message)); } } - // Send a keep-alive packet to the server. - _ = interval.tick() => { - let packet_bytes = Ping::default().packet_to_bytes().unwrap(); - stream.write_all(&packet_bytes).await.map_err(|_| NetworkTaskHandle::ConnectionClosed)?; + } + UseSkillSuccessPacket::HEADER => { + let _packet = UseSkillSuccessPacket::payload_from_bytes_recorded(byte_stream)?; + } + ToUseSkillSuccessPacket::HEADER => { + let _packet = ToUseSkillSuccessPacket::payload_from_bytes_recorded(byte_stream)?; + } + NotifySkillUnitPacket::HEADER => { + let packet = NotifySkillUnitPacket::payload_from_bytes_recorded(byte_stream)?; + events.push(NetworkEvent::AddSkillUnit( + packet.entity_id, + packet.unit_id, + Vector2::new(packet.position.x as usize, packet.position.y as usize), + )); + } + SkillUnitDisappearPacket::HEADER => { + let packet = SkillUnitDisappearPacket::payload_from_bytes_recorded(byte_stream)?; + events.push(NetworkEvent::RemoveSkillUnit(packet.entity_id)); + } + NotifyGroundSkillPacket::HEADER => { + let _packet = NotifyGroundSkillPacket::payload_from_bytes_recorded(byte_stream)?; + } + FriendListPacket::HEADER => { + let packet = FriendListPacket::payload_from_bytes_recorded(byte_stream)?; + self.friend_list.mutate(|friends| { + *friends = packet.friends.into_iter().map(|friend| (friend, UnsafeCell::new(None))).collect(); + }); + } + FriendOnlineStatusPacket::HEADER => { + let _packet = FriendOnlineStatusPacket::payload_from_bytes_recorded(byte_stream)?; + } + FriendRequestPacket::HEADER => { + let packet = FriendRequestPacket::payload_from_bytes_recorded(byte_stream)?; + events.push(NetworkEvent::FriendRequest(packet.friend)); + } + FriendRequestResultPacket::HEADER => { + let packet = FriendRequestResultPacket::payload_from_bytes_recorded(byte_stream)?; + if packet.result == FriendRequestResult::Accepted { + self.friend_list.push((packet.friend.clone(), UnsafeCell::new(None))); } + + let color = Color::rgb_u8(220, 200, 30); + let chat_message = ChatMessage::new(packet.into_message(), color); + events.push(NetworkEvent::ChatMessage(chat_message)); + } + NotifyFriendRemovedPacket::HEADER => { + let packet = NotifyFriendRemovedPacket::payload_from_bytes_recorded(byte_stream)?; + self.friend_list.with_mut(|friends| { + friends.retain(|(friend, _)| !(friend.account_id == packet.account_id && friend.character_id == packet.character_id)); + ValueState::Mutated(()) + }); + } + PartyInvitePacket::HEADER => { + let _packet = PartyInvitePacket::payload_from_bytes_recorded(byte_stream)?; + } + StatusChangeSequencePacket::HEADER => { + let _packet = StatusChangeSequencePacket::payload_from_bytes_recorded(byte_stream)?; + } + ReputationPacket::HEADER => { + let _packet = ReputationPacket::payload_from_bytes_recorded(byte_stream)?; + } + ClanInfoPacket::HEADER => { + let _packet = ClanInfoPacket::payload_from_bytes_recorded(byte_stream)?; + } + ClanOnlineCountPacket::HEADER => { + let _packet = ClanOnlineCountPacket::payload_from_bytes_recorded(byte_stream)?; } + ChangeMapCellPacket::HEADER => { + let _packet = ChangeMapCellPacket::payload_from_bytes_recorded(byte_stream)?; + } + _ => return Ok(false), } - } - - pub fn connect_to_login_server(&mut self, address: impl ToSocketAddrs + Send) { - let (action_sender, action_receiver) = tokio::sync::mpsc::channel(16); - let (event_sender, event_receiver) = tokio::sync::mpsc::channel(16); - let handle = self.runtime_handle.spawn(Self::handle_server_thing::( - address, - action_receiver, - Duration::from_secs(1), - )); - - self.login_server_connection = ServerConnection::Healthy { - handle, - action_sender, - event_receiver, - }; - } - - 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); - events - } - - // TODO: Return the original packet? - pub fn login_server_packet(&mut self, packet: Packet) -> Result<(), NetworkTaskHandle> - where - Packet: OutgoingPacket + LoginServerPacket, - { - match &mut self.login_server_connection { - // FIX: Don't unwrap. - ServerConnection::Healthy { action_sender, .. } => action_sender - .send(packet.packet_to_bytes().unwrap()) - .map_err(|_| NetworkTaskHandle::ConnectionClosed), - _ => Err(NetworkTaskHandle::ConnectionClosed), - } + Ok(true) } - // TODO: Return the original packet? - pub fn character_server_packet(&mut self, packet: Packet) -> Result<(), NetworkTaskHandle> - where - Packet: OutgoingPacket + CharacterServerPacket, - { - match &mut self.character_server_connection { - // FIX: Don't unwrap. - ServerConnection::Healthy { action_sender, .. } => action_sender - .send(packet.packet_to_bytes().unwrap()) - .map_err(|_| NetworkTaskHandle::ConnectionClosed), - _ => Err(NetworkTaskHandle::ConnectionClosed), - } + #[cfg(feature = "debug")] + pub fn clear_packet_history(&mut self) { + self.packet_history.mutate(|buffer| { + buffer.clear(); + }); } - // TODO: Return the original packet? - pub fn map_server_packet(&mut self, packet: Packet) -> Result<(), NetworkTaskHandle> - where - Packet: OutgoingPacket + MapServerPacket, - { - match &mut self.map_server_connection { - // FIX: Don't unwrap. - ServerConnection::Healthy { action_sender, .. } => action_sender - .send(packet.packet_to_bytes().unwrap()) - .map_err(|_| NetworkTaskHandle::ConnectionClosed), - _ => Err(NetworkTaskHandle::ConnectionClosed), - } + #[cfg(feature = "debug")] + pub fn packet_window(&self) -> PacketWindow<256> { + PacketWindow::new(self.packet_history.new_remote(), self.update_packets.clone()) } } diff --git a/korangar_interface/README.md b/korangar_interface/README.md index faf26c5cd..01c2e877b 100644 --- a/korangar_interface/README.md +++ b/korangar_interface/README.md @@ -1,3 +1,3 @@ -# Ragnarok Interface +# Korangar Interface A crate that exposes a UI that can be used to display Ragnarok Online windows. diff --git a/korangar_networking/Cargo.toml b/korangar_networking/Cargo.toml new file mode 100644 index 000000000..ea780f477 --- /dev/null +++ b/korangar_networking/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "korangar_networking" +version = "0.1.0" +edition = "2021" + +[dependencies] +ragnarok_bytes = { workspace = true } +ragnarok_networking = { workspace = true } +korangar_debug = { workspace = true, optional = true } +tokio = { version = "1.37", features = ["full"] } + +[features] +debug = ["korangar_debug"] diff --git a/korangar_networking/README.md b/korangar_networking/README.md new file mode 100644 index 000000000..4dd73c69d --- /dev/null +++ b/korangar_networking/README.md @@ -0,0 +1,4 @@ +# Korangar Networking + +An opinionated wrapper around the `ragnarok_networking` crate. +This crate exposes a networking system that can run in a seperate thread and maintain connections to the login, character, and map servers. diff --git a/korangar_networking/src/lib.rs b/korangar_networking/src/lib.rs new file mode 100644 index 000000000..cb6000fda --- /dev/null +++ b/korangar_networking/src/lib.rs @@ -0,0 +1,721 @@ +use std::marker::PhantomData; +use std::net::SocketAddr; +use std::time::Duration; + +use ragnarok_bytes::ByteStream; +use ragnarok_networking::*; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::TcpStream; +use tokio::sync::mpsc::error::TryRecvError; +use tokio::sync::mpsc::{Receiver, Sender}; +use tokio::task::JoinHandle; + +pub struct EntityData { + pub entity_id: EntityId, + pub movement_speed: u16, + pub job: u16, + pub position: WorldPosition, + pub destination: Option, + pub health_points: i32, + pub maximum_health_points: i32, + pub head_direction: usize, + pub sex: Sex, +} + +impl EntityData { + pub fn from_character(account_id: AccountId, character_information: CharacterInformation, position: WorldPosition) -> Self { + Self { + entity_id: EntityId(account_id.0), + movement_speed: character_information.movement_speed as u16, + job: character_information.job as u16, + position, + destination: None, + health_points: character_information.health_points as i32, + maximum_health_points: character_information.maximum_health_points as i32, + head_direction: 0, // TODO: get correct rotation + sex: character_information.sex, + } + } +} + +impl From for EntityData { + fn from(packet: EntityAppearedPacket) -> Self { + Self { + entity_id: packet.entity_id, + movement_speed: packet.movement_speed, + job: packet.job, + position: packet.position, + destination: None, + health_points: packet.health_points, + maximum_health_points: packet.maximum_health_points, + head_direction: packet.head_direction as usize, + sex: packet.sex, + } + } +} + +impl From for EntityData { + fn from(packet: EntityAppeared2Packet) -> Self { + Self { + entity_id: packet.entity_id, + movement_speed: packet.movement_speed, + job: packet.job, + position: packet.position, + destination: None, + health_points: packet.health_points, + maximum_health_points: packet.maximum_health_points, + head_direction: packet.head_direction as usize, + sex: packet.sex, + } + } +} + +impl From for EntityData { + fn from(packet: MovingEntityAppearedPacket) -> Self { + let (origin, destination) = packet.position.to_origin_destination(); + + Self { + entity_id: packet.entity_id, + movement_speed: packet.movement_speed, + job: packet.job, + position: origin, + destination: Some(destination), + health_points: packet.health_points, + maximum_health_points: packet.maximum_health_points, + head_direction: packet.head_direction as usize, + sex: packet.sex, + } + } +} + +/// An event triggered by the a server. +pub enum NetworkEvent { + LoginServerDisconnected, + /// 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 + /// by its id. + RemoveEntity(EntityId), + /// The player is pathing to a new position. + PlayerMove(WorldPosition, WorldPosition, ClientTick), + /// An Entity nearby is pathing to a new position. + EntityMove(EntityId, WorldPosition, WorldPosition, ClientTick), + /// Player was moved to a new position on a different map or the current map + ChangeMap(String, TilePosition), + /// Update the client side [`tick + /// counter`](crate::system::GameTimer::base_client_tick) to keep server and + /// client synchronized. + UpdateClientTick(ClientTick), + /// New chat message for the client. + ChatMessage { + // FIX: Color. + text: String, + }, + /// Update entity details. Mostly received when the client sends + /// [RequestDetailsPacket] after the player hovered an entity. + UpdateEntityDetails(EntityId, String), + UpdateEntityHealth(EntityId, usize, usize), + DamageEffect(EntityId, usize), + HealEffect(EntityId, usize), + UpdateStatus(StatusType), + OpenDialog(String, EntityId), + AddNextButton, + AddCloseButton, + AddChoiceButtons(Vec), + AddQuestEffect(QuestEffectPacket), + RemoveQuestEffect(EntityId), + Inventory(Vec<(ItemIndex, ItemId, EquipPosition, EquipPosition)>), + AddIventoryItem(ItemIndex, ItemId, EquipPosition, EquipPosition), + SkillTree(Vec), + UpdateEquippedPosition { + index: ItemIndex, + equipped_position: EquipPosition, + }, + ChangeJob(AccountId, u32), + SetPlayerPosition(WorldPosition), + Disconnect, + FriendRequest(Friend), + VisualEffect(&'static str, EntityId), + AddSkillUnit(EntityId, UnitId, TilePosition), + RemoveSkillUnit(EntityId), +} + +impl Into> for NetworkEvent { + fn into(self) -> Vec { + vec![self] + } +} + +enum ConnectCommand { + LoginServer { + address: SocketAddr, + action_receiver: Receiver>, + event_sender: Sender, + }, + CharacterServer { + address: SocketAddr, + action_receiver: Receiver>, + event_sender: Sender, + }, + MapServer { + address: SocketAddr, + action_receiver: Receiver>, + event_sender: Sender, + }, +} + +pub enum NetworkTaskError { + FailedToConnect, + ConnectionClosed, +} + +enum ServerConnection { + Connected { + action_sender: Sender>, + event_receiver: Receiver, + }, + Disconnected, +} + +impl ServerConnection { + fn take(&mut self) -> Self { + std::mem::replace(self, ServerConnection::Disconnected) + } +} + +pub struct NetworkingSystem2 { + command_sender: Sender, + login_server_connection: ServerConnection, + character_server_connection: ServerConnection, + map_server_connection: ServerConnection, + _marker: PhantomData, +} + +impl NetworkingSystem2 +where + Meta: Default + 'static, +{ + pub fn new() -> Self { + let command_sender = Self::spawn_networking_thread(); + + Self { + command_sender, + login_server_connection: ServerConnection::Disconnected, + character_server_connection: ServerConnection::Disconnected, + map_server_connection: ServerConnection::Disconnected, + _marker: PhantomData, + } + } + + fn spawn_networking_thread() -> Sender { + let (command_sender, mut command_receiver) = tokio::sync::mpsc::channel::(20); + + std::thread::spawn(move || { + println!("Networking thread started"); + let runtime = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); + println!("Runtime started"); + + let _guard = runtime.enter(); + let local_set = tokio::task::LocalSet::new(); + + let mut login_server_task_handle: Option>> = None; + let mut character_server_task_handle: Option>> = None; + let mut map_server_task_handle: Option>> = None; + + local_set.block_on(&runtime, async { + while let Some(command) = command_receiver.recv().await { + match command { + ConnectCommand::LoginServer { + address, + action_receiver, + event_sender, + } => { + if let Some(handle) = login_server_task_handle.take() { + // TODO: Maybe add a timeout here? + handle.await.unwrap(); + } + + let packet_handler = Self::create_login_server_packet_handler(); + let handle = local_set.spawn_local(Self::handle_server_thing::( + address, + action_receiver, + event_sender, + packet_handler, + Duration::from_secs(58), + )); + + login_server_task_handle = Some(handle); + } + ConnectCommand::CharacterServer { + address, + action_receiver, + event_sender, + } => { + if let Some(handle) = character_server_task_handle.take() { + // TODO: Maybe add a timeout here? + handle.await.unwrap(); + } + + let packet_handler = Self::create_character_server_packet_handler(); + let handle = local_set.spawn_local(Self::handle_server_thing::( + address, + action_receiver, + event_sender, + packet_handler, + Duration::from_secs(10), + )); + + character_server_task_handle = Some(handle); + } + ConnectCommand::MapServer { + address, + action_receiver, + event_sender, + } => { + if let Some(handle) = map_server_task_handle.take() { + // TODO: Maybe add a timeout here? + handle.await.unwrap(); + } + + let packet_handler = Self::create_map_server_packet_handler(); + let handle = local_set.spawn_local(Self::handle_server_thing::( + address, + action_receiver, + event_sender, + packet_handler, + Duration::from_secs(4), + )); + + map_server_task_handle = Some(handle); + } + } + } + }); + }); + + command_sender + } + + fn handle_connection(connection: &mut ServerConnection, events: &mut Vec) { + if let ServerConnection::Connected { + action_sender, + mut event_receiver, + } = connection.take() + { + match event_receiver.try_recv() { + Ok(login_event) => { + events.push(login_event); + *connection = ServerConnection::Connected { + action_sender, + event_receiver, + }; + } + Err(TryRecvError::Empty) => { + *connection = ServerConnection::Connected { + action_sender, + event_receiver, + }; + } + Err(..) => { + events.push(NetworkEvent::LoginServerDisconnected); + *connection = ServerConnection::Disconnected; + } + } + }; + } + + async fn handle_server_thing( + address: SocketAddr, + mut action_receiver: Receiver>, + event_sender: Sender, + mut packet_handler: PacketHandler, Meta>, + ping_frequency: Duration, + ) -> Result<(), NetworkTaskError> + where + Ping: OutgoingPacket + Default, + { + println!("Server thing started"); + + let mut stream = TcpStream::connect(address).await.map_err(|_| NetworkTaskError::FailedToConnect)?; + let mut interval = tokio::time::interval(ping_frequency); + let mut buffer = [0u8; 4096]; + + println!("Server connected"); + + let result = loop { + tokio::select! { + // Send a packet to the server. + action = action_receiver.recv() => { + let Some(action) = action else { + // Channel was closed by the main thread. + break Ok(()); + }; + + println!("Sending action"); + + stream.write_all(&action).await.map_err(|_| NetworkTaskError::ConnectionClosed)?; + } + // Receive some packets from the server. + received_bytes = stream.read(&mut buffer) => { + let Ok(received_bytes) = received_bytes else { + // Channel was closed by the main thread. + break Err(NetworkTaskError::ConnectionClosed); + }; + + // FIX: Handle cut-off packets + let data = &buffer[..received_bytes]; + let mut byte_stream = ByteStream::without_metadata(data); + let mut events = Vec::new(); + + println!("Got response"); + + while !byte_stream.is_empty() { + match packet_handler.process_one(&mut byte_stream) { + HandlerResult::Ok(packet_events) => events.extend(packet_events.into_iter()), + HandlerResult::UnhandledPacket => { + // FIX: make this work again. + // #[cfg(feature = "debug")] + // { + // let packet = UnknownPacket::new(byte_stream.remaining_bytes()); + // byte_stream.incoming_packet(&packet); + // } + break; + }, + HandlerResult::PacketCutOff => { + panic!("Implement logic"); + }, + HandlerResult::InternalError(error) => panic!("Error parsing packet: {:?}", error), + } + } + + for event in events { + event_sender.send(event).await.map_err(|_| NetworkTaskError::ConnectionClosed)?; + } + } + // Send a keep-alive packet to the server. + _ = interval.tick() => { + println!("Sending ping packet"); + let packet_bytes = Ping::default().packet_to_bytes().unwrap(); + stream.write_all(&packet_bytes).await.map_err(|_| NetworkTaskError::ConnectionClosed)?; + } + } + }; + + // FIX: Remove. + println!("Server thing ended"); + + result + } + + pub fn connect_to_login_server(&mut self, address: SocketAddr, username: &str, password: &str) { + let (action_sender, action_receiver) = tokio::sync::mpsc::channel(16); + let (event_sender, event_receiver) = tokio::sync::mpsc::channel(16); + + // FIX: Don't unwrap. + self.command_sender + .try_send(ConnectCommand::LoginServer { + address, + action_receiver, + event_sender, + }) + .unwrap(); + + let login_packet = LoginServerLoginPacket::new(username.to_owned(), password.to_owned()); + // FIX: Don't unwrap (x2). + action_sender.try_send(login_packet.packet_to_bytes().unwrap()).unwrap(); + + self.login_server_connection = ServerConnection::Connected { + action_sender, + event_receiver, + }; + } + + 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); + + events + } + + // TODO: Return the original packet? + pub fn login_server_packet(&mut self, packet: Packet) -> Result<(), NetworkTaskError> + where + Packet: OutgoingPacket + LoginServerPacket, + { + match &mut self.login_server_connection { + // FIX: Don't unwrap. + ServerConnection::Connected { action_sender, .. } => action_sender + .try_send(packet.packet_to_bytes().unwrap()) + // FIX: This can also be an error about the queue being full. Handle that properly. + .map_err(|_| NetworkTaskError::ConnectionClosed), + _ => Err(NetworkTaskError::ConnectionClosed), + } + } + + // TODO: Return the original packet? + pub fn character_server_packet(&mut self, packet: Packet) -> Result<(), NetworkTaskError> + where + Packet: OutgoingPacket + CharacterServerPacket, + { + match &mut self.character_server_connection { + // FIX: Don't unwrap. + ServerConnection::Connected { action_sender, .. } => action_sender + .try_send(packet.packet_to_bytes().unwrap()) + // FIX: This can also be an error about the queue being full. Handle that properly. + .map_err(|_| NetworkTaskError::ConnectionClosed), + _ => Err(NetworkTaskError::ConnectionClosed), + } + } + + // TODO: Return the original packet? + pub fn map_server_packet(&mut self, packet: Packet) -> Result<(), NetworkTaskError> + where + Packet: OutgoingPacket + MapServerPacket, + { + match &mut self.map_server_connection { + // FIX: Don't unwrap. + ServerConnection::Connected { action_sender, .. } => action_sender + .try_send(packet.packet_to_bytes().unwrap()) + // FIX: This can also be an error about the queue being full. Handle that properly. + .map_err(|_| NetworkTaskError::ConnectionClosed), + _ => Err(NetworkTaskError::ConnectionClosed), + } + } + + fn create_login_server_packet_handler() -> PacketHandler, Meta> { + let mut packet_handler = PacketHandler::, Meta>::default(); + + packet_handler.register::(|packet| { + println!("Logged in!!!!!!!!!!!!"); + vec![] + }); + + packet_handler + } + + fn create_character_server_packet_handler() -> PacketHandler, Meta> { + let mut packet_handler = PacketHandler::, Meta>::default(); + + packet_handler + } + + fn create_map_server_packet_handler() -> PacketHandler, Meta> { + let mut packet_handler = PacketHandler::, Meta>::default(); + + packet_handler.register::(|packet| NetworkEvent::ChatMessage { text: packet.message }); + packet_handler.register::(|packet| { + // NOTE: Drop the alpha channel because it might be 0. + // FIX: Use again + // let color = Color::rgb_u8(packet.font_color.red, packet.font_color.green, + // packet.font_color.blue); + NetworkEvent::ChatMessage { text: packet.message } + }); + packet_handler.register::(|packet| NetworkEvent::ChatMessage { text: packet.message }); + packet_handler.register::(|packet| NetworkEvent::ChatMessage { text: packet.message }); + packet_handler.register::(|packet| { + // NOTE: Drop the alpha channel because it might be 0. + // FIX: Use again + // let color = Color::rgb_u8(packet.color.red, packet.color.green, + // packet.color.blue); + NetworkEvent::ChatMessage { text: packet.message } + }); + packet_handler.register_noop::(); + packet_handler.register::(|packet| { + let (origin, destination) = packet.from_to.to_origin_destination(); + NetworkEvent::EntityMove(packet.entity_id, origin, destination, packet.timestamp) + }); + packet_handler.register_noop::(); + packet_handler.register::(|packet| { + let (origin, destination) = packet.from_to.to_origin_destination(); + NetworkEvent::PlayerMove(origin, destination, packet.timestamp) + }); + packet_handler + .register::(|packet| NetworkEvent::ChangeMap(packet.map_name.replace(".gat", ""), packet.position)); + packet_handler.register::(|packet| NetworkEvent::AddEntity(packet.into())); + packet_handler.register::(|packet| NetworkEvent::AddEntity(packet.into())); + packet_handler.register::(|packet| NetworkEvent::AddEntity(packet.into())); + packet_handler.register::(|packet| NetworkEvent::RemoveEntity(packet.entity_id)); + packet_handler.register::(|packet| NetworkEvent::UpdateStatus(packet.status_type)); + packet_handler.register::(|packet| NetworkEvent::UpdateStatus(packet.status_type)); + packet_handler.register::(|packet| NetworkEvent::UpdateStatus(packet.status_type)); + packet_handler.register::(|packet| NetworkEvent::UpdateStatus(packet.status_type)); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register::(|packet| match packet.sprite_type == 0 { + true => vec![NetworkEvent::ChangeJob(packet.account_id, packet.value)], + false => Vec::new(), + }); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register::(|packet| NetworkEvent::SkillTree(packet.skill_information)); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register::(|_| NetworkEvent::AddNextButton); + packet_handler.register::(|_| NetworkEvent::AddCloseButton); + packet_handler.register::(|packet| { + let choices = packet + .message + .split(':') + .map(String::from) + .filter(|text| !text.is_empty()) + .collect(); + + NetworkEvent::AddChoiceButtons(choices) + }); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register::(|packet| { + NetworkEvent::HealEffect(packet.destination_entity_id, packet.heal_amount as usize) + + //NetworkEvent::VisualEffect() + }); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register::(|packet| { + let path = match packet.effect { + VisualEffect::BaseLevelUp => "angel.str", + VisualEffect::JobLevelUp => "joblvup.str", + VisualEffect::RefineFailure => "bs_refinefailed.str", + VisualEffect::RefineSuccess => "bs_refinesuccess.str", + VisualEffect::GameOver => "help_angel\\help_angel\\help_angel.str", + VisualEffect::PharmacySuccess => "p_success.str", + VisualEffect::PharmacyFailure => "p_failed.str", + VisualEffect::BaseLevelUpSuperNovice => "help_angel\\help_angel\\help_angel.str", + VisualEffect::JobLevelUpSuperNovice => "help_angel\\help_angel\\help_angel.str", + VisualEffect::BaseLevelUpTaekwon => "help_angel\\help_angel\\help_angel.str", + }; + + NetworkEvent::VisualEffect(path, packet.entity_id) + }); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + + packet_handler.register::(|packet| match packet.effect { + QuestEffect::None => NetworkEvent::RemoveQuestEffect(packet.entity_id), + _ => NetworkEvent::AddQuestEffect(packet), + }); + packet_handler.register::(|packet| { + NetworkEvent::AddIventoryItem(packet.index, packet.item_id, packet.equip_position, EquipPosition::None) + }); + packet_handler.register_noop::(); + packet_handler.register::(|packet| NetworkEvent::UpdateClientTick(packet.client_tick)); + packet_handler.register::(|packet| { + NetworkEvent::UpdateEntityDetails(EntityId(packet.character_id.0), packet.name) + }); + packet_handler + .register::(|packet| NetworkEvent::UpdateEntityDetails(packet.entity_id, packet.name)); + packet_handler.register::(|packet| { + NetworkEvent::UpdateEntityHealth( + packet.entity_id, + packet.health_points as usize, + packet.maximum_health_points as usize, + ) + }); + packet_handler.register_noop::(); + packet_handler + .register::(|packet| NetworkEvent::DamageEffect(packet.destination_entity_id, packet.damage_amount as usize)); + packet_handler.register::(|packet| NetworkEvent::OpenDialog(packet.text, packet.npc_id)); + packet_handler.register::(|packet| match packet.result { + RequestEquipItemStatus::Success => { + vec![NetworkEvent::UpdateEquippedPosition { + index: packet.inventory_index, + equipped_position: packet.equipped_position, + }] + } + _ => Vec::new(), + }); + packet_handler.register::(|packet| match packet.result { + RequestUnequipItemStatus::Success => { + vec![NetworkEvent::UpdateEquippedPosition { + index: packet.inventory_index, + equipped_position: EquipPosition::None, + }] + } + _ => Vec::new(), + }); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register::(|packet| { + vec![ + NetworkEvent::UpdateClientTick(packet.client_tick), + NetworkEvent::SetPlayerPosition(packet.position), + ] + }); + packet_handler.register::(|packet| { + match packet.result { + RestartResponseStatus::Ok => NetworkEvent::Disconnect, + RestartResponseStatus::Nothing => { + // FIX: Use again + // let color = Color::rgb_u8(255, 100, 100); + NetworkEvent::ChatMessage { + text: "Failed to log out.".to_string(), + } + } + } + }); + packet_handler.register::(|packet| match packet.result { + DisconnectResponseStatus::Ok => NetworkEvent::Disconnect, + DisconnectResponseStatus::Wait10Seconds => NetworkEvent::ChatMessage { + text: "Please wait 10 seconds before trying to log out.".to_string(), + }, + }); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler + .register::(|packet| NetworkEvent::AddSkillUnit(packet.entity_id, packet.unit_id, packet.position)); + packet_handler.register::(|packet| NetworkEvent::RemoveSkillUnit(packet.entity_id)); + packet_handler.register_noop::(); + packet_handler.register::(|packet| { + // events.push(NetworkEvent::SetFriends(friends)); + vec![] + }); + packet_handler.register_noop::(); + packet_handler.register::(|packet| { + // events.push(NetworkEvent::FriendRequest(packet.friend)); + vec![] + }); + packet_handler.register::(|packet| { + // let color = Color::rgb_u8(220, 200, 30); + // let chat_message = ChatMessage::new(packet.into_message(), + // color); + // events.push(NetworkEvent::ChatMessage(chat_message)); + // events.push(NetworkEvent::AcceptFriendRequest(packet.friend)); + vec![] + }); + packet_handler.register::(|packet| { + // events.push(NetworkEvent::RemoveFriend(packet.account_id, + // packet.character_id)); + vec![] + }); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + packet_handler.register_noop::(); + + packet_handler + } +} diff --git a/ragnarok_networking/src/lib.rs b/ragnarok_networking/src/lib.rs index bc37f3d0a..43178fd22 100644 --- a/ragnarok_networking/src/lib.rs +++ b/ragnarok_networking/src/lib.rs @@ -293,6 +293,15 @@ pub struct WorldPosition2 { pub y2: usize, } +impl WorldPosition2 { + pub fn to_origin_destination(self) -> (WorldPosition, WorldPosition) { + (WorldPosition { x: self.x1, y: self.y1 }, WorldPosition { + x: self.x2, + y: self.y2, + }) + } +} + impl FromBytes for WorldPosition2 { fn from_bytes(byte_stream: &mut ByteStream) -> ConversionResult { let coordinates: Vec = byte_stream.slice::(6)?.iter().map(|byte| *byte as usize).collect(); @@ -2737,7 +2746,6 @@ pub struct PacketHandler where Meta: 'static, { - // TOOD: Why don't we need `for<'a>` here? handlers: HashMap>, } @@ -2759,7 +2767,6 @@ where { pub fn register( &mut self, - from_bytes: impl Fn(&mut ByteStream) -> ConversionResult + 'static, handler: impl Fn(Packet) -> Return + 'static, ) -> Result<(), DuplicateHandlerError> where @@ -2769,7 +2776,10 @@ where let old_handler = self.handlers.insert( Packet::HEADER, Box::new(move |byte_stream| { - let packet = from_bytes(byte_stream)?; + let packet = Packet::payload_from_bytes(byte_stream)?; + + // TODO: Call some generic function that can register the packet. + Ok(handler(packet).into()) }), ); @@ -2780,17 +2790,17 @@ where } } - pub fn register_noop( - &mut self, - from_bytes: impl Fn(&mut ByteStream) -> ConversionResult + 'static, - ) -> Result<(), DuplicateHandlerError> + pub fn register_noop(&mut self) -> Result<(), DuplicateHandlerError> where Packet: IncomingPacket, { let old_handler = self.handlers.insert( Packet::HEADER, Box::new(move |byte_stream| { - let _ = from_bytes(byte_stream)?; + let _ = Packet::payload_from_bytes(byte_stream)?; + + // TODO: Call some generic function that can register the packet. + Ok(Output::default()) }), );