Merge pull request #37 from ldmonster/chore/application-extract-appearance-controller
Some checks are pending
Build / Build (arm64) (push) Waiting to run
Build / Build (x86-64) (push) Waiting to run
Build / Build (macOS arm64) (push) Waiting to run
Build / Build (windows-arm64) (push) Waiting to run
Build / Build (windows-x86-64) (push) Waiting to run
Security / CodeQL (C/C++) (push) Waiting to run
Security / Semgrep (push) Waiting to run
Security / Sanitizer Build (ASan/UBSan) (push) Waiting to run

[chore] refactor: application extract appearance controller
This commit is contained in:
Kelsi Rae Davis 2026-04-02 00:26:59 -07:00 committed by GitHub
commit 6a938b1181
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 2423 additions and 1714 deletions

View file

@ -487,6 +487,8 @@ set(WOWEE_SOURCES
# Core # Core
src/core/application.cpp src/core/application.cpp
src/core/entity_spawner.cpp src/core/entity_spawner.cpp
src/core/appearance_composer.cpp
src/core/world_loader.cpp
src/core/window.cpp src/core/window.cpp
src/core/input.cpp src/core/input.cpp
src/core/logger.cpp src/core/logger.cpp
@ -542,6 +544,7 @@ set(WOWEE_SOURCES
# Audio # Audio
src/audio/audio_engine.cpp src/audio/audio_engine.cpp
src/audio/audio_coordinator.cpp
src/audio/music_manager.cpp src/audio/music_manager.cpp
src/audio/footstep_manager.cpp src/audio/footstep_manager.cpp
src/audio/activity_sound_manager.cpp src/audio/activity_sound_manager.cpp

View file

@ -347,3 +347,13 @@ This project does not include any Blizzard Entertainment proprietary data, asset
## Known Issues ## Known Issues
MANY issues this is actively under development MANY issues this is actively under development
## Star History
<a href="https://www.star-history.com/?repos=Kelsidavis%2FWoWee&type=date&legend=top-left">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/image?repos=Kelsidavis/WoWee&type=date&theme=dark&legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/image?repos=Kelsidavis/WoWee&type=date&legend=top-left" />
<img alt="Star History Chart" src="https://api.star-history.com/image?repos=Kelsidavis/WoWee&type=date&legend=top-left" />
</picture>
</a>

View file

@ -0,0 +1,66 @@
#pragma once
#include <memory>
namespace wowee {
namespace pipeline { class AssetManager; }
namespace audio {
class MusicManager;
class FootstepManager;
class ActivitySoundManager;
class MountSoundManager;
class NpcVoiceManager;
class AmbientSoundManager;
class UiSoundManager;
class CombatSoundManager;
class SpellSoundManager;
class MovementSoundManager;
/// Coordinates all audio subsystems.
/// Extracted from Renderer to separate audio lifecycle from rendering.
/// Owned by Application; Renderer and UI components access through Application.
class AudioCoordinator {
public:
AudioCoordinator();
~AudioCoordinator();
/// Initialize the audio engine and all managers.
/// @return true if audio is available (engine initialized successfully)
bool initialize();
/// Initialize managers that need AssetManager (music lookups, sound banks).
void initializeWithAssets(pipeline::AssetManager* assetManager);
/// Shutdown all audio managers and engine.
void shutdown();
// Accessors for all audio managers (same interface as Renderer had)
MusicManager* getMusicManager() { return musicManager_.get(); }
FootstepManager* getFootstepManager() { return footstepManager_.get(); }
ActivitySoundManager* getActivitySoundManager() { return activitySoundManager_.get(); }
MountSoundManager* getMountSoundManager() { return mountSoundManager_.get(); }
NpcVoiceManager* getNpcVoiceManager() { return npcVoiceManager_.get(); }
AmbientSoundManager* getAmbientSoundManager() { return ambientSoundManager_.get(); }
UiSoundManager* getUiSoundManager() { return uiSoundManager_.get(); }
CombatSoundManager* getCombatSoundManager() { return combatSoundManager_.get(); }
SpellSoundManager* getSpellSoundManager() { return spellSoundManager_.get(); }
MovementSoundManager* getMovementSoundManager() { return movementSoundManager_.get(); }
private:
std::unique_ptr<MusicManager> musicManager_;
std::unique_ptr<FootstepManager> footstepManager_;
std::unique_ptr<ActivitySoundManager> activitySoundManager_;
std::unique_ptr<MountSoundManager> mountSoundManager_;
std::unique_ptr<NpcVoiceManager> npcVoiceManager_;
std::unique_ptr<AmbientSoundManager> ambientSoundManager_;
std::unique_ptr<UiSoundManager> uiSoundManager_;
std::unique_ptr<CombatSoundManager> combatSoundManager_;
std::unique_ptr<SpellSoundManager> spellSoundManager_;
std::unique_ptr<MovementSoundManager> movementSoundManager_;
bool audioAvailable_ = false;
};
} // namespace audio
} // namespace wowee

View file

@ -0,0 +1,101 @@
#pragma once
#include "game/character.hpp"
#include <string>
#include <vector>
#include <unordered_set>
#include <cstdint>
namespace wowee {
namespace rendering { class Renderer; }
namespace pipeline { class AssetManager; class DBCLayout; struct M2Model; }
namespace game { class GameHandler; }
namespace core {
class EntitySpawner;
// Default (bare) geoset IDs per equipment group.
// Each group's base is groupNumber * 100; variant 01 is typically bare/default.
constexpr uint16_t kGeosetDefaultConnector = 101; // Group 1: default hair connector
constexpr uint16_t kGeosetBareForearms = 401; // Group 4: no gloves
constexpr uint16_t kGeosetBareShins = 503; // Group 5: no boots
constexpr uint16_t kGeosetDefaultEars = 702; // Group 7: ears
constexpr uint16_t kGeosetBareSleeves = 801; // Group 8: no chest armor sleeves
constexpr uint16_t kGeosetDefaultKneepads = 902; // Group 9: kneepads
constexpr uint16_t kGeosetDefaultTabard = 1201; // Group 12: tabard base
constexpr uint16_t kGeosetBarePants = 1301; // Group 13: no leggings
constexpr uint16_t kGeosetNoCape = 1501; // Group 15: no cape
constexpr uint16_t kGeosetWithCape = 1502; // Group 15: with cape
constexpr uint16_t kGeosetBareFeet = 2002; // Group 20: bare feet
/// Resolved texture paths from CharSections.dbc for player character compositing.
struct PlayerTextureInfo {
std::string bodySkinPath;
std::string faceLowerPath;
std::string faceUpperPath;
std::string hairTexturePath;
std::vector<std::string> underwearPaths;
};
/// Handles player character visual appearance: skin compositing, geoset selection,
/// texture path lookups, and equipment weapon rendering.
class AppearanceComposer {
public:
AppearanceComposer(rendering::Renderer* renderer,
pipeline::AssetManager* assetManager,
game::GameHandler* gameHandler,
pipeline::DBCLayout* dbcLayout,
EntitySpawner* entitySpawner);
// Player model path resolution
std::string getPlayerModelPath(game::Race race, game::Gender gender) const;
// Phase 1: Resolve texture paths from CharSections.dbc and fill model texture slots.
// Call BEFORE charRenderer->loadModel().
PlayerTextureInfo resolvePlayerTextures(pipeline::M2Model& model,
game::Race race, game::Gender gender,
uint32_t appearanceBytes);
// Phase 2: Apply composited textures to loaded model instance.
// Call AFTER charRenderer->loadModel(). Saves skin state for re-compositing.
void compositePlayerSkin(uint32_t modelSlotId, const PlayerTextureInfo& texInfo);
// Build default active geosets for player character
std::unordered_set<uint16_t> buildDefaultPlayerGeosets(uint8_t hairStyleId, uint8_t facialId);
// Equipment weapon loading (reads inventory, attaches weapon M2 models)
void loadEquippedWeapons();
// Weapon sheathe state
void setWeaponsSheathed(bool sheathed) { weaponsSheathed_ = sheathed; }
bool isWeaponsSheathed() const { return weaponsSheathed_; }
void toggleWeaponsSheathed() { weaponsSheathed_ = !weaponsSheathed_; }
// Saved skin state accessors (used by game_screen.cpp for equipment re-compositing)
const std::string& getBodySkinPath() const { return bodySkinPath_; }
const std::vector<std::string>& getUnderwearPaths() const { return underwearPaths_; }
uint32_t getSkinTextureSlotIndex() const { return skinTextureSlotIndex_; }
uint32_t getCloakTextureSlotIndex() const { return cloakTextureSlotIndex_; }
private:
bool loadWeaponM2(const std::string& m2Path, pipeline::M2Model& outModel);
rendering::Renderer* renderer_;
pipeline::AssetManager* assetManager_;
game::GameHandler* gameHandler_;
pipeline::DBCLayout* dbcLayout_;
EntitySpawner* entitySpawner_;
// Saved at spawn for skin re-compositing on equipment changes
std::string bodySkinPath_;
std::vector<std::string> underwearPaths_;
uint32_t skinTextureSlotIndex_ = 0;
uint32_t cloakTextureSlotIndex_ = 0;
bool weaponsSheathed_ = false;
};
} // namespace core
} // namespace wowee

View file

@ -3,6 +3,8 @@
#include "core/window.hpp" #include "core/window.hpp"
#include "core/input.hpp" #include "core/input.hpp"
#include "core/entity_spawner.hpp" #include "core/entity_spawner.hpp"
#include "core/appearance_composer.hpp"
#include "core/world_loader.hpp"
#include "game/character.hpp" #include "game/character.hpp"
#include "game/game_services.hpp" #include "game/game_services.hpp"
#include "pipeline/blp_loader.hpp" #include "pipeline/blp_loader.hpp"
@ -27,7 +29,7 @@ namespace ui { class UIManager; }
namespace auth { class AuthHandler; } namespace auth { class AuthHandler; }
namespace game { class GameHandler; class World; class ExpansionRegistry; } namespace game { class GameHandler; class World; class ExpansionRegistry; }
namespace pipeline { class AssetManager; class DBCLayout; struct M2Model; struct WMOModel; } namespace pipeline { class AssetManager; class DBCLayout; struct M2Model; struct WMOModel; }
namespace audio { enum class VoiceType; } namespace audio { enum class VoiceType; class AudioCoordinator; }
namespace addons { class AddonManager; } namespace addons { class AddonManager; }
namespace core { namespace core {
@ -42,6 +44,8 @@ enum class AppState {
}; };
class Application { class Application {
friend class WorldLoader;
public: public:
Application(); Application();
~Application(); ~Application();
@ -73,9 +77,7 @@ public:
// Singleton access // Singleton access
static Application& getInstance() { return *instance; } static Application& getInstance() { return *instance; }
// Weapon loading (called at spawn and on equipment change)
void loadEquippedWeapons();
bool loadWeaponM2(const std::string& m2Path, pipeline::M2Model& outModel);
// Logout to login screen // Logout to login screen
void logoutToLogin(); void logoutToLogin();
@ -85,26 +87,31 @@ public:
bool getRenderFootZForGuid(uint64_t guid, float& outFootZ) const; bool getRenderFootZForGuid(uint64_t guid, float& outFootZ) const;
bool getRenderPositionForGuid(uint64_t guid, glm::vec3& outPos) const; bool getRenderPositionForGuid(uint64_t guid, glm::vec3& outPos) const;
// Character skin composite state (saved at spawn for re-compositing on equipment change) // Character skin composite state — delegated to AppearanceComposer
const std::string& getBodySkinPath() const { return bodySkinPath_; } const std::string& getBodySkinPath() const { return appearanceComposer_ ? appearanceComposer_->getBodySkinPath() : emptyString_; }
const std::vector<std::string>& getUnderwearPaths() const { return underwearPaths_; } const std::vector<std::string>& getUnderwearPaths() const { return appearanceComposer_ ? appearanceComposer_->getUnderwearPaths() : emptyStringVec_; }
uint32_t getSkinTextureSlotIndex() const { return skinTextureSlotIndex_; } uint32_t getSkinTextureSlotIndex() const { return appearanceComposer_ ? appearanceComposer_->getSkinTextureSlotIndex() : 0; }
uint32_t getCloakTextureSlotIndex() const { return cloakTextureSlotIndex_; } uint32_t getCloakTextureSlotIndex() const { return appearanceComposer_ ? appearanceComposer_->getCloakTextureSlotIndex() : 0; }
uint32_t getGryphonDisplayId() const { return entitySpawner_ ? entitySpawner_->getGryphonDisplayId() : 0; } uint32_t getGryphonDisplayId() const { return entitySpawner_ ? entitySpawner_->getGryphonDisplayId() : 0; }
uint32_t getWyvernDisplayId() const { return entitySpawner_ ? entitySpawner_->getWyvernDisplayId() : 0; } uint32_t getWyvernDisplayId() const { return entitySpawner_ ? entitySpawner_->getWyvernDisplayId() : 0; }
// Entity spawner access // Entity spawner access
EntitySpawner* getEntitySpawner() { return entitySpawner_.get(); } EntitySpawner* getEntitySpawner() { return entitySpawner_.get(); }
// Appearance composer access
AppearanceComposer* getAppearanceComposer() { return appearanceComposer_.get(); }
// World loader access
WorldLoader* getWorldLoader() { return worldLoader_.get(); }
// Audio coordinator access (Section 4.1: extracted audio subsystem)
audio::AudioCoordinator* getAudioCoordinator() { return audioCoordinator_.get(); }
private: private:
void update(float deltaTime); void update(float deltaTime);
void render(); void render();
void setupUICallbacks(); void setupUICallbacks();
void spawnPlayerCharacter(); void spawnPlayerCharacter();
std::string getPlayerModelPath() const;
static const char* mapIdToName(uint32_t mapId);
static const char* mapDisplayName(uint32_t mapId);
void loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float z);
void buildFactionHostilityMap(uint8_t playerRace); void buildFactionHostilityMap(uint8_t playerRace);
void setupTestTransport(); // Test transport boat for development void setupTestTransport(); // Test transport boat for development
@ -123,6 +130,9 @@ private:
std::unique_ptr<game::ExpansionRegistry> expansionRegistry_; std::unique_ptr<game::ExpansionRegistry> expansionRegistry_;
std::unique_ptr<pipeline::DBCLayout> dbcLayout_; std::unique_ptr<pipeline::DBCLayout> dbcLayout_;
std::unique_ptr<EntitySpawner> entitySpawner_; std::unique_ptr<EntitySpawner> entitySpawner_;
std::unique_ptr<AppearanceComposer> appearanceComposer_;
std::unique_ptr<WorldLoader> worldLoader_;
std::unique_ptr<audio::AudioCoordinator> audioCoordinator_;
AppState state = AppState::AUTHENTICATION; AppState state = AppState::AUTHENTICATION;
bool running = false; bool running = false;
@ -140,20 +150,11 @@ private:
uint32_t spawnedAppearanceBytes_ = 0; uint32_t spawnedAppearanceBytes_ = 0;
uint8_t spawnedFacialFeatures_ = 0; uint8_t spawnedFacialFeatures_ = 0;
// Saved at spawn for skin re-compositing // Static empty values for null-safe delegation
std::string bodySkinPath_; static inline const std::string emptyString_;
std::vector<std::string> underwearPaths_; static inline const std::vector<std::string> emptyStringVec_;
uint32_t skinTextureSlotIndex_ = 0;
uint32_t cloakTextureSlotIndex_ = 0;
bool lastTaxiFlight_ = false; bool lastTaxiFlight_ = false;
uint32_t loadedMapId_ = 0xFFFFFFFF; // Map ID of currently loaded terrain (0xFFFFFFFF = none)
uint32_t worldLoadGeneration_ = 0; // Incremented on each world entry to detect re-entrant loads
bool loadingWorld_ = false; // True while loadOnlineWorldTerrain is running
struct PendingWorldEntry {
uint32_t mapId; float x, y, z;
};
std::optional<PendingWorldEntry> pendingWorldEntry_; // Deferred world entry during loading
float taxiLandingClampTimer_ = 0.0f; float taxiLandingClampTimer_ = 0.0f;
float worldEntryMovementGraceTimer_ = 0.0f; float worldEntryMovementGraceTimer_ = 0.0f;
@ -174,31 +175,11 @@ private:
glm::vec3 chargeEndPos_{0.0f}; // Render coordinates glm::vec3 chargeEndPos_{0.0f}; // Render coordinates
uint64_t chargeTargetGuid_ = 0; uint64_t chargeTargetGuid_ = 0;
bool weaponsSheathed_ = false;
bool wasAutoAttacking_ = false; bool wasAutoAttacking_ = false;
bool mapNameCacheLoaded_ = false;
std::unordered_map<uint32_t, std::string> mapNameById_;
// Quest marker billboard sprites (above NPCs) // Quest marker billboard sprites (above NPCs)
void loadQuestMarkerModels(); // Now loads BLP textures void loadQuestMarkerModels(); // Now loads BLP textures
void updateQuestMarkers(); // Updates billboard positions void updateQuestMarkers(); // Updates billboard positions
// Background world preloader — warms AssetManager file cache for the
// expected world before the user clicks Enter World.
struct WorldPreload {
uint32_t mapId = 0;
std::string mapName;
int centerTileX = 0;
int centerTileY = 0;
std::atomic<bool> cancel{false};
std::vector<std::thread> workers;
};
std::unique_ptr<WorldPreload> worldPreload_;
void startWorldPreload(uint32_t mapId, const std::string& mapName, float serverX, float serverY);
void cancelWorldPreload();
void saveLastWorldInfo(uint32_t mapId, const std::string& mapName, float serverX, float serverY);
struct LastWorldInfo { uint32_t mapId = 0; std::string mapName; float x = 0, y = 0; bool valid = false; };
LastWorldInfo loadLastWorldInfo() const;
}; };
} // namespace core } // namespace core

View file

@ -0,0 +1,125 @@
#pragma once
#include <string>
#include <vector>
#include <unordered_map>
#include <optional>
#include <memory>
#include <atomic>
#include <thread>
#include <cstdint>
namespace wowee {
namespace rendering { class Renderer; }
namespace pipeline { class AssetManager; class DBCLayout; }
namespace game { class GameHandler; class World; }
namespace addons { class AddonManager; }
namespace core {
class Application;
class EntitySpawner;
class AppearanceComposer;
class Window;
/// Handles terrain streaming, map transitions, world preloading,
/// and coordinate-aware tile management for online world entry.
class WorldLoader {
public:
WorldLoader(Application& app,
rendering::Renderer* renderer,
pipeline::AssetManager* assetManager,
game::GameHandler* gameHandler,
EntitySpawner* entitySpawner,
AppearanceComposer* appearanceComposer,
Window* window,
game::World* world,
addons::AddonManager* addonManager);
~WorldLoader();
// Main terrain loading — drives loading screen, WMO/ADT detection, player spawn
void loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float z);
// Process deferred world entry (called from Application::update each frame)
void processPendingEntry();
// Map name utilities
static const char* mapIdToName(uint32_t mapId);
static const char* mapDisplayName(uint32_t mapId);
// Background preloading — warms AssetManager file cache
void startWorldPreload(uint32_t mapId, const std::string& mapName,
float serverX, float serverY);
void cancelWorldPreload();
// Persistent world info for session-to-session preloading
void saveLastWorldInfo(uint32_t mapId, const std::string& mapName,
float serverX, float serverY);
struct LastWorldInfo {
uint32_t mapId = 0;
std::string mapName;
float x = 0, y = 0;
bool valid = false;
};
LastWorldInfo loadLastWorldInfo() const;
// State accessors
uint32_t getLoadedMapId() const { return loadedMapId_; }
bool isLoadingWorld() const { return loadingWorld_; }
bool hasPendingEntry() const { return pendingWorldEntry_.has_value(); }
// Get cached map name by ID (returns empty string if not found)
std::string getMapNameById(uint32_t mapId) const {
auto it = mapNameById_.find(mapId);
return (it != mapNameById_.end()) ? it->second : std::string{};
}
// Set pending world entry for deferred processing via processPendingEntry()
void setPendingEntry(uint32_t mapId, float x, float y, float z) {
pendingWorldEntry_ = PendingWorldEntry{mapId, x, y, z};
}
// Reset methods (for logout / character switch)
void resetLoadedMap() { loadedMapId_ = 0xFFFFFFFF; }
void resetMapNameCache() { mapNameCacheLoaded_ = false; mapNameById_.clear(); }
private:
Application& app_;
rendering::Renderer* renderer_;
pipeline::AssetManager* assetManager_;
game::GameHandler* gameHandler_;
EntitySpawner* entitySpawner_;
AppearanceComposer* appearanceComposer_;
Window* window_;
game::World* world_;
addons::AddonManager* addonManager_;
uint32_t loadedMapId_ = 0xFFFFFFFF; // Map ID of currently loaded terrain (0xFFFFFFFF = none)
uint32_t worldLoadGeneration_ = 0; // Incremented on each world entry to detect re-entrant loads
bool loadingWorld_ = false; // True while loadOnlineWorldTerrain is running
struct PendingWorldEntry {
uint32_t mapId; float x, y, z;
};
std::optional<PendingWorldEntry> pendingWorldEntry_;
// Map.dbc name cache (loaded once per session)
bool mapNameCacheLoaded_ = false;
std::unordered_map<uint32_t, std::string> mapNameById_;
// Background world preloader — warms AssetManager file cache for the
// expected world before the user clicks Enter World.
struct WorldPreload {
uint32_t mapId = 0;
std::string mapName;
int centerTileX = 0;
int centerTileY = 0;
std::atomic<bool> cancel{false};
std::vector<std::thread> workers;
};
std::unique_ptr<WorldPreload> worldPreload_;
};
} // namespace core
} // namespace wowee

