diff --git a/src/wad.rs b/src/wad.rs index 2f98034..d4ad2f8 100644 --- a/src/wad.rs +++ b/src/wad.rs @@ -1,9 +1,9 @@ use bincode; use serde::Deserialize; +use std::collections::HashMap; use std::fs::File; use std::io::{BufReader, Seek, SeekFrom}; use std::path::PathBuf; -use std::collections::HashMap; /* A great document I've used as a reference is The Unofficial Doom Specs v1.666 @@ -49,14 +49,20 @@ pub struct WadThingLump { pub options: u16, } -pub fn open_wad(path: &PathBuf) -> WadHeader { +pub struct OpenWad { + path: PathBuf, + header: WadHeader, + nice_lumps: Vec, + level_indicies: Vec, +} + +pub fn open_wad(path: &PathBuf) -> OpenWad { let mut file = BufReader::new(File::open(path).unwrap()); let header: WadHeader = bincode::deserialize_from(&mut file).unwrap(); file.seek(SeekFrom::Start(header.file_offset_to_start as u64)) .unwrap(); - let mut lumps: Vec = Vec::new(); let mut nice_lumps: Vec = Vec::new(); - let mut levels_indicies = Vec::new(); + let mut level_indicies = Vec::new(); for lump_num in 0..header.num_lumps { let lump: WadLumpDirectoryEntry = bincode::deserialize_from(&mut file).unwrap(); let nice_lump = NiceWadLumpEntry { @@ -64,7 +70,7 @@ pub fn open_wad(path: &PathBuf) -> WadHeader { size: lump.size, name: std::str::from_utf8(&lump.name).unwrap().to_string(), }; - println!("{:?}", nice_lump); + // I stole this from rust-doom. I looked up idSoftware Linux Doom, but they searched // for the strings "mapXY" or ExMy - I'm not 100% sure, but I bet many PWADs don't // neccesarily use this... I do think that probably PWADs provide different THINGS @@ -72,40 +78,79 @@ pub fn open_wad(path: &PathBuf) -> WadHeader { // https://doomwiki.org/wiki/WAD says THINGS always comes after map name if &lump.name == b"THINGS\0\0" { assert!(lump_num > 0); - levels_indicies.push((lump_num - 1) as usize); + level_indicies.push((lump_num - 1) as usize); } - lumps.push(lump); nice_lumps.push(nice_lump); } - let mut _level_summaries: Vec = Vec::new(); + OpenWad { + path: path.clone(), + header, + nice_lumps, + level_indicies, + } +} + +pub fn get_enemies_and_health_per_level( + ow: OpenWad, +) -> ( + HashMap>, + HashMap>, +) { + let mut file = BufReader::new(File::open(ow.path).unwrap()); let mut enemy_maps: HashMap> = HashMap::new(); - for level_i in levels_indicies { - let name = nice_lumps.get(level_i).unwrap().name.clone(); - let level_things_wad_lump = nice_lumps.get(level_i + 1).unwrap(); + let mut health_maps: HashMap> = HashMap::new(); + for level_i in ow.level_indicies { + let name = ow.nice_lumps.get(level_i).unwrap().name.clone(); + let level_things_wad_lump = ow.nice_lumps.get(level_i + 1).unwrap(); file.seek(SeekFrom::Start(level_things_wad_lump.offset as u64)) .unwrap(); let mut enemy_map: HashMap = HashMap::new(); + let mut health_map: HashMap = HashMap::new(); for _ in 0..(level_things_wad_lump.size / std::mem::size_of::() as i32) { let map_thing: WadThingLump = bincode::deserialize_from(&mut file).unwrap(); // Just checking UV if map_thing.options & (1 << 2) > 0 { - if let Some(n) = enemy_map.get(&map_thing.thing_type.into()) { - enemy_map.insert(map_thing.thing_type.into(), n + 1); - } else { - enemy_map.insert(map_thing.thing_type.into(), 1); + // This doesn't feel overly rust like - but it works... + let health_thing: HealthAndArmour = map_thing.thing_type.into(); + let enemy_thing: Enemy = map_thing.thing_type.into(); + if health_thing != HealthAndArmour::Unknown { + if let Some(n) = health_map.get(&map_thing.thing_type.into()) { + health_map.insert(map_thing.thing_type.into(), n + 1); + } else { + health_map.insert(map_thing.thing_type.into(), 1); + } + } + if enemy_thing != Enemy::Unknown { + if let Some(n) = enemy_map.get(&map_thing.thing_type.into()) { + enemy_map.insert(map_thing.thing_type.into(), n + 1); + } else { + enemy_map.insert(map_thing.thing_type.into(), 1); + } } } - } - enemy_maps.insert(name, enemy_map); - break; + enemy_maps.insert(name.clone(), enemy_map); + health_maps.insert(name, health_map); } - println!("{:#?}", enemy_maps); - header + (enemy_maps, health_maps) } #[derive(Eq, Hash, PartialEq, Debug)] -enum Enemy { +pub enum HealthAndArmour { + HealthPotion, + Stimpack, + Medikit, + Soulsphere, + SpiritArmour, + GreenArmour, + BlueArmour, + Megasphere, + Invulnerability, + Unknown, +} + +#[derive(Eq, Hash, PartialEq, Debug)] +pub enum Enemy { FormerHuman, WolfensteinSs, FormerHumanSergeant, @@ -136,7 +181,7 @@ impl std::convert::From for Enemy { 9 => Self::FormerHumanSergeant, 65 => Self::HeavyWeaponDude, 3001 => Self::Imp, - 3002 => Self:: Demon, + 3002 => Self::Demon, 58 => Self::Spectre, 3006 => Self::LostSoul, 3005 => Self::Cacodemon, @@ -150,13 +195,27 @@ impl std::convert::From for Enemy { 7 => Self::SpiderMastermind, 16 => Self::CyberDemon, 88 => Self::BossBrain, - _ => Self::Unknown + _ => Self::Unknown, + } + } +} + +impl std::convert::From for HealthAndArmour { + fn from(ttype: u16) -> Self { + match ttype { + 2011 => Self::Stimpack, + 2012 => Self::Medikit, + 2014 => Self::HealthPotion, + 2015 => Self::SpiritArmour, + 2018 => Self::GreenArmour, + 2019 => Self::BlueArmour, + 83 => Self::Soulsphere, + 2013 => Self::Megasphere, + 2022 => Self::Invulnerability, + _ => Self::Unknown, } } } - - - #[cfg(test)] mod tests { @@ -170,19 +229,37 @@ mod tests { "WAD test need freedoom1.wad - get it from here https://freedoom.github.io" ); let ow = open_wad(&freedoom_iwad); - assert_eq!(&ow.identifier, b"IWAD"); - assert_eq!(std::str::from_utf8(&ow.identifier).unwrap(), "IWAD"); + assert_eq!(&ow.header.identifier, b"IWAD"); + assert_eq!(std::str::from_utf8(&ow.header.identifier).unwrap(), "IWAD"); } #[test] - fn test_failed() { - let freedoom_iwad = PathBuf::from("DOOM2.WAD"); - assert!( - freedoom_iwad.exists(), - "WAD test need freedoom1.wad - get it from here https://freedoom.github.io" - ); - let _ow = open_wad(&freedoom_iwad); - panic!(); + fn test_num_levels_correct() { + let freedoom_iwad = PathBuf::from("freedoom1.wad"); + let ow = open_wad(&freedoom_iwad); + let (enemy_summary, _) = get_enemies_and_health_per_level(ow); + assert_eq!(enemy_summary.len(), 9 * 4); + } + + #[test] + fn test_enemies_correct() { + let freedoom_iwad = PathBuf::from("freedoom1.wad"); + let ow = open_wad(&freedoom_iwad); + let (enemy_summary, _) = get_enemies_and_health_per_level(ow); + let c1m1 = enemy_summary.get("E1M1\0\0\0\0").unwrap(); + assert_eq!(c1m1.get(&Enemy::Imp), Some(&14)); + } + + #[test] + fn test_health_correct() { + let freedoom_iwad = PathBuf::from("freedoom1.wad"); + let ow = open_wad(&freedoom_iwad); + let (_, health_summary) = get_enemies_and_health_per_level(ow); + let c1m1 = health_summary.get("E1M1\0\0\0\0").unwrap(); + assert_eq!(c1m1.get(&HealthAndArmour::BlueArmour), None); // I wonder if this should be a Some(&0) + assert_eq!(c1m1.get(&HealthAndArmour::HealthPotion), Some(&17)); + let c4m1 = health_summary.get("E4M1\0\0\0\0").unwrap(); + assert_eq!(c4m1.get(&HealthAndArmour::Stimpack), Some(&12)); } #[test]