//! Serializes / Deserializes game data for saving and loading use std::fs::{self, File}; use std::path::Path; use ::bracket_lib::prelude::*; use ::specs::error::NoError; use ::specs::prelude::*; use ::specs::saveload::{ DeserializeComponents, MarkedBuilder, SerializeComponents, SimpleMarker, SimpleMarkerAllocator, }; use crate::components::*; use crate::map::{Map, MasterDungeonMap}; use crate::spatial; macro_rules! serialize_individually { ($ecs:expr, $ser:expr, $data:expr, $( $type:ty),*,) => { $( SerializeComponents::>::serialize( &( $ecs.read_storage::<$type>(), ), &$data.0, &$data.1, &mut $ser, ) .unwrap(); )* }; } #[cfg(target_arch = "wasm32")] pub fn save_game(_ecs: &mut World) {} /// Saves the game data to a file #[cfg(not(target_arch = "wasm32"))] pub fn save_game(ecs: &mut World) { // Create helper let mapcopy = ecs.get_mut::().unwrap().clone(); let dungeon_master = ecs.get_mut::().unwrap().clone(); let savehelper = ecs .create_entity() .with(SerializationHelper { map: mapcopy }) .marked::>() .build(); let savehelper2 = ecs .create_entity() .with(DMSerializationHelper { map: dungeon_master, log: crate::gamelog::clone_log(), events: crate::gamelog::clone_events(), }) .marked::>() .build(); // Actually serialize { let data = ( ecs.entities(), ecs.read_storage::>(), ); let writer = File::create("./savegame.json").unwrap(); let mut serializer = ::serde_json::Serializer::new(writer); serialize_individually!( ecs, serializer, data, AlwaysTargetsSelf, ApplyMove, ApplyTeleport, AreaOfEffect, AttributeBonus, Attributes, BlocksTile, BlocksVisibility, Chasing, Confusion, Consumable, CursedItem, DamageOverTime, DMSerializationHelper, Door, Duration, EntityMoved, EntryTrigger, EquipmentChanged, Equippable, Equipped, Faction, Hidden, HungerClock, IdentifiedItem, InBackpack, InflictsDamage, Initiative, Item, KnownSpells, LightSource, LootTable, MagicItem, MagicMapper, MoveMode, MyTurn, Name, NaturalAttackDefense, ObfuscatedName, OnDeath, OtherLevelPosition, ParticleLifetime, Player, Pools, Position, ProvidesFood, ProvidesHealing, ProvidesIdentification, ProvidesMana, ProvidesRemoveCurse, Quips, Ranged, Renderable, SerializationHelper, SingleActivation, Skills, Slow, SpawnParticleBurst, SpawnParticleLine, SpecialAbilities, SpellTemplate, StatusEffect, Target, TeachesSpell, TeleportTo, TileSize, TownPortal, Vendor, Viewshed, WantsToApproach, WantsToCastSpell, WantsToDropItem, WantsToFlee, WantsToMelee, WantsToPickupItem, WantsToRemoveItem, WantsToShoot, WantsToUseItem, Weapon, Wearable, ); } // Clean up ecs.delete_entity(savehelper) .expect("Failed to clean up savehelper component"); ecs.delete_entity(savehelper2) .expect("Failed to clean up savehelper2 component"); } /// Does the save game exist? pub fn does_save_exist() -> bool { Path::new("./savegame.json").exists() } macro_rules! deserialize_individually { ($ecs:expr, $de:expr, $data:expr, $( $type:ty),*,) => { $( DeserializeComponents::::deserialize( &mut ( &mut $ecs.write_storage::<$type>(), ), &$data.0, // entities &mut $data.1, // marker &mut $data.2, // allocater &mut $de, ) .unwrap(); )* }; } /// Loads the game data from a file pub fn load_game(ecs: &mut World) { { // Delete everything let mut to_delete = Vec::new(); for e in ecs.entities().join() { to_delete.push(e); } for del in to_delete.iter() { ecs.delete_entity(*del).expect("Failed to delete entity"); } } let data = fs::read_to_string("./savegame.json").unwrap(); let mut de = ::serde_json::Deserializer::from_str(&data); { let mut d = ( &mut ecs.entities(), &mut ecs.write_storage::>(), &mut ecs.write_resource::>(), ); deserialize_individually!( ecs, de, d, AlwaysTargetsSelf, ApplyMove, ApplyTeleport, AreaOfEffect, AttributeBonus, Attributes, BlocksTile, BlocksVisibility, Chasing, Confusion, Consumable, CursedItem, DamageOverTime, DMSerializationHelper, Door, Duration, EntityMoved, EntryTrigger, EquipmentChanged, Equippable, Equipped, Faction, Hidden, HungerClock, IdentifiedItem, InBackpack, InflictsDamage, Initiative, Item, KnownSpells, LightSource, LootTable, MagicItem, MagicMapper, MoveMode, MyTurn, Name, NaturalAttackDefense, ObfuscatedName, OnDeath, OtherLevelPosition, ParticleLifetime, Player, Pools, Position, ProvidesFood, ProvidesHealing, ProvidesIdentification, ProvidesMana, ProvidesRemoveCurse, Quips, Ranged, Renderable, SerializationHelper, SingleActivation, Skills, Slow, SpawnParticleBurst, SpawnParticleLine, SpecialAbilities, SpellTemplate, StatusEffect, Target, TeachesSpell, TeleportTo, TileSize, TownPortal, Vendor, Viewshed, WantsToApproach, WantsToCastSpell, WantsToDropItem, WantsToFlee, WantsToMelee, WantsToPickupItem, WantsToRemoveItem, WantsToShoot, WantsToUseItem, Weapon, Wearable, ); } let mut deleteme: Option = None; let mut deleteme2: Option = None; { let entities = ecs.entities(); let helper = ecs.read_storage::(); let helper2 = ecs.read_storage::(); let player = ecs.read_storage::(); let position = ecs.read_storage::(); for (e, h) in (&entities, &helper).join() { let mut worldmap = ecs.write_resource::(); *worldmap = h.map.clone(); spatial::set_size((worldmap.height * worldmap.width) as usize); deleteme = Some(e); } for (e, h) in (&entities, &helper2).join() { let mut dungeon_master = ecs.write_resource::(); *dungeon_master = h.map.clone(); deleteme2 = Some(e); crate::gamelog::restore_log(&mut h.log.clone()); crate::gamelog::load_events(h.events.clone()); } for (e, _p, pos) in (&entities, &player, &position).join() { let mut ppos = ecs.write_resource::(); *ppos = Point::from(*pos); let mut player_resource = ecs.write_resource::(); *player_resource = e; } } ecs.delete_entity(deleteme.unwrap()) .expect("Unable to delete helper entity"); ecs.delete_entity(deleteme2.unwrap()) .expect("Unable to delete helper2 entity"); } /// Deletes the save file, if it exists pub fn delete_save() { if Path::new("./savegame.json").exists() { std::fs::remove_file("./savegame.json").expect("Failed to delete save file."); } }