View file

@ -4,6 +4,7 @@
// XP bar, reputation bar, macro resolution. // XP bar, reputation bar, macro resolution.
// ============================================================ // ============================================================
#pragma once #pragma once
#include "ui/ui_services.hpp"
#include <cstdint> #include <cstdint>
#include <unordered_map> #include <unordered_map>
#include <functional> #include <functional>
@ -70,7 +71,11 @@ public:
std::unordered_map<uint32_t, uint32_t> macroPrimarySpellCache_; std::unordered_map<uint32_t, uint32_t> macroPrimarySpellCache_;
size_t macroCacheSpellCount_ = 0; size_t macroCacheSpellCount_ = 0;
// Section 3.5: UIServices injection (Phase B singleton breaking)
void setServices(const UIServices& services) { services_ = services; }
private: private:
UIServices services_;
uint32_t resolveMacroPrimarySpellId(uint32_t macroId, game::GameHandler& gameHandler); uint32_t resolveMacroPrimarySpellId(uint32_t macroId, game::GameHandler& gameHandler);
}; };

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "ui/ui_services.hpp"
#include "auth/auth_handler.hpp" #include "auth/auth_handler.hpp"
#include <vulkan/vulkan.h> #include <vulkan/vulkan.h>
#include <string> #include <string>
@ -30,6 +31,9 @@ public:
*/ */
void setOnSuccess(std::function<void()> callback) { onSuccess = callback; } void setOnSuccess(std::function<void()> callback) { onSuccess = callback; }
/// Set services (dependency injection)
void setServices(const UIServices& services) { services_ = services; }
/** /**
* Check if authentication is in progress * Check if authentication is in progress
@ -44,6 +48,8 @@ public:
const std::string& getStatusMessage() const { return statusMessage; } const std::string& getStatusMessage() const { return statusMessage; }
private: private:
UIServices services_; // Injected service references
struct ServerProfile { struct ServerProfile {
std::string hostname; std::string hostname;
int port = 3724; int port = 3724;

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "ui/ui_services.hpp"
#include "game/game_handler.hpp" #include "game/game_handler.hpp"
#include <imgui.h> #include <imgui.h>
#include <string> #include <string>
@ -48,6 +49,9 @@ public:
void setOnBack(std::function<void()> cb) { onBack = std::move(cb); } void setOnBack(std::function<void()> cb) { onBack = std::move(cb); }
void setOnDeleteCharacter(std::function<void(uint64_t)> cb) { onDeleteCharacter = std::move(cb); } void setOnDeleteCharacter(std::function<void(uint64_t)> cb) { onDeleteCharacter = std::move(cb); }
/// Set services (dependency injection)
void setServices(const UIServices& services) { services_ = services; }
/** /**
* Reset selection state (e.g., when switching servers) * Reset selection state (e.g., when switching servers)
*/ */
@ -89,6 +93,8 @@ public:
void selectCharacterByName(const std::string& name); void selectCharacterByName(const std::string& name);
private: private:
UIServices services_; // Injected service references
// UI state // UI state
int selectedCharacterIndex = -1; int selectedCharacterIndex = -1;
bool characterSelected = false; bool characterSelected = false;

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "game/game_handler.hpp" #include "game/game_handler.hpp"
#include "ui/ui_services.hpp"
#include <vulkan/vulkan.h> #include <vulkan/vulkan.h>
#include <imgui.h> #include <imgui.h>
#include <string> #include <string>
@ -109,10 +110,16 @@ public:
/** Reset all chat settings to defaults. */ /** Reset all chat settings to defaults. */
void restoreDefaults(); void restoreDefaults();
// Section 3.5: UIServices injection (Phase B singleton breaking)
void setServices(const UIServices& services) { services_ = services; }
/** Replace $g/$G and $n/$N gender/name placeholders in quest/chat text. */ /** Replace $g/$G and $n/$N gender/name placeholders in quest/chat text. */
std::string replaceGenderPlaceholders(const std::string& text, game::GameHandler& gameHandler); std::string replaceGenderPlaceholders(const std::string& text, game::GameHandler& gameHandler);
private: private:
// Section 3.5: Injected UI services (Phase B singleton breaking)
UIServices services_;
// ---- Chat input state ---- // ---- Chat input state ----
char chatInputBuffer_[512] = ""; char chatInputBuffer_[512] = "";
char whisperTargetBuffer_[256] = ""; char whisperTargetBuffer_[256] = "";

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "ui/ui_services.hpp"
#include <imgui.h> #include <imgui.h>
#include <vulkan/vulkan.h> #include <vulkan/vulkan.h>
#include <string> #include <string>
@ -70,6 +71,12 @@ public:
SpellbookScreen& spellbookScreen); SpellbookScreen& spellbookScreen);
void renderThreatWindow(game::GameHandler& gameHandler); void renderThreatWindow(game::GameHandler& gameHandler);
void renderBgScoreboard(game::GameHandler& gameHandler); void renderBgScoreboard(game::GameHandler& gameHandler);
// Section 3.5: UIServices injection (Phase B singleton breaking)
void setServices(const UIServices& services) { services_ = services; }
private:
UIServices services_;
}; };
} // namespace ui } // namespace ui

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "ui/ui_services.hpp"
#include <imgui.h> #include <imgui.h>
#include <string> #include <string>
#include <cstdint> #include <cstdint>
@ -34,7 +35,12 @@ public:
/// called in render() after reclaim corpse button /// called in render() after reclaim corpse button
void renderLateDialogs(game::GameHandler& gameHandler); void renderLateDialogs(game::GameHandler& gameHandler);
// Section 3.5: UIServices injection (Phase B singleton breaking)
void setServices(const UIServices& services) { services_ = services; }
private: private:
// Section 3.5: Injected UI services
UIServices services_;
// Common ImGui window flags for popup dialogs // Common ImGui window flags for popup dialogs
static constexpr ImGuiWindowFlags kDialogFlags = static constexpr ImGuiWindowFlags kDialogFlags =
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize; ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize;

View file

@ -17,12 +17,14 @@
#include "ui/social_panel.hpp" #include "ui/social_panel.hpp"
#include "ui/action_bar_panel.hpp" #include "ui/action_bar_panel.hpp"
#include "ui/window_manager.hpp" #include "ui/window_manager.hpp"
#include "ui/ui_services.hpp"
#include <vulkan/vulkan.h> #include <vulkan/vulkan.h>
#include <imgui.h> #include <imgui.h>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
namespace wowee { namespace wowee {
namespace core { class AppearanceComposer; }
namespace pipeline { class AssetManager; } namespace pipeline { class AssetManager; }
namespace rendering { class Renderer; } namespace rendering { class Renderer; }
namespace ui { namespace ui {
@ -50,7 +52,17 @@ public:
void saveSettings(); void saveSettings();
void loadSettings(); void loadSettings();
// Dependency injection for extracted classes (Phase A singleton breaking)
void setAppearanceComposer(core::AppearanceComposer* ac) { appearanceComposer_ = ac; }
// Section 3.5: UIServices injection (Phase B singleton breaking)
void setServices(const UIServices& services);
private: private:
// Injected UI services (Section 3.5 Phase B - replaces getInstance() calls)
UIServices services_;
// Legacy pointer for Phase A compatibility (will be removed when all callsites migrate)
core::AppearanceComposer* appearanceComposer_ = nullptr;
// Chat panel (extracted from GameScreen — owns all chat state and rendering) // Chat panel (extracted from GameScreen — owns all chat state and rendering)
ChatPanel chatPanel_; ChatPanel chatPanel_;

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "ui/ui_services.hpp"
#include <vulkan/vulkan.h> #include <vulkan/vulkan.h>
#include <string> #include <string>
#include <functional> #include <functional>
@ -149,7 +150,12 @@ public:
/// Return the platform-specific settings file path /// Return the platform-specific settings file path
static std::string getSettingsPath(); static std::string getSettingsPath();
/// Set services (dependency injection)
void setServices(const UIServices& services) { services_ = services; }
private: private:
UIServices services_; // Injected service references
// Keybinding customization (private — only used in Controls tab) // Keybinding customization (private — only used in Controls tab)
int pendingRebindAction_ = -1; // -1 = not rebinding, otherwise action index int pendingRebindAction_ = -1; // -1 = not rebinding, otherwise action index
bool awaitingKeyPress_ = false; bool awaitingKeyPress_ = false;

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "ui/ui_services.hpp"
#include <imgui.h> #include <imgui.h>
#include <vulkan/vulkan.h> #include <vulkan/vulkan.h>
#include <string> #include <string>
@ -71,6 +72,12 @@ public:
ChatPanel& chatPanel); ChatPanel& chatPanel);
void renderInspectWindow(game::GameHandler& gameHandler, void renderInspectWindow(game::GameHandler& gameHandler,
InventoryScreen& inventoryScreen); InventoryScreen& inventoryScreen);
// Section 3.5: UIServices injection (Phase B singleton breaking)
void setServices(const UIServices& services) { services_ = services; }
private:
UIServices services_;
}; };
} // namespace ui } // namespace ui

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "ui/ui_services.hpp"
#include <vulkan/vulkan.h> #include <vulkan/vulkan.h>
#include <imgui.h> #include <imgui.h>
#include <string> #include <string>
@ -40,11 +41,17 @@ public:
/// Fire achievement earned toast + sound /// Fire achievement earned toast + sound
void triggerAchievementToast(uint32_t achievementId, std::string name = {}); void triggerAchievementToast(uint32_t achievementId, std::string name = {});
// Section 3.5: UIServices injection (Phase B singleton breaking)
void setServices(const UIServices& services) { services_ = services; }
// --- public state consumed by GameScreen for the golden burst overlay --- // --- public state consumed by GameScreen for the golden burst overlay ---
float levelUpFlashAlpha = 0.0f; float levelUpFlashAlpha = 0.0f;
uint32_t levelUpDisplayLevel = 0; uint32_t levelUpDisplayLevel = 0;
private: private:
// Section 3.5: Injected UI services
UIServices services_;
// ---- Ding effect (own level-up) ---- // ---- Ding effect (own level-up) ----
static constexpr float DING_DURATION = 4.0f; static constexpr float DING_DURATION = 4.0f;
float dingTimer_ = 0.0f; float dingTimer_ = 0.0f;

