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.

Working timer merging.

+288 -39
+190 -39
bevy_status_effects/src/lib.rs
··· 11 11 use bevy_ecs::world::DeferredWorld; 12 12 use bevy_reflect::prelude::ReflectDefault; 13 13 14 - use crate::timer::{Delay, Lifetime, TimerMergeMode}; 14 + use crate::timer::{Delay, EffectTimer, Lifetime, TimerMergeMode}; 15 15 pub use bevy_app::Startup; 16 16 use bevy_reflect::{Reflect, reflect_trait}; 17 17 pub use bevy_status_effects_macros::StatusEffect; ··· 70 70 }; 71 71 72 72 let effected_by = match world.get::<EffectedBy>(target.0) { 73 - None => return, 74 73 Some(e) => e.collection().clone(), 74 + None => return, 75 75 }; 76 76 77 - for effect in effected_by { 77 + let old = effected_by.iter().find_map(|entity| { 78 78 // `EffectedBy` not updated until later. 79 - assert_ne!(effect, context.entity); 79 + assert_ne!(*entity, context.entity); 80 80 81 - let Some(other_mode) = world.get::<EffectMode>(effect) else { 82 - continue; 81 + let Some(other_mode) = world.get::<EffectMode>(*entity) else { 82 + return None; 83 83 }; 84 84 85 85 if mode != *other_mode { 86 - continue; 86 + return None; 87 87 } 88 88 89 - if world.get::<T>(effect).is_some() { 90 - world.commands().entity(effect).despawn(); 89 + world 90 + .get::<T>(*entity) 91 + .and_then(|effect| Some((*entity, effect))) 92 + }); 93 + 94 + if let Some((old_entity, _old_effect)) = old { 95 + match mode { 96 + EffectMode::Stack => return, 97 + EffectMode::Replace => world.commands().entity(old_entity).despawn(), 98 + } 99 + 100 + if let Some(old_lifetime) = world.get::<Lifetime>(old_entity).cloned() { 101 + if let Some(mut lifetime) = world.get_mut::<Lifetime>(context.entity) { 102 + lifetime.merge(&old_lifetime) 103 + } 104 + } 105 + 106 + if let Some(old_delay) = world.get::<Delay>(old_entity).cloned() { 107 + if let Some(mut delay) = world.get_mut::<Delay>(context.entity) { 108 + delay.merge(&old_delay) 109 + } 91 110 } 92 111 } 93 112 } ··· 97 116 use super::*; 98 117 use crate as bevy_status_effects; 99 118 use bevy_status_effects_macros::StatusEffect; 119 + use bevy_time::{Timer, TimerMode}; 120 + use std::time::Duration; 100 121 101 122 #[derive(StatusEffect, Component, Debug, Eq, PartialEq, Default)] 102 - struct RefreshOverride; 123 + struct MyEffect; 103 124 104 125 #[test] 105 126 fn stack() { 106 127 let mut world = World::new(); 107 128 world 108 - .register_component_hooks::<RefreshOverride>() 109 - .on_add(effect_refresh_hook::<RefreshOverride>); 129 + .register_component_hooks::<MyEffect>() 130 + .on_add(effect_refresh_hook::<MyEffect>); 110 131 111 132 let target = world.spawn_empty().id(); 112 - let first = world.spawn((RefreshOverride, Effecting(target))).id(); 113 - let second = world.spawn((RefreshOverride, Effecting(target))).id(); 133 + let first = world.spawn((MyEffect, Effecting(target))).id(); 134 + let second = world.spawn((MyEffect, Effecting(target))).id(); 114 135 115 136 world.flush(); 116 137 117 - assert_eq!(world.get::<RefreshOverride>(first), Some(&RefreshOverride)); 118 - assert_eq!(world.get::<RefreshOverride>(second), Some(&RefreshOverride)); 138 + assert_eq!(world.get::<MyEffect>(first), Some(&MyEffect)); 139 + assert_eq!(world.get::<MyEffect>(second), Some(&MyEffect)); 119 140 } 120 141 121 142 #[test] 122 143 fn refresh() { 123 144 let mut world = World::new(); 124 145 world 125 - .register_component_hooks::<RefreshOverride>() 126 - .on_add(effect_refresh_hook::<RefreshOverride>); 146 + .register_component_hooks::<MyEffect>() 147 + .on_add(effect_refresh_hook::<MyEffect>); 127 148 128 149 let target = world.spawn_empty().id(); 129 150 let first = world 130 - .spawn((RefreshOverride, Effecting(target), EffectMode::Replace)) 151 + .spawn((MyEffect, Effecting(target), EffectMode::Replace)) 131 152 .id(); 132 153 let second = world 133 - .spawn((RefreshOverride, Effecting(target), EffectMode::Replace)) 154 + .spawn((MyEffect, Effecting(target), EffectMode::Replace)) 134 155 .id(); 135 156 136 157 world.flush(); 137 158 138 - assert_eq!(world.get::<RefreshOverride>(first), None); 139 - assert_eq!(world.get::<RefreshOverride>(second), Some(&RefreshOverride)); 159 + assert_eq!(world.get::<MyEffect>(first), None); 160 + assert_eq!(world.get::<MyEffect>(second), Some(&MyEffect)); 140 161 } 141 162 142 163 #[test] 143 164 fn mixed() { 144 165 let mut world = World::new(); 145 166 world 146 - .register_component_hooks::<RefreshOverride>() 147 - .on_add(effect_refresh_hook::<RefreshOverride>); 167 + .register_component_hooks::<MyEffect>() 168 + .on_add(effect_refresh_hook::<MyEffect>); 148 169 149 170 let target = world.spawn_empty().id(); 150 171 151 - let stack_1 = world.spawn((RefreshOverride, Effecting(target))).id(); 172 + let stack_1 = world.spawn((MyEffect, Effecting(target))).id(); 152 173 let stack_2 = world 153 - .spawn((RefreshOverride, Effecting(target), EffectMode::Stack)) 174 + .spawn((MyEffect, Effecting(target), EffectMode::Stack)) 154 175 .id(); 155 176 156 177 let replace_1 = world 157 - .spawn((RefreshOverride, Effecting(target), EffectMode::Replace)) 178 + .spawn((MyEffect, Effecting(target), EffectMode::Replace)) 158 179 .id(); 159 180 let replace_2 = world 160 - .spawn((RefreshOverride, Effecting(target), EffectMode::Replace)) 181 + .spawn((MyEffect, Effecting(target), EffectMode::Replace)) 182 + .id(); 183 + 184 + world.flush(); 185 + 186 + assert_eq!(world.get::<MyEffect>(stack_1), Some(&MyEffect)); 187 + assert_eq!(world.get::<MyEffect>(stack_2), Some(&MyEffect)); 188 + 189 + assert_eq!(world.get::<MyEffect>(replace_1), None); 190 + assert_eq!(world.get::<MyEffect>(replace_2), Some(&MyEffect)); 191 + } 192 + 193 + #[test] 194 + fn timer_replace() { 195 + let mut world = World::new(); 196 + world 197 + .register_component_hooks::<MyEffect>() 198 + .on_add(effect_refresh_hook::<MyEffect>); 199 + 200 + let target = world.spawn_empty().id(); 201 + let second_lifetime = Lifetime::from_seconds(2.0).with_mode(TimerMergeMode::Replace); 202 + world.spawn(( 203 + MyEffect, 204 + Effecting(target), 205 + EffectMode::Replace, 206 + Lifetime::from_seconds(1.0).with_mode(TimerMergeMode::Replace), 207 + )); 208 + let second = world 209 + .spawn(( 210 + MyEffect, 211 + Effecting(target), 212 + EffectMode::Replace, 213 + second_lifetime.clone(), 214 + )) 215 + .id(); 216 + 217 + world.flush(); 218 + 219 + assert_eq!(world.get::<Lifetime>(second), Some(&second_lifetime)); 220 + } 221 + 222 + #[test] 223 + fn timer_inherit() { 224 + let mut world = World::new(); 225 + world 226 + .register_component_hooks::<MyEffect>() 227 + .on_add(effect_refresh_hook::<MyEffect>); 228 + 229 + let target = world.spawn_empty().id(); 230 + let first_delay = Delay::from_seconds(1.0).with_mode(TimerMergeMode::Inherit); 231 + 232 + world.spawn(( 233 + MyEffect, 234 + Effecting(target), 235 + EffectMode::Replace, 236 + first_delay.clone(), 237 + )); 238 + let second = world 239 + .spawn(( 240 + MyEffect, 241 + Effecting(target), 242 + EffectMode::Replace, 243 + Delay::from_seconds(2.0).with_mode(TimerMergeMode::Inherit), 244 + )) 161 245 .id(); 162 246 163 247 world.flush(); 164 248 165 - assert_eq!( 166 - world.get::<RefreshOverride>(stack_1), 167 - Some(&RefreshOverride) 168 - ); 169 - assert_eq!( 170 - world.get::<RefreshOverride>(stack_2), 171 - Some(&RefreshOverride) 172 - ); 249 + assert_eq!(world.get::<Delay>(second), Some(&first_delay)); 250 + } 251 + 252 + #[test] 253 + fn timer_fraction() { 254 + let mut world = World::new(); 255 + world 256 + .register_component_hooks::<MyEffect>() 257 + .on_add(effect_refresh_hook::<MyEffect>); 173 258 174 - assert_eq!(world.get::<RefreshOverride>(replace_1), None); 259 + let target = world.spawn_empty().id(); 260 + 261 + let mut first_timer = Timer::from_seconds(2.0, TimerMode::Once); 262 + first_timer.tick(Duration::from_secs_f32(1.0)); 263 + 264 + world.spawn(( 265 + MyEffect, 266 + Effecting(target), 267 + EffectMode::Replace, 268 + Delay { 269 + timer: first_timer, 270 + mode: TimerMergeMode::Fraction, 271 + }, 272 + )); 273 + let second = world 274 + .spawn(( 275 + MyEffect, 276 + Effecting(target), 277 + EffectMode::Replace, 278 + Delay::from_seconds(10.0).with_mode(TimerMergeMode::Fraction), 279 + )) 280 + .id(); 281 + 282 + world.flush(); 283 + 284 + let mut expected_timer = Timer::from_seconds(10.0, TimerMode::Repeating); 285 + expected_timer.tick(Duration::from_secs_f32(5.0)); 286 + 175 287 assert_eq!( 176 - world.get::<RefreshOverride>(replace_2), 177 - Some(&RefreshOverride) 288 + world.get::<Delay>(second), 289 + Some(&Delay { 290 + timer: expected_timer, 291 + mode: TimerMergeMode::Fraction, 292 + }) 178 293 ); 294 + } 295 + 296 + #[test] 297 + fn timer_max() { 298 + let mut world = World::new(); 299 + world 300 + .register_component_hooks::<MyEffect>() 301 + .on_add(effect_refresh_hook::<MyEffect>); 302 + 303 + let target = world.spawn_empty().id(); 304 + let max = Delay::from_seconds(3.0).with_mode(TimerMergeMode::Max); 305 + 306 + world.spawn(( 307 + MyEffect, 308 + Effecting(target), 309 + EffectMode::Replace, 310 + Delay::from_seconds(1.0).with_mode(TimerMergeMode::Max), 311 + )); 312 + world.spawn(( 313 + MyEffect, 314 + Effecting(target), 315 + EffectMode::Replace, 316 + max.clone(), 317 + )); 318 + let third = world 319 + .spawn(( 320 + MyEffect, 321 + Effecting(target), 322 + EffectMode::Replace, 323 + Delay::from_seconds(2.0).with_mode(TimerMergeMode::Max), 324 + )) 325 + .id(); 326 + 327 + world.flush(); 328 + 329 + assert_eq!(world.get::<Delay>(third), Some(&max)); 179 330 } 180 331 }
+98
bevy_status_effects/src/timer.rs
··· 12 12 } 13 13 14 14 fn with_mode(self, mode: TimerMergeMode) -> Self; 15 + 16 + /// Merges a new timer (self) into an existing one (other). 17 + fn merge(&mut self, other: &Self); 15 18 } 16 19 17 20 /// Despawns the entity when the timer finishes. ··· 34 37 self.mode = mode; 35 38 self 36 39 } 40 + 41 + // Todo Remove duplicates, maybe by adding 42 + // ``` 43 + // fn merge(&mut self, other: Self, mode: TimerMergeMode) 44 + // ``` 45 + // method to `Timer`. 46 + fn merge(&mut self, other: &Self) { 47 + match self.mode { 48 + TimerMergeMode::Replace => {} 49 + TimerMergeMode::Inherit => self.timer = other.timer.clone(), 50 + TimerMergeMode::Fraction => { 51 + let fraction = other.timer.fraction(); 52 + let duration = self.timer.duration().as_secs_f32(); 53 + self.timer 54 + .set_elapsed(Duration::from_secs_f32(fraction * duration)) 55 + } 56 + TimerMergeMode::Max => { 57 + let old = other.timer.remaining_secs(); 58 + let new = self.timer.remaining_secs(); 59 + 60 + if old > new { 61 + self.timer = other.timer.clone() 62 + } 63 + } 64 + } 65 + } 37 66 } 38 67 39 68 impl Default for Lifetime { ··· 65 94 self.mode = mode; 66 95 self 67 96 } 97 + 98 + fn merge(&mut self, other: &Self) { 99 + match self.mode { 100 + TimerMergeMode::Replace => {} 101 + TimerMergeMode::Inherit => self.timer = other.timer.clone(), 102 + TimerMergeMode::Fraction => { 103 + let fraction = other.timer.fraction(); 104 + let duration = self.timer.duration().as_secs_f32(); 105 + self.timer 106 + .set_elapsed(Duration::from_secs_f32(fraction * duration)) 107 + } 108 + TimerMergeMode::Max => { 109 + let old = other.timer.remaining_secs(); 110 + let new = self.timer.remaining_secs(); 111 + 112 + if old > new { 113 + self.timer = other.timer.clone() 114 + } 115 + } 116 + } 117 + } 68 118 } 69 119 70 120 impl Default for Delay { ··· 108 158 delay.timer.tick(time.delta()); 109 159 } 110 160 } 161 + 162 + #[cfg(test)] 163 + mod tests { 164 + use super::*; 165 + 166 + #[test] 167 + fn merge_replace() { 168 + let first = Lifetime::from_seconds(1.0).with_mode(TimerMergeMode::Replace); 169 + let second = Lifetime::from_seconds(2.0).with_mode(TimerMergeMode::Replace); 170 + let mut result = second.clone(); 171 + result.merge(&first); 172 + 173 + assert_eq!(result, second); 174 + } 175 + 176 + #[test] 177 + fn merge_inherit() { 178 + let first = Lifetime::from_seconds(1.0).with_mode(TimerMergeMode::Inherit); 179 + let second = Lifetime::from_seconds(2.0).with_mode(TimerMergeMode::Inherit); 180 + let mut result = second.clone(); 181 + result.merge(&first); 182 + 183 + assert_eq!(result, first); 184 + } 185 + 186 + #[test] 187 + fn merge_fraction() { 188 + let first = Lifetime::from_seconds(1.0).with_mode(TimerMergeMode::Fraction); 189 + let second = Lifetime::from_seconds(2.0).with_mode(TimerMergeMode::Fraction); 190 + let mut result = second.clone(); 191 + result.merge(&first); 192 + 193 + assert_eq!(result, second); 194 + } 195 + 196 + #[test] 197 + fn merge_max() { 198 + let first = Lifetime::from_seconds(1.0).with_mode(TimerMergeMode::Max); 199 + let mut second = Lifetime::from_seconds(3.0).with_mode(TimerMergeMode::Max); 200 + second.merge(&first); 201 + let third = Lifetime::from_seconds(2.0).with_mode(TimerMergeMode::Max); 202 + 203 + let mut result = third.clone(); 204 + result.merge(&second); 205 + 206 + assert_eq!(result, second); 207 + } 208 + }