From 9c2ce83430c7ceafc477c5fcc1353d652dc5aba0 Mon Sep 17 00:00:00 2001
From: Snowram <robin.gilh@gmail.com>
Date: Sat, 18 Sep 2021 16:16:20 +0200
Subject: [PATCH] Set projectile offsets in states instead of globally

---
 common/src/event.rs                       |  1 +
 common/src/states/basic_ranged.rs         | 35 ++++++++++++++++++++++-
 common/src/states/charged_ranged.rs       | 35 ++++++++++++++++++++++-
 common/src/states/repeater_ranged.rs      | 35 ++++++++++++++++++++++-
 server/src/events/entity_creation.rs      | 19 ++----------
 server/src/events/mod.rs                  |  5 +++-
 server/src/sys/agent.rs                   | 17 ++++++++---
 server/src/sys/object.rs                  |  1 +
 server/src/sys/wiring/dispatch_actions.rs |  3 +-
 9 files changed, 125 insertions(+), 26 deletions(-)

diff --git a/common/src/event.rs b/common/src/event.rs
index 3c243a4eb1..cdc068cba3 100644
--- a/common/src/event.rs
+++ b/common/src/event.rs
@@ -66,6 +66,7 @@ pub enum ServerEvent {
     Respawn(EcsEntity),
     Shoot {
         entity: EcsEntity,
+        pos: Pos,
         dir: Dir,
         body: comp::Body,
         light: Option<comp::LightEmitter>,
diff --git a/common/src/states/basic_ranged.rs b/common/src/states/basic_ranged.rs
index cf2bb06523..3e223fd51f 100644
--- a/common/src/states/basic_ranged.rs
+++ b/common/src/states/basic_ranged.rs
@@ -1,5 +1,5 @@
 use crate::{
-    comp::{Body, CharacterState, LightEmitter, ProjectileConstructor, StateUpdate},
+    comp::{Body, CharacterState, LightEmitter, Pos, ProjectileConstructor, StateUpdate},
     event::ServerEvent,
     states::{
         behavior::{CharacterBehavior, JoinData},
@@ -10,6 +10,7 @@ use crate::{
 use rand::{thread_rng, Rng};
 use serde::{Deserialize, Serialize};
 use std::time::Duration;
+use vek::*;
 
 /// Separated out to condense update portions of character state
 #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
@@ -83,6 +84,9 @@ impl CharacterBehavior for Data {
                     );
                     // Shoots all projectiles simultaneously
                     for i in 0..self.static_data.num_projectiles {
+                        // Gets offsets
+                        let body_offsets = projectile_offsets(data.body, update.ori.look_vec());
+                        let pos = Pos(data.pos.0 + body_offsets);
                         // Adds a slight spread to the projectiles. First projectile has no spread,
                         // and spread increases linearly with number of projectiles created.
                         let dir = Dir::from_unnormalized(data.inputs.look_dir.map(|x| {
@@ -95,6 +99,7 @@ impl CharacterBehavior for Data {
                         // Tells server to create and shoot the projectile
                         update.server_events.push_front(ServerEvent::Shoot {
                             entity: data.entity,
+                            pos,
                             dir,
                             body: self.static_data.projectile_body,
                             projectile: projectile.clone(),
@@ -141,3 +146,31 @@ impl CharacterBehavior for Data {
 fn reset_state(data: &Data, join: &JoinData, update: &mut StateUpdate) {
     handle_input(join, update, data.static_data.ability_info.input);
 }
+
+fn height_offset(body: &Body) -> f32 {
+    match body {
+        Body::Golem(_) => body.height() * 0.4,
+        _ => body.eye_height(),
+    }
+}
+
+pub fn projectile_offsets(body: &Body, ori: Vec3<f32>) -> Vec3<f32> {
+    let dim = body.dimensions();
+    // The width (shoulder to shoulder) and length (nose to tail)
+    let (width, length) = (dim.x, dim.y);
+    let body_radius = if length > width {
+        // Dachshund-like
+        body.max_radius()
+    } else {
+        // Cyclops-like
+        body.min_radius()
+    };
+
+    let body_offsets_z = height_offset(body);
+
+    Vec3::new(
+        body_radius * ori.x * 1.1,
+        body_radius * ori.y * 1.1,
+        body_offsets_z,
+    )
+}
diff --git a/common/src/states/charged_ranged.rs b/common/src/states/charged_ranged.rs
index 9ef1c430ef..60ee545ccd 100644
--- a/common/src/states/charged_ranged.rs
+++ b/common/src/states/charged_ranged.rs
@@ -1,7 +1,7 @@
 use crate::{
     comp::{
         projectile::ProjectileConstructor, Body, CharacterState, EnergyChange, EnergySource,
-        LightEmitter, StateUpdate,
+        LightEmitter, Pos, StateUpdate,
     },
     event::ServerEvent,
     states::{
@@ -11,6 +11,7 @@ use crate::{
 };
 use serde::{Deserialize, Serialize};
 use std::time::Duration;
+use vek::*;
 
 /// Separated out to condense update portions of character state
 #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
@@ -110,6 +111,9 @@ impl CharacterBehavior for Data {
                     let (crit_chance, crit_mult) =
                         get_crit_data(data, self.static_data.ability_info);
                     let buff_strength = get_buff_strength(data, self.static_data.ability_info);
+                    // Gets offsets
+                    let body_offsets = projectile_offsets(data.body, update.ori.look_vec());
+                    let pos = Pos(data.pos.0 + body_offsets);
                     let projectile = arrow.create_projectile(
                         Some(*data.uid),
                         crit_chance,
@@ -118,6 +122,7 @@ impl CharacterBehavior for Data {
                     );
                     update.server_events.push_front(ServerEvent::Shoot {
                         entity: data.entity,
+                        pos,
                         dir: data.inputs.look_dir,
                         body: self.static_data.projectile_body,
                         projectile,
@@ -187,3 +192,31 @@ impl CharacterBehavior for Data {
         update
     }
 }
+
+fn height_offset(body: &Body) -> f32 {
+    match body {
+        Body::Golem(_) => body.height() * 0.4,
+        _ => body.eye_height(),
+    }
+}
+
+pub fn projectile_offsets(body: &Body, ori: Vec3<f32>) -> Vec3<f32> {
+    let dim = body.dimensions();
+    // The width (shoulder to shoulder) and length (nose to tail)
+    let (width, length) = (dim.x, dim.y);
+    let body_radius = if length > width {
+        // Dachshund-like
+        body.max_radius()
+    } else {
+        // Cyclops-like
+        body.min_radius()
+    };
+
+    let body_offsets_z = height_offset(body);
+
+    Vec3::new(
+        body_radius * ori.x * 1.1,
+        body_radius * ori.y * 1.1,
+        body_offsets_z,
+    )
+}
diff --git a/common/src/states/repeater_ranged.rs b/common/src/states/repeater_ranged.rs
index 07471bcd25..5b443ca399 100644
--- a/common/src/states/repeater_ranged.rs
+++ b/common/src/states/repeater_ranged.rs
@@ -1,6 +1,6 @@
 use crate::{
     comp::{
-        Body, CharacterState, EnergyChange, EnergySource, LightEmitter, ProjectileConstructor,
+        Body, CharacterState, EnergyChange, EnergySource, LightEmitter, Pos, ProjectileConstructor,
         StateUpdate,
     },
     event::ServerEvent,
@@ -11,6 +11,7 @@ use crate::{
 };
 use serde::{Deserialize, Serialize};
 use std::time::Duration;
+use vek::*;
 
 #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
 /// Separated out to condense update portions of character state
@@ -91,6 +92,9 @@ impl CharacterBehavior for Data {
                     let (crit_chance, crit_mult) =
                         get_crit_data(data, self.static_data.ability_info);
                     let buff_strength = get_buff_strength(data, self.static_data.ability_info);
+                    // Gets offsets
+                    let body_offsets = projectile_offsets(data.body, update.ori.look_vec());
+                    let pos = Pos(data.pos.0 + body_offsets);
                     let projectile = self.static_data.projectile.create_projectile(
                         Some(*data.uid),
                         crit_chance,
@@ -99,6 +103,7 @@ impl CharacterBehavior for Data {
                     );
                     update.server_events.push_front(ServerEvent::Shoot {
                         entity: data.entity,
+                        pos,
                         dir: data.inputs.look_dir,
                         body: self.static_data.projectile_body,
                         projectile,
@@ -164,3 +169,31 @@ impl CharacterBehavior for Data {
         update
     }
 }
+
+fn height_offset(body: &Body) -> f32 {
+    match body {
+        Body::Golem(_) => body.height() * 0.4,
+        _ => body.eye_height(),
+    }
+}
+
+pub fn projectile_offsets(body: &Body, ori: Vec3<f32>) -> Vec3<f32> {
+    let dim = body.dimensions();
+    // The width (shoulder to shoulder) and length (nose to tail)
+    let (width, length) = (dim.x, dim.y);
+    let body_radius = if length > width {
+        // Dachshund-like
+        body.max_radius()
+    } else {
+        // Cyclops-like
+        body.min_radius()
+    };
+
+    let body_offsets_z = height_offset(body);
+
+    Vec3::new(
+        body_radius * ori.x * 1.1,
+        body_radius * ori.y * 1.1,
+        body_offsets_z,
+    )
+}
diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs
index 67ecf150c3..43294d0dff 100644
--- a/server/src/events/entity_creation.rs
+++ b/server/src/events/entity_creation.rs
@@ -173,6 +173,7 @@ pub fn handle_create_ship(
 pub fn handle_shoot(
     server: &mut Server,
     entity: EcsEntity,
+    pos: Pos,
     dir: Dir,
     body: Body,
     light: Option<LightEmitter>,
@@ -182,11 +183,7 @@ pub fn handle_shoot(
 ) {
     let state = server.state_mut();
 
-    let mut pos = if let Some(pos) = state.ecs().read_storage::<Pos>().get(entity) {
-        pos.0
-    } else {
-        return;
-    };
+    let pos = pos.0;
 
     let vel = *dir * speed
         + state
@@ -201,18 +198,6 @@ pub fn handle_shoot(
         .write_resource::<Vec<Outcome>>()
         .push(Outcome::ProjectileShot { pos, body, vel });
 
-    let eye_height =
-        state
-            .ecs()
-            .read_storage::<comp::Body>()
-            .get(entity)
-            .map_or(0.0, |b| match b {
-                comp::Body::Golem(_) => b.height() * 0.45,
-                _ => b.eye_height(),
-            });
-
-    pos.z += eye_height;
-
     let mut builder = state.create_projectile(Pos(pos), Vel(vel), body, projectile);
     if let Some(light) = light {
         builder = builder.with(light)
diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs
index 504f593825..6846333301 100644
--- a/server/src/events/mod.rs
+++ b/server/src/events/mod.rs
@@ -71,13 +71,16 @@ impl Server {
                 ServerEvent::Bonk { pos, owner, target } => handle_bonk(self, pos, owner, target),
                 ServerEvent::Shoot {
                     entity,
+                    pos,
                     dir,
                     body,
                     light,
                     projectile,
                     speed,
                     object,
-                } => handle_shoot(self, entity, dir, body, light, projectile, speed, object),
+                } => handle_shoot(
+                    self, entity, pos, dir, body, light, projectile, speed, object,
+                ),
                 ServerEvent::Shockwave {
                     properties,
                     pos,
diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs
index bab6cdb37b..37da940657 100644
--- a/server/src/sys/agent.rs
+++ b/server/src/sys/agent.rs
@@ -28,7 +28,7 @@ use common::{
     path::TraversalConfig,
     resources::{DeltaTime, Time, TimeOfDay},
     rtsim::{Memory, MemoryItem, RtSimEntity, RtSimEvent},
-    states::{basic_beam, utils::StageSection},
+    states::{basic_beam, basic_ranged, charged_ranged, repeater_ranged, utils::StageSection},
     terrain::{Block, TerrainGrid},
     time::DayPeriod,
     trade::{TradeAction, TradePhase, TradeResult},
@@ -1830,7 +1830,10 @@ impl<'a> AgentData<'a> {
                     + charge_factor * c.static_data.scaled_projectile_speed;
                 aim_projectile(
                     projectile_speed,
-                    Vec3::new(self.pos.0.x, self.pos.0.y, self.pos.0.z + eye_offset),
+                    self.pos.0
+                        + self.body.map_or(Vec3::zero(), |body| {
+                            charged_ranged::projectile_offsets(body, self.ori.look_vec())
+                        }),
                     Vec3::new(
                         tgt_data.pos.0.x,
                         tgt_data.pos.0.y,
@@ -1842,7 +1845,10 @@ impl<'a> AgentData<'a> {
                 let projectile_speed = c.static_data.projectile_speed;
                 aim_projectile(
                     projectile_speed,
-                    Vec3::new(self.pos.0.x, self.pos.0.y, self.pos.0.z + eye_offset),
+                    self.pos.0
+                        + self.body.map_or(Vec3::zero(), |body| {
+                            basic_ranged::projectile_offsets(body, self.ori.look_vec())
+                        }),
                     Vec3::new(
                         tgt_data.pos.0.x,
                         tgt_data.pos.0.y,
@@ -1854,7 +1860,10 @@ impl<'a> AgentData<'a> {
                 let projectile_speed = c.static_data.projectile_speed;
                 aim_projectile(
                     projectile_speed,
-                    Vec3::new(self.pos.0.x, self.pos.0.y, self.pos.0.z + eye_offset),
+                    self.pos.0
+                        + self.body.map_or(Vec3::zero(), |body| {
+                            repeater_ranged::projectile_offsets(body, self.ori.look_vec())
+                        }),
                     Vec3::new(
                         tgt_data.pos.0.x,
                         tgt_data.pos.0.y,
diff --git a/server/src/sys/object.rs b/server/src/sys/object.rs
index dc99af33c6..0daf0a081f 100644
--- a/server/src/sys/object.rs
+++ b/server/src/sys/object.rs
@@ -115,6 +115,7 @@ impl<'a> System<'a> for Sys {
                                 .expect("nonzero vector should normalize");
                                 server_emitter.emit(ServerEvent::Shoot {
                                     entity,
+                                    pos: *pos,
                                     dir,
                                     body: Body::Object(object::Body::for_firework(*reagent)),
                                     light: Some(LightEmitter {
diff --git a/server/src/sys/wiring/dispatch_actions.rs b/server/src/sys/wiring/dispatch_actions.rs
index d5ae4dc6ad..ea28c4405a 100644
--- a/server/src/sys/wiring/dispatch_actions.rs
+++ b/server/src/sys/wiring/dispatch_actions.rs
@@ -13,7 +13,7 @@ use common::{
 use common_state::BlockChange;
 use hashbrown::HashMap;
 use specs::{join::Join, Entity, Read, Write};
-use vek::Rgb;
+use vek::{Rgb, Vec3};
 
 pub fn dispatch_actions(system_data: &mut WiringData) {
     let WiringData {
@@ -113,6 +113,7 @@ fn dispatch_action_spawn_projectile(
     // NOTE: constr in RFC is about Arrow projectile
     server_emitter.emit(ServerEvent::Shoot {
         entity,
+        pos: Pos(Vec3::zero()),
         dir: Dir::forward(),
         body: Body::Object(object::Body::Arrow),
         projectile: constr.create_projectile(None, 0.0, 1.0, 1.0),