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
|
- [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
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();
|
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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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::{
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
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.
|
//! 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());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue