mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Guards now respond to villagers calling for help
This commit is contained in:
parent
35d8306c7a
commit
7cfa67ad49
@ -272,15 +272,23 @@ pub struct Sound {
|
|||||||
pub pos: Vec3<f32>,
|
pub pos: Vec3<f32>,
|
||||||
pub vol: f32,
|
pub vol: f32,
|
||||||
pub time: f64,
|
pub time: f64,
|
||||||
|
pub owner: Option<EcsEntity>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sound {
|
impl Sound {
|
||||||
pub fn new(kind: SoundKind, pos: Vec3<f32>, vol: f32, time: f64) -> Self {
|
pub fn new(
|
||||||
|
kind: SoundKind,
|
||||||
|
pos: Vec3<f32>,
|
||||||
|
vol: f32,
|
||||||
|
time: f64,
|
||||||
|
owner: Option<EcsEntity>,
|
||||||
|
) -> Self {
|
||||||
Sound {
|
Sound {
|
||||||
kind,
|
kind,
|
||||||
pos,
|
pos,
|
||||||
vol,
|
vol,
|
||||||
time,
|
time,
|
||||||
|
owner,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,6 +309,8 @@ pub enum SoundKind {
|
|||||||
Beam,
|
Beam,
|
||||||
Shockwave,
|
Shockwave,
|
||||||
Utterance(UtteranceKind, Body),
|
Utterance(UtteranceKind, Body),
|
||||||
|
// TODO: unify VillagerAlarm with Utterance
|
||||||
|
VillagerAlarm,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -94,7 +94,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
if rng.gen_bool(0.005) {
|
if rng.gen_bool(0.005) {
|
||||||
server_events.push(ServerEvent::Sound {
|
server_events.push(ServerEvent::Sound {
|
||||||
sound: Sound::new(SoundKind::Beam, pos.0, 7.0, time),
|
sound: Sound::new(SoundKind::Beam, pos.0, 7.0, time, Some(entity)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,6 +99,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
pos.0 + Vec3::unit_z() * body.eye_height(),
|
pos.0 + Vec3::unit_z() * body.eye_height(),
|
||||||
8.0, // TODO: Come up with a better way of determining this
|
8.0, // TODO: Come up with a better way of determining this
|
||||||
1.0,
|
1.0,
|
||||||
|
Some(entity),
|
||||||
);
|
);
|
||||||
server_emitter.emit(ServerEvent::Sound { sound });
|
server_emitter.emit(ServerEvent::Sound { sound });
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,13 @@ impl<'a> System<'a> for Sys {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
server_emitter.emit(ServerEvent::Sound {
|
server_emitter.emit(ServerEvent::Sound {
|
||||||
sound: Sound::new(SoundKind::Melee, pos.0, 3.0, read_data.time.0),
|
sound: Sound::new(
|
||||||
|
SoundKind::Melee,
|
||||||
|
pos.0,
|
||||||
|
3.0,
|
||||||
|
read_data.time.0,
|
||||||
|
Some(attacker),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
melee_attack.applied = true;
|
melee_attack.applied = true;
|
||||||
|
|
||||||
|
@ -75,7 +75,13 @@ impl<'a> System<'a> for Sys {
|
|||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
if physics.on_surface().is_none() && rng.gen_bool(0.05) {
|
if physics.on_surface().is_none() && rng.gen_bool(0.05) {
|
||||||
server_emitter.emit(ServerEvent::Sound {
|
server_emitter.emit(ServerEvent::Sound {
|
||||||
sound: Sound::new(SoundKind::Projectile, pos.0, 2.0, read_data.time.0),
|
sound: Sound::new(
|
||||||
|
SoundKind::Projectile,
|
||||||
|
pos.0,
|
||||||
|
2.0,
|
||||||
|
read_data.time.0,
|
||||||
|
Some(entity),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
if rng.gen_bool(0.05) {
|
if rng.gen_bool(0.05) {
|
||||||
server_emitter.emit(ServerEvent::Sound {
|
server_emitter.emit(ServerEvent::Sound {
|
||||||
sound: Sound::new(SoundKind::Shockwave, pos.0, 16.0, time),
|
sound: Sound::new(SoundKind::Shockwave, pos.0, 16.0, time, Some(entity)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -692,10 +692,20 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
|
|||||||
let ecs = &server.state.ecs();
|
let ecs = &server.state.ecs();
|
||||||
let server_eventbus = ecs.read_resource::<EventBus<ServerEvent>>();
|
let server_eventbus = ecs.read_resource::<EventBus<ServerEvent>>();
|
||||||
let time = ecs.read_resource::<Time>();
|
let time = ecs.read_resource::<Time>();
|
||||||
|
let owner_entity = owner.and_then(|uid| {
|
||||||
|
ecs.read_resource::<UidAllocator>()
|
||||||
|
.retrieve_entity_internal(uid.into())
|
||||||
|
});
|
||||||
|
|
||||||
let explosion_volume = 2.5 * explosion.radius;
|
let explosion_volume = 2.5 * explosion.radius;
|
||||||
server_eventbus.emit_now(ServerEvent::Sound {
|
server_eventbus.emit_now(ServerEvent::Sound {
|
||||||
sound: Sound::new(SoundKind::Explosion, pos, explosion_volume, time.0),
|
sound: Sound::new(
|
||||||
|
SoundKind::Explosion,
|
||||||
|
pos,
|
||||||
|
explosion_volume,
|
||||||
|
time.0,
|
||||||
|
owner_entity,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add an outcome
|
// Add an outcome
|
||||||
@ -712,10 +722,6 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
|
|||||||
.any(|e| matches!(e, RadiusEffect::Attack(_))),
|
.any(|e| matches!(e, RadiusEffect::Attack(_))),
|
||||||
reagent: explosion.reagent,
|
reagent: explosion.reagent,
|
||||||
});
|
});
|
||||||
let owner_entity = owner.and_then(|uid| {
|
|
||||||
ecs.read_resource::<UidAllocator>()
|
|
||||||
.retrieve_entity_internal(uid.into())
|
|
||||||
});
|
|
||||||
let groups = ecs.read_storage::<comp::Group>();
|
let groups = ecs.read_storage::<comp::Group>();
|
||||||
|
|
||||||
// Used to get strength of explosion effects as they falloff over distance
|
// Used to get strength of explosion effects as they falloff over distance
|
||||||
|
@ -5,7 +5,8 @@ use common::{
|
|||||||
comp::{
|
comp::{
|
||||||
self,
|
self,
|
||||||
agent::{
|
agent::{
|
||||||
AgentEvent, Target, DEFAULT_INTERACTION_TIME, MAX_LISTEN_DIST, TRADE_INTERACTION_TIME,
|
AgentEvent, Sound, SoundKind, Target, DEFAULT_INTERACTION_TIME, MAX_LISTEN_DIST,
|
||||||
|
TRADE_INTERACTION_TIME,
|
||||||
},
|
},
|
||||||
buff::{BuffKind, Buffs},
|
buff::{BuffKind, Buffs},
|
||||||
compass::{Direction, Distance},
|
compass::{Direction, Distance},
|
||||||
@ -399,66 +400,10 @@ impl<'a> System<'a> for Sys {
|
|||||||
&read_data.terrain,
|
&read_data.terrain,
|
||||||
tgt_pos,
|
tgt_pos,
|
||||||
);
|
);
|
||||||
// Attack target's attacker
|
} else if target_was_attacked(target, &read_data) {
|
||||||
} else if tgt_health.last_change.0 < 5.0
|
data.attack_targets_attacker(
|
||||||
&& tgt_health.last_change.1.amount < 0
|
agent, &read_data, controller,
|
||||||
{
|
);
|
||||||
if let comp::HealthSource::Damage {
|
|
||||||
by: Some(by), ..
|
|
||||||
} = tgt_health.last_change.1.cause
|
|
||||||
{
|
|
||||||
if let Some(attacker) = read_data
|
|
||||||
.uid_allocator
|
|
||||||
.retrieve_entity_internal(by.id())
|
|
||||||
{
|
|
||||||
if agent.target.is_none() {
|
|
||||||
controller.push_event(
|
|
||||||
ControlEvent::Utterance(
|
|
||||||
UtteranceKind::Angry,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
agent.target = Some(Target {
|
|
||||||
target: attacker,
|
|
||||||
hostile: true,
|
|
||||||
selected_at: read_data.time.0,
|
|
||||||
});
|
|
||||||
if let Some(tgt_pos) =
|
|
||||||
read_data.positions.get(attacker)
|
|
||||||
{
|
|
||||||
if should_stop_attacking(
|
|
||||||
read_data.healths.get(attacker),
|
|
||||||
read_data.buffs.get(attacker),
|
|
||||||
) {
|
|
||||||
agent.target = Some(Target {
|
|
||||||
target,
|
|
||||||
hostile: false,
|
|
||||||
selected_at: read_data.time.0,
|
|
||||||
});
|
|
||||||
data.idle(
|
|
||||||
agent, controller, &read_data,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let target_data = TargetData {
|
|
||||||
pos: tgt_pos,
|
|
||||||
body: read_data
|
|
||||||
.bodies
|
|
||||||
.get(attacker),
|
|
||||||
scale: read_data
|
|
||||||
.scales
|
|
||||||
.get(attacker),
|
|
||||||
};
|
|
||||||
data.attack(
|
|
||||||
agent,
|
|
||||||
controller,
|
|
||||||
&target_data,
|
|
||||||
&read_data,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Follow owner if too far away and not
|
// Follow owner if too far away and not
|
||||||
// fighting
|
// fighting
|
||||||
} else if dist_sqrd > MAX_FOLLOW_DIST.powi(2) {
|
} else if dist_sqrd > MAX_FOLLOW_DIST.powi(2) {
|
||||||
@ -509,10 +454,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
if let Some(tgt_pos) = read_data.positions.get(attacker) {
|
if let Some(tgt_pos) = read_data.positions.get(attacker) {
|
||||||
// If the target is dead or in a safezone, remove the
|
// If the target is dead or in a safezone, remove the
|
||||||
// target and idle.
|
// target and idle.
|
||||||
if should_stop_attacking(
|
if should_stop_attacking(attacker, &read_data) {
|
||||||
read_data.healths.get(attacker),
|
|
||||||
read_data.buffs.get(attacker),
|
|
||||||
) {
|
|
||||||
agent.target = None;
|
agent.target = None;
|
||||||
data.idle_tree(
|
data.idle_tree(
|
||||||
agent,
|
agent,
|
||||||
@ -527,16 +469,11 @@ impl<'a> System<'a> for Sys {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
agent.target = Some(Target {
|
agent.target =
|
||||||
target: attacker,
|
build_target(attacker, true, read_data.time.0);
|
||||||
hostile: true,
|
let target_data = build_target_data(
|
||||||
selected_at: read_data.time.0,
|
attacker, tgt_pos, &read_data,
|
||||||
});
|
);
|
||||||
let target_data = TargetData {
|
|
||||||
pos: tgt_pos,
|
|
||||||
body: read_data.bodies.get(attacker),
|
|
||||||
scale: read_data.scales.get(attacker),
|
|
||||||
};
|
|
||||||
data.attack(
|
data.attack(
|
||||||
agent,
|
agent,
|
||||||
controller,
|
controller,
|
||||||
@ -625,11 +562,7 @@ impl<'a> AgentData<'a> {
|
|||||||
if agent.target.is_none() && thread_rng().gen_bool(0.1) {
|
if agent.target.is_none() && thread_rng().gen_bool(0.1) {
|
||||||
if let Some(Alignment::Owned(owner)) = self.alignment {
|
if let Some(Alignment::Owned(owner)) = self.alignment {
|
||||||
if let Some(owner) = read_data.uid_allocator.retrieve_entity_internal(owner.id()) {
|
if let Some(owner) = read_data.uid_allocator.retrieve_entity_internal(owner.id()) {
|
||||||
agent.target = Some(Target {
|
agent.target = build_target(owner, false, read_data.time.0);
|
||||||
target: owner,
|
|
||||||
hostile: false,
|
|
||||||
selected_at: read_data.time.0,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -717,6 +650,15 @@ impl<'a> AgentData<'a> {
|
|||||||
let msg = "npc.speech.villager_under_attack".to_string();
|
let msg = "npc.speech.villager_under_attack".to_string();
|
||||||
event_emitter
|
event_emitter
|
||||||
.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg)));
|
.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg)));
|
||||||
|
event_emitter.emit(ServerEvent::Sound {
|
||||||
|
sound: Sound::new(
|
||||||
|
SoundKind::VillagerAlarm,
|
||||||
|
self.pos.0,
|
||||||
|
100.0,
|
||||||
|
read_data.time.0,
|
||||||
|
Some(*self.entity),
|
||||||
|
),
|
||||||
|
});
|
||||||
agent.action_state.timer = 0.01;
|
agent.action_state.timer = 0.01;
|
||||||
} else if agent.action_state.timer < FLEE_DURATION || dist_sqrd < MAX_FLEE_DIST
|
} else if agent.action_state.timer < FLEE_DURATION || dist_sqrd < MAX_FLEE_DIST
|
||||||
{
|
{
|
||||||
@ -732,10 +674,7 @@ impl<'a> AgentData<'a> {
|
|||||||
} else {
|
} else {
|
||||||
// If the hostile entity is dead or has an invulnerability buff (eg, those
|
// If the hostile entity is dead or has an invulnerability buff (eg, those
|
||||||
// applied in safezones), return to idle
|
// applied in safezones), return to idle
|
||||||
if should_stop_attacking(
|
if should_stop_attacking(target, &read_data) {
|
||||||
read_data.healths.get(target),
|
|
||||||
read_data.buffs.get(target),
|
|
||||||
) {
|
|
||||||
if agent.behavior.can(BehaviorCapability::SPEAK) {
|
if agent.behavior.can(BehaviorCapability::SPEAK) {
|
||||||
let msg = "npc.speech.villager_enemy_killed".to_string();
|
let msg = "npc.speech.villager_enemy_killed".to_string();
|
||||||
event_emitter
|
event_emitter
|
||||||
@ -753,11 +692,7 @@ impl<'a> AgentData<'a> {
|
|||||||
self.choose_target(agent, controller, read_data, event_emitter);
|
self.choose_target(agent, controller, read_data, event_emitter);
|
||||||
} else {
|
} else {
|
||||||
// TODO Add utility for attacking vs leaving target alone
|
// TODO Add utility for attacking vs leaving target alone
|
||||||
let target_data = TargetData {
|
let target_data = build_target_data(target, tgt_pos, read_data);
|
||||||
pos: tgt_pos,
|
|
||||||
body: read_data.bodies.get(target),
|
|
||||||
scale: read_data.scales.get(target),
|
|
||||||
};
|
|
||||||
self.attack(agent, controller, &target_data, read_data);
|
self.attack(agent, controller, &target_data, read_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1024,11 +959,7 @@ impl<'a> AgentData<'a> {
|
|||||||
if agent.behavior.can(BehaviorCapability::SPEAK) {
|
if agent.behavior.can(BehaviorCapability::SPEAK) {
|
||||||
if let Some(target) = read_data.uid_allocator.retrieve_entity_internal(by.id())
|
if let Some(target) = read_data.uid_allocator.retrieve_entity_internal(by.id())
|
||||||
{
|
{
|
||||||
agent.target = Some(Target {
|
agent.target = build_target(target, false, read_data.time.0);
|
||||||
target,
|
|
||||||
hostile: false,
|
|
||||||
selected_at: read_data.time.0,
|
|
||||||
});
|
|
||||||
|
|
||||||
if self.look_toward(controller, read_data, &target) {
|
if self.look_toward(controller, read_data, &target) {
|
||||||
controller.actions.push(ControlAction::Stand);
|
controller.actions.push(ControlAction::Stand);
|
||||||
@ -1247,11 +1178,7 @@ impl<'a> AgentData<'a> {
|
|||||||
if let Some(target) =
|
if let Some(target) =
|
||||||
read_data.uid_allocator.retrieve_entity_internal(with.id())
|
read_data.uid_allocator.retrieve_entity_internal(with.id())
|
||||||
{
|
{
|
||||||
agent.target = Some(Target {
|
agent.target = build_target(target, false, read_data.time.0);
|
||||||
target,
|
|
||||||
hostile: false,
|
|
||||||
selected_at: read_data.time.0,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
controller
|
controller
|
||||||
.events
|
.events
|
||||||
@ -1287,11 +1214,7 @@ impl<'a> AgentData<'a> {
|
|||||||
if let Some(target) =
|
if let Some(target) =
|
||||||
read_data.uid_allocator.retrieve_entity_internal(with.id())
|
read_data.uid_allocator.retrieve_entity_internal(with.id())
|
||||||
{
|
{
|
||||||
agent.target = Some(Target {
|
agent.target = build_target(target, false, read_data.time.0);
|
||||||
target,
|
|
||||||
hostile: false,
|
|
||||||
selected_at: read_data.time.0,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
agent.behavior.set(BehaviorState::TRADING);
|
agent.behavior.set(BehaviorState::TRADING);
|
||||||
agent.behavior.set(BehaviorState::TRADING_ISSUER);
|
agent.behavior.set(BehaviorState::TRADING_ISSUER);
|
||||||
@ -1575,6 +1498,15 @@ impl<'a> AgentData<'a> {
|
|||||||
}
|
}
|
||||||
let msg = "npc.speech.villager_cultist_alarm".to_string();
|
let msg = "npc.speech.villager_cultist_alarm".to_string();
|
||||||
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg)));
|
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg)));
|
||||||
|
event_emitter.emit(ServerEvent::Sound {
|
||||||
|
sound: Sound::new(
|
||||||
|
SoundKind::VillagerAlarm,
|
||||||
|
self.pos.0,
|
||||||
|
100.0,
|
||||||
|
read_data.time.0,
|
||||||
|
Some(*self.entity),
|
||||||
|
),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
@ -3937,33 +3869,89 @@ impl<'a> AgentData<'a> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_enemy = matches!(self.alignment, Some(Alignment::Enemy));
|
|
||||||
|
|
||||||
if let Some(sound) = agent.sounds_heard.last() {
|
if let Some(sound) = agent.sounds_heard.last() {
|
||||||
let sound_pos = Pos(sound.pos);
|
// FIXME: Perhaps name should be a field of the agent, not stats
|
||||||
let dist_sqrd = self.pos.0.distance_squared(sound_pos.0);
|
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);
|
||||||
|
|
||||||
if is_enemy {
|
let is_village_guard = agent_stats.name == *"Guard".to_string();
|
||||||
let far_enough = dist_sqrd > 10.0_f32.powi(2);
|
let is_neutral = self.flees && !is_village_guard;
|
||||||
|
let is_enemy = matches!(self.alignment, Some(Alignment::Enemy));
|
||||||
|
|
||||||
if far_enough {
|
if is_enemy {
|
||||||
self.follow(agent, controller, &read_data.terrain, &sound_pos);
|
let far_enough = dist_sqrd > 10.0_f32.powi(2);
|
||||||
|
|
||||||
|
if far_enough {
|
||||||
|
self.follow(agent, controller, &read_data.terrain, &sound_pos);
|
||||||
|
} else {
|
||||||
|
// TODO: Change this to a search action instead of idle
|
||||||
|
self.idle(agent, controller, &read_data);
|
||||||
|
}
|
||||||
|
} else if is_neutral {
|
||||||
|
let aggro = agent.psyche.aggro;
|
||||||
|
let close_enough = dist_sqrd < 35.0_f32.powi(2);
|
||||||
|
let sound_was_loud = sound.vol >= 10.0;
|
||||||
|
|
||||||
|
if close_enough && (aggro <= 0.5 || (aggro <= 0.7 && sound_was_loud)) {
|
||||||
|
self.flee(agent, controller, &read_data.terrain, &sound_pos);
|
||||||
|
} else {
|
||||||
|
self.idle(agent, controller, read_data);
|
||||||
|
}
|
||||||
|
} else if is_village_guard {
|
||||||
|
let sound_is_villager_alarm = matches!(sound.kind, SoundKind::VillagerAlarm);
|
||||||
|
|
||||||
|
if sound_is_villager_alarm {
|
||||||
|
if let Some(alarmist) = sound.owner {
|
||||||
|
agent.target = build_target(alarmist, true, read_data.time.0);
|
||||||
|
self.attack_targets_attacker(agent, &read_data, controller);
|
||||||
|
} else {
|
||||||
|
self.follow(agent, controller, &read_data.terrain, &sound_pos);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.idle(agent, controller, &read_data);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: Change this to a search action instead of idle
|
// TODO: Change this to a search action instead of idle
|
||||||
self.idle(agent, controller, read_data);
|
self.idle(agent, controller, read_data);
|
||||||
}
|
}
|
||||||
} else if self.flees {
|
}
|
||||||
let aggro = agent.psyche.aggro;
|
}
|
||||||
let close_enough = dist_sqrd < 35.0_f32.powi(2);
|
}
|
||||||
let loud_sound = sound.vol >= 10.0;
|
|
||||||
|
|
||||||
if close_enough && (aggro <= 0.5 || (aggro <= 0.7 && loud_sound)) {
|
fn attack_targets_attacker(
|
||||||
self.flee(agent, controller, &read_data.terrain, &sound_pos);
|
&self,
|
||||||
} else {
|
agent: &mut Agent,
|
||||||
self.idle(agent, controller, read_data);
|
read_data: &ReadData,
|
||||||
|
controller: &mut Controller,
|
||||||
|
) {
|
||||||
|
if let Some(Target { target, .. }) = agent.target {
|
||||||
|
if let Some(tgt_health) = read_data.healths.get(target) {
|
||||||
|
if let comp::HealthSource::Damage { by: Some(by), .. } =
|
||||||
|
tgt_health.last_change.1.cause
|
||||||
|
{
|
||||||
|
if let Some(attacker) =
|
||||||
|
read_data.uid_allocator.retrieve_entity_internal(by.id())
|
||||||
|
{
|
||||||
|
if agent.target.is_none() {
|
||||||
|
controller.push_event(ControlEvent::Utterance(UtteranceKind::Angry));
|
||||||
|
}
|
||||||
|
|
||||||
|
agent.target = build_target(attacker, true, read_data.time.0);
|
||||||
|
|
||||||
|
if let Some(tgt_pos) = read_data.positions.get(attacker) {
|
||||||
|
if should_stop_attacking(attacker, &read_data) {
|
||||||
|
agent.target = build_target(target, false, read_data.time.0);
|
||||||
|
|
||||||
|
self.idle(agent, controller, &read_data);
|
||||||
|
} else {
|
||||||
|
let target_data = build_target_data(target, tgt_pos, read_data);
|
||||||
|
|
||||||
|
self.attack(agent, controller, &target_data, &read_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
self.idle(agent, controller, read_data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4037,7 +4025,10 @@ fn can_see_tgt(terrain: &TerrainGrid, pos: &Pos, tgt_pos: &Pos, dist_sqrd: f32)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If target is dead or has invulnerability buff, returns true
|
// If target is dead or has invulnerability buff, returns true
|
||||||
fn should_stop_attacking(health: Option<&Health>, buffs: Option<&Buffs>) -> bool {
|
fn should_stop_attacking(target: EcsEntity, read_data: &ReadData) -> bool {
|
||||||
|
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)
|
health.map_or(true, |a| a.is_dead) || invulnerability_is_in_buffs(buffs)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4114,3 +4105,31 @@ fn decrement_awareness(agent: &mut Agent) {
|
|||||||
|
|
||||||
agent.awareness -= decrement;
|
agent.awareness -= decrement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn target_was_attacked(target: EcsEntity, read_data: &ReadData) -> bool {
|
||||||
|
if let Some(tgt_health) = read_data.healths.get(target) {
|
||||||
|
tgt_health.last_change.0 < 5.0 && tgt_health.last_change.1.amount < 0
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_target(target: EcsEntity, is_hostile: bool, time: f64) -> Option<Target> {
|
||||||
|
Some(Target {
|
||||||
|
target,
|
||||||
|
hostile: is_hostile,
|
||||||
|
selected_at: time,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_target_data<'a>(
|
||||||
|
target: EcsEntity,
|
||||||
|
tgt_pos: &'a Pos,
|
||||||
|
read_data: &'a ReadData,
|
||||||
|
) -> TargetData<'a> {
|
||||||
|
TargetData {
|
||||||
|
pos: tgt_pos,
|
||||||
|
body: read_data.bodies.get(target),
|
||||||
|
scale: read_data.scales.get(target),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user