use std::fs::{ File, create_dir_all }; use std::io::{ self, Write }; use std::time::SystemTime; use super::Map; use crate::gamelog; use crate::components::*; use crate::gui::{ Class, Ancestry, unobf_name_ecs }; use specs::prelude::*; use rltk::prelude::*; use rltk::to_char; use std::collections::HashMap; use crate::data::events::*; #[cfg(target_arch = "wasm32")] pub fn create_morgue_file(ecs: &World) { console::log("wasm32 doesn't support writing files yet, so here's the morgue info:"); let morgue_info = create_morgue_string(ecs); console::log(morgue_info); } #[cfg(not(target_arch = "wasm32"))] pub fn create_morgue_file(ecs: &World) { let morgue_dir = "morgue"; if let Err(err) = create_dir_all(&morgue_dir) { console::log(format!("Unable to create the directory (/{}): {}", morgue_dir, err)); } let morgue_info = create_morgue_string(ecs); let file_name = create_file_name(ecs, morgue_dir); if let Err(err) = write_morgue_file(file_name.as_str(), morgue_info.as_str()) { console::log(format!("Unable to write the morgue file: {}", err)); }; } fn create_file_name(ecs: &World, morgue_dir: &str) -> String { let e = ecs.fetch::(); let pools = ecs.read_storage::(); let pool = pools.get(*e).unwrap(); let class = match ecs.read_storage::().get(*e).unwrap().name { Class::Fighter => "fighter", Class::Wizard => "wizard", Class::Rogue => "rogue", Class::Villager => "villager", }; let ancestry = match ecs.read_storage::().get(*e).unwrap().name { Ancestry::Human => "human", Ancestry::Elf => "elf", Ancestry::Dwarf => "dwarf", Ancestry::Gnome => "gnome", Ancestry::Catfolk => "catfolk", Ancestry::NULL => "NULL", }; return format!("{}/lv{}-{}-{}-{}.txt", morgue_dir, &pool.level, &ancestry, &class, get_timestamp()); } fn create_morgue_string(ecs: &World) -> String { // Initialise default let mut morgue_info: String = Default::default(); let e = ecs.fetch::(); let class = match ecs.read_storage::().get(*e).unwrap().name { Class::Fighter => "fighter", Class::Wizard => "wizard", Class::Rogue => "rogue", Class::Villager => "villager", }; let ancestry = match ecs.read_storage::().get(*e).unwrap().name { Ancestry::Human => "human", Ancestry::Elf => "elf", Ancestry::Dwarf => "dwarf", Ancestry::Gnome => "gnome", Ancestry::Catfolk => "catfolk", Ancestry::NULL => "NULL", }; let pools = ecs.read_storage::(); let pool = pools.get(*e).unwrap(); let header = format!("{} {}, level {}/{}", &ancestry, &class, &pool.level, &pool.xp); morgue_info.push_str(&create_boxed_text(header.as_str(), None)); morgue_info.push_str(&draw_tombstone(ecs, header.len())); morgue_info.push_str(&draw_map(ecs)); morgue_info.push_str("\n"); morgue_info.push_str(&create_boxed_text("Equipment", None)); morgue_info.push_str(&draw_equipment(ecs)); morgue_info.push_str(&create_boxed_text("Backpack", None)); morgue_info.push_str(&draw_backpack(ecs)); morgue_info.push_str(&create_boxed_text("Significant Events", None)); morgue_info.push_str(&draw_events_list()); return morgue_info; } fn write_morgue_file(file_name: &str, morgue_info: &str) -> Result<(), io::Error> { // Save to file let mut file = File::create(&file_name)?; // Open/create morgue file file.write_all(morgue_info.as_bytes())?; Ok(()) } fn get_timestamp() -> String { return SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs().to_string(); } fn create_boxed_text(content: &str, width: Option) -> String { let width = if width.is_some() { width.unwrap() } else { content.len() + 2 }; let horizontal = format!("{:═^w$}", "", w = width); return format!("╔{h}╗\n║ {c} ║\n╚{h}╝\n", h = horizontal, c = content); } fn draw_tombstone(ecs: &World, len: usize) -> String { let pad = (len - 17) / 2; let map = ecs.fetch::(); let pools = ecs.read_storage::(); let pool = pools.get(*ecs.fetch::()).unwrap(); let attrs = ecs.read_storage::(); let attr = attrs.get(*ecs.fetch::()).unwrap(); return format!( "{:^p$} .-'~~~`-. HP {}/{} MP {}/{}\n{:^p$} .' `.\n{:^p$} | rest | STR {:>2} ({:+}) CON {:>2} ({:+}) WIS {:>2} ({:+})\n{:^p$} | in | DEX {:>2} ({:+}) INT {:>2} ({:+}) CHA {:>2} ({:+})\n{:^p$} | peace |\n{:^p$}\\\\| |// You died in {} [id {}], after {} turns.\n{:^p$}^^^^^^^^^^^^^^^^^{:^p$}\n", "", pool.hit_points.current, pool.hit_points.max, pool.mana.current, pool.mana.max, "", "", attr.strength.base + attr.strength.modifiers, attr.strength.bonus, attr.constitution.base + attr.constitution.modifiers, attr.constitution.bonus, attr.wisdom.base + attr.wisdom.modifiers, attr.wisdom.bonus, "", attr.dexterity.base + attr.dexterity.modifiers, attr.dexterity.bonus, attr.intelligence.base + attr.intelligence.modifiers, attr.intelligence.bonus, attr.charisma.base + attr.charisma.modifiers, attr.charisma.bonus, "", "", map.name, map.id, gamelog::get_event_count(EVENT::COUNT_TURN), "", "", p = pad ); } fn draw_map(ecs: &World) -> String { let map = ecs.fetch::(); let mut result: String = Default::default(); let point = ecs.fetch::(); for y in 0..map.height { for x in 0..map.width { let idx = map.xy_idx(x, y); let mut glyph_u16: u16 = 0; if idx == map.xy_idx(point.x, point.y) { glyph_u16 = to_cp437('@'); } else if crate::spatial::has_tile_content(idx) { let mut render_order = 0; crate::spatial::for_each_tile_content(idx, |e| { if let Some(renderable) = ecs.read_storage::().get(e) { if renderable.render_order >= render_order { render_order = renderable.render_order; glyph_u16 = renderable.glyph; } } }); } else { glyph_u16 = crate::map::themes::get_tile_renderables_for_id(idx, &*map, None).0; } let char = to_char((glyph_u16 & 0xff) as u8); result.push_str(&char.to_string()); } result.push_str("\n"); } return result; } fn draw_equipment(ecs: &World) -> String { // Get all of the player's equipment. let mut equip: HashMap = HashMap::new(); let equipped = ecs.read_storage::(); for (entity, _e, _n) in (&ecs.entities(), &equipped, &ecs.read_storage::()) .join() .filter(|item| item.1.owner == *ecs.fetch::()) { equip .entry(entity) .and_modify(|count| { *count += 1; }) .or_insert(1); } let mut result: String = Default::default(); for item in equip { let slot = match equipped.get(item.0).unwrap().slot { EquipmentSlot::Melee => "l-hand -", EquipmentSlot::Shield => "r-hand -", EquipmentSlot::Head => "head -", EquipmentSlot::Body => "body -", EquipmentSlot::Feet => "feet -", EquipmentSlot::Hands => "hands -", EquipmentSlot::Back => "back -", EquipmentSlot::Neck => "neck -", }; let name = if item.1 != 1 { unobf_name_ecs(ecs, item.0).1 } else { unobf_name_ecs(ecs, item.0).0 }; result.push_str(&format!("{:>8} {}\n", slot, name)); } result.push_str("\n"); return result; } fn draw_backpack(ecs: &World) -> String { // Get all of the player's backpack. let mut pack: HashMap<(String, String), (i32, Entity)> = HashMap::new(); for (entity, _bp, _n) in (&ecs.entities(), &ecs.read_storage::(), &ecs.read_storage::()) .join() .filter(|item| item.1.owner == *ecs.fetch::()) { pack.entry(unobf_name_ecs(ecs, entity)) .and_modify(|(count, _e)| { *count += 1; }) .or_insert((1, entity)); } let mut result: String = Default::default(); for item in pack { let name = if item.1.0 != 1 { format!("{} {}", item.1.0, item.0.1) } else { // TODO: Get correct article (a/an/some) here, write a fn for it. item.0.0 }; result.push_str(&format!("- {}\n", name)); } result.push_str("\n"); return result; } fn draw_events_list() -> String { // Initialise default (empty) string let mut result: String = Default::default(); // Get lock on events mutex let lock = gamelog::EVENTS.lock().unwrap(); // Collect all keys, and sort in ascending value (by turn count) let mut sorted_keys: Vec = lock .keys() .map(|k| *k) .collect(); sorted_keys.sort(); // Iterate through sorted keys, looking for corresponding values, and append on newline for key in sorted_keys { if let Some(value) = lock.get(&key) { result.push_str(&format!("{:<4} | ", key)); for event in value.iter() { result.push_str(&format!("{}", event)); } result.push_str("\n"); } } return result; }