View file

@ -5,6 +5,7 @@
#include "ui/character_create_screen.hpp" #include "ui/character_create_screen.hpp"
#include "ui/character_screen.hpp" #include "ui/character_screen.hpp"
#include "ui/game_screen.hpp" #include "ui/game_screen.hpp"
#include "ui/ui_services.hpp"
#include <memory> #include <memory>
// Forward declare SDL_Event // Forward declare SDL_Event
@ -13,7 +14,7 @@ union SDL_Event;
namespace wowee { namespace wowee {
// Forward declarations // Forward declarations
namespace core { class Window; enum class AppState; } namespace core { class Window; class AppearanceComposer; enum class AppState; }
namespace auth { class AuthHandler; } namespace auth { class AuthHandler; }
namespace game { class GameHandler; } namespace game { class GameHandler; }
@ -69,8 +70,23 @@ public:
CharacterScreen& getCharacterScreen() { return *characterScreen; } CharacterScreen& getCharacterScreen() { return *characterScreen; }
GameScreen& getGameScreen() { return *gameScreen; } GameScreen& getGameScreen() { return *gameScreen; }
// Dependency injection forwarding (Phase A singleton breaking)
void setAppearanceComposer(core::AppearanceComposer* ac) {
if (gameScreen) gameScreen->setAppearanceComposer(ac);
}
// Section 3.5: UIServices injection (Phase B singleton breaking)
void setServices(const UIServices& services) {
services_ = services;
if (gameScreen) gameScreen->setServices(services);
if (authScreen) authScreen->setServices(services);
if (characterScreen) characterScreen->setServices(services);
}
const UIServices& getServices() const { return services_; }
private: private:
core::Window* window = nullptr; core::Window* window = nullptr;
UIServices services_; // Section 3.5: Injected services
// UI Screens // UI Screens
std::unique_ptr<AuthScreen> authScreen; std::unique_ptr<AuthScreen> authScreen;

View file

@ -0,0 +1,55 @@
#pragma once
namespace wowee {
// Forward declarations
namespace core {
class Window;
class EntitySpawner;
class AppearanceComposer;
class WorldLoader;
}
namespace rendering { class Renderer; }
namespace pipeline { class AssetManager; }
namespace game {
class GameHandler;
class ExpansionRegistry;
}
namespace addons { class AddonManager; }
namespace audio { class AudioCoordinator; }
namespace ui {
/**
* UI Services - Dependency injection container for UI components.
*
* Section 3.5: Break the singleton Phase B
*
* Replaces Application::getInstance() calls throughout UI code.
* Application creates this struct and injects it into UIManager,
* which propagates it to GameScreen and all child UI components.
*
* Owned by Application, shared as const pointers (non-owning).
*/
struct UIServices {
core::Window* window = nullptr;
rendering::Renderer* renderer = nullptr;
pipeline::AssetManager* assetManager = nullptr;
game::GameHandler* gameHandler = nullptr;
game::ExpansionRegistry* expansionRegistry = nullptr;
addons::AddonManager* addonManager = nullptr;
audio::AudioCoordinator* audioCoordinator = nullptr;
// Extracted classes (also available individually for Phase A compatibility)
core::EntitySpawner* entitySpawner = nullptr;
core::AppearanceComposer* appearanceComposer = nullptr;
core::WorldLoader* worldLoader = nullptr;
// Helper to check if core services are wired
bool isValid() const {
return window && renderer && assetManager && gameHandler;
}
};
} // namespace ui
} // namespace wowee

View file

@ -7,6 +7,7 @@
// equipment sets, skills. // equipment sets, skills.
// ============================================================ // ============================================================
#pragma once #pragma once
#include "ui/ui_services.hpp"
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
@ -173,7 +174,11 @@ public:
std::unordered_map<uint32_t, ExtendedCostEntry> extendedCostCache_; std::unordered_map<uint32_t, ExtendedCostEntry> extendedCostCache_;
bool extendedCostDbLoaded_ = false; bool extendedCostDbLoaded_ = false;
// Section 3.5: UIServices injection (Phase B singleton breaking)
void setServices(const UIServices& services) { services_ = services; }
private: private:
UIServices services_;
void loadExtendedCostDBC(); void loadExtendedCostDBC();
std::string formatExtendedCost(uint32_t extendedCostId, game::GameHandler& gameHandler); std::string formatExtendedCost(uint32_t extendedCostId, game::GameHandler& gameHandler);
}; };

View file

@ -0,0 +1,87 @@
#include "audio/audio_coordinator.hpp"
#include "audio/audio_engine.hpp"
#include "audio/music_manager.hpp"
#include "audio/footstep_manager.hpp"
#include "audio/activity_sound_manager.hpp"
#include "audio/mount_sound_manager.hpp"
#include "audio/npc_voice_manager.hpp"
#include "audio/ambient_sound_manager.hpp"
#include "audio/ui_sound_manager.hpp"
#include "audio/combat_sound_manager.hpp"
#include "audio/spell_sound_manager.hpp"
#include "audio/movement_sound_manager.hpp"
#include "pipeline/asset_manager.hpp"
#include "core/logger.hpp"
namespace wowee {
namespace audio {
AudioCoordinator::AudioCoordinator() = default;
AudioCoordinator::~AudioCoordinator() {
shutdown();
}
bool AudioCoordinator::initialize() {
// Initialize AudioEngine (singleton)
if (!AudioEngine::instance().initialize()) {
LOG_WARNING("Failed to initialize AudioEngine - audio will be disabled");
audioAvailable_ = false;
return false;
}
audioAvailable_ = true;
// Create all audio managers (initialized later with asset manager)
musicManager_ = std::make_unique<MusicManager>();
footstepManager_ = std::make_unique<FootstepManager>();
activitySoundManager_ = std::make_unique<ActivitySoundManager>();
mountSoundManager_ = std::make_unique<MountSoundManager>();
npcVoiceManager_ = std::make_unique<NpcVoiceManager>();
ambientSoundManager_ = std::make_unique<AmbientSoundManager>();
uiSoundManager_ = std::make_unique<UiSoundManager>();
combatSoundManager_ = std::make_unique<CombatSoundManager>();
spellSoundManager_ = std::make_unique<SpellSoundManager>();
movementSoundManager_ = std::make_unique<MovementSoundManager>();
LOG_INFO("AudioCoordinator initialized with ", 10, " audio managers");
return true;
}
void AudioCoordinator::initializeWithAssets(pipeline::AssetManager* assetManager) {
if (!audioAvailable_ || !assetManager) return;
// MusicManager needs asset manager for zone music lookups
if (musicManager_) {
musicManager_->initialize(assetManager);
}
// Other managers may need asset manager for sound bank loading
// (Add similar calls as needed for other managers)
LOG_INFO("AudioCoordinator initialized with asset manager");
}
void AudioCoordinator::shutdown() {
// Reset all managers first (they may reference AudioEngine)
movementSoundManager_.reset();
spellSoundManager_.reset();
combatSoundManager_.reset();
uiSoundManager_.reset();
ambientSoundManager_.reset();
npcVoiceManager_.reset();
mountSoundManager_.reset();
activitySoundManager_.reset();
footstepManager_.reset();
musicManager_.reset();
// Shutdown audio engine last
if (audioAvailable_) {
AudioEngine::instance().shutdown();
audioAvailable_ = false;
}
LOG_INFO("AudioCoordinator shutdown complete");
}
} // namespace audio
} // namespace wowee

View file

