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

Attempt to debounce kill bonus/counter

This commit is contained in:
NG (Graham)
2026-01-12 18:01:23 -05:00
parent d421cfa264
commit 7b7da8d59e
6 changed files with 121 additions and 46 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
/target /target
*/target
*.zip *.zip
# files generated by running servers # files generated by running servers

View File

@@ -73,6 +73,7 @@ impl UserState {
pub(super) struct MachineState { pub(super) struct MachineState {
pub(super) selected_weapon: WeaponInfo, pub(super) selected_weapon: WeaponInfo,
pub(super) location: Location, pub(super) location: Location,
pub(super) is_alive: std::sync::Arc<std::sync::atomic::AtomicBool>
} }
impl MachineState { impl MachineState {
@@ -80,6 +81,7 @@ impl MachineState {
Self { Self {
selected_weapon: WeaponInfo::new(), selected_weapon: WeaponInfo::new(),
location: Location::new(), location: Location::new(),
is_alive: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true)),
} }
} }
} }
@@ -225,6 +227,35 @@ impl ConnectionMode {
} }
} }
struct UnclaimedStats {
kills: tokio::sync::Mutex<std::collections::HashMap<KillAttribution, chrono::DateTime<chrono::Utc>>>,
}
impl UnclaimedStats {
const DEBOUNCE_PERIOD: std::time::Duration = std::time::Duration::from_secs(2);
fn new() -> Self {
Self {
kills: tokio::sync::Mutex::new(std::collections::HashMap::new()),
}
}
// returns true if is new
async fn debounce_kill(&self, attr: KillAttribution) -> bool {
let now = chrono::Utc::now();
if let Some(time) = self.kills.lock().await.insert(attr, now) {
(now - time).to_std().unwrap_or_default() > Self::DEBOUNCE_PERIOD
} else {
true
}
}
}
#[derive(Hash, Eq, PartialEq, Copy, Clone)]
struct KillAttribution {
killer: u8,
victim: u8,
}
pub(super) struct GenericGamemodeEngine<L: super::CustomGameLogic> { pub(super) struct GenericGamemodeEngine<L: super::CustomGameLogic> {
pub users: tokio::sync::RwLock<std::collections::HashMap<u8, std::sync::Arc<UserConnection>>>, pub users: tokio::sync::RwLock<std::collections::HashMap<u8, std::sync::Arc<UserConnection>>>,
descriptors: std::collections::HashMap<u8, UserDescriptor>, descriptors: std::collections::HashMap<u8, UserDescriptor>,
@@ -241,6 +272,7 @@ pub(super) struct GenericGamemodeEngine<L: super::CustomGameLogic> {
pub custom_logic_handler: L, pub custom_logic_handler: L,
//pub fake_users: std::collections::HashMap<u8, FakeUser>, //pub fake_users: std::collections::HashMap<u8, FakeUser>,
pub fakes_handler: super::fake::Handler, pub fakes_handler: super::fake::Handler,
unclaimed: UnclaimedStats,
} }
impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> { impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
@@ -279,6 +311,7 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
game_duration: std::time::Duration::from_secs((config.game_time_minutes as u64) * 60), game_duration: std::time::Duration::from_secs((config.game_time_minutes as u64) * 60),
custom_logic_handler: custom, custom_logic_handler: custom,
fakes_handler, fakes_handler,
unclaimed: UnclaimedStats::new(),
} }
} }
@@ -872,7 +905,10 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
if 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 // the kill tracking is initiated separately by the client with kill bonus event
if let Some(killed) = self.user_descriptor(remote_player) { if let Some(killed) = self.user_descriptor(remote_player) {
let was_killed = killed.machine.is_alive.swap(false, std::sync::atomic::Ordering::Relaxed);
if was_killed {
killed.counters.deaths.fetch_add(1, std::sync::atomic::Ordering::Relaxed); killed.counters.deaths.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
killed.machine.is_alive.store(false, std::sync::atomic::Ordering::Relaxed);
let data = killed.counters.get_generic_packet(remote_player, rlnl::types::IngameStatId::RobotDestroyed, None); let data = killed.counters.get_generic_packet(remote_player, rlnl::types::IngameStatId::RobotDestroyed, None);
self.broadcast( self.broadcast(
rlnl::event_code::NetworkEvent::UpdateGameStats, rlnl::event_code::NetworkEvent::UpdateGameStats,
@@ -880,6 +916,8 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
&data, &data,
true, true,
).await; ).await;
//self.unclaimed.debounce_kill(KillAttribution { killer: killer_player, victim: remote_player }).await;
}
} }
} }
} }
@@ -950,6 +988,7 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
shooter: u8, shooter: u8,
) { ) {
if self.custom_logic_handler.on_kill_bonus(self, shooter, shootee).await { if self.custom_logic_handler.on_kill_bonus(self, shooter, shootee).await {
if self.unclaimed.debounce_kill(KillAttribution { killer: shooter, victim: shootee }).await {
if let Some(to_reward) = self.users.read().await.get(&shooter) { if let Some(to_reward) = self.users.read().await.get(&shooter) {
let to_reward_desc = self.user_descriptor(shooter).unwrap(); let to_reward_desc = self.user_descriptor(shooter).unwrap();
to_reward_desc.counters.kills.fetch_add(1, std::sync::atomic::Ordering::Relaxed); to_reward_desc.counters.kills.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
@@ -972,6 +1011,7 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
} }
} }
} }
}
async fn on_assist_bonus(&self, async fn on_assist_bonus(&self,
_user_id: i32, _user_id: i32,

View File

@@ -1130,8 +1130,18 @@ impl BattleArenaLogic {
z: 10.0 * (player_team as f32) + 10.0, z: 10.0 * (player_team as f32) + 10.0,
} }
}; };
if let Some(player_desc) = generic.user_descriptor(player_id) {
let connections = generic.users.read().await.values().map(|player_info| player_info.connection.clone()).collect(); let connections = generic.users.read().await.values().map(|player_info| player_info.connection.clone()).collect();
tokio::task::spawn(super::respawn_player_after(respawn_timestamp, connections, spawn_point, player_id)); tokio::task::spawn(super::respawn_player_after(
respawn_timestamp,
connections,
spawn_point,
player_id,
player_desc.machine.is_alive.clone(),
));
} else {
log::error!("Player {} cannot respawn because they are not in the game!?", player_id);
}
} else { } else {
log::error!("Player {} cannot respawn because they are not in a team!?", player_id); log::error!("Player {} cannot respawn because they are not in a team!?", player_id);
} }

