Skip to content
12 changes: 2 additions & 10 deletions crates/pumpkin/src/command/commands/team.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use crate::command::context::command_context::CommandContext;
use crate::command::errors::error_types::CommandErrorType;
use crate::command::node::dispatcher::CommandDispatcher;
use crate::command::node::{CommandExecutor, CommandExecutorResult};
use crate::entity::EntityBase;
use crate::world::scoreboard::{CollisionRule, NameTagVisibility, Team};
use pumpkin_data::translation;
use pumpkin_util::PermissionLvl;
Expand Down Expand Up @@ -82,13 +81,6 @@ const SEE_FRIENDLY_INVISIBLES_ALREADY_DISABLED_ERROR: CommandErrorType<0> = Comm
translation::java::COMMANDS_TEAM_OPTION_SEEFRIENDLYINVISIBLES_ALREADYDISABLED,
);

fn get_entity_scoreboard_name(entity: &dyn EntityBase) -> String {
entity.get_player().map_or_else(
|| entity.get_entity().entity_uuid.to_string(),
|player| player.gameprofile.name.clone(),
)
}

struct TeamAddExecutor {
has_display_name: bool,
}
Expand Down Expand Up @@ -244,7 +236,7 @@ impl CommandExecutor for TeamJoinExecutor {
}
targets
.into_iter()
.map(|e| get_entity_scoreboard_name(&*e))
.map(|e| e.get_scoreboard_name())
.collect::<Vec<_>>()
} else {
let sender_name = context.source.name.clone();
Expand Down Expand Up @@ -311,7 +303,7 @@ impl CommandExecutor for TeamLeaveExecutor {
}
targets
.into_iter()
.map(|e| get_entity_scoreboard_name(&*e))
.map(|e| e.get_scoreboard_name())
.collect::<Vec<_>>()
} else {
let sender_name = context.source.name.clone();
Expand Down
67 changes: 31 additions & 36 deletions crates/pumpkin/src/entity/ai/goal/active_target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,49 +77,44 @@ impl ActiveTargetGoal {
self.target = target;
}

fn find_closest_target(&mut self, mob: &MobEntity) {
let follow_range = mob
fn find_closest_target(&mut self, mob: &dyn Mob) {
let mob_entity = mob.get_mob_entity();
let follow_range = mob_entity
.living_entity
.get_attribute_value(&Attributes::FOLLOW_RANGE);

// Vanilla updates the target conditions with the current follow distance on every search
self.target_predicate.base_max_distance = follow_range;

let world = mob.living_entity.entity.world.load();
let world = mob_entity.living_entity.entity.world.load();

// Vanilla searches using getEyeY(), so we offset the position by the eye height
let mut search_pos = mob.living_entity.entity.pos.load();
search_pos.y += mob.living_entity.entity.entity_dimension.load().eye_height as f64;

if self.target_type == &EntityType::PLAYER {
let potential_player = world
.get_closest_player(search_pos, follow_range)
.map(|p: Arc<Player>| p as Arc<dyn EntityBase>);

if let Some(potential_entity) = potential_player
&& let Some(living) = potential_entity.get_living_entity()
&& self
.target_predicate
.test(&world, Some(&mob.living_entity), living)
{
self.target = Some(potential_entity);
return;
}
let mut search_pos = mob_entity.living_entity.entity.pos.load();
search_pos.y += mob_entity
.living_entity
.entity
.entity_dimension
.load()
.eye_height as f64;

// Pick the nearest candidate that passes the conditions, not the nearest overall.
let predicate = &self.target_predicate;
let found = if self.target_type == &EntityType::PLAYER {
world
.get_nearest_player(search_pos, follow_range, |player| {
predicate.test(&world, Some(mob), player.as_ref())
})
.map(|p: Arc<Player>| p as Arc<dyn EntityBase>)
} else {
let potential_entity =
world.get_closest_entity(search_pos, follow_range, Some(&[self.target_type]));

if let Some(potential_entity) = potential_entity
&& let Some(living) = potential_entity.get_living_entity()
&& self
.target_predicate
.test(&world, Some(&mob.living_entity), living)
{
self.target = Some(potential_entity);
return;
}
}
self.target = None;
world.get_nearest_entity(
search_pos,
follow_range,
Some(&[self.target_type]),
|entity| predicate.test(&world, Some(mob), entity.as_ref()),
)
};

self.target = found;
}
}

Expand All @@ -130,11 +125,11 @@ impl Goal for ActiveTargetGoal {
{
return false;
}
self.find_closest_target(mob.get_mob_entity());
self.find_closest_target(mob);
self.target.is_some()
}

fn should_continue(&self, mob: &dyn Mob) -> bool {
fn should_continue(&mut self, mob: &dyn Mob) -> bool {
self.track_target_goal.should_continue(mob)
}

Expand Down
8 changes: 8 additions & 0 deletions crates/pumpkin/src/entity/ai/goal/ambient_stand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ impl AmbientStandGoal {
}

impl Goal for AmbientStandGoal {
fn should_continue(&mut self, _mob: &dyn Mob) -> bool {
false
}

fn can_start(&mut self, mob: &dyn Mob) -> bool {
self.cooldown += 1;
if self.cooldown > 0 && mob.get_random().random_range(0..1000) < self.cooldown {
Expand All @@ -25,6 +29,10 @@ impl Goal for AmbientStandGoal {
false
}

fn should_run_every_tick(&self) -> bool {
true
}

fn controls(&self) -> Controls {
self.goal_control
}
Expand Down
111 changes: 24 additions & 87 deletions crates/pumpkin/src/entity/ai/goal/avoid_entity.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use std::sync::Arc;

use super::{Controls, Goal};
use crate::entity::ai::util::default_random_pos;
use crate::entity::predicate::EntityPredicate;
use crate::entity::{EntityBase, ai::pathfinder::NavigatorGoal, mob::Mob};
use pumpkin_data::entity::EntityType;
use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
use rand::RngExt;
use pumpkin_util::math::vector3::Vector3;

const FAST_DISTANCE_SQ: f64 = 49.0;
const HORIZONTAL_RANGE: f64 = 16.0;
const HORIZONTAL_RANGE: i32 = 16;
const VERTICAL_RANGE: i32 = 7;

pub struct AvoidEntityGoal {
Expand Down Expand Up @@ -46,105 +47,45 @@ impl AvoidEntityGoal {

if self.flee_type == &EntityType::PLAYER {
world
.get_closest_player(pos, self.flee_distance)
.get_nearest_player(pos, self.flee_distance, |player| {
EntityPredicate::ExceptCreativeOrSpectator.test(player.get_entity())
})
.map(|p| p as Arc<dyn EntityBase>)
} else {
world.get_closest_entity(pos, self.flee_distance, Some(&[self.flee_type]))
world.get_nearest_entity(pos, self.flee_distance, Some(&[self.flee_type]), |entity| {
EntityPredicate::ExceptCreativeOrSpectator.test(entity.get_entity())
})
}
}

/// Generates a random walkable position within a cone pointing away from the threat.
/// Mirrors vanilla's `NoPenaltyTargeting.findFrom()`.
fn find_flee_position(mob: &dyn Mob, threat_pos: &Vector3<f64>) -> Option<Vector3<f64>> {
let entity = &mob.get_mob_entity().living_entity.entity;
let mob_pos = entity.pos.load();
let world = entity.world.load();

let candidates = {
let mut rng = mob.get_random();
let dir_x = mob_pos.x - threat_pos.x;
let dir_z = mob_pos.z - threat_pos.z;
let (dir_x, dir_z) = if dir_x == 0.0 && dir_z == 0.0 {
(rng.random_range(-1.0..1.0), rng.random_range(-1.0..1.0))
} else {
(dir_x, dir_z)
};
let base_angle = dir_z.atan2(dir_x) - std::f64::consts::FRAC_PI_2;

let mut candidates = Vec::with_capacity(10);
for _ in 0..10 {
let angle = base_angle
+ (2.0 * rng.random_range(0.0..1.0) - 1.0) * std::f64::consts::FRAC_PI_2;
let t = rng.random_range(0.0..1.0f64).sqrt();
let dist = t * HORIZONTAL_RANGE * std::f64::consts::SQRT_2;
let dx = -dist * angle.sin();
let dz = dist * angle.cos();
let dy = rng.random_range(-VERTICAL_RANGE..=VERTICAL_RANGE);
candidates.push((dx, dy, dz));
}
candidates
};

let threat_to_mob_sq = threat_pos.squared_distance_to_vec(&mob_pos);

for (dx, dy, dz) in candidates {
if dx.abs() > HORIZONTAL_RANGE || dz.abs() > HORIZONTAL_RANGE {
continue;
}

let candidate = BlockPos::new(
(mob_pos.x + dx) as i32,
(mob_pos.y + dy as f64) as i32,
(mob_pos.z + dz) as i32,
);

let block_at = world.get_block_state(&candidate);
let block_below = world.get_block_state(&BlockPos::new(
candidate.0.x,
candidate.0.y - 1,
candidate.0.z,
));

if block_at.is_solid() || !block_below.is_solid() {
continue;
}

let flee_vec = Vector3::new(
candidate.0.x as f64 + 0.5,
candidate.0.y as f64,
candidate.0.z as f64 + 0.5,
);

if threat_pos.squared_distance_to_vec(&flee_vec) < threat_to_mob_sq {
continue;
}

return Some(flee_vec);
}

None
}
}

impl Goal for AvoidEntityGoal {
fn can_start(&mut self, mob: &dyn Mob) -> bool {
let threat = self.find_threat(mob);
let Some(target) = threat else {
let Some(target) = self.find_threat(mob) else {
return false;
};

let threat_pos = target.get_entity().pos.load();
let flee_pos = Self::find_flee_position(mob, &threat_pos);
let Some(pos) = flee_pos else {
let Some(flee_pos) =
default_random_pos::get_pos_away(mob, HORIZONTAL_RANGE, VERTICAL_RANGE, threat_pos)
else {
return false;
};

// Give up when the escape route does not gain any distance.
let mob_pos = mob.get_entity().pos.load();
if threat_pos.squared_distance_to_vec(&flee_pos)
< threat_pos.squared_distance_to_vec(&mob_pos)
{
return false;
}

self.target = Some(target);
self.flee_pos = Some(pos);
self.flee_pos = Some(flee_pos);
true
}

fn should_continue(&self, mob: &dyn Mob) -> bool {
fn should_continue(&mut self, mob: &dyn Mob) -> bool {
let navigator = mob
.get_mob_entity()
.navigator
Expand Down Expand Up @@ -184,10 +125,6 @@ impl Goal for AvoidEntityGoal {
}
}

fn should_run_every_tick(&self) -> bool {
true
}

fn stop(&mut self, _mob: &dyn Mob) {
self.target = None;
self.flee_pos = None;
Expand Down
20 changes: 6 additions & 14 deletions crates/pumpkin/src/entity/ai/goal/beg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use rand::RngExt;
use std::sync::Arc;

pub struct BegGoal {
look_distance: f64,
look_distance_sq: f64,
look_time: i32,
player: Option<Arc<Player>>,
Expand All @@ -18,6 +19,7 @@ impl BegGoal {
#[must_use]
pub fn new(look_distance: f32) -> Box<Self> {
Box::new(Self {
look_distance: f64::from(look_distance),
look_distance_sq: f64::from(look_distance) * f64::from(look_distance),
look_time: 0,
player: None,
Expand Down Expand Up @@ -61,19 +63,9 @@ impl Goal for BegGoal {
let world = mob_entity.living_entity.entity.world.load();
let pos = mob_entity.living_entity.entity.pos.load();

let mut closest_player = None;
let mut min_distance = self.look_distance_sq;

for player in world.get_nearby_players(pos, 8.0) {
let distance = Self::distance_sq(mob, &player);

if distance < min_distance {
min_distance = distance;
closest_player = Some(player);
}
}

let Some(player) = closest_player else {
let Some(player) = world.get_nearest_player(pos, self.look_distance, |player| {
player.living_entity.is_part_of_game()
}) else {
return false;
};

Expand All @@ -85,7 +77,7 @@ impl Goal for BegGoal {
true
}

fn should_continue(&self, mob: &dyn Mob) -> bool {
fn should_continue(&mut self, mob: &dyn Mob) -> bool {
let Some(player) = &self.player else {
return false;
};
Expand Down
Loading
Loading