diff --git a/Cargo.lock b/Cargo.lock index 7f6eaa755..66bff8440 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -946,6 +946,7 @@ dependencies = [ "futures-lite", "hematite-nbt", "libcraft-core", + "libcraft-effects", "libcraft-items", "log", "md-5", @@ -1377,6 +1378,13 @@ dependencies = [ "vek", ] +[[package]] +name = "libcraft-effects" +version = "0.1.0" +dependencies = [ + "serde", +] + [[package]] name = "libcraft-generators" version = "0.1.0" @@ -2052,6 +2060,7 @@ dependencies = [ "bytemuck", "derive_more", "libcraft-core", + "libcraft-effects", "libcraft-particles", "libcraft-text", "quill", diff --git a/Cargo.toml b/Cargo.toml index c4b46578e..07ae527b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "libcraft/particles", "libcraft/text", "libcraft/inventory", + "libcraft/effects", # Quill "quill/sys-macros", diff --git a/feather/server/Cargo.toml b/feather/server/Cargo.toml index 4e9758fa4..1cca33856 100644 --- a/feather/server/Cargo.toml +++ b/feather/server/Cargo.toml @@ -51,6 +51,7 @@ uuid = "0.8" slab = "0.4" libcraft-core = { path = "../../libcraft/core" } libcraft-items = { path = "../../libcraft/items" } +libcraft-effects = { path = "../../libcraft/effects" } worldgen = { path = "../worldgen", package = "feather-worldgen" } [features] diff --git a/feather/server/src/client.rs b/feather/server/src/client.rs index 20211f39b..7f60d852f 100644 --- a/feather/server/src/client.rs +++ b/feather/server/src/client.rs @@ -20,7 +20,8 @@ use common::{ use libcraft_items::InventorySlot; use packets::server::{Particle, SetSlot, SpawnLivingEntity, UpdateLight, WindowConfirmation}; use protocol::packets::server::{ - EntityPosition, EntityPositionAndRotation, EntityTeleport, HeldItemChange, PlayerAbilities, + EntityEffect, EntityPosition, EntityPositionAndRotation, EntityTeleport, HeldItemChange, + PlayerAbilities, RemoveEntityEffect, }; use protocol::{ packets::{ @@ -42,6 +43,7 @@ use crate::{ network_id_registry::NetworkId, Options, }; +use quill_common::components_effects::EffectFlags; use slab::Slab; /// Max number of chunks to send to a client per tick. @@ -602,6 +604,40 @@ impl Client { self.send_packet(HeldItemChange { slot }); } + pub fn send_entity_effect( + &self, + network_id: NetworkId, + effect_id: u8, + amplifier: i8, + duration: i32, + flags: EffectFlags, + ) { + let mut flags_bit = 0; + if flags.ambient { + flags_bit |= 1 << 0; + } + if flags.particle { + flags_bit |= 1 << 1; + } + if flags.icon { + flags_bit |= 1 << 2; + } + self.send_packet(EntityEffect { + entity_id: network_id.0, + effect_id, + amplifier, + duration, + flags: flags_bit, + }) + } + + pub fn send_remove_entity_effect(&self, network_id: NetworkId, effect_id: u8) { + self.send_packet(RemoveEntityEffect { + entity_id: network_id.0, + effect_id, + }) + } + fn register_entity(&self, network_id: NetworkId) { self.sent_entities.borrow_mut().insert(network_id); } diff --git a/feather/server/src/packet_handlers.rs b/feather/server/src/packet_handlers.rs index b0452c96a..9f42640da 100644 --- a/feather/server/src/packet_handlers.rs +++ b/feather/server/src/packet_handlers.rs @@ -20,6 +20,7 @@ mod entity_action; mod interaction; pub mod inventory; mod movement; +mod use_item; /// Handles a packet received from a client. pub fn handle_packet( @@ -77,6 +78,10 @@ pub fn handle_packet( entity_action::handle_entity_action(game, player_id, packet) } + ClientPlayPacket::UseItem(packet) => { + use_item::handle_use_item(game, server, packet, player_id) + } + ClientPlayPacket::TeleportConfirm(_) | ClientPlayPacket::QueryBlockNbt(_) | ClientPlayPacket::SetDifficulty(_) @@ -108,8 +113,7 @@ pub fn handle_packet( | ClientPlayPacket::UpdateJigsawBlock(_) | ClientPlayPacket::UpdateStructureBlock(_) | ClientPlayPacket::UpdateSign(_) - | ClientPlayPacket::Spectate(_) - | ClientPlayPacket::UseItem(_) => Ok(()), + | ClientPlayPacket::Spectate(_) => Ok(()), } } diff --git a/feather/server/src/packet_handlers/use_item.rs b/feather/server/src/packet_handlers/use_item.rs new file mode 100644 index 000000000..64b2ee072 --- /dev/null +++ b/feather/server/src/packet_handlers/use_item.rs @@ -0,0 +1,40 @@ +use crate::Server; +use base::TPS; +use common::Game; +use ecs::{Entity, SysResult}; +use protocol::packets::client::UseItem; +use quill_common::components_effects::*; + +pub fn handle_use_item( + game: &mut Game, + _server: &mut Server, + _packet: UseItem, + player: Entity, +) -> SysResult { + // example for effect system + let mut speed = SpeedEffect::default(); + speed.add_effect(EffectApplication { + amplifier: 0, + duration: 20 * TPS, + flags: EffectFlags { + particle: true, + ambient: false, + icon: true, + }, + start_tick: 0, + }); + + speed.add_effect(EffectApplication { + amplifier: 1, + duration: 10 * TPS, + flags: EffectFlags { + particle: true, + ambient: false, + icon: true, + }, + start_tick: 0, + }); + + game.ecs.insert(player, speed)?; + Ok(()) +} diff --git a/feather/server/src/systems.rs b/feather/server/src/systems.rs index ee7e85b37..f24f6fc71 100644 --- a/feather/server/src/systems.rs +++ b/feather/server/src/systems.rs @@ -2,6 +2,7 @@ mod block; mod chat; +mod effects; mod entity; mod particle; mod player_join; @@ -36,6 +37,7 @@ pub fn register(server: Server, game: &mut Game, systems: &mut SystemExecutor().add_system(tick_clients); } diff --git a/feather/server/src/systems/effects.rs b/feather/server/src/systems/effects.rs new file mode 100644 index 000000000..d5dfb384d --- /dev/null +++ b/feather/server/src/systems/effects.rs @@ -0,0 +1,226 @@ +use base::{Particle, ParticleKind, TPS}; +use common::Game; +use ecs::{Entity, SysResult, SystemExecutor}; +use libcraft_core::Position; +use libcraft_effects::effects::Effect; +use quill_common::components_effects::{EffectApplication, SpeedEffect, WalkEffectModifier}; +use quill_common::entity_init::EntityInit; +use std::collections::HashMap; + +use crate::{Client, ClientId, NetworkId, Server}; + +pub fn register(_game: &mut Game, systems: &mut SystemExecutor) { + systems + .group::() + .add_system(add_start_tick_to_speed_effects) + .add_system(speed_effect) + .add_system(walk_effect_modifier_cleaner) + .add_system(effect_remover); +} + +fn speed_effect(game: &mut Game, _server: &mut Server) -> SysResult { + let mut new_walk_speed = HashMap::new(); + for (entity, speed) in game.ecs.query::<&mut SpeedEffect>().iter() { + if speed.0.is_empty() { + continue; + } + + if let Some(effect_ref) = speed.active_effect() { + let modifier = 20 * (effect_ref.amplifier + 1) as i32; + new_walk_speed.insert(entity, modifier); + }; + } + + for (entity, modifier) in new_walk_speed { + change_modifier(game, entity, modifier)?; + } + + Ok(()) +} + +fn effect_remover(game: &mut Game, server: &mut Server) -> SysResult { + let mut new_walk_speed = HashMap::new(); + for (entity, (&client_id, speed, &network_id)) in game + .ecs + .query::<(&ClientId, &mut SpeedEffect, &NetworkId)>() + .iter() + { + if speed.0.is_empty() { + continue; + } + + let end_effect = speed.ended_on_tick(game.tick_count); + + for effect in end_effect.iter() { + if let Some(client) = server.clients.get(client_id) { + client.send_remove_entity_effect(network_id, Effect::SpeedEffect.id() as u8); + } + + if speed.0.remove(effect) { + log::debug!("speed effect was removed with params {:?}", effect) + } + new_walk_speed.insert(entity, 0); + } + + if !end_effect.is_empty() { + if let Some(active_effect) = speed.active_effect() { + if let Some(client) = server.clients.get(client_id) { + let duration = active_effect.duration as u64 + - (game.tick_count - active_effect.start_tick); + + send_effect_to_client( + Effect::SpeedEffect.id() as u8, + network_id, + active_effect, + client, + duration, + ); + } + } + } + } + + for (entity, modifier) in new_walk_speed { + change_modifier(game, entity, modifier)?; + } + + Ok(()) +} + +fn walk_effect_modifier_cleaner(game: &mut Game, _server: &mut Server) -> SysResult { + let mut rem_comp = vec![]; + + for (entity, wm) in game.ecs.query::<&mut WalkEffectModifier>().iter() { + let mut rem_wm = vec![]; + + for (effect, modifier) in wm.0.iter() { + if *modifier == 0 { + rem_wm.push(*effect); + } + } + + for effect in rem_wm { + wm.0.remove(&effect); + } + + if wm.0.is_empty() { + rem_comp.push(entity); + } + } + + for entity in rem_comp { + game.ecs.remove::(entity)?; + } + + Ok(()) +} + +fn change_modifier(game: &mut Game, entity: Entity, new_modifier: i32) -> SysResult { + if game.ecs.get::(entity).is_err() { + game.ecs.insert(entity, WalkEffectModifier::new())?; + } + + let mut walk_speed_modifier = game.ecs.get_mut::(entity)?; + if walk_speed_modifier.0.contains_key(&Effect::SpeedEffect) + && new_modifier + != *walk_speed_modifier + .0 + .get(&Effect::SpeedEffect) + .unwrap_or(&0) + { + walk_speed_modifier + .0 + .insert(Effect::SpeedEffect, new_modifier); + } + Ok(()) +} + +fn send_effect_to_client( + effect_id: u8, + network_id: NetworkId, + active_effect: &EffectApplication, + client: &Client, + duration: u64, +) { + if duration == 0 { + return; + } + + client.send_entity_effect( + network_id, + effect_id, + active_effect.amplifier as i8, + duration as i32, + active_effect.flags, + ); +} + +// todo change particle color +fn add_particles(game: &mut Game, entity: Entity, _effect_kind: Effect) -> SysResult { + if game.tick_count % (TPS * 2) as u64 == 0 { + let position = *game.ecs.get::(entity)?; + + let mut entity_builder = game.create_entity_builder(position, EntityInit::AreaEffectCloud); + + entity_builder.add(position); + entity_builder.add(Particle { + kind: ParticleKind::Effect, + offset_x: 0.0, + offset_y: 0.0, + offset_z: 0.0, + count: 5, + }); + game.spawn_entity(entity_builder); + } + Ok(()) +} + +/// Set start_tick to all effects in effects_bucket and spawn particles +macro_rules! add_start_tick_to_effects { + ($fn_name:ident,$type:ident) => { + fn $fn_name(game: &mut Game, server: &mut Server) -> SysResult { + let mut entities = vec![]; + for (entity, (&client_id, effects_bucket, &network_id)) in game + .ecs + .query::<(&ClientId, &mut $type, &NetworkId)>() + .iter() + { + if effects_bucket.0.is_empty() { + continue; + } + + if let Some(active_effect) = effects_bucket.active_effect() { + if active_effect.flags.particle { + entities.push((entity, Effect::$type)); + } + } + + let not_started = effects_bucket.not_started(); + + for mut effect in not_started { + effect.start_tick = game.tick_count; + + if let Some(client) = server.clients.get(client_id) { + send_effect_to_client( + Effect::$type.id() as u8, + network_id, + &effect, + client, + effect.duration as u64, + ); + } + + effects_bucket.0.replace(effect); + } + } + + for (entity, effect_kind) in entities { + add_particles(game, entity, effect_kind)?; + } + + Ok(()) + } + }; +} + +add_start_tick_to_effects!(add_start_tick_to_speed_effects, SpeedEffect); diff --git a/libcraft/effects/Cargo.toml b/libcraft/effects/Cargo.toml new file mode 100644 index 000000000..1604777b8 --- /dev/null +++ b/libcraft/effects/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "libcraft-effects" +version = "0.1.0" +authors = ["Yui Tanabe "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = { version = "1", features = ["derive"] } \ No newline at end of file diff --git a/libcraft/effects/src/effects.rs b/libcraft/effects/src/effects.rs new file mode 100644 index 000000000..c6246e59a --- /dev/null +++ b/libcraft/effects/src/effects.rs @@ -0,0 +1,321 @@ +// This file is @generated. Please do not edit. + +#[derive( + Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Deserialize, serde::Serialize, +)] +pub enum Effect { + SpeedEffect, + SlownessEffect, + HasteEffect, + MiningfatigueEffect, + StrengthEffect, + InstanthealthEffect, + InstantdamageEffect, + JumpboostEffect, + NauseaEffect, + RegenerationEffect, + ResistanceEffect, + FireresistanceEffect, + WaterbreathingEffect, + InvisibilityEffect, + BlindnessEffect, + NightvisionEffect, + HungerEffect, + WeaknessEffect, + PoisonEffect, + WitherEffect, + HealthboostEffect, + AbsorptionEffect, + SaturationEffect, + GlowingEffect, + LevitationEffect, + LuckEffect, + BadluckEffect, + SlowfallingEffect, + ConduitpowerEffect, + DolphinsgraceEffect, + BadomenEffect, + HeroofthevillageEffect, +} + +#[allow(warnings)] +#[allow(clippy::all)] +impl Effect { + /// Returns the `id` property of this `Effect`. + pub fn id(&self) -> u8 { + match self { + Effect::SpeedEffect => 1, + Effect::SlownessEffect => 2, + Effect::HasteEffect => 3, + Effect::MiningfatigueEffect => 4, + Effect::StrengthEffect => 5, + Effect::InstanthealthEffect => 6, + Effect::InstantdamageEffect => 7, + Effect::JumpboostEffect => 8, + Effect::NauseaEffect => 9, + Effect::RegenerationEffect => 10, + Effect::ResistanceEffect => 11, + Effect::FireresistanceEffect => 12, + Effect::WaterbreathingEffect => 13, + Effect::InvisibilityEffect => 14, + Effect::BlindnessEffect => 15, + Effect::NightvisionEffect => 16, + Effect::HungerEffect => 17, + Effect::WeaknessEffect => 18, + Effect::PoisonEffect => 19, + Effect::WitherEffect => 20, + Effect::HealthboostEffect => 21, + Effect::AbsorptionEffect => 22, + Effect::SaturationEffect => 23, + Effect::GlowingEffect => 24, + Effect::LevitationEffect => 25, + Effect::LuckEffect => 26, + Effect::BadluckEffect => 27, + Effect::SlowfallingEffect => 28, + Effect::ConduitpowerEffect => 29, + Effect::DolphinsgraceEffect => 31, + Effect::BadomenEffect => 32, + Effect::HeroofthevillageEffect => 32, + } + } + + /// Gets a `Effect` by its `id`. + pub fn from_id(id: u8) -> Option { + match id { + 1 => Some(Effect::SpeedEffect), + 2 => Some(Effect::SlownessEffect), + 3 => Some(Effect::HasteEffect), + 4 => Some(Effect::MiningfatigueEffect), + 5 => Some(Effect::StrengthEffect), + 6 => Some(Effect::InstanthealthEffect), + 7 => Some(Effect::InstantdamageEffect), + 8 => Some(Effect::JumpboostEffect), + 9 => Some(Effect::NauseaEffect), + 10 => Some(Effect::RegenerationEffect), + 11 => Some(Effect::ResistanceEffect), + 12 => Some(Effect::FireresistanceEffect), + 13 => Some(Effect::WaterbreathingEffect), + 14 => Some(Effect::InvisibilityEffect), + 15 => Some(Effect::BlindnessEffect), + 16 => Some(Effect::NightvisionEffect), + 17 => Some(Effect::HungerEffect), + 18 => Some(Effect::WeaknessEffect), + 19 => Some(Effect::PoisonEffect), + 20 => Some(Effect::WitherEffect), + 21 => Some(Effect::HealthboostEffect), + 22 => Some(Effect::AbsorptionEffect), + 23 => Some(Effect::SaturationEffect), + 24 => Some(Effect::GlowingEffect), + 25 => Some(Effect::LevitationEffect), + 26 => Some(Effect::LuckEffect), + 27 => Some(Effect::BadluckEffect), + 28 => Some(Effect::SlowfallingEffect), + 29 => Some(Effect::ConduitpowerEffect), + 31 => Some(Effect::DolphinsgraceEffect), + 32 => Some(Effect::BadomenEffect), + 32 => Some(Effect::HeroofthevillageEffect), + _ => None, + } + } +} +#[allow(warnings)] +#[allow(clippy::all)] +impl Effect { + /// Returns the `name` property of this `Effect`. + pub fn name(&self) -> &'static str { + match self { + Effect::SpeedEffect => "Speed", + Effect::SlownessEffect => "Slowness", + Effect::HasteEffect => "Haste", + Effect::MiningfatigueEffect => "MiningFatigue", + Effect::StrengthEffect => "Strength", + Effect::InstanthealthEffect => "InstantHealth", + Effect::InstantdamageEffect => "InstantDamage", + Effect::JumpboostEffect => "JumpBoost", + Effect::NauseaEffect => "Nausea", + Effect::RegenerationEffect => "Regeneration", + Effect::ResistanceEffect => "Resistance", + Effect::FireresistanceEffect => "FireResistance", + Effect::WaterbreathingEffect => "WaterBreathing", + Effect::InvisibilityEffect => "Invisibility", + Effect::BlindnessEffect => "Blindness", + Effect::NightvisionEffect => "NightVision", + Effect::HungerEffect => "Hunger", + Effect::WeaknessEffect => "Weakness", + Effect::PoisonEffect => "Poison", + Effect::WitherEffect => "Wither", + Effect::HealthboostEffect => "HealthBoost", + Effect::AbsorptionEffect => "Absorption", + Effect::SaturationEffect => "Saturation", + Effect::GlowingEffect => "Glowing", + Effect::LevitationEffect => "Levitation", + Effect::LuckEffect => "Luck", + Effect::BadluckEffect => "BadLuck", + Effect::SlowfallingEffect => "SlowFalling", + Effect::ConduitpowerEffect => "ConduitPower", + Effect::DolphinsgraceEffect => "DolphinsGrace", + Effect::BadomenEffect => "BadOmen", + Effect::HeroofthevillageEffect => "HeroOfTheVillage", + } + } + + /// Gets a `Effect` by its `name`. + pub fn from_name(name: &str) -> Option { + match name { + "Speed" => Some(Effect::SpeedEffect), + "Slowness" => Some(Effect::SlownessEffect), + "Haste" => Some(Effect::HasteEffect), + "MiningFatigue" => Some(Effect::MiningfatigueEffect), + "Strength" => Some(Effect::StrengthEffect), + "InstantHealth" => Some(Effect::InstanthealthEffect), + "InstantDamage" => Some(Effect::InstantdamageEffect), + "JumpBoost" => Some(Effect::JumpboostEffect), + "Nausea" => Some(Effect::NauseaEffect), + "Regeneration" => Some(Effect::RegenerationEffect), + "Resistance" => Some(Effect::ResistanceEffect), + "FireResistance" => Some(Effect::FireresistanceEffect), + "WaterBreathing" => Some(Effect::WaterbreathingEffect), + "Invisibility" => Some(Effect::InvisibilityEffect), + "Blindness" => Some(Effect::BlindnessEffect), + "NightVision" => Some(Effect::NightvisionEffect), + "Hunger" => Some(Effect::HungerEffect), + "Weakness" => Some(Effect::WeaknessEffect), + "Poison" => Some(Effect::PoisonEffect), + "Wither" => Some(Effect::WitherEffect), + "HealthBoost" => Some(Effect::HealthboostEffect), + "Absorption" => Some(Effect::AbsorptionEffect), + "Saturation" => Some(Effect::SaturationEffect), + "Glowing" => Some(Effect::GlowingEffect), + "Levitation" => Some(Effect::LevitationEffect), + "Luck" => Some(Effect::LuckEffect), + "BadLuck" => Some(Effect::BadluckEffect), + "SlowFalling" => Some(Effect::SlowfallingEffect), + "ConduitPower" => Some(Effect::ConduitpowerEffect), + "DolphinsGrace" => Some(Effect::DolphinsgraceEffect), + "BadOmen" => Some(Effect::BadomenEffect), + "HeroOfTheVillage" => Some(Effect::HeroofthevillageEffect), + _ => None, + } + } +} +#[allow(warnings)] +#[allow(clippy::all)] +impl Effect { + /// Returns the `display_name` property of this `Effect`. + pub fn display_name(&self) -> &'static str { + match self { + Effect::SpeedEffect => "Speed", + Effect::SlownessEffect => "Slowness", + Effect::HasteEffect => "Haste", + Effect::MiningfatigueEffect => "Mining Fatigue", + Effect::StrengthEffect => "Strength", + Effect::InstanthealthEffect => "Instant Health", + Effect::InstantdamageEffect => "Instant Damage", + Effect::JumpboostEffect => "Jump Boost", + Effect::NauseaEffect => "Nausea", + Effect::RegenerationEffect => "Regeneration", + Effect::ResistanceEffect => "Resistance", + Effect::FireresistanceEffect => "Fire Resistance", + Effect::WaterbreathingEffect => "Water Breathing", + Effect::InvisibilityEffect => "Invisibility", + Effect::BlindnessEffect => "Blindness", + Effect::NightvisionEffect => "Night Vision", + Effect::HungerEffect => "Hunger", + Effect::WeaknessEffect => "Weakness", + Effect::PoisonEffect => "Poison", + Effect::WitherEffect => "Wither", + Effect::HealthboostEffect => "Health Boost", + Effect::AbsorptionEffect => "Absorption", + Effect::SaturationEffect => "Saturation", + Effect::GlowingEffect => "Glowing", + Effect::LevitationEffect => "Levitation", + Effect::LuckEffect => "Luck", + Effect::BadluckEffect => "Bad Luck", + Effect::SlowfallingEffect => "Slow Falling", + Effect::ConduitpowerEffect => "Conduit Power", + Effect::DolphinsgraceEffect => "Dolphin's Grace", + Effect::BadomenEffect => "Bad Omen", + Effect::HeroofthevillageEffect => "Hero Of The Village", + } + } + + /// Gets a `Effect` by its `display_name`. + pub fn from_display_name(display_name: &str) -> Option { + match display_name { + "Speed" => Some(Effect::SpeedEffect), + "Slowness" => Some(Effect::SlownessEffect), + "Haste" => Some(Effect::HasteEffect), + "Mining Fatigue" => Some(Effect::MiningfatigueEffect), + "Strength" => Some(Effect::StrengthEffect), + "Instant Health" => Some(Effect::InstanthealthEffect), + "Instant Damage" => Some(Effect::InstantdamageEffect), + "Jump Boost" => Some(Effect::JumpboostEffect), + "Nausea" => Some(Effect::NauseaEffect), + "Regeneration" => Some(Effect::RegenerationEffect), + "Resistance" => Some(Effect::ResistanceEffect), + "Fire Resistance" => Some(Effect::FireresistanceEffect), + "Water Breathing" => Some(Effect::WaterbreathingEffect), + "Invisibility" => Some(Effect::InvisibilityEffect), + "Blindness" => Some(Effect::BlindnessEffect), + "Night Vision" => Some(Effect::NightvisionEffect), + "Hunger" => Some(Effect::HungerEffect), + "Weakness" => Some(Effect::WeaknessEffect), + "Poison" => Some(Effect::PoisonEffect), + "Wither" => Some(Effect::WitherEffect), + "Health Boost" => Some(Effect::HealthboostEffect), + "Absorption" => Some(Effect::AbsorptionEffect), + "Saturation" => Some(Effect::SaturationEffect), + "Glowing" => Some(Effect::GlowingEffect), + "Levitation" => Some(Effect::LevitationEffect), + "Luck" => Some(Effect::LuckEffect), + "Bad Luck" => Some(Effect::BadluckEffect), + "Slow Falling" => Some(Effect::SlowfallingEffect), + "Conduit Power" => Some(Effect::ConduitpowerEffect), + "Dolphin's Grace" => Some(Effect::DolphinsgraceEffect), + "Bad Omen" => Some(Effect::BadomenEffect), + "Hero Of The Village" => Some(Effect::HeroofthevillageEffect), + _ => None, + } + } +} +#[allow(warnings)] +#[allow(clippy::all)] +impl Effect { + /// Returns the `is_good` property of this `Effect`. + pub fn is_good(&self) -> bool { + match self { + Effect::SpeedEffect => true, + Effect::SlownessEffect => false, + Effect::HasteEffect => true, + Effect::MiningfatigueEffect => false, + Effect::StrengthEffect => true, + Effect::InstanthealthEffect => true, + Effect::InstantdamageEffect => false, + Effect::JumpboostEffect => true, + Effect::NauseaEffect => false, + Effect::RegenerationEffect => true, + Effect::ResistanceEffect => true, + Effect::FireresistanceEffect => true, + Effect::WaterbreathingEffect => true, + Effect::InvisibilityEffect => true, + Effect::BlindnessEffect => false, + Effect::NightvisionEffect => true, + Effect::HungerEffect => false, + Effect::WeaknessEffect => false, + Effect::PoisonEffect => false, + Effect::WitherEffect => false, + Effect::HealthboostEffect => true, + Effect::AbsorptionEffect => true, + Effect::SaturationEffect => true, + Effect::GlowingEffect => false, + Effect::LevitationEffect => false, + Effect::LuckEffect => true, + Effect::BadluckEffect => false, + Effect::SlowfallingEffect => true, + Effect::ConduitpowerEffect => true, + Effect::DolphinsgraceEffect => true, + Effect::BadomenEffect => true, + Effect::HeroofthevillageEffect => true, + } + } +} diff --git a/libcraft/effects/src/lib.rs b/libcraft/effects/src/lib.rs new file mode 100644 index 000000000..2d60ce321 --- /dev/null +++ b/libcraft/effects/src/lib.rs @@ -0,0 +1,2 @@ +pub mod effects; +pub use effects::Effect; diff --git a/libcraft/generators/python/effects.py b/libcraft/generators/python/effects.py new file mode 100644 index 000000000..861f5144b --- /dev/null +++ b/libcraft/generators/python/effects.py @@ -0,0 +1,25 @@ +from common import load_minecraft_json, camel_case, generate_enum, generate_enum_property, output + +effects = [] +ids = {} +names = {} +display_names = {} +is_good = {} + +for effect in load_minecraft_json("effects.json", "1.16.1"): + variant = camel_case(effect['name'])+'Effect' + effects.append(variant) + ids[variant] = effect['id'] + names[variant] = effect['name'] + display_names[variant] = effect['displayName'] + is_good[variant] = True if effect['type'] == "good" else False + +enumName = "Effect" + +output_data = generate_enum(enumName, effects, ["serde::Deserialize", "serde::Serialize"]) +output_data += generate_enum_property(enumName, "id", "u8", ids, True) +output_data += generate_enum_property(enumName, "name", "&str", names, True, "&'static str") +output_data += generate_enum_property(enumName, "display_name", "&str", display_names, True, "&'static str") +output_data += generate_enum_property(enumName, "is_good", "bool", is_good) + +output("effects/src/effects.rs", output_data) diff --git a/quill/api/src/lib.rs b/quill/api/src/lib.rs index 23b3a9418..e0c1f927c 100644 --- a/quill/api/src/lib.rs +++ b/quill/api/src/lib.rs @@ -22,7 +22,9 @@ pub use libcraft_particles::{Particle, ParticleKind}; pub use libcraft_text::*; #[doc(inline)] -pub use quill_common::{components, entity_init::EntityInit, events, Component}; +pub use quill_common::{ + components, components_effects, entity_init::EntityInit, events, Component, +}; #[doc(inline)] pub use uuid::Uuid; diff --git a/quill/common/Cargo.toml b/quill/common/Cargo.toml index a01736a76..498b4be2a 100644 --- a/quill/common/Cargo.toml +++ b/quill/common/Cargo.toml @@ -11,6 +11,7 @@ derive_more = "0.99.16" libcraft-core = { path = "../../libcraft/core" } libcraft-particles = { path = "../../libcraft/particles" } libcraft-text = { path = "../../libcraft/text" } +libcraft-effects = { path = "../../libcraft/effects" } serde = { version = "1", features = ["derive"] } smartstring = { version = "0.2", features = ["serde"] } uuid = { version = "0.8", features = ["serde"] } diff --git a/quill/common/src/component.rs b/quill/common/src/component.rs index f113fa29a..0ec13e572 100644 --- a/quill/common/src/component.rs +++ b/quill/common/src/component.rs @@ -7,6 +7,7 @@ use libcraft_particles::Particle; use uuid::Uuid; use crate::components::*; +use crate::components_effects::*; use crate::entities::*; use crate::events::*; @@ -64,6 +65,40 @@ host_component_enum! { // `Pod` components Position = 0, + // Effect Components + SpeedEffect = 1, + SlownessEffect = 2, + HasteEffect = 3, + MiningFatigueEffect = 4, + StrengthEffect = 5, + InstantHealthEffect = 6, + InstantDamageEffect = 7, + JumpBoostEffect = 8, + NauseaEffect = 9, + RegenerationEffect = 10, + ResistanceEffect = 11, + FireResistanceEffect = 12, + WaterBreathingEffect = 13, + InvisibilityEffect = 14, + BlindnessEffect = 15, + NightVisionEffect = 16, + HungerEffect = 17, + WeaknessEffect = 18, + PoisonEffect = 19, + WitherEffect = 20, + HealthBoostEffect = 21, + AbsorptionEffect = 22, + SaturationEffect = 23, + GlowingEffect = 24, + LevitationEffect = 25, + LuckEffect = 26, + BadLuckEffect = 27, + SlowFallingEffect = 28, + ConduitPowerEffect = 29, + DolphinsGraceEffect = 30, + BadOmenEffect = 31, + HeroOfTheVillageEffect = 32, + // Entity marker components AreaEffectCloud = 100, ArmorStand = 101, @@ -199,7 +234,7 @@ host_component_enum! { Instabreak = 1021, Invulnerable = 1022, - + WalkEffectModifier = 2023, } } diff --git a/quill/common/src/components_effects.rs b/quill/common/src/components_effects.rs new file mode 100644 index 000000000..caa112b33 --- /dev/null +++ b/quill/common/src/components_effects.rs @@ -0,0 +1,143 @@ +use libcraft_effects::effects::Effect; +use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; +use std::collections::{BTreeMap, BTreeSet}; + +/// Storing effects info. +#[derive(Copy, Clone, Debug, Hash, Serialize, PartialEq, Eq, Deserialize)] +pub struct EffectApplication { + /// Strength Level of effect. + pub amplifier: u8, + /// Tick-based duration of the effect. + pub duration: u32, + /// Effect flags + pub flags: EffectFlags, + + /// Store when effect was added, if start_tick == 0 effect not yet sent to client + pub start_tick: u64, +} +#[derive(Copy, Clone, Debug, Hash, Serialize, PartialEq, Eq, Deserialize)] +pub struct EffectFlags { + /// Whether spawn particles or not. + pub particle: bool, + /// Whether the effect was given by a beacon or not. + pub ambient: bool, + /// Show effect icon or not. + pub icon: bool, +} + +impl EffectApplication { + pub fn start_at(&mut self, start_tick: u64) { + self.start_tick = start_tick + } +} + +impl Ord for EffectApplication { + fn cmp(&self, other: &Self) -> Ordering { + if self.amplifier > other.amplifier || self.duration > other.duration { + Ordering::Greater + } else if self.amplifier == other.amplifier || self.duration == other.duration { + Ordering::Equal + } else { + Ordering::Less + } + } +} +impl PartialOrd for EffectApplication { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +macro_rules! impl_effect { + ($ident:ident) => { + #[derive(Serialize, Deserialize, Eq, PartialEq, Hash)] + pub struct $ident(pub BTreeSet); + impl $ident { + pub fn new() -> $ident { + $ident { 0: BTreeSet::new() } + } + pub fn add_effect(&mut self, effect: EffectApplication) -> bool { + self.0.insert(effect) + } + pub fn not_started(&mut self) -> Vec { + self.0 + .iter() + .filter(|effect| effect.start_tick == 0) + .cloned() + .collect::>() + } + pub fn ended_on_tick(&mut self, tick: u64) -> Vec { + self.0 + .iter() + .filter(|effect| { + tick >= effect.start_tick + effect.duration as u64 && effect.start_tick != 0 + }) + .cloned() + .collect::>() + } + pub fn active_effect(&mut self) -> Option<&EffectApplication> { + self.0.iter().last() + } + } + impl Default for $ident { + fn default() -> Self { + $ident::new() + } + } + bincode_component_impl!($ident); + }; +} + +impl_effect!(SpeedEffect); +impl_effect!(SlownessEffect); +impl_effect!(HasteEffect); +impl_effect!(MiningFatigueEffect); +impl_effect!(StrengthEffect); +impl_effect!(InstantHealthEffect); +impl_effect!(InstantDamageEffect); +impl_effect!(JumpBoostEffect); +impl_effect!(NauseaEffect); +impl_effect!(RegenerationEffect); +impl_effect!(ResistanceEffect); +impl_effect!(FireResistanceEffect); +impl_effect!(WaterBreathingEffect); +impl_effect!(InvisibilityEffect); +impl_effect!(BlindnessEffect); +impl_effect!(NightVisionEffect); +impl_effect!(HungerEffect); +impl_effect!(WeaknessEffect); +impl_effect!(PoisonEffect); +impl_effect!(WitherEffect); +impl_effect!(HealthBoostEffect); +impl_effect!(AbsorptionEffect); +impl_effect!(SaturationEffect); +impl_effect!(GlowingEffect); +impl_effect!(LevitationEffect); +impl_effect!(LuckEffect); +impl_effect!(BadLuckEffect); +impl_effect!(SlowFallingEffect); +impl_effect!(ConduitPowerEffect); +impl_effect!(DolphinsGraceEffect); +impl_effect!(BadOmenEffect); +impl_effect!(HeroOfTheVillageEffect); + +/// A walk speed modifier in percent +#[derive( + Clone, Debug, PartialEq, Serialize, Deserialize, derive_more::Deref, derive_more::DerefMut, +)] +pub struct WalkEffectModifier(pub BTreeMap); + +impl WalkEffectModifier { + pub fn new() -> WalkEffectModifier { + WalkEffectModifier { 0: BTreeMap::new() } + } +} + +impl Default for WalkEffectModifier { + fn default() -> Self { + WalkEffectModifier::new() + } +} + +bincode_component_impl!(WalkEffectModifier); diff --git a/quill/common/src/lib.rs b/quill/common/src/lib.rs index 53d8a262b..7bfb5625f 100644 --- a/quill/common/src/lib.rs +++ b/quill/common/src/lib.rs @@ -4,6 +4,7 @@ mod utils; pub mod component; pub mod block; pub mod components; +pub mod components_effects; pub mod entities; pub mod entity; pub mod entity_init;