@ -0,0 +1,383 @@
#include "core/appearance_composer.hpp"
#include "core/entity_spawner.hpp"
#include "core/logger.hpp"
#include "rendering/renderer.hpp"
#include "rendering/character_renderer.hpp"
#include "pipeline/asset_manager.hpp"
#include "pipeline/m2_loader.hpp"
#include "pipeline/dbc_loader.hpp"
#include "pipeline/dbc_layout.hpp"
#include "game/game_handler.hpp"
namespace wowee {
namespace core {
AppearanceComposer::AppearanceComposer(rendering::Renderer* renderer,
pipeline::AssetManager* assetManager,
game::GameHandler* gameHandler,
pipeline::DBCLayout* dbcLayout,
EntitySpawner* entitySpawner)
: renderer_(renderer)
, assetManager_(assetManager)
, gameHandler_(gameHandler)
, dbcLayout_(dbcLayout)
, entitySpawner_(entitySpawner)
{
}
std::string AppearanceComposer::getPlayerModelPath(game::Race race, game::Gender gender) const {
return game::getPlayerModelPath(race, gender);
}
PlayerTextureInfo AppearanceComposer::resolvePlayerTextures(pipeline::M2Model& model,
game::Race race, game::Gender gender,
uint32_t appearanceBytes) {
PlayerTextureInfo result;
uint32_t targetRaceId = static_cast<uint32_t>(race);
uint32_t targetSexId = (gender == game::Gender::FEMALE) ? 1u : 0u;
// Race name for fallback texture paths
const char* raceFolderName = "Human";
switch (race) {
case game::Race::HUMAN: raceFolderName = "Human"; break;
case game::Race::ORC: raceFolderName = "Orc"; break;
case game::Race::DWARF: raceFolderName = "Dwarf"; break;
case game::Race::NIGHT_ELF: raceFolderName = "NightElf"; break;
case game::Race::UNDEAD: raceFolderName = "Scourge"; break;
case game::Race::TAUREN: raceFolderName = "Tauren"; break;
case game::Race::GNOME: raceFolderName = "Gnome"; break;
case game::Race::TROLL: raceFolderName = "Troll"; break;
case game::Race::BLOOD_ELF: raceFolderName = "BloodElf"; break;
case game::Race::DRAENEI: raceFolderName = "Draenei"; break;
default: break;
}
const char* genderFolder = (gender == game::Gender::FEMALE) ? "Female" : "Male";
std::string raceGender = std::string(raceFolderName) + genderFolder;
result.bodySkinPath = std::string("Character\\") + raceFolderName + "\\" + genderFolder + "\\" + raceGender + "Skin00_00.blp";
std::string pelvisPath = std::string("Character\\") + raceFolderName + "\\" + genderFolder + "\\" + raceGender + "NakedPelvisSkin00_00.blp";
// Extract appearance bytes for texture lookups
uint8_t charSkinId = appearanceBytes & 0xFF;
uint8_t charFaceId = (appearanceBytes >> 8) & 0xFF;
uint8_t charHairStyleId = (appearanceBytes >> 16) & 0xFF;
uint8_t charHairColorId = (appearanceBytes >> 24) & 0xFF;
LOG_INFO("Appearance: skin=", static_cast<int>(charSkinId), " face=", static_cast<int>(charFaceId),
" hairStyle=", static_cast<int>(charHairStyleId), " hairColor=", static_cast<int>(charHairColorId));
// Parse CharSections.dbc for skin/face/hair/underwear texture paths
auto charSectionsDbc = assetManager_->loadDBC("CharSections.dbc");
if (charSectionsDbc) {
LOG_INFO("CharSections.dbc loaded: ", charSectionsDbc->getRecordCount(), " records");
const auto* csL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("CharSections") : nullptr;
auto csF = pipeline::detectCharSectionsFields(charSectionsDbc.get(), csL);
bool foundSkin = false;
bool foundUnderwear = false;
bool foundFaceLower = false;
bool foundHair = false;
for (uint32_t r = 0; r < charSectionsDbc->getRecordCount(); r++) {
uint32_t raceId = charSectionsDbc->getUInt32(r, csF.raceId);
uint32_t sexId = charSectionsDbc->getUInt32(r, csF.sexId);
uint32_t baseSection = charSectionsDbc->getUInt32(r, csF.baseSection);
uint32_t variationIndex = charSectionsDbc->getUInt32(r, csF.variationIndex);
uint32_t colorIndex = charSectionsDbc->getUInt32(r, csF.colorIndex);
if (raceId != targetRaceId || sexId != targetSexId) continue;
// Section 0 = skin: match by colorIndex = skin byte
if (baseSection == 0 && !foundSkin && colorIndex == charSkinId) {
std::string tex1 = charSectionsDbc->getString(r, csF.texture1);
if (!tex1.empty()) {
result.bodySkinPath = tex1;
foundSkin = true;
LOG_INFO(" DBC body skin: ", result.bodySkinPath, " (skin=", static_cast<int>(charSkinId), ")");
}
}
// Section 3 = hair: match variation=hairStyle, color=hairColor
else if (baseSection == 3 && !foundHair &&
variationIndex == charHairStyleId && colorIndex == charHairColorId) {
result.hairTexturePath = charSectionsDbc->getString(r, csF.texture1);
if (!result.hairTexturePath.empty()) {
foundHair = true;
LOG_INFO(" DBC hair texture: ", result.hairTexturePath,
" (style=", static_cast<int>(charHairStyleId), " color=", static_cast<int>(charHairColorId), ")");
}
}
// Section 1 = face: match variation=faceId, colorIndex=skinId
// Texture1 = face lower, Texture2 = face upper
else if (baseSection == 1 && !foundFaceLower &&
variationIndex == charFaceId && colorIndex == charSkinId) {
std::string tex1 = charSectionsDbc->getString(r, csF.texture1);
std::string tex2 = charSectionsDbc->getString(r, csF.texture2);
if (!tex1.empty()) {
result.faceLowerPath = tex1;
LOG_INFO(" DBC face lower: ", result.faceLowerPath);
}
if (!tex2.empty()) {
result.faceUpperPath = tex2;
LOG_INFO(" DBC face upper: ", result.faceUpperPath);
}
foundFaceLower = true;
}
// Section 4 = underwear
else if (baseSection == 4 && !foundUnderwear && colorIndex == charSkinId) {
for (uint32_t f = csF.texture1; f <= csF.texture1 + 2; f++) {
std::string tex = charSectionsDbc->getString(r, f);
if (!tex.empty()) {
result.underwearPaths.push_back(tex);
LOG_INFO(" DBC underwear texture: ", tex);
}
}
foundUnderwear = true;
}
if (foundSkin && foundHair && foundFaceLower && foundUnderwear) break;
}
if (!foundHair) {
LOG_WARNING("No DBC hair match for style=", static_cast<int>(charHairStyleId),
" color=", static_cast<int>(charHairColorId),
" race=", targetRaceId, " sex=", targetSexId);
}
} else {
LOG_WARNING("Failed to load CharSections.dbc, using hardcoded textures");
}
// Fill model texture slots with resolved paths
for (auto& tex : model.textures) {
if (tex.type == 1 && tex.filename.empty()) {
tex.filename = result.bodySkinPath;
} else if (tex.type == 6) {
if (!result.hairTexturePath.empty()) {
tex.filename = result.hairTexturePath;
} else if (tex.filename.empty()) {
tex.filename = std::string("Character\\") + raceFolderName + "\\Hair00_00.blp";
}
} else if (tex.type == 8 && tex.filename.empty()) {
if (!result.underwearPaths.empty()) {
tex.filename = result.underwearPaths[0];
} else {
tex.filename = pelvisPath;
}
}
}
return result;
}
void AppearanceComposer::compositePlayerSkin(uint32_t modelSlotId, const PlayerTextureInfo& texInfo) {
if (!renderer_) return;
auto* charRenderer = renderer_->getCharacterRenderer();
if (!charRenderer) return;
// Save skin composite state for re-compositing on equipment changes
// Include face textures so compositeWithRegions can rebuild the full base
bodySkinPath_ = texInfo.bodySkinPath;
underwearPaths_.clear();
if (!texInfo.faceLowerPath.empty()) underwearPaths_.push_back(texInfo.faceLowerPath);
if (!texInfo.faceUpperPath.empty()) underwearPaths_.push_back(texInfo.faceUpperPath);
for (const auto& up : texInfo.underwearPaths) underwearPaths_.push_back(up);
// Composite body skin + face + underwear overlays
{
std::vector<std::string> layers;
layers.push_back(texInfo.bodySkinPath);
if (!texInfo.faceLowerPath.empty()) layers.push_back(texInfo.faceLowerPath);
if (!texInfo.faceUpperPath.empty()) layers.push_back(texInfo.faceUpperPath);
for (const auto& up : texInfo.underwearPaths) {
layers.push_back(up);
}
if (layers.size() > 1) {
rendering::VkTexture* compositeTex = charRenderer->compositeTextures(layers);
if (compositeTex != 0) {
// Find type-1 (skin) texture slot and replace with composite
// We need model texture info — walk slots via charRenderer
// Use the model slot ID to find the right texture index
auto* modelData = charRenderer->getModelData(modelSlotId);
if (modelData) {
for (size_t ti = 0; ti < modelData->textures.size(); ti++) {
if (modelData->textures[ti].type == 1) {
charRenderer->setModelTexture(modelSlotId, static_cast<uint32_t>(ti), compositeTex);
skinTextureSlotIndex_ = static_cast<uint32_t>(ti);
LOG_INFO("Replaced type-1 texture slot ", ti, " with composited body+face+underwear");
break;
}
}
}
}
}
}
// Override hair texture on GPU (type-6 slot) after model load
if (!texInfo.hairTexturePath.empty()) {
rendering::VkTexture* hairTex = charRenderer->loadTexture(texInfo.hairTexturePath);
if (hairTex) {
auto* modelData = charRenderer->getModelData(modelSlotId);
if (modelData) {
for (size_t ti = 0; ti < modelData->textures.size(); ti++) {
if (modelData->textures[ti].type == 6) {
charRenderer->setModelTexture(modelSlotId, static_cast<uint32_t>(ti), hairTex);
LOG_INFO("Applied DBC hair texture to slot ", ti, ": ", texInfo.hairTexturePath);
break;
}
}
}
}
}
// Find cloak (type-2, Object Skin) texture slot index
{
auto* modelData = charRenderer->getModelData(modelSlotId);
if (modelData) {
for (size_t ti = 0; ti < modelData->textures.size(); ti++) {
if (modelData->textures[ti].type == 2) {
cloakTextureSlotIndex_ = static_cast<uint32_t>(ti);
LOG_INFO("Cloak texture slot: ", ti);
break;
}
}
}
}
}
std::unordered_set<uint16_t> AppearanceComposer::buildDefaultPlayerGeosets(uint8_t hairStyleId, uint8_t facialId) {
std::unordered_set<uint16_t> activeGeosets;
// Body parts (group 0: IDs 0-99, some models use up to 27)
for (uint16_t i = 0; i <= 99; i++) activeGeosets.insert(i);
// Hair style geoset: group 1 = 100 + variation + 1
activeGeosets.insert(static_cast<uint16_t>(100 + hairStyleId + 1));
// Facial hair geoset: group 2 = 200 + variation + 1
activeGeosets.insert(static_cast<uint16_t>(200 + facialId + 1));
activeGeosets.insert(kGeosetBareForearms);
activeGeosets.insert(kGeosetBareShins);
activeGeosets.insert(kGeosetDefaultEars);
activeGeosets.insert(kGeosetBareSleeves);
activeGeosets.insert(kGeosetDefaultKneepads);
activeGeosets.insert(kGeosetBarePants);
activeGeosets.insert(kGeosetWithCape);
activeGeosets.insert(kGeosetBareFeet);
// 1703 = DK eye glow mesh — skip for normal characters
// Normal eyes are part of the face texture on the body mesh
return activeGeosets;
}
bool AppearanceComposer::loadWeaponM2(const std::string& m2Path, pipeline::M2Model& outModel) {
auto m2Data = assetManager_->readFile(m2Path);
if (m2Data.empty()) return false;
outModel = pipeline::M2Loader::load(m2Data);
// Load skin (WotLK+ M2 format): strip .m2, append 00.skin
std::string skinPath = m2Path;
size_t dotPos = skinPath.rfind('.');
if (dotPos != std::string::npos) skinPath = skinPath.substr(0, dotPos);
skinPath += "00.skin";
auto skinData = assetManager_->readFile(skinPath);
if (!skinData.empty() && outModel.version >= 264)
pipeline::M2Loader::loadSkin(skinData, outModel);
return outModel.isValid();
}
void AppearanceComposer::loadEquippedWeapons() {
if (!renderer_ || !renderer_->getCharacterRenderer() || !assetManager_ || !assetManager_->isInitialized())
return;
if (!gameHandler_) return;
auto* charRenderer = renderer_->getCharacterRenderer();
uint32_t charInstanceId = renderer_->getCharacterInstanceId();
if (charInstanceId == 0) return;
auto& inventory = gameHandler_->getInventory();
// Load ItemDisplayInfo.dbc
auto displayInfoDbc = assetManager_->loadDBC("ItemDisplayInfo.dbc");
if (!displayInfoDbc) {
LOG_WARNING("loadEquippedWeapons: failed to load ItemDisplayInfo.dbc");
return;
}
// Mapping: EquipSlot → attachment ID (1=RightHand, 2=LeftHand)
struct WeaponSlot {
game::EquipSlot slot;
uint32_t attachmentId;
};
WeaponSlot weaponSlots[] = {
{ game::EquipSlot::MAIN_HAND, 1 },
{ game::EquipSlot::OFF_HAND, 2 },
};
if (weaponsSheathed_) {
for (const auto& ws : weaponSlots) {
charRenderer->detachWeapon(charInstanceId, ws.attachmentId);
}
return;
}
for (const auto& ws : weaponSlots) {
const auto& equipSlot = inventory.getEquipSlot(ws.slot);
// If slot is empty or has no displayInfoId, detach any existing weapon
if (equipSlot.empty() || equipSlot.item.displayInfoId == 0) {
charRenderer->detachWeapon(charInstanceId, ws.attachmentId);
continue;
}
uint32_t displayInfoId = equipSlot.item.displayInfoId;
int32_t recIdx = displayInfoDbc->findRecordById(displayInfoId);
if (recIdx < 0) {
LOG_WARNING("loadEquippedWeapons: displayInfoId ", displayInfoId, " not found in DBC");
charRenderer->detachWeapon(charInstanceId, ws.attachmentId);
continue;
}
const auto* idiL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("ItemDisplayInfo") : nullptr;
std::string modelName = displayInfoDbc->getString(static_cast<uint32_t>(recIdx), idiL ? (*idiL)["LeftModel"] : 1);
std::string textureName = displayInfoDbc->getString(static_cast<uint32_t>(recIdx), idiL ? (*idiL)["LeftModelTexture"] : 3);
if (modelName.empty()) {
LOG_WARNING("loadEquippedWeapons: empty model name for displayInfoId ", displayInfoId);
charRenderer->detachWeapon(charInstanceId, ws.attachmentId);
continue;
}
// Convert .mdx → .m2
std::string modelFile = modelName;
{
size_t dotPos = modelFile.rfind('.');
if (dotPos != std::string::npos) {
modelFile = modelFile.substr(0, dotPos) + ".m2";
} else {
modelFile += ".m2";
}
}
// Try Weapon directory first, then Shield
std::string m2Path = "Item\\ObjectComponents\\Weapon\\" + modelFile;
pipeline::M2Model weaponModel;
if (!loadWeaponM2(m2Path, weaponModel)) {
m2Path = "Item\\ObjectComponents\\Shield\\" + modelFile;
if (!loadWeaponM2(m2Path, weaponModel)) {
LOG_WARNING("loadEquippedWeapons: failed to load ", modelFile);
charRenderer->detachWeapon(charInstanceId, ws.attachmentId);
continue;
}
}
// Build texture path
std::string texturePath;
if (!textureName.empty()) {
texturePath = "Item\\ObjectComponents\\Weapon\\" + textureName + ".blp";
if (!assetManager_->fileExists(texturePath)) {
texturePath = "Item\\ObjectComponents\\Shield\\" + textureName + ".blp";
}
}
uint32_t weaponModelId = entitySpawner_->allocateWeaponModelId();
bool ok = charRenderer->attachWeapon(charInstanceId, ws.attachmentId,
weaponModel, weaponModelId, texturePath);
if (ok) {
LOG_INFO("Equipped weapon: ", m2Path, " at attachment ", ws.attachmentId);
}
}
}
} // namespace core
} // namespace wowee

File diff suppressed because it is too large Load diff

1217
src/core/world_loader.cpp Normal file

File diff suppressed because it is too large Load diff

View file

