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

Add basic client AI support

This commit is contained in:
NG (Graham)
2025-09-27 12:36:49 -04:00
parent 91fc2db868
commit e55bd6d288
29 changed files with 652 additions and 317 deletions

View File

@@ -2,16 +2,16 @@ use crate::matches::{modes::trackers::SurrenderGameTracker, CustomGameLogic};
struct PlayerTracker {
connected: tokio::sync::Mutex<std::collections::HashMap<u8, std::collections::HashSet<u8>>>, // team -> set of player_id
in_point: tokio::sync::RwLock<std::collections::HashMap<u8, std::sync::atomic::AtomicU16>>, // player_id -> in point state (if val > u8::MAX then not in a point)
respawning: tokio::sync::RwLock<std::collections::HashMap<u8, std::sync::atomic::AtomicI64>>, // player_id -> time when they'll spawn (time since unix epoch)
in_point: std::collections::HashMap<u8, std::sync::atomic::AtomicU16>, // player_id -> in point state (if val > u8::MAX then not in a point)
respawning: std::collections::HashMap<u8, std::sync::atomic::AtomicI64>, // player_id -> time when they'll spawn (time since unix epoch)
}
impl PlayerTracker {
fn new() -> Self {
fn new(players: &[oj_rc_core::persist::user::PlayerDescriptor]) -> Self {
Self {
connected: tokio::sync::Mutex::new(std::collections::HashMap::new()),
in_point: tokio::sync::RwLock::new(std::collections::HashMap::new()),
respawning: tokio::sync::RwLock::new(std::collections::HashMap::new()),
in_point: players.iter().map(|player| (player.player_id, std::sync::atomic::AtomicU16::new(u16::MAX))).collect(),
respawning: players.iter().map(|player| (player.player_id, std::sync::atomic::AtomicI64::new(i64::MIN))).collect(),
}
}
@@ -24,8 +24,8 @@ impl PlayerTracker {
None
}
async fn swap_is_in_point(&self, player_id: u8, point: Option<u8>) -> Option<u8> {
self.in_point.read().await.get(&player_id).and_then(|x| {
fn swap_is_in_point(&self, player_id: u8, point: Option<u8>) -> Option<u8> {
self.in_point.get(&player_id).and_then(|x| {
let old_point = x.swap(point.map(|x| x as u16).unwrap_or(u16::MAX), std::sync::atomic::Ordering::Relaxed);
if old_point > u8::MAX as u16 {
None
@@ -44,8 +44,6 @@ impl PlayerTracker {
new_team.insert(player.player_id);
conn_lock.insert(player.team as u8, new_team);
}
self.in_point.write().await.insert(player.player_id, std::sync::atomic::AtomicU16::new(u16::MAX));
self.respawning.write().await.insert(player.player_id, std::sync::atomic::AtomicI64::new(i64::MIN));
}
async fn disconnect_player(&self, player_id: u8) {
@@ -898,14 +896,14 @@ impl BattleArenaLogic {
const CRYSTAL_ID: u32 = 3950293873;
const CLASP_ID: u32 = 606866102;
pub fn new(config: &oj_rc_core::data::game_mode::GameModeConfig, map: &oj_rc_core::persist::config::MapConfig, parsers: &oj_rc_core::cubes::CubeParsers, ba_config: oj_rc_core::data::battle_arena_config::BattleArenaData) -> Self {
pub fn new(config: &oj_rc_core::data::game_mode::GameModeConfig, map: &oj_rc_core::persist::config::MapConfig, parsers: &oj_rc_core::cubes::CubeParsers, players: &[oj_rc_core::persist::user::PlayerDescriptor], ba_config: oj_rc_core::data::battle_arena_config::BattleArenaData) -> Self {
let cube_parser = parsers.locations_of();
let crystals = cube_parser.locations_of_by_distance_to_first(&mut std::io::Cursor::new(&ba_config.base_machine_map), Self::CRYSTAL_ID, Self::CLASP_ID);
Self {
respawn_full_heal_duration: config.respawn_full_heal_duration,
respawn_heal_duration: config.respawn_heal_duration,
timer_task: tokio::sync::Mutex::new(None),
player_tracking: PlayerTracker::new(),
player_tracking: PlayerTracker::new(players),
capture_tracking: PointTracker::new(map.capture_points.iter().map(|(_, speed)| *speed)),
surrender_tracking: super::trackers::SurrenderGameTracker::new(),
base_tracking: BaseTracker::new(map.bases.keys(), &crystals, &ba_config),
@@ -989,7 +987,8 @@ impl BattleArenaLogic {
winning_team,
end_reason,
};
for player in generic.users.read().await.values() {
for (player_id, player) in generic.user_descriptors().iter() {
if player.descriptor.user_id.is_none() { continue; } // skip non-user players
let is_winner = player.descriptor.team == winning_team as i32;
let net_event = match ty {
WinMode::BaseFull
@@ -1001,21 +1000,18 @@ impl BattleArenaLogic {
if is_winner { rlnl::event_code::NetworkEvent::GameWon } else { rlnl::event_code::NetworkEvent::GameLost }
}
};
crate::events::log_lnl_send_failure(
player.connection.rlnl()
.send_data(
&payload,
net_event,
literustlib::packet::Property::ReliableOrdered,
&player.connection.connection,
).await
);
generic.send_to_player(
*player_id,
net_event,
literustlib::packet::Property::ReliableOrdered,
&payload,
).await;
}
}
async fn do_destruct_tasks(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, player_id: u8) {
if let Some(player_team) = self.player_tracking.team(player_id).await {
let was_in_point = self.player_tracking.swap_is_in_point(player_id, None).await;
let was_in_point = self.player_tracking.swap_is_in_point(player_id, None);
if let Some(was_in_point) = was_in_point {
self.capture_tracking.on_exit(generic, was_in_point, player_id, player_team as i8, self.config.num_segments as f32).await;
}
@@ -1027,7 +1023,7 @@ impl BattleArenaLogic {
let respawn_time = std::time::Duration::from_secs(self.config.respawn_time_seconds as u64);
let now = chrono::Utc::now();
let respawn_timestamp = now + respawn_time;
if let Some(player_respawn) = self.player_tracking.respawning.read().await.get(&player_id) {
if let Some(player_respawn) = self.player_tracking.respawning.get(&player_id) {
player_respawn.store(respawn_timestamp.timestamp(), std::sync::atomic::Ordering::Relaxed);
}
let respawn_payload = rlnl::events::ingame::RespawnTime {
@@ -1142,13 +1138,13 @@ impl BattleArenaLogic {
#[async_trait::async_trait]
impl CustomGameLogic for BattleArenaLogic {
async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, player: &crate::matches::generic::UserConnection, _others: &[oj_rc_core::persist::user::PlayerDescriptor]) -> bool {
async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _conn: &crate::matches::generic::UserConnection, player: &crate::matches::generic::UserDescriptor) -> bool {
log::info!("Player {} joined", player.descriptor.player_id);
self.player_tracking.track_player(&player.descriptor).await;
true
}
async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, player: &crate::matches::generic::UserConnection) -> bool {
async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _connection: &crate::matches::generic::UserConnection, player: &crate::matches::generic::UserDescriptor) -> bool {
if generic.is_game_done() {
return true;
}
@@ -1177,7 +1173,7 @@ impl CustomGameLogic for BattleArenaLogic {
true
}
async fn extra_sync_events(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _player: &crate::matches::generic::UserConnection) -> Vec<crate::matches::RlnlPacket> {
async fn extra_sync_events(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> Vec<crate::matches::RlnlPacket> {
vec![
Some(crate::matches::RlnlPacket {
event: rlnl::event_code::NetworkEvent::GameModeSettings,
@@ -1336,8 +1332,9 @@ impl CustomGameLogic for BattleArenaLogic {
async fn on_countdown_start(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, game_start: chrono::DateTime<chrono::Utc>) -> bool {
let read_lock = generic.users.read().await;
let mut senders = Vec::with_capacity(read_lock.len());
for conn in read_lock.values() {
senders.push((conn.connection.clone(), conn.state.clone()));
for (player_id, conn) in read_lock.iter() {
let state = generic.user_descriptor(*player_id).unwrap().state.clone();
senders.push((conn.connection.clone(), state));
}
drop(read_lock);
let game_end = game_start + generic.game_duration;
@@ -1465,7 +1462,7 @@ impl CustomGameLogic for BattleArenaLogic {
break;
}
}
let was_in_point = self.player_tracking.swap_is_in_point(motion.player_id, now_in_point).await;
let was_in_point = self.player_tracking.swap_is_in_point(motion.player_id, now_in_point);
if was_in_point != now_in_point {
//log::info!("Player {}'s occupied capture point changed from {:?} to {:?}", motion.player_id, was_in_point, now_in_point);
if let Some(now_in_point) = now_in_point {
@@ -1539,7 +1536,7 @@ impl CustomGameLogic for BattleArenaLogic {
}
},
(rlnl::event_code::NetworkEvent::SurrenderVoteCast, literustlib::packet::Property::ReliableOrdered) => {
if let Some(player_id) = generic.user_key_by_user_id(user_id).await {
if let Some(player_id) = generic.user_key_by_user_id(user_id) {
if let Some(team) = self.player_tracking.team(player_id).await {
let maybe_vote = <dyn core::any::Any>::downcast_ref::<rlnl::events::ingame::SurrenderVoteCast>(data.as_ref());
if let Some(vote) = maybe_vote {
@@ -1555,7 +1552,7 @@ impl CustomGameLogic for BattleArenaLogic {
(rlnl::event_code::NetworkEvent::AwardTeamBaseProtoniumDestroyedRequest, literustlib::packet::Property::ReliableOrdered) => {
let maybe_crystal_bonus = <dyn core::any::Any>::downcast_ref::<rlnl::events::ingame::AwardProtoniumDestroyedCubes>(data.as_ref());
if let Some(crystal_destroyed) = maybe_crystal_bonus {
if let Some(generic_player) = generic.users.read().await.get(&crystal_destroyed.player_id) {
if let Some(generic_player) = generic.user_descriptor(crystal_destroyed.player_id) {
generic_player.counters.crystals.fetch_add(crystal_destroyed.destroyed_cubes as u32, std::sync::atomic::Ordering::Relaxed);
let data = generic_player.counters.get_generic_packet(crystal_destroyed.player_id, rlnl::types::IngameStatId::DestroyedProtoniumCubes, Some(crystal_destroyed.destroyed_cubes as _));
generic.broadcast(

View File

@@ -2,10 +2,17 @@ use crate::matches::CustomGameLogic;
struct PlayerTracker {
alive: tokio::sync::Mutex<std::collections::HashMap<u8, std::collections::HashSet<u8>>>, // team -> set of player_id
in_base: tokio::sync::RwLock<std::collections::HashMap<u8, std::sync::atomic::AtomicU16>>, // player_id -> in base state (if base > u8::MAX then not in a base)
in_base: std::collections::HashMap<u8, std::sync::atomic::AtomicU16>, // player_id -> in base state (if base > u8::MAX then not in a base)
}
impl PlayerTracker {
fn new(players: &[oj_rc_core::persist::user::PlayerDescriptor]) -> Self {
Self {
alive: tokio::sync::Mutex::new(std::collections::HashMap::with_capacity(2)),
in_base: players.iter().map(|player| (player.player_id, std::sync::atomic::AtomicU16::new(u16::MAX))).collect()
}
}
async fn track_vehicle(&self, player: &oj_rc_core::persist::user::PlayerDescriptor) {
let mut alive_lock = self.alive.lock().await;
if let Some(team) = alive_lock.get_mut(&(player.team as u8)) {
@@ -15,7 +22,7 @@ impl PlayerTracker {
new_team.insert(player.player_id);
alive_lock.insert(player.team as u8, new_team);
}
self.in_base.write().await.insert(player.player_id, std::sync::atomic::AtomicU16::new(u16::MAX));
//self.in_base.write().await.insert(player.player_id, std::sync::atomic::AtomicU16::new(u16::MAX));
}
async fn destroy_vehicle(&self, player: &oj_rc_core::persist::user::PlayerDescriptor) {
@@ -67,8 +74,8 @@ impl PlayerTracker {
None
}
async fn swap_is_in_base(&self, player_id: u8, base: Option<u8>) -> Option<u8> {
self.in_base.read().await.get(&player_id).and_then(|x| {
fn swap_is_in_base(&self, player_id: u8, base: Option<u8>) -> Option<u8> {
self.in_base.get(&player_id).and_then(|x| {
let base = x.swap(base.map(|x| x as u16).unwrap_or(u16::MAX), std::sync::atomic::Ordering::Relaxed);
if base > u8::MAX as u16 {
None
@@ -288,18 +295,18 @@ impl BaseTracker {
winning_team,
end_reason: rlnl::types::GameEndReason::BaseCaptured,
};
for conn in generic.users.read().await.values() {
let event = if conn.descriptor.team == winning_team_i32 {
for (player_id, player_info) in generic.user_descriptors().iter() {
let event = if player_info.descriptor.team == winning_team_i32 {
rlnl::event_code::NetworkEvent::GameWon
} else {
rlnl::event_code::NetworkEvent::GameLost
};
crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data(
&win_data,
generic.send_to_player(
*player_id,
event,
literustlib::packet::Property::ReliableOrdered,
&conn.connection.connection
).await);
&win_data,
).await;
}
generic.game_done();
break;
@@ -335,12 +342,9 @@ pub struct EliminationLogic {
}
impl EliminationLogic {
pub fn new(config: &oj_rc_core::data::game_mode::GameModeConfig, map: &oj_rc_core::persist::config::MapConfig) -> Self {
pub fn new(config: &oj_rc_core::data::game_mode::GameModeConfig, map: &oj_rc_core::persist::config::MapConfig, players: &[oj_rc_core::persist::user::PlayerDescriptor]) -> Self {
Self {
tracked: PlayerTracker {
alive: tokio::sync::Mutex::new(std::collections::HashMap::new()),
in_base: tokio::sync::RwLock::new(std::collections::HashMap::new()),
},
tracked: PlayerTracker::new(players),
bases: BaseTracker {
bases: map.bases.iter().map(|(team, base)| (*team, BaseCounters::new(base.1))).collect(),
ticker: super::trackers::TickTracker::new(),
@@ -361,12 +365,12 @@ impl EliminationLogic {
*lock = None;
}
async fn on_last_player_gone(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, conn: &crate::matches::generic::UserConnection) {
async fn on_last_player_gone(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, conn: &crate::matches::generic::UserConnection, player: &crate::matches::generic::UserDescriptor) {
log::debug!("Everyone is dead, so long and thanks for all the fish");
generic.game_done();
self.abort_timer_sync().await;
let data = rlnl::events::ingame::GameLoseWin {
winning_team: if conn.descriptor.team == 0 { 1 } else { 0 }, // always the other team
winning_team: if player.descriptor.team == 0 { 1 } else { 0 }, // always the other team
end_reason: rlnl::types::GameEndReason::NoPlayersRemaining,
};
crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data(
@@ -385,8 +389,10 @@ impl EliminationLogic {
end_reason: rlnl::types::GameEndReason::OneTeamRemaining,
};
let winning_team_i32 = winning_team as i32;
for conn in generic.users.read().await.values() {
let event = if conn.descriptor.team == winning_team_i32 {
for (player_id, conn) in generic.users.read().await.iter() {
let user_info = generic.user_descriptor(*player_id).unwrap();
if user_info.descriptor.user_id.is_none() { continue; } // skip non-user players
let event = if user_info.descriptor.team == winning_team_i32 {
rlnl::event_code::NetworkEvent::GameWon
} else {
rlnl::event_code::NetworkEvent::GameLost
@@ -403,12 +409,12 @@ impl EliminationLogic {
#[async_trait::async_trait]
impl CustomGameLogic for EliminationLogic {
async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, player: &crate::matches::generic::UserConnection, _others: &[oj_rc_core::persist::user::PlayerDescriptor]) -> bool {
async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _conn: &crate::matches::generic::UserConnection, player: &crate::matches::generic::UserDescriptor) -> bool {
self.tracked.track_vehicle(&player.descriptor).await;
true
}
async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, player: &crate::matches::generic::UserConnection) -> bool {
async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, connection: &crate::matches::generic::UserConnection, player: &crate::matches::generic::UserDescriptor) -> bool {
if generic.is_game_done() {
return true;
}
@@ -422,14 +428,14 @@ impl CustomGameLogic for EliminationLogic {
log::info!("Team {} has won sudden death game {} because player {} left", winning_team, generic.game_guid(), player.descriptor.player_id);
self.send_win_info(generic, winning_team).await;
} else if self.tracked.alive_count().await.is_empty() {
self.on_last_player_gone(generic, player).await;
self.on_last_player_gone(generic, connection, player).await;
}
true
}
async fn on_vehicle_destroyed(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _killer: u8, victim: u8) -> bool {
if let Some(conn) = generic.users.read().await.get(&victim) {
self.tracked.destroy_vehicle(&conn.descriptor).await;
if let Some(victim_info) = generic.user_descriptor(victim) {
self.tracked.destroy_vehicle(&victim_info.descriptor).await;
let final_score = rlnl::events::ingame::SetFinalGameScore {
player_id: victim,
score: 42,
@@ -446,7 +452,10 @@ impl CustomGameLogic for EliminationLogic {
} else {
log::info!("Player {} has been destroyed in sudden death game {}", victim, generic.game_guid());
if self.tracked.alive_count().await.is_empty() {
self.on_last_player_gone(generic, conn).await;
if let Some(user_conn) = generic.users.read().await.get(&victim) {
self.on_last_player_gone(generic, user_conn, victim_info).await;
}
}
}
}
@@ -466,7 +475,7 @@ impl CustomGameLogic for EliminationLogic {
true
}
async fn extra_sync_events(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _player: &crate::matches::generic::UserConnection) -> Vec<crate::matches::RlnlPacket> {
async fn extra_sync_events(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> Vec<crate::matches::RlnlPacket> {
vec![
crate::matches::RlnlPacket {
event: rlnl::event_code::NetworkEvent::GameModeSettings,
@@ -487,8 +496,9 @@ impl CustomGameLogic for EliminationLogic {
async fn on_countdown_start(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, game_start: chrono::DateTime<chrono::Utc>) -> bool {
let read_lock = generic.users.read().await;
let mut senders = Vec::with_capacity(read_lock.len());
for conn in read_lock.values() {
senders.push((conn.connection.clone(), conn.state.clone()));
for (player_id, conn) in read_lock.iter() {
let state = generic.user_descriptor(*player_id).unwrap().state.clone();
senders.push((conn.connection.clone(), state));
}
drop(read_lock);
let game_end = game_start + generic.game_duration;
@@ -552,7 +562,7 @@ impl CustomGameLogic for EliminationLogic {
break;
}
}
let was_in_base = self.tracked.swap_is_in_base(motion.player_id, now_in_base).await;
let was_in_base = self.tracked.swap_is_in_base(motion.player_id, now_in_base);
if now_in_base != was_in_base {
if let Some(was_in_base) = was_in_base {
self.bases.on_exit(generic, was_in_base, player_team == was_in_base, motion.player_id).await;
@@ -572,7 +582,3 @@ impl CustomGameLogic for EliminationLogic {
true
}
}
// spawn points (best guess)
// Mars 1: (16, 0, 19) and (355, 7, 372)
// Earth vanguard 2: (-248, 10, -251) and (267, 10, 258)

View File

@@ -5,11 +5,11 @@ pub struct NoOpLogic;
#[async_trait::async_trait]
impl CustomGameLogic for NoOpLogic {
async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _player: &crate::matches::generic::UserConnection, _others: &[oj_rc_core::persist::user::PlayerDescriptor]) -> bool {
async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> bool {
true
}
async fn on_player_end(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _player: &crate::matches::generic::UserConnection) -> bool {
async fn on_player_end(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> bool {
true
}
@@ -25,7 +25,7 @@ impl CustomGameLogic for NoOpLogic {
true
}
async fn extra_sync_events(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _player: &crate::matches::generic::UserConnection) -> Vec<crate::matches::RlnlPacket> {
async fn extra_sync_events(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> Vec<crate::matches::RlnlPacket> {
Vec::default()
}

View File

@@ -23,18 +23,18 @@ impl WinTracker {
end_reason: rlnl::types::GameEndReason::PitMaxKillsAchieved,
};
let winning_team_i32 = winning_team as i32;
for conn in generic.users.read().await.values() {
let event = if conn.descriptor.team == winning_team_i32 {
for (player_id, player_info) in generic.user_descriptors().iter() {
let event = if player_info.descriptor.team == winning_team_i32 {
rlnl::event_code::NetworkEvent::GameWon
} else {
rlnl::event_code::NetworkEvent::GameLost
};
crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data(
&data,
generic.send_to_player(
*player_id,
event,
literustlib::packet::Property::ReliableOrdered,
&conn.connection.connection
).await);
&data,
).await;
}
}
@@ -45,7 +45,7 @@ impl WinTracker {
for (player_id, streak) in game.player_tracking.streaks.iter() {
let player_streak = streak.load(std::sync::atomic::Ordering::Relaxed);
if player_streak >= *streak_threshold {
if let Some(conn) = generic.users.read().await.get(player_id) {
if let Some(conn) = generic.user_descriptor(*player_id) {
log::info!("Player {} has reached the streak win condition in game {}", player_id, generic.game_guid());
Self::do_win(generic, game, conn.descriptor.team as u8).await;
break;
@@ -56,28 +56,28 @@ impl WinTracker {
}
},
oj_rc_core::persist::config::PitWinCondition::TotalKills(kills_threshold) => {
for (player_id, conn) in generic.users.read().await.iter() {
if conn.counters.kills.load(std::sync::atomic::Ordering::Relaxed) >= *kills_threshold {
for (player_id, player_info) in generic.user_descriptors() {
if player_info.counters.kills.load(std::sync::atomic::Ordering::Relaxed) >= *kills_threshold {
log::info!("Player {} has reached the total kills win condition in game {}", player_id, generic.game_guid());
Self::do_win(generic, game, conn.descriptor.team as u8).await;
Self::do_win(generic, game, player_info.descriptor.team as u8).await;
break;
}
}
},
oj_rc_core::persist::config::PitWinCondition::Score(score_threshold) => {
for (player_id, conn) in generic.users.read().await.iter() {
if conn.counters.generic_score() >= *score_threshold {
for (player_id, player_info) in generic.user_descriptors() {
if player_info.counters.generic_score() >= *score_threshold {
log::info!("Player {} has reached the total score win condition in game {}", player_id, generic.game_guid());
Self::do_win(generic, game, conn.descriptor.team as u8).await;
Self::do_win(generic, game, player_info.descriptor.team as u8).await;
break;
}
}
},
oj_rc_core::persist::config::PitWinCondition::Damage(dmg_threshold) => {
for (player_id, conn) in generic.users.read().await.iter() {
if conn.counters.cubes.load(std::sync::atomic::Ordering::Relaxed) >= *dmg_threshold {
for (player_id, player_info) in generic.user_descriptors() {
if player_info.counters.cubes.load(std::sync::atomic::Ordering::Relaxed) >= *dmg_threshold {
log::info!("Player {} has reached the total damage win condition in game {}", player_id, generic.game_guid());
Self::do_win(generic, game, conn.descriptor.team as u8).await;
Self::do_win(generic, game, player_info.descriptor.team as u8).await;
break;
}
}
@@ -130,7 +130,7 @@ impl PlayerTracker {
}
}
fn pit_stats(&self, users: &std::collections::HashMap<u8, crate::matches::generic::UserConnection>) -> rlnl::events::ingame::PitModeState {
fn pit_stats(&self, users: &std::collections::HashMap<u8, crate::matches::generic::UserDescriptor>) -> rlnl::events::ingame::PitModeState {
let mut player_stats = Vec::with_capacity(self.streaks.len());
for (player_id, streak) in self.streaks.iter() {
if let Some(user) = users.get(player_id) {
@@ -188,7 +188,7 @@ impl PitLogic {
log::warn!("Pit game {} has no leader", generic.game_guid());
return;
};
let killer_score = if let Some(user) = generic.users.read().await.get(&killer) {
let killer_score = if let Some(user) = generic.user_descriptor(killer) {
user.counters.generic_score()
} else {
log::warn!("Player {} score not found", killer);
@@ -216,7 +216,7 @@ impl PitLogic {
}
async fn do_leaderboard_update(&self, generic: &crate::matches::GenericGamemodeEngine<Self>) {
let latest_stats = self.player_tracking.pit_stats(&*generic.users.read().await);
let latest_stats = self.player_tracking.pit_stats(generic.user_descriptors());
generic.broadcast(
rlnl::event_code::NetworkEvent::PitModeState,
literustlib::packet::Property::ReliableOrdered,
@@ -331,19 +331,20 @@ impl PitLogic {
#[async_trait::async_trait]
impl CustomGameLogic for PitLogic {
async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _player: &crate::matches::generic::UserConnection, _others: &[oj_rc_core::persist::user::PlayerDescriptor]) -> bool {
async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _conn: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> bool {
true
}
async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _player: &crate::matches::generic::UserConnection) -> bool {
async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> bool {
if generic.is_game_done() {
return true;
}
let read_lock = generic.users.read().await;
if read_lock.len() == 1 {
// nobody to play against, automatically end the game
let last_player = &read_lock[&0];
WinTracker::do_win(generic, self, last_player.descriptor.team as u8).await;
let player_id = read_lock.keys().next().unwrap();
let user_info = generic.user_descriptor(*player_id).unwrap();
WinTracker::do_win(generic, self, user_info.descriptor.team as u8).await;
}
true
}
@@ -361,9 +362,18 @@ impl CustomGameLogic for PitLogic {
}
async fn on_kill_bonus(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, killer: u8, victim: u8) -> bool {
if let Some(to_reward) = generic.users.read().await.get(&killer) {
if let Some(to_reward) = generic.user_descriptor(killer) {
to_reward.counters.kills.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
crate::events::log_lnl_send_failure(to_reward.connection.rlnl().send_data(
generic.send_to_player(
killer,
rlnl::event_code::NetworkEvent::ConfirmedKill,
literustlib::packet::Property::ReliableOrdered,
&rlnl::events::ingame::Kill {
killee_player_id: victim,
killer_player_id: killer,
},
).await;
/*crate::events::log_lnl_send_failure(to_reward.connection.rlnl().send_data(
&rlnl::events::ingame::Kill {
killee_player_id: victim,
killer_player_id: killer,
@@ -371,7 +381,7 @@ impl CustomGameLogic for PitLogic {
rlnl::event_code::NetworkEvent::ConfirmedKill,
literustlib::packet::Property::ReliableOrdered,
&to_reward.connection.connection
).await);
).await);*/
let data = to_reward.counters.get_generic_packet(killer, rlnl::types::IngameStatId::Kill, None);
generic.broadcast(
rlnl::event_code::NetworkEvent::UpdateGameStats,
@@ -384,7 +394,7 @@ impl CustomGameLogic for PitLogic {
false
}
async fn extra_sync_events(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _player: &crate::matches::generic::UserConnection) -> Vec<crate::matches::RlnlPacket> {
async fn extra_sync_events(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> Vec<crate::matches::RlnlPacket> {
let mut initial_spawn_packets = self.initial_spawns_to_packets();
initial_spawn_packets.push(
crate::matches::RlnlPacket {
@@ -403,8 +413,9 @@ impl CustomGameLogic for PitLogic {
let read_lock = generic.users.read().await;
let game_end = game_start + generic.game_duration;
let mut senders = Vec::with_capacity(read_lock.len());
for conn in read_lock.values() {
senders.push((conn.connection.clone(), conn.state.clone()));
for (player_id, conn) in read_lock.iter() {
let state = generic.user_descriptor(*player_id).unwrap().state.clone();
senders.push((conn.connection.clone(), state));
}
drop(read_lock);
let end_packets = if self.settings.wins.iter().any(|cond| matches!(cond, oj_rc_core::persist::config::PitWinCondition::Time)) {

View File

@@ -86,18 +86,18 @@ impl ScoreTracker {
end_reason: if was_sudden_death { rlnl::types::GameEndReason::TeamDeathMatchTimeExpiredSuddenDeath } else { rlnl::types::GameEndReason::TeamDeathMatchTimeExpiredMostKills },
};
let winning_team_i32 = winning_team as i32;
for conn in generic.users.read().await.values() {
let event = if conn.descriptor.team == winning_team_i32 {
for (player_id, player_info) in generic.user_descriptors().iter() {
let event = if player_info.descriptor.team == winning_team_i32 {
rlnl::event_code::NetworkEvent::GameWonBaseDestroyed
} else {
rlnl::event_code::NetworkEvent::GameLostBaseDestroyed
};
crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data(
&data,
generic.send_to_player(
*player_id,
event,
literustlib::packet::Property::ReliableOrdered,
&conn.connection.connection
).await);
&data,
).await;
}
} else {
self.is_sudden_death.store(true, std::sync::atomic::Ordering::Relaxed);
@@ -111,18 +111,18 @@ impl ScoreTracker {
end_reason: rlnl::types::GameEndReason::TeamDeathMatchMaxKillsAchieved,
};
let winning_team_i32 = winning_team as i32;
for conn in generic.users.read().await.values() {
let event = if conn.descriptor.team == winning_team_i32 {
for (player_id, player_info) in generic.user_descriptors().iter() {
let event = if player_info.descriptor.team == winning_team_i32 {
rlnl::event_code::NetworkEvent::GameWonBaseDestroyed
} else {
rlnl::event_code::NetworkEvent::GameLostBaseDestroyed
};
crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data(
&data,
generic.send_to_player(
*player_id,
event,
literustlib::packet::Property::ReliableOrdered,
&conn.connection.connection
).await);
&data,
).await;
}
}
@@ -173,7 +173,7 @@ impl PlayerTracker {
async fn single_remaining_team(generic: &crate::matches::GenericGamemodeEngine<TeamDeathMatchLogic>) -> Option<u8> {
let mut first_remaining_team = None;
for conn in generic.users.read().await.values() {
for conn in generic.user_descriptors().values() {
let mode = crate::matches::generic::ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed));
if matches!(mode, crate::matches::generic::ConnectionMode::InGame) {
if let Some(first_remaining_team) = first_remaining_team {
@@ -241,18 +241,19 @@ impl TeamDeathMatchLogic {
end_reason,
};
let winning_team_i32 = winning_team as i32;
for conn in generic.users.read().await.values() {
let event = if conn.descriptor.team == winning_team_i32 {
for (player_id, player_info) in generic.user_descriptors().iter() {
if player_info.descriptor.user_id.is_none() { continue; } // skip non-user players
let event = if player_info.descriptor.team == winning_team_i32 {
rlnl::event_code::NetworkEvent::GameWonBaseDestroyed
} else {
rlnl::event_code::NetworkEvent::GameLostBaseDestroyed
};
crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data(
&data,
generic.send_to_player(
*player_id,
event,
literustlib::packet::Property::ReliableOrdered,
&conn.connection.connection
).await);
&data
).await;
}
}
@@ -274,7 +275,7 @@ impl TeamDeathMatchLogic {
&respawn_payload,
true
).await;
if let Some(user) = generic.users.read().await.get(&player_id) {
if let Some(user) = generic.user_descriptor(player_id) {
let player_team = user.descriptor.team as u8;
let spawn_point = if let Some(team_spawns) = generic.map_config.spawns.get(&player_team) {
if team_spawns.is_empty() {
@@ -304,15 +305,16 @@ impl TeamDeathMatchLogic {
#[async_trait::async_trait]
impl CustomGameLogic for TeamDeathMatchLogic {
async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _player: &crate::matches::generic::UserConnection, _others: &[oj_rc_core::persist::user::PlayerDescriptor]) -> bool {
async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _conn: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> bool {
true
}
async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _player: &crate::matches::generic::UserConnection) -> bool {
async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> bool {
if generic.is_game_done() {
return true;
}
if let Some(winning_team) = PlayerTracker::single_remaining_team(generic).await {
log::info!("All players except those on team {} have disconnected, ending game {} early", winning_team, generic.game_guid());
self.do_win(WinReason::OutOfPlayers, generic, winning_team).await;
}
true
@@ -334,7 +336,7 @@ impl CustomGameLogic for TeamDeathMatchLogic {
true
}
async fn extra_sync_events(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _player: &crate::matches::generic::UserConnection) -> Vec<crate::matches::RlnlPacket> {
async fn extra_sync_events(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> Vec<crate::matches::RlnlPacket> {
vec![
crate::matches::RlnlPacket {
event: rlnl::event_code::NetworkEvent::GameModeSettings,
@@ -355,8 +357,9 @@ impl CustomGameLogic for TeamDeathMatchLogic {
let read_lock = generic.users.read().await;
let game_end = game_start + generic.game_duration;
let mut senders = Vec::with_capacity(read_lock.len());
for conn in read_lock.values() {
senders.push((conn.connection.clone(), conn.state.clone()));
for (player_id, conn) in read_lock.iter() {
let state = generic.user_descriptor(*player_id).unwrap().state.clone();
senders.push((conn.connection.clone(), state));
}
drop(read_lock);
/*let end_packets = vec![
@@ -427,7 +430,7 @@ impl CustomGameLogic for TeamDeathMatchLogic {
}
},
(rlnl::event_code::NetworkEvent::SurrenderVoteCast, literustlib::packet::Property::ReliableOrdered) => {
if let Some(player_id) = generic.user_key_by_user_id(user_id).await {
if let Some(player_id) = generic.user_key_by_user_id(user_id) {
if let Some(&team) = self.player_tracking.teams.get(&player_id) {
let maybe_vote = <dyn core::any::Any>::downcast_ref::<rlnl::events::ingame::SurrenderVoteCast>(data.as_ref());
if let Some(vote) = maybe_vote {