mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Implement most of the occasional packets sent in elimination, expand interface for custom game logic #30
This commit is contained in:
8
Cargo.lock
generated
8
Cargo.lock
generated
@@ -452,6 +452,12 @@ dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic_float"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "628d228f918ac3b82fe590352cc719d30664a0c13ca3a60266fe02c7132d480a"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
@@ -2704,6 +2710,7 @@ name = "oj_rc_lobby_room"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
"clap",
|
||||
"env_logger",
|
||||
"log",
|
||||
@@ -3334,6 +3341,7 @@ name = "rc_multiplayer"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"atomic_float",
|
||||
"bytes",
|
||||
"byteserde",
|
||||
"chrono",
|
||||
|
||||
@@ -17,3 +17,4 @@ oj_polariton_auth = { version = "*", path = "../polariton_auth" }
|
||||
polariton_server.workspace = true
|
||||
oj_rc_core = { version = "*", path = "../rc_core" }
|
||||
async-trait.workspace = true
|
||||
chrono.workspace = true
|
||||
|
||||
@@ -42,6 +42,7 @@ impl QueueHandler {
|
||||
use std::hash::Hasher;
|
||||
let mut hasher = std::hash::DefaultHasher::new();
|
||||
key.hash(&mut hasher);
|
||||
chrono::Utc::now().timestamp().hash(&mut hasher);
|
||||
let guid = oj_rc_core::persist::user::uuid_sanitize(hasher.finish() as i64);
|
||||
let guid_str = oj_rc_core::persist::user::i64_as_uuid_str(guid);
|
||||
let player_descs = players.iter().map(|x| oj_rc_core::persist::user::PlayerLobbyDescriptor {
|
||||
|
||||
@@ -17,6 +17,7 @@ async-trait.workspace = true
|
||||
bytes = "1.10"
|
||||
byteserde = "0.6.2"
|
||||
chrono.workspace = true
|
||||
atomic_float = "1.1"
|
||||
|
||||
literustlib_server = { version = "0.1", path = "../../LiteRustLib/server" }
|
||||
literustlib = { version = "0.1", path = "../../LiteRustLib" }
|
||||
|
||||
30
rc_multiplayer/src/events/flipper_start.rs
Normal file
30
rc_multiplayer/src/events/flipper_start.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
pub struct RectifierStart {
|
||||
msg_router: tokio::sync::mpsc::Sender<crate::matches::GameMessage>,
|
||||
}
|
||||
|
||||
pub(super) fn handler(init_ctx: &crate::InitConfig) -> crate::handlers::Dataless<RectifierStart> {
|
||||
crate::handlers::Dataless::new(RectifierStart::new(init_ctx))
|
||||
}
|
||||
|
||||
impl RectifierStart {
|
||||
fn new(init_ctx: &crate::InitConfig) -> Self {
|
||||
Self {
|
||||
msg_router: init_ctx.matches_chann.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl crate::handlers::DatalessEventCodeHandler for RectifierStart {
|
||||
const CODE: rlnl::event_code::NetworkEvent = rlnl::event_code::NetworkEvent::AlignmentRectifierStarted;
|
||||
|
||||
async fn handle(&self, _peer: &std::sync::Arc<literustlib_server::Connection<crate::PacketData>>, user: &crate::UserData, _sender: &std::sync::Arc<literustlib_server::DataSender<crate::PacketData>>) {
|
||||
if let Some(user_info) = user.user().await {
|
||||
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::FlippingStarted {
|
||||
user_id: user_info.user_id(),
|
||||
}).await);
|
||||
} else {
|
||||
log::error!("Failed to handle sync loading request for unknown user");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ mod loading_done;
|
||||
mod spot_player;
|
||||
mod kill_player;
|
||||
mod client_unregister;
|
||||
mod flipper_start;
|
||||
mod self_destruct_elimination;
|
||||
|
||||
pub async fn handler(init_ctx: &crate::InitConfig) -> crate::handler::LnlEventHandler {
|
||||
crate::handler::LnlEventHandler::new(init_ctx.users.clone(), crate::vehicle_motion::handler(init_ctx))
|
||||
@@ -25,12 +27,14 @@ pub async fn handler(init_ctx: &crate::InitConfig) -> crate::handler::LnlEventHa
|
||||
{literustlib::packet::Property::Unreliable as u8},
|
||||
rlnl::events::ingame::PlayerIdAndInputData,
|
||||
>::handler(init_ctx))
|
||||
.add(crate::handlers::DatalessBroadcaster::<
|
||||
.add(flipper_start::handler(init_ctx))
|
||||
/*.add(crate::handlers::Broadcaster::<
|
||||
true,
|
||||
{rlnl::event_code::NetworkEvent::AlignmentRectifierStarted as i16},
|
||||
{rlnl::event_code::NetworkEvent::AlignmentRectifierStarted as i16},
|
||||
{literustlib::packet::Property::Unreliable as u8},
|
||||
>::handler(init_ctx))
|
||||
rlnl::events::ingame::PlayerId,
|
||||
>::handler(init_ctx))*/
|
||||
.add(crate::handlers::Broadcaster::<
|
||||
true,
|
||||
{rlnl::event_code::NetworkEvent::FireWeaponEffect as i16},
|
||||
@@ -106,28 +110,28 @@ pub async fn handler(init_ctx: &crate::InitConfig) -> crate::handler::LnlEventHa
|
||||
>::handler(init_ctx))
|
||||
.add(crate::handlers::Broadcaster::<
|
||||
true,
|
||||
{rlnl::event_code::NetworkEvent::ActivateTeleportEffect as i16},
|
||||
{rlnl::event_code::NetworkEvent::BroadcastActivateTeleportEffect as i16},
|
||||
{rlnl::event_code::NetworkEvent::ActivateTeleportEffect as i16},
|
||||
{literustlib::packet::Property::ReliableOrdered as u8},
|
||||
rlnl::events::ingame::PlayerId,
|
||||
rlnl::events::ingame::TeleportActivateEffect,
|
||||
>::handler(init_ctx))
|
||||
.add(crate::handlers::Broadcaster::<
|
||||
true,
|
||||
{rlnl::event_code::NetworkEvent::SpawnEmpLocator as i16},
|
||||
{rlnl::event_code::NetworkEvent::BroadcastSpawnEmpLocator as i16},
|
||||
{rlnl::event_code::NetworkEvent::SpawnEmpLocator as i16},
|
||||
{literustlib::packet::Property::ReliableOrdered as u8},
|
||||
rlnl::events::ingame::SpawnEmpLocator,
|
||||
>::handler(init_ctx))
|
||||
.add(crate::handlers::Broadcaster::<
|
||||
true,
|
||||
{rlnl::event_code::NetworkEvent::SpawnEmpMachineEffect as i16},
|
||||
{rlnl::event_code::NetworkEvent::BroadcastSpawnEmpMachineEffect as i16},
|
||||
{rlnl::event_code::NetworkEvent::SpawnEmpMachineEffect as i16},
|
||||
{literustlib::packet::Property::ReliableOrdered as u8},
|
||||
rlnl::events::ingame::NetworkStunnedMachineEffect,
|
||||
>::handler(init_ctx))
|
||||
.add(crate::handlers::Broadcaster::<
|
||||
true,
|
||||
{rlnl::event_code::NetworkEvent::SpawnShield as i16},
|
||||
{rlnl::event_code::NetworkEvent::ShieldSpawned as i16},
|
||||
{rlnl::event_code::NetworkEvent::SpawnShield as i16},
|
||||
{literustlib::packet::Property::ReliableOrdered as u8},
|
||||
rlnl::events::ingame::ShieldModuleEvent,
|
||||
@@ -147,6 +151,28 @@ pub async fn handler(init_ctx: &crate::InitConfig) -> crate::handler::LnlEventHa
|
||||
rlnl::events::ingame::CosmeticAction,
|
||||
>::handler(init_ctx))
|
||||
.add(client_unregister::handler(init_ctx))
|
||||
.add(crate::handlers::Broadcaster::<
|
||||
true,
|
||||
{rlnl::event_code::NetworkEvent::BroadcastInvisible as i16},
|
||||
{rlnl::event_code::NetworkEvent::MakeInvisible as i16},
|
||||
{literustlib::packet::Property::ReliableOrdered as u8},
|
||||
rlnl::events::ingame::PlayerId,
|
||||
>::handler(init_ctx))
|
||||
.add(crate::handlers::Broadcaster::<
|
||||
true,
|
||||
{rlnl::event_code::NetworkEvent::BroadcastVisible as i16},
|
||||
{rlnl::event_code::NetworkEvent::MakeVisible as i16},
|
||||
{literustlib::packet::Property::ReliableOrdered as u8},
|
||||
rlnl::events::ingame::PlayerId,
|
||||
>::handler(init_ctx))
|
||||
.add(crate::handlers::Broadcaster::<
|
||||
true,
|
||||
{rlnl::event_code::NetworkEvent::UpdateVotingAfterBattle as i16},
|
||||
{rlnl::event_code::NetworkEvent::UpdateVotingAfterBattle as i16},
|
||||
{literustlib::packet::Property::ReliableOrdered as u8},
|
||||
rlnl::events::ingame::UpdateVotingAfterBattle,
|
||||
>::handler(init_ctx))
|
||||
.add(self_destruct_elimination::handler(init_ctx))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -180,7 +206,10 @@ mod _broadcast_impls {
|
||||
impl Broadcastable for rlnl::events::ingame::ShieldModuleEvent {}
|
||||
impl Broadcastable for rlnl::events::ingame::Taunt {}
|
||||
impl Broadcastable for rlnl::events::ingame::CosmeticAction {}
|
||||
impl Broadcastable for rlnl::events::ingame::UpdateVotingAfterBattle {}
|
||||
impl Broadcastable for rlnl::events::ingame::TeleportActivateEffect {}
|
||||
|
||||
impl Broadcastable for rlnl::events::sync::UpdateGameModeSettings {}
|
||||
impl Broadcastable for rlnl::events::GameTime {}
|
||||
impl Broadcastable for rlnl::events::ingame::TeamBaseState {}
|
||||
}
|
||||
|
||||
31
rc_multiplayer/src/events/self_destruct_elimination.rs
Normal file
31
rc_multiplayer/src/events/self_destruct_elimination.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
pub struct SelfDestructElim {
|
||||
msg_router: tokio::sync::mpsc::Sender<crate::matches::GameMessage>,
|
||||
}
|
||||
|
||||
pub(super) fn handler(init_ctx: &crate::InitConfig) -> crate::handlers::Dataless<SelfDestructElim> {
|
||||
crate::handlers::Dataless::new(SelfDestructElim::new(init_ctx))
|
||||
}
|
||||
|
||||
impl SelfDestructElim {
|
||||
fn new(init_ctx: &crate::InitConfig) -> Self {
|
||||
Self {
|
||||
msg_router: init_ctx.matches_chann.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl crate::handlers::DatalessEventCodeHandler for SelfDestructElim {
|
||||
const CODE: rlnl::event_code::NetworkEvent = rlnl::event_code::NetworkEvent::SelfDestructClassicMode;
|
||||
|
||||
async fn handle(&self, _peer: &std::sync::Arc<literustlib_server::Connection<crate::PacketData>>, user: &crate::UserData, _sender: &std::sync::Arc<literustlib_server::DataSender<crate::PacketData>>) {
|
||||
if let Some(user_info) = user.user().await {
|
||||
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::SelfDestruct {
|
||||
user_id: user_info.user_id(),
|
||||
is_classic: true,
|
||||
}).await);
|
||||
} else {
|
||||
log::error!("Failed to handle sync loading request for unknown user");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ impl <const EXCLUDE_SENDER: bool, const CODE_IN: i16, const CODE_OUT: i16, const
|
||||
crate::events::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::RebroadcastRlnl {
|
||||
skip_user_id: user_info.user_id(),
|
||||
event: self.code_out,
|
||||
event_in: Self::CODE,
|
||||
property: self.property,
|
||||
data: Some(Box::new(data)),
|
||||
}).await);
|
||||
@@ -39,6 +40,7 @@ impl <const EXCLUDE_SENDER: bool, const CODE_IN: i16, const CODE_OUT: i16, const
|
||||
crate::events::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::BroadcastRlnl {
|
||||
user_id: user_info.user_id(),
|
||||
event: self.code_out,
|
||||
event_in: Self::CODE,
|
||||
property: self.property,
|
||||
data: Some(Box::new(data)),
|
||||
}).await);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#![allow(dead_code)]
|
||||
pub struct DatalessBroadcaster<const EXCLUDE_SENDER: bool, const CODE_IN: i16, const CODE_OUT: i16, const PROPERTY: u8> {
|
||||
msg_router: tokio::sync::mpsc::Sender<crate::matches::GameMessage>,
|
||||
code_out: rlnl::event_code::NetworkEvent,
|
||||
@@ -28,6 +29,7 @@ impl <const EXCLUDE_SENDER: bool, const CODE_IN: i16, const CODE_OUT: i16, const
|
||||
crate::events::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::RebroadcastRlnl {
|
||||
skip_user_id: user_info.user_id(),
|
||||
event: self.code_out,
|
||||
event_in: Self::CODE,
|
||||
property: self.property,
|
||||
data: None,
|
||||
}).await);
|
||||
@@ -35,6 +37,7 @@ impl <const EXCLUDE_SENDER: bool, const CODE_IN: i16, const CODE_OUT: i16, const
|
||||
crate::events::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::BroadcastRlnl {
|
||||
user_id: user_info.user_id(),
|
||||
event: self.code_out,
|
||||
event_in: Self::CODE,
|
||||
property: self.property,
|
||||
data: None,
|
||||
}).await);
|
||||
|
||||
@@ -8,6 +8,7 @@ mod ingame_broadcast;
|
||||
pub use ingame_broadcast::Broadcaster;
|
||||
|
||||
mod ingame_broadcast_dataless;
|
||||
#[allow(unused_imports)]
|
||||
pub use ingame_broadcast_dataless::DatalessBroadcaster;
|
||||
|
||||
mod stub;
|
||||
|
||||
@@ -11,6 +11,11 @@ pub trait CustomGameLogic: Sized + Send + Sync + 'static {
|
||||
async fn on_player_join(&self, generic: &super::GenericGamemodeEngine<Self>, player: &crate::matches::generic::UserConnection, others: &[oj_rc_core::persist::user::PlayerDescriptor]) -> bool;
|
||||
async fn on_player_end(&self, generic: &super::GenericGamemodeEngine<Self>, player: &crate::matches::generic::UserConnection) -> bool;
|
||||
async fn on_vehicle_destroyed(&self, generic: &super::GenericGamemodeEngine<Self>, killer: u8, victim: u8) -> bool;
|
||||
async fn on_vehicle_self_destruct(&self, generic: &super::GenericGamemodeEngine<Self>, user: u8, is_classic: bool) -> bool;
|
||||
async fn extra_sync_events(&self, generic: &super::GenericGamemodeEngine<Self>, player: &crate::matches::generic::UserConnection) -> Vec<RlnlPacket>;
|
||||
async fn on_countdown_start(&self, generic: &super::GenericGamemodeEngine<Self>, game_start: chrono::DateTime<chrono::Utc>) -> bool;
|
||||
async fn on_game_completed(&self, generic: &super::GenericGamemodeEngine<Self>) -> bool;
|
||||
async fn on_broadcast(&self, generic: &super::GenericGamemodeEngine<Self>, user_id: i32, event_out: rlnl::event_code::NetworkEvent, event_in: rlnl::event_code::NetworkEvent, property: literustlib::packet::Property, data: &Option<Box<dyn crate::Broadcastable>>, skip_user: bool) -> bool;
|
||||
async fn on_motion(&self, generic: &super::GenericGamemodeEngine<Self>, motion: &rlnl::machine_motion::MachineMotion) -> bool;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,12 +34,14 @@ impl UserState {
|
||||
|
||||
pub(super) struct MachineState {
|
||||
pub(super) selected_weapon: WeaponInfo,
|
||||
pub(super) location: Location,
|
||||
}
|
||||
|
||||
impl MachineState {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
selected_weapon: WeaponInfo::new(),
|
||||
location: Location::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,6 +60,22 @@ impl WeaponInfo {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct Location {
|
||||
pub x: atomic_float::AtomicF32,
|
||||
pub y: atomic_float::AtomicF32,
|
||||
pub z: atomic_float::AtomicF32,
|
||||
}
|
||||
|
||||
impl Location {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
x: atomic_float::AtomicF32::new(0.0),
|
||||
y: atomic_float::AtomicF32::new(0.0),
|
||||
z: atomic_float::AtomicF32::new(0.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(super) enum ConnectionMode {
|
||||
@@ -267,6 +285,13 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
||||
has_active_connections |= !matches!(mode, ConnectionMode::Disconnected);
|
||||
}
|
||||
is_engaged = has_active_connections;
|
||||
if !has_active_connections {
|
||||
if self.custom_logic_handler.on_game_completed(&self).await {
|
||||
if let Err(e) = conn.user.complete_game(&self.game_guid).await {
|
||||
log::error!("Failed to mark game {} as complete: {}", self.game_guid, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
conn.connection.connection.goodbye(&conn.connection.sender).await;
|
||||
}
|
||||
@@ -437,13 +462,15 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
||||
let player_count = self.player_count.load(std::sync::atomic::Ordering::Relaxed) as usize;
|
||||
log::info!("All players ({}) are ready for game {}", player_count, self.game_guid);
|
||||
tokio::time::sleep(Self::END_OF_SYNC_DELAY).await;
|
||||
let mut senders = Vec::new();
|
||||
for conn in self.users.read().await.values() {
|
||||
senders.push((conn.connection.clone(), conn.state.clone()));
|
||||
}
|
||||
let game_start = chrono::Utc::now() + Self::COUNTDOWN_DURATION;
|
||||
self.game_start.store(game_start.timestamp(), std::sync::atomic::Ordering::Relaxed);
|
||||
super::countdown::match_countdown(senders, game_start);
|
||||
if self.custom_logic_handler.on_countdown_start(&self, game_start).await {
|
||||
let mut senders = Vec::new();
|
||||
for conn in self.users.read().await.values() {
|
||||
senders.push((conn.connection.clone(), conn.state.clone()));
|
||||
}
|
||||
self.game_start.store(game_start.timestamp(), std::sync::atomic::Ordering::Relaxed);
|
||||
super::countdown::match_countdown(senders, game_start);
|
||||
}
|
||||
}
|
||||
},
|
||||
super::GameMessage::SpotVehicle { user_id, remote_player } => {
|
||||
@@ -466,31 +493,96 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
||||
).await;
|
||||
log::info!("Player {} was destroyed by {} ({}) in game {}", remote_player, killer_player, user_id, self.game_guid);
|
||||
self.custom_logic_handler.on_vehicle_destroyed(&self, killer_player, remote_player).await;
|
||||
}
|
||||
super::GameMessage::BroadcastRlnl { user_id: _, event, property, data } => {
|
||||
if let Some(data) = data {
|
||||
self.broadcast(event, property, &*data, true).await;
|
||||
} else {
|
||||
self.broadcast_dataless(event, property, true).await;
|
||||
}
|
||||
},
|
||||
super::GameMessage::RebroadcastRlnl { skip_user_id, event, property, data } => {
|
||||
if let Some(data) = data {
|
||||
self.rebroadcast(skip_user_id, event, property, &*data, true).await;
|
||||
} else {
|
||||
self.rebroadcast_dataless(skip_user_id, event, property, true).await;
|
||||
super::GameMessage::SelfDestruct { user_id, is_classic } => {
|
||||
if let Some(player_id) = self.user_key_by_user_id(user_id).await {
|
||||
self.rebroadcast(
|
||||
user_id,
|
||||
rlnl::event_code::NetworkEvent::MachineDestroyedConfirmed,
|
||||
literustlib::packet::Property::ReliableOrdered,
|
||||
&rlnl::events::ingame::Kill { killee_player_id: player_id, killer_player_id: player_id },
|
||||
true,
|
||||
).await;
|
||||
log::info!("Player {} ({}) self-destructed in game {} (elimination? {})", player_id, user_id, self.game_guid, is_classic);
|
||||
if self.custom_logic_handler.on_vehicle_self_destruct(&self, player_id, is_classic).await {
|
||||
if is_classic {
|
||||
self.rebroadcast(
|
||||
user_id,
|
||||
rlnl::event_code::NetworkEvent::OnAnotherClientDisconnected,
|
||||
literustlib::packet::Property::ReliableOrdered,
|
||||
&rlnl::events::ingame::PlayerId { player: player_id },
|
||||
true,
|
||||
).await;
|
||||
if let Some(conn) = self.users.read().await.get(&player_id) {
|
||||
crate::events::log_lnl_send_failure(conn.connection.rlnl().send_empty(
|
||||
rlnl::event_code::NetworkEvent::PlayerQuitRequestComplete,
|
||||
literustlib::packet::Property::ReliableOrdered,
|
||||
&conn.connection.connection
|
||||
).await);
|
||||
conn.state.mode.store(ConnectionMode::Disconnected.to_u8(), std::sync::atomic::Ordering::Relaxed);
|
||||
conn.connection.connection.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
super::GameMessage::Motion { user_id, data } => {
|
||||
for conn in self.users.read().await.values() {
|
||||
if conn.user.user_id() == user_id { continue; } // fun fact: the game hard crashes if you omit this
|
||||
crate::events::log_lnl_send_failure(conn.connection.sender.send_data(crate::handler::EventData {
|
||||
message_ty: crate::data::MessageType::RobotMotion,
|
||||
variant: 0,
|
||||
data_size: data.len() as _,
|
||||
data: data.clone(),
|
||||
}, literustlib::packet::Property::Unreliable, &conn.connection.connection).await);
|
||||
super::GameMessage::FlippingStarted { user_id } => {
|
||||
if let Some(user_key) = self.user_key_by_user_id(user_id).await {
|
||||
self.rebroadcast(
|
||||
user_id,
|
||||
rlnl::event_code::NetworkEvent::AlignmentRectifierStarted,
|
||||
literustlib::packet::Property::ReliableOrdered,
|
||||
&rlnl::events::ingame::PlayerId { player: user_key },
|
||||
true,
|
||||
).await;
|
||||
}
|
||||
},
|
||||
super::GameMessage::BroadcastRlnl { user_id, event, event_in, property, data } => {
|
||||
if self.custom_logic_handler.on_broadcast(&self, user_id, event, event_in, property, &data, false).await {
|
||||
if let Some(data) = data {
|
||||
self.broadcast(event, property, &*data, true).await;
|
||||
} else {
|
||||
self.broadcast_dataless(event, property, true).await;
|
||||
}
|
||||
}
|
||||
},
|
||||
super::GameMessage::RebroadcastRlnl { skip_user_id, event, event_in, property, data } => {
|
||||
if self.custom_logic_handler.on_broadcast(&self, skip_user_id, event, event_in, property, &data, true).await {
|
||||
if let Some(data) = data {
|
||||
self.rebroadcast(skip_user_id, event, property, &*data, true).await;
|
||||
} else {
|
||||
self.rebroadcast_dataless(skip_user_id, event, property, true).await;
|
||||
}
|
||||
}
|
||||
},
|
||||
super::GameMessage::Motion { user_id, motion } => {
|
||||
if self.custom_logic_handler.on_motion(&self, &motion).await {
|
||||
if let Some(conn) = self.users.read().await.get(&motion.player_id) {
|
||||
let (x, y, z) = motion.rb_state.rb_pos_rot.pos.into();
|
||||
conn.machine.location.x.store(x, std::sync::atomic::Ordering::Relaxed);
|
||||
conn.machine.location.y.store(y, std::sync::atomic::Ordering::Relaxed);
|
||||
conn.machine.location.z.store(z, std::sync::atomic::Ordering::Relaxed);
|
||||
//log::debug!("Player {} is at (x, y, z) ({}, {}, {})", motion.player_id, x, y, z);
|
||||
use byteserde::ser_heap::ByteSerializeHeap;
|
||||
let mut ser = byteserde::ser_heap::ByteSerializerHeap::default();
|
||||
if let Err(e) = motion.byte_serialize_heap(&mut ser) {
|
||||
log::error!("Failed to serialize motion data from user {}: {}", user_id, e);
|
||||
} else {
|
||||
let data = bytes::Bytes::copy_from_slice(ser.as_slice());
|
||||
for conn in self.users.read().await.values() {
|
||||
if conn.user.user_id() == user_id { continue; } // fun fact: the game hard crashes if you omit this
|
||||
crate::events::log_lnl_send_failure(conn.connection.sender.send_data(crate::handler::EventData {
|
||||
message_ty: crate::data::MessageType::RobotMotion,
|
||||
variant: 0,
|
||||
data_size: data.len() as _,
|
||||
data: data.clone(),
|
||||
}, literustlib::packet::Property::Unreliable, &conn.connection.connection).await);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::warn!("Received machine motion with unknown player id {} from user {}", motion.player_id, user_id);
|
||||
}
|
||||
}
|
||||
},
|
||||
super::GameMessage::NoOp => {},
|
||||
@@ -654,4 +746,8 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
||||
pub(super) fn game_done(&self) {
|
||||
self.is_complete.store(true, std::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub(super) fn is_game_done(&self) -> bool {
|
||||
self.is_complete.load(std::sync::atomic::Ordering::SeqCst)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,21 +38,30 @@ pub enum GameMessage {
|
||||
remote_player: u8,
|
||||
killer_player: u8,
|
||||
},
|
||||
SelfDestruct {
|
||||
user_id: i32,
|
||||
is_classic: bool,
|
||||
},
|
||||
FlippingStarted { // flip yeah!
|
||||
user_id: i32,
|
||||
},
|
||||
BroadcastRlnl {
|
||||
user_id: i32,
|
||||
event: rlnl::event_code::NetworkEvent,
|
||||
event_in: rlnl::event_code::NetworkEvent,
|
||||
property: literustlib::packet::Property,
|
||||
data: Option<Box<dyn crate::Broadcastable>>,
|
||||
},
|
||||
RebroadcastRlnl {
|
||||
skip_user_id: i32,
|
||||
event: rlnl::event_code::NetworkEvent,
|
||||
event_in: rlnl::event_code::NetworkEvent,
|
||||
property: literustlib::packet::Property,
|
||||
data: Option<Box<dyn crate::Broadcastable>>,
|
||||
},
|
||||
Motion {
|
||||
user_id: i32,
|
||||
data: bytes::Bytes,
|
||||
motion: rlnl::machine_motion::MachineMotion,
|
||||
},
|
||||
NoOp,
|
||||
}
|
||||
@@ -71,6 +80,8 @@ impl GameMessage {
|
||||
Self::LoadComplete { user_id, .. } => *user_id,
|
||||
Self::SpotVehicle { user_id, .. } => *user_id,
|
||||
Self::DestroyVehicle { user_id, .. } => *user_id,
|
||||
Self::SelfDestruct { user_id, .. } => *user_id,
|
||||
Self::FlippingStarted { user_id, .. } => *user_id,
|
||||
Self::BroadcastRlnl { user_id, .. } => *user_id,
|
||||
Self::RebroadcastRlnl { skip_user_id, .. } => *skip_user_id,
|
||||
Self::Motion { user_id, .. } => *user_id,
|
||||
|
||||
@@ -14,4 +14,6 @@ mod countdown;
|
||||
|
||||
pub mod modes;
|
||||
|
||||
mod timer;
|
||||
|
||||
pub const CHANNEL_BOUND: usize = 16;
|
||||
|
||||
@@ -40,20 +40,41 @@ impl PlayerTracker {
|
||||
}
|
||||
only_alive_team
|
||||
}
|
||||
|
||||
async fn teams(&self) -> std::collections::HashSet<u8> {
|
||||
self.alive.lock().await.keys().map(|x| *x).collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EliminationLogic {
|
||||
tracked: PlayerTracker
|
||||
tracked: PlayerTracker,
|
||||
game_duration: std::time::Duration,
|
||||
game_end: std::sync::atomic::AtomicI64,
|
||||
timer_task: tokio::sync::Mutex<Option<tokio::task::JoinHandle<()>>>,
|
||||
}
|
||||
|
||||
impl EliminationLogic {
|
||||
pub fn new() -> Self {
|
||||
let dur = std::time::Duration::from_secs(300);
|
||||
let fake_end = (chrono::Utc::now() + dur).timestamp();
|
||||
Self {
|
||||
tracked: PlayerTracker {
|
||||
alive: tokio::sync::Mutex::new(std::collections::HashMap::new()),
|
||||
},
|
||||
game_duration: dur,
|
||||
game_end: std::sync::atomic::AtomicI64::new(fake_end),
|
||||
timer_task: tokio::sync::Mutex::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
async fn abort_timer_sync(&self) {
|
||||
let mut lock = self.timer_task.lock().await;
|
||||
if let Some(timer_t) = &*lock {
|
||||
timer_t.abort();
|
||||
log::debug!("Aborted elimination match timer task");
|
||||
}
|
||||
*lock = None;
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@@ -64,6 +85,14 @@ impl CustomGameLogic for EliminationLogic {
|
||||
}
|
||||
|
||||
async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, player: &crate::matches::generic::UserConnection) -> bool {
|
||||
if generic.is_game_done() {
|
||||
return true;
|
||||
}
|
||||
if chrono::Utc::now().timestamp() >= self.game_end.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
generic.game_done();
|
||||
self.abort_timer_sync().await;
|
||||
return true;
|
||||
}
|
||||
self.tracked.destroy_vehicle(&player.descriptor).await;
|
||||
if let Some(winning_team) = self.tracked.winner_team().await {
|
||||
log::info!("Team {} has won sudden death game {} because player {} left", winning_team, generic.game_guid, player.descriptor.player_id);
|
||||
@@ -86,6 +115,7 @@ impl CustomGameLogic for EliminationLogic {
|
||||
).await);
|
||||
}
|
||||
generic.game_done();
|
||||
self.abort_timer_sync().await;
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -142,6 +172,15 @@ impl CustomGameLogic for EliminationLogic {
|
||||
true
|
||||
}
|
||||
|
||||
async fn on_vehicle_self_destruct(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, user: u8, is_classic: bool) -> bool {
|
||||
if is_classic {
|
||||
self.on_vehicle_destroyed(generic, u8::MAX, user).await
|
||||
} else {
|
||||
log::warn!("Received non-elimination self destruct in elimination game mode");
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
async fn extra_sync_events(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _player: &crate::matches::generic::UserConnection) -> Vec<crate::matches::RlnlPacket> {
|
||||
vec![
|
||||
crate::matches::RlnlPacket {
|
||||
@@ -155,8 +194,48 @@ impl CustomGameLogic for EliminationLogic {
|
||||
crate::matches::RlnlPacket {
|
||||
event: rlnl::event_code::NetworkEvent::CurrentGameTime,
|
||||
property: literustlib::packet::Property::ReliableOrdered,
|
||||
data: Box::new(rlnl::events::GameTime(300.0)), // FIXME use value from config
|
||||
data: Box::new(rlnl::events::GameTime(self.game_duration.as_millis() as f32 / 1000.0)), // FIXME use value from config
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async fn on_countdown_start(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, game_start: chrono::DateTime<chrono::Utc>) -> bool {
|
||||
let mut senders = Vec::new();
|
||||
for conn in generic.users.read().await.values() {
|
||||
senders.push((conn.connection.clone(), conn.state.clone()));
|
||||
}
|
||||
let game_end = game_start + self.game_duration;
|
||||
let teams = self.tracked.teams().await;
|
||||
let extra_packets = teams.iter().map(|team| crate::matches::RlnlPacket {
|
||||
event: rlnl::event_code::NetworkEvent::TeamBaseInitialise,
|
||||
property: literustlib::packet::Property::ReliableOrdered,
|
||||
data: Box::new(rlnl::events::ingame::TeamBaseState {
|
||||
base_team_or_mining_point_index: *team,
|
||||
current_progress: rlnl::types::ByteFloat::from(0.0),
|
||||
max_progress: rlnl::types::ByteFloat::from(4.0),
|
||||
}),
|
||||
}).collect();
|
||||
let new_timer_task = crate::matches::timer::match_time_syncer(senders, game_start, game_end, extra_packets);
|
||||
let mut timer_lock = self.timer_task.lock().await;
|
||||
if let Some(timer_t) = &*timer_lock { // this is quite unlikely (i.e. impossible), but I've done it for completeness
|
||||
log::warn!("Aborting an existing timer task for elimination mode suggests an assumption was wrong");
|
||||
timer_t.abort();
|
||||
}
|
||||
*timer_lock = Some(new_timer_task);
|
||||
self.game_end.store(game_end.timestamp(), std::sync::atomic::Ordering::Relaxed);
|
||||
true
|
||||
}
|
||||
|
||||
async fn on_game_completed(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>) -> bool {
|
||||
self.abort_timer_sync().await;
|
||||
true
|
||||
}
|
||||
|
||||
async fn on_broadcast(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _user_id: i32, _event_out: rlnl::event_code::NetworkEvent, _event_in: rlnl::event_code::NetworkEvent, _property: literustlib::packet::Property, _data: &Option<Box<dyn crate::Broadcastable>>, _skip_user: bool) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
async fn on_motion(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _motion: &rlnl::machine_motion::MachineMotion) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,27 @@ impl CustomGameLogic for NoOpLogic {
|
||||
true
|
||||
}
|
||||
|
||||
async fn on_vehicle_self_destruct(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _user: u8, _is_classic: bool) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
async fn extra_sync_events(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _player: &crate::matches::generic::UserConnection) -> Vec<crate::matches::RlnlPacket> {
|
||||
Vec::default()
|
||||
}
|
||||
|
||||
async fn on_countdown_start(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _game_start: chrono::DateTime<chrono::Utc>) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
async fn on_game_completed(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
async fn on_broadcast(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _user_id: i32, _event_out: rlnl::event_code::NetworkEvent, _event_in: rlnl::event_code::NetworkEvent, _property: literustlib::packet::Property, _data: &Option<Box<dyn crate::Broadcastable>>, _skip_user: bool) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
async fn on_motion(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _motion: &rlnl::machine_motion::MachineMotion) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
71
rc_multiplayer/src/matches/timer.rs
Normal file
71
rc_multiplayer/src/matches/timer.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
const SLEEP_PERIOD: std::time::Duration = std::time::Duration::from_millis(250);
|
||||
|
||||
pub fn match_time_syncer(players: Vec<(super::generic::UserSender, std::sync::Arc<super::generic::UserState>)>, game_start: chrono::DateTime<chrono::Utc>, game_end: chrono::DateTime<chrono::Utc>, extra_packets: Vec<super::RlnlPacket>) -> tokio::task::JoinHandle<()> {
|
||||
tokio::spawn(do_match_timer_async(players, game_start, game_end, extra_packets))
|
||||
}
|
||||
|
||||
pub fn time_to_game_end_payload(game_end: chrono::DateTime<chrono::Utc>) -> rlnl::events::GameTime {
|
||||
let now = chrono::Utc::now();
|
||||
let time_until_end = game_end.signed_duration_since(now);
|
||||
let time_until_end_f32 = (time_until_end.num_milliseconds().clamp(0, i64::MAX) as f32) / 1000.0;
|
||||
rlnl::events::GameTime(time_until_end_f32 * 4.0)
|
||||
}
|
||||
|
||||
async fn do_match_timer_async(players: Vec<(super::generic::UserSender, std::sync::Arc<super::generic::UserState>)>, game_start: chrono::DateTime<chrono::Utc>, game_end: chrono::DateTime<chrono::Utc>, extra_packets: Vec<super::RlnlPacket>) {
|
||||
let now = chrono::Utc::now();
|
||||
let time_until_start_ms = game_start.signed_duration_since(now).num_milliseconds().clamp(0, i64::MAX) + SLEEP_PERIOD.as_millis() as i64;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(time_until_start_ms as u64)).await;
|
||||
let extras_count = extra_packets.len();
|
||||
for packet in extra_packets {
|
||||
for player in players.iter() {
|
||||
let mode = super::generic::ConnectionMode::from_u8(player.1.mode.load(std::sync::atomic::Ordering::Relaxed));
|
||||
if matches!(mode, super::generic::ConnectionMode::InGame) {
|
||||
let sender = player.0.rlnl();
|
||||
crate::events::log_lnl_send_failure(sender.send_data(
|
||||
&*packet.data,
|
||||
packet.event,
|
||||
packet.property,
|
||||
&player.0.connection
|
||||
).await);
|
||||
}
|
||||
}
|
||||
}
|
||||
if extras_count != 0 {
|
||||
log::info!("Broadcast {} custom packets for game start", extras_count);
|
||||
}
|
||||
'timer_loop: loop {
|
||||
let payload = time_to_game_end_payload(game_end);
|
||||
'player_loop: for player in players.iter() {
|
||||
if !player.0.connection.is_connected() { continue 'player_loop; }
|
||||
let sender = player.0.rlnl();
|
||||
if let Err(e) = sender.send_data(
|
||||
&payload,
|
||||
rlnl::event_code::NetworkEvent::CurrentGameTime,
|
||||
literustlib::packet::Property::Unreliable,
|
||||
&player.0.connection)
|
||||
.await {
|
||||
log::error!("Failed to send CurrentGameTime event to a user: {}", e);
|
||||
}
|
||||
}
|
||||
if payload.0 > f32::EPSILON {
|
||||
tokio::time::sleep(SLEEP_PERIOD).await;
|
||||
} else {
|
||||
break 'timer_loop;
|
||||
}
|
||||
}
|
||||
let payload = rlnl::events::ingame::GameEnd {
|
||||
reason: rlnl::types::GameEndReason::TimeOut,
|
||||
};
|
||||
for player in players.iter() {
|
||||
let sender = player.0.rlnl();
|
||||
if let Err(e) = sender.send_data(
|
||||
&payload,
|
||||
rlnl::event_code::NetworkEvent::EndGame,
|
||||
literustlib::packet::Property::ReliableOrdered,
|
||||
&player.0.connection)
|
||||
.await {
|
||||
log::error!("Failed to send EndGame event to a user: {}", e);
|
||||
}
|
||||
}
|
||||
log::debug!("Game timer (a)sync thread has completed");
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
use byteserde::prelude::ByteDeserializeSlice;
|
||||
|
||||
pub struct VehicleMotionHandler {
|
||||
msg_router: tokio::sync::mpsc::Sender<crate::matches::GameMessage>,
|
||||
}
|
||||
@@ -18,10 +20,19 @@ impl VehicleMotionHandler {
|
||||
impl crate::RobotMotionHandler for VehicleMotionHandler {
|
||||
async fn handle(&self, data: &bytes::Bytes, user: &crate::UserData) {
|
||||
if let Some(user_info) = user.user().await {
|
||||
crate::events::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::Motion {
|
||||
user_id: user_info.user_id(),
|
||||
data: data.to_owned(),
|
||||
}).await);
|
||||
let mut des = byteserde::des_slice::ByteDeserializerSlice::new(&data);
|
||||
match rlnl::machine_motion::MachineMotion::byte_deserialize(&mut des) {
|
||||
Ok(motion_data) => {
|
||||
crate::events::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::Motion {
|
||||
user_id: user_info.user_id(),
|
||||
motion: motion_data,
|
||||
}).await);
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Bad deserialization for machine motion, bytes {:?}: {}", &data[..], e);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
log::error!("Failed to handle motion unknown user");
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@ mod garage_slot_controls;
|
||||
mod garage_slot_set_customisations;
|
||||
mod garage_slot_name;
|
||||
mod garage_slot_copy;
|
||||
mod steam_promo;
|
||||
|
||||
use polariton_server::operations::OperationsHandler;
|
||||
|
||||
@@ -219,4 +220,5 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler<crate::UserTy>
|
||||
.add(garage_slot_copy::garage_slot_copy_provider())
|
||||
.add(polariton_server::operations::Ack::<12, _>::default()) // TODO handle UpdatePlayerDailyQuestProgressRequest instead of ignoring it
|
||||
.add(polariton_server::operations::Ack::<90, _>::default()) // TODO handle SubmitCRFRatingRequest instead of ignoring it
|
||||
.add(steam_promo::steam_promos_provider())
|
||||
}
|
||||
|
||||
22
rc_services_room/src/operations/steam_promo.rs
Normal file
22
rc_services_room/src/operations/steam_promo.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use polariton_server::operations::Immediate;
|
||||
use polariton::operation::Typed;
|
||||
|
||||
const CODE: u8 = 52;
|
||||
const PROMO_LIST_PARAM_KEY: u8 = 63; // list of str
|
||||
const STEAM_PROMOS_PARAM_KEY: u8 = 121; // string (json)
|
||||
const CUBES_AWARDED_PARAM_KEY: u8 = 128; // string (json)
|
||||
const PASS_AWARDED_PARAM_KEY: u8 = 4; // bool
|
||||
|
||||
pub(super) fn steam_promos_provider() -> Immediate<CODE, crate::UserTy> {
|
||||
Immediate::new(|| {
|
||||
let mut params = std::collections::HashMap::with_capacity(4);
|
||||
params.insert(PROMO_LIST_PARAM_KEY, Typed::Arr(polariton::operation::Arr {
|
||||
ty: polariton::serdes::TypePrefix::Str,
|
||||
items: Vec::default(),
|
||||
}));
|
||||
params.insert(STEAM_PROMOS_PARAM_KEY, Typed::Str("{}".into()));
|
||||
params.insert(CUBES_AWARDED_PARAM_KEY, Typed::Str("{}".into()));
|
||||
params.insert(PASS_AWARDED_PARAM_KEY, Typed::Bool(false));
|
||||
params.into()
|
||||
})
|
||||
}
|
||||
13
rc_social_room/src/operations/calculate_mmr.rs
Normal file
13
rc_social_room/src/operations/calculate_mmr.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use polariton_server::operations::Immediate;
|
||||
use polariton::operation::Typed;
|
||||
|
||||
const CODE: u8 = 60;
|
||||
const MMR_PARAM_KEY: u8 = 77; // f64
|
||||
|
||||
pub(super) fn mmr_provider<C: Clone + Send + Sync>() -> Immediate<CODE, crate::UserTy, C> {
|
||||
Immediate::new(|| {
|
||||
let mut params = std::collections::HashMap::with_capacity(1);
|
||||
params.insert(MMR_PARAM_KEY, Typed::Double(1.0));
|
||||
params.into()
|
||||
})
|
||||
}
|
||||
@@ -7,6 +7,7 @@ mod search_clan;
|
||||
mod season_rewards;
|
||||
mod previous_battle_rewards;
|
||||
mod platoon_data;
|
||||
mod calculate_mmr;
|
||||
|
||||
use polariton_server::operations::OperationsHandler;
|
||||
|
||||
@@ -27,4 +28,5 @@ pub fn handler() -> OperationsHandler<crate::UserTy, crate::data::custom::Custom
|
||||
.add(previous_battle_rewards::pending_battle_rewards_provider())
|
||||
.add(platoon_data::platoon_provider())
|
||||
.add(polariton_server::operations::Ack::<6, _>::default()) // AvatarUpdatedRequest, sent on services_room avatar_set success (just needs to be ack-ed; no params)
|
||||
.add(calculate_mmr::mmr_provider())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user