1
0
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:
NG (Graham)
2025-07-22 18:27:41 -04:00
parent dfde0247a4
commit 52493bf166
22 changed files with 482 additions and 41 deletions

8
Cargo.lock generated
View File

@@ -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",

View File

@@ -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

View File

@@ -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 {

View File

@@ -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" }

View 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");
}
}
}

View File

@@ -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 {}
}

View 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");
}
}
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;
}

View File

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

View File

@@ -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,

View File

@@ -14,4 +14,6 @@ mod countdown;
pub mod modes;
mod timer;
pub const CHANNEL_BOUND: usize = 16;

View File

@@ -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
}
}

View File

@@ -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
}
}

View 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");
}

View File

@@ -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");
}

View File

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

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

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

View File

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