| 1 |
|
| 2 |
|
| 3 |
use std::time::Duration; |
| 4 |
|
| 5 |
use bevy::{log::LogPlugin, prelude::*, time::common_conditions::on_timer}; |
| 6 |
use rand::{rng, seq::IteratorRandom, RngExt}; |
| 7 |
|
| 8 |
fn main() { |
| 9 |
App::new() |
| 10 |
.add_plugins((MinimalPlugins, LogPlugin::default())) |
| 11 |
.add_systems(Startup, setup) |
| 12 |
.add_systems( |
| 13 |
Update, |
| 14 |
attack_armor.run_if(on_timer(Duration::from_millis(200))), |
| 15 |
) |
| 16 |
|
| 17 |
.add_observer(attack_hits) |
| 18 |
.run(); |
| 19 |
} |
| 20 |
|
| 21 |
|
| 22 |
|
| 23 |
|
| 24 |
|
| 25 |
|
| 26 |
|
| 27 |
fn setup(mut commands: Commands) { |
| 28 |
commands |
| 29 |
.spawn((Name::new("Goblin"), HitPoints(50))) |
| 30 |
.observe(take_damage) |
| 31 |
.with_children(|parent| { |
| 32 |
parent |
| 33 |
.spawn((Name::new("Helmet"), Armor(5))) |
| 34 |
.observe(block_attack); |
| 35 |
parent |
| 36 |
.spawn((Name::new("Socks"), Armor(10))) |
| 37 |
.observe(block_attack); |
| 38 |
parent |
| 39 |
.spawn((Name::new("Shirt"), Armor(15))) |
| 40 |
.observe(block_attack); |
| 41 |
}); |
| 42 |
} |
| 43 |
|
| 44 |
|
| 45 |
|
| 46 |
|
| 47 |
|
| 48 |
|
| 49 |
|
| 50 |
|
| 51 |
|
| 52 |
|
| 53 |
|
| 54 |
#[derive(Clone, Component, EntityEvent)] |
| 55 |
#[entity_event(propagate, auto_propagate)] |
| 56 |
struct Attack { |
| 57 |
entity: Entity, |
| 58 |
damage: u16, |
| 59 |
} |
| 60 |
|
| 61 |
|
| 62 |
#[derive(Component, Deref, DerefMut)] |
| 63 |
struct HitPoints(u16); |
| 64 |
|
| 65 |
|
| 66 |
#[derive(Component, Deref)] |
| 67 |
struct Armor(u16); |
| 68 |
|
| 69 |
|
| 70 |
fn attack_armor(entities: Query<Entity, With<Armor>>, mut commands: Commands) { |
| 71 |
let mut rng = rng(); |
| 72 |
if let Some(entity) = entities.iter().choose(&mut rng) { |
| 73 |
let damage = rng.random_range(1..20); |
| 74 |
commands.trigger(Attack { damage, entity }); |
| 75 |
info!("⚔️ Attack for {} damage", damage); |
| 76 |
} |
| 77 |
} |
| 78 |
|
| 79 |
fn attack_hits(attack: On<Attack>, name: Query<&Name>) { |
| 80 |
if let Ok(name) = name.get(attack.entity) { |
| 81 |
info!("Attack hit {}", name); |
| 82 |
} |
| 83 |
} |
| 84 |
|
| 85 |
|
| 86 |
fn block_attack(mut attack: On<Attack>, armor: Query<(&Armor, &Name)>) { |
| 87 |
let (armor, name) = armor.get(attack.entity).unwrap(); |
| 88 |
let damage = attack.damage.saturating_sub(**armor); |
| 89 |
if damage > 0 { |
| 90 |
info!("🩸 {} damage passed through {}", damage, name); |
| 91 |
|
| 92 |
|
| 93 |
attack.damage = damage; |
| 94 |
} else { |
| 95 |
info!("🛡️ {} damage blocked by {}", attack.damage, name); |
| 96 |
|
| 97 |
attack.propagate(false); |
| 98 |
info!("(propagation halted early)\n"); |
| 99 |
} |
| 100 |
} |
| 101 |
|
| 102 |
|
| 103 |
|
| 104 |
fn take_damage( |
| 105 |
attack: On<Attack>, |
| 106 |
mut hp: Query<(&mut HitPoints, &Name)>, |
| 107 |
mut commands: Commands, |
| 108 |
mut app_exit: MessageWriter<AppExit>, |
| 109 |
) { |
| 110 |
let (mut hp, name) = hp.get_mut(attack.entity).unwrap(); |
| 111 |
**hp = hp.saturating_sub(attack.damage); |
| 112 |
|
| 113 |
if **hp > 0 { |
| 114 |
info!("{} has {:.1} HP", name, hp.0); |
| 115 |
} else { |
| 116 |
warn!("💀 {} has died a gruesome death", name); |
| 117 |
commands.entity(attack.entity).despawn(); |
| 118 |
app_exit.write(AppExit::Success); |
| 119 |
} |
| 120 |
|
| 121 |
info!("(propagation reached root)\n"); |
| 122 |
} |