mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'tormod/opportunistic-ambush' into 'master'
Tormod/opportunistic ambush See merge request veloren/veloren!3441
This commit is contained in:
commit
c8a41e724c
@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Changed
|
||||
- Use fluent for translations
|
||||
- First tab on Login screen triggers username focus
|
||||
- Certain NPCs will now attack when alone with victim
|
||||
|
||||
### Removed
|
||||
|
||||
|
@ -92,6 +92,9 @@ npc-speech-merchant_trade_declined =
|
||||
npc-speech-merchant_trade_cancelled_hostile =
|
||||
.a0 = Sorry to cut it short, we have a problem to solve here!
|
||||
.a1 = We'll trade later, I need to take care of this first!
|
||||
npc-speech-ambush =
|
||||
.a0 = It's unwise to travel alone!
|
||||
.a1 = Like stealing candy from a baby!
|
||||
npc-speech-villager_cultist_alarm =
|
||||
.a0 = Lookout! There is a cultist on the loose!
|
||||
.a1 = To arms! The cultists are attacking!
|
||||
|
@ -130,6 +130,7 @@ pub enum UtteranceKind {
|
||||
Hurt,
|
||||
Greeting,
|
||||
Scream,
|
||||
Ambush,
|
||||
/* Death,
|
||||
* TODO: Wait for more post-death features (i.e. animations) before implementing death
|
||||
* sounds */
|
||||
|
@ -706,6 +706,8 @@ impl PersonalityBase {
|
||||
* don't want everyone to be completely weird.
|
||||
*/
|
||||
pub fn to_personality(&self) -> Personality {
|
||||
let will_ambush = self.agreeableness < Personality::LOW_THRESHOLD
|
||||
&& self.conscientiousness < Personality::LOW_THRESHOLD;
|
||||
let mut chat_traits: EnumSet<PersonalityTrait> = EnumSet::new();
|
||||
if self.openness > Personality::HIGH_THRESHOLD {
|
||||
chat_traits.insert(PersonalityTrait::Open);
|
||||
@ -752,12 +754,14 @@ impl PersonalityBase {
|
||||
}
|
||||
Personality {
|
||||
personality_traits: chat_traits,
|
||||
will_ambush,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Personality {
|
||||
pub personality_traits: EnumSet<PersonalityTrait>,
|
||||
pub will_ambush: bool,
|
||||
}
|
||||
|
||||
#[derive(EnumSetType)]
|
||||
|
@ -48,6 +48,7 @@ use common::{
|
||||
};
|
||||
use common_base::prof_span;
|
||||
use common_ecs::{Job, Origin, ParMode, Phase, System};
|
||||
use itertools::Itertools;
|
||||
use rand::{thread_rng, Rng};
|
||||
use rayon::iter::ParallelIterator;
|
||||
use specs::{Entity as EcsEntity, Join, ParJoin, Read, WriteExpect, WriteStorage};
|
||||
@ -744,15 +745,60 @@ impl<'a> AgentData<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn choose_target(&self, agent: &mut Agent, controller: &mut Controller, read_data: &ReadData) {
|
||||
fn choose_target(
|
||||
&self,
|
||||
agent: &mut Agent,
|
||||
controller: &mut Controller,
|
||||
read_data: &ReadData,
|
||||
event_emitter: &mut Emitter<ServerEvent>,
|
||||
) {
|
||||
agent.action_state.timer = 0.0;
|
||||
let mut aggro_on = false;
|
||||
|
||||
// Search the area.
|
||||
// TODO: choose target by more than just distance
|
||||
let common::CachedSpatialGrid(grid) = self.cached_spatial_grid;
|
||||
|
||||
let entities_nearby = grid
|
||||
.in_circle_aabr(self.pos.0.xy(), agent.psyche.search_dist())
|
||||
.collect_vec();
|
||||
|
||||
let can_ambush = |entity: EcsEntity, read_data: &ReadData| {
|
||||
let self_different_from_entity = || {
|
||||
read_data
|
||||
.uids
|
||||
.get(entity)
|
||||
.map_or(false, |eu| eu != self.uid)
|
||||
};
|
||||
if self.will_ambush()
|
||||
&& self_different_from_entity()
|
||||
&& !self.passive_towards(entity, read_data)
|
||||
{
|
||||
let surrounding_humanoids = entities_nearby
|
||||
.iter()
|
||||
.filter(|e| read_data.bodies.get(**e).map_or(false, |b| b.is_humanoid()))
|
||||
.collect_vec();
|
||||
surrounding_humanoids.len() == 2
|
||||
&& surrounding_humanoids.iter().any(|e| **e == entity)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
let get_pos = |entity| read_data.positions.get(entity);
|
||||
let get_enemy = |(entity, attack_target): (EcsEntity, bool)| {
|
||||
if attack_target {
|
||||
if self.is_enemy(entity, read_data) {
|
||||
Some((entity, true))
|
||||
} else if can_ambush(entity, read_data) {
|
||||
controller.clone().push_utterance(UtteranceKind::Ambush);
|
||||
self.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-ambush".to_string(),
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
aggro_on = true;
|
||||
Some((entity, true))
|
||||
} else if self.should_defend(entity, read_data) {
|
||||
if let Some(attacker) = get_attacker(entity, read_data) {
|
||||
if !self.passive_towards(attacker, read_data) {
|
||||
@ -822,13 +868,9 @@ impl<'a> AgentData<'a> {
|
||||
|| self.can_see_entity(agent, controller, entity, e_pos, read_data)
|
||||
};
|
||||
|
||||
// Search the area.
|
||||
// TODO: choose target by more than just distance
|
||||
let common::CachedSpatialGrid(grid) = self.cached_spatial_grid;
|
||||
|
||||
let target = grid
|
||||
.in_circle_aabr(self.pos.0.xy(), agent.psyche.search_dist())
|
||||
.filter_map(is_valid_target)
|
||||
let target = entities_nearby
|
||||
.iter()
|
||||
.filter_map(|e| is_valid_target(*e))
|
||||
.filter_map(get_enemy)
|
||||
.filter_map(|(entity, attack_target)| {
|
||||
get_pos(entity).map(|pos| (entity, pos, attack_target))
|
||||
@ -1632,6 +1674,14 @@ impl<'a> AgentData<'a> {
|
||||
|| (is_villager(self.alignment) && is_dressed_as_cultist(entity, read_data)))
|
||||
}
|
||||
|
||||
fn will_ambush(&self) -> bool {
|
||||
self.health
|
||||
.map_or(false, |h| h.current() / h.maximum() > 0.7)
|
||||
&& self
|
||||
.rtsim_entity
|
||||
.map_or(false, |re| re.brain.personality.will_ambush)
|
||||
}
|
||||
|
||||
fn should_defend(&self, entity: EcsEntity, read_data: &ReadData) -> bool {
|
||||
let entity_alignment = read_data.alignments.get(entity);
|
||||
|
||||
|
@ -455,9 +455,12 @@ fn handle_timed_events(bdata: &mut BehaviorData) -> bool {
|
||||
}
|
||||
|
||||
if bdata.rng.gen::<f32>() < 0.1 {
|
||||
bdata
|
||||
.agent_data
|
||||
.choose_target(bdata.agent, bdata.controller, bdata.read_data);
|
||||
bdata.agent_data.choose_target(
|
||||
bdata.agent,
|
||||
bdata.controller,
|
||||
bdata.read_data,
|
||||
bdata.event_emitter,
|
||||
);
|
||||
} else {
|
||||
bdata.agent_data.handle_sounds_heard(
|
||||
bdata.agent,
|
||||
@ -577,7 +580,7 @@ fn do_combat(bdata: &mut BehaviorData) -> bool {
|
||||
read_data.time.0 - selected_at > RETARGETING_THRESHOLD_SECONDS;
|
||||
|
||||
if !in_aggro_range && is_time_to_retarget {
|
||||
agent_data.choose_target(agent, controller, read_data);
|
||||
agent_data.choose_target(agent, controller, read_data, event_emitter);
|
||||
}
|
||||
|
||||
if aggro_on {
|
||||
|
@ -94,7 +94,13 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
||||
{
|
||||
let personality = &rtsim_entity.brain.personality;
|
||||
let standard_response_msg = || -> String {
|
||||
if personality
|
||||
if personality.will_ambush {
|
||||
format!(
|
||||
"I'm heading to {}! Want to come along? We'll make \
|
||||
great travel buddies, hehe.",
|
||||
destination_name
|
||||
)
|
||||
} else if personality
|
||||
.personality_traits
|
||||
.contains(PersonalityTrait::Extroverted)
|
||||
{
|
||||
@ -121,7 +127,9 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
||||
},
|
||||
));
|
||||
if rtsim_entity.brain.remembers_character(&tgt_stats.name) {
|
||||
if personality
|
||||
if personality.will_ambush {
|
||||
"Just follow me a bit more, hehe.".to_string()
|
||||
} else if personality
|
||||
.personality_traits
|
||||
.contains(PersonalityTrait::Extroverted)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user