Add Particles (#150)

Implements the clientbound particle packet.

Co-authored-by: Ryan Johnson <ryanj00a@gmail.com>
This commit is contained in:
jivvy 2022-12-29 02:26:19 -06:00 committed by GitHub
parent 5d3364d452
commit 295678e92d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 761 additions and 18 deletions

View file

@ -41,10 +41,10 @@ place. Here are some noteworthy achievements:
- [x] A Fabric mod for extracting data from the game into JSON files. These files are processed by a build script to - [x] A Fabric mod for extracting data from the game into JSON files. These files are processed by a build script to
generate Rust code for the project. The JSON files can be used in other projects as well. generate Rust code for the project. The JSON files can be used in other projects as well.
- [x] Items - [x] Items
- [x] Particles
- [ ] Inventory - [ ] Inventory
- [ ] Block entities - [ ] Block entities
- [x] Proxy support ([Velocity](https://velocitypowered.com/), [Bungeecord](https://www.spigotmc.org/wiki/bungeecord/) and [Waterfall](https://docs.papermc.io/waterfall)) - [x] Proxy support ([Velocity](https://velocitypowered.com/), [Bungeecord](https://www.spigotmc.org/wiki/bungeecord/) and [Waterfall](https://docs.papermc.io/waterfall))
- [ ] Sounds, particles, etc.
- [ ] Utilities for continuous collision detection - [ ] Utilities for continuous collision detection
Here is a [short video](https://www.youtube.com/watch?v=6P072lKE01s) showing the examples and some of its current Here is a [short video](https://www.youtube.com/watch?v=6P072lKE01s) showing the examples and some of its current

312
examples/particles.rs Normal file
View file

@ -0,0 +1,312 @@
use std::fmt;
use std::net::SocketAddr;
use std::sync::atomic::{AtomicUsize, Ordering};
use valence::prelude::*;
pub fn main() -> ShutdownResult {
tracing_subscriber::fmt().init();
valence::start_server(
Game {
player_count: AtomicUsize::new(0),
},
ServerState {
player_list: None,
particle_list: create_particle_vec(),
particle_idx: 0,
},
)
}
struct Game {
player_count: AtomicUsize,
}
struct ServerState {
player_list: Option<PlayerListId>,
particle_list: Vec<Particle>,
particle_idx: usize,
}
const MAX_PLAYERS: usize = 10;
const SPAWN_POS: BlockPos = BlockPos::new(0, 100, 0);
#[async_trait]
impl Config for Game {
type ServerState = ServerState;
type ClientState = EntityId;
type EntityState = ();
type WorldState = ();
type ChunkState = ();
type PlayerListState = ();
type InventoryState = ();
async fn server_list_ping(
&self,
_server: &SharedServer<Self>,
_remote_addr: SocketAddr,
_protocol_version: i32,
) -> ServerListPing {
ServerListPing::Respond {
online_players: self.player_count.load(Ordering::SeqCst) as i32,
max_players: MAX_PLAYERS as i32,
player_sample: Default::default(),
description: "Hello Valence!".color(Color::AQUA),
favicon_png: Some(include_bytes!("../assets/logo-64x64.png").as_slice().into()),
}
}
fn init(&self, server: &mut Server<Self>) {
let (_, world) = server.worlds.insert(DimensionId::default(), ());
server.state.player_list = Some(server.player_lists.insert(()).0);
let size = 5;
for z in -size..size {
for x in -size..size {
world.chunks.insert([x, z], UnloadedChunk::default(), ());
}
}
world.chunks.set_block_state(SPAWN_POS, BlockState::BEDROCK);
}
fn update(&self, server: &mut Server<Self>) {
let (world_id, _) = server.worlds.iter_mut().next().expect("missing world");
server.clients.retain(|_, client| {
if client.created_this_tick() {
if self
.player_count
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
(count < MAX_PLAYERS).then_some(count + 1)
})
.is_err()
{
client.disconnect("The server is full!".color(Color::RED));
return false;
}
match server
.entities
.insert_with_uuid(EntityKind::Player, client.uuid(), ())
{
Some((id, _)) => client.state = id,
None => {
client.disconnect("Conflicting UUID");
return false;
}
}
client.respawn(world_id);
client.set_flat(true);
client.set_game_mode(GameMode::Creative);
client.teleport(
[
SPAWN_POS.x as f64 + 0.5,
SPAWN_POS.y as f64 + 1.0,
SPAWN_POS.z as f64 + 0.5,
],
0.0,
0.0,
);
client.set_player_list(server.state.player_list.clone());
client.send_message("Sneak to speed up the cycling of particles");
if let Some(id) = &server.state.player_list {
server.player_lists.get_mut(id).insert(
client.uuid(),
client.username(),
client.textures().cloned(),
client.game_mode(),
0,
None,
true,
);
}
}
if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst);
if let Some(id) = &server.state.player_list {
server.player_lists[id].remove(client.uuid());
}
server.entities[client.state].set_deleted(true);
return false;
}
let entity = server
.entities
.get_mut(client.state)
.expect("missing player entity");
while let Some(event) = client.next_event() {
event.handle_default(client, entity);
}
true
});
let players_are_sneaking = server.clients.iter().any(|(_, client)| -> bool {
let player = &server.entities[client.state];
if let TrackedData::Player(data) = player.data() {
return data.get_pose() == Pose::Sneaking;
}
false
});
let cycle_time = if players_are_sneaking { 5 } else { 30 };
if !server.clients.is_empty() && server.current_tick() % cycle_time == 0 {
if server.state.particle_idx == server.state.particle_list.len() {
server.state.particle_idx = 0;
}
let pos = [
SPAWN_POS.x as f64 + 0.5,
SPAWN_POS.y as f64 + 2.0,
SPAWN_POS.z as f64 + 5.5,
];
let offset = [0.5, 0.5, 0.5];
let particle = &server.state.particle_list[server.state.particle_idx];
server.clients.iter_mut().for_each(|(_, client)| {
client.set_title(
"",
dbg_name(particle).bold(),
SetTitleAnimationTimes {
fade_in: 0,
stay: 100,
fade_out: 2,
},
);
client.play_particle(particle, true, pos, offset, 0.1, 100);
});
server.state.particle_idx += 1;
}
}
}
fn dbg_name(dbg: &impl fmt::Debug) -> String {
let string = format!("{dbg:?}");
string
.split_once(|ch: char| !ch.is_ascii_alphabetic())
.map(|(fst, _)| fst.to_owned())
.unwrap_or(string)
}
fn create_particle_vec() -> Vec<Particle> {
vec![
Particle::AmbientEntityEffect,
Particle::AngryVillager,
Particle::Block(BlockState::OAK_PLANKS),
Particle::BlockMarker(BlockState::GOLD_BLOCK),
Particle::Bubble,
Particle::Cloud,
Particle::Crit,
Particle::DamageIndicator,
Particle::DragonBreath,
Particle::DrippingLava,
Particle::FallingLava,
Particle::LandingLava,
Particle::DrippingWater,
Particle::FallingWater,
Particle::Dust {
rgb: [1.0, 1.0, 0.0],
scale: 2.0,
},
Particle::DustColorTransition {
from_rgb: [1.0, 0.0, 0.0],
scale: 2.0,
to_rgb: [0.0, 1.0, 0.0],
},
Particle::Effect,
Particle::ElderGuardian,
Particle::EnchantedHit,
Particle::Enchant,
Particle::EndRod,
Particle::EntityEffect,
Particle::ExplosionEmitter,
Particle::Explosion,
Particle::SonicBoom,
Particle::FallingDust(BlockState::RED_SAND),
Particle::Firework,
Particle::Fishing,
Particle::Flame,
Particle::SculkSoul,
Particle::SculkCharge { roll: 1.0 },
Particle::SculkChargePop,
Particle::SoulFireFlame,
Particle::Soul,
Particle::Flash,
Particle::HappyVillager,
Particle::Composter,
Particle::Heart,
Particle::InstantEffect,
Particle::Item(None),
Particle::Item(Some(ItemStack::new(ItemKind::IronPickaxe, 1, None))),
Particle::VibrationBlock {
block_pos: SPAWN_POS,
ticks: 50,
},
Particle::VibrationEntity {
entity_id: 0,
entity_eye_height: 1.0,
ticks: 50,
},
Particle::ItemSlime,
Particle::ItemSnowball,
Particle::LargeSmoke,
Particle::Lava,
Particle::Mycelium,
Particle::Note,
Particle::Poof,
Particle::Portal,
Particle::Rain,
Particle::Smoke,
Particle::Sneeze,
Particle::Spit,
Particle::SquidInk,
Particle::SweepAttack,
Particle::TotemOfUndying,
Particle::Underwater,
Particle::Splash,
Particle::Witch,
Particle::BubblePop,
Particle::CurrentDown,
Particle::BubbleColumnUp,
Particle::Nautilus,
Particle::Dolphin,
Particle::CampfireCosySmoke,
Particle::CampfireSignalSmoke,
Particle::DrippingHoney,
Particle::FallingHoney,
Particle::LandingHoney,
Particle::FallingNectar,
Particle::FallingSporeBlossom,
Particle::Ash,
Particle::CrimsonSpore,
Particle::WarpedSpore,
Particle::SporeBlossomAir,
Particle::DrippingObsidianTear,
Particle::FallingObsidianTear,
Particle::LandingObsidianTear,
Particle::ReversePortal,
Particle::WhiteAsh,
Particle::SmallFlame,
Particle::Snowflake,
Particle::DrippingDripstoneLava,
Particle::FallingDripstoneLava,
Particle::DrippingDripstoneWater,
Particle::FallingDripstoneWater,
Particle::GlowSquidInk,
Particle::Glow,
Particle::WaxOn,
Particle::WaxOff,
Particle::ElectricSpark,
Particle::Scrape,
]
}

