Skip to content

Commit

Permalink
Add: Interactive blocks (#333)
Browse files Browse the repository at this point in the history
* WIP interactive blocks

* Added jukebox and crafting table

* Making feature more dynamic allowing every block action to be registered here

* Renamed interactive_block_manager to block_manager

* Simplifying register functions

* Added on placed

* Fix master merge

* Added pumpkin_block macro

* Removed junk

* Added the option to block placing in on_use_with_item

* Fixed inventory lock

* Adding on_broken and implementing it for the jukebox

* Fixed merge
  • Loading branch information
leobeg authored Dec 7, 2024
1 parent cd6ac14 commit b82a4e1
Show file tree
Hide file tree
Showing 17 changed files with 440 additions and 35 deletions.
26 changes: 26 additions & 0 deletions pumpkin-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,32 @@ pub fn server_packet(input: TokenStream, item: TokenStream) -> TokenStream {
gen.into()
}

#[proc_macro_attribute]
pub fn pumpkin_block(input: TokenStream, item: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(item.clone()).unwrap();
let name = &ast.ident;
let (impl_generics, ty_generics, _) = ast.generics.split_for_impl();

let input_string = input.to_string();
let packet_name = input_string.trim_matches('"');
let packet_name_split: Vec<&str> = packet_name.split(":").collect();

let namespace = packet_name_split[0];
let id = packet_name_split[1];

let item: proc_macro2::TokenStream = item.into();

let gen = quote! {
#item
impl #impl_generics crate::block::pumpkin_block::BlockMetadata for #name #ty_generics {
const NAMESPACE: &'static str = #namespace;
const ID: &'static str = #id;
}
};

gen.into()
}

