diff --git a/Cargo.lock b/Cargo.lock index 07cfe7aa8c..86350ff101 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7088,6 +7088,7 @@ dependencies = [ "tracing", "vek 0.15.8", "veloren-common", + "veloren-common-assets", "veloren-common-base", "veloren-common-ecs", "veloren-common-net", diff --git a/assets/voxygen/i18n/en/command.ftl b/assets/voxygen/i18n/en/command.ftl index cbc6007b08..4b76ba0b6a 100644 --- a/assets/voxygen/i18n/en/command.ftl +++ b/assets/voxygen/i18n/en/command.ftl @@ -64,6 +64,8 @@ command-battlemode-available-modes = Available modes: pvp, pve command-battlemode-same = Attempted to set the same battlemode command-battlemode-updated = New battlemode: { $battlemode } command-buff-unknown = Unknown buff: { $buff } +command-buff-data = Buff argument '{ $buff }' requires additional data +command-buff-body-unknown = Unknown body spec: { $spec } command-skillpreset-load-error = Error while loading presets command-skillpreset-broken = Skill preset is broken command-skillpreset-missing = Preset does not exist: { $preset } @@ -95,4 +97,4 @@ command-you-dont-exist = You do not exist, so you cannot use this command command-destroyed-tethers = All tethers destroyed! You are now free command-destroyed-no-tethers = You're not connected to any tethers command-dismounted = Dismounted -command-no-dismount = You're not riding or being ridden \ No newline at end of file +command-no-dismount = You're not riding or being ridden diff --git a/common/assets/src/lib.rs b/common/assets/src/lib.rs index 952c196e9d..f6368a20c9 100644 --- a/common/assets/src/lib.rs +++ b/common/assets/src/lib.rs @@ -31,14 +31,14 @@ pub use walk::{walk_tree, Walk}; #[cfg(feature = "plugins")] lazy_static! { -/// The HashMap where all loaded assets are stored in. -static ref ASSETS: plugin_cache::CombinedCache = plugin_cache::CombinedCache::new().unwrap(); + /// The HashMap where all loaded assets are stored in. + static ref ASSETS: plugin_cache::CombinedCache = plugin_cache::CombinedCache::new().unwrap(); } #[cfg(not(feature = "plugins"))] lazy_static! { -/// The HashMap where all loaded assets are stored in. -static ref ASSETS: AssetCache = - AssetCache::with_source(fs::FileSystem::new().unwrap()); + /// The HashMap where all loaded assets are stored in. + static ref ASSETS: AssetCache = + AssetCache::with_source(fs::FileSystem::new().unwrap()); } #[cfg(feature = "hot-reloading")] diff --git a/common/src/cmd.rs b/common/src/cmd.rs index a41a9c93b7..b26059550e 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -201,9 +201,10 @@ lazy_static! { }; static ref BUFFS: Vec = { - let mut buff_pack: Vec<_> = BUFF_PARSER.keys().cloned().collect(); - // Add all as valid command - buff_pack.push("all".to_string()); + let mut buff_pack: Vec = BUFF_PARSER.keys().cloned().collect(); + + // Add `all` as valid command + buff_pack.push("all".to_owned()); buff_pack }; @@ -429,6 +430,7 @@ impl ServerChatCommand { Enum("buff", BUFFS.clone(), Required), Float("strength", 0.01, Optional), Float("duration", 10.0, Optional), + Any("buff data spec", Optional), ], "Cast a buff on player", Some(Admin), diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index cef10d70c3..70f9b47905 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -165,19 +165,38 @@ pub enum BuffKind { /// Results from drinking a potion. /// Decreases the health gained from subsequent potions. PotionSickness, - /// Changed into another body. - Polymorphed, /// Slows movement speed and reduces energy reward. /// Both scales non-linearly to strength, 0.5 lead to movespeed reduction /// by 25% and energy reward reduced by 150%, 1.0 lead to MS reduction by /// 33.3% and energy reward reduced by 200%. Energy reward can't be /// reduced by more than 200%, to a minimum value of -100%. Heatstroke, + // Complex, non-obvious buffs + /// Changed into another body. + Polymorphed, +} + +/// Tells a little more about the buff kind than simple buff/debuff +pub enum BuffDescriptor { + /// Simple positive buffs, like `BuffKind::Saturation` + SimplePositive, + /// Simple negative buffs, like `BuffKind::Bleeding` + SimpleNegative, + /// Buffs that require unusual data that can't be governed just by strength + /// and duration, like `BuffKind::Polymorhped` + Complex, + // For future additions, we may want to tell about non-obvious buffs, + // like Agility. + // Also maybe extend Complex to differentiate between Positive, Negative + // and Neutral buffs? + // For now, Complex is assumed to be neutral/non-obvious. } impl BuffKind { - /// Checks if buff is buff or debuff. - pub fn is_buff(self) -> bool { + /// Tells a little more about buff kind than simple buff/debuff + /// + /// Read more in [BuffDescriptor]. + pub fn differentiate(self) -> BuffDescriptor { match self { BuffKind::Regeneration | BuffKind::Saturation @@ -202,7 +221,7 @@ impl BuffKind { | BuffKind::Sunderer | BuffKind::Defiance | BuffKind::Bloodfeast - | BuffKind::Berserk => true, + | BuffKind::Berserk => BuffDescriptor::SimplePositive, BuffKind::Bleeding | BuffKind::Cursed | BuffKind::Burning @@ -213,8 +232,23 @@ impl BuffKind { | BuffKind::Poisoned | BuffKind::Parried | BuffKind::PotionSickness - | BuffKind::Polymorphed - | BuffKind::Heatstroke => false, + | BuffKind::Heatstroke => BuffDescriptor::SimpleNegative, + BuffKind::Polymorphed => BuffDescriptor::Complex, + } + } + + /// Checks if buff is buff or debuff. + pub fn is_buff(self) -> bool { + match self.differentiate() { + BuffDescriptor::SimplePositive => true, + BuffDescriptor::SimpleNegative | BuffDescriptor::Complex => false, + } + } + + pub fn is_simple(self) -> bool { + match self.differentiate() { + BuffDescriptor::SimplePositive | BuffDescriptor::SimpleNegative => true, + BuffDescriptor::Complex => false, } } diff --git a/common/state/Cargo.toml b/common/state/Cargo.toml index 2e71eb301d..a94ba64808 100644 --- a/common/state/Cargo.toml +++ b/common/state/Cargo.toml @@ -6,7 +6,7 @@ version = "0.10.0" [features] simd = ["vek/platform_intrinsics"] -plugins = ["toml", "tar", "wasmer", "wasmer-wasix-types", "bincode", "plugin-api", "serde"] +plugins = ["common-assets/plugins", "toml", "tar", "wasmer", "wasmer-wasix-types", "bincode", "plugin-api", "serde"] default = ["simd"] @@ -15,6 +15,7 @@ common = { package = "veloren-common", path = ".." } common-net = { package = "veloren-common-net", path = "../net" } common-ecs = { package = "veloren-common-ecs", path = "../ecs" } common-base = { package = "veloren-common-base", path = "../base" } +common-assets = { package = "veloren-common-assets", path = "../assets", optional = true} rayon = { workspace = true } num_cpus = "1.0" diff --git a/server/src/cmd.rs b/server/src/cmd.rs index d7c7ae24c2..a478ca4108 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -26,7 +26,7 @@ use common::{ }, comp::{ self, - buff::{Buff, BuffData, BuffKind, BuffSource}, + buff::{Buff, BuffData, BuffKind, BuffSource, MiscBuffData}, inventory::{ item::{tool::AbilityMap, MaterialStatManifest, Quality}, slot::Slot, @@ -4140,49 +4140,128 @@ fn handle_buff( args: Vec, action: &ServerChatCommand, ) -> CmdResult<()> { - if let (Some(buff), strength, duration) = parse_cmd_args!(args, String, f32, f64) { - let strength = strength.unwrap_or(0.01); - let duration = duration.unwrap_or(1.0); + let (Some(buff), strength, duration, misc_data_spec) = + parse_cmd_args!(args, String, f32, f64, String) + else { + return Err(Content::Plain(action.help_string())); + }; + + let strength = strength.unwrap_or(0.01); + + if buff == "all" { + let duration = duration.unwrap_or(5.0); let buffdata = BuffData::new(strength, Some(Secs(duration))); - if buff != "all" { - cast_buff(&buff, buffdata, server, target) - } else { - for kind in BUFF_PACK.iter() { - cast_buff(kind, buffdata, server, target)?; - } - Ok(()) - } + + // apply every(*) non-complex buff + // + // (*) BUFF_PACK contains all buffs except + // invulnerability + BUFF_PACK + .iter() + .filter_map(|kind_key| parse_buffkind(kind_key)) + .filter(|buffkind| buffkind.is_simple()) + .for_each(|buffkind| cast_buff(buffkind, buffdata, server, target)); + Ok(()) } else { - Err(Content::Plain(action.help_string())) + let buffkind = parse_buffkind(&buff).ok_or_else(|| { + Content::localized_with_args("command-buff-unknown", [("buff", buff.clone())]) + })?; + + if buffkind.is_simple() { + let duration = duration.unwrap_or(10.0); + let buffdata = BuffData::new(strength, Some(Secs(duration))); + cast_buff(buffkind, buffdata, server, target); + Ok(()) + } else { + // default duration is longer for complex buffs + let duration = duration.unwrap_or(20.0); + let spec = misc_data_spec.ok_or_else(|| { + Content::localized_with_args("command-buff-data", [("buff", buff.clone())]) + })?; + cast_buff_complex(buffkind, server, target, spec, strength, duration) + } } } -fn cast_buff(kind: &str, data: BuffData, server: &mut Server, target: EcsEntity) -> CmdResult<()> { - if let Some(buffkind) = parse_buffkind(kind) { - let ecs = &server.state.ecs(); - let mut buffs_all = ecs.write_storage::(); - let stats = ecs.read_storage::(); - let healths = ecs.read_storage::(); - let time = ecs.read_resource::