mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Get capture points workins #30 (mars 1 only)
This commit is contained in:
11
Cargo.lock
generated
11
Cargo.lock
generated
@@ -2476,6 +2476,16 @@ dependencies = [
|
|||||||
"num-traits 0.2.19",
|
"num-traits 0.2.19",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-quaternion"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d7fa2cef9166a2dcb9f1aa92e0ce2c6ddef7d664cc6d4af93e7f210bfb8a19b"
|
||||||
|
dependencies = [
|
||||||
|
"num-integer",
|
||||||
|
"num-traits 0.2.19",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-rational"
|
name = "num-rational"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
@@ -3350,6 +3360,7 @@ dependencies = [
|
|||||||
"literustlib",
|
"literustlib",
|
||||||
"literustlib_server",
|
"literustlib_server",
|
||||||
"log",
|
"log",
|
||||||
|
"num-quaternion",
|
||||||
"oj_rc_core",
|
"oj_rc_core",
|
||||||
"rlnl",
|
"rlnl",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|||||||
@@ -391,14 +391,14 @@ impl <C: Clone + Send> super::ConfigProvider<C> for CubeConfig {
|
|||||||
spawns.insert(point.team, list);
|
spawns.insert(point.team, list);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let bases = conf.bases.iter().map(|base| (base.team, super::Sphere {
|
let bases = conf.bases.iter().map(|base| (base.team, (super::Sphere {
|
||||||
radius: base.radius,
|
radius: base.radius,
|
||||||
center: super::Point {
|
center: super::Point {
|
||||||
x: base.x,
|
x: base.x,
|
||||||
y: base.y,
|
y: base.y,
|
||||||
z: base.z,
|
z: base.z,
|
||||||
},
|
},
|
||||||
})).collect();
|
}, base.percent_per_second))).collect();
|
||||||
let map_conf = super::MapConfig {
|
let map_conf = super::MapConfig {
|
||||||
spawns,
|
spawns,
|
||||||
bases,
|
bases,
|
||||||
|
|||||||
@@ -347,5 +347,5 @@ pub struct Sphere {
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct MapConfig {
|
pub struct MapConfig {
|
||||||
pub spawns: std::collections::HashMap<u8, Vec<Point>>, // team -> points
|
pub spawns: std::collections::HashMap<u8, Vec<Point>>, // team -> points
|
||||||
pub bases: std::collections::HashMap<u8, Sphere>, // team -> base
|
pub bases: std::collections::HashMap<u8, (Sphere, f32)>, // team -> base
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,12 +27,13 @@ pub struct CaptureBase {
|
|||||||
pub y: f32,
|
pub y: f32,
|
||||||
pub z: f32,
|
pub z: f32,
|
||||||
pub radius: f32,
|
pub radius: f32,
|
||||||
|
pub percent_per_second: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn default_map() -> std::collections::HashMap<super::combat::GameMap, MapConfig> {
|
pub(super) fn default_map() -> std::collections::HashMap<super::combat::GameMap, MapConfig> {
|
||||||
let mut map = std::collections::HashMap::with_capacity(9);
|
let mut map = std::collections::HashMap::with_capacity(9);
|
||||||
let coords_t0 = corner_to_center((6.60, 4.09, 20.3), 10.0);
|
//let coords_t0 = corner_to_center((6.60, 4.09, 20.3), 10.0);
|
||||||
let coords_t1 = corner_to_center((364.60, 10.63, 372.20), 10.0);
|
//let coords_t1 = corner_to_center((364.60, 10.63, 372.20), 10.0);
|
||||||
map.insert(super::combat::GameMap::Mars1, MapConfig {
|
map.insert(super::combat::GameMap::Mars1, MapConfig {
|
||||||
spawn_points: vec![
|
spawn_points: vec![
|
||||||
// team 0
|
// team 0
|
||||||
@@ -161,17 +162,19 @@ pub(super) fn default_map() -> std::collections::HashMap<super::combat::GameMap,
|
|||||||
bases: vec![
|
bases: vec![
|
||||||
CaptureBase {
|
CaptureBase {
|
||||||
team: 0,
|
team: 0,
|
||||||
x: coords_t0.0,
|
x: 6.60,
|
||||||
y: coords_t0.1,
|
y: 4.09,
|
||||||
z: coords_t0.2,
|
z: 20.3,
|
||||||
radius: 10.0,
|
radius: 20.0,
|
||||||
|
percent_per_second: 2.5,
|
||||||
},
|
},
|
||||||
CaptureBase {
|
CaptureBase {
|
||||||
team: 1,
|
team: 1,
|
||||||
x: coords_t1.0,
|
x: 364.60,
|
||||||
y: coords_t1.1,
|
y: 10.63,
|
||||||
z: coords_t1.2,
|
z: 372.20,
|
||||||
radius: 10.0,
|
radius: 20.0,
|
||||||
|
percent_per_second: 2.5,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@@ -206,6 +209,6 @@ pub(super) fn default_map() -> std::collections::HashMap<super::combat::GameMap,
|
|||||||
map
|
map
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn corner_to_center(corner: (f32, f32, f32), radius: f32) -> (f32, f32, f32) {
|
/*const fn corner_to_center(corner: (f32, f32, f32), radius: f32) -> (f32, f32, f32) {
|
||||||
(corner.0 + radius, corner.1, corner.2 + radius)
|
(corner.0 + radius, corner.1, corner.2 + radius)
|
||||||
}
|
}*/
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ bytes = "1.10"
|
|||||||
byteserde = "0.6.2"
|
byteserde = "0.6.2"
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
atomic_float = "1.1"
|
atomic_float = "1.1"
|
||||||
|
num-quaternion = "1.0"
|
||||||
|
|
||||||
literustlib_server = { version = "0.1", path = "../../LiteRustLib/server" }
|
literustlib_server = { version = "0.1", path = "../../LiteRustLib/server" }
|
||||||
literustlib = { version = "0.1", path = "../../LiteRustLib" }
|
literustlib = { version = "0.1", path = "../../LiteRustLib" }
|
||||||
|
|||||||
@@ -45,11 +45,12 @@ impl GameMatches {
|
|||||||
}
|
}
|
||||||
match game_info.mode {
|
match game_info.mode {
|
||||||
oj_rc_core::data::game_mode::GameMode::SuddenDeath => {
|
oj_rc_core::data::game_mode::GameMode::SuddenDeath => {
|
||||||
|
let inner = super::modes::EliminationLogic::new(&self.mode_configs.elimination, &map_config);
|
||||||
let engine = super::GenericGamemodeEngine::new(
|
let engine = super::GenericGamemodeEngine::new(
|
||||||
game_info,
|
game_info,
|
||||||
map_config,
|
map_config,
|
||||||
players,
|
players,
|
||||||
super::modes::EliminationLogic::new(&self.mode_configs.elimination)
|
inner,
|
||||||
);
|
);
|
||||||
Ok(engine.spawn())
|
Ok(engine.spawn())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,6 @@ pub trait CustomGameLogic: Sized + Send + Sync + 'static {
|
|||||||
async fn on_countdown_start(&self, generic: &super::GenericGamemodeEngine<Self>, game_start: chrono::DateTime<chrono::Utc>) -> bool;
|
async fn on_countdown_start(&self, generic: &super::GenericGamemodeEngine<Self>, game_start: chrono::DateTime<chrono::Utc>) -> bool;
|
||||||
async fn on_game_completed(&self, generic: &super::GenericGamemodeEngine<Self>) -> bool;
|
async fn on_game_completed(&self, generic: &super::GenericGamemodeEngine<Self>) -> bool;
|
||||||
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;
|
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;
|
||||||
async fn on_motion(&self, generic: &super::GenericGamemodeEngine<Self>, motion: &rlnl::machine_motion::MachineMotion) -> bool;
|
async fn on_motion(&self, generic: &super::GenericGamemodeEngine<Self>, motion: &rlnl::machine_motion::MachineMotion, location: (f32, f32, f32)) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -554,31 +554,38 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
super::GameMessage::Motion { user_id, motion } => {
|
super::GameMessage::Motion { user_id, motion } => {
|
||||||
if self.custom_logic_handler.on_motion(&self, &motion).await {
|
let (x, y, z) = motion.rb_state.rb_pos_rot.pos.into();
|
||||||
if let Some(conn) = self.users.read().await.get(&motion.player_id) {
|
let (x2, y2, z2) = motion.rb_state.center_of_mass.into();
|
||||||
let (x, y, z) = motion.rb_state.rb_pos_rot.pos.into();
|
let (w3, x3, y3, z3) = motion.rb_state.rb_pos_rot.rot.into();
|
||||||
conn.machine.location.x.store(x, std::sync::atomic::Ordering::Relaxed);
|
let quat = num_quaternion::Quaternion::new(w3, x3, y3, z3);
|
||||||
conn.machine.location.y.store(y, std::sync::atomic::Ordering::Relaxed);
|
if let Some(unit_quat) = quat.normalize() {
|
||||||
conn.machine.location.z.store(z, std::sync::atomic::Ordering::Relaxed);
|
let coords = unit_quat.rotate_vector([x2, y2, z2]);
|
||||||
log::debug!("Player {} is at (x, y, z) ({}, {}, {})", motion.player_id, x, y, z);
|
let (x4, y4, z4) = (x + coords[0], y + coords[1], z + coords[2]);
|
||||||
use byteserde::ser_heap::ByteSerializeHeap;
|
//log::debug!("Player {} world CoM is at (x, y, z) ({}, {}, {})", motion.player_id, x4, y4, z4);
|
||||||
let mut ser = byteserde::ser_heap::ByteSerializerHeap::default();
|
if self.custom_logic_handler.on_motion(&self, &motion, (x4, y4, z4)).await {
|
||||||
if let Err(e) = motion.byte_serialize_heap(&mut ser) {
|
if let Some(conn) = self.users.read().await.get(&motion.player_id) {
|
||||||
log::error!("Failed to serialize motion data from user {}: {}", user_id, e);
|
conn.machine.location.x.store(x4, std::sync::atomic::Ordering::Relaxed);
|
||||||
} else {
|
conn.machine.location.y.store(y4, std::sync::atomic::Ordering::Relaxed);
|
||||||
let data = bytes::Bytes::copy_from_slice(ser.as_slice());
|
conn.machine.location.z.store(z4, std::sync::atomic::Ordering::Relaxed);
|
||||||
for conn in self.users.read().await.values() {
|
use byteserde::ser_heap::ByteSerializeHeap;
|
||||||
if conn.user.user_id() == user_id { continue; } // fun fact: the game hard crashes if you omit this
|
let mut ser = byteserde::ser_heap::ByteSerializerHeap::default();
|
||||||
crate::events::log_lnl_send_failure(conn.connection.sender.send_data(crate::handler::EventData {
|
if let Err(e) = motion.byte_serialize_heap(&mut ser) {
|
||||||
message_ty: crate::data::MessageType::RobotMotion,
|
log::error!("Failed to serialize motion data from user {}: {}", user_id, e);
|
||||||
variant: 0,
|
} else {
|
||||||
data_size: data.len() as _,
|
let data = bytes::Bytes::copy_from_slice(ser.as_slice());
|
||||||
data: data.clone(),
|
for conn in self.users.read().await.values() {
|
||||||
}, literustlib::packet::Property::Unreliable, &conn.connection.connection).await);
|
if conn.user.user_id() == user_id { continue; } // fun fact: the game hard crashes if you omit this
|
||||||
|
crate::events::log_lnl_send_failure(conn.connection.sender.send_data(crate::handler::EventData {
|
||||||
|
message_ty: crate::data::MessageType::RobotMotion,
|
||||||
|
variant: 0,
|
||||||
|
data_size: data.len() as _,
|
||||||
|
data: data.clone(),
|
||||||
|
}, literustlib::packet::Property::Unreliable, &conn.connection.connection).await);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
log::warn!("Received machine motion with unknown player id {} from user {}", motion.player_id, user_id);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log::warn!("Received machine motion with unknown player id {} from user {}", motion.player_id, user_id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -798,4 +805,15 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
pub(super) fn is_game_done(&self) -> bool {
|
pub(super) fn is_game_done(&self) -> bool {
|
||||||
self.is_complete.load(std::sync::atomic::Ordering::SeqCst)
|
self.is_complete.load(std::sync::atomic::Ordering::SeqCst)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn is_in(loc: &(f32, f32, f32), sphere: &oj_rc_core::persist::config::Sphere) -> bool {
|
||||||
|
let distance = (
|
||||||
|
(loc.0 - sphere.center.x).powi(2)
|
||||||
|
+ (loc.1 - sphere.center.y).powi(2)
|
||||||
|
+ (loc.2 - sphere.center.z).powi(2)
|
||||||
|
).sqrt();
|
||||||
|
//log::info!("{} away from sphere", distance);
|
||||||
|
distance < sphere.radius
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use crate::matches::CustomGameLogic;
|
|||||||
|
|
||||||
struct PlayerTracker {
|
struct PlayerTracker {
|
||||||
alive: tokio::sync::Mutex<std::collections::HashMap<u8, std::collections::HashSet<u8>>>, // team -> set of player_id
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlayerTracker {
|
impl PlayerTracker {
|
||||||
@@ -14,6 +15,7 @@ impl PlayerTracker {
|
|||||||
new_team.insert(player.player_id);
|
new_team.insert(player.player_id);
|
||||||
alive_lock.insert(player.team as u8, new_team);
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn destroy_vehicle(&self, player: &oj_rc_core::persist::user::PlayerDescriptor) {
|
async fn destroy_vehicle(&self, player: &oj_rc_core::persist::user::PlayerDescriptor) {
|
||||||
@@ -52,13 +54,291 @@ impl PlayerTracker {
|
|||||||
alive_players
|
alive_players
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn teams(&self) -> std::collections::HashSet<u8> {
|
/*async fn teams(&self) -> std::collections::HashSet<u8> {
|
||||||
self.alive.lock().await.keys().map(|x| *x).collect()
|
self.alive.lock().await.keys().map(|x| *x).collect()
|
||||||
|
}*/
|
||||||
|
|
||||||
|
async fn player_team(&self, player_id: u8) -> Option<u8> {
|
||||||
|
for (team, players) in self.alive.lock().await.iter() {
|
||||||
|
if players.contains(&player_id) {
|
||||||
|
return Some(*team);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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| {
|
||||||
|
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
|
||||||
|
} else {
|
||||||
|
Some(base as u8)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BaseCounters {
|
||||||
|
enemies: std::sync::atomic::AtomicI16,
|
||||||
|
friendlies: std::sync::atomic::AtomicI16,
|
||||||
|
capture: atomic_float::AtomicF32,
|
||||||
|
percent_per_second: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BaseCounters {
|
||||||
|
fn new(percent_per_second: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
enemies: std::sync::atomic::AtomicI16::new(0),
|
||||||
|
friendlies: std::sync::atomic::AtomicI16::new(0),
|
||||||
|
capture: atomic_float::AtomicF32::new(0.0),
|
||||||
|
percent_per_second,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BaseTracker {
|
||||||
|
bases: std::collections::HashMap<u8, BaseCounters>,
|
||||||
|
last_tick: std::sync::atomic::AtomicI64,
|
||||||
|
is_baseless: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BaseTracker {
|
||||||
|
const TICK_MS: i64 = 50;
|
||||||
|
//const CAPTURE_PER_TICK: f32 = 0.007;
|
||||||
|
|
||||||
|
async fn on_enter(&self, generic: &crate::matches::GenericGamemodeEngine<EliminationLogic>, base_id: u8, is_friendly: bool, player_id: u8) {
|
||||||
|
if let Some(base) = self.bases.get(&base_id) {
|
||||||
|
if let Some(conn) = generic.users.read().await.get(&player_id) {
|
||||||
|
crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data(
|
||||||
|
&rlnl::events::ingame::TeamBaseBoolean {
|
||||||
|
team: base_id,
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
rlnl::event_code::NetworkEvent::PlayerInsideBase,
|
||||||
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
|
&conn.connection.connection,
|
||||||
|
).await);
|
||||||
|
} else { return; }
|
||||||
|
if is_friendly {
|
||||||
|
let friendlies = base.friendlies.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||||
|
if friendlies == 0 {
|
||||||
|
// newly contesting
|
||||||
|
if base.enemies.load(std::sync::atomic::Ordering::SeqCst) > 0 {
|
||||||
|
generic.broadcast(
|
||||||
|
rlnl::event_code::NetworkEvent::TeamBaseContested,
|
||||||
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
|
&rlnl::events::ingame::TeamBaseBoolean {
|
||||||
|
team: base_id,
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
true
|
||||||
|
).await;
|
||||||
|
generic.broadcast(
|
||||||
|
rlnl::event_code::NetworkEvent::TeamBaseCaptureStop,
|
||||||
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
|
&rlnl::events::ingame::TeamBaseState {
|
||||||
|
base_team_or_mining_point_index: base_id,
|
||||||
|
current_progress: rlnl::types::ByteFloat::from(base.capture.load(std::sync::atomic::Ordering::SeqCst)),
|
||||||
|
max_progress: rlnl::types::ByteFloat::from(4.0),
|
||||||
|
},
|
||||||
|
true
|
||||||
|
).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let enemies = base.enemies.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||||
|
if enemies == 0 {
|
||||||
|
// newly capturing
|
||||||
|
if base.friendlies.load(std::sync::atomic::Ordering::SeqCst) > 0 {
|
||||||
|
// contested
|
||||||
|
generic.broadcast(
|
||||||
|
rlnl::event_code::NetworkEvent::TeamBaseContested,
|
||||||
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
|
&rlnl::events::ingame::TeamBaseBoolean {
|
||||||
|
team: base_id,
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
true
|
||||||
|
).await;
|
||||||
|
} else {
|
||||||
|
generic.broadcast(
|
||||||
|
rlnl::event_code::NetworkEvent::TeamBaseCaptureStart,
|
||||||
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
|
&rlnl::events::ingame::TeamBaseState {
|
||||||
|
base_team_or_mining_point_index: base_id,
|
||||||
|
current_progress: rlnl::types::ByteFloat::from(base.capture.load(std::sync::atomic::Ordering::SeqCst)),
|
||||||
|
max_progress: rlnl::types::ByteFloat::from(4.0),
|
||||||
|
},
|
||||||
|
true
|
||||||
|
).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn on_exit(&self, generic: &crate::matches::GenericGamemodeEngine<EliminationLogic>, base_id: u8, is_friendly: bool, player_id: u8) {
|
||||||
|
if let Some(base) = self.bases.get(&base_id) {
|
||||||
|
if let Some(conn) = generic.users.read().await.get(&player_id) {
|
||||||
|
crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data(
|
||||||
|
&rlnl::events::ingame::TeamBaseBoolean {
|
||||||
|
team: base_id,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
rlnl::event_code::NetworkEvent::PlayerInsideBase,
|
||||||
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
|
&conn.connection.connection,
|
||||||
|
).await);
|
||||||
|
} else { return; }
|
||||||
|
if is_friendly {
|
||||||
|
let friendlies = base.friendlies.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
|
||||||
|
if friendlies <= 1 {
|
||||||
|
// no longer contesting
|
||||||
|
generic.broadcast(
|
||||||
|
rlnl::event_code::NetworkEvent::TeamBaseContested,
|
||||||
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
|
&rlnl::events::ingame::TeamBaseBoolean {
|
||||||
|
team: base_id,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
true
|
||||||
|
).await;
|
||||||
|
if base.enemies.load(std::sync::atomic::Ordering::SeqCst) > 0 {
|
||||||
|
generic.broadcast(
|
||||||
|
rlnl::event_code::NetworkEvent::TeamBaseCaptureStart,
|
||||||
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
|
&rlnl::events::ingame::TeamBaseState {
|
||||||
|
base_team_or_mining_point_index: base_id,
|
||||||
|
current_progress: rlnl::types::ByteFloat::from(base.capture.load(std::sync::atomic::Ordering::SeqCst)),
|
||||||
|
max_progress: rlnl::types::ByteFloat::from(4.0),
|
||||||
|
},
|
||||||
|
true
|
||||||
|
).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let enemies = base.enemies.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
|
||||||
|
if enemies <= 1 {
|
||||||
|
// no longer capturing
|
||||||
|
let last_state = base.capture.load(std::sync::atomic::Ordering::SeqCst);
|
||||||
|
let rounded_state = last_state.floor();
|
||||||
|
base.capture.store(rounded_state, std::sync::atomic::Ordering::SeqCst);
|
||||||
|
generic.broadcast(
|
||||||
|
rlnl::event_code::NetworkEvent::TeamBaseCaptureStop,
|
||||||
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
|
&rlnl::events::ingame::TeamBaseState {
|
||||||
|
base_team_or_mining_point_index: base_id,
|
||||||
|
current_progress: rlnl::types::ByteFloat::from(last_state),
|
||||||
|
max_progress: rlnl::types::ByteFloat::from(4.0),
|
||||||
|
},
|
||||||
|
true
|
||||||
|
).await;
|
||||||
|
/*generic.broadcast(
|
||||||
|
rlnl::event_code::NetworkEvent::TeamBaseCaptureReset,
|
||||||
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
|
&rlnl::events::ingame::TeamBaseState {
|
||||||
|
base_team_or_mining_point_index: base_id,
|
||||||
|
current_progress: rlnl::types::ByteFloat::from(rounded_state),
|
||||||
|
max_progress: rlnl::types::ByteFloat::from(4.0),
|
||||||
|
},
|
||||||
|
true
|
||||||
|
).await;*/
|
||||||
|
generic.broadcast(
|
||||||
|
rlnl::event_code::NetworkEvent::TeamBaseState,
|
||||||
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
|
&rlnl::events::ingame::TeamBaseState {
|
||||||
|
base_team_or_mining_point_index: base_id,
|
||||||
|
current_progress: rlnl::types::ByteFloat::from(rounded_state + f32::EPSILON),
|
||||||
|
max_progress: rlnl::types::ByteFloat::from(4.0),
|
||||||
|
},
|
||||||
|
true
|
||||||
|
).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn tick(&self, generic: &crate::matches::GenericGamemodeEngine<EliminationLogic>) {
|
||||||
|
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; }
|
||||||
|
self.last_tick.store(last_tick + (delta * Self::TICK_MS), std::sync::atomic::Ordering::SeqCst);
|
||||||
|
delta
|
||||||
|
};
|
||||||
|
for (team, base) in self.bases.iter() {
|
||||||
|
if base.friendlies.load(std::sync::atomic::Ordering::SeqCst) > 0 { continue; }
|
||||||
|
if base.enemies.load(std::sync::atomic::Ordering::SeqCst) <= 0 { continue; }
|
||||||
|
let to_add = (delta as f32) * (Self::TICK_MS as f32) * base.percent_per_second * 4.0 / (100.0 * 1000.0);
|
||||||
|
let pre_add = base.capture.fetch_add(to_add, std::sync::atomic::Ordering::SeqCst);
|
||||||
|
let post_add = pre_add + to_add;
|
||||||
|
if post_add >= 4.0 {
|
||||||
|
log::info!("Team {}'s base was captured in game {}", team, generic.game_guid());
|
||||||
|
base.capture.store(4.0, std::sync::atomic::Ordering::SeqCst);
|
||||||
|
let data = rlnl::events::ingame::TeamBaseState {
|
||||||
|
base_team_or_mining_point_index: *team,
|
||||||
|
current_progress: rlnl::types::ByteFloat::from(4.0),
|
||||||
|
max_progress: rlnl::types::ByteFloat::from(4.0),
|
||||||
|
};
|
||||||
|
generic.broadcast(
|
||||||
|
rlnl::event_code::NetworkEvent::TeamBaseFinalSectionComplete,
|
||||||
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
|
&data,
|
||||||
|
true
|
||||||
|
).await;
|
||||||
|
let winning_team = if *team == 0 { 1u8 } else { 0u8 };
|
||||||
|
let winning_team_i32 = winning_team as i32;
|
||||||
|
let win_data = rlnl::events::ingame::GameLoseWin {
|
||||||
|
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 {
|
||||||
|
rlnl::event_code::NetworkEvent::GameWon
|
||||||
|
} else {
|
||||||
|
rlnl::event_code::NetworkEvent::GameLost
|
||||||
|
};
|
||||||
|
crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data(
|
||||||
|
&win_data,
|
||||||
|
event,
|
||||||
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
|
&conn.connection.connection
|
||||||
|
).await);
|
||||||
|
}
|
||||||
|
generic.game_done();
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
let data = rlnl::events::ingame::TeamBaseState {
|
||||||
|
base_team_or_mining_point_index: *team,
|
||||||
|
current_progress: rlnl::types::ByteFloat::from(post_add),
|
||||||
|
max_progress: rlnl::types::ByteFloat::from(4.0),
|
||||||
|
};
|
||||||
|
let is_section_complete = pre_add.floor() < post_add.floor();
|
||||||
|
generic.broadcast(
|
||||||
|
if is_section_complete { rlnl::event_code::NetworkEvent::TeamBaseSectionComplete } else { rlnl::event_code::NetworkEvent::TeamBaseState },
|
||||||
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
|
&data,
|
||||||
|
true
|
||||||
|
).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn teams(&self) -> std::collections::HashSet<u8> {
|
||||||
|
self.bases.keys().map(|x| *x).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EliminationLogic {
|
pub struct EliminationLogic {
|
||||||
tracked: PlayerTracker,
|
tracked: PlayerTracker,
|
||||||
|
bases: BaseTracker,
|
||||||
game_duration: std::time::Duration,
|
game_duration: std::time::Duration,
|
||||||
respawn_full_heal_duration: f32,
|
respawn_full_heal_duration: f32,
|
||||||
respawn_heal_duration: f32,
|
respawn_heal_duration: f32,
|
||||||
@@ -67,12 +347,18 @@ pub struct EliminationLogic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EliminationLogic {
|
impl EliminationLogic {
|
||||||
pub fn new(config: &oj_rc_core::data::game_mode::GameModeConfig) -> Self {
|
pub fn new(config: &oj_rc_core::data::game_mode::GameModeConfig, map: &oj_rc_core::persist::config::MapConfig) -> Self {
|
||||||
let dur = std::time::Duration::from_secs((config.game_time_minutes as u64) * 60);
|
let dur = std::time::Duration::from_secs((config.game_time_minutes as u64) * 60);
|
||||||
let fake_end = (chrono::Utc::now() + dur).timestamp();
|
let fake_end = (chrono::Utc::now() + dur).timestamp();
|
||||||
Self {
|
Self {
|
||||||
tracked: PlayerTracker {
|
tracked: PlayerTracker {
|
||||||
alive: tokio::sync::Mutex::new(std::collections::HashMap::new()),
|
alive: tokio::sync::Mutex::new(std::collections::HashMap::new()),
|
||||||
|
in_base: tokio::sync::RwLock::new(std::collections::HashMap::new()),
|
||||||
|
},
|
||||||
|
bases: BaseTracker {
|
||||||
|
bases: map.bases.iter().map(|(team, base)| (*team, BaseCounters::new(base.1))).collect(),
|
||||||
|
last_tick: std::sync::atomic::AtomicI64::new(i64::MIN),
|
||||||
|
is_baseless: map.bases.is_empty(),
|
||||||
},
|
},
|
||||||
game_duration: dur,
|
game_duration: dur,
|
||||||
respawn_full_heal_duration: config.respawn_full_heal_duration,
|
respawn_full_heal_duration: config.respawn_full_heal_duration,
|
||||||
@@ -90,6 +376,45 @@ impl EliminationLogic {
|
|||||||
}
|
}
|
||||||
*lock = None;
|
*lock = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn on_last_player_gone(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, conn: &crate::matches::generic::UserConnection) {
|
||||||
|
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
|
||||||
|
end_reason: rlnl::types::GameEndReason::NoPlayersRemaining,
|
||||||
|
};
|
||||||
|
crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data(
|
||||||
|
&data,
|
||||||
|
rlnl::event_code::NetworkEvent::GameLost,
|
||||||
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
|
&conn.connection.connection
|
||||||
|
).await);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_win_info(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, winning_team: u8) {
|
||||||
|
generic.game_done();
|
||||||
|
self.abort_timer_sync().await;
|
||||||
|
let data = rlnl::events::ingame::GameLoseWin {
|
||||||
|
winning_team,
|
||||||
|
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 {
|
||||||
|
rlnl::event_code::NetworkEvent::GameWon
|
||||||
|
} else {
|
||||||
|
rlnl::event_code::NetworkEvent::GameLost
|
||||||
|
};
|
||||||
|
crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data(
|
||||||
|
&data,
|
||||||
|
event,
|
||||||
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
|
&conn.connection.connection
|
||||||
|
).await);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
@@ -111,30 +436,9 @@ impl CustomGameLogic for EliminationLogic {
|
|||||||
self.tracked.destroy_vehicle(&player.descriptor).await;
|
self.tracked.destroy_vehicle(&player.descriptor).await;
|
||||||
if let Some(winning_team) = self.tracked.winner_team().await {
|
if let Some(winning_team) = self.tracked.winner_team().await {
|
||||||
log::info!("Team {} has won sudden death game {} because player {} left", winning_team, generic.game_guid(), player.descriptor.player_id);
|
log::info!("Team {} has won sudden death game {} because player {} left", winning_team, generic.game_guid(), player.descriptor.player_id);
|
||||||
let data = rlnl::events::ingame::GameLoseWin {
|
self.send_win_info(generic, winning_team).await;
|
||||||
winning_team,
|
|
||||||
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 {
|
|
||||||
rlnl::event_code::NetworkEvent::GameWon
|
|
||||||
} else {
|
|
||||||
rlnl::event_code::NetworkEvent::GameLost
|
|
||||||
};
|
|
||||||
crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data(
|
|
||||||
&data,
|
|
||||||
event,
|
|
||||||
literustlib::packet::Property::ReliableOrdered,
|
|
||||||
&conn.connection.connection
|
|
||||||
).await);
|
|
||||||
}
|
|
||||||
generic.game_done();
|
|
||||||
self.abort_timer_sync().await;
|
|
||||||
} else if self.tracked.alive_count().await.is_empty() {
|
} else if self.tracked.alive_count().await.is_empty() {
|
||||||
log::debug!("Everyone is dead, so long and thanks for all the fish");
|
self.on_last_player_gone(generic, player).await;
|
||||||
generic.game_done();
|
|
||||||
self.abort_timer_sync().await;
|
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -154,42 +458,13 @@ impl CustomGameLogic for EliminationLogic {
|
|||||||
).await;
|
).await;
|
||||||
if let Some(winning_team) = self.tracked.winner_team().await {
|
if let Some(winning_team) = self.tracked.winner_team().await {
|
||||||
log::info!("Team {} has won sudden death game {}", winning_team, generic.game_guid());
|
log::info!("Team {} has won sudden death game {}", winning_team, generic.game_guid());
|
||||||
let data = rlnl::events::ingame::GameLoseWin {
|
self.send_win_info(generic, winning_team).await;
|
||||||
winning_team,
|
|
||||||
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 {
|
|
||||||
rlnl::event_code::NetworkEvent::GameWon
|
|
||||||
} else {
|
|
||||||
rlnl::event_code::NetworkEvent::GameLost
|
|
||||||
};
|
|
||||||
crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data(
|
|
||||||
&data,
|
|
||||||
event,
|
|
||||||
literustlib::packet::Property::ReliableOrdered,
|
|
||||||
&conn.connection.connection
|
|
||||||
).await);
|
|
||||||
}
|
|
||||||
generic.game_done();
|
|
||||||
} else {
|
} else {
|
||||||
log::info!("Player {} has been destroyed in sudden death game {}", victim, generic.game_guid());
|
log::info!("Player {} has been destroyed in sudden death game {}", victim, generic.game_guid());
|
||||||
let data = rlnl::events::ingame::GameLoseWin {
|
if self.tracked.alive_count().await.is_empty() {
|
||||||
winning_team: if conn.descriptor.team == 0 { 1 } else { 0 }, // always the other team
|
self.on_last_player_gone(generic, conn).await;
|
||||||
end_reason: rlnl::types::GameEndReason::NoPlayersRemaining,
|
}
|
||||||
};
|
|
||||||
crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data(
|
|
||||||
&data,
|
|
||||||
rlnl::event_code::NetworkEvent::GameLost,
|
|
||||||
literustlib::packet::Property::ReliableOrdered,
|
|
||||||
&conn.connection.connection
|
|
||||||
).await);
|
|
||||||
}
|
}
|
||||||
} else if self.tracked.alive_count().await.is_empty() {
|
|
||||||
log::debug!("Everyone is dead, this must be the West Seth was talking about!");
|
|
||||||
generic.game_done();
|
|
||||||
self.abort_timer_sync().await;
|
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -227,7 +502,7 @@ impl CustomGameLogic for EliminationLogic {
|
|||||||
senders.push((conn.connection.clone(), conn.state.clone()));
|
senders.push((conn.connection.clone(), conn.state.clone()));
|
||||||
}
|
}
|
||||||
let game_end = game_start + self.game_duration;
|
let game_end = game_start + self.game_duration;
|
||||||
let teams = self.tracked.teams().await;
|
let teams = self.bases.teams();
|
||||||
let extra_packets = teams.iter().map(|team| crate::matches::RlnlPacket {
|
let extra_packets = teams.iter().map(|team| crate::matches::RlnlPacket {
|
||||||
event: rlnl::event_code::NetworkEvent::TeamBaseInitialise,
|
event: rlnl::event_code::NetworkEvent::TeamBaseInitialise,
|
||||||
property: literustlib::packet::Property::ReliableOrdered,
|
property: literustlib::packet::Property::ReliableOrdered,
|
||||||
@@ -257,7 +532,48 @@ impl CustomGameLogic for EliminationLogic {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn on_motion(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _motion: &rlnl::machine_motion::MachineMotion) -> bool {
|
async fn on_motion(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, motion: &rlnl::machine_motion::MachineMotion, location: (f32, f32, f32)) -> bool {
|
||||||
|
if generic.is_game_done() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if self.bases.is_baseless {
|
||||||
|
return true; // don't bother trying to track whether players are in bases since there are no bases
|
||||||
|
}
|
||||||
|
// ignore dead or invalid players
|
||||||
|
if let Some(player_team) = self.tracked.player_team(motion.player_id).await {
|
||||||
|
let mut now_in_base = None;
|
||||||
|
for (&team, base) in generic.map_config.bases.iter() {
|
||||||
|
if crate::matches::GenericGamemodeEngine::<Self>::is_in(&location, &base.0) {
|
||||||
|
now_in_base = Some(team);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let in_base = self.tracked.swap_is_in_base(motion.player_id, now_in_base).await;
|
||||||
|
if let Some(team) = in_base {
|
||||||
|
// was in a base
|
||||||
|
if let Some(now_team) = now_in_base {
|
||||||
|
if now_team != team {
|
||||||
|
// changed bases !?
|
||||||
|
self.bases.on_exit(generic, team, player_team == team, motion.player_id).await;
|
||||||
|
self.bases.on_enter(generic, now_team, player_team == now_team, motion.player_id).await;
|
||||||
|
}
|
||||||
|
// still in same base
|
||||||
|
} else {
|
||||||
|
// player has left the base
|
||||||
|
self.bases.on_exit(generic, team, player_team == team, motion.player_id).await;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let Some(now_team) = now_in_base {
|
||||||
|
// player entered a base
|
||||||
|
self.bases.on_enter(generic, now_team, player_team == now_team, motion.player_id).await;
|
||||||
|
}
|
||||||
|
// still outside of base
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.bases.tick(generic).await;
|
||||||
|
if generic.is_game_done() {
|
||||||
|
self.abort_timer_sync().await;
|
||||||
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ impl CustomGameLogic for NoOpLogic {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn on_motion(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _motion: &rlnl::machine_motion::MachineMotion) -> bool {
|
async fn on_motion(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _motion: &rlnl::machine_motion::MachineMotion, _location: (f32, f32, f32)) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user