mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
Add Nether Portals
This commit is contained in:
@@ -72,7 +72,7 @@ pub struct SyncedRegistry {
|
||||
instrument: IndexMap<String, Instrument>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DimensionType {
|
||||
Overworld,
|
||||
OverworldCaves,
|
||||
|
||||
@@ -9,6 +9,65 @@ use crate::math::vector2::Vector2;
|
||||
use num_traits::Euclid;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub struct BlockPosIterator {
|
||||
start_x: i32,
|
||||
start_y: i32,
|
||||
start_z: i32,
|
||||
end_x: i32,
|
||||
end_y: i32,
|
||||
index: usize,
|
||||
count: usize,
|
||||
}
|
||||
|
||||
impl BlockPosIterator {
|
||||
pub fn new(
|
||||
start_x: i32,
|
||||
start_y: i32,
|
||||
start_z: i32,
|
||||
end_x: i32,
|
||||
end_y: i32,
|
||||
end_z: i32,
|
||||
) -> Self {
|
||||
let count_x = end_x - start_x + 1;
|
||||
let count_y = end_y - start_y + 1;
|
||||
let count_z = end_z - start_z + 1;
|
||||
let count = (count_x * count_y * count_z) as usize;
|
||||
BlockPosIterator {
|
||||
start_x,
|
||||
start_y,
|
||||
start_z,
|
||||
end_x,
|
||||
end_y,
|
||||
index: 0,
|
||||
count,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for BlockPosIterator {
|
||||
type Item = BlockPos;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.index >= self.count {
|
||||
return None;
|
||||
}
|
||||
|
||||
let size_x = (self.end_x - self.start_x + 1) as usize;
|
||||
let size_y = (self.end_y - self.start_y + 1) as usize;
|
||||
|
||||
let x_offset = self.index % size_x;
|
||||
let y_offset = (self.index / size_x) % size_y;
|
||||
let z_offset = (self.index / size_x) / size_y;
|
||||
|
||||
let x = self.start_x + x_offset as i32;
|
||||
let y = self.start_y + y_offset as i32;
|
||||
let z = self.start_z + z_offset as i32;
|
||||
|
||||
self.index += 1;
|
||||
Some(BlockPos::new(x, y, z))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
/// Aka Block Position
|
||||
pub struct BlockPos(pub Vector3<i32>);
|
||||
@@ -18,6 +77,28 @@ impl BlockPos {
|
||||
Self(Vector3::new(x, y, z))
|
||||
}
|
||||
|
||||
pub fn iterate(start: BlockPos, end: BlockPos) -> BlockPosIterator {
|
||||
BlockPosIterator::new(
|
||||
start.0.x.min(end.0.x),
|
||||
start.0.y.min(end.0.y),
|
||||
start.0.z.min(end.0.z),
|
||||
start.0.x.max(end.0.x),
|
||||
start.0.y.max(end.0.y),
|
||||
start.0.z.max(end.0.z),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn iterate_block_pos(
|
||||
start_x: i32,
|
||||
start_y: i32,
|
||||
start_z: i32,
|
||||
end_x: i32,
|
||||
end_y: i32,
|
||||
end_z: i32,
|
||||
) -> BlockPosIterator {
|
||||
BlockPosIterator::new(start_x, start_y, start_z, end_x, end_y, end_z)
|
||||
}
|
||||
|
||||
pub fn chunk_and_chunk_relative_position(&self) -> (Vector2<i32>, Vector3<i32>) {
|
||||
let (z_chunk, z_rem) = self.0.z.div_rem_euclid(&16);
|
||||
let (x_chunk, x_rem) = self.0.x.div_rem_euclid(&16);
|
||||
@@ -75,6 +156,14 @@ impl BlockPos {
|
||||
BlockPos(self.0 + offset)
|
||||
}
|
||||
|
||||
pub fn offset_dir(&self, offset: Vector3<i32>, direction: i32) -> Self {
|
||||
BlockPos(Vector3::new(
|
||||
self.0.x + offset.x * direction,
|
||||
self.0.y + offset.y * direction,
|
||||
self.0.z + offset.z * direction,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn up(&self) -> Self {
|
||||
self.offset(Vector3::new(0, 1, 0))
|
||||
}
|
||||
|
||||
@@ -4,9 +4,12 @@ use crate::block::pumpkin_block::PumpkinBlock;
|
||||
use crate::entity::player::Player;
|
||||
use crate::server::Server;
|
||||
use crate::world::World;
|
||||
use crate::world::portal::NetherPortal;
|
||||
use async_trait::async_trait;
|
||||
use pumpkin_data::block_properties::HorizontalAxis;
|
||||
use pumpkin_data::world::WorldEvent;
|
||||
use pumpkin_data::{Block, BlockState};
|
||||
use pumpkin_registry::DimensionType;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_world::BlockStateId;
|
||||
use pumpkin_world::block::BlockDirection;
|
||||
@@ -33,16 +36,24 @@ impl FireBlockBase {
|
||||
impl PumpkinBlock for FireBlockBase {
|
||||
async fn placed(
|
||||
&self,
|
||||
_world: &Arc<World>,
|
||||
world: &Arc<World>,
|
||||
_block: &Block,
|
||||
state_id: BlockStateId,
|
||||
_pos: &BlockPos,
|
||||
pos: &BlockPos,
|
||||
old_state_id: BlockStateId,
|
||||
_notify: bool,
|
||||
) {
|
||||
if old_state_id == state_id {
|
||||
return;
|
||||
}
|
||||
let dimension = world.dimension_type;
|
||||
// First lets check if we are in OverWorld or Nether, its not possible to place an Nether portal in other dimensions in Vanilla
|
||||
if dimension == DimensionType::Overworld || dimension == DimensionType::TheNether {
|
||||
if let Some(portal) = NetherPortal::get_new_portal(world, pos, HorizontalAxis::X).await
|
||||
{
|
||||
portal.create(world).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn can_place_at(
|
||||
|
||||
@@ -19,6 +19,7 @@ pub mod glass_panes;
|
||||
pub mod iron_bars;
|
||||
pub mod jukebox;
|
||||
pub mod logs;
|
||||
pub mod nether_portal;
|
||||
pub mod redstone;
|
||||
pub mod signs;
|
||||
pub mod slabs;
|
||||
|
||||
41
pumpkin/src/block/blocks/nether_portal.rs
Normal file
41
pumpkin/src/block/blocks/nether_portal.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use crate::block::pumpkin_block::PumpkinBlock;
|
||||
use crate::world::World;
|
||||
use crate::world::portal::NetherPortal;
|
||||
use async_trait::async_trait;
|
||||
use pumpkin_data::Block;
|
||||
use pumpkin_data::block_properties::{Axis, BlockProperties, NetherPortalLikeProperties};
|
||||
use pumpkin_macros::pumpkin_block;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_world::BlockStateId;
|
||||
use pumpkin_world::block::BlockDirection;
|
||||
|
||||
#[pumpkin_block("minecraft:nether_portal")]
|
||||
pub struct NetherPortalBlock;
|
||||
|
||||
#[async_trait]
|
||||
impl PumpkinBlock for NetherPortalBlock {
|
||||
async fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
world: &World,
|
||||
_block: &Block,
|
||||
state: BlockStateId,
|
||||
pos: &BlockPos,
|
||||
direction: BlockDirection,
|
||||
_neighbor_pos: &BlockPos,
|
||||
neighbor_state: BlockStateId,
|
||||
) -> BlockStateId {
|
||||
let axis = direction.to_axis();
|
||||
let is_horizontal = axis == Axis::X && axis == Axis::Z;
|
||||
let state_axis =
|
||||
NetherPortalLikeProperties::from_state_id(state, &Block::NETHER_PORTAL).axis;
|
||||
if is_horizontal
|
||||
|| neighbor_state == state
|
||||
|| NetherPortal::get_on_axis(world, pos, state_axis)
|
||||
.await
|
||||
.is_some_and(|e| e.was_already_valid())
|
||||
{
|
||||
return state;
|
||||
}
|
||||
Block::AIR.default_state_id
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use blocks::fire::soul_fire::SoulFireBlock;
|
||||
use blocks::glass_panes::GlassPaneBlock;
|
||||
use blocks::iron_bars::IronBarsBlock;
|
||||
use blocks::logs::LogBlock;
|
||||
use blocks::nether_portal::NetherPortalBlock;
|
||||
use blocks::redstone::buttons::ButtonBlock;
|
||||
use blocks::redstone::observer::ObserverBlock;
|
||||
use blocks::redstone::piston::PistonBlock;
|
||||
@@ -85,6 +86,7 @@ pub fn default_registry() -> Arc<BlockRegistry> {
|
||||
manager.register(TNTBlock);
|
||||
manager.register(TorchBlock);
|
||||
manager.register(WallBlock);
|
||||
manager.register(NetherPortalBlock);
|
||||
|
||||
// Fire
|
||||
manager.register(SoulFireBlock);
|
||||
|
||||
@@ -6,6 +6,7 @@ use std::{
|
||||
|
||||
pub mod chunker;
|
||||
pub mod explosion;
|
||||
pub mod portal;
|
||||
pub mod time;
|
||||
|
||||
use crate::{
|
||||
|
||||
251
pumpkin/src/world/portal.rs
Normal file
251
pumpkin/src/world/portal.rs
Normal file
@@ -0,0 +1,251 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use pumpkin_data::{
|
||||
Block, BlockState,
|
||||
block_properties::{BlockProperties, HorizontalAxis, NetherPortalLikeProperties},
|
||||
tag::Tagable,
|
||||
};
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_world::block::BlockDirection;
|
||||
|
||||
use super::{BlockFlags, World};
|
||||
|
||||
pub struct NetherPortal {
|
||||
axis: HorizontalAxis,
|
||||
found_portal_blocks: u32,
|
||||
negative_direction: BlockDirection,
|
||||
lower_conor: BlockPos,
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
impl NetherPortal {
|
||||
/// This is Vanilla
|
||||
const MIN_WIDTH: u32 = 2;
|
||||
const MAX_WIDTH: u32 = 21;
|
||||
|
||||
const MAX_HEIGHT: u32 = 21;
|
||||
const MIN_HEIGHT: u32 = 3;
|
||||
|
||||
const FRAME_BLOCK: Block = Block::OBSIDIAN;
|
||||
|
||||
#[must_use]
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.width >= Self::MIN_WIDTH
|
||||
&& self.width <= Self::MAX_WIDTH
|
||||
&& self.height >= Self::MIN_WIDTH
|
||||
&& self.height <= Self::MAX_HEIGHT
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn was_already_valid(&self) -> bool {
|
||||
self.is_valid() && self.found_portal_blocks == self.width * self.height
|
||||
}
|
||||
|
||||
pub async fn create(&self, world: &Arc<World>) {
|
||||
let mut props = NetherPortalLikeProperties::default(&Block::NETHER_PORTAL);
|
||||
props.axis = self.axis;
|
||||
let state = props.to_state_id(&Block::NETHER_PORTAL);
|
||||
// We remove 1 block because of the frame border
|
||||
let blocks = BlockPos::iterate(
|
||||
self.lower_conor,
|
||||
self.lower_conor
|
||||
.offset_dir(BlockDirection::Up.to_offset(), self.height as i32 - 1)
|
||||
.offset_dir(self.negative_direction.to_offset(), self.width as i32 - 1),
|
||||
);
|
||||
for pos in blocks {
|
||||
world
|
||||
.set_block_state(
|
||||
&pos,
|
||||
state,
|
||||
BlockFlags::NOTIFY_LISTENERS | BlockFlags::FORCE_STATE,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_new_portal(
|
||||
world: &World,
|
||||
pos: &BlockPos,
|
||||
first_axis: HorizontalAxis,
|
||||
) -> Option<Self> {
|
||||
// We check both axis here X and Z
|
||||
if let Some(portal) = Self::get_on_axis(world, pos, first_axis).await {
|
||||
if portal.is_valid() && portal.found_portal_blocks == 0 {
|
||||
return Some(portal);
|
||||
}
|
||||
}
|
||||
let next_axis = if first_axis == HorizontalAxis::X {
|
||||
HorizontalAxis::Z
|
||||
} else {
|
||||
HorizontalAxis::X
|
||||
};
|
||||
if let Some(portal) = Self::get_on_axis(world, pos, next_axis).await {
|
||||
if portal.is_valid() && portal.found_portal_blocks == 0 {
|
||||
return Some(portal);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub async fn get_on_axis(world: &World, pos: &BlockPos, axis: HorizontalAxis) -> Option<Self> {
|
||||
let direction = if axis == HorizontalAxis::X {
|
||||
BlockDirection::West
|
||||
} else {
|
||||
BlockDirection::South
|
||||
};
|
||||
let cornor = Self::get_lower_cornor(world, direction, pos).await?;
|
||||
let width = Self::get_width(world, &cornor, &direction).await;
|
||||
if !(Self::MIN_WIDTH..=Self::MAX_WIDTH).contains(&width) {
|
||||
return None;
|
||||
}
|
||||
let mut found_portal_blocks = 0;
|
||||
let height =
|
||||
Self::get_height(world, &cornor, &direction, width, &mut found_portal_blocks).await?;
|
||||
Some(Self {
|
||||
axis,
|
||||
found_portal_blocks,
|
||||
negative_direction: direction,
|
||||
lower_conor: cornor,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_lower_cornor(
|
||||
world: &World,
|
||||
direction: BlockDirection,
|
||||
pos: &BlockPos,
|
||||
) -> Option<BlockPos> {
|
||||
// TODO: check max getBottomY
|
||||
let limit_y = pos.0.y - Self::MAX_HEIGHT as i32;
|
||||
let mut pos = *pos;
|
||||
while pos.0.y > limit_y {
|
||||
let (block, state) = world.get_block_and_block_state(&pos.down()).await.unwrap();
|
||||
if !Self::valid_state_inside_portal(&block, &state) {
|
||||
break;
|
||||
}
|
||||
pos = pos.down();
|
||||
}
|
||||
let neg_dir = direction.opposite();
|
||||
let width = (Self::get_width(world, &pos, &neg_dir).await as i32) - 1;
|
||||
if width < 0 {
|
||||
return None;
|
||||
}
|
||||
Some(pos.offset_dir(neg_dir.to_offset(), width as i32))
|
||||
}
|
||||
|
||||
async fn get_width(
|
||||
world: &World,
|
||||
original_lower_corner: &BlockPos,
|
||||
negative_dir: &BlockDirection,
|
||||
) -> u32 {
|
||||
let mut lower_corner;
|
||||
for i in 0..=Self::MAX_WIDTH {
|
||||
lower_corner = original_lower_corner.offset_dir(negative_dir.to_offset(), i as i32);
|
||||
let (block, block_state) = world
|
||||
.get_block_and_block_state(&lower_corner)
|
||||
.await
|
||||
.unwrap();
|
||||
if !Self::valid_state_inside_portal(&block, &block_state) {
|
||||
if Self::FRAME_BLOCK != block {
|
||||
break;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
let block = world.get_block(&lower_corner.down()).await.unwrap();
|
||||
if Self::FRAME_BLOCK != block {
|
||||
break;
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
async fn get_height(
|
||||
world: &World,
|
||||
lower_corner: &BlockPos,
|
||||
negative_dir: &BlockDirection,
|
||||
width: u32,
|
||||
found_portal_blocks: &mut u32,
|
||||
) -> Option<u32> {
|
||||
let height = Self::get_potential_height(
|
||||
world,
|
||||
lower_corner,
|
||||
negative_dir,
|
||||
width,
|
||||
found_portal_blocks,
|
||||
)
|
||||
.await;
|
||||
if !(Self::MIN_HEIGHT..=Self::MAX_HEIGHT).contains(&height)
|
||||
|| !Self::is_horizontal_frame_valid(world, lower_corner, negative_dir, width, height)
|
||||
.await
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some(height)
|
||||
}
|
||||
|
||||
async fn get_potential_height(
|
||||
world: &World,
|
||||
lower_corner: &BlockPos,
|
||||
negative_dir: &BlockDirection,
|
||||
width: u32,
|
||||
found_portal_blocks: &mut u32,
|
||||
) -> u32 {
|
||||
for i in 0..Self::MAX_HEIGHT as i32 {
|
||||
let mut pos = lower_corner
|
||||
.offset_dir(BlockDirection::Up.to_offset(), i)
|
||||
.offset_dir(negative_dir.to_offset(), -1);
|
||||
if world.get_block(&pos).await.unwrap() != Self::FRAME_BLOCK {
|
||||
return i as u32;
|
||||
}
|
||||
|
||||
pos = lower_corner
|
||||
.offset_dir(BlockDirection::Up.to_offset(), i)
|
||||
.offset_dir(negative_dir.to_offset(), width as i32);
|
||||
if world.get_block(&pos).await.unwrap() != Self::FRAME_BLOCK {
|
||||
return i as u32;
|
||||
}
|
||||
|
||||
for j in 0..width {
|
||||
pos = lower_corner
|
||||
.offset_dir(BlockDirection::Up.to_offset(), i)
|
||||
.offset_dir(negative_dir.to_offset(), j as i32);
|
||||
let (block, block_state) = world.get_block_and_block_state(&pos).await.unwrap();
|
||||
if !Self::valid_state_inside_portal(&block, &block_state) {
|
||||
return i as u32;
|
||||
}
|
||||
if block == Block::NETHER_PORTAL {
|
||||
*found_portal_blocks += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
|
||||
}
|
||||
|
||||
async fn is_horizontal_frame_valid(
|
||||
world: &World,
|
||||
lower_corner: &BlockPos,
|
||||
dir: &BlockDirection,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> bool {
|
||||
let mut pos;
|
||||
for i in 0..width {
|
||||
pos = lower_corner
|
||||
.offset_dir(BlockDirection::Up.to_offset(), height as i32)
|
||||
.offset_dir(dir.to_offset(), i as i32);
|
||||
if Self::FRAME_BLOCK != world.get_block(&pos).await.unwrap() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// What is allowed to be inside the Portal frame
|
||||
fn valid_state_inside_portal(block: &Block, state: &BlockState) -> bool {
|
||||
state.is_air()
|
||||
|| block.is_tagged_with("minecraft:fire").unwrap()
|
||||
|| block == &Block::NETHER_PORTAL
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user