mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Refactor tick counting into one spot
This commit is contained in:
@@ -177,14 +177,14 @@ impl PointInfo {
|
|||||||
|
|
||||||
struct PointTracker {
|
struct PointTracker {
|
||||||
points: Vec<PointInfo>,
|
points: Vec<PointInfo>,
|
||||||
last_tick: std::sync::atomic::AtomicI64,
|
ticker: super::trackers::TickTracker<{Self::TICK_MS}>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PointTickInfo {
|
struct PointTickInfo {
|
||||||
owned: std::collections::HashMap<u8, u8>, // team -> capture point count
|
owned: std::collections::HashMap<u8, u8>, // team -> capture point count
|
||||||
captured_firsts: std::collections::HashSet<u8>, // team
|
captured_firsts: std::collections::HashSet<u8>, // team
|
||||||
lost_lasts: std::collections::HashSet<u8>, // team
|
lost_lasts: std::collections::HashSet<u8>, // team
|
||||||
delta: i64,
|
delta: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PointTracker {
|
impl PointTracker {
|
||||||
@@ -193,7 +193,7 @@ impl PointTracker {
|
|||||||
fn new(points: impl Iterator<Item=f32>) -> Self {
|
fn new(points: impl Iterator<Item=f32>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
points: points.map(PointInfo::new).collect(),
|
points: points.map(PointInfo::new).collect(),
|
||||||
last_tick: std::sync::atomic::AtomicI64::new(i64::MIN),
|
ticker: super::trackers::TickTracker::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,18 +325,8 @@ impl PointTracker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn tick(&self, generic: &crate::matches::GenericGamemodeEngine<BattleArenaLogic>, max_progress: f32) -> Option<PointTickInfo> {
|
async fn tick(&self, generic: &crate::matches::GenericGamemodeEngine<BattleArenaLogic>, max_progress: f32) -> Option<PointTickInfo> {
|
||||||
let now = chrono::Utc::now().timestamp_millis();
|
let delta = self.ticker.tick();
|
||||||
let last_tick = self.last_tick.load(std::sync::atomic::Ordering::SeqCst);
|
if delta == 0 { return None; }
|
||||||
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 owned_points = std::collections::HashMap::with_capacity(2);
|
||||||
let mut captured_firsts = std::collections::HashSet::new();
|
let mut captured_firsts = std::collections::HashSet::new();
|
||||||
let mut lost_lasts = std::collections::HashSet::new();
|
let mut lost_lasts = std::collections::HashSet::new();
|
||||||
@@ -410,6 +400,10 @@ struct BaseTracker {
|
|||||||
bases: std::collections::HashMap<u8, BaseInfo>,
|
bases: std::collections::HashMap<u8, BaseInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct BaseTickInfo {
|
||||||
|
win: Option<(u8, WinMode)>,
|
||||||
|
}
|
||||||
|
|
||||||
impl BaseTracker {
|
impl BaseTracker {
|
||||||
fn new<'a>(bases_iter: impl std::iter::Iterator<Item=&'a u8>, crystals: &[oj_rc_core::cubes::CubeLocationInfo]) -> Self {
|
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();
|
let mut bases = std::collections::HashMap::new();
|
||||||
@@ -420,6 +414,83 @@ impl BaseTracker {
|
|||||||
bases,
|
bases,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn tick(&self, tick_info: &PointTickInfo, crystals: &[oj_rc_core::cubes::CubeLocationInfo], generic: &crate::matches::GenericGamemodeEngine<BattleArenaLogic>, capture_points_count: usize, crystal_health: u32) -> BaseTickInfo {
|
||||||
|
for (base_id, tracked_base) in self.bases.iter() {
|
||||||
|
//log::info!("Healing base {}", base_id);
|
||||||
|
if let Some(owned_points) = tick_info.owned.get(base_id) {
|
||||||
|
let one_tick = (crystals.len() as f32)
|
||||||
|
* ((PointTracker::TICK_MS as f32) / (generic.game_duration.as_millis() as f32))
|
||||||
|
* ((self.bases.len() as f32) / (capture_points_count 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, crystals.len());
|
||||||
|
let new_index = (new_float_index.ceil() as usize).clamp(0, 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, crystal_health);
|
||||||
|
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 = crystal_health - tracked_base.calculate_crystal_health(first_damaged, crystal_health);
|
||||||
|
tracked_base.crystals_healths[first_damaged].store(u8::MAX, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
let target_crystal = &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 = &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: crystal_health as i32,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
generic.broadcast(
|
||||||
|
rlnl::event_code::NetworkEvent::SyncTeamBaseCubes,
|
||||||
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
|
&payload,
|
||||||
|
true
|
||||||
|
).await;
|
||||||
|
|
||||||
|
if new_index == crystals.len() {
|
||||||
|
// team base is charged to 100%
|
||||||
|
//self.do_win(*base_id, WinMode::BaseFull, generic).await;
|
||||||
|
return BaseTickInfo {
|
||||||
|
win: Some((*base_id, WinMode::BaseFull)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BaseTickInfo {
|
||||||
|
win: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BaseInfo {
|
struct BaseInfo {
|
||||||
@@ -1072,23 +1143,23 @@ impl CustomGameLogic for BattleArenaLogic {
|
|||||||
}
|
}
|
||||||
if let Some(tick_info) = self.capture_tracking.tick(generic, 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
|
// handle shield (de)activation
|
||||||
for team in tick_info.captured_firsts {
|
for team in tick_info.captured_firsts.iter() {
|
||||||
generic.broadcast(
|
generic.broadcast(
|
||||||
rlnl::event_code::NetworkEvent::SetShieldState,
|
rlnl::event_code::NetworkEvent::SetShieldState,
|
||||||
literustlib::packet::Property::ReliableOrdered,
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
&rlnl::events::sync::FusionShieldState {
|
&rlnl::events::sync::FusionShieldState {
|
||||||
team_id: team as i8,
|
team_id: *team as i8,
|
||||||
full_power: 1,
|
full_power: 1,
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
).await;
|
).await;
|
||||||
}
|
}
|
||||||
for team in tick_info.lost_lasts {
|
for team in tick_info.lost_lasts.iter() {
|
||||||
generic.broadcast(
|
generic.broadcast(
|
||||||
rlnl::event_code::NetworkEvent::SetShieldState,
|
rlnl::event_code::NetworkEvent::SetShieldState,
|
||||||
literustlib::packet::Property::ReliableOrdered,
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
&rlnl::events::sync::FusionShieldState {
|
&rlnl::events::sync::FusionShieldState {
|
||||||
team_id: team as i8,
|
team_id: *team as i8,
|
||||||
full_power: 0,
|
full_power: 0,
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
@@ -1096,75 +1167,9 @@ impl CustomGameLogic for BattleArenaLogic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// do base charge tick
|
// do base charge tick
|
||||||
for base_id in generic.map_config.bases.keys() {
|
let base_tick_info = self.base_tracking.tick(&tick_info, &self.crystals, generic, self.capture_tracking.points.len(), self.config.protonium_health as u32).await;
|
||||||
//log::info!("Healing base {}", base_id);
|
if let Some((winning_team, win_mode)) = base_tick_info.win {
|
||||||
if let Some(owned_points) = tick_info.owned.get(base_id) {
|
self.do_win(winning_team, win_mode, generic).await;
|
||||||
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) / (generic.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle surrender vote tick
|
// handle surrender vote tick
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ impl BaseCounters {
|
|||||||
|
|
||||||
struct BaseTracker {
|
struct BaseTracker {
|
||||||
bases: std::collections::HashMap<u8, BaseCounters>,
|
bases: std::collections::HashMap<u8, BaseCounters>,
|
||||||
last_tick: std::sync::atomic::AtomicI64,
|
ticker: super::trackers::TickTracker<{Self::TICK_MS}>,
|
||||||
is_baseless: bool,
|
is_baseless: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,18 +260,8 @@ impl BaseTracker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn tick(&self, generic: &crate::matches::GenericGamemodeEngine<EliminationLogic>) {
|
async fn tick(&self, generic: &crate::matches::GenericGamemodeEngine<EliminationLogic>) {
|
||||||
let now = chrono::Utc::now().timestamp_millis();
|
let delta = self.ticker.tick();
|
||||||
let last_tick = self.last_tick.load(std::sync::atomic::Ordering::SeqCst);
|
if delta == 0 { return; }
|
||||||
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; }
|
|
||||||
self.last_tick.store(last_tick + (delta * Self::TICK_MS), std::sync::atomic::Ordering::SeqCst);
|
|
||||||
delta
|
|
||||||
};
|
|
||||||
for (team, base) in self.bases.iter() {
|
for (team, base) in self.bases.iter() {
|
||||||
if base.friendlies.load(std::sync::atomic::Ordering::SeqCst) > 0 { continue; }
|
if base.friendlies.load(std::sync::atomic::Ordering::SeqCst) > 0 { continue; }
|
||||||
if base.enemies.load(std::sync::atomic::Ordering::SeqCst) <= 0 { continue; }
|
if base.enemies.load(std::sync::atomic::Ordering::SeqCst) <= 0 { continue; }
|
||||||
@@ -356,7 +346,7 @@ impl EliminationLogic {
|
|||||||
},
|
},
|
||||||
bases: BaseTracker {
|
bases: BaseTracker {
|
||||||
bases: map.bases.iter().map(|(team, base)| (*team, BaseCounters::new(base.1))).collect(),
|
bases: map.bases.iter().map(|(team, base)| (*team, BaseCounters::new(base.1))).collect(),
|
||||||
last_tick: std::sync::atomic::AtomicI64::new(i64::MIN),
|
ticker: super::trackers::TickTracker::new(),
|
||||||
is_baseless: map.bases.is_empty(),
|
is_baseless: map.bases.is_empty(),
|
||||||
},
|
},
|
||||||
respawn_full_heal_duration: config.respawn_full_heal_duration,
|
respawn_full_heal_duration: config.respawn_full_heal_duration,
|
||||||
|
|||||||
@@ -1,2 +1,5 @@
|
|||||||
mod surrender;
|
mod surrender;
|
||||||
pub use surrender::{SurrenderGameTracker, SurrenderVoteResult};
|
pub use surrender::{SurrenderGameTracker, SurrenderVoteResult};
|
||||||
|
|
||||||
|
mod ticker;
|
||||||
|
pub use ticker::TickTracker;
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ impl SurrenderTracker {
|
|||||||
|
|
||||||
pub struct SurrenderGameTracker {
|
pub struct SurrenderGameTracker {
|
||||||
surrenders: tokio::sync::RwLock<std::collections::HashMap<u8, SurrenderTracker>>,
|
surrenders: tokio::sync::RwLock<std::collections::HashMap<u8, SurrenderTracker>>,
|
||||||
|
ticker: super::TickTracker<250>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum SurrenderVoteResult {
|
pub enum SurrenderVoteResult {
|
||||||
@@ -125,6 +126,7 @@ impl SurrenderGameTracker {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
surrenders: tokio::sync::RwLock::new(std::collections::HashMap::with_capacity(2)),
|
surrenders: tokio::sync::RwLock::new(std::collections::HashMap::with_capacity(2)),
|
||||||
|
ticker: super::TickTracker::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,6 +176,7 @@ impl SurrenderGameTracker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn tick<L: crate::matches::CustomGameLogic>(&self, generic: &crate::matches::GenericGamemodeEngine<L>) {
|
pub async fn tick<L: crate::matches::CustomGameLogic>(&self, generic: &crate::matches::GenericGamemodeEngine<L>) {
|
||||||
|
if self.ticker.tick() == 0 { return; }
|
||||||
let mut keys_to_remove = Vec::default();
|
let mut keys_to_remove = Vec::default();
|
||||||
let generic_user_lock = generic.users.read().await;
|
let generic_user_lock = generic.users.read().await;
|
||||||
for (team, tracker) in self.surrenders.read().await.iter() {
|
for (team, tracker) in self.surrenders.read().await.iter() {
|
||||||
|
|||||||
26
rc_multiplayer/src/matches/modes/trackers/ticker.rs
Normal file
26
rc_multiplayer/src/matches/modes/trackers/ticker.rs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
pub struct TickTracker<const TICK_MS: i64 = 50> {
|
||||||
|
last_tick: std::sync::atomic::AtomicI64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <const TICK_MS: i64> TickTracker<TICK_MS> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
last_tick: std::sync::atomic::AtomicI64::new(i64::MIN),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(&self) -> u16 {
|
||||||
|
let now = chrono::Utc::now().timestamp_millis();
|
||||||
|
let last_tick = self.last_tick.load(std::sync::atomic::Ordering::SeqCst);
|
||||||
|
if last_tick == i64::MIN {
|
||||||
|
// first tick
|
||||||
|
self.last_tick.store(now, std::sync::atomic::Ordering::SeqCst);
|
||||||
|
1u16
|
||||||
|
} else {
|
||||||
|
let delta = (now - last_tick) / TICK_MS;
|
||||||
|
if delta == 0 { return 0; }
|
||||||
|
self.last_tick.store(last_tick + (delta * TICK_MS), std::sync::atomic::Ordering::SeqCst);
|
||||||
|
delta as u16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user