1
0
mirror of https://git.ngram.ca/OpenJam/rc-servers synced 2026-08-23 23:08:52 +00:00

Add basic stats tracking #30

This commit is contained in:
NG (Graham)
2025-08-03 18:15:09 -04:00
parent 580ce69839
commit df017883c1
10 changed files with 331 additions and 11 deletions

View File

@@ -0,0 +1,31 @@
pub struct AssistBonus {
msg_router: tokio::sync::mpsc::Sender<crate::matches::GameMessage>,
}
pub(super) fn handler(init_ctx: &crate::InitConfig) -> crate::handlers::SimpleRlnl<rlnl::events::ingame::AssistBonus, AssistBonus> {
crate::handlers::SimpleRlnl::new(AssistBonus::new(init_ctx))
}
impl AssistBonus {
fn new(init_ctx: &crate::InitConfig) -> Self {
Self {
msg_router: init_ctx.matches_chann.clone(),
}
}
}
#[async_trait::async_trait]
impl crate::handlers::RlnlEventCodeHandler for AssistBonus {
type In = rlnl::events::ingame::AssistBonus;
const CODE: rlnl::event_code::NetworkEvent = rlnl::event_code::NetworkEvent::AssistBonusRequest;
async fn handle(&self, data: Self::In, _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::AssistBonus {
user_id: user_info.user_id(),
shootee: data.requester_player_id as u8,
shooters: data.player_ids.into_iter().map(|x| x.player).collect(),
}).await);
}
}
}

View File

@@ -0,0 +1,30 @@
pub struct DamageBonus {
msg_router: tokio::sync::mpsc::Sender<crate::matches::GameMessage>,
}
pub(super) fn handler(init_ctx: &crate::InitConfig) -> crate::handlers::SimpleRlnl<rlnl::events::ingame::DestroyedHealedCubesBonus, DamageBonus> {
crate::handlers::SimpleRlnl::new(DamageBonus::new(init_ctx))
}
impl DamageBonus {
fn new(init_ctx: &crate::InitConfig) -> Self {
Self {
msg_router: init_ctx.matches_chann.clone(),
}
}
}
#[async_trait::async_trait]
impl crate::handlers::RlnlEventCodeHandler for DamageBonus {
type In = rlnl::events::ingame::DestroyedHealedCubesBonus;
const CODE: rlnl::event_code::NetworkEvent = rlnl::event_code::NetworkEvent::DestroyCubesBonusRequest;
async fn handle(&self, data: Self::In, _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::DestroyCubesBonus {
user_id: user_info.user_id(),
info: data,
}).await);
}
}
}

View File

@@ -0,0 +1,30 @@
pub struct HealBonus {
msg_router: tokio::sync::mpsc::Sender<crate::matches::GameMessage>,
}
pub(super) fn handler(init_ctx: &crate::InitConfig) -> crate::handlers::SimpleRlnl<rlnl::events::ingame::DestroyedHealedCubesBonus, HealBonus> {
crate::handlers::SimpleRlnl::new(HealBonus::new(init_ctx))
}
impl HealBonus {
fn new(init_ctx: &crate::InitConfig) -> Self {
Self {
msg_router: init_ctx.matches_chann.clone(),
}
}
}
#[async_trait::async_trait]
impl crate::handlers::RlnlEventCodeHandler for HealBonus {
type In = rlnl::events::ingame::DestroyedHealedCubesBonus;
const CODE: rlnl::event_code::NetworkEvent = rlnl::event_code::NetworkEvent::HealCubesBonusRequest;
async fn handle(&self, data: Self::In, _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::DestroyCubesBonus {
user_id: user_info.user_id(),
info: data,
}).await);
}
}
}

View File

@@ -0,0 +1,31 @@
pub struct KillEnemyBonus {
msg_router: tokio::sync::mpsc::Sender<crate::matches::GameMessage>,
}
pub(super) fn handler(init_ctx: &crate::InitConfig) -> crate::handlers::SimpleRlnl<rlnl::events::ingame::Kill, KillEnemyBonus> {
crate::handlers::SimpleRlnl::new(KillEnemyBonus::new(init_ctx))
}
impl KillEnemyBonus {
fn new(init_ctx: &crate::InitConfig) -> Self {
Self {
msg_router: init_ctx.matches_chann.clone(),
}
}
}
#[async_trait::async_trait]
impl crate::handlers::RlnlEventCodeHandler for KillEnemyBonus {
type In = rlnl::events::ingame::Kill;
const CODE: rlnl::event_code::NetworkEvent = rlnl::event_code::NetworkEvent::KillBonusRequest;
async fn handle(&self, data: Self::In, _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::KillBonus {
user_id: user_info.user_id(),
shootee: data.killee_player_id,
shooter: data.killer_player_id,
}).await);
}
}
}

