feat: add name tag logic

This commit is contained in:
Alexander Medvedev
2025-08-22 12:09:30 +02:00
parent 736f073b3c
commit c5c3ffc005
12 changed files with 267 additions and 12 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,7 @@ pub struct EntityType {
pub loot_table: Option<LootTableStruct>,
pub summonable: bool,
pub fire_immune: bool,
pub saveable: bool,
pub category: MobCategory,
pub can_spawn_far_from_player: bool,
pub dimension: [f32; 2],
@@ -106,6 +107,7 @@ impl ToTokens for NamedEntityType<'_> {
MobCategory::MISC => quote! { MobCategory::MISC },
};
let saveable = entity.saveable;
let summonable = entity.summonable;
let fire_immune = entity.fire_immune;
let eye_height = entity.eye_height;
@@ -136,6 +138,7 @@ impl ToTokens for NamedEntityType<'_> {
max_health: #max_health,
attackable: #attackable,
mob: #mob,
saveable: #saveable,
limit_per_chunk: #limit_per_chunk,
summonable: #summonable,
fire_immune: #fire_immune,
@@ -192,6 +195,7 @@ pub(crate) fn build() -> TokenStream {
pub max_health: Option<f32>,
pub attackable: Option<bool>,
pub mob: bool,
pub saveable: bool,
pub limit_per_chunk: i32,
pub summonable: bool,
pub fire_immune: bool,

View File

@@ -151,7 +151,13 @@ impl DataComponentImpl for DamageImpl {
#[derive(Clone, Debug, Hash, PartialEq)]
pub struct UnbreakableImpl;
#[derive(Clone, Debug, Hash, PartialEq)]
pub struct CustomNameImpl;
pub struct CustomNameImpl {
// TODO make TextComponent const
pub name: &'static str,
}
impl DataComponentImpl for CustomNameImpl {
default_impl!(CustomName);
}
#[derive(Clone, Debug, Hash, PartialEq)]
pub struct ItemNameImpl {
// TODO make TextComponent const

View File

@@ -405,6 +405,16 @@ impl Entity {
self.send_velocity().await;
}
/// Sets a custom name for the entity, typically used with nametags
pub async fn set_custon_name(&self, name: TextComponent) {
self.send_meta_data(&[Metadata::new(
2,
MetaDataType::OptionalTextComponent,
Some(name),
)])
.await;
}
pub async fn send_velocity(&self) {
let velocity = self.velocity.load();
self.world

View File

@@ -91,4 +91,8 @@ impl EntityBase for ThrownItemEntity {
fn as_nbt_storage(&self) -> &dyn NBTStorage {
self
}
fn get_gravity(&self) -> f64 {
0.03
}
}

View File

@@ -85,7 +85,7 @@ impl ItemBehaviour for HoeItem {
Uuid::new_v4(),
world.clone(),
location,
&EntityType::SNOWBALL,
&EntityType::ITEM,
false,
);
// TODO: Merge stacks together

View File

@@ -11,6 +11,7 @@ pub mod ignite;
pub mod ink_sac;
pub mod mace;
pub mod minecart;
pub mod name_tag;
pub mod shovel;
pub mod snowball;
pub mod swords;
@@ -18,6 +19,7 @@ pub mod trident;
use crate::item::items::end_crystal::EndCrystalItem;
use crate::item::items::minecart::MinecartItem;
use crate::item::items::name_tag::NameTagItem;
use super::registry::ItemRegistry;
use axe::AxeItem;
@@ -56,6 +58,7 @@ pub fn default_registry() -> Arc<ItemRegistry> {
manager.register(EndCrystalItem);
manager.register(MinecartItem);
manager.register(HoneyCombItem);
manager.register(NameTagItem);
manager.register(EnderEyeItem);
manager.register(FireChargeItem);
manager.register(DyeItem);

View File

@@ -0,0 +1,40 @@
use std::sync::Arc;
use crate::entity::EntityBase;
use crate::entity::player::Player;
use crate::item::{ItemBehaviour, ItemMetadata};
use async_trait::async_trait;
use pumpkin_data::data_component_impl::CustomNameImpl;
use pumpkin_data::item::Item;
use pumpkin_util::text::TextComponent;
use pumpkin_world::item::ItemStack;
pub struct NameTagItem;
impl ItemMetadata for NameTagItem {
fn ids() -> Box<[u16]> {
[Item::NAME_TAG.id].into()
}
}
#[async_trait]
impl ItemBehaviour for NameTagItem {
async fn use_on_entity(
&self,
item: &mut ItemStack,
player: &Player,
entity: Arc<dyn EntityBase>,
) {
let entity = entity.get_entity();
if entity.entity_type.saveable
&& let Some(name) = item.get_data_component::<CustomNameImpl>()
{
// TODO
entity.set_custon_name(TextComponent::text(name.name)).await;
item.decrement_unless_creative(player.gamemode.load(), 1);
}
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}

View File

@@ -2,7 +2,9 @@ pub mod items;
pub mod registry;
use std::any::Any;
use std::sync::Arc;
use crate::entity::EntityBase;
use crate::entity::player::Player;
use crate::server::Server;
use async_trait::async_trait;
@@ -32,6 +34,14 @@ pub trait ItemBehaviour: Send + Sync {
) {
}
async fn use_on_entity(
&self,
_item: &mut ItemStack,
_player: &Player,
_entity: Arc<dyn EntityBase>,
) {
}
fn can_mine(&self, _player: &Player) -> bool {
true
}

View File

@@ -1,3 +1,4 @@
use crate::entity::EntityBase;
use crate::entity::player::Player;
use crate::server::Server;
use pumpkin_data::Block;
@@ -48,6 +49,18 @@ impl ItemRegistry {
}
}
pub async fn use_on_entity(
&self,
stack: &mut ItemStack,
player: &Player,
entity: Arc<dyn EntityBase>,
) {
let pumpkin_item = self.get_pumpkin_item(stack.item);
if let Some(pumpkin_item) = pumpkin_item {
pumpkin_item.use_on_entity(stack, player, entity).await;
}
}
pub fn can_mine(&self, item: &Item, player: &Player) -> bool {
let pumpkin_block = self.get_pumpkin_item(item);
if let Some(pumpkin_block) = pumpkin_block {

View File

@@ -571,7 +571,7 @@ impl JavaClient {
.await;
}
SInteract::PACKET_ID => {
self.handle_interact(player, SInteract::read(payload)?)
self.handle_interact(player, SInteract::read(payload)?, server)
.await;
}
SKeepAlive::PACKET_ID => {

View File

@@ -1065,15 +1065,21 @@ impl JavaClient {
}
}
pub async fn handle_interact(&self, player: &Player, interact: SInteract) {
pub async fn handle_interact(
&self,
player: &Player,
interact: SInteract,
server: &Arc<Server>,
) {
if !player.has_client_loaded() {
return;
}
let entity_id = interact.entity_id;
let sneaking = interact.sneaking;
let entity = &player.living_entity.entity;
if entity.sneaking.load(Ordering::Relaxed) != sneaking {
entity.set_sneaking(sneaking).await;
let player_entity = &player.living_entity.entity;
if player_entity.sneaking.load(Ordering::Relaxed) != sneaking {
player_entity.set_sneaking(sneaking).await;
}
let Ok(action) = ActionType::try_from(interact.r#type.0) else {
self.kick(TextComponent::text("Invalid action type")).await;
@@ -1082,7 +1088,6 @@ impl JavaClient {
match action {
ActionType::Attack => {
let entity_id = interact.entity_id;
let config = &advanced_config().pvp;
// TODO: do validation and stuff
if !config.enabled {
@@ -1091,7 +1096,7 @@ impl JavaClient {
// TODO: set as camera entity when spectator
let world = &entity.world;
let world = &player_entity.world;
let player_victim = world.get_player_by_id(entity_id.0).await;
if entity_id.0 == player.entity_id() {
// This can't be triggered from a non-modded client.
@@ -1137,7 +1142,16 @@ impl JavaClient {
}
}
ActionType::Interact | ActionType::InteractAt => {
log::debug!("todo");
// TODO: split this up
let entity = player.world().get_player_by_id(entity_id.0).await;
if let Some(entity) = entity {
let held = player.inventory.held_item();
let mut stack = held.lock().await;
server
.item_registry
.use_on_entity(&mut stack, player, entity)
.await;
}
}
}
}
@@ -1425,14 +1439,14 @@ impl JavaClient {
return Ok(());
}
}
let mut stack = item.lock().await;
if item.lock().await.is_empty() {
if stack.is_empty() {
// TODO item cool down
// If the hand is empty we stop here
return Ok(());
}
let mut stack = item.lock().await;
server
.item_registry
.use_on_block(&mut stack, player, position, face, block, server)