feat: Eye of Ender pointing to Stronghold
This commit is contained in:
parent
0017b1bf4c
commit
eed57ce5fc
@ -9,6 +9,8 @@ use super::noise::router::proto_noise_router::ProtoNoiseRouters;
|
||||
use crate::generation::proto_chunk::TerrainCache;
|
||||
use crate::generation::{GlobalRandomConfig, Seed};
|
||||
|
||||
pub mod structure_finder;
|
||||
|
||||
pub trait GeneratorInit {
|
||||
fn new(seed: Seed, dimension: Dimension) -> Self;
|
||||
}
|
||||
|
||||
170
pumpkin-world/src/generation/generator/structure_finder.rs
Normal file
170
pumpkin-world/src/generation/generator/structure_finder.rs
Normal file
@ -0,0 +1,170 @@
|
||||
use pumpkin_data::structures::{
|
||||
ConcentricRingsStructurePlacement, RandomSpreadStructurePlacement, StructurePlacement,
|
||||
StructurePlacementType,
|
||||
};
|
||||
use pumpkin_util::math::{floor_div, position::BlockPos};
|
||||
|
||||
use crate::generation::structure::placement::{
|
||||
GlobalStructureCache, get_structure_chunk_in_region,
|
||||
};
|
||||
|
||||
/// Block-level position of a found structure plus squared distance from the
|
||||
/// search origin, used internally to track the running nearest candidate.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FoundStructure {
|
||||
pub pos: BlockPos,
|
||||
pub distance_sq: f64,
|
||||
}
|
||||
|
||||
/// Finds the block position of the nearest structure whose placement is listed
|
||||
/// in `placements`, within `max_search_radius` chunk-region rings.
|
||||
///
|
||||
/// Mirrors the two-pass logic in vanilla's
|
||||
/// `ChunkGenerator.findNearestMapStructure`:
|
||||
///
|
||||
/// 1. **Concentric-rings** placements (strongholds) are resolved in one pass
|
||||
/// from the pre-computed [`GlobalStructureCache`].
|
||||
/// 2. **Random-spread** placements are searched ring-by-ring outward, stopping
|
||||
/// at the first radius that produces any result.
|
||||
///
|
||||
/// The best candidate from both passes is returned.
|
||||
pub fn find_nearest_structure(
|
||||
origin: BlockPos,
|
||||
placements: &[&StructurePlacement],
|
||||
max_search_radius: i32,
|
||||
world_seed: i64,
|
||||
global_cache: &GlobalStructureCache,
|
||||
) -> Option<BlockPos> {
|
||||
if placements.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut nearest: Option<FoundStructure> = None;
|
||||
|
||||
// ── Pass 1: Concentric-rings (strongholds) ──────────────────────────────
|
||||
for p in placements {
|
||||
if let StructurePlacementType::ConcentricRings(rings) = &p.placement_type
|
||||
&& let Some(found) = find_nearest_concentric(origin, rings, global_cache)
|
||||
&& nearest
|
||||
.as_ref()
|
||||
.is_none_or(|n| found.distance_sq < n.distance_sq)
|
||||
{
|
||||
nearest = Some(found);
|
||||
}
|
||||
}
|
||||
|
||||
let random_spread: Vec<(&RandomSpreadStructurePlacement, u32)> = placements
|
||||
.iter()
|
||||
.filter_map(|p| {
|
||||
if let StructurePlacementType::RandomSpread(r) = &p.placement_type {
|
||||
Some((r, p.salt))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if !random_spread.is_empty() {
|
||||
let chunk_origin_x = origin.0.x >> 4;
|
||||
let chunk_origin_z = origin.0.z >> 4;
|
||||
|
||||
'radius: for radius in 0..=max_search_radius {
|
||||
for (placement, salt) in &random_spread {
|
||||
if let Some(found) = find_nearest_random_spread_at_radius(
|
||||
origin,
|
||||
chunk_origin_x,
|
||||
chunk_origin_z,
|
||||
radius,
|
||||
world_seed,
|
||||
placement,
|
||||
*salt,
|
||||
) {
|
||||
if nearest
|
||||
.as_ref()
|
||||
.is_none_or(|n| found.distance_sq < n.distance_sq)
|
||||
{
|
||||
nearest = Some(found);
|
||||
}
|
||||
break 'radius;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nearest.map(|f| f.pos)
|
||||
}
|
||||
|
||||
fn find_nearest_concentric(
|
||||
origin: BlockPos,
|
||||
// Kept for potential future bounds / distance validation.
|
||||
_rings: &ConcentricRingsStructurePlacement,
|
||||
global_cache: &GlobalStructureCache,
|
||||
) -> Option<FoundStructure> {
|
||||
let strongholds = global_cache.get_stronghold_chunks();
|
||||
if strongholds.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let ox = origin.0.x as f64;
|
||||
let oz = origin.0.z as f64;
|
||||
|
||||
strongholds
|
||||
.iter()
|
||||
.map(|(cx, cz)| {
|
||||
// Centre of the chunk in block coords.
|
||||
let bx = (cx << 4) + 8;
|
||||
let bz = (cz << 4) + 8;
|
||||
let dx = bx as f64 - ox;
|
||||
let dz = bz as f64 - oz;
|
||||
FoundStructure {
|
||||
pos: BlockPos::new(bx, 0, bz),
|
||||
distance_sq: dx * dx + dz * dz,
|
||||
}
|
||||
})
|
||||
.min_by(|a, b| a.distance_sq.partial_cmp(&b.distance_sq).unwrap())
|
||||
}
|
||||
|
||||
fn find_nearest_random_spread_at_radius(
|
||||
origin: BlockPos,
|
||||
chunk_origin_x: i32,
|
||||
chunk_origin_z: i32,
|
||||
radius: i32,
|
||||
world_seed: i64,
|
||||
placement: &RandomSpreadStructurePlacement,
|
||||
salt: u32,
|
||||
) -> Option<FoundStructure> {
|
||||
let spacing = placement.spacing;
|
||||
let ox = origin.0.x as f64;
|
||||
let oz = origin.0.z as f64;
|
||||
|
||||
let mut best: Option<FoundStructure> = None;
|
||||
|
||||
for rx_off in -radius..=radius {
|
||||
for rz_off in -radius..=radius {
|
||||
if rx_off.abs() != radius && rz_off.abs() != radius {
|
||||
continue;
|
||||
}
|
||||
|
||||
let rx = floor_div(chunk_origin_x, spacing) + rx_off;
|
||||
let rz = floor_div(chunk_origin_z, spacing) + rz_off;
|
||||
|
||||
let (struct_cx, struct_cz) =
|
||||
get_structure_chunk_in_region(placement, world_seed, rx, rz, salt);
|
||||
|
||||
let bx = (struct_cx << 4) + 8;
|
||||
let bz = (struct_cz << 4) + 8;
|
||||
let dx = bx as f64 - ox;
|
||||
let dz = bz as f64 - oz;
|
||||
let dist_sq = dx * dx + dz * dz;
|
||||
|
||||
if best.as_ref().is_none_or(|b| dist_sq < b.distance_sq) {
|
||||
best = Some(FoundStructure {
|
||||
pos: BlockPos::new(bx, 0, bz),
|
||||
distance_sq: dist_sq,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
best
|
||||
}
|
||||
@ -32,6 +32,13 @@ impl GlobalStructureCache {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_stronghold_chunks(&self) -> &[(i32, i32)] {
|
||||
self.stronghold_chunks
|
||||
.get()
|
||||
.map(|v| v.as_slice())
|
||||
.unwrap_or(&[])
|
||||
}
|
||||
|
||||
/// Retrieves the list of chunk coordinates for Concentric Ring structures.
|
||||
/// If the cache is empty, it calculates the 128 ring positions mathematically.
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
|
||||
222
pumpkin/src/entity/projectile/eye_of_ender.rs
Normal file
222
pumpkin/src/entity/projectile/eye_of_ender.rs
Normal file
@ -0,0 +1,222 @@
|
||||
use crate::entity::item::ItemEntity;
|
||||
use crate::entity::living::LivingEntity;
|
||||
use crate::entity::player::Player;
|
||||
use crate::{entity::EntityBaseFuture, server::Server};
|
||||
use core::f64;
|
||||
use pumpkin_data::entity::EntityType;
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_data::sound::Sound;
|
||||
use pumpkin_data::world::WorldEvent;
|
||||
use pumpkin_data::{damage::DamageType, meta_data_type::MetaDataType, tracked_data::TrackedData};
|
||||
use pumpkin_protocol::{
|
||||
codec::item_stack_seralizer::ItemStackSerializer, java::client::play::Metadata,
|
||||
};
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, AtomicU32, Ordering},
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use super::{Entity, EntityBase, NBTStorage};
|
||||
|
||||
/// Maximum horizontal distance the eye will travel before signalling at an elevated height.
|
||||
const TOO_FAR_DISTANCE: f64 = 12.0;
|
||||
|
||||
/// Height offset used when the target is beyond [`TOO_FAR_DISTANCE`].
|
||||
const TOO_FAR_SIGNAL_HEIGHT: f64 = 8.0;
|
||||
|
||||
/// The eye despawns after this many ticks.
|
||||
const MAX_LIFE: u32 = 80;
|
||||
|
||||
/// Chance (1-in-5) the eye drops its item instead of playing the break effect.
|
||||
const SURVIVE_CHANCE: u32 = 5;
|
||||
|
||||
pub struct EyeOfEnder {
|
||||
entity: Entity,
|
||||
item_stack: Mutex<ItemStack>,
|
||||
target: Mutex<Option<Vector3<f64>>>,
|
||||
life: AtomicU32,
|
||||
survive_after_death: AtomicBool,
|
||||
}
|
||||
|
||||
impl EyeOfEnder {
|
||||
pub fn new(entity: Entity) -> Self {
|
||||
Self {
|
||||
entity,
|
||||
item_stack: Mutex::new(ItemStack::new(1, &Item::ENDER_EYE)),
|
||||
target: Mutex::new(None),
|
||||
life: AtomicU32::new(0),
|
||||
survive_after_death: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
/// Aim the eye at `target`, clamping to [`TOO_FAR_DISTANCE`] if necessary,
|
||||
/// and randomly decide whether it should drop its item on expiry.
|
||||
pub async fn signal_to(&self, target: Vector3<f64>) {
|
||||
let pos = self.entity.pos.load();
|
||||
let delta = target.sub(&pos);
|
||||
let horizontal_dist = delta.x.hypot(delta.z);
|
||||
|
||||
let clamped_target = if horizontal_dist > TOO_FAR_DISTANCE {
|
||||
Vector3::new(
|
||||
pos.x + delta.x / horizontal_dist * TOO_FAR_DISTANCE,
|
||||
pos.y + TOO_FAR_SIGNAL_HEIGHT,
|
||||
pos.z + delta.z / horizontal_dist * TOO_FAR_DISTANCE,
|
||||
)
|
||||
} else {
|
||||
target
|
||||
};
|
||||
|
||||
*self.target.lock().await = Some(clamped_target);
|
||||
self.life.store(0, Ordering::Relaxed);
|
||||
|
||||
// 4-in-5 chance to survive (drop item); 1-in-5 plays the break effect.
|
||||
let survive = !rand::random::<u32>().is_multiple_of(SURVIVE_CHANCE);
|
||||
self.survive_after_death.store(survive, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Compute the next delta-movement so the eye smoothly homes toward `target`.
|
||||
///
|
||||
/// Mirrors the vanilla `updateDeltaMovement` logic.
|
||||
fn compute_new_velocity(
|
||||
old_velo: Vector3<f64>,
|
||||
position: Vector3<f64>,
|
||||
target: Vector3<f64>,
|
||||
) -> Vector3<f64> {
|
||||
let horizontal_delta = Vector3::new(target.x - position.x, 0.0, target.z - position.z);
|
||||
let horizontal_len = horizontal_delta.x.hypot(horizontal_delta.z);
|
||||
|
||||
let old_h_speed = old_velo.x.hypot(old_velo.z);
|
||||
let mut wanted_speed = lerp(0.0025, old_h_speed, horizontal_len);
|
||||
let mut move_y = old_velo.y;
|
||||
|
||||
if horizontal_len < 1.0 {
|
||||
wanted_speed *= 0.8;
|
||||
move_y *= 0.8;
|
||||
}
|
||||
|
||||
let wanted_y_dir = if position.y - old_velo.y < target.y {
|
||||
1.0f64
|
||||
} else {
|
||||
-1.0f64
|
||||
};
|
||||
|
||||
let scale = wanted_speed / horizontal_len;
|
||||
Vector3::new(
|
||||
horizontal_delta.x * scale,
|
||||
move_y + (wanted_y_dir - move_y) * 0.015,
|
||||
horizontal_delta.z * scale,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Linear interpolation helper (`start + t * (end - start)`).
|
||||
#[inline]
|
||||
fn lerp(t: f64, start: f64, end: f64) -> f64 {
|
||||
start + t * (end - start)
|
||||
}
|
||||
|
||||
impl NBTStorage for EyeOfEnder {}
|
||||
|
||||
impl EntityBase for EyeOfEnder {
|
||||
fn tick<'a>(
|
||||
&'a self,
|
||||
caller: Arc<dyn EntityBase>,
|
||||
server: &'a Server,
|
||||
) -> EntityBaseFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let entity = &self.entity;
|
||||
entity.tick(caller, server).await;
|
||||
|
||||
// Advance position by current velocity.
|
||||
let velocity = entity.velocity.load();
|
||||
let new_pos = entity.pos.load().add(&velocity);
|
||||
|
||||
// Server-side: steer toward target if one is set.
|
||||
{
|
||||
let target_guard = self.target.lock().await;
|
||||
if let Some(target) = *target_guard {
|
||||
let new_velo = Self::compute_new_velocity(velocity, new_pos, target);
|
||||
entity.velocity.store(new_velo);
|
||||
}
|
||||
}
|
||||
|
||||
entity.set_pos(new_pos);
|
||||
entity.send_pos_rot().await;
|
||||
|
||||
// Tick lifetime and handle expiry.
|
||||
let life = self.life.fetch_add(1, Ordering::Relaxed) + 1;
|
||||
if life > MAX_LIFE {
|
||||
entity.play_sound(Sound::EntityEnderEyeDeath).await;
|
||||
entity.remove().await;
|
||||
|
||||
if self.survive_after_death.load(Ordering::Relaxed) {
|
||||
// Drop the item at the current position.
|
||||
let item_stack = self.item_stack.lock().await.clone();
|
||||
let world = entity.world.load();
|
||||
let entity = Entity::new(world.clone(), new_pos, &EntityType::ITEM);
|
||||
let item_entity = Arc::new(ItemEntity::new(entity, item_stack).await);
|
||||
world.spawn_entity(item_entity).await;
|
||||
} else {
|
||||
entity
|
||||
.world
|
||||
.load()
|
||||
.sync_world_event(
|
||||
WorldEvent::ParticlesEyeOfEnderDeath,
|
||||
new_pos.to_block_pos(),
|
||||
0,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn init_data_tracker(&self) -> EntityBaseFuture<'_, ()> {
|
||||
Box::pin(async {
|
||||
self.entity
|
||||
.send_meta_data(&[Metadata::new(
|
||||
TrackedData::ITEM,
|
||||
MetaDataType::ITEM_STACK,
|
||||
&ItemStackSerializer::from(self.item_stack.lock().await.clone()),
|
||||
)])
|
||||
.await;
|
||||
})
|
||||
}
|
||||
|
||||
fn damage_with_context<'a>(
|
||||
&'a self,
|
||||
_caller: &'a dyn EntityBase,
|
||||
_amount: f32,
|
||||
_damage_type: DamageType,
|
||||
_position: Option<Vector3<f64>>,
|
||||
_source: Option<&'a dyn EntityBase>,
|
||||
_cause: Option<&'a dyn EntityBase>,
|
||||
) -> EntityBaseFuture<'a, bool> {
|
||||
// Eye of Ender is not attackable.
|
||||
Box::pin(async { false })
|
||||
}
|
||||
|
||||
fn on_player_collision<'a>(&'a self, _player: &'a Arc<Player>) -> EntityBaseFuture<'a, ()> {
|
||||
// Eye of Ender cannot be picked up.
|
||||
Box::pin(async {})
|
||||
}
|
||||
|
||||
fn get_entity(&self) -> &Entity {
|
||||
&self.entity
|
||||
}
|
||||
|
||||
fn get_living_entity(&self) -> Option<&LivingEntity> {
|
||||
None
|
||||
}
|
||||
|
||||
fn get_item_entity(self: Arc<Self>) -> Option<Arc<ItemEntity>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_nbt_storage(&self) -> &dyn NBTStorage {
|
||||
self
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,7 @@ use std::{
|
||||
};
|
||||
pub mod arrow;
|
||||
pub mod egg;
|
||||
pub mod eye_of_ender;
|
||||
pub mod firework_rocket;
|
||||
pub mod lingering_potion;
|
||||
pub mod snowball;
|
||||
|
||||
@ -1,15 +1,25 @@
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{server::Server, world::portal::end::EndPortal};
|
||||
use crate::entity::Entity;
|
||||
use crate::entity::projectile::eye_of_ender::EyeOfEnder;
|
||||
use crate::item::{ItemBehaviour, ItemMetadata};
|
||||
use crate::server::Server;
|
||||
use crate::world::World;
|
||||
use crate::world::portal::end::EndPortal;
|
||||
use pumpkin_data::entity::EntityType;
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_data::{Block, BlockDirection, item::Item};
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
use pumpkin_data::structures::StructureSet;
|
||||
use pumpkin_data::world::WorldEvent;
|
||||
use pumpkin_data::{Block, BlockDirection};
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_world::generation::generator::structure_finder::find_nearest_structure;
|
||||
use pumpkin_world::world::BlockFlags;
|
||||
|
||||
use crate::item::{ItemBehaviour, ItemMetadata};
|
||||
use crate::{entity::player::Player, world::World};
|
||||
use crate::entity::player::Player;
|
||||
|
||||
pub struct EnderEyeItem;
|
||||
|
||||
@ -22,7 +32,7 @@ impl ItemMetadata for EnderEyeItem {
|
||||
impl ItemBehaviour for EnderEyeItem {
|
||||
fn use_on_block<'a>(
|
||||
&'a self,
|
||||
_item: &'a mut ItemStack,
|
||||
item: &'a mut ItemStack,
|
||||
player: &'a Player,
|
||||
location: BlockPos,
|
||||
_face: BlockDirection,
|
||||
@ -37,17 +47,17 @@ impl ItemBehaviour for EnderEyeItem {
|
||||
|
||||
let world = player.world();
|
||||
let state_id = world.get_block_state_id(&location).await;
|
||||
let original_props = block.properties(state_id).unwrap().to_props();
|
||||
|
||||
let props: Vec<(&str, &str)> = original_props
|
||||
.iter()
|
||||
.map(|(key, value)| {
|
||||
if *key == "eye" {
|
||||
(*key, "true")
|
||||
} else {
|
||||
(*key, *value)
|
||||
// Skip if the frame already holds an eye.
|
||||
let props_raw = block.properties(state_id).unwrap().to_props();
|
||||
if props_raw.iter().any(|(k, v)| *k == "eye" && *v == "true") {
|
||||
return;
|
||||
}
|
||||
})
|
||||
|
||||
// Build new state with eye=true.
|
||||
let props: Vec<(&str, &str)> = props_raw
|
||||
.iter()
|
||||
.map(|(k, v)| if *k == "eye" { (*k, "true") } else { (*k, *v) })
|
||||
.collect();
|
||||
|
||||
let new_state_id = block.from_properties(&props).to_state_id(block);
|
||||
@ -55,7 +65,15 @@ impl ItemBehaviour for EnderEyeItem {
|
||||
.set_block_state(&location, new_state_id, BlockFlags::empty())
|
||||
.await;
|
||||
|
||||
world
|
||||
.sync_world_event(WorldEvent::EndPortalFrameFill, location, 0)
|
||||
.await;
|
||||
|
||||
// Try to complete the portal.
|
||||
EndPortal::get_new_portal(&world, location).await;
|
||||
|
||||
// Consume one item.
|
||||
item.decrement(1);
|
||||
})
|
||||
}
|
||||
|
||||
@ -66,25 +84,73 @@ impl ItemBehaviour for EnderEyeItem {
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
let world = player.world();
|
||||
let (start_pos, end_pos) = self.get_start_and_end_pos(player);
|
||||
let checker = async |pos: &BlockPos, world_inner: &Arc<World>| {
|
||||
let state_id = world_inner.get_block_state_id(pos).await;
|
||||
state_id != Block::AIR.default_state.id
|
||||
};
|
||||
|
||||
let Some((block_pos, _direction)) = world.raycast(start_pos, end_pos, checker).await
|
||||
else {
|
||||
let (start_pos, end_pos) = self.get_start_and_end_pos(player);
|
||||
let checker = async |pos: &BlockPos, w: &Arc<World>| {
|
||||
w.get_block_state_id(pos).await != Block::AIR.default_state.id
|
||||
};
|
||||
if let Some((hit_pos, _)) = world.raycast(start_pos, end_pos, checker).await
|
||||
&& world.get_block(&hit_pos).await == &Block::END_PORTAL_FRAME
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let origin = player.living_entity.entity.block_pos.load();
|
||||
let target_block_pos = find_stronghold(&world, origin);
|
||||
|
||||
let Some(target) = target_block_pos else {
|
||||
return;
|
||||
};
|
||||
|
||||
let block = world.get_block(&block_pos).await;
|
||||
let spawn_pos = Vector3::new(
|
||||
player.living_entity.entity.pos.load().x,
|
||||
player.living_entity.entity.pos.load().y
|
||||
+ f64::from(EntityType::EYE_OF_ENDER.dimension[1]) * 0.5,
|
||||
player.living_entity.entity.pos.load().z,
|
||||
);
|
||||
|
||||
if block == &Block::END_PORTAL_FRAME {}
|
||||
let entity = Entity::new(world.clone(), spawn_pos, &EntityType::EYE_OF_ENDER);
|
||||
let eye = Arc::new(EyeOfEnder::new(entity));
|
||||
|
||||
let target_vec = Vector3::new(
|
||||
f64::from(target.0.x),
|
||||
f64::from(target.0.y),
|
||||
f64::from(target.0.z),
|
||||
);
|
||||
eye.signal_to(target_vec).await;
|
||||
|
||||
world.spawn_entity(eye).await;
|
||||
|
||||
let pitch = 0.33f32 + rand::random::<f32>() * (0.5 - 0.33);
|
||||
world
|
||||
.play_sound_fine(
|
||||
Sound::EntityEnderEyeLaunch,
|
||||
SoundCategory::Neutral,
|
||||
&spawn_pos,
|
||||
1.0,
|
||||
pitch,
|
||||
)
|
||||
.await;
|
||||
|
||||
player.inventory.held_item().lock().await.decrement(1);
|
||||
})
|
||||
//TODO Throw the Ender Eye in the direction of the stronghold.
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn find_stronghold(world: &Arc<World>, origin: BlockPos) -> Option<BlockPos> {
|
||||
let level = &world.level;
|
||||
let generator = &level.world_gen;
|
||||
let seed = level.seed.0;
|
||||
|
||||
find_nearest_structure(
|
||||
origin,
|
||||
&[&StructureSet::get("strongholds").unwrap().placement],
|
||||
100, // max search radius in chunks, matches vanilla default
|
||||
seed as i64,
|
||||
&generator.global_structure_cache,
|
||||
)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user