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.
163 lines
4.8 KiB
Rust
163 lines
4.8 KiB
Rust
use std::sync::Arc;
|
|
use service_rpc::{
|
|
AccountRef, AuthApi, AuthRequest, AuthResponse, DeviceInfo, GameApi, GameRequest, GameResponse,
|
|
HomeRequestKind, LoginOutcome, RpcClient, RpcError, RpcResult, WireMessage,
|
|
};
|
|
pub struct Backends {
|
|
pub auth: Arc<dyn AuthApi>,
|
|
pub game: Arc<dyn GameApi>,
|
|
}
|
|
impl Backends {
|
|
pub fn new(auth: Arc<dyn AuthApi>, game: Arc<dyn GameApi>) -> Arc<Self> {
|
|
Arc::new(Self { auth, game })
|
|
}
|
|
pub fn remote(auth_endpoint: &str, game_endpoint: &str) -> Arc<Self> {
|
|
Self::new(
|
|
Arc::new(RemoteAuth::new(auth_endpoint)),
|
|
Arc::new(RemoteGame::new(game_endpoint)),
|
|
)
|
|
}
|
|
}
|
|
pub struct RemoteAuth {
|
|
client: RpcClient<AuthRequest, AuthResponse>,
|
|
}
|
|
impl RemoteAuth {
|
|
pub fn new(endpoint: impl Into<String>) -> Self {
|
|
Self {
|
|
client: RpcClient::new(endpoint),
|
|
}
|
|
}
|
|
}
|
|
#[async_trait::async_trait]
|
|
impl AuthApi for RemoteAuth {
|
|
async fn login(
|
|
&self,
|
|
account: AccountRef,
|
|
pass_token: Option<String>,
|
|
device: DeviceInfo,
|
|
) -> RpcResult<LoginOutcome> {
|
|
match self
|
|
.client
|
|
.call(&AuthRequest::Login {
|
|
account,
|
|
pass_token,
|
|
device,
|
|
})
|
|
.await?
|
|
{
|
|
AuthResponse::Login(outcome) => Ok(outcome),
|
|
other => Err(RpcError::Rejected(format!(
|
|
"unexpected auth reply {other:?}"
|
|
))),
|
|
}
|
|
}
|
|
async fn resolve(&self, account: AccountRef) -> RpcResult<bool> {
|
|
match self.client.call(&AuthRequest::Resolve { account }).await? {
|
|
AuthResponse::Resolved { known } => Ok(known),
|
|
other => Err(RpcError::Rejected(format!(
|
|
"unexpected auth reply {other:?}"
|
|
))),
|
|
}
|
|
}
|
|
}
|
|
pub struct RemoteGame {
|
|
client: RpcClient<GameRequest, GameResponse>,
|
|
}
|
|
impl RemoteGame {
|
|
pub fn new(endpoint: impl Into<String>) -> Self {
|
|
Self {
|
|
client: RpcClient::new(endpoint),
|
|
}
|
|
}
|
|
}
|
|
#[async_trait::async_trait]
|
|
impl GameApi for RemoteGame {
|
|
async fn load_home(
|
|
&self,
|
|
account: AccountRef,
|
|
kind: HomeRequestKind,
|
|
) -> RpcResult<Vec<WireMessage>> {
|
|
Ok(self
|
|
.client
|
|
.call(&GameRequest::LoadHome { account, kind })
|
|
.await?
|
|
.into_messages())
|
|
}
|
|
async fn client_capabilities(&self, account: AccountRef, ping_ms: i32) -> RpcResult<()> {
|
|
self.client
|
|
.call(&GameRequest::ClientCapabilities { account, ping_ms })
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
async fn start_mission(
|
|
&self,
|
|
account: AccountRef,
|
|
payload: Vec<u8>,
|
|
) -> RpcResult<Vec<WireMessage>> {
|
|
Ok(self
|
|
.client
|
|
.call(&GameRequest::StartMission { account, payload })
|
|
.await?
|
|
.into_messages())
|
|
}
|
|
async fn home_logic_stopped(
|
|
&self,
|
|
account: AccountRef,
|
|
payload: Vec<u8>,
|
|
) -> RpcResult<Vec<WireMessage>> {
|
|
Ok(self
|
|
.client
|
|
.call(&GameRequest::HomeLogicStopped { account, payload })
|
|
.await?
|
|
.into_messages())
|
|
}
|
|
async fn end_client_turn(
|
|
&self,
|
|
account: AccountRef,
|
|
payload: Vec<u8>,
|
|
) -> RpcResult<Vec<WireMessage>> {
|
|
Ok(self
|
|
.client
|
|
.call(&GameRequest::EndClientTurn { account, payload })
|
|
.await?
|
|
.into_messages())
|
|
}
|
|
async fn battle_tick(&self, account: AccountRef) -> RpcResult<Vec<WireMessage>> {
|
|
Ok(self
|
|
.client
|
|
.call(&GameRequest::BattleTick { account })
|
|
.await?
|
|
.into_messages())
|
|
}
|
|
async fn battle_event(&self, account: AccountRef, payload: Vec<u8>) -> RpcResult<()> {
|
|
self.client
|
|
.call(&GameRequest::BattleEvent { account, payload })
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
async fn cancel_matchmake(&self, account: AccountRef) -> RpcResult<()> {
|
|
self.client
|
|
.call(&GameRequest::CancelMatchmake { account })
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
async fn sector_command(&self, account: AccountRef, payload: Vec<u8>) -> RpcResult<()> {
|
|
self.client
|
|
.call(&GameRequest::SectorCommand { account, payload })
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
async fn request_sector_state(&self, account: AccountRef) -> RpcResult<Vec<WireMessage>> {
|
|
Ok(self
|
|
.client
|
|
.call(&GameRequest::RequestSectorState { account })
|
|
.await?
|
|
.into_messages())
|
|
}
|
|
async fn disconnect(&self, account: AccountRef) -> RpcResult<()> {
|
|
self.client
|
|
.call(&GameRequest::Disconnect { account })
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
}
|