Skip to main content

issun/plugin/modular_synthesis/
hook.rs

1//! Hook for game-specific synthesis customization
2
3use super::types::*;
4use async_trait::async_trait;
5
6/// Hook for game-specific synthesis customization
7#[async_trait]
8pub trait SynthesisHook: Send + Sync {
9    /// Consume ingredients from inventory/resources
10    ///
11    /// **Game-specific logic**:
12    /// - Remove items from inventory
13    /// - Deduct technology points
14    /// - Consume resources
15    ///
16    /// # Arguments
17    ///
18    /// * `entity_id` - Entity performing synthesis
19    /// * `ingredients` - Ingredients to consume
20    ///
21    /// # Returns
22    ///
23    /// Ok(()) if consumption succeeded, Err otherwise
24    async fn consume_ingredients(
25        &self,
26        _entity_id: &EntityId,
27        _ingredients: &[(IngredientType, u32)],
28    ) -> Result<(), SynthesisError> {
29        // Default: always succeed
30        Ok(())
31    }
32
33    /// Apply synthesis result to game state
34    ///
35    /// **Game-specific logic**:
36    /// - Add item to inventory
37    /// - Unlock technology
38    /// - Modify entity properties
39    ///
40    /// # Arguments
41    ///
42    /// * `entity_id` - Entity receiving result
43    /// * `result` - Synthesis result to apply
44    async fn apply_synthesis_result(&self, _entity_id: &EntityId, _result: &SynthesisResult) {
45        // Default: no-op
46    }
47
48    /// Refund ingredients on failure
49    ///
50    /// # Arguments
51    ///
52    /// * `entity_id` - Entity to refund to
53    /// * `ingredients` - Ingredients to return
54    async fn refund_ingredients(
55        &self,
56        _entity_id: &EntityId,
57        _ingredients: &[(IngredientType, u32)],
58    ) {
59        // Default: no-op
60    }
61
62    /// Get skill modifier for success rate
63    ///
64    /// **Game-specific logic**:
65    /// - Player crafting skill
66    /// - Equipment bonuses
67    /// - Location modifiers
68    ///
69    /// # Arguments
70    ///
71    /// * `entity_id` - Entity performing synthesis
72    /// * `recipe_id` - Recipe being synthesized
73    ///
74    /// # Returns
75    ///
76    /// Skill modifier to add to base success rate (0.0-1.0)
77    async fn get_skill_modifier(&self, _entity_id: &EntityId, _recipe_id: &RecipeId) -> f32 {
78        // Default: no modifier
79        0.0
80    }
81
82    /// Generate byproduct
83    ///
84    /// **Game-specific logic**:
85    /// - Random bonus items
86    /// - Skill experience
87    /// - Achievements
88    ///
89    /// # Arguments
90    ///
91    /// * `entity_id` - Entity receiving byproduct
92    /// * `recipe_id` - Recipe that generated byproduct
93    async fn generate_byproduct(&self, _entity_id: &EntityId, _recipe_id: &RecipeId) {
94        // Default: no-op
95    }
96
97    /// Synthesis started event
98    ///
99    /// # Arguments
100    ///
101    /// * `entity_id` - Entity starting synthesis
102    /// * `recipe_id` - Recipe being synthesized
103    async fn on_synthesis_started(&self, _entity_id: &EntityId, _recipe_id: &RecipeId) {
104        // Default: no-op
105    }
106
107    /// Synthesis succeeded event
108    ///
109    /// # Arguments
110    ///
111    /// * `entity_id` - Entity that succeeded
112    /// * `recipe_id` - Recipe that succeeded
113    /// * `quality` - Quality of result (0.0-1.0)
114    async fn on_synthesis_success(
115        &self,
116        _entity_id: &EntityId,
117        _recipe_id: &RecipeId,
118        _quality: f32,
119    ) {
120        // Default: no-op
121    }
122
123    /// Synthesis failed event
124    ///
125    /// # Arguments
126    ///
127    /// * `entity_id` - Entity that failed
128    /// * `recipe_id` - Recipe that failed
129    async fn on_synthesis_failure(&self, _entity_id: &EntityId, _recipe_id: &RecipeId) {
130        // Default: no-op
131    }
132
133    /// Recipe discovered event
134    ///
135    /// # Arguments
136    ///
137    /// * `entity_id` - Entity that discovered recipe
138    /// * `recipe_id` - Recipe that was discovered
139    async fn on_recipe_discovered(&self, _entity_id: &EntityId, _recipe_id: &RecipeId) {
140        // Default: no-op
141    }
142}
143
144/// Default hook (no customization)
145#[derive(Clone, Copy, Debug, Default)]
146pub struct DefaultSynthesisHook;
147
148#[async_trait]
149impl SynthesisHook for DefaultSynthesisHook {}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[tokio::test]
156    async fn test_default_hook_consume() {
157        let hook = DefaultSynthesisHook;
158
159        let result = hook
160            .consume_ingredients(
161                &"player1".to_string(),
162                &[(
163                    IngredientType::Item {
164                        item_id: "iron".to_string(),
165                    },
166                    3,
167                )],
168            )
169            .await;
170
171        assert!(result.is_ok());
172    }
173
174    #[tokio::test]
175    async fn test_default_hook_apply_result() {
176        let hook = DefaultSynthesisHook;
177
178        let result = SynthesisResult {
179            result_type: ResultType::Item {
180                item_id: "sword".to_string(),
181            },
182            quantity: 1,
183            quality_range: (0.8, 1.2),
184        };
185
186        // Should not panic
187        hook.apply_synthesis_result(&"player1".to_string(), &result)
188            .await;
189    }
190
191    #[tokio::test]
192    async fn test_default_hook_get_skill_modifier() {
193        let hook = DefaultSynthesisHook;
194
195        let modifier = hook
196            .get_skill_modifier(&"player1".to_string(), &"sword".to_string())
197            .await;
198
199        assert_eq!(modifier, 0.0);
200    }
201
202    #[tokio::test]
203    async fn test_default_hook_events() {
204        let hook = DefaultSynthesisHook;
205
206        // All event callbacks should work without panic
207        hook.on_synthesis_started(&"player1".to_string(), &"sword".to_string())
208            .await;
209        hook.on_synthesis_success(&"player1".to_string(), &"sword".to_string(), 0.9)
210            .await;
211        hook.on_synthesis_failure(&"player1".to_string(), &"sword".to_string())
212            .await;
213        hook.on_recipe_discovered(&"player1".to_string(), &"sword".to_string())
214            .await;
215        hook.generate_byproduct(&"player1".to_string(), &"sword".to_string())
216            .await;
217    }
218}