mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-03 20:03:50 +00:00
Merge pull request #38 from ldmonster/chore/renderer-refactor
[chore] refactor: renderer extract domains
This commit is contained in:
commit
0a4cc9ab96
27 changed files with 4880 additions and 4207 deletions
|
|
@ -611,6 +611,9 @@ set(WOWEE_SOURCES
|
||||||
src/rendering/mount_dust.cpp
|
src/rendering/mount_dust.cpp
|
||||||
src/rendering/levelup_effect.cpp
|
src/rendering/levelup_effect.cpp
|
||||||
src/rendering/charge_effect.cpp
|
src/rendering/charge_effect.cpp
|
||||||
|
src/rendering/spell_visual_system.cpp
|
||||||
|
src/rendering/post_process_pipeline.cpp
|
||||||
|
src/rendering/animation_controller.cpp
|
||||||
src/rendering/loading_screen.cpp
|
src/rendering/loading_screen.cpp
|
||||||
|
|
||||||
# UI
|
# UI
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
namespace wowee {
|
namespace wowee {
|
||||||
namespace rendering { class Renderer; }
|
namespace rendering { class Renderer; }
|
||||||
namespace pipeline { class AssetManager; }
|
namespace pipeline { class AssetManager; }
|
||||||
|
namespace audio { class AudioCoordinator; }
|
||||||
namespace game { class ExpansionRegistry; }
|
namespace game { class ExpansionRegistry; }
|
||||||
|
|
||||||
namespace game {
|
namespace game {
|
||||||
|
|
@ -13,6 +14,7 @@ namespace game {
|
||||||
// Replaces hidden Application::getInstance() singleton access.
|
// Replaces hidden Application::getInstance() singleton access.
|
||||||
struct GameServices {
|
struct GameServices {
|
||||||
rendering::Renderer* renderer = nullptr;
|
rendering::Renderer* renderer = nullptr;
|
||||||
|
audio::AudioCoordinator* audioCoordinator = nullptr;
|
||||||
pipeline::AssetManager* assetManager = nullptr;
|
pipeline::AssetManager* assetManager = nullptr;
|
||||||
ExpansionRegistry* expansionRegistry = nullptr;
|
ExpansionRegistry* expansionRegistry = nullptr;
|
||||||
uint32_t gryphonDisplayId = 0;
|
uint32_t gryphonDisplayId = 0;
|
||||||
|
|
|
||||||
182
include/rendering/animation_controller.hpp
Normal file
182
include/rendering/animation_controller.hpp
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
namespace wowee {
|
||||||
|
namespace audio { enum class FootstepSurface : uint8_t; }
|
||||||
|
namespace rendering {
|
||||||
|
|
||||||
|
class Renderer;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// AnimationController — extracted from Renderer (§4.2)
|
||||||
|
//
|
||||||
|
// Owns the character locomotion state machine, mount animation state,
|
||||||
|
// emote system, footstep triggering, surface detection, melee combat
|
||||||
|
// animation, and activity SFX transition tracking.
|
||||||
|
// ============================================================================
|
||||||
|
class AnimationController {
|
||||||
|
public:
|
||||||
|
AnimationController();
|
||||||
|
~AnimationController();
|
||||||
|
|
||||||
|
void initialize(Renderer* renderer);
|
||||||
|
|
||||||
|
// ── Per-frame update hooks (called from Renderer::update) ──────────────
|
||||||
|
// Runs the character animation state machine (mounted + unmounted).
|
||||||
|
void updateCharacterAnimation();
|
||||||
|
// Processes animation-driven footstep events (player + mount).
|
||||||
|
void updateFootsteps(float deltaTime);
|
||||||
|
// Tracks state transitions for activity SFX (jump, landing, swim) and
|
||||||
|
// mount ambient sounds.
|
||||||
|
void updateSfxState(float deltaTime);
|
||||||
|
// Decrements melee swing timer / cooldown.
|
||||||
|
void updateMeleeTimers(float deltaTime);
|
||||||
|
// Store per-frame delta time (used inside animation state machine).
|
||||||
|
void setDeltaTime(float dt) { lastDeltaTime_ = dt; }
|
||||||
|
|
||||||
|
// ── Character follow ───────────────────────────────────────────────────
|
||||||
|
void onCharacterFollow(uint32_t instanceId);
|
||||||
|
|
||||||
|
// ── Emote support ──────────────────────────────────────────────────────
|
||||||
|
void playEmote(const std::string& emoteName);
|
||||||
|
void cancelEmote();
|
||||||
|
bool isEmoteActive() const { return emoteActive_; }
|
||||||
|
static std::string getEmoteText(const std::string& emoteName,
|
||||||
|
const std::string* targetName = nullptr);
|
||||||
|
static uint32_t getEmoteDbcId(const std::string& emoteName);
|
||||||
|
static std::string getEmoteTextByDbcId(uint32_t dbcId,
|
||||||
|
const std::string& senderName,
|
||||||
|
const std::string* targetName = nullptr);
|
||||||
|
static uint32_t getEmoteAnimByDbcId(uint32_t dbcId);
|
||||||
|
|
||||||
|
// ── Targeting / combat ─────────────────────────────────────────────────
|
||||||
|
void setTargetPosition(const glm::vec3* pos);
|
||||||
|
void setInCombat(bool combat) { inCombat_ = combat; }
|
||||||
|
bool isInCombat() const { return inCombat_; }
|
||||||
|
const glm::vec3* getTargetPosition() const { return targetPosition_; }
|
||||||
|
void resetCombatVisualState();
|
||||||
|
bool isMoving() const;
|
||||||
|
|
||||||
|
// ── Melee combat ───────────────────────────────────────────────────────
|
||||||
|
void triggerMeleeSwing();
|
||||||
|
void setEquippedWeaponType(uint32_t inventoryType) { equippedWeaponInvType_ = inventoryType; meleeAnimId_ = 0; }
|
||||||
|
void setCharging(bool charging) { charging_ = charging; }
|
||||||
|
bool isCharging() const { return charging_; }
|
||||||
|
|
||||||
|
// ── Effect triggers ────────────────────────────────────────────────────
|
||||||
|
void triggerLevelUpEffect(const glm::vec3& position);
|
||||||
|
void startChargeEffect(const glm::vec3& position, const glm::vec3& direction);
|
||||||
|
void emitChargeEffect(const glm::vec3& position, const glm::vec3& direction);
|
||||||
|
void stopChargeEffect();
|
||||||
|
|
||||||
|
// ── Mount ──────────────────────────────────────────────────────────────
|
||||||
|
void setMounted(uint32_t mountInstId, uint32_t mountDisplayId,
|
||||||
|
float heightOffset, const std::string& modelPath = "");
|
||||||
|
void setTaxiFlight(bool onTaxi) { taxiFlight_ = onTaxi; }
|
||||||
|
void setMountPitchRoll(float pitch, float roll) { mountPitch_ = pitch; mountRoll_ = roll; }
|
||||||
|
void clearMount();
|
||||||
|
bool isMounted() const { return mountInstanceId_ != 0; }
|
||||||
|
uint32_t getMountInstanceId() const { return mountInstanceId_; }
|
||||||
|
|
||||||
|
// ── Query helpers (used by Renderer) ───────────────────────────────────
|
||||||
|
bool isFootstepAnimationState() const;
|
||||||
|
float getMeleeSwingTimer() const { return meleeSwingTimer_; }
|
||||||
|
float getMountHeightOffset() const { return mountHeightOffset_; }
|
||||||
|
bool isTaxiFlight() const { return taxiFlight_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Renderer* renderer_ = nullptr;
|
||||||
|
|
||||||
|
// Character animation state machine
|
||||||
|
enum class CharAnimState {
|
||||||
|
IDLE, WALK, RUN, JUMP_START, JUMP_MID, JUMP_END, SIT_DOWN, SITTING,
|
||||||
|
EMOTE, SWIM_IDLE, SWIM, MELEE_SWING, MOUNT, CHARGE, COMBAT_IDLE
|
||||||
|
};
|
||||||
|
CharAnimState charAnimState_ = CharAnimState::IDLE;
|
||||||
|
float locomotionStopGraceTimer_ = 0.0f;
|
||||||
|
bool locomotionWasSprinting_ = false;
|
||||||
|
uint32_t lastPlayerAnimRequest_ = UINT32_MAX;
|
||||||
|
bool lastPlayerAnimLoopRequest_ = true;
|
||||||
|
|
||||||
|
// Emote state
|
||||||
|
bool emoteActive_ = false;
|
||||||
|
uint32_t emoteAnimId_ = 0;
|
||||||
|
bool emoteLoop_ = false;
|
||||||
|
|
||||||
|
// Target facing
|
||||||
|
const glm::vec3* targetPosition_ = nullptr;
|
||||||
|
bool inCombat_ = false;
|
||||||
|
|
||||||
|
// Footstep event tracking (animation-driven)
|
||||||
|
uint32_t footstepLastAnimationId_ = 0;
|
||||||
|
float footstepLastNormTime_ = 0.0f;
|
||||||
|
bool footstepNormInitialized_ = false;
|
||||||
|
|
||||||
|
// Footstep surface cache (avoid expensive queries every step)
|
||||||
|
mutable audio::FootstepSurface cachedFootstepSurface_{};
|
||||||
|
mutable glm::vec3 cachedFootstepPosition_{0.0f, 0.0f, 0.0f};
|
||||||
|
mutable float cachedFootstepUpdateTimer_{999.0f};
|
||||||
|
|
||||||
|
// Mount footstep tracking (separate from player's)
|
||||||
|
uint32_t mountFootstepLastAnimId_ = 0;
|
||||||
|
float mountFootstepLastNormTime_ = 0.0f;
|
||||||
|
bool mountFootstepNormInitialized_ = false;
|
||||||
|
|
||||||
|
// SFX transition state
|
||||||
|
bool sfxStateInitialized_ = false;
|
||||||
|
bool sfxPrevGrounded_ = true;
|
||||||
|
bool sfxPrevJumping_ = false;
|
||||||
|
bool sfxPrevFalling_ = false;
|
||||||
|
bool sfxPrevSwimming_ = false;
|
||||||
|
|
||||||
|
// Melee combat
|
||||||
|
bool charging_ = false;
|
||||||
|
float meleeSwingTimer_ = 0.0f;
|
||||||
|
float meleeSwingCooldown_ = 0.0f;
|
||||||
|
float meleeAnimDurationMs_ = 0.0f;
|
||||||
|
uint32_t meleeAnimId_ = 0;
|
||||||
|
uint32_t equippedWeaponInvType_ = 0;
|
||||||
|
|
||||||
|
// Mount animation capabilities (discovered at mount time, varies per model)
|
||||||
|
struct MountAnimSet {
|
||||||
|
uint32_t jumpStart = 0; // Jump start animation
|
||||||
|
uint32_t jumpLoop = 0; // Jump airborne loop
|
||||||
|
uint32_t jumpEnd = 0; // Jump landing
|
||||||
|
uint32_t rearUp = 0; // Rear-up / special flourish
|
||||||
|
uint32_t run = 0; // Run animation (discovered, don't assume)
|
||||||
|
uint32_t stand = 0; // Stand animation (discovered)
|
||||||
|
std::vector<uint32_t> fidgets; // Idle fidget animations (head turn, tail swish, etc.)
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class MountAction { None, Jump, RearUp };
|
||||||
|
|
||||||
|
uint32_t mountInstanceId_ = 0;
|
||||||
|
float mountHeightOffset_ = 0.0f;
|
||||||
|
float mountPitch_ = 0.0f; // Up/down tilt (radians)
|
||||||
|
float mountRoll_ = 0.0f; // Left/right banking (radians)
|
||||||
|
int mountSeatAttachmentId_ = -1; // -1 unknown, -2 unavailable
|
||||||
|
glm::vec3 smoothedMountSeatPos_ = glm::vec3(0.0f);
|
||||||
|
bool mountSeatSmoothingInit_ = false;
|
||||||
|
float prevMountYaw_ = 0.0f; // Previous yaw for turn rate calculation (procedural lean)
|
||||||
|
float lastDeltaTime_ = 0.0f; // Cached for use in updateCharacterAnimation()
|
||||||
|
MountAction mountAction_ = MountAction::None; // Current mount action (jump/rear-up)
|
||||||
|
uint32_t mountActionPhase_ = 0; // 0=start, 1=loop, 2=end (for jump chaining)
|
||||||
|
MountAnimSet mountAnims_; // Cached animation IDs for current mount
|
||||||
|
float mountIdleFidgetTimer_ = 0.0f; // Timer for random idle fidgets
|
||||||
|
float mountIdleSoundTimer_ = 0.0f; // Timer for ambient idle sounds
|
||||||
|
uint32_t mountActiveFidget_ = 0; // Currently playing fidget animation ID (0 = none)
|
||||||
|
bool taxiFlight_ = false;
|
||||||
|
bool taxiAnimsLogged_ = false;
|
||||||
|
|
||||||
|
// Private animation helpers
|
||||||
|
bool shouldTriggerFootstepEvent(uint32_t animationId, float animationTimeMs, float animationDurationMs);
|
||||||
|
audio::FootstepSurface resolveFootstepSurface() const;
|
||||||
|
uint32_t resolveMeleeAnimId();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rendering
|
||||||
|
} // namespace wowee
|
||||||
282
include/rendering/post_process_pipeline.hpp
Normal file
282
include/rendering/post_process_pipeline.hpp
Normal file
|
|
@ -0,0 +1,282 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include <vk_mem_alloc.h>
|
||||||
|
#include "rendering/vk_utils.hpp"
|
||||||
|
#if WOWEE_HAS_AMD_FSR2
|
||||||
|
#include "ffx_fsr2.h"
|
||||||
|
#include "ffx_fsr2_vk.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace wowee {
|
||||||
|
namespace rendering {
|
||||||
|
|
||||||
|
class VkContext;
|
||||||
|
class Camera;
|
||||||
|
class AmdFsr3Runtime;
|
||||||
|
|
||||||
|
/// Returned by setFSREnabled/setFSR2Enabled when they need the Renderer
|
||||||
|
/// to schedule an MSAA sample-count change (§4.3).
|
||||||
|
struct MsaaChangeRequest {
|
||||||
|
bool requested = false;
|
||||||
|
VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// PostProcessPipeline owns all FSR 1.0, FXAA, and FSR 2.2/3 state and
|
||||||
|
/// orchestrates post-processing passes between the scene render pass and
|
||||||
|
/// the final swapchain presentation (§4.3 extraction from Renderer).
|
||||||
|
class PostProcessPipeline {
|
||||||
|
public:
|
||||||
|
PostProcessPipeline();
|
||||||
|
~PostProcessPipeline();
|
||||||
|
|
||||||
|
void initialize(VkContext* ctx);
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
// --- Frame-loop integration (called from Renderer::beginFrame) ---
|
||||||
|
|
||||||
|
/// Lazy-create / lazy-destroy FSR/FXAA/FSR2 resources between frames.
|
||||||
|
void manageResources();
|
||||||
|
|
||||||
|
/// Recreate post-process resources after swapchain resize.
|
||||||
|
void handleSwapchainResize();
|
||||||
|
|
||||||
|
/// Apply FSR2 temporal jitter to the camera projection.
|
||||||
|
void applyJitter(Camera* camera);
|
||||||
|
|
||||||
|
/// Returns the framebuffer the scene should render into.
|
||||||
|
/// If no post-processing is active, returns VK_NULL_HANDLE (use swapchain).
|
||||||
|
VkFramebuffer getSceneFramebuffer() const;
|
||||||
|
|
||||||
|
/// Returns the render extent for the active post-process pipeline.
|
||||||
|
/// Falls back to swapchain extent if nothing is active.
|
||||||
|
VkExtent2D getSceneRenderExtent() const;
|
||||||
|
|
||||||
|
/// True if any post-process pipeline is active (FSR/FXAA/FSR2).
|
||||||
|
bool hasActivePostProcess() const;
|
||||||
|
|
||||||
|
/// True when FXAA alone (no FSR2) needs its own off-screen pass.
|
||||||
|
bool useFXAAPostPass() const { return fxaa_.enabled; }
|
||||||
|
|
||||||
|
// --- Frame-loop integration (called from Renderer::endFrame) ---
|
||||||
|
|
||||||
|
/// Execute all post-processing passes. Returns true if an INLINE
|
||||||
|
/// render pass was started (affects ImGui recording mode).
|
||||||
|
bool executePostProcessing(VkCommandBuffer cmd, uint32_t imageIndex,
|
||||||
|
Camera* camera, float deltaTime);
|
||||||
|
|
||||||
|
// --- MSAA interop (called from Renderer::applyMsaaChange) ---
|
||||||
|
|
||||||
|
/// Destroy FSR/FSR2/FXAA resources (they will be lazily recreated).
|
||||||
|
void destroyAllResources();
|
||||||
|
|
||||||
|
/// True when FSR2 is active and MSAA changes should be blocked.
|
||||||
|
bool isFsr2BlockingMsaa() const { return fsr2_.enabled; }
|
||||||
|
|
||||||
|
// --- Public API (delegated from Renderer) ---
|
||||||
|
|
||||||
|
// FXAA
|
||||||
|
void setFXAAEnabled(bool enabled);
|
||||||
|
bool isFXAAEnabled() const { return fxaa_.enabled; }
|
||||||
|
|
||||||
|
// FSR 1.0
|
||||||
|
MsaaChangeRequest setFSREnabled(bool enabled);
|
||||||
|
bool isFSREnabled() const { return fsr_.enabled; }
|
||||||
|
void setFSRQuality(float scaleFactor);
|
||||||
|
void setFSRSharpness(float sharpness);
|
||||||
|
float getFSRScaleFactor() const { return fsr_.scaleFactor; }
|
||||||
|
float getFSRSharpness() const { return fsr_.sharpness; }
|
||||||
|
|
||||||
|
// FSR 2.2
|
||||||
|
MsaaChangeRequest setFSR2Enabled(bool enabled, Camera* camera);
|
||||||
|
bool isFSR2Enabled() const { return fsr2_.enabled; }
|
||||||
|
void setFSR2DebugTuning(float jitterSign, float motionVecScaleX, float motionVecScaleY);
|
||||||
|
|
||||||
|
// FSR3 Framegen
|
||||||
|
void setAmdFsr3FramegenEnabled(bool enabled);
|
||||||
|
bool isAmdFsr3FramegenEnabled() const { return fsr2_.amdFsr3FramegenEnabled; }
|
||||||
|
float getFSR2JitterSign() const { return fsr2_.jitterSign; }
|
||||||
|
float getFSR2MotionVecScaleX() const { return fsr2_.motionVecScaleX; }
|
||||||
|
float getFSR2MotionVecScaleY() const { return fsr2_.motionVecScaleY; }
|
||||||
|
#if WOWEE_HAS_AMD_FSR2
|
||||||
|
bool isAmdFsr2SdkAvailable() const { return true; }
|
||||||
|
#else
|
||||||
|
bool isAmdFsr2SdkAvailable() const { return false; }
|
||||||
|
#endif
|
||||||
|
#if WOWEE_HAS_AMD_FSR3_FRAMEGEN
|
||||||
|
bool isAmdFsr3FramegenSdkAvailable() const { return true; }
|
||||||
|
#else
|
||||||
|
bool isAmdFsr3FramegenSdkAvailable() const { return false; }
|
||||||
|
#endif
|
||||||
|
bool isAmdFsr3FramegenRuntimeActive() const { return fsr2_.amdFsr3FramegenRuntimeActive; }
|
||||||
|
bool isAmdFsr3FramegenRuntimeReady() const { return fsr2_.amdFsr3FramegenRuntimeReady; }
|
||||||
|
const char* getAmdFsr3FramegenRuntimePath() const;
|
||||||
|
const std::string& getAmdFsr3FramegenRuntimeError() const { return fsr2_.amdFsr3RuntimeLastError; }
|
||||||
|
size_t getAmdFsr3UpscaleDispatchCount() const { return fsr2_.amdFsr3UpscaleDispatchCount; }
|
||||||
|
size_t getAmdFsr3FramegenDispatchCount() const { return fsr2_.amdFsr3FramegenDispatchCount; }
|
||||||
|
size_t getAmdFsr3FallbackCount() const { return fsr2_.amdFsr3FallbackCount; }
|
||||||
|
|
||||||
|
// Brightness (1.0 = default, <1 darkens, >1 brightens)
|
||||||
|
void setBrightness(float b) { brightness_ = b; }
|
||||||
|
float getBrightness() const { return brightness_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
VkContext* vkCtx_ = nullptr;
|
||||||
|
|
||||||
|
// Per-frame state set during executePostProcessing
|
||||||
|
VkCommandBuffer currentCmd_ = VK_NULL_HANDLE;
|
||||||
|
Camera* camera_ = nullptr;
|
||||||
|
float lastDeltaTime_ = 0.0f;
|
||||||
|
|
||||||
|
// Brightness
|
||||||
|
float brightness_ = 1.0f;
|
||||||
|
|
||||||
|
// FSR 1.0 upscaling state
|
||||||
|
struct FSRState {
|
||||||
|
bool enabled = false;
|
||||||
|
bool needsRecreate = false;
|
||||||
|
float scaleFactor = 1.00f; // Native default
|
||||||
|
float sharpness = 1.6f;
|
||||||
|
uint32_t internalWidth = 0;
|
||||||
|
uint32_t internalHeight = 0;
|
||||||
|
|
||||||
|
// Off-screen scene target (reduced resolution)
|
||||||
|
AllocatedImage sceneColor{}; // 1x color (non-MSAA render target / MSAA resolve target)
|
||||||
|
AllocatedImage sceneDepth{}; // Depth (matches current MSAA sample count)
|
||||||
|
AllocatedImage sceneMsaaColor{}; // MSAA color target (only when MSAA > 1x)
|
||||||
|
AllocatedImage sceneDepthResolve{}; // Depth resolve (only when MSAA + depth resolve)
|
||||||
|
VkFramebuffer sceneFramebuffer = VK_NULL_HANDLE;
|
||||||
|
VkSampler sceneSampler = VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
// Upscale pipeline
|
||||||
|
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||||
|
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||||
|
VkDescriptorSetLayout descSetLayout = VK_NULL_HANDLE;
|
||||||
|
VkDescriptorPool descPool = VK_NULL_HANDLE;
|
||||||
|
VkDescriptorSet descSet = VK_NULL_HANDLE;
|
||||||
|
};
|
||||||
|
FSRState fsr_;
|
||||||
|
bool initFSRResources();
|
||||||
|
void destroyFSRResources();
|
||||||
|
void renderFSRUpscale();
|
||||||
|
|
||||||
|
// FXAA post-process state
|
||||||
|
struct FXAAState {
|
||||||
|
bool enabled = false;
|
||||||
|
bool needsRecreate = false;
|
||||||
|
|
||||||
|
// Off-screen scene target (same resolution as swapchain — no scaling)
|
||||||
|
AllocatedImage sceneColor{}; // 1x resolved color target
|
||||||
|
AllocatedImage sceneDepth{}; // Depth (matches MSAA sample count)
|
||||||
|
AllocatedImage sceneMsaaColor{}; // MSAA color target (when MSAA > 1x)
|
||||||
|
AllocatedImage sceneDepthResolve{}; // Depth resolve (MSAA + depth resolve)
|
||||||
|
VkFramebuffer sceneFramebuffer = VK_NULL_HANDLE;
|
||||||
|
VkSampler sceneSampler = VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
// FXAA fullscreen pipeline
|
||||||
|
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||||
|
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||||
|
VkDescriptorSetLayout descSetLayout = VK_NULL_HANDLE;
|
||||||
|
VkDescriptorPool descPool = VK_NULL_HANDLE;
|
||||||
|
VkDescriptorSet descSet = VK_NULL_HANDLE;
|
||||||
|
};
|
||||||
|
FXAAState fxaa_;
|
||||||
|
bool initFXAAResources();
|
||||||
|
void destroyFXAAResources();
|
||||||
|
void renderFXAAPass();
|
||||||
|
|
||||||
|
// FSR 2.2 temporal upscaling state
|
||||||
|
struct FSR2State {
|
||||||
|
bool enabled = false;
|
||||||
|
bool needsRecreate = false;
|
||||||
|
float scaleFactor = 0.77f;
|
||||||
|
float sharpness = 3.0f; // Very strong RCAS to counteract upscale softness
|
||||||
|
uint32_t internalWidth = 0;
|
||||||
|
uint32_t internalHeight = 0;
|
||||||
|
|
||||||
|
// Off-screen scene targets (internal resolution, no MSAA — FSR2 replaces AA)
|
||||||
|
AllocatedImage sceneColor{};
|
||||||
|
AllocatedImage sceneDepth{};
|
||||||
|
VkFramebuffer sceneFramebuffer = VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
// Samplers
|
||||||
|
VkSampler linearSampler = VK_NULL_HANDLE; // For color
|
||||||
|
VkSampler nearestSampler = VK_NULL_HANDLE; // For depth / motion vectors
|
||||||
|
|
||||||
|
// Motion vector buffer (internal resolution)
|
||||||
|
AllocatedImage motionVectors{};
|
||||||
|
|
||||||
|
// History buffers (display resolution, ping-pong)
|
||||||
|
AllocatedImage history[2]{};
|
||||||
|
AllocatedImage framegenOutput{};
|
||||||
|
bool framegenOutputValid = false;
|
||||||
|
uint32_t currentHistory = 0; // Output index (0 or 1)
|
||||||
|
|
||||||
|
// Compute pipelines
|
||||||
|
VkPipeline motionVecPipeline = VK_NULL_HANDLE;
|
||||||
|
VkPipelineLayout motionVecPipelineLayout = VK_NULL_HANDLE;
|
||||||
|
VkDescriptorSetLayout motionVecDescSetLayout = VK_NULL_HANDLE;
|
||||||
|
VkDescriptorPool motionVecDescPool = VK_NULL_HANDLE;
|
||||||
|
VkDescriptorSet motionVecDescSet = VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
VkPipeline accumulatePipeline = VK_NULL_HANDLE;
|
||||||
|
VkPipelineLayout accumulatePipelineLayout = VK_NULL_HANDLE;
|
||||||
|
VkDescriptorSetLayout accumulateDescSetLayout = VK_NULL_HANDLE;
|
||||||
|
VkDescriptorPool accumulateDescPool = VK_NULL_HANDLE;
|
||||||
|
VkDescriptorSet accumulateDescSets[2] = {}; // Per ping-pong
|
||||||
|
|
||||||
|
// RCAS sharpening pass (display resolution)
|
||||||
|
VkPipeline sharpenPipeline = VK_NULL_HANDLE;
|
||||||
|
VkPipelineLayout sharpenPipelineLayout = VK_NULL_HANDLE;
|
||||||
|
VkDescriptorSetLayout sharpenDescSetLayout = VK_NULL_HANDLE;
|
||||||
|
VkDescriptorPool sharpenDescPool = VK_NULL_HANDLE;
|
||||||
|
VkDescriptorSet sharpenDescSets[2] = {};
|
||||||
|
|
||||||
|
// Previous frame state for motion vector reprojection
|
||||||
|
glm::mat4 prevViewProjection = glm::mat4(1.0f);
|
||||||
|
glm::vec2 prevJitter = glm::vec2(0.0f);
|
||||||
|
uint32_t frameIndex = 0;
|
||||||
|
bool needsHistoryReset = true;
|
||||||
|
bool useAmdBackend = false;
|
||||||
|
bool amdFsr3FramegenEnabled = false;
|
||||||
|
bool amdFsr3FramegenRuntimeActive = false;
|
||||||
|
bool amdFsr3FramegenRuntimeReady = false;
|
||||||
|
std::string amdFsr3RuntimePath = "Path C";
|
||||||
|
std::string amdFsr3RuntimeLastError{};
|
||||||
|
size_t amdFsr3UpscaleDispatchCount = 0;
|
||||||
|
size_t amdFsr3FramegenDispatchCount = 0;
|
||||||
|
size_t amdFsr3FallbackCount = 0;
|
||||||
|
uint64_t amdFsr3InteropSyncValue = 1;
|
||||||
|
float jitterSign = 0.38f;
|
||||||
|
float motionVecScaleX = 1.0f;
|
||||||
|
float motionVecScaleY = 1.0f;
|
||||||
|
#if WOWEE_HAS_AMD_FSR2
|
||||||
|
FfxFsr2Context amdContext{};
|
||||||
|
FfxFsr2Interface amdInterface{};
|
||||||
|
void* amdScratchBuffer = nullptr;
|
||||||
|
size_t amdScratchBufferSize = 0;
|
||||||
|
#endif
|
||||||
|
std::unique_ptr<AmdFsr3Runtime> amdFsr3Runtime;
|
||||||
|
|
||||||
|
// Convergent accumulation: jitter for N frames then freeze
|
||||||
|
int convergenceFrame = 0;
|
||||||
|
static constexpr int convergenceMaxFrames = 8;
|
||||||
|
glm::mat4 lastStableVP = glm::mat4(1.0f);
|
||||||
|
};
|
||||||
|
FSR2State fsr2_;
|
||||||
|
bool initFSR2Resources();
|
||||||
|
void destroyFSR2Resources();
|
||||||
|
void dispatchMotionVectors();
|
||||||
|
void dispatchTemporalAccumulate();
|
||||||
|
void dispatchAmdFsr2();
|
||||||
|
void dispatchAmdFsr3Framegen();
|
||||||
|
void renderFSR2Sharpen();
|
||||||
|
static float halton(uint32_t index, uint32_t base);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rendering
|
||||||
|
} // namespace wowee
|
||||||
|
|
@ -14,16 +14,12 @@
|
||||||
#include "rendering/vk_frame_data.hpp"
|
#include "rendering/vk_frame_data.hpp"
|
||||||
#include "rendering/vk_utils.hpp"
|
#include "rendering/vk_utils.hpp"
|
||||||
#include "rendering/sky_system.hpp"
|
#include "rendering/sky_system.hpp"
|
||||||
#if WOWEE_HAS_AMD_FSR2
|
|
||||||
#include "ffx_fsr2.h"
|
|
||||||
#include "ffx_fsr2_vk.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace wowee {
|
namespace wowee {
|
||||||
namespace core { class Window; }
|
namespace core { class Window; }
|
||||||
namespace rendering { class VkContext; }
|
namespace rendering { class VkContext; }
|
||||||
namespace game { class World; class ZoneManager; class GameHandler; }
|
namespace game { class World; class ZoneManager; class GameHandler; }
|
||||||
namespace audio { class MusicManager; class FootstepManager; class ActivitySoundManager; class MountSoundManager; class NpcVoiceManager; class AmbientSoundManager; class UiSoundManager; class CombatSoundManager; class SpellSoundManager; class MovementSoundManager; enum class FootstepSurface : uint8_t; enum class VoiceType; }
|
namespace audio { class AudioCoordinator; }
|
||||||
namespace pipeline { class AssetManager; }
|
namespace pipeline { class AssetManager; }
|
||||||
|
|
||||||
namespace rendering {
|
namespace rendering {
|
||||||
|
|
@ -54,6 +50,12 @@ class WorldMap;
|
||||||
class QuestMarkerRenderer;
|
class QuestMarkerRenderer;
|
||||||
class CharacterPreview;
|
class CharacterPreview;
|
||||||
class AmdFsr3Runtime;
|
class AmdFsr3Runtime;
|
||||||
|
class SpellVisualSystem;
|
||||||
|
class PostProcessPipeline;
|
||||||
|
class AnimationController;
|
||||||
|
class LevelUpEffect;
|
||||||
|
class ChargeEffect;
|
||||||
|
class SwimEffects;
|
||||||
|
|
||||||
class Renderer {
|
class Renderer {
|
||||||
public:
|
public:
|
||||||
|
|
@ -148,7 +150,7 @@ public:
|
||||||
float getCharacterYaw() const { return characterYaw; }
|
float getCharacterYaw() const { return characterYaw; }
|
||||||
void setCharacterYaw(float yawDeg) { characterYaw = yawDeg; }
|
void setCharacterYaw(float yawDeg) { characterYaw = yawDeg; }
|
||||||
|
|
||||||
// Emote support
|
// Emote support — delegates to AnimationController (§4.2)
|
||||||
void playEmote(const std::string& emoteName);
|
void playEmote(const std::string& emoteName);
|
||||||
void triggerLevelUpEffect(const glm::vec3& position);
|
void triggerLevelUpEffect(const glm::vec3& position);
|
||||||
void cancelEmote();
|
void cancelEmote();
|
||||||
|
|
@ -157,34 +159,41 @@ public:
|
||||||
bool captureScreenshot(const std::string& outputPath);
|
bool captureScreenshot(const std::string& outputPath);
|
||||||
|
|
||||||
// Spell visual effects (SMSG_PLAY_SPELL_VISUAL / SMSG_PLAY_SPELL_IMPACT)
|
// Spell visual effects (SMSG_PLAY_SPELL_VISUAL / SMSG_PLAY_SPELL_IMPACT)
|
||||||
// useImpactKit=false → CastKit path; useImpactKit=true → ImpactKit path
|
// Delegates to SpellVisualSystem (owned by Renderer)
|
||||||
void playSpellVisual(uint32_t visualId, const glm::vec3& worldPosition,
|
void playSpellVisual(uint32_t visualId, const glm::vec3& worldPosition,
|
||||||
bool useImpactKit = false);
|
bool useImpactKit = false);
|
||||||
bool isEmoteActive() const { return emoteActive; }
|
SpellVisualSystem* getSpellVisualSystem() const { return spellVisualSystem_.get(); }
|
||||||
|
bool isEmoteActive() const;
|
||||||
static std::string getEmoteText(const std::string& emoteName, const std::string* targetName = nullptr);
|
static std::string getEmoteText(const std::string& emoteName, const std::string* targetName = nullptr);
|
||||||
static uint32_t getEmoteDbcId(const std::string& emoteName);
|
static uint32_t getEmoteDbcId(const std::string& emoteName);
|
||||||
static std::string getEmoteTextByDbcId(uint32_t dbcId, const std::string& senderName, const std::string* targetName = nullptr);
|
static std::string getEmoteTextByDbcId(uint32_t dbcId, const std::string& senderName, const std::string* targetName = nullptr);
|
||||||
static uint32_t getEmoteAnimByDbcId(uint32_t dbcId);
|
static uint32_t getEmoteAnimByDbcId(uint32_t dbcId);
|
||||||
|
|
||||||
// Targeting support
|
// Targeting support — delegates to AnimationController (§4.2)
|
||||||
void setTargetPosition(const glm::vec3* pos);
|
void setTargetPosition(const glm::vec3* pos);
|
||||||
void setInCombat(bool combat) { inCombat_ = combat; }
|
void setInCombat(bool combat);
|
||||||
void resetCombatVisualState();
|
void resetCombatVisualState();
|
||||||
bool isMoving() const;
|
bool isMoving() const;
|
||||||
void triggerMeleeSwing();
|
void triggerMeleeSwing();
|
||||||
void setEquippedWeaponType(uint32_t inventoryType) { equippedWeaponInvType_ = inventoryType; meleeAnimId = 0; }
|
void setEquippedWeaponType(uint32_t inventoryType);
|
||||||
void setCharging(bool charging) { charging_ = charging; }
|
void setCharging(bool charging);
|
||||||
bool isCharging() const { return charging_; }
|
bool isCharging() const;
|
||||||
void startChargeEffect(const glm::vec3& position, const glm::vec3& direction);
|
void startChargeEffect(const glm::vec3& position, const glm::vec3& direction);
|
||||||
void emitChargeEffect(const glm::vec3& position, const glm::vec3& direction);
|
void emitChargeEffect(const glm::vec3& position, const glm::vec3& direction);
|
||||||
void stopChargeEffect();
|
void stopChargeEffect();
|
||||||
|
|
||||||
// Mount rendering
|
// Mount rendering — delegates to AnimationController (§4.2)
|
||||||
void setMounted(uint32_t mountInstId, uint32_t mountDisplayId, float heightOffset, const std::string& modelPath = "");
|
void setMounted(uint32_t mountInstId, uint32_t mountDisplayId, float heightOffset, const std::string& modelPath = "");
|
||||||
void setTaxiFlight(bool onTaxi) { taxiFlight_ = onTaxi; }
|
void setTaxiFlight(bool onTaxi);
|
||||||
void setMountPitchRoll(float pitch, float roll) { mountPitch_ = pitch; mountRoll_ = roll; }
|
void setMountPitchRoll(float pitch, float roll);
|
||||||
void clearMount();
|
void clearMount();
|
||||||
bool isMounted() const { return mountInstanceId_ != 0; }
|
bool isMounted() const;
|
||||||
|
|
||||||
|
// AnimationController access (§4.2)
|
||||||
|
AnimationController* getAnimationController() const { return animationController_.get(); }
|
||||||
|
LevelUpEffect* getLevelUpEffect() const { return levelUpEffect.get(); }
|
||||||
|
ChargeEffect* getChargeEffect() const { return chargeEffect.get(); }
|
||||||
|
SwimEffects* getSwimEffects() const { return swimEffects.get(); }
|
||||||
|
|
||||||
// Selection circle for targeted entity
|
// Selection circle for targeted entity
|
||||||
void setSelectionCircle(const glm::vec3& pos, float radius, const glm::vec3& color);
|
void setSelectionCircle(const glm::vec3& pos, float radius, const glm::vec3& color);
|
||||||
|
|
@ -197,17 +206,10 @@ public:
|
||||||
double getLastTerrainRenderMs() const { return lastTerrainRenderMs; }
|
double getLastTerrainRenderMs() const { return lastTerrainRenderMs; }
|
||||||
double getLastWMORenderMs() const { return lastWMORenderMs; }
|
double getLastWMORenderMs() const { return lastWMORenderMs; }
|
||||||
double getLastM2RenderMs() const { return lastM2RenderMs; }
|
double getLastM2RenderMs() const { return lastM2RenderMs; }
|
||||||
audio::MusicManager* getMusicManager() { return musicManager.get(); }
|
// Audio coordinator — owned by Application, set via setAudioCoordinator().
|
||||||
|
void setAudioCoordinator(audio::AudioCoordinator* ac) { audioCoordinator_ = ac; }
|
||||||
|
audio::AudioCoordinator* getAudioCoordinator() { return audioCoordinator_; }
|
||||||
game::ZoneManager* getZoneManager() { return zoneManager.get(); }
|
game::ZoneManager* getZoneManager() { return zoneManager.get(); }
|
||||||
audio::FootstepManager* getFootstepManager() { return footstepManager.get(); }
|
|
||||||
audio::ActivitySoundManager* getActivitySoundManager() { return activitySoundManager.get(); }
|
|
||||||
audio::MountSoundManager* getMountSoundManager() { return mountSoundManager.get(); }
|
|
||||||
audio::NpcVoiceManager* getNpcVoiceManager() { return npcVoiceManager.get(); }
|
|
||||||
audio::AmbientSoundManager* getAmbientSoundManager() { return ambientSoundManager.get(); }
|
|
||||||
audio::UiSoundManager* getUiSoundManager() { return uiSoundManager.get(); }
|
|
||||||
audio::CombatSoundManager* getCombatSoundManager() { return combatSoundManager.get(); }
|
|
||||||
audio::SpellSoundManager* getSpellSoundManager() { return spellSoundManager.get(); }
|
|
||||||
audio::MovementSoundManager* getMovementSoundManager() { return movementSoundManager.get(); }
|
|
||||||
LightingManager* getLightingManager() { return lightingManager.get(); }
|
LightingManager* getLightingManager() { return lightingManager.get(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -239,16 +241,8 @@ private:
|
||||||
std::unique_ptr<Minimap> minimap;
|
std::unique_ptr<Minimap> minimap;
|
||||||
std::unique_ptr<WorldMap> worldMap;
|
std::unique_ptr<WorldMap> worldMap;
|
||||||
std::unique_ptr<QuestMarkerRenderer> questMarkerRenderer;
|
std::unique_ptr<QuestMarkerRenderer> questMarkerRenderer;
|
||||||
std::unique_ptr<audio::MusicManager> musicManager;
|
audio::AudioCoordinator* audioCoordinator_ = nullptr; // Owned by Application
|
||||||
std::unique_ptr<audio::FootstepManager> footstepManager;
|
std::unique_ptr<AnimationController> animationController_; // §4.2
|
||||||
std::unique_ptr<audio::ActivitySoundManager> activitySoundManager;
|
|
||||||
std::unique_ptr<audio::MountSoundManager> mountSoundManager;
|
|
||||||
std::unique_ptr<audio::NpcVoiceManager> npcVoiceManager;
|
|
||||||
std::unique_ptr<audio::AmbientSoundManager> ambientSoundManager;
|
|
||||||
std::unique_ptr<audio::UiSoundManager> uiSoundManager;
|
|
||||||
std::unique_ptr<audio::CombatSoundManager> combatSoundManager;
|
|
||||||
std::unique_ptr<audio::SpellSoundManager> spellSoundManager;
|
|
||||||
std::unique_ptr<audio::MovementSoundManager> movementSoundManager;
|
|
||||||
std::unique_ptr<game::ZoneManager> zoneManager;
|
std::unique_ptr<game::ZoneManager> zoneManager;
|
||||||
// Shadow mapping (Vulkan)
|
// Shadow mapping (Vulkan)
|
||||||
static constexpr uint32_t SHADOW_MAP_SIZE = 4096;
|
static constexpr uint32_t SHADOW_MAP_SIZE = 4096;
|
||||||
|
|
@ -282,42 +276,35 @@ public:
|
||||||
float getShadowDistance() const { return shadowDistance_; }
|
float getShadowDistance() const { return shadowDistance_; }
|
||||||
void setMsaaSamples(VkSampleCountFlagBits samples);
|
void setMsaaSamples(VkSampleCountFlagBits samples);
|
||||||
|
|
||||||
// FXAA post-process anti-aliasing (combinable with MSAA)
|
// Post-process pipeline API — delegates to PostProcessPipeline (§4.3)
|
||||||
|
PostProcessPipeline* getPostProcessPipeline() const;
|
||||||
void setFXAAEnabled(bool enabled);
|
void setFXAAEnabled(bool enabled);
|
||||||
bool isFXAAEnabled() const { return fxaa_.enabled; }
|
bool isFXAAEnabled() const;
|
||||||
|
|
||||||
// FSR (FidelityFX Super Resolution) upscaling
|
|
||||||
void setFSREnabled(bool enabled);
|
void setFSREnabled(bool enabled);
|
||||||
bool isFSREnabled() const { return fsr_.enabled; }
|
bool isFSREnabled() const;
|
||||||
void setFSRQuality(float scaleFactor); // 0.59=Balanced, 0.67=Quality, 0.77=UltraQuality, 1.00=Native
|
void setFSRQuality(float scaleFactor);
|
||||||
void setFSRSharpness(float sharpness); // 0.0 - 2.0
|
void setFSRSharpness(float sharpness);
|
||||||
float getFSRScaleFactor() const { return fsr_.scaleFactor; }
|
float getFSRScaleFactor() const;
|
||||||
float getFSRSharpness() const { return fsr_.sharpness; }
|
float getFSRSharpness() const;
|
||||||
void setFSR2Enabled(bool enabled);
|
void setFSR2Enabled(bool enabled);
|
||||||
bool isFSR2Enabled() const { return fsr2_.enabled; }
|
bool isFSR2Enabled() const;
|
||||||
void setFSR2DebugTuning(float jitterSign, float motionVecScaleX, float motionVecScaleY);
|
void setFSR2DebugTuning(float jitterSign, float motionVecScaleX, float motionVecScaleY);
|
||||||
void setAmdFsr3FramegenEnabled(bool enabled);
|
void setAmdFsr3FramegenEnabled(bool enabled);
|
||||||
bool isAmdFsr3FramegenEnabled() const { return fsr2_.amdFsr3FramegenEnabled; }
|
bool isAmdFsr3FramegenEnabled() const;
|
||||||
float getFSR2JitterSign() const { return fsr2_.jitterSign; }
|
float getFSR2JitterSign() const;
|
||||||
float getFSR2MotionVecScaleX() const { return fsr2_.motionVecScaleX; }
|
float getFSR2MotionVecScaleX() const;
|
||||||
float getFSR2MotionVecScaleY() const { return fsr2_.motionVecScaleY; }
|
float getFSR2MotionVecScaleY() const;
|
||||||
#if WOWEE_HAS_AMD_FSR2
|
bool isAmdFsr2SdkAvailable() const;
|
||||||
bool isAmdFsr2SdkAvailable() const { return true; }
|
bool isAmdFsr3FramegenSdkAvailable() const;
|
||||||
#else
|
bool isAmdFsr3FramegenRuntimeActive() const;
|
||||||
bool isAmdFsr2SdkAvailable() const { return false; }
|
bool isAmdFsr3FramegenRuntimeReady() const;
|
||||||
#endif
|
|
||||||
#if WOWEE_HAS_AMD_FSR3_FRAMEGEN
|
|
||||||
bool isAmdFsr3FramegenSdkAvailable() const { return true; }
|
|
||||||
#else
|
|
||||||
bool isAmdFsr3FramegenSdkAvailable() const { return false; }
|
|
||||||
#endif
|
|
||||||
bool isAmdFsr3FramegenRuntimeActive() const { return fsr2_.amdFsr3FramegenRuntimeActive; }
|
|
||||||
bool isAmdFsr3FramegenRuntimeReady() const { return fsr2_.amdFsr3FramegenRuntimeReady; }
|
|
||||||
const char* getAmdFsr3FramegenRuntimePath() const;
|
const char* getAmdFsr3FramegenRuntimePath() const;
|
||||||
const std::string& getAmdFsr3FramegenRuntimeError() const { return fsr2_.amdFsr3RuntimeLastError; }
|
const std::string& getAmdFsr3FramegenRuntimeError() const;
|
||||||
size_t getAmdFsr3UpscaleDispatchCount() const { return fsr2_.amdFsr3UpscaleDispatchCount; }
|
size_t getAmdFsr3UpscaleDispatchCount() const;
|
||||||
size_t getAmdFsr3FramegenDispatchCount() const { return fsr2_.amdFsr3FramegenDispatchCount; }
|
size_t getAmdFsr3FramegenDispatchCount() const;
|
||||||
size_t getAmdFsr3FallbackCount() const { return fsr2_.amdFsr3FallbackCount; }
|
size_t getAmdFsr3FallbackCount() const;
|
||||||
|
void setBrightness(float b);
|
||||||
|
float getBrightness() const;
|
||||||
|
|
||||||
void setWaterRefractionEnabled(bool enabled);
|
void setWaterRefractionEnabled(bool enabled);
|
||||||
bool isWaterRefractionEnabled() const;
|
bool isWaterRefractionEnabled() const;
|
||||||
|
|
@ -331,23 +318,11 @@ private:
|
||||||
|
|
||||||
pipeline::AssetManager* cachedAssetManager = nullptr;
|
pipeline::AssetManager* cachedAssetManager = nullptr;
|
||||||
|
|
||||||
// Spell visual effects — transient M2 instances spawned by SMSG_PLAY_SPELL_VISUAL/IMPACT
|
// Spell visual effects — owned SpellVisualSystem (extracted from Renderer §4.4)
|
||||||
struct SpellVisualInstance {
|
std::unique_ptr<SpellVisualSystem> spellVisualSystem_;
|
||||||
uint32_t instanceId;
|
|
||||||
float elapsed;
|
// Post-process pipeline — owns all FSR/FXAA/FSR2 state (extracted §4.3)
|
||||||
float duration; // per-instance lifetime in seconds (from M2 anim or default)
|
std::unique_ptr<PostProcessPipeline> postProcessPipeline_;
|
||||||
};
|
|
||||||
std::vector<SpellVisualInstance> activeSpellVisuals_;
|
|
||||||
std::unordered_map<uint32_t, std::string> spellVisualCastPath_; // visualId → cast M2 path
|
|
||||||
std::unordered_map<uint32_t, std::string> spellVisualImpactPath_; // visualId → impact M2 path
|
|
||||||
std::unordered_map<std::string, uint32_t> spellVisualModelIds_; // M2 path → M2Renderer modelId
|
|
||||||
std::unordered_set<uint32_t> spellVisualFailedModels_; // modelIds that failed to load (negative cache)
|
|
||||||
uint32_t nextSpellVisualModelId_ = 999000; // Reserved range 999000-999799
|
|
||||||
bool spellVisualDbcLoaded_ = false;
|
|
||||||
void loadSpellVisualDbc();
|
|
||||||
void updateSpellVisuals(float deltaTime);
|
|
||||||
static constexpr float SPELL_VISUAL_MAX_DURATION = 5.0f;
|
|
||||||
static constexpr float SPELL_VISUAL_DEFAULT_DURATION = 2.0f;
|
|
||||||
|
|
||||||
uint32_t currentZoneId = 0;
|
uint32_t currentZoneId = 0;
|
||||||
std::string currentZoneName;
|
std::string currentZoneName;
|
||||||
|
|
@ -365,27 +340,7 @@ private:
|
||||||
uint32_t characterInstanceId = 0;
|
uint32_t characterInstanceId = 0;
|
||||||
float characterYaw = 0.0f;
|
float characterYaw = 0.0f;
|
||||||
|
|
||||||
// Character animation state
|
|
||||||
enum class CharAnimState { IDLE, WALK, RUN, JUMP_START, JUMP_MID, JUMP_END, SIT_DOWN, SITTING, EMOTE, SWIM_IDLE, SWIM, MELEE_SWING, MOUNT, CHARGE, COMBAT_IDLE };
|
|
||||||
CharAnimState charAnimState = CharAnimState::IDLE;
|
|
||||||
float locomotionStopGraceTimer_ = 0.0f;
|
|
||||||
bool locomotionWasSprinting_ = false;
|
|
||||||
uint32_t lastPlayerAnimRequest_ = UINT32_MAX;
|
|
||||||
bool lastPlayerAnimLoopRequest_ = true;
|
|
||||||
void updateCharacterAnimation();
|
|
||||||
bool isFootstepAnimationState() const;
|
|
||||||
bool shouldTriggerFootstepEvent(uint32_t animationId, float animationTimeMs, float animationDurationMs);
|
|
||||||
audio::FootstepSurface resolveFootstepSurface() const;
|
|
||||||
uint32_t resolveMeleeAnimId();
|
|
||||||
|
|
||||||
// Emote state
|
|
||||||
bool emoteActive = false;
|
|
||||||
uint32_t emoteAnimId = 0;
|
|
||||||
bool emoteLoop = false;
|
|
||||||
|
|
||||||
// Target facing
|
|
||||||
const glm::vec3* targetPosition = nullptr;
|
|
||||||
bool inCombat_ = false;
|
|
||||||
|
|
||||||
// Selection circle rendering (Vulkan)
|
// Selection circle rendering (Vulkan)
|
||||||
VkPipeline selCirclePipeline = VK_NULL_HANDLE;
|
VkPipeline selCirclePipeline = VK_NULL_HANDLE;
|
||||||
|
|
@ -408,213 +363,7 @@ private:
|
||||||
void initOverlayPipeline();
|
void initOverlayPipeline();
|
||||||
void renderOverlay(const glm::vec4& color, VkCommandBuffer overrideCmd = VK_NULL_HANDLE);
|
void renderOverlay(const glm::vec4& color, VkCommandBuffer overrideCmd = VK_NULL_HANDLE);
|
||||||
|
|
||||||
// Brightness (1.0 = default, <1 darkens, >1 brightens)
|
|
||||||
float brightness_ = 1.0f;
|
|
||||||
public:
|
|
||||||
void setBrightness(float b) { brightness_ = b; }
|
|
||||||
float getBrightness() const { return brightness_; }
|
|
||||||
private:
|
|
||||||
|
|
||||||
// FSR 1.0 upscaling state
|
|
||||||
struct FSRState {
|
|
||||||
bool enabled = false;
|
|
||||||
bool needsRecreate = false;
|
|
||||||
float scaleFactor = 1.00f; // Native default
|
|
||||||
float sharpness = 1.6f;
|
|
||||||
uint32_t internalWidth = 0;
|
|
||||||
uint32_t internalHeight = 0;
|
|
||||||
|
|
||||||
// Off-screen scene target (reduced resolution)
|
|
||||||
AllocatedImage sceneColor{}; // 1x color (non-MSAA render target / MSAA resolve target)
|
|
||||||
AllocatedImage sceneDepth{}; // Depth (matches current MSAA sample count)
|
|
||||||
AllocatedImage sceneMsaaColor{}; // MSAA color target (only when MSAA > 1x)
|
|
||||||
AllocatedImage sceneDepthResolve{}; // Depth resolve (only when MSAA + depth resolve)
|
|
||||||
VkFramebuffer sceneFramebuffer = VK_NULL_HANDLE;
|
|
||||||
VkSampler sceneSampler = VK_NULL_HANDLE;
|
|
||||||
|
|
||||||
// Upscale pipeline
|
|
||||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
|
||||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
|
||||||
VkDescriptorSetLayout descSetLayout = VK_NULL_HANDLE;
|
|
||||||
VkDescriptorPool descPool = VK_NULL_HANDLE;
|
|
||||||
VkDescriptorSet descSet = VK_NULL_HANDLE;
|
|
||||||
};
|
|
||||||
FSRState fsr_;
|
|
||||||
bool initFSRResources();
|
|
||||||
void destroyFSRResources();
|
|
||||||
void renderFSRUpscale();
|
|
||||||
|
|
||||||
// FXAA post-process state
|
|
||||||
struct FXAAState {
|
|
||||||
bool enabled = false;
|
|
||||||
bool needsRecreate = false;
|
|
||||||
|
|
||||||
// Off-screen scene target (same resolution as swapchain — no scaling)
|
|
||||||
AllocatedImage sceneColor{}; // 1x resolved color target
|
|
||||||
AllocatedImage sceneDepth{}; // Depth (matches MSAA sample count)
|
|
||||||
AllocatedImage sceneMsaaColor{}; // MSAA color target (when MSAA > 1x)
|
|
||||||
AllocatedImage sceneDepthResolve{}; // Depth resolve (MSAA + depth resolve)
|
|
||||||
VkFramebuffer sceneFramebuffer = VK_NULL_HANDLE;
|
|
||||||
VkSampler sceneSampler = VK_NULL_HANDLE;
|
|
||||||
|
|
||||||
// FXAA fullscreen pipeline
|
|
||||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
|
||||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
|
||||||
VkDescriptorSetLayout descSetLayout = VK_NULL_HANDLE;
|
|
||||||
VkDescriptorPool descPool = VK_NULL_HANDLE;
|
|
||||||
VkDescriptorSet descSet = VK_NULL_HANDLE;
|
|
||||||
};
|
|
||||||
FXAAState fxaa_;
|
|
||||||
bool initFXAAResources();
|
|
||||||
void destroyFXAAResources();
|
|
||||||
void renderFXAAPass();
|
|
||||||
|
|
||||||
// FSR 2.2 temporal upscaling state
|
|
||||||
struct FSR2State {
|
|
||||||
bool enabled = false;
|
|
||||||
bool needsRecreate = false;
|
|
||||||
float scaleFactor = 0.77f;
|
|
||||||
float sharpness = 3.0f; // Very strong RCAS to counteract upscale softness
|
|
||||||
uint32_t internalWidth = 0;
|
|
||||||
uint32_t internalHeight = 0;
|
|
||||||
|
|
||||||
// Off-screen scene targets (internal resolution, no MSAA — FSR2 replaces AA)
|
|
||||||
AllocatedImage sceneColor{};
|
|
||||||
AllocatedImage sceneDepth{};
|
|
||||||
VkFramebuffer sceneFramebuffer = VK_NULL_HANDLE;
|
|
||||||
|
|
||||||
// Samplers
|
|
||||||
VkSampler linearSampler = VK_NULL_HANDLE; // For color
|
|
||||||
VkSampler nearestSampler = VK_NULL_HANDLE; // For depth / motion vectors
|
|
||||||
|
|
||||||
// Motion vector buffer (internal resolution)
|
|
||||||
AllocatedImage motionVectors{};
|
|
||||||
|
|
||||||
// History buffers (display resolution, ping-pong)
|
|
||||||
AllocatedImage history[2]{};
|
|
||||||
AllocatedImage framegenOutput{};
|
|
||||||
bool framegenOutputValid = false;
|
|
||||||
uint32_t currentHistory = 0; // Output index (0 or 1)
|
|
||||||
|
|
||||||
// Compute pipelines
|
|
||||||
VkPipeline motionVecPipeline = VK_NULL_HANDLE;
|
|
||||||
VkPipelineLayout motionVecPipelineLayout = VK_NULL_HANDLE;
|
|
||||||
VkDescriptorSetLayout motionVecDescSetLayout = VK_NULL_HANDLE;
|
|
||||||
VkDescriptorPool motionVecDescPool = VK_NULL_HANDLE;
|
|
||||||
VkDescriptorSet motionVecDescSet = VK_NULL_HANDLE;
|
|
||||||
|
|
||||||
VkPipeline accumulatePipeline = VK_NULL_HANDLE;
|
|
||||||
VkPipelineLayout accumulatePipelineLayout = VK_NULL_HANDLE;
|
|
||||||
VkDescriptorSetLayout accumulateDescSetLayout = VK_NULL_HANDLE;
|
|
||||||
VkDescriptorPool accumulateDescPool = VK_NULL_HANDLE;
|
|
||||||
VkDescriptorSet accumulateDescSets[2] = {}; // Per ping-pong
|
|
||||||
|
|
||||||
// RCAS sharpening pass (display resolution)
|
|
||||||
VkPipeline sharpenPipeline = VK_NULL_HANDLE;
|
|
||||||
VkPipelineLayout sharpenPipelineLayout = VK_NULL_HANDLE;
|
|
||||||
VkDescriptorSetLayout sharpenDescSetLayout = VK_NULL_HANDLE;
|
|
||||||
VkDescriptorPool sharpenDescPool = VK_NULL_HANDLE;
|
|
||||||
VkDescriptorSet sharpenDescSets[2] = {};
|
|
||||||
|
|
||||||
// Previous frame state for motion vector reprojection
|
|
||||||
glm::mat4 prevViewProjection = glm::mat4(1.0f);
|
|
||||||
glm::vec2 prevJitter = glm::vec2(0.0f);
|
|
||||||
uint32_t frameIndex = 0;
|
|
||||||
bool needsHistoryReset = true;
|
|
||||||
bool useAmdBackend = false;
|
|
||||||
bool amdFsr3FramegenEnabled = false;
|
|
||||||
bool amdFsr3FramegenRuntimeActive = false;
|
|
||||||
bool amdFsr3FramegenRuntimeReady = false;
|
|
||||||
std::string amdFsr3RuntimePath = "Path C";
|
|
||||||
std::string amdFsr3RuntimeLastError{};
|
|
||||||
size_t amdFsr3UpscaleDispatchCount = 0;
|
|
||||||
size_t amdFsr3FramegenDispatchCount = 0;
|
|
||||||
size_t amdFsr3FallbackCount = 0;
|
|
||||||
uint64_t amdFsr3InteropSyncValue = 1;
|
|
||||||
float jitterSign = 0.38f;
|
|
||||||
float motionVecScaleX = 1.0f;
|
|
||||||
float motionVecScaleY = 1.0f;
|
|
||||||
#if WOWEE_HAS_AMD_FSR2
|
|
||||||
FfxFsr2Context amdContext{};
|
|
||||||
FfxFsr2Interface amdInterface{};
|
|
||||||
void* amdScratchBuffer = nullptr;
|
|
||||||
size_t amdScratchBufferSize = 0;
|
|
||||||
#endif
|
|
||||||
std::unique_ptr<AmdFsr3Runtime> amdFsr3Runtime;
|
|
||||||
|
|
||||||
// Convergent accumulation: jitter for N frames then freeze
|
|
||||||
int convergenceFrame = 0;
|
|
||||||
static constexpr int convergenceMaxFrames = 8;
|
|
||||||
glm::mat4 lastStableVP = glm::mat4(1.0f);
|
|
||||||
};
|
|
||||||
FSR2State fsr2_;
|
|
||||||
bool initFSR2Resources();
|
|
||||||
void destroyFSR2Resources();
|
|
||||||
void dispatchMotionVectors();
|
|
||||||
void dispatchTemporalAccumulate();
|
|
||||||
void dispatchAmdFsr2();
|
|
||||||
void dispatchAmdFsr3Framegen();
|
|
||||||
void renderFSR2Sharpen();
|
|
||||||
static float halton(uint32_t index, uint32_t base);
|
|
||||||
|
|
||||||
// Footstep event tracking (animation-driven)
|
|
||||||
uint32_t footstepLastAnimationId = 0;
|
|
||||||
float footstepLastNormTime = 0.0f;
|
|
||||||
bool footstepNormInitialized = false;
|
|
||||||
|
|
||||||
// Footstep surface cache (avoid expensive queries every step)
|
|
||||||
mutable audio::FootstepSurface cachedFootstepSurface{};
|
|
||||||
mutable glm::vec3 cachedFootstepPosition{0.0f, 0.0f, 0.0f};
|
|
||||||
mutable float cachedFootstepUpdateTimer{999.0f}; // Force initial query
|
|
||||||
|
|
||||||
// Mount footstep tracking (separate from player's)
|
|
||||||
uint32_t mountFootstepLastAnimId = 0;
|
|
||||||
float mountFootstepLastNormTime = 0.0f;
|
|
||||||
bool mountFootstepNormInitialized = false;
|
|
||||||
bool sfxStateInitialized = false;
|
|
||||||
bool sfxPrevGrounded = true;
|
|
||||||
bool sfxPrevJumping = false;
|
|
||||||
bool sfxPrevFalling = false;
|
|
||||||
bool sfxPrevSwimming = false;
|
|
||||||
|
|
||||||
bool charging_ = false;
|
|
||||||
float meleeSwingTimer = 0.0f;
|
|
||||||
float meleeSwingCooldown = 0.0f;
|
|
||||||
float meleeAnimDurationMs = 0.0f;
|
|
||||||
uint32_t meleeAnimId = 0;
|
|
||||||
uint32_t equippedWeaponInvType_ = 0;
|
|
||||||
|
|
||||||
// Mount state
|
|
||||||
// Mount animation capabilities (discovered at mount time, varies per model)
|
|
||||||
struct MountAnimSet {
|
|
||||||
uint32_t jumpStart = 0; // Jump start animation
|
|
||||||
uint32_t jumpLoop = 0; // Jump airborne loop
|
|
||||||
uint32_t jumpEnd = 0; // Jump landing
|
|
||||||
uint32_t rearUp = 0; // Rear-up / special flourish
|
|
||||||
uint32_t run = 0; // Run animation (discovered, don't assume)
|
|
||||||
uint32_t stand = 0; // Stand animation (discovered)
|
|
||||||
std::vector<uint32_t> fidgets; // Idle fidget animations (head turn, tail swish, etc.)
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class MountAction { None, Jump, RearUp };
|
|
||||||
|
|
||||||
uint32_t mountInstanceId_ = 0;
|
|
||||||
float mountHeightOffset_ = 0.0f;
|
|
||||||
float mountPitch_ = 0.0f; // Up/down tilt (radians)
|
|
||||||
float mountRoll_ = 0.0f; // Left/right banking (radians)
|
|
||||||
int mountSeatAttachmentId_ = -1; // -1 unknown, -2 unavailable
|
|
||||||
glm::vec3 smoothedMountSeatPos_ = glm::vec3(0.0f);
|
|
||||||
bool mountSeatSmoothingInit_ = false;
|
|
||||||
float prevMountYaw_ = 0.0f; // Previous yaw for turn rate calculation (procedural lean)
|
|
||||||
float lastDeltaTime_ = 0.0f; // Cached for use in updateCharacterAnimation()
|
|
||||||
MountAction mountAction_ = MountAction::None; // Current mount action (jump/rear-up)
|
|
||||||
uint32_t mountActionPhase_ = 0; // 0=start, 1=loop, 2=end (for jump chaining)
|
|
||||||
MountAnimSet mountAnims_; // Cached animation IDs for current mount
|
|
||||||
float mountIdleFidgetTimer_ = 0.0f; // Timer for random idle fidgets
|
|
||||||
float mountIdleSoundTimer_ = 0.0f; // Timer for ambient idle sounds
|
|
||||||
uint32_t mountActiveFidget_ = 0; // Currently playing fidget animation ID (0 = none)
|
|
||||||
bool taxiFlight_ = false;
|
|
||||||
bool taxiAnimsLogged_ = false;
|
|
||||||
|
|
||||||
// Vulkan frame state
|
// Vulkan frame state
|
||||||
VkContext* vkCtx = nullptr;
|
VkContext* vkCtx = nullptr;
|
||||||
|
|
@ -665,6 +414,7 @@ private:
|
||||||
|
|
||||||
bool parallelRecordingEnabled_ = false; // set true after pools/buffers created
|
bool parallelRecordingEnabled_ = false; // set true after pools/buffers created
|
||||||
bool endFrameInlineMode_ = false; // true when endFrame switched to INLINE render pass
|
bool endFrameInlineMode_ = false; // true when endFrame switched to INLINE render pass
|
||||||
|
float lastDeltaTime_ = 0.0f; // cached for post-process pipeline
|
||||||
bool createSecondaryCommandResources();
|
bool createSecondaryCommandResources();
|
||||||
void destroySecondaryCommandResources();
|
void destroySecondaryCommandResources();
|
||||||
VkCommandBuffer beginSecondary(uint32_t secondaryIndex);
|
VkCommandBuffer beginSecondary(uint32_t secondaryIndex);
|
||||||
|
|
|
||||||
62
include/rendering/spell_visual_system.hpp
Normal file
62
include/rendering/spell_visual_system.hpp
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
namespace wowee {
|
||||||
|
namespace pipeline { class AssetManager; }
|
||||||
|
namespace rendering {
|
||||||
|
|
||||||
|
class M2Renderer;
|
||||||
|
|
||||||
|
class SpellVisualSystem {
|
||||||
|
public:
|
||||||
|
SpellVisualSystem() = default;
|
||||||
|
~SpellVisualSystem() = default;
|
||||||
|
|
||||||
|
// Initialize with references to the M2 renderer (for model loading/instance spawning)
|
||||||
|
void initialize(M2Renderer* m2Renderer);
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
// Spawn a spell visual at a world position.
|
||||||
|
// useImpactKit=false → CastKit path; useImpactKit=true → ImpactKit path
|
||||||
|
void playSpellVisual(uint32_t visualId, const glm::vec3& worldPosition,
|
||||||
|
bool useImpactKit = false);
|
||||||
|
|
||||||
|
// Advance lifetime timers and remove expired instances.
|
||||||
|
void update(float deltaTime);
|
||||||
|
|
||||||
|
// Remove all active spell visual instances and reset caches.
|
||||||
|
// Called on map change / combat reset.
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Spell visual effects — transient M2 instances spawned by SMSG_PLAY_SPELL_VISUAL/IMPACT
|
||||||
|
struct SpellVisualInstance {
|
||||||
|
uint32_t instanceId;
|
||||||
|
float elapsed;
|
||||||
|
float duration; // per-instance lifetime in seconds (from M2 anim or default)
|
||||||
|
};
|
||||||
|
|
||||||
|
void loadSpellVisualDbc();
|
||||||
|
|
||||||
|
M2Renderer* m2Renderer_ = nullptr;
|
||||||
|
pipeline::AssetManager* cachedAssetManager_ = nullptr;
|
||||||
|
|
||||||
|
std::vector<SpellVisualInstance> activeSpellVisuals_;
|
||||||
|
std::unordered_map<uint32_t, std::string> spellVisualCastPath_; // visualId → cast M2 path
|
||||||
|
std::unordered_map<uint32_t, std::string> spellVisualImpactPath_; // visualId → impact M2 path
|
||||||
|
std::unordered_map<std::string, uint32_t> spellVisualModelIds_; // M2 path → M2Renderer modelId
|
||||||
|
std::unordered_set<uint32_t> spellVisualFailedModels_; // modelIds that failed to load (negative cache)
|
||||||
|
uint32_t nextSpellVisualModelId_ = 999000; // Reserved range 999000-999799
|
||||||
|
bool spellVisualDbcLoaded_ = false;
|
||||||
|
static constexpr float SPELL_VISUAL_MAX_DURATION = 5.0f;
|
||||||
|
static constexpr float SPELL_VISUAL_DEFAULT_DURATION = 2.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rendering
|
||||||
|
} // namespace wowee
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
namespace wowee {
|
namespace wowee {
|
||||||
namespace rendering { class Renderer; }
|
namespace rendering { class Renderer; }
|
||||||
|
namespace audio { class AudioCoordinator; }
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
class InventoryScreen;
|
class InventoryScreen;
|
||||||
|
|
@ -144,8 +145,8 @@ public:
|
||||||
void renderSettingsWindow(InventoryScreen& inventoryScreen, ChatPanel& chatPanel,
|
void renderSettingsWindow(InventoryScreen& inventoryScreen, ChatPanel& chatPanel,
|
||||||
std::function<void()> saveCallback);
|
std::function<void()> saveCallback);
|
||||||
|
|
||||||
/// Apply audio volume levels to all renderer sound managers
|
/// Apply audio volume levels to all audio coordinator sound managers
|
||||||
void applyAudioVolumes(rendering::Renderer* renderer);
|
void applyAudioVolumes(audio::AudioCoordinator* ac);
|
||||||
|
|
||||||
/// Return the platform-specific settings file path
|
/// Return the platform-specific settings file path
|
||||||
static std::string getSettingsPath();
|
static std::string getSettingsPath();
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include "core/logger.hpp"
|
#include "core/logger.hpp"
|
||||||
#include "core/application.hpp"
|
#include "core/application.hpp"
|
||||||
#include "rendering/renderer.hpp"
|
#include "rendering/renderer.hpp"
|
||||||
|
#include "audio/audio_coordinator.hpp"
|
||||||
#include "audio/ui_sound_manager.hpp"
|
#include "audio/ui_sound_manager.hpp"
|
||||||
#include "game/expansion_profile.hpp"
|
#include "game/expansion_profile.hpp"
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
@ -1003,9 +1004,9 @@ static int lua_IsInRaid(lua_State* L) {
|
||||||
|
|
||||||
// PlaySound(soundId) — play a WoW UI sound by ID or name
|
// PlaySound(soundId) — play a WoW UI sound by ID or name
|
||||||
static int lua_PlaySound(lua_State* L) {
|
static int lua_PlaySound(lua_State* L) {
|
||||||
auto* renderer = core::Application::getInstance().getRenderer();
|
auto* ac = core::Application::getInstance().getAudioCoordinator();
|
||||||
if (!renderer) return 0;
|
if (!ac) return 0;
|
||||||
auto* sfx = renderer->getUiSoundManager();
|
auto* sfx = ac->getUiSoundManager();
|
||||||
if (!sfx) return 0;
|
if (!sfx) return 0;
|
||||||
|
|
||||||
// Accept numeric sound ID or string name
|
// Accept numeric sound ID or string name
|
||||||
|
|
|
||||||
|
|
@ -50,13 +50,16 @@ bool AudioCoordinator::initialize() {
|
||||||
void AudioCoordinator::initializeWithAssets(pipeline::AssetManager* assetManager) {
|
void AudioCoordinator::initializeWithAssets(pipeline::AssetManager* assetManager) {
|
||||||
if (!audioAvailable_ || !assetManager) return;
|
if (!audioAvailable_ || !assetManager) return;
|
||||||
|
|
||||||
// MusicManager needs asset manager for zone music lookups
|
if (musicManager_) musicManager_->initialize(assetManager);
|
||||||
if (musicManager_) {
|
if (footstepManager_) footstepManager_->initialize(assetManager);
|
||||||
musicManager_->initialize(assetManager);
|
if (activitySoundManager_) activitySoundManager_->initialize(assetManager);
|
||||||
}
|
if (mountSoundManager_) mountSoundManager_->initialize(assetManager);
|
||||||
|
if (npcVoiceManager_) npcVoiceManager_->initialize(assetManager);
|
||||||
// Other managers may need asset manager for sound bank loading
|
if (ambientSoundManager_) ambientSoundManager_->initialize(assetManager);
|
||||||
// (Add similar calls as needed for other managers)
|
if (uiSoundManager_) uiSoundManager_->initialize(assetManager);
|
||||||
|
if (combatSoundManager_) combatSoundManager_->initialize(assetManager);
|
||||||
|
if (spellSoundManager_) spellSoundManager_->initialize(assetManager);
|
||||||
|
if (movementSoundManager_) movementSoundManager_->initialize(assetManager);
|
||||||
|
|
||||||
LOG_INFO("AudioCoordinator initialized with asset manager");
|
LOG_INFO("AudioCoordinator initialized with asset manager");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,11 @@ bool Application::initialize() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create and initialize audio coordinator (owns all audio managers)
|
||||||
|
audioCoordinator_ = std::make_unique<audio::AudioCoordinator>();
|
||||||
|
audioCoordinator_->initialize();
|
||||||
|
renderer->setAudioCoordinator(audioCoordinator_.get());
|
||||||
|
|
||||||
// Create UI manager
|
// Create UI manager
|
||||||
uiManager = std::make_unique<ui::UIManager>();
|
uiManager = std::make_unique<ui::UIManager>();
|
||||||
if (!uiManager->initialize(window.get())) {
|
if (!uiManager->initialize(window.get())) {
|
||||||
|
|
@ -147,6 +152,7 @@ bool Application::initialize() {
|
||||||
|
|
||||||
// Populate game services — all subsystems now available
|
// Populate game services — all subsystems now available
|
||||||
gameServices_.renderer = renderer.get();
|
gameServices_.renderer = renderer.get();
|
||||||
|
gameServices_.audioCoordinator = audioCoordinator_.get();
|
||||||
gameServices_.assetManager = assetManager.get();
|
gameServices_.assetManager = assetManager.get();
|
||||||
gameServices_.expansionRegistry = expansionRegistry_.get();
|
gameServices_.expansionRegistry = expansionRegistry_.get();
|
||||||
|
|
||||||
|
|
@ -845,6 +851,12 @@ void Application::shutdown() {
|
||||||
LOG_WARNING("Renderer shutdown complete, resetting...");
|
LOG_WARNING("Renderer shutdown complete, resetting...");
|
||||||
renderer.reset();
|
renderer.reset();
|
||||||
|
|
||||||
|
// Shutdown audio coordinator after renderer (renderer may reference audio during shutdown)
|
||||||
|
if (audioCoordinator_) {
|
||||||
|
audioCoordinator_->shutdown();
|
||||||
|
}
|
||||||
|
audioCoordinator_.reset();
|
||||||
|
|
||||||
LOG_WARNING("Resetting world...");
|
LOG_WARNING("Resetting world...");
|
||||||
world.reset();
|
world.reset();
|
||||||
LOG_WARNING("Resetting gameHandler...");
|
LOG_WARNING("Resetting gameHandler...");
|
||||||
|
|
@ -1080,7 +1092,7 @@ void Application::logoutToLogin() {
|
||||||
}
|
}
|
||||||
renderer->clearMount();
|
renderer->clearMount();
|
||||||
renderer->setCharacterFollow(0);
|
renderer->setCharacterFollow(0);
|
||||||
if (auto* music = renderer->getMusicManager()) {
|
if (auto* music = audioCoordinator_ ? audioCoordinator_->getMusicManager() : nullptr) {
|
||||||
music->stopMusic(0.0f);
|
music->stopMusic(0.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2817,7 +2829,7 @@ void Application::setupUICallbacks() {
|
||||||
// Resolves soundId → SoundEntries.dbc → MPQ path → MusicManager.
|
// Resolves soundId → SoundEntries.dbc → MPQ path → MusicManager.
|
||||||
gameHandler->setPlayMusicCallback([this](uint32_t soundId) {
|
gameHandler->setPlayMusicCallback([this](uint32_t soundId) {
|
||||||
if (!assetManager || !renderer) return;
|
if (!assetManager || !renderer) return;
|
||||||
auto* music = renderer->getMusicManager();
|
auto* music = audioCoordinator_ ? audioCoordinator_->getMusicManager() : nullptr;
|
||||||
if (!music) return;
|
if (!music) return;
|
||||||
|
|
||||||
auto dbc = assetManager->loadDBC("SoundEntries.dbc");
|
auto dbc = assetManager->loadDBC("SoundEntries.dbc");
|
||||||
|
|
@ -3407,7 +3419,7 @@ void Application::setupUICallbacks() {
|
||||||
|
|
||||||
// NPC greeting callback - play voice line
|
// NPC greeting callback - play voice line
|
||||||
gameHandler->setNpcGreetingCallback([this](uint64_t guid, const glm::vec3& position) {
|
gameHandler->setNpcGreetingCallback([this](uint64_t guid, const glm::vec3& position) {
|
||||||
if (renderer && renderer->getNpcVoiceManager()) {
|
if (audioCoordinator_ && audioCoordinator_->getNpcVoiceManager()) {
|
||||||
// Convert canonical to render coords for 3D audio
|
// Convert canonical to render coords for 3D audio
|
||||||
glm::vec3 renderPos = core::coords::canonicalToRender(position);
|
glm::vec3 renderPos = core::coords::canonicalToRender(position);
|
||||||
|
|
||||||
|
|
@ -3420,13 +3432,13 @@ void Application::setupUICallbacks() {
|
||||||
voiceType = entitySpawner_->detectVoiceTypeFromDisplayId(displayId);
|
voiceType = entitySpawner_->detectVoiceTypeFromDisplayId(displayId);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer->getNpcVoiceManager()->playGreeting(guid, voiceType, renderPos);
|
audioCoordinator_->getNpcVoiceManager()->playGreeting(guid, voiceType, renderPos);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// NPC farewell callback - play farewell voice line
|
// NPC farewell callback - play farewell voice line
|
||||||
gameHandler->setNpcFarewellCallback([this](uint64_t guid, const glm::vec3& position) {
|
gameHandler->setNpcFarewellCallback([this](uint64_t guid, const glm::vec3& position) {
|
||||||
if (renderer && renderer->getNpcVoiceManager()) {
|
if (audioCoordinator_ && audioCoordinator_->getNpcVoiceManager()) {
|
||||||
glm::vec3 renderPos = core::coords::canonicalToRender(position);
|
glm::vec3 renderPos = core::coords::canonicalToRender(position);
|
||||||
|
|
||||||
audio::VoiceType voiceType = audio::VoiceType::GENERIC;
|
audio::VoiceType voiceType = audio::VoiceType::GENERIC;
|
||||||
|
|
@ -3437,13 +3449,13 @@ void Application::setupUICallbacks() {
|
||||||
voiceType = entitySpawner_->detectVoiceTypeFromDisplayId(displayId);
|
voiceType = entitySpawner_->detectVoiceTypeFromDisplayId(displayId);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer->getNpcVoiceManager()->playFarewell(guid, voiceType, renderPos);
|
audioCoordinator_->getNpcVoiceManager()->playFarewell(guid, voiceType, renderPos);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// NPC vendor callback - play vendor voice line
|
// NPC vendor callback - play vendor voice line
|
||||||
gameHandler->setNpcVendorCallback([this](uint64_t guid, const glm::vec3& position) {
|
gameHandler->setNpcVendorCallback([this](uint64_t guid, const glm::vec3& position) {
|
||||||
if (renderer && renderer->getNpcVoiceManager()) {
|
if (audioCoordinator_ && audioCoordinator_->getNpcVoiceManager()) {
|
||||||
glm::vec3 renderPos = core::coords::canonicalToRender(position);
|
glm::vec3 renderPos = core::coords::canonicalToRender(position);
|
||||||
|
|
||||||
audio::VoiceType voiceType = audio::VoiceType::GENERIC;
|
audio::VoiceType voiceType = audio::VoiceType::GENERIC;
|
||||||
|
|
@ -3454,13 +3466,13 @@ void Application::setupUICallbacks() {
|
||||||
voiceType = entitySpawner_->detectVoiceTypeFromDisplayId(displayId);
|
voiceType = entitySpawner_->detectVoiceTypeFromDisplayId(displayId);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer->getNpcVoiceManager()->playVendor(guid, voiceType, renderPos);
|
audioCoordinator_->getNpcVoiceManager()->playVendor(guid, voiceType, renderPos);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// NPC aggro callback - play combat start voice line
|
// NPC aggro callback - play combat start voice line
|
||||||
gameHandler->setNpcAggroCallback([this](uint64_t guid, const glm::vec3& position) {
|
gameHandler->setNpcAggroCallback([this](uint64_t guid, const glm::vec3& position) {
|
||||||
if (renderer && renderer->getNpcVoiceManager()) {
|
if (audioCoordinator_ && audioCoordinator_->getNpcVoiceManager()) {
|
||||||
glm::vec3 renderPos = core::coords::canonicalToRender(position);
|
glm::vec3 renderPos = core::coords::canonicalToRender(position);
|
||||||
|
|
||||||
audio::VoiceType voiceType = audio::VoiceType::GENERIC;
|
audio::VoiceType voiceType = audio::VoiceType::GENERIC;
|
||||||
|
|
@ -3471,7 +3483,7 @@ void Application::setupUICallbacks() {
|
||||||
voiceType = entitySpawner_->detectVoiceTypeFromDisplayId(displayId);
|
voiceType = entitySpawner_->detectVoiceTypeFromDisplayId(displayId);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer->getNpcVoiceManager()->playAggro(guid, voiceType, renderPos);
|
audioCoordinator_->getNpcVoiceManager()->playAggro(guid, voiceType, renderPos);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -3707,7 +3719,7 @@ void Application::spawnPlayerCharacter() {
|
||||||
playerCharacterSpawned = true;
|
playerCharacterSpawned = true;
|
||||||
|
|
||||||
// Set voice profile to match character race/gender
|
// Set voice profile to match character race/gender
|
||||||
if (auto* asm_ = renderer->getActivitySoundManager()) {
|
if (auto* asm_ = audioCoordinator_ ? audioCoordinator_->getActivitySoundManager() : nullptr) {
|
||||||
const char* raceFolder = "Human";
|
const char* raceFolder = "Human";
|
||||||
const char* raceBase = "Human";
|
const char* raceBase = "Human";
|
||||||
switch (playerRace_) {
|
switch (playerRace_) {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include "game/update_field_table.hpp"
|
#include "game/update_field_table.hpp"
|
||||||
#include "game/opcode_table.hpp"
|
#include "game/opcode_table.hpp"
|
||||||
#include "rendering/renderer.hpp"
|
#include "rendering/renderer.hpp"
|
||||||
|
#include "audio/audio_coordinator.hpp"
|
||||||
#include "audio/combat_sound_manager.hpp"
|
#include "audio/combat_sound_manager.hpp"
|
||||||
#include "audio/activity_sound_manager.hpp"
|
#include "audio/activity_sound_manager.hpp"
|
||||||
#include "core/application.hpp"
|
#include "core/application.hpp"
|
||||||
|
|
@ -451,8 +452,8 @@ void CombatHandler::handleAttackerStateUpdate(network::Packet& packet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Play combat sounds via CombatSoundManager + character vocalizations
|
// Play combat sounds via CombatSoundManager + character vocalizations
|
||||||
if (auto* renderer = owner_.services().renderer) {
|
if (auto* ac = owner_.services().audioCoordinator) {
|
||||||
if (auto* csm = renderer->getCombatSoundManager()) {
|
if (auto* csm = ac->getCombatSoundManager()) {
|
||||||
auto weaponSize = audio::CombatSoundManager::WeaponSize::MEDIUM;
|
auto weaponSize = audio::CombatSoundManager::WeaponSize::MEDIUM;
|
||||||
if (data.isMiss()) {
|
if (data.isMiss()) {
|
||||||
csm->playWeaponMiss(false);
|
csm->playWeaponMiss(false);
|
||||||
|
|
@ -466,7 +467,7 @@ void CombatHandler::handleAttackerStateUpdate(network::Packet& packet) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Character vocalizations
|
// Character vocalizations
|
||||||
if (auto* asm_ = renderer->getActivitySoundManager()) {
|
if (auto* asm_ = ac->getActivitySoundManager()) {
|
||||||
if (isPlayerAttacker && !data.isMiss() && data.victimState != 1 && data.victimState != 2) {
|
if (isPlayerAttacker && !data.isMiss() && data.victimState != 1 && data.victimState != 2) {
|
||||||
asm_->playAttackGrunt();
|
asm_->playAttackGrunt();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
#include "game/update_field_table.hpp"
|
#include "game/update_field_table.hpp"
|
||||||
#include "game/expansion_profile.hpp"
|
#include "game/expansion_profile.hpp"
|
||||||
#include "rendering/renderer.hpp"
|
#include "rendering/renderer.hpp"
|
||||||
|
#include "audio/audio_coordinator.hpp"
|
||||||
#include "audio/activity_sound_manager.hpp"
|
#include "audio/activity_sound_manager.hpp"
|
||||||
#include "audio/combat_sound_manager.hpp"
|
#include "audio/combat_sound_manager.hpp"
|
||||||
#include "audio/spell_sound_manager.hpp"
|
#include "audio/spell_sound_manager.hpp"
|
||||||
|
|
@ -599,8 +600,8 @@ static QuestQueryRewards tryParseQuestRewards(const std::vector<uint8_t>& data,
|
||||||
|
|
||||||
template<typename ManagerGetter, typename Callback>
|
template<typename ManagerGetter, typename Callback>
|
||||||
void GameHandler::withSoundManager(ManagerGetter getter, Callback cb) {
|
void GameHandler::withSoundManager(ManagerGetter getter, Callback cb) {
|
||||||
if (auto* renderer = services_.renderer) {
|
if (auto* ac = services_.audioCoordinator) {
|
||||||
if (auto* mgr = (renderer->*getter)()) cb(mgr);
|
if (auto* mgr = (ac->*getter)()) cb(mgr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1198,9 +1199,9 @@ void GameHandler::updateTimers(float deltaTime) {
|
||||||
}
|
}
|
||||||
if (!alreadyAnnounced && pendingLootMoneyAmount_ > 0) {
|
if (!alreadyAnnounced && pendingLootMoneyAmount_ > 0) {
|
||||||
addSystemChatMessage("Looted: " + formatCopperAmount(pendingLootMoneyAmount_));
|
addSystemChatMessage("Looted: " + formatCopperAmount(pendingLootMoneyAmount_));
|
||||||
auto* renderer = services_.renderer;
|
auto* ac = services_.audioCoordinator;
|
||||||
if (renderer) {
|
if (ac) {
|
||||||
if (auto* sfx = renderer->getUiSoundManager()) {
|
if (auto* sfx = ac->getUiSoundManager()) {
|
||||||
if (pendingLootMoneyAmount_ >= 10000) {
|
if (pendingLootMoneyAmount_ >= 10000) {
|
||||||
sfx->playLootCoinLarge();
|
sfx->playLootCoinLarge();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1974,7 +1975,7 @@ void GameHandler::registerOpcodeHandlers() {
|
||||||
ping.age = 0.0f;
|
ping.age = 0.0f;
|
||||||
minimapPings_.push_back(ping);
|
minimapPings_.push_back(ping);
|
||||||
if (senderGuid != playerGuid) {
|
if (senderGuid != playerGuid) {
|
||||||
withSoundManager(&rendering::Renderer::getUiSoundManager, [](auto* sfx) { sfx->playMinimapPing(); });
|
withSoundManager(&audio::AudioCoordinator::getUiSoundManager, [](auto* sfx) { sfx->playMinimapPing(); });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
dispatchTable_[Opcode::SMSG_ZONE_UNDER_ATTACK] = [this](network::Packet& packet) {
|
dispatchTable_[Opcode::SMSG_ZONE_UNDER_ATTACK] = [this](network::Packet& packet) {
|
||||||
|
|
@ -2124,7 +2125,7 @@ void GameHandler::registerOpcodeHandlers() {
|
||||||
if (info && info->type == 17) {
|
if (info && info->type == 17) {
|
||||||
addUIError("A fish is on your line!");
|
addUIError("A fish is on your line!");
|
||||||
addSystemChatMessage("A fish is on your line!");
|
addSystemChatMessage("A fish is on your line!");
|
||||||
withSoundManager(&rendering::Renderer::getUiSoundManager, [](auto* sfx) { sfx->playQuestUpdate(); });
|
withSoundManager(&audio::AudioCoordinator::getUiSoundManager, [](auto* sfx) { sfx->playQuestUpdate(); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2350,7 +2351,7 @@ void GameHandler::registerOpcodeHandlers() {
|
||||||
}
|
}
|
||||||
if (newLevel > oldLevel) {
|
if (newLevel > oldLevel) {
|
||||||
addSystemChatMessage("You have reached level " + std::to_string(newLevel) + "!");
|
addSystemChatMessage("You have reached level " + std::to_string(newLevel) + "!");
|
||||||
withSoundManager(&rendering::Renderer::getUiSoundManager, [](auto* sfx) { sfx->playLevelUp(); });
|
withSoundManager(&audio::AudioCoordinator::getUiSoundManager, [](auto* sfx) { sfx->playLevelUp(); });
|
||||||
if (levelUpCallback_) levelUpCallback_(newLevel);
|
if (levelUpCallback_) levelUpCallback_(newLevel);
|
||||||
fireAddonEvent("PLAYER_LEVEL_UP", {std::to_string(newLevel)});
|
fireAddonEvent("PLAYER_LEVEL_UP", {std::to_string(newLevel)});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include "game/entity.hpp"
|
#include "game/entity.hpp"
|
||||||
#include "game/packet_parsers.hpp"
|
#include "game/packet_parsers.hpp"
|
||||||
#include "rendering/renderer.hpp"
|
#include "rendering/renderer.hpp"
|
||||||
|
#include "audio/audio_coordinator.hpp"
|
||||||
#include "audio/ui_sound_manager.hpp"
|
#include "audio/ui_sound_manager.hpp"
|
||||||
#include "core/application.hpp"
|
#include "core/application.hpp"
|
||||||
#include "core/logger.hpp"
|
#include "core/logger.hpp"
|
||||||
|
|
@ -70,9 +71,9 @@ void InventoryHandler::registerOpcodes(DispatchTable& table) {
|
||||||
}
|
}
|
||||||
if (!alreadyAnnounced) {
|
if (!alreadyAnnounced) {
|
||||||
owner_.addSystemChatMessage("Looted: " + formatCopperAmount(amount));
|
owner_.addSystemChatMessage("Looted: " + formatCopperAmount(amount));
|
||||||
auto* renderer = owner_.services().renderer;
|
auto* ac = owner_.services().audioCoordinator;
|
||||||
if (renderer) {
|
if (ac) {
|
||||||
if (auto* sfx = renderer->getUiSoundManager()) {
|
if (auto* sfx = ac->getUiSoundManager()) {
|
||||||
if (amount >= 10000) sfx->playLootCoinLarge();
|
if (amount >= 10000) sfx->playLootCoinLarge();
|
||||||
else sfx->playLootCoinSmall();
|
else sfx->playLootCoinSmall();
|
||||||
}
|
}
|
||||||
|
|
@ -222,8 +223,8 @@ void InventoryHandler::registerOpcodes(DispatchTable& table) {
|
||||||
std::string msg = "Received item: " + link;
|
std::string msg = "Received item: " + link;
|
||||||
if (count > 1) msg += " x" + std::to_string(count);
|
if (count > 1) msg += " x" + std::to_string(count);
|
||||||
owner_.addSystemChatMessage(msg);
|
owner_.addSystemChatMessage(msg);
|
||||||
if (auto* renderer = owner_.services().renderer) {
|
if (auto* ac = owner_.services().audioCoordinator) {
|
||||||
if (auto* sfx = renderer->getUiSoundManager())
|
if (auto* sfx = ac->getUiSoundManager())
|
||||||
sfx->playLootItem();
|
sfx->playLootItem();
|
||||||
}
|
}
|
||||||
if (owner_.addonEventCallback_) {
|
if (owner_.addonEventCallback_) {
|
||||||
|
|
@ -253,8 +254,8 @@ void InventoryHandler::registerOpcodes(DispatchTable& table) {
|
||||||
" result=", static_cast<int>(result));
|
" result=", static_cast<int>(result));
|
||||||
if (result == 0) {
|
if (result == 0) {
|
||||||
pendingSellToBuyback_.erase(itemGuid);
|
pendingSellToBuyback_.erase(itemGuid);
|
||||||
if (auto* renderer = owner_.services().renderer) {
|
if (auto* ac = owner_.services().audioCoordinator) {
|
||||||
if (auto* sfx = renderer->getUiSoundManager())
|
if (auto* sfx = ac->getUiSoundManager())
|
||||||
sfx->playDropOnGround();
|
sfx->playDropOnGround();
|
||||||
}
|
}
|
||||||
if (owner_.addonEventCallback_) {
|
if (owner_.addonEventCallback_) {
|
||||||
|
|
@ -295,8 +296,8 @@ void InventoryHandler::registerOpcodes(DispatchTable& table) {
|
||||||
const char* msg = (result < 7) ? sellErrors[result] : "Unknown sell error";
|
const char* msg = (result < 7) ? sellErrors[result] : "Unknown sell error";
|
||||||
owner_.addUIError(std::string("Sell failed: ") + msg);
|
owner_.addUIError(std::string("Sell failed: ") + msg);
|
||||||
owner_.addSystemChatMessage(std::string("Sell failed: ") + msg);
|
owner_.addSystemChatMessage(std::string("Sell failed: ") + msg);
|
||||||
if (auto* renderer = owner_.services().renderer) {
|
if (auto* ac = owner_.services().audioCoordinator) {
|
||||||
if (auto* sfx = renderer->getUiSoundManager())
|
if (auto* sfx = ac->getUiSoundManager())
|
||||||
sfx->playError();
|
sfx->playError();
|
||||||
}
|
}
|
||||||
LOG_WARNING("SMSG_SELL_ITEM error: ", (int)result, " (", msg, ")");
|
LOG_WARNING("SMSG_SELL_ITEM error: ", (int)result, " (", msg, ")");
|
||||||
|
|
@ -392,8 +393,8 @@ void InventoryHandler::registerOpcodes(DispatchTable& table) {
|
||||||
std::string msg = errMsg ? errMsg : "Inventory error (" + std::to_string(error) + ").";
|
std::string msg = errMsg ? errMsg : "Inventory error (" + std::to_string(error) + ").";
|
||||||
owner_.addUIError(msg);
|
owner_.addUIError(msg);
|
||||||
owner_.addSystemChatMessage(msg);
|
owner_.addSystemChatMessage(msg);
|
||||||
if (auto* renderer = owner_.services().renderer) {
|
if (auto* ac = owner_.services().audioCoordinator) {
|
||||||
if (auto* sfx = renderer->getUiSoundManager())
|
if (auto* sfx = ac->getUiSoundManager())
|
||||||
sfx->playError();
|
sfx->playError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -450,8 +451,8 @@ void InventoryHandler::registerOpcodes(DispatchTable& table) {
|
||||||
}
|
}
|
||||||
owner_.addUIError(msg);
|
owner_.addUIError(msg);
|
||||||
owner_.addSystemChatMessage(msg);
|
owner_.addSystemChatMessage(msg);
|
||||||
if (auto* renderer = owner_.services().renderer) {
|
if (auto* ac = owner_.services().audioCoordinator) {
|
||||||
if (auto* sfx = renderer->getUiSoundManager())
|
if (auto* sfx = ac->getUiSoundManager())
|
||||||
sfx->playError();
|
sfx->playError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -474,8 +475,8 @@ void InventoryHandler::registerOpcodes(DispatchTable& table) {
|
||||||
std::string msg = "Purchased: " + buildItemLink(pendingBuyItemId_, buyQuality, itemLabel);
|
std::string msg = "Purchased: " + buildItemLink(pendingBuyItemId_, buyQuality, itemLabel);
|
||||||
if (itemCount > 1) msg += " x" + std::to_string(itemCount);
|
if (itemCount > 1) msg += " x" + std::to_string(itemCount);
|
||||||
owner_.addSystemChatMessage(msg);
|
owner_.addSystemChatMessage(msg);
|
||||||
if (auto* renderer = owner_.services().renderer) {
|
if (auto* ac = owner_.services().audioCoordinator) {
|
||||||
if (auto* sfx = renderer->getUiSoundManager())
|
if (auto* sfx = ac->getUiSoundManager())
|
||||||
sfx->playPickupBag();
|
sfx->playPickupBag();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -766,8 +767,8 @@ void InventoryHandler::handleLootRemoved(network::Packet& packet) {
|
||||||
std::string msgStr = "Looted: " + link;
|
std::string msgStr = "Looted: " + link;
|
||||||
if (it->count > 1) msgStr += " x" + std::to_string(it->count);
|
if (it->count > 1) msgStr += " x" + std::to_string(it->count);
|
||||||
owner_.addSystemChatMessage(msgStr);
|
owner_.addSystemChatMessage(msgStr);
|
||||||
if (auto* renderer = owner_.services().renderer) {
|
if (auto* ac = owner_.services().audioCoordinator) {
|
||||||
if (auto* sfx = renderer->getUiSoundManager())
|
if (auto* sfx = ac->getUiSoundManager())
|
||||||
sfx->playLootItem();
|
sfx->playLootItem();
|
||||||
}
|
}
|
||||||
currentLoot_.items.erase(it);
|
currentLoot_.items.erase(it);
|
||||||
|
|
@ -2382,8 +2383,8 @@ void InventoryHandler::handleItemQueryResponse(network::Packet& packet) {
|
||||||
std::string msg = "Received: " + link;
|
std::string msg = "Received: " + link;
|
||||||
if (it->count > 1) msg += " x" + std::to_string(it->count);
|
if (it->count > 1) msg += " x" + std::to_string(it->count);
|
||||||
owner_.addSystemChatMessage(msg);
|
owner_.addSystemChatMessage(msg);
|
||||||
if (auto* renderer = owner_.services().renderer) {
|
if (auto* ac = owner_.services().audioCoordinator) {
|
||||||
if (auto* sfx = renderer->getUiSoundManager()) sfx->playLootItem();
|
if (auto* sfx = ac->getUiSoundManager()) sfx->playLootItem();
|
||||||
}
|
}
|
||||||
if (owner_.itemLootCallback_) owner_.itemLootCallback_(data.entry, it->count, data.quality, itemName);
|
if (owner_.itemLootCallback_) owner_.itemLootCallback_(data.entry, it->count, data.quality, itemName);
|
||||||
it = owner_.pendingItemPushNotifs_.erase(it);
|
it = owner_.pendingItemPushNotifs_.erase(it);
|
||||||
|
|
@ -3149,8 +3150,8 @@ void InventoryHandler::handleTrainerBuySucceeded(network::Packet& packet) {
|
||||||
owner_.addSystemChatMessage("You have learned " + name + ".");
|
owner_.addSystemChatMessage("You have learned " + name + ".");
|
||||||
else
|
else
|
||||||
owner_.addSystemChatMessage("Spell learned.");
|
owner_.addSystemChatMessage("Spell learned.");
|
||||||
if (auto* renderer = owner_.services().renderer)
|
if (auto* ac = owner_.services().audioCoordinator)
|
||||||
if (auto* sfx = renderer->getUiSoundManager()) sfx->playQuestActivate();
|
if (auto* sfx = ac->getUiSoundManager()) sfx->playQuestActivate();
|
||||||
owner_.fireAddonEvent("TRAINER_UPDATE", {});
|
owner_.fireAddonEvent("TRAINER_UPDATE", {});
|
||||||
owner_.fireAddonEvent("SPELLS_CHANGED", {});
|
owner_.fireAddonEvent("SPELLS_CHANGED", {});
|
||||||
}
|
}
|
||||||
|
|
@ -3171,8 +3172,8 @@ void InventoryHandler::handleTrainerBuyFailed(network::Packet& packet) {
|
||||||
else if (errorCode != 0) msg += " (error " + std::to_string(errorCode) + ")";
|
else if (errorCode != 0) msg += " (error " + std::to_string(errorCode) + ")";
|
||||||
owner_.addUIError(msg);
|
owner_.addUIError(msg);
|
||||||
owner_.addSystemChatMessage(msg);
|
owner_.addSystemChatMessage(msg);
|
||||||
if (auto* renderer = owner_.services().renderer)
|
if (auto* ac = owner_.services().audioCoordinator)
|
||||||
if (auto* sfx = renderer->getUiSoundManager()) sfx->playError();
|
if (auto* sfx = ac->getUiSoundManager()) sfx->playError();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include "game/packet_parsers.hpp"
|
#include "game/packet_parsers.hpp"
|
||||||
#include "network/world_socket.hpp"
|
#include "network/world_socket.hpp"
|
||||||
#include "rendering/renderer.hpp"
|
#include "rendering/renderer.hpp"
|
||||||
|
#include "audio/audio_coordinator.hpp"
|
||||||
#include "audio/ui_sound_manager.hpp"
|
#include "audio/ui_sound_manager.hpp"
|
||||||
#include "core/application.hpp"
|
#include "core/application.hpp"
|
||||||
#include "core/logger.hpp"
|
#include "core/logger.hpp"
|
||||||
|
|
@ -469,8 +470,8 @@ void QuestHandler::registerOpcodes(DispatchTable& table) {
|
||||||
owner_.questCompleteCallback_(questId, it->title);
|
owner_.questCompleteCallback_(questId, it->title);
|
||||||
}
|
}
|
||||||
// Play quest-complete sound
|
// Play quest-complete sound
|
||||||
if (auto* renderer = owner_.services().renderer) {
|
if (auto* ac = owner_.services().audioCoordinator) {
|
||||||
if (auto* sfx = renderer->getUiSoundManager())
|
if (auto* sfx = ac->getUiSoundManager())
|
||||||
sfx->playQuestComplete();
|
sfx->playQuestComplete();
|
||||||
}
|
}
|
||||||
questLog_.erase(it);
|
questLog_.erase(it);
|
||||||
|
|
@ -1095,8 +1096,8 @@ void QuestHandler::acceptQuest() {
|
||||||
pendingQuestAcceptNpcGuids_[questId] = npcGuid;
|
pendingQuestAcceptNpcGuids_[questId] = npcGuid;
|
||||||
|
|
||||||
// Play quest-accept sound
|
// Play quest-accept sound
|
||||||
if (auto* renderer = owner_.services().renderer) {
|
if (auto* ac = owner_.services().audioCoordinator) {
|
||||||
if (auto* sfx = renderer->getUiSoundManager())
|
if (auto* sfx = ac->getUiSoundManager())
|
||||||
sfx->playQuestActivate();
|
sfx->playQuestActivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include "game/packet_parsers.hpp"
|
#include "game/packet_parsers.hpp"
|
||||||
#include "game/update_field_table.hpp"
|
#include "game/update_field_table.hpp"
|
||||||
#include "game/opcode_table.hpp"
|
#include "game/opcode_table.hpp"
|
||||||
|
#include "audio/audio_coordinator.hpp"
|
||||||
#include "audio/ui_sound_manager.hpp"
|
#include "audio/ui_sound_manager.hpp"
|
||||||
#include "network/world_socket.hpp"
|
#include "network/world_socket.hpp"
|
||||||
#include "rendering/renderer.hpp"
|
#include "rendering/renderer.hpp"
|
||||||
|
|
@ -1053,8 +1054,8 @@ void SocialHandler::handleDuelRequested(network::Packet& packet) {
|
||||||
}
|
}
|
||||||
pendingDuelRequest_ = true;
|
pendingDuelRequest_ = true;
|
||||||
owner_.addSystemChatMessage(duelChallengerName_ + " challenges you to a duel!");
|
owner_.addSystemChatMessage(duelChallengerName_ + " challenges you to a duel!");
|
||||||
if (auto* renderer = owner_.services().renderer)
|
if (auto* ac = owner_.services().audioCoordinator)
|
||||||
if (auto* sfx = renderer->getUiSoundManager()) sfx->playTargetSelect();
|
if (auto* sfx = ac->getUiSoundManager()) sfx->playTargetSelect();
|
||||||
if (owner_.addonEventCallback_) owner_.addonEventCallback_("DUEL_REQUESTED", {duelChallengerName_});
|
if (owner_.addonEventCallback_) owner_.addonEventCallback_("DUEL_REQUESTED", {duelChallengerName_});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1219,8 +1220,8 @@ void SocialHandler::handleGroupInvite(network::Packet& packet) {
|
||||||
pendingInviterName = data.inviterName;
|
pendingInviterName = data.inviterName;
|
||||||
if (!data.inviterName.empty())
|
if (!data.inviterName.empty())
|
||||||
owner_.addSystemChatMessage(data.inviterName + " has invited you to a group.");
|
owner_.addSystemChatMessage(data.inviterName + " has invited you to a group.");
|
||||||
if (auto* renderer = owner_.services().renderer)
|
if (auto* ac = owner_.services().audioCoordinator)
|
||||||
if (auto* sfx = renderer->getUiSoundManager()) sfx->playTargetSelect();
|
if (auto* sfx = ac->getUiSoundManager()) sfx->playTargetSelect();
|
||||||
if (owner_.addonEventCallback_)
|
if (owner_.addonEventCallback_)
|
||||||
owner_.addonEventCallback_("PARTY_INVITE_REQUEST", {data.inviterName});
|
owner_.addonEventCallback_("PARTY_INVITE_REQUEST", {data.inviterName});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include "game/packet_parsers.hpp"
|
#include "game/packet_parsers.hpp"
|
||||||
#include "game/entity.hpp"
|
#include "game/entity.hpp"
|
||||||
#include "rendering/renderer.hpp"
|
#include "rendering/renderer.hpp"
|
||||||
|
#include "audio/audio_coordinator.hpp"
|
||||||
#include "audio/spell_sound_manager.hpp"
|
#include "audio/spell_sound_manager.hpp"
|
||||||
#include "audio/combat_sound_manager.hpp"
|
#include "audio/combat_sound_manager.hpp"
|
||||||
#include "core/application.hpp"
|
#include "core/application.hpp"
|
||||||
|
|
@ -795,8 +796,8 @@ void SpellHandler::handleCastFailed(network::Packet& packet) {
|
||||||
queuedSpellTarget_ = 0;
|
queuedSpellTarget_ = 0;
|
||||||
|
|
||||||
// Stop precast sound
|
// Stop precast sound
|
||||||
if (auto* renderer = owner_.services().renderer) {
|
if (auto* ac = owner_.services().audioCoordinator) {
|
||||||
if (auto* ssm = renderer->getSpellSoundManager()) {
|
if (auto* ssm = ac->getSpellSoundManager()) {
|
||||||
ssm->stopPrecast();
|
ssm->stopPrecast();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -817,8 +818,8 @@ void SpellHandler::handleCastFailed(network::Packet& packet) {
|
||||||
msg.message = errMsg;
|
msg.message = errMsg;
|
||||||
owner_.addLocalChatMessage(msg);
|
owner_.addLocalChatMessage(msg);
|
||||||
|
|
||||||
if (auto* renderer = owner_.services().renderer) {
|
if (auto* ac = owner_.services().audioCoordinator) {
|
||||||
if (auto* sfx = renderer->getUiSoundManager())
|
if (auto* sfx = ac->getUiSoundManager())
|
||||||
sfx->playError();
|
sfx->playError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -869,8 +870,8 @@ void SpellHandler::handleSpellStart(network::Packet& packet) {
|
||||||
|
|
||||||
// Play precast sound — skip profession/tradeskill spells
|
// Play precast sound — skip profession/tradeskill spells
|
||||||
if (!owner_.isProfessionSpell(data.spellId)) {
|
if (!owner_.isProfessionSpell(data.spellId)) {
|
||||||
if (auto* renderer = owner_.services().renderer) {
|
if (auto* ac = owner_.services().audioCoordinator) {
|
||||||
if (auto* ssm = renderer->getSpellSoundManager()) {
|
if (auto* ssm = ac->getSpellSoundManager()) {
|
||||||
owner_.loadSpellNameCache();
|
owner_.loadSpellNameCache();
|
||||||
auto it = owner_.spellNameCache_.find(data.spellId);
|
auto it = owner_.spellNameCache_.find(data.spellId);
|
||||||
auto school = (it != owner_.spellNameCache_.end() && it->second.schoolMask)
|
auto school = (it != owner_.spellNameCache_.end() && it->second.schoolMask)
|
||||||
|
|
@ -907,8 +908,8 @@ void SpellHandler::handleSpellGo(network::Packet& packet) {
|
||||||
if (data.casterUnit == owner_.playerGuid) {
|
if (data.casterUnit == owner_.playerGuid) {
|
||||||
// Play cast-complete sound
|
// Play cast-complete sound
|
||||||
if (!owner_.isProfessionSpell(data.spellId)) {
|
if (!owner_.isProfessionSpell(data.spellId)) {
|
||||||
if (auto* renderer = owner_.services().renderer) {
|
if (auto* ac = owner_.services().audioCoordinator) {
|
||||||
if (auto* ssm = renderer->getSpellSoundManager()) {
|
if (auto* ssm = ac->getSpellSoundManager()) {
|
||||||
owner_.loadSpellNameCache();
|
owner_.loadSpellNameCache();
|
||||||
auto it = owner_.spellNameCache_.find(data.spellId);
|
auto it = owner_.spellNameCache_.find(data.spellId);
|
||||||
auto school = (it != owner_.spellNameCache_.end() && it->second.schoolMask)
|
auto school = (it != owner_.spellNameCache_.end() && it->second.schoolMask)
|
||||||
|
|
@ -931,8 +932,8 @@ void SpellHandler::handleSpellGo(network::Packet& packet) {
|
||||||
}
|
}
|
||||||
if (isMeleeAbility) {
|
if (isMeleeAbility) {
|
||||||
if (owner_.meleeSwingCallback_) owner_.meleeSwingCallback_();
|
if (owner_.meleeSwingCallback_) owner_.meleeSwingCallback_();
|
||||||
if (auto* renderer = owner_.services().renderer) {
|
if (auto* ac = owner_.services().audioCoordinator) {
|
||||||
if (auto* csm = renderer->getCombatSoundManager()) {
|
if (auto* csm = ac->getCombatSoundManager()) {
|
||||||
csm->playWeaponSwing(audio::CombatSoundManager::WeaponSize::MEDIUM, false);
|
csm->playWeaponSwing(audio::CombatSoundManager::WeaponSize::MEDIUM, false);
|
||||||
csm->playImpact(audio::CombatSoundManager::WeaponSize::MEDIUM,
|
csm->playImpact(audio::CombatSoundManager::WeaponSize::MEDIUM,
|
||||||
audio::CombatSoundManager::ImpactType::FLESH, false);
|
audio::CombatSoundManager::ImpactType::FLESH, false);
|
||||||
|
|
@ -990,8 +991,8 @@ void SpellHandler::handleSpellGo(network::Packet& packet) {
|
||||||
if (tgt == owner_.playerGuid) { targetsPlayer = true; break; }
|
if (tgt == owner_.playerGuid) { targetsPlayer = true; break; }
|
||||||
}
|
}
|
||||||
if (targetsPlayer) {
|
if (targetsPlayer) {
|
||||||
if (auto* renderer = owner_.services().renderer) {
|
if (auto* ac = owner_.services().audioCoordinator) {
|
||||||
if (auto* ssm = renderer->getSpellSoundManager()) {
|
if (auto* ssm = ac->getSpellSoundManager()) {
|
||||||
owner_.loadSpellNameCache();
|
owner_.loadSpellNameCache();
|
||||||
auto it = owner_.spellNameCache_.find(data.spellId);
|
auto it = owner_.spellNameCache_.find(data.spellId);
|
||||||
auto school = (it != owner_.spellNameCache_.end() && it->second.schoolMask)
|
auto school = (it != owner_.spellNameCache_.end() && it->second.schoolMask)
|
||||||
|
|
@ -1036,8 +1037,8 @@ void SpellHandler::handleSpellGo(network::Packet& packet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playerIsHit || playerHitEnemy) {
|
if (playerIsHit || playerHitEnemy) {
|
||||||
if (auto* renderer = owner_.services().renderer) {
|
if (auto* ac = owner_.services().audioCoordinator) {
|
||||||
if (auto* ssm = renderer->getSpellSoundManager()) {
|
if (auto* ssm = ac->getSpellSoundManager()) {
|
||||||
owner_.loadSpellNameCache();
|
owner_.loadSpellNameCache();
|
||||||
auto it = owner_.spellNameCache_.find(data.spellId);
|
auto it = owner_.spellNameCache_.find(data.spellId);
|
||||||
auto school = (it != owner_.spellNameCache_.end() && it->second.schoolMask)
|
auto school = (it != owner_.spellNameCache_.end() && it->second.schoolMask)
|
||||||
|
|
@ -1396,8 +1397,8 @@ void SpellHandler::handleAchievementEarned(network::Packet& packet) {
|
||||||
|
|
||||||
owner_.earnedAchievements_.insert(achievementId);
|
owner_.earnedAchievements_.insert(achievementId);
|
||||||
owner_.achievementDates_[achievementId] = earnDate;
|
owner_.achievementDates_[achievementId] = earnDate;
|
||||||
if (auto* renderer = owner_.services().renderer) {
|
if (auto* ac = owner_.services().audioCoordinator) {
|
||||||
if (auto* sfx = renderer->getUiSoundManager())
|
if (auto* sfx = ac->getUiSoundManager())
|
||||||
sfx->playAchievementAlert();
|
sfx->playAchievementAlert();
|
||||||
}
|
}
|
||||||
if (owner_.achievementEarnedCallback_) {
|
if (owner_.achievementEarnedCallback_) {
|
||||||
|
|
@ -2350,8 +2351,8 @@ void SpellHandler::handleSpellFailure(network::Packet& packet) {
|
||||||
craftQueueRemaining_ = 0;
|
craftQueueRemaining_ = 0;
|
||||||
queuedSpellId_ = 0;
|
queuedSpellId_ = 0;
|
||||||
queuedSpellTarget_ = 0;
|
queuedSpellTarget_ = 0;
|
||||||
if (auto* renderer = owner_.services().renderer) {
|
if (auto* ac = owner_.services().audioCoordinator) {
|
||||||
if (auto* ssm = renderer->getSpellSoundManager()) {
|
if (auto* ssm = ac->getSpellSoundManager()) {
|
||||||
ssm->stopPrecast();
|
ssm->stopPrecast();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1703
src/rendering/animation_controller.cpp
Normal file
1703
src/rendering/animation_controller.cpp
Normal file
File diff suppressed because it is too large
Load diff
1879
src/rendering/post_process_pipeline.cpp
Normal file
1879
src/rendering/post_process_pipeline.cpp
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
231
src/rendering/spell_visual_system.cpp
Normal file
231
src/rendering/spell_visual_system.cpp
Normal file
|
|
@ -0,0 +1,231 @@
|
||||||
|
#include "rendering/spell_visual_system.hpp"
|
||||||
|
#include "rendering/m2_renderer.hpp"
|
||||||
|
#include "pipeline/asset_manager.hpp"
|
||||||
|
#include "pipeline/dbc_loader.hpp"
|
||||||
|
#include "pipeline/dbc_layout.hpp"
|
||||||
|
#include "pipeline/m2_loader.hpp"
|
||||||
|
#include "core/application.hpp"
|
||||||
|
#include "core/logger.hpp"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace wowee {
|
||||||
|
namespace rendering {
|
||||||
|
|
||||||
|
void SpellVisualSystem::initialize(M2Renderer* m2Renderer) {
|
||||||
|
m2Renderer_ = m2Renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpellVisualSystem::shutdown() {
|
||||||
|
reset();
|
||||||
|
m2Renderer_ = nullptr;
|
||||||
|
cachedAssetManager_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load SpellVisual DBC chain: SpellVisualEffectName → SpellVisualKit → SpellVisual
|
||||||
|
// to build cast/impact M2 path lookup maps.
|
||||||
|
void SpellVisualSystem::loadSpellVisualDbc() {
|
||||||
|
if (spellVisualDbcLoaded_) return;
|
||||||
|
spellVisualDbcLoaded_ = true; // Set early to prevent re-entry on failure
|
||||||
|
|
||||||
|
if (!cachedAssetManager_) {
|
||||||
|
cachedAssetManager_ = core::Application::getInstance().getAssetManager();
|
||||||
|
}
|
||||||
|
if (!cachedAssetManager_) return;
|
||||||
|
|
||||||
|
auto* layout = pipeline::getActiveDBCLayout();
|
||||||
|
const pipeline::DBCFieldMap* svLayout = layout ? layout->getLayout("SpellVisual") : nullptr;
|
||||||
|
const pipeline::DBCFieldMap* kitLayout = layout ? layout->getLayout("SpellVisualKit") : nullptr;
|
||||||
|
const pipeline::DBCFieldMap* fxLayout = layout ? layout->getLayout("SpellVisualEffectName") : nullptr;
|
||||||
|
|
||||||
|
uint32_t svCastKitField = svLayout ? (*svLayout)["CastKit"] : 2;
|
||||||
|
uint32_t svImpactKitField = svLayout ? (*svLayout)["ImpactKit"] : 3;
|
||||||
|
uint32_t svMissileField = svLayout ? (*svLayout)["MissileModel"] : 8;
|
||||||
|
uint32_t kitSpecial0Field = kitLayout ? (*kitLayout)["SpecialEffect0"] : 11;
|
||||||
|
uint32_t kitBaseField = kitLayout ? (*kitLayout)["BaseEffect"] : 5;
|
||||||
|
uint32_t fxFilePathField = fxLayout ? (*fxLayout)["FilePath"] : 2;
|
||||||
|
|
||||||
|
// Helper to look up effectName path from a kit ID
|
||||||
|
// Load SpellVisualEffectName.dbc — ID → M2 path
|
||||||
|
auto fxDbc = cachedAssetManager_->loadDBC("SpellVisualEffectName.dbc");
|
||||||
|
if (!fxDbc || !fxDbc->isLoaded() || fxDbc->getFieldCount() <= fxFilePathField) {
|
||||||
|
LOG_DEBUG("SpellVisual: SpellVisualEffectName.dbc unavailable (fc=",
|
||||||
|
fxDbc ? fxDbc->getFieldCount() : 0, ")");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::unordered_map<uint32_t, std::string> effectPaths; // effectNameId → path
|
||||||
|
for (uint32_t i = 0; i < fxDbc->getRecordCount(); ++i) {
|
||||||
|
uint32_t id = fxDbc->getUInt32(i, 0);
|
||||||
|
std::string p = fxDbc->getString(i, fxFilePathField);
|
||||||
|
if (id && !p.empty()) effectPaths[id] = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load SpellVisualKit.dbc — kitId → best SpellVisualEffectName ID
|
||||||
|
auto kitDbc = cachedAssetManager_->loadDBC("SpellVisualKit.dbc");
|
||||||
|
std::unordered_map<uint32_t, uint32_t> kitToEffectName; // kitId → effectNameId
|
||||||
|
if (kitDbc && kitDbc->isLoaded()) {
|
||||||
|
uint32_t fc = kitDbc->getFieldCount();
|
||||||
|
for (uint32_t i = 0; i < kitDbc->getRecordCount(); ++i) {
|
||||||
|
uint32_t kitId = kitDbc->getUInt32(i, 0);
|
||||||
|
if (!kitId) continue;
|
||||||
|
// Prefer SpecialEffect0, fall back to BaseEffect
|
||||||
|
uint32_t eff = 0;
|
||||||
|
if (kitSpecial0Field < fc) eff = kitDbc->getUInt32(i, kitSpecial0Field);
|
||||||
|
if (!eff && kitBaseField < fc) eff = kitDbc->getUInt32(i, kitBaseField);
|
||||||
|
if (eff) kitToEffectName[kitId] = eff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: resolve path for a given kit ID
|
||||||
|
auto kitPath = [&](uint32_t kitId) -> std::string {
|
||||||
|
if (!kitId) return {};
|
||||||
|
auto kitIt = kitToEffectName.find(kitId);
|
||||||
|
if (kitIt == kitToEffectName.end()) return {};
|
||||||
|
auto fxIt = effectPaths.find(kitIt->second);
|
||||||
|
return (fxIt != effectPaths.end()) ? fxIt->second : std::string{};
|
||||||
|
};
|
||||||
|
auto missilePath = [&](uint32_t effId) -> std::string {
|
||||||
|
if (!effId) return {};
|
||||||
|
auto fxIt = effectPaths.find(effId);
|
||||||
|
return (fxIt != effectPaths.end()) ? fxIt->second : std::string{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load SpellVisual.dbc — visualId → cast/impact M2 paths via kit chain
|
||||||
|
auto svDbc = cachedAssetManager_->loadDBC("SpellVisual.dbc");
|
||||||
|
if (!svDbc || !svDbc->isLoaded()) {
|
||||||
|
LOG_DEBUG("SpellVisual: SpellVisual.dbc unavailable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint32_t svFc = svDbc->getFieldCount();
|
||||||
|
uint32_t loadedCast = 0, loadedImpact = 0;
|
||||||
|
for (uint32_t i = 0; i < svDbc->getRecordCount(); ++i) {
|
||||||
|
uint32_t vid = svDbc->getUInt32(i, 0);
|
||||||
|
if (!vid) continue;
|
||||||
|
|
||||||
|
// Cast path: CastKit → SpecialEffect0/BaseEffect, fallback to MissileModel
|
||||||
|
{
|
||||||
|
std::string path;
|
||||||
|
if (svCastKitField < svFc)
|
||||||
|
path = kitPath(svDbc->getUInt32(i, svCastKitField));
|
||||||
|
if (path.empty() && svMissileField < svFc)
|
||||||
|
path = missilePath(svDbc->getUInt32(i, svMissileField));
|
||||||
|
if (!path.empty()) { spellVisualCastPath_[vid] = path; ++loadedCast; }
|
||||||
|
}
|
||||||
|
// Impact path: ImpactKit → SpecialEffect0/BaseEffect, fallback to MissileModel
|
||||||
|
{
|
||||||
|
std::string path;
|
||||||
|
if (svImpactKitField < svFc)
|
||||||
|
path = kitPath(svDbc->getUInt32(i, svImpactKitField));
|
||||||
|
if (path.empty() && svMissileField < svFc)
|
||||||
|
path = missilePath(svDbc->getUInt32(i, svMissileField));
|
||||||
|
if (!path.empty()) { spellVisualImpactPath_[vid] = path; ++loadedImpact; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG_INFO("SpellVisual: loaded cast=", loadedCast, " impact=", loadedImpact,
|
||||||
|
" visual→M2 mappings (of ", svDbc->getRecordCount(), " records)");
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpellVisualSystem::playSpellVisual(uint32_t visualId, const glm::vec3& worldPosition,
|
||||||
|
bool useImpactKit) {
|
||||||
|
if (!m2Renderer_ || visualId == 0) return;
|
||||||
|
|
||||||
|
if (!cachedAssetManager_)
|
||||||
|
cachedAssetManager_ = core::Application::getInstance().getAssetManager();
|
||||||
|
if (!cachedAssetManager_) return;
|
||||||
|
|
||||||
|
if (!spellVisualDbcLoaded_) loadSpellVisualDbc();
|
||||||
|
|
||||||
|
// Select cast or impact path map
|
||||||
|
auto& pathMap = useImpactKit ? spellVisualImpactPath_ : spellVisualCastPath_;
|
||||||
|
auto pathIt = pathMap.find(visualId);
|
||||||
|
if (pathIt == pathMap.end()) return; // No model for this visual
|
||||||
|
|
||||||
|
const std::string& modelPath = pathIt->second;
|
||||||
|
|
||||||
|
// Get or assign a model ID for this path
|
||||||
|
auto midIt = spellVisualModelIds_.find(modelPath);
|
||||||
|
uint32_t modelId = 0;
|
||||||
|
if (midIt != spellVisualModelIds_.end()) {
|
||||||
|
modelId = midIt->second;
|
||||||
|
} else {
|
||||||
|
if (nextSpellVisualModelId_ >= 999800) {
|
||||||
|
LOG_WARNING("SpellVisual: model ID pool exhausted");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
modelId = nextSpellVisualModelId_++;
|
||||||
|
spellVisualModelIds_[modelPath] = modelId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip models that have previously failed to load (avoid repeated I/O)
|
||||||
|
if (spellVisualFailedModels_.count(modelId)) return;
|
||||||
|
|
||||||
|
// Load the M2 model if not already loaded
|
||||||
|
if (!m2Renderer_->hasModel(modelId)) {
|
||||||
|
auto m2Data = cachedAssetManager_->readFile(modelPath);
|
||||||
|
if (m2Data.empty()) {
|
||||||
|
LOG_DEBUG("SpellVisual: could not read model: ", modelPath);
|
||||||
|
spellVisualFailedModels_.insert(modelId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pipeline::M2Model model = pipeline::M2Loader::load(m2Data);
|
||||||
|
if (model.vertices.empty() && model.particleEmitters.empty()) {
|
||||||
|
LOG_DEBUG("SpellVisual: empty model: ", modelPath);
|
||||||
|
spellVisualFailedModels_.insert(modelId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Load skin file for WotLK-format M2s
|
||||||
|
if (model.version >= 264) {
|
||||||
|
std::string skinPath = modelPath.substr(0, modelPath.rfind('.')) + "00.skin";
|
||||||
|
auto skinData = cachedAssetManager_->readFile(skinPath);
|
||||||
|
if (!skinData.empty()) pipeline::M2Loader::loadSkin(skinData, model);
|
||||||
|
}
|
||||||
|
if (!m2Renderer_->loadModel(model, modelId)) {
|
||||||
|
LOG_WARNING("SpellVisual: failed to load model to GPU: ", modelPath);
|
||||||
|
spellVisualFailedModels_.insert(modelId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOG_DEBUG("SpellVisual: loaded model id=", modelId, " path=", modelPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn instance at world position
|
||||||
|
uint32_t instanceId = m2Renderer_->createInstance(modelId, worldPosition,
|
||||||
|
glm::vec3(0.0f), 1.0f);
|
||||||
|
if (instanceId == 0) {
|
||||||
|
LOG_WARNING("SpellVisual: failed to create instance for visualId=", visualId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Determine lifetime from M2 animation duration (clamp to reasonable range)
|
||||||
|
float animDurMs = m2Renderer_->getInstanceAnimDuration(instanceId);
|
||||||
|
float duration = (animDurMs > 100.0f)
|
||||||
|
? std::clamp(animDurMs / 1000.0f, 0.5f, SPELL_VISUAL_MAX_DURATION)
|
||||||
|
: SPELL_VISUAL_DEFAULT_DURATION;
|
||||||
|
activeSpellVisuals_.push_back({instanceId, 0.0f, duration});
|
||||||
|
LOG_DEBUG("SpellVisual: spawned visualId=", visualId, " instanceId=", instanceId,
|
||||||
|
" duration=", duration, "s model=", modelPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpellVisualSystem::update(float deltaTime) {
|
||||||
|
if (activeSpellVisuals_.empty() || !m2Renderer_) return;
|
||||||
|
for (auto it = activeSpellVisuals_.begin(); it != activeSpellVisuals_.end(); ) {
|
||||||
|
it->elapsed += deltaTime;
|
||||||
|
if (it->elapsed >= it->duration) {
|
||||||
|
m2Renderer_->removeInstance(it->instanceId);
|
||||||
|
it = activeSpellVisuals_.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpellVisualSystem::reset() {
|
||||||
|
// Clear lingering spell visual instances from the previous map/combat session.
|
||||||
|
// Without this, old effects could remain visible after teleport or map change.
|
||||||
|
for (auto& sv : activeSpellVisuals_) {
|
||||||
|
if (m2Renderer_) m2Renderer_->removeInstance(sv.instanceId);
|
||||||
|
}
|
||||||
|
activeSpellVisuals_.clear();
|
||||||
|
// Reset the negative cache so models that failed during asset loading can retry.
|
||||||
|
spellVisualFailedModels_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace rendering
|
||||||
|
} // namespace wowee
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include "rendering/renderer.hpp"
|
#include "rendering/renderer.hpp"
|
||||||
#include "rendering/vk_context.hpp"
|
#include "rendering/vk_context.hpp"
|
||||||
#include "pipeline/asset_manager.hpp"
|
#include "pipeline/asset_manager.hpp"
|
||||||
|
#include "audio/audio_coordinator.hpp"
|
||||||
#include "audio/music_manager.hpp"
|
#include "audio/music_manager.hpp"
|
||||||
#include "game/expansion_profile.hpp"
|
#include "game/expansion_profile.hpp"
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
@ -199,20 +200,20 @@ void AuthScreen::render(auth::AuthHandler& authHandler) {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& app = core::Application::getInstance();
|
auto& app = core::Application::getInstance();
|
||||||
auto* renderer = app.getRenderer();
|
auto* ac = app.getAudioCoordinator();
|
||||||
if (!musicInitAttempted) {
|
if (!musicInitAttempted) {
|
||||||
musicInitAttempted = true;
|
musicInitAttempted = true;
|
||||||
auto* assets = app.getAssetManager();
|
auto* assets = app.getAssetManager();
|
||||||
if (renderer) {
|
if (ac) {
|
||||||
auto* music = renderer->getMusicManager();
|
auto* music = ac->getMusicManager();
|
||||||
if (music && assets && assets->isInitialized() && !music->isInitialized()) {
|
if (music && assets && assets->isInitialized() && !music->isInitialized()) {
|
||||||
music->initialize(assets);
|
music->initialize(assets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Login screen music
|
// Login screen music
|
||||||
if (renderer) {
|
if (ac) {
|
||||||
auto* music = renderer->getMusicManager();
|
auto* music = ac->getMusicManager();
|
||||||
if (music) {
|
if (music) {
|
||||||
if (!loginMusicVolumeAdjusted_) {
|
if (!loginMusicVolumeAdjusted_) {
|
||||||
savedMusicVolume_ = music->getVolume();
|
savedMusicVolume_ = music->getVolume();
|
||||||
|
|
@ -506,9 +507,9 @@ void AuthScreen::render(auth::AuthHandler& authHandler) {
|
||||||
|
|
||||||
void AuthScreen::stopLoginMusic() {
|
void AuthScreen::stopLoginMusic() {
|
||||||
auto& app = core::Application::getInstance();
|
auto& app = core::Application::getInstance();
|
||||||
auto* renderer = app.getRenderer();
|
auto* ac = app.getAudioCoordinator();
|
||||||
if (!renderer) return;
|
if (!ac) return;
|
||||||
auto* music = renderer->getMusicManager();
|
auto* music = ac->getMusicManager();
|
||||||
if (!music) return;
|
if (!music) return;
|
||||||
if (musicPlaying) {
|
if (musicPlaying) {
|
||||||
music->stopMusic(500.0f);
|
music->stopMusic(500.0f);
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include "rendering/renderer.hpp"
|
#include "rendering/renderer.hpp"
|
||||||
#include "rendering/camera.hpp"
|
#include "rendering/camera.hpp"
|
||||||
#include "rendering/camera_controller.hpp"
|
#include "rendering/camera_controller.hpp"
|
||||||
|
#include "audio/audio_coordinator.hpp"
|
||||||
#include "audio/audio_engine.hpp"
|
#include "audio/audio_engine.hpp"
|
||||||
#include "audio/ui_sound_manager.hpp"
|
#include "audio/ui_sound_manager.hpp"
|
||||||
#include "pipeline/asset_manager.hpp"
|
#include "pipeline/asset_manager.hpp"
|
||||||
|
|
@ -1109,8 +1110,8 @@ 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 = services_.renderer) {
|
if (auto* ac = services_.audioCoordinator) {
|
||||||
if (auto* ui = renderer->getUiSoundManager())
|
if (auto* ui = ac->getUiSoundManager())
|
||||||
ui->playWhisperReceived();
|
ui->playWhisperReceived();
|
||||||
}
|
}
|
||||||
break; // play at most once per scan pass
|
break; // play at most once per scan pass
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
#include "rendering/camera.hpp"
|
#include "rendering/camera.hpp"
|
||||||
#include "game/game_handler.hpp"
|
#include "game/game_handler.hpp"
|
||||||
#include "pipeline/asset_manager.hpp"
|
#include "pipeline/asset_manager.hpp"
|
||||||
|
#include "audio/audio_coordinator.hpp"
|
||||||
#include "audio/audio_engine.hpp"
|
#include "audio/audio_engine.hpp"
|
||||||
#include "audio/ui_sound_manager.hpp"
|
#include "audio/ui_sound_manager.hpp"
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
@ -283,9 +284,11 @@ void CombatUI::renderRaidWarningOverlay(game::GameHandler& gameHandler) {
|
||||||
raidWarnEntries_.erase(raidWarnEntries_.begin());
|
raidWarnEntries_.erase(raidWarnEntries_.begin());
|
||||||
}
|
}
|
||||||
// Whisper audio notification
|
// Whisper audio notification
|
||||||
if (msg.type == game::ChatType::WHISPER && renderer) {
|
if (msg.type == game::ChatType::WHISPER) {
|
||||||
if (auto* ui = renderer->getUiSoundManager())
|
if (auto* ac = services_.audioCoordinator) {
|
||||||
ui->playWhisperReceived();
|
if (auto* ui = ac->getUiSoundManager())
|
||||||
|
ui->playWhisperReceived();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
raidWarnChatSeenCount_ = newCount;
|
raidWarnChatSeenCount_ = newCount;
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
#include "rendering/character_renderer.hpp"
|
#include "rendering/character_renderer.hpp"
|
||||||
#include "rendering/camera.hpp"
|
#include "rendering/camera.hpp"
|
||||||
#include "rendering/camera_controller.hpp"
|
#include "rendering/camera_controller.hpp"
|
||||||
|
#include "audio/audio_coordinator.hpp"
|
||||||
#include "audio/audio_engine.hpp"
|
#include "audio/audio_engine.hpp"
|
||||||
#include "audio/music_manager.hpp"
|
#include "audio/music_manager.hpp"
|
||||||
#include "game/zone_manager.hpp"
|
#include "game/zone_manager.hpp"
|
||||||
|
|
@ -285,8 +286,8 @@ 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 = services_.renderer) {
|
if (auto* ac = services_.audioCoordinator) {
|
||||||
if (auto* sfx = r->getUiSoundManager()) sfx->playError();
|
if (auto* sfx = ac->getUiSoundManager()) sfx->playError();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
uiErrorCallbackSet_ = true;
|
uiErrorCallbackSet_ = true;
|
||||||
|
|
@ -345,9 +346,9 @@ 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 = services_.renderer;
|
auto* ac = services_.audioCoordinator;
|
||||||
if (renderer && renderer->getUiSoundManager()) {
|
if (ac && ac->getUiSoundManager()) {
|
||||||
settingsPanel_.applyAudioVolumes(renderer);
|
settingsPanel_.applyAudioVolumes(ac);
|
||||||
settingsPanel_.volumeSettingsApplied_ = true;
|
settingsPanel_.volumeSettingsApplied_ = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6525,38 +6526,38 @@ void GameScreen::renderMinimapMarkers(game::GameHandler& gameHandler) {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto applyMuteState = [&]() {
|
auto applyMuteState = [&]() {
|
||||||
auto* activeRenderer = services_.renderer;
|
auto* ac = services_.audioCoordinator;
|
||||||
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 (!ac) return;
|
||||||
if (auto* music = activeRenderer->getMusicManager()) {
|
if (auto* music = ac->getMusicManager()) {
|
||||||
music->setVolume(settingsPanel_.pendingMusicVolume);
|
music->setVolume(settingsPanel_.pendingMusicVolume);
|
||||||
}
|
}
|
||||||
if (auto* ambient = activeRenderer->getAmbientSoundManager()) {
|
if (auto* ambient = ac->getAmbientSoundManager()) {
|
||||||
ambient->setVolumeScale(settingsPanel_.pendingAmbientVolume / 100.0f);
|
ambient->setVolumeScale(settingsPanel_.pendingAmbientVolume / 100.0f);
|
||||||
}
|
}
|
||||||
if (auto* ui = activeRenderer->getUiSoundManager()) {
|
if (auto* ui = ac->getUiSoundManager()) {
|
||||||
ui->setVolumeScale(settingsPanel_.pendingUiVolume / 100.0f);
|
ui->setVolumeScale(settingsPanel_.pendingUiVolume / 100.0f);
|
||||||
}
|
}
|
||||||
if (auto* combat = activeRenderer->getCombatSoundManager()) {
|
if (auto* combat = ac->getCombatSoundManager()) {
|
||||||
combat->setVolumeScale(settingsPanel_.pendingCombatVolume / 100.0f);
|
combat->setVolumeScale(settingsPanel_.pendingCombatVolume / 100.0f);
|
||||||
}
|
}
|
||||||
if (auto* spell = activeRenderer->getSpellSoundManager()) {
|
if (auto* spell = ac->getSpellSoundManager()) {
|
||||||
spell->setVolumeScale(settingsPanel_.pendingSpellVolume / 100.0f);
|
spell->setVolumeScale(settingsPanel_.pendingSpellVolume / 100.0f);
|
||||||
}
|
}
|
||||||
if (auto* movement = activeRenderer->getMovementSoundManager()) {
|
if (auto* movement = ac->getMovementSoundManager()) {
|
||||||
movement->setVolumeScale(settingsPanel_.pendingMovementVolume / 100.0f);
|
movement->setVolumeScale(settingsPanel_.pendingMovementVolume / 100.0f);
|
||||||
}
|
}
|
||||||
if (auto* footstep = activeRenderer->getFootstepManager()) {
|
if (auto* footstep = ac->getFootstepManager()) {
|
||||||
footstep->setVolumeScale(settingsPanel_.pendingFootstepVolume / 100.0f);
|
footstep->setVolumeScale(settingsPanel_.pendingFootstepVolume / 100.0f);
|
||||||
}
|
}
|
||||||
if (auto* npcVoice = activeRenderer->getNpcVoiceManager()) {
|
if (auto* npcVoice = ac->getNpcVoiceManager()) {
|
||||||
npcVoice->setVolumeScale(settingsPanel_.pendingNpcVoiceVolume / 100.0f);
|
npcVoice->setVolumeScale(settingsPanel_.pendingNpcVoiceVolume / 100.0f);
|
||||||
}
|
}
|
||||||
if (auto* mount = activeRenderer->getMountSoundManager()) {
|
if (auto* mount = ac->getMountSoundManager()) {
|
||||||
mount->setVolumeScale(settingsPanel_.pendingMountVolume / 100.0f);
|
mount->setVolumeScale(settingsPanel_.pendingMountVolume / 100.0f);
|
||||||
}
|
}
|
||||||
if (auto* activity = activeRenderer->getActivitySoundManager()) {
|
if (auto* activity = ac->getActivitySoundManager()) {
|
||||||
activity->setVolumeScale(settingsPanel_.pendingActivityVolume / 100.0f);
|
activity->setVolumeScale(settingsPanel_.pendingActivityVolume / 100.0f);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
#include "rendering/wmo_renderer.hpp"
|
#include "rendering/wmo_renderer.hpp"
|
||||||
#include "rendering/character_renderer.hpp"
|
#include "rendering/character_renderer.hpp"
|
||||||
#include "game/zone_manager.hpp"
|
#include "game/zone_manager.hpp"
|
||||||
|
#include "audio/audio_coordinator.hpp"
|
||||||
#include "audio/audio_engine.hpp"
|
#include "audio/audio_engine.hpp"
|
||||||
#include "audio/music_manager.hpp"
|
#include "audio/music_manager.hpp"
|
||||||
#include "audio/ambient_sound_manager.hpp"
|
#include "audio/ambient_sound_manager.hpp"
|
||||||
|
|
@ -439,7 +440,7 @@ ImGui::BeginChild("AudioSettings", ImVec2(0, 360), true);
|
||||||
|
|
||||||
// Helper lambda to apply audio settings
|
// Helper lambda to apply audio settings
|
||||||
auto applyAudioSettings = [&]() {
|
auto applyAudioSettings = [&]() {
|
||||||
applyAudioVolumes(renderer);
|
applyAudioVolumes(services_.audioCoordinator);
|
||||||
saveCallback();
|
saveCallback();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1227,29 +1228,29 @@ std::string SettingsPanel::getSettingsPath() {
|
||||||
return dir + "/settings.cfg";
|
return dir + "/settings.cfg";
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsPanel::applyAudioVolumes(rendering::Renderer* renderer) {
|
void SettingsPanel::applyAudioVolumes(audio::AudioCoordinator* ac) {
|
||||||
if (!renderer) return;
|
if (!ac) return;
|
||||||
float masterScale = soundMuted_ ? 0.0f : static_cast<float>(pendingMasterVolume) / 100.0f;
|
float masterScale = soundMuted_ ? 0.0f : static_cast<float>(pendingMasterVolume) / 100.0f;
|
||||||
audio::AudioEngine::instance().setMasterVolume(masterScale);
|
audio::AudioEngine::instance().setMasterVolume(masterScale);
|
||||||
if (auto* music = renderer->getMusicManager())
|
if (auto* music = ac->getMusicManager())
|
||||||
music->setVolume(pendingMusicVolume);
|
music->setVolume(pendingMusicVolume);
|
||||||
if (auto* ambient = renderer->getAmbientSoundManager())
|
if (auto* ambient = ac->getAmbientSoundManager())
|
||||||
ambient->setVolumeScale(pendingAmbientVolume / 100.0f);
|
ambient->setVolumeScale(pendingAmbientVolume / 100.0f);
|
||||||
if (auto* ui = renderer->getUiSoundManager())
|
if (auto* ui = ac->getUiSoundManager())
|
||||||
ui->setVolumeScale(pendingUiVolume / 100.0f);
|
ui->setVolumeScale(pendingUiVolume / 100.0f);
|
||||||
if (auto* combat = renderer->getCombatSoundManager())
|
if (auto* combat = ac->getCombatSoundManager())
|
||||||
combat->setVolumeScale(pendingCombatVolume / 100.0f);
|
combat->setVolumeScale(pendingCombatVolume / 100.0f);
|
||||||
if (auto* spell = renderer->getSpellSoundManager())
|
if (auto* spell = ac->getSpellSoundManager())
|
||||||
spell->setVolumeScale(pendingSpellVolume / 100.0f);
|
spell->setVolumeScale(pendingSpellVolume / 100.0f);
|
||||||
if (auto* movement = renderer->getMovementSoundManager())
|
if (auto* movement = ac->getMovementSoundManager())
|
||||||
movement->setVolumeScale(pendingMovementVolume / 100.0f);
|
movement->setVolumeScale(pendingMovementVolume / 100.0f);
|
||||||
if (auto* footstep = renderer->getFootstepManager())
|
if (auto* footstep = ac->getFootstepManager())
|
||||||
footstep->setVolumeScale(pendingFootstepVolume / 100.0f);
|
footstep->setVolumeScale(pendingFootstepVolume / 100.0f);
|
||||||
if (auto* npcVoice = renderer->getNpcVoiceManager())
|
if (auto* npcVoice = ac->getNpcVoiceManager())
|
||||||
npcVoice->setVolumeScale(pendingNpcVoiceVolume / 100.0f);
|
npcVoice->setVolumeScale(pendingNpcVoiceVolume / 100.0f);
|
||||||
if (auto* mount = renderer->getMountSoundManager())
|
if (auto* mount = ac->getMountSoundManager())
|
||||||
mount->setVolumeScale(pendingMountVolume / 100.0f);
|
mount->setVolumeScale(pendingMountVolume / 100.0f);
|
||||||
if (auto* activity = renderer->getActivitySoundManager())
|
if (auto* activity = ac->getActivitySoundManager())
|
||||||
activity->setVolumeScale(pendingActivityVolume / 100.0f);
|
activity->setVolumeScale(pendingActivityVolume / 100.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#include "game/game_handler.hpp"
|
#include "game/game_handler.hpp"
|
||||||
#include "core/application.hpp"
|
#include "core/application.hpp"
|
||||||
#include "rendering/renderer.hpp"
|
#include "rendering/renderer.hpp"
|
||||||
|
#include "audio/audio_coordinator.hpp"
|
||||||
#include "audio/ui_sound_manager.hpp"
|
#include "audio/ui_sound_manager.hpp"
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
@ -461,11 +462,13 @@ 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 = services_.renderer;
|
auto* ac = services_.audioCoordinator;
|
||||||
if (renderer) {
|
if (ac) {
|
||||||
if (auto* sfx = renderer->getUiSoundManager()) {
|
if (auto* sfx = ac->getUiSoundManager()) {
|
||||||
sfx->playLevelUp();
|
sfx->playLevelUp();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (auto* renderer = services_.renderer) {
|
||||||
renderer->playEmote("cheer");
|
renderer->playEmote("cheer");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -550,9 +553,9 @@ 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 = services_.renderer;
|
auto* ac = services_.audioCoordinator;
|
||||||
if (renderer) {
|
if (ac) {
|
||||||
if (auto* sfx = renderer->getUiSoundManager()) {
|
if (auto* sfx = ac->getUiSoundManager()) {
|
||||||
sfx->playAchievementAlert();
|
sfx->playAchievementAlert();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
#include "game/game_handler.hpp"
|
#include "game/game_handler.hpp"
|
||||||
#include "pipeline/asset_manager.hpp"
|
#include "pipeline/asset_manager.hpp"
|
||||||
#include "pipeline/dbc_layout.hpp"
|
#include "pipeline/dbc_layout.hpp"
|
||||||
|
#include "audio/audio_coordinator.hpp"
|
||||||
#include "audio/ui_sound_manager.hpp"
|
#include "audio/ui_sound_manager.hpp"
|
||||||
#include "audio/music_manager.hpp"
|
#include "audio/music_manager.hpp"
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
@ -1701,9 +1702,9 @@ 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 = services_.renderer;
|
auto* ac = services_.audioCoordinator;
|
||||||
if (renderer) {
|
if (ac) {
|
||||||
if (auto* music = renderer->getMusicManager()) {
|
if (auto* music = ac->getMusicManager()) {
|
||||||
music->stopMusic(0.0f);
|
music->stopMusic(0.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue