use glam::{DVec3, Vec2, IVec3};
use crate::block::material::Material;
use crate::util::default as def;
use crate::geom::BoundingBox;
use crate::rand::JavaRandom;
use crate::item::ItemStack;
use crate::world::World;
use crate::block;
pub mod common;
mod tick;
mod tick_state;
mod tick_ai;
mod tick_attack;
use tick_state::tick_state;
use tick_ai::tick_ai;
use tick_attack::tick_attack;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntityKind {
Item,
Painting,
Boat,
Minecart,
Bobber,
LightningBolt,
FallingBlock,
Tnt,
Arrow,
Egg,
Fireball,
Snowball,
Human,
Ghast,
Slime,
Pig,
Chicken,
Cow,
Sheep,
Squid,
Wolf,
Creeper,
Giant,
PigZombie,
Skeleton,
Spider,
Zombie,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntityCategory {
Animal = 0,
WaterAnimal = 1,
Mob = 2,
Other = 3,
}
#[derive(Debug, Clone)]
pub struct Entity(pub Base, pub BaseKind);
#[derive(Debug, Clone)]
pub enum BaseKind {
Item(Item),
Painting(Painting),
Boat(Boat),
Minecart(Minecart),
LightningBolt(LightningBolt),
FallingBlock(FallingBlock),
Tnt(Tnt),
Projectile(Projectile, ProjectileKind),
Living(Living, LivingKind),
}
#[derive(Debug, Clone)]
pub enum ProjectileKind {
Arrow(Arrow),
Egg(Egg),
Fireball(Fireball),
Snowball(Snowball),
Bobber(Bobber),
}
#[derive(Debug, Clone)]
pub enum LivingKind {
Human(Human),
Ghast(Ghast),
Slime(Slime),
Pig(Pig),
Chicken(Chicken),
Cow(Cow),
Sheep(Sheep),
Squid(Squid),
Wolf(Wolf),
Creeper(Creeper),
Giant(Giant),
PigZombie(PigZombie),
Skeleton(Skeleton),
Spider(Spider),
Zombie(Zombie),
}
#[derive(Debug, Clone, Default)]
pub struct Base {
pub persistent: bool,
pub size: Size,
pub bb: BoundingBox,
pub pos: DVec3,
pub vel: DVec3,
pub look: Vec2,
pub lifetime: u32,
pub eye_height: f32,
pub can_pickup: bool,
pub no_clip: bool,
pub on_ground: bool,
pub in_water: bool,
pub in_lava: bool,
pub fall_distance: f32,
pub fire_time: u32,
pub air_time: u32,
pub hurt: Vec<Hurt>,
pub rider_id: Option<u32>,
pub bobber_id: Option<u32>,
pub rand: JavaRandom,
}
#[derive(Debug, Clone, Default)]
pub struct Hurt {
pub damage: u16,
pub origin_id: Option<u32>,
}
#[derive(Debug, Clone, Default)]
pub struct Living {
pub artificial: bool,
pub health: u16,
pub hurt_last_damage: u16,
pub hurt_time: u16,
pub attack_time: u16,
pub death_time: u16,
pub accel_strafing: f32,
pub accel_forward: f32,
pub yaw_velocity: f32,
pub jumping: bool,
pub look_target: Option<LookTarget>,
pub attack_target: Option<u32>,
pub path: Option<Path>,
pub wander_time: u16,
}
#[derive(Debug, Clone, Default)]
pub struct Projectile {
pub state: Option<ProjectileHit>,
pub state_time: u16,
pub owner_id: Option<u32>,
pub shake: u8,
}
#[derive(Debug, Copy, Clone, Default)]
pub struct ProjectileHit {
pub pos: IVec3,
pub block: u8,
pub metadata: u8,
}
#[derive(Debug, Clone, Default)]
pub struct Item {
pub stack: ItemStack,
pub health: u16,
pub frozen_time: u32,
}
#[derive(Debug, Clone, Default)]
pub struct Painting {
pub block_pos: IVec3,
pub orientation: PaintingOrientation,
pub art: PaintingArt,
pub check_valid_time: u8,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum PaintingOrientation {
#[default]
NegX,
PosX,
NegZ,
PosZ,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum PaintingArt {
#[default]
Kebab,
Aztec,
Alban,
Aztec2,
Bomb,
Plant,
Wasteland,
Pool,
Courbet,
Sea,
Sunset,
Creebet,
Wanderer,
Graham,
Match,
Bust,
Stage,
Void,
SkullAndRoses,
Fighters,
Pointer,
Pigscene,
BurningSkull,
Skeleton,
DonkeyKong,
}
#[derive(Debug, Clone, Default)]
pub struct Boat { }
#[derive(Debug, Clone, Default)]
pub enum Minecart {
#[default]
Normal,
Chest {
inv: Box<[ItemStack; 27]>,
},
Furnace {
push_x: f64,
push_z: f64,
fuel: u32,
}
}
#[derive(Debug, Clone, Default)]
pub struct Bobber {
pub attached_id: Option<u32>,
pub catch_time: u16,
}
#[derive(Debug, Clone, Default)]
pub struct LightningBolt { }
#[derive(Debug, Clone, Default)]
pub struct FallingBlock {
pub fall_time: u32,
pub block_id: u8,
}
#[derive(Debug, Clone, Default)]
pub struct Tnt {
pub fuse_time: u32,
}
#[derive(Debug, Clone, Default)]
pub struct Arrow {
pub from_player: bool,
}
#[derive(Debug, Clone, Default)]
pub struct Egg { }
#[derive(Debug, Clone, Default)]
pub struct Fireball {
pub accel: DVec3,
}
#[derive(Debug, Clone, Default)]
pub struct Snowball { }
#[derive(Debug, Clone, Default)]
pub struct Human {
pub username: String,
pub sleeping: bool,
pub sneaking: bool,
}
#[derive(Debug, Clone, Default)]
pub struct Ghast {
pub waypoint: DVec3,
pub waypoint_check_time: u8,
pub attack_target_time: u8,
}
#[derive(Debug, Clone, Default)]
pub struct Slime {
pub size: u8,
pub jump_remaining_time: u32,
}
#[derive(Debug, Clone, Default)]
pub struct Pig {
pub saddle: bool,
}
#[derive(Debug, Clone, Default)]
pub struct Chicken {
pub next_egg_ticks: u32,
}
#[derive(Debug, Clone, Default)]
pub struct Cow { }
#[derive(Debug, Clone, Default)]
pub struct Sheep {
pub sheared: bool,
pub color: u8, }
#[derive(Debug, Clone, Default)]
pub struct Squid {
pub animation: f32,
pub animation_speed: f32,
}
#[derive(Debug, Clone, Default)]
pub struct Wolf {
pub angry: bool,
pub sitting: bool,
pub owner: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct Creeper {
pub powered: bool,
pub ignited_time: Option<u16>
}
#[derive(Debug, Clone, Default)]
pub struct Giant { }
#[derive(Debug, Clone, Default)]
pub struct PigZombie {
pub anger: bool,
}
#[derive(Debug, Clone, Default)]
pub struct Skeleton { }
#[derive(Debug, Clone, Default)]
pub struct Spider { }
#[derive(Debug, Clone, Default)]
pub struct Zombie { }
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct Size {
pub width: f32,
pub height: f32,
pub center: f32,
}
impl Size {
pub fn new(width: f32, height: f32) -> Self {
Self { width, height, center: 0.0 }
}
pub fn new_centered(width: f32, height: f32) -> Self {
Self { width, height, center: height / 2.0 }
}
}
#[derive(Debug, Clone, Default)]
pub struct LookTarget {
pub entity_id: u32,
pub remaining_time: u32,
}
#[derive(Debug, Clone)]
pub struct Path {
pub points: Vec<IVec3>,
pub index: usize,
}
impl From<Vec<IVec3>> for Path {
fn from(points: Vec<IVec3>) -> Self {
Self { points, index: 0 }
}
}
impl From<IVec3> for Path {
fn from(value: IVec3) -> Self {
Self { points: vec![value], index: 0 }
}
}
impl Path {
pub fn point(&self) -> Option<IVec3> {
self.points.get(self.index).copied()
}
pub fn advance(&mut self) {
self.index += 1;
}
}
impl Entity {
pub fn kind(&self) -> EntityKind {
self.1.entity_kind()
}
#[inline]
pub fn category(&self) -> EntityCategory {
self.kind().category()
}
pub fn tick(&mut self, world: &mut World, id: u32) {
tick::tick(world, id, self);
}
pub fn resize(&mut self) {
let Entity(base, base_kind) = self;
base.size = match base_kind {
BaseKind::Item(_) => Size::new_centered(0.25, 0.25),
BaseKind::Painting(_) => Size::new(0.5, 0.5),
BaseKind::Boat(_) => Size::new_centered(1.5, 0.6),
BaseKind::Minecart(_) => Size::new_centered(0.98, 0.7),
BaseKind::LightningBolt(_) => Size::new(0.0, 0.0),
BaseKind::FallingBlock(_) => Size::new_centered(0.98, 0.98),
BaseKind::Tnt(_) => Size::new_centered(0.98, 0.98),
BaseKind::Projectile(_, ProjectileKind::Arrow(_)) => Size::new(0.5, 0.5),
BaseKind::Projectile(_, ProjectileKind::Egg(_)) => Size::new(0.5, 0.5),
BaseKind::Projectile(_, ProjectileKind::Fireball(_)) => Size::new(1.0, 1.0),
BaseKind::Projectile(_, ProjectileKind::Snowball(_)) => Size::new(0.5, 0.5),
BaseKind::Projectile(_, ProjectileKind::Bobber(_)) => Size::new(0.25, 0.25),
BaseKind::Living(_, LivingKind::Human(player)) => {
if player.sleeping {
Size::new(0.2, 0.2)
} else {
Size::new(0.6, 1.8)
}
}
BaseKind::Living(_, LivingKind::Ghast(_)) => Size::new(4.0, 4.0),
BaseKind::Living(_, LivingKind::Slime(slime)) => {
let factor = slime.size as f32 + 1.0;
Size::new(0.6 * factor, 0.6 * factor)
}
BaseKind::Living(_, LivingKind::Pig(_)) => Size::new(0.9, 0.9),
BaseKind::Living(_, LivingKind::Chicken(_)) => Size::new(0.3, 0.4),
BaseKind::Living(_, LivingKind::Cow(_)) => Size::new(0.9, 1.3),
BaseKind::Living(_, LivingKind::Sheep(_)) =>Size::new(0.9, 1.3),
BaseKind::Living(_, LivingKind::Squid(_)) => Size::new(0.95, 0.95),
BaseKind::Living(_, LivingKind::Wolf(_)) => Size::new(0.8, 0.8),
BaseKind::Living(_, LivingKind::Creeper(_)) => Size::new(0.6, 1.8),
BaseKind::Living(_, LivingKind::Giant(_)) => Size::new(3.6, 10.8),
BaseKind::Living(_, LivingKind::PigZombie(_)) => Size::new(0.6, 1.8),
BaseKind::Living(_, LivingKind::Skeleton(_)) => Size::new(0.6, 1.8),
BaseKind::Living(_, LivingKind::Spider(_)) => Size::new(1.4, 0.9),
BaseKind::Living(_, LivingKind::Zombie(_)) => Size::new(0.6, 1.8),
};
base.eye_height = match base_kind {
BaseKind::Living(_, LivingKind::Human(_)) => 1.62,
BaseKind::Living(_, LivingKind::Wolf(_)) => base.size.height * 0.8,
BaseKind::Living(_, _) => base.size.height * 0.85,
_ => 0.0,
};
common::update_bounding_box_from_pos(base);
}
pub fn teleport(&mut self, pos: DVec3) {
let Entity(base, _) = self;
base.pos = pos;
common::update_bounding_box_from_pos(base);
}
pub fn can_natural_spawn(&mut self, world: &World) -> bool {
let Entity(base, BaseKind::Living(_, living_kind)) = self else {
return false;
};
let kind = living_kind.entity_kind();
let block_pos = IVec3 {
x: base.bb.center_x().floor() as i32,
y: base.bb.min.y.floor() as i32,
z: base.bb.center_z().floor() as i32,
};
let category = kind.category();
if category == EntityCategory::Animal {
if !world.is_block(block_pos - IVec3::Y, block::GRASS) {
return false;
}
if world.get_light(block_pos).max() <= 8 {
return false;
}
} else if category == EntityCategory::Mob {
let light = world.get_light(block_pos);
if light.sky as i32 > base.rand.next_int_bounded(32) {
return false;
}
if light.max_real() as i32 > base.rand.next_int_bounded(8) {
return false;
}
}
if category != EntityCategory::Other {
let weight_func = common::path_weight_func(living_kind);
if weight_func(world, block_pos) < 0.0 {
return false;
}
}
if world.has_entity_colliding(base.bb, true) {
return false;
}
if category != EntityCategory::WaterAnimal {
if world.iter_blocks_boxes_colliding(base.bb).next().is_some() {
return false;
}
if world.iter_blocks_in_box(base.bb).any(|(_pos, block, _)| block::material::is_fluid(block)) {
return false;
}
}
true
}
pub fn init_natural_spawn(&mut self, _world: &mut World) {
let Entity(base, BaseKind::Living(_, living_kind)) = self else {
return;
};
match living_kind {
LivingKind::Slime(slime) => {
slime.size = 1 << base.rand.next_int_bounded(3) as u8;
self.resize();
}
LivingKind::Sheep(sheep) => {
let rand = base.rand.next_int_bounded(100) as u8;
sheep.color = match rand {
0..=4 => 15,
5..=9 => 7,
10..=14 => 8,
15..=17 => 12,
_ if base.rand.next_int_bounded(500) == 0 => 6,
_ => 0,
};
}
_ => {}
}
}
}
impl BaseKind {
pub fn entity_kind(&self) -> EntityKind {
match self {
BaseKind::Item(_) => EntityKind::Item,
BaseKind::Painting(_) => EntityKind::Painting,
BaseKind::Boat(_) => EntityKind::Boat,
BaseKind::Minecart(_) => EntityKind::Minecart,
BaseKind::LightningBolt(_) => EntityKind::LightningBolt,
BaseKind::FallingBlock(_) => EntityKind::FallingBlock,
BaseKind::Tnt(_) => EntityKind::Tnt,
BaseKind::Projectile(_, kind) => kind.entity_kind(),
BaseKind::Living(_, kind) => kind.entity_kind(),
}
}
}
impl LivingKind {
pub fn entity_kind(&self) -> EntityKind {
match self {
LivingKind::Human(_) => EntityKind::Human,
LivingKind::Ghast(_) => EntityKind::Ghast,
LivingKind::Slime(_) => EntityKind::Slime,
LivingKind::Pig(_) => EntityKind::Pig,
LivingKind::Chicken(_) => EntityKind::Chicken,
LivingKind::Cow(_) => EntityKind::Cow,
LivingKind::Sheep(_) => EntityKind::Sheep,
LivingKind::Squid(_) => EntityKind::Squid,
LivingKind::Wolf(_) => EntityKind::Wolf,
LivingKind::Creeper(_) => EntityKind::Creeper,
LivingKind::Giant(_) => EntityKind::Giant,
LivingKind::PigZombie(_) => EntityKind::PigZombie,
LivingKind::Skeleton(_) => EntityKind::Skeleton,
LivingKind::Spider(_) => EntityKind::Spider,
LivingKind::Zombie(_) => EntityKind::Zombie,
}
}
}
impl ProjectileKind {
pub fn entity_kind(&self) -> EntityKind {
match self {
ProjectileKind::Arrow(_) => EntityKind::Arrow,
ProjectileKind::Egg(_) => EntityKind::Egg,
ProjectileKind::Fireball(_) => EntityKind::Fireball,
ProjectileKind::Snowball(_) => EntityKind::Snowball,
ProjectileKind::Bobber(_) => EntityKind::Bobber,
}
}
}
impl EntityKind {
pub fn new_default(self, pos: DVec3) -> Box<Entity> {
match self {
EntityKind::Item => Item::new_default(pos),
EntityKind::Painting => Painting::new_default(pos),
EntityKind::Boat => Boat::new_default(pos),
EntityKind::Minecart => Minecart::new_default(pos),
EntityKind::Bobber => Bobber::new_default(pos),
EntityKind::LightningBolt => LightningBolt::new_default(pos),
EntityKind::FallingBlock => FallingBlock::new_default(pos),
EntityKind::Tnt => Tnt::new_default(pos),
EntityKind::Arrow => Arrow::new_default(pos),
EntityKind::Egg => Egg::new_default(pos),
EntityKind::Fireball => Fireball::new_default(pos),
EntityKind::Snowball => Snowball::new_default(pos),
EntityKind::Human => Human::new_default(pos),
EntityKind::Ghast => Ghast::new_default(pos),
EntityKind::Slime => Slime::new_default(pos),
EntityKind::Pig => Pig::new_default(pos),
EntityKind::Chicken => Chicken::new_default(pos),
EntityKind::Cow => Cow::new_default(pos),
EntityKind::Sheep => Sheep::new_default(pos),
EntityKind::Squid => Squid::new_default(pos),
EntityKind::Wolf => Wolf::new_default(pos),
EntityKind::Creeper => Creeper::new_default(pos),
EntityKind::Giant => Giant::new_default(pos),
EntityKind::PigZombie => PigZombie::new_default(pos),
EntityKind::Skeleton => Skeleton::new_default(pos),
EntityKind::Spider => Spider::new_default(pos),
EntityKind::Zombie => Zombie::new_default(pos),
}
}
#[inline]
pub fn is_hard(self) -> bool {
match self {
EntityKind::Item |
EntityKind::Bobber |
EntityKind::LightningBolt |
EntityKind::Arrow |
EntityKind::Egg |
EntityKind::Fireball |
EntityKind::Snowball => false,
_ => true
}
}
pub fn category(self) -> EntityCategory {
match self {
EntityKind::Pig |
EntityKind::Chicken |
EntityKind::Cow |
EntityKind::Sheep |
EntityKind::Wolf => EntityCategory::Animal,
EntityKind::Squid => EntityCategory::WaterAnimal,
EntityKind::Creeper |
EntityKind::Giant |
EntityKind::PigZombie |
EntityKind::Skeleton |
EntityKind::Spider |
EntityKind::Zombie |
EntityKind::Slime => EntityCategory::Mob,
_ => EntityCategory::Other
}
}
pub fn natural_spawn_max_chunk_count(self) -> usize {
match self {
EntityKind::Ghast => 1,
EntityKind::Wolf => 8,
_ => 4,
}
}
}
impl EntityCategory {
pub const ALL: [Self; 4] = [Self::Animal, Self::WaterAnimal, Self::Mob, Self::Other];
pub fn natural_spawn_max_world_count(self) -> usize {
match self {
EntityCategory::Animal => 15,
EntityCategory::WaterAnimal => 5,
EntityCategory::Mob => 70,
EntityCategory::Other => 0,
}
}
pub fn natural_spawn_material(self) -> Material {
match self {
EntityCategory::Animal => Material::Air,
EntityCategory::WaterAnimal => Material::Water,
EntityCategory::Mob => Material::Air,
EntityCategory::Other => Material::Air,
}
}
}
macro_rules! impl_new_with {
( Base: $( $kind:ident $($def:expr)? ),* ) => {
$(impl $kind {
/// Create a new instance of this entity type and initialize the entity with
/// a closure, the entity is then resized to initialize its bounding box.
#[inline]
pub fn new_with(func: impl FnOnce(&mut Base, &mut $kind)) -> Box<Entity> {
let mut entity = Box::new(Entity(def(), BaseKind::$kind(def())));
let Entity(base, BaseKind::$kind(this)) = &mut *entity else { unreachable!() };
$( ($def)(base, this); )?
func(base, this);
entity.resize();
entity
}
pub fn new_default(pos: DVec3) -> Box<Entity> {
Self::new_with(|base, _| base.pos = pos)
}
})*
};
( Living: $( $kind:ident ($def_health:expr) $($def:expr)?),* ) => {
$(impl $kind {
#[inline]
pub fn new_with(func: impl FnOnce(&mut Base, &mut Living, &mut $kind)) -> Box<Entity> {
let mut entity = Box::new(Entity(def(), BaseKind::Living(def(), LivingKind::$kind(def()))));
let Entity(base, BaseKind::Living(living, LivingKind::$kind(this))) = &mut *entity else { unreachable!() };
living.health = $def_health;
$( ($def)(base, living, this); )?
func(base, living, this);
entity.resize();
entity
}
pub fn new_default(pos: DVec3) -> Box<Entity> {
Self::new_with(|base, _, _| base.pos = pos)
}
})*
};
( Projectile: $( $kind:ident ),* ) => {
$(impl $kind {
#[inline]
pub fn new_with(func: impl FnOnce(&mut Base, &mut Projectile, &mut $kind)) -> Box<Entity> {
let mut entity = Box::new(Entity(def(), BaseKind::Projectile(def(), ProjectileKind::$kind(def()))));
let Entity(base, BaseKind::Projectile(projectile, ProjectileKind::$kind(this))) = &mut *entity else { unreachable!() };
func(base, projectile, this);
entity.resize();
entity
}
pub fn new_default(pos: DVec3) -> Box<Entity> {
Self::new_with(|base, _, _| base.pos = pos)
}
})*
};
}
impl_new_with!(Base:
Item |_: &mut Base, this: &mut Item| {
this.health = 5;
this.stack = ItemStack::new_block(block::STONE, 0);
},
Painting,
Boat,
Minecart,
LightningBolt,
FallingBlock |_: &mut Base, this: &mut FallingBlock| {
this.block_id = block::SAND;
},
Tnt);
impl_new_with!(Living:
Human(20),
Ghast(10),
Slime(1),
Pig(10),
Chicken(4),
Cow(10),
Sheep(10),
Squid(10),
Wolf(8),
Creeper(20),
Giant(200),
PigZombie(20),
Skeleton(20),
Spider(20),
Zombie(20));
impl_new_with!(Projectile:
Arrow,
Egg,
Fireball,
Snowball,
Bobber);