View File

@@ -11,6 +11,10 @@ mod client_unregister;
mod flipper_start;
mod self_destruct_elimination;
mod map_ping;
mod kill_bonus;
mod assist_bonus;
mod damage_bonus;
mod heal_bonus;
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))
@@ -79,10 +83,8 @@ pub async fn handler(init_ctx: &crate::InitConfig) -> crate::handler::LnlEventHa
{literustlib::packet::Property::Unreliable as u8},
rlnl::events::ingame::DestroyCubeEffectOnly,
>::handler(init_ctx))
.add(crate::handlers::Stub::<
{rlnl::event_code::NetworkEvent::DestroyCubesBonusRequest as i16},
rlnl::events::ingame::DestroyedHealedCubesBonus,
>::handler(init_ctx))
.add(damage_bonus::handler(init_ctx))
.add(heal_bonus::handler(init_ctx))
.add(crate::handlers::Broadcaster::<
false,
{rlnl::event_code::NetworkEvent::HealSelf as i16},
@@ -90,10 +92,8 @@ pub async fn handler(init_ctx: &crate::InitConfig) -> crate::handler::LnlEventHa
{literustlib::packet::Property::ReliableOrdered as u8},
rlnl::events::HealedCubes,
>::handler(init_ctx))
.add(crate::handlers::Stub::<
{rlnl::event_code::NetworkEvent::KillBonusRequest as i16},
rlnl::events::ingame::Kill, // FIXME this is not a player ID -- it's actually sending 2 bytes not 1
>::handler(init_ctx))
.add(kill_bonus::handler(init_ctx))
.add(assist_bonus::handler(init_ctx))
.add(kill_player::handler(init_ctx))
.add(crate::handlers::Broadcaster::<
true,

View File

@@ -12,4 +12,5 @@ mod ingame_broadcast_dataless;
pub use ingame_broadcast_dataless::DatalessBroadcaster;
mod stub;
#[allow(unused_imports)]
pub use stub::Stub;

View File

@@ -3,6 +3,7 @@ pub struct Stub<const CODE_IN: i16, In: byteserde::des_slice::ByteDeserializeSli
}
impl <const CODE_IN: i16, In: byteserde::des_slice::ByteDeserializeSlice<In> + Send + Sync + 'static> Stub<CODE_IN, In> {
#[allow(dead_code)]
pub fn handler(init_ctx: &crate::InitConfig) -> crate::handlers::simple_typed::SimpleRlnl<In, Self> {
crate::handlers::simple_typed::SimpleRlnl::new(Stub::new(init_ctx))
}

View File

