Add Particle command
This commit is contained in:
parent
af0573d9b8
commit
06334ec658
@ -55,12 +55,15 @@ pub(crate) fn build() -> TokenStream {
|
||||
.expect("Failed to parse damage_type.json");
|
||||
|
||||
let mut constants = Vec::new();
|
||||
let mut enum_variants = Vec::new();
|
||||
let mut type_from_name = TokenStream::new();
|
||||
|
||||
for (name, entry) in damage_types {
|
||||
let const_ident = format_ident!("{}", name.to_shouty_snake_case());
|
||||
let resource_name = name.to_lowercase();
|
||||
|
||||
enum_variants.push(const_ident.clone());
|
||||
type_from_name.extend(quote! {
|
||||
#resource_name => Some(Self::#const_ident),
|
||||
});
|
||||
|
||||
let data = &entry.components;
|
||||
let death_message_type = match &data.death_message_type {
|
||||
@ -100,15 +103,6 @@ pub(crate) fn build() -> TokenStream {
|
||||
});
|
||||
}
|
||||
|
||||
let type_name_pairs = enum_variants.iter().map(|variant| {
|
||||
let name = variant.to_string();
|
||||
let name_lowercase = name.to_lowercase();
|
||||
let resource_name = format!("minecraft:{}", name_lowercase);
|
||||
quote! {
|
||||
#resource_name => Some(Self::#variant)
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct DamageType {
|
||||
@ -150,7 +144,7 @@ pub(crate) fn build() -> TokenStream {
|
||||
#[doc = r" Try to parse a damage type from a resource location string"]
|
||||
pub fn from_name(name: &str) -> Option<Self> {
|
||||
match name {
|
||||
#(#type_name_pairs,)*
|
||||
#type_from_name
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
use heck::ToPascalCase;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use quote::{format_ident, quote};
|
||||
|
||||
use crate::array_to_tokenstream;
|
||||
|
||||
@ -9,11 +10,47 @@ pub(crate) fn build() -> TokenStream {
|
||||
let particle: Vec<String> = serde_json::from_str(include_str!("../../assets/particles.json"))
|
||||
.expect("Failed to parse particles.json");
|
||||
let variants = array_to_tokenstream(&particle);
|
||||
let type_from_name = &particle
|
||||
.iter()
|
||||
.map(|particle| {
|
||||
let id = &particle;
|
||||
let name = format_ident!("{}", particle.to_pascal_case());
|
||||
|
||||
quote! {
|
||||
#id => Some(Self::#name),
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
let type_to_name = &particle
|
||||
.iter()
|
||||
.map(|particle| {
|
||||
let id = &particle;
|
||||
let name = format_ident!("{}", particle.to_pascal_case());
|
||||
|
||||
quote! {
|
||||
Self::#name => #id,
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
quote! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Particle {
|
||||
#variants
|
||||
}
|
||||
|
||||
impl Particle {
|
||||
#[doc = r" Try to parse Particle from a resource location string"]
|
||||
pub fn from_name(name: &str) -> Option<Self> {
|
||||
match name {
|
||||
#type_from_name
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
pub const fn to_name(&self) -> &'static str {
|
||||
match self {
|
||||
#type_to_name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,10 +8,11 @@ use crate::VarInt;
|
||||
#[derive(Serialize)]
|
||||
#[client_packet(PLAY_LEVEL_PARTICLES)]
|
||||
pub struct CParticle<'a> {
|
||||
force_spawn: bool,
|
||||
/// If true, particle distance increases from 256 to 65536.
|
||||
long_distance: bool,
|
||||
important: bool,
|
||||
position: Vector3<f64>,
|
||||
offset: Vector3<f64>,
|
||||
offset: Vector3<f32>,
|
||||
max_speed: f32,
|
||||
particle_count: i32,
|
||||
pariticle_id: VarInt,
|
||||
@ -19,17 +20,20 @@ pub struct CParticle<'a> {
|
||||
}
|
||||
|
||||
impl<'a> CParticle<'a> {
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
long_distance: bool,
|
||||
force_spawn: bool,
|
||||
important: bool,
|
||||
position: Vector3<f64>,
|
||||
offset: Vector3<f64>,
|
||||
offset: Vector3<f32>,
|
||||
max_speed: f32,
|
||||
particle_count: i32,
|
||||
pariticle_id: VarInt,
|
||||
data: &'a [u8],
|
||||
) -> Self {
|
||||
Self {
|
||||
long_distance,
|
||||
force_spawn,
|
||||
important,
|
||||
position,
|
||||
offset,
|
||||
max_speed,
|
||||
|
||||
@ -52,7 +52,7 @@ impl ArgumentConsumer for DamageTypeArgumentConsumer {
|
||||
|
||||
impl DefaultNameArgConsumer for DamageTypeArgumentConsumer {
|
||||
fn default_name(&self) -> &'static str {
|
||||
"damageType"
|
||||
"damage_type"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ use std::{collections::HashMap, hash::Hash, sync::Arc};
|
||||
use async_trait::async_trait;
|
||||
use bounded_num::{NotInBounds, Number};
|
||||
use pumpkin_data::damage::DamageType;
|
||||
use pumpkin_data::particle::Particle;
|
||||
use pumpkin_data::sound::SoundCategory;
|
||||
use pumpkin_protocol::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders};
|
||||
use pumpkin_util::text::TextComponent;
|
||||
@ -32,6 +33,7 @@ pub mod entity;
|
||||
pub mod gamemode;
|
||||
pub mod item;
|
||||
pub mod message;
|
||||
pub mod particle;
|
||||
pub mod players;
|
||||
pub mod position_2d;
|
||||
pub mod position_3d;
|
||||
@ -93,6 +95,7 @@ pub enum Arg<'a> {
|
||||
Block(&'a str),
|
||||
BossbarColor(BossbarColor),
|
||||
BossbarStyle(BossbarDivisions),
|
||||
Particle(Particle),
|
||||
Msg(String),
|
||||
TextComponent(TextComponent),
|
||||
Time(i32),
|
||||
|
||||
68
pumpkin/src/command/args/particle.rs
Normal file
68
pumpkin/src/command/args/particle.rs
Normal file
@ -0,0 +1,68 @@
|
||||
use async_trait::async_trait;
|
||||
use pumpkin_data::particle::Particle;
|
||||
use pumpkin_protocol::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders};
|
||||
|
||||
use crate::command::{
|
||||
args::{Arg, ArgumentConsumer, DefaultNameArgConsumer, FindArg, GetClientSideArgParser},
|
||||
dispatcher::CommandError,
|
||||
tree::RawArgs,
|
||||
CommandSender,
|
||||
};
|
||||
use crate::server::Server;
|
||||
|
||||
pub struct ParticleArgumentConsumer;
|
||||
|
||||
impl GetClientSideArgParser for ParticleArgumentConsumer {
|
||||
fn get_client_side_parser(&self) -> ArgumentType {
|
||||
ArgumentType::Resource {
|
||||
identifier: "particle_type",
|
||||
}
|
||||
}
|
||||
|
||||
fn get_client_side_suggestion_type_override(&self) -> Option<SuggestionProviders> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ArgumentConsumer for ParticleArgumentConsumer {
|
||||
async fn consume<'a>(
|
||||
&'a self,
|
||||
_sender: &CommandSender<'a>,
|
||||
_server: &'a Server,
|
||||
args: &mut RawArgs<'a>,
|
||||
) -> Option<Arg<'a>> {
|
||||
let name = args.pop()?;
|
||||
|
||||
// Create a static damage type first
|
||||
let particle = Particle::from_name(&name.replace("minecraft:", ""))?;
|
||||
// Find matching static damage type from values array
|
||||
Some(Arg::Particle(particle))
|
||||
}
|
||||
|
||||
async fn suggest<'a>(
|
||||
&'a self,
|
||||
_sender: &CommandSender<'a>,
|
||||
_server: &'a Server,
|
||||
_input: &'a str,
|
||||
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl DefaultNameArgConsumer for ParticleArgumentConsumer {
|
||||
fn default_name(&self) -> &'static str {
|
||||
"particle_type"
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FindArg<'a> for ParticleArgumentConsumer {
|
||||
type Data = &'a Particle;
|
||||
|
||||
fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result<Self::Data, CommandError> {
|
||||
match args.get(name) {
|
||||
Some(Arg::Particle(data)) => Ok(data),
|
||||
_ => Err(CommandError::InvalidConsumption(Some(name.to_string()))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -18,6 +18,7 @@ pub mod msg;
|
||||
pub mod op;
|
||||
pub mod pardon;
|
||||
pub mod pardonip;
|
||||
pub mod particle;
|
||||
pub mod playsound;
|
||||
pub mod plugin;
|
||||
pub mod plugins;
|
||||
|
||||
92
pumpkin/src/command/commands/particle.rs
Normal file
92
pumpkin/src/command/commands/particle.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use async_trait::async_trait;
|
||||
use pumpkin_util::{math::vector3::Vector3, text::TextComponent};
|
||||
|
||||
use crate::command::{
|
||||
args::{
|
||||
bounded_num::BoundedNumArgumentConsumer, particle::ParticleArgumentConsumer,
|
||||
position_3d::Position3DArgumentConsumer, ConsumedArgs, FindArg,
|
||||
},
|
||||
tree::{builder::argument, CommandTree},
|
||||
CommandError, CommandExecutor, CommandSender,
|
||||
};
|
||||
const NAMES: [&str; 1] = ["particle"];
|
||||
|
||||
const DESCRIPTION: &str = "Spawns a Particle at position.";
|
||||
|
||||
const ARG_NAME: &str = "name";
|
||||
|
||||
const ARG_POS: &str = "pos";
|
||||
const ARG_DELTA: &str = "delta";
|
||||
const ARG_SPEED: &str = "speed";
|
||||
const ARG_COUNT: &str = "count";
|
||||
|
||||
struct ParticleExecutor;
|
||||
|
||||
#[async_trait]
|
||||
impl CommandExecutor for ParticleExecutor {
|
||||
async fn execute<'a>(
|
||||
&self,
|
||||
sender: &mut CommandSender<'a>,
|
||||
_server: &crate::server::Server,
|
||||
args: &ConsumedArgs<'a>,
|
||||
) -> Result<(), CommandError> {
|
||||
let particle = ParticleArgumentConsumer::find_arg(args, ARG_NAME)?;
|
||||
let pos = Position3DArgumentConsumer::find_arg(args, ARG_POS);
|
||||
let delta = Position3DArgumentConsumer::find_arg(args, ARG_DELTA);
|
||||
let speed = BoundedNumArgumentConsumer::<f32>::find_arg(args, ARG_SPEED);
|
||||
let count = BoundedNumArgumentConsumer::<i32>::find_arg(args, ARG_COUNT);
|
||||
|
||||
// TODO: Make this work in console
|
||||
if let Some(player) = sender.as_player() {
|
||||
let pos = pos.unwrap_or(player.living_entity.entity.pos.load());
|
||||
let delta = delta.unwrap_or(Vector3::new(0.0, 0.0, 0.0));
|
||||
let delta: Vector3<f32> = Vector3::new(delta.x as f32, delta.y as f32, delta.z as f32);
|
||||
let speed = speed.unwrap_or(Ok(0.0)).unwrap_or(0.0);
|
||||
let count = count.unwrap_or(Ok(0)).unwrap_or(0);
|
||||
|
||||
player
|
||||
.world()
|
||||
.await
|
||||
.spawn_particle(pos, delta, speed, count, *particle)
|
||||
.await;
|
||||
sender
|
||||
.send_message(TextComponent::translate(
|
||||
"commands.particle.success",
|
||||
[TextComponent::text(format!("{particle:?}"))].into(),
|
||||
))
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_command_tree() -> CommandTree {
|
||||
CommandTree::new(NAMES, DESCRIPTION).then(
|
||||
argument(ARG_NAME, ParticleArgumentConsumer)
|
||||
.execute(ParticleExecutor)
|
||||
.then(
|
||||
argument(ARG_POS, Position3DArgumentConsumer)
|
||||
.execute(ParticleExecutor)
|
||||
.then(
|
||||
argument(ARG_DELTA, Position3DArgumentConsumer)
|
||||
.execute(ParticleExecutor)
|
||||
.then(
|
||||
argument(
|
||||
ARG_SPEED,
|
||||
BoundedNumArgumentConsumer::<f32>::new().min(0.0),
|
||||
)
|
||||
.execute(ParticleExecutor)
|
||||
.then(
|
||||
argument(
|
||||
ARG_COUNT,
|
||||
BoundedNumArgumentConsumer::<i32>::new().min(0),
|
||||
)
|
||||
.execute(ParticleExecutor),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// TODO: Add NBT
|
||||
)
|
||||
}
|
||||
@ -11,8 +11,8 @@ use args::ConsumedArgs;
|
||||
use async_trait::async_trait;
|
||||
use commands::{
|
||||
ban, banip, banlist, clear, damage, deop, experience, fill, gamemode, give, help, kick, kill,
|
||||
list, me, msg, op, pardon, pardonip, playsound, plugin, plugins, pumpkin, say, setblock, stop,
|
||||
summon, teleport, time, title, weather, worldborder,
|
||||
list, me, msg, op, pardon, pardonip, particle, playsound, plugin, plugins, pumpkin, say,
|
||||
setblock, stop, summon, teleport, time, title, weather, worldborder,
|
||||
};
|
||||
use dispatcher::CommandError;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
@ -110,41 +110,46 @@ impl CommandSender<'_> {
|
||||
pub fn default_dispatcher() -> CommandDispatcher {
|
||||
let mut dispatcher = CommandDispatcher::default();
|
||||
|
||||
// Zerp
|
||||
dispatcher.register(pumpkin::init_command_tree(), PermissionLvl::Zero);
|
||||
dispatcher.register(bossbar::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(say::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(gamemode::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(stop::init_command_tree(), PermissionLvl::Four);
|
||||
dispatcher.register(help::init_command_tree(), PermissionLvl::Zero);
|
||||
dispatcher.register(list::init_command_tree(), PermissionLvl::Zero);
|
||||
dispatcher.register(transfer::init_command_tree(), PermissionLvl::Zero);
|
||||
dispatcher.register(me::init_command_tree(), PermissionLvl::Zero);
|
||||
dispatcher.register(msg::init_command_tree(), PermissionLvl::Zero);
|
||||
// Two
|
||||
dispatcher.register(kill::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(kick::init_command_tree(), PermissionLvl::Three);
|
||||
dispatcher.register(plugin::init_command_tree(), PermissionLvl::Three);
|
||||
dispatcher.register(plugins::init_command_tree(), PermissionLvl::Three);
|
||||
dispatcher.register(worldborder::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(teleport::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(time::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(give::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(list::init_command_tree(), PermissionLvl::Zero);
|
||||
dispatcher.register(clear::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(setblock::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(seed::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(transfer::init_command_tree(), PermissionLvl::Zero);
|
||||
dispatcher.register(fill::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(op::init_command_tree(), PermissionLvl::Three);
|
||||
dispatcher.register(deop::init_command_tree(), PermissionLvl::Three);
|
||||
dispatcher.register(me::init_command_tree(), PermissionLvl::Zero);
|
||||
dispatcher.register(playsound::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(title::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(summon::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(msg::init_command_tree(), PermissionLvl::Zero);
|
||||
dispatcher.register(experience::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(weather::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(particle::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(damage::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(bossbar::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(say::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(gamemode::init_command_tree(), PermissionLvl::Two);
|
||||
// Three
|
||||
dispatcher.register(op::init_command_tree(), PermissionLvl::Three);
|
||||
dispatcher.register(deop::init_command_tree(), PermissionLvl::Three);
|
||||
dispatcher.register(kick::init_command_tree(), PermissionLvl::Three);
|
||||
dispatcher.register(plugin::init_command_tree(), PermissionLvl::Three);
|
||||
dispatcher.register(plugins::init_command_tree(), PermissionLvl::Three);
|
||||
dispatcher.register(ban::init_command_tree(), PermissionLvl::Three);
|
||||
dispatcher.register(banip::init_command_tree(), PermissionLvl::Three);
|
||||
dispatcher.register(banlist::init_command_tree(), PermissionLvl::Three);
|
||||
dispatcher.register(pardon::init_command_tree(), PermissionLvl::Three);
|
||||
dispatcher.register(pardonip::init_command_tree(), PermissionLvl::Three);
|
||||
dispatcher.register(experience::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(weather::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(damage::init_command_tree(), PermissionLvl::Two);
|
||||
// Four
|
||||
dispatcher.register(stop::init_command_tree(), PermissionLvl::Four);
|
||||
|
||||
dispatcher
|
||||
}
|
||||
|
||||
@ -2,10 +2,7 @@ use pumpkin_data::{
|
||||
particle::Particle,
|
||||
sound::{Sound, SoundCategory},
|
||||
};
|
||||
use pumpkin_protocol::{
|
||||
client::play::{CEntityVelocity, CParticle},
|
||||
codec::var_int::VarInt,
|
||||
};
|
||||
use pumpkin_protocol::{client::play::CEntityVelocity, codec::var_int::VarInt};
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_world::item::ItemStack;
|
||||
|
||||
@ -93,19 +90,16 @@ pub async fn spawn_sweep_particle(attacker_entity: &Entity, world: &World, pos:
|
||||
let e = f64::from((yaw.to_radians()).cos());
|
||||
|
||||
let scale = 0.5;
|
||||
// TODO: use entity height
|
||||
let body_y = pos.y * 2.0 * scale;
|
||||
let body_y = pos.y + f64::from(attacker_entity.height()) * scale;
|
||||
|
||||
world
|
||||
.broadcast_packet_all(&CParticle::new(
|
||||
false,
|
||||
.spawn_particle(
|
||||
Vector3::new(pos.x + d, body_y, pos.z + e),
|
||||
Vector3::new(0.0, 0.0, 0.0),
|
||||
0.0,
|
||||
0,
|
||||
VarInt(Particle::SweepAttack as i32), // sweep
|
||||
&[],
|
||||
))
|
||||
Particle::SweepAttack,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
|
||||
@ -273,6 +273,13 @@ impl Entity {
|
||||
entity_vel,
|
||||
)
|
||||
}
|
||||
pub fn width(&self) -> f32 {
|
||||
self.bounding_box_size.load().width
|
||||
}
|
||||
|
||||
pub fn height(&self) -> f32 {
|
||||
self.bounding_box_size.load().height
|
||||
}
|
||||
|
||||
/// Applies knockback to the entity, following vanilla Minecraft's mechanics.
|
||||
///
|
||||
|
||||
@ -14,6 +14,7 @@ use pumpkin_data::{
|
||||
damage::DamageType,
|
||||
entity::EntityType,
|
||||
item::Operation,
|
||||
particle::Particle,
|
||||
sound::{Sound, SoundCategory},
|
||||
};
|
||||
use pumpkin_inventory::player::PlayerInventory;
|
||||
@ -22,9 +23,9 @@ use pumpkin_protocol::{
|
||||
bytebuf::packet::Packet,
|
||||
client::play::{
|
||||
CActionBar, CCombatDeath, CDisguisedChatMessage, CEntityStatus, CGameEvent, CHurtAnimation,
|
||||
CKeepAlive, CPlayDisconnect, CPlayerAbilities, CPlayerInfoUpdate, CPlayerPosition,
|
||||
CRespawn, CSetExperience, CSetHealth, CSubtitle, CSystemChatMessage, CTitleText,
|
||||
CUnloadChunk, GameEvent, MetaDataType, PlayerAction,
|
||||
CKeepAlive, CParticle, CPlayDisconnect, CPlayerAbilities, CPlayerInfoUpdate,
|
||||
CPlayerPosition, CRespawn, CSetExperience, CSetHealth, CSubtitle, CSystemChatMessage,
|
||||
CTitleText, CUnloadChunk, GameEvent, MetaDataType, PlayerAction,
|
||||
},
|
||||
server::play::{
|
||||
SChatCommand, SChatMessage, SClientCommand, SClientInformationPlay, SClientTickEnd,
|
||||
@ -385,6 +386,28 @@ impl Player {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn spawn_particle(
|
||||
&self,
|
||||
position: Vector3<f64>,
|
||||
offset: Vector3<f32>,
|
||||
max_speed: f32,
|
||||
particle_count: i32,
|
||||
pariticle: Particle,
|
||||
) {
|
||||
self.client
|
||||
.send_packet(&CParticle::new(
|
||||
false,
|
||||
false,
|
||||
position,
|
||||
offset,
|
||||
max_speed,
|
||||
particle_count,
|
||||
VarInt(pariticle as i32),
|
||||
&[],
|
||||
))
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn play_sound(
|
||||
&self,
|
||||
sound_id: u16,
|
||||
|
||||
@ -23,6 +23,7 @@ use border::Worldborder;
|
||||
use pumpkin_config::BasicConfiguration;
|
||||
use pumpkin_data::{
|
||||
entity::EntityType,
|
||||
particle::Particle,
|
||||
sound::{Sound, SoundCategory},
|
||||
world::WorldEvent,
|
||||
};
|
||||
@ -187,6 +188,22 @@ impl World {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn spawn_particle(
|
||||
&self,
|
||||
position: Vector3<f64>,
|
||||
offset: Vector3<f32>,
|
||||
max_speed: f32,
|
||||
particle_count: i32,
|
||||
pariticle: Particle,
|
||||
) {
|
||||
let players = self.players.lock().await;
|
||||
for (_, player) in players.iter() {
|
||||
player
|
||||
.spawn_particle(position, offset, max_speed, particle_count, pariticle)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn play_sound(&self, sound: Sound, category: SoundCategory, position: &Vector3<f64>) {
|
||||
self.play_sound_raw(sound as u16, category, position, 1.0, 1.0)
|
||||
.await;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user