matchmake against a bot instead of searching forever
537 only tells the client's ui to start searching, the handshake after it was missing: server sends 24106 StopHomeLogic, client answers 14105, server sends 21903. that last step now builds the same snapshot the npc mission does, using npcs row 0 as the opponent. battle type stays 1 so it runs on the client's offline path. a real pvp battle is type 0 and needs the udp sector channel, which does not exist here. 14107 CancelMatchmake now answers 24125 instead of being ignored.
This commit is contained in:
parent
774e7c60cf
commit
6dc4caede9
9 changed files with 141 additions and 34 deletions
|
|
@ -26,6 +26,7 @@ fn diamonds() -> LogicDataRef {
|
|||
pub struct TurnResult {
|
||||
pub claims: Vec<LogicClaimRewardCommand>,
|
||||
pub purchases: Vec<Purchase>,
|
||||
pub matchmake: bool,
|
||||
pub out_of_sync: Option<OutOfSyncMessage>,
|
||||
pub changed: bool,
|
||||
}
|
||||
|
|
@ -214,6 +215,7 @@ impl HomeMode {
|
|||
}
|
||||
}
|
||||
}
|
||||
CommandOutcome::MatchmakeStarted => result.matchmake = true,
|
||||
CommandOutcome::Applied => result.changed = true,
|
||||
CommandOutcome::Ignored => {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use std::sync::Arc;
|
||||
use logic::table;
|
||||
use logic::{table, LogicDataTables};
|
||||
use logic::{
|
||||
AvailableServerCommandMessage, EndClientTurnMessage, LogicCommandManager, LogicDataRef,
|
||||
LogicShopSeedChangedCommand, OutOfSyncMessage, OwnHomeDataMessage, SectorStateMessage,
|
||||
StartMissionMessage,
|
||||
StartMissionMessage, StopHomeLogicMessage,
|
||||
};
|
||||
use service_rpc::{
|
||||
AccountRef, GameApi, GameRequest, GameResponse, HomeRequestKind, RpcError, RpcResult,
|
||||
|
|
@ -77,6 +77,47 @@ impl GameService {
|
|||
pub fn shop(&self) -> &Arc<ShopCatalog> {
|
||||
&self.shop
|
||||
}
|
||||
async fn sector_state_for(
|
||||
&self,
|
||||
account: AccountRef,
|
||||
npc: LogicDataRef,
|
||||
) -> RpcResult<Vec<WireMessage>> {
|
||||
let profile = self.profile(account).await?;
|
||||
let avatar = build_avatar(&profile);
|
||||
let location = npc
|
||||
.data()
|
||||
.map(|row| LogicDataRef::by_name(table::LOCATIONS, row.string("Location")))
|
||||
.unwrap_or_default();
|
||||
let battle = self
|
||||
.battles
|
||||
.build(
|
||||
location.clone(),
|
||||
npc.clone(),
|
||||
profile.arena.clone(),
|
||||
vec![avatar.clone(), avatar],
|
||||
self.config.random_seed,
|
||||
)
|
||||
.map_err(|error| RpcError::Rejected(error.to_string()))?;
|
||||
let snapshot = battle
|
||||
.snapshot()
|
||||
.map_err(|error| RpcError::Rejected(error.to_string()))?;
|
||||
tracing::info!(
|
||||
%account,
|
||||
npc = %npc,
|
||||
location = %location,
|
||||
objects = battle.battle.objects.objects.len(),
|
||||
bytes = snapshot.len(),
|
||||
"sending a battle sector state"
|
||||
);
|
||||
Ok(vec![encode(&SectorStateMessage::new(snapshot))?])
|
||||
}
|
||||
fn bot_opponent(&self) -> LogicDataRef {
|
||||
LogicDataTables::instance()
|
||||
.table(table::NPCS)
|
||||
.and_then(|rows| rows.get_at(0).cloned())
|
||||
.map(LogicDataRef::from)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
async fn roller(&self, account: AccountRef) -> RpcResult<RewardRoller> {
|
||||
let profile = self.profile(account).await?;
|
||||
let fallback: Vec<LogicDataRef> = profile
|
||||
|
|
@ -178,35 +219,21 @@ impl GameApi for GameService {
|
|||
) -> RpcResult<Vec<WireMessage>> {
|
||||
let mission = StartMissionMessage::from_bytes(&payload)
|
||||
.map_err(|error| RpcError::Rejected(error.to_string()))?;
|
||||
let profile = self.profile(account).await?;
|
||||
let avatar = build_avatar(&profile);
|
||||
let location = mission
|
||||
.npc
|
||||
.data()
|
||||
.map(|npc| LogicDataRef::by_name(table::LOCATIONS, npc.string("Location")))
|
||||
.unwrap_or_default();
|
||||
let battle = self
|
||||
.battles
|
||||
.build(
|
||||
location.clone(),
|
||||
mission.npc.clone(),
|
||||
profile.arena.clone(),
|
||||
vec![avatar.clone(), avatar],
|
||||
self.config.random_seed,
|
||||
)
|
||||
.map_err(|error| RpcError::Rejected(error.to_string()))?;
|
||||
let snapshot = battle
|
||||
.snapshot()
|
||||
.map_err(|error| RpcError::Rejected(error.to_string()))?;
|
||||
tracing::info!(
|
||||
%account,
|
||||
npc = %mission.npc,
|
||||
location = %location,
|
||||
objects = battle.battle.objects.objects.len(),
|
||||
bytes = snapshot.len(),
|
||||
"sending a battle sector state"
|
||||
);
|
||||
Ok(vec![encode(&SectorStateMessage::new(snapshot))?])
|
||||
self.sector_state_for(account, mission.npc).await
|
||||
}
|
||||
async fn home_logic_stopped(
|
||||
&self,
|
||||
account: AccountRef,
|
||||
_payload: Vec<u8>,
|
||||
) -> RpcResult<Vec<WireMessage>> {
|
||||
let npc = self.bot_opponent();
|
||||
if npc.is_none() {
|
||||
return Err(RpcError::Rejected(
|
||||
"npcs.csv has no bot to match against".into(),
|
||||
));
|
||||
}
|
||||
tracing::info!(%account, bot = %npc, "matchmaking against a bot");
|
||||
self.sector_state_for(account, npc).await
|
||||
}
|
||||
async fn end_client_turn(
|
||||
&self,
|
||||
|
|
@ -238,6 +265,10 @@ impl GameApi for GameService {
|
|||
"end client turn"
|
||||
);
|
||||
let mut replies = Vec::new();
|
||||
if result.matchmake {
|
||||
tracing::info!(%account, "client started matchmaking, stopping home logic");
|
||||
replies.push(encode(&StopHomeLogicMessage::default())?);
|
||||
}
|
||||
if let Some(out_of_sync) = result.out_of_sync {
|
||||
tracing::warn!(
|
||||
%account,
|
||||
|
|
@ -304,6 +335,9 @@ impl RpcService for GameService {
|
|||
GameRequest::StartMission { account, payload } => Ok(GameResponse::messages(
|
||||
self.start_mission(account, payload).await?,
|
||||
)),
|
||||
GameRequest::HomeLogicStopped { account, payload } => Ok(GameResponse::messages(
|
||||
self.home_logic_stopped(account, payload).await?,
|
||||
)),
|
||||
GameRequest::EndClientTurn { account, payload } => Ok(GameResponse::messages(
|
||||
self.end_client_turn(account, payload).await?,
|
||||
)),
|
||||
|
|
|
|||
|
|
@ -100,6 +100,17 @@ impl GameApi for RemoteGame {
|
|||
.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,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use logic::{
|
||||
message_type, ClientCapabilitiesMessage, GoHomeMessage, KeepAliveServerMessage,
|
||||
LoginFailedMessage, LoginMessage, LoginOkMessage, ServerErrorMessage,
|
||||
message_type, CancelMatchmakeDoneMessage, ClientCapabilitiesMessage, GoHomeMessage,
|
||||
KeepAliveServerMessage, LoginFailedMessage, LoginMessage, LoginOkMessage, ServerErrorMessage,
|
||||
};
|
||||
use service_rpc::{
|
||||
AccountRef, DeviceInfo, HomeRequestKind, LoginOutcome, Session as AuthSession, WireMessage,
|
||||
|
|
@ -88,6 +88,33 @@ impl MessageManager {
|
|||
let _ = incoming.downcast::<GoHomeMessage>();
|
||||
self.push_home(HomeRequestKind::GoHome).await
|
||||
}
|
||||
message_type::CANCEL_MATCHMAKE => {
|
||||
tracing::info!(peer = %self.peer, "client cancelled matchmaking");
|
||||
self.sender
|
||||
.send_message(&CancelMatchmakeDoneMessage::default())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
message_type::HOME_LOGIC_STOPPED => {
|
||||
let Some(account) = self.account else {
|
||||
return Ok(());
|
||||
};
|
||||
match self
|
||||
.backends
|
||||
.game
|
||||
.home_logic_stopped(account, incoming.payload)
|
||||
.await
|
||||
{
|
||||
Ok(replies) => self.push_wire_messages(replies).await?,
|
||||
Err(error) => {
|
||||
tracing::warn!(peer = %self.peer, %error, "could not match the player");
|
||||
self.sender
|
||||
.send_message(&CancelMatchmakeDoneMessage::default())
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
message_type::START_MISSION => {
|
||||
let Some(account) = self.account else {
|
||||
return Ok(());
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ pub enum CommandOutcome {
|
|||
data: LogicDataRef,
|
||||
count: i32,
|
||||
},
|
||||
MatchmakeStarted,
|
||||
Applied,
|
||||
Rejected(&'static str),
|
||||
Ignored,
|
||||
|
|
|
|||
|
|
@ -43,6 +43,6 @@ impl Execute for LogicStartMatchmakeCommand {
|
|||
if mode.is_claiming_reward() {
|
||||
return CommandOutcome::Rejected("a reward claim is in progress");
|
||||
}
|
||||
CommandOutcome::Ignored
|
||||
CommandOutcome::MatchmakeStarted
|
||||
}
|
||||
}
|
||||
|
|
|
|||
18
crates/logic/src/messages/matchmake.rs
Normal file
18
crates/logic/src/messages/matchmake.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
use titan::Message;
|
||||
use crate::commands::LogicCommand;
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Message)]
|
||||
#[message(id = 24106, direction = "server", name = "StopHomeLogicMessage")]
|
||||
pub struct StopHomeLogicMessage {}
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Message)]
|
||||
#[message(id = 24125, direction = "server", name = "CancelMatchmakeDoneMessage")]
|
||||
pub struct CancelMatchmakeDoneMessage {}
|
||||
#[derive(Debug, Default, Message)]
|
||||
#[message(id = 14105, direction = "client", name = "HomeLogicStoppedMessage")]
|
||||
#[codec(partial)]
|
||||
pub struct HomeLogicStoppedMessage {
|
||||
#[codec(vint)]
|
||||
pub tick: i32,
|
||||
#[codec(vint)]
|
||||
pub checksum: i32,
|
||||
pub commands: Vec<Box<dyn LogicCommand>>,
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ mod keep_alive;
|
|||
mod login;
|
||||
mod login_failed;
|
||||
mod login_ok;
|
||||
mod matchmake;
|
||||
mod out_of_sync;
|
||||
mod own_home_data;
|
||||
mod sector_state;
|
||||
|
|
@ -28,6 +29,7 @@ pub use keep_alive::{KeepAliveMessage, KeepAliveServerMessage};
|
|||
pub use login::LoginMessage;
|
||||
pub use login_failed::{LoginFailedMessage, LoginFailureReason};
|
||||
pub use login_ok::LoginOkMessage;
|
||||
pub use matchmake::{CancelMatchmakeDoneMessage, HomeLogicStoppedMessage, StopHomeLogicMessage};
|
||||
pub use out_of_sync::OutOfSyncMessage;
|
||||
pub use own_home_data::OwnHomeDataMessage;
|
||||
pub use sector_state::SectorStateMessage;
|
||||
|
|
@ -45,6 +47,8 @@ pub mod message_type {
|
|||
pub const GO_HOME: u16 = 14101;
|
||||
pub const END_CLIENT_TURN: u16 = 14102;
|
||||
pub const START_MISSION: u16 = 14104;
|
||||
pub const HOME_LOGIC_STOPPED: u16 = 14105;
|
||||
pub const CANCEL_MATCHMAKE: u16 = 14107;
|
||||
pub const SERVER_HELLO: u16 = 20100;
|
||||
pub const LOGIN_FAILED: u16 = 20103;
|
||||
pub const LOGIN_OK: u16 = 20104;
|
||||
|
|
|
|||
|
|
@ -48,6 +48,11 @@ pub enum GameRequest {
|
|||
#[serde(with = "crate::base64::serde_bytes")]
|
||||
payload: Vec<u8>,
|
||||
},
|
||||
HomeLogicStopped {
|
||||
account: AccountRef,
|
||||
#[serde(with = "crate::base64::serde_bytes")]
|
||||
payload: Vec<u8>,
|
||||
},
|
||||
Disconnect {
|
||||
account: AccountRef,
|
||||
},
|
||||
|
|
@ -82,6 +87,11 @@ pub trait GameApi: Send + Sync + 'static {
|
|||
account: AccountRef,
|
||||
payload: Vec<u8>,
|
||||
) -> RpcResult<Vec<WireMessage>>;
|
||||
async fn home_logic_stopped(
|
||||
&self,
|
||||
account: AccountRef,
|
||||
payload: Vec<u8>,
|
||||
) -> RpcResult<Vec<WireMessage>>;
|
||||
async fn end_client_turn(
|
||||
&self,
|
||||
account: AccountRef,
|
||||
|
|
|
|||
Loading…
Reference in a new issue