diff --git a/.gitignore b/.gitignore index 00687fe4f..8eb9a1b20 100644 --- a/.gitignore +++ b/.gitignore @@ -157,3 +157,4 @@ version_history.json world_nether/ world_the_end/ patchbukkit/ +minecraft-sources*/ diff --git a/crates/pumpkin-data/src/data_component_impl/basic.rs b/crates/pumpkin-data/src/data_component_impl/basic.rs index 5c22fb95d..53e6db678 100644 --- a/crates/pumpkin-data/src/data_component_impl/basic.rs +++ b/crates/pumpkin-data/src/data_component_impl/basic.rs @@ -352,18 +352,33 @@ impl DataComponentImpl for CustomModelDataImpl { #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct TooltipDisplayImpl; +impl TooltipDisplayImpl { + pub const fn read_data(_data: &NbtTag) -> Option { + Some(Self) + } +} impl DataComponentImpl for TooltipDisplayImpl { default_impl!(TooltipDisplay); } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct CreativeSlotLockImpl; +impl CreativeSlotLockImpl { + pub const fn read_data(_data: &NbtTag) -> Option { + Some(Self) + } +} impl DataComponentImpl for CreativeSlotLockImpl { default_impl!(CreativeSlotLock); } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct EnchantmentGlintOverrideImpl; +impl EnchantmentGlintOverrideImpl { + pub const fn read_data(_data: &NbtTag) -> Option { + Some(Self) + } +} impl DataComponentImpl for EnchantmentGlintOverrideImpl { default_impl!(EnchantmentGlintOverride); } @@ -431,18 +446,33 @@ impl DataComponentImpl for BaseColorImpl { #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct InstrumentImpl; +impl InstrumentImpl { + pub const fn read_data(_data: &NbtTag) -> Option { + Some(Self) + } +} impl DataComponentImpl for InstrumentImpl { default_impl!(Instrument); } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct ProvidesTrimMaterialImpl; +impl ProvidesTrimMaterialImpl { + pub const fn read_data(_data: &NbtTag) -> Option { + Some(Self) + } +} impl DataComponentImpl for ProvidesTrimMaterialImpl { default_impl!(ProvidesTrimMaterial); } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct ProvidesBannerPatternsImpl; +impl ProvidesBannerPatternsImpl { + pub const fn read_data(_data: &NbtTag) -> Option { + Some(Self) + } +} impl DataComponentImpl for ProvidesBannerPatternsImpl { default_impl!(ProvidesBannerPatterns); } @@ -495,6 +525,11 @@ impl DataComponentImpl for BannerPatternsImpl { #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct PotDecorationsImpl; +impl PotDecorationsImpl { + pub const fn read_data(_data: &NbtTag) -> Option { + Some(Self) + } +} impl DataComponentImpl for PotDecorationsImpl { default_impl!(PotDecorations); } @@ -522,6 +557,11 @@ impl DataComponentImpl for LockImpl { #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct BreakSoundImpl; +impl BreakSoundImpl { + pub const fn read_data(_data: &NbtTag) -> Option { + Some(Self) + } +} impl DataComponentImpl for BreakSoundImpl { default_impl!(BreakSound); } diff --git a/crates/pumpkin-data/src/data_component_impl/block_entity.rs b/crates/pumpkin-data/src/data_component_impl/block_entity.rs index d6844391f..9c1f3ffc3 100644 --- a/crates/pumpkin-data/src/data_component_impl/block_entity.rs +++ b/crates/pumpkin-data/src/data_component_impl/block_entity.rs @@ -26,12 +26,22 @@ impl DataComponentImpl for BlockEntityDataImpl { #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct EntityDataImpl; +impl EntityDataImpl { + pub const fn read_data(_data: &NbtTag) -> Option { + Some(Self) + } +} impl DataComponentImpl for EntityDataImpl { default_impl!(EntityData); } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct BucketEntityDataImpl; +impl BucketEntityDataImpl { + pub const fn read_data(_data: &NbtTag) -> Option { + Some(Self) + } +} impl DataComponentImpl for BucketEntityDataImpl { default_impl!(BucketEntityData); } @@ -148,6 +158,11 @@ impl DataComponentImpl for BlockStateImpl { #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct BeesImpl; +impl BeesImpl { + pub const fn read_data(_data: &NbtTag) -> Option { + Some(Self) + } +} impl DataComponentImpl for BeesImpl { default_impl!(Bees); } @@ -177,6 +192,11 @@ impl DataComponentImpl for ContainerLootImpl { #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct SulfurCubeContentImpl; +impl SulfurCubeContentImpl { + pub const fn read_data(_data: &NbtTag) -> Option { + Some(Self) + } +} impl DataComponentImpl for SulfurCubeContentImpl { default_impl!(SulfurCubeContent); } diff --git a/crates/pumpkin-data/src/data_component_impl/book.rs b/crates/pumpkin-data/src/data_component_impl/book.rs index 13af2b414..eef331119 100644 --- a/crates/pumpkin-data/src/data_component_impl/book.rs +++ b/crates/pumpkin-data/src/data_component_impl/book.rs @@ -90,6 +90,11 @@ impl DataComponentImpl for WrittenBookContentImpl { #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct DebugStickStateImpl; +impl DebugStickStateImpl { + pub const fn read_data(_data: &NbtTag) -> Option { + Some(Self) + } +} impl DataComponentImpl for DebugStickStateImpl { default_impl!(DebugStickState); } diff --git a/crates/pumpkin-data/src/data_component_impl/combat.rs b/crates/pumpkin-data/src/data_component_impl/combat.rs index c577df799..251c750fd 100644 --- a/crates/pumpkin-data/src/data_component_impl/combat.rs +++ b/crates/pumpkin-data/src/data_component_impl/combat.rs @@ -48,6 +48,13 @@ impl Hash for Modifier { pub struct AttributeModifiersImpl { pub attribute_modifiers: Cow<'static, [Modifier]>, } +impl AttributeModifiersImpl { + pub const fn read_data(_data: &NbtTag) -> Option { + Some(Self { + attribute_modifiers: Cow::Borrowed(&[]), + }) + } +} impl DataComponentImpl for AttributeModifiersImpl { default_impl!(AttributeModifiers); } @@ -742,12 +749,22 @@ impl DataComponentImpl for GliderImpl { #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct DeathProtectionImpl; +impl DeathProtectionImpl { + pub const fn read_data(_data: &NbtTag) -> Option { + Some(Self) + } +} impl DataComponentImpl for DeathProtectionImpl { default_impl!(DeathProtection); } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct BlocksAttacksImpl; +impl BlocksAttacksImpl { + pub const fn read_data(_data: &NbtTag) -> Option { + Some(Self) + } +} impl DataComponentImpl for BlocksAttacksImpl { default_impl!(BlocksAttacks); } @@ -1037,6 +1054,11 @@ impl DataComponentImpl for SwingAnimationImpl { #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct AdditionalTradeCostImpl; +impl AdditionalTradeCostImpl { + pub const fn read_data(_data: &NbtTag) -> Option { + Some(Self) + } +} impl DataComponentImpl for AdditionalTradeCostImpl { default_impl!(AdditionalTradeCost); } diff --git a/crates/pumpkin-data/src/data_component_impl/food.rs b/crates/pumpkin-data/src/data_component_impl/food.rs index b9c78f01b..14fedac4e 100644 --- a/crates/pumpkin-data/src/data_component_impl/food.rs +++ b/crates/pumpkin-data/src/data_component_impl/food.rs @@ -384,12 +384,22 @@ impl Hash for ConsumableImpl { #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct UseEffectsImpl; +impl UseEffectsImpl { + pub const fn read_data(_data: &NbtTag) -> Option { + Some(Self) + } +} impl DataComponentImpl for UseEffectsImpl { default_impl!(UseEffects); } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct UseRemainderImpl; +impl UseRemainderImpl { + pub const fn read_data(_data: &NbtTag) -> Option { + Some(Self) + } +} impl DataComponentImpl for UseRemainderImpl { default_impl!(UseRemainder); } diff --git a/crates/pumpkin-data/src/data_component_impl/mod.rs b/crates/pumpkin-data/src/data_component_impl/mod.rs index 21ffb3efb..efc3ceb8e 100644 --- a/crates/pumpkin-data/src/data_component_impl/mod.rs +++ b/crates/pumpkin-data/src/data_component_impl/mod.rs @@ -652,7 +652,41 @@ pub fn read_data(id: DataComponent, data: &NbtTag) -> Option Some(SwingAnimationImpl::read_data(data)?.to_dyn()), DataComponent::Rarity => Some(RarityImpl::read_data(data)?.to_dyn()), DataComponent::BannerPatterns => Some(BannerPatternsImpl::read_data(data)?.to_dyn()), - _ => None, + DataComponent::UseEffects => Some(UseEffectsImpl::read_data(data)?.to_dyn()), + DataComponent::UseRemainder => Some(UseRemainderImpl::read_data(data)?.to_dyn()), + DataComponent::Weapon => Some(WeaponImpl::read_data(data)?.to_dyn()), + DataComponent::TooltipDisplay => Some(TooltipDisplayImpl::read_data(data)?.to_dyn()), + DataComponent::CreativeSlotLock => Some(CreativeSlotLockImpl::read_data(data)?.to_dyn()), + DataComponent::EnchantmentGlintOverride => { + Some(EnchantmentGlintOverrideImpl::read_data(data)?.to_dyn()) + } + DataComponent::DeathProtection => Some(DeathProtectionImpl::read_data(data)?.to_dyn()), + DataComponent::BlocksAttacks => Some(BlocksAttacksImpl::read_data(data)?.to_dyn()), + DataComponent::AdditionalTradeCost => { + Some(AdditionalTradeCostImpl::read_data(data)?.to_dyn()) + } + DataComponent::Dye => Some(DyeImpl::read_data(data)?.to_dyn()), + DataComponent::MapColor => Some(MapColorImpl::read_data(data)?.to_dyn()), + DataComponent::MapDecorations => Some(MapDecorationsImpl::read_data(data)?.to_dyn()), + DataComponent::DebugStickState => Some(DebugStickStateImpl::read_data(data)?.to_dyn()), + DataComponent::EntityData => Some(EntityDataImpl::read_data(data)?.to_dyn()), + DataComponent::BucketEntityData => Some(BucketEntityDataImpl::read_data(data)?.to_dyn()), + DataComponent::Instrument => Some(InstrumentImpl::read_data(data)?.to_dyn()), + DataComponent::ProvidesTrimMaterial => { + Some(ProvidesTrimMaterialImpl::read_data(data)?.to_dyn()) + } + DataComponent::JukeboxPlayable => Some(JukeboxPlayableImpl::read_data(data)?.to_dyn()), + DataComponent::ProvidesBannerPatterns => { + Some(ProvidesBannerPatternsImpl::read_data(data)?.to_dyn()) + } + DataComponent::Recipes => Some(RecipesImpl::read_data(data)?.to_dyn()), + DataComponent::PotDecorations => Some(PotDecorationsImpl::read_data(data)?.to_dyn()), + DataComponent::Bees => Some(BeesImpl::read_data(data)?.to_dyn()), + DataComponent::SulfurCubeContent => Some(SulfurCubeContentImpl::read_data(data)?.to_dyn()), + DataComponent::BreakSound => Some(BreakSoundImpl::read_data(data)?.to_dyn()), + DataComponent::AttributeModifiers => { + Some(AttributeModifiersImpl::read_data(data)?.to_dyn()) + } } } diff --git a/crates/pumpkin-data/src/data_component_impl/utility.rs b/crates/pumpkin-data/src/data_component_impl/utility.rs index 62b4c451e..27360542e 100644 --- a/crates/pumpkin-data/src/data_component_impl/utility.rs +++ b/crates/pumpkin-data/src/data_component_impl/utility.rs @@ -6,6 +6,11 @@ use pumpkin_nbt::tag::NbtTag; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct DyeImpl; +impl DyeImpl { + pub const fn read_data(_data: &NbtTag) -> Option { + Some(Self) + } +} impl DataComponentImpl for DyeImpl { default_impl!(Dye); } @@ -31,6 +36,11 @@ impl DataComponentImpl for DyedColorImpl { #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct MapColorImpl; +impl MapColorImpl { + pub const fn read_data(_data: &NbtTag) -> Option { + Some(Self) + } +} impl DataComponentImpl for MapColorImpl { default_impl!(MapColor); } @@ -56,6 +66,11 @@ impl DataComponentImpl for MapIdImpl { #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct MapDecorationsImpl; +impl MapDecorationsImpl { + pub const fn read_data(_data: &NbtTag) -> Option { + Some(Self) + } +} impl DataComponentImpl for MapDecorationsImpl { default_impl!(MapDecorations); } @@ -590,12 +605,28 @@ impl DataComponentImpl for ProfileImpl { pub struct JukeboxPlayableImpl { pub song: &'static str, } +impl JukeboxPlayableImpl { + pub fn read_data(data: &NbtTag) -> Option { + let compound = data.extract_compound()?; + let song = compound.get_string("song")?; + let static_song = crate::jukebox_song::JukeboxSong::from_name( + song.strip_prefix("minecraft:").unwrap_or(song), + ) + .map_or("", |s| s.to_name()); + Some(Self { song: static_song }) + } +} impl DataComponentImpl for JukeboxPlayableImpl { default_impl!(JukeboxPlayable); } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct RecipesImpl; +impl RecipesImpl { + pub const fn read_data(_data: &NbtTag) -> Option { + Some(Self) + } +} impl DataComponentImpl for RecipesImpl { default_impl!(Recipes); } diff --git a/crates/pumpkin-protocol/src/codec/data_component.rs b/crates/pumpkin-protocol/src/codec/data_component.rs index ca5c710b9..eaf01e69b 100644 --- a/crates/pumpkin-protocol/src/codec/data_component.rs +++ b/crates/pumpkin-protocol/src/codec/data_component.rs @@ -1,30 +1,18 @@ +#![allow(clippy::wildcard_imports)] + use std::borrow::Cow; use crate::codec::var_int::VarInt; use crate::ser::{NetworkReadExt, NetworkWriteExt, ReadingError, WritingError}; use pumpkin_data::Enchantment; use pumpkin_data::data_component::DataComponent; -use pumpkin_data::data_component_impl::{ - AxolotlVariantImpl, BundleContentsImpl, CatCollarImpl, CatSoundVariantImpl, CatVariantImpl, - ChickenSoundVariantImpl, ChickenVariantImpl, ConsumableImpl, ConsumeAnimation, ConsumeEffect, - CowSoundVariantImpl, CowVariantImpl, CustomDataImpl, CustomNameImpl, DamageImpl, - DataComponentImpl, DyedColorImpl, EnchantmentsImpl, EquipmentSlot, EquippableImpl, - FireworkExplosionImpl, FireworkExplosionShape, FireworksImpl, FoxVariantImpl, FrogVariantImpl, - HorseVariantImpl, IDSet, IDSetContent, IdOr, ItemModelImpl, ItemNameImpl, LlamaVariantImpl, - LoreImpl, MapIdImpl, MaxStackSizeImpl, MooshroomVariantImpl, PaintingVariantImpl, - ParrotVariantImpl, PigSoundVariantImpl, PigVariantImpl, PotionContentsImpl, RabbitVariantImpl, - Rarity, RarityImpl, RepairCostImpl, RepairableImpl, SalmonSizeImpl, SheepColorImpl, - ShulkerColorImpl, SoundEvent, StatusEffectInstance, StoredEnchantmentsImpl, - SuspiciousStewEffect, SuspiciousStewEffectsImpl, SwingAnimationImpl, SwingAnimationType, - TropicalFishBaseColorImpl, TropicalFishPatternColorImpl, TropicalFishPatternImpl, - UnbreakableImpl, UseCooldownImpl, VillagerVariantImpl, WolfCollarImpl, WolfSoundVariantImpl, - WolfVariantImpl, ZombieNautilusVariantImpl, get, -}; +use pumpkin_data::data_component_impl::*; use pumpkin_data::effect::StatusEffect; use pumpkin_data::entity::EntityType; use pumpkin_data::sound::Sound; use pumpkin_nbt::{serializer::NbtWriteHelperJava, tag::NbtTag}; +use pumpkin_util::version::JavaMinecraftVersion; const MAX_STATUS_EFFECTS: usize = 128; @@ -937,78 +925,292 @@ impl DataComponentCodec for RarityImpl { } } +#[allow(clippy::too_many_lines)] pub fn deserialize( id: DataComponent, seq: &mut impl NetworkReadExt, ) -> Result, ReadingError> { match id { + DataComponent::CustomData => Ok(CustomDataImpl::deserialize(seq)?.to_dyn()), DataComponent::MaxStackSize => Ok(MaxStackSizeImpl::deserialize(seq)?.to_dyn()), - DataComponent::CustomData => Err(ReadingError::Message( - "CustomData raw component decoding is not supported; use the custom-data item-stack API".into(), - )), - DataComponent::Enchantments => Ok(EnchantmentsImpl::deserialize(seq)?.to_dyn()), + DataComponent::MaxDamage => Ok(MaxDamageImpl::deserialize(seq)?.to_dyn()), DataComponent::Damage => Ok(DamageImpl::deserialize(seq)?.to_dyn()), DataComponent::Unbreakable => Ok(UnbreakableImpl::deserialize(seq)?.to_dyn()), - DataComponent::PotionContents => Ok(PotionContentsImpl::deserialize(seq)?.to_dyn()), + DataComponent::UseEffects => Ok(UseEffectsImpl::deserialize(seq)?.to_dyn()), + DataComponent::CustomName => Ok(CustomNameImpl::deserialize(seq)?.to_dyn()), + DataComponent::MinimumAttackCharge => { + Ok(MinimumAttackChargeImpl::deserialize(seq)?.to_dyn()) + } + DataComponent::DamageType => Ok(DamageTypeImpl::deserialize(seq)?.to_dyn()), + DataComponent::ItemName => Ok(ItemNameImpl::deserialize(seq)?.to_dyn()), + DataComponent::ItemModel => Ok(ItemModelImpl::deserialize(seq)?.to_dyn()), + DataComponent::Lore => Ok(LoreImpl::deserialize(seq)?.to_dyn()), + DataComponent::Rarity => Ok(RarityImpl::deserialize(seq)?.to_dyn()), + DataComponent::Enchantments => Ok(EnchantmentsImpl::deserialize(seq)?.to_dyn()), + DataComponent::CanPlaceOn => Ok(CanPlaceOnImpl::deserialize(seq)?.to_dyn()), + DataComponent::CanBreak => Ok(CanBreakImpl::deserialize(seq)?.to_dyn()), + DataComponent::AttributeModifiers => Ok(AttributeModifiersImpl::deserialize(seq)?.to_dyn()), + DataComponent::CustomModelData => Ok(CustomModelDataImpl::deserialize(seq)?.to_dyn()), + DataComponent::TooltipDisplay => Ok(TooltipDisplayImpl::deserialize(seq)?.to_dyn()), + DataComponent::RepairCost => Ok(RepairCostImpl::deserialize(seq)?.to_dyn()), + DataComponent::CreativeSlotLock => Ok(CreativeSlotLockImpl::deserialize(seq)?.to_dyn()), + DataComponent::EnchantmentGlintOverride => { + Ok(EnchantmentGlintOverrideImpl::deserialize(seq)?.to_dyn()) + } + DataComponent::IntangibleProjectile => { + Ok(IntangibleProjectileImpl::deserialize(seq)?.to_dyn()) + } + DataComponent::Food => Ok(FoodImpl::deserialize(seq)?.to_dyn()), + DataComponent::Consumable => Ok(ConsumableImpl::deserialize(seq)?.to_dyn()), + DataComponent::UseRemainder => Ok(UseRemainderImpl::deserialize(seq)?.to_dyn()), + DataComponent::UseCooldown => Ok(UseCooldownImpl::deserialize(seq)?.to_dyn()), + DataComponent::DamageResistant => Ok(DamageResistantImpl::deserialize(seq)?.to_dyn()), + DataComponent::Tool => Ok(ToolImpl::deserialize(seq)?.to_dyn()), + DataComponent::Weapon => Ok(WeaponImpl::deserialize(seq)?.to_dyn()), + DataComponent::AttackRange => Ok(AttackRangeImpl::deserialize(seq)?.to_dyn()), + DataComponent::Enchantable => Ok(EnchantableImpl::deserialize(seq)?.to_dyn()), + DataComponent::Equippable => Ok(EquippableImpl::deserialize(seq)?.to_dyn()), + DataComponent::Repairable => Ok(RepairableImpl::deserialize(seq)?.to_dyn()), + DataComponent::Glider => Ok(GliderImpl::deserialize(seq)?.to_dyn()), + DataComponent::TooltipStyle => Ok(TooltipStyleImpl::deserialize(seq)?.to_dyn()), + DataComponent::DeathProtection => Ok(DeathProtectionImpl::deserialize(seq)?.to_dyn()), + DataComponent::BlocksAttacks => Ok(BlocksAttacksImpl::deserialize(seq)?.to_dyn()), + DataComponent::PiercingWeapon => Ok(PiercingWeaponImpl::deserialize(seq)?.to_dyn()), + DataComponent::KineticWeapon => Ok(KineticWeaponImpl::deserialize(seq)?.to_dyn()), + DataComponent::SwingAnimation => Ok(SwingAnimationImpl::deserialize(seq)?.to_dyn()), + DataComponent::AdditionalTradeCost => { + Ok(AdditionalTradeCostImpl::deserialize(seq)?.to_dyn()) + } + DataComponent::StoredEnchantments => Ok(StoredEnchantmentsImpl::deserialize(seq)?.to_dyn()), + DataComponent::Dye => Ok(DyeImpl::deserialize(seq)?.to_dyn()), DataComponent::DyedColor => Ok(DyedColorImpl::deserialize(seq)?.to_dyn()), + DataComponent::MapColor => Ok(MapColorImpl::deserialize(seq)?.to_dyn()), + DataComponent::MapId => Ok(MapIdImpl::deserialize(seq)?.to_dyn()), + DataComponent::MapDecorations => Ok(MapDecorationsImpl::deserialize(seq)?.to_dyn()), + DataComponent::MapPostProcessing => Ok(MapPostProcessingImpl::deserialize(seq)?.to_dyn()), + DataComponent::ChargedProjectiles => Ok(ChargedProjectilesImpl::deserialize(seq)?.to_dyn()), + DataComponent::BundleContents => Ok(BundleContentsImpl::deserialize(seq)?.to_dyn()), + DataComponent::PotionContents => Ok(PotionContentsImpl::deserialize(seq)?.to_dyn()), + DataComponent::PotionDurationScale => { + Ok(PotionDurationScaleImpl::deserialize(seq)?.to_dyn()) + } DataComponent::SuspiciousStewEffects => { Ok(SuspiciousStewEffectsImpl::deserialize(seq)?.to_dyn()) } + DataComponent::WritableBookContent => { + Ok(WritableBookContentImpl::deserialize(seq)?.to_dyn()) + } + DataComponent::WrittenBookContent => Ok(WrittenBookContentImpl::deserialize(seq)?.to_dyn()), + DataComponent::Trim => Ok(TrimImpl::deserialize(seq)?.to_dyn()), + DataComponent::DebugStickState => Ok(DebugStickStateImpl::deserialize(seq)?.to_dyn()), + DataComponent::EntityData => Ok(EntityDataImpl::deserialize(seq)?.to_dyn()), + DataComponent::BucketEntityData => Ok(BucketEntityDataImpl::deserialize(seq)?.to_dyn()), + DataComponent::BlockEntityData => Ok(BlockEntityDataImpl::deserialize(seq)?.to_dyn()), + DataComponent::Instrument => Ok(InstrumentImpl::deserialize(seq)?.to_dyn()), + DataComponent::ProvidesTrimMaterial => { + Ok(ProvidesTrimMaterialImpl::deserialize(seq)?.to_dyn()) + } + DataComponent::OminousBottleAmplifier => { + Ok(OminousBottleAmplifierImpl::deserialize(seq)?.to_dyn()) + } + DataComponent::JukeboxPlayable => Ok(JukeboxPlayableImpl::deserialize(seq)?.to_dyn()), + DataComponent::ProvidesBannerPatterns => { + Ok(ProvidesBannerPatternsImpl::deserialize(seq)?.to_dyn()) + } + DataComponent::Recipes => Ok(RecipesImpl::deserialize(seq)?.to_dyn()), + DataComponent::LodestoneTracker => Ok(LodestoneTrackerImpl::deserialize(seq)?.to_dyn()), DataComponent::FireworkExplosion => Ok(FireworkExplosionImpl::deserialize(seq)?.to_dyn()), DataComponent::Fireworks => Ok(FireworksImpl::deserialize(seq)?.to_dyn()), - DataComponent::ItemModel => Ok(ItemModelImpl::deserialize(seq)?.to_dyn()), - DataComponent::ItemName => Ok(ItemNameImpl::deserialize(seq)?.to_dyn()), - DataComponent::CustomName => Ok(CustomNameImpl::deserialize(seq)?.to_dyn()), - DataComponent::Lore => Ok(LoreImpl::deserialize(seq)?.to_dyn()), - DataComponent::Consumable => Ok(ConsumableImpl::deserialize(seq)?.to_dyn()), - DataComponent::Equippable => Ok(EquippableImpl::deserialize(seq)?.to_dyn()), - DataComponent::StoredEnchantments => Ok(StoredEnchantmentsImpl::deserialize(seq)?.to_dyn()), - DataComponent::RepairCost => Ok(RepairCostImpl::deserialize(seq)?.to_dyn()), - DataComponent::Repairable => Ok(RepairableImpl::deserialize(seq)?.to_dyn()), - DataComponent::SwingAnimation => Ok(SwingAnimationImpl::deserialize(seq)?.to_dyn()), - DataComponent::Rarity => Ok(RarityImpl::deserialize(seq)?.to_dyn()), - DataComponent::UseCooldown => Ok(UseCooldownImpl::deserialize(seq)?.to_dyn()), - DataComponent::MapId => Ok(MapIdImpl::deserialize(seq)?.to_dyn()), - DataComponent::BundleContents => Ok(BundleContentsImpl::deserialize(seq)?.to_dyn()), - _ => Err(ReadingError::Message(format!("component_id_{} (TODO)", id.to_id()))), + DataComponent::Profile => Ok(ProfileImpl::deserialize(seq)?.to_dyn()), + DataComponent::NoteBlockSound => Ok(NoteBlockSoundImpl::deserialize(seq)?.to_dyn()), + DataComponent::BannerPatterns => Ok(BannerPatternsImpl::deserialize(seq)?.to_dyn()), + DataComponent::BaseColor => Ok(BaseColorImpl::deserialize(seq)?.to_dyn()), + DataComponent::PotDecorations => Ok(PotDecorationsImpl::deserialize(seq)?.to_dyn()), + DataComponent::Container => Ok(ContainerImpl::deserialize(seq)?.to_dyn()), + DataComponent::BlockState => Ok(BlockStateImpl::deserialize(seq)?.to_dyn()), + DataComponent::Bees => Ok(BeesImpl::deserialize(seq)?.to_dyn()), + DataComponent::SulfurCubeContent => Ok(SulfurCubeContentImpl::deserialize(seq)?.to_dyn()), + DataComponent::Lock => Ok(LockImpl::deserialize(seq)?.to_dyn()), + DataComponent::ContainerLoot => Ok(ContainerLootImpl::deserialize(seq)?.to_dyn()), + DataComponent::BreakSound => Ok(BreakSoundImpl::deserialize(seq)?.to_dyn()), + DataComponent::VillagerVariant => Ok(VillagerVariantImpl::deserialize(seq)?.to_dyn()), + DataComponent::WolfVariant => Ok(WolfVariantImpl::deserialize(seq)?.to_dyn()), + DataComponent::WolfSoundVariant => Ok(WolfSoundVariantImpl::deserialize(seq)?.to_dyn()), + DataComponent::WolfCollar => Ok(WolfCollarImpl::deserialize(seq)?.to_dyn()), + DataComponent::FoxVariant => Ok(FoxVariantImpl::deserialize(seq)?.to_dyn()), + DataComponent::SalmonSize => Ok(SalmonSizeImpl::deserialize(seq)?.to_dyn()), + DataComponent::ParrotVariant => Ok(ParrotVariantImpl::deserialize(seq)?.to_dyn()), + DataComponent::TropicalFishPattern => { + Ok(TropicalFishPatternImpl::deserialize(seq)?.to_dyn()) + } + DataComponent::TropicalFishBaseColor => { + Ok(TropicalFishBaseColorImpl::deserialize(seq)?.to_dyn()) + } + DataComponent::TropicalFishPatternColor => { + Ok(TropicalFishPatternColorImpl::deserialize(seq)?.to_dyn()) + } + DataComponent::MooshroomVariant => Ok(MooshroomVariantImpl::deserialize(seq)?.to_dyn()), + DataComponent::RabbitVariant => Ok(RabbitVariantImpl::deserialize(seq)?.to_dyn()), + DataComponent::PigVariant => Ok(PigVariantImpl::deserialize(seq)?.to_dyn()), + DataComponent::PigSoundVariant => Ok(PigSoundVariantImpl::deserialize(seq)?.to_dyn()), + DataComponent::CowVariant => Ok(CowVariantImpl::deserialize(seq)?.to_dyn()), + DataComponent::CowSoundVariant => Ok(CowSoundVariantImpl::deserialize(seq)?.to_dyn()), + DataComponent::ChickenVariant => Ok(ChickenVariantImpl::deserialize(seq)?.to_dyn()), + DataComponent::ChickenSoundVariant => { + Ok(ChickenSoundVariantImpl::deserialize(seq)?.to_dyn()) + } + DataComponent::ZombieNautilusVariant => { + Ok(ZombieNautilusVariantImpl::deserialize(seq)?.to_dyn()) + } + DataComponent::FrogVariant => Ok(FrogVariantImpl::deserialize(seq)?.to_dyn()), + DataComponent::HorseVariant => Ok(HorseVariantImpl::deserialize(seq)?.to_dyn()), + DataComponent::PaintingVariant => Ok(PaintingVariantImpl::deserialize(seq)?.to_dyn()), + DataComponent::LlamaVariant => Ok(LlamaVariantImpl::deserialize(seq)?.to_dyn()), + DataComponent::AxolotlVariant => Ok(AxolotlVariantImpl::deserialize(seq)?.to_dyn()), + DataComponent::CatVariant => Ok(CatVariantImpl::deserialize(seq)?.to_dyn()), + DataComponent::CatSoundVariant => Ok(CatSoundVariantImpl::deserialize(seq)?.to_dyn()), + DataComponent::CatCollar => Ok(CatCollarImpl::deserialize(seq)?.to_dyn()), + DataComponent::SheepColor => Ok(SheepColorImpl::deserialize(seq)?.to_dyn()), + DataComponent::ShulkerColor => Ok(ShulkerColorImpl::deserialize(seq)?.to_dyn()), } } + +#[allow(clippy::too_many_lines)] pub fn serialize( id: DataComponent, value: &dyn DataComponentImpl, seq: &mut impl NetworkWriteExt, ) -> Result<(), WritingError> { match id { - DataComponent::MaxStackSize => get::(value).serialize(seq), DataComponent::CustomData => get::(value).serialize(seq), - DataComponent::Enchantments => get::(value).serialize(seq), + DataComponent::MaxStackSize => get::(value).serialize(seq), + DataComponent::MaxDamage => get::(value).serialize(seq), DataComponent::Damage => get::(value).serialize(seq), DataComponent::Unbreakable => get::(value).serialize(seq), - DataComponent::PotionContents => get::(value).serialize(seq), + DataComponent::UseEffects => get::(value).serialize(seq), + DataComponent::CustomName => get::(value).serialize(seq), + DataComponent::MinimumAttackCharge => get::(value).serialize(seq), + DataComponent::DamageType => get::(value).serialize(seq), + DataComponent::ItemName => get::(value).serialize(seq), + DataComponent::ItemModel => get::(value).serialize(seq), + DataComponent::Lore => get::(value).serialize(seq), + DataComponent::Rarity => get::(value).serialize(seq), + DataComponent::Enchantments => get::(value).serialize(seq), + DataComponent::CanPlaceOn => get::(value).serialize(seq), + DataComponent::CanBreak => get::(value).serialize(seq), + DataComponent::AttributeModifiers => get::(value).serialize(seq), + DataComponent::CustomModelData => get::(value).serialize(seq), + DataComponent::TooltipDisplay => get::(value).serialize(seq), + DataComponent::RepairCost => get::(value).serialize(seq), + DataComponent::CreativeSlotLock => get::(value).serialize(seq), + DataComponent::EnchantmentGlintOverride => { + get::(value).serialize(seq) + } + DataComponent::IntangibleProjectile => { + get::(value).serialize(seq) + } + DataComponent::Food => get::(value).serialize(seq), + DataComponent::Consumable => get::(value).serialize(seq), + DataComponent::UseRemainder => get::(value).serialize(seq), + DataComponent::UseCooldown => get::(value).serialize(seq), + DataComponent::DamageResistant => get::(value).serialize(seq), + DataComponent::Tool => get::(value).serialize(seq), + DataComponent::Weapon => get::(value).serialize(seq), + DataComponent::AttackRange => get::(value).serialize(seq), + DataComponent::Enchantable => get::(value).serialize(seq), + DataComponent::Equippable => get::(value).serialize(seq), + DataComponent::Repairable => get::(value).serialize(seq), + DataComponent::Glider => get::(value).serialize(seq), + DataComponent::TooltipStyle => get::(value).serialize(seq), + DataComponent::DeathProtection => get::(value).serialize(seq), + DataComponent::BlocksAttacks => get::(value).serialize(seq), + DataComponent::PiercingWeapon => get::(value).serialize(seq), + DataComponent::KineticWeapon => get::(value).serialize(seq), + DataComponent::SwingAnimation => get::(value).serialize(seq), + DataComponent::AdditionalTradeCost => get::(value).serialize(seq), + DataComponent::StoredEnchantments => get::(value).serialize(seq), + DataComponent::Dye => get::(value).serialize(seq), DataComponent::DyedColor => get::(value).serialize(seq), + DataComponent::MapColor => get::(value).serialize(seq), + DataComponent::MapId => get::(value).serialize(seq), + DataComponent::MapDecorations => get::(value).serialize(seq), + DataComponent::MapPostProcessing => get::(value).serialize(seq), + DataComponent::ChargedProjectiles => get::(value).serialize(seq), + DataComponent::BundleContents => get::(value).serialize(seq), + DataComponent::PotionContents => get::(value).serialize(seq), + DataComponent::PotionDurationScale => get::(value).serialize(seq), DataComponent::SuspiciousStewEffects => { get::(value).serialize(seq) } + DataComponent::WritableBookContent => get::(value).serialize(seq), + DataComponent::WrittenBookContent => get::(value).serialize(seq), + DataComponent::Trim => get::(value).serialize(seq), + DataComponent::DebugStickState => get::(value).serialize(seq), + DataComponent::EntityData => get::(value).serialize(seq), + DataComponent::BucketEntityData => get::(value).serialize(seq), + DataComponent::BlockEntityData => get::(value).serialize(seq), + DataComponent::Instrument => get::(value).serialize(seq), + DataComponent::ProvidesTrimMaterial => { + get::(value).serialize(seq) + } + DataComponent::OminousBottleAmplifier => { + get::(value).serialize(seq) + } + DataComponent::JukeboxPlayable => get::(value).serialize(seq), + DataComponent::ProvidesBannerPatterns => { + get::(value).serialize(seq) + } + DataComponent::Recipes => get::(value).serialize(seq), + DataComponent::LodestoneTracker => get::(value).serialize(seq), DataComponent::FireworkExplosion => get::(value).serialize(seq), DataComponent::Fireworks => get::(value).serialize(seq), - DataComponent::ItemModel => get::(value).serialize(seq), - DataComponent::ItemName => get::(value).serialize(seq), - DataComponent::CustomName => get::(value).serialize(seq), - DataComponent::Lore => get::(value).serialize(seq), - DataComponent::Consumable => get::(value).serialize(seq), - DataComponent::Equippable => get::(value).serialize(seq), - DataComponent::StoredEnchantments => get::(value).serialize(seq), - DataComponent::RepairCost => get::(value).serialize(seq), - DataComponent::Repairable => get::(value).serialize(seq), - DataComponent::SwingAnimation => get::(value).serialize(seq), - DataComponent::Rarity => get::(value).serialize(seq), - DataComponent::UseCooldown => get::(value).serialize(seq), - DataComponent::MapId => get::(value).serialize(seq), - DataComponent::BundleContents => get::(value).serialize(seq), - _ => Err(WritingError::Message(format!( - "{} not yet implemented", - id.to_name() - ))), + DataComponent::Profile => get::(value).serialize(seq), + DataComponent::NoteBlockSound => get::(value).serialize(seq), + DataComponent::BannerPatterns => get::(value).serialize(seq), + DataComponent::BaseColor => get::(value).serialize(seq), + DataComponent::PotDecorations => get::(value).serialize(seq), + DataComponent::Container => get::(value).serialize(seq), + DataComponent::BlockState => get::(value).serialize(seq), + DataComponent::Bees => get::(value).serialize(seq), + DataComponent::SulfurCubeContent => get::(value).serialize(seq), + DataComponent::Lock => get::(value).serialize(seq), + DataComponent::ContainerLoot => get::(value).serialize(seq), + DataComponent::BreakSound => get::(value).serialize(seq), + DataComponent::VillagerVariant => get::(value).serialize(seq), + DataComponent::WolfVariant => get::(value).serialize(seq), + DataComponent::WolfSoundVariant => get::(value).serialize(seq), + DataComponent::WolfCollar => get::(value).serialize(seq), + DataComponent::FoxVariant => get::(value).serialize(seq), + DataComponent::SalmonSize => get::(value).serialize(seq), + DataComponent::ParrotVariant => get::(value).serialize(seq), + DataComponent::TropicalFishPattern => get::(value).serialize(seq), + DataComponent::TropicalFishBaseColor => { + get::(value).serialize(seq) + } + DataComponent::TropicalFishPatternColor => { + get::(value).serialize(seq) + } + DataComponent::MooshroomVariant => get::(value).serialize(seq), + DataComponent::RabbitVariant => get::(value).serialize(seq), + DataComponent::PigVariant => get::(value).serialize(seq), + DataComponent::PigSoundVariant => get::(value).serialize(seq), + DataComponent::CowVariant => get::(value).serialize(seq), + DataComponent::CowSoundVariant => get::(value).serialize(seq), + DataComponent::ChickenVariant => get::(value).serialize(seq), + DataComponent::ChickenSoundVariant => get::(value).serialize(seq), + DataComponent::ZombieNautilusVariant => { + get::(value).serialize(seq) + } + DataComponent::FrogVariant => get::(value).serialize(seq), + DataComponent::HorseVariant => get::(value).serialize(seq), + DataComponent::PaintingVariant => get::(value).serialize(seq), + DataComponent::LlamaVariant => get::(value).serialize(seq), + DataComponent::AxolotlVariant => get::(value).serialize(seq), + DataComponent::CatVariant => get::(value).serialize(seq), + DataComponent::CatSoundVariant => get::(value).serialize(seq), + DataComponent::CatCollar => get::(value).serialize(seq), + DataComponent::SheepColor => get::(value).serialize(seq), + DataComponent::ShulkerColor => get::(value).serialize(seq), } } @@ -1209,3 +1411,1395 @@ codec_string_variant!(CatSoundVariantImpl); codec_string_variant!(CatCollarImpl); codec_string_variant!(SheepColorImpl); codec_string_variant!(ShulkerColorImpl); + +impl DataComponentCodec for MaxDamageImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt::from(self.max_damage)) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let max_damage = seq.get_var_int()?.0; + Ok(Self { max_damage }) + } +} + +impl DataComponentCodec for UseEffectsImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_bool(false)?; + seq.write_bool(true)?; + seq.write_f32(0.2) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let _can_sprint = seq.get_bool()?; + let _interact_vibrations = seq.get_bool()?; + let _speed_multiplier = seq.get_f32()?; + Ok(Self) + } +} + +impl DataComponentCodec for MinimumAttackChargeImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_f32(self.charge) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let charge = seq.get_f32()?; + Ok(Self { charge }) + } +} + +impl DataComponentCodec for DamageTypeImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt::from(self.damage_type.id as i32)) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let id = seq.get_var_int()?.0 as u8; + let damage_type = pumpkin_data::damage::DamageType::from_id(id) + .ok_or_else(|| ReadingError::Message(format!("Invalid DamageType id {id}")))?; + Ok(Self { damage_type }) + } +} + +impl DataComponentCodec for CanPlaceOnImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt(0)) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let count = seq.get_var_int()?.0; + for _ in 0..count { + let has_blocks = seq.get_bool()?; + if has_blocks { + let id_type = seq.get_var_int()?.0; + if id_type == 0 { + let _ = seq.get_str()?; + } else if id_type > 0 { + for _ in 0..(id_type - 1) { + let _ = seq.get_var_int()?; + } + } + } + let has_props = seq.get_bool()?; + if has_props { + let props_len = seq.get_var_int()?.0; + for _ in 0..props_len { + let _ = seq.get_str()?; + let is_exact = seq.get_bool()?; + if is_exact { + let _ = seq.get_str()?; + } else { + if seq.get_bool()? { + let _ = seq.get_str()?; + } + if seq.get_bool()? { + let _ = seq.get_str()?; + } + } + } + } + let has_nbt = seq.get_bool()?; + if has_nbt { + let _ = seq.get_nbt_with_version(&JavaMinecraftVersion::V_26_2)?; + } + let exact_len = seq.get_var_int()?.0; + for _ in 0..exact_len { + let comp_id = seq.get_var_int()?.0 as u8; + if let Some(comp) = DataComponent::try_from_id(comp_id) { + let _ = deserialize(comp, seq)?; + } + } + let partial_len = seq.get_var_int()?.0; + for _ in 0..partial_len { + let _ = seq.get_var_int()?; + } + } + Ok(Self { + predicate: NbtTag::List(Vec::new()), + }) + } +} + +impl DataComponentCodec for CanBreakImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt(0)) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let count = seq.get_var_int()?.0; + for _ in 0..count { + let has_blocks = seq.get_bool()?; + if has_blocks { + let id_type = seq.get_var_int()?.0; + if id_type == 0 { + let _ = seq.get_str()?; + } else if id_type > 0 { + for _ in 0..(id_type - 1) { + let _ = seq.get_var_int()?; + } + } + } + let has_props = seq.get_bool()?; + if has_props { + let props_len = seq.get_var_int()?.0; + for _ in 0..props_len { + let _ = seq.get_str()?; + let is_exact = seq.get_bool()?; + if is_exact { + let _ = seq.get_str()?; + } else { + if seq.get_bool()? { + let _ = seq.get_str()?; + } + if seq.get_bool()? { + let _ = seq.get_str()?; + } + } + } + } + let has_nbt = seq.get_bool()?; + if has_nbt { + let _ = seq.get_nbt_with_version(&JavaMinecraftVersion::V_26_2)?; + } + let exact_len = seq.get_var_int()?.0; + for _ in 0..exact_len { + let comp_id = seq.get_var_int()?.0 as u8; + if let Some(comp) = DataComponent::try_from_id(comp_id) { + let _ = deserialize(comp, seq)?; + } + } + let partial_len = seq.get_var_int()?.0; + for _ in 0..partial_len { + let _ = seq.get_var_int()?; + } + } + Ok(Self { + predicate: NbtTag::List(Vec::new()), + }) + } +} + +impl DataComponentCodec for AttributeModifiersImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt::from(self.attribute_modifiers.len() as i32))?; + for modifier in self.attribute_modifiers.iter() { + seq.write_var_int(&VarInt::from(modifier.r#type.id as i32))?; + seq.write_string(modifier.id)?; + seq.write_f64(modifier.amount)?; + seq.write_var_int(&VarInt::from(modifier.operation as i32))?; + let slot_id = match modifier.slot { + pumpkin_data::enchantment::AttributeModifierSlot::Any => 0, + pumpkin_data::enchantment::AttributeModifierSlot::MainHand => 1, + pumpkin_data::enchantment::AttributeModifierSlot::OffHand => 2, + pumpkin_data::enchantment::AttributeModifierSlot::Hand => 3, + pumpkin_data::enchantment::AttributeModifierSlot::Feet => 4, + pumpkin_data::enchantment::AttributeModifierSlot::Legs => 5, + pumpkin_data::enchantment::AttributeModifierSlot::Chest => 6, + pumpkin_data::enchantment::AttributeModifierSlot::Head => 7, + pumpkin_data::enchantment::AttributeModifierSlot::Armor => 8, + pumpkin_data::enchantment::AttributeModifierSlot::Body => 9, + pumpkin_data::enchantment::AttributeModifierSlot::Saddle => 10, + }; + seq.write_var_int(&VarInt(slot_id))?; + seq.write_var_int(&VarInt(0))?; + } + Ok(()) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let len = seq.get_var_int()?.0 as usize; + for _ in 0..len { + let _attr_id = seq.get_var_int()?; + let _id = seq.get_str()?; + let _amount = seq.get_f64()?; + let _operation = seq.get_var_int()?; + let _slot = seq.get_var_int()?; + let display_type = seq.get_var_int()?.0; + if display_type == 2 { + let _ = seq.get_nbt_with_version(&JavaMinecraftVersion::V_26_2)?; + } + } + Ok(Self { + attribute_modifiers: Cow::Borrowed(&[]), + }) + } +} + +impl DataComponentCodec for CustomModelDataImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt::from(self.floats.len() as i32))?; + for f in &self.floats { + seq.write_f32(*f)?; + } + seq.write_var_int(&VarInt::from(self.flags.len() as i32))?; + for b in &self.flags { + seq.write_bool(*b)?; + } + seq.write_var_int(&VarInt::from(self.strings.len() as i32))?; + for s in &self.strings { + seq.write_string(s)?; + } + seq.write_var_int(&VarInt::from(self.colors.len() as i32))?; + for c in &self.colors { + seq.write_i32(*c)?; + } + Ok(()) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let floats_len = seq.get_var_int()?.0 as usize; + let mut floats = Vec::with_capacity(floats_len); + for _ in 0..floats_len { + floats.push(seq.get_f32()?); + } + let flags_len = seq.get_var_int()?.0 as usize; + let mut flags = Vec::with_capacity(flags_len); + for _ in 0..flags_len { + flags.push(seq.get_bool()?); + } + let strings_len = seq.get_var_int()?.0 as usize; + let mut strings = Vec::with_capacity(strings_len); + for _ in 0..strings_len { + strings.push(seq.get_str()?.to_string()); + } + let colors_len = seq.get_var_int()?.0 as usize; + let mut colors = Vec::with_capacity(colors_len); + for _ in 0..colors_len { + colors.push(seq.get_i32()?); + } + Ok(Self { + floats, + flags, + strings, + colors, + }) + } +} + +impl DataComponentCodec for TooltipDisplayImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_bool(false)?; + seq.write_var_int(&VarInt(0)) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let _hide_tooltip = seq.get_bool()?; + let len = seq.get_var_int()?.0 as usize; + for _ in 0..len { + let _comp_id = seq.get_var_int()?; + } + Ok(Self) + } +} + +impl DataComponentCodec for CreativeSlotLockImpl { + fn serialize(&self, _seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + Ok(()) + } + + fn deserialize(_seq: &mut impl NetworkReadExt) -> Result { + Ok(Self) + } +} + +impl DataComponentCodec for EnchantmentGlintOverrideImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_bool(true) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let _ = seq.get_bool()?; + Ok(Self) + } +} + +impl DataComponentCodec for IntangibleProjectileImpl { + fn serialize(&self, _seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + Ok(()) + } + + fn deserialize(_seq: &mut impl NetworkReadExt) -> Result { + Ok(Self) + } +} + +impl DataComponentCodec for FoodImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt::from(self.nutrition))?; + seq.write_f32(self.saturation)?; + seq.write_bool(self.can_always_eat) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let nutrition = seq.get_var_int()?.0; + let saturation = seq.get_f32()?; + let can_always_eat = seq.get_bool()?; + Ok(Self { + nutrition, + saturation, + can_always_eat, + }) + } +} + +impl DataComponentCodec for UseRemainderImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt(0))?; + seq.write_var_int(&VarInt(0))?; + seq.write_var_int(&VarInt(0))?; + seq.write_var_int(&VarInt(0)) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let _ = deserialize_item_stack_template(seq)?; + Ok(Self) + } +} + +impl DataComponentCodec for DamageResistantImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_string(self.res_type.as_str()) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let tag = seq.get_str()?; + Ok(Self { + res_type: DamageResistantType::from_tag(&tag), + }) + } +} + +impl DataComponentCodec for ToolImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt::from(self.rules.len() as i32))?; + for rule in self.rules.iter() { + serialize_idset(&rule.blocks, seq)?; + seq.write_bool(rule.speed.is_some())?; + if let Some(speed) = rule.speed { + seq.write_f32(speed)?; + } + seq.write_bool(rule.correct_for_drops.is_some())?; + if let Some(correct) = rule.correct_for_drops { + seq.write_bool(correct)?; + } + } + seq.write_f32(self.default_mining_speed)?; + seq.write_var_int(&VarInt::from(self.damage_per_block as i32))?; + seq.write_bool(self.can_destroy_blocks_in_creative) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let rules_len = seq.get_var_int()?.0 as usize; + let mut rules = Vec::with_capacity(rules_len); + for _ in 0..rules_len { + let blocks = deserialize_idset(seq)?; + let speed = if seq.get_bool()? { + Some(seq.get_f32()?) + } else { + None + }; + let correct_for_drops = if seq.get_bool()? { + Some(seq.get_bool()?) + } else { + None + }; + rules.push(pumpkin_data::data_component_impl::ToolRule { + blocks, + speed, + correct_for_drops, + }); + } + let default_mining_speed = seq.get_f32()?; + let damage_per_block = seq.get_var_int()?.0 as u32; + let can_destroy_blocks_in_creative = seq.get_bool()?; + Ok(Self { + rules: Cow::Owned(rules), + default_mining_speed, + damage_per_block, + can_destroy_blocks_in_creative, + }) + } +} + +impl DataComponentCodec for WeaponImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt::from(self.item_damage_per_attack as i32))?; + seq.write_f32(0.0) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let item_damage_per_attack = seq.get_var_int()?.0 as u32; + let _disable_blocking_for_seconds = seq.get_f32()?; + Ok(Self { + item_damage_per_attack, + }) + } +} + +impl DataComponentCodec for AttackRangeImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_f32(self.min_reach)?; + seq.write_f32(self.max_reach)?; + seq.write_f32(self.min_creative_reach)?; + seq.write_f32(self.max_creative_reach)?; + seq.write_f32(self.hitbox_margin)?; + seq.write_f32(self.mob_factor) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let min_reach = seq.get_f32()?; + let max_reach = seq.get_f32()?; + let min_creative_reach = seq.get_f32()?; + let max_creative_reach = seq.get_f32()?; + let hitbox_margin = seq.get_f32()?; + let mob_factor = seq.get_f32()?; + Ok(Self { + min_reach, + max_reach, + min_creative_reach, + max_creative_reach, + hitbox_margin, + mob_factor, + }) + } +} + +impl DataComponentCodec for EnchantableImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt::from(self.value)) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let value = seq.get_var_int()?.0; + Ok(Self { value }) + } +} + +impl DataComponentCodec for GliderImpl { + fn serialize(&self, _seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + Ok(()) + } + + fn deserialize(_seq: &mut impl NetworkReadExt) -> Result { + Ok(Self) + } +} + +impl DataComponentCodec for TooltipStyleImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_string(&self.id) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let id = seq.get_str()?.to_string(); + Ok(Self { id }) + } +} + +impl DataComponentCodec for DeathProtectionImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt(0)) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let len = seq.get_var_int()?.0 as usize; + for _ in 0..len { + let _ = deserialize_consume_effect(seq)?; + } + Ok(Self) + } +} + +impl DataComponentCodec for BlocksAttacksImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_f32(0.0)?; + seq.write_f32(1.0)?; + seq.write_var_int(&VarInt(0))?; + seq.write_var_int(&VarInt(0))?; + seq.write_bool(false)?; + seq.write_bool(false)?; + seq.write_bool(false) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let _block_delay = seq.get_f32()?; + let _disable_scale = seq.get_f32()?; + let red_len = seq.get_var_int()?.0 as usize; + for _ in 0..red_len { + let _ = seq.get_f32()?; + if seq.get_bool()? { + let id_type = seq.get_var_int()?.0; + if id_type == 0 { + let _ = seq.get_str()?; + } else if id_type > 0 { + for _ in 0..(id_type - 1) { + let _ = seq.get_var_int()?; + } + } + } + let _ = seq.get_f32()?; + let _ = seq.get_f32()?; + } + let item_damage_type = seq.get_var_int()?.0; + if item_damage_type == 1 { + let _ = seq.get_f32()?; + let _ = seq.get_f32()?; + } + if seq.get_bool()? { + let id_type = seq.get_var_int()?.0; + if id_type == 0 { + let _ = seq.get_str()?; + } else if id_type > 0 { + for _ in 0..(id_type - 1) { + let _ = seq.get_var_int()?; + } + } + } + if seq.get_bool()? { + let _ = seq.get_var_int()?; + } + if seq.get_bool()? { + let _ = seq.get_var_int()?; + } + Ok(Self) + } +} + +impl DataComponentCodec for PiercingWeaponImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_bool(self.deals_knockback)?; + seq.write_bool(self.dismounts)?; + if let Some(sound) = &self.sound { + seq.write_bool(true)?; + let proto_sound = data_to_proto_sound(sound); + crate::IdOr::::write(&proto_sound, seq, |w, e| { + w.write_string(&e.sound_name)?; + w.write_option(&e.range, |w2, r| w2.write_f32(*r)) + })?; + } else { + seq.write_bool(false)?; + } + if let Some(sound) = &self.hit_sound { + seq.write_bool(true)?; + let proto_sound = data_to_proto_sound(sound); + crate::IdOr::::write(&proto_sound, seq, |w, e| { + w.write_string(&e.sound_name)?; + w.write_option(&e.range, |w2, r| w2.write_f32(*r)) + })?; + } else { + seq.write_bool(false)?; + } + Ok(()) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let deals_knockback = seq.get_bool()?; + let dismounts = seq.get_bool()?; + let sound = if seq.get_bool()? { + let proto = crate::IdOr::::read(seq, |r| { + let sound_name = r.get_str()?.to_string(); + let range = r.get_option(NetworkReadExt::get_f32)?; + Ok(crate::SoundEvent { sound_name, range }) + }) + .map_err(|e| ReadingError::Message(format!("No sound: {e}")))?; + proto_to_data_sound(&proto) + } else { + None + }; + let hit_sound = if seq.get_bool()? { + let proto = crate::IdOr::::read(seq, |r| { + let sound_name = r.get_str()?.to_string(); + let range = r.get_option(NetworkReadExt::get_f32)?; + Ok(crate::SoundEvent { sound_name, range }) + }) + .map_err(|e| ReadingError::Message(format!("No sound: {e}")))?; + proto_to_data_sound(&proto) + } else { + None + }; + Ok(Self { + deals_knockback, + dismounts, + sound, + hit_sound, + }) + } +} + +fn serialize_kinetic_condition( + cond: &pumpkin_data::data_component_impl::KineticConditionImpl, + seq: &mut impl NetworkWriteExt, +) -> Result<(), WritingError> { + seq.write_var_int(&VarInt::from(cond.max_duration_ticks))?; + seq.write_f32(cond.min_speed)?; + seq.write_f32(cond.min_relative_speed) +} + +fn deserialize_kinetic_condition( + seq: &mut impl NetworkReadExt, +) -> Result { + let max_duration_ticks = seq.get_var_int()?.0; + let min_speed = seq.get_f32()?; + let min_relative_speed = seq.get_f32()?; + Ok(pumpkin_data::data_component_impl::KineticConditionImpl { + max_duration_ticks, + min_speed, + min_relative_speed, + }) +} + +impl DataComponentCodec for KineticWeaponImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt::from(self.contact_cooldown_ticks))?; + seq.write_var_int(&VarInt::from(self.delay_ticks))?; + if let Some(cond) = &self.dismount_conditions { + seq.write_bool(true)?; + serialize_kinetic_condition(cond, seq)?; + } else { + seq.write_bool(false)?; + } + if let Some(cond) = &self.knockback_conditions { + seq.write_bool(true)?; + serialize_kinetic_condition(cond, seq)?; + } else { + seq.write_bool(false)?; + } + if let Some(cond) = &self.damage_conditions { + seq.write_bool(true)?; + serialize_kinetic_condition(cond, seq)?; + } else { + seq.write_bool(false)?; + } + seq.write_f32(self.forward_movement)?; + seq.write_f32(self.damage_multiplier)?; + if let Some(sound) = &self.sound { + seq.write_bool(true)?; + let proto = data_to_proto_sound(sound); + crate::IdOr::::write(&proto, seq, |w, e| { + w.write_string(&e.sound_name)?; + w.write_option(&e.range, |w2, r| w2.write_f32(*r)) + })?; + } else { + seq.write_bool(false)?; + } + if let Some(sound) = &self.hit_sound { + seq.write_bool(true)?; + let proto = data_to_proto_sound(sound); + crate::IdOr::::write(&proto, seq, |w, e| { + w.write_string(&e.sound_name)?; + w.write_option(&e.range, |w2, r| w2.write_f32(*r)) + })?; + } else { + seq.write_bool(false)?; + } + Ok(()) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let contact_cooldown_ticks = seq.get_var_int()?.0; + let delay_ticks = seq.get_var_int()?.0; + let dismount_conditions = if seq.get_bool()? { + Some(deserialize_kinetic_condition(seq)?) + } else { + None + }; + let knockback_conditions = if seq.get_bool()? { + Some(deserialize_kinetic_condition(seq)?) + } else { + None + }; + let damage_conditions = if seq.get_bool()? { + Some(deserialize_kinetic_condition(seq)?) + } else { + None + }; + let forward_movement = seq.get_f32()?; + let damage_multiplier = seq.get_f32()?; + let sound = if seq.get_bool()? { + let proto = crate::IdOr::::read(seq, |r| { + let sound_name = r.get_str()?.to_string(); + let range = r.get_option(NetworkReadExt::get_f32)?; + Ok(crate::SoundEvent { sound_name, range }) + }) + .map_err(|e| ReadingError::Message(format!("No sound: {e}")))?; + proto_to_data_sound(&proto) + } else { + None + }; + let hit_sound = if seq.get_bool()? { + let proto = crate::IdOr::::read(seq, |r| { + let sound_name = r.get_str()?.to_string(); + let range = r.get_option(NetworkReadExt::get_f32)?; + Ok(crate::SoundEvent { sound_name, range }) + }) + .map_err(|e| ReadingError::Message(format!("No sound: {e}")))?; + proto_to_data_sound(&proto) + } else { + None + }; + Ok(Self { + contact_cooldown_ticks, + delay_ticks, + dismount_conditions, + knockback_conditions, + damage_conditions, + forward_movement, + damage_multiplier, + sound, + hit_sound, + }) + } +} + +impl DataComponentCodec for AdditionalTradeCostImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt(0)) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let _ = seq.get_var_int()?; + Ok(Self) + } +} + +impl DataComponentCodec for DyeImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt(0)) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let _ = seq.get_var_int()?; + Ok(Self) + } +} + +impl DataComponentCodec for MapColorImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_i32(0) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let _ = seq.get_i32()?; + Ok(Self) + } +} + +impl DataComponentCodec for MapDecorationsImpl { + fn serialize(&self, _seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + Ok(()) + } + + fn deserialize(_seq: &mut impl NetworkReadExt) -> Result { + Ok(Self) + } +} + +impl DataComponentCodec for MapPostProcessingImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt::from(self.processing.map_or(0, |p| p as i32))) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let val = seq.get_var_int()?.0; + let processing = match val { + 0 => Some(pumpkin_data::data_component_impl::MapPostProcessing::Lock), + 1 => Some(pumpkin_data::data_component_impl::MapPostProcessing::Scale), + _ => None, + }; + Ok(Self { processing }) + } +} + +impl DataComponentCodec for ChargedProjectilesImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt::from(self.projectiles.len() as i32))?; + for _ in &self.projectiles { + seq.write_var_int(&VarInt(0))?; + seq.write_var_int(&VarInt(0))?; + seq.write_var_int(&VarInt(0))?; + seq.write_var_int(&VarInt(0))?; + } + Ok(()) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let len = seq.get_var_int()?.0 as usize; + let mut projectiles = Vec::with_capacity(len); + for _ in 0..len { + let _ = deserialize_item_stack_template(seq)?; + projectiles.push(pumpkin_nbt::compound::NbtCompound::new()); + } + Ok(Self { projectiles }) + } +} + +impl DataComponentCodec for PotionDurationScaleImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_f32(self.scale) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let scale = seq.get_f32()?; + Ok(Self { scale }) + } +} + +impl DataComponentCodec for WritableBookContentImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt::from(self.pages.len() as i32))?; + for page in &self.pages { + seq.write_string(page)?; + seq.write_bool(false)?; + } + Ok(()) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let len = seq.get_var_int()?.0 as usize; + let mut pages = Vec::with_capacity(len); + for _ in 0..len { + let raw = seq.get_str()?.to_string(); + let has_filtered = seq.get_bool()?; + if has_filtered { + let _ = seq.get_str()?; + } + pages.push(raw); + } + Ok(Self { pages }) + } +} + +impl DataComponentCodec for WrittenBookContentImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_string(&self.title)?; + seq.write_bool(false)?; + seq.write_string(&self.author)?; + seq.write_var_int(&VarInt(0))?; + seq.write_var_int(&VarInt::from(self.pages.len() as i32))?; + for page in &self.pages { + let comp = pumpkin_util::text::TextComponent::text(page.clone()); + seq.write_slice(&comp.encode_for_version(&JavaMinecraftVersion::V_26_2))?; + seq.write_bool(false)?; + } + seq.write_bool(true) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let title = seq.get_str()?.to_string(); + if seq.get_bool()? { + let _ = seq.get_str()?; + } + let author = seq.get_str()?.to_string(); + let _generation = seq.get_var_int()?.0; + let pages_len = seq.get_var_int()?.0 as usize; + let mut pages = Vec::with_capacity(pages_len); + for _ in 0..pages_len { + let tag = seq.get_nbt_with_version(&JavaMinecraftVersion::V_26_2)?; + let comp = tag.as_ref().map_or_else( + pumpkin_util::text::TextComponent::empty, + pumpkin_util::text::TextComponent::from_nbt, + ); + if seq.get_bool()? { + let _ = seq.get_nbt_with_version(&JavaMinecraftVersion::V_26_2)?; + } + pages.push(comp.get_text()); + } + let _resolved = seq.get_bool()?; + Ok(Self { + title, + author, + pages, + }) + } +} + +impl DataComponentCodec for TrimImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt(0))?; + seq.write_var_int(&VarInt(0)) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let _material = seq.get_var_int()?; + let _pattern = seq.get_var_int()?; + Ok(Self { + material: NbtTag::String("minecraft:quartz".into()), + pattern: NbtTag::String("minecraft:coast".into()), + }) + } +} + +impl DataComponentCodec for DebugStickStateImpl { + fn serialize(&self, _seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + Ok(()) + } + + fn deserialize(_seq: &mut impl NetworkReadExt) -> Result { + Ok(Self) + } +} + +impl DataComponentCodec for EntityDataImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt(0))?; + seq.write_nbt(NbtTag::Compound(pumpkin_nbt::compound::NbtCompound::new())) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let _type_id = seq.get_var_int()?; + let _nbt = seq.get_nbt_with_version(&JavaMinecraftVersion::V_26_2)?; + Ok(Self) + } +} + +impl DataComponentCodec for BucketEntityDataImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_nbt(NbtTag::Compound(pumpkin_nbt::compound::NbtCompound::new())) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let _nbt = seq.get_nbt_with_version(&JavaMinecraftVersion::V_26_2)?; + Ok(Self) + } +} + +impl DataComponentCodec for BlockEntityDataImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt(0))?; + seq.write_nbt(NbtTag::Compound(self.nbt.clone())) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let _type_id = seq.get_var_int()?; + let tag = seq.get_nbt_with_version(&JavaMinecraftVersion::V_26_2)?; + let nbt = if let Some(NbtTag::Compound(c)) = tag { + c + } else { + pumpkin_nbt::compound::NbtCompound::new() + }; + Ok(Self { nbt }) + } +} + +impl DataComponentCodec for InstrumentImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt(0)) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let _ = seq.get_var_int()?; + Ok(Self) + } +} + +impl DataComponentCodec for ProvidesTrimMaterialImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt(0)) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let _ = seq.get_var_int()?; + Ok(Self) + } +} + +impl DataComponentCodec for OminousBottleAmplifierImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt::from(self.amplifier)) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let amplifier = seq.get_var_int()?.0; + Ok(Self { amplifier }) + } +} + +impl DataComponentCodec for JukeboxPlayableImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + let song_id = Sound::from_name(self.song).map_or(0, |s| s as i32); + seq.write_var_int(&VarInt::from(song_id)) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let _ = seq.get_var_int()?; + Ok(Self { song: "" }) + } +} + +impl DataComponentCodec for ProvidesBannerPatternsImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt(0)) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let id_type = seq.get_var_int()?.0; + if id_type == 0 { + let _ = seq.get_str()?; + } else if id_type > 0 { + for _ in 0..(id_type - 1) { + let _ = seq.get_var_int()?; + } + } + Ok(Self) + } +} + +impl DataComponentCodec for RecipesImpl { + fn serialize(&self, _seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + Ok(()) + } + + fn deserialize(_seq: &mut impl NetworkReadExt) -> Result { + Ok(Self) + } +} + +impl DataComponentCodec for LodestoneTrackerImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + if let Some(target) = &self.target { + seq.write_bool(true)?; + seq.write_string(&target.dimension)?; + let pos = pumpkin_util::math::position::BlockPos::new(target.x, target.y, target.z); + seq.write_block_pos(&pos, &JavaMinecraftVersion::V_26_2)?; + } else { + seq.write_bool(false)?; + } + seq.write_bool(self.tracked) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let target = if seq.get_bool()? { + let dimension = seq.get_str()?.to_string(); + let pos = seq.get_block_pos(&JavaMinecraftVersion::V_26_2)?; + Some(pumpkin_data::data_component_impl::LodestoneTarget { + dimension, + x: pos.0.x, + y: pos.0.y, + z: pos.0.z, + }) + } else { + None + }; + let tracked = seq.get_bool()?; + Ok(Self { target, tracked }) + } +} + +impl DataComponentCodec for ProfileImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt(1))?; + if let Some(name) = &self.name { + seq.write_bool(true)?; + seq.write_string(name)?; + } else { + seq.write_bool(false)?; + } + if let Some(id) = &self.id { + seq.write_bool(true)?; + let uuid = uuid::Uuid::from_u128( + ((id[0] as u128) << 96) + | ((id[1] as u128 & 0xFFFFFFFF) << 64) + | ((id[2] as u128 & 0xFFFFFFFF) << 32) + | (id[3] as u128 & 0xFFFFFFFF), + ); + seq.write_uuid(&uuid)?; + } else { + seq.write_bool(false)?; + } + seq.write_var_int(&VarInt::from(self.properties.len() as i32))?; + for prop in &self.properties { + seq.write_string(&prop.name)?; + seq.write_string(&prop.value)?; + if let Some(sig) = &prop.signature { + seq.write_bool(true)?; + seq.write_string(sig)?; + } else { + seq.write_bool(false)?; + } + } + if let Some(texture) = &self.texture { + seq.write_bool(true)?; + seq.write_string(texture)?; + } else { + seq.write_bool(false)?; + } + if let Some(cape) = &self.cape { + seq.write_bool(true)?; + seq.write_string(cape)?; + } else { + seq.write_bool(false)?; + } + if let Some(elytra) = &self.elytra { + seq.write_bool(true)?; + seq.write_string(elytra)?; + } else { + seq.write_bool(false)?; + } + if self.model.is_some() { + seq.write_bool(true)?; + seq.write_var_int(&VarInt(0))?; + } else { + seq.write_bool(false)?; + } + Ok(()) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let either = seq.get_var_int()?.0; + let mut name = None; + let mut id = None; + let mut properties = Vec::new(); + if either == 0 { + let uuid = seq.get_uuid()?; + let u = uuid.as_u128(); + id = Some([ + (u >> 96) as i32, + (u >> 64) as i32, + (u >> 32) as i32, + u as i32, + ]); + name = Some(seq.get_str()?.to_string()); + } else { + if seq.get_bool()? { + name = Some(seq.get_str()?.to_string()); + } + if seq.get_bool()? { + let uuid = seq.get_uuid()?; + let u = uuid.as_u128(); + id = Some([ + (u >> 96) as i32, + (u >> 64) as i32, + (u >> 32) as i32, + u as i32, + ]); + } + } + let props_len = seq.get_var_int()?.0 as usize; + for _ in 0..props_len { + let prop_name = seq.get_str()?.to_string(); + let prop_value = seq.get_str()?.to_string(); + let sig = if seq.get_bool()? { + Some(seq.get_str()?.to_string()) + } else { + None + }; + properties.push(pumpkin_data::data_component_impl::ProfileProperty { + name: prop_name, + value: prop_value, + signature: sig, + }); + } + let texture = if seq.get_bool()? { + Some(seq.get_str()?.to_string()) + } else { + None + }; + let cape = if seq.get_bool()? { + Some(seq.get_str()?.to_string()) + } else { + None + }; + let elytra = if seq.get_bool()? { + Some(seq.get_str()?.to_string()) + } else { + None + }; + let model = if seq.get_bool()? { + let _ = seq.get_var_int()?; + Some("wide".to_string()) + } else { + None + }; + Ok(Self { + name, + id, + properties, + texture, + cape, + elytra, + model, + }) + } +} + +impl DataComponentCodec for NoteBlockSoundImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_string(&self.sound) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let sound = seq.get_str()?.to_string(); + Ok(Self { sound }) + } +} + +impl DataComponentCodec for BannerPatternsImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt::from(self.layers.len() as i32))?; + for layer in &self.layers { + seq.write_var_int(&VarInt(0))?; + seq.write_var_int(&VarInt::from(layer.color.id() as i32))?; + } + Ok(()) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let len = seq.get_var_int()?.0 as usize; + let mut layers = Vec::with_capacity(len); + for _ in 0..len { + let _pattern = seq.get_var_int()?.0; + let color_id = seq.get_var_int()?.0 as u8; + let color = pumpkin_data::dye_color::DyeColor::by_id(color_id).unwrap_or_default(); + layers.push(pumpkin_data::data_component_impl::BannerPatternLayer { + pattern: String::new(), + color, + }); + } + Ok(Self { layers }) + } +} + +impl DataComponentCodec for BaseColorImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + let color_id = + pumpkin_data::dye_color::DyeColor::by_name(&self.color).map_or(0, |c| c.id() as i32); + seq.write_var_int(&VarInt::from(color_id)) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let id = seq.get_var_int()?.0 as u8; + let color = pumpkin_data::dye_color::DyeColor::by_id(id) + .map_or("white", |c| c.name()) + .to_string(); + Ok(Self { color }) + } +} + +impl DataComponentCodec for PotDecorationsImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt(0)) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let len = seq.get_var_int()?.0 as usize; + for _ in 0..len { + let _ = seq.get_var_int()?; + } + Ok(Self) + } +} + +impl DataComponentCodec for ContainerImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt::from(self.items.len() as i32))?; + for (_slot, stack) in &self.items { + seq.write_bool(true)?; + serialize_item_stack_template(stack, seq)?; + } + Ok(()) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let len = seq.get_var_int()?.0 as usize; + let mut items = Vec::with_capacity(len); + for slot in 0..len { + if seq.get_bool()? { + let stack = deserialize_item_stack_template(seq)?; + items.push((slot as u8, stack)); + } + } + Ok(Self { items }) + } +} + +impl DataComponentCodec for BlockStateImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt::from(self.properties.len() as i32))?; + for (k, v) in self.properties.iter() { + seq.write_string(k)?; + seq.write_string(v)?; + } + Ok(()) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let len = seq.get_var_int()?.0 as usize; + let mut properties = Vec::with_capacity(len); + for _ in 0..len { + let k = seq.get_str()?.to_string(); + let v = seq.get_str()?.to_string(); + properties.push((Cow::Owned(k), Cow::Owned(v))); + } + Ok(Self { + properties: Cow::Owned(properties), + }) + } +} + +impl DataComponentCodec for BeesImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt(0)) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let len = seq.get_var_int()?.0 as usize; + for _ in 0..len { + let _entity_type = seq.get_var_int()?; + let _nbt = seq.get_nbt_with_version(&JavaMinecraftVersion::V_26_2)?; + let _ticks = seq.get_var_int()?; + let _min_ticks = seq.get_var_int()?; + } + Ok(Self) + } +} + +impl DataComponentCodec for SulfurCubeContentImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt(0))?; + seq.write_var_int(&VarInt(0))?; + seq.write_var_int(&VarInt(0))?; + seq.write_var_int(&VarInt(0)) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let _ = deserialize_item_stack_template(seq)?; + Ok(Self) + } +} + +impl DataComponentCodec for LockImpl { + fn serialize(&self, _seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + Ok(()) + } + + fn deserialize(_seq: &mut impl NetworkReadExt) -> Result { + Ok(Self { + predicate: pumpkin_nbt::compound::NbtCompound::new(), + }) + } +} + +impl DataComponentCodec for ContainerLootImpl { + fn serialize(&self, _seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + Ok(()) + } + + fn deserialize(_seq: &mut impl NetworkReadExt) -> Result { + Ok(Self { + loot_table: String::new(), + seed: 0, + }) + } +} + +impl DataComponentCodec for BreakSoundImpl { + fn serialize(&self, seq: &mut impl NetworkWriteExt) -> Result<(), WritingError> { + seq.write_var_int(&VarInt(0)) + } + + fn deserialize(seq: &mut impl NetworkReadExt) -> Result { + let _ = seq.get_var_int()?; + Ok(Self) + } +} diff --git a/crates/pumpkin/src/command/argument_types/game_profile.rs b/crates/pumpkin/src/command/argument_types/game_profile.rs index 31da71fbd..cf9a1b8e5 100644 --- a/crates/pumpkin/src/command/argument_types/game_profile.rs +++ b/crates/pumpkin/src/command/argument_types/game_profile.rs @@ -49,8 +49,17 @@ impl GameProfileResult { /// /// Instead, call this method *before* using `write()`/`read()` on a lock. pub fn resolve(&self, source: &CommandSource) -> Result, CommandSyntaxError> { - let players = match self { - Self::Selector(selector) => selector.find_players(source), + match self { + Self::Selector(selector) => { + let players = selector.find_players(source)?; + if players.is_empty() { + return Err( + crate::command::argument_types::entity::NO_PLAYERS_ERROR_TYPE + .create_without_context(), + ); + } + Ok(players.iter().map(|p| p.gameprofile.clone()).collect()) + } Self::Name(name) => { let server = source.server(); if let Some(player) = server.get_player_by_name(name) { @@ -72,7 +81,7 @@ impl GameProfileResult { } if server.advanced_config.networking.java.online_mode { - return match lookup_profile_by_name_blocking( + match lookup_profile_by_name_blocking( name, &server.advanced_config.networking.java.authentication, ) { @@ -86,7 +95,7 @@ impl GameProfileResult { Ok(vec![Self::profile_from_uuid_name(uuid, resolved_name)]) } _ => Err(Self::unknown_player_syntax_error()), - }; + } } else if let Ok(uuid) = offline_uuid(name) { let profile = Self::profile_from_uuid_name(uuid, name.clone()); server @@ -95,10 +104,10 @@ impl GameProfileResult { .write() .unwrap_or_else(std::sync::PoisonError::into_inner) .upsert(profile.id, profile.name.clone()); - return Ok(vec![profile]); + Ok(vec![profile]) + } else { + Err(Self::unknown_player_syntax_error()) } - - return Err(Self::unknown_player_syntax_error()); } Self::Uuid(uuid) => { let server = source.server(); @@ -121,11 +130,9 @@ impl GameProfileResult { return Ok(vec![profile]); } - return Err(Self::unknown_player_syntax_error()); + Err(Self::unknown_player_syntax_error()) } - }?; - - Ok(players.iter().map(|p| &p.gameprofile).cloned().collect()) + } } fn resolve_known_profile_by_name(server: &Server, name: &str) -> Option { diff --git a/crates/pumpkin/src/command/commands/advancement.rs b/crates/pumpkin/src/command/commands/advancement.rs index 7f496d25c..7ecfb56fd 100644 --- a/crates/pumpkin/src/command/commands/advancement.rs +++ b/crates/pumpkin/src/command/commands/advancement.rs @@ -28,7 +28,6 @@ const ARG_TARGETS: &str = "targets"; const ARG_ADVANCEMENT: &str = "advancement"; const ARG_CRITERION: &str = "criterion"; -#[allow(unused)] const ERROR_CRITERION_NOT_FOUND: CommandErrorType<2> = CommandErrorType::new( translation::java::COMMANDS_ADVANCEMENT_CRITERIONNOTFOUND, translation::java::COMMANDS_ADVANCEMENT_CRITERIONNOTFOUND, @@ -112,7 +111,7 @@ impl Action { true } Self::Revoke => { - if !progress.is_done() { + if !progress.has_progress() { return false; } let criteria: Vec> = progress.get_completed_criteria().collect(); @@ -306,11 +305,16 @@ fn perform( advancements: &[&'static Advancement], show_advancement: bool, ) -> Result { - let mut i = 0; + let mut advancement_count = 0; + let mut player_count = 0; for player in targets { - i += action.perform(player, advancements, show_advancement); + let changed = action.perform(player, advancements, show_advancement); + if changed > 0 { + player_count += 1; + } + advancement_count += changed; } - if i == 0 { + if advancement_count == 0 { return if let [first_advancement] = advancements[..] { if let [first_player] = targets { Err(match action { @@ -362,7 +366,7 @@ fn perform( format!("{}.one.to.many.success", action.get_key()), [ first_advancement.name(), - TextComponent::text(targets.len().to_string()), + TextComponent::text(player_count.to_string()), ], ) } @@ -370,7 +374,7 @@ fn perform( TextComponent::translate( format!("{}.many.to.one.success", action.get_key()), [ - TextComponent::text(advancements.len().to_string()), + TextComponent::text(advancement_count.to_string()), first.get_display_name(), ], ) @@ -378,13 +382,13 @@ fn perform( TextComponent::translate( format!("{}.many.to.many.success", action.get_key()), [ - TextComponent::text(advancements.len().to_string()), - TextComponent::text(targets.len().to_string()), + TextComponent::text(advancement_count.to_string()), + TextComponent::text(player_count.to_string()), ], ) }; context.send_feedback(translate, true); - Ok(i) + Ok(advancement_count) } /// Performs an action (grant or revoke) on a specific advancement criterion for multiple players. @@ -414,64 +418,65 @@ pub fn perform_criterion( advancement: &'static Advancement, criterion: &str, ) -> Result { - if advancement.criteria.contains(&criterion) { - let count = targets - .iter() - .map(|player| action.perform_criterion(player, advancement, criterion)) - .filter(|&success| success) - .count() as i32; - if count == 0 { - if let [first_player] = targets { - Err(match action { - Action::Grant => &ERROR_GRANT_CRITERION_TO_ONE_FAILURE, - Action::Revoke => &ERROR_REVOKE_CRITERION_TO_ONE_FAILURE, - } - .create_without_context_args_slice(&[ - TextComponent::text(criterion.to_owned()), - advancement.name(), - first_player.get_display_name(), - ])) - } else { - Err(match action { - Action::Grant => &ERROR_GRANT_CRITERION_TO_MANY_FAILURE, - Action::Revoke => &ERROR_REVOKE_CRITERION_TO_MANY_FAILURE, - } - .create_without_context_args_slice(&[ - TextComponent::text(criterion.to_owned()), - advancement.name(), - TextComponent::text(targets.len().to_string()), - ])) - } - } else { - let translate = if let [first_player] = targets { - TextComponent::translate( - format!("{}.criterion.to.one.success", action.get_key()), - [ - TextComponent::text(criterion.to_owned()), - advancement.name(), - first_player.get_display_name(), - ], - ) - } else { - TextComponent::translate( - format!("{}.criterion.to.many.success", action.get_key()), - [ - TextComponent::text(criterion.to_owned()), - advancement.name(), - TextComponent::text(targets.len().to_string()), - ], - ) - }; - context.send_feedback(translate, true); - Ok(count) - } - } else { - Err( + if !advancement.criteria.contains(&criterion) { + return Err( ERROR_CRITERION_NOT_FOUND.create_without_context_args_slice(&[ advancement.name(), TextComponent::text(criterion.to_owned()), ]), - ) + ); + } + + let count = targets + .iter() + .map(|player| action.perform_criterion(player, advancement, criterion)) + .filter(|&success| success) + .count() as i32; + + if count == 0 { + if let [first_player] = targets { + Err(match action { + Action::Grant => &ERROR_GRANT_CRITERION_TO_ONE_FAILURE, + Action::Revoke => &ERROR_REVOKE_CRITERION_TO_ONE_FAILURE, + } + .create_without_context_args_slice(&[ + TextComponent::text(criterion.to_owned()), + advancement.name(), + first_player.get_display_name(), + ])) + } else { + Err(match action { + Action::Grant => &ERROR_GRANT_CRITERION_TO_MANY_FAILURE, + Action::Revoke => &ERROR_REVOKE_CRITERION_TO_MANY_FAILURE, + } + .create_without_context_args_slice(&[ + TextComponent::text(criterion.to_owned()), + advancement.name(), + TextComponent::text(targets.len().to_string()), + ])) + } + } else { + let translate = if let [first_player] = targets { + TextComponent::translate( + format!("{}.criterion.to.one.success", action.get_key()), + [ + TextComponent::text(criterion.to_owned()), + advancement.name(), + first_player.get_display_name(), + ], + ) + } else { + TextComponent::translate( + format!("{}.criterion.to.many.success", action.get_key()), + [ + TextComponent::text(criterion.to_owned()), + advancement.name(), + TextComponent::text(count.to_string()), + ], + ) + }; + context.send_feedback(translate, true); + Ok(count) } } @@ -537,17 +542,19 @@ impl SuggestionProvider for CriterionSuggestionProvider { /// executor to grant/revoke every advancement to specified players struct EveryAdvancementExecutor { action: Action, + show_advancement: bool, } impl CommandExecutor for EveryAdvancementExecutor { fn execute(&self, context: &CommandContext) -> CommandExecutorResult { let action = self.action; + let show_advancement = self.show_advancement; perform( &context.source, &EntityArgumentType::get_players(context, ARG_TARGETS)?, action, &Advancement::get_advancements_list(), - false, + show_advancement, ) } } @@ -607,10 +614,10 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr }), ), ) - .then( - literal("everything") - .executes(EveryAdvancementExecutor { action: $action }), - ), + .then(literal("everything").executes(EveryAdvancementExecutor { + action: $action, + show_advancement: matches!($action, Action::Revoke), + })), ) }; } diff --git a/crates/pumpkin/src/command/commands/ban.rs b/crates/pumpkin/src/command/commands/ban.rs index 662ae70d8..4c9d7a9e2 100644 --- a/crates/pumpkin/src/command/commands/ban.rs +++ b/crates/pumpkin/src/command/commands/ban.rs @@ -80,7 +80,7 @@ struct BanExecutor { impl CommandExecutor for BanExecutor { fn execute(&self, context: &CommandContext) -> CommandExecutorResult { - let targets = GameProfileArgumentType::get(context, "player")?; + let targets = GameProfileArgumentType::get(context, "targets")?; let reason = if self.has_reason { Some(StringArgumentType::get(context, "reason")?.to_string()) } else { @@ -111,7 +111,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr dispatcher.register( command("ban", DESCRIPTION).requires(PERMISSION).then( - argument("player", GameProfileArgumentType) + argument("targets", GameProfileArgumentType) .executes(BanExecutor { has_reason: false }) .then( argument("reason", StringArgumentType::GreedyPhrase) diff --git a/crates/pumpkin/src/command/commands/banip.rs b/crates/pumpkin/src/command/commands/banip.rs index 7b2c468d7..475acf7ff 100644 --- a/crates/pumpkin/src/command/commands/banip.rs +++ b/crates/pumpkin/src/command/commands/banip.rs @@ -14,6 +14,7 @@ use crate::command::node::dispatcher::CommandDispatcher; use crate::command::node::{CommandExecutor, CommandExecutorResult}; use crate::data::SaveJSONConfiguration; use crate::data::banlist_serializer::BannedIpEntry; +use crate::entity::EntityBase; use crate::net::DisconnectReason; use crate::server::Server; @@ -80,6 +81,26 @@ fn ban_ip(context: &CommandContext, target: &str, reason: Option) -> Com .cloned() .collect(); + let kick_count = players_to_kick.len(); + if !players_to_kick.is_empty() { + let player_names = players_to_kick + .iter() + .map(|p| p.get_display_name()) + .reduce(|acc, name| { + TextComponent::text(format!("{}, {}", acc.get_text(), name.get_text())) + }) + .unwrap_or_else(TextComponent::empty); + + context.source.send_feedback( + TextComponent::translate_cross( + translation::java::COMMANDS_BANIP_INFO, + translation::java::COMMANDS_BANIP_INFO, + [TextComponent::text(kick_count.to_string()), player_names], + ), + true, + ); + } + for player in players_to_kick { let kick_msg = TextComponent::translate_cross( translation::java::MULTIPLAYER_DISCONNECT_IP_BANNED, @@ -89,7 +110,7 @@ fn ban_ip(context: &CommandContext, target: &str, reason: Option) -> Com player.kick(DisconnectReason::Kicked, &kick_msg); } - Ok(1) + Ok(kick_count as i32) } struct BanIpExecutor { @@ -98,7 +119,7 @@ struct BanIpExecutor { impl CommandExecutor for BanIpExecutor { fn execute(&self, context: &CommandContext) -> CommandExecutorResult { - let target = StringArgumentType::get(context, "ip")?; + let target = StringArgumentType::get(context, "target")?; let reason = if self.has_reason { Some(StringArgumentType::get(context, "reason")?.to_string()) } else { @@ -117,7 +138,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr )); let cmd = command("ban-ip", DESCRIPTION).requires(PERMISSION).then( - argument("ip", StringArgumentType::SingleWord) + argument("target", StringArgumentType::SingleWord) .executes(BanIpExecutor { has_reason: false }) .then( argument("reason", StringArgumentType::GreedyPhrase) diff --git a/crates/pumpkin/src/command/commands/bossbar.rs b/crates/pumpkin/src/command/commands/bossbar.rs index b7a7e36ab..1ec26191f 100644 --- a/crates/pumpkin/src/command/commands/bossbar.rs +++ b/crates/pumpkin/src/command/commands/bossbar.rs @@ -278,7 +278,7 @@ impl CommandExecutor for GetExecutor { TextComponent::text(bossbar.max.to_string()), ], ), - false, + true, ); Ok(bossbar.max) } @@ -298,7 +298,7 @@ impl CommandExecutor for GetExecutor { translation::bedrock::COMMANDS_BOSSBAR_GET_PLAYERS_NONE, [bossbar_prefix(bossbar.bossbar_data.title, namespace)], ), - false, + true, ); } else { context.source.send_feedback( @@ -315,7 +315,7 @@ impl CommandExecutor for GetExecutor { TextComponent::text(online_players.join(", ")), ], ), - false, + true, ); } Ok(count) @@ -330,7 +330,7 @@ impl CommandExecutor for GetExecutor { TextComponent::text(bossbar.value.to_string()), ], ), - false, + true, ); Ok(bossbar.value) } @@ -355,7 +355,7 @@ impl CommandExecutor for GetExecutor { namespace, )], ), - false, + true, ); Ok(bossbar.visible as i32) } @@ -509,7 +509,7 @@ impl CommandExecutor for SetExecutor { true, ); - Ok(0) + Ok(max_value) } CommandValueSet::Name => { let name = ComponentArgumentType::get(context, "name")?; diff --git a/crates/pumpkin/src/command/commands/damage.rs b/crates/pumpkin/src/command/commands/damage.rs index 084a2a51b..0ceadef43 100644 --- a/crates/pumpkin/src/command/commands/damage.rs +++ b/crates/pumpkin/src/command/commands/damage.rs @@ -85,7 +85,7 @@ impl CommandExecutor for DamageEntityExecutor { let source = match self.0 { EntityMode::WithSource | EntityMode::WithSourceAndCause => { - Some(EntityArgumentType::get_entity(context, "source")?) + Some(EntityArgumentType::get_entity(context, "entity")?) } EntityMode::Basic | EntityMode::WithType => None, }; @@ -133,7 +133,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr ) .then( literal("by").then( - argument("source", EntityArgumentType::Entity) + argument("entity", EntityArgumentType::Entity) .executes(DamageEntityExecutor(EntityMode::WithSource)) .then(literal("from").then( argument("cause", EntityArgumentType::Entity).executes( diff --git a/crates/pumpkin/src/command/commands/deop.rs b/crates/pumpkin/src/command/commands/deop.rs index 1b67b07f4..8239c108a 100644 --- a/crates/pumpkin/src/command/commands/deop.rs +++ b/crates/pumpkin/src/command/commands/deop.rs @@ -8,6 +8,8 @@ use crate::command::context::command_context::CommandContext; use crate::command::errors::error_types::CommandErrorType; use crate::command::node::dispatcher::CommandDispatcher; use crate::command::node::{CommandExecutor, CommandExecutorResult}; +use crate::command::suggestion::provider::{SuggestionProvider, SuggestionProviderResult}; +use crate::command::suggestion::suggestions::SuggestionsBuilder; use crate::data::SaveJSONConfiguration; const DESCRIPTION: &str = "Revokes operator status from a player."; @@ -52,18 +54,35 @@ impl CommandExecutor for DeopExecutor { } } - if succeeded_deops > 0 { - config.save(); - } - if succeeded_deops == 0 { Err(ERROR_DEOP_FAILED.create_without_context()) } else { + config.save(); + drop(config); + + crate::command::commands::whitelist::kick_non_whitelisted_players(server); + Ok(succeeded_deops) } } } +struct DeopSuggestionProvider; + +impl SuggestionProvider for DeopSuggestionProvider { + fn suggest( + &self, + context: &CommandContext, + mut builder: SuggestionsBuilder, + ) -> SuggestionProviderResult { + let ops = context.server().data.operator_config.read().unwrap(); + for op in &ops.ops { + builder = builder.suggest(op.name.clone()); + } + builder.build() + } +} + pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistry) { registry.register_permission_or_panic(Permission::new( PERMISSION, @@ -72,8 +91,10 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr )); dispatcher.register( - command("deop", DESCRIPTION) - .requires(PERMISSION) - .then(argument("targets", GameProfileArgumentType).executes(DeopExecutor)), + command("deop", DESCRIPTION).requires(PERMISSION).then( + argument("targets", GameProfileArgumentType) + .suggests(DeopSuggestionProvider) + .executes(DeopExecutor), + ), ); } diff --git a/crates/pumpkin/src/command/commands/experience.rs b/crates/pumpkin/src/command/commands/experience.rs index bc5c1f593..2583c38d9 100644 --- a/crates/pumpkin/src/command/commands/experience.rs +++ b/crates/pumpkin/src/command/commands/experience.rs @@ -134,7 +134,7 @@ impl ExperienceExecutor { target.add_experience_points(amount); } else { let current_lvl = target.experience_level.load(Ordering::Relaxed); - if amount > experience::points_in_level(current_lvl) { + if amount >= experience::points_in_level(current_lvl) { return false; } target.set_experience_points(amount); @@ -147,7 +147,7 @@ impl ExperienceExecutor { impl CommandExecutor for ExperienceExecutor { fn execute(&self, context: &CommandContext) -> CommandExecutorResult { - let targets = EntityArgumentType::get_players(context, "targets")?; + let targets = EntityArgumentType::get_players(context, "target")?; if self.mode == Mode::Query { let target = targets[0].clone(); @@ -163,7 +163,7 @@ impl CommandExecutor for ExperienceExecutor { } } - if successes == 0 { + if self.mode == Mode::Set && successes == 0 { return Err(ERROR_SET_POINTS_INVALID.create_without_context()); } @@ -171,7 +171,7 @@ impl CommandExecutor for ExperienceExecutor { let msg = self.get_success_message(amount, &targets, first_name); context.source.send_feedback(msg, true); - Ok(successes) + Ok(targets.len() as i32) } } @@ -183,50 +183,50 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr )); let add_node = literal("add").then( - argument("targets", EntityArgumentType::Players).then( - argument("amount", IntegerArgumentType::with_min(0)) + argument("target", EntityArgumentType::Players).then( + argument("amount", IntegerArgumentType::any()) .executes(ExperienceExecutor { mode: Mode::Add, exp_type: ExpType::Points, }) - .then(literal("levels").executes(ExperienceExecutor { - mode: Mode::Add, - exp_type: ExpType::Levels, - })) .then(literal("points").executes(ExperienceExecutor { mode: Mode::Add, exp_type: ExpType::Points, + })) + .then(literal("levels").executes(ExperienceExecutor { + mode: Mode::Add, + exp_type: ExpType::Levels, })), ), ); let set_node = literal("set").then( - argument("targets", EntityArgumentType::Players).then( + argument("target", EntityArgumentType::Players).then( argument("amount", IntegerArgumentType::with_min(0)) .executes(ExperienceExecutor { mode: Mode::Set, exp_type: ExpType::Points, }) - .then(literal("levels").executes(ExperienceExecutor { - mode: Mode::Set, - exp_type: ExpType::Levels, - })) .then(literal("points").executes(ExperienceExecutor { mode: Mode::Set, exp_type: ExpType::Points, + })) + .then(literal("levels").executes(ExperienceExecutor { + mode: Mode::Set, + exp_type: ExpType::Levels, })), ), ); let query_node = literal("query").then( - argument("targets", EntityArgumentType::Player) - .then(literal("levels").executes(ExperienceExecutor { - mode: Mode::Query, - exp_type: ExpType::Levels, - })) + argument("target", EntityArgumentType::Player) .then(literal("points").executes(ExperienceExecutor { mode: Mode::Query, exp_type: ExpType::Points, + })) + .then(literal("levels").executes(ExperienceExecutor { + mode: Mode::Query, + exp_type: ExpType::Levels, })), ); diff --git a/crates/pumpkin/src/command/commands/fillbiome.rs b/crates/pumpkin/src/command/commands/fillbiome.rs index edc5cb176..c00bfa9d9 100644 --- a/crates/pumpkin/src/command/commands/fillbiome.rs +++ b/crates/pumpkin/src/command/commands/fillbiome.rs @@ -1,4 +1,4 @@ -use crate::command::argument_builder::{ArgumentBuilder, argument, command}; +use crate::command::argument_builder::{ArgumentBuilder, argument, command, literal}; use crate::command::argument_types::coordinates::block_pos::{ BlockPosArgumentType, OUT_OF_BOUNDS_ERROR_TYPE, }; @@ -67,7 +67,7 @@ impl CommandExecutor for FillBiomeExecutor { let target_biome = ResourceKeyArgument::get_biome(context, "biome")?; let replace_biome = if self.has_replace { - Some(ResourceKeyArgument::get_biome(context, "replace_biome")?) + Some(ResourceKeyArgument::get_biome(context, "filter")?) } else { None }; @@ -126,11 +126,19 @@ impl CommandExecutor for FillBiomeExecutor { let msg = TextComponent::translate_cross( translation::java::COMMANDS_FILLBIOME_SUCCESS_COUNT, translation::java::COMMANDS_FILLBIOME_SUCCESS_COUNT, - [TextComponent::text(changed_count.to_string())], + [ + TextComponent::text(changed_count.to_string()), + TextComponent::text(min_x.to_string()), + TextComponent::text(min_y.to_string()), + TextComponent::text(min_z.to_string()), + TextComponent::text(max_x.to_string()), + TextComponent::text(max_y.to_string()), + TextComponent::text(max_z.to_string()), + ], ); context.source.send_feedback(msg, true); - Ok(1) + Ok(changed_count) } } @@ -147,8 +155,10 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr argument("biome", ResourceKeyArgument(BIOME_REGISTRY)) .executes(FillBiomeExecutor { has_replace: false }) .then( - argument("replace_biome", ResourceKeyArgument(BIOME_REGISTRY)) - .executes(FillBiomeExecutor { has_replace: true }), + literal("replace").then( + argument("filter", ResourceKeyArgument(BIOME_REGISTRY)) + .executes(FillBiomeExecutor { has_replace: true }), + ), ), ), ), diff --git a/crates/pumpkin/src/command/commands/forceload.rs b/crates/pumpkin/src/command/commands/forceload.rs index 530e97b24..4fdd48b97 100644 --- a/crates/pumpkin/src/command/commands/forceload.rs +++ b/crates/pumpkin/src/command/commands/forceload.rs @@ -10,7 +10,6 @@ use pumpkin_util::PermissionLvl; use pumpkin_util::math::vector2::Vector2; use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry}; use pumpkin_util::text::TextComponent; -use pumpkin_util::text::color::{Color, NamedColor}; const DESCRIPTION: &str = "Constantly load chunks in the world."; const PERMISSION: &str = "minecraft:command.forceload"; @@ -86,13 +85,15 @@ impl CommandExecutor for ForceloadAddExecutor { world.update_active_chunks(); + let dimension_name = world.dimension.minecraft_name.to_string(); + let text = if total_chunks == 1 { TextComponent::translate_cross( translation::java::COMMANDS_FORCELOAD_ADDED_SINGLE, translation::java::COMMANDS_FORCELOAD_ADDED_SINGLE, [ - TextComponent::text(min_x.to_string()), - TextComponent::text(min_z.to_string()), + TextComponent::text(format!("[{min_x}, {min_z}]")), + TextComponent::text(dimension_name), ], ) } else { @@ -101,16 +102,13 @@ impl CommandExecutor for ForceloadAddExecutor { translation::java::COMMANDS_FORCELOAD_ADDED_MULTIPLE, [ TextComponent::text(total_chunks.to_string()), - TextComponent::text(min_x.to_string()), - TextComponent::text(min_z.to_string()), - TextComponent::text(max_x.to_string()), - TextComponent::text(max_z.to_string()), + TextComponent::text(dimension_name), + TextComponent::text(format!("[{min_x}, {min_z}]")), + TextComponent::text(format!("[{max_x}, {max_z}]")), ], ) }; - context - .source - .send_feedback(text.color(Color::Named(NamedColor::Green)), false); + context.source.send_feedback(text, true); Ok(total_chunks) } @@ -170,13 +168,15 @@ impl CommandExecutor for ForceloadRemoveExecutor { world.update_active_chunks(); + let dimension_name = world.dimension.minecraft_name.to_string(); + let text = if total_chunks == 1 { TextComponent::translate_cross( translation::java::COMMANDS_FORCELOAD_REMOVED_SINGLE, translation::java::COMMANDS_FORCELOAD_REMOVED_SINGLE, [ - TextComponent::text(min_x.to_string()), - TextComponent::text(min_z.to_string()), + TextComponent::text(format!("[{min_x}, {min_z}]")), + TextComponent::text(dimension_name), ], ) } else { @@ -185,16 +185,13 @@ impl CommandExecutor for ForceloadRemoveExecutor { translation::java::COMMANDS_FORCELOAD_REMOVED_MULTIPLE, [ TextComponent::text(total_chunks.to_string()), - TextComponent::text(min_x.to_string()), - TextComponent::text(min_z.to_string()), - TextComponent::text(max_x.to_string()), - TextComponent::text(max_z.to_string()), + TextComponent::text(dimension_name), + TextComponent::text(format!("[{min_x}, {min_z}]")), + TextComponent::text(format!("[{max_x}, {max_z}]")), ], ) }; - context - .source - .send_feedback(text.color(Color::Named(NamedColor::Green)), false); + context.source.send_feedback(text, true); Ok(total_chunks) } @@ -222,14 +219,14 @@ impl CommandExecutor for ForceloadRemoveAllExecutor { world.update_active_chunks(); + let dimension_name = world.dimension.minecraft_name.to_string(); + let text = TextComponent::translate_cross( translation::java::COMMANDS_FORCELOAD_REMOVED_ALL, translation::java::COMMANDS_FORCELOAD_REMOVED_ALL, - [], + [TextComponent::text(dimension_name)], ); - context - .source - .send_feedback(text.color(Color::Named(NamedColor::Green)), false); + context.source.send_feedback(text, true); Ok(removed_count as i32) } @@ -262,30 +259,28 @@ impl CommandExecutor for ForceloadQueryExecutor { forced.contains(&chunk_pos) }; + let dimension_name = world.dimension.minecraft_name.to_string(); + if is_forced { let text = TextComponent::translate_cross( translation::java::COMMANDS_FORCELOAD_QUERY_SUCCESS, translation::java::COMMANDS_FORCELOAD_QUERY_SUCCESS, [ - TextComponent::text(chunk_pos.x.to_string()), - TextComponent::text(chunk_pos.y.to_string()), + TextComponent::text(format!("[{}, {}]", chunk_pos.x, chunk_pos.y)), + TextComponent::text(dimension_name), ], ); - context - .source - .send_feedback(text.color(Color::Named(NamedColor::Green)), false); + context.source.send_feedback(text, false); } else { let text = TextComponent::translate_cross( translation::java::COMMANDS_FORCELOAD_QUERY_FAILURE, translation::java::COMMANDS_FORCELOAD_QUERY_FAILURE, [ - TextComponent::text(chunk_pos.x.to_string()), - TextComponent::text(chunk_pos.y.to_string()), + TextComponent::text(format!("[{}, {}]", chunk_pos.x, chunk_pos.y)), + TextComponent::text(dimension_name), ], ); - context - .source - .send_feedback(text.color(Color::Named(NamedColor::Red)), false); + context.source.send_error(text); } let all_forced = { @@ -303,29 +298,34 @@ impl CommandExecutor for ForceloadQueryExecutor { let text = TextComponent::translate_cross( translation::java::COMMANDS_FORCELOAD_ADDED_NONE, translation::java::COMMANDS_FORCELOAD_ADDED_NONE, - [], + [TextComponent::text( + world.dimension.minecraft_name.to_string(), + )], ); - context - .source - .send_feedback(text.color(Color::Named(NamedColor::Gray)), false); + context.source.send_error(text); } else { let list_str = all_forced.join(", "); let text = if all_forced.len() == 1 { TextComponent::translate_cross( translation::java::COMMANDS_FORCELOAD_LIST_SINGLE, translation::java::COMMANDS_FORCELOAD_LIST_SINGLE, - [TextComponent::text(list_str)], + [ + TextComponent::text(world.dimension.minecraft_name.to_string()), + TextComponent::text(list_str), + ], ) } else { TextComponent::translate_cross( translation::java::COMMANDS_FORCELOAD_LIST_MULTIPLE, translation::java::COMMANDS_FORCELOAD_LIST_MULTIPLE, - [TextComponent::text(list_str)], + [ + TextComponent::text(all_forced.len().to_string()), + TextComponent::text(world.dimension.minecraft_name.to_string()), + TextComponent::text(list_str), + ], ) }; - context - .source - .send_feedback(text.color(Color::Named(NamedColor::Gray)), false); + context.source.send_feedback(text, false); } Ok(i32::from(is_forced)) diff --git a/crates/pumpkin/src/command/commands/item.rs b/crates/pumpkin/src/command/commands/item.rs index d0052a8fc..e1f7c5c68 100644 --- a/crates/pumpkin/src/command/commands/item.rs +++ b/crates/pumpkin/src/command/commands/item.rs @@ -290,21 +290,15 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr PermissionDefault::Op(PermissionLvl::Two), )); - let block_node = literal("block").then( - argument("pos", BlockPosArgumentType).then( - argument("slot", SlotArgumentType) - .then(block_item_node()) - .then(literal("with").then(block_item_node())), - ), - ); + let block_node = literal("block") + .then(argument("pos", BlockPosArgumentType).then( + argument("slot", SlotArgumentType).then(literal("with").then(block_item_node())), + )); - let entity_node = literal("entity").then( - argument("targets", EntityArgumentType::Entities).then( - argument("slot", SlotArgumentType) - .then(entity_item_node()) - .then(literal("with").then(entity_item_node())), - ), - ); + let entity_node = + literal("entity").then(argument("targets", EntityArgumentType::Entities).then( + argument("slot", SlotArgumentType).then(literal("with").then(entity_item_node())), + )); dispatcher.register( command("item", DESCRIPTION) diff --git a/crates/pumpkin/src/command/commands/kill.rs b/crates/pumpkin/src/command/commands/kill.rs index 8c5b3a161..60bdb38d3 100644 --- a/crates/pumpkin/src/command/commands/kill.rs +++ b/crates/pumpkin/src/command/commands/kill.rs @@ -69,7 +69,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr registry.register_permission_or_panic(Permission::new( PERMISSION, DESCRIPTION, - PermissionDefault::Op(PermissionLvl::Four), + PermissionDefault::Op(PermissionLvl::Two), )); dispatcher.register( diff --git a/crates/pumpkin/src/command/commands/pardonip.rs b/crates/pumpkin/src/command/commands/pardonip.rs index c8c22229a..920189549 100644 --- a/crates/pumpkin/src/command/commands/pardonip.rs +++ b/crates/pumpkin/src/command/commands/pardonip.rs @@ -31,7 +31,7 @@ struct PardonIpExecutor; impl CommandExecutor for PardonIpExecutor { fn execute(&self, context: &CommandContext) -> CommandExecutorResult { - let target = StringArgumentType::get(context, "ip")?; + let target = StringArgumentType::get(context, "target")?; let ip = IpAddr::from_str(target) .map_err(|_| ERROR_PARDONIP_INVALID.create_without_context())?; @@ -77,7 +77,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr let cmd = command("pardon-ip", DESCRIPTION) .requires(PERMISSION) - .then(argument("ip", StringArgumentType::SingleWord).executes(PardonIpExecutor)); + .then(argument("target", StringArgumentType::SingleWord).executes(PardonIpExecutor)); dispatcher.register_with_aliases(cmd, &["pardonip"]); } diff --git a/crates/pumpkin/src/command/commands/raid.rs b/crates/pumpkin/src/command/commands/raid.rs index a5485fd49..064892dc0 100644 --- a/crates/pumpkin/src/command/commands/raid.rs +++ b/crates/pumpkin/src/command/commands/raid.rs @@ -357,7 +357,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr registry.register_permission_or_panic(Permission::new( PERMISSION, DESCRIPTION, - PermissionDefault::Op(PermissionLvl::Two), + PermissionDefault::Op(PermissionLvl::Three), )); dispatcher.register( diff --git a/crates/pumpkin/src/command/commands/random.rs b/crates/pumpkin/src/command/commands/random.rs index 6eaae61ff..fe2e46e6d 100644 --- a/crates/pumpkin/src/command/commands/random.rs +++ b/crates/pumpkin/src/command/commands/random.rs @@ -1,22 +1,31 @@ use pumpkin_data::translation; +use pumpkin_util::PermissionLvl; use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry}; use pumpkin_util::text::TextComponent; -use crate::command::argument_builder::{ArgumentBuilder, argument, command, literal}; +use crate::command::argument_builder::{ + ArgumentBuilder, LiteralArgumentBuilder, argument, command, literal, +}; +use crate::command::argument_types::core::bool::BoolArgumentType; +use crate::command::argument_types::core::integer::IntegerArgumentType; +use crate::command::argument_types::identifier::IdentifierArgumentType; use crate::command::argument_types::range::IntRangeArgumentType; use crate::command::context::command_context::CommandContext; use crate::command::errors::error_types::CommandErrorType; use crate::command::node::dispatcher::CommandDispatcher; use crate::command::node::{CommandExecutor, CommandExecutorResult}; +use crate::command::suggestion::provider::{SuggestionProvider, SuggestionProviderResult}; +use crate::command::suggestion::suggestions::SuggestionsBuilder; const DESCRIPTION: &str = "Draws a random value."; - const PERMISSION: &str = "minecraft:command.random"; +const PERMISSION_RESET: &str = "minecraft:command.random.reset"; const ARG_RANGE: &str = "range"; - -/// The largest allowed range span, matching Vanilla (`i32::MAX - 1`). -const MAX_RANGE_SPAN: i64 = (i32::MAX - 1) as i64; +const ARG_SEQUENCE: &str = "sequence"; +const ARG_SEED: &str = "seed"; +const ARG_INCLUDE_WORLD_SEED: &str = "includeWorldSeed"; +const ARG_INCLUDE_SEQUENCE_ID: &str = "includeSequenceId"; const RANGE_TOO_LARGE_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new( translation::java::COMMANDS_RANDOM_ERROR_RANGE_TOO_LARGE, @@ -28,10 +37,25 @@ const RANGE_TOO_SMALL_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new( translation::java::COMMANDS_RANDOM_ERROR_RANGE_TOO_SMALL, ); +struct RandomSequenceSuggestionProvider; + +impl SuggestionProvider for RandomSequenceSuggestionProvider { + fn suggest( + &self, + context: &CommandContext, + mut builder: SuggestionsBuilder, + ) -> SuggestionProviderResult { + let sequences = context.server().random_sequences.lock().unwrap(); + for key in sequences.get_sequence_keys() { + builder = builder.suggest(key); + } + builder.build() + } +} + struct RandomExecutor { - /// Whether the result is announced to every player (`roll`) instead of - /// only the command source (`value`). roll: bool, + has_sequence: bool, } impl CommandExecutor for RandomExecutor { @@ -44,11 +68,20 @@ impl CommandExecutor for RandomExecutor { if span == 0 { return Err(RANGE_TOO_SMALL_ERROR_TYPE.create_without_context()); } - if span >= MAX_RANGE_SPAN { + if span >= 2_147_483_647 { return Err(RANGE_TOO_LARGE_ERROR_TYPE.create_without_context()); } - let result = rand::random_range(min..=max); + let value = if self.has_sequence { + let sequence = IdentifierArgumentType::get(context, ARG_SEQUENCE)?; + let world_seed = context.server().level_info.load().world_gen_settings.seed; + let mut sequences = context.server().random_sequences.lock().unwrap(); + sequences + .get_or_create(&sequence, world_seed) + .random_between_inclusive(min, max) + } else { + rand::random_range(min..=max) + }; if self.roll { let msg = TextComponent::translate_cross( @@ -56,7 +89,7 @@ impl CommandExecutor for RandomExecutor { translation::java::COMMANDS_RANDOM_ROLL, [ context.source.display_name.clone(), - TextComponent::text(result.to_string()), + TextComponent::text(value.to_string()), TextComponent::text(min.to_string()), TextComponent::text(max.to_string()), ], @@ -64,7 +97,6 @@ impl CommandExecutor for RandomExecutor { for player in context.server().get_all_players() { player.send_system_message(&msg); } - // Also show the roll on the console (or another non-player source). if context.source.player_or_none().is_none() { context.source.send_message(msg); } @@ -73,32 +105,209 @@ impl CommandExecutor for RandomExecutor { TextComponent::translate_cross( translation::java::COMMANDS_RANDOM_SAMPLE_SUCCESS, translation::java::COMMANDS_RANDOM_SAMPLE_SUCCESS, - [TextComponent::text(result.to_string())], + [TextComponent::text(value.to_string())], ), false, ); } - Ok(result) + Ok(value) } } +struct ResetSequenceExecutor { + has_seed: bool, + has_include_world_seed: bool, + has_include_sequence_id: bool, +} + +impl CommandExecutor for ResetSequenceExecutor { + fn execute(&self, context: &CommandContext) -> CommandExecutorResult { + let sequence = IdentifierArgumentType::get(context, ARG_SEQUENCE)?; + let world_seed = context.server().level_info.load().world_gen_settings.seed; + let mut sequences = context.server().random_sequences.lock().unwrap(); + + if self.has_seed { + let seed = IntegerArgumentType::get(context, ARG_SEED)?; + let include_world_seed = if self.has_include_world_seed { + BoolArgumentType::get(context, ARG_INCLUDE_WORLD_SEED)? + } else { + true + }; + let include_sequence_id = if self.has_include_sequence_id { + BoolArgumentType::get(context, ARG_INCLUDE_SEQUENCE_ID)? + } else { + true + }; + sequences.reset_with_options( + &sequence, + world_seed, + seed, + include_world_seed, + include_sequence_id, + ); + } else { + sequences.reset(&sequence, world_seed); + } + + context.source.send_feedback( + TextComponent::translate_cross( + translation::java::COMMANDS_RANDOM_RESET_SUCCESS, + translation::java::COMMANDS_RANDOM_RESET_SUCCESS, + [TextComponent::text(sequence.to_string())], + ), + false, + ); + + Ok(1) + } +} + +struct ResetAllSequencesExecutor { + has_seed: bool, + has_include_world_seed: bool, + has_include_sequence_id: bool, +} + +impl CommandExecutor for ResetAllSequencesExecutor { + fn execute(&self, context: &CommandContext) -> CommandExecutorResult { + let mut sequences = context.server().random_sequences.lock().unwrap(); + + if self.has_seed { + let seed = IntegerArgumentType::get(context, ARG_SEED)?; + let include_world_seed = if self.has_include_world_seed { + BoolArgumentType::get(context, ARG_INCLUDE_WORLD_SEED)? + } else { + true + }; + let include_sequence_id = if self.has_include_sequence_id { + BoolArgumentType::get(context, ARG_INCLUDE_SEQUENCE_ID)? + } else { + true + }; + sequences.set_seed_defaults(seed, include_world_seed, include_sequence_id); + } + + let count = sequences.clear() as i32; + + context.source.send_feedback( + TextComponent::translate_cross( + translation::java::COMMANDS_RANDOM_RESET_ALL_SUCCESS, + translation::java::COMMANDS_RANDOM_RESET_ALL_SUCCESS, + [TextComponent::text(count.to_string())], + ), + false, + ); + + Ok(count) + } +} + +fn draw_random_value_tree(name: &'static str, announce: bool) -> LiteralArgumentBuilder { + literal(name).then( + argument(ARG_RANGE, IntRangeArgumentType) + .executes(RandomExecutor { + roll: announce, + has_sequence: false, + }) + .then( + argument(ARG_SEQUENCE, IdentifierArgumentType) + .suggests(RandomSequenceSuggestionProvider) + .requires(PERMISSION_RESET) + .executes(RandomExecutor { + roll: announce, + has_sequence: true, + }), + ), + ) +} + pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistry) { registry.register_permission_or_panic(Permission::new( PERMISSION, DESCRIPTION, - // Vanilla allows every player to use `/random value` and `/random roll`. PermissionDefault::Allow, )); + registry.register_permission_or_panic(Permission::new( + PERMISSION_RESET, + DESCRIPTION, + PermissionDefault::Op(PermissionLvl::Two), + )); + dispatcher.register( command("random", DESCRIPTION) .requires(PERMISSION) - .then(literal("value").then( - argument(ARG_RANGE, IntRangeArgumentType).executes(RandomExecutor { roll: false }), - )) - .then(literal("roll").then( - argument(ARG_RANGE, IntRangeArgumentType).executes(RandomExecutor { roll: true }), - )), + .then(draw_random_value_tree("value", false)) + .then(draw_random_value_tree("roll", true)) + .then( + literal("reset") + .requires(PERMISSION_RESET) + .then( + literal("*") + .executes(ResetAllSequencesExecutor { + has_seed: false, + has_include_world_seed: false, + has_include_sequence_id: false, + }) + .then( + argument(ARG_SEED, IntegerArgumentType::any()) + .executes(ResetAllSequencesExecutor { + has_seed: true, + has_include_world_seed: false, + has_include_sequence_id: false, + }) + .then( + argument(ARG_INCLUDE_WORLD_SEED, BoolArgumentType) + .executes(ResetAllSequencesExecutor { + has_seed: true, + has_include_world_seed: true, + has_include_sequence_id: false, + }) + .then( + argument(ARG_INCLUDE_SEQUENCE_ID, BoolArgumentType) + .executes(ResetAllSequencesExecutor { + has_seed: true, + has_include_world_seed: true, + has_include_sequence_id: true, + }), + ), + ), + ), + ) + .then( + argument(ARG_SEQUENCE, IdentifierArgumentType) + .suggests(RandomSequenceSuggestionProvider) + .executes(ResetSequenceExecutor { + has_seed: false, + has_include_world_seed: false, + has_include_sequence_id: false, + }) + .then( + argument(ARG_SEED, IntegerArgumentType::any()) + .executes(ResetSequenceExecutor { + has_seed: true, + has_include_world_seed: false, + has_include_sequence_id: false, + }) + .then( + argument(ARG_INCLUDE_WORLD_SEED, BoolArgumentType) + .executes(ResetSequenceExecutor { + has_seed: true, + has_include_world_seed: true, + has_include_sequence_id: false, + }) + .then( + argument(ARG_INCLUDE_SEQUENCE_ID, BoolArgumentType) + .executes(ResetSequenceExecutor { + has_seed: true, + has_include_world_seed: true, + has_include_sequence_id: true, + }), + ), + ), + ), + ), + ), ); } diff --git a/crates/pumpkin/src/command/commands/ride.rs b/crates/pumpkin/src/command/commands/ride.rs index afae76106..1356d6049 100644 --- a/crates/pumpkin/src/command/commands/ride.rs +++ b/crates/pumpkin/src/command/commands/ride.rs @@ -33,10 +33,6 @@ static ERROR_NOT_RIDING: CommandErrorType<1> = CommandErrorType::new( translation::java::COMMANDS_RIDE_NOT_RIDING, "Not riding anything", ); -static ERROR_GENERIC: CommandErrorType<2> = CommandErrorType::new( - translation::java::COMMANDS_RIDE_MOUNT_FAILURE_GENERIC, - "Failed to mount", -); #[allow(clippy::assigning_clones)] fn is_riding_recursive(entity: &dyn EntityBase, possible_vehicle: &dyn EntityBase) -> bool { @@ -64,79 +60,48 @@ struct RideMountExecutor; impl CommandExecutor for RideMountExecutor { fn execute(&self, context: &CommandContext) -> CommandExecutorResult { - let targets = EntityArgumentType::get_entities(context, "target")?; + let target = EntityArgumentType::get_entity(context, "target")?; let vehicle = EntityArgumentType::get_entity(context, "vehicle")?; + let current_vehicle = target + .get_entity() + .vehicle + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone(); + if let Some(ref curr_veh) = current_vehicle { + return Err(ERROR_ALREADY_RIDING + .create_without_context(target.get_display_name(), curr_veh.get_display_name())); + } + if vehicle.get_player().is_some() { return Err(ERROR_CANT_RIDE_PLAYERS.create_without_context()); } + if target.get_entity().entity_id == vehicle.get_entity().entity_id + || is_riding_recursive(target.as_ref(), vehicle.as_ref()) + { + return Err(ERROR_LOOP.create_without_context()); + } + + let target_world = target.get_entity().world.load(); let vehicle_world = vehicle.get_entity().world.load(); - - let mut success_count = 0; - let mut last_error = None; - - for target in &targets { - let target_world = target.get_entity().world.load(); - if target_world.dimension.minecraft_name != vehicle_world.dimension.minecraft_name { - last_error = Some(ERROR_WRONG_DIMENSION.create_without_context()); - continue; - } - - if target.get_entity().entity_id == vehicle.get_entity().entity_id { - last_error = Some(ERROR_LOOP.create_without_context()); - continue; - } - - if is_riding_recursive(target.as_ref(), vehicle.as_ref()) { - last_error = Some(ERROR_LOOP.create_without_context()); - continue; - } - - let current_vehicle = target - .get_entity() - .vehicle - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner) - .clone(); - if let Some(ref curr_veh) = current_vehicle { - if curr_veh.get_entity().entity_id == vehicle.get_entity().entity_id { - last_error = Some(ERROR_ALREADY_RIDING.create_without_context( - target.get_display_name(), - vehicle.get_display_name(), - )); - continue; - } - // Dismount first - curr_veh - .get_entity() - .remove_passenger_sync(target.get_entity().entity_id); - } - - vehicle - .get_entity() - .add_passenger(vehicle.clone(), target.clone()); - success_count += 1; - - let msg = TextComponent::translate_cross( - translation::java::COMMANDS_RIDE_MOUNT_SUCCESS, - translation::java::COMMANDS_RIDE_MOUNT_SUCCESS, - [target.get_display_name(), vehicle.get_display_name()], - ); - context.source.send_feedback(msg, true); + if target_world.dimension.minecraft_name != vehicle_world.dimension.minecraft_name { + return Err(ERROR_WRONG_DIMENSION.create_without_context()); } - if success_count == 0 { - if let Some(err) = last_error { - return Err(err); - } - return Err(ERROR_GENERIC.create_without_context( - targets[0].get_display_name(), - vehicle.get_display_name(), - )); - } + vehicle + .get_entity() + .add_passenger(vehicle.clone(), target.clone()); - Ok(success_count) + let msg = TextComponent::translate_cross( + translation::java::COMMANDS_RIDE_MOUNT_SUCCESS, + translation::java::COMMANDS_RIDE_MOUNT_SUCCESS, + [target.get_display_name(), vehicle.get_display_name()], + ); + context.source.send_feedback(msg, true); + + Ok(1) } } @@ -144,43 +109,30 @@ struct RideDismountExecutor; impl CommandExecutor for RideDismountExecutor { fn execute(&self, context: &CommandContext) -> CommandExecutorResult { - let targets = EntityArgumentType::get_entities(context, "target")?; + let target = EntityArgumentType::get_entity(context, "target")?; - let mut success_count = 0; - let mut last_error = None; - - for target in &targets { - let current_vehicle = target - .get_entity() - .vehicle - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner) - .clone(); - if let Some(vehicle) = current_vehicle { + let current_vehicle = target + .get_entity() + .vehicle + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone(); + current_vehicle.map_or_else( + || Err(ERROR_NOT_RIDING.create_without_context(target.get_display_name())), + |vehicle| { let target_id = target.get_entity().entity_id; vehicle.get_entity().remove_passenger_sync(target_id); - success_count += 1; let msg = TextComponent::translate_cross( translation::java::COMMANDS_RIDE_DISMOUNT_SUCCESS, translation::java::COMMANDS_RIDE_DISMOUNT_SUCCESS, - [target.get_display_name()], + [target.get_display_name(), vehicle.get_display_name()], ); context.source.send_feedback(msg, true); - } else { - last_error = - Some(ERROR_NOT_RIDING.create_without_context(target.get_display_name())); - } - } - if success_count == 0 { - if let Some(err) = last_error { - return Err(err); - } - return Err(ERROR_NOT_RIDING.create_without_context(targets[0].get_display_name())); - } - - Ok(success_count) + Ok(1) + }, + ) } } @@ -193,7 +145,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr let builder = command("ride", DESCRIPTION).requires(PERMISSION).then( - argument("target", EntityArgumentType::Entities) + argument("target", EntityArgumentType::Entity) .then(literal("mount").then( argument("vehicle", EntityArgumentType::Entity).executes(RideMountExecutor), )) diff --git a/crates/pumpkin/src/command/commands/rotate.rs b/crates/pumpkin/src/command/commands/rotate.rs index f2c78289f..7247926ed 100644 --- a/crates/pumpkin/src/command/commands/rotate.rs +++ b/crates/pumpkin/src/command/commands/rotate.rs @@ -78,10 +78,10 @@ impl CommandExecutor for RotateFacingLocationExecutor { let facing_pos = Vec3ArgumentType::get_coordinates(context, "facingLocation")?.resolve(&context.source); - let entity = target.get_entity(); - let eye_height = entity.get_eye_height(); - let pos = entity.pos.load(); - let looking_from = Vector3::new(pos.x, pos.y + eye_height, pos.z); + let looking_from = context + .source + .entity_anchor + .position_at_entity(target.get_entity()); let (yaw, pitch) = yaw_pitch_facing_position(&looking_from, &facing_pos); @@ -106,10 +106,10 @@ impl CommandExecutor for RotateFacingEntityExecutor { EntityAnchor::Feet }; - let target_entity = target.get_entity(); - let eye_height = target_entity.get_eye_height(); - let pos = target_entity.pos.load(); - let looking_from = Vector3::new(pos.x, pos.y + eye_height, pos.z); + let looking_from = context + .source + .entity_anchor + .position_at_entity(target.get_entity()); let looking_towards = anchor.position_at_entity(facing_entity.get_entity()); diff --git a/crates/pumpkin/src/command/commands/saveall.rs b/crates/pumpkin/src/command/commands/saveall.rs index a18a3bdbd..0e75f1dea 100644 --- a/crates/pumpkin/src/command/commands/saveall.rs +++ b/crates/pumpkin/src/command/commands/saveall.rs @@ -4,7 +4,7 @@ use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry use pumpkin_util::text::TextComponent; use tracing::error; -use crate::command::argument_builder::{ArgumentBuilder, command}; +use crate::command::argument_builder::{ArgumentBuilder, command, literal}; use crate::command::context::command_context::CommandContext; use crate::command::node::dispatcher::CommandDispatcher; use crate::command::node::{CommandExecutor, CommandExecutorResult}; @@ -63,6 +63,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr dispatcher.register( command("save-all", DESCRIPTION) .requires(PERMISSION) - .executes(SaveAllExecutor), + .executes(SaveAllExecutor) + .then(literal("flush").executes(SaveAllExecutor)), ); } diff --git a/crates/pumpkin/src/command/commands/setworldspawn.rs b/crates/pumpkin/src/command/commands/setworldspawn.rs index 01d09e356..ae7f30ce7 100644 --- a/crates/pumpkin/src/command/commands/setworldspawn.rs +++ b/crates/pumpkin/src/command/commands/setworldspawn.rs @@ -1,6 +1,5 @@ use std::sync::Arc; -use pumpkin_data::dimension::Dimension; use pumpkin_data::translation; use pumpkin_util::PermissionLvl; use pumpkin_util::math::position::BlockPos; @@ -8,10 +7,9 @@ use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry use pumpkin_util::text::TextComponent; use crate::command::argument_builder::{ArgumentBuilder, argument, command}; -use crate::command::argument_types::coordinates::angle::AngleArgumentType; use crate::command::argument_types::coordinates::block_pos::BlockPosArgumentType; +use crate::command::argument_types::coordinates::rotation::RotationArgumentType; use crate::command::context::command_context::CommandContext; -use crate::command::errors::error_types::CommandErrorType; use crate::command::node::dispatcher::CommandDispatcher; use crate::command::node::{CommandExecutor, CommandExecutorResult}; use crate::plugin::world::spawn_change::SpawnChangeEvent; @@ -19,43 +17,33 @@ use crate::plugin::world::spawn_change::SpawnChangeEvent; const DESCRIPTION: &str = "Sets the world spawn point."; const PERMISSION: &str = "minecraft:command.setworldspawn"; -const ERROR_NOT_OVERWORLD: CommandErrorType<0> = CommandErrorType::new( - translation::java::COMMANDS_SETWORLDSPAWN_FAILURE_NOT_OVERWORLD, - translation::java::COMMANDS_SETWORLDSPAWN_FAILURE_NOT_OVERWORLD, -); - enum SetWorldSpawnMode { SelfPos, PosOnly, - PosAndAngle, + PosAndRotation, } struct SetWorldSpawnExecutor(SetWorldSpawnMode); impl CommandExecutor for SetWorldSpawnExecutor { fn execute(&self, context: &CommandContext) -> CommandExecutorResult { - let (pos, angle) = match self.0 { + let (pos, (pitch, yaw)) = match self.0 { SetWorldSpawnMode::SelfPos => { let block_pos = BlockPos::floored_v(context.source.position); - (block_pos, 0.0) + (block_pos, (0.0, 0.0)) } SetWorldSpawnMode::PosOnly => { let block_pos = BlockPosArgumentType::get_block_pos(context, "pos")?; - (block_pos, 0.0) + (block_pos, (0.0, 0.0)) } - SetWorldSpawnMode::PosAndAngle => { + SetWorldSpawnMode::PosAndRotation => { let block_pos = BlockPosArgumentType::get_block_pos(context, "pos")?; - let angle = AngleArgumentType::get(context, "angle")?.get_angle(&context.source); - (block_pos, angle) + let rot = RotationArgumentType::get(context, "rotation")?.rotation(&context.source); + (block_pos, (rot.x, rot.y)) } }; let world = context.source.world(); - if world.dimension != Dimension::OVERWORLD && world.dimension != Dimension::OVERWORLD_CAVES - { - return Err(ERROR_NOT_OVERWORLD.create_without_context()); - } - let server = context.source.server(); let current_info = server.level_info.load(); let previous_position = BlockPos::new( @@ -66,8 +54,8 @@ impl CommandExecutor for SetWorldSpawnExecutor { let new_position = pos; let previous_yaw = current_info.spawn_yaw; let previous_pitch = current_info.spawn_pitch; - let new_yaw = angle; - let new_pitch = 0.0; + let new_yaw = yaw; + let new_pitch = pitch; let mut event = SpawnChangeEvent::new( world.clone(), previous_position, @@ -128,8 +116,8 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr argument("pos", BlockPosArgumentType) .executes(SetWorldSpawnExecutor(SetWorldSpawnMode::PosOnly)) .then( - argument("angle", AngleArgumentType) - .executes(SetWorldSpawnExecutor(SetWorldSpawnMode::PosAndAngle)), + argument("rotation", RotationArgumentType) + .executes(SetWorldSpawnExecutor(SetWorldSpawnMode::PosAndRotation)), ), ), ); diff --git a/crates/pumpkin/src/command/commands/spawnpoint.rs b/crates/pumpkin/src/command/commands/spawnpoint.rs index 3a77e0755..3f15e4d11 100644 --- a/crates/pumpkin/src/command/commands/spawnpoint.rs +++ b/crates/pumpkin/src/command/commands/spawnpoint.rs @@ -1,11 +1,12 @@ use pumpkin_data::translation; use pumpkin_util::PermissionLvl; +use pumpkin_util::math::position::BlockPos; use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry}; use pumpkin_util::text::TextComponent; use crate::command::argument_builder::{ArgumentBuilder, argument, command}; -use crate::command::argument_types::coordinates::angle::AngleArgumentType; use crate::command::argument_types::coordinates::block_pos::BlockPosArgumentType; +use crate::command::argument_types::coordinates::rotation::RotationArgumentType; use crate::command::argument_types::entity::EntityArgumentType; use crate::command::context::command_context::CommandContext; use crate::command::errors::error_types::CommandErrorType; @@ -24,7 +25,7 @@ enum SpawnpointMode { SelfDefault, TargetsDefault, TargetsPos, - TargetsPosAngle, + TargetsPosRotation, } struct SpawnpointExecutor(SpawnpointMode); @@ -43,37 +44,60 @@ impl CommandExecutor for SpawnpointExecutor { _ => EntityArgumentType::get_players(context, "targets")?, }; + let (pos, (pitch, yaw)) = match self.0 { + SpawnpointMode::SelfDefault | SpawnpointMode::TargetsDefault => { + let block_pos = BlockPos::floored_v(context.source.position); + (block_pos, (0.0, 0.0)) + } + SpawnpointMode::TargetsPos => { + let block_pos = BlockPosArgumentType::get_block_pos(context, "pos")?; + (block_pos, (0.0, 0.0)) + } + SpawnpointMode::TargetsPosRotation => { + let block_pos = BlockPosArgumentType::get_block_pos(context, "pos")?; + let rot = RotationArgumentType::get(context, "rotation")?.rotation(&context.source); + (block_pos, (rot.x, rot.y)) + } + }; + + let world = context.source.world(); + let dimension = world.dimension.clone(); + let dimension_name = dimension.minecraft_name.to_string(); + for target in &targets { - let (pos, yaw) = match self.0 { - SpawnpointMode::SelfDefault | SpawnpointMode::TargetsDefault => { - let block_pos = target.position().to_block_pos(); - let yaw = target.living_entity.entity.yaw.load(); - (block_pos, yaw) - } - SpawnpointMode::TargetsPos => { - let block_pos = BlockPosArgumentType::get_block_pos(context, "pos")?; - let yaw = target.living_entity.entity.yaw.load(); - (block_pos, yaw) - } - SpawnpointMode::TargetsPosAngle => { - let block_pos = BlockPosArgumentType::get_block_pos(context, "pos")?; - let yaw = AngleArgumentType::get(context, "angle")?.get_angle(&context.source); - (block_pos, yaw) - } - }; - - let dimension = target.world().dimension.clone(); - target.set_respawn_point(dimension, pos, yaw, 0.0, true); + target.set_respawn_point(dimension.clone(), pos, yaw, pitch, true); + } + if targets.len() == 1 { context.source.send_feedback( TextComponent::translate_cross( translation::java::COMMANDS_SPAWNPOINT_SUCCESS_SINGLE, translation::bedrock::COMMANDS_SPAWNPOINT_SUCCESS_SINGLE, [ - TextComponent::text(target.gameprofile.name.clone()), TextComponent::text(pos.0.x.to_string()), TextComponent::text(pos.0.y.to_string()), TextComponent::text(pos.0.z.to_string()), + TextComponent::text(yaw.to_string()), + TextComponent::text(pitch.to_string()), + TextComponent::text(dimension_name), + TextComponent::text(targets[0].gameprofile.name.clone()), + ], + ), + true, + ); + } else { + context.source.send_feedback( + TextComponent::translate_cross( + translation::java::COMMANDS_SPAWNPOINT_SUCCESS_MULTIPLE, + translation::java::COMMANDS_SPAWNPOINT_SUCCESS_MULTIPLE, + [ + TextComponent::text(pos.0.x.to_string()), + TextComponent::text(pos.0.y.to_string()), + TextComponent::text(pos.0.z.to_string()), + TextComponent::text(yaw.to_string()), + TextComponent::text(pitch.to_string()), + TextComponent::text(dimension_name), + TextComponent::text(targets.len().to_string()), ], ), true, @@ -102,8 +126,9 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr argument("pos", BlockPosArgumentType) .executes(SpawnpointExecutor(SpawnpointMode::TargetsPos)) .then( - argument("angle", AngleArgumentType) - .executes(SpawnpointExecutor(SpawnpointMode::TargetsPosAngle)), + argument("rotation", RotationArgumentType).executes( + SpawnpointExecutor(SpawnpointMode::TargetsPosRotation), + ), ), ), ), diff --git a/crates/pumpkin/src/command/commands/spectate.rs b/crates/pumpkin/src/command/commands/spectate.rs index aaf6486c3..9d6e44f83 100644 --- a/crates/pumpkin/src/command/commands/spectate.rs +++ b/crates/pumpkin/src/command/commands/spectate.rs @@ -78,47 +78,39 @@ impl CommandExecutor for SpectateTargetExecutor { let target_entity = target.get_entity(); let target_world = target_entity.world.load_full(); - let players = if self.is_self { - let player = context + let player = if self.is_self { + context .source .output .as_player() - .ok_or_else(|| ERROR_NOT_PLAYER.create_without_context())?; - vec![player] + .ok_or_else(|| ERROR_NOT_PLAYER.create_without_context())? } else { - EntityArgumentType::get_players(context, "player")? + EntityArgumentType::get_player(context, "player")? }; - for player in &players { - if player.gamemode.load() != GameMode::Spectator { - let display_name = player.get_display_name(); - return Err(ERROR_NOT_SPECTATOR.create_without_context(display_name)); - } - - if target_entity.entity_id == player.entity_id() { - return Err(ERROR_SELF.create_without_context()); - } - - let player_world = player.world(); - if !Arc::ptr_eq(&target_world, &player_world) { - let target_name = target.get_display_name(); - return Err(ERROR_CANNOT_SPECTATE.create_without_context(target_name)); - } + if player.gamemode.load() != GameMode::Spectator { + let display_name = player.get_display_name(); + return Err(ERROR_NOT_SPECTATOR.create_without_context(display_name)); } - let mut succeeded = 0; - for player in &players { - let target_id = target_entity.entity_id; - player.camera_target_id.store(Some(target_id)); - let pos = target_entity.pos.load(); - let yaw = target_entity.yaw.load(); - let pitch = target_entity.pitch.load(); - let player_world = player.world(); - player.try_send_client_packet(&CSetCamera::new(target_id.into())); - player.teleport(pos, Some(yaw), Some(pitch), player_world); - succeeded += 1; + if target_entity.entity_id == player.entity_id() { + return Err(ERROR_SELF.create_without_context()); } + let player_world = player.world(); + if !Arc::ptr_eq(&target_world, &player_world) { + let target_name = target.get_display_name(); + return Err(ERROR_CANNOT_SPECTATE.create_without_context(target_name)); + } + + let target_id = target_entity.entity_id; + player.camera_target_id.store(Some(target_id)); + let pos = target_entity.pos.load(); + let yaw = target_entity.yaw.load(); + let pitch = target_entity.pitch.load(); + player.try_send_client_packet(&CSetCamera::new(target_id.into())); + player.teleport(pos, Some(yaw), Some(pitch), player_world); + let target_name = target.get_display_name(); context.source.send_feedback( TextComponent::translate_cross( @@ -129,7 +121,7 @@ impl CommandExecutor for SpectateTargetExecutor { false, ); - Ok(succeeded) + Ok(1) } } @@ -148,7 +140,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr argument("target", EntityArgumentType::Entity) .executes(SpectateTargetExecutor { is_self: true }) .then( - argument("player", EntityArgumentType::Players) + argument("player", EntityArgumentType::Player) .executes(SpectateTargetExecutor { is_self: false }), ), ), diff --git a/crates/pumpkin/src/command/commands/stopwatch.rs b/crates/pumpkin/src/command/commands/stopwatch.rs index b1a0f6f7f..85bcaad93 100644 --- a/crates/pumpkin/src/command/commands/stopwatch.rs +++ b/crates/pumpkin/src/command/commands/stopwatch.rs @@ -112,7 +112,7 @@ impl CommandExecutor for QueryExecutor { ), true, ); - Ok((elapsed_seconds * scale).floor() as i32) + Ok((elapsed_seconds * scale) as i32) } } diff --git a/crates/pumpkin/src/command/commands/team.rs b/crates/pumpkin/src/command/commands/team.rs index 48a5c7ce5..78c852457 100644 --- a/crates/pumpkin/src/command/commands/team.rs +++ b/crates/pumpkin/src/command/commands/team.rs @@ -21,11 +21,14 @@ use pumpkin_util::text::color::NamedColor; const DESCRIPTION: &str = "Manages teams."; const PERMISSION: &str = "minecraft:command.team"; -const ARG_TEAM_NAME: &str = "name"; +const ARG_TEAM_NAME: &str = "team"; const ARG_DISPLAY_NAME: &str = "displayName"; const ARG_TEAM: &str = "team"; const ARG_MEMBERS: &str = "members"; const ARG_VALUE: &str = "value"; +const ARG_PREFIX: &str = "prefix"; +const ARG_SUFFIX: &str = "suffix"; +const ARG_ALLOWED: &str = "allowed"; const DUPLICATE_TEAM_ERROR: CommandErrorType<0> = CommandErrorType::new( translation::java::COMMANDS_TEAM_ADD_DUPLICATE, @@ -543,7 +546,7 @@ struct TeamModifyDisplayNameExecutor; impl CommandExecutor for TeamModifyDisplayNameExecutor { fn execute(&self, context: &CommandContext) -> CommandExecutorResult { let team_name = TeamArgumentType::get(context, ARG_TEAM)?; - let new_name_str = StringArgumentType::get(context, ARG_VALUE)?; + let new_name_str = StringArgumentType::get(context, ARG_DISPLAY_NAME)?; let world = context.world().clone(); let team_name_owned = team_name.to_string(); @@ -588,7 +591,7 @@ struct TeamModifyPrefixExecutor; impl CommandExecutor for TeamModifyPrefixExecutor { fn execute(&self, context: &CommandContext) -> CommandExecutorResult { let team_name = TeamArgumentType::get(context, ARG_TEAM)?; - let new_prefix_str = StringArgumentType::get(context, ARG_VALUE)?; + let new_prefix_str = StringArgumentType::get(context, ARG_PREFIX)?; let world = context.world().clone(); let team_name_owned = team_name.to_string(); @@ -629,7 +632,7 @@ struct TeamModifySuffixExecutor; impl CommandExecutor for TeamModifySuffixExecutor { fn execute(&self, context: &CommandContext) -> CommandExecutorResult { let team_name = TeamArgumentType::get(context, ARG_TEAM)?; - let new_suffix_str = StringArgumentType::get(context, ARG_VALUE)?; + let new_suffix_str = StringArgumentType::get(context, ARG_SUFFIX)?; let world = context.world().clone(); let team_name_owned = team_name.to_string(); @@ -670,7 +673,7 @@ struct TeamModifyFriendlyFireExecutor; impl CommandExecutor for TeamModifyFriendlyFireExecutor { fn execute(&self, context: &CommandContext) -> CommandExecutorResult { let team_name = TeamArgumentType::get(context, ARG_TEAM)?; - let value = BoolArgumentType::get(context, ARG_VALUE)?; + let value = BoolArgumentType::get(context, ARG_ALLOWED)?; let world = context.world().clone(); let team_name_owned = team_name.to_string(); @@ -724,7 +727,7 @@ struct TeamModifySeeFriendlyInvisiblesExecutor; impl CommandExecutor for TeamModifySeeFriendlyInvisiblesExecutor { fn execute(&self, context: &CommandContext) -> CommandExecutorResult { let team_name = TeamArgumentType::get(context, ARG_TEAM)?; - let value = BoolArgumentType::get(context, ARG_VALUE)?; + let value = BoolArgumentType::get(context, ARG_ALLOWED)?; let world = context.world().clone(); let team_name_owned = team_name.to_string(); @@ -976,12 +979,10 @@ fn join_branch() -> LiteralArgumentBuilder { } fn leave_branch() -> LiteralArgumentBuilder { - literal("leave") - .executes(TeamLeaveExecutor { has_members: false }) - .then( - argument(ARG_MEMBERS, EntityArgumentType::Entities) - .executes(TeamLeaveExecutor { has_members: true }), - ) + literal("leave").then( + argument(ARG_MEMBERS, EntityArgumentType::Entities) + .executes(TeamLeaveExecutor { has_members: true }), + ) } fn list_branch() -> LiteralArgumentBuilder { @@ -1004,28 +1005,28 @@ fn modify_branch() -> LiteralArgumentBuilder { ) .then( literal("displayName").then( - argument(ARG_VALUE, StringArgumentType::GreedyPhrase) + argument(ARG_DISPLAY_NAME, StringArgumentType::GreedyPhrase) .executes(TeamModifyDisplayNameExecutor), ), ) .then( literal("prefix").then( - argument(ARG_VALUE, StringArgumentType::GreedyPhrase) + argument(ARG_PREFIX, StringArgumentType::GreedyPhrase) .executes(TeamModifyPrefixExecutor), ), ) .then( literal("suffix").then( - argument(ARG_VALUE, StringArgumentType::GreedyPhrase) + argument(ARG_SUFFIX, StringArgumentType::GreedyPhrase) .executes(TeamModifySuffixExecutor), ), ) .then(literal("friendlyFire").then( - argument(ARG_VALUE, BoolArgumentType).executes(TeamModifyFriendlyFireExecutor), + argument(ARG_ALLOWED, BoolArgumentType).executes(TeamModifyFriendlyFireExecutor), )) .then( literal("seeFriendlyInvisibles").then( - argument(ARG_VALUE, BoolArgumentType) + argument(ARG_ALLOWED, BoolArgumentType) .executes(TeamModifySeeFriendlyInvisiblesExecutor), ), ) diff --git a/crates/pumpkin/src/command/commands/teammsg.rs b/crates/pumpkin/src/command/commands/teammsg.rs index 18b690a99..3a394ded9 100644 --- a/crates/pumpkin/src/command/commands/teammsg.rs +++ b/crates/pumpkin/src/command/commands/teammsg.rs @@ -48,6 +48,7 @@ impl CommandExecutor for TeamMsgCommandExecutor { let message_component = TextComponent::text(message_text); let online_players = world.players.load(); + let mut recipients = 0; for player in online_players.iter() { if team.players.contains(&player.gameprofile.name) { @@ -74,10 +75,11 @@ impl CommandExecutor for TeamMsgCommandExecutor { }; player.send_system_message(&msg); + recipients += 1; } } - Ok(1) + Ok(recipients) } } diff --git a/crates/pumpkin/src/command/commands/teleport.rs b/crates/pumpkin/src/command/commands/teleport.rs index 3f91c2147..bc6192d7e 100644 --- a/crates/pumpkin/src/command/commands/teleport.rs +++ b/crates/pumpkin/src/command/commands/teleport.rs @@ -11,6 +11,7 @@ use crate::command::argument_builder::{ArgumentBuilder, argument, command, liter use crate::command::argument_types::coordinates::rotation::RotationArgumentType; use crate::command::argument_types::coordinates::vec3::Vec3ArgumentType; use crate::command::argument_types::entity::EntityArgumentType; +use crate::command::argument_types::entity_anchor::{EntityAnchor, EntityAnchorArgumentType}; use crate::command::context::command_context::CommandContext; use crate::command::errors::error_types::CommandErrorType; use crate::command::node::dispatcher::CommandDispatcher; @@ -21,11 +22,6 @@ use crate::world::World; const DESCRIPTION: &str = "Teleports entities, including players."; const PERMISSION: &str = "minecraft:command.teleport"; -const ERROR_NOT_PLAYER: CommandErrorType<0> = CommandErrorType::new( - translation::java::PERMISSIONS_REQUIRES_PLAYER, - translation::java::PERMISSIONS_REQUIRES_PLAYER, -); - const ERROR_INVALID_POSITION: CommandErrorType<0> = CommandErrorType::new( translation::java::COMMANDS_TELEPORT_INVALIDPOSITION, translation::java::COMMANDS_TELEPORT_INVALIDPOSITION, @@ -62,28 +58,24 @@ struct SelfToPosExecutor; impl CommandExecutor for SelfToPosExecutor { fn execute(&self, context: &CommandContext) -> CommandExecutorResult { - let player = context - .source - .output - .as_player() - .ok_or_else(|| ERROR_NOT_PLAYER.create_without_context())?; + let entity = context.source.entity_or_err()?; let pos = Vec3ArgumentType::get_coordinates(context, "location")?.resolve(&context.source); if !World::is_valid(BlockPos(pos.floor_to_i32())) { return Err(ERROR_INVALID_POSITION.create_without_context()); } - let yaw = player.get_entity().yaw.load(); - let pitch = player.get_entity().pitch.load(); - let player_world = player.world(); - player.teleport(pos, Some(yaw), Some(pitch), player_world); + let yaw = entity.get_entity().yaw.load(); + let pitch = entity.get_entity().pitch.load(); + let world = context.source.world(); + entity.teleport(pos, Some(yaw), Some(pitch), world.clone()); context.source.send_feedback( TextComponent::translate_cross( translation::java::COMMANDS_TELEPORT_SUCCESS_LOCATION_SINGLE, translation::bedrock::COMMANDS_TP_SUCCESS_COORDINATES, [ - player.get_display_name(), + entity.get_display_name(), TextComponent::text(pos.x.to_string()), TextComponent::text(pos.y.to_string()), TextComponent::text(pos.z.to_string()), @@ -100,11 +92,7 @@ struct SelfToEntityExecutor; impl CommandExecutor for SelfToEntityExecutor { fn execute(&self, context: &CommandContext) -> CommandExecutorResult { - let player = context - .source - .output - .as_player() - .ok_or_else(|| ERROR_NOT_PLAYER.create_without_context())?; + let entity = context.source.entity_or_err()?; let destination = EntityArgumentType::get_entity(context, "destination")?; let destination_entity = destination.get_entity(); @@ -117,14 +105,14 @@ impl CommandExecutor for SelfToEntityExecutor { return Err(ERROR_INVALID_POSITION.create_without_context()); } - player.teleport(pos, Some(yaw), Some(pitch), world); + entity.teleport(pos, Some(yaw), Some(pitch), world); context.source.send_feedback( TextComponent::translate_cross( translation::java::COMMANDS_TELEPORT_SUCCESS_ENTITY_SINGLE, translation::bedrock::COMMANDS_TP_SUCCESSVICTIM, [ - player.get_display_name(), + entity.get_display_name(), destination_entity.get_display_name(), ], ), @@ -270,10 +258,15 @@ impl CommandExecutor for EntitiesToPosFacingPosExecutor { let facing_pos = Vec3ArgumentType::get_coordinates(context, "facingLocation")?.resolve(&context.source); - let (yaw, pitch) = yaw_pitch_facing_position(&pos, &facing_pos); let world = context.source.world(); for target in &targets { + let eye_offset = match context.source.entity_anchor { + EntityAnchor::Feet => 0.0, + EntityAnchor::Eyes => target.get_entity().get_eye_height(), + }; + let looking_from = Vector3::new(pos.x, pos.y + eye_offset, pos.z); + let (yaw, pitch) = yaw_pitch_facing_position(&looking_from, &facing_pos); target.teleport(pos, Some(yaw), Some(pitch), world.clone()); } @@ -300,7 +293,9 @@ impl CommandExecutor for EntitiesToPosFacingPosExecutor { } } -struct EntitiesToPosFacingEntityExecutor; +struct EntitiesToPosFacingEntityExecutor { + has_anchor: bool, +} impl CommandExecutor for EntitiesToPosFacingEntityExecutor { fn execute(&self, context: &CommandContext) -> CommandExecutorResult { @@ -312,10 +307,22 @@ impl CommandExecutor for EntitiesToPosFacingEntityExecutor { } let facing_entity = EntityArgumentType::get_entity(context, "facingEntity")?; - let (yaw, pitch) = yaw_pitch_facing_position(&pos, &facing_entity.get_entity().pos.load()); + let anchor = if self.has_anchor { + EntityAnchorArgumentType::get(context, "facingAnchor")? + } else { + EntityAnchor::Feet + }; + + let facing_pos = anchor.position_at_entity(facing_entity.get_entity()); let world = context.source.world(); for target in &targets { + let eye_offset = match context.source.entity_anchor { + EntityAnchor::Feet => 0.0, + EntityAnchor::Eyes => target.get_entity().get_eye_height(), + }; + let looking_from = Vector3::new(pos.x, pos.y + eye_offset, pos.z); + let (yaw, pitch) = yaw_pitch_facing_position(&looking_from, &facing_pos); target.teleport(pos, Some(yaw), Some(pitch), world.clone()); } @@ -367,7 +374,15 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr .then( literal("entity").then( argument("facingEntity", EntityArgumentType::Entity) - .executes(EntitiesToPosFacingEntityExecutor), + .executes(EntitiesToPosFacingEntityExecutor { + has_anchor: false, + }) + .then( + argument("facingAnchor", EntityAnchorArgumentType) + .executes(EntitiesToPosFacingEntityExecutor { + has_anchor: true, + }), + ), ), ) .then( diff --git a/crates/pumpkin/src/command/commands/tick.rs b/crates/pumpkin/src/command/commands/tick.rs index e59775200..a75f6b557 100644 --- a/crates/pumpkin/src/command/commands/tick.rs +++ b/crates/pumpkin/src/command/commands/tick.rs @@ -148,6 +148,7 @@ impl TickExecutor { TextComponent::text(nanos_to_millis_string(p50)), TextComponent::text(nanos_to_millis_string(p95)), TextComponent::text(nanos_to_millis_string(p99)), + TextComponent::text(slice.len().to_string()), ], ), false, diff --git a/crates/pumpkin/src/command/commands/trigger.rs b/crates/pumpkin/src/command/commands/trigger.rs index 2a951da0e..717a43146 100644 --- a/crates/pumpkin/src/command/commands/trigger.rs +++ b/crates/pumpkin/src/command/commands/trigger.rs @@ -87,10 +87,10 @@ impl CommandExecutor for SimpleTriggerExecutor { translation::java::COMMANDS_TRIGGER_SIMPLE_SUCCESS, [objective_display_name], ), - false, + true, ); - Ok(1) + Ok(new_value) } } @@ -158,10 +158,10 @@ impl CommandExecutor for AddTriggerExecutor { TextComponent::text(value.to_string()), ], ), - false, + true, ); - Ok(value) + Ok(new_value) } } @@ -221,7 +221,7 @@ impl CommandExecutor for SetTriggerExecutor { TextComponent::text(value.to_string()), ], ), - false, + true, ); Ok(value) diff --git a/crates/pumpkin/src/command/commands/weather.rs b/crates/pumpkin/src/command/commands/weather.rs index 032d0710a..a83eb35fc 100644 --- a/crates/pumpkin/src/command/commands/weather.rs +++ b/crates/pumpkin/src/command/commands/weather.rs @@ -33,7 +33,7 @@ impl CommandExecutor for WeatherExecutor { }; let world = context.source.world(); - let message = { + let (message, return_val) = { let mut weather = world .weather .lock() @@ -45,10 +45,13 @@ impl CommandExecutor for WeatherExecutor { duration.unwrap_or_else(|| rand::random_range(12_000..=180_000)); weather.set_weather_parameters(world, processed_duration, 0, false, false); - TextComponent::translate_cross( - translation::java::COMMANDS_WEATHER_SET_CLEAR, - translation::bedrock::COMMANDS_WEATHER_CLEAR, - [], + ( + TextComponent::translate_cross( + translation::java::COMMANDS_WEATHER_SET_CLEAR, + translation::bedrock::COMMANDS_WEATHER_CLEAR, + [], + ), + duration.unwrap_or(-1), ) } WeatherMode::Rain => { @@ -56,10 +59,13 @@ impl CommandExecutor for WeatherExecutor { duration.unwrap_or_else(|| rand::random_range(12_000..=24_000)); weather.set_weather_parameters(world, 0, processed_duration, true, false); - TextComponent::translate_cross( - translation::java::COMMANDS_WEATHER_SET_RAIN, - translation::bedrock::COMMANDS_WEATHER_RAIN, - [], + ( + TextComponent::translate_cross( + translation::java::COMMANDS_WEATHER_SET_RAIN, + translation::bedrock::COMMANDS_WEATHER_RAIN, + [], + ), + duration.unwrap_or(-1), ) } WeatherMode::Thunder => { @@ -67,10 +73,13 @@ impl CommandExecutor for WeatherExecutor { duration.unwrap_or_else(|| rand::random_range(3600..=15_600)); weather.set_weather_parameters(world, 0, processed_duration, true, true); - TextComponent::translate_cross( - translation::java::COMMANDS_WEATHER_SET_THUNDER, - translation::bedrock::COMMANDS_WEATHER_THUNDER, - [], + ( + TextComponent::translate_cross( + translation::java::COMMANDS_WEATHER_SET_THUNDER, + translation::bedrock::COMMANDS_WEATHER_THUNDER, + [], + ), + duration.unwrap_or(-1), ) } } @@ -78,7 +87,7 @@ impl CommandExecutor for WeatherExecutor { context.source.send_feedback(message, true); - Ok(1) + Ok(return_val) } } @@ -98,7 +107,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr mode: WeatherMode::Clear, has_duration: false, }) - .then(argument("duration", TimeArgumentType::any()).executes( + .then(argument("duration", TimeArgumentType::new(1)).executes( WeatherExecutor { mode: WeatherMode::Clear, has_duration: true, @@ -111,7 +120,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr mode: WeatherMode::Rain, has_duration: false, }) - .then(argument("duration", TimeArgumentType::any()).executes( + .then(argument("duration", TimeArgumentType::new(1)).executes( WeatherExecutor { mode: WeatherMode::Rain, has_duration: true, @@ -124,7 +133,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr mode: WeatherMode::Thunder, has_duration: false, }) - .then(argument("duration", TimeArgumentType::any()).executes( + .then(argument("duration", TimeArgumentType::new(1)).executes( WeatherExecutor { mode: WeatherMode::Thunder, has_duration: true, diff --git a/crates/pumpkin/src/command/commands/whitelist.rs b/crates/pumpkin/src/command/commands/whitelist.rs index ab2ab60d1..5e9502307 100644 --- a/crates/pumpkin/src/command/commands/whitelist.rs +++ b/crates/pumpkin/src/command/commands/whitelist.rs @@ -12,6 +12,8 @@ use crate::command::context::command_context::CommandContext; use crate::command::errors::error_types::CommandErrorType; use crate::command::node::dispatcher::CommandDispatcher; use crate::command::node::{CommandExecutor, CommandExecutorResult}; +use crate::command::suggestion::provider::{SuggestionProvider, SuggestionProviderResult}; +use crate::command::suggestion::suggestions::SuggestionsBuilder; use crate::data::{LoadJSONConfiguration, SaveJSONConfiguration, whitelist::WhitelistConfig}; use crate::net::DisconnectReason; use crate::server::Server; @@ -39,11 +41,14 @@ const ERROR_REMOVE_FAILED: CommandErrorType<0> = CommandErrorType::new( translation::bedrock::COMMANDS_ALLOWLIST_REMOVE_FAILED, ); -fn kick_non_whitelisted_players(server: &Server) { +pub fn kick_non_whitelisted_players(server: &Server) { let whitelist = server.data.whitelist_config.read().unwrap(); + let ops = server.data.operator_config.read().unwrap(); if server.basic_config.enforce_whitelist && server.white_list.load(Ordering::Relaxed) { for player in server.get_all_players() { - if whitelist.is_whitelisted(&player.gameprofile) { + if ops.get_entry(&player.gameprofile.id).is_some() + || whitelist.is_whitelisted(&player.gameprofile) + { continue; } player.kick( @@ -66,7 +71,6 @@ impl CommandExecutor for OnExecutor { if previous { Err(ERROR_ALREADY_ON.create_without_context()) } else { - kick_non_whitelisted_players(server); context.source.send_feedback( pumpkin_macros::translate_cross!( translation::java::COMMANDS_WHITELIST_ENABLED, @@ -74,6 +78,7 @@ impl CommandExecutor for OnExecutor { ), true, ); + kick_non_whitelisted_players(server); Ok(1) } } @@ -109,9 +114,10 @@ impl CommandExecutor for ListExecutor { let whitelist = &whitelist_guard.whitelist; if whitelist.is_empty() { context.source.send_feedback( - pumpkin_macros::translate_cross!( + TextComponent::translate_cross( translation::java::COMMANDS_WHITELIST_NONE, - translation::bedrock::COMMANDS_ALLOWLIST_LIST + translation::java::COMMANDS_WHITELIST_NONE, + [], ), false, ); @@ -124,19 +130,21 @@ impl CommandExecutor for ListExecutor { .collect::>() .join(", "); - let names_len = names.len() as i32; + let count = whitelist.len() as i32; context.source.send_feedback( - pumpkin_macros::translate_cross!( + TextComponent::translate_cross( translation::java::COMMANDS_WHITELIST_LIST, - translation::bedrock::COMMANDS_ALLOWLIST_LIST, - TextComponent::text(whitelist.len().to_string()), - TextComponent::text(names) + translation::java::COMMANDS_WHITELIST_LIST, + [ + TextComponent::text(count.to_string()), + TextComponent::text(names), + ], ), false, ); - Ok(names_len) + Ok(count) } } @@ -146,7 +154,6 @@ impl CommandExecutor for ReloadExecutor { fn execute(&self, context: &CommandContext) -> CommandExecutorResult { let server = context.source.server(); *server.data.whitelist_config.write().unwrap() = WhitelistConfig::load(); - kick_non_whitelisted_players(server); context.source.send_feedback( pumpkin_macros::translate_cross!( translation::java::COMMANDS_WHITELIST_RELOADED, @@ -154,10 +161,29 @@ impl CommandExecutor for ReloadExecutor { ), true, ); + kick_non_whitelisted_players(server); Ok(1) } } +struct AddSuggestionProvider; + +impl SuggestionProvider for AddSuggestionProvider { + fn suggest( + &self, + context: &CommandContext, + mut builder: SuggestionsBuilder, + ) -> SuggestionProviderResult { + let whitelist = context.server().data.whitelist_config.read().unwrap(); + for player in context.server().get_all_players() { + if !whitelist.is_whitelisted(&player.gameprofile) { + builder = builder.suggest(player.gameprofile.name.clone()); + } + } + builder.build() + } +} + struct AddExecutor; impl CommandExecutor for AddExecutor { @@ -166,6 +192,8 @@ impl CommandExecutor for AddExecutor { let server = context.source.server(); let mut whitelist = server.data.whitelist_config.write().unwrap(); let mut successes: i32 = 0; + let mut modified = false; + for profile in &targets { if let Some(existing_entry) = whitelist .whitelist @@ -174,6 +202,7 @@ impl CommandExecutor for AddExecutor { { if existing_entry.name != profile.name { existing_entry.name.clone_from(&profile.name); + modified = true; } continue; } @@ -189,9 +218,12 @@ impl CommandExecutor for AddExecutor { true, ); successes += 1; + modified = true; } - whitelist.save(); + if modified { + whitelist.save(); + } if successes == 0 { Err(ERROR_ADD_FAILED.create_without_context()) @@ -201,6 +233,22 @@ impl CommandExecutor for AddExecutor { } } +struct RemoveSuggestionProvider; + +impl SuggestionProvider for RemoveSuggestionProvider { + fn suggest( + &self, + context: &CommandContext, + mut builder: SuggestionsBuilder, + ) -> SuggestionProviderResult { + let whitelist = context.server().data.whitelist_config.read().unwrap(); + for entry in &whitelist.whitelist { + builder = builder.suggest(entry.name.clone()); + } + builder.build() + } +} + struct RemoveExecutor; impl CommandExecutor for RemoveExecutor { @@ -229,14 +277,14 @@ impl CommandExecutor for RemoveExecutor { } } - whitelist.save(); - drop(whitelist); - - kick_non_whitelisted_players(server); - if successes == 0 { Err(ERROR_REMOVE_FAILED.create_without_context()) } else { + whitelist.save(); + drop(whitelist); + + kick_non_whitelisted_players(server); + Ok(successes) } } @@ -255,14 +303,20 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr .then(literal("on").executes(OnExecutor)) .then(literal("off").executes(OffExecutor)) .then(literal("list").executes(ListExecutor)) - .then(literal("reload").executes(ReloadExecutor)) .then( - literal("add") - .then(argument("targets", GameProfileArgumentType).executes(AddExecutor)), + literal("add").then( + argument("targets", GameProfileArgumentType) + .suggests(AddSuggestionProvider) + .executes(AddExecutor), + ), ) .then( - literal("remove") - .then(argument("targets", GameProfileArgumentType).executes(RemoveExecutor)), - ), + literal("remove").then( + argument("targets", GameProfileArgumentType) + .suggests(RemoveSuggestionProvider) + .executes(RemoveExecutor), + ), + ) + .then(literal("reload").executes(ReloadExecutor)), ); } diff --git a/crates/pumpkin/src/server/mod.rs b/crates/pumpkin/src/server/mod.rs index d032f674e..f5e8f35a9 100644 --- a/crates/pumpkin/src/server/mod.rs +++ b/crates/pumpkin/src/server/mod.rs @@ -121,6 +121,8 @@ pub struct Server { std::sync::Mutex>, /// Global server stopwatches pub stopwatches: std::sync::Mutex, + /// Global server random sequences + pub random_sequences: std::sync::Mutex, // Manages player advancement pub advancement_manager: Arc, // Whether the server whitelist is on or off @@ -299,6 +301,9 @@ impl Server { player_data_storage, command_storage: std::sync::Mutex::new(std::collections::HashMap::new()), stopwatches: std::sync::Mutex::new(crate::world::stopwatches::Stopwatches::new()), + random_sequences: std::sync::Mutex::new( + crate::world::random_sequences::RandomSequences::new(), + ), advancement_manager, white_list, tick_rate_manager, diff --git a/crates/pumpkin/src/world/mod.rs b/crates/pumpkin/src/world/mod.rs index fdddb6c01..65560bf19 100644 --- a/crates/pumpkin/src/world/mod.rs +++ b/crates/pumpkin/src/world/mod.rs @@ -23,6 +23,7 @@ pub mod loot; pub mod map; pub mod portal; pub mod raid; +pub mod random_sequences; pub mod stopwatches; pub mod time; pub mod villager_poi; diff --git a/crates/pumpkin/src/world/random_sequences.rs b/crates/pumpkin/src/world/random_sequences.rs new file mode 100644 index 000000000..ce722d483 --- /dev/null +++ b/crates/pumpkin/src/world/random_sequences.rs @@ -0,0 +1,140 @@ +use std::collections::HashMap; +use std::hash::{DefaultHasher, Hasher}; + +use pumpkin_util::identifier::Identifier; +use pumpkin_util::random::RandomImpl; +use pumpkin_util::random::xoroshiro128::Xoroshiro; + +/// A single random sequence wrapper. +pub struct RandomSequence { + rng: Xoroshiro, +} + +impl RandomSequence { + #[must_use] + pub const fn new(seed: u64) -> Self { + Self { + rng: Xoroshiro::from_seed(seed), + } + } + + pub fn random_between_inclusive(&mut self, min: i32, max: i32) -> i32 { + if min >= max { + return min; + } + self.rng.next_inbetween_i32(min, max) + } +} + +/// Persistent/runtime manager for server random sequences. +pub struct RandomSequences { + salt: i32, + include_world_seed: bool, + include_sequence_id: bool, + sequences: HashMap, +} + +impl Default for RandomSequences { + fn default() -> Self { + Self::new() + } +} + +impl RandomSequences { + #[must_use] + pub fn new() -> Self { + Self { + salt: 0, + include_world_seed: true, + include_sequence_id: true, + sequences: HashMap::new(), + } + } + + fn calculate_seed( + sequence: &Identifier, + world_seed: i64, + salt: i32, + include_world_seed: bool, + include_sequence_id: bool, + ) -> u64 { + let base_seed = if include_world_seed { world_seed } else { 0 }; + let mut seed = (base_seed ^ i64::from(salt)) as u64; + if include_sequence_id { + let mut hasher = DefaultHasher::new(); + hasher.write(sequence.to_string().as_bytes()); + seed ^= hasher.finish(); + } + seed + } + + pub fn get_or_create(&mut self, sequence: &Identifier, world_seed: i64) -> &mut RandomSequence { + let key = sequence.to_string(); + let salt = self.salt; + let include_world_seed = self.include_world_seed; + let include_sequence_id = self.include_sequence_id; + self.sequences.entry(key).or_insert_with(|| { + let seed = Self::calculate_seed( + sequence, + world_seed, + salt, + include_world_seed, + include_sequence_id, + ); + RandomSequence::new(seed) + }) + } + + pub fn reset(&mut self, sequence: &Identifier, world_seed: i64) { + let key = sequence.to_string(); + let seed = Self::calculate_seed( + sequence, + world_seed, + self.salt, + self.include_world_seed, + self.include_sequence_id, + ); + self.sequences.insert(key, RandomSequence::new(seed)); + } + + pub fn reset_with_options( + &mut self, + sequence: &Identifier, + world_seed: i64, + salt: i32, + include_world_seed: bool, + include_sequence_id: bool, + ) { + let key = sequence.to_string(); + let seed = Self::calculate_seed( + sequence, + world_seed, + salt, + include_world_seed, + include_sequence_id, + ); + self.sequences.insert(key, RandomSequence::new(seed)); + } + + pub fn clear(&mut self) -> usize { + let count = self.sequences.len(); + self.sequences.clear(); + count + } + + pub const fn set_seed_defaults( + &mut self, + salt: i32, + include_world_seed: bool, + include_sequence_id: bool, + ) { + self.salt = salt; + self.include_world_seed = include_world_seed; + self.include_sequence_id = include_sequence_id; + } + + #[must_use] + pub fn get_sequence_keys(&self) -> Vec { + self.sequences.keys().cloned().collect() + } +}