Add Noteblock support
This commit is contained in:
parent
575f17948f
commit
cb51228adb
@ -355,12 +355,12 @@ impl ToTokens for CollisionShape {
|
||||
pub struct BlockState {
|
||||
pub id: u16,
|
||||
pub state_flags: u8,
|
||||
pub instrument: String, // TODO: make this an enum
|
||||
pub luminance: u8,
|
||||
pub hardness: f32,
|
||||
pub collision_shapes: Vec<u16>,
|
||||
pub opacity: Option<u8>,
|
||||
pub block_entity_type: Option<u16>,
|
||||
// pub instrument: String, // TODO: make this an enum
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
@ -374,6 +374,7 @@ impl BlockState {
|
||||
let mut tokens = TokenStream::new();
|
||||
let id = LitInt::new(&self.id.to_string(), Span::call_site());
|
||||
let state_flags = LitInt::new(&self.state_flags.to_string(), Span::call_site());
|
||||
let instrument = self.instrument.clone();
|
||||
let luminance = LitInt::new(&self.luminance.to_string(), Span::call_site());
|
||||
let hardness = self.hardness;
|
||||
let opacity = match self.opacity {
|
||||
@ -401,6 +402,7 @@ impl BlockState {
|
||||
BlockState {
|
||||
id: #id,
|
||||
state_flags: #state_flags,
|
||||
instrument: #instrument,
|
||||
luminance: #luminance,
|
||||
hardness: #hardness,
|
||||
collision_shapes: &[#(#collision_shapes),*],
|
||||
|
||||
@ -2,12 +2,13 @@
|
||||
pub struct BlockState {
|
||||
pub id: u16,
|
||||
pub state_flags: u8,
|
||||
pub instrument: &'static str,
|
||||
pub luminance: u8,
|
||||
pub hardness: f32,
|
||||
pub collision_shapes: &'static [u16],
|
||||
//u8::MAX is used as None
|
||||
/// u8::MAX is used as None
|
||||
pub opacity: u8,
|
||||
//u16::MAX is used as None
|
||||
/// u16::MAX is used as None
|
||||
pub block_entity_type: u16,
|
||||
}
|
||||
|
||||
|
||||
@ -8,14 +8,14 @@ use crate::VarInt;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[packet(PLAY_BLOCK_EVENT)]
|
||||
pub struct CBlockAction {
|
||||
pub struct CBlockEvent {
|
||||
location: BlockPos,
|
||||
action_id: u8,
|
||||
action_parameter: u8,
|
||||
block_type: VarInt,
|
||||
}
|
||||
|
||||
impl CBlockAction {
|
||||
impl CBlockEvent {
|
||||
pub fn new(
|
||||
location: BlockPos,
|
||||
action_id: u8,
|
||||
|
||||
@ -15,6 +15,7 @@ pub mod iron_bars;
|
||||
pub mod jukebox;
|
||||
pub mod logs;
|
||||
pub mod nether_portal;
|
||||
pub mod note;
|
||||
pub mod redstone;
|
||||
pub mod signs;
|
||||
pub mod slabs;
|
||||
|
||||
249
pumpkin/src/block/blocks/note.rs
Normal file
249
pumpkin/src/block/blocks/note.rs
Normal file
@ -0,0 +1,249 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::block::BlockIsReplacing;
|
||||
use crate::server::Server;
|
||||
use crate::{block::registry::BlockActionResult, entity::player::Player};
|
||||
use async_trait::async_trait;
|
||||
use pumpkin_data::block_properties::Axis;
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
use pumpkin_data::{
|
||||
Block,
|
||||
block_properties::{
|
||||
BlockProperties, EnumVariants, Instrument, Integer0To24, NoteBlockLikeProperties,
|
||||
},
|
||||
};
|
||||
use pumpkin_macros::pumpkin_block;
|
||||
use pumpkin_protocol::server::play::SUseItemOn;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_world::BlockStateId;
|
||||
use pumpkin_world::block::BlockDirection;
|
||||
|
||||
use crate::{
|
||||
block::pumpkin_block::PumpkinBlock,
|
||||
world::{BlockFlags, World},
|
||||
};
|
||||
|
||||
use super::redstone::block_receives_redstone_power;
|
||||
|
||||
#[pumpkin_block("minecraft:note_block")]
|
||||
pub struct NoteBlock;
|
||||
|
||||
impl NoteBlock {
|
||||
pub async fn play_note(props: &NoteBlockLikeProperties, world: &World, pos: &BlockPos) {
|
||||
if !is_base_block(props.instrument)
|
||||
|| world.get_block_state(&pos.up()).await.unwrap().is_air()
|
||||
{
|
||||
world.add_synced_block_event(*pos, 0, 0).await;
|
||||
}
|
||||
}
|
||||
fn get_note_pitch(note: u16) -> f32 {
|
||||
2.0f64.powf((f64::from(note) - 12.0) / 12.0) as f32
|
||||
}
|
||||
|
||||
async fn get_state_with_instrument(
|
||||
world: &World,
|
||||
pos: &BlockPos,
|
||||
state: BlockStateId,
|
||||
block: &Block,
|
||||
) -> BlockStateId {
|
||||
let upper_instrument = Instrument::from_value(
|
||||
&world
|
||||
.get_block_state(&pos.up())
|
||||
.await
|
||||
.unwrap()
|
||||
.instrument
|
||||
.to_lowercase(),
|
||||
);
|
||||
let mut note_props = NoteBlockLikeProperties::from_state_id(state, block);
|
||||
if !is_base_block(upper_instrument) {
|
||||
note_props.instrument = upper_instrument;
|
||||
return note_props.to_state_id(block);
|
||||
}
|
||||
let below_instrument = Instrument::from_value(
|
||||
&world
|
||||
.get_block_state(&pos.down())
|
||||
.await
|
||||
.unwrap()
|
||||
.instrument
|
||||
.to_lowercase(),
|
||||
);
|
||||
let below_instrument = if is_base_block(below_instrument) {
|
||||
below_instrument
|
||||
} else {
|
||||
Instrument::Harp
|
||||
};
|
||||
note_props.instrument = below_instrument;
|
||||
note_props.to_state_id(block)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl PumpkinBlock for NoteBlock {
|
||||
async fn on_neighbor_update(
|
||||
&self,
|
||||
world: &Arc<World>,
|
||||
block: &Block,
|
||||
pos: &BlockPos,
|
||||
_source_block: &Block,
|
||||
_notify: bool,
|
||||
) {
|
||||
let block_state = world.get_block_state(pos).await.unwrap();
|
||||
let mut note_props = NoteBlockLikeProperties::from_state_id(block_state.id, block);
|
||||
let powered = block_receives_redstone_power(world, pos).await;
|
||||
// check if powered state changed
|
||||
if note_props.powered != powered {
|
||||
if powered {
|
||||
Self::play_note(¬e_props, world, pos).await;
|
||||
}
|
||||
note_props.powered = powered;
|
||||
world
|
||||
.set_block_state(pos, note_props.to_state_id(block), BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn normal_use(
|
||||
&self,
|
||||
block: &Block,
|
||||
_player: &Player,
|
||||
pos: BlockPos,
|
||||
_server: &Server,
|
||||
world: &Arc<World>,
|
||||
) {
|
||||
let block_state = world.get_block_state(&pos).await.unwrap();
|
||||
let mut note_props = NoteBlockLikeProperties::from_state_id(block_state.id, block);
|
||||
let next_index = note_props.note.to_index() + 1;
|
||||
// Increment and check if max
|
||||
note_props.note = if next_index >= Integer0To24::variant_count() {
|
||||
Integer0To24::from_index(0)
|
||||
} else {
|
||||
Integer0To24::from_index(next_index)
|
||||
};
|
||||
world
|
||||
.set_block_state(&pos, note_props.to_state_id(block), BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
Self::play_note(¬e_props, world, &pos).await;
|
||||
}
|
||||
|
||||
async fn use_with_item(
|
||||
&self,
|
||||
_block: &Block,
|
||||
_player: &Player,
|
||||
_location: BlockPos,
|
||||
_item: &Item,
|
||||
_server: &Server,
|
||||
_world: &Arc<World>,
|
||||
) -> BlockActionResult {
|
||||
BlockActionResult::Continue
|
||||
}
|
||||
|
||||
async fn on_synced_block_event(
|
||||
&self,
|
||||
block: &Block,
|
||||
world: &Arc<World>,
|
||||
pos: &BlockPos,
|
||||
_type: u8,
|
||||
_data: u8,
|
||||
) -> bool {
|
||||
let block_state = world.get_block_state(pos).await.unwrap();
|
||||
let note_props = NoteBlockLikeProperties::from_state_id(block_state.id, block);
|
||||
let instrument = note_props.instrument;
|
||||
let pitch = if is_base_block(instrument) {
|
||||
// checks if can be pitched
|
||||
Self::get_note_pitch(note_props.note.to_index())
|
||||
} else {
|
||||
1.0 // default pitch
|
||||
};
|
||||
// check hasCustomSound
|
||||
world
|
||||
.play_sound_raw(
|
||||
convert_instrument_to_sound(instrument) as u16,
|
||||
SoundCategory::Records,
|
||||
&pos.to_f64(),
|
||||
3.0,
|
||||
pitch,
|
||||
)
|
||||
.await;
|
||||
true
|
||||
}
|
||||
|
||||
async fn on_place(
|
||||
&self,
|
||||
_server: &Server,
|
||||
world: &World,
|
||||
_player: &Player,
|
||||
block: &Block,
|
||||
pos: &BlockPos,
|
||||
_face: BlockDirection,
|
||||
_replacing: BlockIsReplacing,
|
||||
_use_item_on: &SUseItemOn,
|
||||
) -> BlockStateId {
|
||||
Self::get_state_with_instrument(world, pos, Block::NOTE_BLOCK.default_state_id, block).await
|
||||
}
|
||||
|
||||
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 {
|
||||
if direction.to_axis() == Axis::Y {
|
||||
return Self::get_state_with_instrument(world, pos, state, block).await;
|
||||
}
|
||||
state
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_instrument_to_sound(instrument: Instrument) -> Sound {
|
||||
match instrument {
|
||||
Instrument::Harp => Sound::BlockNoteBlockHarp,
|
||||
Instrument::Basedrum => Sound::BlockNoteBlockBasedrum,
|
||||
Instrument::Snare => Sound::BlockNoteBlockSnare,
|
||||
Instrument::Hat => Sound::BlockNoteBlockHat,
|
||||
Instrument::Bass => Sound::BlockNoteBlockBass,
|
||||
Instrument::Flute => Sound::BlockNoteBlockFlute,
|
||||
Instrument::Bell => Sound::BlockNoteBlockBell,
|
||||
Instrument::Guitar => Sound::BlockNoteBlockGuitar,
|
||||
Instrument::Chime => Sound::BlockNoteBlockChime,
|
||||
Instrument::Xylophone => Sound::BlockNoteBlockXylophone,
|
||||
Instrument::IronXylophone => Sound::BlockNoteBlockIronXylophone,
|
||||
Instrument::CowBell => Sound::BlockNoteBlockCowBell,
|
||||
Instrument::Didgeridoo => Sound::BlockNoteBlockDidgeridoo,
|
||||
Instrument::Bit => Sound::BlockNoteBlockBit,
|
||||
Instrument::Banjo => Sound::BlockNoteBlockBanjo,
|
||||
Instrument::Pling => Sound::BlockNoteBlockPling,
|
||||
Instrument::Zombie => Sound::BlockNoteBlockImitateZombie,
|
||||
Instrument::Skeleton => Sound::BlockNoteBlockImitateSkeleton,
|
||||
Instrument::Creeper => Sound::BlockNoteBlockImitateCreeper,
|
||||
Instrument::Dragon => Sound::BlockNoteBlockImitateEnderDragon,
|
||||
Instrument::WitherSkeleton => Sound::BlockNoteBlockImitateWitherSkeleton,
|
||||
Instrument::Piglin => Sound::BlockNoteBlockImitatePiglin,
|
||||
Instrument::CustomHead => Sound::UiButtonClick,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_base_block(instrument: Instrument) -> bool {
|
||||
matches!(
|
||||
instrument,
|
||||
Instrument::Harp
|
||||
| Instrument::Basedrum
|
||||
| Instrument::Snare
|
||||
| Instrument::Hat
|
||||
| Instrument::Bass
|
||||
| Instrument::Flute
|
||||
| Instrument::Bell
|
||||
| Instrument::Guitar
|
||||
| Instrument::Chime
|
||||
| Instrument::Xylophone
|
||||
| Instrument::IronXylophone
|
||||
| Instrument::CowBell
|
||||
| Instrument::Didgeridoo
|
||||
| Instrument::Bit
|
||||
| Instrument::Banjo
|
||||
)
|
||||
}
|
||||
@ -12,6 +12,7 @@ use blocks::glass_panes::GlassPaneBlock;
|
||||
use blocks::iron_bars::IronBarsBlock;
|
||||
use blocks::logs::LogBlock;
|
||||
use blocks::nether_portal::NetherPortalBlock;
|
||||
use blocks::note::NoteBlock;
|
||||
use blocks::redstone::buttons::ButtonBlock;
|
||||
use blocks::redstone::observer::ObserverBlock;
|
||||
use blocks::redstone::piston::PistonBlock;
|
||||
@ -88,6 +89,7 @@ pub fn default_registry() -> Arc<BlockRegistry> {
|
||||
manager.register(TorchBlock);
|
||||
manager.register(WallBlock);
|
||||
manager.register(NetherPortalBlock);
|
||||
manager.register(NoteBlock);
|
||||
|
||||
// Fire
|
||||
manager.register(SoulFireBlock);
|
||||
@ -118,6 +120,12 @@ pub fn default_registry() -> Arc<BlockRegistry> {
|
||||
Arc::new(manager)
|
||||
}
|
||||
|
||||
pub struct BlockEvent {
|
||||
pub pos: BlockPos,
|
||||
pub r#type: u8,
|
||||
pub data: u8,
|
||||
}
|
||||
|
||||
pub async fn drop_loot(
|
||||
world: &Arc<World>,
|
||||
block: &Block,
|
||||
|
||||
@ -36,12 +36,6 @@ pub trait PumpkinBlock: Send + Sync {
|
||||
) {
|
||||
}
|
||||
|
||||
fn should_drop_items_on_explosion(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
async fn explode(&self, _block: &Block, _world: &Arc<World>, _location: BlockPos) {}
|
||||
|
||||
async fn use_with_item(
|
||||
&self,
|
||||
_block: &Block,
|
||||
@ -54,6 +48,26 @@ pub trait PumpkinBlock: Send + Sync {
|
||||
BlockActionResult::Continue
|
||||
}
|
||||
|
||||
fn should_drop_items_on_explosion(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
async fn explode(&self, _block: &Block, _world: &Arc<World>, _location: BlockPos) {}
|
||||
|
||||
/// Handles the block event, which is an event specific to a block with an integer ID and data.
|
||||
///
|
||||
/// returns whether the event was handled successfully
|
||||
async fn on_synced_block_event(
|
||||
&self,
|
||||
_block: &Block,
|
||||
_world: &Arc<World>,
|
||||
_pos: &BlockPos,
|
||||
_type: u8,
|
||||
_data: u8,
|
||||
) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
/// getPlacementState in source code
|
||||
async fn on_place(
|
||||
|
||||
@ -37,6 +37,23 @@ impl BlockRegistry {
|
||||
self.fluids.insert(fluid.names(), Arc::new(fluid));
|
||||
}
|
||||
|
||||
pub async fn on_synced_block_event(
|
||||
&self,
|
||||
block: &Block,
|
||||
world: &Arc<World>,
|
||||
pos: &BlockPos,
|
||||
r#type: u8,
|
||||
data: u8,
|
||||
) -> bool {
|
||||
let pumpkin_block = self.get_pumpkin_block(block);
|
||||
if let Some(pumpkin_block) = pumpkin_block {
|
||||
return pumpkin_block
|
||||
.on_synced_block_event(block, world, pos, r#type, data)
|
||||
.await;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub async fn on_use(
|
||||
&self,
|
||||
block: &Block,
|
||||
|
||||
@ -9,6 +9,7 @@ pub mod explosion;
|
||||
pub mod portal;
|
||||
pub mod time;
|
||||
|
||||
use crate::block::BlockEvent;
|
||||
use crate::{
|
||||
PLUGIN_MANAGER,
|
||||
block::{self, registry::BlockRegistry},
|
||||
@ -43,7 +44,7 @@ use pumpkin_data::{
|
||||
use pumpkin_macros::send_cancellable;
|
||||
use pumpkin_nbt::to_bytes_unnamed;
|
||||
use pumpkin_protocol::client::play::{
|
||||
CRemoveMobEffect, CSetEntityMetadata, MetaDataType, Metadata,
|
||||
CBlockEvent, CRemoveMobEffect, CSetEntityMetadata, MetaDataType, Metadata,
|
||||
};
|
||||
use pumpkin_protocol::codec::identifier::Identifier;
|
||||
use pumpkin_protocol::ser::serializer::Serializer;
|
||||
@ -166,6 +167,7 @@ pub struct World {
|
||||
pub weather: Mutex<Weather>,
|
||||
/// Block Behaviour
|
||||
pub block_registry: Arc<BlockRegistry>,
|
||||
synced_block_event_queue: Mutex<Vec<BlockEvent>>,
|
||||
/// A map of unsent block changes, keyed by block position.
|
||||
unsent_block_changes: Mutex<HashMap<BlockPos, u16>>,
|
||||
// TODO: entities
|
||||
@ -193,6 +195,7 @@ impl World {
|
||||
weather: Mutex::new(Weather::new()),
|
||||
block_registry,
|
||||
sea_level: generation_settings.sea_level,
|
||||
synced_block_event_queue: Mutex::new(Vec::new()),
|
||||
unsent_block_changes: Mutex::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
@ -216,6 +219,35 @@ impl World {
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn add_synced_block_event(&self, pos: BlockPos, r#type: u8, data: u8) {
|
||||
let mut queue = self.synced_block_event_queue.lock().await;
|
||||
queue.push(BlockEvent { pos, r#type, data });
|
||||
}
|
||||
|
||||
async fn flush_synced_block_events(self: &Arc<Self>) {
|
||||
let mut queue = self.synced_block_event_queue.lock().await;
|
||||
let events: Vec<BlockEvent> = queue.drain(..).collect();
|
||||
// lets drop it here, allows us to add new things
|
||||
drop(queue);
|
||||
for event in events {
|
||||
let block = self.get_block(&event.pos).await.unwrap(); // TODO
|
||||
if !self
|
||||
.block_registry
|
||||
.on_synced_block_event(&block, self, &event.pos, event.r#type, event.data)
|
||||
.await
|
||||
{
|
||||
continue;
|
||||
}
|
||||
self.broadcast_packet_all(&CBlockEvent::new(
|
||||
event.pos,
|
||||
event.r#type,
|
||||
event.data,
|
||||
VarInt(i32::from(block.id)),
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Broadcasts a packet to all connected players within the world.
|
||||
///
|
||||
/// Sends the specified packet to every player currently logged in to the world.
|
||||
@ -364,6 +396,7 @@ impl World {
|
||||
|
||||
pub async fn tick(self: &Arc<Self>, server: &Server) {
|
||||
self.flush_block_updates().await;
|
||||
self.flush_synced_block_events().await;
|
||||
|
||||
// world ticks
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user