use bevy::prelude::*;
use bevy_gauge::prelude::*;
define_tags! {
Tags,
damage_type {
elemental { fire, cold },
physical,
},
weapon_type {
melee { sword, axe },
ranged { bow },
},
}
#[derive(Clone, Debug)]
struct DamagePipeline;
impl AttributeBuilder for DamagePipeline {
fn apply(&self, entity: Entity, attributes: &mut AttributesMut) {
let _ = attributes.tagged_attribute(
entity,
"Damage",
&[
("added", ReduceFn::Sum),
("increased", ReduceFn::Sum),
("more", ReduceFn::Product),
],
"added * (1 + increased) * more",
);
}
fn clone_box(&self) -> Box<dyn AttributeBuilder> {
Box::new(self.clone())
}
fn fmt_debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "DamagePipeline")
}
}
trait DamageExt {
fn damage(&self, tags: TagMask) -> f32;
fn damage_added(&self, tags: TagMask) -> f32;
fn damage_increased(&self, tags: TagMask) -> f32;
fn damage_more(&self, tags: TagMask) -> f32;
}
impl DamageExt for Attributes {
fn damage(&self, tags: TagMask) -> f32 {
self.value_tagged("Damage", tags)
}
fn damage_added(&self, tags: TagMask) -> f32 {
self.value_tagged("Damage.added", tags)
}
fn damage_increased(&self, tags: TagMask) -> f32 {
self.value_tagged("Damage.increased", tags)
}
fn damage_more(&self, tags: TagMask) -> f32 {
self.value_tagged("Damage.more", tags)
}
}
trait DamageMutExt {
fn evaluate_damage(&mut self, entity: Entity, tags: TagMask) -> f32;
}
impl<F: bevy::ecs::query::QueryFilter> DamageMutExt for AttributesMut<'_, '_, F> {
fn evaluate_damage(&mut self, entity: Entity, tags: TagMask) -> f32 {
self.evaluate_tagged(entity, "Damage", tags)
}
}
#[derive(Resource)]
struct Entities {
sword: Entity,
}
fn main() {
App::new()
.add_plugins(MinimalPlugins)
.add_plugins(AttributesPlugin)
.add_systems(
Startup,
(
register_tags,
spawn_and_configure.after(register_tags),
demo.after(spawn_and_configure),
),
)
.run();
}
fn register_tags(mut resolver: ResMut<TagResolver>) {
Tags::register(&mut resolver);
println!("--- Tags registered ---\n");
}
fn spawn_and_configure(mut commands: Commands) {
let sword = commands
.spawn((
Name::new("Flaming Greatsword"),
attributes! {
@build DamagePipeline,
},
))
.id();
commands.entity(sword).attrs(|attrs| {
attrs.add_modifier_tagged("Damage.added", Modifier::Flat(100.0), Tags::PHYSICAL);
attrs.add_modifier_tagged("Damage.added", Modifier::Flat(30.0), Tags::FIRE);
attrs.add_modifier_tagged("Damage.added", Modifier::Flat(5.0), Tags::SWORD);
attrs.add_modifier_tagged("Damage.increased", Modifier::Flat(0.50), Tags::PHYSICAL);
attrs.add_modifier_tagged("Damage.increased", Modifier::Flat(0.25), Tags::FIRE);
attrs.add_modifier_tagged("Damage.more", Modifier::Flat(0.20), Tags::PHYSICAL);
});
commands.insert_resource(Entities { sword });
println!("--- Sword spawned with DamagePipeline builder ---\n");
}
fn demo(
handles: Res<Entities>,
mut attributes: AttributesMut,
) {
let sword = handles.sword;
let phys_sword = attributes.evaluate_damage(sword, Tags::PHYSICAL | Tags::SWORD);
let fire_sword = attributes.evaluate_damage(sword, Tags::FIRE | Tags::SWORD);
println!("=== Damage via typed AttributesMut API ===\n");
println!(" Damage [PHYSICAL|SWORD]: {phys_sword:.2}");
println!(" Damage [FIRE|SWORD]: {fire_sword:.2}");
if let Some(attrs) = attributes.get_attributes(sword) {
println!("\n=== Damage via typed Attributes API (read-only) ===\n");
let phys = attrs.damage(Tags::PHYSICAL | Tags::SWORD);
let fire = attrs.damage(Tags::FIRE | Tags::SWORD);
let phys_added = attrs.damage_added(Tags::PHYSICAL | Tags::SWORD);
let phys_inc = attrs.damage_increased(Tags::PHYSICAL | Tags::SWORD);
let phys_more = attrs.damage_more(Tags::PHYSICAL | Tags::SWORD);
println!(" Damage [PHYSICAL|SWORD]: {phys:.2}");
println!(" added: {phys_added:.1}");
println!(" increased: {phys_inc:.2}");
println!(" more: {phys_more:.2}");
println!(" Damage [FIRE|SWORD]: {fire:.2}");
}
println!("\n=== String-key API still works ===\n");
let raw_added = attributes.evaluate_tagged(sword, "Damage.added", Tags::PHYSICAL | Tags::SWORD);
println!(" attrs.evaluate_tagged(\"Damage.added\", PHYSICAL|SWORD): {raw_added:.1}");
println!("\n Expected:");
println!(" Physical+Sword added: 100 (physical) + 5 (sword) = 105");
println!(" Physical+Sword increased: 0.50");
println!(" Physical+Sword more: 1.20 (Product: 1 + 0.20)");
println!(" Physical+Sword total: 105 * (1 + 0.50) * 1.20 = 189.00");
println!(" Fire+Sword added: 30 (fire) + 5 (sword) = 35");
println!(" Fire+Sword increased: 0.25");
println!(" Fire+Sword more: 1.00 (no fire-tagged more)");
println!(" Fire+Sword total: 35 * (1 + 0.25) * 1.00 = 43.75");
println!("\n--- Done ---");
std::process::exit(0);
}