use std::sync::{ Arc, Weak, atomic::{AtomicBool, AtomicI32, Ordering}, }; use pumpkin_data::item_stack::ItemStack; use pumpkin_data::{ entity::EntityType, item::Item, sound::{Sound, SoundCategory}, }; use pumpkin_nbt::compound::NbtCompound; use pumpkin_protocol::{codec::var_int::VarInt, java::client::play::Metadata}; use crate::entity::{ Entity, EntityBase, ai::goal::{ active_target::ActiveTargetGoal, creeper_ignite::CreeperIgniteGoal, look_around::RandomLookAroundGoal, look_at_entity::LookAtEntityGoal, melee_attack::MeleeAttackGoal, revenge::RevengeGoal, swim::SwimGoal, wander_around::WanderAroundGoal, }, mob::{Mob, MobEntity}, player::Player, }; const DEFAULT_FUSE_TIME: i32 = 30; const DEFAULT_EXPLOSION_RADIUS: i32 = 3; pub struct CreeperEntity { pub mob_entity: MobEntity, pub fuse_speed: AtomicI32, pub current_fuse_time: AtomicI32, pub last_fuse_time: AtomicI32, pub fuse_time: AtomicI32, pub explosion_radius: AtomicI32, pub ignited: AtomicBool, pub charged: AtomicBool, } impl CreeperEntity { pub fn new(entity: Entity) -> Arc { let mob_entity = MobEntity::new(entity); let entity = Self { mob_entity, fuse_speed: AtomicI32::new(-1), current_fuse_time: AtomicI32::new(0), last_fuse_time: AtomicI32::new(0), fuse_time: AtomicI32::new(DEFAULT_FUSE_TIME), explosion_radius: AtomicI32::new(DEFAULT_EXPLOSION_RADIUS), ignited: AtomicBool::new(false), charged: AtomicBool::new(false), }; let mob_arc = Arc::new(entity); let mob_weak: Weak = { let mob_arc: Arc = mob_arc.clone(); Arc::downgrade(&mob_arc) }; { let mut goal_selector = mob_arc .mob_entity .goals_selector .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); let mut target_selector = mob_arc .mob_entity .target_selector .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); goal_selector.add_goal(1, Box::new(SwimGoal::default())); goal_selector.add_goal(2, Box::new(CreeperIgniteGoal::new(mob_arc.clone()))); goal_selector.add_goal(4, Box::new(MeleeAttackGoal::new(1.0, false))); goal_selector.add_goal(5, Box::new(WanderAroundGoal::new(0.8))); goal_selector.add_goal( 6, LookAtEntityGoal::with_default(mob_weak, &EntityType::PLAYER, 8.0), ); goal_selector.add_goal(6, Box::new(RandomLookAroundGoal::default())); target_selector.add_goal( 1, ActiveTargetGoal::with_default(&mob_arc.mob_entity, &EntityType::PLAYER, true), ); target_selector.add_goal(2, Box::new(RevengeGoal::new(true))); }; mob_arc } pub fn set_fuse_speed(&self, speed: i32) { self.fuse_speed.store(speed, Ordering::Relaxed); self.mob_entity.living_entity.entity.send_meta_data( &[Metadata::new( pumpkin_data::tracked_data::creeper::FUSE_ID, VarInt(speed), )], None, ); } pub fn explode(&self) { let entity = &self.mob_entity.living_entity.entity; let radius = self.explosion_radius.load(Ordering::Relaxed) as f32; let multiplier = if self.charged.load(Ordering::Relaxed) { 2.0 } else { 1.0 }; self.mob_entity .living_entity .dead .store(true, Ordering::Relaxed); let world = entity.world.load(); let pos = entity.pos.load(); world.explode( pos, radius * multiplier, crate::world::ExplosionInteraction::Mob, ); // TODO: spawn area effect cloud with potion effects entity.remove(); } } impl Mob for CreeperEntity { fn mob_write_nbt(&self, nbt: &mut NbtCompound) { nbt.put_bool("powered", self.charged.load(Ordering::Relaxed)); nbt.put_short("Fuse", self.fuse_time.load(Ordering::Relaxed) as i16); nbt.put_byte( "ExplosionRadius", self.explosion_radius.load(Ordering::Relaxed) as i8, ); nbt.put_bool("ignited", self.ignited.load(Ordering::Relaxed)); } fn mob_read_nbt(&self, nbt: &NbtCompound) { if let Some(powered) = nbt.get_bool("powered") { self.charged.store(powered, Ordering::Relaxed); } if let Some(fuse) = nbt.get_short("Fuse") { self.fuse_time.store(i32::from(fuse), Ordering::Relaxed); } if let Some(radius) = nbt.get_byte("ExplosionRadius") { self.explosion_radius .store(i32::from(radius), Ordering::Relaxed); } if let Some(ignited) = nbt.get_bool("ignited") { self.ignited.store(ignited, Ordering::Relaxed); } } fn get_mob_entity(&self) -> &MobEntity { &self.mob_entity } fn mob_on_lightning_strike( &self, caller: &dyn EntityBase, lightning: &crate::entity::lightning::LightningBoltEntity, ) { self.charged.store(true, Ordering::Relaxed); self.mob_entity.living_entity.entity.send_meta_data( &[Metadata::new( pumpkin_data::tracked_data::creeper::CHARGED, true, )], None, ); self.mob_entity .living_entity .on_lightning_strike(caller, lightning); } fn mob_tick<'a>(&'a self, _caller: &'a Arc) { let entity = &self.mob_entity.living_entity.entity; if !entity.is_alive() { return; } self.last_fuse_time.store( self.current_fuse_time.load(Ordering::Relaxed), Ordering::Relaxed, ); if self.ignited.load(Ordering::Relaxed) { self.set_fuse_speed(1); } let fuse_speed = self.fuse_speed.load(Ordering::Relaxed); let current = self.current_fuse_time.load(Ordering::Relaxed); if fuse_speed > 0 && current == 0 { let world = entity.world.load(); world.play_sound_fine( Sound::EntityCreeperPrimed, SoundCategory::Hostile, &entity.pos.load(), 1.0, 0.5, ); } let fuse_time = self.fuse_time.load(Ordering::Relaxed); let new_fuse = (current + fuse_speed).max(0); self.current_fuse_time.store(new_fuse, Ordering::Relaxed); if new_fuse >= fuse_time { self.current_fuse_time.store(fuse_time, Ordering::Relaxed); self.explode(); } } fn mob_interact(&self, player: &Arc, item_stack: &mut ItemStack) -> bool { if item_stack.item.id != Item::FLINT_AND_STEEL.id { return self.mob_entity.mob_interact(player, item_stack); } let entity = &self.mob_entity.living_entity.entity; let world = entity.world.load(); let pos = entity.pos.load(); world.play_sound_fine( Sound::ItemFlintandsteelUse, SoundCategory::Hostile, &pos, 1.0, rand::random::() * 0.4 + 0.8, ); self.ignited.store(true, Ordering::Relaxed); entity.send_meta_data( &[Metadata::new( pumpkin_data::tracked_data::creeper::IS_IGNITED, true, )], None, ); if player.gamemode.load() != pumpkin_util::GameMode::Creative { // TODO: Handle DamageResult::Broken to broadcast item break and update player slot. let _ = item_stack.damage_item(1); } true } } impl CreeperEntity { pub fn is_charged(&self) -> bool { self.charged.load(Ordering::Relaxed) } pub fn set_charged(&self, charged: bool) { self.charged.store(charged, Ordering::Relaxed); let entity = &self.mob_entity.living_entity.entity; entity.send_meta_data( &[Metadata::new( pumpkin_data::tracked_data::creeper::CHARGED, charged, )], None, ); } pub fn is_ignited(&self) -> bool { self.ignited.load(Ordering::Relaxed) } pub fn set_ignited(&self, ignited: bool) { self.ignited.store(ignited, Ordering::Relaxed); let entity = &self.mob_entity.living_entity.entity; entity.send_meta_data( &[Metadata::new( pumpkin_data::tracked_data::creeper::IS_IGNITED, ignited, )], None, ); } pub fn get_fuse(&self) -> i32 { self.fuse_time.load(Ordering::Relaxed) } pub fn set_fuse(&self, fuse: i32) { self.fuse_time.store(fuse, Ordering::Relaxed); } pub fn get_explosion_radius(&self) -> i32 { self.explosion_radius.load(Ordering::Relaxed) } pub fn set_explosion_radius(&self, radius: i32) { self.explosion_radius.store(radius, Ordering::Relaxed); } }