2025-08-30 21:36:01 -04:00
use crate ::matches ::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)
}
impl PlayerTracker {
fn new ( ) -> 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 ( ) ) ,
}
}
async fn team ( & self , player_id : u8 ) -> Option < u8 > {
for ( team , players ) in self . connected . lock ( ) . await . iter ( ) {
if players . contains ( & player_id ) {
return Some ( * team ) ;
}
}
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 | {
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
} else {
Some ( old_point as u8 )
}
} )
}
async fn track_player ( & self , player : & oj_rc_core ::persist ::user ::PlayerDescriptor ) {
let mut conn_lock = self . connected . lock ( ) . await ;
if let Some ( team ) = conn_lock . get_mut ( & ( player . team as u8 ) ) {
team . insert ( player . player_id ) ;
} else {
let mut new_team = std ::collections ::HashSet ::new ( ) ;
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 ) ) ;
}
2025-08-31 12:12:43 -04:00
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
}
2025-08-30 21:36:01 -04:00
}
struct PointInfo {
team : std ::sync ::atomic ::AtomicI8 ,
on_point : tokio ::sync ::RwLock < std ::collections ::HashMap < u8 , std ::sync ::atomic ::AtomicU8 > > ,
capture : atomic_float ::AtomicF32 ,
percent_per_second : f32 ,
}
impl PointInfo {
fn new ( percent_per_second : f32 ) -> Self {
Self {
team : std ::sync ::atomic ::AtomicI8 ::new ( - 1 ) ,
on_point : tokio ::sync ::RwLock ::new ( [
( 0 , std ::sync ::atomic ::AtomicU8 ::new ( 0 ) ) ,
( 1 , std ::sync ::atomic ::AtomicU8 ::new ( 0 ) ) ,
] . into_iter ( ) . collect ( ) ) ,
capture : atomic_float ::AtomicF32 ::new ( 0.0 ) ,
percent_per_second ,
}
}
/* async fn friendlies_on_point(&self, team: u8) -> u8 {
if let Some ( counter ) = self . on_point . read ( ) . await . get ( & team ) {
counter . load ( std ::sync ::atomic ::Ordering ::SeqCst )
} else {
0
}
} * /
async fn enemies_on_point ( & self , team : u8 ) -> u8 {
let mut total = 0 ;
for ( iter_team , counter ) in self . on_point . read ( ) . await . iter ( ) {
if team = = * iter_team { continue ; }
total + = counter . load ( std ::sync ::atomic ::Ordering ::SeqCst ) ;
}
total
}
async fn owners_on_point ( & self ) -> u8 {
let team = self . team . load ( std ::sync ::atomic ::Ordering ::SeqCst ) ;
if team < 0 {
0
2025-09-03 22:33:26 -04:00
} else if let Some ( counter ) = self . on_point . read ( ) . await . get ( & ( team as u8 ) ) {
counter . load ( std ::sync ::atomic ::Ordering ::SeqCst )
2025-08-30 21:36:01 -04:00
} else {
2025-09-03 22:33:26 -04:00
0
2025-08-30 21:36:01 -04:00
}
}
async fn stealers_on_point ( & self ) -> u8 {
let owning_team = self . team . load ( std ::sync ::atomic ::Ordering ::SeqCst ) ;
let mut total = 0 ;
if owning_team < 0 {
for counter in self . on_point . read ( ) . await . values ( ) {
total + = counter . load ( std ::sync ::atomic ::Ordering ::SeqCst ) ;
}
} else {
for ( team , counter ) in self . on_point . read ( ) . await . iter ( ) {
if ( owning_team as u8 ) = = * team { continue ; }
total + = counter . load ( std ::sync ::atomic ::Ordering ::SeqCst ) ;
}
}
total
}
async fn stealers_team ( & self ) -> Option < u8 > {
let owning_team = self . team . load ( std ::sync ::atomic ::Ordering ::SeqCst ) ;
let mut stealing_team = None ;
if owning_team < 0 {
for ( team , counter ) in self . on_point . read ( ) . await . iter ( ) {
let count = counter . load ( std ::sync ::atomic ::Ordering ::SeqCst ) ;
if count ! = 0 {
if stealing_team . is_some ( ) {
return None ;
} else {
stealing_team = Some ( * team ) ;
}
}
}
} else {
let owning_team = owning_team as u8 ;
for ( team , counter ) in self . on_point . read ( ) . await . iter ( ) {
if owning_team = = * team { continue ; }
let count = counter . load ( std ::sync ::atomic ::Ordering ::SeqCst ) ;
if count ! = 0 {
if stealing_team . is_some ( ) {
return None ;
} else {
stealing_team = Some ( * team ) ;
}
}
}
}
stealing_team
}
}
struct PointTracker {
points : Vec < PointInfo > ,
last_tick : std ::sync ::atomic ::AtomicI64 ,
}
struct PointTickInfo {
owned : std ::collections ::HashMap < u8 , u8 > , // team -> capture point count
captured_firsts : std ::collections ::HashSet < u8 > , // team
lost_lasts : std ::collections ::HashSet < u8 > , // team
delta : i64 ,
}
impl PointTracker {
const TICK_MS : i64 = 50 ;
fn new ( points : impl Iterator < Item = f32 > ) -> Self {
Self {
points : points . map ( PointInfo ::new ) . collect ( ) ,
last_tick : std ::sync ::atomic ::AtomicI64 ::new ( i64 ::MIN ) ,
}
}
async fn on_enter ( & self , generic : & crate ::matches ::GenericGamemodeEngine < BattleArenaLogic > , point_i : u8 , _player_id : u8 , player_team : i8 ) {
if player_team < 0 {
return ;
}
let player_team_u8 = player_team as u8 ;
if let Some ( point ) = self . points . get ( point_i as usize ) {
let point_team = point . team . load ( std ::sync ::atomic ::Ordering ::SeqCst ) ;
if ! point . on_point . read ( ) . await . contains_key ( & player_team_u8 ) {
point . on_point . write ( ) . await . insert ( player_team_u8 , std ::sync ::atomic ::AtomicU8 ::new ( 0 ) ) ;
}
if point_team = = player_team {
let old_friendlies = point . on_point . read ( ) . await [ & player_team_u8 ] . fetch_add ( 1 , std ::sync ::atomic ::Ordering ::SeqCst ) ;
let current_enemies = point . enemies_on_point ( player_team_u8 ) . await ;
if current_enemies ! = 0 & & old_friendlies = = 0 {
generic . broadcast (
rlnl ::event_code ::NetworkEvent ::CapturePointNotification ,
literustlib ::packet ::Property ::ReliableOrdered ,
& rlnl ::events ::ingame ::CapturePointNotification {
notification : rlnl ::types ::CapturePointNotificationType ::CaptureLocked ,
id : point_i ,
defending_team : point_team ,
2025-09-03 22:33:26 -04:00
attacking_team : player_team ,
2025-08-30 21:36:01 -04:00
} ,
true ,
) . await ;
}
} else {
let old_enemies = point . on_point . read ( ) . await [ & player_team_u8 ] . fetch_add ( 1 , std ::sync ::atomic ::Ordering ::SeqCst ) ;
let current_contesters = point . enemies_on_point ( player_team_u8 ) . await ;
if old_enemies = = 0 {
generic . broadcast (
rlnl ::event_code ::NetworkEvent ::CapturePointNotification ,
literustlib ::packet ::Property ::ReliableOrdered ,
& rlnl ::events ::ingame ::CapturePointNotification {
notification : rlnl ::types ::CapturePointNotificationType ::CaptureStarted ,
id : point_i ,
defending_team : point_team ,
2025-09-03 22:33:26 -04:00
attacking_team : player_team ,
2025-08-30 21:36:01 -04:00
} ,
true ,
) . await ;
if current_contesters ! = 0 {
generic . broadcast (
rlnl ::event_code ::NetworkEvent ::CapturePointNotification ,
literustlib ::packet ::Property ::ReliableOrdered ,
& rlnl ::events ::ingame ::CapturePointNotification {
notification : rlnl ::types ::CapturePointNotificationType ::CaptureLocked ,
id : point_i ,
defending_team : point_team ,
2025-09-03 22:33:26 -04:00
attacking_team : player_team ,
2025-08-30 21:36:01 -04:00
} ,
true ,
) . await ;
}
}
}
}
}
async fn on_exit ( & self , generic : & crate ::matches ::GenericGamemodeEngine < BattleArenaLogic > , point_i : u8 , _player_id : u8 , player_team : i8 , max_progress : f32 ) {
if player_team < 0 {
return ;
}
let player_team_u8 = player_team as u8 ;
if let Some ( point ) = self . points . get ( point_i as usize ) {
let point_team = point . team . load ( std ::sync ::atomic ::Ordering ::SeqCst ) ;
if ! point . on_point . read ( ) . await . contains_key ( & player_team_u8 ) {
point . on_point . write ( ) . await . insert ( player_team_u8 , std ::sync ::atomic ::AtomicU8 ::new ( 0 ) ) ;
}
if point_team = = player_team {
let old_friendlies = point . on_point . read ( ) . await [ & player_team_u8 ] . fetch_sub ( 1 , std ::sync ::atomic ::Ordering ::SeqCst ) ;
let current_enemies = point . enemies_on_point ( player_team_u8 ) . await ;
if old_friendlies = = 0 {
// something is out of sync, let's just ignore it and try to undo any underflow
2025-09-03 18:38:56 -04:00
log ::warn! ( " Team {} players on point {} counting error " , player_team , point_i ) ;
2025-08-30 21:36:01 -04:00
point . on_point . read ( ) . await [ & player_team_u8 ] . store ( 0 , std ::sync ::atomic ::Ordering ::SeqCst ) ;
2025-09-03 22:33:26 -04:00
} else if old_friendlies = = 1 & & current_enemies ! = 0 {
generic . broadcast (
rlnl ::event_code ::NetworkEvent ::CapturePointNotification ,
literustlib ::packet ::Property ::ReliableOrdered ,
& rlnl ::events ::ingame ::CapturePointNotification {
notification : rlnl ::types ::CapturePointNotificationType ::CaptureUnlocked ,
id : point_i ,
defending_team : point_team ,
attacking_team : player_team ,
} ,
true ,
) . await ;
2025-08-30 21:36:01 -04:00
}
} else {
let old_enemies = point . on_point . read ( ) . await [ & player_team_u8 ] . fetch_sub ( 1 , std ::sync ::atomic ::Ordering ::SeqCst ) ;
//let current_friendlies = point.friendlies.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
if old_enemies = = 0 {
// something is out of sync, let's just ignore it and try to undo any underflow
2025-09-03 18:38:56 -04:00
log ::warn! ( " Team {} players on point {} counting error " , player_team , point_i ) ;
2025-08-30 21:36:01 -04:00
point . on_point . read ( ) . await [ & player_team_u8 ] . store ( 0 , std ::sync ::atomic ::Ordering ::SeqCst ) ;
2025-09-03 22:33:26 -04:00
} else if old_enemies = = 1 {
//log::info!("Enemy has left the capture point");
generic . broadcast (
rlnl ::event_code ::NetworkEvent ::CapturePointNotification ,
literustlib ::packet ::Property ::ReliableOrdered ,
& rlnl ::events ::ingame ::CapturePointNotification {
notification : rlnl ::types ::CapturePointNotificationType ::CaptureStoppedNoAttackers ,
id : point_i ,
defending_team : point_team ,
attacking_team : player_team ,
} ,
true ,
) . await ;
let progress_now = point . capture . load ( std ::sync ::atomic ::Ordering ::SeqCst ) . floor ( ) ;
point . capture . store ( progress_now , std ::sync ::atomic ::Ordering ::SeqCst ) ;
let data = rlnl ::events ::ingame ::TeamBaseState {
base_team_or_mining_point_index : point_i ,
current_progress : rlnl ::types ::ByteFloat ::from ( progress_now ) ,
max_progress : rlnl ::types ::ByteFloat ::from ( max_progress ) ,
} ;
generic . broadcast (
rlnl ::event_code ::NetworkEvent ::CapturePointProgress ,
literustlib ::packet ::Property ::ReliableOrdered ,
& data ,
true
) . await ;
2025-08-30 21:36:01 -04:00
}
}
}
}
async fn tick ( & self , generic : & crate ::matches ::GenericGamemodeEngine < BattleArenaLogic > , max_progress : f32 ) -> Option < PointTickInfo > {
let now = chrono ::Utc ::now ( ) . timestamp_millis ( ) ;
let last_tick = self . last_tick . load ( std ::sync ::atomic ::Ordering ::SeqCst ) ;
let delta = if last_tick = = i64 ::MIN {
// first tick
self . last_tick . store ( now , std ::sync ::atomic ::Ordering ::SeqCst ) ;
1
} else {
let delta = ( now - last_tick ) / Self ::TICK_MS ;
if delta = = 0 { return None ; }
self . last_tick . store ( last_tick + ( delta * Self ::TICK_MS ) , std ::sync ::atomic ::Ordering ::SeqCst ) ;
delta
} ;
let mut owned_points = std ::collections ::HashMap ::with_capacity ( 2 ) ;
let mut captured_firsts = std ::collections ::HashSet ::new ( ) ;
let mut lost_lasts = std ::collections ::HashSet ::new ( ) ;
for ( i , cap_point ) in self . points . iter ( ) . enumerate ( ) {
let point_owner = cap_point . team . load ( std ::sync ::atomic ::Ordering ::SeqCst ) ;
if point_owner > = 0 {
let point_owner = point_owner as u8 ;
if let Some ( count ) = owned_points . get_mut ( & point_owner ) {
* count + = 1 ;
} else {
owned_points . insert ( point_owner , 1 ) ;
}
}
let friendlies = cap_point . owners_on_point ( ) . await ;
let enemies = cap_point . stealers_on_point ( ) . await ;
if friendlies ! = 0 { continue ; }
if enemies = = 0 { continue ; }
let stealing_team = cap_point . stealers_team ( ) . await ;
if stealing_team . is_none ( ) { continue ; }
let stealing_team = stealing_team . unwrap ( ) ;
2025-09-03 18:38:56 -04:00
//log::info!("Team {} is capturing point {} (# of players: {})", stealing_team, i, enemies);
2025-08-30 21:36:01 -04:00
let to_add = ( delta as f32 ) * ( Self ::TICK_MS as f32 ) * cap_point . percent_per_second * max_progress / ( 100.0 * 1000.0 ) ;
let pre_add = cap_point . capture . fetch_add ( to_add , std ::sync ::atomic ::Ordering ::SeqCst ) ;
let post_add = pre_add + to_add ;
if post_add > = max_progress {
let new_team = stealing_team as i8 ;
log ::info! ( " Point {} was captured by team {} in game {} " , i , new_team , generic . game_guid ( ) ) ;
cap_point . capture . store ( 0.0 , std ::sync ::atomic ::Ordering ::SeqCst ) ;
cap_point . team . store ( new_team , std ::sync ::atomic ::Ordering ::SeqCst ) ;
2025-09-03 22:33:26 -04:00
if owned_points . get ( & ( new_team as u8 ) ) . copied ( ) . unwrap_or ( 0 ) = = 0 {
2025-08-30 21:36:01 -04:00
captured_firsts . insert ( new_team as u8 ) ;
}
if point_owner > = 0 & & * owned_points . get ( & ( point_owner as u8 ) ) . unwrap ( ) = = 1 {
lost_lasts . insert ( point_owner as u8 ) ;
}
generic . broadcast (
rlnl ::event_code ::NetworkEvent ::CapturePointNotification ,
literustlib ::packet ::Property ::ReliableOrdered ,
& rlnl ::events ::ingame ::CapturePointNotification {
notification : rlnl ::types ::CapturePointNotificationType ::CaptureCompleted ,
id : i as u8 ,
defending_team : point_owner ,
attacking_team : new_team ,
} ,
true ,
) . await ;
}
let progress_now = cap_point . capture . load ( std ::sync ::atomic ::Ordering ::SeqCst ) ;
let data = rlnl ::events ::ingame ::TeamBaseState {
base_team_or_mining_point_index : i as u8 ,
current_progress : rlnl ::types ::ByteFloat ::from ( progress_now ) ,
max_progress : rlnl ::types ::ByteFloat ::from ( max_progress ) ,
} ;
generic . broadcast (
rlnl ::event_code ::NetworkEvent ::CapturePointProgress ,
literustlib ::packet ::Property ::ReliableOrdered ,
& data ,
true
) . await ;
}
Some ( PointTickInfo {
owned : owned_points ,
captured_firsts ,
lost_lasts ,
delta ,
} )
}
}
struct BaseTracker {
bases : std ::collections ::HashMap < u8 , BaseInfo > ,
}
impl BaseTracker {
fn new < ' a > ( bases_iter : impl std ::iter ::Iterator < Item = & ' a u8 > , crystals : & [ oj_rc_core ::cubes ::CubeLocationInfo ] ) -> Self {
let mut bases = std ::collections ::HashMap ::new ( ) ;
for base_id in bases_iter {
bases . insert ( * base_id , BaseInfo ::new ( crystals ) ) ;
}
Self {
bases ,
}
}
}
struct BaseInfo {
cube_index : atomic_float ::AtomicF32 ,
crystals_healths : Vec < std ::sync ::atomic ::AtomicU8 > ,
}
impl BaseInfo {
fn new ( crystals : & [ oj_rc_core ::cubes ::CubeLocationInfo ] ) -> Self {
Self {
cube_index : atomic_float ::AtomicF32 ::new ( 0.0 ) ,
crystals_healths : ( 0 .. crystals . len ( ) )
. map ( | _ | std ::sync ::atomic ::AtomicU8 ::new ( 0 ) )
. collect ( )
}
}
#[ inline ]
fn calculate_crystal_health ( & self , i : usize , max_health : u32 ) -> u32 {
( ( ( self . crystals_healths [ i ] . load ( std ::sync ::atomic ::Ordering ::Relaxed ) as f32 ) / ( u8 ::MAX as f32 ) )
* ( max_health as f32 ) ) . ceil ( ) as u32
}
fn first_damaged ( & self , old_index : usize , max_health : u32 ) -> Option < usize > {
for i in 0 .. old_index {
let health = self . calculate_crystal_health ( i , max_health ) ;
if health ! = 0 & & health ! = max_health {
return Some ( i ) ;
}
}
None
}
}
enum WinMode {
BaseFull ,
OutOfTime ,
2025-08-31 12:12:43 -04:00
OutOfPlayers ,
2025-08-30 21:36:01 -04:00
}
pub struct BattleArenaLogic {
game_duration : std ::time ::Duration ,
game_end : std ::sync ::atomic ::AtomicI64 ,
respawn_full_heal_duration : f32 ,
respawn_heal_duration : f32 ,
timer_task : tokio ::sync ::Mutex < Option < tokio ::task ::JoinHandle < ( ) > > > ,
player_tracking : PlayerTracker ,
capture_tracking : PointTracker ,
base_tracking : BaseTracker ,
//cube_parser: std::sync::Arc<oj_rc_core::cubes::CubeLocationsParser>,
crystals : Vec < oj_rc_core ::cubes ::CubeLocationInfo > ,
config : oj_rc_core ::data ::battle_arena_config ::BattleArenaData ,
}
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 {
let dur = std ::time ::Duration ::from_secs ( ( config . game_time_minutes as u64 ) * 60 ) ;
let fake_end = ( chrono ::Utc ::now ( ) + dur ) . timestamp ( ) ;
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 {
game_duration : dur ,
respawn_full_heal_duration : config . respawn_full_heal_duration ,
respawn_heal_duration : config . respawn_heal_duration ,
game_end : std ::sync ::atomic ::AtomicI64 ::new ( fake_end ) ,
timer_task : tokio ::sync ::Mutex ::new ( None ) ,
player_tracking : PlayerTracker ::new ( ) ,
capture_tracking : PointTracker ::new ( map . capture_points . iter ( ) . map ( | ( _ , speed ) | * speed ) ) ,
base_tracking : BaseTracker ::new ( map . bases . keys ( ) , & crystals ) ,
//cube_parser,
//ba_base: teambase,
//ba_equalizer: equalizer,
crystals ,
config : ba_config ,
}
}
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 battle arena match timer task " ) ;
}
* lock = None ;
}
fn sphere_to_capture_point ( sphere : & oj_rc_core ::persist ::config ::Sphere , max_progress : f32 ) -> rlnl ::types ::CapturePoint {
rlnl ::types ::CapturePoint {
pos : rlnl ::types ::PosQuatPair {
pos : ( sphere . center . x , sphere . center . y , sphere . center . z ) . into ( ) ,
rot : ( 0.0 , 0.0 , 0.0 , 0.0 ) . into ( ) ,
} ,
team : - 1 ,
progress : 0. 0. into ( ) ,
max_progress : max_progress . into ( ) ,
}
}
fn default_capture_point ( max_progress : f32 ) -> rlnl ::types ::CapturePoint {
rlnl ::types ::CapturePoint {
pos : rlnl ::types ::PosQuatPair {
pos : ( 0.0 , 0.0 , 0.0 ) . into ( ) ,
rot : ( 0.0 , 0.0 , 0.0 , 0.0 ) . into ( ) ,
} ,
team : - 1 ,
progress : 0. 0. into ( ) ,
max_progress : max_progress . into ( ) ,
}
}
async fn check_if_match_time_is_done ( & self , generic : & crate ::matches ::GenericGamemodeEngine < Self > ) -> bool {
if self . game_end . load ( std ::sync ::atomic ::Ordering ::Relaxed ) < = chrono ::Utc ::now ( ) . timestamp ( ) {
// find winning team
let mut winning_team = None ;
for ( base , tracking ) in self . base_tracking . bases . iter ( ) {
if let Some ( ( _ , winning_score ) ) = winning_team {
let score = tracking . cube_index . load ( std ::sync ::atomic ::Ordering ::SeqCst ) ;
if score > winning_score {
winning_team = Some ( ( * base , score ) ) ;
}
} else {
winning_team = Some ( ( * base , tracking . cube_index . load ( std ::sync ::atomic ::Ordering ::SeqCst ) ) ) ;
}
}
let winners = if let Some ( ( winning_team , _ ) ) = winning_team {
winning_team
} else {
u8 ::MAX
} ;
// game is done, hooray
self . do_win ( winners , WinMode ::OutOfTime , generic ) . await ;
true
} else {
false
}
}
async fn do_win ( & self , winning_team : u8 , ty : WinMode , generic : & crate ::matches ::GenericGamemodeEngine < Self > ) {
generic . game_done ( ) ;
let end_reason = match ty {
2025-08-31 12:12:43 -04:00
WinMode ::BaseFull
| WinMode ::OutOfPlayers = > rlnl ::types ::GameEndReason ::BaseDestroyed ,
2025-08-30 21:36:01 -04:00
WinMode ::OutOfTime = > rlnl ::types ::GameEndReason ::TimeOut ,
} ;
let payload = rlnl ::events ::ingame ::GameLoseWin {
winning_team ,
end_reason ,
} ;
for player in generic . users . read ( ) . await . values ( ) {
let is_winner = player . descriptor . team = = winning_team as i32 ;
let net_event = match ty {
2025-08-31 12:12:43 -04:00
WinMode ::BaseFull
| WinMode ::OutOfPlayers = > {
2025-08-30 21:36:01 -04:00
if is_winner { rlnl ::event_code ::NetworkEvent ::GameWonBaseDestroyed } else { rlnl ::event_code ::NetworkEvent ::GameLostBaseDestroyed }
} ,
WinMode ::OutOfTime = > {
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
) ;
}
}
2025-08-31 12:12:43 -04:00
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 ;
}
}
}
2025-09-01 18:50:03 -04:00
async fn do_respawn_tasks ( & self , generic : & crate ::matches ::GenericGamemodeEngine < Self > , player_id : u8 ) {
log ::info! ( " Handling respawn player {} in game {} " , player_id , generic . game_guid ( ) ) ;
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 ) {
player_respawn . store ( respawn_timestamp . timestamp ( ) , std ::sync ::atomic ::Ordering ::Relaxed ) ;
}
let respawn_payload = rlnl ::events ::ingame ::RespawnTime {
owner : player_id ,
waiting_time : self . config . respawn_time_seconds as i16 ,
} ;
generic . broadcast (
rlnl ::event_code ::NetworkEvent ::SetRespawnWaitingTime ,
literustlib ::packet ::Property ::ReliableOrdered ,
& respawn_payload ,
true
) . await ;
if let Some ( player_team ) = self . player_tracking . team ( player_id ) . await {
let spawn_point = if let Some ( team_spawns ) = generic . map_config . spawns . get ( & player_team ) {
if team_spawns . is_empty ( ) {
oj_rc_core ::persist ::config ::Point {
x : 10.0 * ( player_id as f32 ) ,
y : 100.0 ,
z : 10.0 * ( player_team as f32 ) + 10.0 ,
}
} else {
let index = ( player_id as usize ) % team_spawns . len ( ) ;
team_spawns [ index ] . clone ( )
}
} else {
oj_rc_core ::persist ::config ::Point {
x : 10.0 * ( player_id as f32 ) ,
y : 100.0 ,
z : 10.0 * ( player_team as f32 ) + 10.0 ,
}
} ;
let connections = generic . users . read ( ) . await . values ( ) . map ( | player_info | player_info . connection . clone ( ) ) . collect ( ) ;
tokio ::task ::spawn ( Self ::respawn_player_after ( respawn_timestamp , connections , spawn_point , player_id ) ) ;
} else {
log ::error! ( " Player {} cannot respawn because they are not in a team!? " , player_id ) ;
}
}
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 ) {
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 ;
let spawn_payload = rlnl ::events ::sync ::SpawnPoint {
pos : rlnl ::types ::PosQuatPair {
pos : rlnl ::types ::CompressedVec3 ::from ( ( spawn . x , spawn . y , spawn . z ) ) ,
rot : rlnl ::types ::CompressedQuat { x : 0 , y : 0 , z : 0 } ,
} ,
owner : player_id ,
} ;
log ::debug! ( " Respawning player {} after {}ms " , player_id , sleep_dur . as_millis ( ) ) ;
for player in players {
if ! player . connection . is_connected ( ) { continue ; }
crate ::events ::log_lnl_send_failure ( player . rlnl ( ) . send_data (
& spawn_payload ,
rlnl ::event_code ::NetworkEvent ::FreeRespawnPoint ,
literustlib ::packet ::Property ::ReliableOrdered ,
& player . connection ,
) . await ) ;
}
}
2025-08-30 21:36:01 -04:00
}
#[ 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 {
log ::info! ( " Player {} joined " , player . descriptor . player_id ) ;
self . player_tracking . track_player ( & player . descriptor ) . await ;
true
}
2025-08-31 12:12:43 -04:00
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 ;
}
2025-08-30 21:36:01 -04:00
true
}
async fn on_vehicle_destroyed ( & self , generic : & crate ::matches ::GenericGamemodeEngine < Self > , _killer : u8 , victim : u8 ) -> bool {
2025-08-31 12:12:43 -04:00
self . do_destruct_tasks ( generic , victim ) . await ;
2025-09-01 18:50:03 -04:00
self . do_respawn_tasks ( generic , victim ) . await ;
2025-08-30 21:36:01 -04:00
true
}
2025-08-31 12:12:43 -04:00
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 ;
2025-09-01 18:50:03 -04:00
self . do_respawn_tasks ( generic , user ) . await ;
2025-08-30 21:36:01 -04:00
true
}
async fn extra_sync_events ( & self , generic : & crate ::matches ::GenericGamemodeEngine < Self > , _player : & crate ::matches ::generic ::UserConnection ) -> Vec < crate ::matches ::RlnlPacket > {
vec! [
Some ( crate ::matches ::RlnlPacket {
event : rlnl ::event_code ::NetworkEvent ::GameModeSettings ,
property : literustlib ::packet ::Property ::ReliableOrdered ,
data : Box ::new ( rlnl ::events ::sync ::UpdateGameModeSettings {
respawn_heal_duration : self . respawn_heal_duration ,
respawn_full_heal_duration : self . respawn_full_heal_duration ,
} ) ,
} ) ,
// TeamBase
if generic . map_config . bases . is_empty ( ) {
None
} else {
Some ( crate ::matches ::RlnlPacket {
event : rlnl ::event_code ::NetworkEvent ::TeamBase ,
property : literustlib ::packet ::Property ::ReliableOrdered ,
data : Box ::new ( rlnl ::events ::sync ::GetTeamBase {
base_1 : rlnl ::types ::PosQuatPair {
pos : generic . map_config . bases . get ( & 0 ) . map ( | ( s , _ ) | ( s . center . x , s . center . y , s . center . z ) ) . unwrap_or ( ( 0.0 , 0.0 , 0.0 ) ) . into ( ) ,
rot : ( 0.0 , 0.0 , 0.0 , 0.0 ) . into ( ) ,
} ,
base_2 : rlnl ::types ::PosQuatPair {
pos : generic . map_config . bases . get ( & 1 ) . map ( | ( s , _ ) | ( s . center . x , s . center . y , s . center . z ) ) . unwrap_or ( ( 0.0 , 0.0 , 0.0 ) ) . into ( ) ,
rot : ( 0.0 , 0.0 , 0.0 , 0.0 ) . into ( ) ,
} ,
protonium_cube_health : self . config . protonium_health as i32 ,
} ) ,
} )
} ,
// RegisterCapturePoints
if generic . map_config . capture_points . is_empty ( ) {
None
} else {
Some ( crate ::matches ::RlnlPacket {
event : rlnl ::event_code ::NetworkEvent ::RegisterCapturePoints ,
property : literustlib ::packet ::Property ::ReliableOrdered ,
data : Box ::new ( rlnl ::events ::sync ::GetCapturePoints {
points : [
2025-09-03 22:33:26 -04:00
generic . map_config . capture_points . first ( ) . map ( | ( s , _ ) | Self ::sphere_to_capture_point ( s , self . config . num_segments as f32 ) ) . unwrap_or_else ( | | Self ::default_capture_point ( self . config . num_segments as f32 ) ) ,
2025-08-30 21:36:01 -04:00
generic . map_config . capture_points . get ( 1 ) . map ( | ( s , _ ) | Self ::sphere_to_capture_point ( s , self . config . num_segments as f32 ) ) . unwrap_or_else ( | | Self ::default_capture_point ( self . config . num_segments as f32 ) ) ,
generic . map_config . capture_points . get ( 2 ) . map ( | ( s , _ ) | Self ::sphere_to_capture_point ( s , self . config . num_segments as f32 ) ) . unwrap_or_else ( | | Self ::default_capture_point ( self . config . num_segments as f32 ) ) ,
]
} ) ,
} )
} ,
// RegisterEqualizer
Some ( crate ::matches ::RlnlPacket {
event : rlnl ::event_code ::NetworkEvent ::RegisterEqualizer ,
property : literustlib ::packet ::Property ::ReliableOrdered ,
data : Box ::new ( rlnl ::events ::sync ::GetEqualizer {
pos : rlnl ::types ::PosQuatPair {
pos : ( 0.0 , 0.0 , 0.0 ) . into ( ) ,
rot : ( 0.0 , 0.0 , 0.0 , 0.0 ) . into ( ) ,
} ,
total_health : 42 ,
} ) ,
} ) ,
// SetShieldState
2025-09-03 22:33:26 -04:00
if generic . map_config . bases . contains_key ( & 0 ) {
2025-08-30 21:36:01 -04:00
Some ( crate ::matches ::RlnlPacket {
event : rlnl ::event_code ::NetworkEvent ::SetShieldState ,
property : literustlib ::packet ::Property ::ReliableOrdered ,
data : Box ::new ( rlnl ::events ::sync ::FusionShieldState {
team_id : 0 ,
full_power : 0 ,
} ) ,
} )
} else {
None
} ,
2025-09-03 22:33:26 -04:00
if generic . map_config . bases . contains_key ( & 1 ) {
2025-08-30 21:36:01 -04:00
Some ( crate ::matches ::RlnlPacket {
event : rlnl ::event_code ::NetworkEvent ::SetShieldState ,
property : literustlib ::packet ::Property ::ReliableOrdered ,
data : Box ::new ( rlnl ::events ::sync ::FusionShieldState {
team_id : 1 ,
full_power : 0 ,
} ) ,
} )
} else {
None
} ,
// CurrentGameTime
Some ( crate ::matches ::RlnlPacket {
event : rlnl ::event_code ::NetworkEvent ::CurrentGameTime ,
property : literustlib ::packet ::Property ::ReliableOrdered ,
data : Box ::new ( rlnl ::events ::GameTime ( self . game_duration . as_millis ( ) as f32 / 1000.0 ) ) ,
} ) ,
// SyncTeamBaseCubes
// TODO ???
/* crate::matches::RlnlPacket {
event : rlnl ::event_code ::NetworkEvent ::SyncTeamBaseCubes ,
property : literustlib ::packet ::Property ::ReliableOrdered ,
data : Box ::new ( rlnl ::events ::HealedCubes {
healed_machine : 0 ,
type_performing_healing : rlnl ::types ::TargetType ::TeamBase ,
target_type : rlnl ::types ::TargetType ::TeamBase ,
num_healed_cubes : 1 ,
hit_cubes : vec ! [
rlnl ::types ::HitCubeInfo {
pos : rlnl ::types ::Byte3 { x : 0 , y : 0 , z : 0 , } ,
damage : 1 ,
}
] ,
} ) ,
} , * /
/* Some(
crate ::matches ::RlnlPacket {
event : rlnl ::event_code ::NetworkEvent ::SyncTeamBaseCubes ,
property : literustlib ::packet ::Property ::ReliableOrdered ,
data : Box ::new ( rlnl ::events ::HealedCubes {
healed_machine : 0 ,
type_performing_healing : rlnl ::types ::TargetType ::TeamBase ,
target_type : rlnl ::types ::TargetType ::TeamBase ,
num_healed_cubes : oj_rc_core ::cubes ::prefabs ::CRYSTAL_COUNT as _ ,
hit_cubes : oj_rc_core ::cubes ::prefabs ::team_base_ba_crystals ( oj_rc_core ::cubes ::prefabs ::CRYSTAL_COUNT )
. into_iter ( )
//.chain(vec![oj_rc_core::cubes::prefabs::team_base_ba_location()].into_iter())
. map ( | loc | {
//log::info!("Doing sync-time base heal for cube at ({}, {}, {})", loc.0, loc.1, loc.2);
rlnl ::types ::HitCubeInfo {
pos : rlnl ::types ::Byte3 { x : loc . 0 , y : loc . 1 , z : loc . 2 , } ,
damage : Self ::CRYSTAL_HEALTH ,
}
} )
. collect ( ) ,
} ) ,
}
) , * /
// SyncEqualizerNotification
/* crate::matches::RlnlPacket {
event : rlnl ::event_code ::NetworkEvent ::SyncEqualizerNotification ,
property : literustlib ::packet ::Property ::ReliableOrdered ,
data : Box ::new ( rlnl ::events ::sync ::EqualizerNotification {
notification : rlnl ::types ::EqualizerState ::Lost ,
team_id : 0 ,
time : 0 ,
max_health : 42 ,
health : 7 ,
} ) ,
} ,
crate ::matches ::RlnlPacket {
event : rlnl ::event_code ::NetworkEvent ::SyncEqualizerNotification ,
property : literustlib ::packet ::Property ::ReliableOrdered ,
data : Box ::new ( rlnl ::events ::sync ::EqualizerNotification {
notification : rlnl ::types ::EqualizerState ::Lost ,
team_id : 1 ,
time : 0 ,
max_health : 42 ,
health : 7 ,
} ) ,
} , * /
2025-09-03 22:33:26 -04:00
] . into_iter ( ) . flatten ( ) . collect ( )
2025-08-30 21:36:01 -04:00
}
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 ( ) ) ) ;
}
drop ( read_lock ) ;
let game_end = game_start + self . game_duration ;
let extra_packets = Vec ::default ( ) ;
let new_timer_task = crate ::matches ::timer ::match_time_syncer ( senders , game_start , game_end , extra_packets , Vec ::default ( ) ) ;
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 battle arena 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 , location : ( f32 , f32 , f32 ) ) -> bool {
let game_start = generic . game_start . load ( std ::sync ::atomic ::Ordering ::Relaxed ) ;
if generic . game_start . load ( std ::sync ::atomic ::Ordering ::Relaxed ) = = - 1 | | chrono ::Utc ::now ( ) . timestamp ( ) < game_start {
// game is not in progress, ignore motion event
log ::debug! ( " Ignoring early motion event from player {} " , motion . player_id ) ;
return true ;
}
if generic . is_game_done ( ) {
self . abort_timer_sync ( ) . await ;
return true ;
}
if self . check_if_match_time_is_done ( generic ) . await {
return true ;
}
if generic . map_config . capture_points . is_empty ( ) {
return true ; // don't bother trying to track whether players are in capture points since there are none
}
if let Some ( player_team ) = self . player_tracking . team ( motion . player_id ) . await {
let mut now_in_point = None ;
for ( point_i , point ) in generic . map_config . capture_points . iter ( ) . enumerate ( ) {
if crate ::matches ::GenericGamemodeEngine ::< Self > ::is_in ( & location , & point . 0 ) {
now_in_point = Some ( point_i as u8 ) ;
break ;
}
}
2025-09-03 18:38:56 -04:00
let was_in_point = self . player_tracking . swap_is_in_point ( motion . player_id , now_in_point ) . await ;
2025-08-30 21:36:01 -04:00
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 {
self . capture_tracking . on_enter ( generic , now_in_point , motion . player_id , player_team as i8 ) . await ;
}
if let Some ( was_in_point ) = was_in_point {
self . capture_tracking . on_exit ( generic , was_in_point , motion . player_id , player_team as i8 , self . config . num_segments as f32 ) . await ;
}
}
}
if let Some ( tick_info ) = self . capture_tracking . tick ( generic , self . config . num_segments as f32 ) . await {
// handle shield (de)activation
for team in tick_info . captured_firsts {
generic . broadcast (
rlnl ::event_code ::NetworkEvent ::SetShieldState ,
literustlib ::packet ::Property ::ReliableOrdered ,
& rlnl ::events ::sync ::FusionShieldState {
team_id : team as i8 ,
full_power : 1 ,
} ,
true ,
) . await ;
}
for team in tick_info . lost_lasts {
generic . broadcast (
rlnl ::event_code ::NetworkEvent ::SetShieldState ,
literustlib ::packet ::Property ::ReliableOrdered ,
& rlnl ::events ::sync ::FusionShieldState {
team_id : team as i8 ,
full_power : 0 ,
} ,
true ,
) . await ;
}
// do base charge tick
for base_id in generic . map_config . bases . keys ( ) {
//log::info!("Healing base {}", base_id);
if let Some ( owned_points ) = tick_info . owned . get ( base_id ) {
if let Some ( tracked_base ) = self . base_tracking . bases . get ( base_id ) {
let one_tick = ( self . crystals . len ( ) as f32 )
* ( ( PointTracker ::TICK_MS as f32 ) / ( self . game_duration . as_millis ( ) as f32 ) )
* ( ( self . base_tracking . bases . len ( ) as f32 ) / ( self . capture_tracking . points . len ( ) as f32 ) ) ;
let increment = tick_info . delta as f32 * ( * owned_points as f32 ) * one_tick ;
let old_float_index = tracked_base . cube_index . fetch_add ( increment , std ::sync ::atomic ::Ordering ::SeqCst ) ;
let new_float_index = old_float_index + increment ;
let old_index = ( old_float_index . ceil ( ) as usize ) . clamp ( 0 , self . crystals . len ( ) ) ;
let new_index = ( new_float_index . ceil ( ) as usize ) . clamp ( 0 , self . crystals . len ( ) ) ;
if new_index ! = old_index {
log ::debug! ( " Base {} increment passed a crystal index barrier " , base_id ) ;
let first_damaged = tracked_base . first_damaged ( old_index , self . config . protonium_health as u32 ) ;
let payload = if new_index - old_index = = 1 & & first_damaged . is_some ( ) {
// undo cube_index update
tracked_base . cube_index . fetch_sub ( increment , std ::sync ::atomic ::Ordering ::SeqCst ) ;
log ::debug! ( " Skipping increment in favour of healing damaged/destroyed cube " ) ;
let first_damaged = first_damaged . unwrap ( ) ;
let healing = self . config . protonium_health as u32 - tracked_base . calculate_crystal_health ( first_damaged , self . config . protonium_health as u32 ) ;
tracked_base . crystals_healths [ first_damaged ] . store ( u8 ::MAX , std ::sync ::atomic ::Ordering ::Relaxed ) ;
let target_crystal = & self . crystals [ first_damaged ] ;
rlnl ::events ::HealedCubes {
healed_machine : * base_id as u16 ,
type_performing_healing : rlnl ::types ::TargetType ::TeamBase ,
target_type : rlnl ::types ::TargetType ::TeamBase ,
num_healed_cubes : 1 ,
hit_cubes : vec ! [
rlnl ::types ::HitCubeInfo {
pos : rlnl ::types ::Byte3 { x : target_crystal . x , y : target_crystal . y , z : target_crystal . z , } ,
damage : healing as i32 ,
}
] ,
}
} else {
let target_crystals = & self . crystals [ old_index .. new_index ] ;
for crystal_i in old_index .. new_index {
tracked_base . crystals_healths [ crystal_i ] . store ( u8 ::MAX , std ::sync ::atomic ::Ordering ::Relaxed ) ;
}
rlnl ::events ::HealedCubes {
healed_machine : * base_id as u16 ,
type_performing_healing : rlnl ::types ::TargetType ::TeamBase ,
target_type : rlnl ::types ::TargetType ::TeamBase ,
num_healed_cubes : target_crystals . len ( ) as _ ,
hit_cubes : target_crystals
. iter ( )
. map ( | loc | rlnl ::types ::HitCubeInfo {
pos : rlnl ::types ::Byte3 { x : loc . x , y : loc . y , z : loc . z , } ,
damage : self . config . protonium_health as i32 ,
} )
. collect ( ) ,
}
} ;
generic . broadcast (
rlnl ::event_code ::NetworkEvent ::SyncTeamBaseCubes ,
literustlib ::packet ::Property ::ReliableOrdered ,
& payload ,
true
) . await ;
if new_index = = self . crystals . len ( ) {
// team base is charged to 100%
self . do_win ( * base_id , WinMode ::BaseFull , generic ) . await ;
}
}
}
}
}
}
true
}
async fn on_custom ( & self , generic : & crate ::matches ::GenericGamemodeEngine < Self > , _user_id : i32 , event : rlnl ::event_code ::NetworkEvent , property : literustlib ::packet ::Property , data : Box < dyn crate ::Broadcastable > ) {
match ( event , property ) {
( rlnl ::event_code ::NetworkEvent ::SendDamagedByEnemyShield , literustlib ::packet ::Property ::ReliableOrdered ) = > {
generic . broadcast (
rlnl ::event_code ::NetworkEvent ::DamagedByEnemyShield ,
literustlib ::packet ::Property ::ReliableOrdered ,
& * data ,
true ,
) . await ;
} ,
( rlnl ::event_code ::NetworkEvent ::SurrenderRequest , literustlib ::packet ::Property ::ReliableOrdered ) = > {
// TODO
log ::warn! ( " Ignoring SurrenderRequest because it's not implemented (yet) " ) ;
}
( rlnl ::event_code ::NetworkEvent ::AwardTeamBaseProtoniumDestroyedRequest , literustlib ::packet ::Property ::ReliableOrdered ) = > {
// TODO
log ::warn! ( " Ignoring AwardTeamBaseProtoniumDestroyedRequest because it's not implemented (yet) " ) ;
}
_ = > { }
}
}
}