View file

@ -85,7 +85,11 @@ impl State {
self.buf.clear(); self.buf.clear();
write!(&mut self.buf, "{pkt:?}")?; write!(&mut self.buf, "{pkt:?}")?;
let packet_name = self.buf.split_ascii_whitespace().next().unwrap(); let packet_name = self
.buf
.split_once(|ch: char| !ch.is_ascii_alphabetic())
.map(|(fst, _)| fst)
.unwrap_or(&self.buf);
if let Some(r) = &self.cli.include_regex { if let Some(r) = &self.cli.include_regex {
if !r.is_match(packet_name) { if !r.is_match(packet_name) {

View file

@ -22,6 +22,7 @@ use valence_protocol::packets::s2c::play::{
SetSubtitleText, SetTitleAnimationTimes, SetTitleText, SynchronizePlayerPosition, SetSubtitleText, SetTitleAnimationTimes, SetTitleText, SynchronizePlayerPosition,
SystemChatMessage, UnloadChunk, UpdateAttributes, UpdateTime, SystemChatMessage, UnloadChunk, UpdateAttributes, UpdateTime,
}; };
use valence_protocol::particle::{Particle, ParticleS2c};
use valence_protocol::types::{ use valence_protocol::types::{
AttributeProperty, DisplayedSkinParts, GameMode, GameStateChangeReason, SyncPlayerPosLookFlags, AttributeProperty, DisplayedSkinParts, GameMode, GameStateChangeReason, SyncPlayerPosLookFlags,
}; };
@ -575,6 +576,25 @@ impl<C: Config> Client<C> {
}); });
} }
pub fn play_particle(
&mut self,
particle: &Particle,
long_distance: bool,
position: impl Into<Vec3<f64>>,
offset: impl Into<Vec3<f32>>,
max_speed: f32,
count: i32,
) {
self.queue_packet(&ParticleS2c {
particle: particle.clone(),
long_distance,
position: position.into().into_array(),
offset: offset.into().into_array(),
max_speed,
count,
})
}
/// Sets the title this client sees. /// Sets the title this client sees.
/// ///
/// A title is a large piece of text displayed in the center of the screen /// A title is a large piece of text displayed in the center of the screen