@@ -4,6 +4,7 @@ pub(super) struct UserConnection {
pub(super) state: std::sync::Arc<UserState>,
pub(super) machine: MachineState,
pub(super) descriptor: oj_rc_core::persist::user::PlayerDescriptor,
pub(super) counters: UserData,
}
#[derive(Clone)]
@@ -76,6 +77,57 @@ impl Location {
}
}
pub(super) struct UserData {
pub kills: std::sync::atomic::AtomicU32,
pub deaths: std::sync::atomic::AtomicU32,
pub assists: std::sync::atomic::AtomicU32,
pub healed: std::sync::atomic::AtomicU32,
pub received_healed: std::sync::atomic::AtomicU32,
pub cubes: std::sync::atomic::AtomicU32,
pub received_cubes: std::sync::atomic::AtomicU32, // damage taken
}
impl UserData {
fn new() -> Self {
Self {
kills: std::sync::atomic::AtomicU32::new(0),
deaths: std::sync::atomic::AtomicU32::new(0),
assists: std::sync::atomic::AtomicU32::new(0),
healed: std::sync::atomic::AtomicU32::new(0),
received_healed: std::sync::atomic::AtomicU32::new(0),
cubes: std::sync::atomic::AtomicU32::new(0),
received_cubes: std::sync::atomic::AtomicU32::new(0),
}
}
pub(super) fn generic_score(&self) -> u32 {
self.kills.load(std::sync::atomic::Ordering::Relaxed) * 1_000
+ self.assists.load(std::sync::atomic::Ordering::Relaxed) * 100
+ self.healed.load(std::sync::atomic::Ordering::Relaxed)
+ self.cubes.load(std::sync::atomic::Ordering::Relaxed)
}
pub(super) fn get_generic_packet(&self, player_id: u8, stat: rlnl::types::IngameStatId, delta: Option<u32>) -> rlnl::events::ingame::UpdateGameStats {
let (stat_amount, backup_delta) = match stat {
rlnl::types::IngameStatId::DestroyedCubes
| rlnl::types::IngameStatId::DestroyedCubesInProtection
| rlnl::types::IngameStatId::DestroyedCubesDefendingTheBase => (self.cubes.load(std::sync::atomic::Ordering::SeqCst), 1),
rlnl::types::IngameStatId::Kill => (self.kills.load(std::sync::atomic::Ordering::Relaxed), 1_000),
rlnl::types::IngameStatId::KillAssist => (self.assists.load(std::sync::atomic::Ordering::Relaxed), 100),
rlnl::types::IngameStatId::HealCubes => (self.assists.load(std::sync::atomic::Ordering::SeqCst), 1),
rlnl::types::IngameStatId::RobotDestroyed => (self.deaths.load(std::sync::atomic::Ordering::Relaxed), 0),
s => panic!("Cannot generate game stat {:?}", s)
};
rlnl::events::ingame::UpdateGameStats {
player_id,
stat_id: stat,
amount: stat_amount,
score: self.generic_score(),
delta_score: delta.unwrap_or(backup_delta),
}
}
}
#[repr(u8)]
#[derive(Debug, Copy, Clone)]
pub(super) enum ConnectionMode {
@@ -252,6 +304,7 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
state: std::sync::Arc::new(UserState::new()),
machine: MachineState::new(),
descriptor: player_info.to_owned(),
counters: UserData::new(),
};
if self.custom_logic_handler.on_player_join(&self, &new_user, &self.players_info).await {
self.spawn_send_loading_events(&new_user, id, self.players_info.clone());
@@ -489,7 +542,19 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
true,
).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;
if self.custom_logic_handler.on_vehicle_destroyed(&self, killer_player, remote_player).await {
// the kill tracking is initiated separately by the client with kill bonus event
if let Some(killed) = self.users.read().await.get(&remote_player) {
killed.counters.deaths.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let data = killed.counters.get_generic_packet(remote_player, rlnl::types::IngameStatId::RobotDestroyed, None);
self.broadcast(
rlnl::event_code::NetworkEvent::UpdateGameStats,
literustlib::packet::Property::ReliableOrdered,
&data,
true,
).await;
}
}
},
super::GameMessage::SelfDestruct { user_id, is_classic } => {
if let Some(player_id) = self.user_key_by_user_id(user_id).await {
@@ -520,6 +585,16 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
conn.connection.connection.disconnect();
}
}
if let Some(killed) = self.users.read().await.get(&player_id) {
killed.counters.deaths.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let data = killed.counters.get_generic_packet(player_id, rlnl::types::IngameStatId::RobotDestroyed, None);
self.broadcast(
rlnl::event_code::NetworkEvent::UpdateGameStats,
literustlib::packet::Property::ReliableOrdered,
&data,
true,
).await;
}
}
}
@@ -546,7 +621,106 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
).await);
}
}
}
},
super::GameMessage::KillBonus { user_id: _, shootee, shooter } => {
if let Some(to_reward) = self.users.read().await.get(&shooter) {
to_reward.counters.kills.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
crate::events::log_lnl_send_failure(to_reward.connection.rlnl().send_data(
&rlnl::events::ingame::Kill {
killee_player_id: shootee,
killer_player_id: shooter,
},
rlnl::event_code::NetworkEvent::ConfirmedKill,
literustlib::packet::Property::ReliableOrdered,
&to_reward.connection.connection
).await);
let data = to_reward.counters.get_generic_packet(shooter, rlnl::types::IngameStatId::Kill, None);
self.broadcast(
rlnl::event_code::NetworkEvent::UpdateGameStats,
literustlib::packet::Property::ReliableOrdered,
&data,
true,
).await;
}
},
super::GameMessage::AssistBonus { user_id: _, shootee, shooters } => {
let lock = self.users.read().await;
for shooter in shooters {
if let Some(to_reward) = lock.get(&shooter) {
to_reward.counters.assists.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
crate::events::log_lnl_send_failure(to_reward.connection.rlnl().send_data(
&rlnl::events::ingame::Kill {
killee_player_id: shootee,
killer_player_id: shooter,
},
rlnl::event_code::NetworkEvent::ConfirmedAssist,
literustlib::packet::Property::ReliableOrdered,
&to_reward.connection.connection
).await);
let data = to_reward.counters.get_generic_packet(shooter, rlnl::types::IngameStatId::KillAssist, None);
self.broadcast(
rlnl::event_code::NetworkEvent::UpdateGameStats,
literustlib::packet::Property::ReliableOrdered,
&data,
true,
).await;
}
}
},
super::GameMessage::DestroyCubesBonus { user_id: _, info } => {
let lock = self.users.read().await;
for shooter in info.shooters {
if let Some(to_reward) = lock.get(&shooter.shooting_player_id) {
let mut total_cubes = 0;
for target in shooter.shooter_targets {
if let Some(to_punish) = lock.get(&target.target_player_id) {
let mut total_cubes_received = 0;
for cubes in target.cube_amounts {
// TODO use cube_id for something!?
total_cubes += cubes.cube_count;
total_cubes_received += cubes.cube_count;
}
to_punish.counters.received_cubes.fetch_add(total_cubes_received, std::sync::atomic::Ordering::SeqCst);
}
}
to_reward.counters.cubes.fetch_add(total_cubes, std::sync::atomic::Ordering::SeqCst);
let data = to_reward.counters.get_generic_packet(shooter.shooting_player_id, rlnl::types::IngameStatId::DestroyedCubes, Some(total_cubes));
self.broadcast(
rlnl::event_code::NetworkEvent::UpdateGameStats,
literustlib::packet::Property::Unreliable,
&data,
true,
).await;
}
}
},
super::GameMessage::HealCubesBonus { user_id: _, info } => {
let lock = self.users.read().await;
for shooter in info.shooters {
if let Some(to_reward) = lock.get(&shooter.shooting_player_id) {
let mut total_cubes = 0;
for target in shooter.shooter_targets {
if let Some(to_punish) = lock.get(&target.target_player_id) {
let mut total_cubes_received = 0;
for cubes in target.cube_amounts {
// TODO use cube_id for something!?
total_cubes += cubes.cube_count;
total_cubes_received += cubes.cube_count;
}
to_punish.counters.received_healed.fetch_add(total_cubes_received, std::sync::atomic::Ordering::SeqCst);
}
}
to_reward.counters.healed.fetch_add(total_cubes, std::sync::atomic::Ordering::SeqCst);
let data = to_reward.counters.get_generic_packet(shooter.shooting_player_id, rlnl::types::IngameStatId::HealCubes, Some(total_cubes));
self.broadcast(
rlnl::event_code::NetworkEvent::UpdateGameStats,
literustlib::packet::Property::Unreliable,
&data,
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 {

View File

@@ -49,6 +49,24 @@ pub enum GameMessage {
user_id: i32,
ping: rlnl::events::ingame::MapPing,
},
KillBonus {
user_id: i32,
shootee: u8,
shooter: u8,
},
AssistBonus {
user_id: i32,
shootee: u8,
shooters: Vec<u8>,
},
DestroyCubesBonus {
user_id: i32,
info: rlnl::events::ingame::DestroyedHealedCubesBonus,
},
HealCubesBonus {
user_id: i32,
info: rlnl::events::ingame::DestroyedHealedCubesBonus,
},
BroadcastRlnl {
user_id: i32,
event: rlnl::event_code::NetworkEvent,
@@ -87,6 +105,10 @@ impl GameMessage {
Self::SelfDestruct { user_id, .. } => *user_id,
Self::FlippingStarted { user_id, .. } => *user_id,
Self::MapPing { user_id, .. } => *user_id,
Self::KillBonus { user_id, .. } => *user_id,
Self::AssistBonus { user_id, .. } => *user_id,
Self::DestroyCubesBonus { user_id, .. } => *user_id,
Self::HealCubesBonus { user_id, .. } => *user_id,
Self::BroadcastRlnl { user_id, .. } => *user_id,
Self::RebroadcastRlnl { skip_user_id, .. } => *skip_user_id,
Self::Motion { user_id, .. } => *user_id,