1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
//! Map element implementations.
//!
//! A map element is anything that can be placed on the map in the editor.

use std::sync::Mutex;

use crate::{impl_system_param, prelude::*};

pub mod buss;
pub mod cannon;
pub mod crab;
pub mod crate_item;
pub mod decoration;
pub mod fish_school;
pub mod flappy_jellyfish;
pub mod grenade;
pub mod jellyfish;
pub mod kick_bomb;
pub mod machine_gun;
pub mod mine;
pub mod musket;
pub mod periscope;
pub mod player_spawner;
pub mod slippery;
pub mod slippery_seaweed;
pub mod snail;
pub mod spike;
pub mod sproinger;
pub mod stomp_boots;
pub mod sword;
pub mod urchin;

pub mod prelude {
    pub use super::{
        buss::*, crab::*, crate_item::*, decoration::*, fish_school::*, grenade::*, jellyfish::*,
        kick_bomb::*, machine_gun::*, mine::*, musket::*, periscope::*, player_spawner::*,
        slippery::*, slippery_seaweed::*, snail::*, spike::*, sproinger::*, stomp_boots::*,
        sword::*, urchin::*, *,
    };
}

#[derive(HasSchema, Default, Clone, Debug)]
#[type_data(metadata_asset("element"))]
#[repr(C)]
pub struct ElementMeta {
    pub name: Ustr,
    pub category: Ustr,
    pub data: Handle<SchemaBox>,
    pub editor: ElementEditorMeta,
    pub plugin: Handle<LuaPlugin>,
}

#[derive(HasSchema, Default, Debug, Clone, Copy)]
#[type_data(metadata_asset("solid"))]
#[repr(C)]
pub struct ElementSolidMeta {
    pub disabled: bool,
    pub offset: Vec2,
    pub size: Vec2,
}

#[derive(HasSchema, Deserialize, Clone, Debug)]
#[repr(C)]
pub struct ElementEditorMeta {
    /// The size of the bounding rect for the element in the editor
    pub grab_size: Vec2,
    /// The offset of the bounding rect for the element in the editor.
    pub grab_offset: Vec2,
    /// Show the element name above the bounding rect in the editor.
    pub show_name: bool,
}

impl Default for ElementEditorMeta {
    fn default() -> Self {
        Self {
            grab_size: Vec2::splat(45.0),
            grab_offset: Vec2::ZERO,
            show_name: true,
        }
    }
}

/// Marker component added to map elements that have been hydrated.
#[derive(Clone, HasSchema, Default)]
#[repr(C)]
pub struct MapElementHydrated;

/// Component that contains the [`Entity`] to de-hydrate when the entity with this component is out
/// of the [`LoadedMap`] bounds.
///
/// This is useful for map elements that spawn items: when the item falls off the map, it should
/// de-hydrate it's spawner, so that the spawner will re-spawn the item in it's default state.
#[derive(Clone, HasSchema, Default, Deref, DerefMut)]
#[repr(C)]
pub struct DehydrateOutOfBounds(pub Entity);

/// Component containing an element's metadata handle.
#[derive(Clone, Copy, HasSchema, Default, Deref, DerefMut)]
#[repr(C)]
pub struct ElementHandle(pub Handle<ElementMeta>);

#[derive(Clone, HasSchema, Default, Deref, DerefMut)]
#[repr(C)]
pub struct ElementSolid(pub Entity);

#[derive(Clone, HasSchema)]
#[schema(no_default)]
pub struct ElementKillCallback {
    pub system: Arc<Mutex<StaticSystem<(), ()>>>,
}

impl ElementKillCallback {
    pub fn new<Args>(system: impl IntoSystem<Args, (), (), Sys = StaticSystem<(), ()>>) -> Self {
        ElementKillCallback {
            system: Arc::new(Mutex::new(system.system())),
        }
    }
}

#[derive(Clone, HasSchema)]
pub struct Spawner {
    /// The group identifier where all of the elements are meant to be shared between them
    pub group_identifier: String,
}

impl Default for Spawner {
    fn default() -> Self {
        Self {
            group_identifier: Ulid::create().to_string(),
        }
    }
}

impl Spawner {
    pub fn new() -> Self {
        Spawner {
            group_identifier: Ulid::create().to_string(),
        }
    }
    pub fn new_grouped(group_identifier: String) -> Self {
        Spawner { group_identifier }
    }
}

#[derive(HasSchema, Default, Clone)]
pub struct SpawnerEntities {
    pub entities_per_spawner_group_identifier: HashMap<String, Vec<Entity>>,
}

impl_system_param! {
    pub struct SpawnerManager<'a> {
        spawners: CompMut<'a, Spawner>,
        spawner_entities: ResMutInit<'a, SpawnerEntities>,
    }
}

impl<'a> SpawnerManager<'a> {
    /// Stores the spawned elements as having been spawned by the provided entity, finding an
    /// existing spawner element, if it exists, to group these spawned elements with.
    pub fn create_grouped_spawner<T: HasSchema>(
        &mut self,
        entity: Entity,
        mut spawned_elements: Vec<Entity>,
        spawner_elements: &CompMut<T>,
        entities: &Res<Entities>,
    ) {
        // try to find one other spawner and store the existing player entities
        if let Some((_, (_, first_spawner))) = entities
            .iter_with((spawner_elements, &self.spawners))
            .next()
        {
            // all of the player spawners share the same group identifier
            let spawner = Spawner::new_grouped(first_spawner.group_identifier.clone());

            // add the spawned elements to the existing resource
            self.spawner_entities
                .entities_per_spawner_group_identifier
                .get_mut(&spawner.group_identifier)
                .expect("The spawner group should already exist in the SpawnerEntities resource.")
                .append(&mut spawned_elements);

            self.spawners.insert(entity, spawner);
        } else {
            let spawner = Spawner::new();

            // add the spawned elements to the newly created resource
            self.spawner_entities
                .entities_per_spawner_group_identifier
                .insert(spawner.group_identifier.clone(), spawned_elements);

            self.spawners.insert(entity, spawner);
        }
    }
    /// Stores the spawned elements as having been spawned by the provided entity
    pub fn create_spawner(&mut self, entity: Entity, spawned_elements: Vec<Entity>) {
        let spawner = Spawner::new();

        // add the spawned elements to the newly created resource
        self.spawner_entities
            .entities_per_spawner_group_identifier
            .insert(spawner.group_identifier.clone(), spawned_elements);

        self.spawners.insert(entity, spawner);
    }

