From 81c45b7c78f5f044237c2cb4a6656617b11db3c9 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Wed, 14 May 2025 20:11:06 +0200 Subject: [PATCH] Add Pressure Plates --- pumpkin-util/src/math/vector3.rs | 11 ++ pumpkin/src/block/blocks/redstone/mod.rs | 1 + .../blocks/redstone/pressure_plate/mod.rs | 84 ++++++++++ .../blocks/redstone/pressure_plate/plate.rs | 133 ++++++++++++++++ .../redstone/pressure_plate/weighted.rs | 145 ++++++++++++++++++ pumpkin/src/block/mod.rs | 4 + pumpkin/src/block/pumpkin_block.rs | 11 ++ pumpkin/src/block/registry.rs | 17 ++ pumpkin/src/entity/living.rs | 5 +- pumpkin/src/entity/mod.rs | 28 ++++ pumpkin/src/world/mod.rs | 19 ++- 11 files changed, 455 insertions(+), 3 deletions(-) create mode 100644 pumpkin/src/block/blocks/redstone/pressure_plate/mod.rs create mode 100644 pumpkin/src/block/blocks/redstone/pressure_plate/plate.rs create mode 100644 pumpkin/src/block/blocks/redstone/pressure_plate/weighted.rs diff --git a/pumpkin-util/src/math/vector3.rs b/pumpkin-util/src/math/vector3.rs index 561a94768..bab4da85b 100644 --- a/pumpkin-util/src/math/vector3.rs +++ b/pumpkin-util/src/math/vector3.rs @@ -3,6 +3,8 @@ use std::ops::{Add, AddAssign, Div, Mul, Sub}; use num_traits::{Float, Num}; +use super::vector2::Vector2; + #[derive(Clone, Copy, Debug, PartialEq, Hash, Eq, Default)] pub struct Vector3 { pub x: T, @@ -209,6 +211,15 @@ where z: z.round() as i32, } } + + pub fn to_vec2_i32(&self) -> Vector2 { + let x: f64 = self.x.into(); + let z: f64 = self.z.into(); + Vector2 { + x: x.round() as i32, + z: z.round() as i32, + } + } } pub trait Math: diff --git a/pumpkin/src/block/blocks/redstone/mod.rs b/pumpkin/src/block/blocks/redstone/mod.rs index 79ae330e2..2d54db881 100644 --- a/pumpkin/src/block/blocks/redstone/mod.rs +++ b/pumpkin/src/block/blocks/redstone/mod.rs @@ -13,6 +13,7 @@ use crate::world::World; pub mod buttons; pub mod lever; pub mod observer; +pub mod pressure_plate; pub mod rails; pub mod redstone_block; pub mod redstone_lamp; diff --git a/pumpkin/src/block/blocks/redstone/pressure_plate/mod.rs b/pumpkin/src/block/blocks/redstone/pressure_plate/mod.rs new file mode 100644 index 000000000..6e8154775 --- /dev/null +++ b/pumpkin/src/block/blocks/redstone/pressure_plate/mod.rs @@ -0,0 +1,84 @@ +use std::sync::Arc; + +use pumpkin_data::{Block, BlockState}; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::{BlockStateId, chunk::TickPriority, world::BlockFlags}; + +use crate::world::World; + +pub mod plate; +pub mod weighted; + +pub trait PressurePlate { + async fn on_entity_collision_pp( + &self, + world: &Arc, + pos: BlockPos, + block: Block, + state: BlockState, + ) { + let output = self.get_redstone_output(&block, state.id); + if output == 0 { + self.update_plate_state(world, pos, &block, state, output) + .await; + } + } + + async fn on_scheduled_tick_pp(&self, world: &Arc, block: &Block, pos: &BlockPos) { + let state = world.get_block_state(pos).await.unwrap(); + let output = self.get_redstone_output(block, state.id); + if output > 0 { + self.update_plate_state(world, *pos, block, state, output) + .await; + } + } + + async fn on_state_replaced_pp( + &self, + world: &Arc, + block: &Block, + pos: BlockPos, + old_state_id: BlockStateId, + moved: bool, + ) { + if !moved && self.get_redstone_output(block, old_state_id) > 0 { + world.update_neighbors(&pos, None).await; + world.update_neighbors(&pos.down(), None).await; + } + } + + async fn update_plate_state( + &self, + world: &Arc, + pos: BlockPos, + block: &Block, + state: BlockState, + output: u8, + ) { + let calc_output = self.calculate_redstone_output(world, block, &pos).await; + let has_output = calc_output > 0; + if calc_output != output { + let state = self.set_redstone_output(block, &state, calc_output); + world + .set_block_state(&pos, state, BlockFlags::NOTIFY_LISTENERS) + .await; + world.update_neighbors(&pos, None).await; + world.update_neighbors(&pos.down(), None).await; + } + if has_output { + world + .schedule_block_tick(block, pos, self.tick_rate(), TickPriority::Normal) + .await; + } + } + + fn get_redstone_output(&self, block: &Block, state: BlockStateId) -> u8; + + fn set_redstone_output(&self, block: &Block, state: &BlockState, output: u8) -> BlockStateId; + + async fn calculate_redstone_output(&self, world: &World, block: &Block, pos: &BlockPos) -> u8; + + fn tick_rate(&self) -> u16 { + 20 + } +} diff --git a/pumpkin/src/block/blocks/redstone/pressure_plate/plate.rs b/pumpkin/src/block/blocks/redstone/pressure_plate/plate.rs new file mode 100644 index 000000000..50c67a39a --- /dev/null +++ b/pumpkin/src/block/blocks/redstone/pressure_plate/plate.rs @@ -0,0 +1,133 @@ +use std::sync::Arc; + +use async_trait::async_trait; +use pumpkin_data::{ + Block, BlockState, + block_properties::BlockProperties, + tag::{RegistryKey, get_tag_values}, +}; +use pumpkin_util::math::{boundingbox::BoundingBox, position::BlockPos}; +use pumpkin_world::{BlockStateId, block::BlockDirection}; + +use crate::{ + block::pumpkin_block::{BlockMetadata, PumpkinBlock}, + entity::Entity, + world::World, +}; + +use super::PressurePlate; + +/// This is for Normal Pressure plates, so not Gold or Iron +pub struct PressurePlateBlock; + +type PressurePlateProps = pumpkin_data::block_properties::StonePressurePlateLikeProperties; + +impl BlockMetadata for PressurePlateBlock { + fn namespace(&self) -> &'static str { + "minecraft" + } + + fn ids(&self) -> &'static [&'static str] { + let mut combined = Vec::new(); + combined.extend_from_slice( + get_tag_values(RegistryKey::Block, "minecraft:wooden_pressure_plates").unwrap(), + ); + combined.extend_from_slice( + get_tag_values(RegistryKey::Block, "minecraft:stone_pressure_plates").unwrap(), + ); + combined.leak() + } +} + +#[async_trait] +impl PumpkinBlock for PressurePlateBlock { + async fn on_entity_collision( + &self, + world: &Arc, + _entity: &Entity, + pos: BlockPos, + block: Block, + state: BlockState, + ) { + self.on_entity_collision_pp(world, pos, block, state).await; + } + + async fn on_scheduled_tick(&self, world: &Arc, block: &Block, pos: &BlockPos) { + self.on_scheduled_tick_pp(world, block, pos).await; + } + + async fn on_state_replaced( + &self, + world: &Arc, + block: &Block, + pos: BlockPos, + old_state_id: BlockStateId, + moved: bool, + ) { + self.on_state_replaced_pp(world, block, pos, old_state_id, moved) + .await; + } + + async fn get_weak_redstone_power( + &self, + block: &Block, + _world: &World, + _pos: &BlockPos, + state: &BlockState, + _direction: BlockDirection, + ) -> u8 { + self.get_redstone_output(block, state.id) + } + + async fn get_strong_redstone_power( + &self, + block: &Block, + _world: &World, + _pos: &BlockPos, + state: &BlockState, + direction: BlockDirection, + ) -> u8 { + if direction == BlockDirection::Up { + return self.get_redstone_output(block, state.id); + } + 0 + } + + async fn emits_redstone_power( + &self, + _block: &Block, + _state: &BlockState, + _direction: BlockDirection, + ) -> bool { + true + } +} + +impl PressurePlate for PressurePlateBlock { + fn get_redstone_output(&self, block: &Block, state: BlockStateId) -> u8 { + let props = PressurePlateProps::from_state_id(state, block); + if props.powered { 15 } else { 0 } + } + + async fn calculate_redstone_output(&self, world: &World, _block: &Block, pos: &BlockPos) -> u8 { + // TODO: this is bad use real box + let aabb = BoundingBox::from_block(pos); + if !world.get_entities_at_box(&aabb).await.is_empty() + || !world.get_players_at_box(&aabb).await.is_empty() + { + return 15; + } + 0 + } + + fn set_redstone_output( + &self, + block: &Block, + state: &BlockState, + output: u8, + ) -> pumpkin_world::BlockStateId { + let mut props = PressurePlateProps::from_state_id(state.id, block); + props.powered = output > 0; + props.to_state_id(block) + } +} diff --git a/pumpkin/src/block/blocks/redstone/pressure_plate/weighted.rs b/pumpkin/src/block/blocks/redstone/pressure_plate/weighted.rs new file mode 100644 index 000000000..7118d8eb8 --- /dev/null +++ b/pumpkin/src/block/blocks/redstone/pressure_plate/weighted.rs @@ -0,0 +1,145 @@ +use std::sync::Arc; + +use async_trait::async_trait; +use pumpkin_data::{ + Block, BlockState, + block_properties::{BlockProperties, EnumVariants, Integer0To15}, +}; +use pumpkin_util::math::{boundingbox::BoundingBox, position::BlockPos}; +use pumpkin_world::{BlockStateId, block::BlockDirection}; + +use crate::{ + block::pumpkin_block::{BlockMetadata, PumpkinBlock}, + entity::Entity, + world::World, +}; + +use super::PressurePlate; + +/// This is for Gold and Iron Pressure Plate +pub struct WeightedPressurePlateBlock; + +type PressurePlateProps = pumpkin_data::block_properties::LightWeightedPressurePlateLikeProperties; + +impl BlockMetadata for WeightedPressurePlateBlock { + fn namespace(&self) -> &'static str { + "minecraft" + } + + fn ids(&self) -> &'static [&'static str] { + // light = Gold + // heavy = Iron + &[ + "light_weighted_pressure_plate", + "heavy_weighted_pressure_plate", + ] + } +} + +#[async_trait] +impl PumpkinBlock for WeightedPressurePlateBlock { + async fn on_entity_collision( + &self, + world: &Arc, + _entity: &Entity, + pos: BlockPos, + block: Block, + state: BlockState, + ) { + self.on_entity_collision_pp(world, pos, block, state).await; + } + + async fn on_scheduled_tick(&self, world: &Arc, block: &Block, pos: &BlockPos) { + self.on_scheduled_tick_pp(world, block, pos).await; + } + + async fn on_state_replaced( + &self, + world: &Arc, + block: &Block, + pos: BlockPos, + old_state_id: BlockStateId, + moved: bool, + ) { + self.on_state_replaced_pp(world, block, pos, old_state_id, moved) + .await; + } + + async fn get_weak_redstone_power( + &self, + block: &Block, + _world: &World, + _pos: &BlockPos, + state: &BlockState, + _direction: BlockDirection, + ) -> u8 { + self.get_redstone_output(block, state.id) + } + + async fn get_strong_redstone_power( + &self, + block: &Block, + _world: &World, + _pos: &BlockPos, + state: &BlockState, + direction: BlockDirection, + ) -> u8 { + if direction == BlockDirection::Up { + return self.get_redstone_output(block, state.id); + } + 0 + } + + async fn emits_redstone_power( + &self, + _block: &Block, + _state: &BlockState, + _direction: BlockDirection, + ) -> bool { + true + } +} + +impl PressurePlate for WeightedPressurePlateBlock { + fn get_redstone_output(&self, block: &Block, state: BlockStateId) -> u8 { + let props = PressurePlateProps::from_state_id(state, block); + props.power.to_index() as u8 + } + + async fn calculate_redstone_output(&self, world: &World, block: &Block, pos: &BlockPos) -> u8 { + // light = Gold + // heavy = Iron + let weight = if block == &Block::LIGHT_WEIGHTED_PRESSURE_PLATE { + // Gold + 15 + } else { + // Iron + 150 + }; + // TODO: this is bad use real box + let aabb = BoundingBox::from_block(pos); + let len = world.get_entities_at_box(&aabb).await.len() + + world.get_players_at_box(&aabb).await.len(); + let len = len.min(weight); + if len > 0 { + let f = (weight.min(len) / weight) as f32; + return (f * 15.0).ceil() as u8; + } + 0 + } + + fn set_redstone_output( + &self, + block: &Block, + state: &BlockState, + output: u8, + ) -> pumpkin_world::BlockStateId { + let mut props = PressurePlateProps::from_state_id(state.id, block); + props.power = Integer0To15::from_index(u16::from(output)); + props.to_state_id(block) + } + + fn tick_rate(&self) -> u16 { + 10 + } +} diff --git a/pumpkin/src/block/mod.rs b/pumpkin/src/block/mod.rs index f9dc73ad1..abb06e6be 100644 --- a/pumpkin/src/block/mod.rs +++ b/pumpkin/src/block/mod.rs @@ -19,6 +19,8 @@ use blocks::piston::piston_head::PistonHeadBlock; use blocks::pumpkin::PumpkinBlock; use blocks::redstone::buttons::ButtonBlock; use blocks::redstone::observer::ObserverBlock; +use blocks::redstone::pressure_plate::plate::PressurePlateBlock; +use blocks::redstone::pressure_plate::weighted::WeightedPressurePlateBlock; use blocks::redstone::rails::activator_rail::ActivatorRailBlock; use blocks::redstone::rails::detector_rail::DetectorRailBlock; use blocks::redstone::rails::powered_rail::PoweredRailBlock; @@ -94,6 +96,8 @@ pub fn default_registry() -> Arc { manager.register(NetherPortalBlock); manager.register(NoteBlock); manager.register(PumpkinBlock); + manager.register(PressurePlateBlock); + manager.register(WeightedPressurePlateBlock); // Fire manager.register(SoulFireBlock); diff --git a/pumpkin/src/block/pumpkin_block.rs b/pumpkin/src/block/pumpkin_block.rs index 30818292d..76fbdb291 100644 --- a/pumpkin/src/block/pumpkin_block.rs +++ b/pumpkin/src/block/pumpkin_block.rs @@ -1,4 +1,5 @@ use crate::block::registry::BlockActionResult; +use crate::entity::Entity; use crate::entity::player::Player; use crate::server::Server; use crate::world::World; @@ -49,6 +50,16 @@ pub trait PumpkinBlock: Send + Sync { BlockActionResult::Continue } + async fn on_entity_collision( + &self, + _world: &Arc, + _entity: &Entity, + _pos: BlockPos, + _block: Block, + _state: BlockState, + ) { + } + fn should_drop_items_on_explosion(&self) -> bool { true } diff --git a/pumpkin/src/block/registry.rs b/pumpkin/src/block/registry.rs index ab9076ca9..fabfea938 100644 --- a/pumpkin/src/block/registry.rs +++ b/pumpkin/src/block/registry.rs @@ -1,4 +1,5 @@ use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock}; +use crate::entity::Entity; use crate::entity::player::Player; use crate::server::Server; use crate::world::World; @@ -55,6 +56,22 @@ impl BlockRegistry { false } + pub async fn on_entity_collision( + &self, + block: Block, + world: &Arc, + entity: &Entity, + pos: BlockPos, + state: BlockState, + ) { + let pumpkin_block = self.get_pumpkin_block(&block); + if let Some(pumpkin_block) = pumpkin_block { + pumpkin_block + .on_entity_collision(world, entity, pos, block, state) + .await; + } + } + pub async fn on_use( &self, block: &Block, diff --git a/pumpkin/src/entity/living.rs b/pumpkin/src/entity/living.rs index c5c9cd72c..43b430e8e 100644 --- a/pumpkin/src/entity/living.rs +++ b/pumpkin/src/entity/living.rs @@ -262,7 +262,7 @@ impl LivingEntity { .await; } - fn tick_move(&self) { + async fn tick_move(&self) { let velo = self.entity.velocity.load(); let pos = self.entity.pos.load(); self.entity @@ -272,6 +272,7 @@ impl LivingEntity { self.entity .velocity .store(velo.multiply(multiplier, 1.0, multiplier)); + self.entity.check_block_collision().await; } async fn tick_effects(&self) { @@ -297,7 +298,7 @@ impl LivingEntity { impl EntityBase for LivingEntity { async fn tick(&self, server: &Server) { self.entity.tick(server).await; - self.tick_move(); + self.tick_move().await; self.tick_effects().await; if self.time_until_regen.load(Relaxed) > 0 { self.time_until_regen.fetch_sub(1, Relaxed); diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index 874caadae..2cdfa10c4 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -484,6 +484,34 @@ impl Entity { // } else { // } } + + pub async fn check_block_collision(&self) { + let aabb = self.bounding_box.load(); + let blockpos = BlockPos::new( + (aabb.min.x + 0.001).floor() as i32, + (aabb.min.y + 0.001).floor() as i32, + (aabb.min.z + 0.001).floor() as i32, + ); + let blockpos1 = BlockPos::new( + (aabb.max.x - 0.001).floor() as i32, + (aabb.max.y - 0.001).floor() as i32, + (aabb.max.z - 0.001).floor() as i32, + ); + let world = self.world.read().await; + + for x in blockpos.0.x..=blockpos1.0.x { + for y in blockpos.0.y..=blockpos1.0.y { + for z in blockpos.0.z..=blockpos1.0.z { + let pos = BlockPos::new(x, y, z); + let (block, state) = world.get_block_and_block_state(&pos).await.unwrap(); + world + .block_registry + .on_entity_collision(block, &world, self, pos, state) + .await; + } + } + } + } } #[async_trait] diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 6a80e375a..6cd4c99c2 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -64,7 +64,7 @@ use pumpkin_protocol::{ codec::var_int::VarInt, }; use pumpkin_registry::DimensionType; -use pumpkin_util::math::{position::BlockPos, vector3::Vector3}; +use pumpkin_util::math::{boundingbox::BoundingBox, position::BlockPos, vector3::Vector3}; use pumpkin_util::math::{position::chunk_section_from_pos, vector2::Vector2}; use pumpkin_util::text::{TextComponent, color::NamedColor}; use pumpkin_world::world::BlockFlags; @@ -1070,6 +1070,23 @@ impl World { None } + pub async fn get_entities_at_box(&self, aabb: &BoundingBox) -> Vec> { + let entities_guard = self.entities.read().await; + entities_guard + .values() + .filter(|entity| entity.get_entity().bounding_box.load().intersects(aabb)) + .cloned() + .collect() + } + pub async fn get_players_at_box(&self, aabb: &BoundingBox) -> Vec> { + let players_guard = self.players.read().await; + players_guard + .values() + .filter(|player| player.get_entity().bounding_box.load().intersects(aabb)) + .cloned() + .collect() + } + /// Retrieves a player by their unique UUID. /// /// This function searches the world's active player list for a player with the specified UUID.