From 4c0184dad163d1101d8a35e1b7f58d46c4b24065 Mon Sep 17 00:00:00 2001 From: "NG (Graham)" Date: Thu, 9 Jul 2026 22:08:30 -0400 Subject: [PATCH] Rotate players and reactors to face center of map by default --- rc_multiplayer/src/matches/generic.rs | 3 +- .../src/matches/modes/battle_arena.rs | 33 ++++++++++++++++--- rc_multiplayer/src/matches/modes/mod.rs | 28 ++++++++++++++-- rc_multiplayer/src/matches/modes/pit.rs | 5 ++- .../src/matches/modes/team_death_match.rs | 5 ++- 5 files changed, 65 insertions(+), 9 deletions(-) diff --git a/rc_multiplayer/src/matches/generic.rs b/rc_multiplayer/src/matches/generic.rs index e603002..5337e5c 100644 --- a/rc_multiplayer/src/matches/generic.rs +++ b/rc_multiplayer/src/matches/generic.rs @@ -1450,6 +1450,7 @@ impl GenericGamemodeEngine { } async fn send_sync_events(connection: UserSender, _player_id: u8, players: Vec>, extra_packets: Vec, map: std::sync::Arc) -> std::io::Result<()> { + let map_center = super::modes::calculate_center(map.spawns.values().flat_map(|x| x.iter())); let num_players = players.len() as u8; let sender = connection.rlnl(); sender.send_empty( @@ -1518,7 +1519,7 @@ impl GenericGamemodeEngine { &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 }, + rot: super::modes::looking_at_point(spawn, &map_center), }, owner: player.player_id, }, diff --git a/rc_multiplayer/src/matches/modes/battle_arena.rs b/rc_multiplayer/src/matches/modes/battle_arena.rs index 544a823..0f8ceb5 100644 --- a/rc_multiplayer/src/matches/modes/battle_arena.rs +++ b/rc_multiplayer/src/matches/modes/battle_arena.rs @@ -1124,6 +1124,7 @@ pub struct BattleArenaLogic { crystals: std::sync::Arc>, config: oj_rc_core::data::battle_arena_config::BattleArenaData, can_drop_shields: bool, + map_center: oj_rc_core::persist::config::Point, } impl BattleArenaLogic { @@ -1151,6 +1152,7 @@ impl BattleArenaLogic { can_drop_shields: super_conf.descriptor.overrides.as_ref() .map(|overrides| overrides.base_shields_go_down) .unwrap_or(true), + map_center: super::calculate_center(super_conf.map.spawns.values().flat_map(|x| x.iter())), } } @@ -1300,6 +1302,7 @@ impl BattleArenaLogic { respawn_timestamp, connections, spawn_point, + Some(self.map_center.clone()), player_id, player_desc.machine.is_alive.clone(), )); @@ -1458,12 +1461,34 @@ impl CustomGameLogic for BattleArenaLogic { 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(), + 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: generic.map_config.bases.get(&0) + .map(|(s, _)| { + let loc = oj_rc_core::persist::config::Point { + x: s.center.x, + y: s.center.y, + z: s.center.z, + }; + super::looking_at_point(&loc, &self.map_center) + }) + .unwrap_or((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(), + 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: generic.map_config.bases.get(&1) + .map(|(s, _)| { + let loc = oj_rc_core::persist::config::Point { + x: s.center.x, + y: s.center.y, + z: s.center.z, + }; + super::looking_at_point(&loc, &self.map_center) + }) + .unwrap_or((0.0, 0.0, 0.0, 0.0).into()), }, protonium_cube_health: self.config.protonium_health as i32, }), diff --git a/rc_multiplayer/src/matches/modes/mod.rs b/rc_multiplayer/src/matches/modes/mod.rs index f0368a6..3333278 100644 --- a/rc_multiplayer/src/matches/modes/mod.rs +++ b/rc_multiplayer/src/matches/modes/mod.rs @@ -16,13 +16,14 @@ pub use team_death_match::TeamDeathMatchLogic; pub(super) mod trackers; -async fn respawn_player_after(after: chrono::DateTime, players: Vec, spawn: oj_rc_core::persist::config::Point, player_id: u8, alive_flag: std::sync::Arc) { +async fn respawn_player_after(after: chrono::DateTime, players: Vec, spawn: oj_rc_core::persist::config::Point, looking_at: Option, player_id: u8, alive_flag: std::sync::Arc) { 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 }, + rot: looking_at.map(|at| looking_at_point(&spawn, &at)) + .unwrap_or(rlnl::types::CompressedQuat { x: 0, y: 0, z: 0 }), }, owner: player_id, }; @@ -38,3 +39,26 @@ async fn respawn_player_after(after: chrono::DateTime, players: Vec } alive_flag.store(true, std::sync::atomic::Ordering::Relaxed); } + +#[inline] +pub fn looking_at_point(loc: &oj_rc_core::persist::config::Point, at: &oj_rc_core::persist::config::Point) -> rlnl::types::CompressedQuat<4> { + let unit_quat = num_quaternion::UnitQuaternion::from_two_vectors(&[0.0, 0.0, 1.0], &[at.x - loc.x, 0.0, at.z - loc.z]); + let actual_quat = unit_quat.as_quaternion(); + rlnl::types::CompressedQuat::from((actual_quat.w, actual_quat.x, actual_quat.y, actual_quat.z)) +} + +pub fn calculate_center<'a>(points: impl Iterator) -> oj_rc_core::persist::config::Point { + let mut tally = (0.0_f64, 0.0_f64, 0.0_f64); + let mut total = 0; + for point in points { + total += 1; + tally.0 += point.x as f64; + tally.1 += point.y as f64; + tally.2 += point.z as f64; + } + oj_rc_core::persist::config::Point { + x: (tally.0 / total as f64) as _, + y: (tally.1 / total as f64) as _, + z: (tally.2 / total as f64) as _, + } +} diff --git a/rc_multiplayer/src/matches/modes/pit.rs b/rc_multiplayer/src/matches/modes/pit.rs index 8e102ad..dd1d639 100644 --- a/rc_multiplayer/src/matches/modes/pit.rs +++ b/rc_multiplayer/src/matches/modes/pit.rs @@ -160,6 +160,7 @@ pub struct PitLogic { settings: std::sync::Arc, timer_task: tokio::sync::Mutex>>, initial_spawns: Vec<(u8, oj_rc_core::persist::config::Point)>, // (player_id, spawn_point) + map_center: oj_rc_core::persist::config::Point, } impl PitLogic { @@ -172,6 +173,7 @@ impl PitLogic { timer_task: tokio::sync::Mutex::new(None), settings: pit_settings, initial_spawns: Self::generate_first_spawns(map, players), + map_center: super::calculate_center(map.pit_spawns.iter()), } } @@ -338,7 +340,7 @@ impl PitLogic { data: Box::new(rlnl::events::sync::SpawnPoint { pos: rlnl::types::PosQuatPair { pos: rlnl::types::CompressedVec3::from((spawn_point.x, spawn_point.y, spawn_point.z)), - rot: rlnl::types::CompressedQuat { x: 0, y: 0, z: 0 }, + rot: super::looking_at_point(spawn_point, &self.map_center), }, owner: *player_id, }) @@ -370,6 +372,7 @@ impl PitLogic { respawn_timestamp, connections, spawn_point, + Some(self.map_center.clone()), player_id, player_desc.machine.is_alive.clone(), )); diff --git a/rc_multiplayer/src/matches/modes/team_death_match.rs b/rc_multiplayer/src/matches/modes/team_death_match.rs index e056f02..855d37b 100644 --- a/rc_multiplayer/src/matches/modes/team_death_match.rs +++ b/rc_multiplayer/src/matches/modes/team_death_match.rs @@ -212,10 +212,11 @@ pub struct TeamDeathMatchLogic { score_tracking: ScoreTracker, player_tracking: PlayerTracker, surrender_tracking: super::trackers::SurrenderGameTracker, + map_center: oj_rc_core::persist::config::Point, } impl TeamDeathMatchLogic { - pub fn new(config: &oj_rc_core::data::game_mode::GameModeConfig, _map: &oj_rc_core::persist::config::MapConfig, players: &[oj_rc_core::persist::user::PlayerDescriptor], tdm_settings: std::sync::Arc) -> Self { + pub fn new(config: &oj_rc_core::data::game_mode::GameModeConfig, map: &oj_rc_core::persist::config::MapConfig, players: &[oj_rc_core::persist::user::PlayerDescriptor], tdm_settings: std::sync::Arc) -> Self { let self_destruct_is_kill = tdm_settings.self_destruct_is_kill; TeamDeathMatchLogic { respawn_heal_duration: config.respawn_heal_duration, @@ -226,6 +227,7 @@ impl TeamDeathMatchLogic { score_tracking: ScoreTracker::new(players, self_destruct_is_kill), player_tracking: PlayerTracker::new(players), surrender_tracking: super::trackers::SurrenderGameTracker::new(), + map_center: super::calculate_center(map.spawns.values().flat_map(|x| x.iter())), } } @@ -309,6 +311,7 @@ impl TeamDeathMatchLogic { respawn_timestamp, connections, spawn_point, + Some(self.map_center.clone()), player_id, player_desc.machine.is_alive.clone(), ));