From 697cf418c334f4046cb8d8f7339ccf40a76f9601 Mon Sep 17 00:00:00 2001
From: Joshua Barretto <joshua.s.barretto@gmail.com>
Date: Mon, 2 Aug 2021 13:08:39 +0100
Subject: [PATCH] Better Agent API

---
 common/src/comp/agent.rs                 | 55 ++++++++++++++----------
 common/src/comp/body/quadruped_low.rs    |  2 +-
 common/src/comp/body/quadruped_medium.rs |  2 +-
 common/src/comp/body/quadruped_small.rs  |  2 +-
 common/src/states/basic_summon.rs        | 11 +++--
 server/src/cmd.rs                        | 13 +++---
 server/src/pet.rs                        |  6 +--
 server/src/rtsim/tick.rs                 |  5 +--
 server/src/sys/terrain.rs                | 23 ++++++----
 9 files changed, 66 insertions(+), 53 deletions(-)

diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs
index 7226c44a9c..30473b8c8f 100644
--- a/common/src/comp/agent.rs
+++ b/common/src/comp/agent.rs
@@ -341,7 +341,7 @@ pub struct Target {
 }
 
 #[allow(clippy::type_complexity)]
-#[derive(Clone, Debug, Default)]
+#[derive(Clone, Debug)]
 pub struct Agent {
     pub rtsim_controller: RtSimController,
     pub patrol_origin: Option<Vec3<f32>>,
@@ -366,11 +366,43 @@ pub struct ActionState {
 }
 
 impl Agent {
+    pub fn from_body(body: &Body) -> Self {
+        Agent {
+            rtsim_controller: RtSimController::default(),
+            patrol_origin: None,
+            target: None,
+            chaser: Chaser::default(),
+            behavior: Behavior::default(),
+            psyche: Psyche::from(body),
+            inbox: VecDeque::new(),
+            action_state: ActionState::default(),
+            bearing: Vec2::zero(),
+            sounds_heard: Vec::new(),
+            awareness: 0.0,
+            position_pid_controller: None,
+        }
+    }
+
     pub fn with_patrol_origin(mut self, origin: Vec3<f32>) -> Self {
         self.patrol_origin = Some(origin);
         self
     }
 
+    pub fn with_behavior(mut self, behavior: Behavior) -> Self {
+        self.behavior = behavior;
+        self
+    }
+
+    pub fn with_no_flee(mut self, no_flee: bool) -> Self {
+        if no_flee {
+            self.set_no_flee();
+        }
+        self
+    }
+
+    pub fn set_no_flee(&mut self) { self.psyche.flee_health = 0.0; }
+
+    // TODO: Get rid of this method, it does weird things
     pub fn with_destination(mut self, pos: Vec3<f32>) -> Self {
         self.psyche.flee_health = 0.0;
         self.rtsim_controller = RtSimController::with_destination(pos);
@@ -386,27 +418,6 @@ impl Agent {
         self.position_pid_controller = Some(pid);
         self
     }
-
-    pub fn new(
-        patrol_origin: Option<Vec3<f32>>,
-        body: &Body,
-        behavior: Behavior,
-        no_flee: bool,
-    ) -> Self {
-        Agent {
-            patrol_origin,
-            psyche: if no_flee {
-                Psyche {
-                    flee_health: 0.0,
-                    ..Psyche::from(body)
-                }
-            } else {
-                Psyche::from(body)
-            },
-            behavior,
-            ..Default::default()
-        }
-    }
 }
 
 impl Component for Agent {
diff --git a/common/src/comp/body/quadruped_low.rs b/common/src/comp/body/quadruped_low.rs
index 9b39604c0a..5ddd680500 100644
--- a/common/src/comp/body/quadruped_low.rs
+++ b/common/src/comp/body/quadruped_low.rs
@@ -1,7 +1,7 @@
 use crate::{make_case_elim, make_proj_elim};
 use rand::{seq::SliceRandom, thread_rng};
 use serde::{Deserialize, Serialize};
-use strum::{Display, EnumString};
+use strum_macros::{Display, EnumString};
 
 make_proj_elim!(
     body,
diff --git a/common/src/comp/body/quadruped_medium.rs b/common/src/comp/body/quadruped_medium.rs
index 7f8d36ea0f..708c55db9e 100644
--- a/common/src/comp/body/quadruped_medium.rs
+++ b/common/src/comp/body/quadruped_medium.rs
@@ -1,7 +1,7 @@
 use crate::{make_case_elim, make_proj_elim};
 use rand::{seq::SliceRandom, thread_rng};
 use serde::{Deserialize, Serialize};
-use strum::{Display, EnumString};
+use strum_macros::{Display, EnumString};
 
 make_proj_elim!(
     body,
diff --git a/common/src/comp/body/quadruped_small.rs b/common/src/comp/body/quadruped_small.rs
index 75a5428283..fe0cdd9404 100644
--- a/common/src/comp/body/quadruped_small.rs
+++ b/common/src/comp/body/quadruped_small.rs
@@ -1,7 +1,7 @@
 use crate::{make_case_elim, make_proj_elim};
 use rand::{seq::SliceRandom, thread_rng};
 use serde::{Deserialize, Serialize};
-use strum::{Display, EnumString};
+use strum_macros::{Display, EnumString};
 
 make_proj_elim!(
     body,
diff --git a/common/src/states/basic_summon.rs b/common/src/states/basic_summon.rs
index 35f012b5bf..562175c12c 100644
--- a/common/src/states/basic_summon.rs
+++ b/common/src/states/basic_summon.rs
@@ -174,12 +174,11 @@ impl CharacterBehavior for Data {
                             poise: comp::Poise::new(body),
                             loadout,
                             body,
-                            agent: Some(comp::Agent::new(
-                                None,
-                                &body,
-                                Behavior::from(BehaviorCapability::SPEAK),
-                                true,
-                            )),
+                            agent: Some(
+                                comp::Agent::from_body(&body)
+                                    .with_behavior(Behavior::from(BehaviorCapability::SPEAK))
+                                    .with_no_flee(true),
+                            ),
                             alignment: comp::Alignment::Owned(*data.uid),
                             scale: self
                                 .static_data
diff --git a/server/src/cmd.rs b/server/src/cmd.rs
index 2a7bb6f0a4..e319d789b9 100644
--- a/server/src/cmd.rs
+++ b/server/src/cmd.rs
@@ -1005,11 +1005,12 @@ fn handle_spawn(
 
             let ai = opt_ai.unwrap_or(true);
             let pos = position(server, target, "target")?;
-            let agent = if let comp::Alignment::Owned(_) | comp::Alignment::Npc = alignment {
-                comp::Agent::default()
-            } else {
-                comp::Agent::default().with_patrol_origin(pos.0)
-            };
+            let mut agent = comp::Agent::from_body(&body());
+
+            // If unowned, the agent should stay in a particular place
+            if !matches!(alignment, comp::Alignment::Owned(_)) {
+                agent = agent.with_patrol_origin(pos.0);
+            }
 
             for _ in 0..amount {
                 let vel = Vec3::new(
@@ -1160,7 +1161,7 @@ fn handle_spawn_airship(
     if let Some(pos) = destination {
         let (kp, ki, kd) = comp::agent::pid_coefficients(&comp::Body::Ship(ship));
         fn pure_z(sp: Vec3<f32>, pv: Vec3<f32>) -> f32 { (sp - pv).z }
-        let agent = comp::Agent::default()
+        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);
diff --git a/server/src/pet.rs b/server/src/pet.rs
index 1077b6fe9f..ffef6cc3d9 100644
--- a/server/src/pet.rs
+++ b/server/src/pet.rs
@@ -49,9 +49,9 @@ fn tame_pet_internal(ecs: &specs::World, pet_entity: Entity, owner: Entity, pet:
         .write_storage()
         .insert(pet_entity, pet.unwrap_or_default());
 
-    // TODO: Review whether we should be doing this or not, should the Agent always
-    // be overwritten when taming a pet?
-    let _ = ecs.write_storage().insert(pet_entity, Agent::default());
+    if let Some(agent) = ecs.write_storage::<Agent>().get_mut(pet_entity) {
+        agent.set_no_flee();
+    }
 
     // Add to group system
     let clients = ecs.read_storage::<Client>();
diff --git a/server/src/rtsim/tick.rs b/server/src/rtsim/tick.rs
index 5121a7193a..c8c9362bc3 100644
--- a/server/src/rtsim/tick.rs
+++ b/server/src/rtsim/tick.rs
@@ -103,15 +103,12 @@ impl<'a> System<'a> for Sys {
                 .map(|e| e as f32)
                 + Vec3::new(0.5, 0.5, body.flying_height());
             let pos = comp::Pos(spawn_pos);
-            let agent = Some(comp::Agent::new(
-                None,
-                &body,
+            let agent = Some(comp::Agent::from_body(&body).with_behavior(
                 if matches!(body, comp::Body::Humanoid(_)) {
                     Behavior::from(BehaviorCapability::SPEAK)
                 } else {
                     Behavior::default()
                 },
-                false,
             ));
 
             let rtsim_entity = Some(RtSimEntity(id));
diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs
index 589b742592..c68189a39c 100644
--- a/server/src/sys/terrain.rs
+++ b/server/src/sys/terrain.rs
@@ -296,16 +296,21 @@ impl<'a> System<'a> for Sys {
                     poise,
                     loadout,
                     agent: if entity.has_agency {
-                        Some(comp::Agent::new(
-                            Some(entity.pos),
-                            &body,
-                            Behavior::default()
-                                .maybe_with_capabilities(
-                                    can_speak.then(|| BehaviorCapability::SPEAK),
+                        Some(
+                            comp::Agent::from_body(&body)
+                                .with_behavior(
+                                    Behavior::default()
+                                        .maybe_with_capabilities(
+                                            can_speak.then(|| BehaviorCapability::SPEAK),
+                                        )
+                                        .with_trade_site(trade_for_site),
                                 )
-                                .with_trade_site(trade_for_site),
-                            matches!(entity.agent_mark, Some(agent::Mark::Guard)),
-                        ))
+                                .with_patrol_origin(entity.pos)
+                                .with_no_flee(!matches!(
+                                    entity.agent_mark,
+                                    Some(agent::Mark::Guard)
+                                )),
+                        )
                     } else {
                         None
                     },