mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Re-allow and improve fleeing.
This commit is contained in:
parent
de5ca67615
commit
b40d94dd53
@ -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
|
- Trading over long distances using ghost characters or client-side exploits is no longer possible
|
||||||
- Merchant cost percentages displayed as floored, whole numbers
|
- Merchant cost percentages displayed as floored, whole numbers
|
||||||
- Bodies of water no longer contain black chunks on the voxel minimap.
|
- 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
|
## [0.11.0] - 2021-09-11
|
||||||
|
|
||||||
|
@ -186,5 +186,13 @@
|
|||||||
"Turn around if you want to live!",
|
"Turn around if you want to live!",
|
||||||
"You're not welcome here!",
|
"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!",
|
||||||
|
]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
use crate::{
|
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,
|
path::Chaser,
|
||||||
rtsim::RtSimController,
|
rtsim::RtSimController,
|
||||||
trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult},
|
trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult},
|
||||||
@ -189,72 +192,82 @@ impl<'a> From<&'a Body> for Psyche {
|
|||||||
Self {
|
Self {
|
||||||
flee_health: match body {
|
flee_health: match body {
|
||||||
Body::Humanoid(humanoid) => match humanoid.species {
|
Body::Humanoid(humanoid) => match humanoid.species {
|
||||||
humanoid::Species::Danari => 0.1,
|
humanoid::Species::Danari => 0.4,
|
||||||
humanoid::Species::Dwarf => 0.2,
|
humanoid::Species::Dwarf => 0.3,
|
||||||
humanoid::Species::Elf => 0.3,
|
humanoid::Species::Elf => 0.4,
|
||||||
humanoid::Species::Human => 0.4,
|
humanoid::Species::Human => 0.4,
|
||||||
humanoid::Species::Orc => 0.1,
|
humanoid::Species::Orc => 0.3,
|
||||||
humanoid::Species::Undead => 0.1,
|
humanoid::Species::Undead => 0.3,
|
||||||
},
|
},
|
||||||
Body::QuadrupedSmall(quadruped_small) => match quadruped_small.species {
|
Body::QuadrupedSmall(quadruped_small) => match quadruped_small.species {
|
||||||
quadruped_small::Species::Pig => 0.5,
|
quadruped_small::Species::Pig => 0.5,
|
||||||
quadruped_small::Species::Fox => 0.7,
|
quadruped_small::Species::Fox => 0.7,
|
||||||
quadruped_small::Species::Sheep => 0.5,
|
quadruped_small::Species::Sheep => 0.6,
|
||||||
quadruped_small::Species::Boar => 0.2,
|
quadruped_small::Species::Boar => 0.1,
|
||||||
quadruped_small::Species::Jackalope => 0.6,
|
quadruped_small::Species::Jackalope => 0.0,
|
||||||
quadruped_small::Species::Skunk => 0.4,
|
quadruped_small::Species::Skunk => 0.4,
|
||||||
quadruped_small::Species::Cat => 0.8,
|
quadruped_small::Species::Cat => 0.9,
|
||||||
quadruped_small::Species::Batfox => 0.4,
|
quadruped_small::Species::Batfox => 0.1,
|
||||||
quadruped_small::Species::Raccoon => 0.6,
|
quadruped_small::Species::Raccoon => 0.6,
|
||||||
quadruped_small::Species::Quokka => 0.6,
|
quadruped_small::Species::Dodarock => 0.0,
|
||||||
quadruped_small::Species::Dodarock => 0.1,
|
|
||||||
quadruped_small::Species::Holladon => 0.0,
|
quadruped_small::Species::Holladon => 0.0,
|
||||||
quadruped_small::Species::Hyena => 0.6,
|
quadruped_small::Species::Hyena => 0.2,
|
||||||
quadruped_small::Species::Rabbit => 0.9,
|
quadruped_small::Species::Dog => 0.8,
|
||||||
|
quadruped_small::Species::Rabbit => 0.7,
|
||||||
quadruped_small::Species::Truffler => 0.2,
|
quadruped_small::Species::Truffler => 0.2,
|
||||||
quadruped_small::Species::Frog => 0.6,
|
quadruped_small::Species::Hare => 0.3,
|
||||||
quadruped_small::Species::Hare => 0.8,
|
|
||||||
quadruped_small::Species::Goat => 0.5,
|
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,
|
_ => 1.0,
|
||||||
},
|
},
|
||||||
Body::QuadrupedMedium(quadruped_medium) => match quadruped_medium.species {
|
Body::QuadrupedMedium(quadruped_medium) => match quadruped_medium.species {
|
||||||
quadruped_medium::Species::Tuskram => 0.3,
|
|
||||||
quadruped_medium::Species::Frostfang => 0.1,
|
quadruped_medium::Species::Frostfang => 0.1,
|
||||||
quadruped_medium::Species::Mouflon => 0.3,
|
|
||||||
quadruped_medium::Species::Catoblepas => 0.2,
|
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::Darkhound => 0.1,
|
||||||
quadruped_medium::Species::Dreadhorn => 0.2,
|
quadruped_medium::Species::Dreadhorn => 0.2,
|
||||||
quadruped_medium::Species::Snowleopard => 0.3,
|
quadruped_medium::Species::Bonerattler => 0.0,
|
||||||
quadruped_medium::Species::Llama => 0.4,
|
quadruped_medium::Species::Tiger => 0.1,
|
||||||
quadruped_medium::Species::Alpaca => 0.4,
|
_ => 0.3,
|
||||||
_ => 0.5,
|
|
||||||
},
|
},
|
||||||
Body::QuadrupedLow(quadruped_low) => match quadruped_low.species {
|
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::Monitor => 0.3,
|
||||||
quadruped_low::Species::Asp => 0.1,
|
|
||||||
quadruped_low::Species::Pangolin => 0.6,
|
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::BirdLarge(_) => 0.1,
|
||||||
Body::FishMedium(_) => 0.85,
|
|
||||||
Body::FishSmall(_) => 1.0,
|
Body::FishSmall(_) => 1.0,
|
||||||
|
Body::FishMedium(_) => 0.75,
|
||||||
Body::BipedLarge(_) => 0.0,
|
Body::BipedLarge(_) => 0.0,
|
||||||
Body::Object(_) => 0.0,
|
Body::Object(_) => 0.0,
|
||||||
Body::Golem(_) => 0.0,
|
Body::Golem(_) => 0.0,
|
||||||
Body::Theropod(_) => 0.0,
|
Body::Theropod(_) => 0.0,
|
||||||
Body::Dragon(_) => 0.0,
|
|
||||||
Body::Ship(_) => 0.0,
|
Body::Ship(_) => 0.0,
|
||||||
|
Body::Dragon(_) => 0.0,
|
||||||
},
|
},
|
||||||
sight_dist: 40.0,
|
sight_dist: 40.0,
|
||||||
listen_dist: 30.0,
|
listen_dist: 30.0,
|
||||||
@ -477,16 +490,14 @@ impl Agent {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_no_flee(mut self, no_flee: bool) -> Self {
|
pub fn with_no_flee_if(mut self, condition: bool) -> Self {
|
||||||
if no_flee {
|
if condition {
|
||||||
self.set_no_flee();
|
self.psyche.flee_health = 0.0;
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_no_flee(&mut self) { self.psyche.flee_health = 0.0; }
|
// FIXME: Only one of *three* things in this method sets a location.
|
||||||
|
|
||||||
// TODO: Get rid of this method, it does weird things
|
|
||||||
pub fn with_destination(mut self, pos: Vec3<f32>) -> Self {
|
pub fn with_destination(mut self, pos: Vec3<f32>) -> Self {
|
||||||
self.psyche.flee_health = 0.0;
|
self.psyche.flee_health = 0.0;
|
||||||
self.rtsim_controller = RtSimController::with_destination(pos);
|
self.rtsim_controller = RtSimController::with_destination(pos);
|
||||||
|
@ -511,7 +511,24 @@ impl Body {
|
|||||||
quadruped_small::Species::Holladon => 80,
|
quadruped_small::Species::Holladon => 80,
|
||||||
quadruped_small::Species::Hyena => 45,
|
quadruped_small::Species::Hyena => 45,
|
||||||
quadruped_small::Species::Truffler => 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 {
|
Body::QuadrupedMedium(quadruped_medium) => match quadruped_medium.species {
|
||||||
quadruped_medium::Species::Grolgar => 90,
|
quadruped_medium::Species::Grolgar => 90,
|
||||||
@ -519,9 +536,9 @@ impl Body {
|
|||||||
quadruped_medium::Species::Tiger => 70,
|
quadruped_medium::Species::Tiger => 70,
|
||||||
quadruped_medium::Species::Lion => 90,
|
quadruped_medium::Species::Lion => 90,
|
||||||
quadruped_medium::Species::Tarasque => 150,
|
quadruped_medium::Species::Tarasque => 150,
|
||||||
quadruped_medium::Species::Wolf => 55,
|
quadruped_medium::Species::Wolf => 45,
|
||||||
quadruped_medium::Species::Frostfang => 40,
|
quadruped_medium::Species::Frostfang => 40,
|
||||||
quadruped_medium::Species::Mouflon => 50,
|
quadruped_medium::Species::Mouflon => 40,
|
||||||
quadruped_medium::Species::Catoblepas => 100,
|
quadruped_medium::Species::Catoblepas => 100,
|
||||||
quadruped_medium::Species::Bonerattler => 50,
|
quadruped_medium::Species::Bonerattler => 50,
|
||||||
quadruped_medium::Species::Deer => 50,
|
quadruped_medium::Species::Deer => 50,
|
||||||
@ -545,21 +562,20 @@ impl Body {
|
|||||||
_ => 70,
|
_ => 70,
|
||||||
},
|
},
|
||||||
Body::BirdMedium(bird_medium) => match bird_medium.species {
|
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::Goose => 30,
|
||||||
bird_medium::Species::Parrot => 25,
|
|
||||||
bird_medium::Species::Peacock => 35,
|
bird_medium::Species::Peacock => 35,
|
||||||
bird_medium::Species::Eagle => 45,
|
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::Dragon(_) => 500,
|
||||||
Body::BirdLarge(bird_large) => match bird_large.species {
|
Body::BirdLarge(bird_large) => match bird_large.species {
|
||||||
bird_large::Species::Roc => 280,
|
bird_large::Species::Roc => 280,
|
||||||
_ => 300,
|
_ => 300,
|
||||||
},
|
},
|
||||||
Body::FishSmall(_) => 2,
|
Body::FishSmall(_) => 3,
|
||||||
Body::BipedLarge(biped_large) => match biped_large.species {
|
Body::BipedLarge(biped_large) => match biped_large.species {
|
||||||
biped_large::Species::Ogre => 320,
|
biped_large::Species::Ogre => 320,
|
||||||
biped_large::Species::Cyclops => 320,
|
biped_large::Species::Cyclops => 320,
|
||||||
|
@ -180,7 +180,7 @@ impl CharacterBehavior for Data {
|
|||||||
agent: Some(
|
agent: Some(
|
||||||
comp::Agent::from_body(&body)
|
comp::Agent::from_body(&body)
|
||||||
.with_behavior(Behavior::from(BehaviorCapability::SPEAK))
|
.with_behavior(Behavior::from(BehaviorCapability::SPEAK))
|
||||||
.with_no_flee(true),
|
.with_no_flee_if(true),
|
||||||
),
|
),
|
||||||
alignment: comp::Alignment::Owned(*data.uid),
|
alignment: comp::Alignment::Owned(*data.uid),
|
||||||
scale: self
|
scale: self
|
||||||
|
@ -38,10 +38,17 @@ impl Body {
|
|||||||
quadruped_small::Species::Axolotl => 70.0,
|
quadruped_small::Species::Axolotl => 70.0,
|
||||||
quadruped_small::Species::Pig => 70.0,
|
quadruped_small::Species::Pig => 70.0,
|
||||||
quadruped_small::Species::Sheep => 70.0,
|
quadruped_small::Species::Sheep => 70.0,
|
||||||
quadruped_small::Species::Cat => 70.0,
|
|
||||||
quadruped_small::Species::Truffler => 70.0,
|
quadruped_small::Species::Truffler => 70.0,
|
||||||
quadruped_small::Species::Fungome => 70.0,
|
quadruped_small::Species::Fungome => 70.0,
|
||||||
quadruped_small::Species::Goat => 80.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,
|
_ => 125.0,
|
||||||
},
|
},
|
||||||
Body::QuadrupedMedium(quadruped_medium) => match quadruped_medium.species {
|
Body::QuadrupedMedium(quadruped_medium) => match quadruped_medium.species {
|
||||||
@ -111,7 +118,7 @@ impl Body {
|
|||||||
quadruped_low::Species::Asp => 110.0,
|
quadruped_low::Species::Asp => 110.0,
|
||||||
quadruped_low::Species::Tortoise => 60.0,
|
quadruped_low::Species::Tortoise => 60.0,
|
||||||
quadruped_low::Species::Rocksnapper => 70.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::Maneater => 80.0,
|
||||||
quadruped_low::Species::Sandshark => 160.0,
|
quadruped_low::Species::Sandshark => 160.0,
|
||||||
quadruped_low::Species::Hakulaq => 140.0,
|
quadruped_low::Species::Hakulaq => 140.0,
|
||||||
|
@ -67,7 +67,6 @@ struct AgentData<'a> {
|
|||||||
alignment: Option<&'a Alignment>,
|
alignment: Option<&'a Alignment>,
|
||||||
traversal_config: TraversalConfig,
|
traversal_config: TraversalConfig,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
flees: bool,
|
|
||||||
damage: f32,
|
damage: f32,
|
||||||
light_emitter: Option<&'a LightEmitter>,
|
light_emitter: Option<&'a LightEmitter>,
|
||||||
glider_equipped: bool,
|
glider_equipped: bool,
|
||||||
@ -319,10 +318,6 @@ impl<'a> System<'a> for Sys {
|
|||||||
can_climb: body.map_or(false, Body::can_climb),
|
can_climb: body.map_or(false, Body::can_climb),
|
||||||
can_fly: body.map_or(false, |b| b.fly_thrust().is_some()),
|
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 health_fraction = health.map_or(1.0, Health::fraction);
|
||||||
let rtsim_entity = read_data
|
let rtsim_entity = read_data
|
||||||
.rtsim_entities
|
.rtsim_entities
|
||||||
@ -356,7 +351,6 @@ impl<'a> System<'a> for Sys {
|
|||||||
alignment: alignment.as_ref(),
|
alignment: alignment.as_ref(),
|
||||||
traversal_config,
|
traversal_config,
|
||||||
scale,
|
scale,
|
||||||
flees,
|
|
||||||
damage: health_fraction,
|
damage: health_fraction,
|
||||||
light_emitter,
|
light_emitter,
|
||||||
glider_equipped,
|
glider_equipped,
|
||||||
@ -398,19 +392,15 @@ impl<'a> System<'a> for Sys {
|
|||||||
event_emitter| {
|
event_emitter| {
|
||||||
if let Some(tgt_pos) = read_data.positions.get(target) {
|
if let Some(tgt_pos) = read_data.positions.get(target) {
|
||||||
let dist_sqrd = pos.0.distance_squared(tgt_pos.0);
|
let dist_sqrd = pos.0.distance_squared(tgt_pos.0);
|
||||||
// If really far away drop everything and follow
|
let too_far_away = dist_sqrd > (MAX_FOLLOW_DIST).powi(2);
|
||||||
if dist_sqrd > (2.0 * MAX_FOLLOW_DIST).powi(2) {
|
|
||||||
agent.bearing = Vec2::zero();
|
// If too far away, then follow
|
||||||
|
if too_far_away {
|
||||||
data.follow(agent, controller, &read_data.terrain, tgt_pos);
|
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) {
|
} else if entity_was_attacked(target, &read_data) {
|
||||||
data.attack_target_attacker(agent, &read_data, controller);
|
data.attack_target_attacker(agent, &read_data, controller);
|
||||||
// Follow owner if too far away and not
|
// Otherwise, just idle
|
||||||
// fighting
|
|
||||||
} else if dist_sqrd > MAX_FOLLOW_DIST.powi(2) {
|
|
||||||
data.follow(agent, controller, &read_data.terrain, tgt_pos);
|
|
||||||
|
|
||||||
// Otherwise just idle
|
|
||||||
} else {
|
} else {
|
||||||
idle(agent, controller, event_emitter);
|
idle(agent, controller, event_emitter);
|
||||||
}
|
}
|
||||||
@ -424,7 +414,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
controller: &mut Controller,
|
controller: &mut Controller,
|
||||||
event_emitter| {
|
event_emitter| {
|
||||||
if let Some(tgt_health) = read_data.healths.get(target) {
|
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 tgt_health.is_dead {
|
||||||
if let Some(tgt_stats) =
|
if let Some(tgt_stats) =
|
||||||
data.rtsim_entity.and(read_data.stats.get(target))
|
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);
|
rtsim_forget_enemy(&tgt_stats.name, agent);
|
||||||
}
|
}
|
||||||
agent.target = None;
|
agent.target = None;
|
||||||
// If the target is hostile
|
// Else, if target is hostile, hostile tree
|
||||||
// (either based on alignment or if
|
|
||||||
// the target just attacked)
|
|
||||||
} else if hostile {
|
} else if hostile {
|
||||||
data.hostile_tree(agent, controller, &read_data, event_emitter);
|
data.hostile_tree(agent, controller, &read_data, event_emitter);
|
||||||
// Target is something worth following
|
// Else, if owned, act as pet to them
|
||||||
// methinks
|
|
||||||
} else if let Some(Alignment::Owned(uid)) = data.alignment {
|
} else if let Some(Alignment::Owned(uid)) = data.alignment {
|
||||||
if read_data.uids.get(target) == Some(uid) {
|
if read_data.uids.get(target) == Some(uid) {
|
||||||
react_as_pet(agent, target, controller, event_emitter);
|
react_as_pet(agent, target, controller, event_emitter);
|
||||||
@ -481,9 +468,9 @@ impl<'a> System<'a> for Sys {
|
|||||||
if let Some(attacker) =
|
if let Some(attacker) =
|
||||||
read_data.uid_allocator.retrieve_entity_internal(by.uid().0)
|
read_data.uid_allocator.retrieve_entity_internal(by.uid().0)
|
||||||
{
|
{
|
||||||
// If the target is dead or in a safezone, remove the
|
// If target is dead or invulnerable (for now, this only
|
||||||
// target and idle.
|
// means safezone), untarget them and idle.
|
||||||
if should_stop_attacking(attacker, &read_data) {
|
if is_dead_or_invulnerable(attacker, &read_data) {
|
||||||
agent.target = None;
|
agent.target = None;
|
||||||
} else if let Some(tgt_pos) =
|
} else if let Some(tgt_pos) =
|
||||||
read_data.positions.get(attacker)
|
read_data.positions.get(attacker)
|
||||||
@ -495,9 +482,8 @@ impl<'a> System<'a> for Sys {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Determine whether the new target should be a priority
|
// Determine whether the new target should be a priority
|
||||||
// over the old one
|
// over the old one (i.e: because it's either close or
|
||||||
// (i.e: because it's either close or because they
|
// because they attacked us)
|
||||||
// attacked us)
|
|
||||||
let more_dangerous_than_old_target =
|
let more_dangerous_than_old_target =
|
||||||
agent.target.map_or(true, |old_tgt| {
|
agent.target.map_or(true, |old_tgt| {
|
||||||
if let Some(old_tgt_pos) =
|
if let Some(old_tgt_pos) =
|
||||||
@ -606,8 +592,9 @@ impl<'a> AgentData<'a> {
|
|||||||
decrement_awareness(agent);
|
decrement_awareness(agent);
|
||||||
forget_old_sounds(agent, read_data);
|
forget_old_sounds(agent, read_data);
|
||||||
|
|
||||||
|
let small_chance = thread_rng().gen_bool(0.1);
|
||||||
// Set owner if no target
|
// 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(Alignment::Owned(owner)) = self.alignment {
|
||||||
if let Some(owner) = get_entity_by_id(owner.id(), read_data) {
|
if let Some(owner) = get_entity_by_id(owner.id(), read_data) {
|
||||||
agent.target = build_target(owner, false, read_data.time.0, false);
|
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 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);
|
agent.timer.start(read_data.time.0, TimerAction::Interact);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -714,22 +703,23 @@ impl<'a> AgentData<'a> {
|
|||||||
.psyche
|
.psyche
|
||||||
.aggro_dist
|
.aggro_dist
|
||||||
.map_or(true, |ad| dist_sq < ad.powi(2));
|
.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 {
|
if in_aggro_range {
|
||||||
*aggro_on = true;
|
*aggro_on = true;
|
||||||
}
|
}
|
||||||
let aggro_on = *aggro_on;
|
let aggro_on = *aggro_on;
|
||||||
|
|
||||||
if self.damage.min(1.0) < agent.psyche.flee_health && self.flees {
|
let should_flee = self.damage.min(1.0) < agent.psyche.flee_health;
|
||||||
// Should the agent flee?
|
if should_flee {
|
||||||
if agent.action_state.timer == 0.0 && can_speak(agent) {
|
let has_opportunity_to_flee = agent.action_state.timer < FLEE_DURATION;
|
||||||
self.chat_general("npc.speech.villager_under_attack", event_emitter);
|
let within_flee_distance = dist_sq < MAX_FLEE_DIST.powi(2);
|
||||||
self.emit_scream(read_data.time.0, event_emitter);
|
|
||||||
|
// 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;
|
agent.action_state.timer = 0.01;
|
||||||
} else if agent.action_state.timer < FLEE_DURATION
|
} else if within_flee_distance && has_opportunity_to_flee {
|
||||||
|| dist_sq < MAX_FLEE_DIST.powi(2)
|
|
||||||
{
|
|
||||||
self.flee(agent, controller, &read_data.terrain, tgt_pos);
|
self.flee(agent, controller, &read_data.terrain, tgt_pos);
|
||||||
agent.action_state.timer += read_data.dt.0;
|
agent.action_state.timer += read_data.dt.0;
|
||||||
} else {
|
} else {
|
||||||
@ -737,36 +727,30 @@ impl<'a> AgentData<'a> {
|
|||||||
agent.target = None;
|
agent.target = None;
|
||||||
self.idle(agent, controller, read_data);
|
self.idle(agent, controller, read_data);
|
||||||
}
|
}
|
||||||
} else if should_stop_attacking(target, read_data) {
|
} else if is_dead(target, read_data) {
|
||||||
if can_speak(agent) {
|
self.exclaim_relief_about_enemy_dead(agent, event_emitter);
|
||||||
let msg = "npc.speech.villager_enemy_killed".to_string();
|
agent.target = None;
|
||||||
event_emitter
|
self.idle(agent, controller, read_data);
|
||||||
.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg)));
|
} else if is_invulnerable(target, read_data) {
|
||||||
}
|
|
||||||
agent.target = None;
|
agent.target = None;
|
||||||
self.idle(agent, controller, read_data);
|
self.idle(agent, controller, read_data);
|
||||||
} else {
|
} else {
|
||||||
// Potentially choose a new target
|
let is_time_to_retarget =
|
||||||
if read_data.time.0 - selected_at > RETARGETING_THRESHOLD_SECONDS
|
read_data.time.0 - selected_at > RETARGETING_THRESHOLD_SECONDS;
|
||||||
&& !in_aggro_range
|
|
||||||
{
|
if !in_aggro_range && is_time_to_retarget {
|
||||||
self.choose_target(agent, controller, read_data, event_emitter);
|
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 {
|
if aggro_on {
|
||||||
let target_data = build_target_data(target, tgt_pos, read_data);
|
let target_data = build_target_data(target, tgt_pos, read_data);
|
||||||
self.attack(agent, controller, &target_data, read_data);
|
self.attack(agent, controller, &target_data, read_data);
|
||||||
} else {
|
} else {
|
||||||
// If we're not yet aggro-ed, strike a menacing pose
|
self.menacing(agent, target, controller, read_data, event_emitter);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1040,7 +1024,7 @@ impl<'a> AgentData<'a> {
|
|||||||
let msg = agent.inbox.pop_front();
|
let msg = agent.inbox.pop_front();
|
||||||
match msg {
|
match msg {
|
||||||
Some(AgentEvent::Talk(by, subject)) => {
|
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) {
|
if let Some(target) = get_entity_by_id(by.id(), read_data) {
|
||||||
agent.target = build_target(target, false, read_data.time.0, false);
|
agent.target = build_target(target, false, read_data.time.0, false);
|
||||||
|
|
||||||
@ -1088,25 +1072,25 @@ impl<'a> AgentData<'a> {
|
|||||||
destination_name
|
destination_name
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
self.chat_general(msg, event_emitter);
|
self.chat_npc(msg, event_emitter);
|
||||||
} else if agent.behavior.can_trade() {
|
} else if agent.behavior.can_trade() {
|
||||||
if !agent.behavior.is(BehaviorState::TRADING) {
|
if !agent.behavior.is(BehaviorState::TRADING) {
|
||||||
controller.events.push(ControlEvent::InitiateInvite(
|
controller.events.push(ControlEvent::InitiateInvite(
|
||||||
by,
|
by,
|
||||||
InviteKind::Trade,
|
InviteKind::Trade,
|
||||||
));
|
));
|
||||||
self.chat_general(
|
self.chat_npc(
|
||||||
"npc.speech.merchant_advertisement",
|
"npc.speech.merchant_advertisement",
|
||||||
event_emitter,
|
event_emitter,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
self.chat_general(
|
self.chat_npc(
|
||||||
"npc.speech.merchant_busy",
|
"npc.speech.merchant_busy",
|
||||||
event_emitter,
|
event_emitter,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.chat_general("npc.speech.villager", event_emitter);
|
self.chat_npc("npc.speech.villager", event_emitter);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Subject::Trade => {
|
Subject::Trade => {
|
||||||
@ -1116,12 +1100,12 @@ impl<'a> AgentData<'a> {
|
|||||||
by,
|
by,
|
||||||
InviteKind::Trade,
|
InviteKind::Trade,
|
||||||
));
|
));
|
||||||
self.chat_general(
|
self.chat_npc(
|
||||||
"npc.speech.merchant_advertisement",
|
"npc.speech.merchant_advertisement",
|
||||||
event_emitter,
|
event_emitter,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
self.chat_general(
|
self.chat_npc(
|
||||||
"npc.speech.merchant_busy",
|
"npc.speech.merchant_busy",
|
||||||
event_emitter,
|
event_emitter,
|
||||||
);
|
);
|
||||||
@ -1129,7 +1113,7 @@ impl<'a> AgentData<'a> {
|
|||||||
} else {
|
} else {
|
||||||
// TODO: maybe make some travellers willing to trade with
|
// TODO: maybe make some travellers willing to trade with
|
||||||
// simpler goods like potions
|
// simpler goods like potions
|
||||||
self.chat_general(
|
self.chat_npc(
|
||||||
"npc.speech.villager_decline_trade",
|
"npc.speech.villager_decline_trade",
|
||||||
event_emitter,
|
event_emitter,
|
||||||
);
|
);
|
||||||
@ -1181,7 +1165,7 @@ impl<'a> AgentData<'a> {
|
|||||||
MemoryItem::Mood { state } => state.describe(),
|
MemoryItem::Mood { state } => state.describe(),
|
||||||
_ => "".to_string(),
|
_ => "".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!",
|
"{} ? I think it's {} {} from here!",
|
||||||
location.name, dist, dir
|
location.name, dist, dir
|
||||||
);
|
);
|
||||||
self.chat_general(msg, event_emitter);
|
self.chat_npc(msg, event_emitter);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Subject::Person(person) => {
|
Subject::Person(person) => {
|
||||||
@ -1230,7 +1214,7 @@ impl<'a> AgentData<'a> {
|
|||||||
person.name()
|
person.name()
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
self.chat_general(msg, event_emitter);
|
self.chat_npc(msg, event_emitter);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Subject::Work => {},
|
Subject::Work => {},
|
||||||
@ -1257,9 +1241,9 @@ impl<'a> AgentData<'a> {
|
|||||||
controller
|
controller
|
||||||
.events
|
.events
|
||||||
.push(ControlEvent::InviteResponse(InviteResponse::Decline));
|
.push(ControlEvent::InviteResponse(InviteResponse::Decline));
|
||||||
self.chat_general_if_can_speak(
|
self.chat_npc_if_allowed_to_speak(
|
||||||
agent,
|
|
||||||
"npc.speech.merchant_busy",
|
"npc.speech.merchant_busy",
|
||||||
|
agent,
|
||||||
event_emitter,
|
event_emitter,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1268,9 +1252,9 @@ impl<'a> AgentData<'a> {
|
|||||||
controller
|
controller
|
||||||
.events
|
.events
|
||||||
.push(ControlEvent::InviteResponse(InviteResponse::Decline));
|
.push(ControlEvent::InviteResponse(InviteResponse::Decline));
|
||||||
self.chat_general_if_can_speak(
|
self.chat_npc_if_allowed_to_speak(
|
||||||
agent,
|
|
||||||
"npc.speech.villager_decline_trade",
|
"npc.speech.villager_decline_trade",
|
||||||
|
agent,
|
||||||
event_emitter,
|
event_emitter,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1288,13 +1272,10 @@ impl<'a> AgentData<'a> {
|
|||||||
if agent.behavior.is(BehaviorState::TRADING) {
|
if agent.behavior.is(BehaviorState::TRADING) {
|
||||||
match result {
|
match result {
|
||||||
TradeResult::Completed => {
|
TradeResult::Completed => {
|
||||||
self.chat_general(
|
self.chat_npc("npc.speech.merchant_trade_successful", event_emitter);
|
||||||
"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);
|
agent.behavior.unset(BehaviorState::TRADING);
|
||||||
@ -1390,19 +1371,27 @@ impl<'a> AgentData<'a> {
|
|||||||
|
|
||||||
fn menacing(
|
fn menacing(
|
||||||
&self,
|
&self,
|
||||||
_agent: &mut Agent,
|
agent: &Agent,
|
||||||
|
target: EcsEntity,
|
||||||
controller: &mut Controller,
|
controller: &mut Controller,
|
||||||
read_data: &ReadData,
|
read_data: &ReadData,
|
||||||
target: EcsEntity,
|
event_emitter: &mut Emitter<ServerEvent>,
|
||||||
_tgt_pos: &Pos,
|
|
||||||
) {
|
) {
|
||||||
|
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);
|
self.look_toward(controller, read_data, target);
|
||||||
controller.actions.push(ControlAction::Wield);
|
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 {
|
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)
|
|| 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(self.alignment, read_data).map_or(false, |owner_alignment| {
|
||||||
try_owner_alignment(e_alignment, read_data).map_or(false, |e_owner_alignment| {
|
try_owner_alignment(e_alignment, read_data).map_or(false, |e_owner_alignment| {
|
||||||
owner_alignment.hostile_towards(*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>| {
|
let guard_defending_villager = |e_health: &Health, e_alignment: Option<&Alignment>| {
|
||||||
// I'm a guard and a villager is in distress
|
let i_am_a_guard = read_data
|
||||||
let other_is_npc = matches!(e_alignment, Some(Alignment::Npc));
|
.stats
|
||||||
let remembers_damage = read_data.time.0 - e_health.last_change.time.0 < 5.0;
|
.get(*self.entity)
|
||||||
let need_help = read_data.stats.get(*self.entity).map_or(false, |stats| {
|
.map_or(false, |stats| stats.name == "Guard");
|
||||||
stats.name == "Guard" && other_is_npc && remembers_damage
|
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();
|
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(|| {
|
.then(|| {
|
||||||
attacker_of(e_health)
|
attacker_of(e_health)
|
||||||
.and_then(|damage_contributor| {
|
.and_then(|damage_contributor| {
|
||||||
@ -1617,15 +1607,14 @@ impl<'a> AgentData<'a> {
|
|||||||
.remembers_fight_with_character(&target_stats.name)
|
.remembers_fight_with_character(&target_stats.name)
|
||||||
{
|
{
|
||||||
rtsim_new_enemy(&target_stats.name, agent, read_data);
|
rtsim_new_enemy(&target_stats.name, agent, read_data);
|
||||||
if can_speak(agent) {
|
self.chat_npc_if_allowed_to_speak(
|
||||||
let message = format!(
|
format!(
|
||||||
"{}! How dare you cross me again!",
|
"{}! How dare you cross me again!",
|
||||||
target_stats.name.clone()
|
target_stats.name.clone()
|
||||||
);
|
),
|
||||||
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
|
agent,
|
||||||
*self.uid, message,
|
event_emitter,
|
||||||
)));
|
);
|
||||||
}
|
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
@ -1651,12 +1640,13 @@ impl<'a> AgentData<'a> {
|
|||||||
if self.rtsim_entity.is_some() {
|
if self.rtsim_entity.is_some() {
|
||||||
rtsim_new_enemy(&target_stats.name, agent, read_data);
|
rtsim_new_enemy(&target_stats.name, agent, read_data);
|
||||||
}
|
}
|
||||||
if can_speak(agent) {
|
|
||||||
let message = "npc.speech.villager_cultist_alarm".to_string();
|
self.chat_npc_if_allowed_to_speak(
|
||||||
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
|
"npc.speech.villager_cultist_alarm",
|
||||||
*self.uid, message,
|
agent,
|
||||||
)));
|
event_emitter,
|
||||||
}
|
);
|
||||||
|
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
@ -1677,13 +1667,13 @@ impl<'a> AgentData<'a> {
|
|||||||
let can_target = within_reach(e_pos, e_char_state, e_inventory)
|
let can_target = within_reach(e_pos, e_char_state, e_inventory)
|
||||||
&& entity != *self.entity
|
&& entity != *self.entity
|
||||||
&& !e_health.is_dead
|
&& !e_health.is_dead
|
||||||
&& !invulnerability_is_in_buffs(read_data.buffs.get(entity));
|
&& !is_invulnerable(entity, read_data);
|
||||||
|
|
||||||
if !can_target {
|
if !can_target {
|
||||||
None
|
None
|
||||||
} else if owners_hostile(e_alignment) {
|
} else if is_owner_hostile(e_alignment) {
|
||||||
Some((entity, *e_pos))
|
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)
|
Some(villain_info)
|
||||||
} else if rtsim_remember(e_stats, agent, event_emitter)
|
} else if rtsim_remember(e_stats, agent, event_emitter)
|
||||||
|| npc_sees_cultist(e_stats, e_inventory, agent, event_emitter)
|
|| npc_sees_cultist(e_stats, e_inventory, agent, event_emitter)
|
||||||
@ -3453,8 +3443,9 @@ impl<'a> AgentData<'a> {
|
|||||||
read_data: &ReadData,
|
read_data: &ReadData,
|
||||||
) {
|
) {
|
||||||
if attack_data.dist_sqrd > 30.0_f32.powi(2) {
|
if attack_data.dist_sqrd > 30.0_f32.powi(2) {
|
||||||
// If random chance and can see target
|
let small_chance = thread_rng().gen_bool(0.05);
|
||||||
if thread_rng().gen_bool(0.05)
|
|
||||||
|
if small_chance
|
||||||
&& can_see_tgt(
|
&& can_see_tgt(
|
||||||
&*read_data.terrain,
|
&*read_data.terrain,
|
||||||
self.pos,
|
self.pos,
|
||||||
@ -4166,20 +4157,20 @@ impl<'a> AgentData<'a> {
|
|||||||
controller: &mut Controller,
|
controller: &mut Controller,
|
||||||
read_data: &ReadData,
|
read_data: &ReadData,
|
||||||
) {
|
) {
|
||||||
// Currently this means that we are in a safezone
|
if is_invulnerable(*self.entity, read_data) {
|
||||||
if invulnerability_is_in_buffs(read_data.buffs.get(*self.entity)) {
|
|
||||||
self.idle(agent, controller, read_data);
|
self.idle(agent, controller, read_data);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(sound) = agent.sounds_heard.last() {
|
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) {
|
if let Some(agent_stats) = read_data.stats.get(*self.entity) {
|
||||||
let sound_pos = Pos(sound.pos);
|
let sound_pos = Pos(sound.pos);
|
||||||
let dist_sqrd = self.pos.0.distance_squared(sound_pos.0);
|
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_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));
|
let is_enemy = matches!(self.alignment, Some(Alignment::Enemy));
|
||||||
|
|
||||||
if is_enemy {
|
if is_enemy {
|
||||||
@ -4193,7 +4184,7 @@ impl<'a> AgentData<'a> {
|
|||||||
}
|
}
|
||||||
} else if is_village_guard {
|
} else if is_village_guard {
|
||||||
self.follow(agent, controller, &read_data.terrain, &sound_pos);
|
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 flee_health = agent.psyche.flee_health;
|
||||||
let close_enough = dist_sqrd < 35.0_f32.powi(2);
|
let close_enough = dist_sqrd < 35.0_f32.powi(2);
|
||||||
let sound_was_loud = sound.vol >= 10.0;
|
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);
|
agent.target = build_target(attacker, true, read_data.time.0, true);
|
||||||
|
|
||||||
if let Some(tgt_pos) = read_data.positions.get(attacker) {
|
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);
|
agent.target = build_target(target, false, read_data.time.0, false);
|
||||||
|
|
||||||
self.idle(agent, controller, read_data);
|
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,
|
&self,
|
||||||
agent: &Agent,
|
|
||||||
msg: impl ToString,
|
msg: impl ToString,
|
||||||
|
agent: &Agent,
|
||||||
event_emitter: &mut Emitter<'_, ServerEvent>,
|
event_emitter: &mut Emitter<'_, ServerEvent>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if can_speak(agent) {
|
if allowed_to_speak(agent) {
|
||||||
self.chat_general(msg, event_emitter);
|
self.chat_npc(msg, event_emitter);
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
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(
|
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
|
||||||
*self.uid,
|
*self.uid,
|
||||||
msg.to_string(),
|
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) {
|
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
|
>= dist_sqrd
|
||||||
}
|
}
|
||||||
|
|
||||||
// If target is dead or has invulnerability buff, returns true
|
fn is_dead_or_invulnerable(entity: EcsEntity, read_data: &ReadData) -> bool {
|
||||||
fn should_stop_attacking(target: EcsEntity, read_data: &ReadData) -> bool {
|
is_dead(entity, read_data) || is_invulnerable(entity, read_data)
|
||||||
let health = read_data.healths.get(target);
|
}
|
||||||
let buffs = read_data.buffs.get(target);
|
|
||||||
|
|
||||||
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
|
// 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.
|
// 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))
|
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> {
|
fn get_entity_by_id(id: u64, read_data: &ReadData) -> Option<EcsEntity> {
|
||||||
read_data.uid_allocator.retrieve_entity_internal(id)
|
read_data.uid_allocator.retrieve_entity_internal(id)
|
||||||
|
@ -471,10 +471,10 @@ impl NpcData {
|
|||||||
let health = Some(comp::Health::new(body, health_scaling.unwrap_or(0)));
|
let health = Some(comp::Health::new(body, health_scaling.unwrap_or(0)));
|
||||||
let poise = comp::Poise::new(body);
|
let poise = comp::Poise::new(body);
|
||||||
|
|
||||||
|
// Allow Humanoid, BirdMedium, and Parrot to speak
|
||||||
let can_speak = match body {
|
let can_speak = match body {
|
||||||
comp::Body::Humanoid(_) => true,
|
comp::Body::Humanoid(_) => true,
|
||||||
comp::Body::BirdMedium(bird_medium) => match bird_medium.species {
|
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,
|
bird_medium::Species::Parrot => alignment == comp::Alignment::Npc,
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
@ -495,13 +495,13 @@ impl NpcData {
|
|||||||
.with_trade_site(trade_for_site),
|
.with_trade_site(trade_for_site),
|
||||||
)
|
)
|
||||||
.with_patrol_origin(pos)
|
.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)
|
let agent = if matches!(alignment, comp::Alignment::Enemy)
|
||||||
&& matches!(body, comp::Body::Humanoid(_))
|
&& 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 {
|
} else {
|
||||||
agent
|
agent
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user