mod screen;
#[proc_macro]
pub fn screen(item: TokenStream) -> TokenStream {
Expand Down
28 changes: 28 additions & 0 deletions pumpkin-protocol/src/client/play/c_level_event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use pumpkin_core::math::position::WorldPosition;
use pumpkin_macros::client_packet;
use serde::Serialize;

#[derive(Serialize)]
#[client_packet("play:level_event")]
pub struct CLevelEvent {
event: i32,
location: WorldPosition,
data: i32,
disable_relative_volume: bool,
}

impl CLevelEvent {
pub fn new(
event: i32,
location: WorldPosition,
data: i32,
disable_relative_volume: bool,
) -> Self {
Self {
event,
location,
data,
disable_relative_volume,
}
}
}
2 changes: 2 additions & 0 deletions pumpkin-protocol/src/client/play/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ mod c_head_rot;
mod c_hurt_animation;
mod c_initialize_world_border;
mod c_keep_alive;
mod c_level_event;
mod c_login;
mod c_open_screen;
mod c_particle;
Expand Down Expand Up @@ -94,6 +95,7 @@ pub use c_head_rot::*;
pub use c_hurt_animation::*;
pub use c_initialize_world_border::*;
pub use c_keep_alive::*;
pub use c_level_event::*;
pub use c_login::*;
pub use c_open_screen::*;
pub use c_particle::*;
Expand Down
7 changes: 6 additions & 1 deletion pumpkin-registry/src/jukebox_song.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JukeboxSong {
sound_event: String,
// description: TextComponent<'static>,
description: Description,
length_in_seconds: f32,
comparator_output: u32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Description {
translate: String,
}
28 changes: 14 additions & 14 deletions pumpkin-registry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub struct SyncedRegistry {
damage_type: IndexMap<String, DamageType>,
banner_pattern: IndexMap<String, BannerPattern>,
enchantment: IndexMap<String, Enchantment>,
jukebox_song: IndexMap<String, JukeboxSong>,
pub jukebox_song: IndexMap<String, JukeboxSong>,
instrument: IndexMap<String, Instrument>,
}

Expand Down Expand Up @@ -225,18 +225,18 @@ impl Registry {
// registry_entries,
// };

// let registry_entries = SYNCED_REGISTRIES
// .jukebox_song
// .iter()
// .map(|s| RegistryEntry {
// entry_id: s.0,
// data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(),
// })
// .collect();
// let jukebox_song = Registry {
// registry_id: "minecraft:jukebox_song".to_string(),
// registry_entries,
// };
let registry_entries = SYNCED_REGISTRIES
.jukebox_song
.iter()
.map(|s| RegistryEntry {
entry_id: s.0,
data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(),
})
.collect();
let jukebox_song = Registry {
registry_id: "minecraft:jukebox_song".to_string(),
registry_entries,
};

// let registry_entries = SYNCED_REGISTRIES
// .instrument
Expand All @@ -262,7 +262,7 @@ impl Registry {
damage_type,
banner_pattern,
// enchantment,
// jukebox_song,
jukebox_song,
// instrument,
]
}
Expand Down
18 changes: 17 additions & 1 deletion pumpkin-world/src/item/item_registry.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::{collections::HashMap, sync::LazyLock};
use std::collections::HashMap;
use std::sync::LazyLock;

use serde::Deserialize;

Expand All @@ -12,6 +13,14 @@ pub fn get_item(name: &str) -> Option<&Item> {
ITEMS.get(&name.replace("minecraft:", ""))
}

pub fn get_item_by_id<'a>(id: u16) -> Option<&'a Item> {
let item = ITEMS.iter().find(|item| item.1.id == id);
if let Some(item) = item {
return Some(item.1);
}
None
}

#[derive(Deserialize, Clone, Debug)]
pub struct Item {
pub id: u16,
Expand All @@ -22,4 +31,11 @@ pub struct Item {
pub struct ItemComponents {
#[serde(rename = "minecraft:max_stack_size")]
pub max_stack_size: u8,
#[serde(rename = "minecraft:jukebox_playable")]
pub jukebox_playable: Option<JukeboxPlayable>,
}

#[derive(Deserialize, Clone, Debug)]
pub struct JukeboxPlayable {
pub song: String,
}
89 changes: 89 additions & 0 deletions pumpkin/src/block/block_manager.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock};
use crate::entity::player::Player;
use crate::server::Server;
use pumpkin_core::math::position::WorldPosition;
use pumpkin_world::block::block_registry::Block;
use pumpkin_world::item::item_registry::Item;
use std::collections::HashMap;
use std::sync::Arc;

pub enum BlockActionResult {
/// Allow other actions to be executed
Continue,
/// Block other actions
Consume,
}

#[derive(Default)]
pub struct BlockManager {
blocks: HashMap<String, Arc<dyn PumpkinBlock>>,
}

impl BlockManager {
pub fn register<T: PumpkinBlock + BlockMetadata + 'static>(&mut self, block: T) {
self.blocks
.insert(block.name().to_string(), Arc::new(block));
}

pub async fn on_use(
&self,
block: &Block,
player: &Player,
location: WorldPosition,
server: &Server,
) {
let pumpkin_block = self.get_pumpkin_block(block);
if let Some(pumpkin_block) = pumpkin_block {
pumpkin_block.on_use(player, location, server).await;
}
}

pub async fn on_use_with_item(
&self,
block: &Block,
player: &Player,
location: WorldPosition,
item: &Item,
server: &Server,
) -> BlockActionResult {
let pumpkin_block = self.get_pumpkin_block(block);
if let Some(pumpkin_block) = pumpkin_block {
return pumpkin_block
.on_use_with_item(player, location, item, server)
.await;
}
BlockActionResult::Continue
}

pub async fn on_placed(
&self,
block: &Block,
player: &Player,
location: WorldPosition,
server: &Server,
) {
let pumpkin_block = self.get_pumpkin_block(block);
if let Some(pumpkin_block) = pumpkin_block {
pumpkin_block.on_placed(player, location, server).await;
}
}

pub async fn on_broken(
&self,
block: &Block,
player: &Player,
location: WorldPosition,
server: &Server,
) {
let pumpkin_block = self.get_pumpkin_block(block);
if let Some(pumpkin_block) = pumpkin_block {
pumpkin_block.on_broken(player, location, server).await;
}
}

#[must_use]
pub fn get_pumpkin_block(&self, block: &Block) -> Option<&Arc<dyn PumpkinBlock>> {
self.blocks
.get(format!("minecraft:{}", block.name).as_str())
}
}
50 changes: 50 additions & 0 deletions pumpkin/src/block/blocks/crafting_table.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use crate::block::block_manager::BlockActionResult;
use crate::block::pumpkin_block::PumpkinBlock;
use crate::entity::player::Player;
use crate::server::Server;
use async_trait::async_trait;
use pumpkin_core::math::position::WorldPosition;
use pumpkin_inventory::{CraftingTable, OpenContainer, WindowType};
use pumpkin_macros::pumpkin_block;
use pumpkin_world::item::item_registry::Item;

#[pumpkin_block("minecraft:crafting_table")]
pub struct CraftingTableBlock;

#[async_trait]
impl PumpkinBlock for CraftingTableBlock {
async fn on_use<'a>(&self, player: &Player, _location: WorldPosition, server: &Server) {
self.open_crafting_screen(player, server).await;
}

async fn on_use_with_item<'a>(
&self,
player: &Player,
_location: WorldPosition,
_item: &Item,
server: &Server,
) -> BlockActionResult {
self.open_crafting_screen(player, server).await;
BlockActionResult::Consume
}
}