View File

@@ -16,7 +16,7 @@ pub use team_death_match::TeamDeathMatchLogic;
mod trackers; mod trackers;
async fn respawn_player_after(after: chrono::DateTime<chrono::Utc>, players: Vec<crate::matches::generic::UserSender>, spawn: oj_rc_core::persist::config::Point, player_id: u8) { async fn respawn_player_after(after: chrono::DateTime<chrono::Utc>, players: Vec<crate::matches::generic::UserSender>, spawn: oj_rc_core::persist::config::Point, player_id: u8, alive_flag: std::sync::Arc<std::sync::atomic::AtomicBool>) {
let sleep_dur = after.signed_duration_since(chrono::Utc::now()).to_std().expect("Respawn duration too long to sleep"); let sleep_dur = after.signed_duration_since(chrono::Utc::now()).to_std().expect("Respawn duration too long to sleep");
tokio::time::sleep(sleep_dur).await; tokio::time::sleep(sleep_dur).await;
let spawn_payload = rlnl::events::sync::SpawnPoint { let spawn_payload = rlnl::events::sync::SpawnPoint {
@@ -36,4 +36,5 @@ async fn respawn_player_after(after: chrono::DateTime<chrono::Utc>, players: Vec
&player.connection, &player.connection,
).await); ).await);
} }
alive_flag.store(true, std::sync::atomic::Ordering::Relaxed);
} }

View File

@@ -238,8 +238,13 @@ impl PitLogic {
player_streak.store(0, std::sync::atomic::Ordering::Relaxed); player_streak.store(0, std::sync::atomic::Ordering::Relaxed);
} }
if let Some(player_streak) = self.player_tracking.streaks.get(&killer) { if let Some(player_streak) = self.player_tracking.streaks.get(&killer) {
if let Some(victim_respawn) = self.player_tracking.respawns.get(&victim) {
let now = chrono::Utc::now().timestamp();
if victim_respawn.load(std::sync::atomic::Ordering::Relaxed) <= now {
player_streak.fetch_add(1, std::sync::atomic::Ordering::Relaxed); player_streak.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
} }
}
}
self.do_leader_update(generic, killer, victim).await; self.do_leader_update(generic, killer, victim).await;
} }
@@ -314,6 +319,7 @@ impl PitLogic {
if let Some(player_respawn) = self.player_tracking.respawns.get(&player_id) { if let Some(player_respawn) = self.player_tracking.respawns.get(&player_id) {
player_respawn.store(respawn_timestamp.timestamp(), std::sync::atomic::Ordering::Relaxed); player_respawn.store(respawn_timestamp.timestamp(), std::sync::atomic::Ordering::Relaxed);
} }
if let Some(player_desc) = generic.user_descriptor(player_id) {
let respawn_payload = rlnl::events::ingame::RespawnTime { let respawn_payload = rlnl::events::ingame::RespawnTime {
owner: player_id, owner: player_id,
waiting_time: self.settings.respawn_time_seconds as i16, waiting_time: self.settings.respawn_time_seconds as i16,
@@ -326,7 +332,14 @@ impl PitLogic {
).await; ).await;
let spawn_point = Self::choose_spawn_point(&generic.map_config, player_id).1; let spawn_point = Self::choose_spawn_point(&generic.map_config, player_id).1;
let connections = generic.users.read().await.values().map(|player_info| player_info.connection.clone()).collect(); let connections = generic.users.read().await.values().map(|player_info| player_info.connection.clone()).collect();
tokio::task::spawn(super::respawn_player_after(respawn_timestamp, connections, spawn_point, player_id)); tokio::task::spawn(super::respawn_player_after(
respawn_timestamp,
connections,
spawn_point,
player_id,
player_desc.machine.is_alive.clone(),
));
}
} }
} }

View File

@@ -297,8 +297,18 @@ impl TeamDeathMatchLogic {
z: 10.0 * (player_team as f32) + 10.0, z: 10.0 * (player_team as f32) + 10.0,
} }
}; };
if let Some(player_desc) = generic.user_descriptor(player_id) {
let connections = generic.users.read().await.values().map(|player_info| player_info.connection.clone()).collect(); let connections = generic.users.read().await.values().map(|player_info| player_info.connection.clone()).collect();
tokio::task::spawn(super::respawn_player_after(respawn_timestamp, connections, spawn_point, player_id)); tokio::task::spawn(super::respawn_player_after(
respawn_timestamp,
connections,
spawn_point,
player_id,
player_desc.machine.is_alive.clone(),
));
} else {
log::error!("Player {} cannot respawn because they are not in the game!?", player_id);
}
} else { } else {
log::error!("Player {} cannot respawn because they are not connected!?", player_id); log::error!("Player {} cannot respawn because they are not connected!?", player_id);
} }