logic_constructor 0.1.0

Move combat and ability logic out of code and into HOCON config — designers tweak damage, healing, and targeting in a text file; the engine parses it into typed actions and runs them against your entities.
Documentation
use hocon_rs::Value;

use crate::lc_action::LcAction;
use crate::lc_entity_type::LcEntityType;
use crate::lc_single_action_config::{LcSingleActionConfig, LcConfigRaw};
use crate::parser::lc_config_parser::{parse_lc_config, parse_lc_config_raw};

/// Parses a HOCON array into a `Vec<LcConfigRaw>` without interpreting effect bodies.
pub fn parse_lc_config_list_raw(value: &Value) -> Result<Vec<LcConfigRaw>, String> {
    match value {
        Value::Array(array) => {
            let mut configs = Vec::with_capacity(array.len());
            for (index, item) in array.iter().enumerate() {
                let config = parse_lc_config_raw(item)
                    .map_err(|e| format!("Failed to parse LcaConfig at index {}: {}", index, e))?;
                configs.push(config);
            }
            Ok(configs)
        }
        _ => Err(format!(
            "LcaConfig list parser expects an array, got: {:?}",
            value
        )),
    }
}

/// Parses a HOCON array into `Vec<LcConfig<T>>`, delegating effect parsing to `parse_effect`.
pub fn parse_lc_config_list<T, F>(
    value: &Value,
    parse_effect: &F,
) -> Result<Vec<LcSingleActionConfig<T>>, String>
where
    T: LcEntityType,
    F: Fn(&Value) -> Result<Box<dyn LcAction<T>>, String>,
{
    match value {
        Value::Array(array) => {
            let mut configs = Vec::with_capacity(array.len());
            for (index, item) in array.iter().enumerate() {
                let config = parse_lc_config(item, parse_effect)
                    .map_err(|e| format!("Failed to parse LcaConfig at index {}: {}", index, e))?;
                configs.push(config);
            }
            Ok(configs)
        }
        _ => Err(format!(
            "LcaConfig list parser expects an array, got: {:?}",
            value
        )),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::collision_kind::CollisionKind;
    use hocon_rs::Config;

    #[test]
    fn test_parse_empty_array() {
        let hocon_str = r#"list = []"#;
        let parsed: Value = Config::parse_str(hocon_str, None).unwrap();
        let value = parsed.as_object().unwrap().get("list").unwrap();

        let result = parse_lc_config_list_raw(value).unwrap();
        assert_eq!(result.len(), 0);
    }

    #[test]
    fn test_parse_simple_format_only_raw() {
        let hocon_str = r#"
        list = [
            { DealDamage: 10 }
            { DealDamage: 20 }
            { DealDamage: 30 }
        ]
        "#;
        let parsed: Value = Config::parse_str(hocon_str, None).unwrap();
        let value = parsed.as_object().unwrap().get("list").unwrap();

        let result = parse_lc_config_list_raw(value).unwrap();
        assert_eq!(result.len(), 3);
        assert_eq!(result[0].collision, CollisionKind::OTHER);
        assert_eq!(result[1].collision, CollisionKind::OTHER);
        assert_eq!(result[2].collision, CollisionKind::OTHER);
    }

    #[test]
    fn test_parse_full_format_only_raw() {
        let hocon_str = r#"
        list = [
            { lca: { DealDamage: 10 }, collision: "Self" }
            { lca: { DealDamage: 20 }, collision: "SameKind" }
            { lca: { DealDamage: 30 }, collision: "Other" }
        ]
        "#;
        let parsed: Value = Config::parse_str(hocon_str, None).unwrap();
        let value = parsed.as_object().unwrap().get("list").unwrap();

        let result = parse_lc_config_list_raw(value).unwrap();
        assert_eq!(result.len(), 3);
        assert_eq!(result[0].collision, CollisionKind::SELF);
        assert_eq!(result[1].collision, CollisionKind::SAME_KIND);
        assert_eq!(result[2].collision, CollisionKind::OTHER);
    }

    #[test]
    fn test_parse_mixed_formats_raw() {
        let hocon_str = r#"
        list = [
            { DealDamage: 10 }
            { lca: { DealDamage: 20 }, collision: "Self" }
            { DealDamage: 30 }
            { lca: { DealDamage: 40 }, collision: "SameKind" }
            { DealDamage: 50 }
        ]
        "#;
        let parsed: Value = Config::parse_str(hocon_str, None).unwrap();
        let value = parsed.as_object().unwrap().get("list").unwrap();

        let result = parse_lc_config_list_raw(value).unwrap();
        assert_eq!(result.len(), 5);
        assert_eq!(result[0].collision, CollisionKind::OTHER);
        assert_eq!(result[1].collision, CollisionKind::SELF);
        assert_eq!(result[2].collision, CollisionKind::OTHER);
        assert_eq!(result[3].collision, CollisionKind::SAME_KIND);
        assert_eq!(result[4].collision, CollisionKind::OTHER);
    }

    #[test]
    fn test_parse_not_an_array() {
        let hocon_str = r#"{ DealDamage: 10 }"#;
        let value: Value = Config::parse_str(hocon_str, None).unwrap();

        let result = parse_lc_config_list_raw(&value);
        assert!(result.is_err());
        if let Err(err) = result {
            assert!(err.contains("expects an array"));
        }
    }
}