use crate::bundle_inspector::BundleInspector;
use crate::registry::EffectMergeRegistry;
use crate::{EffectMode, EffectedBy, Effecting};
use bevy_ecs::prelude::*;
use bevy_log::{warn, warn_once};
pub struct AddEffectCommand<B: Bundle> {
pub target: Entity,
pub bundle: B,
}
impl<B: Bundle> AddEffectCommand<B> {
fn bundle_full(self) -> (Effecting, B) {
(Effecting(self.target), self.bundle)
}
fn merge(self, world: &mut World, existing_entity: Entity) {
world
.try_resource_scope::<BundleInspector, ()>(|world, inspector| {
world.try_resource_scope::<EffectMergeRegistry, ()>(|world, registry| {
for incoming_component_id in inspector.get_ref().archetype().components() {
let type_id = inspector.get_type_id(*incoming_component_id).unwrap();
if let Some(merge) = registry.merges.get(&type_id) {
let entity_mut = world.entity_mut(existing_entity);
if entity_mut.contains_type_id(type_id) {
merge(entity_mut, inspector.get_ref());
continue;
}
}
unsafe {
_ = inspector.copy_to_world(world, existing_entity, type_id, *incoming_component_id)
.inspect_err(|e| {
warn!("{e}");
});
}
}
})
.or_else(|| {
warn_once!("No `EffectMergeRegistry` found. Did you forget to add the `AlchemyPlugin`?");
None
});
})
.or_else(|| {
warn_once!("No `BundleInspector` found. Did you forget to add the `AlchemyPlugin`?");
None
});
}
}
impl<B: Bundle + Clone> Command for AddEffectCommand<B> {
fn apply(self, world: &mut World) {
let mut inspector = world.get_resource_or_init::<BundleInspector>();
let (name, mode) = inspector
.stash_bundle(self.bundle.clone())
.get_effect_meta();
if mode == EffectMode::Stack {
world.spawn(self.bundle_full());
return;
}
let Some(effected_by) = world.get::<EffectedBy>(self.target).map(|e| e.collection()) else {
world.spawn(self.bundle_full());
return;
};
let old_entity = effected_by.iter().find_map(|entity| {
let other_mode = world.get::<EffectMode>(*entity)?;
if mode != *other_mode {
return None;
}
let other_name = world.get::<Name>(*entity);
if name.as_ref() == other_name {
return Some(*entity);
}
None
});
let Some(old_entity) = old_entity else {
world.spawn(self.bundle_full());
return;
};
match mode {
EffectMode::Stack => unreachable!(),
EffectMode::Insert => {
world.entity_mut(old_entity).insert(self.bundle);
}
EffectMode::Merge => {
world.register_bundle::<B>();
self.merge(world, old_entity)
}
}
}
}
#[doc = include_str!("../docs/with_effects_example.md")]
pub struct EffectSpawner<'a> {
target: Entity,
commands: &'a mut Commands<'a, 'a>,
}
impl<'a> EffectSpawner<'a> {
#[doc = include_str!("../docs/with_effects_example.md")]
pub fn spawn<B: Bundle + Clone>(&mut self, bundle: B) {
self.commands.queue(AddEffectCommand {
target: self.target,
bundle,
});
}
}
pub trait EffectCommandsExt {
#[doc = include_str!("../docs/with_effect_example.md")]
fn with_effect<B: Bundle + Clone>(&mut self, bundle: B) -> &mut Self;
#[doc = include_str!("../docs/with_effects_example.md")]
fn with_effects(&mut self, f: impl FnOnce(&mut EffectSpawner)) -> &mut Self;
}
impl EffectCommandsExt for EntityCommands<'_> {
fn with_effect<B: Bundle + Clone>(&mut self, bundle: B) -> &mut Self {
let target = self.id();
self.commands().queue(AddEffectCommand { target, bundle });
self
}
fn with_effects(&mut self, f: impl FnOnce(&mut EffectSpawner)) -> &mut Self {
f(&mut EffectSpawner {
target: self.id(),
commands: &mut self.commands(),
});
self
}
}