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}