Browse Source

fix loading screen

djeada 1 day ago
parent
commit
57abd0b614

+ 1 - 0
CMakeLists.txt

@@ -276,6 +276,7 @@ if(QT_VERSION_MAJOR EQUAL 6)
             assets/visuals/unit_visuals.json
             assets/visuals/emblems/rome.png
             assets/visuals/emblems/cartaghe.png
+            assets/visuals/load_screen.png
             assets/visuals/icons/archer_rome.png
             assets/visuals/icons/archer_cartaghe.png
             assets/visuals/icons/swordsman_rome.png

+ 126 - 78
app/core/game_engine.cpp

@@ -27,6 +27,8 @@
 #include <QCoreApplication>
 #include <QCursor>
 #include <QDebug>
+#include <QElapsedTimer>
+#include <QEventLoop>
 #include <QImage>
 #include <QOpenGLContext>
 #include <QPainter>
@@ -171,14 +173,11 @@ GameEngine::GameEngine(QObject *parent)
   m_victoryService = std::make_unique<Game::Systems::VictoryService>();
   m_saveLoadService = std::make_unique<Game::Systems::SaveLoadService>();
   m_cameraService = std::make_unique<Game::Systems::CameraService>();
-  
-  // Initialize loading progress tracker
+
   m_loading_progress_tracker = std::make_unique<LoadingProgressTracker>(this);
   connect(m_loading_progress_tracker.get(),
           &LoadingProgressTracker::progress_changed, this,
-          [this](float progress) {
-            emit loading_progress_changed(progress);
-          });
+          [this](float progress) { emit loading_progress_changed(progress); });
   connect(m_loading_progress_tracker.get(),
           &LoadingProgressTracker::stage_changed, this,
           [this](LoadingProgressTracker::LoadingStage, QString detail) {
@@ -738,6 +737,25 @@ void GameEngine::render(int pixelWidth, int pixelHeight) {
   }
   m_renderer->end_frame();
 
+  if (m_loading_overlay_wait_for_first_frame) {
+    if (m_loading_overlay_frames_remaining > 0) {
+      m_loading_overlay_frames_remaining--;
+    }
+    const bool enough_time = m_loading_overlay_timer.isValid() &&
+                             (m_loading_overlay_timer.elapsed() >=
+                              m_loading_overlay_min_duration_ms);
+    if (enough_time && m_loading_overlay_frames_remaining <= 0) {
+      m_loading_overlay_wait_for_first_frame = false;
+      m_loading_overlay_active = false;
+      if (m_finalize_progress_after_overlay && m_loading_progress_tracker) {
+        m_loading_progress_tracker->set_stage(
+            LoadingProgressTracker::LoadingStage::COMPLETED);
+      }
+      m_finalize_progress_after_overlay = false;
+      emit is_loading_changed();
+    }
+  }
+
   qreal const current_x = global_cursor_x();
   qreal const current_y = global_cursor_y();
   if (current_x != m_runtime.last_cursor_x ||
@@ -1166,99 +1184,116 @@ void GameEngine::start_skirmish(const QString &map_path,
 
   if (!m_runtime.initialized) {
     ensure_initialized();
+  }
+
+  if (!m_world || !m_renderer || !m_camera) {
+    set_error("Cannot start skirmish: renderer not initialized");
     return;
   }
 
-  if (m_world && m_renderer && m_camera) {
+  m_finalize_progress_after_overlay = false;
+  m_loading_overlay_active = true;
+  m_runtime.loading = true;
+  emit is_loading_changed();
 
-    m_runtime.loading = true;
-    emit is_loading_changed();
-    
-    // Start progress tracking
-    if (m_loading_progress_tracker) {
-      m_loading_progress_tracker->start_loading();
-    }
-    
-    // Process events to allow UI to update before heavy loading
-    QCoreApplication::processEvents();
+  if (m_loading_progress_tracker) {
+    m_loading_progress_tracker->start_loading();
+  }
 
-    if (m_hoverTracker) {
-      m_hoverTracker->update_hover(-1, -1, *m_world, *m_camera, 0, 0);
-    }
+  QCoreApplication::processEvents(QEventLoop::AllEvents);
+  QTimer::singleShot(50, this, [this, map_path, playerConfigs]() {
+    perform_skirmish_load(map_path, playerConfigs);
+  });
+}
 
-    LevelOrchestrator orchestrator;
-    LevelOrchestrator::RendererRefs renderers{
-        m_renderer.get(), m_camera.get(), m_ground.get(),  m_terrain.get(),
-        m_biome.get(),    m_river.get(),  m_road.get(),    m_riverbank.get(),
-        m_bridge.get(),   m_fog.get(),    m_stone.get(),   m_plant.get(),
-        m_pine.get(),     m_olive.get(),  m_firecamp.get()};
-
-    auto visibility_ready = [this]() {
-      m_runtime.visibility_version =
-          Game::Map::VisibilityService::instance().version();
-      m_runtime.visibility_update_accumulator = 0.0F;
-    };
-
-    auto owner_update = [this]() { emit owner_info_changed(); };
-
-    auto load_result = orchestrator.load_skirmish(
-        map_path, playerConfigs, m_selected_player_id, *m_world, renderers,
-        m_level, m_entity_cache, m_victoryService.get(),
-        m_minimap_manager.get(), visibility_ready, owner_update,
-        m_loading_progress_tracker.get());
-
-    if (load_result.updated_player_id != m_selected_player_id) {
-      m_selected_player_id = load_result.updated_player_id;
-      emit selected_player_id_changed();
-    }
+void GameEngine::perform_skirmish_load(const QString &map_path,
+                                       const QVariantList &playerConfigs) {
 
-    if (!load_result.success) {
-      set_error(load_result.error_message);
-      m_runtime.loading = false;
-      return;
-    }
+  if (!(m_world && m_renderer && m_camera)) {
+    set_error("Cannot start skirmish: renderer not initialized");
+    m_runtime.loading = false;
+    emit is_loading_changed();
+    return;
+  }
+
+  if (m_hoverTracker) {
+    m_hoverTracker->update_hover(-1, -1, *m_world, *m_camera, 0, 0);
+  }
+
+  LevelOrchestrator orchestrator;
+  LevelOrchestrator::RendererRefs renderers{
+      m_renderer.get(), m_camera.get(), m_ground.get(),  m_terrain.get(),
+      m_biome.get(),    m_river.get(),  m_road.get(),    m_riverbank.get(),
+      m_bridge.get(),   m_fog.get(),    m_stone.get(),   m_plant.get(),
+      m_pine.get(),     m_olive.get(),  m_firecamp.get()};
 
-    m_runtime.local_owner_id = load_result.updated_player_id;
+  auto visibility_ready = [this]() {
+    m_runtime.visibility_version =
+        Game::Map::VisibilityService::instance().version();
+    m_runtime.visibility_update_accumulator = 0.0F;
+  };
 
-    if (m_victoryService) {
-      m_victoryService->setVictoryCallback([this](const QString &state) {
-        if (m_runtime.victory_state != state) {
-          m_runtime.victory_state = state;
-          emit victory_state_changed();
+  auto owner_update = [this]() { emit owner_info_changed(); };
 
-          if (state == "victory" && !m_current_campaign_id.isEmpty()) {
-            mark_current_mission_completed();
-          }
-        }
-      });
-    }
+  auto load_result = orchestrator.load_skirmish(
+      map_path, playerConfigs, m_selected_player_id, *m_world, renderers,
+      m_level, m_entity_cache, m_victoryService.get(), m_minimap_manager.get(),
+      visibility_ready, owner_update, m_loading_progress_tracker.get());
 
-    // Mark loading complete
-    if (m_loading_progress_tracker) {
-      m_loading_progress_tracker->set_stage(
-          LoadingProgressTracker::LoadingStage::COMPLETED);
-    }
+  if (load_result.updated_player_id != m_selected_player_id) {
+    m_selected_player_id = load_result.updated_player_id;
+    emit selected_player_id_changed();
+  }
 
+  if (!load_result.success) {
+    set_error(load_result.error_message);
     m_runtime.loading = false;
+    m_loading_overlay_active = false;
+    m_loading_overlay_wait_for_first_frame = false;
+    m_finalize_progress_after_overlay = false;
     emit is_loading_changed();
+    return;
+  }
+
+  m_runtime.local_owner_id = load_result.updated_player_id;
+
+  if (m_victoryService) {
+    m_victoryService->setVictoryCallback([this](const QString &state) {
+      if (m_runtime.victory_state != state) {
+        m_runtime.victory_state = state;
+        emit victory_state_changed();
 
-    GameStateRestorer::rebuild_entity_cache(m_world.get(), m_entity_cache,
-                                            m_runtime.local_owner_id);
+        if (state == "victory" && !m_current_campaign_id.isEmpty()) {
+          mark_current_mission_completed();
+        }
+      }
+    });
+  }
 
-    m_ambient_state_manager = std::make_unique<AmbientStateManager>();
+  m_runtime.loading = false;
+  m_loading_overlay_wait_for_first_frame = true;
+  m_loading_overlay_frames_remaining = 3;
+  m_loading_overlay_min_duration_ms = 1200;
+  m_loading_overlay_timer.restart();
+  m_finalize_progress_after_overlay = true;
+  emit is_loading_changed();
 
-    Engine::Core::EventManager::instance().publish(
-        Engine::Core::AmbientStateChangedEvent(
-            Engine::Core::AmbientState::PEACEFUL,
-            Engine::Core::AmbientState::PEACEFUL));
+  GameStateRestorer::rebuild_entity_cache(m_world.get(), m_entity_cache,
+                                          m_runtime.local_owner_id);
 
-    if (m_input_handler) {
-      m_input_handler->set_spectator_mode(m_level.is_spectator_mode);
-    }
+  m_ambient_state_manager = std::make_unique<AmbientStateManager>();
+
+  Engine::Core::EventManager::instance().publish(
+      Engine::Core::AmbientStateChangedEvent(
+          Engine::Core::AmbientState::PEACEFUL,
+          Engine::Core::AmbientState::PEACEFUL));
 
-    emit owner_info_changed();
-    emit spectator_mode_changed();
+  if (m_input_handler) {
+    m_input_handler->set_spectator_mode(m_level.is_spectator_mode);
   }
+
+  emit owner_info_changed();
+  emit spectator_mode_changed();
 }
 
 void GameEngine::open_settings() {
@@ -1287,11 +1322,18 @@ auto GameEngine::load_from_slot(const QString &slot) -> bool {
     return false;
   }
 
+  m_finalize_progress_after_overlay = false;
+  m_loading_overlay_active = true;
   m_runtime.loading = true;
+  emit is_loading_changed();
 
   if (!m_saveLoadService->load_game_from_slot(*m_world, slot)) {
     set_error(m_saveLoadService->get_last_error());
     m_runtime.loading = false;
+    m_loading_overlay_active = false;
+    m_loading_overlay_wait_for_first_frame = false;
+    m_finalize_progress_after_overlay = false;
+    emit is_loading_changed();
     return false;
   }
 
@@ -1336,6 +1378,12 @@ auto GameEngine::load_from_slot(const QString &slot) -> bool {
   }
 
   m_runtime.loading = false;
+  m_loading_overlay_wait_for_first_frame = true;
+  m_loading_overlay_frames_remaining = 3;
+  m_loading_overlay_min_duration_ms = 1200;
+  m_loading_overlay_timer.restart();
+  m_finalize_progress_after_overlay = true;
+  emit is_loading_changed();
   qInfo() << "Game load complete, victory/defeat checks re-enabled";
 
   emit selected_units_changed();

+ 10 - 1
app/core/game_engine.h

@@ -15,6 +15,7 @@
 #include "input_command_handler.h"
 #include "minimap_manager.h"
 #include "renderer_bootstrap.h"
+#include <QElapsedTimer>
 #include <QJsonObject>
 #include <QList>
 #include <QMatrix4x4>
@@ -252,7 +253,7 @@ public:
   }
 
   [[nodiscard]] bool is_loading() const {
-    return m_runtime.loading;
+    return m_runtime.loading || m_loading_overlay_active;
   }
 
   [[nodiscard]] float loading_progress() const;
@@ -310,6 +311,8 @@ private:
   [[nodiscard]] Game::Systems::RuntimeSnapshot to_runtime_snapshot() const;
   void apply_runtime_snapshot(const Game::Systems::RuntimeSnapshot &snapshot);
   [[nodiscard]] QByteArray capture_screenshot() const;
+  void perform_skirmish_load(const QString &map_path,
+                             const QVariantList &playerConfigs);
 
   std::unique_ptr<Engine::Core::World> m_world;
   std::unique_ptr<Render::GL::Renderer> m_renderer;
@@ -357,6 +360,12 @@ private:
   QVariantList m_available_campaigns;
   bool m_maps_loading = false;
   QString m_current_campaign_id;
+  bool m_loading_overlay_active = false;
+  bool m_loading_overlay_wait_for_first_frame = false;
+  int m_loading_overlay_frames_remaining = 0;
+  qint64 m_loading_overlay_min_duration_ms = 0;
+  QElapsedTimer m_loading_overlay_timer;
+  bool m_finalize_progress_after_overlay = false;
   Engine::Core::ScopedEventSubscription<Engine::Core::UnitDiedEvent>
       m_unit_died_subscription;
   Engine::Core::ScopedEventSubscription<Engine::Core::UnitSpawnedEvent>

+ 34 - 23
app/core/level_orchestrator.cpp

@@ -31,7 +31,8 @@ auto LevelOrchestrator::load_skirmish(
   result.updated_player_id = selected_player_id;
 
   if (progress_tracker) {
-    progress_tracker->set_stage(LoadingProgressTracker::LoadingStage::LOADING_MAP_DATA);
+    progress_tracker->set_stage(
+        LoadingProgressTracker::LoadingStage::LOADING_MAP_DATA);
     QCoreApplication::processEvents();
   }
 
@@ -39,61 +40,68 @@ auto LevelOrchestrator::load_skirmish(
 
   Game::Map::SkirmishLoader loader(world, *renderers.renderer,
                                    *renderers.camera);
-  
+
   if (progress_tracker) {
-    progress_tracker->set_stage(LoadingProgressTracker::LoadingStage::LOADING_TERRAIN);
+    progress_tracker->set_stage(
+        LoadingProgressTracker::LoadingStage::LOADING_TERRAIN);
     QCoreApplication::processEvents();
   }
-  
+
   loader.set_ground_renderer(renderers.ground);
   loader.set_terrain_renderer(renderers.terrain);
-  
+
   if (progress_tracker) {
-    progress_tracker->set_stage(LoadingProgressTracker::LoadingStage::LOADING_BIOME);
+    progress_tracker->set_stage(
+        LoadingProgressTracker::LoadingStage::LOADING_BIOME);
     QCoreApplication::processEvents();
   }
-  
+
   loader.set_biome_renderer(renderers.biome);
-  
+
   if (progress_tracker) {
-    progress_tracker->set_stage(LoadingProgressTracker::LoadingStage::LOADING_WATER_FEATURES);
+    progress_tracker->set_stage(
+        LoadingProgressTracker::LoadingStage::LOADING_WATER_FEATURES);
     QCoreApplication::processEvents();
   }
-  
+
   loader.set_river_renderer(renderers.river);
   loader.set_riverbank_renderer(renderers.riverbank);
   loader.set_bridge_renderer(renderers.bridge);
-  
+
   if (progress_tracker) {
-    progress_tracker->set_stage(LoadingProgressTracker::LoadingStage::LOADING_ROADS);
+    progress_tracker->set_stage(
+        LoadingProgressTracker::LoadingStage::LOADING_ROADS);
     QCoreApplication::processEvents();
   }
-  
+
   loader.set_road_renderer(renderers.road);
-  
+
   if (progress_tracker) {
-    progress_tracker->set_stage(LoadingProgressTracker::LoadingStage::LOADING_ENVIRONMENT);
+    progress_tracker->set_stage(
+        LoadingProgressTracker::LoadingStage::LOADING_ENVIRONMENT);
     QCoreApplication::processEvents();
   }
-  
+
   loader.set_stone_renderer(renderers.stone);
   loader.set_plant_renderer(renderers.plant);
   loader.set_pine_renderer(renderers.pine);
   loader.set_olive_renderer(renderers.olive);
   loader.set_fire_camp_renderer(renderers.firecamp);
-  
+
   if (progress_tracker) {
-    progress_tracker->set_stage(LoadingProgressTracker::LoadingStage::LOADING_FOG);
+    progress_tracker->set_stage(
+        LoadingProgressTracker::LoadingStage::LOADING_FOG);
     QCoreApplication::processEvents();
   }
-  
+
   loader.set_fog_renderer(renderers.fog);
 
   loader.set_on_owners_updated(owner_update);
   loader.set_on_visibility_mask_ready(visibility_ready);
 
   if (progress_tracker) {
-    progress_tracker->set_stage(LoadingProgressTracker::LoadingStage::LOADING_ENTITIES);
+    progress_tracker->set_stage(
+        LoadingProgressTracker::LoadingStage::LOADING_ENTITIES);
     QCoreApplication::processEvents();
   }
 
@@ -110,7 +118,8 @@ auto LevelOrchestrator::load_skirmish(
   }
 
   if (progress_tracker) {
-    progress_tracker->set_stage(LoadingProgressTracker::LoadingStage::LOADING_AUDIO);
+    progress_tracker->set_stage(
+        LoadingProgressTracker::LoadingStage::LOADING_AUDIO);
     QCoreApplication::processEvents();
   }
 
@@ -138,7 +147,8 @@ auto LevelOrchestrator::load_skirmish(
   }
 
   if (progress_tracker) {
-    progress_tracker->set_stage(LoadingProgressTracker::LoadingStage::GENERATING_MINIMAP);
+    progress_tracker->set_stage(
+        LoadingProgressTracker::LoadingStage::GENERATING_MINIMAP);
     QCoreApplication::processEvents();
   }
 
@@ -173,7 +183,8 @@ auto LevelOrchestrator::load_skirmish(
   }
 
   if (progress_tracker) {
-    progress_tracker->set_stage(LoadingProgressTracker::LoadingStage::FINALIZING);
+    progress_tracker->set_stage(
+        LoadingProgressTracker::LoadingStage::FINALIZING);
     QCoreApplication::processEvents();
   }
 

+ 3 - 4
app/core/loading_progress_tracker.cpp

@@ -29,7 +29,7 @@ void LoadingProgressTracker::set_stage(LoadingStage stage,
 }
 
 void LoadingProgressTracker::complete_stage(LoadingStage stage) {
-  // Automatically advance to next stage
+
   auto next_stage = static_cast<int>(stage) + 1;
   if (next_stage < static_cast<int>(LoadingStage::COMPLETED)) {
     set_stage(static_cast<LoadingStage>(next_stage));
@@ -94,9 +94,8 @@ QString LoadingProgressTracker::stage_name(LoadingStage stage) const {
 }
 
 float LoadingProgressTracker::stage_to_progress(LoadingStage stage) const {
-  // Map each stage to a progress value (0.0 to 1.0)
-  // Distribute progress across all stages
-  const float total_stages = 13.0F; // Excluding NOT_STARTED, COMPLETED, FAILED
+
+  const float total_stages = 13.0F;
 
   switch (stage) {
   case LoadingStage::NOT_STARTED:

+ 5 - 26
app/core/loading_progress_tracker.h

@@ -5,13 +5,6 @@
 #include <QStringList>
 #include <functional>
 
-/**
- * @brief Tracks loading progress through defined stages with signal emission
- * 
- * This class provides a clean interface for tracking what is being loaded
- * during game initialization. It uses a signal-based (push) approach where
- * loading stages emit progress updates.
- */
 class LoadingProgressTracker : public QObject {
   Q_OBJECT
 
@@ -22,14 +15,14 @@ public:
     LOADING_MAP_DATA,
     LOADING_TERRAIN,
     LOADING_BIOME,
-    LOADING_WATER_FEATURES,  // Rivers, riverbanks, bridges
+    LOADING_WATER_FEATURES,
     LOADING_ROADS,
-    LOADING_ENVIRONMENT,     // Stones, plants, pines, olives, fire camps
+    LOADING_ENVIRONMENT,
     LOADING_FOG,
-    LOADING_ENTITIES,        // Units, buildings
+    LOADING_ENTITIES,
     LOADING_AUDIO,
     GENERATING_MINIMAP,
-    INITIALIZING_SYSTEMS,    // AI, combat, etc.
+    INITIALIZING_SYSTEMS,
     FINALIZING,
     COMPLETED,
     FAILED
@@ -38,47 +31,34 @@ public:
 
   explicit LoadingProgressTracker(QObject *parent = nullptr);
 
-  // Start tracking a new loading session
   void start_loading();
 
-  // Update to a specific stage
   void set_stage(LoadingStage stage, const QString &detail = QString());
 
-  // Mark stage as complete and move to next
   void complete_stage(LoadingStage stage);
 
-  // Report error and fail
   void report_error(const QString &error_message);
 
-  // Check if loading is complete
   [[nodiscard]] bool is_complete() const;
 
-  // Check if loading failed
   [[nodiscard]] bool has_failed() const;
 
-  // Get current stage
   [[nodiscard]] LoadingStage current_stage() const { return m_current_stage; }
 
-  // Get current progress (0.0 to 1.0)
   [[nodiscard]] float progress() const;
 
-  // Get human-readable stage name
   [[nodiscard]] QString stage_name(LoadingStage stage) const;
 
-  // Get current stage detail text
   [[nodiscard]] QString current_detail() const { return m_current_detail; }
 
 signals:
-  // Emitted when stage changes
+
   void stage_changed(LoadingStage stage, QString detail);
 
-  // Emitted when progress updates (0.0 to 1.0)
   void progress_changed(float progress);
 
-  // Emitted when loading completes successfully
   void loading_completed();
 
-  // Emitted when loading fails
   void loading_failed(QString error_message);
 
 private:
@@ -86,6 +66,5 @@ private:
   QString m_current_detail;
   bool m_failed;
 
-  // Map stages to progress values
   float stage_to_progress(LoadingStage stage) const;
 };

+ 13 - 0
game/map/skirmish_loader.cpp

@@ -31,7 +31,9 @@
 #include "render/scene_renderer.h"
 #include "units/spawn_type.h"
 #include "units/troop_type.h"
+#include <QCoreApplication>
 #include <QDebug>
+#include <QEventLoop>
 #include <QFile>
 #include <QJsonArray>
 #include <QJsonDocument>
@@ -109,7 +111,12 @@ auto SkirmishLoader::start(const QString &map_path,
                            int &out_selected_player_id) -> SkirmishLoadResult {
   SkirmishLoadResult result;
 
+  auto pump_events = []() {
+    QCoreApplication::processEvents(QEventLoop::AllEvents);
+  };
+
   reset_game_state();
+  pump_events();
 
   QSet<int> map_player_ids;
   QFile map_file(map_path);
@@ -256,6 +263,7 @@ auto SkirmishLoader::start(const QString &map_path,
 
   auto level_result = Game::Map::LevelLoader::loadFromAssets(
       map_path, m_world, m_renderer, m_camera);
+  pump_events();
 
   if (!level_result.ok && !level_result.errorMessage.isEmpty()) {
     result.errorMessage = level_result.errorMessage;
@@ -286,6 +294,7 @@ auto SkirmishLoader::start(const QString &map_path,
     }
 
     auto entities = m_world.get_entities_with<Engine::Core::UnitComponent>();
+    pump_events();
     std::unordered_map<int, int> owner_entity_count;
     for (auto *entity : entities) {
       auto *unit = entity->get_component<Engine::Core::UnitComponent>();
@@ -301,6 +310,7 @@ auto SkirmishLoader::start(const QString &map_path,
       }
     }
   }
+  pump_events();
 
   if (m_onOwnersUpdated) {
     m_onOwnersUpdated();
@@ -436,6 +446,7 @@ auto SkirmishLoader::start(const QString &map_path,
       }
     }
   }
+  pump_events();
 
   constexpr int default_map_size = 100;
   const int map_width =
@@ -446,6 +457,7 @@ auto SkirmishLoader::start(const QString &map_path,
 
   auto &visibility_service = Game::Map::VisibilityService::instance();
   visibility_service.initialize(map_width, map_height, level_result.tile_size);
+  pump_events();
 
   if (is_spectator_mode) {
     visibility_service.reveal_all();
@@ -468,6 +480,7 @@ auto SkirmishLoader::start(const QString &map_path,
       m_onVisibilityMaskReady();
     }
   }
+  pump_events();
 
   if (m_biome != nullptr) {
     m_biome->refreshGrass();

+ 44 - 46
game/systems/combat_system/attack_processor.cpp

@@ -1,8 +1,4 @@
 #include "attack_processor.h"
-#include "combat_mode_processor.h"
-#include "combat_types.h"
-#include "combat_utils.h"
-#include "damage_processor.h"
 #include "../../core/component.h"
 #include "../../core/world.h"
 #include "../../units/spawn_type.h"
@@ -11,6 +7,10 @@
 #include "../arrow_system.h"
 #include "../command_service.h"
 #include "../owner_registry.h"
+#include "combat_mode_processor.h"
+#include "combat_types.h"
+#include "combat_utils.h"
+#include "damage_processor.h"
 
 #include <algorithm>
 #include <cmath>
@@ -45,8 +45,10 @@ void face_target(Engine::Core::TransformComponent *attacker_transform,
   if ((attacker_transform == nullptr) || (target_transform == nullptr)) {
     return;
   }
-  float const dx = target_transform->position.x - attacker_transform->position.x;
-  float const dz = target_transform->position.z - attacker_transform->position.z;
+  float const dx =
+      target_transform->position.x - attacker_transform->position.x;
+  float const dz =
+      target_transform->position.z - attacker_transform->position.z;
   float const yaw = std::atan2(dx, dz) * 180.0F / std::numbers::pi_v<float>;
   attacker_transform->desired_yaw = yaw;
   attacker_transform->has_desired_yaw = true;
@@ -93,9 +95,9 @@ void process_melee_lock(Engine::Core::Entity *attacker,
 
   if (dist > Constants::kMaxMeleeSeparation) {
     if (!is_unit_in_hold_mode(attacker)) {
-      float const pull_amount =
-          (dist - Constants::kIdealMeleeDistance) * Constants::kMeleePullFactor *
-          delta_time * Constants::kMeleePullSpeed;
+      float const pull_amount = (dist - Constants::kIdealMeleeDistance) *
+                                Constants::kMeleePullFactor * delta_time *
+                                Constants::kMeleePullSpeed;
 
       if (dist > Constants::kMinDistance) {
         QVector3D const direction(dx / dist, 0.0F, dz / dist);
@@ -135,17 +137,14 @@ void apply_hold_mode_bonuses(Engine::Core::Entity *attacker,
 
   if (unit_comp->spawn_type == Game::Units::SpawnType::Archer) {
     range *= Constants::kRangeMultiplierHold;
-    damage =
-        static_cast<int>(static_cast<float>(damage) *
-                         Constants::kDamageMultiplierArcherHold);
+    damage = static_cast<int>(static_cast<float>(damage) *
+                              Constants::kDamageMultiplierArcherHold);
   } else if (unit_comp->spawn_type == Game::Units::SpawnType::Spearman) {
-    damage =
-        static_cast<int>(static_cast<float>(damage) *
-                         Constants::kDamageMultiplierSpearmanHold);
+    damage = static_cast<int>(static_cast<float>(damage) *
+                              Constants::kDamageMultiplierSpearmanHold);
   } else {
-    damage =
-        static_cast<int>(static_cast<float>(damage) *
-                         Constants::kDamageMultiplierDefaultHold);
+    damage = static_cast<int>(static_cast<float>(damage) *
+                              Constants::kDamageMultiplierDefaultHold);
   }
 }
 
@@ -169,9 +168,8 @@ void spawn_arrows(Engine::Core::Entity *attacker, Engine::Core::Entity *target,
                         tgt_t->position.z);
   QVector3D const dir = (t_pos - a_pos).normalized();
   QVector3D const color =
-      (att_u != nullptr)
-          ? Game::Visuals::team_colorForOwner(att_u->owner_id)
-          : QVector3D(0.8F, 0.9F, 1.0F);
+      (att_u != nullptr) ? Game::Visuals::team_colorForOwner(att_u->owner_id)
+                         : QVector3D(0.8F, 0.9F, 1.0F);
 
   int arrow_count = 1;
   if (att_u != nullptr) {
@@ -187,8 +185,8 @@ void spawn_arrows(Engine::Core::Entity *attacker, Engine::Core::Entity *target,
 
   for (int i = 0; i < arrow_count; ++i) {
     static thread_local std::mt19937 spread_gen(std::random_device{}());
-    std::uniform_real_distribution<float> spread_dist(Constants::kArrowSpreadMin,
-                                                      Constants::kArrowSpreadMax);
+    std::uniform_real_distribution<float> spread_dist(
+        Constants::kArrowSpreadMin, Constants::kArrowSpreadMax);
 
     QVector3D const perpendicular(-dir.z(), 0.0F, dir.x());
     QVector3D const up_vector(0.0F, 1.0F, 0.0F);
@@ -205,14 +203,13 @@ void spawn_arrows(Engine::Core::Entity *attacker, Engine::Core::Entity *target,
                                  up_vector * vertical_offset +
                                  dir * depth_offset;
 
-    QVector3D const start = a_pos +
-                            QVector3D(0.0F, Constants::kArrowStartHeight, 0.0F) +
-                            dir * Constants::kArrowStartOffset + start_offset;
-    QVector3D const end =
-        t_pos +
-        QVector3D(Constants::kArrowTargetOffset, Constants::kArrowTargetOffset,
-                  0.0F) +
-        end_offset;
+    QVector3D const start =
+        a_pos + QVector3D(0.0F, Constants::kArrowStartHeight, 0.0F) +
+        dir * Constants::kArrowStartOffset + start_offset;
+    QVector3D const end = t_pos +
+                          QVector3D(Constants::kArrowTargetOffset,
+                                    Constants::kArrowTargetOffset, 0.0F) +
+                          end_offset;
 
     arrow_sys->spawnArrow(start, end, color, Constants::kArrowSpeed);
   }
@@ -231,8 +228,8 @@ void initiate_melee_combat(Engine::Core::Entity *attacker,
     combat_state =
         attacker->add_component<Engine::Core::CombatStateComponent>();
   }
-  if (combat_state != nullptr &&
-      combat_state->animation_state == Engine::Core::CombatAnimationState::Idle) {
+  if (combat_state != nullptr && combat_state->animation_state ==
+                                     Engine::Core::CombatAnimationState::Idle) {
     combat_state->animation_state = Engine::Core::CombatAnimationState::Advance;
     combat_state->state_time = 0.0F;
     combat_state->state_duration =
@@ -241,8 +238,7 @@ void initiate_melee_combat(Engine::Core::Entity *attacker,
     combat_state->attack_offset = offset_dist(gen);
     std::uniform_int_distribution<int> variant_dist(
         0, Engine::Core::CombatStateComponent::kMaxAttackVariants - 1);
-    combat_state->attack_variant =
-        static_cast<std::uint8_t>(variant_dist(gen));
+    combat_state->attack_variant = static_cast<std::uint8_t>(variant_dist(gen));
   }
 
   auto *target_atk = target->get_component<Engine::Core::AttackComponent>();
@@ -259,8 +255,8 @@ void initiate_melee_combat(Engine::Core::Entity *attacker,
     float const dist = std::sqrt(dx * dx + dz * dz);
 
     if (dist > Constants::kIdealMeleeDistance + 0.1F) {
-      float const move_amount =
-          (dist - Constants::kIdealMeleeDistance) * Constants::kMoveAmountFactor;
+      float const move_amount = (dist - Constants::kIdealMeleeDistance) *
+                                Constants::kMoveAmountFactor;
 
       if (dist > Constants::kMinDistance) {
         QVector3D const direction(dx / dist, 0.0F, dz / dist);
@@ -346,8 +342,8 @@ void process_attacks(Engine::Core::World *world, float delta_time) {
             target->get_component<Engine::Core::UnitComponent>();
 
         auto &owner_registry = Game::Systems::OwnerRegistry::instance();
-        bool const is_ally = owner_registry.are_allies(
-            attacker_unit->owner_id, target_unit->owner_id);
+        bool const is_ally = owner_registry.are_allies(attacker_unit->owner_id,
+                                                       target_unit->owner_id);
 
         if ((target_unit != nullptr) && target_unit->health > 0 &&
             target_unit->owner_id != attacker_unit->owner_id && !is_ally) {
@@ -358,8 +354,9 @@ void process_attacks(Engine::Core::World *world, float delta_time) {
             if (ranged_unit) {
               stop_unit_movement(attacker, attacker_transform);
             }
-            face_target(attacker_transform,
-                        target->get_component<Engine::Core::TransformComponent>());
+            face_target(
+                attacker_transform,
+                target->get_component<Engine::Core::TransformComponent>());
           } else if (attack_target->should_chase) {
             auto *hold_mode =
                 attacker->get_component<Engine::Core::HoldModeComponent>();
@@ -389,8 +386,7 @@ void process_attacks(Engine::Core::World *world, float delta_time) {
                 if (target_is_building) {
                   float const scale_x = target_transform->scale.x;
                   float const scale_z = target_transform->scale.z;
-                  float const target_radius =
-                      std::max(scale_x, scale_z) * 0.5F;
+                  float const target_radius = std::max(scale_x, scale_z) * 0.5F;
                   QVector3D direction = target_pos - attacker_pos;
                   float const distance_sq = direction.lengthSquared();
                   if (distance_sq > 0.000001F) {
@@ -425,7 +421,8 @@ void process_attacks(Engine::Core::World *world, float delta_time) {
                     attacker->get_component<Engine::Core::MovementComponent>();
                 if (movement == nullptr) {
                   movement =
-                      attacker->add_component<Engine::Core::MovementComponent>();
+                      attacker
+                          ->add_component<Engine::Core::MovementComponent>();
                 }
 
                 if (movement != nullptr) {
@@ -540,8 +537,9 @@ void process_attacks(Engine::Core::World *world, float delta_time) {
         stop_unit_movement(attacker, attacker_transform);
       }
 
-      face_target(attacker_transform,
-                  best_target->get_component<Engine::Core::TransformComponent>());
+      face_target(
+          attacker_transform,
+          best_target->get_component<Engine::Core::TransformComponent>());
 
       auto *att_u = attacker->get_component<Engine::Core::UnitComponent>();
       bool const should_show_arrow_vfx =

+ 2 - 2
game/systems/combat_system/attack_processor.h

@@ -2,10 +2,10 @@
 
 namespace Engine::Core {
 class World;
-} // namespace Engine::Core
+}
 
 namespace Game::Systems::Combat {
 
 void process_attacks(Engine::Core::World *world, float delta_time);
 
-} // namespace Game::Systems::Combat
+}

+ 2 - 2
game/systems/combat_system/auto_engagement.cpp

@@ -1,8 +1,8 @@
 #include "auto_engagement.h"
-#include "combat_types.h"
-#include "combat_utils.h"
 #include "../../core/component.h"
 #include "../../core/world.h"
+#include "combat_types.h"
+#include "combat_utils.h"
 
 namespace Game::Systems::Combat {
 

+ 1 - 1
game/systems/combat_system/auto_engagement.h

@@ -5,7 +5,7 @@
 
 namespace Engine::Core {
 class World;
-} // namespace Engine::Core
+}
 
 namespace Game::Systems::Combat {
 

+ 1 - 1
game/systems/combat_system/combat_mode_processor.h

@@ -13,4 +13,4 @@ void update_combat_mode(Engine::Core::Entity *attacker,
                         Engine::Core::World *world,
                         Engine::Core::AttackComponent *attack_comp);
 
-} // namespace Game::Systems::Combat
+}

+ 2 - 2
game/systems/combat_system/combat_state_processor.h

@@ -2,10 +2,10 @@
 
 namespace Engine::Core {
 class World;
-} // namespace Engine::Core
+}
 
 namespace Game::Systems::Combat {
 
 void process_combat_state(Engine::Core::World *world, float delta_time);
 
-} // namespace Game::Systems::Combat
+}

+ 2 - 1
game/systems/combat_system/damage_processor.cpp

@@ -63,7 +63,8 @@ void deal_damage(Engine::Core::World *world, Engine::Core::Entity *target,
     if ((target_atk != nullptr) && target_atk->in_melee_lock &&
         target_atk->melee_lock_target_id != 0) {
       if (world != nullptr) {
-        auto *lock_partner = world->get_entity(target_atk->melee_lock_target_id);
+        auto *lock_partner =
+            world->get_entity(target_atk->melee_lock_target_id);
         if ((lock_partner != nullptr) &&
             !lock_partner
                  ->has_component<Engine::Core::PendingRemovalComponent>()) {

+ 1 - 1
game/systems/combat_system/damage_processor.h

@@ -4,7 +4,7 @@
 
 namespace Engine::Core {
 class World;
-} // namespace Engine::Core
+}
 
 namespace Game::Systems::Combat {
 

+ 1 - 1
game/systems/combat_system/hit_feedback_processor.cpp

@@ -1,8 +1,8 @@
 #include "hit_feedback_processor.h"
-#include "combat_types.h"
 #include "../../core/component.h"
 #include "../../core/world.h"
 #include "../camera_visibility_service.h"
+#include "combat_types.h"
 
 #include <cmath>
 

+ 2 - 2
game/systems/combat_system/hit_feedback_processor.h

@@ -2,10 +2,10 @@
 
 namespace Engine::Core {
 class World;
-} // namespace Engine::Core
+}
 
 namespace Game::Systems::Combat {
 
 void process_hit_feedback(Engine::Core::World *world, float delta_time);
 
-} // namespace Game::Systems::Combat
+}

+ 47 - 37
ui/qml/LoadScreen.qml

@@ -4,24 +4,44 @@ import QtQuick.Controls 2.15
 Rectangle {
     id: load_screen
 
-    property real progress: 0.0
+    property real progress: 0
     property bool is_loading: false
     property string stage_text: "Loading..."
-    property bool use_real_progress: true  // Toggle between real and fake progress
+    property bool use_real_progress: true
+    property var _bgSources: ["qrc:/qt/qml/StandardOfIron/assets/visuals/load_screen.png", "qrc:/StandardOfIron/assets/visuals/load_screen.png", "qrc:/assets/visuals/load_screen.png", "assets/visuals/load_screen.png"]
+    property int _bgIndex: 0
+
+    function complete_loading() {
+        load_screen.progress = 1;
+    }
 
     anchors.fill: parent
     color: "#000000"
     visible: is_loading
+    onIs_loadingChanged: {
+        if (is_loading) {
+            if (!use_real_progress)
+                progress = 0;
+
+        }
+    }
 
-    // Background image
     Image {
         id: background_image
+
         anchors.fill: parent
-        source: "qrc:/assets/visuals/load_screen.png"
+        source: load_screen._bgSources[load_screen._bgIndex]
+        cache: true
+        asynchronous: false
         fillMode: Image.PreserveAspectCrop
+        onStatusChanged: {
+            if (status === Image.Error && load_screen._bgIndex + 1 < load_screen._bgSources.length) {
+                load_screen._bgIndex += 1;
+                source = load_screen._bgSources[load_screen._bgIndex];
+            }
+        }
     }
 
-    // Dark overlay for better text visibility
     Rectangle {
         anchors.fill: parent
         color: "#40000000"
@@ -41,7 +61,6 @@ Rectangle {
             anchors.horizontalCenter: parent.horizontalCenter
         }
 
-        // Progress bar container
         Rectangle {
             width: parent.width
             height: 40
@@ -50,12 +69,11 @@ Rectangle {
             border.width: 2
             radius: 4
 
-            // Progress fill
             Rectangle {
                 id: progress_fill
-                
+
                 readonly property real available_width: parent.width - 8
-                
+
                 anchors.left: parent.left
                 anchors.top: parent.top
                 anchors.bottom: parent.bottom
@@ -64,19 +82,32 @@ Rectangle {
                 color: "#f39c12"
                 radius: 2
 
-                // Shine effect
                 Rectangle {
                     anchors.fill: parent
+                    radius: parent.radius
+
                     gradient: Gradient {
-                        GradientStop { position: 0.0; color: "#40ffffff" }
-                        GradientStop { position: 0.5; color: "#00ffffff" }
-                        GradientStop { position: 1.0; color: "#40ffffff" }
+                        GradientStop {
+                            position: 0
+                            color: "#40ffffff"
+                        }
+
+                        GradientStop {
+                            position: 0.5
+                            color: "#00ffffff"
+                        }
+
+                        GradientStop {
+                            position: 1
+                            color: "#40ffffff"
+                        }
+
                     }
-                    radius: parent.radius
+
                 }
+
             }
 
-            // Progress text
             Text {
                 anchors.centerIn: parent
                 text: Math.floor(load_screen.progress * 100) + "%"
@@ -84,6 +115,7 @@ Rectangle {
                 font.pixelSize: 18
                 font.bold: true
             }
+
         }
 
         Text {
@@ -92,29 +124,7 @@ Rectangle {
             font.pixelSize: 18
             anchors.horizontalCenter: parent.horizontalCenter
         }
-    }
-
-    // Reset progress when loading starts
-    onIs_loadingChanged: {
-        if (is_loading) {
-            if (!use_real_progress) {
-                progress = 0.0;
-            }
-        }
-    }
 
-    // Function to complete loading immediately
-    function complete_loading() {
-        load_screen.progress = 1.0;
-        complete_timer.start();
     }
 
-    Timer {
-        id: complete_timer
-        interval: 300
-        repeat: false
-        onTriggered: {
-            load_screen.is_loading = false;
-        }
-    }
 }

+ 6 - 4
ui/qml/Main.qml

@@ -115,17 +115,19 @@ ApplicationWindow {
         anchors.fill: parent
         z: 15
         is_loading: (typeof game !== 'undefined') ? game.is_loading : false
-        progress: (typeof game !== 'undefined') ? game.loading_progress : 0.0
+        progress: (typeof game !== 'undefined') ? game.loading_progress : 0
         stage_text: (typeof game !== 'undefined') ? game.loading_stage_text : "Loading..."
 
         Connections {
-            target: game
             function onIs_loading_changed() {
-                if (!game.is_loading) {
+                if (!game.is_loading)
                     load_screen.complete_loading();
-                }
+
             }
+
+            target: game
         }
+
     }
 
     MainMenu {