Updated how we handle client ids in the world service and logic service

Implemented the bidirectional comms stream between the world service and game logic service
This commit is contained in:
2025-07-22 00:21:28 -04:00
parent a20a44fd29
commit da6d7518e5
10 changed files with 265 additions and 55 deletions

View File

@@ -1,6 +1,6 @@
#[derive(Debug)]
struct CharacterGraphics {
pub(crate) struct CharacterGraphics {
race: u8,
hair: u8,
face: u8,

View File

@@ -1,20 +1,20 @@
#[derive(Debug)]
pub struct Client {
pub client_id: u16,
pub client_id: String,
pub access_level: u16,
pub session_id: String
}
impl Default for Client {
fn default() -> Self {
Self { client_id: 0, access_level: 1, session_id: "".to_string() }
Self { client_id: "".to_string(), access_level: 1, session_id: "".to_string() }
}
}
impl Client {
pub fn get_client_id(&self) -> u16 {
self.client_id
pub fn get_client_id(&self) -> &str {
&self.client_id
}
pub fn get_access_level(&self) -> u16 {

View File

@@ -4,7 +4,9 @@ use hecs::{Entity, World};
use tracing::debug;
use crate::components::basic_info::BasicInfo;
use crate::components::destination::Destination;
use crate::components::level::Level;
use crate::components::position::Position;
use crate::components::character_graphics::CharacterGraphics;
use crate::components::markers::*;
use crate::components::life::Life;
use crate::spatial_grid::SpatialGrid;
@@ -101,8 +103,8 @@ impl EntitySystem {
*spatial_grid = temp_factory.spatial_grid;
}
/// Get nearby objects for a specific client
pub fn get_nearby_objects_for_client(&self, client_id: u16, x: f32, y: f32, z: f32, map_id: u16) -> Vec<EntityInfo> {
/// Get nearby objects for a specific client (excluding the client itself)
pub fn get_nearby_objects_for_client(&self, client_id: &str, x: f32, y: f32, z: f32, map_id: u16) -> Vec<EntityInfo> {
let world = self.world.lock().unwrap();
let spatial_grid = self.spatial_grid.lock().unwrap();
@@ -146,7 +148,7 @@ impl EntitySystem {
entity_infos
}
/// Get nearby objects at a specific position
/// Get all nearby objects at a specific position
pub fn get_nearby_objects_at_position(&self, x: f32, y: f32, z: f32, map_id: u16) -> Vec<EntityInfo> {
let world = self.world.lock().unwrap();
let spatial_grid = self.spatial_grid.lock().unwrap();
@@ -208,6 +210,46 @@ impl EntitySystem {
return None; // Skip entities without recognized types
};
// get the extra entity data based on the type
let extra_data = match entity_type {
EntityType::Player => {
let player = world.get::<&Player>(entity).ok()?;
let level = world.get::<&Level>(entity).ok()?;
let character_graphics = world.get::<&CharacterGraphics>(entity).ok()?;
ExtraEntityData {
player: Some(PlayerData {
level: level.get_level() as u32,
class: basic_info.job as u32,
race: character_graphics.get_race() as u32,
}),
npc: None,
mob: None,
}
},
EntityType::Npc => {
let npc = world.get::<&Npc>(entity).ok()?;
ExtraEntityData {
player: None,
npc: Some(NpcData {
quest_id: npc.quest_id,
angle: npc.angle as u32,
event_status: npc.event_status as u32,
}),
mob: None,
}
},
EntityType::Mob => {
let mob = world.get::<&Mob>(entity).ok()?;
ExtraEntityData {
player: None,
npc: None,
mob: Some(MobData {
quest_id: mob.quest_id,
}),
}
}
};
Some(EntityInfo {
id: basic_info.id as i32,
entity_type,
@@ -217,6 +259,7 @@ impl EntitySystem {
name: basic_info.name.clone(),
hp,
max_hp,
extra_data,
})
}
@@ -225,7 +268,7 @@ impl EntitySystem {
self.world.clone()
}
pub fn set_destination(&self, client_id: u16, x: f32, y: f32, z: f32) {
pub fn set_destination(&self, client_id: &str, x: f32, y: f32, z: f32) {
use crate::components::client::Client;
let mut world = self.world.lock().unwrap();
@@ -252,7 +295,7 @@ impl EntitySystem {
/// Update player position and check for nearby entity changes
pub fn update_player_position(&self, client_id: u16, x: f32, y: f32, z: f32, map_id: u16) {
pub fn update_player_position(&self, client_id: &str, x: f32, y: f32, z: f32, map_id: u16) {
use crate::components::client::Client;
let mut world = self.world.lock().unwrap();
@@ -324,6 +367,33 @@ pub struct EntityInfo {
pub name: String,
pub hp: i32,
pub max_hp: i32,
pub extra_data: ExtraEntityData,
}
#[derive(Debug, Clone)]
pub struct ExtraEntityData {
pub player: Option<PlayerData>,
pub npc: Option<NpcData>,
pub mob: Option<MobData>,
}
#[derive(Debug, Clone)]
pub struct PlayerData {
pub level: u32,
pub class: u32,
pub race: u32,
}
#[derive(Debug, Clone)]
pub struct NpcData {
pub quest_id: u32,
pub angle: u32,
pub event_status: u32,
}
#[derive(Debug, Clone)]
pub struct MobData {
pub quest_id: u32,
}
/// Type of entity

View File

@@ -27,7 +27,7 @@ impl GameLogicService for MyGameLogicService {
// Get nearby entities from the entity system
let entity_infos = self.entity_system.get_nearby_objects_for_client(
req.client_id as u16,
&req.client_id,
req.x,
req.y,
req.z,
@@ -35,6 +35,7 @@ impl GameLogicService for MyGameLogicService {
);
// Convert EntityInfo to proto Object
// TODO: we also need to grab the extra data from the entity and add it to the objects
let objects: Vec<Object> = entity_infos
.into_iter()
.map(|info| Object {

View File

@@ -63,6 +63,7 @@ impl WorldGameLogicService for WorldGameLogicServiceImpl {
let map_id = self.map_id;
let entity_system = self.entity_system.clone();
let outbound_tx_clone = outbound_tx.clone();
// Handle incoming events from world service
tokio::spawn(async move {
@@ -76,10 +77,38 @@ impl WorldGameLogicService for WorldGameLogicServiceImpl {
Some(world::game_logic_event::Event::PlayerConnect(connect_event)) => {
info!("Player {} connected to map {}", connect_event.session_id, map_id);
// Handle player connection logic
if let Some(entity_system) = &entity_system {
let world = entity_system.get_world();
let mut world_lock = world.lock().unwrap();
//TODO: add an entity for the player if it doesn't exist
// entity_system.spawn_player(crate::components::position::Position {
// x: connect_event.x,
// y: connect_event.y,
// z: connect_event.z,
// map_id: map_id as u16,
// spawn_id: 0,
// });
// Find the player entity and update session_id using UUID client_id
let mut query = world_lock.query::<&mut crate::components::client::Client>();
for (entity, mut client) in query.iter() {
if client.client_id == connect_event.client_id {
client.session_id = connect_event.session_id.clone();
debug!("Updated session_id for client_id {} to {}", connect_event.client_id, connect_event.session_id);
break;
}
}
}
}
Some(world::game_logic_event::Event::PlayerDisconnect(disconnect_event)) => {
info!("Player {} disconnected from map {}", disconnect_event.session_id, map_id);
// Handle player disconnection logic
// Handle player disconnection logic - for now just log it
// TODO: Remove player entity from the world when disconnect handling is implemented
if let Some(entity_system) = &entity_system {
// entity_system.remove_player(&disconnect_event.session_id);
}
}
Some(world::game_logic_event::Event::PlayerMove(move_event)) => {
debug!("Player {} moved to ({}, {}, {}) in map {}",
@@ -87,18 +116,13 @@ impl WorldGameLogicService for WorldGameLogicServiceImpl {
// Handle player movement logic - update position and check for nearby changes
if let Some(entity_system) = &entity_system {
// Parse client_id from string to u16
if let Ok(client_id) = move_event.client_id.parse::<u16>() {
entity_system.update_player_position(
client_id,
move_event.x,
move_event.y,
move_event.z,
map_id as u16
);
} else {
debug!("Failed to parse client_id: {}", move_event.client_id);
}
entity_system.update_player_position(
&move_event.client_id,
move_event.x,
move_event.y,
move_event.z,
map_id as u16
);
}
}
_ => {
@@ -115,6 +139,35 @@ impl WorldGameLogicService for WorldGameLogicServiceImpl {
info!("World service stream ended for map {}", map_id);
});
// Send an initial connection acknowledgment event to keep the stream alive
let initial_event = world::GameLogicEvent {
client_ids: vec![],
map_id: map_id as i32,
event: None, // Empty event just to establish the stream
};
if let Err(e) = outbound_tx.send(initial_event) {
error!("Failed to send initial event for map {}: {}", map_id, e);
}
// Spawn a task to periodically check for spawner events and send them
let entity_system_spawner = self.entity_system.clone();
let _outbound_tx_spawner = outbound_tx.clone();
let _spawner_map_id = map_id;
tokio::spawn(async move {
let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(1));
loop {
interval.tick().await;
if let Some(ref _entity_system) = entity_system_spawner {
// Check for any spawner events that need to be sent
// For now, this keeps the task alive and ready for future spawner events
// Maybe we want to use a pub sub channel for spawner events in the future
// See entity_factory.rs ln 285 for where we would send the events
}
}
});
// Create outbound stream for sending events to world service
let outbound_stream = tokio_stream::wrappers::UnboundedReceiverStream::new(outbound_rx)
.map(|event| Ok(event));