@ -172,7 +172,7 @@ void ActionBarPanel::renderActionBar(game::GameHandler& gameHandler,
ImVec2 displaySize = ImGui::GetIO().DisplaySize; ImVec2 displaySize = ImGui::GetIO().DisplaySize;
float screenW = displaySize.x > 0.0f ? displaySize.x : 1280.0f; float screenW = displaySize.x > 0.0f ? displaySize.x : 1280.0f;
float screenH = displaySize.y > 0.0f ? displaySize.y : 720.0f; float screenH = displaySize.y > 0.0f ? displaySize.y : 720.0f;
auto* assetMgr = core::Application::getInstance().getAssetManager(); auto* assetMgr = services_.assetManager;
float slotSize = 48.0f * settingsPanel.pendingActionBarScale; float slotSize = 48.0f * settingsPanel.pendingActionBarScale;
float spacing = 4.0f; float spacing = 4.0f;
@ -1107,7 +1107,7 @@ void ActionBarPanel::renderStanceBar(game::GameHandler& gameHandler,
ImVec2 displaySize = ImGui::GetIO().DisplaySize; ImVec2 displaySize = ImGui::GetIO().DisplaySize;
float screenW = displaySize.x > 0.0f ? displaySize.x : 1280.0f; float screenW = displaySize.x > 0.0f ? displaySize.x : 1280.0f;
float screenH = displaySize.y > 0.0f ? displaySize.y : 720.0f; float screenH = displaySize.y > 0.0f ? displaySize.y : 720.0f;
auto* assetMgr = core::Application::getInstance().getAssetManager(); auto* assetMgr = services_.assetManager;
// Match the action bar slot size so they align neatly // Match the action bar slot size so they align neatly
float slotSize = 38.0f; float slotSize = 38.0f;
@ -1196,7 +1196,7 @@ void ActionBarPanel::renderBagBar(game::GameHandler& gameHandler,
ImVec2 displaySize = ImGui::GetIO().DisplaySize; ImVec2 displaySize = ImGui::GetIO().DisplaySize;
float screenW = displaySize.x > 0.0f ? displaySize.x : 1280.0f; float screenW = displaySize.x > 0.0f ? displaySize.x : 1280.0f;
float screenH = displaySize.y > 0.0f ? displaySize.y : 720.0f; float screenH = displaySize.y > 0.0f ? displaySize.y : 720.0f;
auto* assetMgr = core::Application::getInstance().getAssetManager(); auto* assetMgr = services_.assetManager;
float slotSize = 42.0f; float slotSize = 42.0f;
float spacing = 4.0f; float spacing = 4.0f;
@ -1232,7 +1232,7 @@ void ActionBarPanel::renderBagBar(game::GameHandler& gameHandler,
if (!blpData.empty()) { if (!blpData.empty()) {
auto image = pipeline::BLPLoader::load(blpData); auto image = pipeline::BLPLoader::load(blpData);
if (image.isValid()) { if (image.isValid()) {
auto* w = core::Application::getInstance().getWindow(); auto* w = services_.window;
auto* vkCtx = w ? w->getVkContext() : nullptr; auto* vkCtx = w ? w->getVkContext() : nullptr;
if (vkCtx) if (vkCtx)
backpackIconTexture_ = vkCtx->uploadImGuiTexture(image.data.data(), image.width, image.height); backpackIconTexture_ = vkCtx->uploadImGuiTexture(image.data.data(), image.width, image.height);
@ -1483,7 +1483,7 @@ void ActionBarPanel::renderXpBar(game::GameHandler& gameHandler,
ImVec2 displaySize = ImGui::GetIO().DisplaySize; ImVec2 displaySize = ImGui::GetIO().DisplaySize;
float screenW = displaySize.x > 0.0f ? displaySize.x : 1280.0f; float screenW = displaySize.x > 0.0f ? displaySize.x : 1280.0f;
float screenH = displaySize.y > 0.0f ? displaySize.y : 720.0f; float screenH = displaySize.y > 0.0f ? displaySize.y : 720.0f;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
(void)window; // Not used for positioning; kept for AssetManager if needed (void)window; // Not used for positioning; kept for AssetManager if needed
// Position just above both action bars (bar1 at screenH-barH, bar2 above that) // Position just above both action bars (bar1 at screenH-barH, bar2 above that)

View file

@ -197,8 +197,8 @@ void ChatPanel::render(game::GameHandler& gameHandler,
InventoryScreen& inventoryScreen, InventoryScreen& inventoryScreen,
SpellbookScreen& spellbookScreen, SpellbookScreen& spellbookScreen,
QuestLogScreen& questLogScreen) { QuestLogScreen& questLogScreen) {
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
auto* assetMgr = core::Application::getInstance().getAssetManager(); auto* assetMgr = services_.assetManager;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
float chatW = std::min(500.0f, screenW * 0.4f); float chatW = std::min(500.0f, screenW * 0.4f);
@ -1109,7 +1109,7 @@ void ChatPanel::render(game::GameHandler& gameHandler,
std::string bodyLower = mMsg.message; std::string bodyLower = mMsg.message;
for (auto& c : bodyLower) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c))); for (auto& c : bodyLower) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
if (bodyLower.find(selfNameLower) != std::string::npos) { if (bodyLower.find(selfNameLower) != std::string::npos) {
if (auto* renderer = core::Application::getInstance().getRenderer()) { if (auto* renderer = services_.renderer) {
if (auto* ui = renderer->getUiSoundManager()) if (auto* ui = renderer->getUiSoundManager())
ui->playWhisperReceived(); ui->playWhisperReceived();
} }
@ -2151,7 +2151,7 @@ void ChatPanel::sendChatMessage(game::GameHandler& gameHandler,
// /run <lua code> — execute Lua script via addon system // /run <lua code> — execute Lua script via addon system
if ((cmdLower == "run" || cmdLower == "script") && spacePos != std::string::npos) { if ((cmdLower == "run" || cmdLower == "script") && spacePos != std::string::npos) {
std::string luaCode = command.substr(spacePos + 1); std::string luaCode = command.substr(spacePos + 1);
auto* am = core::Application::getInstance().getAddonManager(); auto* am = services_.addonManager;
if (am) { if (am) {
am->runScript(luaCode); am->runScript(luaCode);
} else { } else {
@ -2164,7 +2164,7 @@ void ChatPanel::sendChatMessage(game::GameHandler& gameHandler,
// /dump <expression> — evaluate Lua expression and print result // /dump <expression> — evaluate Lua expression and print result
if ((cmdLower == "dump" || cmdLower == "print") && spacePos != std::string::npos) { if ((cmdLower == "dump" || cmdLower == "print") && spacePos != std::string::npos) {
std::string expr = command.substr(spacePos + 1); std::string expr = command.substr(spacePos + 1);
auto* am = core::Application::getInstance().getAddonManager(); auto* am = services_.addonManager;
if (am && am->isInitialized()) { if (am && am->isInitialized()) {
// Wrap expression in print(tostring(...)) to display the value // Wrap expression in print(tostring(...)) to display the value
std::string wrapped = "local __v = " + expr + std::string wrapped = "local __v = " + expr +
@ -2187,7 +2187,7 @@ void ChatPanel::sendChatMessage(game::GameHandler& gameHandler,
// Check addon slash commands (SlashCmdList) before built-in commands // Check addon slash commands (SlashCmdList) before built-in commands
{ {
auto* am = core::Application::getInstance().getAddonManager(); auto* am = services_.addonManager;
if (am && am->isInitialized()) { if (am && am->isInitialized()) {
std::string slashCmd = "/" + cmdLower; std::string slashCmd = "/" + cmdLower;
std::string slashArgs; std::string slashArgs;
@ -2214,7 +2214,7 @@ void ChatPanel::sendChatMessage(game::GameHandler& gameHandler,
// /reload or /reloadui — reload all addons (save variables, re-init Lua, re-scan .toc files) // /reload or /reloadui — reload all addons (save variables, re-init Lua, re-scan .toc files)
if (cmdLower == "reload" || cmdLower == "reloadui" || cmdLower == "rl") { if (cmdLower == "reload" || cmdLower == "reloadui" || cmdLower == "rl") {
auto* am = core::Application::getInstance().getAddonManager(); auto* am = services_.addonManager;
if (am) { if (am) {
am->reload(); am->reload();
am->fireEvent("VARIABLES_LOADED"); am->fireEvent("VARIABLES_LOADED");
@ -2301,7 +2301,7 @@ void ChatPanel::sendChatMessage(game::GameHandler& gameHandler,
if (cmdLower == "loc" || cmdLower == "coords" || cmdLower == "whereami") { if (cmdLower == "loc" || cmdLower == "coords" || cmdLower == "whereami") {
const auto& pmi = gameHandler.getMovementInfo(); const auto& pmi = gameHandler.getMovementInfo();
std::string zoneName; std::string zoneName;
if (auto* rend = core::Application::getInstance().getRenderer()) if (auto* rend = services_.renderer)
zoneName = rend->getCurrentZoneName(); zoneName = rend->getCurrentZoneName();
char buf[256]; char buf[256];
snprintf(buf, sizeof(buf), "%.1f, %.1f, %.1f%s%s", snprintf(buf, sizeof(buf), "%.1f, %.1f, %.1f%s%s",
@ -2327,7 +2327,7 @@ void ChatPanel::sendChatMessage(game::GameHandler& gameHandler,
// /zone command — print current zone name // /zone command — print current zone name
if (cmdLower == "zone") { if (cmdLower == "zone") {
std::string zoneName; std::string zoneName;
if (auto* rend = core::Application::getInstance().getRenderer()) if (auto* rend = services_.renderer)
zoneName = rend->getCurrentZoneName(); zoneName = rend->getCurrentZoneName();
game::MessageChatData sysMsg; game::MessageChatData sysMsg;
sysMsg.type = game::ChatType::SYSTEM; sysMsg.type = game::ChatType::SYSTEM;
@ -4323,7 +4323,7 @@ void ChatPanel::sendChatMessage(game::GameHandler& gameHandler,
std::string emoteText = rendering::Renderer::getEmoteText(cmdLower, targetNamePtr); std::string emoteText = rendering::Renderer::getEmoteText(cmdLower, targetNamePtr);
if (!emoteText.empty()) { if (!emoteText.empty()) {
// Play the emote animation // Play the emote animation
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
if (renderer) { if (renderer) {
renderer->playEmote(cmdLower); renderer->playEmote(cmdLower);
} }
@ -4697,11 +4697,11 @@ std::string ChatPanel::replaceGenderPlaceholders(const std::string& text, game::
void ChatPanel::renderBubbles(game::GameHandler& gameHandler) { void ChatPanel::renderBubbles(game::GameHandler& gameHandler) {
if (chatBubbles_.empty()) return; if (chatBubbles_.empty()) return;
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
auto* camera = renderer ? renderer->getCamera() : nullptr; auto* camera = renderer ? renderer->getCamera() : nullptr;
if (!camera) return; if (!camera) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;

View file

@ -60,7 +60,7 @@ namespace ui {
void CombatUI::renderCastBar(game::GameHandler& gameHandler, SpellIconFn getSpellIcon) { void CombatUI::renderCastBar(game::GameHandler& gameHandler, SpellIconFn getSpellIcon) {
if (!gameHandler.isCasting()) return; if (!gameHandler.isCasting()) return;
auto* assetMgr = core::Application::getInstance().getAssetManager(); auto* assetMgr = services_.assetManager;
ImVec2 displaySize = ImGui::GetIO().DisplaySize; ImVec2 displaySize = ImGui::GetIO().DisplaySize;
float screenW = displaySize.x > 0.0f ? displaySize.x : 1280.0f; float screenW = displaySize.x > 0.0f ? displaySize.x : 1280.0f;
@ -187,8 +187,8 @@ void CombatUI::renderCooldownTracker(game::GameHandler& gameHandler,
return a.remaining > b.remaining; return a.remaining > b.remaining;
}); });
auto* assetMgr = core::Application::getInstance().getAssetManager(); auto* assetMgr = services_.assetManager;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -268,7 +268,7 @@ void CombatUI::renderRaidWarningOverlay(game::GameHandler& gameHandler) {
// Walk only the new messages (deque — iterate from back by skipping old ones) // Walk only the new messages (deque — iterate from back by skipping old ones)
size_t toScan = newCount - raidWarnChatSeenCount_; size_t toScan = newCount - raidWarnChatSeenCount_;
size_t startIdx = newCount > toScan ? newCount - toScan : 0; size_t startIdx = newCount > toScan ? newCount - toScan : 0;
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
for (size_t i = startIdx; i < newCount; ++i) { for (size_t i = startIdx; i < newCount; ++i) {
const auto& msg = chatHistory[i]; const auto& msg = chatHistory[i];
if (msg.type == game::ChatType::RAID_WARNING || if (msg.type == game::ChatType::RAID_WARNING ||
@ -361,13 +361,13 @@ void CombatUI::renderCombatText(game::GameHandler& gameHandler) {
const auto& entries = gameHandler.getCombatText(); const auto& entries = gameHandler.getCombatText();
if (entries.empty()) return; if (entries.empty()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
if (!window) return; if (!window) return;
const float screenW = static_cast<float>(window->getWidth()); const float screenW = static_cast<float>(window->getWidth());
const float screenH = static_cast<float>(window->getHeight()); const float screenH = static_cast<float>(window->getHeight());
// Camera for world-space projection // Camera for world-space projection
auto* appRenderer = core::Application::getInstance().getRenderer(); auto* appRenderer = services_.renderer;
rendering::Camera* camera = appRenderer ? appRenderer->getCamera() : nullptr; rendering::Camera* camera = appRenderer ? appRenderer->getCamera() : nullptr;
glm::mat4 viewProj; glm::mat4 viewProj;
if (camera) viewProj = camera->getProjectionMatrix() * camera->getViewMatrix(); if (camera) viewProj = camera->getProjectionMatrix() * camera->getViewMatrix();
@ -785,7 +785,7 @@ void CombatUI::renderDPSMeter(game::GameHandler& gameHandler,
fmtNum(hps, hpsBuf, sizeof(hpsBuf)); fmtNum(hps, hpsBuf, sizeof(hpsBuf));
// Position: small floating label just above the action bar, right of center // Position: small floating label just above the action bar, right of center
auto* appWin = core::Application::getInstance().getWindow(); auto* appWin = services_.window;
float screenW = appWin ? static_cast<float>(appWin->getWidth()) : 1280.0f; float screenW = appWin ? static_cast<float>(appWin->getWidth()) : 1280.0f;
float screenH = appWin ? static_cast<float>(appWin->getHeight()) : 720.0f; float screenH = appWin ? static_cast<float>(appWin->getHeight()) : 720.0f;
@ -866,7 +866,7 @@ void CombatUI::renderBuffBar(game::GameHandler& gameHandler,
} }
if (activeCount == 0 && !gameHandler.hasPet()) return; if (activeCount == 0 && !gameHandler.hasPet()) return;
auto* assetMgr = core::Application::getInstance().getAssetManager(); auto* assetMgr = services_.assetManager;
// Position below the minimap (minimap: 200x200 at top-right, bottom edge at Y≈210) // Position below the minimap (minimap: 200x200 at top-right, bottom edge at Y≈210)
// Anchored to the right side to stay away from party frames on the left // Anchored to the right side to stay away from party frames on the left
@ -1201,7 +1201,7 @@ void CombatUI::renderBattlegroundScore(game::GameHandler& gameHandler) {
if (auto mv = gameHandler.getWorldState(def->maxKey)) maxScore = *mv; if (auto mv = gameHandler.getWorldState(def->maxKey)) maxScore = *mv;
} }
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
// Width scales with screen but stays reasonable // Width scales with screen but stays reasonable
@ -1598,7 +1598,7 @@ void CombatUI::renderCombatLog(game::GameHandler& gameHandler,
ImGui::TextColored(color, "%s", desc); ImGui::TextColored(color, "%s", desc);
// Hover tooltip: show rich spell info for entries with a known spell // Hover tooltip: show rich spell info for entries with a known spell
if (e.spellId != 0 && ImGui::IsItemHovered()) { if (e.spellId != 0 && ImGui::IsItemHovered()) {
auto* assetMgrLog = core::Application::getInstance().getAssetManager(); auto* assetMgrLog = services_.assetManager;
ImGui::BeginTooltip(); ImGui::BeginTooltip();
bool richOk = spellbookScreen.renderSpellInfoTooltip(e.spellId, gameHandler, assetMgrLog); bool richOk = spellbookScreen.renderSpellInfoTooltip(e.spellId, gameHandler, assetMgrLog);
if (!richOk) { if (!richOk) {

View file

@ -69,7 +69,7 @@ void DialogManager::renderLateDialogs(game::GameHandler& gameHandler) {
void DialogManager::renderGroupInvitePopup(game::GameHandler& gameHandler) { void DialogManager::renderGroupInvitePopup(game::GameHandler& gameHandler) {
if (!gameHandler.hasPendingGroupInvite()) return; if (!gameHandler.hasPendingGroupInvite()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 150, 200), ImGuiCond_Always); ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 150, 200), ImGuiCond_Always);
@ -93,7 +93,7 @@ void DialogManager::renderGroupInvitePopup(game::GameHandler& gameHandler) {
void DialogManager::renderDuelRequestPopup(game::GameHandler& gameHandler) { void DialogManager::renderDuelRequestPopup(game::GameHandler& gameHandler) {
if (!gameHandler.hasPendingDuelRequest()) return; if (!gameHandler.hasPendingDuelRequest()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 150, 250), ImGuiCond_Always); ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 150, 250), ImGuiCond_Always);
@ -158,7 +158,7 @@ void DialogManager::renderDuelCountdown(game::GameHandler& gameHandler) {
void DialogManager::renderItemTextWindow(game::GameHandler& gameHandler) { void DialogManager::renderItemTextWindow(game::GameHandler& gameHandler) {
if (!gameHandler.isItemTextOpen()) return; if (!gameHandler.isItemTextOpen()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -194,7 +194,7 @@ void DialogManager::renderItemTextWindow(game::GameHandler& gameHandler) {
void DialogManager::renderSharedQuestPopup(game::GameHandler& gameHandler) { void DialogManager::renderSharedQuestPopup(game::GameHandler& gameHandler) {
if (!gameHandler.hasPendingSharedQuest()) return; if (!gameHandler.hasPendingSharedQuest()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 175, 490), ImGuiCond_Always); ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 175, 490), ImGuiCond_Always);
@ -224,7 +224,7 @@ void DialogManager::renderSummonRequestPopup(game::GameHandler& gameHandler) {
gameHandler.tickSummonTimeout(dt); gameHandler.tickSummonTimeout(dt);
if (!gameHandler.hasPendingSummonRequest()) return; // expired if (!gameHandler.hasPendingSummonRequest()) return; // expired
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 175, 430), ImGuiCond_Always); ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 175, 430), ImGuiCond_Always);
@ -252,7 +252,7 @@ void DialogManager::renderSummonRequestPopup(game::GameHandler& gameHandler) {
void DialogManager::renderTradeRequestPopup(game::GameHandler& gameHandler) { void DialogManager::renderTradeRequestPopup(game::GameHandler& gameHandler) {
if (!gameHandler.hasPendingTradeRequest()) return; if (!gameHandler.hasPendingTradeRequest()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 150, 370), ImGuiCond_Always); ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 150, 370), ImGuiCond_Always);
@ -284,7 +284,7 @@ void DialogManager::renderTradeWindow(game::GameHandler& gameHandler,
const uint64_t peerGold = gameHandler.getPeerTradeGold(); const uint64_t peerGold = gameHandler.getPeerTradeGold();
const auto& peerName = gameHandler.getTradePeerName(); const auto& peerName = gameHandler.getTradePeerName();
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -443,7 +443,7 @@ void DialogManager::renderLootRollPopup(game::GameHandler& gameHandler,
const auto& roll = gameHandler.getPendingLootRoll(); const auto& roll = gameHandler.getPendingLootRoll();
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 175, 310), ImGuiCond_Always); ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 175, 310), ImGuiCond_Always);
@ -584,7 +584,7 @@ void DialogManager::renderLootRollPopup(game::GameHandler& gameHandler,
void DialogManager::renderGuildInvitePopup(game::GameHandler& gameHandler) { void DialogManager::renderGuildInvitePopup(game::GameHandler& gameHandler) {
if (!gameHandler.hasPendingGuildInvite()) return; if (!gameHandler.hasPendingGuildInvite()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 175, 250), ImGuiCond_Always); ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 175, 250), ImGuiCond_Always);
@ -610,7 +610,7 @@ void DialogManager::renderGuildInvitePopup(game::GameHandler& gameHandler) {
void DialogManager::renderReadyCheckPopup(game::GameHandler& gameHandler) { void DialogManager::renderReadyCheckPopup(game::GameHandler& gameHandler) {
if (!gameHandler.hasPendingReadyCheck()) return; if (!gameHandler.hasPendingReadyCheck()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -684,7 +684,7 @@ void DialogManager::renderBgInvitePopup(game::GameHandler& gameHandler) {
return; return;
} }
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -745,7 +745,7 @@ void DialogManager::renderBfMgrInvitePopup(game::GameHandler& gameHandler) {
// Only shown on WotLK servers (outdoor battlefields like Wintergrasp use the BF Manager) // Only shown on WotLK servers (outdoor battlefields like Wintergrasp use the BF Manager)
if (!gameHandler.hasBfMgrInvite()) return; if (!gameHandler.hasBfMgrInvite()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -802,7 +802,7 @@ void DialogManager::renderLfgProposalPopup(game::GameHandler& gameHandler) {
using LfgState = game::GameHandler::LfgState; using LfgState = game::GameHandler::LfgState;
if (gameHandler.getLfgState() != LfgState::Proposal) return; if (gameHandler.getLfgState() != LfgState::Proposal) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -851,7 +851,7 @@ void DialogManager::renderLfgRoleCheckPopup(game::GameHandler& gameHandler) {
using LfgState = game::GameHandler::LfgState; using LfgState = game::GameHandler::LfgState;
if (gameHandler.getLfgState() != LfgState::RoleCheck) return; if (gameHandler.getLfgState() != LfgState::RoleCheck) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -915,7 +915,7 @@ void DialogManager::renderLfgRoleCheckPopup(game::GameHandler& gameHandler) {
void DialogManager::renderResurrectDialog(game::GameHandler& gameHandler) { void DialogManager::renderResurrectDialog(game::GameHandler& gameHandler) {
if (!gameHandler.showResurrectDialog()) return; if (!gameHandler.showResurrectDialog()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -976,7 +976,7 @@ void DialogManager::renderResurrectDialog(game::GameHandler& gameHandler) {
void DialogManager::renderTalentWipeConfirmDialog(game::GameHandler& gameHandler) { void DialogManager::renderTalentWipeConfirmDialog(game::GameHandler& gameHandler) {
if (!gameHandler.showTalentWipeConfirmDialog()) return; if (!gameHandler.showTalentWipeConfirmDialog()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -1046,7 +1046,7 @@ void DialogManager::renderTalentWipeConfirmDialog(game::GameHandler& gameHandler
void DialogManager::renderPetUnlearnConfirmDialog(game::GameHandler& gameHandler) { void DialogManager::renderPetUnlearnConfirmDialog(game::GameHandler& gameHandler) {
if (!gameHandler.showPetUnlearnDialog()) return; if (!gameHandler.showPetUnlearnDialog()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;

View file

@ -2,6 +2,7 @@
#include "ui/ui_colors.hpp" #include "ui/ui_colors.hpp"
#include "rendering/vk_context.hpp" #include "rendering/vk_context.hpp"
#include "core/application.hpp" #include "core/application.hpp"
#include "core/appearance_composer.hpp"
#include "addons/addon_manager.hpp" #include "addons/addon_manager.hpp"
#include "core/coordinates.hpp" #include "core/coordinates.hpp"
#include "core/input.hpp" #include "core/input.hpp"
@ -249,6 +250,22 @@ GameScreen::GameScreen() {
loadSettings(); loadSettings();
} }
// Section 3.5: Set UI services and propagate to child components
void GameScreen::setServices(const UIServices& services) {
services_ = services;
// Update legacy pointer for Phase A compatibility
appearanceComposer_ = services.appearanceComposer;
// Propagate to child panels
chatPanel_.setServices(services);
toastManager_.setServices(services);
dialogManager_.setServices(services);
settingsPanel_.setServices(services);
combatUI_.setServices(services);
socialPanel_.setServices(services);
actionBarPanel_.setServices(services);
windowManager_.setServices(services);
}
void GameScreen::render(game::GameHandler& gameHandler) { void GameScreen::render(game::GameHandler& gameHandler) {
// Set up chat bubble callback (once) and cache game handler in ChatPanel // Set up chat bubble callback (once) and cache game handler in ChatPanel
chatPanel_.setupCallbacks(gameHandler); chatPanel_.setupCallbacks(gameHandler);
@ -268,7 +285,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
uiErrors_.push_back({msg, 0.0f}); uiErrors_.push_back({msg, 0.0f});
if (uiErrors_.size() > 5) uiErrors_.erase(uiErrors_.begin()); if (uiErrors_.size() > 5) uiErrors_.erase(uiErrors_.begin());
// Play error sound for each new error (rate-limited by deque cap of 5) // Play error sound for each new error (rate-limited by deque cap of 5)
if (auto* r = core::Application::getInstance().getRenderer()) { if (auto* r = services_.renderer) {
if (auto* sfx = r->getUiSoundManager()) sfx->playError(); if (auto* sfx = r->getUiSoundManager()) sfx->playError();
} }
}); });
@ -291,7 +308,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
// Sync minimap opacity with UI opacity // Sync minimap opacity with UI opacity
{ {
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
if (renderer) { if (renderer) {
if (auto* minimap = renderer->getMinimap()) { if (auto* minimap = renderer->getMinimap()) {
minimap->setOpacity(settingsPanel_.uiOpacity_); minimap->setOpacity(settingsPanel_.uiOpacity_);
@ -301,7 +318,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
// Apply initial settings when renderer becomes available // Apply initial settings when renderer becomes available
if (!settingsPanel_.minimapSettingsApplied_) { if (!settingsPanel_.minimapSettingsApplied_) {
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
if (renderer) { if (renderer) {
if (auto* minimap = renderer->getMinimap()) { if (auto* minimap = renderer->getMinimap()) {
settingsPanel_.minimapRotate_ = false; settingsPanel_.minimapRotate_ = false;
@ -328,7 +345,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
// Apply saved volume settings once when audio managers first become available // Apply saved volume settings once when audio managers first become available
if (!settingsPanel_.volumeSettingsApplied_) { if (!settingsPanel_.volumeSettingsApplied_) {
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
if (renderer && renderer->getUiSoundManager()) { if (renderer && renderer->getUiSoundManager()) {
settingsPanel_.applyAudioVolumes(renderer); settingsPanel_.applyAudioVolumes(renderer);
settingsPanel_.volumeSettingsApplied_ = true; settingsPanel_.volumeSettingsApplied_ = true;
@ -337,7 +354,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
// Apply saved MSAA setting once when renderer is available // Apply saved MSAA setting once when renderer is available
if (!settingsPanel_.msaaSettingsApplied_ && settingsPanel_.pendingAntiAliasing > 0) { if (!settingsPanel_.msaaSettingsApplied_ && settingsPanel_.pendingAntiAliasing > 0) {
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
if (renderer) { if (renderer) {
static const VkSampleCountFlagBits aaSamples[] = { static const VkSampleCountFlagBits aaSamples[] = {
VK_SAMPLE_COUNT_1_BIT, VK_SAMPLE_COUNT_2_BIT, VK_SAMPLE_COUNT_1_BIT, VK_SAMPLE_COUNT_2_BIT,
@ -352,7 +369,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
// Apply saved FXAA setting once when renderer is available // Apply saved FXAA setting once when renderer is available
if (!settingsPanel_.fxaaSettingsApplied_) { if (!settingsPanel_.fxaaSettingsApplied_) {
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
if (renderer) { if (renderer) {
renderer->setFXAAEnabled(settingsPanel_.pendingFXAA); renderer->setFXAAEnabled(settingsPanel_.pendingFXAA);
settingsPanel_.fxaaSettingsApplied_ = true; settingsPanel_.fxaaSettingsApplied_ = true;
@ -361,7 +378,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
// Apply saved water refraction setting once when renderer is available // Apply saved water refraction setting once when renderer is available
if (!settingsPanel_.waterRefractionApplied_) { if (!settingsPanel_.waterRefractionApplied_) {
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
if (renderer) { if (renderer) {
renderer->setWaterRefractionEnabled(settingsPanel_.pendingWaterRefraction); renderer->setWaterRefractionEnabled(settingsPanel_.pendingWaterRefraction);
settingsPanel_.waterRefractionApplied_ = true; settingsPanel_.waterRefractionApplied_ = true;
@ -370,7 +387,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
// Apply saved normal mapping / POM settings once when WMO renderer is available // Apply saved normal mapping / POM settings once when WMO renderer is available
if (!settingsPanel_.normalMapSettingsApplied_) { if (!settingsPanel_.normalMapSettingsApplied_) {
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
if (renderer) { if (renderer) {
if (auto* wr = renderer->getWMORenderer()) { if (auto* wr = renderer->getWMORenderer()) {
wr->setNormalMappingEnabled(settingsPanel_.pendingNormalMapping); wr->setNormalMappingEnabled(settingsPanel_.pendingNormalMapping);
@ -390,7 +407,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
// Apply saved upscaling setting once when renderer is available // Apply saved upscaling setting once when renderer is available
if (!settingsPanel_.fsrSettingsApplied_) { if (!settingsPanel_.fsrSettingsApplied_) {
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
if (renderer) { if (renderer) {
static constexpr float fsrScales[] = { 0.77f, 0.67f, 0.59f, 1.00f }; static constexpr float fsrScales[] = { 0.77f, 0.67f, 0.59f, 1.00f };
settingsPanel_.pendingFSRQuality = std::clamp(settingsPanel_.pendingFSRQuality, 0, 3); settingsPanel_.pendingFSRQuality = std::clamp(settingsPanel_.pendingFSRQuality, 0, 3);
@ -561,7 +578,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
questLogScreen.render(gameHandler, inventoryScreen); questLogScreen.render(gameHandler, inventoryScreen);
// Spellbook (P key toggle handled inside) // Spellbook (P key toggle handled inside)
spellbookScreen.render(gameHandler, core::Application::getInstance().getAssetManager()); spellbookScreen.render(gameHandler, services_.assetManager);
// Insert spell link into chat if player shift-clicked a spellbook entry // Insert spell link into chat if player shift-clicked a spellbook entry
{ {
@ -578,7 +595,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
{ {
uint64_t activeGuid = gameHandler.getActiveCharacterGuid(); uint64_t activeGuid = gameHandler.getActiveCharacterGuid();
if (activeGuid != 0 && activeGuid != inventoryScreenCharGuid_) { if (activeGuid != 0 && activeGuid != inventoryScreenCharGuid_) {
auto* am = core::Application::getInstance().getAssetManager(); auto* am = services_.assetManager;
if (am) { if (am) {
inventoryScreen.setAssetManager(am); inventoryScreen.setAssetManager(am);
const auto* ch = gameHandler.getActiveCharacter(); const auto* ch = gameHandler.getActiveCharacter();
@ -631,10 +648,10 @@ void GameScreen::render(game::GameHandler& gameHandler) {
if (inventoryScreen.consumeEquipmentDirty() || gameHandler.consumeOnlineEquipmentDirty()) { if (inventoryScreen.consumeEquipmentDirty() || gameHandler.consumeOnlineEquipmentDirty()) {
updateCharacterGeosets(gameHandler.getInventory()); updateCharacterGeosets(gameHandler.getInventory());
updateCharacterTextures(gameHandler.getInventory()); updateCharacterTextures(gameHandler.getInventory());
core::Application::getInstance().loadEquippedWeapons(); if (appearanceComposer_) appearanceComposer_->loadEquippedWeapons();
inventoryScreen.markPreviewDirty(); inventoryScreen.markPreviewDirty();
// Update renderer weapon type for animation selection // Update renderer weapon type for animation selection
auto* r = core::Application::getInstance().getRenderer(); auto* r = services_.renderer;
if (r) { if (r) {
const auto& mh = gameHandler.getInventory().getEquipSlot(game::EquipSlot::MAIN_HAND); const auto& mh = gameHandler.getInventory().getEquipSlot(game::EquipSlot::MAIN_HAND);
r->setEquippedWeaponType(mh.empty() ? 0 : mh.item.inventoryType); r->setEquippedWeaponType(mh.empty() ? 0 : mh.item.inventoryType);
@ -642,7 +659,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
} }
// Update renderer face-target position and selection circle // Update renderer face-target position and selection circle
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
if (renderer) { if (renderer) {
renderer->setInCombat(gameHandler.isInCombat() && renderer->setInCombat(gameHandler.isInCombat() &&
!gameHandler.isPlayerDead() && !gameHandler.isPlayerDead() &&
@ -1201,9 +1218,9 @@ void GameScreen::processTargetInput(game::GameHandler& gameHandler) {
// Cursor affordance: show hand cursor over interactable entities. // Cursor affordance: show hand cursor over interactable entities.
if (!io.WantCaptureMouse) { if (!io.WantCaptureMouse) {
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
auto* camera = renderer ? renderer->getCamera() : nullptr; auto* camera = renderer ? renderer->getCamera() : nullptr;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
if (camera && window) { if (camera && window) {
glm::vec2 mousePos = input.getMousePosition(); glm::vec2 mousePos = input.getMousePosition();
float screenW = static_cast<float>(window->getWidth()); float screenW = static_cast<float>(window->getWidth());
@ -1257,9 +1274,9 @@ void GameScreen::processTargetInput(game::GameHandler& gameHandler) {
constexpr float CLICK_THRESHOLD = 5.0f; // pixels constexpr float CLICK_THRESHOLD = 5.0f; // pixels
if (dragDistSq < CLICK_THRESHOLD * CLICK_THRESHOLD) { if (dragDistSq < CLICK_THRESHOLD * CLICK_THRESHOLD) {
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
auto* camera = renderer ? renderer->getCamera() : nullptr; auto* camera = renderer ? renderer->getCamera() : nullptr;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
if (camera && window) { if (camera && window) {
float screenW = static_cast<float>(window->getWidth()); float screenW = static_cast<float>(window->getWidth());
@ -1354,9 +1371,9 @@ void GameScreen::processTargetInput(game::GameHandler& gameHandler) {
// If no target or right-clicking in world, try to pick one under cursor // If no target or right-clicking in world, try to pick one under cursor
{ {
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
auto* camera = renderer ? renderer->getCamera() : nullptr; auto* camera = renderer ? renderer->getCamera() : nullptr;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
if (camera && window) { if (camera && window) {
// If a quest objective gameobject is under the cursor, prefer it over // If a quest objective gameobject is under the cursor, prefer it over
// hostile units so quest pickups (e.g. "Bundle of Wood") are reliable. // hostile units so quest pickups (e.g. "Bundle of Wood") are reliable.
@ -1647,7 +1664,7 @@ void GameScreen::renderPlayerFrame(game::GameHandler& gameHandler) {
ImGui::TextColored(ImVec4(0.9f, 0.5f, 0.2f, 1.0f), "<DND>"); ImGui::TextColored(ImVec4(0.9f, 0.5f, 0.2f, 1.0f), "<DND>");
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Do not disturb — /dnd to cancel"); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Do not disturb — /dnd to cancel");
} }
if (auto* ren = core::Application::getInstance().getRenderer()) { if (auto* ren = services_.renderer) {
if (auto* cam = ren->getCameraController()) { if (auto* cam = ren->getCameraController()) {
if (cam->isAutoRunning()) { if (cam->isAutoRunning()) {
ImGui::SameLine(); ImGui::SameLine();
@ -2175,7 +2192,7 @@ void GameScreen::renderPetFrame(game::GameHandler& gameHandler) {
// Raw slot value layout (WotLK 3.3.5): low 24 bits = spell/action ID, // Raw slot value layout (WotLK 3.3.5): low 24 bits = spell/action ID,
// high byte = flag (0x80=autocast on, 0x40=can-autocast, 0x0C=type). // high byte = flag (0x80=autocast on, 0x40=can-autocast, 0x0C=type).
// Built-in commands: id=2 follow, id=3 stay/move, id=5 attack. // Built-in commands: id=2 follow, id=3 stay/move, id=5 attack.
auto* assetMgr = core::Application::getInstance().getAssetManager(); auto* assetMgr = services_.assetManager;
const float iconSz = 20.0f; const float iconSz = 20.0f;
const float spacing = 2.0f; const float spacing = 2.0f;
ImGui::Separator(); ImGui::Separator();
@ -2278,7 +2295,7 @@ void GameScreen::renderPetFrame(game::GameHandler& gameHandler) {
else if (actionId == 6) tip = "Aggressive"; else if (actionId == 6) tip = "Aggressive";
if (tip) ImGui::SetTooltip("%s", tip); if (tip) ImGui::SetTooltip("%s", tip);
} else if (actionId > 6) { } else if (actionId > 6) {
auto* spellAsset = core::Application::getInstance().getAssetManager(); auto* spellAsset = services_.assetManager;
ImGui::BeginTooltip(); ImGui::BeginTooltip();
bool richOk = spellbookScreen.renderSpellInfoTooltip(actionId, gameHandler, spellAsset); bool richOk = spellbookScreen.renderSpellInfoTooltip(actionId, gameHandler, spellAsset);
if (!richOk) { if (!richOk) {
@ -2399,7 +2416,7 @@ void GameScreen::renderTargetFrame(game::GameHandler& gameHandler) {
auto target = gameHandler.getTarget(); auto target = gameHandler.getTarget();
if (!target) return; if (!target) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float frameW = 250.0f; float frameW = 250.0f;
@ -2838,7 +2855,7 @@ void GameScreen::renderTargetFrame(game::GameHandler& gameHandler) {
else else
snprintf(castLabel, sizeof(castLabel), "Casting... (%.1fs)", castLeft); snprintf(castLabel, sizeof(castLabel), "Casting... (%.1fs)", castLeft);
{ {
auto* tcastAsset = core::Application::getInstance().getAssetManager(); auto* tcastAsset = services_.assetManager;
VkDescriptorSet tIcon = (tspell != 0 && tcastAsset) VkDescriptorSet tIcon = (tspell != 0 && tcastAsset)
? getSpellIcon(tspell, tcastAsset) : VK_NULL_HANDLE; ? getSpellIcon(tspell, tcastAsset) : VK_NULL_HANDLE;
if (tIcon) { if (tIcon) {
@ -2935,7 +2952,7 @@ void GameScreen::renderTargetFrame(game::GameHandler& gameHandler) {
if (!a.isEmpty()) activeAuras++; if (!a.isEmpty()) activeAuras++;
} }
if (activeAuras > 0) { if (activeAuras > 0) {
auto* assetMgr = core::Application::getInstance().getAssetManager(); auto* assetMgr = services_.assetManager;
constexpr float ICON_SIZE = 24.0f; constexpr float ICON_SIZE = 24.0f;
constexpr int ICONS_PER_ROW = 8; constexpr int ICONS_PER_ROW = 8;
@ -3233,7 +3250,7 @@ void GameScreen::renderTargetFrame(game::GameHandler& gameHandler) {
int totActive = 0; int totActive = 0;
for (const auto& a : *totAuras) if (!a.isEmpty()) totActive++; for (const auto& a : *totAuras) if (!a.isEmpty()) totActive++;
if (totActive > 0) { if (totActive > 0) {
auto* totAsset = core::Application::getInstance().getAssetManager(); auto* totAsset = services_.assetManager;
constexpr float TA_ICON = 16.0f; constexpr float TA_ICON = 16.0f;
constexpr int TA_PER_ROW = 8; constexpr int TA_PER_ROW = 8;
@ -3349,7 +3366,7 @@ void GameScreen::renderFocusFrame(game::GameHandler& gameHandler) {
auto focus = gameHandler.getFocus(); auto focus = gameHandler.getFocus();
if (!focus) return; if (!focus) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
// Position: right side of screen, mirroring the target frame on the opposite side // Position: right side of screen, mirroring the target frame on the opposite side
@ -3651,7 +3668,7 @@ void GameScreen::renderFocusFrame(game::GameHandler& gameHandler) {
else else
snprintf(castBuf, sizeof(castBuf), "Casting... (%.1fs)", rem); snprintf(castBuf, sizeof(castBuf), "Casting... (%.1fs)", rem);
{ {
auto* fcAsset = core::Application::getInstance().getAssetManager(); auto* fcAsset = services_.assetManager;
VkDescriptorSet fcIcon = (focusCast->spellId != 0 && fcAsset) VkDescriptorSet fcIcon = (focusCast->spellId != 0 && fcAsset)
? getSpellIcon(focusCast->spellId, fcAsset) : VK_NULL_HANDLE; ? getSpellIcon(focusCast->spellId, fcAsset) : VK_NULL_HANDLE;
if (fcIcon) { if (fcIcon) {
@ -3677,7 +3694,7 @@ void GameScreen::renderFocusFrame(game::GameHandler& gameHandler) {
int activeCount = 0; int activeCount = 0;
for (const auto& a : *focusAuras) if (!a.isEmpty()) activeCount++; for (const auto& a : *focusAuras) if (!a.isEmpty()) activeCount++;
if (activeCount > 0) { if (activeCount > 0) {
auto* focusAsset = core::Application::getInstance().getAssetManager(); auto* focusAsset = services_.assetManager;
constexpr float FA_ICON = 20.0f; constexpr float FA_ICON = 20.0f;
constexpr int FA_PER_ROW = 10; constexpr int FA_PER_ROW = 10;
@ -4348,7 +4365,7 @@ VkDescriptorSet GameScreen::getSpellIcon(uint32_t spellId, pipeline::AssetManage
} }
// Upload to Vulkan via VkContext // Upload to Vulkan via VkContext
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
auto* vkCtx = window ? window->getVkContext() : nullptr; auto* vkCtx = window ? window->getVkContext() : nullptr;
if (!vkCtx) { if (!vkCtx) {
spellIconCache_[spellId] = VK_NULL_HANDLE; spellIconCache_[spellId] = VK_NULL_HANDLE;
@ -4455,7 +4472,7 @@ void GameScreen::renderQuestObjectiveTracker(game::GameHandler& gameHandler) {
const auto& questLog = gameHandler.getQuestLog(); const auto& questLog = gameHandler.getQuestLog();
if (questLog.empty()) return; if (questLog.empty()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
constexpr float TRACKER_W = 220.0f; constexpr float TRACKER_W = 220.0f;
@ -4693,12 +4710,12 @@ void GameScreen::renderNameplates(game::GameHandler& gameHandler) {
// Reset mouseover each frame; we'll set it below when the cursor is over a nameplate // Reset mouseover each frame; we'll set it below when the cursor is over a nameplate
gameHandler.setMouseoverGuid(0); gameHandler.setMouseoverGuid(0);
auto* appRenderer = core::Application::getInstance().getRenderer(); auto* appRenderer = services_.renderer;
if (!appRenderer) return; if (!appRenderer) return;
rendering::Camera* camera = appRenderer->getCamera(); rendering::Camera* camera = appRenderer->getCamera();
if (!camera) return; if (!camera) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
if (!window) return; if (!window) return;
const float screenW = static_cast<float>(window->getWidth()); const float screenW = static_cast<float>(window->getWidth());
const float screenH = static_cast<float>(window->getHeight()); const float screenH = static_cast<float>(window->getHeight());
@ -4907,7 +4924,7 @@ void GameScreen::renderNameplates(game::GameHandler& gameHandler) {
// Spell icon + name above the cast bar // Spell icon + name above the cast bar
const std::string& spellName = gameHandler.getSpellName(cs->spellId); const std::string& spellName = gameHandler.getSpellName(cs->spellId);
{ {
auto* castAm = core::Application::getInstance().getAssetManager(); auto* castAm = services_.assetManager;
VkDescriptorSet castIcon = (cs->spellId && castAm) VkDescriptorSet castIcon = (cs->spellId && castAm)
? getSpellIcon(cs->spellId, castAm) : VK_NULL_HANDLE; ? getSpellIcon(cs->spellId, castAm) : VK_NULL_HANDLE;
float iconSz = cbH + 8.0f; float iconSz = cbH + 8.0f;
@ -5300,7 +5317,7 @@ void GameScreen::renderNameplates(game::GameHandler& gameHandler) {
// ============================================================ // ============================================================
void GameScreen::takeScreenshot(game::GameHandler& /*gameHandler*/) { void GameScreen::takeScreenshot(game::GameHandler& /*gameHandler*/) {
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
if (!renderer) return; if (!renderer) return;
// Build path: ~/.wowee/screenshots/WoWee_YYYYMMDD_HHMMSS.png // Build path: ~/.wowee/screenshots/WoWee_YYYYMMDD_HHMMSS.png
@ -5331,7 +5348,7 @@ void GameScreen::takeScreenshot(game::GameHandler& /*gameHandler*/) {
sysMsg.type = game::ChatType::SYSTEM; sysMsg.type = game::ChatType::SYSTEM;
sysMsg.language = game::ChatLanguage::UNIVERSAL; sysMsg.language = game::ChatLanguage::UNIVERSAL;
sysMsg.message = "Screenshot saved: " + path; sysMsg.message = "Screenshot saved: " + path;
core::Application::getInstance().getGameHandler()->addLocalChatMessage(sysMsg); services_.gameHandler->addLocalChatMessage(sysMsg);
} }
} }
@ -5411,7 +5428,7 @@ void GameScreen::renderUIErrors(game::GameHandler& /*gameHandler*/, float deltaT
if (uiErrors_.empty()) return; if (uiErrors_.empty()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -5550,9 +5567,9 @@ void GameScreen::renderQuestMarkers(game::GameHandler& gameHandler) {
const auto& statuses = gameHandler.getNpcQuestStatuses(); const auto& statuses = gameHandler.getNpcQuestStatuses();
if (statuses.empty()) return; if (statuses.empty()) return;
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
auto* camera = renderer ? renderer->getCamera() : nullptr; auto* camera = renderer ? renderer->getCamera() : nullptr;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
if (!camera || !window) return; if (!camera || !window) return;
float screenW = static_cast<float>(window->getWidth()); float screenW = static_cast<float>(window->getWidth());
@ -5628,10 +5645,10 @@ void GameScreen::renderQuestMarkers(game::GameHandler& gameHandler) {
void GameScreen::renderMinimapMarkers(game::GameHandler& gameHandler) { void GameScreen::renderMinimapMarkers(game::GameHandler& gameHandler) {
const auto& statuses = gameHandler.getNpcQuestStatuses(); const auto& statuses = gameHandler.getNpcQuestStatuses();
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
auto* camera = renderer ? renderer->getCamera() : nullptr; auto* camera = renderer ? renderer->getCamera() : nullptr;
auto* minimap = renderer ? renderer->getMinimap() : nullptr; auto* minimap = renderer ? renderer->getMinimap() : nullptr;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
if (!camera || !minimap || !window) return; if (!camera || !minimap || !window) return;
float screenW = static_cast<float>(window->getWidth()); float screenW = static_cast<float>(window->getWidth());
@ -6508,7 +6525,7 @@ void GameScreen::renderMinimapMarkers(game::GameHandler& gameHandler) {
} }
auto applyMuteState = [&]() { auto applyMuteState = [&]() {
auto* activeRenderer = core::Application::getInstance().getRenderer(); auto* activeRenderer = services_.renderer;
float masterScale = settingsPanel_.soundMuted_ ? 0.0f : static_cast<float>(settingsPanel_.pendingMasterVolume) / 100.0f; float masterScale = settingsPanel_.soundMuted_ ? 0.0f : static_cast<float>(settingsPanel_.pendingMasterVolume) / 100.0f;
audio::AudioEngine::instance().setMasterVolume(masterScale); audio::AudioEngine::instance().setMasterVolume(masterScale);
if (!activeRenderer) return; if (!activeRenderer) return;
@ -6842,7 +6859,7 @@ void GameScreen::renderMinimapMarkers(game::GameHandler& gameHandler) {
// Calendar pending invites indicator (WotLK only) // Calendar pending invites indicator (WotLK only)
{ {
auto* expReg = core::Application::getInstance().getExpansionRegistry(); auto* expReg = services_.expansionRegistry;
bool isWotLK = expReg && expReg->getActive() && expReg->getActive()->id == "wotlk"; bool isWotLK = expReg && expReg->getActive() && expReg->getActive()->id == "wotlk";
if (isWotLK) { if (isWotLK) {
uint32_t calPending = gameHandler.getCalendarPendingInvites(); uint32_t calPending = gameHandler.getCalendarPendingInvites();
@ -7196,7 +7213,7 @@ void GameScreen::loadSettings() {
else if (key == "shadow_distance") settingsPanel_.pendingShadowDistance = std::clamp(std::stof(val), 40.0f, 500.0f); else if (key == "shadow_distance") settingsPanel_.pendingShadowDistance = std::clamp(std::stof(val), 40.0f, 500.0f);
else if (key == "brightness") { else if (key == "brightness") {
settingsPanel_.pendingBrightness = std::clamp(std::stoi(val), 0, 100); settingsPanel_.pendingBrightness = std::clamp(std::stoi(val), 0, 100);
if (auto* r = core::Application::getInstance().getRenderer()) if (auto* r = services_.renderer)
r->setBrightness(static_cast<float>(settingsPanel_.pendingBrightness) / 50.0f); r->setBrightness(static_cast<float>(settingsPanel_.pendingBrightness) / 50.0f);
} }
else if (key == "water_refraction") settingsPanel_.pendingWaterRefraction = (std::stoi(val) != 0); else if (key == "water_refraction") settingsPanel_.pendingWaterRefraction = (std::stoi(val) != 0);
@ -7228,7 +7245,7 @@ void GameScreen::loadSettings() {
else if (key == "camera_pivot_height") settingsPanel_.pendingPivotHeight = std::clamp(std::stof(val), 0.0f, 3.0f); else if (key == "camera_pivot_height") settingsPanel_.pendingPivotHeight = std::clamp(std::stof(val), 0.0f, 3.0f);
else if (key == "fov") { else if (key == "fov") {
settingsPanel_.pendingFov = std::clamp(std::stof(val), 45.0f, 110.0f); settingsPanel_.pendingFov = std::clamp(std::stof(val), 45.0f, 110.0f);
if (auto* renderer = core::Application::getInstance().getRenderer()) { if (auto* renderer = services_.renderer) {
if (auto* camera = renderer->getCamera()) camera->setFov(settingsPanel_.pendingFov); if (auto* camera = renderer->getCamera()) camera->setFov(settingsPanel_.pendingFov);
} }
} }

View file

@ -152,7 +152,7 @@ ImGui::EndChild();
void SettingsPanel::renderSettingsGameplayTab(InventoryScreen& inventoryScreen, void SettingsPanel::renderSettingsGameplayTab(InventoryScreen& inventoryScreen,
std::function<void()> saveCallback) { std::function<void()> saveCallback) {
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
ImGui::Spacing(); ImGui::Spacing();
ImGui::Text("Controls"); ImGui::Text("Controls");
@ -433,7 +433,7 @@ if (ImGui::Button("Reset to Defaults", ImVec2(-1, 0))) {
} }
void SettingsPanel::renderSettingsAudioTab(std::function<void()> saveCallback) { void SettingsPanel::renderSettingsAudioTab(std::function<void()> saveCallback) {
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
ImGui::Spacing(); ImGui::Spacing();
ImGui::BeginChild("AudioSettings", ImVec2(0, 360), true); ImGui::BeginChild("AudioSettings", ImVec2(0, 360), true);
@ -599,8 +599,8 @@ void SettingsPanel::renderSettingsWindow(InventoryScreen& inventoryScreen, ChatP
std::function<void()> saveCallback) { std::function<void()> saveCallback) {
if (!showSettingsWindow) return; if (!showSettingsWindow) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
if (!window) return; if (!window) return;
static constexpr int kResolutions[][2] = { static constexpr int kResolutions[][2] = {
@ -1045,7 +1045,7 @@ void SettingsPanel::renderSettingsWindow(InventoryScreen& inventoryScreen, ChatP
} }
void SettingsPanel::applyGraphicsPreset(GraphicsPreset preset) { void SettingsPanel::applyGraphicsPreset(GraphicsPreset preset) {
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
// Define preset values based on quality level // Define preset values based on quality level
switch (preset) { switch (preset) {

View file

@ -86,7 +86,7 @@ void SocialPanel::renderPartyFrames(game::GameHandler& gameHandler,
SpellIconFn getSpellIcon) { SpellIconFn getSpellIcon) {
if (!gameHandler.isInGroup()) return; if (!gameHandler.isInGroup()) return;
auto* assetMgr = core::Application::getInstance().getAssetManager(); auto* assetMgr = services_.assetManager;
const auto& partyData = gameHandler.getPartyData(); const auto& partyData = gameHandler.getPartyData();
const bool isRaid = (partyData.groupType == 1); const bool isRaid = (partyData.groupType == 1);
float frameY = 120.0f; float frameY = 120.0f;
@ -117,7 +117,7 @@ void SocialPanel::renderPartyFrames(game::GameHandler& gameHandler,
float winW = activeSgs * (CELL_W + CELL_PAD) + CELL_PAD + 8.0f; float winW = activeSgs * (CELL_W + CELL_PAD) + CELL_PAD + 8.0f;
float winH = MAX_PER_GROUP * (CELL_H + CELL_PAD) + CELL_PAD + 20.0f; float winH = MAX_PER_GROUP * (CELL_H + CELL_PAD) + CELL_PAD + 20.0f;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
float raidX = (screenW - winW) / 2.0f; float raidX = (screenW - winW) / 2.0f;
@ -757,7 +757,7 @@ void SocialPanel::renderPartyFrames(game::GameHandler& gameHandler,
void SocialPanel::renderBossFrames(game::GameHandler& gameHandler, void SocialPanel::renderBossFrames(game::GameHandler& gameHandler,
SpellbookScreen& spellbookScreen, SpellbookScreen& spellbookScreen,
SpellIconFn getSpellIcon) { SpellIconFn getSpellIcon) {
auto* assetMgr = core::Application::getInstance().getAssetManager(); auto* assetMgr = services_.assetManager;
// Collect active boss unit slots // Collect active boss unit slots
struct BossSlot { uint32_t slot; uint64_t guid; }; struct BossSlot { uint32_t slot; uint64_t guid; };
@ -1143,11 +1143,11 @@ void SocialPanel::renderGuildRoster(game::GameHandler& gameHandler,
// Get zone manager for name lookup // Get zone manager for name lookup
game::ZoneManager* zoneManager = nullptr; game::ZoneManager* zoneManager = nullptr;
if (auto* renderer = core::Application::getInstance().getRenderer()) { if (auto* renderer = services_.renderer) {
zoneManager = renderer->getZoneManager(); zoneManager = renderer->getZoneManager();
} }
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -1683,7 +1683,7 @@ void SocialPanel::renderSocialFrame(game::GameHandler& gameHandler,
for (const auto& c : contacts) for (const auto& c : contacts)
if (c.isFriend() && c.isOnline()) ++onlineCount; if (c.isFriend() && c.isOnline()) ++onlineCount;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
ImGui::SetNextWindowPos(ImVec2(screenW - 230.0f, 240.0f), ImGuiCond_Once); ImGui::SetNextWindowPos(ImVec2(screenW - 230.0f, 240.0f), ImGuiCond_Once);
@ -1705,7 +1705,7 @@ void SocialPanel::renderSocialFrame(game::GameHandler& gameHandler,
// Get zone manager for area name lookups // Get zone manager for area name lookups
game::ZoneManager* socialZoneMgr = nullptr; game::ZoneManager* socialZoneMgr = nullptr;
if (auto* rend = core::Application::getInstance().getRenderer()) if (auto* rend = services_.renderer)
socialZoneMgr = rend->getZoneManager(); socialZoneMgr = rend->getZoneManager();
if (ImGui::BeginTabBar("##SocialTabs")) { if (ImGui::BeginTabBar("##SocialTabs")) {
@ -2048,7 +2048,7 @@ void SocialPanel::renderDungeonFinderWindow(game::GameHandler& gameHandler,
if (!showDungeonFinder_) return; if (!showDungeonFinder_) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -2423,7 +2423,7 @@ void SocialPanel::renderInspectWindow(game::GameHandler& gameHandler,
// Lazy-load SpellItemEnchantment.dbc for enchant name lookup // Lazy-load SpellItemEnchantment.dbc for enchant name lookup
static std::unordered_map<uint32_t, std::string> s_enchantNames; static std::unordered_map<uint32_t, std::string> s_enchantNames;
static bool s_enchantDbLoaded = false; static bool s_enchantDbLoaded = false;
auto* assetMgrEnchant = core::Application::getInstance().getAssetManager(); auto* assetMgrEnchant = services_.assetManager;
if (!s_enchantDbLoaded && assetMgrEnchant && assetMgrEnchant->isInitialized()) { if (!s_enchantDbLoaded && assetMgrEnchant && assetMgrEnchant->isInitialized()) {
s_enchantDbLoaded = true; s_enchantDbLoaded = true;
auto dbc = assetMgrEnchant->loadDBC("SpellItemEnchantment.dbc"); auto dbc = assetMgrEnchant->loadDBC("SpellItemEnchantment.dbc");

View file

@ -128,7 +128,7 @@ void ToastManager::setupCallbacks(game::GameHandler& gameHandler) {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
void ToastManager::renderEarlyToasts(float deltaTime, game::GameHandler& gameHandler) { void ToastManager::renderEarlyToasts(float deltaTime, game::GameHandler& gameHandler) {
// Zone entry detection — fire a toast when the renderer's zone name changes // Zone entry detection — fire a toast when the renderer's zone name changes
if (auto* rend = core::Application::getInstance().getRenderer()) { if (auto* rend = services_.renderer) {
const std::string& curZone = rend->getCurrentZoneName(); const std::string& curZone = rend->getCurrentZoneName();
if (!curZone.empty() && curZone != lastKnownZone_) { if (!curZone.empty() && curZone != lastKnownZone_) {
if (!lastKnownZone_.empty()) { if (!lastKnownZone_.empty()) {
@ -175,7 +175,7 @@ void ToastManager::renderRepToasts(float deltaTime) {
if (repToasts_.empty()) return; if (repToasts_.empty()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -254,7 +254,7 @@ void ToastManager::renderQuestCompleteToasts(float deltaTime) {
if (questCompleteToasts_.empty()) return; if (questCompleteToasts_.empty()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -329,7 +329,7 @@ void ToastManager::renderZoneToasts(float deltaTime) {
if (zoneToasts_.empty()) return; if (zoneToasts_.empty()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
ImDrawList* draw = ImGui::GetForegroundDrawList(); ImDrawList* draw = ImGui::GetForegroundDrawList();
@ -395,7 +395,7 @@ void ToastManager::renderAreaTriggerToasts(float deltaTime, game::GameHandler& g
areaTriggerToasts_.end()); areaTriggerToasts_.end());
if (areaTriggerToasts_.empty()) return; if (areaTriggerToasts_.empty()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -461,7 +461,7 @@ void ToastManager::triggerDing(uint32_t newLevel, uint32_t hpDelta, uint32_t man
dingStats_[3] = intel; dingStats_[3] = intel;
dingStats_[4] = spi; dingStats_[4] = spi;
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
if (renderer) { if (renderer) {
if (auto* sfx = renderer->getUiSoundManager()) { if (auto* sfx = renderer->getUiSoundManager()) {
sfx->playLevelUp(); sfx->playLevelUp();
@ -550,7 +550,7 @@ void ToastManager::triggerAchievementToast(uint32_t achievementId, std::string n
achievementToastTimer_ = ACHIEVEMENT_TOAST_DURATION; achievementToastTimer_ = ACHIEVEMENT_TOAST_DURATION;
// Play a UI sound if available // Play a UI sound if available
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
if (renderer) { if (renderer) {
if (auto* sfx = renderer->getUiSoundManager()) { if (auto* sfx = renderer->getUiSoundManager()) {
sfx->playAchievementAlert(); sfx->playAchievementAlert();
@ -565,7 +565,7 @@ void ToastManager::renderAchievementToast() {
achievementToastTimer_ -= dt; achievementToastTimer_ -= dt;
if (achievementToastTimer_ < 0.0f) achievementToastTimer_ = 0.0f; if (achievementToastTimer_ < 0.0f) achievementToastTimer_ = 0.0f;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -641,7 +641,7 @@ void ToastManager::renderDiscoveryToast() {
alpha = 1.0f; alpha = 1.0f;
alpha = std::clamp(alpha, 0.0f, 1.0f); alpha = std::clamp(alpha, 0.0f, 1.0f);
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -1183,7 +1183,7 @@ void ToastManager::renderZoneText(game::GameHandler& gameHandler) {
// Also poll the renderer for zone name changes (covers map-level transitions // Also poll the renderer for zone name changes (covers map-level transitions
// where worldStateZoneId may not change immediately). // where worldStateZoneId may not change immediately).
auto* appRenderer = core::Application::getInstance().getRenderer(); auto* appRenderer = services_.renderer;
if (appRenderer) { if (appRenderer) {
const std::string& zoneName = appRenderer->getCurrentZoneName(); const std::string& zoneName = appRenderer->getCurrentZoneName();
if (!zoneName.empty() && zoneName != lastKnownZoneName_) { if (!zoneName.empty() && zoneName != lastKnownZoneName_) {
@ -1202,7 +1202,7 @@ void ToastManager::renderZoneText(game::GameHandler& gameHandler) {
zoneTextTimer_ -= dt; zoneTextTimer_ -= dt;
if (zoneTextTimer_ < 0.0f) zoneTextTimer_ = 0.0f; if (zoneTextTimer_ < 0.0f) zoneTextTimer_ = 0.0f;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;

View file

@ -83,7 +83,7 @@ void WindowManager::renderLootWindow(game::GameHandler& gameHandler,
ChatPanel& chatPanel) { ChatPanel& chatPanel) {
if (!gameHandler.isLootWindowOpen()) return; if (!gameHandler.isLootWindowOpen()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 150, 200), ImGuiCond_Appearing); ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 150, 200), ImGuiCond_Appearing);
@ -273,7 +273,7 @@ void WindowManager::renderGossipWindow(game::GameHandler& gameHandler,
ChatPanel& chatPanel) { ChatPanel& chatPanel) {
if (!gameHandler.isGossipWindowOpen()) return; if (!gameHandler.isGossipWindowOpen()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 200, 150), ImGuiCond_Appearing); ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 200, 150), ImGuiCond_Appearing);
@ -445,7 +445,7 @@ void WindowManager::renderQuestDetailsWindow(game::GameHandler& gameHandler,
InventoryScreen& inventoryScreen) { InventoryScreen& inventoryScreen) {
if (!gameHandler.isQuestDetailsOpen()) return; if (!gameHandler.isQuestDetailsOpen()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -569,7 +569,7 @@ void WindowManager::renderQuestRequestItemsWindow(game::GameHandler& gameHandler
InventoryScreen& inventoryScreen) { InventoryScreen& inventoryScreen) {
if (!gameHandler.isQuestRequestItemsOpen()) return; if (!gameHandler.isQuestRequestItemsOpen()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -672,7 +672,7 @@ void WindowManager::renderQuestOfferRewardWindow(game::GameHandler& gameHandler,
InventoryScreen& inventoryScreen) { InventoryScreen& inventoryScreen) {
if (!gameHandler.isQuestOfferRewardOpen()) return; if (!gameHandler.isQuestOfferRewardOpen()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -845,7 +845,7 @@ void WindowManager::renderQuestOfferRewardWindow(game::GameHandler& gameHandler,
void WindowManager::loadExtendedCostDBC() { void WindowManager::loadExtendedCostDBC() {
if (extendedCostDbLoaded_) return; if (extendedCostDbLoaded_) return;
extendedCostDbLoaded_ = true; extendedCostDbLoaded_ = true;
auto* am = core::Application::getInstance().getAssetManager(); auto* am = services_.assetManager;
if (!am || !am->isInitialized()) return; if (!am || !am->isInitialized()) return;
auto dbc = am->loadDBC("ItemExtendedCost.dbc"); auto dbc = am->loadDBC("ItemExtendedCost.dbc");
if (!dbc || !dbc->isLoaded()) return; if (!dbc || !dbc->isLoaded()) return;
@ -898,7 +898,7 @@ void WindowManager::renderVendorWindow(game::GameHandler& gameHandler,
ChatPanel& chatPanel) { ChatPanel& chatPanel) {
if (!gameHandler.isVendorWindowOpen()) return; if (!gameHandler.isVendorWindowOpen()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 200, 100), ImGuiCond_Appearing); ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 200, 100), ImGuiCond_Appearing);
@ -1236,9 +1236,9 @@ void WindowManager::renderTrainerWindow(game::GameHandler& gameHandler,
SpellIconFn getSpellIcon) { SpellIconFn getSpellIcon) {
if (!gameHandler.isTrainerWindowOpen()) return; if (!gameHandler.isTrainerWindowOpen()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
auto* assetMgr = core::Application::getInstance().getAssetManager(); auto* assetMgr = services_.assetManager;
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 225, 100), ImGuiCond_Appearing); ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 225, 100), ImGuiCond_Appearing);
ImGui::SetNextWindowSize(ImVec2(500, 450), ImGuiCond_Appearing); ImGui::SetNextWindowSize(ImVec2(500, 450), ImGuiCond_Appearing);
@ -1701,7 +1701,7 @@ void WindowManager::renderEscapeMenu(SettingsPanel& settingsPanel) {
settingsPanel.showEscapeSettingsNotice = false; settingsPanel.showEscapeSettingsNotice = false;
} }
if (ImGui::Button("Quit", ImVec2(-1, 0))) { if (ImGui::Button("Quit", ImVec2(-1, 0))) {
auto* renderer = core::Application::getInstance().getRenderer(); auto* renderer = services_.renderer;
if (renderer) { if (renderer) {
if (auto* music = renderer->getMusicManager()) { if (auto* music = renderer->getMusicManager()) {
music->stopMusic(0.0f); music->stopMusic(0.0f);
@ -1763,7 +1763,7 @@ void WindowManager::renderBarberShopWindow(game::GameHandler& gameHandler) {
int maxHairColor = static_cast<int>(game::getMaxHairColor(raceEnum, gender)); int maxHairColor = static_cast<int>(game::getMaxHairColor(raceEnum, gender));
int maxFacialHair = static_cast<int>(game::getMaxFacialFeature(raceEnum, gender)); int maxFacialHair = static_cast<int>(game::getMaxFacialFeature(raceEnum, gender));
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
float winW = 300.0f; float winW = 300.0f;
@ -1847,7 +1847,7 @@ void WindowManager::renderBarberShopWindow(game::GameHandler& gameHandler) {
void WindowManager::renderStableWindow(game::GameHandler& gameHandler) { void WindowManager::renderStableWindow(game::GameHandler& gameHandler) {
if (!gameHandler.isStableWindowOpen()) return; if (!gameHandler.isStableWindowOpen()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -1960,7 +1960,7 @@ void WindowManager::renderStableWindow(game::GameHandler& gameHandler) {
void WindowManager::renderTaxiWindow(game::GameHandler& gameHandler) { void WindowManager::renderTaxiWindow(game::GameHandler& gameHandler) {
if (!gameHandler.isTaxiWindowOpen()) return; if (!gameHandler.isTaxiWindowOpen()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 200, 150), ImGuiCond_Appearing); ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 200, 150), ImGuiCond_Appearing);
@ -2058,7 +2058,7 @@ void WindowManager::renderTaxiWindow(game::GameHandler& gameHandler) {
void WindowManager::renderLogoutCountdown(game::GameHandler& gameHandler) { void WindowManager::renderLogoutCountdown(game::GameHandler& gameHandler) {
if (!gameHandler.isLoggingOut()) return; if (!gameHandler.isLoggingOut()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -2127,7 +2127,7 @@ void WindowManager::renderDeathScreen(game::GameHandler& gameHandler) {
deathElapsed_ += dt; deathElapsed_ += dt;
} }
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -2216,7 +2216,7 @@ void WindowManager::renderDeathScreen(game::GameHandler& gameHandler) {
void WindowManager::renderReclaimCorpseButton(game::GameHandler& gameHandler) { void WindowManager::renderReclaimCorpseButton(game::GameHandler& gameHandler) {
if (!gameHandler.isPlayerGhost() || !gameHandler.canReclaimCorpse()) return; if (!gameHandler.isPlayerGhost() || !gameHandler.canReclaimCorpse()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -2274,7 +2274,7 @@ void WindowManager::renderMailWindow(game::GameHandler& gameHandler,
ChatPanel& chatPanel) { ChatPanel& chatPanel) {
if (!gameHandler.isMailboxOpen()) return; if (!gameHandler.isMailboxOpen()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 250, 80), ImGuiCond_Appearing); ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 250, 80), ImGuiCond_Appearing);
@ -2553,7 +2553,7 @@ void WindowManager::renderMailComposeWindow(game::GameHandler& gameHandler,
InventoryScreen& inventoryScreen) { InventoryScreen& inventoryScreen) {
if (!gameHandler.isMailComposeOpen()) return; if (!gameHandler.isMailComposeOpen()) return;
auto* window = core::Application::getInstance().getWindow(); auto* window = services_.window;
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f; float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f; float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
@ -3769,7 +3769,7 @@ void WindowManager::renderAchievementWindow(game::GameHandler& gameHandler) {
static bool s_criteriaDataLoaded = false; static bool s_criteriaDataLoaded = false;
if (!s_criteriaDataLoaded) { if (!s_criteriaDataLoaded) {
s_criteriaDataLoaded = true; s_criteriaDataLoaded = true;
auto* am = core::Application::getInstance().getAssetManager(); auto* am = services_.assetManager;
if (am && am->isInitialized()) { if (am && am->isInitialized()) {
auto dbc = am->loadDBC("AchievementCriteria.dbc"); auto dbc = am->loadDBC("AchievementCriteria.dbc");
if (dbc && dbc->isLoaded() && dbc->getFieldCount() >= 10) { if (dbc && dbc->isLoaded() && dbc->getFieldCount() >= 10) {