From 8699a6884daf0b81c0460a1d51e84a4376d6dbc1 Mon Sep 17 00:00:00 2001 From: WiseDev <83840010+wisedevik@users.noreply.github.com> Date: Sun, 23 Aug 2026 14:32:20 +0300 Subject: [PATCH] answer the client when it asks for the sector state RequestSectorStateMessage, 12903, one vint of client tick, sent through sendUdpMessage and so arriving on tcp like everything else. we were ignoring it. it matters because of how the client builds models. the factory marks a freshly created object at [obj+0x14], and only for those does LogicGameObjectManager::decode call the listener at [mgr+0x28] that builds the visual. every later snapshot matches the same object by global id and reuses it, so the flag is never set again - an object that was decoded before the battle screen installed its listener stays invisible for the whole battle while still walking and fighting. that is the tower archers and the invisible units; the knight shows because the client creates that one itself, after the screen is up. the client asks for the state when it is ready, and now it gets it. --- crates/game-service/src/battle_session.rs | 7 ++++++- crates/game-service/src/service.rs | 6 ++++++ crates/gateway/src/backend.rs | 7 +++++++ crates/gateway/src/message_manager.rs | 13 +++++++++++++ crates/logic/src/messages/mod.rs | 3 +++ crates/logic/src/messages/request_sector_state.rs | 7 +++++++ crates/service-rpc/src/game.rs | 4 ++++ 7 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 crates/logic/src/messages/request_sector_state.rs diff --git a/crates/game-service/src/battle_session.rs b/crates/game-service/src/battle_session.rs index 23426b9..788144b 100644 --- a/crates/game-service/src/battle_session.rs +++ b/crates/game-service/src/battle_session.rs @@ -114,7 +114,7 @@ impl BattleSession { } }) } - fn snapshot_message(&mut self) -> Option { + pub fn snapshot_message(&mut self) -> Option { let snapshot = self.mode.snapshot().ok()?; let report = verify_snapshot(&snapshot); let unit = self @@ -344,6 +344,11 @@ impl BattleRegistry { session.push_outbound(message); } } + pub async fn resend(&self, account: AccountRef) -> Option { + let handle = self.session(account).await?; + let mut session = handle.lock().await; + session.snapshot_message() + } pub async fn send(&self, account: AccountRef, message: WireMessage) { if let Some(handle) = self.session(account).await { handle.lock().await.push_outbound(message); diff --git a/crates/game-service/src/service.rs b/crates/game-service/src/service.rs index 69d4f7a..4f98fd0 100644 --- a/crates/game-service/src/service.rs +++ b/crates/game-service/src/service.rs @@ -481,6 +481,9 @@ impl GameApi for GameService { self.running_battles.answer_emote(account).await; Ok(()) } + async fn request_sector_state(&self, account: AccountRef) -> RpcResult> { + Ok(self.running_battles.resend(account).await.into_iter().collect()) + } async fn sector_command(&self, account: AccountRef, payload: Vec) -> RpcResult<()> { let Ok(sector) = SectorCommandMessage::from_bytes(&payload) else { return Ok(()); @@ -567,6 +570,9 @@ impl RpcService for GameService { GameRequest::BattleTick { account } => Ok(GameResponse::messages( self.battle_tick(account).await?, )), + GameRequest::RequestSectorState { account } => Ok(GameResponse::messages( + self.request_sector_state(account).await?, + )), GameRequest::SectorCommand { account, payload } => { self.sector_command(account, payload).await?; Ok(GameResponse::Empty) diff --git a/crates/gateway/src/backend.rs b/crates/gateway/src/backend.rs index 089cdc7..21df291 100644 --- a/crates/gateway/src/backend.rs +++ b/crates/gateway/src/backend.rs @@ -147,6 +147,13 @@ impl GameApi for RemoteGame { .await?; Ok(()) } + async fn request_sector_state(&self, account: AccountRef) -> RpcResult> { + Ok(self + .client + .call(&GameRequest::RequestSectorState { account }) + .await? + .into_messages()) + } async fn disconnect(&self, account: AccountRef) -> RpcResult<()> { self.client .call(&GameRequest::Disconnect { account }) diff --git a/crates/gateway/src/message_manager.rs b/crates/gateway/src/message_manager.rs index ebac6d0..2ff2eb4 100644 --- a/crates/gateway/src/message_manager.rs +++ b/crates/gateway/src/message_manager.rs @@ -99,6 +99,19 @@ impl MessageManager { self.stop_battle_ticker(); self.push_home(HomeRequestKind::GoHome).await } + message_type::REQUEST_SECTOR_STATE => { + let Some(account) = self.account else { + return Ok(()); + }; + tracing::info!(peer = %self.peer, "client asked for the sector state"); + match self.backends.game.request_sector_state(account).await { + Ok(replies) => self.push_wire_messages(replies).await?, + Err(error) => { + tracing::warn!(peer = %self.peer, %error, "could not serve the sector state") + } + } + Ok(()) + } message_type::SECTOR_COMMAND => { let Some(account) = self.account else { return Ok(()); diff --git a/crates/logic/src/messages/mod.rs b/crates/logic/src/messages/mod.rs index 9e70c65..d356991 100644 --- a/crates/logic/src/messages/mod.rs +++ b/crates/logic/src/messages/mod.rs @@ -12,6 +12,7 @@ mod matchmake; mod out_of_sync; mod own_home_data; mod battle_event; +mod request_sector_state; mod sector_command; mod sector_state; mod server_error; @@ -35,6 +36,7 @@ pub use matchmake::{CancelMatchmakeDoneMessage, HomeLogicStoppedMessage, StopHom pub use out_of_sync::OutOfSyncMessage; pub use own_home_data::OwnHomeDataMessage; pub use battle_event::{BattleEventMessage, SendBattleEventMessage}; +pub use request_sector_state::RequestSectorStateMessage; pub use sector_command::SectorCommandMessage; pub use sector_state::SectorStateMessage; pub use server_error::ServerErrorMessage; @@ -53,6 +55,7 @@ pub mod message_type { pub const START_MISSION: u16 = 14104; pub const HOME_LOGIC_STOPPED: u16 = 14105; pub const CANCEL_MATCHMAKE: u16 = 14107; + pub const REQUEST_SECTOR_STATE: u16 = 12903; pub const SECTOR_COMMAND: u16 = 12904; pub const SEND_BATTLE_EVENT: u16 = 12951; pub const BATTLE_EVENT: u16 = 22952; diff --git a/crates/logic/src/messages/request_sector_state.rs b/crates/logic/src/messages/request_sector_state.rs new file mode 100644 index 0000000..502838f --- /dev/null +++ b/crates/logic/src/messages/request_sector_state.rs @@ -0,0 +1,7 @@ +use titan::Message; +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Message)] +#[message(id = 12903, direction = "client", name = "RequestSectorStateMessage")] +pub struct RequestSectorStateMessage { + #[codec(vint)] + pub client_tick: i32, +} diff --git a/crates/service-rpc/src/game.rs b/crates/service-rpc/src/game.rs index b9c4982..a265177 100644 --- a/crates/service-rpc/src/game.rs +++ b/crates/service-rpc/src/game.rs @@ -56,6 +56,9 @@ pub enum GameRequest { BattleTick { account: AccountRef, }, + RequestSectorState { + account: AccountRef, + }, SectorCommand { account: AccountRef, #[serde(with = "crate::base64::serde_bytes")] @@ -117,5 +120,6 @@ pub trait GameApi: Send + Sync + 'static { async fn battle_event(&self, account: AccountRef, payload: Vec) -> RpcResult<()>; async fn cancel_matchmake(&self, account: AccountRef) -> RpcResult<()>; async fn sector_command(&self, account: AccountRef, payload: Vec) -> RpcResult<()>; + async fn request_sector_state(&self, account: AccountRef) -> RpcResult>; async fn disconnect(&self, account: AccountRef) -> RpcResult<()>; }