Skip to main content

fantasy_craft/core/
app.rs

1use hecs::World;
2use macroquad::prelude::*;
3use futures::{FutureExt, future::BoxFuture};
4use crate::core::context::Context;
5use crate::core::event::EventBus;
6use crate::core::schedule::{Schedule, Stage};
7use crate::core::asset_server::AssetServer;
8use crate::core::plugins::Plugin;
9use crate::core::time::DeltaTime;
10use crate::core::web_context::WebContext;
11use crate::graphics::splash_screen::{SplashScreenData, animate_splash_screen, despawn_splash_screen, setup_splash_screen};
12use crate::input::manager::InputManager;
13use crate::prelude::{Spritesheet, System};
14use crate::gui::resources::PreviousMousePosition;
15use crate::scene::scene_loader::SceneLoader;
16
17pub struct App {
18    pub context: Context,
19    pub schedule: Schedule,
20    pub scene_loader: SceneLoader,
21    pub window_conf: Conf,
22    pub scene_path: Option<String>,
23    pub assets_file: Option<String>,
24    show_splash_screen: bool,
25    splash_screen_logo: String,
26    splash_screen_background_color: Color,
27    binding_path: Option<String>
28}
29
30impl App {
31    pub fn new(conf: Conf) -> Self {
32        let world = World::new();
33        let asset_server = AssetServer::new();
34
35        App {
36            context: Context::new(
37                world,
38                asset_server
39            ),
40            schedule: Schedule::new(),
41            scene_loader: SceneLoader::new(),
42            window_conf: conf,
43            scene_path: None,
44            show_splash_screen: true,
45            assets_file: None,
46            splash_screen_logo: "resources/textures/logo_engine.png".to_string(),
47            splash_screen_background_color: Color::new(1.0, 0.980392157, 0.960784314, 1.0),
48            binding_path: None
49        }
50    }
51
52    pub fn with_splash_screen_enabled(&mut self, enabled: bool) -> &mut Self {
53        self.show_splash_screen = enabled;
54        self
55    }
56
57    pub fn with_splash_screen_logo(&mut self, path: &str) -> &mut Self {
58        self.splash_screen_logo = path.to_string();
59        self
60    }
61
62    pub fn with_splash_screen_background_color(&mut self, color: Color) -> &mut Self {
63        self.splash_screen_background_color = color;
64        self
65    }
66
67    pub fn with_scene_path(&mut self, scene_path: String) -> &mut Self {
68        self.scene_path = Some(scene_path);
69        self
70    }
71
72    pub fn with_assets_file(&mut self, file_path: String) -> &mut Self {
73        self.assets_file = Some(file_path);
74        self
75    }
76
77    pub fn with_binding_file(&mut self, file_path: String) -> &mut Self {
78        self.binding_path = Some(file_path);
79        self
80    }
81
82    pub fn add_system(&mut self, stage: Stage, system: System) -> &mut Self {
83        self.schedule.add_system(stage, system);
84        self
85    }
86
87    pub fn add_plugin<T: Plugin>(&mut self, plugin: T) -> &mut Self {
88        plugin.build(self);
89        self
90    }
91
92    fn resolve_path(base: &str, path: &str) -> String {
93        if path.starts_with("http") {
94            return path.to_string();
95        }
96        format!("{}{}", base, path)
97    }
98
99    pub async fn run(mut self) {
100        // 1. Fetch the Base URL from JavaScript
101        let base_url = WebContext::get_base_url();
102        info!("App: Resolved Base URL: {}", base_url);
103
104        // 2. Resolve paths using the base URL
105        let resolved_splash_path = Self::resolve_path(&base_url, &self.splash_screen_logo);
106        
107        // Resolve optional paths
108        let resolved_assets_file = self.assets_file.as_ref().map(|p| Self::resolve_path(&base_url, p));
109        let resolved_scene_path = self.scene_path.as_ref().map(|p| Self::resolve_path(&base_url, p));
110        let resolved_binding_path = self.binding_path.as_ref().map(|p| Self::resolve_path(&base_url, p));
111
112        const SPLASH_MIN_DURATION: f64 = 3.0;
113
114        let mut maybe_asset_server: Option<AssetServer> = None;
115
116        if self.show_splash_screen {
117            let splash_texture = match load_texture(&resolved_splash_path).await {
118                Ok(tex) => tex,
119                Err(e) => {
120                    error!("Failed to load splash logo at: {}. Error: {}", resolved_splash_path, e);
121                    // Generate a 1x1 Magenta texture as fallback
122                    let img = Image::gen_image_color(1, 1, MAGENTA);
123                    Texture2D::from_image(&img)
124                }
125            };
126
127            // --- Splash setup ---
128            self.context.asset_server.add_spritesheet(
129                "splash_screen_logo".to_string(),
130                Spritesheet::new(
131                    splash_texture,
132                    1024.0,
133                    1024.0,
134                ),
135            );
136
137            self.context.insert_resource(SplashScreenData {
138                background_color: self.splash_screen_background_color
139            });
140
141            setup_splash_screen(&mut self.context);
142            let start_time = get_time();
143
144            // --- Création d'une future pour le chargement ---
145            let mut loading_asset_server = AssetServer::new();
146
147            let asset_path_for_future = resolved_assets_file.clone();
148
149            let mut load_future: BoxFuture<'static, (AssetServer, Result<(), Box<dyn std::error::Error>>)> =
150                Box::pin(async move {
151                    let result = if let Some(path) = asset_path_for_future {
152                        loading_asset_server.load_assets_from_file(&path).await
153                    } else {
154                        Ok(())
155                    };
156                    (loading_asset_server, result)
157                });
158
159            let mut assets_loaded = false;
160
161            // --- Boucle du splash ---
162            loop {
163                let dt = self.context.resource_mut::<DeltaTime>();
164                dt.0 = get_frame_time();
165                clear_background(self.splash_screen_background_color);
166
167                // Animation + rendu
168                animate_splash_screen(&mut self.context);
169                self.schedule.run_stage(Stage::Render, &mut self.context);
170                self.schedule.run_stage(Stage::PostRender, &mut self.context);
171                set_default_camera();
172                self.schedule.run_stage(Stage::GuiRender, &mut self.context);
173
174                next_frame().await;
175
176                let elapsed = get_time() - start_time;
177                let duration_done = elapsed >= SPLASH_MIN_DURATION;
178
179                if !assets_loaded {
180                    if let Some((loaded_server, result)) = load_future.as_mut().now_or_never() {
181                        // Log error instead of crashing if assets.json is missing
182                        if let Err(e) = result {
183                             error!("Failed to load assets from JSON file: {}", e);
184                        }
185                        maybe_asset_server = Some(loaded_server);
186                        assets_loaded = true;
187                    }
188                }
189
190                if assets_loaded && duration_done {
191                    break;
192                }
193            }
194
195            despawn_splash_screen(&mut self.context);
196        } else {
197            // --- No splash: Load assets directly ---
198            let mut asset_server = AssetServer::new();
199
200            if let Some(path) = &resolved_assets_file {
201                if let Err(e) = asset_server.load_assets_from_file(path).await {
202                     error!("Failed to load assets from JSON file: {}", e);
203                }
204            }
205
206            maybe_asset_server = Some(asset_server);
207        }
208
209        // Réinjection du AssetServer chargé
210        if let Some(loaded_server) = maybe_asset_server {
211            self.context.asset_server.merge(loaded_server);
212            self.context.asset_server.finalize_textures().await;
213        }
214
215        self.context.asset_server.prepare_loaded_tiledmap().await;
216
217        if let Some(scene_path) = resolved_scene_path {
218            self.scene_loader.load_scene_from_file(&scene_path, &mut self.context).await.unwrap();
219        }
220
221        if let Some(binding_path) = resolved_binding_path {
222            info!("App: Loading bindings from: {}", binding_path);
223            
224            // On utilise load_string pour le support WASM/HTTP
225            match load_string(&binding_path).await {
226                Ok(content) => {
227                    if let Some(input_manager) = self.context.get_resource_mut::<InputManager>() {
228                        // On passe le contenu, plus le chemin !
229                        input_manager.load_from_string(&content);
230                    }
231                },
232                Err(e) => {
233                    error!("App: Failed to load binding file '{}': {}", binding_path, e);
234                }
235            }
236        }
237
238        // --- Démarrage du jeu ---
239        self.schedule.run_stage(Stage::StartUp, &mut self.context);
240
241        loop {
242            let dt = self.context.resource_mut::<DeltaTime>();
243            dt.0 = get_frame_time();
244            clear_background(LIGHTGRAY);
245
246            self.schedule.run_stage(Stage::Update, &mut self.context);
247            self.schedule.run_stage(Stage::PostUpdate, &mut self.context);
248            self.schedule.run_stage(Stage::Render, &mut self.context);
249            self.schedule.run_stage(Stage::PostRender, &mut self.context);
250
251            set_default_camera();
252            self.schedule.run_stage(Stage::GuiRender, &mut self.context);
253
254            if let Some(event_bus) = self.context.get_resource_mut::<EventBus>() {
255                event_bus.clear();
256            }
257
258            if let Some(prev_mouse_pos) = self.context.get_resource_mut::<PreviousMousePosition>() {
259                prev_mouse_pos.0 = mouse_position().into();
260            }
261
262            next_frame().await;
263        }
264    }
265}