halbu 0.3.0

Diablo II save file parsing library.
Documentation
use crate::{waypoints::*, Act};

const NONTRIVIAL_WAYPOINT_BYTES: [u8; 80] = [
    0x57, 0x53, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x02, 0x01, 0xEF, 0xEB, 0xD7, 0xFF, 0x47, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x02, 0x01, 0xEF, 0xE3, 0xBD, 0xFF, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0xEF, 0xEF, 0xEF, 0xFE, 0x43, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];

#[test]
fn parse_waypoints_reads_expected_flags() {
    let parsed_waypoints = Waypoints::parse(&NONTRIVIAL_WAYPOINT_BYTES)
        .expect("Waypoint payload should parse in test fixture.");

    assert_eq!(parsed_waypoints.hell.act1.get_by_index(8), Ok(true));
    assert_eq!(parsed_waypoints.nightmare.act2.get_by_index(3), Ok(false));
}

#[test]
fn waypoints_round_trip_preserves_nontrivial_bytes() {
    let parsed_waypoints = Waypoints::parse(&NONTRIVIAL_WAYPOINT_BYTES)
        .expect("Waypoint payload should parse in test fixture.");
    assert_eq!(parsed_waypoints.to_bytes(), NONTRIVIAL_WAYPOINT_BYTES);
}

#[test]
fn default_waypoints_encode_to_expected_layout() {
    let mut expected_bytes = [0x00; 80];
    expected_bytes[0..8].copy_from_slice(&[0x57, 0x53, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00]);
    for difficulty_start in [8, 32, 56] {
        expected_bytes[difficulty_start] = 0x02;
        expected_bytes[difficulty_start + 1] = 0x01;
        expected_bytes[difficulty_start + 2] = 0x01;
    }

    assert_eq!(expected_bytes, Waypoints::default().to_bytes());
}

#[test]
fn act_set_rejects_wrong_act_waypoint() {
    let mut act1 = ActWaypoints::<9>::new_for_act(Act::Act1);
    let error = act1
        .set(Waypoint::LutGholein, true)
        .expect_err("Setting an Act II waypoint on Act I must fail.");
    assert_eq!(
        error,
        WaypointError::WrongAct {
            waypoint: Waypoint::LutGholein,
            expected: Act::Act1,
            actual: Act::Act2,
        }
    );
}

#[test]
fn act_get_rejects_wrong_act_waypoint() {
    let act4 = ActWaypoints::<3>::new_for_act(Act::Act4);
    let error = act4
        .get(Waypoint::Harrogath)
        .expect_err("Reading an Act V waypoint from Act IV must fail.");
    assert_eq!(
        error,
        WaypointError::WrongAct {
            waypoint: Waypoint::Harrogath,
            expected: Act::Act4,
            actual: Act::Act5,
        }
    );
}

#[test]
fn get_by_index_reports_out_of_range() {
    let act4 = ActWaypoints::<3>::new_for_act(Act::Act4);
    let error = act4.get_by_index(3).expect_err("Act IV has indices 0..=2 only.");
    assert_eq!(error, WaypointError::IndexOutOfRange { act: Act::Act4, index: 3, max_index: 2 });
}

#[test]
fn set_by_index_reports_out_of_range() {
    let mut act4 = ActWaypoints::<3>::new_for_act(Act::Act4);
    let error = act4.set_by_index(3, true).expect_err("Act IV has indices 0..=2 only.");
    assert_eq!(error, WaypointError::IndexOutOfRange { act: Act::Act4, index: 3, max_index: 2 });
}

#[test]
fn difficulty_set_all_updates_every_act() {
    let mut difficulty_waypoints = DifficultyWaypoints::default();
    assert_eq!(difficulty_waypoints.act1.get_by_index(0), Ok(true));

    difficulty_waypoints.set_all(false);

    assert_eq!(difficulty_waypoints.act1.get_by_index(0), Ok(false));
    assert_eq!(difficulty_waypoints.act2.get_by_index(0), Ok(false));
    assert_eq!(difficulty_waypoints.act3.get_by_index(4), Ok(false));
    assert_eq!(difficulty_waypoints.act4.get_by_index(2), Ok(false));
    assert_eq!(difficulty_waypoints.act5.get_by_index(8), Ok(false));
}

#[test]
fn waypoint_try_from_rejects_invalid_absolute_index() {
    let _error = Waypoint::try_from(39).expect_err("Only ids 0..=38 are valid.");
}

#[test]
fn waypoint_from_act_index_rejects_invalid_index_for_act() {
    let _error =
        Waypoint::from_act_index(Act::Act4, 3).expect_err("Act IV only has indices 0..=2.");
}

#[test]
fn waypoint_metadata_methods_match_known_values() {
    let catacombs = Waypoint::Catacombs;
    assert_eq!(catacombs.act(), Act::Act1);
    assert_eq!(catacombs.name(), "Catacombs");
    assert_eq!(catacombs.index_within_act(), 8);

    let river_of_flames = Waypoint::RiverOfFlames;
    assert_eq!(river_of_flames.act(), Act::Act4);
    assert_eq!(river_of_flames.name(), "River of Flames");
    assert_eq!(river_of_flames.index_within_act(), 2);
}