mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
fix(ai): respect target visibility (#2701)
This commit is contained in:
@@ -79,7 +79,7 @@ impl ActiveTargetGoal {
|
||||
self.target = target;
|
||||
}
|
||||
|
||||
fn find_closest_target(&mut self, mob: &MobEntity) {
|
||||
async fn find_closest_target(&mut self, mob: &MobEntity) {
|
||||
let follow_range = mob
|
||||
.living_entity
|
||||
.get_attribute_value(&Attributes::FOLLOW_RANGE);
|
||||
@@ -103,6 +103,7 @@ impl ActiveTargetGoal {
|
||||
&& self
|
||||
.target_predicate
|
||||
.test(&world, Some(&mob.living_entity), living)
|
||||
.await
|
||||
{
|
||||
self.target = Some(potential_entity);
|
||||
return;
|
||||
@@ -116,6 +117,7 @@ impl ActiveTargetGoal {
|
||||
&& self
|
||||
.target_predicate
|
||||
.test(&world, Some(&mob.living_entity), living)
|
||||
.await
|
||||
{
|
||||
self.target = Some(potential_entity);
|
||||
return;
|
||||
@@ -133,7 +135,7 @@ impl Goal for ActiveTargetGoal {
|
||||
{
|
||||
return false;
|
||||
}
|
||||
self.find_closest_target(mob.get_mob_entity());
|
||||
self.find_closest_target(mob.get_mob_entity()).await;
|
||||
self.target.is_some()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ impl Goal for RevengeGoal {
|
||||
if !self
|
||||
.target_predicate
|
||||
.test(&world, Some(&mob_entity.living_entity), attacker_living)
|
||||
.await
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -58,11 +58,15 @@ impl TeleportTowardsPlayerGoal {
|
||||
}
|
||||
|
||||
let living = player.get_living_entity()?;
|
||||
if !self.target_predicate.test(
|
||||
&world,
|
||||
Some(&self.enderman.mob_entity.living_entity),
|
||||
living,
|
||||
) {
|
||||
if !self
|
||||
.target_predicate
|
||||
.test(
|
||||
&world,
|
||||
Some(&self.enderman.mob_entity.living_entity),
|
||||
living,
|
||||
)
|
||||
.await
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ impl TrackTargetGoal {
|
||||
check_can_navigate_cooldown: AtomicI32::new(0),
|
||||
time_without_visibility: AtomicI32::new(0),
|
||||
max_time_without_visibility: 60,
|
||||
target_predicate: TargetPredicate::create_attackable(),
|
||||
target_predicate: TargetPredicate::create_attackable().ignore_visibility(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +55,18 @@ impl TrackTargetGoal {
|
||||
false
|
||||
}
|
||||
|
||||
fn remembers_visible_target(&self, has_line_of_sight: bool) -> bool {
|
||||
if has_line_of_sight {
|
||||
self.time_without_visibility.store(0, Ordering::Relaxed);
|
||||
true
|
||||
} else {
|
||||
let unseen_ticks = self.time_without_visibility.fetch_add(1, Ordering::Relaxed) + 1;
|
||||
unseen_ticks <= to_goal_ticks(self.max_time_without_visibility)
|
||||
}
|
||||
}
|
||||
|
||||
/// Equivalent to Vanilla's `canAttack` check inside `TargetGoal`
|
||||
pub fn can_track(
|
||||
pub async fn can_track(
|
||||
&self,
|
||||
mob: &dyn Mob,
|
||||
target: Option<&LivingEntity>,
|
||||
@@ -69,7 +79,10 @@ impl TrackTargetGoal {
|
||||
let mob_entity = mob.get_mob_entity();
|
||||
let world = mob_entity.living_entity.entity.world.load();
|
||||
|
||||
if !target_predicate.test(&world, Some(&mob_entity.living_entity), target) {
|
||||
if !target_predicate
|
||||
.test(&world, Some(&mob_entity.living_entity), target)
|
||||
.await
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -119,7 +132,10 @@ impl Goal for TrackTargetGoal {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !self.can_track(mob, Some(target), &self.target_predicate) {
|
||||
if !self
|
||||
.can_track(mob, Some(target), &self.target_predicate)
|
||||
.await
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -142,17 +158,18 @@ impl Goal for TrackTargetGoal {
|
||||
}
|
||||
|
||||
if self.check_visibility {
|
||||
// TODO: mob.getSensing().hasLineOfSight(target)
|
||||
let has_line_of_sight = true;
|
||||
let world = mob_entity.living_entity.entity.world.load();
|
||||
let has_line_of_sight = world
|
||||
.raycast(
|
||||
mob_entity.living_entity.entity.get_eye_pos(),
|
||||
target.entity.get_eye_pos(),
|
||||
async |block_pos, world| world.get_block_state(block_pos).is_solid(),
|
||||
)
|
||||
.await
|
||||
.is_none();
|
||||
|
||||
if has_line_of_sight {
|
||||
self.time_without_visibility.store(0, Ordering::Relaxed);
|
||||
} else {
|
||||
let unseen_ticks =
|
||||
self.time_without_visibility.fetch_add(1, Ordering::Relaxed) + 1;
|
||||
if unseen_ticks > to_goal_ticks(self.max_time_without_visibility) {
|
||||
return false;
|
||||
}
|
||||
if !self.remembers_visible_target(has_line_of_sight) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,3 +196,28 @@ impl Goal for TrackTargetGoal {
|
||||
self.goal_control
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{TrackTargetGoal, to_goal_ticks};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
#[test]
|
||||
fn forgets_unseen_target_after_vanilla_memory_window() {
|
||||
let goal = TrackTargetGoal::with_default(true);
|
||||
let memory_ticks = to_goal_ticks(goal.max_time_without_visibility);
|
||||
|
||||
for _ in 0..memory_ticks {
|
||||
assert!(goal.remembers_visible_target(false));
|
||||
}
|
||||
assert!(!goal.remembers_visible_target(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seeing_target_resets_unseen_memory() {
|
||||
let goal = TrackTargetGoal::with_default(true);
|
||||
assert!(goal.remembers_visible_target(false));
|
||||
assert!(goal.remembers_visible_target(true));
|
||||
assert_eq!(goal.time_without_visibility.load(Ordering::Relaxed), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ impl TargetPredicate {
|
||||
));
|
||||
}
|
||||
|
||||
pub fn test(
|
||||
pub async fn test(
|
||||
&self,
|
||||
world: &World,
|
||||
tester: Option<&LivingEntity>,
|
||||
@@ -126,7 +126,23 @@ impl TargetPredicate {
|
||||
if dist_sq > max_dist * max_dist {
|
||||
return false;
|
||||
}
|
||||
// TODO: visibility check (needs world raycast)
|
||||
}
|
||||
|
||||
if self.respects_visibility
|
||||
&& let Some(tester_ent) = tester
|
||||
&& tester_ent
|
||||
.entity
|
||||
.world
|
||||
.load_full()
|
||||
.raycast(
|
||||
tester_ent.entity.get_eye_pos(),
|
||||
target.entity.get_eye_pos(),
|
||||
async |block_pos, world| world.get_block_state(block_pos).is_solid(),
|
||||
)
|
||||
.await
|
||||
.is_some()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
|
||||
Reference in New Issue
Block a user