1use bevy::asset::AssetLoader;
2use bevy::{ecs::system::SystemId, prelude::*};
3use bevy_prototype_lyon::shapes;
4use futures_lite::AsyncReadExt;
5use serde::*;
6use serde_json::{self, from_slice};
7use std::collections::hash_map::DefaultHasher;
8use std::fs::OpenOptions;
9use std::hash::{Hash, Hasher};
10use std::io::Read;
11use std::io::Write;
12use std::path::PathBuf;
13
14use crate::core::{EditorObject, EditorWorldObject};
15use crate::core::{LevelEditProperties, LEVEL_ENT_Z_ORDER};
16use crate::geometry::{GeometryBundle, GeometryComponent, Polygon};
17use crate::map_entity::{MapEntity, MapEntityBundle, MapEntityListing, MapEntityListings};
18use crate::user_data::{
19 HashIdLink, MapEntityUserDataConverter, TypedUserData, TypedUserDataFiller, UserData,
20 UserDataComponent, UserDataListing, UserDataListingHandle, UserDataListings,
21};
22
23pub struct SavePlugin;
26
27pub struct LevelAssetLoader;
28
29#[derive(Resource)]
30pub struct SaveSystemIds {
31 pub save_file_id: SystemId,
34
35 pub load_file_id: SystemId,
39
40 pub clear_level_id: SystemId,
42}
43
44#[derive(Asset, TypePath, Serialize, Deserialize, Clone)]
45pub struct LevelData {
46 pub user_data: Vec<UserDataListingData>,
47 pub poly_data: PolygonCollectionData,
48 pub entity_data: EntityCollectionData,
49}
50
51#[derive(TypePath, Serialize, Deserialize, Clone)]
52pub struct PolygonCollectionData {
53 pub polys: Vec<PolygonData>,
54}
55
56#[derive(TypePath, Serialize, Deserialize, Clone)]
57pub struct PolygonData {
58 pub translation: Vec2,
59 pub verts: Vec<Vec2>,
60 pub user_data: Option<CustomData>,
61 pub id_hash: u64,
62}
63
64#[derive(TypePath, Serialize, Deserialize, Default, Clone)]
65pub struct EntityCollectionData {
66 pub listings: Vec<MapEntityTypeListingData>,
67 pub entities: Vec<EntityData>,
68}
69
70#[derive(TypePath, Serialize, Deserialize, Clone)]
71pub struct EntityData {
72 pub translation: Vec2,
73 pub entity_type_id: usize,
74 pub user_data: Option<CustomData>,
75 pub id_hash: u64,
76}
77
78#[derive(TypePath, Serialize, Deserialize, Clone)]
79pub struct CustomData {
80 pub listing_id: Option<usize>,
81 pub data: UserData,
82}
83
84#[derive(Reflect, Serialize, Deserialize, Clone)]
85pub struct MapEntityTypeListingData {
86 pub label: String,
87 pub icon_color: Color,
88 pub icon_image: Option<PathBuf>,
89 pub scale: Vec2,
90 pub id: usize,
91 pub usr_data: Option<usize>,
92}
93
94#[derive(TypePath, Serialize, Deserialize, Clone)]
95pub struct UserDataListingData {
96 pub label: String,
97 pub data: TypedUserData,
98}
99
100impl Plugin for SavePlugin {
103 fn build(&self, app: &mut App) {
104 let sys_ids = SaveSystemIds {
105 save_file_id: app.world.register_system(save_file),
106 load_file_id: app.world.register_system(load_file),
107 clear_level_id: app.world.register_system(clear_level),
108 };
109 app.register_asset_loader(LevelAssetLoader)
110 .init_asset::<LevelData>()
111 .insert_resource(sys_ids);
112 }
113}
114
115impl AssetLoader for LevelAssetLoader {
116 type Asset = LevelData;
117 type Settings = ();
118 type Error = std::io::Error;
119 fn load<'a>(
120 &'a self,
121 reader: &'a mut bevy::asset::io::Reader,
122 _settings: &'a Self::Settings,
123 _load_context: &'a mut bevy::asset::LoadContext,
124 ) -> bevy::utils::BoxedFuture<'a, Result<Self::Asset, Self::Error>> {
125 Box::pin(async move {
126 let mut bytes = Vec::new();
127 reader.read_to_end(&mut bytes).await?;
128 let asset = from_slice::<LevelData>(&bytes).expect("unable to parse level");
129 Ok(asset)
130 })
131 }
132 fn extensions(&self) -> &[&str] {
133 &["lvl.json"]
134 }
135}
136
137impl PolygonData {
138 fn from_polygon(
139 translation: Vec2,
140 poly: &Polygon,
141 usr_dat: Option<&UserDataComponent>,
142 ent: Entity,
143 ) -> Self {
144 let verts = poly.verts().clone();
145 let mut hasher = DefaultHasher::new();
146 ent.hash(&mut hasher);
147 let usr_dat = if let Some(dat) = usr_dat {
148 Some(CustomData {
149 listing_id: dat.listing.get_index(),
150 data: dat.data.to_map_ent_usr_dat(),
151 })
152 } else {
153 None
154 };
155 Self {
156 translation,
157 verts,
158 user_data: usr_dat.clone(),
159 id_hash: hasher.finish(),
160 }
161 }
162}
163
164impl MapEntityTypeListingData {
165 pub fn from_map_ent_listing(listing: &MapEntityListing) -> Self {
166 Self {
167 label: listing.label.clone(),
168 id: listing.id.clone(),
169 scale: listing.scale.clone(),
170 icon_color: listing.icon.color.clone(),
171 icon_image: if listing.icon_image == Handle::<Image>::default() {
172 None
173 } else {
174 todo!("implement image asset path")
175 },
176 usr_data: listing.user_data.get_index(),
177 }
178 }
179
180 pub fn to_map_ent_listing(&self) -> MapEntityListing {
181 MapEntityListing {
182 label: self.label.clone(),
183 id: self.id.clone(),
184 scale: self.scale.clone(),
185 icon: Sprite {
186 color: self.icon_color.clone(),
187 ..Default::default()
188 },
189 icon_image: default(),
190 user_data: if let Some(dat_ind) = self.usr_data {
191 UserDataListingHandle::from_index(dat_ind)
192 } else {
193 UserDataListingHandle::INVALID
194 },
195 }
196 }
197}
198
199impl UserDataListingData {
200 pub fn from_listing(listing: &UserDataListing) -> Self {
201 Self {
202 label: listing.label.clone(),
203 data: listing.data.clone(),
204 }
205 }
206
207 pub fn to_listing(&self) -> UserDataListing {
208 UserDataListing {
209 label: self.label.clone(),
210 data: self.data.clone(),
211 }
212 }
213}
214
215fn save_file(
219 editor_props: Res<LevelEditProperties>,
220 ent_listings: Res<MapEntityListings>,
221 usr_dat_listings: Res<UserDataListings>,
222 geom_query: Query<(
223 Entity,
224 &GlobalTransform,
225 &GeometryComponent,
226 Option<&UserDataComponent>,
227 )>,
228 entity_query: Query<(
229 Entity,
230 &GlobalTransform,
231 &MapEntity,
232 Option<&UserDataComponent>,
233 )>,
234) {
235 if editor_props.cur_file_path.is_none() {
236 warn!("File path not specified");
237 return;
238 }
239
240 let file_path = editor_props.cur_file_path.as_ref().unwrap();
242 let file = OpenOptions::new()
243 .write(true)
244 .truncate(true)
245 .create(true)
246 .open(file_path);
247
248 if let Ok(mut file) = file {
249 let level_data =
251 marshal_level_data(&ent_listings, &usr_dat_listings, &geom_query, &entity_query);
252 let serialized_data = serde_json::to_string_pretty(&level_data);
253
254 if let Ok(data) = serialized_data {
255 let write = file.write_all(data.as_bytes());
257
258 if write.is_ok() {
260 println!("File Saved!");
261
262 } else if let Err(err) = write {
264 warn!("{}", err);
265 }
266
267 } else if let Err(err) = serialized_data {
269 warn!("{}", err);
270 }
271 } else if let Err(err) = file {
273 warn!("{}", err);
274 }
275}
276
277fn load_file(mut commands: Commands, editor_props: Res<LevelEditProperties>) {
281 if editor_props.cur_file_path.is_none() {
282 warn!("File path not specified");
283 return;
284 }
285
286 let file_path = editor_props.cur_file_path.as_ref().unwrap();
287 let file = OpenOptions::new().read(true).open(file_path);
288
289 if let Ok(mut file) = file {
290 let mut file_data = Vec::<u8>::new();
292 let success = file.read_to_end(&mut file_data);
293
294 if success.is_ok() {
295 let level_data = serde_json::from_slice::<LevelData>(file_data.as_slice());
297 if let Ok(level_data) = level_data {
298 build_level_from_data(&mut commands, &level_data);
299
300 } else if let Err(err) = level_data {
302 warn!("{}", err);
303 }
304
305 } else {
307 warn!("{}", success.unwrap_err());
308 }
309
310 } else if let Err(err) = file {
312 warn!("{}", err);
313 }
314}
315
316pub fn load_file_at(file_path: PathBuf) -> Option<LevelData> {
319 let file = OpenOptions::new().read(true).open(file_path);
321 if let Err(err) = file {
322 warn!("{}", err);
323 return None;
324 }
325 let mut file = file.unwrap();
326
327 let mut file_data = Vec::<u8>::new();
329 if let Err(err) = file.read_to_end(&mut file_data) {
330 warn!("{}", err);
331 return None;
332 }
333
334 let data = serde_json::from_slice::<LevelData>(file_data.as_slice());
336 if let Err(err) = data {
337 warn!("{}", err);
338 return None;
339 }
340
341 Some(data.unwrap())
343}
344
345fn clear_level(
347 mut commands: Commands,
348 query: Query<Entity, Or<(With<GeometryComponent>, With<MapEntity>)>>,
349) {
350 for ent in &query {
352 commands.entity(ent).despawn();
353 }
354}
355
356pub fn marshal_level_data(
360 ent_listings: &Res<MapEntityListings>,
361 usr_dat_listings: &Res<UserDataListings>,
362 geom_query: &Query<(
363 Entity,
364 &GlobalTransform,
365 &GeometryComponent,
366 Option<&UserDataComponent>,
367 )>,
368 entity_query: &Query<(
369 Entity,
370 &GlobalTransform,
371 &MapEntity,
372 Option<&UserDataComponent>,
373 )>,
374) -> LevelData {
375 LevelData {
376 user_data: marshal_user_data_listings(&usr_dat_listings),
377 poly_data: marshal_poly_collection_data(&geom_query),
378 entity_data: marshal_entity_collection_data(&ent_listings, &entity_query),
379 }
380}
381
382pub fn marshal_poly_collection_data(
384 geom_query: &Query<(
385 Entity,
386 &GlobalTransform,
387 &GeometryComponent,
388 Option<&UserDataComponent>,
389 )>,
390) -> PolygonCollectionData {
391 let mut polys = Vec::<PolygonData>::new();
392 for (ent, glob_trans, poly, usr_dat) in geom_query {
393 polys.push(marshal_poly_data(glob_trans, poly, usr_dat, ent));
394 }
395 PolygonCollectionData { polys }
396}
397
398pub fn marshal_poly_data(
400 glob_trans: &GlobalTransform,
401 geom: &GeometryComponent,
402 usr_dat: Option<&UserDataComponent>,
403 ent: Entity,
404) -> PolygonData {
405 PolygonData::from_polygon(
406 glob_trans.translation().truncate(),
407 geom.poly(),
408 usr_dat,
409 ent,
410 )
411}
412
413pub fn marshal_entity_collection_data(
415 ent_listings: &Res<MapEntityListings>,
416 ent_query: &Query<(
417 Entity,
418 &GlobalTransform,
419 &MapEntity,
420 Option<&UserDataComponent>,
421 )>,
422) -> EntityCollectionData {
423 let mut ents = Vec::<EntityData>::new();
424 for (ent, glob_trans, ent_dat, usr_dat) in ent_query {
425 ents.push(marshal_entity_data(glob_trans, ent_dat, usr_dat, ent));
426 }
427 EntityCollectionData {
428 listings: marshal_entity_listing_data(ent_listings),
429 entities: ents,
430 }
431}
432
433pub fn marshal_entity_listing_data(
435 ent_listings: &Res<MapEntityListings>,
436) -> Vec<MapEntityTypeListingData> {
437 let mut listing_data = Vec::<MapEntityTypeListingData>::new();
438 for listing in &ent_listings.entity_types {
439 listing_data.push(MapEntityTypeListingData::from_map_ent_listing(listing));
440 }
441 listing_data
442}
443
444pub fn marshal_user_data_listings(
446 usr_dat_listings: &Res<UserDataListings>,
447) -> Vec<UserDataListingData> {
448 let mut listing_data = Vec::<UserDataListingData>::new();
449 for listing in &usr_dat_listings.data_listings {
450 listing_data.push(UserDataListingData::from_listing(listing));
451 }
452 listing_data
453}
454
455pub fn marshal_entity_data(
457 glob_trans: &GlobalTransform,
458 entity: &MapEntity,
459 user_data: Option<&UserDataComponent>,
460 ent: Entity,
461) -> EntityData {
462 let id = entity.listing_id;
463 let mut hasher = DefaultHasher::new();
464 ent.hash(&mut hasher);
465 EntityData {
466 entity_type_id: id,
467 translation: glob_trans.translation().truncate(),
468 user_data: if let Some(typed_data) = user_data {
469 Some(CustomData {
470 listing_id: typed_data.listing.get_index(),
471 data: typed_data.data.to_map_ent_usr_dat(),
472 })
473 } else {
474 None
475 },
476 id_hash: hasher.finish(),
477 }
478}
479
480pub fn build_level_from_data(commands: &mut Commands, data: &LevelData) {
482 build_usr_dat_from_data(commands, &data.user_data);
483 build_geometries_from_data(commands, &data.poly_data);
484 build_entities_from_data(commands, &data.entity_data);
485}
486
487pub fn build_geometries_from_data(commands: &mut Commands, data: &PolygonCollectionData) {
489 for poly in &data.polys {
490 build_geometry_from_data(commands, poly);
491 }
492}
493
494pub fn build_geometry_from_data(commands: &mut Commands, data: &PolygonData) -> Entity {
496 let shape = shapes::Polygon {
497 closed: true,
498 points: data.verts.clone(),
499 };
500 let mut ent_cmd = commands.spawn((
501 EditorObject,
502 EditorWorldObject,
503 GeometryBundle::from_shape(data.translation.extend(0.0), shape),
504 HashIdLink {
505 hash_id: data.id_hash,
506 },
507 ));
508 if let Some(usr_dat) = &data.user_data {
509 if let Some(listing_index) = usr_dat.listing_id {
510 ent_cmd.insert((
511 UserDataComponent {
512 listing: UserDataListingHandle::from_index(listing_index),
513 data: default(),
514 },
515 TypedUserDataFiller {
516 data_untyped: usr_dat.data.clone(),
517 },
518 ));
519 }
520 }
521 ent_cmd.id()
522}
523
524pub fn build_usr_dat_from_data(commands: &mut Commands, data: &Vec<UserDataListingData>) {
526 let mut listings: Vec<UserDataListing> = Vec::new();
527 for list_dat in data {
528 listings.push(list_dat.to_listing());
529 }
530 commands.insert_resource(UserDataListings {
531 data_listings: listings,
532 });
533}
534
535pub fn build_entities_from_data(commands: &mut Commands, data: &EntityCollectionData) {
537 let mut listings: Vec<MapEntityListing> = Vec::new();
539 for listing_dat in &data.listings {
540 listings.push(listing_dat.to_map_ent_listing());
541 }
542 for ent_dat in &data.entities {
544 build_entity_from_data(commands, &ent_dat, &listings[ent_dat.entity_type_id]);
545 }
546 commands.insert_resource(MapEntityListings {
548 entity_types: listings,
549 });
550}
551
552pub fn build_entity_from_data(
554 commands: &mut Commands,
555 data: &EntityData,
556 listing: &MapEntityListing,
557) -> Entity {
558 let mut ent_cmd = commands.spawn((
559 MapEntityBundle {
560 name: Name::new(listing.label.clone()),
561 level_ent_dat: MapEntity {
562 listing_id: data.entity_type_id,
563 },
564 sprite_bundle: SpriteBundle {
565 sprite: listing.icon.clone(),
566 texture: listing.icon_image.clone(),
567 transform: Transform {
568 translation: data.translation.extend(LEVEL_ENT_Z_ORDER),
569 scale: listing.scale.extend(1.0),
570 ..Default::default()
571 },
572 ..Default::default()
573 },
574 ..Default::default()
575 },
576 HashIdLink {
577 hash_id: data.id_hash,
578 },
579 ));
580 if listing.user_data.is_valid() {
581 if let Some(usr_dat) = &data.user_data {
582 ent_cmd.insert((
583 UserDataComponent {
584 listing: listing.user_data.clone(),
585 data: default(),
586 },
587 TypedUserDataFiller {
588 data_untyped: usr_dat.data.clone(),
589 },
590 ));
591 }
592 }
593 ent_cmd.id()
594}