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.

at main 182 lines 6.7 kB view raw
1use crate::bundle_inspector::BundleInspector; 2use crate::registry::EffectMergeRegistry; 3use crate::{EffectMode, EffectedBy, Effecting}; 4use bevy_ecs::prelude::*; 5use bevy_log::{warn, warn_once}; 6 7/// Applies an effect to a target entity. 8/// This *might* spawn a new entity, depending on what effects are already applied to the target. 9/// 10/// This is normally used via [`with_effect`](EffectCommandsExt::with_effect) 11/// or related spawners ([`EffectedBy::spawn`](SpawnRelated::spawn)). 12pub struct AddEffectCommand<B: Bundle> { 13 /// The entity to apply the effect to. 14 pub target: Entity, 15 /// The effect to apply. 16 pub bundle: B, 17} 18 19impl<B: Bundle> AddEffectCommand<B> { 20 /// Returns the bundle with the relationship component. 21 fn bundle_full(self) -> (Effecting, B) { 22 (Effecting(self.target), self.bundle) 23 } 24 25 /// Merges the [stashed bundle](Self::stash_bundle) with an entity from the given world. 26 /// This is done by calling the [`EffectMergeFn`] for all components in the [registry](EffectMergeRegistry). 27 /// Components not in the registry will be copied to the target entity. 28 fn merge(self, world: &mut World, existing_entity: Entity) { 29 world 30 .try_resource_scope::<BundleInspector, ()>(|world, inspector| { 31 world.try_resource_scope::<EffectMergeRegistry, ()>(|world, registry| { 32 for incoming_component_id in inspector.get_ref().archetype().components() { 33 let type_id = inspector.get_type_id(*incoming_component_id).unwrap(); 34 35 if let Some(merge) = registry.merges.get(&type_id) { 36 let entity_mut = world.entity_mut(existing_entity); 37 38 if entity_mut.contains_type_id(type_id) { 39 merge(entity_mut, inspector.get_ref()); 40 continue; 41 } 42 } 43 44 unsafe { 45 // SAFETY: `incoming_component_id` `type_id` were extracted from the inspector. 46 _ = inspector.copy_to_world(world, existing_entity, type_id, *incoming_component_id) 47 .inspect_err(|e| { 48 warn!("{e}"); 49 }); 50 } 51 } 52 }) 53 .or_else(|| { 54 warn_once!("No `EffectMergeRegistry` found. Did you forget to add the `AlchemyPlugin`?"); 55 None 56 }); 57 }) 58 .or_else(|| { 59 warn_once!("No `BundleInspector` found. Did you forget to add the `AlchemyPlugin`?"); 60 None 61 }); 62 } 63} 64 65impl<B: Bundle + Clone> Command for AddEffectCommand<B> { 66 fn apply(self, world: &mut World) { 67 let mut inspector = world.get_resource_or_init::<BundleInspector>(); 68 let (name, mode) = inspector 69 .stash_bundle(self.bundle.clone()) 70 .get_effect_meta(); 71 72 if mode == EffectMode::Stack { 73 world.spawn(self.bundle_full()); 74 return; 75 } 76 77 let Some(effected_by) = world.get::<EffectedBy>(self.target).map(|e| e.collection()) else { 78 world.spawn(self.bundle_full()); 79 return; 80 }; 81 82 // Find previous entity that is: 83 // 1. effecting the same target, 84 // 2. and has the same name (ID). 85 let old_entity = effected_by.iter().find_map(|entity| { 86 let other_mode = world.get::<EffectMode>(*entity)?; 87 88 // Todo Think more about. 89 if mode != *other_mode { 90 return None; 91 } 92 93 let other_name = world.get::<Name>(*entity); 94 95 if name.as_ref() == other_name { 96 return Some(*entity); 97 } 98 99 None 100 }); 101 102 let Some(old_entity) = old_entity else { 103 world.spawn(self.bundle_full()); 104 return; 105 }; 106 107 match mode { 108 EffectMode::Stack => unreachable!(), 109 EffectMode::Insert => { 110 world.entity_mut(old_entity).insert(self.bundle); 111 } 112 EffectMode::Merge => { 113 // Ensure that all components are registered in the main world for cloning into. 114 world.register_bundle::<B>(); 115 self.merge(world, old_entity) 116 } 117 } 118 } 119} 120 121/// Uses commands to apply effects to a specific target entity. 122/// 123/// This is normally used during [`with_effects`](EffectCommandsExt::with_effects). 124/// 125/// # Example 126#[doc = include_str!("../docs/with_effects_example.md")] 127pub struct EffectSpawner<'a> { 128 target: Entity, 129 commands: &'a mut Commands<'a, 'a>, 130} 131 132impl<'a> EffectSpawner<'a> { 133 /// Applies an effect to the target entity. 134 /// This *might* spawn a new entity, depending on what effects are already applied to the target. 135 /// 136 /// This is normally used during [`with_effects`](EffectCommandsExt::with_effects). 137 /// 138 /// # Example 139 #[doc = include_str!("../docs/with_effects_example.md")] 140 pub fn spawn<B: Bundle + Clone>(&mut self, bundle: B) { 141 self.commands.queue(AddEffectCommand { 142 target: self.target, 143 bundle, 144 }); 145 } 146} 147 148/// An extension trait for adding effect methods to [`EntityCommands`]. 149pub trait EffectCommandsExt { 150 /// Applies an effect to this entity. 151 /// This *might* spawn a new entity, depending on what effects are already applied to it. 152 /// 153 /// For applying multiple effects, see [`with_effects`](Self::with_effects). 154 /// 155 /// # Example 156 #[doc = include_str!("../docs/with_effect_example.md")] 157 fn with_effect<B: Bundle + Clone>(&mut self, bundle: B) -> &mut Self; 158 159 /// Applies effects to this entity by taking a function that operates on an [`EffectSpawner`]. 160 /// 161 /// For applying a single effect, see [`with_effect`](Self::with_effect). 162 /// 163 /// # Example 164 #[doc = include_str!("../docs/with_effects_example.md")] 165 fn with_effects(&mut self, f: impl FnOnce(&mut EffectSpawner)) -> &mut Self; 166} 167 168impl EffectCommandsExt for EntityCommands<'_> { 169 fn with_effect<B: Bundle + Clone>(&mut self, bundle: B) -> &mut Self { 170 let target = self.id(); 171 self.commands().queue(AddEffectCommand { target, bundle }); 172 self 173 } 174 175 fn with_effects(&mut self, f: impl FnOnce(&mut EffectSpawner)) -> &mut Self { 176 f(&mut EffectSpawner { 177 target: self.id(), 178 commands: &mut self.commands(), 179 }); 180 self 181 } 182}