diff options
| author | Jakob L. Kreuze <jakob@memeware.net> | 2018-06-12 17:45:19 -0400 |
|---|---|---|
| committer | Jakob L. Kreuze <jakob@memeware.net> | 2018-06-12 17:45:19 -0400 |
| commit | 527c2f8e341d87a94031644697dec354e9b67910 (patch) | |
| tree | a61b775b35f0042c5856051d388431109db594ba /src/world.rs | |
| parent | c1fcfc3a58fc0061fb9262c95d728063029e1c2f (diff) | |
Cleaned up and began to implement tests.
Diffstat (limited to 'src/world.rs')
| -rw-r--r-- | src/world.rs | 187 |
1 files changed, 129 insertions, 58 deletions
diff --git a/src/world.rs b/src/world.rs index 34a11f4..113e315 100644 --- a/src/world.rs +++ b/src/world.rs @@ -24,20 +24,67 @@ use std::io::Cursor; use self::byteorder::{LE, ReadBytesExt}; -/// Maintains the current state of the game world - the map geometry and -/// everything contained within it. +/// The basic element of a level, as understood by the BUILD engine - a logical +/// collection of a floor, a ceiling, and some number of walls. +#[derive(Debug)] +pub struct Sector { + first_wall: i16, + wall_count: i16, + visibility: u8, + tags: (i16, i16, i16), + + ceiling_height: i32, + ceiling_slope: i16, + ceiling_status: i16, + ceiling_bitmap: i16, + ceiling_shade: i8, + ceiling_palette: u8, + ceiling_panning: (u8, u8), + + floor_height: i32, + floor_slope: i16, + floor_status: i16, + floor_bitmap: i16, + floor_shade: i8, + floor_palette: u8, + floor_panning: (u8, u8), +} + +/// A "wall," taken to be some line segment as part of a sector's enclosing +/// geometry. Portals, openings between sectors, are represented as walls, even +/// though that isn't in-line with our typical definition of a "wall" in the +/// real world. +#[derive(Debug)] +pub struct Wall { + position: (i32, i32), + + adjacent_wall_index: i16, + opposite_wall_index: i16, + into_sector_index: i16, + + bitmap: i16, + bitmap_overlay: i16, + shade: i8, + palette: u8, + stretch: (u8, u8), + panning: (u8, u8), + + status: i16, + tags: (i16, i16, i16), +} + +/// Maintains and advances the state of the entire game world, indluding the +/// geometry of the current map. #[derive(Debug)] pub struct World { - sectors: Vec<Sector>, - walls: Vec<Wall>, + pub sectors: Vec<Sector>, + pub walls: Vec<Wall>, } impl World { - // TODO: Document this. - // FIXME: Doesn't do any sort of sanity checks on length. + /// Create a new World from the geometry and sprites specified in the given + /// MAP file. pub fn from_map(data: &[u8]) -> Result<World, Box<Error>> { - let mut data = Cursor::new(data); - // From BUILDINF.TXT // // Here is Ken's documentation on the COMPLETE BUILD map format: @@ -72,13 +119,26 @@ impl World { // close(fil); // } + let len = data.len(); + + // This is the absolute minimum possible size for a MAP, containing the + // header and a 0 short for each of the three arrays. This will be + // incremented as we find out more information about the MAP file - + // specifically, the number of sectors, walls, and sprites we expect. + let mut expected_len = 22; + + if len < expected_len { + bail!("File too small to possibly contain valid MAP."); + } + + let mut data = Cursor::new(data); let version = data.read_u32::<LE>()?; if version != 7 { bail!("Unsupported MAP version."); } - // TODO: Use this to position the world's player (?) + // TODO: Use this to position when initializing the player. let _start_x = data.read_i32::<LE>()?; let _start_y = data.read_i32::<LE>()?; let _start_z = data.read_i32::<LE>()?; @@ -137,6 +197,11 @@ impl World { let mut sectors = Vec::new(); let sector_count = data.read_u16::<LE>()?; + expected_len += 40 * (sector_count as usize); + + if len < expected_len { + bail!(format!("Invalid sector count (given: {})", sector_count)); + } for _ in 0..sector_count { let first_wall = data.read_i16::<LE>()?; @@ -235,6 +300,11 @@ impl World { let mut walls = Vec::new(); let wall_count = data.read_u16::<LE>()?; + expected_len += 32 * (wall_count as usize); + + if len < expected_len { + bail!(format!("Invalid wall count (given: {})", wall_count)); + } for _ in 0..wall_count { let position_x = data.read_i32::<LE>()?; @@ -298,19 +368,21 @@ impl World { // } spritetype; // spritetype sprite[4096]; // - // x, y, z - position of sprite - can be defined at center bottom or center + // x, y, z - position of sprite - can be defined at center bottom or + // center + // // cstat: - // bit 0: 1 = Blocking sprite (use with clipmove, getzrange) "B" - // bit 1: 1 = transluscence, 0 = normal "T" - // bit 2: 1 = x-flipped, 0 = normal "F" - // bit 3: 1 = y-flipped, 0 = normal "F" - // bits 5-4: 00 = FACE sprite (default) "R" - // 01 = WALL sprite (like masked walls) - // 10 = FLOOR sprite (parallel to ceilings&floors) - // bit 6: 1 = 1-sided sprite, 0 = normal "1" - // bit 7: 1 = Real centered centering, 0 = foot center "C" - // bit 8: 1 = Blocking sprite (use with hitscan / cliptype 1) "H" - // bit 9: 1 = Transluscence reversing, 0 = normal "T" + // bit 0: 1 = Blocking sprite (use with clipmove, getzrange) "B" + // bit 1: 1 = transluscence, 0 = normal "T" + // bit 2: 1 = x-flipped, 0 = normal "F" + // bit 3: 1 = y-flipped, 0 = normal "F" + // bits 5-4: 00 = FACE sprite (default) "R" + // 01 = WALL sprite (like masked walls) + // 10 = FLOOR sprite (parallel to ceilings&floors) + // bit 6: 1 = 1-sided sprite, 0 = normal "1" + // bit 7: 1 = Real centered centering, 0 = foot center "C" + // bit 8: 1 = Blocking sprite (use with hitscan/cliptype 1) "H" + // bit 9: 1 = Transluscence reversing, 0 = normal "T" // bits 10-14: reserved // bit 15: 1 = Invisible sprite, 0 = not invisible // picnum - texture index into art file @@ -324,10 +396,16 @@ impl World { // statnum - current status of sprite (inactive/monster/bullet, etc.) // // ang - angle the sprite is facing - // owner, xvel, yvel, zvel, lotag, hitag, extra - These variables used by the game programmer only + // owner, xvel, yvel, zvel, lotag, hitag, extra - These variables used + // by the game programmer only let mut sprites = Vec::new(); let sprite_count = data.read_u16::<LE>()?; + expected_len += 44 * (sprite_count as usize); + + if len < expected_len { + bail!(format!("Invalid sprite count (given: {})", sprite_count)); + } for _ in 0..sprite_count { let position_x = data.read_i32::<LE>()?; @@ -380,47 +458,40 @@ impl World { } } -#[derive(Debug)] -struct Sector { - first_wall: i16, - wall_count: i16, - visibility: u8, - tags: (i16, i16, i16), +#[cfg(test)] +mod tests { + use super::*; - ceiling_height: i32, - ceiling_slope: i16, - ceiling_status: i16, - ceiling_bitmap: i16, - ceiling_shade: i8, - ceiling_palette: u8, - ceiling_panning: (u8, u8), - - floor_height: i32, - floor_slope: i16, - floor_status: i16, - floor_bitmap: i16, - floor_shade: i8, - floor_palette: u8, - floor_panning: (u8, u8), -} + #[test] + fn test_load_slice() { + // Binary blob containing a MAP test vector, made by me. Contains an + // arbitrary header, no walls, no sectors, and no sprites. + let data = vec![ + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + ]; -#[derive(Debug)] -struct Wall { - position: (i32, i32), + if let Err(e) = World::from_map(&data) { + panic!("{}", e); + } - adjacent_wall_index: i16, - opposite_wall_index: i16, - into_sector_index: i16, + // TODO: Test the contents of the map. + } - bitmap: i16, - bitmap_overlay: i16, - shade: i8, - palette: u8, - stretch: (u8, u8), - panning: (u8, u8), + #[test] + fn test_incomplete_header() { + // Binary blob similar to the MAP test vector above, but with a header + // that would be too small to be valid. + let data = vec![ + 0x07, 0x00, 0x00, 0x00, + ]; - status: i16, - tags: (i16, i16, i16), + if let Ok(_) = World::from_map(&data) { + panic!("Accepted invalid header."); + } + } } #[derive(Debug)] |