mirror of
https://github.com/italicsjenga/valence.git
synced 2024-12-23 22:41:30 +11:00
Add Particles (#150)
Implements the clientbound particle packet. Co-authored-by: Ryan Johnson <ryanj00a@gmail.com>
This commit is contained in:
parent
5d3364d452
commit
295678e92d
|
@ -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
|
||||
generate Rust code for the project. The JSON files can be used in other projects as well.
|
||||
- [x] Items
|
||||
- [x] Particles
|
||||
- [ ] Inventory
|
||||
- [ ] Block entities
|
||||
- [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
|
||||
|
||||
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
312
examples/particles.rs
Normal 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,
|
||||
]
|
||||
}
|
|
@ -85,7 +85,11 @@ impl State {
|
|||
self.buf.clear();
|
||||
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 !r.is_match(packet_name) {
|
||||
|
|
|
@ -22,6 +22,7 @@ use valence_protocol::packets::s2c::play::{
|
|||
SetSubtitleText, SetTitleAnimationTimes, SetTitleText, SynchronizePlayerPosition,
|
||||
SystemChatMessage, UnloadChunk, UpdateAttributes, UpdateTime,
|
||||
};
|
||||
use valence_protocol::particle::{Particle, ParticleS2c};
|
||||
use valence_protocol::types::{
|
||||
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.
|
||||
///
|
||||
/// A title is a large piece of text displayed in the center of the screen
|
||||
|
|
|
@ -136,6 +136,7 @@ pub mod prelude {
|
|||
pub use valence_protocol::entity_meta::Pose;
|
||||
pub use valence_protocol::ident::IdentError;
|
||||
pub use valence_protocol::packets::s2c::play::SetTitleAnimationTimes;
|
||||
pub use valence_protocol::particle::Particle;
|
||||
pub use valence_protocol::text::Color;
|
||||
pub use valence_protocol::types::{GameMode, Hand, SoundCategory};
|
||||
pub use valence_protocol::{
|
||||
|
|
|
@ -109,6 +109,7 @@ mod impls;
|
|||
mod inventory;
|
||||
mod item;
|
||||
pub mod packets;
|
||||
pub mod particle;
|
||||
pub mod player_list;
|
||||
mod raw_bytes;
|
||||
pub mod text;
|
||||
|
|
|
@ -95,6 +95,7 @@ pub mod login {
|
|||
|
||||
pub mod play {
|
||||
use super::*;
|
||||
pub use crate::particle::ParticleS2c;
|
||||
pub use crate::player_list::PlayerInfoUpdate;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, EncodePacket, Decode, DecodePacket)]
|
||||
|
@ -332,18 +333,6 @@ pub mod play {
|
|||
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)]
|
||||
#[packet_id = 0x24]
|
||||
pub struct LoginPlay<'a> {
|
||||
|
@ -711,7 +700,7 @@ pub mod play {
|
|||
WorldBorderInitialize,
|
||||
KeepAliveS2c,
|
||||
ChunkDataAndUpdateLight<'a>,
|
||||
ParticleS2c<'a>,
|
||||
ParticleS2c,
|
||||
LoginPlay<'a>,
|
||||
UpdateEntityPosition,
|
||||
UpdateEntityPositionAndRotation,
|
||||
|
|
416
valence_protocol/src/particle.rs
Normal file
416
valence_protocol/src/particle.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
//! Formatted text.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::io::Write;
|
||||
use std::{fmt, ops};
|
||||
|
||||
use serde::de::Visitor;
|
||||
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>> std::ops::Add<T> for Text {
|
||||
impl<T: Into<Text>> ops::Add<T> for Text {
|
||||
type Output = Self;
|
||||
|
||||
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) {
|
||||
self.0.extra.push(rhs.into());
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue