war 0.2.0

A viewer/editor for Darksiders saves, worlds, and scripts
Documentation
use crate::darksiders1::gfc;
use std::{
    convert::{TryFrom, TryInto},
    sync::Arc,
};

#[derive(Debug)]
pub struct WorldObjectData {
    pub values: Vec<gfc::SaveValue>,
    pub object_id: u32,
    pub data: WorldObjectDataData,
}

impl From<WorldObjectData> for gfc::Value {
    fn from(data: WorldObjectData) -> Self {
        let values = data.values.into_iter().map(Into::into).collect();

        gfc::Value::Object(Arc::new(gfc::Object {
            classname: "WorldObjectData".to_string(),
            properties: vec![
                prop!(
                    "ObjectID",
                    gfc::Value::Int(data.object_id.try_into().unwrap()),
                ),
                prop!("Data", data.data.into()),
                prop!("Values", gfc::Value::Array(values)),
            ],
        }))
    }
}

impl TryFrom<&gfc::Value> for WorldObjectData {
    type Error = ();

    fn try_from(value: &gfc::Value) -> Result<Self, Self::Error> {
        let object = value.as_object().ok_or(())?;
        if object.classname != "WorldObjectData" {
            return Err(());
        }

        Ok(Self {
            values: object
                .get_property("Values")
                .and_then(gfc::Value::as_array)
                .ok_or(())?
                .iter()
                .map(TryInto::try_into)
                .collect::<Result<_, _>>()?,
            object_id: object
                .get_property("ObjectID")
                .and_then(gfc::Value::as_int)
                .ok_or(())?
                .try_into()
                .map_err(|_| ())?,
            data: object.get_property("Data").ok_or(())?.try_into()?,
        })
    }
}

#[derive(Debug)]
pub enum WorldObjectDataData {
    None,
    MapTileData(gfc::MapTileData),
    DraggableActorData(gfc::DraggableActorData),
}

impl From<WorldObjectDataData> for gfc::Value {
    fn from(object: WorldObjectDataData) -> Self {
        match object {
            WorldObjectDataData::None => gfc::Value::Null,
            WorldObjectDataData::MapTileData(data) => data.into(),
            WorldObjectDataData::DraggableActorData(data) => data.into(),
        }
    }
}

impl TryFrom<&gfc::Value> for WorldObjectDataData {
    type Error = ();

    fn try_from(value: &gfc::Value) -> Result<Self, Self::Error> {
        let object = match value {
            gfc::Value::Null => return Ok(Self::None),
            gfc::Value::Object(object) => object,
            _ => return Err(()),
        };
        match object.classname.as_str() {
            "MapTileData" => Ok(Self::MapTileData(value.try_into()?)),
            "DraggableActorData" => Ok(Self::DraggableActorData(value.try_into()?)),
            _ => Err(()),
        }
    }
}

#[derive(Debug)]
pub struct LayerData {
    pub layer_id: u16,
    pub world_objects: Vec<WorldObjectData>,
}

impl From<LayerData> for gfc::Value {
    fn from(data: LayerData) -> Self {
        let world_objects = data.world_objects.into_iter().map(Into::into).collect();

        gfc::Value::Object(Arc::new(gfc::Object {
            classname: "LayerData".to_string(),
            properties: vec![
                prop!("LayerID", gfc::Value::Int(data.layer_id.into())),
                prop!("WorldObjects", gfc::Value::Array(world_objects)),
            ],
        }))
    }
}

impl TryFrom<&gfc::Value> for LayerData {
    type Error = ();

    fn try_from(value: &gfc::Value) -> Result<Self, Self::Error> {
        let object = value.as_object().ok_or(())?;
        if object.classname != "LayerData" {
            return Err(());
        }

        Ok(Self {
            layer_id: object
                .get_property("LayerID")
                .and_then(gfc::Value::as_int)
                .ok_or(())?
                .try_into()
                .map_err(|_| ())?,
            world_objects: object
                .get_property("WorldObjects")
                .and_then(gfc::Value::as_array)
                .ok_or(())?
                .iter()
                .map(TryInto::try_into)
                .collect::<Result<_, _>>()?,
        })
    }
}

#[derive(Debug)]
pub struct RegionData {
    pub region_id: u16,
    pub layers: Vec<LayerData>,
}

impl From<RegionData> for gfc::Value {
    fn from(data: RegionData) -> Self {
        let layers = data.layers.into_iter().map(Into::into).collect();

        gfc::Value::Object(Arc::new(gfc::Object {
            classname: "RegionData".to_string(),
            properties: vec![
                prop!("RegionID", gfc::Value::Int(data.region_id.into())),
                prop!("Layers", gfc::Value::Array(layers)),
            ],
        }))
    }
}

impl TryFrom<&gfc::Value> for RegionData {
    type Error = ();

    fn try_from(value: &gfc::Value) -> Result<Self, Self::Error> {
        let object = value.as_object().ok_or(())?;
        if object.classname != "RegionData" {
            return Err(());
        }

        Ok(Self {
            region_id: object
                .get_property("RegionID")
                .and_then(gfc::Value::as_int)
                .ok_or(())?
                .try_into()
                .map_err(|_| ())?,
            layers: object
                .get_property("Layers")
                .and_then(gfc::Value::as_array)
                .ok_or(())?
                .iter()
                .map(TryInto::try_into)
                .collect::<Result<_, _>>()?,
        })
    }
}

