An experimental, status effects-as-entities system for Bevy.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Reorganizing for reexports.

Also added lints.

+94 -84
+7
bevy_status_effects/Cargo.toml
··· 29 29 bevy_time = { version = "0.16.0", default-features = false, features = [ 30 30 "bevy_reflect", 31 31 ] } 32 + 33 + [lints.rust] 34 + missing_docs = "warn" 35 + unexpected_cfgs = { level = "warn", check-cfg = ['cfg(docsrs_dep)'] } 36 + unsafe_code = "deny" 37 + unsafe_op_in_unsafe_fn = "warn" 38 + unused_qualifications = "warn"
+70
bevy_status_effects/src/hook.rs
··· 1 + use crate::relation::{EffectedBy, Effecting}; 2 + use crate::timer::{Delay, EffectTimer, Lifetime}; 3 + use crate::{EffectMode, StatusEffect}; 4 + use bevy_ecs::component::HookContext; 5 + use bevy_ecs::prelude::{Component, RelationshipTarget, World}; 6 + use bevy_ecs::world::DeferredWorld; 7 + 8 + pub fn init_effect_hook<T: Component + StatusEffect>(world: &mut World) { 9 + world 10 + .register_component_hooks::<T>() 11 + .on_add(effect_refresh_hook::<T>); 12 + } 13 + 14 + fn effect_refresh_hook<T: Component + StatusEffect>( 15 + mut world: DeferredWorld, 16 + context: HookContext, 17 + ) { 18 + let Some(mode) = world.get::<EffectMode>(context.entity).copied() else { 19 + return; 20 + }; 21 + 22 + if mode == EffectMode::Stack { 23 + return; 24 + } 25 + 26 + let Some(target) = world.get::<Effecting>(context.entity) else { 27 + return; 28 + }; 29 + 30 + let effected_by = match world.get::<EffectedBy>(target.0) { 31 + Some(e) => e.collection().clone(), 32 + None => return, 33 + }; 34 + 35 + let old = effected_by.iter().find_map(|entity| { 36 + // `EffectedBy` not updated until later. 37 + assert_ne!(*entity, context.entity); 38 + 39 + let Some(other_mode) = world.get::<EffectMode>(*entity) else { 40 + return None; 41 + }; 42 + 43 + if mode != *other_mode { 44 + return None; 45 + } 46 + 47 + world 48 + .get::<T>(*entity) 49 + .and_then(|effect| Some((*entity, effect))) 50 + }); 51 + 52 + if let Some((old_entity, _old_effect)) = old { 53 + match mode { 54 + EffectMode::Stack => return, 55 + EffectMode::Replace => world.commands().entity(old_entity).despawn(), 56 + } 57 + 58 + if let Some(old_lifetime) = world.get::<Lifetime>(old_entity).cloned() { 59 + if let Some(mut lifetime) = world.get_mut::<Lifetime>(context.entity) { 60 + lifetime.merge(&old_lifetime) 61 + } 62 + } 63 + 64 + if let Some(old_delay) = world.get::<Delay>(old_entity).cloned() { 65 + if let Some(mut delay) = world.get_mut::<Delay>(context.entity) { 66 + delay.merge(&old_delay) 67 + } 68 + } 69 + } 70 + }
+13 -76
bevy_status_effects/src/lib.rs
··· 1 - pub mod relation; 2 - pub mod timer; 1 + //! Relationship-based status effects for bevy. 3 2 4 - use crate::relation::{EffectedBy, Effecting}; 3 + mod hook; 4 + mod relation; 5 + mod timer; 6 + 5 7 use bevy_app::{App, Plugin, PreUpdate}; 6 - use bevy_ecs::component::HookContext; 7 8 use bevy_ecs::prelude::*; 8 - use bevy_ecs::world::DeferredWorld; 9 9 use bevy_reflect::prelude::ReflectDefault; 10 + use bevy_reflect::{Reflect, reflect_trait}; 10 11 11 - use crate::timer::{Delay, EffectTimer, Lifetime, TimerMergeMode}; 12 - pub use bevy_app::Startup; 13 - use bevy_reflect::{Reflect, reflect_trait}; 14 12 pub use bevy_status_effects_macros::StatusEffect; 13 + pub use hook::*; 14 + pub use relation::*; 15 + pub use timer::*; 16 + 17 + #[doc(hidden)] 18 + pub use bevy_app::Startup as __Startup; 15 19 16 20 pub struct StatusEffectPlugin; 17 21 ··· 23 27 .register_type::<Lifetime>() 24 28 .register_type::<Delay>() 25 29 .register_type::<TimerMergeMode>() 26 - .add_systems( 27 - PreUpdate, 28 - (timer::despawn_finished_lifetimes, timer::tick_delay).chain(), 29 - ); 30 + .add_systems(PreUpdate, (despawn_finished_lifetimes, tick_delay).chain()); 30 31 } 31 32 } 32 33 ··· 43 44 // Todo 44 45 // Merge, 45 46 } 46 - 47 - pub fn init_effect_hook<T: Component + StatusEffect>(world: &mut World) { 48 - world 49 - .register_component_hooks::<T>() 50 - .on_add(effect_refresh_hook::<T>); 51 - } 52 - 53 - fn effect_refresh_hook<T: Component + StatusEffect>( 54 - mut world: DeferredWorld, 55 - context: HookContext, 56 - ) { 57 - let Some(mode) = world.get::<EffectMode>(context.entity).copied() else { 58 - return; 59 - }; 60 - 61 - if mode == EffectMode::Stack { 62 - return; 63 - } 64 - 65 - let Some(target) = world.get::<Effecting>(context.entity) else { 66 - return; 67 - }; 68 - 69 - let effected_by = match world.get::<EffectedBy>(target.0) { 70 - Some(e) => e.collection().clone(), 71 - None => return, 72 - }; 73 - 74 - let old = effected_by.iter().find_map(|entity| { 75 - // `EffectedBy` not updated until later. 76 - assert_ne!(*entity, context.entity); 77 - 78 - let Some(other_mode) = world.get::<EffectMode>(*entity) else { 79 - return None; 80 - }; 81 - 82 - if mode != *other_mode { 83 - return None; 84 - } 85 - 86 - world 87 - .get::<T>(*entity) 88 - .and_then(|effect| Some((*entity, effect))) 89 - }); 90 - 91 - if let Some((old_entity, _old_effect)) = old { 92 - match mode { 93 - EffectMode::Stack => return, 94 - EffectMode::Replace => world.commands().entity(old_entity).despawn(), 95 - } 96 - 97 - if let Some(old_lifetime) = world.get::<Lifetime>(old_entity).cloned() { 98 - if let Some(mut lifetime) = world.get_mut::<Lifetime>(context.entity) { 99 - lifetime.merge(&old_lifetime) 100 - } 101 - } 102 - 103 - if let Some(old_delay) = world.get::<Delay>(old_entity).cloned() { 104 - if let Some(mut delay) = world.get_mut::<Delay>(context.entity) { 105 - delay.merge(&old_delay) 106 - } 107 - } 108 - } 109 - }
-2
bevy_status_effects/tests/bevy_butler.rs
··· 3 3 use bevy_app::App; 4 4 use bevy_butler::butler_plugin; 5 5 use bevy_ecs::prelude::Component; 6 - use bevy_status_effects::relation::Effecting; 7 6 use bevy_status_effects::*; 8 - use bevy_status_effects_macros::StatusEffect; 9 7 10 8 #[butler_plugin] 11 9 struct ButlerPlugin;
-2
bevy_status_effects/tests/merge_mode.rs
··· 1 1 use bevy_ecs::prelude::*; 2 - use bevy_status_effects::relation::Effecting; 3 - use bevy_status_effects::timer::{Delay, EffectTimer, Lifetime, TimerMergeMode}; 4 2 use bevy_status_effects::*; 5 3 use bevy_time::*; 6 4 use std::time::Duration;
+1 -1
bevy_status_effects/tests/timer.rs
··· 1 - use bevy_status_effects::timer::{EffectTimer, Lifetime, TimerMergeMode}; 1 + use bevy_status_effects::*; 2 2 3 3 #[test] 4 4 fn merge_replace() {
+3 -3
bevy_status_effects_macros/src/bevy_butler.rs
··· 39 39 if let Some(plugin_path) = &self.plugin { 40 40 let ident = &self.ident; 41 41 let plugin = &plugin_path.0; 42 - let use_as = format_ident!("__{ident}_component"); 42 + let use_as = format_ident!("__{ident}_status_effect"); 43 43 44 44 // Due to some strange import scoping issues, we cannot use the plugins. 45 45 // Instead, we can just recreate the plugin's functionality. 46 46 tokens.extend(quote! { 47 47 #[bevy_butler::add_system( 48 48 generics = <#ident>, 49 - plugin = #plugin, schedule = 50 - bevy_status_effects::Startup 49 + plugin = #plugin, 50 + schedule = bevy_status_effects::__Startup 51 51 )] 52 52 use bevy_status_effects::init_effect_hook as #use_as; 53 53 });