npc missions used to get a ServerErrorMessage back. now StartMission builds a LogicGameMode snapshot off the arena tilemap and answers 21903. towers come from assets/locations/*.csv the way initDefaultSector does it: tile coordinates times 500, leader index decided by which half of the map the tower sits in. the two king towers must be there, the client dereferences them without a null check. they live in buildings.csv, not characters.csv. LogicCharacter puts the base object fields fourth, not first. the buff component writes a fixed array sized by the character_buffs row count even with no buffs. training_arena parses to 2 kings, 4 princess towers, 36x64 subtiles. snapshot is 602 bytes over 6 objects. the real client has not seen it yet.
120 lines
3.4 KiB
Rust
120 lines
3.4 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 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 disconnect(&self, account: AccountRef) -> RpcResult<()> {
|
|
self.client
|
|
.call(&GameRequest::Disconnect { account })
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
}
|