···22//! to add a decaying movement speed buff.
33//! This means that the strength of the buff decreases throughout its duration.
44//!
55-//! This uses [`EffectMode::Merge`], which prevents having multiple of the effect applied at the same time (no 10x speed multiplier for you).
55+//! This uses [`EffectMode::Merge`], which prevents having multiple of the effect applied at the
66+//! same time (no 10x speed multiplier for you).
67//!
78//! There is a second version of this example, which uses Bevy Auto Plugin.
89
···11//! This example shows using [Immediate Stats](https://github.com/AlephCubed/immediate_stats)
22-//! to add a decaying movement speed buff, using Bevy Auto Plugin (there is a second version of this example which just uses normal Bevy).
22+//! to add a decaying movement speed buff, using Bevy Auto Plugin
33+//! (there is a second version of this example which just uses normal Bevy).
34//! This means that the strength of the buff decreases throughout its duration.
45//!
55-//! This uses [`EffectMode::Merge`], which prevents having multiple of the effect applied at the same time (no 10x speed multiplier for you).
66+//! This uses [`EffectMode::Merge`], which prevents having multiple of the effect applied at the
77+//! same time (no 10x speed multiplier for you).
6879use bevy::prelude::*;
810use bevy_alchemy::*;
+4
examples/poison.rs
···11//! A simple damage-over-time effect.
22+//!
33+//! Each application of the effect is its own entity, meaning an entity can be poisoned multiple times.
44+//! This can be changed by using a different [`EffectMode`](bevy_alchemy::EffectMode).
55+//! The `poison_falloff` example shows a different way to handle effect stacking.
2637use bevy::prelude::*;
48use bevy_alchemy::{
+109
examples/poison_falloff.rs
···11+//! A damage-over-time effect where the damage falls off as more stacks are added.
22+//!
33+//! When an entity is already poisoned, subsequent applications deal less damage.
44+//! In this case the first stack deals 5 damage, the next 4, then 3, and so on.
55+//!
66+//! This works by [merging](EffectMode::Merge) the effects into a single entity and using the
77+//! [number of stacks](EffectStacks) in damage calculations.
88+//! A slightly simpler version is available in the `poison` example.
99+1010+use bevy::prelude::*;
1111+use bevy_alchemy::{
1212+ AlchemyPlugin, Delay, EffectBundle, EffectCommandsExt, EffectMode, EffectStacks, EffectTimer,
1313+ Effecting, Lifetime,
1414+};
1515+1616+fn main() {
1717+ App::new()
1818+ .add_plugins((DefaultPlugins, AlchemyPlugin))
1919+ .add_systems(Startup, init_scene)
2020+ .add_systems(Update, (on_space_pressed, deal_poison_damage))
2121+ .add_systems(PostUpdate, update_ui)
2222+ .run();
2323+}
2424+2525+#[derive(Component)]
2626+struct Health(i32);
2727+2828+/// Deals damage over time to the target entity.
2929+#[derive(Component, Default)]
3030+struct Poison {
3131+ damage: i32,
3232+}
3333+3434+/// Spawn a target on startup.
3535+fn init_scene(mut commands: Commands) {
3636+ commands.spawn((Name::new("Target"), Health(500)));
3737+ commands.spawn(Text::default());
3838+ commands.spawn(Camera2d);
3939+}
4040+4141+/// When space is pressed, apply poison to the target.
4242+fn on_space_pressed(
4343+ mut commands: Commands,
4444+ keyboard_input: Res<ButtonInput<KeyCode>>,
4545+ target: Single<Entity, With<Health>>,
4646+) {
4747+ if !keyboard_input.just_pressed(KeyCode::Space) {
4848+ return;
4949+ }
5050+5151+ commands.entity(*target).with_effect(EffectBundle {
5252+ mode: EffectMode::Merge, // Stack tracking requires effect merging.
5353+ bundle: (
5454+ EffectStacks::default(), // Enable stack tracking.
5555+ Lifetime::from_seconds(4.0), // The duration of the effect.
5656+ Delay::from_seconds(1.0), // The time between damage ticks.
5757+ Poison { damage: 5 }, // The amount of damage to apply per tick.
5858+ ),
5959+ ..default()
6060+ });
6161+}
6262+6363+/// Runs every frame and deals the poison damage.
6464+fn deal_poison_damage(
6565+ effects: Query<(&Effecting, &EffectStacks, &Delay, &Poison)>,
6666+ mut targets: Query<&mut Health>,
6767+) {
6868+ for (target, stacks, delay, poison) in effects {
6969+ // We wait until the delay finishes to apply the damage.
7070+ if !delay.timer.is_finished() {
7171+ continue;
7272+ }
7373+7474+ // Skip if the target doesn't have health.
7575+ let Ok(mut health) = targets.get_mut(target.0) else {
7676+ continue;
7777+ };
7878+7979+ // Otherwise, deal the damage scaled with the number of stacks.
8080+ // Each subsequent stack has a decreasing effect, the first deals 5 damage, the next 4, then 3, and so on.
8181+ let stacks = poison.damage.min(stacks.0 as i32); // Clamp stacks to prevent negative damage.
8282+ let sub = (stacks * (stacks - 1)) / 2;
8383+ let damage = (poison.damage * stacks - sub).max(0);
8484+8585+ info!("Dealt {damage} damage!");
8686+8787+ health.0 -= damage;
8888+ }
8989+}
9090+9191+fn update_ui(
9292+ mut ui: Single<&mut Text>,
9393+ target: Single<&Health>,
9494+ effects: Query<(Entity, &EffectStacks, &Lifetime, &Delay), With<Poison>>,
9595+) {
9696+ ui.0 = "Press Space to apply poison\n\n".to_string();
9797+9898+ ui.0 += &format!("Health: {}\n\n", target.0);
9999+100100+ for (entity, stacks, lifetime, delay) in &effects {
101101+ ui.0 += &format!(
102102+ "{}, {} stacks - {:.1}s (tick in {:.1}s)\n",
103103+ entity,
104104+ stacks.0,
105105+ lifetime.timer.remaining_secs(),
106106+ delay.timer.remaining_secs()
107107+ );
108108+ }
109109+}