scroll.server/crates/gateway/src/backend.rs
WiseDev 774e7c60cf build and send the battle sector state
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.
2026-08-23 10:24:20 +03:00

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