Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interactive blocks #333

Merged
merged 15 commits into from
Dec 7, 2024
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 @@ -219,18 +219,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 @@ -256,7 +256,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