From e243aa07f5e53da5c6367a9f4b45ce9d863ad560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20P=C3=A9rez?= <3212100-walpo@users.noreply.gitlab.com> Date: Sun, 19 Nov 2023 22:54:21 +0100 Subject: [PATCH 01/10] docs: remove references to the Twitter account --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 5c95e65561..647f6ab8b2 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,6 @@ Most Veloren servers require you to register with the official authentication se - [Website](https://veloren.net) - [Discord](https://discord.gg/veloren-community-449602562165833758) - [Matrix](https://matrix.to/#/#veloren-space:fachschaften.org) -- [Twitter](https://twitter.com/velorenproject) - [Mastodon](https://floss.social/@veloren) - [Reddit](https://www.reddit.com/r/Veloren) - [YouTube](https://youtube.com/@Veloren) From 5514df330b47474e9b6d14269ae4dc3020b5eedc Mon Sep 17 00:00:00 2001 From: juliancoffee <lightdarkdaughter@gmail.com> Date: Fri, 5 Jan 2024 19:56:41 +0200 Subject: [PATCH 02/10] Add BuffDescriptor enum --- common/src/comp/buff.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index cef10d70c3..d0ccf30d76 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -175,6 +175,21 @@ pub enum BuffKind { Heatstroke, } +/// Tells how buffs influence the target +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? +} + impl BuffKind { /// Checks if buff is buff or debuff. pub fn is_buff(self) -> bool { From f4939220ccccfe443e7eeb07de1dfd8ec75ce680 Mon Sep 17 00:00:00 2001 From: juliancoffee <lightdarkdaughter@gmail.com> Date: Fri, 5 Jan 2024 20:15:59 +0200 Subject: [PATCH 03/10] Add BuffKind::differentiate --- common/src/comp/buff.rs | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index d0ccf30d76..bc3c8ca93c 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -165,18 +165,19 @@ 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 how buffs influence the target -enum BuffDescriptor { +/// 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` @@ -188,11 +189,14 @@ enum BuffDescriptor { // 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 @@ -217,7 +221,7 @@ impl BuffKind { | BuffKind::Sunderer | BuffKind::Defiance | BuffKind::Bloodfeast - | BuffKind::Berserk => true, + | BuffKind::Berserk => BuffDescriptor::SimplePositive, BuffKind::Bleeding | BuffKind::Cursed | BuffKind::Burning @@ -228,8 +232,16 @@ 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, } } From 18742bc7fba2b7995ee2dd2aae266f3a79b5bdc8 Mon Sep 17 00:00:00 2001 From: juliancoffee <lightdarkdaughter@gmail.com> Date: Fri, 5 Jan 2024 21:36:09 +0200 Subject: [PATCH 04/10] Fix veloren-server compilation As veloren-server enables plugin feature automatically, it results in veloren-common-state inherit this feature, which enables common/state/plugin/mod.rs which asks for common::assets function that is enabled only if plugin feature is enabled, but because veloren-common-state doesn't depend on common::assets, this feature is kind of lost half-way. This commit fixes this by adding explicit optional dependency on common-assets in common-state that is enabled by plugin feature. --- Cargo.lock | 1 + common/assets/src/lib.rs | 10 +++++----- common/state/Cargo.toml | 3 ++- 3 files changed, 8 insertions(+), 6 deletions(-) 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/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<fs::FileSystem> = - AssetCache::with_source(fs::FileSystem::new().unwrap()); + /// The HashMap where all loaded assets are stored in. + static ref ASSETS: AssetCache<fs::FileSystem> = + AssetCache::with_source(fs::FileSystem::new().unwrap()); } #[cfg(feature = "hot-reloading")] 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" From 5aa30b017558065bdd5b227d796c69479b3ae5b6 Mon Sep 17 00:00:00 2001 From: juliancoffee <lightdarkdaughter@gmail.com> Date: Fri, 5 Jan 2024 21:40:34 +0200 Subject: [PATCH 05/10] Warn about complex buffs when using /buff --- assets/voxygen/i18n/en/command.ftl | 3 +- common/src/comp/buff.rs | 7 +++ server/src/cmd.rs | 76 +++++++++++++++++++----------- 3 files changed, 57 insertions(+), 29 deletions(-) diff --git a/assets/voxygen/i18n/en/command.ftl b/assets/voxygen/i18n/en/command.ftl index cbc6007b08..b840ca39cd 100644 --- a/assets/voxygen/i18n/en/command.ftl +++ b/assets/voxygen/i18n/en/command.ftl @@ -64,6 +64,7 @@ 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-complex = /buff doesn't work with this buff, use /buff_complex 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 +96,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/src/comp/buff.rs b/common/src/comp/buff.rs index bc3c8ca93c..70f9b47905 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -245,6 +245,13 @@ impl BuffKind { } } + pub fn is_simple(self) -> bool { + match self.differentiate() { + BuffDescriptor::SimplePositive | BuffDescriptor::SimpleNegative => true, + BuffDescriptor::Complex => false, + } + } + /// Checks if buff should queue. pub fn queues(self) -> bool { matches!(self, BuffKind::Saturation) } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index d7c7ae24c2..8c559a80ee 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -4145,10 +4145,30 @@ fn handle_buff( let duration = duration.unwrap_or(1.0); let buffdata = BuffData::new(strength, Some(Secs(duration))); if buff != "all" { - cast_buff(&buff, buffdata, server, target) + let buffkind = parse_buffkind(&buff).ok_or_else(|| { + Content::localized_with_args("command-buff-unknown", [("buff", buff.clone())]) + })?; + + if buffkind.is_simple() { + cast_buff(buffkind, buffdata, server, target) + } else { + return Err(Content::localized_with_args("command-buff-complex", [( + "buff", buff, + )])); + } } else { - for kind in BUFF_PACK.iter() { - cast_buff(kind, buffdata, server, target)?; + for kind_key in BUFF_PACK.iter() { + let buffkind = parse_buffkind(kind_key).ok_or_else(|| { + Content::localized_with_args("command-buff-unknown", [( + "buff", + kind_key.to_owned(), + )]) + })?; + + // Execute only simple buffs, ignore complex + if buffkind.is_simple() { + cast_buff(buffkind, buffdata, server, target)?; + } } Ok(()) } @@ -4157,33 +4177,33 @@ fn handle_buff( } } -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::<comp::Buffs>(); - let stats = ecs.read_storage::<comp::Stats>(); - let healths = ecs.read_storage::<comp::Health>(); - let time = ecs.read_resource::<Time>(); - if let Some(mut buffs) = buffs_all.get_mut(target) { - buffs.insert( - Buff::new( - buffkind, - data, - vec![], - BuffSource::Command, - *time, - stats.get(target), - healths.get(target), - ), +fn cast_buff( + buffkind: BuffKind, + data: BuffData, + server: &mut Server, + target: EcsEntity, +) -> CmdResult<()> { + let ecs = &server.state.ecs(); + let mut buffs_all = ecs.write_storage::<comp::Buffs>(); + let stats = ecs.read_storage::<comp::Stats>(); + let healths = ecs.read_storage::<comp::Health>(); + let time = ecs.read_resource::<Time>(); + if let Some(mut buffs) = buffs_all.get_mut(target) { + buffs.insert( + Buff::new( + buffkind, + data, + vec![], + BuffSource::Command, *time, - ); - } - Ok(()) - } else { - Err(Content::localized_with_args("command-buff-unknown", [( - "buff", kind, - )])) + stats.get(target), + healths.get(target), + ), + *time, + ); } + + Ok(()) } fn parse_buffkind(buff: &str) -> Option<BuffKind> { BUFF_PARSER.get(buff).copied() } From 2746a98f40f6621cd01c18173138128e11c1fce9 Mon Sep 17 00:00:00 2001 From: juliancoffee <lightdarkdaughter@gmail.com> Date: Sat, 6 Jan 2024 18:00:03 +0200 Subject: [PATCH 06/10] Add /buff_complex command --- assets/voxygen/i18n/en/command.ftl | 4 +- common/src/cmd.rs | 30 ++++++- server/src/cmd.rs | 140 +++++++++++++++++++++-------- 3 files changed, 132 insertions(+), 42 deletions(-) diff --git a/assets/voxygen/i18n/en/command.ftl b/assets/voxygen/i18n/en/command.ftl index b840ca39cd..963caa7360 100644 --- a/assets/voxygen/i18n/en/command.ftl +++ b/assets/voxygen/i18n/en/command.ftl @@ -64,7 +64,9 @@ 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-complex = /buff doesn't work with this buff, use /buff_complex +command-buff-complex = /buff doesn't work with [{ $buff }], use /buff_complex +command-buff-simple = /buff_complex doesn't work with [{ $buff }], use /buff +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 } diff --git a/common/src/cmd.rs b/common/src/cmd.rs index a41a9c93b7..a7d7228490 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -200,13 +200,23 @@ lazy_static! { buff_pack }; - static ref BUFFS: Vec<String> = { - let mut buff_pack: Vec<_> = BUFF_PARSER.keys().cloned().collect(); - // Add all as valid command + static ref BUFFS_SIMPLE: Vec<String> = { + let mut buff_pack: Vec<String> = BUFF_PARSER + .iter() + .filter_map(|(key, kind)| kind.is_simple().then_some(key.clone())) + .collect(); + + // Add `all` as valid command buff_pack.push("all".to_string()); buff_pack }; + static ref BUFFS_COMPLEX: Vec<String> = + BUFF_PARSER + .iter() + .filter_map(|(key, kind)| (!kind.is_simple()).then_some(key.clone())) + .collect(); + static ref BLOCK_KINDS: Vec<String> = terrain::block::BlockKind::iter() .map(|bk| bk.to_string()) .collect(); @@ -312,6 +322,7 @@ pub enum ServerChatCommand { BattleModeForce, Body, Buff, + BuffComplex, Build, Campfire, CreateLocation, @@ -426,13 +437,23 @@ impl ServerChatCommand { ), ServerChatCommand::Buff => cmd( vec![ - Enum("buff", BUFFS.clone(), Required), + Enum("buff", BUFFS_SIMPLE.clone(), Required), Float("strength", 0.01, Optional), Float("duration", 10.0, Optional), ], "Cast a buff on player", Some(Admin), ), + ServerChatCommand::BuffComplex => cmd( + vec![ + Enum("buff", BUFFS_COMPLEX.clone(), Required), + Any("buff data spec", Required), + Float("strength", 0.01, Optional), + Float("duration", 10.0, Optional), + ], + "Cast a complex buff on player", + Some(Admin), + ), ServerChatCommand::Ban => cmd( vec![ PlayerName(Required), @@ -934,6 +955,7 @@ impl ServerChatCommand { ServerChatCommand::BattleModeForce => "battlemode_force", ServerChatCommand::Body => "body", ServerChatCommand::Buff => "buff", + ServerChatCommand::BuffComplex => "buff_complex", ServerChatCommand::Build => "build", ServerChatCommand::AreaAdd => "area_add", ServerChatCommand::AreaList => "area_list", diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 8c559a80ee..afe0a243cc 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, @@ -131,6 +131,7 @@ fn do_command( ServerChatCommand::BattleModeForce => handle_battlemode_force, ServerChatCommand::Body => handle_body, ServerChatCommand::Buff => handle_buff, + ServerChatCommand::BuffComplex => handle_buff_complex, ServerChatCommand::Build => handle_build, ServerChatCommand::AreaAdd => handle_area_add, ServerChatCommand::AreaList => handle_area_list, @@ -4140,49 +4141,116 @@ fn handle_buff( args: Vec<String>, 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 buffdata = BuffData::new(strength, Some(Secs(duration))); - if buff != "all" { - let buffkind = parse_buffkind(&buff).ok_or_else(|| { - Content::localized_with_args("command-buff-unknown", [("buff", buff.clone())]) - })?; + let (Some(buff), strength, duration) = parse_cmd_args!(args, String, f32, f64) else { + return Err(Content::Plain(action.help_string())); + }; - if buffkind.is_simple() { - cast_buff(buffkind, buffdata, server, target) - } else { - return Err(Content::localized_with_args("command-buff-complex", [( - "buff", buff, - )])); - } - } else { - for kind_key in BUFF_PACK.iter() { - let buffkind = parse_buffkind(kind_key).ok_or_else(|| { - Content::localized_with_args("command-buff-unknown", [( - "buff", - kind_key.to_owned(), - )]) - })?; + let strength = strength.unwrap_or(0.01); + let duration = duration.unwrap_or(1.0); + let buffdata = BuffData::new(strength, Some(Secs(duration))); - // Execute only simple buffs, ignore complex - if buffkind.is_simple() { - cast_buff(buffkind, buffdata, server, target)?; - } - } - Ok(()) - } + if buff == "all" { + 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)); } 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() { + return Err(Content::localized_with_args("command-buff-complex", [( + "buff", buff, + )])); + } + + cast_buff(buffkind, buffdata, server, target); } + + Ok(()) } -fn cast_buff( - buffkind: BuffKind, - data: BuffData, +fn handle_buff_complex( server: &mut Server, + _client: EcsEntity, target: EcsEntity, + args: Vec<String>, + action: &ServerChatCommand, ) -> CmdResult<()> { + let (Some(buff), Some(spec), strength, duration) = + parse_cmd_args!(args, String, String, f32, f64) + else { + return 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() { + return Err(Content::localized_with_args("command-buff-simple", [( + "buff", buff, + )])); + } + + // explicit match to remember that this function exists + let misc_data = match buffkind { + BuffKind::Polymorphed => { + let Ok(npc::NpcBody(_id, mut body)) = spec.parse() else { + return Err(Content::localized_with_args("command-buff-body-unknown", [ + ("spec", spec.clone()), + ])); + }; + MiscBuffData::Body(body()) + }, + BuffKind::Regeneration + | BuffKind::Saturation + | BuffKind::Potion + | BuffKind::Agility + | BuffKind::CampfireHeal + | BuffKind::Frenzied + | BuffKind::EnergyRegen + | BuffKind::IncreaseMaxEnergy + | BuffKind::IncreaseMaxHealth + | BuffKind::Invulnerability + | BuffKind::ProtectingWard + | BuffKind::Hastened + | BuffKind::Fortitude + | BuffKind::Reckless + | BuffKind::Flame + | BuffKind::Frigid + | BuffKind::Lifesteal + | BuffKind::ImminentCritical + | BuffKind::Fury + | BuffKind::Sunderer + | BuffKind::Defiance + | BuffKind::Bloodfeast + | BuffKind::Berserk + | BuffKind::Bleeding + | BuffKind::Cursed + | BuffKind::Burning + | BuffKind::Crippled + | BuffKind::Frozen + | BuffKind::Wet + | BuffKind::Ensnared + | BuffKind::Poisoned + | BuffKind::Parried + | BuffKind::PotionSickness + | BuffKind::Heatstroke => unreachable!("is_simple() above"), + }; + + let strength = strength.unwrap_or(0.01); + let duration = duration.unwrap_or(20.0); + + let buffdata = BuffData::new(strength, Some(Secs(duration))).with_misc_data(misc_data); + + cast_buff(buffkind, buffdata, server, target); + Ok(()) +} + +fn cast_buff(buffkind: BuffKind, data: BuffData, server: &mut Server, target: EcsEntity) { let ecs = &server.state.ecs(); let mut buffs_all = ecs.write_storage::<comp::Buffs>(); let stats = ecs.read_storage::<comp::Stats>(); @@ -4202,8 +4270,6 @@ fn cast_buff( *time, ); } - - Ok(()) } fn parse_buffkind(buff: &str) -> Option<BuffKind> { BUFF_PARSER.get(buff).copied() } From 296f70c1b2c17667e10a8756cdcb2ce82f89bfd2 Mon Sep 17 00:00:00 2001 From: juliancoffee <lightdarkdaughter@gmail.com> Date: Mon, 8 Jan 2024 19:55:10 +0200 Subject: [PATCH 07/10] Unify /buff_complex and /buff Turns out parse_cmd_args allows omitting arguments, /buff <buff> [misc_data] will be idential to /buff <buff> [strength] [duration] [misc_data] --- assets/voxygen/i18n/en/command.ftl | 3 +- common/src/cmd.rs | 30 +++----------- server/src/cmd.rs | 63 +++++++++++++----------------- 3 files changed, 34 insertions(+), 62 deletions(-) diff --git a/assets/voxygen/i18n/en/command.ftl b/assets/voxygen/i18n/en/command.ftl index 963caa7360..4b76ba0b6a 100644 --- a/assets/voxygen/i18n/en/command.ftl +++ b/assets/voxygen/i18n/en/command.ftl @@ -64,8 +64,7 @@ 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-complex = /buff doesn't work with [{ $buff }], use /buff_complex -command-buff-simple = /buff_complex doesn't work with [{ $buff }], use /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 diff --git a/common/src/cmd.rs b/common/src/cmd.rs index a7d7228490..b26059550e 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -200,23 +200,14 @@ lazy_static! { buff_pack }; - static ref BUFFS_SIMPLE: Vec<String> = { - let mut buff_pack: Vec<String> = BUFF_PARSER - .iter() - .filter_map(|(key, kind)| kind.is_simple().then_some(key.clone())) - .collect(); + static ref BUFFS: Vec<String> = { + let mut buff_pack: Vec<String> = BUFF_PARSER.keys().cloned().collect(); // Add `all` as valid command - buff_pack.push("all".to_string()); + buff_pack.push("all".to_owned()); buff_pack }; - static ref BUFFS_COMPLEX: Vec<String> = - BUFF_PARSER - .iter() - .filter_map(|(key, kind)| (!kind.is_simple()).then_some(key.clone())) - .collect(); - static ref BLOCK_KINDS: Vec<String> = terrain::block::BlockKind::iter() .map(|bk| bk.to_string()) .collect(); @@ -322,7 +313,6 @@ pub enum ServerChatCommand { BattleModeForce, Body, Buff, - BuffComplex, Build, Campfire, CreateLocation, @@ -437,23 +427,14 @@ impl ServerChatCommand { ), ServerChatCommand::Buff => cmd( vec![ - Enum("buff", BUFFS_SIMPLE.clone(), Required), + 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), ), - ServerChatCommand::BuffComplex => cmd( - vec![ - Enum("buff", BUFFS_COMPLEX.clone(), Required), - Any("buff data spec", Required), - Float("strength", 0.01, Optional), - Float("duration", 10.0, Optional), - ], - "Cast a complex buff on player", - Some(Admin), - ), ServerChatCommand::Ban => cmd( vec![ PlayerName(Required), @@ -955,7 +936,6 @@ impl ServerChatCommand { ServerChatCommand::BattleModeForce => "battlemode_force", ServerChatCommand::Body => "body", ServerChatCommand::Buff => "buff", - ServerChatCommand::BuffComplex => "buff_complex", ServerChatCommand::Build => "build", ServerChatCommand::AreaAdd => "area_add", ServerChatCommand::AreaList => "area_list", diff --git a/server/src/cmd.rs b/server/src/cmd.rs index afe0a243cc..a478ca4108 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -131,7 +131,6 @@ fn do_command( ServerChatCommand::BattleModeForce => handle_battlemode_force, ServerChatCommand::Body => handle_body, ServerChatCommand::Buff => handle_buff, - ServerChatCommand::BuffComplex => handle_buff_complex, ServerChatCommand::Build => handle_build, ServerChatCommand::AreaAdd => handle_area_add, ServerChatCommand::AreaList => handle_area_list, @@ -4141,60 +4140,57 @@ fn handle_buff( args: Vec<String>, action: &ServerChatCommand, ) -> CmdResult<()> { - let (Some(buff), strength, duration) = parse_cmd_args!(args, String, f32, f64) else { + 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); - let duration = duration.unwrap_or(1.0); - let buffdata = BuffData::new(strength, Some(Secs(duration))); if buff == "all" { + let duration = duration.unwrap_or(5.0); + let buffdata = BuffData::new(strength, Some(Secs(duration))); + + // 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 { let buffkind = parse_buffkind(&buff).ok_or_else(|| { Content::localized_with_args("command-buff-unknown", [("buff", buff.clone())]) })?; - if !buffkind.is_simple() { - return Err(Content::localized_with_args("command-buff-complex", [( - "buff", buff, - )])); + 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) } - - cast_buff(buffkind, buffdata, server, target); } - - Ok(()) } -fn handle_buff_complex( +fn cast_buff_complex( + buffkind: BuffKind, server: &mut Server, - _client: EcsEntity, target: EcsEntity, - args: Vec<String>, - action: &ServerChatCommand, + spec: String, + strength: f32, + duration: f64, ) -> CmdResult<()> { - let (Some(buff), Some(spec), strength, duration) = - parse_cmd_args!(args, String, String, f32, f64) - else { - return 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() { - return Err(Content::localized_with_args("command-buff-simple", [( - "buff", buff, - )])); - } - // explicit match to remember that this function exists let misc_data = match buffkind { BuffKind::Polymorphed => { @@ -4241,9 +4237,6 @@ fn handle_buff_complex( | BuffKind::Heatstroke => unreachable!("is_simple() above"), }; - let strength = strength.unwrap_or(0.01); - let duration = duration.unwrap_or(20.0); - let buffdata = BuffData::new(strength, Some(Secs(duration))).with_misc_data(misc_data); cast_buff(buffkind, buffdata, server, target); From da08376e9aec818df75aad4b9da51c3f437581b4 Mon Sep 17 00:00:00 2001 From: Maxicarlos08 <maxicarlos08@gmail.com> Date: Sat, 13 Jan 2024 18:45:54 +0100 Subject: [PATCH 08/10] Don't stack buffs of the same kind with equal attributes --- common/src/combat.rs | 6 ++--- common/src/comp/buff.rs | 49 +++++++++++++++++++++++++------------- common/systems/src/buff.rs | 12 ++++------ 3 files changed, 39 insertions(+), 28 deletions(-) diff --git a/common/src/combat.rs b/common/src/combat.rs index 1a13ba314d..1d7f881eb4 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -791,7 +791,7 @@ impl AttackDamage { } } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct AttackEffect { target: Option<GroupTarget>, effect: CombatEffect, @@ -890,7 +890,7 @@ impl CombatEffect { } } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub enum CombatRequirement { AnyDamage, Energy(f32), @@ -898,7 +898,7 @@ pub enum CombatRequirement { TargetHasBuff(BuffKind), } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub enum DamagedEffect { Combo(i32), } diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index cef10d70c3..ec58a4c0af 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -460,9 +460,6 @@ pub struct BuffData { pub strength: f32, pub duration: Option<Secs>, pub delay: Option<Secs>, - /// Force the buff effects to be applied each tick, ignoring num_ticks - #[serde(default)] - pub force_immediate: bool, /// Used for buffs that have rider buffs (e.g. Flame, Frigid) pub secondary_duration: Option<Secs>, /// Used to add random data to buffs if needed (e.g. polymorphed) @@ -479,7 +476,6 @@ impl BuffData { Self { strength, duration, - force_immediate: false, delay: None, secondary_duration: None, misc_data: None, @@ -496,12 +492,6 @@ impl BuffData { self } - /// Force the buff effects to be applied each tick, ignoring num_ticks - pub fn with_force_immediate(mut self, force_immediate: bool) -> Self { - self.force_immediate = force_immediate; - self - } - pub fn with_misc_data(mut self, misc_data: MiscBuffData) -> Self { self.misc_data = Some(misc_data); self @@ -524,14 +514,14 @@ pub enum BuffCategory { SelfBuff, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub enum ModifierKind { Additive, Multiplicative, } /// Data indicating and configuring behaviour of a de/buff. -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub enum BuffEffect { /// Periodically damages or heals entity HealthChangeOverTime { @@ -782,11 +772,36 @@ impl Buffs { pub fn insert(&mut self, buff: Buff, current_time: Time) -> BuffKey { let kind = buff.kind; - let key = self.buffs.insert(buff); - self.kinds[kind] - .get_or_insert_with(|| (Vec::new(), current_time)) - .0 - .push(key); + // Try to find another buff with same data, cat_ids and source + let other_key = self.kinds[kind].as_ref().and_then(|(keys, _)| { + keys.iter() + .find(|key| { + self.buffs.get(**key).map_or(false, |other_buff| { + other_buff.data == buff.data + && other_buff.cat_ids == buff.cat_ids + && other_buff.source == buff.source + }) + }) + .copied() + }); + + // If another buff with the same fields is found, update end_time and effects + let key = if let Some((other_buff, key)) = + other_key.and_then(|key| Some((self.buffs.get_mut(key)?, key))) + { + other_buff.end_time = buff.end_time; + other_buff.effects = buff.effects; + key + // Otherwise, insert a new buff + } else { + let key = self.buffs.insert(buff); + self.kinds[kind] + .get_or_insert_with(|| (Vec::new(), current_time)) + .0 + .push(key); + key + }; + self.sort_kind(kind); if kind.queues() { self.delay_queueable_buffs(kind, current_time); diff --git a/common/systems/src/buff.rs b/common/systems/src/buff.rs index f7d48fcb18..c4f9ea769a 100644 --- a/common/systems/src/buff.rs +++ b/common/systems/src/buff.rs @@ -161,7 +161,7 @@ impl<'a> System<'a> for Sys { entity, buff_change: BuffChange::Add(Buff::new( BuffKind::Bleeding, - BuffData::new(1.0, Some(Secs(6.0))).with_force_immediate(true), + BuffData::new(1.0, Some(Secs(6.0))), Vec::new(), BuffSource::World, *read_data.time, @@ -179,7 +179,7 @@ impl<'a> System<'a> for Sys { entity, buff_change: BuffChange::Add(Buff::new( BuffKind::Bleeding, - BuffData::new(5.0, Some(Secs(3.0))).with_force_immediate(true), + BuffData::new(5.0, Some(Secs(3.0))), Vec::new(), BuffSource::World, *read_data.time, @@ -215,7 +215,7 @@ impl<'a> System<'a> for Sys { entity, buff_change: BuffChange::Add(Buff::new( BuffKind::Bleeding, - BuffData::new(15.0, Some(Secs(0.1))).with_force_immediate(true), + BuffData::new(15.0, Some(Secs(0.1))), Vec::new(), BuffSource::World, *read_data.time, @@ -420,7 +420,6 @@ impl<'a> System<'a> for Sys { execute_effect( effect, buff.kind, - &buff.data, buff.start_time, kind_start_time, &read_data, @@ -478,7 +477,6 @@ impl<'a> System<'a> for Sys { fn execute_effect( effect: &BuffEffect, buff_kind: BuffKind, - buff_data: &BuffData, buff_start_time: Time, buff_kind_start_time: Time, read_data: &ReadData, @@ -516,9 +514,7 @@ fn execute_effect( let prev_tick = ((time_passed - dt).max(0.0) / tick_dur.0).floor(); let whole_ticks = curr_tick - prev_tick; - if buff_data.force_immediate { - Some((1.0 / tick_dur.0 * dt) as f32) - } else if buff_will_expire { + if buff_will_expire { // If the buff is ending, include the fraction of progress towards the next // tick. let fractional_tick = (time_passed % tick_dur.0) / tick_dur.0; From df7c2ee70a2d9b1b7e6e8efa9f0b9fd4316fd2c6 Mon Sep 17 00:00:00 2001 From: Maxicarlos08 <maxicarlos08@gmail.com> Date: Sat, 13 Jan 2024 18:50:51 +0100 Subject: [PATCH 09/10] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ad39e4695..2f495ea27a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,6 +96,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed Perforate icon not displaying - Make cave entrances easier to follow - Renamed Twiggy Shoulders to match the Twig Armor set +- No longer stack buffs of the same kind with equal attributes, this could lead to a DoS if ie. an entity stayed long enough in lava. ## [0.15.0] - 2023-07-01 From 3a7bb698fc376fddd5e397050bd5e8e75b6f87d7 Mon Sep 17 00:00:00 2001 From: Maxicarlos08 <maxicarlos08@gmail.com> Date: Sat, 13 Jan 2024 19:53:02 +0100 Subject: [PATCH 10/10] handle overlap and queueing correctly --- common/src/comp/buff.rs | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index ec58a4c0af..8b98f38284 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -772,18 +772,26 @@ impl Buffs { pub fn insert(&mut self, buff: Buff, current_time: Time) -> BuffKey { let kind = buff.kind; - // Try to find another buff with same data, cat_ids and source - let other_key = self.kinds[kind].as_ref().and_then(|(keys, _)| { - keys.iter() - .find(|key| { - self.buffs.get(**key).map_or(false, |other_buff| { - other_buff.data == buff.data - && other_buff.cat_ids == buff.cat_ids - && other_buff.source == buff.source + // Try to find another overlaping non-queueable buff with same data, cat_ids and + // source. + let other_key = if kind.queues() { + None + } else { + self.kinds[kind].as_ref().and_then(|(keys, _)| { + keys.iter() + .find(|key| { + self.buffs.get(**key).map_or(false, |other_buff| { + other_buff.data == buff.data + && other_buff.cat_ids == buff.cat_ids + && other_buff.source == buff.source + && other_buff + .end_time + .map_or(true, |end_time| end_time.0 >= buff.start_time.0) + }) }) - }) - .copied() - }); + .copied() + }) + }; // If another buff with the same fields is found, update end_time and effects let key = if let Some((other_buff, key)) =