zzstat 0.1.6

A deterministic, hardcode-free stat calculation engine designed for MMORPGs
Documentation
//! Example 04: JSON Driven Combat Engine with RNG
//!
//! Demonstrates how to calculate damage between an Attacker and Defender
//! using an entirely data-driven Combat AST (Abstract Syntax Tree) with
//! deterministic RNG support for Dodge and Crit.

use zzstat::combat::{CombatEngine, CombatFormula};
use zzstat::context::StatContext;
use zzstat::resolver::StatResolver;
use zzstat::source::ConstantSource;
use zzstat::stat_id::StatId;

fn main() {
    let mut attacker = StatResolver::new();
    attacker.register_source(StatId::from("ATK"), Box::new(ConstantSource(100.0)));
    attacker.register_source(StatId::from("CRIT_CHANCE"), Box::new(ConstantSource(0.30))); // 30% Crit
    attacker.register_source(StatId::from("CRIT_MULT"), Box::new(ConstantSource(2.0))); // 2x Damage

    let mut defender = StatResolver::new();
    defender.register_source(StatId::from("DEF"), Box::new(ConstantSource(20.0)));
    defender.register_source(StatId::from("DODGE_CHANCE"), Box::new(ConstantSource(0.15))); // 15% Dodge

    let ctx = StatContext::new();

    // 1. The Combat Formula (usually loaded from a JSON file generated by AI or Game Designers)
    // Logic:
    // Is it dodged? (15% chance) -> 0 Damage
    // Else -> Is it critical? (30% chance) -> (ATK * 2) - DEF
    // Else -> Normal Hit -> ATK - DEF
    let json_data = r#"{
        "name": "Sword Slash",
        "expression": {
            "type": "Chance",
            "chance_expr": { "type": "Stat", "target": "defender", "stat": "DODGE_CHANCE" },
            "success_expr": { "type": "Constant", "value": 0.0 },
            "fail_expr": {
                "type": "Chance",
                "chance_expr": { "type": "Stat", "target": "attacker", "stat": "CRIT_CHANCE" },
                "success_expr": {
                    "type": "Clamp",
                    "min": 0.0,
                    "max": null,
                    "expr": {
                        "type": "Subtract",
                        "left": {
                            "type": "Multiply",
                            "left": { "type": "Stat", "target": "attacker", "stat": "ATK" },
                            "right": { "type": "Stat", "target": "attacker", "stat": "CRIT_MULT" }
                        },
                        "right": { "type": "Stat", "target": "defender", "stat": "DEF" }
                    }
                },
                "fail_expr": {
                    "type": "Clamp",
                    "min": 0.0,
                    "max": null,
                    "expr": {
                        "type": "Subtract",
                        "left": { "type": "Stat", "target": "attacker", "stat": "ATK" },
                        "right": { "type": "Stat", "target": "defender", "stat": "DEF" }
                    }
                }
            }
        }
    }"#;

    let formula: CombatFormula = serde_json::from_str(json_data).unwrap();

    println!("Executing Formula: {}\n", formula.name);

    // 2. Scenario 1: Unlucky attacker misses! (Rolls 0.10 <= 0.15 Dodge)
    let mut rng_miss = || 0.10;
    let dmg_miss = CombatEngine::evaluate(
        &formula,
        &mut attacker,
        &ctx,
        &mut defender,
        &ctx,
        &mut rng_miss,
    )
    .unwrap();
    println!("Scenario 1 (Dodge): {} Damage", dmg_miss);

    // 3. Scenario 2: Normal Hit (Roll 1: 0.50 Dodge Fail, Roll 2: 0.50 Crit Fail)
    let mut rolls = vec![0.50, 0.50].into_iter();
    let mut rng_normal = || rolls.next().unwrap();
    let dmg_normal = CombatEngine::evaluate(
        &formula,
        &mut attacker,
        &ctx,
        &mut defender,
        &ctx,
        &mut rng_normal,
    )
    .unwrap();
    println!("Scenario 2 (Normal Hit): {} Damage", dmg_normal); // (100 - 20) = 80

    // 4. Scenario 3: CRITICAL HIT! (Roll 1: 0.50 Dodge Fail, Roll 2: 0.10 Crit Success!)
    let mut rolls = vec![0.50, 0.10].into_iter();
    let mut rng_crit = || rolls.next().unwrap();
    let dmg_crit = CombatEngine::evaluate(
        &formula,
        &mut attacker,
        &ctx,
        &mut defender,
        &ctx,
        &mut rng_crit,
    )
    .unwrap();
    println!("Scenario 3 (Critical Hit): {} Damage", dmg_crit); // (100 * 2) - 20 = 180
}