//! # 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<FromClient<ThrustSolution>>,
clients: Query<&ConnectedNetworkEntity>,
q_ls_me: Query<Entity, With<crate::ecs::Me>>,
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<Forces, With<Part>>,
) {
//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()
);
}
}
}