diff --git a/common/src/cmd.rs b/common/src/cmd.rs index de60502c03..b2c2d0aefb 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -93,6 +93,7 @@ pub enum ChatCommand { Say, ServerPhysics, SetMotd, + Ship, Site, SkillPoint, SkillPreset, @@ -573,6 +574,11 @@ impl ChatCommand { "Set the server description", Some(Admin), ), + ChatCommand::Ship => cmd( + vec![Float("destination_degrees_ccw_of_east", 90.0, Optional)], + "Spawns a ship", + Some(Admin), + ), // Uses Message because site names can contain spaces, // which would be assumed to be separators otherwise ChatCommand::Site => cmd( @@ -716,6 +722,7 @@ impl ChatCommand { ChatCommand::Say => "say", ChatCommand::ServerPhysics => "server_physics", ChatCommand::SetMotd => "set_motd", + ChatCommand::Ship => "ship", ChatCommand::Site => "site", ChatCommand::SkillPoint => "skill_point", ChatCommand::SkillPreset => "skill_preset", diff --git a/common/src/comp/body/ship.rs b/common/src/comp/body/ship.rs index 5a69a38498..bc4d56a28b 100644 --- a/common/src/comp/body/ship.rs +++ b/common/src/comp/body/ship.rs @@ -17,6 +17,7 @@ pub const ALL_BODIES: [Body; 4] = [ ]; pub const ALL_AIRSHIPS: [Body; 2] = [Body::DefaultAirship, Body::AirBalloon]; +pub const ALL_SHIPS: [Body; 2] = [Body::SailBoat, Body::Galleon]; make_case_elim!( body, @@ -47,6 +48,8 @@ impl Body { *(&ALL_AIRSHIPS).choose(rng).unwrap() } + pub fn random_ship_with(rng: &mut impl rand::Rng) -> Self { *(&ALL_SHIPS).choose(rng).unwrap() } + /// Return the structure manifest that this ship uses. `None` means that it /// should be derived from the collider. pub fn manifest_entry(&self) -> Option<&'static str> { diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 97eff4ed8c..66889f0ef9 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -168,6 +168,7 @@ fn do_command( ChatCommand::Say => handle_say, ChatCommand::ServerPhysics => handle_server_physics, ChatCommand::SetMotd => handle_set_motd, + ChatCommand::Ship => handle_spawn_ship, ChatCommand::Site => handle_site, ChatCommand::SkillPoint => handle_skill_point, ChatCommand::SkillPreset => handle_skill_preset, @@ -1307,7 +1308,8 @@ fn handle_spawn_airship( 200.0, ) }); - let ship = comp::ship::Body::random(); + let mut rng = rand::thread_rng(); + let ship = comp::ship::Body::random_airship_with(&mut rng); let mut builder = server .state .create_ship(pos, ship, |ship| ship.make_collider(), true) @@ -1334,6 +1336,54 @@ fn handle_spawn_airship( Ok(()) } +fn handle_spawn_ship( + server: &mut Server, + client: EcsEntity, + target: EcsEntity, + args: Vec, + _action: &ChatCommand, +) -> CmdResult<()> { + let angle = parse_args!(args, f32); + let mut pos = position(server, target, "target")?; + pos.0.z += 50.0; + const DESTINATION_RADIUS: f32 = 2000.0; + let angle = angle.map(|a| a * std::f32::consts::PI / 180.0); + let destination = angle.map(|a| { + pos.0 + + Vec3::new( + DESTINATION_RADIUS * a.cos(), + DESTINATION_RADIUS * a.sin(), + 200.0, + ) + }); + let mut rng = rand::thread_rng(); + let ship = comp::ship::Body::random_ship_with(&mut rng); + let mut builder = server + .state + .create_ship(pos, ship, |ship| ship.make_collider(), true) + .with(LightEmitter { + col: Rgb::new(1.0, 0.65, 0.2), + strength: 2.0, + flicker: 1.0, + animated: true, + }); + if let Some(pos) = destination { + let (kp, ki, kd) = comp::agent::pid_coefficients(&comp::Body::Ship(ship)); + fn pure_z(sp: Vec3, pv: Vec3) -> f32 { (sp - pv).z } + let agent = comp::Agent::from_body(&comp::Body::Ship(ship)) + .with_destination(pos) + .with_position_pid_controller(comp::PidController::new(kp, ki, kd, pos, 0.0, pure_z)); + builder = builder.with(agent); + } + builder.build(); + + server.notify_client( + client, + ServerGeneral::server_msg(ChatType::CommandInfo, "Spawned a ship"), + ); + Ok(()) +} + fn handle_make_volume( server: &mut Server, client: EcsEntity,