impl CraftingTableBlock {
pub async fn open_crafting_screen(&self, player: &Player, server: &Server) {
//TODO: Adjust /craft command to real crafting table
let entity_id = player.entity_id();
player.open_container.store(Some(1));
{
let mut open_containers = server.open_containers.write().await;
if let Some(ender_chest) = open_containers.get_mut(&1) {
ender_chest.add_player(entity_id);
} else {
let open_container = OpenContainer::new_empty_container::<CraftingTable>(entity_id);
open_containers.insert(1, open_container);
}
}
player
.open_container(server, WindowType::CraftingTable)
.await;
}
}
58 changes: 58 additions & 0 deletions pumpkin/src/block/blocks/jukebox.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use crate::block::block_manager::BlockActionResult;
use crate::block::pumpkin_block::PumpkinBlock;
use crate::entity::player::Player;
use crate::server::Server;
use async_trait::async_trait;
use pumpkin_core::math::position::WorldPosition;
use pumpkin_macros::pumpkin_block;
use pumpkin_registry::SYNCED_REGISTRIES;
use pumpkin_world::item::item_registry::Item;

#[pumpkin_block("minecraft:jukebox")]
pub struct JukeboxBlock;

#[async_trait]
impl PumpkinBlock for JukeboxBlock {
async fn on_use<'a>(&self, player: &Player, location: WorldPosition, _server: &Server) {
// For now just stop the music at this position
let world = &player.living_entity.entity.world;

world.stop_record(location).await;
}

async fn on_use_with_item<'a>(
&self,
player: &Player,
location: WorldPosition,
item: &Item,
_server: &Server,
) -> BlockActionResult {
let world = &player.living_entity.entity.world;

let Some(jukebox_playable) = &item.components.jukebox_playable else {
return BlockActionResult::Continue;
};

let Some(song) = jukebox_playable.song.split(':').nth(1) else {
return BlockActionResult::Continue;
};

let Some(jukebox_song) = SYNCED_REGISTRIES.jukebox_song.get_index_of(song) else {
log::error!("Jukebox playable song not registered!");
return BlockActionResult::Continue;
};

//TODO: Update block state and block nbt

world.play_record(jukebox_song as i32, location).await;

BlockActionResult::Consume
}

async fn on_broken<'a>(&self, player: &Player, location: WorldPosition, _server: &Server) {
// For now just stop the music at this position
let world = &player.living_entity.entity.world;

world.stop_record(location).await;
}
}
2 changes: 2 additions & 0 deletions pumpkin/src/block/blocks/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub(crate) mod crafting_table;
pub(crate) mod jukebox;
18 changes: 18 additions & 0 deletions pumpkin/src/block/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use crate::block::block_manager::BlockManager;
use crate::block::blocks::crafting_table::CraftingTableBlock;
use crate::block::blocks::jukebox::JukeboxBlock;
use std::sync::Arc;

pub mod block_manager;
mod blocks;
pub mod pumpkin_block;

#[must_use]
pub fn default_block_manager() -> Arc<BlockManager> {
let mut manager = BlockManager::default();

manager.register(JukeboxBlock);
manager.register(CraftingTableBlock);

Arc::new(manager)
}
Loading

0 comments on commit b82a4e1

Please sign in to comment.