1use std::collections::HashMap;
4
5use bevy::{
6 ecs::{
7 message::MessageCursor,
8 system::{BoxedSystem, SystemId},
9 },
10 prelude::*,
11};
12
13use crate::{
14 enchanting::{
15 ActiveEnchantments, ApplyEnchantmentCursor, ApplyEnchantmentMessage,
16 EnchantmentTrigger, PendingDespawnTriggers, RemoveEnchantmentCursor,
17 RemoveEnchantmentMessage, TriggerEnchantmentCursor, TriggerEnchantmentMessage,
18 apply_enchantments, flush_despawn_triggers, ondespawn_trigger_enchantments,
19 remove_enchantments, tick_enchantments, trigger_enchantments,
20 },
21 runes::{ActiveSpells, CastContext, Rune, RuneRegistry},
22 spell::{Spell, SpellAssetLoader},
23};
24
25#[derive(Message, Clone, Debug)]
37pub struct CastSpellMessage {
38 pub caster: Entity,
40 pub targets: Vec<Entity>,
42 pub spell: Handle<Spell>,
44}
45
46#[derive(Resource, Default)]
53struct SpellCastCursor(MessageCursor<CastSpellMessage>);
54
55#[derive(Resource, Default)]
62struct RuneSystemCache {
63 systems: HashMap<(AssetId<Spell>, usize), SystemId<In<CastContext>>>,
64}
65
66impl RuneSystemCache {
67 pub fn get(&self, spell_id: AssetId<Spell>, rune_index: usize) -> Option<SystemId<In<CastContext>>> {
70 self.systems.get(&(spell_id, rune_index)).cloned()
71 }
72
73 pub fn insert(&mut self, spell_id: AssetId<Spell>, rune_index: usize, system_id: SystemId<In<CastContext>>) {
75 self.systems.insert((spell_id, rune_index), system_id);
76 }
77
78 #[allow(unused)]
79 pub fn remove(&mut self, spell_id: AssetId<Spell>, rune_index: usize) {
81 self.systems.remove(&(spell_id, rune_index));
82 }
83
84 pub fn clear_spell(&mut self, spell_id: AssetId<Spell>) {
86 self.systems.retain(|(id, _), _| *id != spell_id);
87 }
88}
89
90#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
96pub struct MagicSystems;
97
98pub struct MagicPlugin {
108 rune_registrations: Vec<Box<dyn Fn(&RuneRegistry) + Send + Sync>>,
109}
110
111impl Default for MagicPlugin {
112 fn default() -> Self {
113 MagicPlugin {
114 rune_registrations: Vec::new(),
115 }
116 }
117}
118
119impl MagicPlugin {
120 pub fn rune<R>(mut self) -> Self
122 where
123 R: Rune + TypePath + for<'de> serde::Deserialize<'de> + 'static,
124 {
125 self.rune_registrations
126 .push(Box::new(move |registry: &RuneRegistry| {
127 registry.register::<R>();
128 }));
129 self
130 }
131}
132
133impl Plugin for MagicPlugin {
134 fn build(&self, app: &mut App) {
135 let registry = RuneRegistry::default();
136 for f in &self.rune_registrations {
138 f(®istry);
139 }
140
141 app.init_asset::<Spell>()
142 .insert_resource(registry.clone())
143 .register_asset_loader(SpellAssetLoader { registry: registry.clone() })
144 .add_message::<CastSpellMessage>()
145 .add_message::<ApplyEnchantmentMessage>()
146 .add_message::<RemoveEnchantmentMessage>()
147 .add_message::<TriggerEnchantmentMessage>()
148 .init_resource::<SpellCastCursor>()
149 .init_resource::<RuneSystemCache>()
150 .init_resource::<ApplyEnchantmentCursor>()
151 .init_resource::<RemoveEnchantmentCursor>()
152 .init_resource::<TriggerEnchantmentCursor>()
153 .init_resource::<PendingDespawnTriggers>()
154 .add_observer(ondespawn_trigger_enchantments)
155 .add_systems(
156 Update,
157 (
158 invalidate_spell_cache,
159 execute_cast_spell_events,
160 tick_spell_executions,
161 apply_enchantments,
162 remove_enchantments,
163 tick_enchantments,
164 trigger_enchantments,
165 flush_despawn_triggers,
166 )
167 .chain()
168 .in_set(MagicSystems),
169 );
170 }
171}
172
173fn invalidate_spell_cache(
187 mut events: MessageReader<AssetEvent<Spell>>,
188 mut cache: ResMut<RuneSystemCache>,
189) {
190 for event in events.read() {
191 match event {
192 AssetEvent::Modified { id } | AssetEvent::Removed { id } => {
193 cache.clear_spell(*id);
194 }
195 _ => {}
196 }
197 }
198}
199
200fn tick_spell_executions(world: &mut World) {
206 let time_delta = world.resource::<Time>().delta();
207
208 let mut systems_to_run: Vec<(SystemId<In<CastContext>>, CastContext)> = Vec::new();
210
211 let casters: Vec<Entity> = world
213 .query_filtered::<Entity, With<ActiveSpells>>()
214 .iter(world)
215 .collect();
216
217 for caster in casters.iter() {
219 if let Some(mut active) = world.entity_mut(*caster).get_mut::<ActiveSpells>() {
220 active.spells.retain_mut(|spell| {
221 spell.runes.retain_mut(|rune| {
222 rune.timer.tick(time_delta);
223 if rune.timer.just_finished() {
224 systems_to_run.push((rune.system, spell.ctx.clone()));
225 if rune.repeating {
226 rune.timer.reset();
227 true } else {
229 false }
231 } else {
232 true }
234 });
235 !spell.runes.is_empty() });
237 }
238 }
239
240 for caster in casters {
242 if let Some(active) = world.entity(caster).get::<ActiveSpells>() {
243 if active.spells.is_empty() {
244 world.entity_mut(caster).remove::<ActiveSpells>();
245 }
246 }
247 }
248
249 for (system_id, mut context) in systems_to_run {
251 context.targets.retain(|&e| world.get_entity(e).is_ok());
252 let _ = world.run_system_with(system_id, context);
253 }
254}
255
256pub fn execute_cast_spell_events(world: &mut World) {
262 let messages: Vec<CastSpellMessage> = world.resource_scope(|world, mut cursor: Mut<SpellCastCursor>| {
263 let messages_res = world.resource::<Messages<CastSpellMessage>>();
264 cursor.0.read(messages_res).cloned().collect()
265 });
266
267 for message in messages {
269 let spell_id = message.spell.id();
270 let context = CastContext {
271 caster: message.caster,
272 targets: message.targets.clone(),
273 origin: None,
274 };
275
276 let missing: Vec<(usize, BoxedSystem<In<CastContext>, ()>)> = {
278 let cache = world.resource::<RuneSystemCache>();
279 match world.resource::<Assets<Spell>>().get(&message.spell) {
280 None => continue,
281 Some(spell) => (0..spell.runes.len())
282 .filter(|&i| !cache.systems.contains_key(&(spell_id, i)))
283 .map(|i| (i, spell.runes[i].build()))
284 .collect(),
285 }
286 }; for (i, boxed) in missing {
290 let id = world.register_boxed_system(boxed);
291 world
292 .resource_mut::<RuneSystemCache>()
293 .insert(spell_id, i, id);
294 }
295
296 let spell_opt = world.resource::<Assets<Spell>>().get(&message.spell);
298 if spell_opt.is_none() {
299 continue;
300 }
301 let spell = spell_opt.unwrap();
302
303 let cache = world.resource::<RuneSystemCache>();
304 let mut rune_systems: Vec<(SystemId<In<CastContext>>, Timer, bool)> = Vec::new();
305
306 for i in 0..spell.runes.len() {
307 if let Some(sys_id) = cache.get(spell_id, i) {
308 let rune = &spell.runes[i];
309 let delay = rune.delay();
310 let interval = rune.interval();
311
312 let timer = Timer::from_seconds(
314 delay.as_secs_f32(),
315 if interval.is_zero() {
316 TimerMode::Once
317 } else {
318 TimerMode::Repeating
319 },
320 );
321
322 let repeating = !interval.is_zero();
323 rune_systems.push((sys_id, timer, repeating));
324 }
325 }
326
327 if let Ok(mut entity) = world.get_entity_mut(message.caster) {
329 if let Some(mut active) = entity.get_mut::<ActiveSpells>() {
330 active.add_spell(context.clone(), rune_systems);
331 } else {
332 let mut active = ActiveSpells::new();
333 active.add_spell(context.clone(), rune_systems);
334 entity.insert(active);
335 }
336 }
337
338 let mut oncast_systems: Vec<(SystemId<In<CastContext>>, CastContext)> = Vec::new();
340 if let Some(active_enchantments) = world.entity(message.caster).get::<ActiveEnchantments>() {
341 for enchantment in active_enchantments.enchantments.iter() {
342 if matches!(enchantment.trigger, EnchantmentTrigger::OnCast) {
343 for rune in &enchantment.rune_executions {
344 oncast_systems.push((
345 rune.system_id,
346 CastContext {
347 caster: message.caster,
348 targets: message.targets.clone(),
349 origin: None,
350 },
351 ));
352 }
353 }
354 }
355 }
356
357 for (system_id, context) in oncast_systems {
358 let _ = world.run_system_with(system_id, context);
359 }
360 }
361}