//! # Server thrust handling //! The bulk of the actual thrust work is done in the thrust solver on the client. //! It sends us it's `ThrustSolution` when it's done; in this file we process it //! and apply it to the physics simulation. use crate::attachment::Parts; use crate::ecs::Part; use crate::ecs::thruster::{PartThrusters, Thruster, ThrusterOfPart}; use crate::prelude::*; use crate::server::ConnectedNetworkEntity; use crate::thrust::ThrustSolution; pub fn server_thrust_plugin(app: &mut App) { app .add_systems(Update, process_thrust_events) .add_systems(Update, apply_thrust_solutions); } /// Handle new `ThrustSolution`s from clients as they come in fn process_thrust_events( mut events: MessageReader>, clients: Query<&ConnectedNetworkEntity>, q_ls_me: Query>, mut commands: Commands ) { // For each event from a client... for FromClient { client_id, message: thrust_solution, } in events.read() { // Find the hearty entity of the player... let player_hearty_entity = match client_id { ClientId::Client(client_entity) => { let ConnectedNetworkEntity { game_entity: player_hearty_entity, } = clients.get(*client_entity).unwrap(); player_hearty_entity }, ClientId::Server => &q_ls_me.iter().next().unwrap() }; // and apply the new thrust solution commands.entity(*player_hearty_entity).insert(thrust_solution.clone()); trace!("installed thrust solution {:?}", thrust_solution); } } /// Find all players, and apply their current `ThrustSolution`s fn apply_thrust_solutions( players: Query<(Entity, &ThrustSolution, Option<&Parts>)>, thrusters: Query<(&Thruster, &ThrusterOfPart, &GlobalTransform)>, mut parts: Query>, ) { //gizmos.arrow_2d( // thruster.2.translation().xy(), // thruster.2.translation().xy() + thruster.2.rotation().mul_vec3(thruster.0.thrust_vector.extend(0.0)).xy(), // color // ); // Iterate through all players with a ThrustSolution for (player_entity, thrust_solution, maybe_parts) in players { // If their reported thrust solution didn't converge, do nothing if !thrust_solution.converged { debug!(?player_entity, "ignoring unconverged thrust solution"); } // If it's empty, exit early (no reason to waste time) if thrust_solution.thrusters_on.is_empty() { continue } // Collect a list of all parts in the player's ship, to validate // the thrusters the player sends let attached_parts = if let Some(parts) = maybe_parts { parts.as_slice() } else { &[] }; // Go over each active thruster in the thrust solution... for thruster_entity in &thrust_solution.thrusters_on { // ...load it's data... let Ok((thruster_info, parent_part, thruster_transform)) = thrusters.get(*thruster_entity) else { debug!(?thruster_entity, "couldn't find thruster to apply force"); continue }; // ...verify this user is allowed to control this thruster... let parent_is_hearty = parent_part.0 == player_entity; let parent_is_in_ship = attached_parts.contains(&parent_part.0); if !(parent_is_hearty || parent_is_in_ship) { debug!(?thruster_entity, "ignoring disallowed thruster action"); continue } // not permitted // great, it's valid; apply the force let mut part_forces = parts.get_mut(parent_part.0).unwrap(); part_forces.apply_force_at_point( (thruster_transform.rotation() * thruster_info.thrust_vector.extend(0.0)).xy(), thruster_transform.translation().xy() ); } } }