Skip to content

Commit

Permalink
Add Entity AI
Browse files Browse the repository at this point in the history
  • Loading branch information
Snowiiii committed Jan 4, 2025
1 parent 0fbc730 commit f2cfad0
Show file tree
Hide file tree
Showing 11 changed files with 281 additions and 71 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ and customizable experience. It prioritizes performance and player enjoyment whi
- [x] Players
- [x] Mobs
- [x] Animals
- [ ] Entity AI
- [x] Entity AI
- [ ] Boss
- Server
- [ ] Plugins
Expand Down
11 changes: 11 additions & 0 deletions pumpkin-core/src/math/vector3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ impl<T: Math + Copy> Vector3<T> {
z: self.z * z,
}
}

pub fn squared_distance_to_vec(&self, other: Self) -> T {
self.squared_distance_to(other.x, other.y, other.z)
}

pub fn squared_distance_to(&self, x: T, y: T, z: T) -> T {
let delta_x = self.x - x;
let delta_y = self.y - y;
let delta_z = self.z - z;
delta_x * delta_x + delta_y * delta_y + delta_z * delta_z
}
}

impl<T: Math + Copy + Float> Vector3<T> {
Expand Down
55 changes: 55 additions & 0 deletions pumpkin/src/entity/ai/goal/look_at_entity.rs
Original file line number Diff line number Diff line change
@@ -1 +1,56 @@
use std::sync::Arc;

use async_trait::async_trait;
use tokio::sync::Mutex;

use crate::entity::{mob::MobEntity, player::Player};

use super::Goal;

pub struct LookAtEntityGoal {
// TODO: make this an entity
target: Mutex<Option<Arc<Player>>>,
range: f64,
}

impl LookAtEntityGoal {
#[must_use]
pub fn new(range: f64) -> Self {
Self {
target: Mutex::new(None),
range,
}
}
}

#[async_trait]
impl Goal for LookAtEntityGoal {
async fn can_start(&self, mob: &crate::entity::mob::MobEntity) -> bool {
// TODO: make this an entity
let mut target = self.target.lock().await;

*target = mob
.living_entity
.entity
.world
.get_closest_player(mob.living_entity.entity.pos.load(), self.range)
.await;
target.is_some()
}

async fn should_continue(&self, mob: &MobEntity) -> bool {
if let Some(target) = self.target.lock().await.as_ref() {
let mob_pos = mob.living_entity.entity.pos.load();
let target_pos = target.living_entity.entity.pos.load();
return mob_pos.squared_distance_to_vec(target_pos) <= (self.range * self.range);
}
false
}

async fn tick(&self, mob: &MobEntity) {
if let Some(target) = self.target.lock().await.as_ref() {
let target_pos = target.living_entity.entity.pos.load();
mob.living_entity.entity.look_at(target_pos).await;
}
}
}
13 changes: 9 additions & 4 deletions pumpkin/src/entity/ai/goal/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
use async_trait::async_trait;

use crate::entity::mob::MobEntity;

pub mod look_at_entity;

pub trait Goal {
#[async_trait]
pub trait Goal: Send + Sync {
/// How Should the Goal initially start?
fn can_start(&self) -> bool;
async fn can_start(&self, mob: &MobEntity) -> bool;
/// When its started, How it should Continue to run
fn should_continue() -> bool;
async fn should_continue(&self, mob: &MobEntity) -> bool;
/// If the Goal is running, this gets called every tick
fn tick(&self);
async fn tick(&self, mob: &MobEntity);
}
53 changes: 53 additions & 0 deletions pumpkin/src/entity/mob/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,54 @@
use std::sync::Arc;

use pumpkin_core::math::vector3::Vector3;
use pumpkin_entity::entity_type::EntityType;
use tokio::sync::Mutex;
use uuid::Uuid;
use zombie::Zombie;

use crate::{server::Server, world::World};

use super::{ai::goal::Goal, living::LivingEntity};

pub mod zombie;

pub struct MobEntity {
pub living_entity: Arc<LivingEntity>,
pub goals: Mutex<Vec<(Arc<dyn Goal>, bool)>>,
}

impl MobEntity {
pub async fn tick(&self) {
let mut goals = self.goals.lock().await;
for (goal, running) in goals.iter_mut() {
if *running {
if goal.should_continue(self).await {
goal.tick(self).await;
} else {
*running = false;
}
} else {
*running = goal.can_start(self).await;
}
}
}
}

pub async fn from_type(
entity_type: EntityType,
server: &Server,
position: Vector3<f64>,
world: &Arc<World>,
) -> (Arc<MobEntity>, Uuid) {
match entity_type {
EntityType::Zombie => Zombie::make(server, position, world).await,
// TODO
_ => server.add_mob_entity(entity_type, position, world).await,
}
}

impl MobEntity {
pub async fn goal<T: Goal + 'static>(&self, goal: T) {
self.goals.lock().await.push((Arc::new(goal), false));
}
}
24 changes: 24 additions & 0 deletions pumpkin/src/entity/mob/zombie.rs
Original file line number Diff line number Diff line change
@@ -1 +1,25 @@
use std::sync::Arc;

use pumpkin_core::math::vector3::Vector3;
use pumpkin_entity::entity_type::EntityType;
use uuid::Uuid;

use crate::{entity::ai::goal::look_at_entity::LookAtEntityGoal, server::Server, world::World};

use super::MobEntity;

pub struct Zombie;

impl Zombie {
pub async fn make(
server: &Server,
position: Vector3<f64>,
world: &Arc<World>,
) -> (Arc<MobEntity>, Uuid) {
let (zombie_entity, uuid) = server
.add_mob_entity(EntityType::Zombie, position, world)
.await;
zombie_entity.goal(LookAtEntityGoal::new(8.0)).await;
(zombie_entity, uuid)
}
}
31 changes: 26 additions & 5 deletions pumpkin/src/entity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use pumpkin_core::math::{
};
use pumpkin_entity::{entity_type::EntityType, pose::EntityPose, EntityId};
use pumpkin_protocol::{
client::play::{CSetEntityMetadata, CTeleportEntity, Metadata},
client::play::{CHeadRot, CSetEntityMetadata, CTeleportEntity, CUpdateEntityRot, Metadata},
codec::var_int::VarInt,
};

Expand Down Expand Up @@ -67,23 +67,29 @@ pub struct Entity {
}

impl Entity {
#[allow(clippy::too_many_arguments)]
pub fn new(
entity_id: EntityId,
entity_uuid: uuid::Uuid,
world: Arc<World>,
position: Vector3<f64>,
entity_type: EntityType,
standing_eye_height: f32,
bounding_box: AtomicCell<BoundingBox>,
bounding_box_size: AtomicCell<BoundingBoxSize>,
) -> Self {
let floor_x = position.x.floor() as i32;
let floor_y = position.y.floor() as i32;
let floor_z = position.z.floor() as i32;

Self {
entity_id,
entity_uuid,
entity_type,
on_ground: AtomicBool::new(false),
pos: AtomicCell::new(Vector3::new(0.0, 0.0, 0.0)),
block_pos: AtomicCell::new(WorldPosition(Vector3::new(0, 0, 0))),
chunk_pos: AtomicCell::new(Vector2::new(0, 0)),
pos: AtomicCell::new(position),
block_pos: AtomicCell::new(WorldPosition(Vector3::new(floor_x, floor_y, floor_z))),
chunk_pos: AtomicCell::new(Vector2::new(floor_x, floor_z)),
sneaking: AtomicBool::new(false),
world,
// TODO: Load this from previous instance
Expand Down Expand Up @@ -141,14 +147,29 @@ impl Entity {
}

/// Changes this entity's pitch and yaw to look at target
pub fn look_at(&self, target: Vector3<f64>) {
pub async fn look_at(&self, target: Vector3<f64>) {
let position = self.pos.load();
let delta = target.sub(&position);
let root = delta.x.hypot(delta.z);
let pitch = wrap_degrees(-delta.y.atan2(root) as f32 * 180.0 / f32::consts::PI);
let yaw = wrap_degrees((delta.z.atan2(delta.x) as f32 * 180.0 / f32::consts::PI) - 90.0);
self.pitch.store(pitch);
self.yaw.store(yaw);

// send packet
let yaw = (yaw * 256.0 / 360.0).rem_euclid(256.0);
let pitch = (pitch * 256.0 / 360.0).rem_euclid(256.0);
self.world
.broadcast_packet_all(&CUpdateEntityRot::new(
self.entity_id.into(),
yaw as u8,
pitch as u8,
self.on_ground.load(std::sync::atomic::Ordering::Relaxed),
))
.await;
self.world
.broadcast_packet_all(&CHeadRot::new(self.entity_id.into(), yaw as u8))
.await;
}

pub async fn teleport(&self, position: Vector3<f64>, yaw: f32, pitch: f32) {
Expand Down
1 change: 1 addition & 0 deletions pumpkin/src/entity/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ impl Player {
entity_id,
player_uuid,
world,
Vector3::new(0.0, 0.0, 0.0),
EntityType::Player,
1.62,
AtomicCell::new(BoundingBox::new_default(&bounding_box_size)),
Expand Down
21 changes: 13 additions & 8 deletions pumpkin/src/net/packet/play.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use std::num::NonZeroU8;
use std::sync::Arc;

use crate::block::block_manager::BlockActionResult;
use crate::entity::mob;
use crate::net::PlayerConfig;
use crate::world::World;
use crate::{
command::CommandSender,
entity::player::{ChatMode, Hand, Player},
Expand Down Expand Up @@ -664,7 +664,7 @@ impl Player {
return;
}
entity_victim.kill().await;
World::remove_living_entity(entity_victim, world.clone()).await;
world.clone().remove_mob_entity(entity_victim).await;
// TODO: block entities should be checked here (signs)
} else {
log::error!(
Expand Down Expand Up @@ -1006,19 +1006,24 @@ impl Player {
if let Some(spawn_item_name) = get_entity_id(&item_t) {
let head_yaw = 10.0;
let world_pos = WorldPosition(location.0 + face.to_offset());
let pos = Vector3::new(
f64::from(world_pos.0.x),
f64::from(world_pos.0.y),
f64::from(world_pos.0.z),
);

// TODO: this should not be hardcoded
let (mob, _world, uuid) = server.add_living_entity(EntityType::Chicken).await;
let (mob, uuid) = mob::from_type(EntityType::Zombie, server, pos, self.world()).await;

let opposite_yaw = self.living_entity.entity.yaw.load() + 180.0;
server
.broadcast_packet_all(&CSpawnEntity::new(
VarInt(mob.entity.entity_id),
VarInt(mob.living_entity.entity.entity_id),
uuid,
VarInt((*spawn_item_name).into()),
f64::from(world_pos.0.x) + f64::from(cursor_pos.x),
f64::from(world_pos.0.y),
f64::from(world_pos.0.z) + f64::from(cursor_pos.z),
pos.x + f64::from(cursor_pos.x),
pos.y,
pos.z + f64::from(cursor_pos.z),
10.0,
head_yaw,
opposite_yaw,
Expand Down Expand Up @@ -1075,7 +1080,7 @@ impl Player {

let block_bounding_box = BoundingBox::from_block(&world_pos);
let mut intersects = false;
for player in world.get_nearby_players(entity.pos.load(), 20).await {
for player in world.get_nearby_players(entity.pos.load(), 20.0).await {
let bounding_box = player.1.living_entity.entity.bounding_box.load();
if bounding_box.intersects(&block_bounding_box) {
intersects = true;
Expand Down
Loading

0 comments on commit f2cfad0

Please sign in to comment.