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:
2025-06-25 12:30:07 -04:00
parent f75782885b
commit d906cd8d64
34 changed files with 3550 additions and 186 deletions

View File

@@ -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(())
}

View File

@@ -3,3 +3,4 @@ pub mod character;
pub mod world;
pub mod chat;
pub mod chat_client;
pub mod world_client;

View File

@@ -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;
});
}

View 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),
})
}
}

View File

@@ -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(())
}

View File

@@ -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(())

View 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
}
}
}