View file

@ -136,6 +136,7 @@ pub mod prelude {
pub use valence_protocol::entity_meta::Pose; pub use valence_protocol::entity_meta::Pose;
pub use valence_protocol::ident::IdentError; pub use valence_protocol::ident::IdentError;
pub use valence_protocol::packets::s2c::play::SetTitleAnimationTimes; pub use valence_protocol::packets::s2c::play::SetTitleAnimationTimes;
pub use valence_protocol::particle::Particle;
pub use valence_protocol::text::Color; pub use valence_protocol::text::Color;
pub use valence_protocol::types::{GameMode, Hand, SoundCategory}; pub use valence_protocol::types::{GameMode, Hand, SoundCategory};
pub use valence_protocol::{ pub use valence_protocol::{

View file

@ -109,6 +109,7 @@ mod impls;
mod inventory; mod inventory;
mod item; mod item;
pub mod packets; pub mod packets;
pub mod particle;
pub mod player_list; pub mod player_list;
mod raw_bytes; mod raw_bytes;
pub mod text; pub mod text;

View file

@ -95,6 +95,7 @@ pub mod login {
pub mod play { pub mod play {
use super::*; use super::*;
pub use crate::particle::ParticleS2c;
pub use crate::player_list::PlayerInfoUpdate; pub use crate::player_list::PlayerInfoUpdate;
#[derive(Copy, Clone, Debug, Encode, EncodePacket, Decode, DecodePacket)] #[derive(Copy, Clone, Debug, Encode, EncodePacket, Decode, DecodePacket)]
@ -332,18 +333,6 @@ pub mod play {
pub block_light_arrays: &'a [LengthPrefixedArray<u8, 2048>], pub block_light_arrays: &'a [LengthPrefixedArray<u8, 2048>],
} }
#[derive(Copy, Clone, Debug, Encode, EncodePacket, Decode, DecodePacket)]
#[packet_id = 0x22]
pub struct ParticleS2c<'a> {
pub particle_id: VarInt,
pub long_distance: bool,
pub position: [f64; 3],
pub offset: [f32; 3],
pub max_speed: f32,
pub count: i32,
pub data: RawBytes<'a>,
}
#[derive(Clone, Debug, Encode, EncodePacket, Decode, DecodePacket)] #[derive(Clone, Debug, Encode, EncodePacket, Decode, DecodePacket)]
#[packet_id = 0x24] #[packet_id = 0x24]
pub struct LoginPlay<'a> { pub struct LoginPlay<'a> {
@ -711,7 +700,7 @@ pub mod play {
WorldBorderInitialize, WorldBorderInitialize,
KeepAliveS2c, KeepAliveS2c,
ChunkDataAndUpdateLight<'a>, ChunkDataAndUpdateLight<'a>,
ParticleS2c<'a>, ParticleS2c,
LoginPlay<'a>, LoginPlay<'a>,
UpdateEntityPosition, UpdateEntityPosition,
UpdateEntityPositionAndRotation, UpdateEntityPositionAndRotation,

View file

@ -0,0 +1,416 @@
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<ItemStack>),
/// 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<Self> {
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,
})
}
}

View file

@ -1,8 +1,8 @@
//! Formatted text. //! Formatted text.
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt;
use std::io::Write; use std::io::Write;
use std::{fmt, ops};
use serde::de::Visitor; use serde::de::Visitor;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
@ -687,7 +687,7 @@ pub trait TextFormat: Into<Text> {
impl<T: Into<Text>> TextFormat for T {} impl<T: Into<Text>> TextFormat for T {}
impl<T: Into<Text>> std::ops::Add<T> for Text { impl<T: Into<Text>> ops::Add<T> for Text {
type Output = Self; type Output = Self;
fn add(self, rhs: T) -> Self::Output { fn add(self, rhs: T) -> Self::Output {
@ -695,7 +695,7 @@ impl<T: Into<Text>> std::ops::Add<T> for Text {
} }
} }
impl<T: Into<Text>> std::ops::AddAssign<T> for Text { impl<T: Into<Text>> ops::AddAssign<T> for Text {
fn add_assign(&mut self, rhs: T) { fn add_assign(&mut self, rhs: T) {
self.0.extra.push(rhs.into()); self.0.extra.push(rhs.into());
} }