issun 0.10.0

A mini game engine for logic-focused games - Build games in ISSUN (一寸) of time
Documentation
//! Hook for game-specific synthesis customization

use super::types::*;
use async_trait::async_trait;

/// Hook for game-specific synthesis customization
#[async_trait]
pub trait SynthesisHook: Send + Sync {
    /// Consume ingredients from inventory/resources
    ///
    /// **Game-specific logic**:
    /// - Remove items from inventory
    /// - Deduct technology points
    /// - Consume resources
    ///
    /// # Arguments
    ///
    /// * `entity_id` - Entity performing synthesis
    /// * `ingredients` - Ingredients to consume
    ///
    /// # Returns
    ///
    /// Ok(()) if consumption succeeded, Err otherwise
    async fn consume_ingredients(
        &self,
        _entity_id: &EntityId,
        _ingredients: &[(IngredientType, u32)],
    ) -> Result<(), SynthesisError> {
        // Default: always succeed
        Ok(())
    }

    /// Apply synthesis result to game state
    ///
    /// **Game-specific logic**:
    /// - Add item to inventory
    /// - Unlock technology
    /// - Modify entity properties
    ///
    /// # Arguments
    ///
    /// * `entity_id` - Entity receiving result
    /// * `result` - Synthesis result to apply
    async fn apply_synthesis_result(&self, _entity_id: &EntityId, _result: &SynthesisResult) {
        // Default: no-op
    }

    /// Refund ingredients on failure
    ///
    /// # Arguments
    ///
    /// * `entity_id` - Entity to refund to
    /// * `ingredients` - Ingredients to return
    async fn refund_ingredients(
        &self,
        _entity_id: &EntityId,
        _ingredients: &[(IngredientType, u32)],
    ) {
        // Default: no-op
    }

    /// Get skill modifier for success rate
    ///
    /// **Game-specific logic**:
    /// - Player crafting skill
    /// - Equipment bonuses
    /// - Location modifiers
    ///
    /// # Arguments
    ///
    /// * `entity_id` - Entity performing synthesis
    /// * `recipe_id` - Recipe being synthesized
    ///
    /// # Returns
    ///
    /// Skill modifier to add to base success rate (0.0-1.0)
    async fn get_skill_modifier(&self, _entity_id: &EntityId, _recipe_id: &RecipeId) -> f32 {
        // Default: no modifier
        0.0
    }

    /// Generate byproduct
    ///
    /// **Game-specific logic**:
    /// - Random bonus items
    /// - Skill experience
    /// - Achievements
    ///
    /// # Arguments
    ///
    /// * `entity_id` - Entity receiving byproduct
    /// * `recipe_id` - Recipe that generated byproduct
    async fn generate_byproduct(&self, _entity_id: &EntityId, _recipe_id: &RecipeId) {
        // Default: no-op
    }

    /// Synthesis started event
    ///
    /// # Arguments
    ///
    /// * `entity_id` - Entity starting synthesis
    /// * `recipe_id` - Recipe being synthesized
    async fn on_synthesis_started(&self, _entity_id: &EntityId, _recipe_id: &RecipeId) {
        // Default: no-op
    }

    /// Synthesis succeeded event
    ///
    /// # Arguments
    ///
    /// * `entity_id` - Entity that succeeded
    /// * `recipe_id` - Recipe that succeeded
    /// * `quality` - Quality of result (0.0-1.0)
    async fn on_synthesis_success(
        &self,
        _entity_id: &EntityId,
        _recipe_id: &RecipeId,
        _quality: f32,
    ) {
        // Default: no-op
    }

    /// Synthesis failed event
    ///
    /// # Arguments
    ///
    /// * `entity_id` - Entity that failed
    /// * `recipe_id` - Recipe that failed
    async fn on_synthesis_failure(&self, _entity_id: &EntityId, _recipe_id: &RecipeId) {
        // Default: no-op
    }

    /// Recipe discovered event
    ///
    /// # Arguments
    ///
    /// * `entity_id` - Entity that discovered recipe
    /// * `recipe_id` - Recipe that was discovered
    async fn on_recipe_discovered(&self, _entity_id: &EntityId, _recipe_id: &RecipeId) {
        // Default: no-op
    }
}

/// Default hook (no customization)
#[derive(Clone, Copy, Debug, Default)]
pub struct DefaultSynthesisHook;

#[async_trait]
impl SynthesisHook for DefaultSynthesisHook {}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_default_hook_consume() {
        let hook = DefaultSynthesisHook;

        let result = hook
            .consume_ingredients(
                &"player1".to_string(),
                &[(
                    IngredientType::Item {
                        item_id: "iron".to_string(),
                    },
                    3,
                )],
            )
            .await;

        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn test_default_hook_apply_result() {
        let hook = DefaultSynthesisHook;

        let result = SynthesisResult {
            result_type: ResultType::Item {
                item_id: "sword".to_string(),
            },
            quantity: 1,
            quality_range: (0.8, 1.2),
        };

        // Should not panic
        hook.apply_synthesis_result(&"player1".to_string(), &result)
            .await;
    }

    #[tokio::test]
    async fn test_default_hook_get_skill_modifier() {
        let hook = DefaultSynthesisHook;

        let modifier = hook
            .get_skill_modifier(&"player1".to_string(), &"sword".to_string())
            .await;

        assert_eq!(modifier, 0.0);
    }

    #[tokio::test]
    async fn test_default_hook_events() {
        let hook = DefaultSynthesisHook;

        // All event callbacks should work without panic
        hook.on_synthesis_started(&"player1".to_string(), &"sword".to_string())
            .await;
        hook.on_synthesis_success(&"player1".to_string(), &"sword".to_string(), 0.9)
            .await;
        hook.on_synthesis_failure(&"player1".to_string(), &"sword".to_string())
            .await;
        hook.on_recipe_discovered(&"player1".to_string(), &"sword".to_string())
            .await;
        hook.generate_byproduct(&"player1".to_string(), &"sword".to_string())
            .await;
    }
}