#[derive(Debug)]
pub struct WorldData {
    pub values: Vec<gfc::SaveValue>,
    pub world: String,
    pub regions: Vec<RegionData>,
}

impl From<WorldData> for gfc::Value {
    fn from(data: WorldData) -> Self {
        let regions = data.regions.into_iter().map(Into::into).collect();
        let values = data.values.into_iter().map(Into::into).collect();

        gfc::Value::Object(Arc::new(gfc::Object {
            classname: "WorldData".to_string(),
            properties: vec![
                prop!("World", gfc::Value::HString(data.world)),
                prop!("Regions", gfc::Value::Array(regions)),
                prop!("Values", gfc::Value::Array(values)),
            ],
        }))
    }
}

impl TryFrom<&gfc::Value> for WorldData {
    type Error = ();

    fn try_from(value: &gfc::Value) -> Result<Self, Self::Error> {
        let object = value.as_object().ok_or(())?;
        if object.classname != "WorldData" {
            return Err(());
        }

        Ok(Self {
            values: object
                .get_property("Values")
                .and_then(gfc::Value::as_array)
                .ok_or(())?
                .iter()
                .map(TryInto::try_into)
                .collect::<Result<_, _>>()?,
            world: object
                .get_property("World")
                .and_then(gfc::Value::as_hstring)
                .ok_or(())?
                .to_string(),
            regions: object
                .get_property("Regions")
                .and_then(gfc::Value::as_array)
                .ok_or(())?
                .iter()
                .map(TryInto::try_into)
                .collect::<Result<_, _>>()?,
        })
    }
}

#[derive(Debug)]
pub struct SaveGameInfo {
    pub difficulty_level: u8,
    pub health_stones: u8,
    pub health_level: u8,
    pub wrath_stones: u32,
    pub wrath_level: u8,
    pub game_time: u32,
    pub time_created: u32,
    pub overview_region_id: i32,
    pub user_id: i32,
}

impl From<SaveGameInfo> for gfc::Value {
    fn from(info: SaveGameInfo) -> Self {
        gfc::Value::Object(Arc::new(gfc::Object {
            classname: "SaveGameInfo".to_string(),
            properties: vec![
                prop!(
                    "DifficultyLevel",
                    gfc::Value::Int(info.difficulty_level.into()),
                ),
                prop!("HealthStones", gfc::Value::Int(info.health_stones.into())),
                prop!("HealthLevel", gfc::Value::Int(info.health_level.into())),
                prop!(
                    "WrathStones",
                    gfc::Value::Int(info.wrath_stones.try_into().unwrap()),
                ),
                prop!("WrathLevel", gfc::Value::Int(info.wrath_level.into())),
                prop!(
                    "GameTime",
                    gfc::Value::Int(info.game_time.try_into().unwrap()),
                ),
                prop!(
                    "TimeCreated",
                    gfc::Value::Int(info.time_created.try_into().unwrap()),
                ),
                prop!("OverviewRegionID", gfc::Value::Int(info.overview_region_id)),
                prop!("UserId", gfc::Value::Int(info.user_id)),
            ],
        }))
    }
}

impl TryFrom<&gfc::Value> for SaveGameInfo {
    type Error = ();

    fn try_from(value: &gfc::Value) -> Result<Self, Self::Error> {
        let object = value.as_object().ok_or(())?;
        if object.classname != "SaveGameInfo" {
            return Err(());
        }

        Ok(Self {
            difficulty_level: object
                .get_property("DifficultyLevel")
                .and_then(gfc::Value::as_int)
                .ok_or(())?
                .try_into()
                .map_err(|_| ())?,
            health_stones: object
                .get_property("HealthStones")
                .and_then(gfc::Value::as_int)
                .ok_or(())?
                .try_into()
                .map_err(|_| ())?,
            health_level: object
                .get_property("HealthLevel")
                .and_then(gfc::Value::as_int)
                .ok_or(())?
                .try_into()
                .map_err(|_| ())?,
            wrath_stones: object
                .get_property("WrathStones")
                .and_then(gfc::Value::as_int)
                .ok_or(())?
                .try_into()
                .map_err(|_| ())?,
            wrath_level: object
                .get_property("WrathLevel")
                .and_then(gfc::Value::as_int)
                .ok_or(())?
                .try_into()
                .map_err(|_| ())?,
            game_time: object
                .get_property("GameTime")
                .and_then(gfc::Value::as_int)
                .ok_or(())?
                .try_into()
                .map_err(|_| ())?,
            time_created: object
                .get_property("TimeCreated")
                .and_then(gfc::Value::as_int)
                .ok_or(())?
                .try_into()
                .map_err(|_| ())?,
            overview_region_id: object
                .get_property("OverviewRegionID")
                .and_then(gfc::Value::as_int)
                .ok_or(())?,
            user_id: object
                .get_property("UserId")
                .and_then(gfc::Value::as_int)
                .ok_or(())?,
        })
    }
}