mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Add BA player disconnect win condition, make loading a bit more forgiving
This commit is contained in:
@@ -8,15 +8,27 @@ pub struct RlnlPacket {
|
||||
/// (true = yes, do default behaviour)
|
||||
#[async_trait::async_trait]
|
||||
pub trait CustomGameLogic: Sized + Send + Sync + 'static {
|
||||
/// Called when player joins the game server (after authentication).
|
||||
async fn on_player_join(&self, generic: &super::GenericGamemodeEngine<Self>, player: &crate::matches::generic::UserConnection, others: &[oj_rc_core::persist::user::PlayerDescriptor]) -> bool;
|
||||
/// Called when player leaves the game server (i.e. player disconnects).
|
||||
async fn on_player_end(&self, generic: &super::GenericGamemodeEngine<Self>, player: &crate::matches::generic::UserConnection) -> bool;
|
||||
/// Called when a player's vehicle is destroyed by another player.
|
||||
async fn on_vehicle_destroyed(&self, generic: &super::GenericGamemodeEngine<Self>, killer: u8, victim: u8) -> bool;
|
||||
/// Called when a player's vehicle self-destructs.
|
||||
async fn on_vehicle_self_destruct(&self, generic: &super::GenericGamemodeEngine<Self>, user: u8, is_classic: bool) -> bool;
|
||||
/// Called during loading, before the sync stage is entered.
|
||||
///
|
||||
/// Roughly, loading is as follows: `Loading -> WaitingForSync -> Sync -> WaitingToStart -> InGame`.
|
||||
async fn extra_sync_events(&self, generic: &super::GenericGamemodeEngine<Self>, player: &crate::matches::generic::UserConnection) -> Vec<RlnlPacket>;
|
||||
/// Called when loading completes and the countdown is starting
|
||||
async fn on_countdown_start(&self, generic: &super::GenericGamemodeEngine<Self>, game_start: chrono::DateTime<chrono::Utc>) -> bool;
|
||||
/// Called when the game is marked as complete
|
||||
async fn on_game_completed(&self, generic: &super::GenericGamemodeEngine<Self>) -> bool;
|
||||
/// Called when various network events are broadcast from one client but before they are sent to the rest of the clients
|
||||
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;
|
||||
/// Called when a vehicle motion event is received from a client
|
||||
async fn on_motion(&self, generic: &super::GenericGamemodeEngine<Self>, motion: &rlnl::machine_motion::MachineMotion, location: (f32, f32, f32)) -> bool;
|
||||
/// Called when a gamemode-specific network event is received from a client
|
||||
async fn on_custom(&self, generic: &super::GenericGamemodeEngine<Self>, user_id: i32, event: rlnl::event_code::NetworkEvent, property: literustlib::packet::Property, data: Box<dyn crate::Broadcastable>);
|
||||
}
|
||||
|
||||
|
||||
@@ -390,6 +390,16 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
||||
super::GameMessage::RequestLeave { user_id } => {
|
||||
log::info!("User {} wants to leave game {}", user_id, self.game_guid());
|
||||
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;
|
||||
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,
|
||||
@@ -498,6 +508,11 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
||||
let mut ready_count = 0;
|
||||
for user in self.users.read().await.values() {
|
||||
if user.user.user_id() == user_id {
|
||||
if !matches!(ConnectionMode::from_u8(user.state.mode.load(std::sync::atomic::Ordering::Relaxed)), ConnectionMode::Loading | ConnectionMode::Disconnected) {
|
||||
log::warn!("Got RequestLoadingSync after user {} was already in/past WaitingForSync stage", user_id);
|
||||
continue;
|
||||
}
|
||||
log::info!("User {} is awaiting sync", user_id);
|
||||
user.state.mode.store(ConnectionMode::WaitingForSync.to_u8(), std::sync::atomic::Ordering::Relaxed);
|
||||
ready_count += 1;
|
||||
} else {
|
||||
@@ -520,9 +535,11 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
||||
if let Some(conn) = self.users.read().await.get(&user_key) {
|
||||
log::info!("Loading complete for game {}, user {} (player {})", self.game_guid(), user_id, user_key);
|
||||
conn.state.progress.store(100, std::sync::atomic::Ordering::Relaxed);
|
||||
if matches!(ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed)), ConnectionMode::Sync) {
|
||||
conn.state.mode.store(ConnectionMode::WaitingToStart.to_u8(), std::sync::atomic::Ordering::Relaxed);
|
||||
let mode = ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed));
|
||||
if !matches!(mode, ConnectionMode::Sync) {
|
||||
log::warn!("Player {} completed loading but is in mode {:?} (should be Sync)", conn.descriptor.player_id, mode);
|
||||
}
|
||||
conn.state.mode.store(ConnectionMode::WaitingToStart.to_u8(), std::sync::atomic::Ordering::Relaxed);
|
||||
self.spawn_initial_ingame_events(conn, user_id);
|
||||
} else {
|
||||
log::warn!("Invalid LoadComplete user key {} for game {}", user_key, self.game_guid());
|
||||
@@ -800,7 +817,7 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
||||
}, literustlib::packet::Property::Unreliable, &conn.connection.connection).await);
|
||||
}
|
||||
}
|
||||
if self.game_start.load(std::sync::atomic::Ordering::Relaxed) == -1 {
|
||||
/*if self.game_start.load(std::sync::atomic::Ordering::Relaxed) == -1 {
|
||||
let mut all_users_loading_complete = true;
|
||||
for conn in self.users.read().await.values() {
|
||||
let mode = ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed));
|
||||
@@ -819,7 +836,7 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
} else {
|
||||
log::warn!("Received machine motion with unknown player id {} from user {}", motion.player_id, user_id);
|
||||
}
|
||||
@@ -1009,6 +1026,14 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
||||
literustlib::packet::Property::ReliableOrdered,
|
||||
&user.connection)
|
||||
.await?;*/
|
||||
|
||||
tokio::time::sleep(GenericGamemodeEngine::<super::modes::NoOpLogic>::END_OF_SYNC_DELAY).await;
|
||||
|
||||
sender.send_empty(
|
||||
rlnl::event_code::NetworkEvent::EndOfSync,
|
||||
literustlib::packet::Property::ReliableOrdered,
|
||||
&connection.connection
|
||||
).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,28 @@ impl PlayerTracker {
|
||||
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) {
|
||||
let mut conn_lock = self.connected.lock().await;
|
||||
for team_members in conn_lock.values_mut() {
|
||||
team_members.remove(&player_id);
|
||||
}
|
||||
}
|
||||
|
||||
async fn single_remaining_team(&self) -> Option<u8> {
|
||||
let conn_lock = self.connected.lock().await;
|
||||
let mut first_remaining_team = None;
|
||||
for (team, team_members) in conn_lock.iter() {
|
||||
if !team_members.is_empty() {
|
||||
if first_remaining_team.is_some() {
|
||||
return None;
|
||||
} else {
|
||||
first_remaining_team = Some(*team)
|
||||
}
|
||||
}
|
||||
}
|
||||
first_remaining_team
|
||||
}
|
||||
}
|
||||
|
||||
struct PointInfo {
|
||||
@@ -430,6 +452,7 @@ impl BaseInfo {
|
||||
enum WinMode {
|
||||
BaseFull,
|
||||
OutOfTime,
|
||||
OutOfPlayers,
|
||||
}
|
||||
|
||||
pub struct BattleArenaLogic {
|
||||
@@ -535,7 +558,8 @@ impl BattleArenaLogic {
|
||||
async fn do_win(&self, winning_team: u8, ty: WinMode, generic: &crate::matches::GenericGamemodeEngine<Self>) {
|
||||
generic.game_done();
|
||||
let end_reason = match ty {
|
||||
WinMode::BaseFull => rlnl::types::GameEndReason::BaseDestroyed,
|
||||
WinMode::BaseFull
|
||||
| WinMode::OutOfPlayers => rlnl::types::GameEndReason::BaseDestroyed,
|
||||
WinMode::OutOfTime => rlnl::types::GameEndReason::TimeOut,
|
||||
};
|
||||
let payload = rlnl::events::ingame::GameLoseWin {
|
||||
@@ -545,7 +569,8 @@ impl BattleArenaLogic {
|
||||
for player in generic.users.read().await.values() {
|
||||
let is_winner = player.descriptor.team == winning_team as i32;
|
||||
let net_event = match ty {
|
||||
WinMode::BaseFull => {
|
||||
WinMode::BaseFull
|
||||
| WinMode::OutOfPlayers => {
|
||||
if is_winner { rlnl::event_code::NetworkEvent::GameWonBaseDestroyed } else { rlnl::event_code::NetworkEvent::GameLostBaseDestroyed }
|
||||
},
|
||||
WinMode::OutOfTime => {
|
||||
@@ -563,6 +588,15 @@ impl BattleArenaLogic {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@@ -573,22 +607,25 @@ impl CustomGameLogic for BattleArenaLogic {
|
||||
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>, player: &crate::matches::generic::UserConnection) -> bool {
|
||||
let player_id = player.descriptor.player_id;
|
||||
self.do_destruct_tasks(generic, player_id).await;
|
||||
self.player_tracking.disconnect_player(player_id).await;
|
||||
if let Some(winning_team) = self.player_tracking.single_remaining_team().await {
|
||||
self.do_win(winning_team, WinMode::OutOfPlayers, generic).await;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
async fn on_vehicle_destroyed(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _killer: u8, victim: u8) -> bool {
|
||||
if let Some(player_team) = self.player_tracking.team(victim).await {
|
||||
let was_in_point = self.player_tracking.swap_is_in_point(victim, None).await;
|
||||
if let Some(was_in_point) = was_in_point {
|
||||
self.capture_tracking.on_exit(generic, was_in_point, victim, player_team as i8, self.config.num_segments as f32).await;
|
||||
}
|
||||
}
|
||||
self.do_destruct_tasks(generic, victim).await;
|
||||
// TODO handle respawn
|
||||
true
|
||||
}
|
||||
|
||||
async fn on_vehicle_self_destruct(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _user: u8, _is_classic: bool) -> bool {
|
||||
async fn on_vehicle_self_destruct(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, user: u8, _is_classic: bool) -> bool {
|
||||
self.do_destruct_tasks(generic, user).await;
|
||||
// TODO handle respawn
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user