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.

Update tests, examples, and benches.

+151 -253
+16 -19
benches/insert_mode.rs
··· 6 6 use bevy_ecs::prelude::{Component, Entity, SpawnRelated}; 7 7 use criterion::{Criterion, criterion_group, criterion_main}; 8 8 9 - #[derive(Component)] 9 + #[derive(Component, Clone)] 10 10 struct BenchEffect; 11 11 12 12 fn init_app() -> (App, Entity) { ··· 17 17 .world_mut() 18 18 .spawn(( 19 19 Name::new("Target"), 20 - EffectedBy::spawn(EffectBundle { 21 - name: Name::new("Effect"), 22 - mode: EffectMode::Insert, 23 - bundle: BenchEffect, 24 - }), 20 + EffectedBy::spawn(EffectBundle(( 21 + Name::new("Effect"), 22 + EffectMode::Insert, 23 + BenchEffect, 24 + ))), 25 25 )) 26 26 .id(); 27 27 ··· 33 33 34 34 c.bench_function("Insert mode matched `with_effect`", |b| { 35 35 b.iter(|| { 36 - app.world_mut() 37 - .commands() 38 - .entity(entity) 39 - .with_effect(EffectBundle { 40 - name: Name::new("Effect"), 41 - mode: EffectMode::Insert, 42 - bundle: BenchEffect, 43 - }); 36 + app.world_mut().commands().entity(entity).with_effect(( 37 + Name::new("Effect"), 38 + EffectMode::Insert, 39 + BenchEffect, 40 + )); 44 41 app.world_mut().flush(); 45 42 }) 46 43 }); ··· 54 51 app.world_mut() 55 52 .commands() 56 53 .entity(entity) 57 - .insert(EffectedBy::spawn(EffectBundle { 58 - name: Name::new("Effect"), 59 - mode: EffectMode::Insert, 60 - bundle: BenchEffect, 61 - })); 54 + .insert(EffectedBy::spawn(EffectBundle(( 55 + Name::new("Effect"), 56 + EffectMode::Insert, 57 + BenchEffect, 58 + )))); 62 59 app.world_mut().flush(); 63 60 }) 64 61 });
+11 -14
benches/stack_mode.rs
··· 6 6 use bevy_ecs::prelude::{Component, Entity, SpawnRelated}; 7 7 use criterion::{Criterion, criterion_group, criterion_main}; 8 8 9 - #[derive(Component)] 9 + #[derive(Component, Clone)] 10 10 struct BenchEffect; 11 11 12 12 fn init_app() -> (App, Entity) { ··· 23 23 24 24 c.bench_function("Stack mode `with_effect`", |b| { 25 25 b.iter(|| { 26 - app.world_mut() 27 - .commands() 28 - .entity(entity) 29 - .with_effect(EffectBundle { 30 - name: Name::new("Effect"), 31 - mode: EffectMode::Stack, 32 - bundle: BenchEffect, 33 - }); 26 + app.world_mut().commands().entity(entity).with_effect(( 27 + Name::new("Effect"), 28 + EffectMode::Stack, 29 + BenchEffect, 30 + )); 34 31 app.world_mut().flush(); 35 32 }) 36 33 }); ··· 44 41 app.world_mut() 45 42 .commands() 46 43 .entity(entity) 47 - .insert(EffectedBy::spawn(EffectBundle { 48 - name: Name::new("Effect"), 49 - mode: EffectMode::Stack, 50 - bundle: BenchEffect, 51 - })); 44 + .insert(EffectedBy::spawn(EffectBundle(( 45 + Name::new("Effect"), 46 + EffectMode::Stack, 47 + BenchEffect, 48 + )))); 52 49 app.world_mut().flush(); 53 50 }) 54 51 });
+4 -6
docs/effected_by_spawn_example.md
··· 2 2 # use bevy::prelude::*; 3 3 # use bevy_alchemy::*; 4 4 # 5 - # #[derive(Component, Default)] 5 + # #[derive(Component, Default, Clone)] 6 6 # struct MyEffect; 7 7 # 8 8 # fn main() { ··· 11 11 # let mut commands = world.commands(); 12 12 commands.spawn(( 13 13 Name::new("Target"), 14 - EffectedBy::spawn(EffectBundle { 15 - name: Name::new("Effect"), 16 - bundle: MyEffect, 17 - ..default() 18 - }), 14 + EffectedBy::spawn( 15 + EffectBundle((Name::new("Effect"), MyEffect)) 16 + ), 19 17 )); 20 18 # } 21 19 ```
+2 -6
docs/with_effect_example.md
··· 2 2 # use bevy::prelude::*; 3 3 # use bevy_alchemy::*; 4 4 # 5 - # #[derive(Component, Default)] 5 + # #[derive(Component, Default, Clone)] 6 6 # struct MyEffect; 7 7 # 8 8 # fn main() { 9 9 # let mut world = World::new(); 10 10 # let target = world.spawn_empty().id(); 11 11 # let mut commands = world.commands(); 12 - commands.entity(target).with_effect(EffectBundle { 13 - name: Name::new("Effect"), 14 - bundle: MyEffect, 15 - ..default() 16 - }); 12 + commands.entity(target).with_effect((Name::new("Effect"), MyEffect)); 17 13 # } 18 14 ```
+3 -12
docs/with_effects_example.md
··· 2 2 # use bevy::prelude::*; 3 3 # use bevy_alchemy::*; 4 4 # 5 - # #[derive(Component, Default)] 5 + # #[derive(Component, Default, Clone)] 6 6 # struct MyEffect; 7 7 # 8 8 # fn main() { ··· 10 10 # let target = world.spawn_empty().id(); 11 11 # let mut commands = world.commands(); 12 12 commands.entity(target).with_effects(|effects| { 13 - effects.spawn(EffectBundle { 14 - name: Name::new("EffectA"), 15 - bundle: MyEffect, 16 - ..default() 17 - }); 18 - 19 - effects.spawn(EffectBundle { 20 - name: Name::new("EffectB"), 21 - bundle: MyEffect, 22 - ..default() 23 - }); 13 + effects.spawn((Name::new("EffectA"), MyEffect)); 14 + effects.spawn((Name::new("EffectB"), MyEffect)); 24 15 }); 25 16 # } 26 17 ```
+10 -13
examples/immediate_stats/decaying_speed.rs
··· 26 26 struct MovementSpeed(Stat); 27 27 28 28 /// Applies a speed boost, which decreases throughout its duration. 29 - #[derive(Component, Default)] 29 + #[derive(Component, Default, Clone)] 30 30 struct DecayingSpeed { 31 31 start_speed_boost: Modifier, 32 32 } ··· 48 48 return; 49 49 } 50 50 51 - commands.entity(*target).with_effect(EffectBundle { 52 - mode: EffectMode::Insert, // Block having multiple of effect stacked on a single target. 53 - bundle: ( 54 - Lifetime::from_seconds(2.0), // The duration of the effect. 55 - DecayingSpeed { 56 - start_speed_boost: Modifier { 57 - bonus: 10, 58 - multiplier: 2.0, 59 - }, 51 + commands.entity(*target).with_effect(( 52 + EffectMode::Insert, // Block having multiple of effect stacked on a single target. 53 + Lifetime::from_seconds(2.0), // The duration of the effect. 54 + DecayingSpeed { 55 + start_speed_boost: Modifier { 56 + bonus: 10, 57 + multiplier: 2.0, 60 58 }, 61 - ), 62 - ..default() 63 - }); 59 + }, 60 + )); 64 61 } 65 62 66 63 /// Applies the effect to the target. Because of how Immediate Stats works, this needs to run every frame.
+10 -13
examples/immediate_stats/decaying_speed_auto_plugin.rs
··· 28 28 struct MovementSpeed(Stat); 29 29 30 30 /// Applies a speed boost, which decreases throughout its duration. 31 - #[derive(Component, Default)] 31 + #[derive(Component, Default, Clone)] 32 32 struct DecayingSpeed { 33 33 start_speed_boost: Modifier, 34 34 } ··· 52 52 return; 53 53 } 54 54 55 - commands.entity(*target).with_effect(EffectBundle { 56 - mode: EffectMode::Insert, // Block having multiple of effect stacked on a single target. 57 - bundle: ( 58 - Lifetime::from_seconds(2.0), // The duration of the effect. 59 - DecayingSpeed { 60 - start_speed_boost: Modifier { 61 - bonus: 10, 62 - multiplier: 2.0, 63 - }, 55 + commands.entity(*target).with_effect(( 56 + EffectMode::Insert, // Block having multiple of effect stacked on a single target. 57 + Lifetime::from_seconds(2.0), // The duration of the effect. 58 + DecayingSpeed { 59 + start_speed_boost: Modifier { 60 + bonus: 10, 61 + multiplier: 2.0, 64 62 }, 65 - ), 66 - ..default() 67 - }); 63 + }, 64 + )); 68 65 } 69 66 70 67 /// Applies the effect to the target. Because of how Immediate Stats works, this needs to run every frame.
+8 -13
examples/poison.rs
··· 5 5 //! The `poison_falloff` example shows a different way to handle effect stacking. 6 6 7 7 use bevy::prelude::*; 8 - use bevy_alchemy::{ 9 - AlchemyPlugin, Delay, EffectBundle, EffectCommandsExt, EffectTimer, Effecting, Lifetime, 10 - }; 8 + use bevy_alchemy::{AlchemyPlugin, Delay, EffectCommandsExt, EffectTimer, Effecting, Lifetime}; 11 9 12 10 fn main() { 13 11 App::new() ··· 22 20 struct Health(i32); 23 21 24 22 /// Deals damage over time to the target entity. 25 - #[derive(Component, Default)] 23 + #[derive(Component, Default, Clone)] 26 24 struct Poison { 27 25 damage: i32, 28 26 } ··· 44 42 return; 45 43 } 46 44 47 - commands.entity(*target).with_effect(EffectBundle { 48 - bundle: ( 49 - Lifetime::from_seconds(3.0), // The duration of the effect. 50 - Delay::from_seconds(1.0) // The time between damage ticks. 51 - .trigger_immediately(), // Make damage tick immediately when the effect is applied. 52 - Poison { damage: 1 }, // The amount of damage to apply per tick. 53 - ), 54 - ..default() 55 - }); 45 + commands.entity(*target).with_effect(( 46 + Lifetime::from_seconds(3.0), // The duration of the effect. 47 + Delay::from_seconds(1.0) // The time between damage ticks. 48 + .trigger_immediately(), // Make damage tick immediately when the effect is applied. 49 + Poison { damage: 1 }, // The amount of damage to apply per tick. 50 + )); 56 51 } 57 52 58 53 /// Runs every frame and deals the poison damage.
+11 -14
examples/poison_falloff.rs
··· 9 9 10 10 use bevy::prelude::*; 11 11 use bevy_alchemy::{ 12 - AlchemyPlugin, Delay, EffectBundle, EffectCommandsExt, EffectMode, EffectStacks, EffectTimer, 13 - Effecting, Lifetime, 12 + AlchemyPlugin, Delay, EffectCommandsExt, EffectMode, EffectStacks, EffectTimer, Effecting, 13 + Lifetime, 14 14 }; 15 15 16 16 fn main() { ··· 26 26 struct Health(i32); 27 27 28 28 /// Deals damage over time to the target entity. 29 - #[derive(Component, Default)] 29 + #[derive(Component, Default, Clone)] 30 30 struct Poison { 31 31 damage: i32, 32 32 } ··· 48 48 return; 49 49 } 50 50 51 - commands.entity(*target).with_effect(EffectBundle { 52 - mode: EffectMode::Merge, // Stack tracking requires effect merging. 53 - bundle: ( 54 - EffectStacks::default(), // Enable stack tracking. 55 - Lifetime::from_seconds(3.0), // The duration of the effect. 56 - Delay::from_seconds(1.0) // The time between damage ticks. 57 - .trigger_immediately(), // Make damage tick immediately when the effect is applied. 58 - Poison { damage: 5 }, // The amount of damage to apply per tick. 59 - ), 60 - ..default() 61 - }); 51 + commands.entity(*target).with_effect(( 52 + EffectMode::Merge, // Stack tracking requires effect merging. 53 + EffectStacks::default(), // Enable stack tracking. 54 + Lifetime::from_seconds(3.0), // The duration of the effect. 55 + Delay::from_seconds(1.0) // The time between damage ticks. 56 + .trigger_immediately(), // Make damage tick immediately when the effect is applied. 57 + Poison { damage: 5 }, // The amount of damage to apply per tick. 58 + )); 62 59 } 63 60 64 61 /// Runs every frame and deals the poison damage.
+68 -104
tests/merge_mode.rs
··· 5 5 use bevy_time::*; 6 6 use std::time::Duration; 7 7 8 - #[derive(Component, Debug, Eq, PartialEq, Default)] 8 + #[derive(Component, Debug, Eq, PartialEq, Default, Clone)] 9 9 struct MyEffect(u8); 10 10 11 11 fn init_world() -> World { ··· 28 28 let target = world.spawn_empty().id(); 29 29 30 30 world.commands().entity(target).with_effects(|effects| { 31 - effects.spawn(EffectBundle { 32 - bundle: MyEffect(0), 33 - ..Default::default() 34 - }); 35 - 36 - effects.spawn(EffectBundle { 37 - bundle: MyEffect(1), 38 - ..Default::default() 39 - }); 31 + effects.spawn(MyEffect(0)); 32 + effects.spawn(MyEffect(1)); 40 33 }); 41 34 42 35 world.flush(); ··· 57 50 58 51 let target = world.spawn_empty().id(); 59 52 60 - world.commands().entity(target).with_effect(EffectBundle { 61 - mode: EffectMode::Insert, 62 - bundle: MyEffect(0), 63 - ..Default::default() 64 - }); 65 - world.commands().entity(target).with_effect(EffectBundle { 66 - mode: EffectMode::Insert, 67 - bundle: MyEffect(1), 68 - ..Default::default() 69 - }); 53 + world 54 + .commands() 55 + .entity(target) 56 + .with_effect((EffectMode::Insert, MyEffect(0))); 57 + world 58 + .commands() 59 + .entity(target) 60 + .with_effect((EffectMode::Insert, MyEffect(1))); 70 61 71 62 world.flush(); 72 63 ··· 86 77 87 78 let target = world.spawn_empty().id(); 88 79 89 - world.commands().entity(target).with_effect(EffectBundle { 90 - bundle: MyEffect(0), 91 - ..Default::default() 92 - }); 93 - world.commands().entity(target).with_effect(EffectBundle { 94 - bundle: MyEffect(1), 95 - ..Default::default() 96 - }); 80 + world.commands().entity(target).with_effect(MyEffect(0)); 81 + world.commands().entity(target).with_effect(MyEffect(1)); 97 82 98 - world.commands().entity(target).with_effect(EffectBundle { 99 - mode: EffectMode::Insert, 100 - bundle: MyEffect(2), 101 - ..Default::default() 102 - }); 103 - world.commands().entity(target).with_effect(EffectBundle { 104 - mode: EffectMode::Insert, 105 - bundle: MyEffect(3), 106 - ..Default::default() 107 - }); 83 + world 84 + .commands() 85 + .entity(target) 86 + .with_effect((EffectMode::Insert, MyEffect(2))); 87 + world 88 + .commands() 89 + .entity(target) 90 + .with_effect((EffectMode::Insert, MyEffect(3))); 108 91 109 92 world.flush(); 110 93 ··· 126 109 127 110 let target = world.spawn_empty().id(); 128 111 let second_lifetime = Lifetime::from_seconds(2.0).with_mode(TimerMergeMode::Replace); 129 - world.commands().entity(target).with_effect(EffectBundle { 130 - mode: EffectMode::Merge, 131 - bundle: ( 132 - Lifetime::from_seconds(1.0).with_mode(TimerMergeMode::Replace), 133 - MyEffect(0), 134 - ), 135 - ..Default::default() 136 - }); 137 - world.commands().entity(target).with_effect(EffectBundle { 138 - mode: EffectMode::Merge, 139 - bundle: (second_lifetime.clone(), MyEffect(1)), 140 - ..Default::default() 141 - }); 112 + world.commands().entity(target).with_effect(( 113 + EffectMode::Merge, 114 + Lifetime::from_seconds(1.0).with_mode(TimerMergeMode::Replace), 115 + MyEffect(0), 116 + )); 117 + world.commands().entity(target).with_effect(( 118 + EffectMode::Merge, 119 + second_lifetime.clone(), 120 + MyEffect(1), 121 + )); 142 122 143 123 world.flush(); 144 124 ··· 158 138 159 139 let target = world.spawn_empty().id(); 160 140 let first_delay = Delay::from_seconds(1.0).with_mode(TimerMergeMode::Keep); 161 - world.commands().entity(target).with_effect(EffectBundle { 162 - mode: EffectMode::Merge, 163 - bundle: (first_delay.clone(), MyEffect(0)), 164 - ..Default::default() 165 - }); 166 - world.commands().entity(target).with_effect(EffectBundle { 167 - mode: EffectMode::Merge, 168 - bundle: ( 169 - Delay::from_seconds(2.0).with_mode(TimerMergeMode::Keep), 170 - MyEffect(1), 171 - ), 172 - ..Default::default() 173 - }); 141 + world.commands().entity(target).with_effect(( 142 + EffectMode::Merge, 143 + first_delay.clone(), 144 + MyEffect(0), 145 + )); 146 + world.commands().entity(target).with_effect(( 147 + EffectMode::Merge, 148 + Delay::from_seconds(2.0).with_mode(TimerMergeMode::Keep), 149 + MyEffect(1), 150 + )); 174 151 175 152 world.flush(); 176 153 ··· 192 169 let mut first_timer = Timer::from_seconds(2.0, TimerMode::Once); 193 170 first_timer.tick(Duration::from_secs_f32(1.0)); 194 171 195 - world.commands().entity(target).with_effect(EffectBundle { 196 - mode: EffectMode::Merge, 197 - bundle: ( 198 - Delay { 199 - timer: first_timer, 200 - mode: TimerMergeMode::Fraction, 201 - }, 202 - MyEffect(0), 203 - ), 204 - ..Default::default() 205 - }); 206 - world.commands().entity(target).with_effect(EffectBundle { 207 - mode: EffectMode::Merge, 208 - bundle: ( 209 - Delay::from_seconds(10.0).with_mode(TimerMergeMode::Fraction), 210 - MyEffect(1), 211 - ), 212 - ..Default::default() 213 - }); 172 + world.commands().entity(target).with_effect(( 173 + EffectMode::Merge, 174 + Delay { 175 + timer: first_timer, 176 + mode: TimerMergeMode::Fraction, 177 + }, 178 + MyEffect(0), 179 + )); 180 + world.commands().entity(target).with_effect(( 181 + EffectMode::Merge, 182 + Delay::from_seconds(10.0).with_mode(TimerMergeMode::Fraction), 183 + MyEffect(1), 184 + )); 214 185 215 186 world.flush(); 216 187 ··· 237 208 let target = world.spawn_empty().id(); 238 209 let max = Delay::from_seconds(3.0).with_mode(TimerMergeMode::Max); 239 210 240 - world.commands().entity(target).with_effect(EffectBundle { 241 - mode: EffectMode::Merge, 242 - bundle: ( 243 - Delay::from_seconds(1.0).with_mode(TimerMergeMode::Max), 244 - MyEffect(0), 245 - ), 246 - ..Default::default() 247 - }); 248 - world.commands().entity(target).with_effect(EffectBundle { 249 - mode: EffectMode::Merge, 250 - bundle: (max.clone(), MyEffect(1)), 251 - ..Default::default() 252 - }); 253 - world.commands().entity(target).with_effect(EffectBundle { 254 - mode: EffectMode::Merge, 255 - bundle: ( 256 - Delay::from_seconds(2.0).with_mode(TimerMergeMode::Max), 257 - MyEffect(2), 258 - ), 259 - ..Default::default() 260 - }); 211 + world.commands().entity(target).with_effect(( 212 + EffectMode::Merge, 213 + Delay::from_seconds(1.0).with_mode(TimerMergeMode::Max), 214 + MyEffect(0), 215 + )); 216 + world 217 + .commands() 218 + .entity(target) 219 + .with_effect((EffectMode::Merge, max.clone(), MyEffect(1))); 220 + world.commands().entity(target).with_effect(( 221 + EffectMode::Merge, 222 + Delay::from_seconds(2.0).with_mode(TimerMergeMode::Max), 223 + MyEffect(2), 224 + )); 261 225 262 226 world.flush(); 263 227
+8 -39
tests/spawn_syntax.rs
··· 3 3 use bevy_alchemy::*; 4 4 use bevy_ecs::prelude::*; 5 5 6 - #[derive(Component, Debug, Eq, PartialEq, Default)] 6 + #[derive(Component, Debug, Eq, PartialEq, Default, Clone)] 7 7 struct MyEffect(u8); 8 8 9 9 #[test] ··· 12 12 13 13 world.spawn(( 14 14 Name::new("Target"), 15 - EffectedBy::spawn(( 16 - EffectBundle { 17 - bundle: MyEffect(0), 18 - ..Default::default() 19 - }, 20 - EffectBundle { 21 - bundle: MyEffect(1), 22 - ..Default::default() 23 - }, 24 - )), 15 + EffectedBy::spawn((EffectBundle(MyEffect(0)), EffectBundle(MyEffect(1)))), 25 16 )); 26 17 27 18 world.flush(); ··· 43 34 world.spawn(( 44 35 Name::new("Target"), 45 36 EffectedBy::spawn(( 46 - EffectBundle { 47 - mode: EffectMode::Insert, 48 - bundle: MyEffect(0), 49 - ..Default::default() 50 - }, 51 - EffectBundle { 52 - mode: EffectMode::Insert, 53 - bundle: MyEffect(1), 54 - ..Default::default() 55 - }, 37 + EffectBundle((EffectMode::Insert, MyEffect(0))), 38 + EffectBundle((EffectMode::Insert, MyEffect(1))), 56 39 )), 57 40 )); 58 41 ··· 75 58 world.spawn(( 76 59 Name::new("Target"), 77 60 EffectedBy::spawn(( 78 - EffectBundle { 79 - bundle: MyEffect(0), 80 - ..Default::default() 81 - }, 82 - EffectBundle { 83 - bundle: MyEffect(1), 84 - ..Default::default() 85 - }, 86 - EffectBundle { 87 - mode: EffectMode::Insert, 88 - bundle: MyEffect(2), 89 - ..Default::default() 90 - }, 91 - EffectBundle { 92 - mode: EffectMode::Insert, 93 - bundle: MyEffect(3), 94 - ..Default::default() 95 - }, 61 + EffectBundle(MyEffect(0)), 62 + EffectBundle(MyEffect(1)), 63 + EffectBundle((EffectMode::Insert, MyEffect(2))), 64 + EffectBundle((EffectMode::Insert, MyEffect(3))), 96 65 )), 97 66 )); 98 67