diff --git a/CHANGELOG.md b/CHANGELOG.md
index f7b124f829..06727e531e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -56,6 +56,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Trading over long distances using ghost characters or client-side exploits is no longer possible
 - Merchant cost percentages displayed as floored, whole numbers
 - Bodies of water no longer contain black chunks on the voxel minimap.
+- Agents can flee once again, and more appropriately
 
 ## [0.11.0] - 2021-09-11
 
diff --git a/assets/voxygen/i18n/en/npc.ron b/assets/voxygen/i18n/en/npc.ron
index b2f5683c05..1b508fc50a 100644
--- a/assets/voxygen/i18n/en/npc.ron
+++ b/assets/voxygen/i18n/en/npc.ron
@@ -186,5 +186,13 @@
             "Turn around if you want to live!",
             "You're not welcome here!",
         ],
+        "npc.speech.cultist_low_health_fleeing": [
+            "Retreat for the cause!",
+            "Retreat!",
+            "Curse you!",
+            "I will curse you in the afterlife!",
+            "I must rest!",
+            "They're too strong!",
+        ]
     }
 )
diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs
index 556537ed63..89255e99a9 100644
--- a/common/src/comp/agent.rs
+++ b/common/src/comp/agent.rs
@@ -1,5 +1,8 @@
 use crate::{
-    comp::{humanoid, quadruped_low, quadruped_medium, quadruped_small, ship, Body, UtteranceKind},
+    comp::{
+        biped_small, bird_medium, humanoid, quadruped_low, quadruped_medium, quadruped_small, ship,
+        Body, UtteranceKind,
+    },
     path::Chaser,
     rtsim::RtSimController,
     trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult},
