Tons of fixes
Added movement updates Updated how entities are checked Events sending between packet service all the way to the logic service
This commit is contained in:
@@ -10,7 +10,8 @@ fn main() {
|
||||
"../proto/chat.proto",
|
||||
"../proto/character.proto",
|
||||
"../proto/character_common.proto",
|
||||
"../proto/game.proto"
|
||||
"../proto/game.proto",
|
||||
"../proto/world.proto"
|
||||
],
|
||||
&["../proto"],
|
||||
)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::auth_client::AuthClient;
|
||||
use crate::character_client::CharacterClient;
|
||||
use crate::connection_service::ConnectionService;
|
||||
use crate::world_client::WorldClientManager;
|
||||
use crate::dataconsts::*;
|
||||
use crate::enums;
|
||||
use crate::{character_common, enums};
|
||||
use crate::enums::ItemType;
|
||||
use crate::packet::{send_packet, Packet, PacketPayload};
|
||||
use crate::packet_type::PacketType;
|
||||
@@ -16,6 +17,8 @@ use tokio::sync::Mutex;
|
||||
use tonic::{Code, Status};
|
||||
use tracing::{debug, error, info, warn};
|
||||
use utils::null_string::NullTerminatedString;
|
||||
use crate::character_common::Location;
|
||||
use crate::handlers::character;
|
||||
|
||||
pub(crate) fn string_to_u32(s: &str) -> u32 {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
@@ -240,6 +243,8 @@ pub(crate) async fn handle_select_char_req(
|
||||
character_client: Arc<Mutex<CharacterClient>>,
|
||||
connection_service: Arc<ConnectionService>,
|
||||
connection_id: String,
|
||||
world_client_manager: Arc<WorldClientManager>,
|
||||
world_url: String,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
use crate::packets::cli_select_char_req::*;
|
||||
use crate::packets::srv_billing_message::*;
|
||||
@@ -436,5 +441,56 @@ pub(crate) async fn handle_select_char_req(
|
||||
send_packet(&mut locked_stream, &response_packet).await?;
|
||||
}
|
||||
|
||||
if let Err(e) = character::establish_world_connection(
|
||||
world_client_manager.clone(),
|
||||
connection_service.clone(),
|
||||
connection_id,
|
||||
world_url.clone(),
|
||||
position,
|
||||
).await {
|
||||
warn!("Failed to establish world connection: {}", e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn establish_world_connection(
|
||||
world_client_manager: Arc<WorldClientManager>,
|
||||
connection_service: Arc<ConnectionService>,
|
||||
connection_id: String,
|
||||
world_url: String,
|
||||
position: character_common::Location,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
if let Some(connection_state) = connection_service.get_connection(&connection_id) {
|
||||
if let (Some(session_id), Some(character_id)) = (&connection_state.session_id, connection_state.character_id) {
|
||||
let client_id = connection_id.clone();
|
||||
|
||||
// Connect to world service for this client
|
||||
if let Err(e) = world_client_manager
|
||||
.add_client_connection(
|
||||
session_id.clone(),
|
||||
client_id,
|
||||
&position,
|
||||
world_url,
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!("Failed to connect client to world service: {}", e);
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
info!("Connected client {} to world service on map {}", session_id, position.map_id);
|
||||
|
||||
// Start world event handler for this client
|
||||
crate::handlers::world::start_world_event_handler(
|
||||
world_client_manager.clone(),
|
||||
connection_service.clone(),
|
||||
session_id.clone()
|
||||
);
|
||||
} else {
|
||||
warn!("Cannot establish world connection: session_id or character_id not set");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -3,3 +3,4 @@ pub mod character;
|
||||
pub mod world;
|
||||
pub mod chat;
|
||||
pub mod chat_client;
|
||||
pub mod world_client;
|
||||
@@ -2,13 +2,14 @@ use crate::character_client::CharacterClient;
|
||||
use crate::connection_service::ConnectionService;
|
||||
use crate::packet::{send_packet, Packet, PacketPayload};
|
||||
use crate::packet_type::PacketType;
|
||||
use crate::world_client::WorldClientManager;
|
||||
use chrono::{Local, Timelike};
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::sync::Mutex;
|
||||
use tonic::transport::Channel;
|
||||
use tracing::debug;
|
||||
use tracing::{debug, error, info, warn};
|
||||
use utils::service_discovery::get_kube_service_endpoints_by_dns;
|
||||
|
||||
fn distance(x1: f64, y1: f64, x2: f64, y2: f64) -> u16 {
|
||||
@@ -21,6 +22,7 @@ pub(crate) async fn handle_change_map_req(
|
||||
character_client: Arc<Mutex<CharacterClient>>,
|
||||
connection_service: Arc<ConnectionService>,
|
||||
connection_id: String,
|
||||
world_client_manager: Arc<WorldClientManager>,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
use crate::packets::cli_change_map_req::*;
|
||||
use crate::packets::srv_change_map_reply::*;
|
||||
@@ -51,6 +53,16 @@ pub(crate) async fn handle_change_map_req(
|
||||
let now = Local::now();
|
||||
let time_as_u16 = (now.hour() * 100 + now.minute()) as u16;
|
||||
|
||||
// if let Err(e) = world_client_manager
|
||||
// .send_client_map_change_event(&*session_id, request.map_id as u32)
|
||||
// .await
|
||||
// {
|
||||
// warn!("Failed to send map change event to world service: {}", e);
|
||||
// // Don't return error as the map change itself was successful
|
||||
// }
|
||||
//
|
||||
// debug!("Sent map change event for client {} to world service: map {}", session_id, request.map_id);
|
||||
|
||||
let data = SrvChangeMapReply {
|
||||
object_index: client_id,
|
||||
hp: stats.hp as u16,
|
||||
@@ -80,6 +92,7 @@ pub(crate) async fn handle_mouse_cmd_req(
|
||||
packet: Packet,
|
||||
connection_service: Arc<ConnectionService>,
|
||||
connection_id: String,
|
||||
world_client_manager: Arc<WorldClientManager>,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
use crate::packets::cli_mouse_cmd::*;
|
||||
use crate::packets::srv_mouse_cmd::*;
|
||||
@@ -89,12 +102,25 @@ pub(crate) async fn handle_mouse_cmd_req(
|
||||
let mut char_id = 0;
|
||||
let mut client_id = 0;
|
||||
let mut character_id_list: Vec<u32> = Vec::new();
|
||||
let mut session_id = "".to_string();
|
||||
if let Some(mut state) = connection_service.get_connection(&connection_id) {
|
||||
char_id = state.character_id.expect("Missing character id in connection state");
|
||||
character_id_list = state.character_list.clone().expect("Missing character id list");
|
||||
client_id = state.client_id;
|
||||
session_id = state.session_id.clone().expect("Missing session id in connection state");
|
||||
}
|
||||
|
||||
if let Err(e) = world_client_manager
|
||||
.send_client_move_event(&*session_id, request.x, request.y, request.z as f32)
|
||||
.await
|
||||
{
|
||||
error!("Failed to send move event to world service: {}", e);
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
debug!("Sent move event for client {} to world service: ({}, {}, {})",
|
||||
session_id, request.x, request.y, request.z);
|
||||
|
||||
let data = SrvMouseCmd {
|
||||
id: client_id,
|
||||
target_id: request.target_id,
|
||||
@@ -112,6 +138,8 @@ pub(crate) async fn handle_mouse_cmd_req(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// World service integration - movement events
|
||||
|
||||
pub(crate) async fn handle_togggle_move_req(
|
||||
packet: Packet,
|
||||
connection_service: Arc<ConnectionService>,
|
||||
@@ -188,4 +216,97 @@ pub(crate) async fn handle_set_animation_req(
|
||||
send_packet(&mut locked_stream, &response_packet).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// World service integration - movement events
|
||||
|
||||
async fn handle_world_events(
|
||||
world_client_manager: Arc<WorldClientManager>,
|
||||
connection_service: Arc<ConnectionService>,
|
||||
session_id: String,
|
||||
) {
|
||||
info!("Starting world event handler for session {}", session_id);
|
||||
|
||||
loop {
|
||||
match world_client_manager.receive_world_event(&session_id).await {
|
||||
Some(world_event) => {
|
||||
// debug!("Received world event for session {}: {:?}", session_id, world_event);
|
||||
|
||||
// Process the world event and convert to game packets
|
||||
match world_event.event {
|
||||
Some(crate::world_client::world::world_event::Event::NpcSpawn(npc_spawn)) => {
|
||||
debug!("Processing NPC spawn event: {:?}", npc_spawn);
|
||||
// Convert to the appropriate game packet and send to client
|
||||
// This would involve creating a spawn packet and sending it through the connection's writer
|
||||
}
|
||||
Some(crate::world_client::world::world_event::Event::MobSpawn(mob_spawn)) => {
|
||||
debug!("Processing mob spawn event: {:?}", mob_spawn);
|
||||
// Convert to the appropriate game packet and send to client
|
||||
}
|
||||
Some(crate::world_client::world::world_event::Event::ObjectDespawn(despawn)) => {
|
||||
debug!("Processing object despawn event: {:?}", despawn);
|
||||
// Convert to the appropriate game packet and send to client
|
||||
}
|
||||
Some(crate::world_client::world::world_event::Event::NearbyUpdate(nearby_update)) => {
|
||||
debug!("Processing nearby objects update: {} objects", nearby_update.objects.len());
|
||||
// Convert to the appropriate game packet and send to client
|
||||
|
||||
let npcs = nearby_update.objects.iter().filter(|obj| obj.object_type == 2).collect::<Vec<_>>();
|
||||
let mobs = nearby_update.objects.iter().filter(|obj| obj.object_type == 3).collect::<Vec<_>>();
|
||||
|
||||
debug!("There are {} npcs and {} mobs", npcs.len(), mobs.len());
|
||||
debug!("Nearby npcs: {:?}", npcs);
|
||||
debug!("Nearby mobs: {:?}", mobs);
|
||||
|
||||
// if !npcs.is_empty() {
|
||||
// // Send all nearby npcs in a single batch update
|
||||
// let batch_event = SrvNpcChar {
|
||||
// id: 0,
|
||||
// x: 0.0,
|
||||
// y: 0.0,
|
||||
// dest_x: 0.0,
|
||||
// dest_y: 0.0,
|
||||
// command: 0,
|
||||
// target_id: 0,
|
||||
// move_mode: 0,
|
||||
// hp: 0,
|
||||
// team_id: 0,
|
||||
// status_flag: 0,
|
||||
// npc_id: 0,
|
||||
// quest_id: 0,
|
||||
// angle: 0.0,
|
||||
// event_status: 0,
|
||||
// };
|
||||
// let response_packet = Packet::new(PacketType::PakwcNpcChar, &batch_event)?;
|
||||
// if let Some(mut state) = connection_service.get_connection_mut(&session_id) {
|
||||
// let writer_clone = state.writer.clone().unwrap();
|
||||
// let mut locked_stream = writer_clone.lock().await;
|
||||
// send_packet(&mut locked_stream, &response_packet).await?;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
None => {
|
||||
warn!("Received world event with no event data for session {}", session_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
debug!("World event stream ended for session {}", session_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!("World event handler ended for session {}", session_id);
|
||||
}
|
||||
|
||||
// Helper function to start world event handling for a client
|
||||
pub fn start_world_event_handler(
|
||||
world_client_manager: Arc<WorldClientManager>,
|
||||
connection_service: Arc<ConnectionService>,
|
||||
session_id: String,
|
||||
) {
|
||||
tokio::spawn(async move {
|
||||
handle_world_events(world_client_manager, connection_service, session_id).await;
|
||||
});
|
||||
}
|
||||
87
packet-service/src/handlers/world_client.rs
Normal file
87
packet-service/src/handlers/world_client.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
use tonic::{Request, transport::Channel};
|
||||
use futures::StreamExt;
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use tokio_stream::wrappers::ReceiverStream;
|
||||
use std::error::Error;
|
||||
use tracing::{debug, error};
|
||||
|
||||
mod character_common {
|
||||
tonic::include_proto!("character_common");
|
||||
}
|
||||
|
||||
pub mod world {
|
||||
tonic::include_proto!("game");
|
||||
}
|
||||
|
||||
use world::event_service_client::EventServiceClient;
|
||||
use world::GenericEvent;
|
||||
use crate::interceptors::auth_interceptor::AuthInterceptor;
|
||||
|
||||
/// WorldClientHandler encapsulates the bidirectional event stream.
|
||||
/// In addition to providing an API to send messages, it also spawns a
|
||||
/// background task which forwards incoming chat messages through an inbound channel.
|
||||
pub struct WorldClientHandler {
|
||||
outbound_tx: mpsc::Sender<GenericEvent>,
|
||||
/// Inbound messages from the chat service are sent here.
|
||||
pub inbound_rx: Mutex<mpsc::Receiver<GenericEvent>>,
|
||||
}
|
||||
|
||||
impl WorldClientHandler {
|
||||
/// Creates and returns a new WorldClientHandler.
|
||||
///
|
||||
/// * `world_url` - Full URL of the World Service (for example, "http://127.0.0.1:50051")
|
||||
/// * `client_id` - The authenticated client ID to be injected into each request.
|
||||
/// * `session_id` - The authenticated session token to be injected into each request.
|
||||
pub async fn new(
|
||||
world_url: String,
|
||||
client_id: String,
|
||||
session_id: String,
|
||||
) -> Result<Self, Box<dyn Error + Send + Sync>> {
|
||||
// Create a channel to the World Service.
|
||||
let channel = Channel::from_shared(world_url)?.connect().await
|
||||
.map_err(|e| Box::new(e) as Box<dyn Error + Send + Sync>)?;
|
||||
let interceptor = AuthInterceptor { client_id, session_id };
|
||||
|
||||
// Create EventService client with interceptor.
|
||||
let mut world_client = EventServiceClient::with_interceptor(channel, interceptor);
|
||||
|
||||
// Create an mpsc channel for outbound messages.
|
||||
let (out_tx, out_rx) = mpsc::channel(32);
|
||||
let outbound_stream = ReceiverStream::new(out_rx);
|
||||
|
||||
// This channel will be used to forward inbound messages to the packet-service.
|
||||
let (in_tx, in_rx) = mpsc::channel(32);
|
||||
|
||||
// Establish the bidirectional chat stream.
|
||||
let request = Request::new(outbound_stream);
|
||||
let mut response = world_client.stream_events(request).await
|
||||
.map_err(|e| Box::new(e) as Box<dyn Error + Send + Sync>)?.into_inner();
|
||||
|
||||
// Spawn a task to continuously receive messages from the Chat Service.
|
||||
// Each received message is sent through the 'in_tx' channel.
|
||||
tokio::spawn(async move {
|
||||
while let Some(result) = response.next().await {
|
||||
match result {
|
||||
Ok(event) => {
|
||||
// Process the event as needed.
|
||||
// debug!("Received event: {:?}", event);
|
||||
if let Err(e) = in_tx.send(event).await {
|
||||
error!("Failed to forward event: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error receiving event stream message: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
debug!("Event inbound stream closed");
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
outbound_tx: out_tx,
|
||||
inbound_rx: Mutex::new(in_rx),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ use crate::connection_service::ConnectionService;
|
||||
use crate::metrics::{ACTIVE_CONNECTIONS, PACKETS_RECEIVED};
|
||||
use crate::packet::Packet;
|
||||
use crate::router::PacketRouter;
|
||||
use crate::world_client::WorldClientManager;
|
||||
use dotenv::dotenv;
|
||||
use prometheus::{self, Encoder, TextEncoder};
|
||||
use prometheus_exporter;
|
||||
@@ -16,7 +17,8 @@ use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::sync::{Mutex, Semaphore};
|
||||
use tokio::sync::{Mutex, Semaphore, oneshot};
|
||||
use tokio::time::{timeout, Duration};
|
||||
use tokio::{io, select, signal};
|
||||
use tracing::Level;
|
||||
use tracing::{debug, error, info, warn};
|
||||
@@ -41,6 +43,7 @@ mod router;
|
||||
mod types;
|
||||
mod interceptors;
|
||||
mod id_manager;
|
||||
mod world_client;
|
||||
|
||||
pub mod common {
|
||||
tonic::include_proto!("common");
|
||||
@@ -82,16 +85,27 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.get(0)
|
||||
.unwrap()
|
||||
);
|
||||
let world_url = format!(
|
||||
"http://{}",
|
||||
get_kube_service_endpoints_by_dns("world-service", "tcp", "world-service")
|
||||
.await?
|
||||
.get(0)
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
// Start health-check endpoint
|
||||
health_check::start_health_check(addr.as_str()).await?;
|
||||
|
||||
let auth_client = Arc::new(Mutex::new(AuthClient::connect(&auth_url).await?));
|
||||
let character_client = Arc::new(Mutex::new(CharacterClient::connect(&character_url).await?));
|
||||
let world_client_manager = Arc::new(WorldClientManager::new());
|
||||
|
||||
let full_addr = format!("{}:{}", &addr, port);
|
||||
|
||||
tokio::spawn(async move {
|
||||
// Create shutdown signal channel for the TCP listener
|
||||
let (tcp_shutdown_tx, mut tcp_shutdown_rx) = oneshot::channel::<()>();
|
||||
|
||||
let tcp_server_task = tokio::spawn(async move {
|
||||
let semaphore = Arc::new(Semaphore::new(MAX_CONCURRENT_CONNECTIONS));
|
||||
let listener = TcpListener::bind(full_addr.clone()).await.unwrap();
|
||||
let buffer_pool = BufferPool::new(BUFFER_POOL_SIZE);
|
||||
@@ -101,39 +115,82 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
auth_client,
|
||||
character_client,
|
||||
connection_service,
|
||||
world_client_manager,
|
||||
world_url,
|
||||
};
|
||||
|
||||
info!("Packet service listening on {}", full_addr);
|
||||
|
||||
loop {
|
||||
let (mut socket, addr) = listener.accept().await.unwrap();
|
||||
let packet_router = packet_router.clone();
|
||||
info!("New connection from {}", addr);
|
||||
|
||||
let pool = buffer_pool.clone();
|
||||
let permit = semaphore.clone().acquire_owned().await.unwrap();
|
||||
let peer_addr = socket.peer_addr().unwrap();
|
||||
let (mut reader, writer) = io::split(socket);
|
||||
let writer = Arc::new(tokio::sync::Mutex::new(writer));
|
||||
|
||||
// Spawn a new task for each connection
|
||||
tokio::spawn(async move {
|
||||
let _permit = permit;
|
||||
let connection_id = packet_router.connection_service.add_connection(writer);
|
||||
if let Err(e) = packet_router
|
||||
.handle_connection(reader, pool, connection_id.clone(), peer_addr.to_string())
|
||||
.await
|
||||
{
|
||||
error!("Error handling connection: {}", e);
|
||||
tokio::select! {
|
||||
// Check for shutdown signal
|
||||
_ = &mut tcp_shutdown_rx => {
|
||||
info!("TCP server received shutdown signal");
|
||||
break;
|
||||
}
|
||||
packet_router.connection_service.remove_connection(&connection_id);
|
||||
});
|
||||
// Accept new connections
|
||||
result = listener.accept() => {
|
||||
match result {
|
||||
Ok((mut socket, addr)) => {
|
||||
let packet_router = packet_router.clone();
|
||||
info!("New connection from {}", addr);
|
||||
|
||||
let pool = buffer_pool.clone();
|
||||
let permit = semaphore.clone().acquire_owned().await.unwrap();
|
||||
let peer_addr = socket.peer_addr().unwrap();
|
||||
let (mut reader, writer) = io::split(socket);
|
||||
let writer = Arc::new(tokio::sync::Mutex::new(writer));
|
||||
|
||||
// Spawn a new task for each connection
|
||||
tokio::spawn(async move {
|
||||
let _permit = permit;
|
||||
let connection_id = packet_router.connection_service.add_connection(writer);
|
||||
if let Err(e) = packet_router
|
||||
.handle_connection(reader, pool, connection_id.clone(), peer_addr.to_string())
|
||||
.await
|
||||
{
|
||||
error!("Error handling connection: {}", e);
|
||||
}
|
||||
packet_router.connection_service.remove_connection(&connection_id);
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to accept connection: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
info!("TCP server shut down gracefully");
|
||||
});
|
||||
|
||||
let binding = format!("{}:{}", &addr, metrics_port);
|
||||
prometheus_exporter::start(binding.parse().unwrap()).unwrap();
|
||||
|
||||
// Wait for shutdown signal
|
||||
info!("Packet service is running. Waiting for shutdown signal...");
|
||||
utils::signal_handler::wait_for_signal().await;
|
||||
|
||||
info!("Shutdown signal received. Beginning graceful shutdown...");
|
||||
|
||||
// Signal the TCP server to stop accepting new connections
|
||||
if let Err(_) = tcp_shutdown_tx.send(()) {
|
||||
warn!("Failed to send shutdown signal to TCP server (receiver may have been dropped)");
|
||||
}
|
||||
|
||||
// Wait for the TCP server to finish with a timeout
|
||||
match timeout(Duration::from_secs(30), tcp_server_task).await {
|
||||
Ok(result) => {
|
||||
if let Err(e) = result {
|
||||
error!("TCP server task failed: {}", e);
|
||||
} else {
|
||||
info!("Packet service shut down successfully");
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
error!("TCP server shutdown timed out after 30 seconds");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::handlers::*;
|
||||
use crate::metrics::{ACTIVE_CONNECTIONS, PACKETS_RECEIVED, PACKET_PROCESSING_TIME};
|
||||
use crate::packet::Packet;
|
||||
use crate::packet_type::PacketType;
|
||||
use crate::world_client::WorldClientManager;
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
@@ -19,6 +20,8 @@ pub struct PacketRouter {
|
||||
pub auth_client: Arc<Mutex<AuthClient>>,
|
||||
pub character_client: Arc<Mutex<CharacterClient>>,
|
||||
pub connection_service: Arc<ConnectionService>,
|
||||
pub world_client_manager: Arc<WorldClientManager>,
|
||||
pub world_url: String,
|
||||
}
|
||||
|
||||
impl PacketRouter {
|
||||
@@ -61,6 +64,10 @@ impl PacketRouter {
|
||||
if let Some(state) = self.connection_service.get_connection(&connection_id) {
|
||||
let session_id = state.session_id.unwrap_or_default();
|
||||
if !session_id.is_empty() {
|
||||
// Disconnect from world service
|
||||
self.world_client_manager.remove_client_connection(&session_id).await;
|
||||
|
||||
// Logout from auth service
|
||||
let mut auth_client = self.auth_client.lock().await;
|
||||
auth_client.logout(&session_id).await?;
|
||||
} else {
|
||||
@@ -93,19 +100,18 @@ impl PacketRouter {
|
||||
PacketType::PakcsCharListReq => character::handle_char_list_req(packet, self.character_client.clone(), self.connection_service.clone(), connection_id).await,
|
||||
PacketType::PakcsCreateCharReq => character::handle_create_char_req(packet, self.character_client.clone(), self.connection_service.clone(), connection_id).await,
|
||||
PacketType::PakcsDeleteCharReq => character::handle_delete_char_req(packet, self.character_client.clone(), self.connection_service.clone(), connection_id).await,
|
||||
PacketType::PakcsSelectCharReq => character::handle_select_char_req(packet, self.character_client.clone(), self.connection_service.clone(), connection_id).await,
|
||||
PacketType::PakcsSelectCharReq => character::handle_select_char_req(packet, self.character_client.clone(), self.connection_service.clone(), connection_id, self.world_client_manager.clone(), self.world_url.clone()).await,
|
||||
|
||||
// World Packets
|
||||
PacketType::PakcsChangeMapReq => world::handle_change_map_req(packet, self.character_client.clone(), self.connection_service.clone(), connection_id).await,
|
||||
PacketType::PakcsMouseCmd => world::handle_mouse_cmd_req(packet, self.connection_service.clone(), connection_id).await,
|
||||
// World Packets (enhanced with world service integration)
|
||||
PacketType::PakcsChangeMapReq => world::handle_change_map_req(packet, self.character_client.clone(), self.connection_service.clone(), connection_id, self.world_client_manager.clone()).await,
|
||||
PacketType::PakcsMouseCmd => world::handle_mouse_cmd_req(packet, self.connection_service.clone(), connection_id, self.world_client_manager.clone()).await,
|
||||
PacketType::PakcsToggleMove => world::handle_togggle_move_req(packet, self.connection_service.clone(), connection_id).await,
|
||||
PacketType::PakcsSetAnimation => world::handle_set_animation_req(packet, self.connection_service.clone(), connection_id).await,
|
||||
|
||||
// Chat Packets
|
||||
PacketType::PakcsNormalChat => chat::handle_normal_chat(packet, self.connection_service.clone(), connection_id).await,
|
||||
PacketType::PakcsShoutChat => chat::handle_shout_chat(packet, self.connection_service.clone(), connection_id).await,
|
||||
// 1 => chat::handle_chat(packet).await?,
|
||||
// 2 => movement::handle_movement(packet).await?,
|
||||
|
||||
_ => {
|
||||
warn!("Unhandled packet type: {:?}", packet.packet_type);
|
||||
Ok(())
|
||||
|
||||
335
packet-service/src/world_client.rs
Normal file
335
packet-service/src/world_client.rs
Normal file
@@ -0,0 +1,335 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use tonic::transport::Channel;
|
||||
use tracing::{debug, error, info, warn};
|
||||
use futures::{Stream, StreamExt};
|
||||
|
||||
pub mod world {
|
||||
tonic::include_proto!("world");
|
||||
}
|
||||
|
||||
pub mod character_common {
|
||||
tonic::include_proto!("character_common");
|
||||
}
|
||||
|
||||
pub mod game {
|
||||
use super::character_common;
|
||||
tonic::include_proto!("game");
|
||||
}
|
||||
|
||||
use world::world_service_client::WorldServiceClient;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WorldClient {
|
||||
client: WorldServiceClient<Channel>,
|
||||
event_sender: Option<mpsc::UnboundedSender<world::ClientEvent>>,
|
||||
}
|
||||
|
||||
impl WorldClient {
|
||||
pub async fn connect(endpoint: &str) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let client = WorldServiceClient::connect(endpoint.to_string()).await?;
|
||||
Ok(WorldClient {
|
||||
client,
|
||||
event_sender: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_character(
|
||||
&mut self,
|
||||
token: &str,
|
||||
user_id: &str,
|
||||
char_id: &str,
|
||||
session_id: &str,
|
||||
) -> Result<world::CharacterResponse, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let request = world::CharacterRequest {
|
||||
token: token.to_string(),
|
||||
user_id: user_id.to_string(),
|
||||
char_id: char_id.to_string(),
|
||||
session_id: session_id.to_string(),
|
||||
};
|
||||
|
||||
let response = self.client.get_character(request).await?;
|
||||
Ok(response.into_inner())
|
||||
}
|
||||
|
||||
pub async fn change_map(
|
||||
&mut self,
|
||||
id: i32,
|
||||
x: f32,
|
||||
y: f32,
|
||||
) -> Result<world::ChangeMapResponse, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let request = world::ChangeMapRequest {
|
||||
id,
|
||||
x,
|
||||
y,
|
||||
};
|
||||
|
||||
let response = self.client.change_map(request).await?;
|
||||
Ok(response.into_inner())
|
||||
}
|
||||
|
||||
pub async fn move_character(
|
||||
&mut self,
|
||||
session_id: &str,
|
||||
target_id: u32,
|
||||
x: f32,
|
||||
y: f32,
|
||||
z: f32,
|
||||
) -> Result<world::CharacterMoveResponse, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let request = world::CharacterMoveRequest {
|
||||
session_id: session_id.to_string(),
|
||||
target_id,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
};
|
||||
|
||||
let response = self.client.move_character(request).await?;
|
||||
Ok(response.into_inner())
|
||||
}
|
||||
|
||||
pub async fn get_target_hp(
|
||||
&mut self,
|
||||
session_id: &str,
|
||||
target_id: u32,
|
||||
) -> Result<world::ObjectHpResponse, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let request = world::ObjectHpRequest {
|
||||
session_id: session_id.to_string(),
|
||||
target_id,
|
||||
};
|
||||
|
||||
let response = self.client.get_target_hp(request).await?;
|
||||
Ok(response.into_inner())
|
||||
}
|
||||
|
||||
pub async fn get_nearby_objects(
|
||||
&mut self,
|
||||
session_id: &str,
|
||||
x: f32,
|
||||
y: f32,
|
||||
z: f32,
|
||||
map_id: i32,
|
||||
radius: f32,
|
||||
) -> Result<world::NearbyObjectsResponse, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let request = world::NearbyObjectsRequest {
|
||||
session_id: session_id.to_string(),
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
map_id,
|
||||
radius,
|
||||
};
|
||||
|
||||
let response = self.client.get_nearby_objects(request).await?;
|
||||
Ok(response.into_inner())
|
||||
}
|
||||
|
||||
pub async fn start_client_event_stream(
|
||||
&mut self,
|
||||
outbound_receiver: mpsc::UnboundedReceiver<world::ClientEvent>,
|
||||
) -> Result<mpsc::UnboundedReceiver<world::WorldEvent>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let (inbound_sender, inbound_receiver) = mpsc::unbounded_channel();
|
||||
|
||||
// Create the bidirectional stream
|
||||
let outbound_stream = tokio_stream::wrappers::UnboundedReceiverStream::new(outbound_receiver);
|
||||
|
||||
let response = self.client.stream_client_events(outbound_stream).await?;
|
||||
let mut inbound_stream = response.into_inner();
|
||||
|
||||
// Spawn task to handle incoming events from world service
|
||||
tokio::spawn(async move {
|
||||
while let Some(event) = inbound_stream.next().await {
|
||||
match event {
|
||||
Ok(world_event) => {
|
||||
debug!("Received world event: {:?}", world_event);
|
||||
if let Err(e) = inbound_sender.send(world_event) {
|
||||
error!("Failed to forward world event: {}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error receiving world event: {}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
info!("World event stream ended");
|
||||
});
|
||||
|
||||
Ok(inbound_receiver)
|
||||
}
|
||||
|
||||
pub fn send_client_event(&self, event: world::ClientEvent) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
if let Some(sender) = &self.event_sender {
|
||||
sender.send(event)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Event sender not initialized".into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WorldClientManager {
|
||||
clients: Arc<Mutex<HashMap<String, WorldClient>>>,
|
||||
event_senders: Arc<Mutex<HashMap<String, mpsc::UnboundedSender<world::ClientEvent>>>>,
|
||||
event_receivers: Arc<Mutex<HashMap<String, mpsc::UnboundedReceiver<world::WorldEvent>>>>,
|
||||
}
|
||||
|
||||
impl WorldClientManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
clients: Arc::new(Mutex::new(HashMap::new())),
|
||||
event_senders: Arc::new(Mutex::new(HashMap::new())),
|
||||
event_receivers: Arc::new(Mutex::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn add_client_connection(
|
||||
&self,
|
||||
session_id: String,
|
||||
client_id: String,
|
||||
position: &crate::character_common::Location,
|
||||
world_endpoint: String,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut clients = self.clients.lock().await;
|
||||
|
||||
if clients.contains_key(&session_id) {
|
||||
warn!("World client for session {} already exists", session_id);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut client = WorldClient::connect(&world_endpoint).await?;
|
||||
|
||||
// Set up bidirectional communication
|
||||
let (outbound_sender, outbound_receiver) = mpsc::unbounded_channel();
|
||||
let inbound_receiver = client.start_client_event_stream(outbound_receiver).await?;
|
||||
|
||||
// Send initial connect event
|
||||
let connect_event = world::ClientEvent {
|
||||
session_id: session_id.clone(),
|
||||
client_id: client_id.clone(),
|
||||
map_id: position.map_id as i32,
|
||||
event: Some(world::client_event::Event::Connect(world::ClientConnectEvent {
|
||||
x: position.x as f32,
|
||||
y: position.y as f32,
|
||||
z: 100.0,
|
||||
})),
|
||||
};
|
||||
|
||||
outbound_sender.send(connect_event)?;
|
||||
|
||||
clients.insert(session_id.clone(), client);
|
||||
|
||||
let mut senders = self.event_senders.lock().await;
|
||||
senders.insert(session_id.clone(), outbound_sender);
|
||||
|
||||
let mut receivers = self.event_receivers.lock().await;
|
||||
receivers.insert(session_id.clone(), inbound_receiver);
|
||||
|
||||
info!("Added world client connection for session {} on map {}", session_id, position.map_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn remove_client_connection(&self, session_id: &str) {
|
||||
// Send disconnect event before removing
|
||||
if let Err(e) = self.send_client_disconnect_event(session_id).await {
|
||||
warn!("Failed to send disconnect event for session {}: {}", session_id, e);
|
||||
}
|
||||
|
||||
let mut clients = self.clients.lock().await;
|
||||
clients.remove(session_id);
|
||||
|
||||
let mut senders = self.event_senders.lock().await;
|
||||
senders.remove(session_id);
|
||||
|
||||
let mut receivers = self.event_receivers.lock().await;
|
||||
receivers.remove(session_id);
|
||||
|
||||
info!("Removed world client connection for session {}", session_id);
|
||||
}
|
||||
|
||||
pub async fn send_client_move_event(
|
||||
&self,
|
||||
session_id: &str,
|
||||
x: f32,
|
||||
y: f32,
|
||||
z: f32,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let senders = self.event_senders.lock().await;
|
||||
if let Some(sender) = senders.get(session_id) {
|
||||
let move_event = world::ClientEvent {
|
||||
session_id: session_id.to_string(),
|
||||
client_id: session_id.to_string(), // Using session_id as client_id for now
|
||||
map_id: 0, // Will be updated by world service
|
||||
event: Some(world::client_event::Event::Move(world::ClientMoveEvent {
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
})),
|
||||
};
|
||||
sender.send(move_event)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send_client_map_change_event(&self, session_id: &str, new_map_id: u32) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let senders = self.event_senders.lock().await;
|
||||
if let Some(sender) = senders.get(session_id) {
|
||||
let map_change_event = world::ClientEvent {
|
||||
session_id: session_id.to_string(),
|
||||
client_id: session_id.to_string(),
|
||||
map_id: new_map_id as i32,
|
||||
event: Some(world::client_event::Event::MapChange(world::ClientMapChangeEvent {
|
||||
old_map_id: 0, // We don't track the old map ID in this context
|
||||
new_map_id: new_map_id as i32,
|
||||
x: 0.0, // Default position - could be enhanced to track actual position
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
})),
|
||||
};
|
||||
sender.send(map_change_event)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send_client_disconnect_event(&self, session_id: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let senders = self.event_senders.lock().await;
|
||||
if let Some(sender) = senders.get(session_id) {
|
||||
let disconnect_event = world::ClientEvent {
|
||||
session_id: session_id.to_string(),
|
||||
client_id: session_id.to_string(),
|
||||
map_id: 0,
|
||||
event: Some(world::client_event::Event::Disconnect(world::ClientDisconnectEvent {})),
|
||||
};
|
||||
sender.send(disconnect_event)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_nearby_objects(
|
||||
&self,
|
||||
session_id: &str,
|
||||
x: f32,
|
||||
y: f32,
|
||||
z: f32,
|
||||
map_id: i32,
|
||||
radius: f32,
|
||||
) -> Result<world::NearbyObjectsResponse, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut clients = self.clients.lock().await;
|
||||
if let Some(client) = clients.get_mut(session_id) {
|
||||
return client.get_nearby_objects(session_id, x, y, z, map_id, radius).await;
|
||||
}
|
||||
Err(format!("No world client found for session {}", session_id).into())
|
||||
}
|
||||
|
||||
pub async fn receive_world_event(&self, session_id: &str) -> Option<world::WorldEvent> {
|
||||
let mut receivers = self.event_receivers.lock().await;
|
||||
if let Some(receiver) = receivers.get_mut(session_id) {
|
||||
receiver.recv().await
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user