use std::io::Write; use anyhow::bail; use crate::block::BlockState; use crate::block_pos::BlockPos; use crate::item::ItemStack; use crate::{Decode, DecodePacket, Encode, EncodePacket, VarInt}; #[derive(Clone, Debug, EncodePacket, DecodePacket)] #[packet_id = 0x22] pub struct ParticleS2c { pub particle: Particle, pub long_distance: bool, pub position: [f64; 3], pub offset: [f32; 3], pub max_speed: f32, pub count: i32, } #[derive(Clone, Debug)] pub enum Particle { AmbientEntityEffect, AngryVillager, Block(BlockState), BlockMarker(BlockState), Bubble, Cloud, Crit, DamageIndicator, DragonBreath, DrippingLava, FallingLava, LandingLava, DrippingWater, FallingWater, Dust { rgb: [f32; 3], scale: f32, }, DustColorTransition { from_rgb: [f32; 3], scale: f32, to_rgb: [f32; 3], }, Effect, ElderGuardian, EnchantedHit, Enchant, EndRod, EntityEffect, ExplosionEmitter, Explosion, SonicBoom, FallingDust(BlockState), Firework, Fishing, Flame, SculkSoul, SculkCharge { roll: f32, }, SculkChargePop, SoulFireFlame, Soul, Flash, HappyVillager, Composter, Heart, InstantEffect, Item(Option), /// The 'Block' variant of the 'Vibration' particle VibrationBlock { block_pos: BlockPos, ticks: i32, }, /// The 'Entity' variant of the 'Vibration' particle VibrationEntity { entity_id: i32, entity_eye_height: f32, ticks: i32, }, ItemSlime, ItemSnowball, LargeSmoke, Lava, Mycelium, Note, Poof, Portal, Rain, Smoke, Sneeze, Spit, SquidInk, SweepAttack, TotemOfUndying, Underwater, Splash, Witch, BubblePop, CurrentDown, BubbleColumnUp, Nautilus, Dolphin, CampfireCosySmoke, CampfireSignalSmoke, DrippingHoney, FallingHoney, LandingHoney, FallingNectar, FallingSporeBlossom, Ash, CrimsonSpore, WarpedSpore, SporeBlossomAir, DrippingObsidianTear, FallingObsidianTear, LandingObsidianTear, ReversePortal, WhiteAsh, SmallFlame, Snowflake, DrippingDripstoneLava, FallingDripstoneLava, DrippingDripstoneWater, FallingDripstoneWater, GlowSquidInk, Glow, WaxOn, WaxOff, ElectricSpark, Scrape, } impl Particle { pub const fn id(&self) -> i32 { match self { Particle::AmbientEntityEffect => 0, Particle::AngryVillager => 1, Particle::Block(_) => 2, Particle::BlockMarker(_) => 3, Particle::Bubble => 4, Particle::Cloud => 5, Particle::Crit => 6, Particle::DamageIndicator => 7, Particle::DragonBreath => 8, Particle::DrippingLava => 9, Particle::FallingLava => 10, Particle::LandingLava => 11, Particle::DrippingWater => 12, Particle::FallingWater => 13, Particle::Dust { .. } => 14, Particle::DustColorTransition { .. } => 15, Particle::Effect => 16, Particle::ElderGuardian => 17, Particle::EnchantedHit => 18, Particle::Enchant => 19, Particle::EndRod => 20, Particle::EntityEffect => 21, Particle::ExplosionEmitter => 22, Particle::Explosion => 23, Particle::SonicBoom => 24, Particle::FallingDust(_) => 25, Particle::Firework => 26, Particle::Fishing => 27, Particle::Flame => 28, Particle::SculkSoul => 29, Particle::SculkCharge { .. } => 30, Particle::SculkChargePop => 31, Particle::SoulFireFlame => 32, Particle::Soul => 33, Particle::Flash => 34, Particle::HappyVillager => 35, Particle::Composter => 36, Particle::Heart => 37, Particle::InstantEffect => 38, Particle::Item(_) => 39, Particle::VibrationBlock { .. } => 40, Particle::VibrationEntity { .. } => 40, Particle::ItemSlime => 41, Particle::ItemSnowball => 42, Particle::LargeSmoke => 43, Particle::Lava => 44, Particle::Mycelium => 45, Particle::Note => 46, Particle::Poof => 47, Particle::Portal => 48, Particle::Rain => 49, Particle::Smoke => 50, Particle::Sneeze => 51, Particle::Spit => 52, Particle::SquidInk => 53, Particle::SweepAttack => 54, Particle::TotemOfUndying => 55, Particle::Underwater => 56, Particle::Splash => 57, Particle::Witch => 58, Particle::BubblePop => 59, Particle::CurrentDown => 60, Particle::BubbleColumnUp => 61, Particle::Nautilus => 62, Particle::Dolphin => 63, Particle::CampfireCosySmoke => 64, Particle::CampfireSignalSmoke => 65, Particle::DrippingHoney => 66, Particle::FallingHoney => 67, Particle::LandingHoney => 68, Particle::FallingNectar => 69, Particle::FallingSporeBlossom => 70, Particle::Ash => 71, Particle::CrimsonSpore => 72, Particle::WarpedSpore => 73, Particle::SporeBlossomAir => 74, Particle::DrippingObsidianTear => 75, Particle::FallingObsidianTear => 76, Particle::LandingObsidianTear => 77, Particle::ReversePortal => 78, Particle::WhiteAsh => 79, Particle::SmallFlame => 80, Particle::Snowflake => 81, Particle::DrippingDripstoneLava => 82, Particle::FallingDripstoneLava => 83, Particle::DrippingDripstoneWater => 84, Particle::FallingDripstoneWater => 85, Particle::GlowSquidInk => 86, Particle::Glow => 87, Particle::WaxOn => 88, Particle::WaxOff => 89, Particle::ElectricSpark => 90, Particle::Scrape => 91, } } } impl Encode for ParticleS2c { fn encode(&self, mut w: impl Write) -> anyhow::Result<()> { VarInt(self.particle.id()).encode(&mut w)?; self.long_distance.encode(&mut w)?; self.position.encode(&mut w)?; self.offset.encode(&mut w)?; self.max_speed.encode(&mut w)?; self.count.encode(&mut w)?; match &self.particle { Particle::Block(block_state) => block_state.encode(w), Particle::BlockMarker(block_state) => block_state.encode(w), Particle::Dust { rgb, scale } => { rgb.encode(&mut w)?; scale.encode(w) } Particle::DustColorTransition { from_rgb, scale, to_rgb, } => { from_rgb.encode(&mut w)?; scale.encode(&mut w)?; to_rgb.encode(w) } Particle::FallingDust(block_state) => block_state.encode(w), Particle::SculkCharge { roll } => roll.encode(w), Particle::Item(stack) => stack.encode(w), Particle::VibrationBlock { block_pos, ticks } => { "block".encode(&mut w)?; block_pos.encode(&mut w)?; VarInt(*ticks).encode(w) } Particle::VibrationEntity { entity_id, entity_eye_height, ticks, } => { "entity".encode(&mut w)?; VarInt(*entity_id).encode(&mut w)?; entity_eye_height.encode(&mut w)?; VarInt(*ticks).encode(w) } _ => Ok(()), } } } impl<'a> Decode<'a> for ParticleS2c { fn decode(r: &mut &'a [u8]) -> anyhow::Result { let particle_id = VarInt::decode(r)?.0; let long_distance = bool::decode(r)?; let position = <[f64; 3]>::decode(r)?; let offset = <[f32; 3]>::decode(r)?; let max_speed = f32::decode(r)?; let particle_count = i32::decode(r)?; Ok(Self { particle: match particle_id { 0 => Particle::AmbientEntityEffect, 1 => Particle::AngryVillager, 2 => Particle::Block(BlockState::decode(r)?), 3 => Particle::BlockMarker(BlockState::decode(r)?), 4 => Particle::Bubble, 5 => Particle::Cloud, 6 => Particle::Crit, 7 => Particle::DamageIndicator, 8 => Particle::DragonBreath, 9 => Particle::DrippingLava, 10 => Particle::FallingLava, 11 => Particle::LandingLava, 12 => Particle::DrippingWater, 13 => Particle::FallingWater, 14 => Particle::Dust { rgb: <[f32; 3]>::decode(r)?, scale: f32::decode(r)?, }, 15 => Particle::DustColorTransition { from_rgb: <[f32; 3]>::decode(r)?, scale: f32::decode(r)?, to_rgb: <[f32; 3]>::decode(r)?, }, 16 => Particle::Effect, 17 => Particle::ElderGuardian, 18 => Particle::EnchantedHit, 19 => Particle::Enchant, 20 => Particle::EndRod, 21 => Particle::EntityEffect, 22 => Particle::ExplosionEmitter, 23 => Particle::Explosion, 24 => Particle::SonicBoom, 25 => Particle::FallingDust(BlockState::decode(r)?), 26 => Particle::Firework, 27 => Particle::Fishing, 28 => Particle::Flame, 29 => Particle::SculkSoul, 30 => Particle::SculkCharge { roll: f32::decode(r)?, }, 31 => Particle::SculkChargePop, 32 => Particle::SoulFireFlame, 33 => Particle::Soul, 34 => Particle::Flash, 35 => Particle::HappyVillager, 36 => Particle::Composter, 37 => Particle::Heart, 38 => Particle::InstantEffect, 39 => Particle::Item(Decode::decode(r)?), 40 => match <&str>::decode(r)? { "block" => Particle::VibrationBlock { block_pos: BlockPos::decode(r)?, ticks: VarInt::decode(r)?.0, }, "entity" => Particle::VibrationEntity { entity_id: VarInt::decode(r)?.0, entity_eye_height: f32::decode(r)?, ticks: VarInt::decode(r)?.0, }, invalid => bail!("invalid vibration position source of \"{invalid}\""), }, 41 => Particle::ItemSlime, 42 => Particle::ItemSnowball, 43 => Particle::LargeSmoke, 44 => Particle::Lava, 45 => Particle::Mycelium, 46 => Particle::Note, 47 => Particle::Poof, 48 => Particle::Portal, 49 => Particle::Rain, 50 => Particle::Smoke, 51 => Particle::Sneeze, 52 => Particle::Spit, 53 => Particle::SquidInk, 54 => Particle::SweepAttack, 55 => Particle::TotemOfUndying, 56 => Particle::Underwater, 57 => Particle::Splash, 58 => Particle::Witch, 59 => Particle::BubblePop, 60 => Particle::CurrentDown, 61 => Particle::BubbleColumnUp, 62 => Particle::Nautilus, 63 => Particle::Dolphin, 64 => Particle::CampfireCosySmoke, 65 => Particle::CampfireSignalSmoke, 66 => Particle::DrippingHoney, 67 => Particle::FallingHoney, 68 => Particle::LandingHoney, 69 => Particle::FallingNectar, 70 => Particle::FallingSporeBlossom, 71 => Particle::Ash, 72 => Particle::CrimsonSpore, 73 => Particle::WarpedSpore, 74 => Particle::SporeBlossomAir, 75 => Particle::DrippingObsidianTear, 76 => Particle::FallingObsidianTear, 77 => Particle::LandingObsidianTear, 78 => Particle::ReversePortal, 79 => Particle::WhiteAsh, 80 => Particle::SmallFlame, 81 => Particle::Snowflake, 82 => Particle::DrippingDripstoneLava, 83 => Particle::FallingDripstoneLava, 84 => Particle::DrippingDripstoneWater, 85 => Particle::FallingDripstoneWater, 86 => Particle::GlowSquidInk, 87 => Particle::Glow, 88 => Particle::WaxOn, 89 => Particle::WaxOff, 90 => Particle::ElectricSpark, 91 => Particle::Scrape, id => bail!("invalid particle ID of {id}"), }, long_distance, position, offset, max_speed, count: particle_count, }) } }