@@ -189,72 +192,82 @@ impl<'a> From<&'a Body> for Psyche {
         Self {
             flee_health: match body {
                 Body::Humanoid(humanoid) => match humanoid.species {
-                    humanoid::Species::Danari => 0.1,
-                    humanoid::Species::Dwarf => 0.2,
-                    humanoid::Species::Elf => 0.3,
+                    humanoid::Species::Danari => 0.4,
+                    humanoid::Species::Dwarf => 0.3,
+                    humanoid::Species::Elf => 0.4,
                     humanoid::Species::Human => 0.4,
-                    humanoid::Species::Orc => 0.1,
-                    humanoid::Species::Undead => 0.1,
+                    humanoid::Species::Orc => 0.3,
+                    humanoid::Species::Undead => 0.3,
                 },
                 Body::QuadrupedSmall(quadruped_small) => match quadruped_small.species {
                     quadruped_small::Species::Pig => 0.5,
                     quadruped_small::Species::Fox => 0.7,
-                    quadruped_small::Species::Sheep => 0.5,
-                    quadruped_small::Species::Boar => 0.2,
-                    quadruped_small::Species::Jackalope => 0.6,
+                    quadruped_small::Species::Sheep => 0.6,
+                    quadruped_small::Species::Boar => 0.1,
+                    quadruped_small::Species::Jackalope => 0.0,
                     quadruped_small::Species::Skunk => 0.4,
-                    quadruped_small::Species::Cat => 0.8,
-                    quadruped_small::Species::Batfox => 0.4,
+                    quadruped_small::Species::Cat => 0.9,
+                    quadruped_small::Species::Batfox => 0.1,
                     quadruped_small::Species::Raccoon => 0.6,
-                    quadruped_small::Species::Quokka => 0.6,
-                    quadruped_small::Species::Dodarock => 0.1,
+                    quadruped_small::Species::Dodarock => 0.0,
                     quadruped_small::Species::Holladon => 0.0,
-                    quadruped_small::Species::Hyena => 0.6,
-                    quadruped_small::Species::Rabbit => 0.9,
+                    quadruped_small::Species::Hyena => 0.2,
+                    quadruped_small::Species::Dog => 0.8,
+                    quadruped_small::Species::Rabbit => 0.7,
                     quadruped_small::Species::Truffler => 0.2,
-                    quadruped_small::Species::Frog => 0.6,
-                    quadruped_small::Species::Hare => 0.8,
+                    quadruped_small::Species::Hare => 0.3,
                     quadruped_small::Species::Goat => 0.5,
+                    quadruped_small::Species::Porcupine => 0.7,
+                    quadruped_small::Species::Turtle => 0.7,
+                    // FIXME: This is to balance for enemy rats in dunegeons
+                    // Normal rats should probably always flee.
+                    quadruped_small::Species::Rat => 0.0,
+                    quadruped_small::Species::Beaver => 0.7,
                     _ => 1.0,
                 },
                 Body::QuadrupedMedium(quadruped_medium) => match quadruped_medium.species {
-                    quadruped_medium::Species::Tuskram => 0.3,
                     quadruped_medium::Species::Frostfang => 0.1,
-                    quadruped_medium::Species::Mouflon => 0.3,
                     quadruped_medium::Species::Catoblepas => 0.2,
-                    quadruped_medium::Species::Deer => 0.4,
-                    quadruped_medium::Species::Hirdrasil => 0.3,
-                    quadruped_medium::Species::Donkey => 0.3,
-                    quadruped_medium::Species::Camel => 0.3,
-                    quadruped_medium::Species::Zebra => 0.3,
-                    quadruped_medium::Species::Antelope => 0.4,
-                    quadruped_medium::Species::Horse => 0.3,
-                    quadruped_medium::Species::Cattle => 0.3,
                     quadruped_medium::Species::Darkhound => 0.1,
                     quadruped_medium::Species::Dreadhorn => 0.2,
-                    quadruped_medium::Species::Snowleopard => 0.3,
-                    quadruped_medium::Species::Llama => 0.4,
-                    quadruped_medium::Species::Alpaca => 0.4,
-                    _ => 0.5,
+                    quadruped_medium::Species::Bonerattler => 0.0,
+                    quadruped_medium::Species::Tiger => 0.1,
+                    _ => 0.3,
                 },
                 Body::QuadrupedLow(quadruped_low) => match quadruped_low.species {
-                    quadruped_low::Species::Salamander => 0.3,
+                    quadruped_low::Species::Salamander => 0.2,
                     quadruped_low::Species::Monitor => 0.3,
-                    quadruped_low::Species::Asp => 0.1,
                     quadruped_low::Species::Pangolin => 0.6,
-                    _ => 0.4,
+                    quadruped_low::Species::Tortoise => 0.2,
+                    quadruped_low::Species::Rocksnapper => 0.05,
+                    quadruped_low::Species::Asp => 0.05,
+                    _ => 0.0,
+                },
+                Body::BipedSmall(biped_small) => match biped_small.species {
+                    biped_small::Species::Gnarling => 0.2,
+                    biped_small::Species::Adlet => 0.2,
+                    biped_small::Species::Haniwa => 0.1,
+                    biped_small::Species::Sahagin => 0.1,
+                    biped_small::Species::Myrmidon => 0.0,
+                    biped_small::Species::Husk => 0.0,
+                    _ => 0.5,
+                },
+                Body::BirdMedium(bird_medium) => match bird_medium.species {
+                    bird_medium::Species::Goose => 0.4,
+                    bird_medium::Species::Peacock => 0.4,
+                    bird_medium::Species::Eagle => 0.3,
+                    bird_medium::Species::Parrot => 0.8,
+                    _ => 0.5,
                 },
-                Body::BipedSmall(_) => 0.5,
-                Body::BirdMedium(_) => 0.5,
                 Body::BirdLarge(_) => 0.1,
-                Body::FishMedium(_) => 0.85,
                 Body::FishSmall(_) => 1.0,
+                Body::FishMedium(_) => 0.75,
                 Body::BipedLarge(_) => 0.0,
                 Body::Object(_) => 0.0,
                 Body::Golem(_) => 0.0,
                 Body::Theropod(_) => 0.0,
-                Body::Dragon(_) => 0.0,
                 Body::Ship(_) => 0.0,
+                Body::Dragon(_) => 0.0,
             },
             sight_dist: 40.0,
             listen_dist: 30.0,
@@ -477,16 +490,14 @@ impl Agent {
         self
     }
 
-    pub fn with_no_flee(mut self, no_flee: bool) -> Self {
-        if no_flee {
-            self.set_no_flee();
+    pub fn with_no_flee_if(mut self, condition: bool) -> Self {
+        if condition {
+            self.psyche.flee_health = 0.0;
         }
         self
     }
 
-    pub fn set_no_flee(&mut self) { self.psyche.flee_health = 0.0; }
-
-    // TODO: Get rid of this method, it does weird things
+    // FIXME: Only one of *three* things in this method sets a location.
     pub fn with_destination(mut self, pos: Vec3<f32>) -> Self {
         self.psyche.flee_health = 0.0;
         self.rtsim_controller = RtSimController::with_destination(pos);
diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs
index fcd04410d9..066e3680d5 100644
--- a/common/src/comp/body.rs
+++ b/common/src/comp/body.rs
@@ -511,7 +511,24 @@ impl Body {
                 quadruped_small::Species::Holladon => 80,
                 quadruped_small::Species::Hyena => 45,
                 quadruped_small::Species::Truffler => 45,
-                _ => 40,
+                quadruped_small::Species::Fox => 15,
+                quadruped_small::Species::Cat => 25,
+                quadruped_small::Species::Quokka => 10,
+                // FIXME: I would have set rats to 5, but that makes enemy ones in dungeons too
+                // easy. Put this back when the two types of rats are distinguishable.
+                quadruped_small::Species::Rat => 20,
+                quadruped_small::Species::Jackalope => 30,
+                quadruped_small::Species::Hare => 15,
+                quadruped_small::Species::Rabbit => 10,
+                quadruped_small::Species::Frog => 5,
+                quadruped_small::Species::Axolotl => 5,
+                quadruped_small::Species::Gecko => 5,
+                quadruped_small::Species::Squirrel => 10,
+                quadruped_small::Species::Porcupine => 15,
+                quadruped_small::Species::Beaver => 15,
+                quadruped_small::Species::Dog => 30,
+                quadruped_small::Species::Sheep => 30,
+                _ => 20,
             },
             Body::QuadrupedMedium(quadruped_medium) => match quadruped_medium.species {
                 quadruped_medium::Species::Grolgar => 90,
@@ -519,9 +536,9 @@ impl Body {
                 quadruped_medium::Species::Tiger => 70,
                 quadruped_medium::Species::Lion => 90,
                 quadruped_medium::Species::Tarasque => 150,
-                quadruped_medium::Species::Wolf => 55,
+                quadruped_medium::Species::Wolf => 45,
                 quadruped_medium::Species::Frostfang => 40,
-                quadruped_medium::Species::Mouflon => 50,
+                quadruped_medium::Species::Mouflon => 40,
                 quadruped_medium::Species::Catoblepas => 100,
                 quadruped_medium::Species::Bonerattler => 50,
                 quadruped_medium::Species::Deer => 50,
@@ -545,21 +562,20 @@ impl Body {
                 _ => 70,
             },
             Body::BirdMedium(bird_medium) => match bird_medium.species {
-                bird_medium::Species::Chicken => 30,
-                bird_medium::Species::Duck => 30,
                 bird_medium::Species::Goose => 30,
-                bird_medium::Species::Parrot => 25,
                 bird_medium::Species::Peacock => 35,
                 bird_medium::Species::Eagle => 45,
-                _ => 25,
+                bird_medium::Species::Owl => 45,
+                bird_medium::Species::Duck => 10,
+                _ => 15,
             },
-            Body::FishMedium(_) => 25,
+            Body::FishMedium(_) => 15,
             Body::Dragon(_) => 500,
             Body::BirdLarge(bird_large) => match bird_large.species {
                 bird_large::Species::Roc => 280,
                 _ => 300,
             },
-            Body::FishSmall(_) => 2,
+            Body::FishSmall(_) => 3,
             Body::BipedLarge(biped_large) => match biped_large.species {
                 biped_large::Species::Ogre => 320,
                 biped_large::Species::Cyclops => 320,
diff --git a/common/src/states/basic_summon.rs b/common/src/states/basic_summon.rs
index f5f9768a2f..1a203ade97 100644
--- a/common/src/states/basic_summon.rs
+++ b/common/src/states/basic_summon.rs
@@ -180,7 +180,7 @@ impl CharacterBehavior for Data {
                             agent: Some(
                                 comp::Agent::from_body(&body)
                                     .with_behavior(Behavior::from(BehaviorCapability::SPEAK))
-                                    .with_no_flee(true),
+                                    .with_no_flee_if(true),
                             ),
                             alignment: comp::Alignment::Owned(*data.uid),
                             scale: self
diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs
index cf47605a51..710b0e353c 100644
--- a/common/src/states/utils.rs
+++ b/common/src/states/utils.rs
@@ -38,10 +38,17 @@ impl Body {
                 quadruped_small::Species::Axolotl => 70.0,
                 quadruped_small::Species::Pig => 70.0,
                 quadruped_small::Species::Sheep => 70.0,
-                quadruped_small::Species::Cat => 70.0,
                 quadruped_small::Species::Truffler => 70.0,
                 quadruped_small::Species::Fungome => 70.0,
                 quadruped_small::Species::Goat => 80.0,
+                quadruped_small::Species::Raccoon => 100.0,
+                quadruped_small::Species::Dodarock => 80.0,
+                quadruped_small::Species::Frog => 150.0,
+                quadruped_small::Species::Porcupine => 100.0,
+                quadruped_small::Species::Beaver => 100.0,
+                quadruped_small::Species::Rabbit => 110.0,
+                quadruped_small::Species::Cat => 150.0,
+                quadruped_small::Species::Quokka => 100.0,
                 _ => 125.0,
             },
             Body::QuadrupedMedium(quadruped_medium) => match quadruped_medium.species {
@@ -111,7 +118,7 @@ impl Body {
                 quadruped_low::Species::Asp => 110.0,
                 quadruped_low::Species::Tortoise => 60.0,
                 quadruped_low::Species::Rocksnapper => 70.0,
-                quadruped_low::Species::Pangolin => 120.0,
+                quadruped_low::Species::Pangolin => 90.0,
                 quadruped_low::Species::Maneater => 80.0,
                 quadruped_low::Species::Sandshark => 160.0,
                 quadruped_low::Species::Hakulaq => 140.0,
diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs
index 41a18b9eeb..2b48d79844 100644
--- a/server/src/sys/agent.rs
+++ b/server/src/sys/agent.rs
@@ -67,7 +67,6 @@ struct AgentData<'a> {
     alignment: Option<&'a Alignment>,
     traversal_config: TraversalConfig,
     scale: f32,
-    flees: bool,
     damage: f32,
     light_emitter: Option<&'a LightEmitter>,
     glider_equipped: bool,
@@ -319,10 +318,6 @@ impl<'a> System<'a> for Sys {
                         can_climb: body.map_or(false, Body::can_climb),
                         can_fly: body.map_or(false, |b| b.fly_thrust().is_some()),
                     };
-
-                    let flees = alignment.map_or(true, |a| {
-                        !matches!(a, Alignment::Enemy | Alignment::Owned(_))
-                    });
                     let health_fraction = health.map_or(1.0, Health::fraction);
                     let rtsim_entity = read_data
                         .rtsim_entities
@@ -356,7 +351,6 @@ impl<'a> System<'a> for Sys {
                         alignment: alignment.as_ref(),
                         traversal_config,
                         scale,
-                        flees,
                         damage: health_fraction,
                         light_emitter,
                         glider_equipped,
@@ -398,19 +392,15 @@ impl<'a> System<'a> for Sys {
                                         event_emitter| {
                         if let Some(tgt_pos) = read_data.positions.get(target) {
                             let dist_sqrd = pos.0.distance_squared(tgt_pos.0);
-                            // If really far away drop everything and follow
-                            if dist_sqrd > (2.0 * MAX_FOLLOW_DIST).powi(2) {
-                                agent.bearing = Vec2::zero();
+                            let too_far_away = dist_sqrd > (MAX_FOLLOW_DIST).powi(2);
+
+                            // If too far away, then follow
+                            if too_far_away {
                                 data.follow(agent, controller, &read_data.terrain, tgt_pos);
-                            // Attack target's attacker
+                            // Else, attack target's attacker (if there is one)
                             } else if entity_was_attacked(target, &read_data) {
                                 data.attack_target_attacker(agent, &read_data, controller);
-                            // Follow owner if too far away and not
-                            // fighting
-                            } else if dist_sqrd > MAX_FOLLOW_DIST.powi(2) {
-                                data.follow(agent, controller, &read_data.terrain, tgt_pos);
-
-                            // Otherwise just idle
+                            // Otherwise, just idle
                             } else {
                                 idle(agent, controller, event_emitter);
                             }
@@ -424,7 +414,7 @@ impl<'a> System<'a> for Sys {
                          controller: &mut Controller,
                          event_emitter| {
                             if let Some(tgt_health) = read_data.healths.get(target) {
-                                // If target is dead, leave it
+                                // If target is dead, forget them
                                 if tgt_health.is_dead {
                                     if let Some(tgt_stats) =
                                         data.rtsim_entity.and(read_data.stats.get(target))
@@ -432,13 +422,10 @@ impl<'a> System<'a> for Sys {
                                         rtsim_forget_enemy(&tgt_stats.name, agent);
                                     }
                                     agent.target = None;
-                                // If the target is hostile
-                                // (either based on alignment or if
-                                // the target just attacked)
+                                // Else, if target is hostile, hostile tree
                                 } else if hostile {
                                     data.hostile_tree(agent, controller, &read_data, event_emitter);
-                                // Target is something worth following
-                                // methinks
+                                // Else, if owned, act as pet to them
                                 } else if let Some(Alignment::Owned(uid)) = data.alignment {
                                     if read_data.uids.get(target) == Some(uid) {
                                         react_as_pet(agent, target, controller, event_emitter);
@@ -481,9 +468,9 @@ impl<'a> System<'a> for Sys {
                                     if let Some(attacker) =
                                         read_data.uid_allocator.retrieve_entity_internal(by.uid().0)
                                     {
-                                        // If the target is dead or in a safezone, remove the
-                                        // target and idle.
-                                        if should_stop_attacking(attacker, &read_data) {
+                                        // If target is dead or invulnerable (for now, this only
+                                        // means safezone), untarget them and idle.
+                                        if is_dead_or_invulnerable(attacker, &read_data) {
                                             agent.target = None;
                                         } else if let Some(tgt_pos) =
                                             read_data.positions.get(attacker)
@@ -495,9 +482,8 @@ impl<'a> System<'a> for Sys {
                                             }
 
                                             // Determine whether the new target should be a priority
-                                            // over the old one
-                                            // (i.e: because it's either close or because they
-                                            // attacked us)
+                                            // over the old one (i.e: because it's either close or
+                                            // because they attacked us)
                                             let more_dangerous_than_old_target =
                                                 agent.target.map_or(true, |old_tgt| {
                                                     if let Some(old_tgt_pos) =
@@ -606,8 +592,9 @@ impl<'a> AgentData<'a> {
         decrement_awareness(agent);
         forget_old_sounds(agent, read_data);
 
+        let small_chance = thread_rng().gen_bool(0.1);
         // Set owner if no target
-        if agent.target.is_none() && thread_rng().gen_bool(0.1) {
+        if agent.target.is_none() && small_chance {
             if let Some(Alignment::Owned(owner)) = self.alignment {
                 if let Some(owner) = get_entity_by_id(owner.id(), read_data) {
                     agent.target = build_target(owner, false, read_data.time.0, false);
@@ -641,7 +628,9 @@ impl<'a> AgentData<'a> {
         }
 
         // If we receive a new interaction, start the interaction timer
-        if can_speak(agent) && self.recv_interaction(agent, controller, read_data, event_emitter) {
+        if allowed_to_speak(agent)
+            && self.recv_interaction(agent, controller, read_data, event_emitter)
+        {
             agent.timer.start(read_data.time.0, TimerAction::Interact);
         }
 
@@ -714,22 +703,23 @@ impl<'a> AgentData<'a> {
                     .psyche
                     .aggro_dist
                     .map_or(true, |ad| dist_sq < ad.powi(2));
-                // If, at any point, the target comes closer than the aggro distance, switch to
-                // aggro mode
+
                 if in_aggro_range {
                     *aggro_on = true;
                 }
                 let aggro_on = *aggro_on;
 
-                if self.damage.min(1.0) < agent.psyche.flee_health && self.flees {
-                    // Should the agent flee?
-                    if agent.action_state.timer == 0.0 && can_speak(agent) {
-                        self.chat_general("npc.speech.villager_under_attack", event_emitter);
-                        self.emit_scream(read_data.time.0, event_emitter);
+                let should_flee = self.damage.min(1.0) < agent.psyche.flee_health;
+                if should_flee {
+                    let has_opportunity_to_flee = agent.action_state.timer < FLEE_DURATION;
+                    let within_flee_distance = dist_sq < MAX_FLEE_DIST.powi(2);
+
+                    // FIXME: Using the action state timer to see if an agent is allowed to speak is
+                    // a hack.
+                    if agent.action_state.timer == 0.0 {
+                        self.cry_out(agent, read_data.time.0, event_emitter);
                         agent.action_state.timer = 0.01;
-                    } else if agent.action_state.timer < FLEE_DURATION
-                        || dist_sq < MAX_FLEE_DIST.powi(2)
-                    {
+                    } else if within_flee_distance && has_opportunity_to_flee {
                         self.flee(agent, controller, &read_data.terrain, tgt_pos);
                         agent.action_state.timer += read_data.dt.0;
                     } else {
@@ -737,36 +727,30 @@ impl<'a> AgentData<'a> {
                         agent.target = None;
                         self.idle(agent, controller, read_data);
                     }
-                } else if should_stop_attacking(target, read_data) {
-                    if can_speak(agent) {
-                        let msg = "npc.speech.villager_enemy_killed".to_string();
-                        event_emitter
-                            .emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg)));
-                    }
+                } else if is_dead(target, read_data) {
+                    self.exclaim_relief_about_enemy_dead(agent, event_emitter);
+                    agent.target = None;
+                    self.idle(agent, controller, read_data);
+                } else if is_invulnerable(target, read_data) {
                     agent.target = None;
                     self.idle(agent, controller, read_data);
                 } else {
-                    // Potentially choose a new target
-                    if read_data.time.0 - selected_at > RETARGETING_THRESHOLD_SECONDS
-                        && !in_aggro_range
-                    {
+                    let is_time_to_retarget =
+                        read_data.time.0 - selected_at > RETARGETING_THRESHOLD_SECONDS;
+
+                    if !in_aggro_range && is_time_to_retarget {
                         self.choose_target(agent, controller, read_data, event_emitter);
                     }
 
+                    // FIXME: This check being a pre-requisite to attack() prevents agents from
+                    // supporting their buddies when necessary. For example, village guards will
+                    // literally watch villagers and other guards be slaughtered until these victims
+                    // get within aggro range (which is what aggro_on checks) or get too far.
                     if aggro_on {
                         let target_data = build_target_data(target, tgt_pos, read_data);
                         self.attack(agent, controller, &target_data, read_data);
                     } else {
-                        // If we're not yet aggro-ed, strike a menacing pose
-                        if thread_rng().gen::<f32>() < read_data.dt.0 * 0.25 {
-                            self.chat_general_if_can_speak(
-                                agent,
-                                "npc.speech.menacing",
-                                event_emitter,
-                            );
-                            controller.push_event(ControlEvent::Utterance(UtteranceKind::Angry));
-                        }
-                        self.menacing(agent, controller, read_data, target, tgt_pos);
+                        self.menacing(agent, target, controller, read_data, event_emitter);
                     }
                 }
             }
@@ -1040,7 +1024,7 @@ impl<'a> AgentData<'a> {
         let msg = agent.inbox.pop_front();
         match msg {
             Some(AgentEvent::Talk(by, subject)) => {
-                if can_speak(agent) {
+                if allowed_to_speak(agent) {
                     if let Some(target) = get_entity_by_id(by.id(), read_data) {
                         agent.target = build_target(target, false, read_data.time.0, false);
 
@@ -1088,25 +1072,25 @@ impl<'a> AgentData<'a> {
                                                     destination_name
                                                 )
                                             };
-                                        self.chat_general(msg, event_emitter);
+                                        self.chat_npc(msg, event_emitter);
                                     } else if agent.behavior.can_trade() {
                                         if !agent.behavior.is(BehaviorState::TRADING) {
                                             controller.events.push(ControlEvent::InitiateInvite(
                                                 by,
                                                 InviteKind::Trade,
                                             ));
-                                            self.chat_general(
+                                            self.chat_npc(
                                                 "npc.speech.merchant_advertisement",
                                                 event_emitter,
                                             );
                                         } else {
-                                            self.chat_general(
+                                            self.chat_npc(
                                                 "npc.speech.merchant_busy",
                                                 event_emitter,
                                             );
                                         }
                                     } else {
-                                        self.chat_general("npc.speech.villager", event_emitter);
+                                        self.chat_npc("npc.speech.villager", event_emitter);
                                     }
                                 },
                                 Subject::Trade => {
@@ -1116,12 +1100,12 @@ impl<'a> AgentData<'a> {
                                                 by,
                                                 InviteKind::Trade,
                                             ));
-                                            self.chat_general(
+                                            self.chat_npc(
                                                 "npc.speech.merchant_advertisement",
                                                 event_emitter,
                                             );
                                         } else {
-                                            self.chat_general(
+                                            self.chat_npc(
                                                 "npc.speech.merchant_busy",
                                                 event_emitter,
                                             );
@@ -1129,7 +1113,7 @@ impl<'a> AgentData<'a> {
                                     } else {
                                         // TODO: maybe make some travellers willing to trade with
                                         // simpler goods like potions
-                                        self.chat_general(
+                                        self.chat_npc(
                                             "npc.speech.villager_decline_trade",
                                             event_emitter,
                                         );
@@ -1181,7 +1165,7 @@ impl<'a> AgentData<'a> {
                                                 MemoryItem::Mood { state } => state.describe(),
                                                 _ => "".to_string(),
                                             };
-                                            self.chat_general(msg, event_emitter);
+                                            self.chat_npc(msg, event_emitter);
                                         }
                                     }
                                 },
@@ -1195,7 +1179,7 @@ impl<'a> AgentData<'a> {
                                             "{} ? I think it's {} {} from here!",
                                             location.name, dist, dir
                                         );
-                                        self.chat_general(msg, event_emitter);
+                                        self.chat_npc(msg, event_emitter);
                                     }
                                 },
                                 Subject::Person(person) => {
@@ -1230,7 +1214,7 @@ impl<'a> AgentData<'a> {
                                                 person.name()
                                             )
                                         };
-                                        self.chat_general(msg, event_emitter);
+                                        self.chat_npc(msg, event_emitter);
                                     }
                                 },
                                 Subject::Work => {},
@@ -1257,9 +1241,9 @@ impl<'a> AgentData<'a> {
                         controller
                             .events
                             .push(ControlEvent::InviteResponse(InviteResponse::Decline));
-                        self.chat_general_if_can_speak(
-                            agent,
+                        self.chat_npc_if_allowed_to_speak(
                             "npc.speech.merchant_busy",
+                            agent,
                             event_emitter,
                         );
                     }
@@ -1268,9 +1252,9 @@ impl<'a> AgentData<'a> {
                     controller
                         .events
                         .push(ControlEvent::InviteResponse(InviteResponse::Decline));
-                    self.chat_general_if_can_speak(
-                        agent,
+                    self.chat_npc_if_allowed_to_speak(
                         "npc.speech.villager_decline_trade",
+                        agent,
                         event_emitter,
                     );
                 }
@@ -1288,13 +1272,10 @@ impl<'a> AgentData<'a> {
                 if agent.behavior.is(BehaviorState::TRADING) {
                     match result {
                         TradeResult::Completed => {
-                            self.chat_general(
-                                "npc.speech.merchant_trade_successful",
-                                event_emitter,
-                            );
+                            self.chat_npc("npc.speech.merchant_trade_successful", event_emitter);
                         },
                         _ => {
-                            self.chat_general("npc.speech.merchant_trade_declined", event_emitter);
+                            self.chat_npc("npc.speech.merchant_trade_declined", event_emitter);
                         },
                     }
                     agent.behavior.unset(BehaviorState::TRADING);
@@ -1390,19 +1371,27 @@ impl<'a> AgentData<'a> {
 
     fn menacing(
         &self,
-        _agent: &mut Agent,
+        agent: &Agent,
+        target: EcsEntity,
         controller: &mut Controller,
         read_data: &ReadData,
-        target: EcsEntity,
-        _tgt_pos: &Pos,
+        event_emitter: &mut Emitter<ServerEvent>,
     ) {
+        let max_move = 0.5;
+        let move_dir = controller.inputs.move_dir;
+        let move_dir_mag = move_dir.magnitude();
+        let small_chance = thread_rng().gen::<f32>() < read_data.dt.0 * 0.25;
+
         self.look_toward(controller, read_data, target);
         controller.actions.push(ControlAction::Wield);
 
-        let max_move = 0.5;
-        let move_dir_mag = controller.inputs.move_dir.magnitude();
         if move_dir_mag > max_move {
-            controller.inputs.move_dir = max_move * controller.inputs.move_dir / move_dir_mag;
+            controller.inputs.move_dir = max_move * move_dir / move_dir_mag;
+        }
+
+        if small_chance {
+            self.chat_npc_if_allowed_to_speak("npc.speech.menacing", agent, event_emitter);
+            controller.push_event(ControlEvent::Utterance(UtteranceKind::Angry));
         }
     }
 
@@ -1573,7 +1562,7 @@ impl<'a> AgentData<'a> {
                     || in_listen_dist(e_pos, e_char_state, e_inventory)
             };
 
-        let owners_hostile = |e_alignment: Option<&Alignment>| {
+        let is_owner_hostile = |e_alignment: Option<&Alignment>| {
             try_owner_alignment(self.alignment, read_data).map_or(false, |owner_alignment| {
                 try_owner_alignment(e_alignment, read_data).map_or(false, |e_owner_alignment| {
                     owner_alignment.hostile_towards(*e_owner_alignment)
@@ -1581,17 +1570,18 @@ impl<'a> AgentData<'a> {
             })
         };
 
-        let guard_duty = |e_health: &Health, e_alignment: Option<&Alignment>| {
-            // I'm a guard and a villager is in distress
-            let other_is_npc = matches!(e_alignment, Some(Alignment::Npc));
-            let remembers_damage = read_data.time.0 - e_health.last_change.time.0 < 5.0;
-            let need_help = read_data.stats.get(*self.entity).map_or(false, |stats| {
-                stats.name == "Guard" && other_is_npc && remembers_damage
-            });
-
+        let guard_defending_villager = |e_health: &Health, e_alignment: Option<&Alignment>| {
+            let i_am_a_guard = read_data
+                .stats
+                .get(*self.entity)
+                .map_or(false, |stats| stats.name == "Guard");
+            let other_is_a_villager = matches!(e_alignment, Some(Alignment::Npc));
+            let villager_has_taken_damage = e_health.last_change.time.0 < 5.0;
             let attacker_of = |health: &Health| health.last_change.damage_by();
 
-            need_help
+            let i_should_defend = i_am_a_guard && other_is_a_villager && villager_has_taken_damage;
+
+            i_should_defend
                 .then(|| {
                     attacker_of(e_health)
                         .and_then(|damage_contributor| {
@@ -1617,15 +1607,14 @@ impl<'a> AgentData<'a> {
                         .remembers_fight_with_character(&target_stats.name)
                     {
                         rtsim_new_enemy(&target_stats.name, agent, read_data);
-                        if can_speak(agent) {
-                            let message = format!(
+                        self.chat_npc_if_allowed_to_speak(
+                            format!(
                                 "{}! How dare you cross me again!",
                                 target_stats.name.clone()
-                            );
-                            event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
-                                *self.uid, message,
-                            )));
-                        }
+                            ),
+                            agent,
+                            event_emitter,
+                        );
                         true
                     } else {
                         false
@@ -1651,12 +1640,13 @@ impl<'a> AgentData<'a> {
                         if self.rtsim_entity.is_some() {
                             rtsim_new_enemy(&target_stats.name, agent, read_data);
                         }
-                        if can_speak(agent) {
-                            let message = "npc.speech.villager_cultist_alarm".to_string();
-                            event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
-                                *self.uid, message,
-                            )));
-                        }
+
+                        self.chat_npc_if_allowed_to_speak(
+                            "npc.speech.villager_cultist_alarm",
+                            agent,
+                            event_emitter,
+                        );
+
                         true
                     } else {
                         false
@@ -1677,13 +1667,13 @@ impl<'a> AgentData<'a> {
                 let can_target = within_reach(e_pos, e_char_state, e_inventory)
                     && entity != *self.entity
                     && !e_health.is_dead
-                    && !invulnerability_is_in_buffs(read_data.buffs.get(entity));
+                    && !is_invulnerable(entity, read_data);
 
                 if !can_target {
                     None
-                } else if owners_hostile(e_alignment) {
+                } else if is_owner_hostile(e_alignment) {
                     Some((entity, *e_pos))
-                } else if let Some(villain_info) = guard_duty(e_health, e_alignment) {
+                } else if let Some(villain_info) = guard_defending_villager(e_health, e_alignment) {
                     Some(villain_info)
                 } else if rtsim_remember(e_stats, agent, event_emitter)
                     || npc_sees_cultist(e_stats, e_inventory, agent, event_emitter)
@@ -3453,8 +3443,9 @@ impl<'a> AgentData<'a> {
         read_data: &ReadData,
     ) {
         if attack_data.dist_sqrd > 30.0_f32.powi(2) {
-            // If random chance and can see target
-            if thread_rng().gen_bool(0.05)
+            let small_chance = thread_rng().gen_bool(0.05);
+
+            if small_chance
                 && can_see_tgt(
                     &*read_data.terrain,
                     self.pos,
@@ -4166,20 +4157,20 @@ impl<'a> AgentData<'a> {
         controller: &mut Controller,
         read_data: &ReadData,
     ) {
-        // Currently this means that we are in a safezone
-        if invulnerability_is_in_buffs(read_data.buffs.get(*self.entity)) {
+        if is_invulnerable(*self.entity, read_data) {
             self.idle(agent, controller, read_data);
             return;
         }
 
         if let Some(sound) = agent.sounds_heard.last() {
-            // FIXME: Perhaps name should be a field of the agent, not stats
             if let Some(agent_stats) = read_data.stats.get(*self.entity) {
                 let sound_pos = Pos(sound.pos);
                 let dist_sqrd = self.pos.0.distance_squared(sound_pos.0);
 
+                // FIXME: We need to be able to change the name of a guard without breaking this
+                // logic The `Mark` enum from common::agent could be used to
+                // match with `agent::Mark::Guard`
                 let is_village_guard = agent_stats.name == *"Guard".to_string();
-                let is_neutral = self.flees && !is_village_guard;
                 let is_enemy = matches!(self.alignment, Some(Alignment::Enemy));
 
                 if is_enemy {
@@ -4193,7 +4184,7 @@ impl<'a> AgentData<'a> {
                     }
                 } else if is_village_guard {
                     self.follow(agent, controller, &read_data.terrain, &sound_pos);
-                } else if is_neutral {
+                } else if !is_village_guard {
                     let flee_health = agent.psyche.flee_health;
                     let close_enough = dist_sqrd < 35.0_f32.powi(2);
                     let sound_was_loud = sound.vol >= 10.0;
@@ -4230,7 +4221,7 @@ impl<'a> AgentData<'a> {
                         agent.target = build_target(attacker, true, read_data.time.0, true);
 
                         if let Some(tgt_pos) = read_data.positions.get(attacker) {
-                            if should_stop_attacking(attacker, read_data) {
+                            if is_dead_or_invulnerable(attacker, read_data) {
                                 agent.target = build_target(target, false, read_data.time.0, false);
 
                                 self.idle(agent, controller, read_data);
@@ -4330,14 +4321,14 @@ impl<'a> AgentData<'a> {
         }
     }
 
-    fn chat_general_if_can_speak(
+    fn chat_npc_if_allowed_to_speak(
         &self,
-        agent: &Agent,
         msg: impl ToString,
+        agent: &Agent,
         event_emitter: &mut Emitter<'_, ServerEvent>,
     ) -> bool {
-        if can_speak(agent) {
-            self.chat_general(msg, event_emitter);
+        if allowed_to_speak(agent) {
+            self.chat_npc(msg, event_emitter);
             true
         } else {
             false
@@ -4356,7 +4347,7 @@ impl<'a> AgentData<'a> {
         }
     }
 
-    fn chat_general(&self, msg: impl ToString, event_emitter: &mut Emitter<'_, ServerEvent>) {
+    fn chat_npc(&self, msg: impl ToString, event_emitter: &mut Emitter<'_, ServerEvent>) {
         event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
             *self.uid,
             msg.to_string(),
@@ -4375,6 +4366,43 @@ impl<'a> AgentData<'a> {
             });
         }
     }
+
+    fn cry_out(&self, agent: &Agent, time: f64, event_emitter: &mut Emitter<'_, ServerEvent>) {
+        let is_enemy = matches!(self.alignment, Some(Alignment::Enemy));
+        // FIXME: This is not necessarily a "villager"
+        let is_villager = matches!(self.alignment, Some(Alignment::Npc));
+
+        if is_enemy {
+            self.chat_npc_if_allowed_to_speak(
+                "npc.speech.cultist_low_health_fleeing",
+                agent,
+                event_emitter,
+            );
+        } else if is_villager {
+            self.chat_npc_if_allowed_to_speak(
+                "npc.speech.villager_under_attack",
+                agent,
+                event_emitter,
+            );
+            self.emit_scream(time, event_emitter);
+        }
+    }
+
+    fn exclaim_relief_about_enemy_dead(
+        &self,
+        agent: &Agent,
+        event_emitter: &mut Emitter<'_, ServerEvent>,
+    ) {
+        let is_villager = matches!(self.alignment, Some(Alignment::Npc));
+
+        if is_villager {
+            self.chat_npc_if_allowed_to_speak(
+                "npc.speech.villager_enemy_killed",
+                agent,
+                event_emitter,
+            );
+        }
+    }
 }
 
 fn rtsim_new_enemy(target_name: &str, agent: &mut Agent, read_data: &ReadData) {
@@ -4406,17 +4434,21 @@ fn can_see_tgt(terrain: &TerrainGrid, pos: &Pos, tgt_pos: &Pos, dist_sqrd: f32)
         >= dist_sqrd
 }
 
-// If target is dead or has invulnerability buff, returns true
-fn should_stop_attacking(target: EcsEntity, read_data: &ReadData) -> bool {
-    let health = read_data.healths.get(target);
-    let buffs = read_data.buffs.get(target);
+fn is_dead_or_invulnerable(entity: EcsEntity, read_data: &ReadData) -> bool {
+    is_dead(entity, read_data) || is_invulnerable(entity, read_data)
+}
 
-    health.map_or(true, |a| a.is_dead) || invulnerability_is_in_buffs(buffs)
+fn is_dead(entity: EcsEntity, read_data: &ReadData) -> bool {
+    let health = read_data.healths.get(entity);
+
+    health.map_or(false, |a| a.is_dead)
 }
 
 // FIXME: The logic that is used in this function and throughout the code
 // shouldn't be used to mean that a character is in a safezone.
-fn invulnerability_is_in_buffs(buffs: Option<&Buffs>) -> bool {
+fn is_invulnerable(entity: EcsEntity, read_data: &ReadData) -> bool {
+    let buffs = read_data.buffs.get(entity);
+
     buffs.map_or(false, |b| b.kinds.contains_key(&BuffKind::Invulnerability))
 }
 
@@ -4515,7 +4547,7 @@ fn build_target_data<'a>(
     }
 }
 
-fn can_speak(agent: &Agent) -> bool { agent.behavior.can(BehaviorCapability::SPEAK) }
+fn allowed_to_speak(agent: &Agent) -> bool { agent.behavior.can(BehaviorCapability::SPEAK) }
 
 fn get_entity_by_id(id: u64, read_data: &ReadData) -> Option<EcsEntity> {
     read_data.uid_allocator.retrieve_entity_internal(id)
diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs
index acafdf5fc6..868ed6270b 100644
--- a/server/src/sys/terrain.rs
+++ b/server/src/sys/terrain.rs
@@ -471,10 +471,10 @@ impl NpcData {
         let health = Some(comp::Health::new(body, health_scaling.unwrap_or(0)));
         let poise = comp::Poise::new(body);
 
+        // Allow Humanoid, BirdMedium, and Parrot to speak
         let can_speak = match body {
             comp::Body::Humanoid(_) => true,
             comp::Body::BirdMedium(bird_medium) => match bird_medium.species {
-                // Parrots like to have a word in this, too...
                 bird_medium::Species::Parrot => alignment == comp::Alignment::Npc,
                 _ => false,
             },
@@ -495,13 +495,13 @@ impl NpcData {
                         .with_trade_site(trade_for_site),
                 )
                 .with_patrol_origin(pos)
-                .with_no_flee(!matches!(agent_mark, Some(agent::Mark::Guard)))
+                .with_no_flee_if(matches!(agent_mark, Some(agent::Mark::Guard)))
         });
 
         let agent = if matches!(alignment, comp::Alignment::Enemy)
             && matches!(body, comp::Body::Humanoid(_))
         {
-            agent.map(|a| a.with_aggro_no_warn())
+            agent.map(|a| a.with_aggro_no_warn().with_no_flee_if(true))
         } else {
             agent
         };