    /// Stores the spawned elements as having come from the same group of spawners as the spawner_elements.
    pub fn insert_spawned_entity_into_grouped_spawner<T: HasSchema>(
        &mut self,
        spawned_entity: Entity,
        spawner_elements: &Comp<T>,
        entities: &ResMutInit<Entities>,
    ) {
        let (_, (_, spawner)) = entities
            .iter_with((spawner_elements, &self.spawners))
            .next()
            .expect("There should already exist at least one spawner of the type provided.");
        self.spawner_entities.entities_per_spawner_group_identifier
            .get_mut(&spawner.group_identifier)
            .expect("There should exist a cooresponding SpawnerEntities for this spawner group identifier.")
            .push(spawned_entity);
    }
    /// Removes that spawned entity from the spawner entities resource
    pub fn remove_spawned_entity_from_grouped_spawner<T: HasSchema>(
        &mut self,
        spawned_entity: Entity,
        spawner_elements: &Comp<T>,
        entities: &ResMutInit<Entities>,
    ) {
        let (_, (_, spawner)) = entities
            .iter_with((spawner_elements, &self.spawners))
            .next()
            .expect("There should already exist at least one spawner of the type provided.");
        self.spawner_entities.entities_per_spawner_group_identifier
            .get_mut(&spawner.group_identifier)
            .expect("There should exist a cooresponding SpawnerEntities for this spawner group identifier.")
            .retain(|entity| *entity != spawned_entity);
    }
    /// Returns if the entity provided is a spawner
    pub fn is_entity_a_spawner(&self, entity: Entity) -> bool {
        self.spawners.contains(entity)
    }
    /// Kills the provided spawner entity and any spawned entities (if applicable)
    pub fn kill_spawner_entity(
        &mut self,
        spawner_entity: Entity,
        entities: &mut ResMutInit<Entities>,
        element_kill_callbacks: &Comp<ElementKillCallback>,
        commands: &mut Commands,
    ) {
        let spawner = self
            .spawners
            .get(spawner_entity)
            .expect("The spawner must exist in order to be deleted.");
        let grouped_spawners_count = self
            .spawners
            .iter()
            .filter(|other_spawner| spawner.group_identifier == other_spawner.group_identifier)
            .count();
        if grouped_spawners_count == 1 {
            let entities_per_spawner_group_identifier = self.spawner_entities
                .entities_per_spawner_group_identifier
                .remove(&spawner.group_identifier)
                .expect("The spawner still exists to be deleted, so there should be a cooresponding vector of spawned entities.");

            entities_per_spawner_group_identifier
                .into_iter()
                .for_each(|spawned_entity| {
                    if let Some(element_kill_callback) = element_kill_callbacks.get(spawned_entity)
                    {
                        let system = element_kill_callback.system.clone();
                        commands.add(move |world: &World| (system.lock().unwrap().run)(world, ()));
                    } else {
                        entities.kill(spawned_entity);
                    }
                });
        }
        entities.kill(spawner_entity);

        // remove the spawner_entity from spawners so that subsequent calls to this function behave appropriately
        self.spawners.remove(spawner_entity);
    }
}

/// Helper macro to install element game and session plugins
macro_rules! install_plugins {
    ($($module:ident),* $(,)?) => {
        pub fn session_plugin(session: &mut SessionBuilder) {
            ElementHandle::register_schema();
            MapElementHydrated::register_schema();
            DehydrateOutOfBounds::register_schema();

            session
                .stages
                .add_system_to_stage(CoreStage::First, handle_out_of_bounds_items);

            $(
                session.install_plugin($module::session_plugin);
            )*
        }

        pub fn game_plugin(game: &mut Game) {
            ElementMeta::register_schema();
            game.init_shared_resource::<AssetServer>();

            $(
                game.install_plugin($module::game_plugin);
            )*
        }
    };
}

install_plugins!(
    crab,
    crate_item,
    cannon,
    decoration,
    fish_school,
    grenade,
    jellyfish,
    kick_bomb,
    mine,
    machine_gun,
    musket,
    buss,
    player_spawner,
    periscope,
    slippery_seaweed,
    slippery,
    snail,
    spike,
    sproinger,
    stomp_boots,
    sword,
    urchin,
);

fn handle_out_of_bounds_items(
    mut commands: Commands,
    mut hydrated: CompMut<MapElementHydrated>,
    entities: ResMutInit<Entities>,
    transforms: CompMut<Transform>,
    spawners: Comp<DehydrateOutOfBounds>,
    map: Res<LoadedMap>,
) {
    for (item_ent, (transform, spawner)) in entities.iter_with((&transforms, &spawners)) {
        if map.is_out_of_bounds(&transform.translation) {
            hydrated.remove(**spawner);
            commands.add(move |mut entities: ResMutInit<Entities>| {
                entities.kill(item_ent);
            });
        }
    }
}