mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Compare commits
87 commits
ae48e4d7a6
...
3eded6772d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3eded6772d | ||
|
|
6cba3f5c95 | ||
|
|
18e6c2e767 | ||
|
|
6a681bcf67 | ||
|
|
2afd455d52 | ||
|
|
c887a460ea | ||
|
|
163dc9618a | ||
|
|
819a38a7ca | ||
|
|
caea24f6ea | ||
|
|
7b3b33e664 | ||
|
|
a335605682 | ||
|
|
8b495a1ce9 | ||
|
|
f43277dc28 | ||
|
|
70dcb6ef43 | ||
|
|
18a3a0fd01 | ||
|
|
6e03866b56 | ||
|
|
068deabb0e | ||
|
|
ea1af87266 | ||
|
|
6d1f3c4caf | ||
|
|
c14bb791a0 | ||
|
|
9d26f8c29e | ||
|
|
01e0c2f9a3 | ||
|
|
f1d31643fc | ||
|
|
22bc5954d7 | ||
|
|
52507b1f74 | ||
|
|
941b2c4894 | ||
|
|
4db686a652 | ||
|
|
c57182627f | ||
|
|
f0d1702d5f | ||
|
|
088a11e62a | ||
|
|
13e3e5ea35 | ||
|
|
f2eabc87ef | ||
|
|
68bf3d32b0 | ||
|
|
4ac32a1206 | ||
|
|
6583ce9c57 | ||
|
|
a654dd5e99 | ||
|
|
97192ab2a4 | ||
|
|
0913146f54 | ||
|
|
a2c2675039 | ||
|
|
55082a0925 | ||
|
|
b23cf06f1c | ||
|
|
43b9ecd857 | ||
|
|
46f2c0df85 | ||
|
|
9c3faa0e16 | ||
|
|
e8d068c5cb | ||
|
|
4e3d50fc20 | ||
|
|
ab32ec8933 | ||
|
|
0a528935e2 | ||
|
|
593fd4e45d | ||
|
|
28c755040f | ||
|
|
bbfb170291 | ||
|
|
99f2b30594 | ||
|
|
22513505fa | ||
|
|
a1dbbf3915 | ||
|
|
7f89bd950a | ||
|
|
830bb3f105 | ||
|
|
d84adb2120 | ||
|
|
5c94b4e7ff | ||
|
|
6a281e468f | ||
|
|
299c725993 | ||
|
|
aa737def7f | ||
|
|
deed8011d7 | ||
|
|
26eefe9529 | ||
|
|
f89840a6aa | ||
|
|
bd3bd1b5a6 | ||
|
|
6df36f4588 | ||
|
|
8e4a0053c4 | ||
|
|
001c80a3db | ||
|
|
acde6070cf | ||
|
|
e793b44151 | ||
|
|
b381f1e13f | ||
|
|
770ac645d5 | ||
|
|
f369fe9c6e | ||
|
|
b4f6ca2ca7 | ||
|
|
3114e80fa8 | ||
|
|
2d124e7e54 | ||
|
|
e4f53ce0c3 | ||
|
|
200a00d4f5 | ||
|
|
63c6039dbb | ||
|
|
9d37f4c946 | ||
|
|
8f7c4a58cd | ||
|
|
b33831d833 | ||
|
|
ae5c05e14e | ||
|
|
e2b89c9b42 | ||
|
|
6a7287bde3 | ||
|
|
b0d7dbc32c | ||
|
|
bae32c1823 |
40 changed files with 6066 additions and 278 deletions
1
.claude/scheduled_tasks.lock
Normal file
1
.claude/scheduled_tasks.lock
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"sessionId":"55a28c7e-8043-44c2-9829-702f303c84ba","pid":3880168,"acquiredAt":1773085726967}
|
||||||
|
|
@ -118,6 +118,7 @@ if(WOWEE_ENABLE_AMD_FSR3_FRAMEGEN AND WOWEE_AMD_FFX_SDK_KITS_READY)
|
||||||
CXX_STANDARD_REQUIRED ON
|
CXX_STANDARD_REQUIRED ON
|
||||||
)
|
)
|
||||||
target_include_directories(wowee_fsr3_framegen_amd_vk_probe PUBLIC
|
target_include_directories(wowee_fsr3_framegen_amd_vk_probe PUBLIC
|
||||||
|
${WOWEE_AMD_FFX_SDK_KITS_DIR}/upscalers/include
|
||||||
${WOWEE_AMD_FFX_SDK_KITS_DIR}/upscalers/fsr3/include
|
${WOWEE_AMD_FFX_SDK_KITS_DIR}/upscalers/fsr3/include
|
||||||
${WOWEE_AMD_FFX_SDK_KITS_DIR}/framegeneration/fsr3/include
|
${WOWEE_AMD_FFX_SDK_KITS_DIR}/framegeneration/fsr3/include
|
||||||
${WOWEE_AMD_FFX_SDK_KITS_DIR}/framegeneration/include
|
${WOWEE_AMD_FFX_SDK_KITS_DIR}/framegeneration/include
|
||||||
|
|
@ -155,21 +156,53 @@ if(WOWEE_ENABLE_AMD_FSR3_FRAMEGEN AND WOWEE_AMD_FFX_SDK_KITS_READY)
|
||||||
set(WOWEE_AMD_FSR3_RUNTIME_BUILD_TYPE Release)
|
set(WOWEE_AMD_FSR3_RUNTIME_BUILD_TYPE Release)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_custom_target(wowee_fsr3_official_runtime_build
|
# Locate bash at configure time so the build-time COMMAND works on Windows
|
||||||
COMMAND ${CMAKE_COMMAND}
|
# (cmake custom commands run via cmd.exe on Windows, so bare 'bash' is not found).
|
||||||
-S ${WOWEE_AMD_FFX_SDK_KITS_DIR}
|
find_program(BASH_EXECUTABLE bash
|
||||||
-B ${WOWEE_AMD_FSR3_RUNTIME_BUILD_DIR}
|
HINTS
|
||||||
-DCMAKE_BUILD_TYPE=${WOWEE_AMD_FSR3_RUNTIME_BUILD_TYPE}
|
/usr/bin
|
||||||
-DFFX_BUILD_VK=ON
|
/bin
|
||||||
-DFFX_BUILD_FRAMEGENERATION=ON
|
"${MSYS2_PATH}/usr/bin"
|
||||||
-DFFX_BUILD_UPSCALER=ON
|
"$ENV{MSYS2_PATH}/usr/bin"
|
||||||
COMMAND ${CMAKE_COMMAND}
|
"C:/msys64/usr/bin"
|
||||||
--build ${WOWEE_AMD_FSR3_RUNTIME_BUILD_DIR}
|
"D:/msys64/usr/bin"
|
||||||
--config $<CONFIG>
|
|
||||||
--parallel
|
|
||||||
COMMENT "Building native AMD FSR3 runtime (Path A) from FidelityFX-SDK Kits"
|
|
||||||
VERBATIM
|
|
||||||
)
|
)
|
||||||
|
if(BASH_EXECUTABLE)
|
||||||
|
add_custom_target(wowee_fsr3_official_runtime_build
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
-S ${WOWEE_AMD_FFX_SDK_KITS_DIR}
|
||||||
|
-B ${WOWEE_AMD_FSR3_RUNTIME_BUILD_DIR}
|
||||||
|
-DCMAKE_BUILD_TYPE=${WOWEE_AMD_FSR3_RUNTIME_BUILD_TYPE}
|
||||||
|
-DFFX_BUILD_VK=ON
|
||||||
|
-DFFX_BUILD_FRAMEGENERATION=ON
|
||||||
|
-DFFX_BUILD_UPSCALER=ON
|
||||||
|
COMMAND ${BASH_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/generate_ffx_sdk_vk_permutations.sh
|
||||||
|
${CMAKE_SOURCE_DIR}/extern/FidelityFX-SDK
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
--build ${WOWEE_AMD_FSR3_RUNTIME_BUILD_DIR}
|
||||||
|
--config $<CONFIG>
|
||||||
|
--parallel
|
||||||
|
COMMENT "Building native AMD FSR3 runtime (Path A) from FidelityFX-SDK Kits"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
message(STATUS "bash not found; VK permutation headers will not be auto-generated")
|
||||||
|
add_custom_target(wowee_fsr3_official_runtime_build
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
-S ${WOWEE_AMD_FFX_SDK_KITS_DIR}
|
||||||
|
-B ${WOWEE_AMD_FSR3_RUNTIME_BUILD_DIR}
|
||||||
|
-DCMAKE_BUILD_TYPE=${WOWEE_AMD_FSR3_RUNTIME_BUILD_TYPE}
|
||||||
|
-DFFX_BUILD_VK=ON
|
||||||
|
-DFFX_BUILD_FRAMEGENERATION=ON
|
||||||
|
-DFFX_BUILD_UPSCALER=ON
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
--build ${WOWEE_AMD_FSR3_RUNTIME_BUILD_DIR}
|
||||||
|
--config $<CONFIG>
|
||||||
|
--parallel
|
||||||
|
COMMENT "Building native AMD FSR3 runtime (Path A) from FidelityFX-SDK Kits (no permutation bootstrap)"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_custom_target(wowee_fsr3_official_runtime_copy
|
add_custom_target(wowee_fsr3_official_runtime_copy
|
||||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
|
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
|
||||||
|
|
@ -473,7 +506,6 @@ set(WOWEE_SOURCES
|
||||||
src/rendering/renderer.cpp
|
src/rendering/renderer.cpp
|
||||||
src/rendering/amd_fsr3_runtime.cpp
|
src/rendering/amd_fsr3_runtime.cpp
|
||||||
src/rendering/shader.cpp
|
src/rendering/shader.cpp
|
||||||
src/rendering/texture.cpp
|
|
||||||
src/rendering/mesh.cpp
|
src/rendering/mesh.cpp
|
||||||
src/rendering/camera.cpp
|
src/rendering/camera.cpp
|
||||||
src/rendering/camera_controller.cpp
|
src/rendering/camera_controller.cpp
|
||||||
|
|
@ -587,7 +619,6 @@ set(WOWEE_HEADERS
|
||||||
include/rendering/vk_render_target.hpp
|
include/rendering/vk_render_target.hpp
|
||||||
include/rendering/renderer.hpp
|
include/rendering/renderer.hpp
|
||||||
include/rendering/shader.hpp
|
include/rendering/shader.hpp
|
||||||
include/rendering/texture.hpp
|
|
||||||
include/rendering/mesh.hpp
|
include/rendering/mesh.hpp
|
||||||
include/rendering/camera.hpp
|
include/rendering/camera.hpp
|
||||||
include/rendering/camera_controller.hpp
|
include/rendering/camera_controller.hpp
|
||||||
|
|
@ -641,6 +672,14 @@ if(TARGET opcodes-generate)
|
||||||
add_dependencies(wowee opcodes-generate)
|
add_dependencies(wowee opcodes-generate)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# FidelityFX-SDK headers can trigger compiler-specific pragma/unused-static noise
|
||||||
|
# when included through the runtime bridge; keep suppression scoped to that TU.
|
||||||
|
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
||||||
|
set_source_files_properties(src/rendering/amd_fsr3_runtime.cpp PROPERTIES
|
||||||
|
COMPILE_OPTIONS "-Wno-unknown-pragmas;-Wno-unused-variable"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Compile GLSL shaders to SPIR-V
|
# Compile GLSL shaders to SPIR-V
|
||||||
if(GLSLC)
|
if(GLSLC)
|
||||||
compile_shaders(wowee)
|
compile_shaders(wowee)
|
||||||
|
|
@ -715,6 +754,12 @@ endif()
|
||||||
if(TARGET wowee_fsr3_framegen_amd_vk_probe)
|
if(TARGET wowee_fsr3_framegen_amd_vk_probe)
|
||||||
target_link_libraries(wowee PRIVATE wowee_fsr3_framegen_amd_vk_probe)
|
target_link_libraries(wowee PRIVATE wowee_fsr3_framegen_amd_vk_probe)
|
||||||
endif()
|
endif()
|
||||||
|
if(TARGET wowee_fsr3_official_runtime_copy)
|
||||||
|
# FSR3 Path A runtime is an opt-in artifact; build explicitly with:
|
||||||
|
# cmake --build build --target wowee_fsr3_official_runtime_copy
|
||||||
|
# Do NOT add as a hard dependency of wowee — it would break arm64 and Windows CI
|
||||||
|
# (no DXC available on arm64; bash context issues on MSYS2 Windows).
|
||||||
|
endif()
|
||||||
|
|
||||||
# Link Unicorn if available
|
# Link Unicorn if available
|
||||||
if(HAVE_UNICORN)
|
if(HAVE_UNICORN)
|
||||||
|
|
@ -735,6 +780,13 @@ if(MSVC)
|
||||||
target_compile_options(wowee PRIVATE /W4)
|
target_compile_options(wowee PRIVATE /W4)
|
||||||
else()
|
else()
|
||||||
target_compile_options(wowee PRIVATE -Wall -Wextra -Wpedantic -Wno-missing-field-initializers)
|
target_compile_options(wowee PRIVATE -Wall -Wextra -Wpedantic -Wno-missing-field-initializers)
|
||||||
|
# GCC LTO emits -Wodr for FFX enum-name mismatches across SDK generations.
|
||||||
|
# We intentionally keep FSR2+FSR3 integrations in separate TUs and suppress
|
||||||
|
# this linker-time diagnostic to avoid CI noise.
|
||||||
|
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||||
|
target_compile_options(wowee PRIVATE -Wno-odr)
|
||||||
|
target_link_options(wowee PRIVATE -Wno-odr)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Debug build flags
|
# Debug build flags
|
||||||
|
|
|
||||||
|
|
@ -278,6 +278,7 @@ make -j$(nproc)
|
||||||
- All build jobs are AMD-FSR2-only (`WOWEE_ENABLE_AMD_FSR2=ON`) and explicitly build `wowee_fsr2_amd_vk`
|
- All build jobs are AMD-FSR2-only (`WOWEE_ENABLE_AMD_FSR2=ON`) and explicitly build `wowee_fsr2_amd_vk`
|
||||||
- Each job clones AMD's FSR2 SDK and FidelityFX-SDK (`Kelsidavis/FidelityFX-SDK`, `main` by default)
|
- Each job clones AMD's FSR2 SDK and FidelityFX-SDK (`Kelsidavis/FidelityFX-SDK`, `main` by default)
|
||||||
- Linux CI validates FidelityFX-SDK Kits framegen headers
|
- Linux CI validates FidelityFX-SDK Kits framegen headers
|
||||||
|
- FSR3 Path A runtime build auto-bootstraps missing VK permutation headers via `tools/generate_ffx_sdk_vk_permutations.sh`
|
||||||
- CI builds `wowee_fsr3_framegen_amd_vk_probe` when that target is generated for the detected SDK layout
|
- CI builds `wowee_fsr3_framegen_amd_vk_probe` when that target is generated for the detected SDK layout
|
||||||
- If FSR2 generated Vulkan permutation headers are absent upstream, WoWee bootstraps them from `third_party/fsr2_vk_permutations`
|
- If FSR2 generated Vulkan permutation headers are absent upstream, WoWee bootstraps them from `third_party/fsr2_vk_permutations`
|
||||||
- Container build via `container/build-in-container.sh` (Podman)
|
- Container build via `container/build-in-container.sh` (Podman)
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ Runtime note:
|
||||||
|
|
||||||
- Renderer/UI expose a persisted experimental framegen toggle.
|
- Renderer/UI expose a persisted experimental framegen toggle.
|
||||||
- Runtime loader is Path A only (official AMD runtime library).
|
- Runtime loader is Path A only (official AMD runtime library).
|
||||||
|
- Path A runtime build now auto-runs `tools/generate_ffx_sdk_vk_permutations.sh` to ensure required VK permutation headers exist for FSR2/FSR3 upscaler shader blobs.
|
||||||
- You can point to an explicit runtime binary with:
|
- You can point to an explicit runtime binary with:
|
||||||
- `WOWEE_FFX_SDK_RUNTIME_LIB=/absolute/path/to/libffx_fsr3_vk.so` (or `.dll` / `.dylib`).
|
- `WOWEE_FFX_SDK_RUNTIME_LIB=/absolute/path/to/libffx_fsr3_vk.so` (or `.dll` / `.dylib`).
|
||||||
- If no official runtime is found, frame generation is disabled cleanly (Path C).
|
- If no official runtime is found, frame generation is disabled cleanly (Path C).
|
||||||
|
|
@ -76,6 +77,10 @@ Runtime note:
|
||||||
- `framegeneration/fsr3/include/ffx_frameinterpolation.h`
|
- `framegeneration/fsr3/include/ffx_frameinterpolation.h`
|
||||||
- `framegeneration/fsr3/include/ffx_opticalflow.h`
|
- `framegeneration/fsr3/include/ffx_opticalflow.h`
|
||||||
- `backend/vk/ffx_vk.h`
|
- `backend/vk/ffx_vk.h`
|
||||||
|
- Runtime build path auto-bootstrap:
|
||||||
|
- Linux downloads DXC automatically when missing.
|
||||||
|
- Windows (MSYS2) downloads DXC automatically when missing.
|
||||||
|
- macOS expects `dxc` to be available in `PATH` (CI installs it via Homebrew).
|
||||||
- CI builds `wowee_fsr3_framegen_amd_vk_probe` when that target is generated by CMake for the detected SDK layout.
|
- CI builds `wowee_fsr3_framegen_amd_vk_probe` when that target is generated by CMake for the detected SDK layout.
|
||||||
- Some upstream SDK checkouts do not include generated Vulkan permutation headers.
|
- Some upstream SDK checkouts do not include generated Vulkan permutation headers.
|
||||||
- WoWee bootstraps those headers from the vendored snapshot so AMD backend builds remain cross-platform and deterministic.
|
- WoWee bootstraps those headers from the vendored snapshot so AMD backend builds remain cross-platform and deterministic.
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,9 @@ public:
|
||||||
void setZoneType(ZoneType type);
|
void setZoneType(ZoneType type);
|
||||||
ZoneType getCurrentZone() const { return currentZone_; }
|
ZoneType getCurrentZone() const { return currentZone_; }
|
||||||
|
|
||||||
|
// Convenience: derive ZoneType and CityType from a WoW zone ID
|
||||||
|
void setZoneId(uint32_t zoneId);
|
||||||
|
|
||||||
// City ambience control
|
// City ambience control
|
||||||
enum class CityType {
|
enum class CityType {
|
||||||
NONE,
|
NONE,
|
||||||
|
|
@ -111,6 +114,8 @@ private:
|
||||||
std::vector<AmbientSample> windSounds_;
|
std::vector<AmbientSample> windSounds_;
|
||||||
std::vector<AmbientSample> tavernSounds_;
|
std::vector<AmbientSample> tavernSounds_;
|
||||||
std::vector<AmbientSample> blacksmithSounds_;
|
std::vector<AmbientSample> blacksmithSounds_;
|
||||||
|
std::vector<AmbientSample> birdSounds_;
|
||||||
|
std::vector<AmbientSample> cricketSounds_;
|
||||||
|
|
||||||
// Weather sound libraries
|
// Weather sound libraries
|
||||||
std::vector<AmbientSample> rainLightSounds_;
|
std::vector<AmbientSample> rainLightSounds_;
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,11 @@ private:
|
||||||
float fadeInTimer = 0.0f;
|
float fadeInTimer = 0.0f;
|
||||||
float fadeInDuration = 0.0f;
|
float fadeInDuration = 0.0f;
|
||||||
float fadeInTargetVolume = 0.0f;
|
float fadeInTargetVolume = 0.0f;
|
||||||
|
// Fade-out state (for stopMusic with fadeMs > 0)
|
||||||
|
bool fadingOut = false;
|
||||||
|
float fadeOutTimer = 0.0f;
|
||||||
|
float fadeOutDuration = 0.0f;
|
||||||
|
float fadeOutStartVolume = 0.0f;
|
||||||
|
|
||||||
std::unordered_map<std::string, std::vector<uint8_t>> musicDataCache_;
|
std::unordered_map<std::string, std::vector<uint8_t>> musicDataCache_;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,6 @@ private:
|
||||||
|
|
||||||
void loadVoiceSounds();
|
void loadVoiceSounds();
|
||||||
bool loadSound(const std::string& path, VoiceSample& sample);
|
bool loadSound(const std::string& path, VoiceSample& sample);
|
||||||
VoiceType detectVoiceType(uint32_t creatureEntry) const;
|
|
||||||
void playSound(uint64_t npcGuid, VoiceType voiceType, SoundCategory category, const glm::vec3& position);
|
void playSound(uint64_t npcGuid, VoiceType voiceType, SoundCategory category, const glm::vec3& position);
|
||||||
|
|
||||||
pipeline::AssetManager* assetManager_ = nullptr;
|
pipeline::AssetManager* assetManager_ = nullptr;
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,9 @@ public:
|
||||||
// Level up
|
// Level up
|
||||||
void playLevelUp();
|
void playLevelUp();
|
||||||
|
|
||||||
|
// Achievement
|
||||||
|
void playAchievementAlert();
|
||||||
|
|
||||||
// Error/feedback
|
// Error/feedback
|
||||||
void playError();
|
void playError();
|
||||||
void playTargetSelect();
|
void playTargetSelect();
|
||||||
|
|
@ -114,6 +117,7 @@ private:
|
||||||
std::vector<UISample> drinkingSounds_;
|
std::vector<UISample> drinkingSounds_;
|
||||||
|
|
||||||
std::vector<UISample> levelUpSounds_;
|
std::vector<UISample> levelUpSounds_;
|
||||||
|
std::vector<UISample> achievementSounds_;
|
||||||
|
|
||||||
std::vector<UISample> errorSounds_;
|
std::vector<UISample> errorSounds_;
|
||||||
std::vector<UISample> selectTargetSounds_;
|
std::vector<UISample> selectTargetSounds_;
|
||||||
|
|
|
||||||
|
|
@ -53,17 +53,20 @@ inline float normalizeAngleRad(float a) {
|
||||||
|
|
||||||
// Convert server/wire yaw (radians) → canonical yaw (radians).
|
// Convert server/wire yaw (radians) → canonical yaw (radians).
|
||||||
//
|
//
|
||||||
// Under server<->canonical X/Y swap:
|
// Codebase canonical convention: atan2(-dy, dx) in (canonical_X=north, canonical_Y=west).
|
||||||
// dir_s = (cos(s), sin(s))
|
// North=0, East=+π/2, South=±π, West=-π/2.
|
||||||
// dir_c = swap(dir_s) = (sin(s), cos(s)) => c = PI/2 - s
|
//
|
||||||
|
// Server direction at angle s: (cos s, sin s) in (server_X=canonical_Y, server_Y=canonical_X).
|
||||||
|
// After swap: dir_c = (sin s, cos s) in (canonical_X, canonical_Y).
|
||||||
|
// atan2(-dy, dx) = atan2(-cos s, sin s) = s - π/2.
|
||||||
inline float serverToCanonicalYaw(float serverYaw) {
|
inline float serverToCanonicalYaw(float serverYaw) {
|
||||||
return normalizeAngleRad((PI * 0.5f) - serverYaw);
|
return normalizeAngleRad(serverYaw - (PI * 0.5f));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert canonical yaw (radians) → server/wire yaw (radians).
|
// Convert canonical yaw (radians) → server/wire yaw (radians).
|
||||||
// This mapping is its own inverse.
|
// Inverse of serverToCanonicalYaw: s = c + π/2.
|
||||||
inline float canonicalToServerYaw(float canonicalYaw) {
|
inline float canonicalToServerYaw(float canonicalYaw) {
|
||||||
return normalizeAngleRad((PI * 0.5f) - canonicalYaw);
|
return normalizeAngleRad(canonicalYaw + (PI * 0.5f));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert between canonical WoW and engine rendering coordinates (just swap X/Y).
|
// Convert between canonical WoW and engine rendering coordinates (just swap X/Y).
|
||||||
|
|
|
||||||
|
|
@ -332,6 +332,10 @@ public:
|
||||||
|
|
||||||
// Stand state
|
// Stand state
|
||||||
void setStandState(uint8_t state); // 0=stand, 1=sit, 2=sit_chair, 3=sleep, 4=sit_low_chair, 5=sit_medium_chair, 6=sit_high_chair, 7=dead, 8=kneel, 9=submerged
|
void setStandState(uint8_t state); // 0=stand, 1=sit, 2=sit_chair, 3=sleep, 4=sit_low_chair, 5=sit_medium_chair, 6=sit_high_chair, 7=dead, 8=kneel, 9=submerged
|
||||||
|
uint8_t getStandState() const { return standState_; }
|
||||||
|
bool isSitting() const { return standState_ >= 1 && standState_ <= 6; }
|
||||||
|
bool isDead() const { return standState_ == 7; }
|
||||||
|
bool isKneeling() const { return standState_ == 8; }
|
||||||
|
|
||||||
// Display toggles
|
// Display toggles
|
||||||
void toggleHelm();
|
void toggleHelm();
|
||||||
|
|
@ -390,6 +394,9 @@ public:
|
||||||
// Ready check
|
// Ready check
|
||||||
void initiateReadyCheck();
|
void initiateReadyCheck();
|
||||||
void respondToReadyCheck(bool ready);
|
void respondToReadyCheck(bool ready);
|
||||||
|
bool hasPendingReadyCheck() const { return pendingReadyCheck_; }
|
||||||
|
void dismissReadyCheck() { pendingReadyCheck_ = false; }
|
||||||
|
const std::string& getReadyCheckInitiator() const { return readyCheckInitiator_; }
|
||||||
|
|
||||||
// Duel
|
// Duel
|
||||||
void forfeitDuel();
|
void forfeitDuel();
|
||||||
|
|
@ -501,6 +508,10 @@ public:
|
||||||
const std::vector<AuraSlot>& getPlayerAuras() const { return playerAuras; }
|
const std::vector<AuraSlot>& getPlayerAuras() const { return playerAuras; }
|
||||||
const std::vector<AuraSlot>& getTargetAuras() const { return targetAuras; }
|
const std::vector<AuraSlot>& getTargetAuras() const { return targetAuras; }
|
||||||
|
|
||||||
|
// Completed quests (populated from SMSG_QUERY_QUESTS_COMPLETED_RESPONSE)
|
||||||
|
bool isQuestCompleted(uint32_t questId) const { return completedQuests_.count(questId) > 0; }
|
||||||
|
const std::unordered_set<uint32_t>& getCompletedQuests() const { return completedQuests_; }
|
||||||
|
|
||||||
// NPC death callback (for animations)
|
// NPC death callback (for animations)
|
||||||
using NpcDeathCallback = std::function<void(uint64_t guid)>;
|
using NpcDeathCallback = std::function<void(uint64_t guid)>;
|
||||||
void setNpcDeathCallback(NpcDeathCallback cb) { npcDeathCallback_ = std::move(cb); }
|
void setNpcDeathCallback(NpcDeathCallback cb) { npcDeathCallback_ = std::move(cb); }
|
||||||
|
|
@ -548,6 +559,8 @@ public:
|
||||||
float getWeatherIntensity() const { return weatherIntensity_; }
|
float getWeatherIntensity() const { return weatherIntensity_; }
|
||||||
bool isRaining() const { return weatherType_ == 1 && weatherIntensity_ > 0.05f; }
|
bool isRaining() const { return weatherType_ == 1 && weatherIntensity_ > 0.05f; }
|
||||||
bool isSnowing() const { return weatherType_ == 2 && weatherIntensity_ > 0.05f; }
|
bool isSnowing() const { return weatherType_ == 2 && weatherIntensity_ > 0.05f; }
|
||||||
|
uint32_t getOverrideLightId() const { return overrideLightId_; }
|
||||||
|
uint32_t getOverrideLightTransMs() const { return overrideLightTransMs_; }
|
||||||
|
|
||||||
// Player skills
|
// Player skills
|
||||||
const std::map<uint32_t, PlayerSkill>& getPlayerSkills() const { return playerSkills_; }
|
const std::map<uint32_t, PlayerSkill>& getPlayerSkills() const { return playerSkills_; }
|
||||||
|
|
@ -703,6 +716,88 @@ public:
|
||||||
bool hasPendingGroupInvite() const { return pendingGroupInvite; }
|
bool hasPendingGroupInvite() const { return pendingGroupInvite; }
|
||||||
const std::string& getPendingInviterName() const { return pendingInviterName; }
|
const std::string& getPendingInviterName() const { return pendingInviterName; }
|
||||||
|
|
||||||
|
// ---- Item text (books / readable items) ----
|
||||||
|
bool isItemTextOpen() const { return itemTextOpen_; }
|
||||||
|
const std::string& getItemText() const { return itemText_; }
|
||||||
|
void closeItemText() { itemTextOpen_ = false; }
|
||||||
|
void queryItemText(uint64_t itemGuid);
|
||||||
|
|
||||||
|
// ---- Shared Quest ----
|
||||||
|
bool hasPendingSharedQuest() const { return pendingSharedQuest_; }
|
||||||
|
uint32_t getSharedQuestId() const { return sharedQuestId_; }
|
||||||
|
const std::string& getSharedQuestTitle() const { return sharedQuestTitle_; }
|
||||||
|
const std::string& getSharedQuestSharerName() const { return sharedQuestSharerName_; }
|
||||||
|
void acceptSharedQuest();
|
||||||
|
void declineSharedQuest();
|
||||||
|
|
||||||
|
// ---- Summon ----
|
||||||
|
bool hasPendingSummonRequest() const { return pendingSummonRequest_; }
|
||||||
|
const std::string& getSummonerName() const { return summonerName_; }
|
||||||
|
float getSummonTimeoutSec() const { return summonTimeoutSec_; }
|
||||||
|
void acceptSummon();
|
||||||
|
void declineSummon();
|
||||||
|
void tickSummonTimeout(float dt) {
|
||||||
|
if (!pendingSummonRequest_) return;
|
||||||
|
summonTimeoutSec_ -= dt;
|
||||||
|
if (summonTimeoutSec_ <= 0.0f) {
|
||||||
|
pendingSummonRequest_ = false;
|
||||||
|
summonTimeoutSec_ = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Trade ----
|
||||||
|
enum class TradeStatus : uint8_t {
|
||||||
|
None = 0, PendingIncoming, Open, Accepted, Complete
|
||||||
|
};
|
||||||
|
TradeStatus getTradeStatus() const { return tradeStatus_; }
|
||||||
|
bool hasPendingTradeRequest() const { return tradeStatus_ == TradeStatus::PendingIncoming; }
|
||||||
|
const std::string& getTradePeerName() const { return tradePeerName_; }
|
||||||
|
void acceptTradeRequest(); // respond to incoming SMSG_TRADE_STATUS(1) with CMSG_BEGIN_TRADE
|
||||||
|
void declineTradeRequest(); // respond with CMSG_CANCEL_TRADE
|
||||||
|
void acceptTrade(); // lock in offer: CMSG_ACCEPT_TRADE
|
||||||
|
void cancelTrade(); // CMSG_CANCEL_TRADE
|
||||||
|
|
||||||
|
// ---- Duel ----
|
||||||
|
bool hasPendingDuelRequest() const { return pendingDuelRequest_; }
|
||||||
|
const std::string& getDuelChallengerName() const { return duelChallengerName_; }
|
||||||
|
void acceptDuel();
|
||||||
|
// forfeitDuel() already declared at line ~399
|
||||||
|
|
||||||
|
// ---- Instance lockouts ----
|
||||||
|
struct InstanceLockout {
|
||||||
|
uint32_t mapId = 0;
|
||||||
|
uint32_t difficulty = 0; // 0=normal,1=heroic/10man,2=25man,3=25man heroic
|
||||||
|
uint64_t resetTime = 0; // Unix timestamp of instance reset
|
||||||
|
bool locked = false;
|
||||||
|
bool extended = false;
|
||||||
|
};
|
||||||
|
const std::vector<InstanceLockout>& getInstanceLockouts() const { return instanceLockouts_; }
|
||||||
|
|
||||||
|
// ---- LFG / Dungeon Finder ----
|
||||||
|
enum class LfgState : uint8_t {
|
||||||
|
None = 0,
|
||||||
|
RoleCheck = 1,
|
||||||
|
Queued = 2,
|
||||||
|
Proposal = 3,
|
||||||
|
Boot = 4,
|
||||||
|
InDungeon = 5,
|
||||||
|
FinishedDungeon= 6,
|
||||||
|
RaidBrowser = 7,
|
||||||
|
};
|
||||||
|
|
||||||
|
// roles bitmask: 0x02=tank, 0x04=healer, 0x08=dps; pass LFGDungeonEntry ID
|
||||||
|
void lfgJoin(uint32_t dungeonId, uint8_t roles);
|
||||||
|
void lfgLeave();
|
||||||
|
void lfgAcceptProposal(uint32_t proposalId, bool accept);
|
||||||
|
void lfgTeleport(bool toLfgDungeon = true);
|
||||||
|
LfgState getLfgState() const { return lfgState_; }
|
||||||
|
bool isLfgQueued() const { return lfgState_ == LfgState::Queued; }
|
||||||
|
bool isLfgInDungeon() const { return lfgState_ == LfgState::InDungeon; }
|
||||||
|
uint32_t getLfgDungeonId() const { return lfgDungeonId_; }
|
||||||
|
uint32_t getLfgProposalId() const { return lfgProposalId_; }
|
||||||
|
int32_t getLfgAvgWaitSec() const { return lfgAvgWaitSec_; }
|
||||||
|
uint32_t getLfgTimeInQueueMs() const { return lfgTimeInQueueMs_; }
|
||||||
|
|
||||||
// ---- Phase 5: Loot ----
|
// ---- Phase 5: Loot ----
|
||||||
void lootTarget(uint64_t guid);
|
void lootTarget(uint64_t guid);
|
||||||
void lootItem(uint8_t slotIndex);
|
void lootItem(uint8_t slotIndex);
|
||||||
|
|
@ -713,6 +808,19 @@ public:
|
||||||
void setAutoLoot(bool enabled) { autoLoot_ = enabled; }
|
void setAutoLoot(bool enabled) { autoLoot_ = enabled; }
|
||||||
bool isAutoLoot() const { return autoLoot_; }
|
bool isAutoLoot() const { return autoLoot_; }
|
||||||
|
|
||||||
|
// Group loot roll
|
||||||
|
struct LootRollEntry {
|
||||||
|
uint64_t objectGuid = 0;
|
||||||
|
uint32_t slot = 0;
|
||||||
|
uint32_t itemId = 0;
|
||||||
|
std::string itemName;
|
||||||
|
uint8_t itemQuality = 0;
|
||||||
|
};
|
||||||
|
bool hasPendingLootRoll() const { return pendingLootRollActive_; }
|
||||||
|
const LootRollEntry& getPendingLootRoll() const { return pendingLootRoll_; }
|
||||||
|
void sendLootRoll(uint64_t objectGuid, uint32_t slot, uint8_t rollType);
|
||||||
|
// rollType: 0=need, 1=greed, 2=disenchant, 96=pass
|
||||||
|
|
||||||
// NPC Gossip
|
// NPC Gossip
|
||||||
void interactWithNpc(uint64_t guid);
|
void interactWithNpc(uint64_t guid);
|
||||||
void interactWithGameObject(uint64_t guid);
|
void interactWithGameObject(uint64_t guid);
|
||||||
|
|
@ -726,6 +834,17 @@ public:
|
||||||
bool isQuestDetailsOpen() const { return questDetailsOpen; }
|
bool isQuestDetailsOpen() const { return questDetailsOpen; }
|
||||||
const QuestDetailsData& getQuestDetails() const { return currentQuestDetails; }
|
const QuestDetailsData& getQuestDetails() const { return currentQuestDetails; }
|
||||||
|
|
||||||
|
// Gossip / quest map POI markers (SMSG_GOSSIP_POI)
|
||||||
|
struct GossipPoi {
|
||||||
|
float x = 0.0f; // WoW canonical X (north)
|
||||||
|
float y = 0.0f; // WoW canonical Y (west)
|
||||||
|
uint32_t icon = 0; // POI icon type
|
||||||
|
uint32_t data = 0;
|
||||||
|
std::string name;
|
||||||
|
};
|
||||||
|
const std::vector<GossipPoi>& getGossipPois() const { return gossipPois_; }
|
||||||
|
void clearGossipPois() { gossipPois_.clear(); }
|
||||||
|
|
||||||
// Quest turn-in
|
// Quest turn-in
|
||||||
bool isQuestRequestItemsOpen() const { return questRequestItemsOpen_; }
|
bool isQuestRequestItemsOpen() const { return questRequestItemsOpen_; }
|
||||||
const QuestRequestItemsData& getQuestRequestItems() const { return currentQuestRequestItems_; }
|
const QuestRequestItemsData& getQuestRequestItems() const { return currentQuestRequestItems_; }
|
||||||
|
|
@ -766,11 +885,39 @@ public:
|
||||||
uint32_t getWorldStateMapId() const { return worldStateMapId_; }
|
uint32_t getWorldStateMapId() const { return worldStateMapId_; }
|
||||||
uint32_t getWorldStateZoneId() const { return worldStateZoneId_; }
|
uint32_t getWorldStateZoneId() const { return worldStateZoneId_; }
|
||||||
|
|
||||||
|
// Mirror timers (0=fatigue, 1=breath, 2=feigndeath)
|
||||||
|
struct MirrorTimer {
|
||||||
|
int32_t value = 0;
|
||||||
|
int32_t maxValue = 0;
|
||||||
|
int32_t scale = 0; // +1 = counting up, -1 = counting down
|
||||||
|
bool paused = false;
|
||||||
|
bool active = false;
|
||||||
|
};
|
||||||
|
const MirrorTimer& getMirrorTimer(int type) const {
|
||||||
|
static MirrorTimer empty;
|
||||||
|
return (type >= 0 && type < 3) ? mirrorTimers_[type] : empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combo points
|
||||||
|
uint8_t getComboPoints() const { return comboPoints_; }
|
||||||
|
uint64_t getComboTarget() const { return comboTarget_; }
|
||||||
|
|
||||||
|
// Death Knight rune state (6 runes: 0-1=Blood, 2-3=Unholy, 4-5=Frost; may become Death=3)
|
||||||
|
enum class RuneType : uint8_t { Blood = 0, Unholy = 1, Frost = 2, Death = 3 };
|
||||||
|
struct RuneSlot {
|
||||||
|
RuneType type = RuneType::Blood;
|
||||||
|
bool ready = true; // Server-confirmed ready state
|
||||||
|
float readyFraction = 1.0f; // 0.0=depleted → 1.0=full (from server sync)
|
||||||
|
};
|
||||||
|
const std::array<RuneSlot, 6>& getPlayerRunes() const { return playerRunes_; }
|
||||||
|
|
||||||
struct FactionStandingInit {
|
struct FactionStandingInit {
|
||||||
uint8_t flags = 0;
|
uint8_t flags = 0;
|
||||||
int32_t standing = 0;
|
int32_t standing = 0;
|
||||||
};
|
};
|
||||||
const std::vector<FactionStandingInit>& getInitialFactions() const { return initialFactions_; }
|
const std::vector<FactionStandingInit>& getInitialFactions() const { return initialFactions_; }
|
||||||
|
const std::unordered_map<uint32_t, int32_t>& getFactionStandings() const { return factionStandings_; }
|
||||||
|
const std::string& getFactionNamePublic(uint32_t factionId) const;
|
||||||
uint32_t getLastContactListMask() const { return lastContactListMask_; }
|
uint32_t getLastContactListMask() const { return lastContactListMask_; }
|
||||||
uint32_t getLastContactListCount() const { return lastContactListCount_; }
|
uint32_t getLastContactListCount() const { return lastContactListCount_; }
|
||||||
bool isServerMovementAllowed() const { return serverMovementAllowed_; }
|
bool isServerMovementAllowed() const { return serverMovementAllowed_; }
|
||||||
|
|
@ -795,6 +942,26 @@ public:
|
||||||
using OtherPlayerLevelUpCallback = std::function<void(uint64_t guid, uint32_t newLevel)>;
|
using OtherPlayerLevelUpCallback = std::function<void(uint64_t guid, uint32_t newLevel)>;
|
||||||
void setOtherPlayerLevelUpCallback(OtherPlayerLevelUpCallback cb) { otherPlayerLevelUpCallback_ = std::move(cb); }
|
void setOtherPlayerLevelUpCallback(OtherPlayerLevelUpCallback cb) { otherPlayerLevelUpCallback_ = std::move(cb); }
|
||||||
|
|
||||||
|
// Achievement earned callback — fires when SMSG_ACHIEVEMENT_EARNED is received
|
||||||
|
using AchievementEarnedCallback = std::function<void(uint32_t achievementId)>;
|
||||||
|
void setAchievementEarnedCallback(AchievementEarnedCallback cb) { achievementEarnedCallback_ = std::move(cb); }
|
||||||
|
|
||||||
|
// Server-triggered music callback — fires when SMSG_PLAY_MUSIC is received.
|
||||||
|
// The soundId corresponds to a SoundEntries.dbc record. The receiver is
|
||||||
|
// responsible for looking up the file path and forwarding to MusicManager.
|
||||||
|
using PlayMusicCallback = std::function<void(uint32_t soundId)>;
|
||||||
|
void setPlayMusicCallback(PlayMusicCallback cb) { playMusicCallback_ = std::move(cb); }
|
||||||
|
|
||||||
|
// Server-triggered 2-D sound effect callback — fires when SMSG_PLAY_SOUND is received.
|
||||||
|
// The soundId corresponds to a SoundEntries.dbc record.
|
||||||
|
using PlaySoundCallback = std::function<void(uint32_t soundId)>;
|
||||||
|
void setPlaySoundCallback(PlaySoundCallback cb) { playSoundCallback_ = std::move(cb); }
|
||||||
|
|
||||||
|
// Server-triggered 3-D positional sound callback — fires for SMSG_PLAY_OBJECT_SOUND and
|
||||||
|
// SMSG_PLAY_SPELL_IMPACT. Includes sourceGuid so the receiver can look up world position.
|
||||||
|
using PlayPositionalSoundCallback = std::function<void(uint32_t soundId, uint64_t sourceGuid)>;
|
||||||
|
void setPlayPositionalSoundCallback(PlayPositionalSoundCallback cb) { playPositionalSoundCallback_ = std::move(cb); }
|
||||||
|
|
||||||
// Mount state
|
// Mount state
|
||||||
using MountCallback = std::function<void(uint32_t mountDisplayId)>; // 0 = dismount
|
using MountCallback = std::function<void(uint32_t mountDisplayId)>; // 0 = dismount
|
||||||
void setMountCallback(MountCallback cb) { mountCallback_ = std::move(cb); }
|
void setMountCallback(MountCallback cb) { mountCallback_ = std::move(cb); }
|
||||||
|
|
@ -1120,6 +1287,11 @@ private:
|
||||||
void handleSpellDamageLog(network::Packet& packet);
|
void handleSpellDamageLog(network::Packet& packet);
|
||||||
void handleSpellHealLog(network::Packet& packet);
|
void handleSpellHealLog(network::Packet& packet);
|
||||||
|
|
||||||
|
// ---- Equipment set handler ----
|
||||||
|
void handleEquipmentSetList(network::Packet& packet);
|
||||||
|
void handleUpdateAuraDuration(uint8_t slot, uint32_t durationMs);
|
||||||
|
void handleSetForcedReactions(network::Packet& packet);
|
||||||
|
|
||||||
// ---- Phase 3 handlers ----
|
// ---- Phase 3 handlers ----
|
||||||
void handleInitialSpells(network::Packet& packet);
|
void handleInitialSpells(network::Packet& packet);
|
||||||
void handleCastFailed(network::Packet& packet);
|
void handleCastFailed(network::Packet& packet);
|
||||||
|
|
@ -1127,6 +1299,7 @@ private:
|
||||||
void handleSpellGo(network::Packet& packet);
|
void handleSpellGo(network::Packet& packet);
|
||||||
void handleSpellCooldown(network::Packet& packet);
|
void handleSpellCooldown(network::Packet& packet);
|
||||||
void handleCooldownEvent(network::Packet& packet);
|
void handleCooldownEvent(network::Packet& packet);
|
||||||
|
void handleAchievementEarned(network::Packet& packet);
|
||||||
void handleAuraUpdate(network::Packet& packet, bool isAll);
|
void handleAuraUpdate(network::Packet& packet, bool isAll);
|
||||||
void handleLearnedSpell(network::Packet& packet);
|
void handleLearnedSpell(network::Packet& packet);
|
||||||
void handleSupercededSpell(network::Packet& packet);
|
void handleSupercededSpell(network::Packet& packet);
|
||||||
|
|
@ -1203,6 +1376,28 @@ private:
|
||||||
void loadAreaTriggerDbc();
|
void loadAreaTriggerDbc();
|
||||||
void checkAreaTriggers();
|
void checkAreaTriggers();
|
||||||
|
|
||||||
|
// ---- Instance lockout handler ----
|
||||||
|
void handleRaidInstanceInfo(network::Packet& packet);
|
||||||
|
void handleItemTextQueryResponse(network::Packet& packet);
|
||||||
|
void handleQuestConfirmAccept(network::Packet& packet);
|
||||||
|
void handleSummonRequest(network::Packet& packet);
|
||||||
|
void handleTradeStatus(network::Packet& packet);
|
||||||
|
void handleDuelRequested(network::Packet& packet);
|
||||||
|
void handleDuelComplete(network::Packet& packet);
|
||||||
|
void handleDuelWinner(network::Packet& packet);
|
||||||
|
void handleLootRoll(network::Packet& packet);
|
||||||
|
void handleLootRollWon(network::Packet& packet);
|
||||||
|
|
||||||
|
// ---- LFG / Dungeon Finder handlers ----
|
||||||
|
void handleLfgJoinResult(network::Packet& packet);
|
||||||
|
void handleLfgQueueStatus(network::Packet& packet);
|
||||||
|
void handleLfgProposalUpdate(network::Packet& packet);
|
||||||
|
void handleLfgRoleCheckUpdate(network::Packet& packet);
|
||||||
|
void handleLfgUpdatePlayer(network::Packet& packet);
|
||||||
|
void handleLfgPlayerReward(network::Packet& packet);
|
||||||
|
void handleLfgBootProposalUpdate(network::Packet& packet);
|
||||||
|
void handleLfgTeleportDenied(network::Packet& packet);
|
||||||
|
|
||||||
// ---- Arena / Battleground handlers ----
|
// ---- Arena / Battleground handlers ----
|
||||||
void handleBattlefieldStatus(network::Packet& packet);
|
void handleBattlefieldStatus(network::Packet& packet);
|
||||||
void handleInstanceDifficulty(network::Packet& packet);
|
void handleInstanceDifficulty(network::Packet& packet);
|
||||||
|
|
@ -1381,6 +1576,7 @@ private:
|
||||||
// ---- Display state ----
|
// ---- Display state ----
|
||||||
bool helmVisible_ = true;
|
bool helmVisible_ = true;
|
||||||
bool cloakVisible_ = true;
|
bool cloakVisible_ = true;
|
||||||
|
uint8_t standState_ = 0; // 0=stand, 1=sit, ..., 7=dead, 8=kneel (server-confirmed)
|
||||||
|
|
||||||
// ---- Follow state ----
|
// ---- Follow state ----
|
||||||
uint64_t followTargetGuid_ = 0;
|
uint64_t followTargetGuid_ = 0;
|
||||||
|
|
@ -1528,11 +1724,68 @@ private:
|
||||||
uint32_t instanceDifficulty_ = 0;
|
uint32_t instanceDifficulty_ = 0;
|
||||||
bool instanceIsHeroic_ = false;
|
bool instanceIsHeroic_ = false;
|
||||||
|
|
||||||
|
// Mirror timers (0=fatigue, 1=breath, 2=feigndeath)
|
||||||
|
MirrorTimer mirrorTimers_[3];
|
||||||
|
|
||||||
|
// Combo points (rogues/druids)
|
||||||
|
uint8_t comboPoints_ = 0;
|
||||||
|
uint64_t comboTarget_ = 0;
|
||||||
|
|
||||||
|
// Instance / raid lockouts
|
||||||
|
std::vector<InstanceLockout> instanceLockouts_;
|
||||||
|
|
||||||
|
// LFG / Dungeon Finder state
|
||||||
|
LfgState lfgState_ = LfgState::None;
|
||||||
|
uint32_t lfgDungeonId_ = 0; // current dungeon entry
|
||||||
|
uint32_t lfgProposalId_ = 0; // pending proposal id (0 = none)
|
||||||
|
int32_t lfgAvgWaitSec_ = -1; // estimated wait, -1=unknown
|
||||||
|
uint32_t lfgTimeInQueueMs_= 0; // ms already in queue
|
||||||
|
|
||||||
|
// Ready check state
|
||||||
|
bool pendingReadyCheck_ = false;
|
||||||
|
std::string readyCheckInitiator_;
|
||||||
|
|
||||||
|
// Faction standings (factionId → absolute standing value)
|
||||||
|
std::unordered_map<uint32_t, int32_t> factionStandings_;
|
||||||
|
// Faction name cache (factionId → name), populated lazily from Faction.dbc
|
||||||
|
std::unordered_map<uint32_t, std::string> factionNameCache_;
|
||||||
|
bool factionNameCacheLoaded_ = false;
|
||||||
|
void loadFactionNameCache();
|
||||||
|
std::string getFactionName(uint32_t factionId) const;
|
||||||
|
|
||||||
// ---- Phase 4: Group ----
|
// ---- Phase 4: Group ----
|
||||||
GroupListData partyData;
|
GroupListData partyData;
|
||||||
bool pendingGroupInvite = false;
|
bool pendingGroupInvite = false;
|
||||||
std::string pendingInviterName;
|
std::string pendingInviterName;
|
||||||
|
|
||||||
|
// Item text state
|
||||||
|
bool itemTextOpen_ = false;
|
||||||
|
std::string itemText_;
|
||||||
|
|
||||||
|
// Shared quest state
|
||||||
|
bool pendingSharedQuest_ = false;
|
||||||
|
uint32_t sharedQuestId_ = 0;
|
||||||
|
std::string sharedQuestTitle_;
|
||||||
|
std::string sharedQuestSharerName_;
|
||||||
|
uint64_t sharedQuestSharerGuid_ = 0;
|
||||||
|
|
||||||
|
// Summon state
|
||||||
|
bool pendingSummonRequest_ = false;
|
||||||
|
uint64_t summonerGuid_ = 0;
|
||||||
|
std::string summonerName_;
|
||||||
|
float summonTimeoutSec_ = 0.0f;
|
||||||
|
|
||||||
|
// Trade state
|
||||||
|
TradeStatus tradeStatus_ = TradeStatus::None;
|
||||||
|
uint64_t tradePeerGuid_= 0;
|
||||||
|
std::string tradePeerName_;
|
||||||
|
|
||||||
|
// Duel state
|
||||||
|
bool pendingDuelRequest_ = false;
|
||||||
|
uint64_t duelChallengerGuid_= 0;
|
||||||
|
uint64_t duelFlagGuid_ = 0;
|
||||||
|
std::string duelChallengerName_;
|
||||||
|
|
||||||
// ---- Guild state ----
|
// ---- Guild state ----
|
||||||
std::string guildName_;
|
std::string guildName_;
|
||||||
std::vector<std::string> guildRankNames_;
|
std::vector<std::string> guildRankNames_;
|
||||||
|
|
@ -1554,6 +1807,10 @@ private:
|
||||||
bool lootWindowOpen = false;
|
bool lootWindowOpen = false;
|
||||||
bool autoLoot_ = false;
|
bool autoLoot_ = false;
|
||||||
LootResponseData currentLoot;
|
LootResponseData currentLoot;
|
||||||
|
|
||||||
|
// Group loot roll state
|
||||||
|
bool pendingLootRollActive_ = false;
|
||||||
|
LootRollEntry pendingLootRoll_;
|
||||||
struct LocalLootState {
|
struct LocalLootState {
|
||||||
LootResponseData data;
|
LootResponseData data;
|
||||||
bool moneyTaken = false;
|
bool moneyTaken = false;
|
||||||
|
|
@ -1585,6 +1842,7 @@ private:
|
||||||
// Gossip
|
// Gossip
|
||||||
bool gossipWindowOpen = false;
|
bool gossipWindowOpen = false;
|
||||||
GossipMessageData currentGossip;
|
GossipMessageData currentGossip;
|
||||||
|
std::vector<GossipPoi> gossipPois_;
|
||||||
|
|
||||||
void performGameObjectInteractionNow(uint64_t guid);
|
void performGameObjectInteractionNow(uint64_t guid);
|
||||||
|
|
||||||
|
|
@ -1783,6 +2041,10 @@ private:
|
||||||
uint32_t weatherType_ = 0; // 0=clear, 1=rain, 2=snow, 3=storm
|
uint32_t weatherType_ = 0; // 0=clear, 1=rain, 2=snow, 3=storm
|
||||||
float weatherIntensity_ = 0.0f; // 0.0 to 1.0
|
float weatherIntensity_ = 0.0f; // 0.0 to 1.0
|
||||||
|
|
||||||
|
// ---- Light override (SMSG_OVERRIDE_LIGHT) ----
|
||||||
|
uint32_t overrideLightId_ = 0; // 0 = no override
|
||||||
|
uint32_t overrideLightTransMs_ = 0;
|
||||||
|
|
||||||
// ---- Player skills ----
|
// ---- Player skills ----
|
||||||
std::map<uint32_t, PlayerSkill> playerSkills_;
|
std::map<uint32_t, PlayerSkill> playerSkills_;
|
||||||
std::unordered_map<uint32_t, std::string> skillLineNames_;
|
std::unordered_map<uint32_t, std::string> skillLineNames_;
|
||||||
|
|
@ -1810,6 +2072,7 @@ private:
|
||||||
ChargeCallback chargeCallback_;
|
ChargeCallback chargeCallback_;
|
||||||
LevelUpCallback levelUpCallback_;
|
LevelUpCallback levelUpCallback_;
|
||||||
OtherPlayerLevelUpCallback otherPlayerLevelUpCallback_;
|
OtherPlayerLevelUpCallback otherPlayerLevelUpCallback_;
|
||||||
|
AchievementEarnedCallback achievementEarnedCallback_;
|
||||||
MountCallback mountCallback_;
|
MountCallback mountCallback_;
|
||||||
TaxiPrecacheCallback taxiPrecacheCallback_;
|
TaxiPrecacheCallback taxiPrecacheCallback_;
|
||||||
TaxiOrientationCallback taxiOrientationCallback_;
|
TaxiOrientationCallback taxiOrientationCallback_;
|
||||||
|
|
@ -1827,12 +2090,42 @@ private:
|
||||||
float serverPitchRate_ = 3.14159f;
|
float serverPitchRate_ = 3.14159f;
|
||||||
bool playerDead_ = false;
|
bool playerDead_ = false;
|
||||||
bool releasedSpirit_ = false;
|
bool releasedSpirit_ = false;
|
||||||
|
// Death Knight runes (class 6): slots 0-1=Blood, 2-3=Unholy, 4-5=Frost initially
|
||||||
|
std::array<RuneSlot, 6> playerRunes_ = [] {
|
||||||
|
std::array<RuneSlot, 6> r{};
|
||||||
|
r[0].type = r[1].type = RuneType::Blood;
|
||||||
|
r[2].type = r[3].type = RuneType::Unholy;
|
||||||
|
r[4].type = r[5].type = RuneType::Frost;
|
||||||
|
return r;
|
||||||
|
}();
|
||||||
uint64_t pendingSpiritHealerGuid_ = 0;
|
uint64_t pendingSpiritHealerGuid_ = 0;
|
||||||
bool resurrectPending_ = false;
|
bool resurrectPending_ = false;
|
||||||
bool resurrectRequestPending_ = false;
|
bool resurrectRequestPending_ = false;
|
||||||
uint64_t resurrectCasterGuid_ = 0;
|
uint64_t resurrectCasterGuid_ = 0;
|
||||||
bool repopPending_ = false;
|
bool repopPending_ = false;
|
||||||
uint64_t lastRepopRequestMs_ = 0;
|
uint64_t lastRepopRequestMs_ = 0;
|
||||||
|
|
||||||
|
// ---- Completed quest IDs (SMSG_QUERY_QUESTS_COMPLETED_RESPONSE) ----
|
||||||
|
std::unordered_set<uint32_t> completedQuests_;
|
||||||
|
|
||||||
|
// ---- Equipment sets (SMSG_EQUIPMENT_SET_LIST) ----
|
||||||
|
struct EquipmentSet {
|
||||||
|
uint64_t setGuid = 0;
|
||||||
|
uint32_t setId = 0;
|
||||||
|
std::string name;
|
||||||
|
std::string iconName;
|
||||||
|
uint32_t ignoreSlotMask = 0;
|
||||||
|
std::array<uint64_t, 19> itemGuids{};
|
||||||
|
};
|
||||||
|
std::vector<EquipmentSet> equipmentSets_;
|
||||||
|
|
||||||
|
// ---- Forced faction reactions (SMSG_SET_FORCED_REACTIONS) ----
|
||||||
|
std::unordered_map<uint32_t, uint8_t> forcedReactions_; // factionId -> reaction tier
|
||||||
|
|
||||||
|
// ---- Server-triggered audio ----
|
||||||
|
PlayMusicCallback playMusicCallback_;
|
||||||
|
PlaySoundCallback playSoundCallback_;
|
||||||
|
PlayPositionalSoundCallback playPositionalSoundCallback_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace game
|
} // namespace game
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,8 @@ struct ActionBarSlot {
|
||||||
struct CombatTextEntry {
|
struct CombatTextEntry {
|
||||||
enum Type : uint8_t {
|
enum Type : uint8_t {
|
||||||
MELEE_DAMAGE, SPELL_DAMAGE, HEAL, MISS, DODGE, PARRY, BLOCK,
|
MELEE_DAMAGE, SPELL_DAMAGE, HEAL, MISS, DODGE, PARRY, BLOCK,
|
||||||
CRIT_DAMAGE, CRIT_HEAL, PERIODIC_DAMAGE, PERIODIC_HEAL, ENVIRONMENTAL
|
CRIT_DAMAGE, CRIT_HEAL, PERIODIC_DAMAGE, PERIODIC_HEAL, ENVIRONMENTAL,
|
||||||
|
ENERGIZE, XP_GAIN
|
||||||
};
|
};
|
||||||
Type type;
|
Type type;
|
||||||
int32_t amount = 0;
|
int32_t amount = 0;
|
||||||
|
|
|
||||||
|
|
@ -1265,6 +1265,12 @@ public:
|
||||||
// Duel
|
// Duel
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
|
/** CMSG_DUEL_ACCEPTED packet builder (no payload) */
|
||||||
|
class DuelAcceptPacket {
|
||||||
|
public:
|
||||||
|
static network::Packet build();
|
||||||
|
};
|
||||||
|
|
||||||
/** CMSG_DUEL_CANCELLED packet builder */
|
/** CMSG_DUEL_CANCELLED packet builder */
|
||||||
class DuelCancelPacket {
|
class DuelCancelPacket {
|
||||||
public:
|
public:
|
||||||
|
|
@ -1320,6 +1326,24 @@ public:
|
||||||
static network::Packet build(uint64_t targetGuid);
|
static network::Packet build(uint64_t targetGuid);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** CMSG_BEGIN_TRADE packet builder (no payload — accepts incoming trade request) */
|
||||||
|
class BeginTradePacket {
|
||||||
|
public:
|
||||||
|
static network::Packet build();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** CMSG_CANCEL_TRADE packet builder (no payload) */
|
||||||
|
class CancelTradePacket {
|
||||||
|
public:
|
||||||
|
static network::Packet build();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** CMSG_ACCEPT_TRADE packet builder (no payload — lock in current offer) */
|
||||||
|
class AcceptTradePacket {
|
||||||
|
public:
|
||||||
|
static network::Packet build();
|
||||||
|
};
|
||||||
|
|
||||||
/** CMSG_ATTACKSWING packet builder */
|
/** CMSG_ATTACKSWING packet builder */
|
||||||
class AttackSwingPacket {
|
class AttackSwingPacket {
|
||||||
public:
|
public:
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace wowee {
|
namespace wowee {
|
||||||
|
namespace pipeline { class AssetManager; }
|
||||||
namespace game {
|
namespace game {
|
||||||
|
|
||||||
struct ZoneInfo {
|
struct ZoneInfo {
|
||||||
|
|
@ -18,6 +19,10 @@ class ZoneManager {
|
||||||
public:
|
public:
|
||||||
void initialize();
|
void initialize();
|
||||||
|
|
||||||
|
// Supplement zone music paths using AreaTable → ZoneMusic → SoundEntries DBC chain.
|
||||||
|
// Safe to call after initialize(); idempotent and additive (does not remove existing paths).
|
||||||
|
void enrichFromDBC(pipeline::AssetManager* assets);
|
||||||
|
|
||||||
uint32_t getZoneId(int tileX, int tileY) const;
|
uint32_t getZoneId(int tileX, int tileY) const;
|
||||||
const ZoneInfo* getZoneInfo(uint32_t zoneId) const;
|
const ZoneInfo* getZoneInfo(uint32_t zoneId) const;
|
||||||
std::string getRandomMusic(uint32_t zoneId);
|
std::string getRandomMusic(uint32_t zoneId);
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,11 @@ public:
|
||||||
const std::string& lastError() const { return lastError_; }
|
const std::string& lastError() const { return lastError_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
enum class ApiMode {
|
||||||
|
LegacyFsr3,
|
||||||
|
GenericApi
|
||||||
|
};
|
||||||
|
|
||||||
void* libHandle_ = nullptr;
|
void* libHandle_ = nullptr;
|
||||||
std::string loadedLibraryPath_;
|
std::string loadedLibraryPath_;
|
||||||
void* scratchBuffer_ = nullptr;
|
void* scratchBuffer_ = nullptr;
|
||||||
|
|
@ -80,6 +85,10 @@ private:
|
||||||
struct RuntimeFns;
|
struct RuntimeFns;
|
||||||
RuntimeFns* fns_ = nullptr;
|
RuntimeFns* fns_ = nullptr;
|
||||||
void* contextStorage_ = nullptr;
|
void* contextStorage_ = nullptr;
|
||||||
|
ApiMode apiMode_ = ApiMode::LegacyFsr3;
|
||||||
|
void* genericUpscaleContext_ = nullptr;
|
||||||
|
void* genericFramegenContext_ = nullptr;
|
||||||
|
uint64_t genericFrameId_ = 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace wowee::rendering
|
} // namespace wowee::rendering
|
||||||
|
|
|
||||||
|
|
@ -381,7 +381,7 @@ private:
|
||||||
VkDescriptorPool materialDescPool_ = VK_NULL_HANDLE;
|
VkDescriptorPool materialDescPool_ = VK_NULL_HANDLE;
|
||||||
VkDescriptorPool boneDescPool_ = VK_NULL_HANDLE;
|
VkDescriptorPool boneDescPool_ = VK_NULL_HANDLE;
|
||||||
static constexpr uint32_t MAX_MATERIAL_SETS = 8192;
|
static constexpr uint32_t MAX_MATERIAL_SETS = 8192;
|
||||||
static constexpr uint32_t MAX_BONE_SETS = 2048;
|
static constexpr uint32_t MAX_BONE_SETS = 8192;
|
||||||
|
|
||||||
// Dynamic particle buffers
|
// Dynamic particle buffers
|
||||||
::VkBuffer smokeVB_ = VK_NULL_HANDLE;
|
::VkBuffer smokeVB_ = VK_NULL_HANDLE;
|
||||||
|
|
|
||||||
|
|
@ -106,9 +106,22 @@ public:
|
||||||
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const Camera& camera);
|
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const Camera& camera);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render terrain into shadow depth map (Phase 6 stub)
|
* Initialize terrain shadow pipeline (must be called after initialize()).
|
||||||
|
* @param shadowRenderPass Depth-only render pass used for the shadow map.
|
||||||
*/
|
*/
|
||||||
void renderShadow(VkCommandBuffer cmd, const glm::vec3& shadowCenter, float halfExtent);
|
bool initializeShadow(VkRenderPass shadowRenderPass);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render terrain into the shadow depth map.
|
||||||
|
* @param cmd Command buffer (inside shadow render pass).
|
||||||
|
* @param lightSpaceMatrix Orthographic light-space transform.
|
||||||
|
* @param shadowCenter World-space centre of shadow coverage.
|
||||||
|
* @param shadowRadius Cull radius around shadowCenter.
|
||||||
|
*/
|
||||||
|
void renderShadow(VkCommandBuffer cmd, const glm::mat4& lightSpaceMatrix,
|
||||||
|
const glm::vec3& shadowCenter, float shadowRadius);
|
||||||
|
|
||||||
|
bool hasShadowPipeline() const { return shadowPipeline_ != VK_NULL_HANDLE; }
|
||||||
|
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
|
|
@ -119,7 +132,6 @@ public:
|
||||||
void setFogEnabled(bool enabled) { fogEnabled = enabled; }
|
void setFogEnabled(bool enabled) { fogEnabled = enabled; }
|
||||||
bool isFogEnabled() const { return fogEnabled; }
|
bool isFogEnabled() const { return fogEnabled; }
|
||||||
|
|
||||||
// Shadow mapping stubs (Phase 6)
|
|
||||||
void setShadowMap(VkDescriptorImageInfo /*depthInfo*/, const glm::mat4& /*lightSpaceMat*/) {}
|
void setShadowMap(VkDescriptorImageInfo /*depthInfo*/, const glm::mat4& /*lightSpaceMat*/) {}
|
||||||
void clearShadowMap() {}
|
void clearShadowMap() {}
|
||||||
|
|
||||||
|
|
@ -142,12 +154,21 @@ private:
|
||||||
VkContext* vkCtx = nullptr;
|
VkContext* vkCtx = nullptr;
|
||||||
pipeline::AssetManager* assetManager = nullptr;
|
pipeline::AssetManager* assetManager = nullptr;
|
||||||
|
|
||||||
// Pipeline
|
// Main pipelines
|
||||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||||
VkPipeline wireframePipeline = VK_NULL_HANDLE;
|
VkPipeline wireframePipeline = VK_NULL_HANDLE;
|
||||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||||
VkDescriptorSetLayout materialSetLayout = VK_NULL_HANDLE;
|
VkDescriptorSetLayout materialSetLayout = VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
// Shadow pipeline
|
||||||
|
VkPipeline shadowPipeline_ = VK_NULL_HANDLE;
|
||||||
|
VkPipelineLayout shadowPipelineLayout_ = VK_NULL_HANDLE;
|
||||||
|
VkDescriptorSetLayout shadowParamsLayout_ = VK_NULL_HANDLE;
|
||||||
|
VkDescriptorPool shadowParamsPool_ = VK_NULL_HANDLE;
|
||||||
|
VkDescriptorSet shadowParamsSet_ = VK_NULL_HANDLE;
|
||||||
|
VkBuffer shadowParamsUBO_ = VK_NULL_HANDLE;
|
||||||
|
VmaAllocation shadowParamsAlloc_ = VK_NULL_HANDLE;
|
||||||
|
|
||||||
// Descriptor pool for material sets
|
// Descriptor pool for material sets
|
||||||
VkDescriptorPool materialDescPool = VK_NULL_HANDLE;
|
VkDescriptorPool materialDescPool = VK_NULL_HANDLE;
|
||||||
static constexpr uint32_t MAX_MATERIAL_SETS = 16384;
|
static constexpr uint32_t MAX_MATERIAL_SETS = 16384;
|
||||||
|
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <GL/glew.h>
|
|
||||||
|
|
||||||
namespace wowee {
|
|
||||||
namespace rendering {
|
|
||||||
|
|
||||||
class Texture {
|
|
||||||
public:
|
|
||||||
Texture() = default;
|
|
||||||
~Texture();
|
|
||||||
|
|
||||||
bool loadFromFile(const std::string& path);
|
|
||||||
bool loadFromMemory(const unsigned char* data, int width, int height, int channels);
|
|
||||||
|
|
||||||
void bind(GLuint unit = 0) const;
|
|
||||||
void unbind() const;
|
|
||||||
|
|
||||||
GLuint getID() const { return textureID; }
|
|
||||||
int getWidth() const { return width; }
|
|
||||||
int getHeight() const { return height; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
GLuint textureID = 0;
|
|
||||||
int width = 0;
|
|
||||||
int height = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply anisotropic filtering to the currently bound GL_TEXTURE_2D.
|
|
||||||
* Queries the driver maximum once and caches it. No-op if the extension
|
|
||||||
* is not available.
|
|
||||||
*/
|
|
||||||
void applyAnisotropicFiltering();
|
|
||||||
|
|
||||||
} // namespace rendering
|
|
||||||
} // namespace wowee
|
|
||||||
|
|
@ -62,6 +62,7 @@ private:
|
||||||
// UI state
|
// UI state
|
||||||
bool showEntityWindow = false;
|
bool showEntityWindow = false;
|
||||||
bool showChatWindow = true;
|
bool showChatWindow = true;
|
||||||
|
bool showNameplates_ = true; // V key toggles nameplates
|
||||||
bool showPlayerInfo = false;
|
bool showPlayerInfo = false;
|
||||||
bool showGuildRoster_ = false;
|
bool showGuildRoster_ = false;
|
||||||
std::string selectedGuildMember_;
|
std::string selectedGuildMember_;
|
||||||
|
|
@ -117,7 +118,7 @@ private:
|
||||||
bool pendingPOM = true; // on by default
|
bool pendingPOM = true; // on by default
|
||||||
int pendingPOMQuality = 1; // 0=Low(16), 1=Medium(32), 2=High(64)
|
int pendingPOMQuality = 1; // 0=Low(16), 1=Medium(32), 2=High(64)
|
||||||
bool pendingFSR = false;
|
bool pendingFSR = false;
|
||||||
int pendingUpscalingMode = 0; // 0=Off, 1=FSR1, 2=FSR2
|
int pendingUpscalingMode = 0; // 0=Off, 1=FSR1, 2=FSR3
|
||||||
int pendingFSRQuality = 3; // 0=UltraQuality, 1=Quality, 2=Balanced, 3=Native(100%)
|
int pendingFSRQuality = 3; // 0=UltraQuality, 1=Quality, 2=Balanced, 3=Native(100%)
|
||||||
float pendingFSRSharpness = 1.6f;
|
float pendingFSRSharpness = 1.6f;
|
||||||
float pendingFSR2JitterSign = 0.38f;
|
float pendingFSR2JitterSign = 0.38f;
|
||||||
|
|
@ -181,6 +182,11 @@ private:
|
||||||
*/
|
*/
|
||||||
void renderTargetFrame(game::GameHandler& gameHandler);
|
void renderTargetFrame(game::GameHandler& gameHandler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render pet frame (below player frame when player has an active pet)
|
||||||
|
*/
|
||||||
|
void renderPetFrame(game::GameHandler& gameHandler);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process targeting input (Tab, Escape, click)
|
* Process targeting input (Tab, Escape, click)
|
||||||
*/
|
*/
|
||||||
|
|
@ -201,9 +207,16 @@ private:
|
||||||
void renderBagBar(game::GameHandler& gameHandler);
|
void renderBagBar(game::GameHandler& gameHandler);
|
||||||
void renderXpBar(game::GameHandler& gameHandler);
|
void renderXpBar(game::GameHandler& gameHandler);
|
||||||
void renderCastBar(game::GameHandler& gameHandler);
|
void renderCastBar(game::GameHandler& gameHandler);
|
||||||
|
void renderMirrorTimers(game::GameHandler& gameHandler);
|
||||||
void renderCombatText(game::GameHandler& gameHandler);
|
void renderCombatText(game::GameHandler& gameHandler);
|
||||||
void renderPartyFrames(game::GameHandler& gameHandler);
|
void renderPartyFrames(game::GameHandler& gameHandler);
|
||||||
void renderGroupInvitePopup(game::GameHandler& gameHandler);
|
void renderGroupInvitePopup(game::GameHandler& gameHandler);
|
||||||
|
void renderDuelRequestPopup(game::GameHandler& gameHandler);
|
||||||
|
void renderLootRollPopup(game::GameHandler& gameHandler);
|
||||||
|
void renderTradeRequestPopup(game::GameHandler& gameHandler);
|
||||||
|
void renderSummonRequestPopup(game::GameHandler& gameHandler);
|
||||||
|
void renderSharedQuestPopup(game::GameHandler& gameHandler);
|
||||||
|
void renderItemTextWindow(game::GameHandler& gameHandler);
|
||||||
void renderBuffBar(game::GameHandler& gameHandler);
|
void renderBuffBar(game::GameHandler& gameHandler);
|
||||||
void renderLootWindow(game::GameHandler& gameHandler);
|
void renderLootWindow(game::GameHandler& gameHandler);
|
||||||
void renderGossipWindow(game::GameHandler& gameHandler);
|
void renderGossipWindow(game::GameHandler& gameHandler);
|
||||||
|
|
@ -219,14 +232,19 @@ private:
|
||||||
void renderSettingsWindow();
|
void renderSettingsWindow();
|
||||||
void renderQuestMarkers(game::GameHandler& gameHandler);
|
void renderQuestMarkers(game::GameHandler& gameHandler);
|
||||||
void renderMinimapMarkers(game::GameHandler& gameHandler);
|
void renderMinimapMarkers(game::GameHandler& gameHandler);
|
||||||
|
void renderQuestObjectiveTracker(game::GameHandler& gameHandler);
|
||||||
void renderGuildRoster(game::GameHandler& gameHandler);
|
void renderGuildRoster(game::GameHandler& gameHandler);
|
||||||
void renderGuildInvitePopup(game::GameHandler& gameHandler);
|
void renderGuildInvitePopup(game::GameHandler& gameHandler);
|
||||||
|
void renderReadyCheckPopup(game::GameHandler& gameHandler);
|
||||||
void renderChatBubbles(game::GameHandler& gameHandler);
|
void renderChatBubbles(game::GameHandler& gameHandler);
|
||||||
void renderMailWindow(game::GameHandler& gameHandler);
|
void renderMailWindow(game::GameHandler& gameHandler);
|
||||||
void renderMailComposeWindow(game::GameHandler& gameHandler);
|
void renderMailComposeWindow(game::GameHandler& gameHandler);
|
||||||
void renderBankWindow(game::GameHandler& gameHandler);
|
void renderBankWindow(game::GameHandler& gameHandler);
|
||||||
void renderGuildBankWindow(game::GameHandler& gameHandler);
|
void renderGuildBankWindow(game::GameHandler& gameHandler);
|
||||||
void renderAuctionHouseWindow(game::GameHandler& gameHandler);
|
void renderAuctionHouseWindow(game::GameHandler& gameHandler);
|
||||||
|
void renderDungeonFinderWindow(game::GameHandler& gameHandler);
|
||||||
|
void renderInstanceLockouts(game::GameHandler& gameHandler);
|
||||||
|
void renderNameplates(game::GameHandler& gameHandler);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inventory screen
|
* Inventory screen
|
||||||
|
|
@ -249,6 +267,9 @@ private:
|
||||||
bool spellIconDbLoaded_ = false;
|
bool spellIconDbLoaded_ = false;
|
||||||
VkDescriptorSet getSpellIcon(uint32_t spellId, pipeline::AssetManager* am);
|
VkDescriptorSet getSpellIcon(uint32_t spellId, pipeline::AssetManager* am);
|
||||||
|
|
||||||
|
// Death Knight rune bar: client-predicted fill (0.0=depleted, 1.0=ready) for smooth animation
|
||||||
|
float runeClientFill_[6] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f};
|
||||||
|
|
||||||
// Action bar drag state (-1 = not dragging)
|
// Action bar drag state (-1 = not dragging)
|
||||||
int actionBarDragSlot_ = -1;
|
int actionBarDragSlot_ = -1;
|
||||||
VkDescriptorSet actionBarDragIcon_ = VK_NULL_HANDLE;
|
VkDescriptorSet actionBarDragIcon_ = VK_NULL_HANDLE;
|
||||||
|
|
@ -259,6 +280,14 @@ private:
|
||||||
int bagBarPickedSlot_ = -1; // Visual drag in progress (-1 = none)
|
int bagBarPickedSlot_ = -1; // Visual drag in progress (-1 = none)
|
||||||
int bagBarDragSource_ = -1; // Mouse pressed on this slot, waiting for drag or click (-1 = none)
|
int bagBarDragSource_ = -1; // Mouse pressed on this slot, waiting for drag or click (-1 = none)
|
||||||
|
|
||||||
|
// Instance Lockouts window
|
||||||
|
bool showInstanceLockouts_ = false;
|
||||||
|
|
||||||
|
// Dungeon Finder state
|
||||||
|
bool showDungeonFinder_ = false;
|
||||||
|
uint8_t lfgRoles_ = 0x08; // default: DPS (0x02=tank, 0x04=healer, 0x08=dps)
|
||||||
|
uint32_t lfgSelectedDungeon_ = 861; // default: random dungeon (entry 861 = Random Dungeon WotLK)
|
||||||
|
|
||||||
// Chat settings
|
// Chat settings
|
||||||
bool chatShowTimestamps_ = false;
|
bool chatShowTimestamps_ = false;
|
||||||
int chatFontSize_ = 1; // 0=small, 1=medium, 2=large
|
int chatFontSize_ = 1; // 0=small, 1=medium, 2=large
|
||||||
|
|
@ -320,8 +349,22 @@ private:
|
||||||
uint32_t dingLevel_ = 0;
|
uint32_t dingLevel_ = 0;
|
||||||
void renderDingEffect();
|
void renderDingEffect();
|
||||||
|
|
||||||
|
// Achievement toast banner
|
||||||
|
static constexpr float ACHIEVEMENT_TOAST_DURATION = 5.0f;
|
||||||
|
float achievementToastTimer_ = 0.0f;
|
||||||
|
uint32_t achievementToastId_ = 0;
|
||||||
|
void renderAchievementToast();
|
||||||
|
|
||||||
|
// Zone discovery text ("Entering: <ZoneName>")
|
||||||
|
static constexpr float ZONE_TEXT_DURATION = 5.0f;
|
||||||
|
float zoneTextTimer_ = 0.0f;
|
||||||
|
std::string zoneTextName_;
|
||||||
|
std::string lastKnownZoneName_;
|
||||||
|
void renderZoneText();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void triggerDing(uint32_t newLevel);
|
void triggerDing(uint32_t newLevel);
|
||||||
|
void triggerAchievementToast(uint32_t achievementId);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,7 @@ private:
|
||||||
void renderEquipmentPanel(game::Inventory& inventory);
|
void renderEquipmentPanel(game::Inventory& inventory);
|
||||||
void renderBackpackPanel(game::Inventory& inventory, bool collapseEmptySections = false);
|
void renderBackpackPanel(game::Inventory& inventory, bool collapseEmptySections = false);
|
||||||
void renderStatsPanel(game::Inventory& inventory, uint32_t playerLevel, int32_t serverArmor = 0);
|
void renderStatsPanel(game::Inventory& inventory, uint32_t playerLevel, int32_t serverArmor = 0);
|
||||||
|
void renderReputationPanel(game::GameHandler& gameHandler);
|
||||||
|
|
||||||
void renderItemSlot(game::Inventory& inventory, const game::ItemSlot& slot,
|
void renderItemSlot(game::Inventory& inventory, const game::ItemSlot& slot,
|
||||||
float size, const char* label,
|
float size, const char* label,
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,34 @@ bool AmbientSoundManager::initialize(pipeline::AssetManager* assets) {
|
||||||
blacksmithSounds_.resize(1);
|
blacksmithSounds_.resize(1);
|
||||||
bool blacksmithLoaded = loadSound("Sound\\Ambience\\WMOAmbience\\BlackSmith.wav", blacksmithSounds_[0], assets);
|
bool blacksmithLoaded = loadSound("Sound\\Ambience\\WMOAmbience\\BlackSmith.wav", blacksmithSounds_[0], assets);
|
||||||
|
|
||||||
|
// Load bird chirp sounds (daytime periodic) — up to 6 variants
|
||||||
|
{
|
||||||
|
static const char* birdPaths[] = {
|
||||||
|
"Sound\\Ambience\\BirdAmbience\\BirdChirp01.wav",
|
||||||
|
"Sound\\Ambience\\BirdAmbience\\BirdChirp02.wav",
|
||||||
|
"Sound\\Ambience\\BirdAmbience\\BirdChirp03.wav",
|
||||||
|
"Sound\\Ambience\\BirdAmbience\\BirdChirp04.wav",
|
||||||
|
"Sound\\Ambience\\BirdAmbience\\BirdChirp05.wav",
|
||||||
|
"Sound\\Ambience\\BirdAmbience\\BirdChirp06.wav",
|
||||||
|
};
|
||||||
|
for (const char* p : birdPaths) {
|
||||||
|
birdSounds_.emplace_back();
|
||||||
|
if (!loadSound(p, birdSounds_.back(), assets)) birdSounds_.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load cricket/insect sounds (nighttime periodic)
|
||||||
|
{
|
||||||
|
static const char* cricketPaths[] = {
|
||||||
|
"Sound\\Ambience\\Insect\\InsectMorning.wav",
|
||||||
|
"Sound\\Ambience\\Insect\\InsectNight.wav",
|
||||||
|
};
|
||||||
|
for (const char* p : cricketPaths) {
|
||||||
|
cricketSounds_.emplace_back();
|
||||||
|
if (!loadSound(p, cricketSounds_.back(), assets)) cricketSounds_.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Load weather sounds
|
// Load weather sounds
|
||||||
rainLightSounds_.resize(1);
|
rainLightSounds_.resize(1);
|
||||||
bool rainLightLoaded = loadSound("Sound\\Ambience\\Weather\\RainLight.wav", rainLightSounds_[0], assets);
|
bool rainLightLoaded = loadSound("Sound\\Ambience\\Weather\\RainLight.wav", rainLightSounds_[0], assets);
|
||||||
|
|
@ -413,9 +441,13 @@ void AmbientSoundManager::updatePeriodicSounds(float deltaTime, bool isIndoor, b
|
||||||
if (isDaytime()) {
|
if (isDaytime()) {
|
||||||
birdTimer_ += deltaTime;
|
birdTimer_ += deltaTime;
|
||||||
if (birdTimer_ >= randomFloat(BIRD_MIN_INTERVAL, BIRD_MAX_INTERVAL)) {
|
if (birdTimer_ >= randomFloat(BIRD_MIN_INTERVAL, BIRD_MAX_INTERVAL)) {
|
||||||
// Play a random bird chirp (we'll use wind sound as placeholder for now)
|
|
||||||
// TODO: Add actual bird sound files when available
|
|
||||||
birdTimer_ = 0.0f;
|
birdTimer_ = 0.0f;
|
||||||
|
if (!birdSounds_.empty()) {
|
||||||
|
std::uniform_int_distribution<size_t> pick(0, birdSounds_.size() - 1);
|
||||||
|
const auto& snd = birdSounds_[pick(gen)];
|
||||||
|
if (snd.loaded)
|
||||||
|
AudioEngine::instance().playSound2D(snd.data, BIRD_VOLUME, 1.0f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -423,9 +455,13 @@ void AmbientSoundManager::updatePeriodicSounds(float deltaTime, bool isIndoor, b
|
||||||
if (isNighttime()) {
|
if (isNighttime()) {
|
||||||
cricketTimer_ += deltaTime;
|
cricketTimer_ += deltaTime;
|
||||||
if (cricketTimer_ >= randomFloat(CRICKET_MIN_INTERVAL, CRICKET_MAX_INTERVAL)) {
|
if (cricketTimer_ >= randomFloat(CRICKET_MIN_INTERVAL, CRICKET_MAX_INTERVAL)) {
|
||||||
// Play cricket sounds
|
|
||||||
// TODO: Add actual cricket sound files when available
|
|
||||||
cricketTimer_ = 0.0f;
|
cricketTimer_ = 0.0f;
|
||||||
|
if (!cricketSounds_.empty()) {
|
||||||
|
std::uniform_int_distribution<size_t> pick(0, cricketSounds_.size() - 1);
|
||||||
|
const auto& snd = cricketSounds_[pick(gen)];
|
||||||
|
if (snd.loaded)
|
||||||
|
AudioEngine::instance().playSound2D(snd.data, CRICKET_VOLUME, 1.0f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -554,6 +590,94 @@ void AmbientSoundManager::setZoneType(ZoneType type) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AmbientSoundManager::setZoneId(uint32_t zoneId) {
|
||||||
|
// Map WoW zone ID to ZoneType + CityType.
|
||||||
|
// City zones: set CityType and clear ZoneType.
|
||||||
|
// Outdoor zones: set ZoneType and clear CityType.
|
||||||
|
CityType city = CityType::NONE;
|
||||||
|
ZoneType zone = ZoneType::NONE;
|
||||||
|
|
||||||
|
switch (zoneId) {
|
||||||
|
// ---- Major cities ----
|
||||||
|
case 1519: city = CityType::STORMWIND; break;
|
||||||
|
case 1537: city = CityType::IRONFORGE; break;
|
||||||
|
case 1657: city = CityType::DARNASSUS; break;
|
||||||
|
case 1637: city = CityType::ORGRIMMAR; break;
|
||||||
|
case 1497: city = CityType::UNDERCITY; break;
|
||||||
|
case 1638: city = CityType::THUNDERBLUFF; break;
|
||||||
|
|
||||||
|
// ---- Forest / snowy forest ----
|
||||||
|
case 12: // Elwynn Forest
|
||||||
|
case 141: // Teldrassil
|
||||||
|
case 148: // Darkshore
|
||||||
|
case 493: // Moonglade
|
||||||
|
case 361: // Felwood
|
||||||
|
case 331: // Ashenvale
|
||||||
|
case 357: // Feralas
|
||||||
|
case 15: // Dustwallow Marsh (lush)
|
||||||
|
case 267: // Hillsbrad Foothills
|
||||||
|
case 36: // Alterac Mountains
|
||||||
|
case 45: // Arathi Highlands
|
||||||
|
zone = ZoneType::FOREST_NORMAL; break;
|
||||||
|
|
||||||
|
case 1: // Dun Morogh
|
||||||
|
case 196: // Winterspring
|
||||||
|
case 3: // Badlands (actually dry but close enough)
|
||||||
|
case 2817: // Crystalsong Forest
|
||||||
|
case 66: // Storm Peaks
|
||||||
|
case 67: // Icecrown
|
||||||
|
case 394: // Dragonblight
|
||||||
|
case 65: // Howling Fjord
|
||||||
|
zone = ZoneType::FOREST_SNOW; break;
|
||||||
|
|
||||||
|
// ---- Grasslands / plains ----
|
||||||
|
case 40: // Westfall
|
||||||
|
case 215: // Mulgore
|
||||||
|
case 44: // Redridge Mountains
|
||||||
|
case 10: // Duskwood (counts as grassland night)
|
||||||
|
case 38: // Loch Modan
|
||||||
|
zone = ZoneType::GRASSLANDS; break;
|
||||||
|
|
||||||
|
// ---- Desert ----
|
||||||
|
case 17: // The Barrens
|
||||||
|
case 14: // Durotar
|
||||||
|
case 440: // Tanaris
|
||||||
|
case 400: // Thousand Needles
|
||||||
|
zone = ZoneType::DESERT_PLAINS; break;
|
||||||
|
|
||||||
|
case 46: // Burning Steppes
|
||||||
|
case 51: // Searing Gorge
|
||||||
|
case 241: // Eastern Plaguelands (barren)
|
||||||
|
case 28: // Western Plaguelands
|
||||||
|
zone = ZoneType::DESERT_CANYON; break;
|
||||||
|
|
||||||
|
// ---- Jungle ----
|
||||||
|
case 33: // Stranglethorn Vale
|
||||||
|
case 78: // Un'Goro Crater
|
||||||
|
case 210: // Uldaman
|
||||||
|
case 1377: // Silithus (arid but closest)
|
||||||
|
zone = ZoneType::JUNGLE; break;
|
||||||
|
|
||||||
|
// ---- Marsh / swamp ----
|
||||||
|
case 8: // Swamp of Sorrows
|
||||||
|
case 11: // Wetlands
|
||||||
|
case 139: // Eastern Plaguelands
|
||||||
|
case 763: // Zangarmarsh
|
||||||
|
zone = ZoneType::MARSH; break;
|
||||||
|
|
||||||
|
// ---- Beach / coast ----
|
||||||
|
case 4: // Barrens coast (Merchant Coast)
|
||||||
|
case 3537: // Azuremyst Isle
|
||||||
|
case 3524: // Bloodmyst Isle
|
||||||
|
zone = ZoneType::BEACH; break;
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCityType(city);
|
||||||
|
setZoneType(zone);
|
||||||
|
}
|
||||||
|
|
||||||
void AmbientSoundManager::setCityType(CityType type) {
|
void AmbientSoundManager::setCityType(CityType type) {
|
||||||
if (currentCity_ != type) {
|
if (currentCity_ != type) {
|
||||||
LOG_INFO("AmbientSoundManager: City changed from ", static_cast<int>(currentCity_),
|
LOG_INFO("AmbientSoundManager: City changed from ", static_cast<int>(currentCity_),
|
||||||
|
|
|
||||||
|
|
@ -144,15 +144,24 @@ void MusicManager::playFilePath(const std::string& filePath, bool loop, float fa
|
||||||
}
|
}
|
||||||
|
|
||||||
void MusicManager::stopMusic(float fadeMs) {
|
void MusicManager::stopMusic(float fadeMs) {
|
||||||
(void)fadeMs; // Fade not implemented yet
|
if (!playing) return;
|
||||||
AudioEngine::instance().stopMusic();
|
|
||||||
playing = false;
|
|
||||||
fadingIn = false;
|
fadingIn = false;
|
||||||
fadeInTimer = 0.0f;
|
crossfading = false;
|
||||||
fadeInDuration = 0.0f;
|
|
||||||
fadeInTargetVolume = 0.0f;
|
if (fadeMs > 0.0f) {
|
||||||
currentTrack.clear();
|
// Begin fade-out; actual stop happens once volume reaches zero in update()
|
||||||
currentTrackIsFile = false;
|
fadingOut = true;
|
||||||
|
fadeOutTimer = 0.0f;
|
||||||
|
fadeOutDuration = fadeMs / 1000.0f;
|
||||||
|
fadeOutStartVolume = effectiveMusicVolume();
|
||||||
|
} else {
|
||||||
|
AudioEngine::instance().stopMusic();
|
||||||
|
playing = false;
|
||||||
|
fadingOut = false;
|
||||||
|
currentTrack.clear();
|
||||||
|
currentTrackIsFile = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MusicManager::setVolume(int volume) {
|
void MusicManager::setVolume(int volume) {
|
||||||
|
|
@ -224,6 +233,22 @@ void MusicManager::update(float deltaTime) {
|
||||||
playing = false;
|
playing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fadingOut) {
|
||||||
|
fadeOutTimer += deltaTime;
|
||||||
|
float t = std::clamp(1.0f - fadeOutTimer / std::max(fadeOutDuration, 0.001f), 0.0f, 1.0f);
|
||||||
|
AudioEngine::instance().setMusicVolume(fadeOutStartVolume * t);
|
||||||
|
if (t <= 0.0f) {
|
||||||
|
// Fade complete — stop playback and restore volume for next track
|
||||||
|
fadingOut = false;
|
||||||
|
AudioEngine::instance().stopMusic();
|
||||||
|
AudioEngine::instance().setMusicVolume(effectiveMusicVolume());
|
||||||
|
playing = false;
|
||||||
|
currentTrack.clear();
|
||||||
|
currentTrackIsFile = false;
|
||||||
|
}
|
||||||
|
return; // Don't process other fade logic while fading out
|
||||||
|
}
|
||||||
|
|
||||||
if (fadingIn) {
|
if (fadingIn) {
|
||||||
fadeInTimer += deltaTime;
|
fadeInTimer += deltaTime;
|
||||||
float t = std::clamp(fadeInTimer / std::max(fadeInDuration, 0.001f), 0.0f, 1.0f);
|
float t = std::clamp(fadeInTimer / std::max(fadeInDuration, 0.001f), 0.0f, 1.0f);
|
||||||
|
|
|
||||||
|
|
@ -373,12 +373,5 @@ void NpcVoiceManager::playFlee(uint64_t npcGuid, VoiceType voiceType, const glm:
|
||||||
playSound(npcGuid, voiceType, SoundCategory::FLEE, position);
|
playSound(npcGuid, voiceType, SoundCategory::FLEE, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
VoiceType NpcVoiceManager::detectVoiceType(uint32_t creatureEntry) const {
|
|
||||||
// TODO: Use CreatureTemplate.dbc or other data to map creature entry to voice type
|
|
||||||
// For now, return generic
|
|
||||||
(void)creatureEntry;
|
|
||||||
return VoiceType::GENERIC;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace audio
|
} // namespace audio
|
||||||
} // namespace wowee
|
} // namespace wowee
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#include "audio/audio_engine.hpp"
|
#include "audio/audio_engine.hpp"
|
||||||
#include "pipeline/asset_manager.hpp"
|
#include "pipeline/asset_manager.hpp"
|
||||||
#include "core/logger.hpp"
|
#include "core/logger.hpp"
|
||||||
|
#include <algorithm>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
|
||||||
namespace wowee {
|
namespace wowee {
|
||||||
|
|
@ -180,7 +181,7 @@ void SpellSoundManager::playRandomSound(const std::vector<SpellSample>& library,
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpellSoundManager::setVolumeScale(float scale) {
|
void SpellSoundManager::setVolumeScale(float scale) {
|
||||||
volumeScale_ = std::max(0.0f, std::min(1.0f, scale));
|
volumeScale_ = std::clamp(scale, .0f, 1.f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpellSoundManager::playPrecast(MagicSchool school, SpellPower power) {
|
void SpellSoundManager::playPrecast(MagicSchool school, SpellPower power) {
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,13 @@ bool UiSoundManager::initialize(pipeline::AssetManager* assets) {
|
||||||
levelUpSounds_.resize(1);
|
levelUpSounds_.resize(1);
|
||||||
bool levelUpLoaded = loadSound("Sound\\Interface\\LevelUp.wav", levelUpSounds_[0], assets);
|
bool levelUpLoaded = loadSound("Sound\\Interface\\LevelUp.wav", levelUpSounds_[0], assets);
|
||||||
|
|
||||||
|
// Load achievement sound (WotLK: Sound\Interface\AchievementSound.wav)
|
||||||
|
achievementSounds_.resize(1);
|
||||||
|
if (!loadSound("Sound\\Interface\\AchievementSound.wav", achievementSounds_[0], assets)) {
|
||||||
|
// Fallback to level-up sound if achievement sound is missing
|
||||||
|
achievementSounds_ = levelUpSounds_;
|
||||||
|
}
|
||||||
|
|
||||||
// Load error/feedback sounds
|
// Load error/feedback sounds
|
||||||
errorSounds_.resize(1);
|
errorSounds_.resize(1);
|
||||||
loadSound("Sound\\Interface\\Error.wav", errorSounds_[0], assets);
|
loadSound("Sound\\Interface\\Error.wav", errorSounds_[0], assets);
|
||||||
|
|
@ -210,6 +217,9 @@ void UiSoundManager::playDrinking() { playSound(drinkingSounds_); }
|
||||||
// Level up
|
// Level up
|
||||||
void UiSoundManager::playLevelUp() { playSound(levelUpSounds_); }
|
void UiSoundManager::playLevelUp() { playSound(levelUpSounds_); }
|
||||||
|
|
||||||
|
// Achievement
|
||||||
|
void UiSoundManager::playAchievementAlert() { playSound(achievementSounds_); }
|
||||||
|
|
||||||
// Error/feedback
|
// Error/feedback
|
||||||
void UiSoundManager::playError() { playSound(errorSounds_); }
|
void UiSoundManager::playError() { playSound(errorSounds_); }
|
||||||
void UiSoundManager::playTargetSelect() { playSound(selectTargetSounds_); }
|
void UiSoundManager::playTargetSelect() { playSound(selectTargetSounds_); }
|
||||||
|
|
|
||||||
|
|
@ -2089,6 +2089,88 @@ void Application::setupUICallbacks() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Achievement earned callback — show toast banner
|
||||||
|
gameHandler->setAchievementEarnedCallback([this](uint32_t achievementId) {
|
||||||
|
if (uiManager) {
|
||||||
|
uiManager->getGameScreen().triggerAchievementToast(achievementId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Server-triggered music callback (SMSG_PLAY_MUSIC)
|
||||||
|
// Resolves soundId → SoundEntries.dbc → MPQ path → MusicManager.
|
||||||
|
gameHandler->setPlayMusicCallback([this](uint32_t soundId) {
|
||||||
|
if (!assetManager || !renderer) return;
|
||||||
|
auto* music = renderer->getMusicManager();
|
||||||
|
if (!music) return;
|
||||||
|
|
||||||
|
auto dbc = assetManager->loadDBC("SoundEntries.dbc");
|
||||||
|
if (!dbc || !dbc->isLoaded()) return;
|
||||||
|
|
||||||
|
int32_t idx = dbc->findRecordById(soundId);
|
||||||
|
if (idx < 0) return;
|
||||||
|
|
||||||
|
// SoundEntries.dbc (WotLK): field 2 = Name (label), fields 3-12 = File[0..9], field 23 = DirectoryBase
|
||||||
|
const uint32_t row = static_cast<uint32_t>(idx);
|
||||||
|
std::string dir = dbc->getString(row, 23);
|
||||||
|
for (uint32_t f = 3; f <= 12; ++f) {
|
||||||
|
std::string name = dbc->getString(row, f);
|
||||||
|
if (name.empty()) continue;
|
||||||
|
std::string path = dir.empty() ? name : dir + "\\" + name;
|
||||||
|
music->playMusic(path, /*loop=*/false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// SMSG_PLAY_SOUND: look up SoundEntries.dbc and play 2-D sound effect
|
||||||
|
gameHandler->setPlaySoundCallback([this](uint32_t soundId) {
|
||||||
|
if (!assetManager) return;
|
||||||
|
|
||||||
|
auto dbc = assetManager->loadDBC("SoundEntries.dbc");
|
||||||
|
if (!dbc || !dbc->isLoaded()) return;
|
||||||
|
|
||||||
|
int32_t idx = dbc->findRecordById(soundId);
|
||||||
|
if (idx < 0) return;
|
||||||
|
|
||||||
|
const uint32_t row = static_cast<uint32_t>(idx);
|
||||||
|
std::string dir = dbc->getString(row, 23);
|
||||||
|
for (uint32_t f = 3; f <= 12; ++f) {
|
||||||
|
std::string name = dbc->getString(row, f);
|
||||||
|
if (name.empty()) continue;
|
||||||
|
std::string path = dir.empty() ? name : dir + "\\" + name;
|
||||||
|
audio::AudioEngine::instance().playSound2D(path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// SMSG_PLAY_OBJECT_SOUND / SMSG_PLAY_SPELL_IMPACT: play as 3D positional sound at source entity
|
||||||
|
gameHandler->setPlayPositionalSoundCallback([this](uint32_t soundId, uint64_t sourceGuid) {
|
||||||
|
if (!assetManager || !gameHandler) return;
|
||||||
|
|
||||||
|
auto dbc = assetManager->loadDBC("SoundEntries.dbc");
|
||||||
|
if (!dbc || !dbc->isLoaded()) return;
|
||||||
|
|
||||||
|
int32_t idx = dbc->findRecordById(soundId);
|
||||||
|
if (idx < 0) return;
|
||||||
|
|
||||||
|
const uint32_t row = static_cast<uint32_t>(idx);
|
||||||
|
std::string dir = dbc->getString(row, 23);
|
||||||
|
for (uint32_t f = 3; f <= 12; ++f) {
|
||||||
|
std::string name = dbc->getString(row, f);
|
||||||
|
if (name.empty()) continue;
|
||||||
|
std::string path = dir.empty() ? name : dir + "\\" + name;
|
||||||
|
|
||||||
|
// Play as 3D sound if source entity position is available
|
||||||
|
auto entity = gameHandler->getEntityManager().getEntity(sourceGuid);
|
||||||
|
if (entity) {
|
||||||
|
glm::vec3 pos{entity->getLatestX(), entity->getLatestY(), entity->getLatestZ()};
|
||||||
|
audio::AudioEngine::instance().playSound3D(path, pos);
|
||||||
|
} else {
|
||||||
|
audio::AudioEngine::instance().playSound2D(path);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Other player level-up callback — trigger 3D effect + chat notification
|
// Other player level-up callback — trigger 3D effect + chat notification
|
||||||
gameHandler->setOtherPlayerLevelUpCallback([this](uint64_t guid, uint32_t newLevel) {
|
gameHandler->setOtherPlayerLevelUpCallback([this](uint64_t guid, uint32_t newLevel) {
|
||||||
if (!gameHandler || !renderer) return;
|
if (!gameHandler || !renderer) return;
|
||||||
|
|
@ -4643,7 +4725,7 @@ audio::VoiceType Application::detectVoiceTypeFromDisplayId(uint32_t displayId) c
|
||||||
switch (raceId) {
|
switch (raceId) {
|
||||||
case 1: raceName = "Human"; result = (sexId == 0) ? audio::VoiceType::HUMAN_MALE : audio::VoiceType::HUMAN_FEMALE; break;
|
case 1: raceName = "Human"; result = (sexId == 0) ? audio::VoiceType::HUMAN_MALE : audio::VoiceType::HUMAN_FEMALE; break;
|
||||||
case 2: raceName = "Orc"; result = (sexId == 0) ? audio::VoiceType::ORC_MALE : audio::VoiceType::ORC_FEMALE; break;
|
case 2: raceName = "Orc"; result = (sexId == 0) ? audio::VoiceType::ORC_MALE : audio::VoiceType::ORC_FEMALE; break;
|
||||||
case 3: raceName = "Dwarf"; result = (sexId == 0) ? audio::VoiceType::DWARF_MALE : audio::VoiceType::GENERIC; break;
|
case 3: raceName = "Dwarf"; result = (sexId == 0) ? audio::VoiceType::DWARF_MALE : audio::VoiceType::DWARF_FEMALE; break;
|
||||||
case 4: raceName = "NightElf"; result = (sexId == 0) ? audio::VoiceType::NIGHTELF_MALE : audio::VoiceType::NIGHTELF_FEMALE; break;
|
case 4: raceName = "NightElf"; result = (sexId == 0) ? audio::VoiceType::NIGHTELF_MALE : audio::VoiceType::NIGHTELF_FEMALE; break;
|
||||||
case 5: raceName = "Undead"; result = (sexId == 0) ? audio::VoiceType::UNDEAD_MALE : audio::VoiceType::UNDEAD_FEMALE; break;
|
case 5: raceName = "Undead"; result = (sexId == 0) ? audio::VoiceType::UNDEAD_MALE : audio::VoiceType::UNDEAD_FEMALE; break;
|
||||||
case 6: raceName = "Tauren"; result = (sexId == 0) ? audio::VoiceType::TAUREN_MALE : audio::VoiceType::TAUREN_FEMALE; break;
|
case 6: raceName = "Tauren"; result = (sexId == 0) ? audio::VoiceType::TAUREN_MALE : audio::VoiceType::TAUREN_FEMALE; break;
|
||||||
|
|
@ -4809,11 +4891,9 @@ void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x
|
||||||
|
|
||||||
// Check model cache - reuse if same displayId was already loaded
|
// Check model cache - reuse if same displayId was already loaded
|
||||||
uint32_t modelId = 0;
|
uint32_t modelId = 0;
|
||||||
bool modelCached = false;
|
|
||||||
auto cacheIt = displayIdModelCache_.find(displayId);
|
auto cacheIt = displayIdModelCache_.find(displayId);
|
||||||
if (cacheIt != displayIdModelCache_.end()) {
|
if (cacheIt != displayIdModelCache_.end()) {
|
||||||
modelId = cacheIt->second;
|
modelId = cacheIt->second;
|
||||||
modelCached = true;
|
|
||||||
} else {
|
} else {
|
||||||
// Load model from disk (only once per displayId)
|
// Load model from disk (only once per displayId)
|
||||||
modelId = nextCreatureModelId_++;
|
modelId = nextCreatureModelId_++;
|
||||||
|
|
@ -6618,7 +6698,9 @@ void Application::spawnOnlineGameObject(uint64_t guid, uint32_t entry, uint32_t
|
||||||
|
|
||||||
glm::vec3 renderPos = core::coords::canonicalToRender(glm::vec3(x, y, z));
|
glm::vec3 renderPos = core::coords::canonicalToRender(glm::vec3(x, y, z));
|
||||||
const float renderYawWmo = orientation;
|
const float renderYawWmo = orientation;
|
||||||
const float renderYawM2go = orientation + glm::radians(180.0f);
|
// M2 game objects: model default faces +renderX. renderYaw = canonical + 90° = server_yaw
|
||||||
|
// (same offset as creature/character renderer so all M2 models face consistently)
|
||||||
|
const float renderYawM2go = orientation + glm::radians(90.0f);
|
||||||
|
|
||||||
bool loadedAsWmo = false;
|
bool loadedAsWmo = false;
|
||||||
if (isWmo) {
|
if (isWmo) {
|
||||||
|
|
@ -8145,15 +8227,17 @@ void Application::despawnOnlineGameObject(uint64_t guid) {
|
||||||
void Application::loadQuestMarkerModels() {
|
void Application::loadQuestMarkerModels() {
|
||||||
if (!assetManager || !renderer) return;
|
if (!assetManager || !renderer) return;
|
||||||
|
|
||||||
// Quest markers in WoW 3.3.5a are billboard sprites (BLP textures), not M2 models
|
// Quest markers are billboard sprites; the renderer's QuestMarkerRenderer handles
|
||||||
// Load the BLP textures for quest markers
|
// texture loading and pipeline setup during world initialization.
|
||||||
LOG_INFO("Quest markers will be rendered as billboard sprites using BLP textures:");
|
// Calling initialize() here is a no-op if already done; harmless if called early.
|
||||||
LOG_INFO(" - Available: Interface\\GossipFrame\\AvailableQuestIcon.blp");
|
if (auto* qmr = renderer->getQuestMarkerRenderer()) {
|
||||||
LOG_INFO(" - Turn-in: Interface\\GossipFrame\\ActiveQuestIcon.blp");
|
if (auto* vkCtx = renderer->getVkContext()) {
|
||||||
LOG_INFO(" - Incomplete: Interface\\GossipFrame\\IncompleteQuestIcon.blp");
|
VkDescriptorSetLayout pfl = renderer->getPerFrameSetLayout();
|
||||||
|
if (pfl != VK_NULL_HANDLE) {
|
||||||
// TODO: Implement billboard sprite rendering for quest markers
|
qmr->initialize(vkCtx, pfl, assetManager.get());
|
||||||
// For now, the 2D ImGui markers will continue to work
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::updateQuestMarkers() {
|
void Application::updateQuestMarkers() {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -4,11 +4,14 @@ namespace wowee {
|
||||||
namespace game {
|
namespace game {
|
||||||
|
|
||||||
void World::update([[maybe_unused]] float deltaTime) {
|
void World::update([[maybe_unused]] float deltaTime) {
|
||||||
// TODO: Update world state
|
// World state updates are handled by Application (terrain streaming, entity sync,
|
||||||
|
// camera, etc.) and GameHandler (server packet processing). World is a thin
|
||||||
|
// ownership token; per-frame logic lives in those subsystems.
|
||||||
}
|
}
|
||||||
|
|
||||||
void World::loadMap([[maybe_unused]] uint32_t mapId) {
|
void World::loadMap([[maybe_unused]] uint32_t mapId) {
|
||||||
// TODO: Load map data
|
// Terrain loading is driven by Application::loadOnlineWorld() via TerrainManager.
|
||||||
|
// This method exists as an extension point; no action needed here.
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace game
|
} // namespace game
|
||||||
|
|
|
||||||
|
|
@ -2083,6 +2083,12 @@ network::Packet ReadyCheckConfirmPacket::build(bool ready) {
|
||||||
// Duel
|
// Duel
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
|
network::Packet DuelAcceptPacket::build() {
|
||||||
|
network::Packet packet(wireOpcode(Opcode::CMSG_DUEL_ACCEPTED));
|
||||||
|
LOG_DEBUG("Built CMSG_DUEL_ACCEPTED");
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
network::Packet DuelCancelPacket::build() {
|
network::Packet DuelCancelPacket::build() {
|
||||||
network::Packet packet(wireOpcode(Opcode::CMSG_DUEL_CANCELLED));
|
network::Packet packet(wireOpcode(Opcode::CMSG_DUEL_CANCELLED));
|
||||||
LOG_DEBUG("Built CMSG_DUEL_CANCELLED");
|
LOG_DEBUG("Built CMSG_DUEL_CANCELLED");
|
||||||
|
|
@ -2125,14 +2131,31 @@ network::Packet RequestRaidInfoPacket::build() {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
network::Packet DuelProposedPacket::build(uint64_t targetGuid) {
|
network::Packet DuelProposedPacket::build(uint64_t targetGuid) {
|
||||||
// TODO: Duels are initiated via CMSG_CAST_SPELL with spell 7266,
|
// Duels are initiated via CMSG_CAST_SPELL with spell 7266 (Duel) targeted at the opponent.
|
||||||
// not a dedicated CMSG_DUEL_PROPOSED opcode (which doesn't exist in WoW).
|
// There is no separate CMSG_DUEL_PROPOSED opcode in WoW.
|
||||||
// For now, build a cast spell packet targeting the opponent.
|
|
||||||
auto packet = CastSpellPacket::build(7266, targetGuid, 0);
|
auto packet = CastSpellPacket::build(7266, targetGuid, 0);
|
||||||
LOG_DEBUG("Built duel request (spell 7266) for target: 0x", std::hex, targetGuid, std::dec);
|
LOG_DEBUG("Built duel request (spell 7266) for target: 0x", std::hex, targetGuid, std::dec);
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
network::Packet BeginTradePacket::build() {
|
||||||
|
network::Packet packet(wireOpcode(Opcode::CMSG_BEGIN_TRADE));
|
||||||
|
LOG_DEBUG("Built CMSG_BEGIN_TRADE");
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
network::Packet CancelTradePacket::build() {
|
||||||
|
network::Packet packet(wireOpcode(Opcode::CMSG_CANCEL_TRADE));
|
||||||
|
LOG_DEBUG("Built CMSG_CANCEL_TRADE");
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
network::Packet AcceptTradePacket::build() {
|
||||||
|
network::Packet packet(wireOpcode(Opcode::CMSG_ACCEPT_TRADE));
|
||||||
|
LOG_DEBUG("Built CMSG_ACCEPT_TRADE");
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
network::Packet InitiateTradePacket::build(uint64_t targetGuid) {
|
network::Packet InitiateTradePacket::build(uint64_t targetGuid) {
|
||||||
network::Packet packet(wireOpcode(Opcode::CMSG_INITIATE_TRADE));
|
network::Packet packet(wireOpcode(Opcode::CMSG_INITIATE_TRADE));
|
||||||
packet.writeUInt64(targetGuid);
|
packet.writeUInt64(targetGuid);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#include "game/zone_manager.hpp"
|
#include "game/zone_manager.hpp"
|
||||||
|
#include "pipeline/asset_manager.hpp"
|
||||||
#include "core/logger.hpp"
|
#include "core/logger.hpp"
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
|
@ -479,5 +480,88 @@ std::vector<std::string> ZoneManager::getAllMusicPaths() const {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ZoneManager::enrichFromDBC(pipeline::AssetManager* assets) {
|
||||||
|
if (!assets) return;
|
||||||
|
|
||||||
|
auto areaDbc = assets->loadDBC("AreaTable.dbc");
|
||||||
|
auto zoneMusicDbc = assets->loadDBC("ZoneMusic.dbc");
|
||||||
|
auto soundDbc = assets->loadDBC("SoundEntries.dbc");
|
||||||
|
|
||||||
|
if (!areaDbc || !areaDbc->isLoaded()) {
|
||||||
|
LOG_WARNING("ZoneManager::enrichFromDBC: AreaTable.dbc not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!zoneMusicDbc || !zoneMusicDbc->isLoaded()) {
|
||||||
|
LOG_WARNING("ZoneManager::enrichFromDBC: ZoneMusic.dbc not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!soundDbc || !soundDbc->isLoaded()) {
|
||||||
|
LOG_WARNING("ZoneManager::enrichFromDBC: SoundEntries.dbc not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build MPQ paths from a SoundEntries record.
|
||||||
|
// Fields 3-12 = File[0..9], field 23 = DirectoryBase.
|
||||||
|
auto getSoundPaths = [&](uint32_t soundId) -> std::vector<std::string> {
|
||||||
|
if (soundId == 0) return {};
|
||||||
|
int32_t idx = soundDbc->findRecordById(soundId);
|
||||||
|
if (idx < 0) return {};
|
||||||
|
uint32_t row = static_cast<uint32_t>(idx);
|
||||||
|
if (soundDbc->getFieldCount() < 24) return {};
|
||||||
|
std::string dir = soundDbc->getString(row, 23);
|
||||||
|
std::vector<std::string> paths;
|
||||||
|
for (uint32_t f = 3; f <= 12; ++f) {
|
||||||
|
std::string name = soundDbc->getString(row, f);
|
||||||
|
if (name.empty()) continue;
|
||||||
|
paths.push_back(dir.empty() ? name : dir + "\\" + name);
|
||||||
|
}
|
||||||
|
return paths;
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint32_t numAreas = areaDbc->getRecordCount();
|
||||||
|
const uint32_t areaFields = areaDbc->getFieldCount();
|
||||||
|
if (areaFields < 9) {
|
||||||
|
LOG_WARNING("ZoneManager::enrichFromDBC: AreaTable.dbc has too few fields (", areaFields, ")");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t zonesEnriched = 0;
|
||||||
|
for (uint32_t i = 0; i < numAreas; ++i) {
|
||||||
|
uint32_t zoneId = areaDbc->getUInt32(i, 0);
|
||||||
|
uint32_t zoneMusicId = areaDbc->getUInt32(i, 8);
|
||||||
|
if (zoneId == 0 || zoneMusicId == 0) continue;
|
||||||
|
|
||||||
|
int32_t zmIdx = zoneMusicDbc->findRecordById(zoneMusicId);
|
||||||
|
if (zmIdx < 0) continue;
|
||||||
|
uint32_t zmRow = static_cast<uint32_t>(zmIdx);
|
||||||
|
if (zoneMusicDbc->getFieldCount() < 8) continue;
|
||||||
|
|
||||||
|
uint32_t daySoundId = zoneMusicDbc->getUInt32(zmRow, 6);
|
||||||
|
uint32_t nightSoundId = zoneMusicDbc->getUInt32(zmRow, 7);
|
||||||
|
|
||||||
|
std::vector<std::string> newPaths;
|
||||||
|
for (const auto& p : getSoundPaths(daySoundId)) newPaths.push_back(p);
|
||||||
|
for (const auto& p : getSoundPaths(nightSoundId)) newPaths.push_back(p);
|
||||||
|
if (newPaths.empty()) continue;
|
||||||
|
|
||||||
|
auto& zone = zones[zoneId];
|
||||||
|
if (zone.id == 0) zone.id = zoneId;
|
||||||
|
|
||||||
|
// Append paths not already present (preserve hardcoded entries).
|
||||||
|
for (const auto& path : newPaths) {
|
||||||
|
bool found = false;
|
||||||
|
for (const auto& existing : zone.musicPaths) {
|
||||||
|
if (existing == path) { found = true; break; }
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
zone.musicPaths.push_back(path);
|
||||||
|
++zonesEnriched;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("Zone music enriched from DBC: ", zones.size(), " zones, ", zonesEnriched, " paths added");
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace game
|
} // namespace game
|
||||||
} // namespace wowee
|
} // namespace wowee
|
||||||
|
|
|
||||||
|
|
@ -110,8 +110,34 @@ bool MPQManager::initialize(const std::string& dataPath_) {
|
||||||
// Load patch archives (highest priority)
|
// Load patch archives (highest priority)
|
||||||
loadPatchArchives();
|
loadPatchArchives();
|
||||||
|
|
||||||
// Load locale archives
|
// Load locale archives — auto-detect from available locale directories
|
||||||
loadLocaleArchives("enUS"); // TODO: Make configurable
|
{
|
||||||
|
// Prefer the locale override from environment, then scan for installed ones
|
||||||
|
const char* localeEnv = std::getenv("WOWEE_LOCALE");
|
||||||
|
std::string detectedLocale;
|
||||||
|
if (localeEnv && localeEnv[0] != '\0') {
|
||||||
|
detectedLocale = localeEnv;
|
||||||
|
LOG_INFO("Using locale from WOWEE_LOCALE env: ", detectedLocale);
|
||||||
|
} else {
|
||||||
|
// Priority order: enUS first, then other common locales
|
||||||
|
static const std::array<const char*, 12> knownLocales = {
|
||||||
|
"enUS", "enGB", "deDE", "frFR", "esES", "esMX",
|
||||||
|
"zhCN", "zhTW", "koKR", "ruRU", "ptBR", "itIT"
|
||||||
|
};
|
||||||
|
for (const char* loc : knownLocales) {
|
||||||
|
if (std::filesystem::exists(dataPath + "/" + loc)) {
|
||||||
|
detectedLocale = loc;
|
||||||
|
LOG_INFO("Auto-detected WoW locale: ", detectedLocale);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (detectedLocale.empty()) {
|
||||||
|
detectedLocale = "enUS";
|
||||||
|
LOG_WARNING("No locale directory found in data path; defaulting to enUS");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadLocaleArchives(detectedLocale);
|
||||||
|
}
|
||||||
|
|
||||||
if (archives.empty()) {
|
if (archives.empty()) {
|
||||||
LOG_WARNING("No MPQ archives loaded - will use loose file fallback");
|
LOG_WARNING("No MPQ archives loaded - will use loose file fallback");
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,11 @@
|
||||||
|
|
||||||
#if WOWEE_HAS_AMD_FSR3_FRAMEGEN
|
#if WOWEE_HAS_AMD_FSR3_FRAMEGEN
|
||||||
#include "third_party/ffx_fsr3_legacy_compat.h"
|
#include "third_party/ffx_fsr3_legacy_compat.h"
|
||||||
|
#include <ffx_api.h>
|
||||||
|
#include <ffx_framegeneration.h>
|
||||||
|
#include <ffx_upscale.h>
|
||||||
#include <ffx_vk.h>
|
#include <ffx_vk.h>
|
||||||
|
#include <vk/ffx_api_vk.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace wowee::rendering {
|
namespace wowee::rendering {
|
||||||
|
|
@ -34,6 +38,10 @@ struct AmdFsr3Runtime::RuntimeFns {
|
||||||
decltype(&ffxFsr3ConfigureFrameGeneration) fsr3ConfigureFrameGeneration = nullptr;
|
decltype(&ffxFsr3ConfigureFrameGeneration) fsr3ConfigureFrameGeneration = nullptr;
|
||||||
decltype(&ffxFsr3DispatchFrameGeneration) fsr3DispatchFrameGeneration = nullptr;
|
decltype(&ffxFsr3DispatchFrameGeneration) fsr3DispatchFrameGeneration = nullptr;
|
||||||
decltype(&ffxFsr3ContextDestroy) fsr3ContextDestroy = nullptr;
|
decltype(&ffxFsr3ContextDestroy) fsr3ContextDestroy = nullptr;
|
||||||
|
PfnFfxCreateContext createContext = nullptr;
|
||||||
|
PfnFfxDestroyContext destroyContext = nullptr;
|
||||||
|
PfnFfxConfigure configure = nullptr;
|
||||||
|
PfnFfxDispatch dispatch = nullptr;
|
||||||
};
|
};
|
||||||
#else
|
#else
|
||||||
struct AmdFsr3Runtime::RuntimeFns {};
|
struct AmdFsr3Runtime::RuntimeFns {};
|
||||||
|
|
@ -51,6 +59,43 @@ FfxErrorCode vkSwapchainConfigureNoop(const FfxFrameGenerationConfig*) {
|
||||||
return FFX_OK;
|
return FFX_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string narrowWString(const wchar_t* msg) {
|
||||||
|
if (!msg) return {};
|
||||||
|
std::string out;
|
||||||
|
for (const wchar_t* p = msg; *p; ++p) {
|
||||||
|
const wchar_t wc = *p;
|
||||||
|
if (wc >= 0 && wc <= 0x7f) {
|
||||||
|
out.push_back(static_cast<char>(wc));
|
||||||
|
} else {
|
||||||
|
out.push_back('?');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ffxApiLogMessage(uint32_t type, const wchar_t* message) {
|
||||||
|
const std::string narrowed = narrowWString(message);
|
||||||
|
if (type == FFX_API_MESSAGE_TYPE_ERROR) {
|
||||||
|
LOG_ERROR("FSR3 runtime/API: ", narrowed);
|
||||||
|
} else {
|
||||||
|
LOG_WARNING("FSR3 runtime/API: ", narrowed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* ffxApiReturnCodeName(ffxReturnCode_t rc) {
|
||||||
|
switch (rc) {
|
||||||
|
case FFX_API_RETURN_OK: return "OK";
|
||||||
|
case FFX_API_RETURN_ERROR: return "ERROR";
|
||||||
|
case FFX_API_RETURN_ERROR_UNKNOWN_DESCTYPE: return "ERROR_UNKNOWN_DESCTYPE";
|
||||||
|
case FFX_API_RETURN_ERROR_RUNTIME_ERROR: return "ERROR_RUNTIME_ERROR";
|
||||||
|
case FFX_API_RETURN_NO_PROVIDER: return "NO_PROVIDER";
|
||||||
|
case FFX_API_RETURN_ERROR_MEMORY: return "ERROR_MEMORY";
|
||||||
|
case FFX_API_RETURN_ERROR_PARAMETER: return "ERROR_PARAMETER";
|
||||||
|
case FFX_API_RETURN_PROVIDER_NO_SUPPORT_NEW_DESCTYPE: return "PROVIDER_NO_SUPPORT_NEW_DESCTYPE";
|
||||||
|
default: return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T, typename = void>
|
template <typename T, typename = void>
|
||||||
struct HasUpscaleOutputSize : std::false_type {};
|
struct HasUpscaleOutputSize : std::false_type {};
|
||||||
|
|
||||||
|
|
@ -141,6 +186,7 @@ FfxResourceDescription makeResourceDescription(VkFormat format,
|
||||||
description.usage = usage;
|
description.usage = usage;
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -184,23 +230,36 @@ bool AmdFsr3Runtime::initialize(const AmdFsr3RuntimeInitDesc& desc) {
|
||||||
candidates.emplace_back("libffx_fsr3.so");
|
candidates.emplace_back("libffx_fsr3.so");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
std::string lastDlopenError;
|
||||||
for (const std::string& path : candidates) {
|
for (const std::string& path : candidates) {
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
HMODULE h = LoadLibraryA(path.c_str());
|
HMODULE h = LoadLibraryA(path.c_str());
|
||||||
if (!h) continue;
|
if (!h) continue;
|
||||||
libHandle_ = reinterpret_cast<void*>(h);
|
libHandle_ = reinterpret_cast<void*>(h);
|
||||||
#else
|
#else
|
||||||
|
dlerror();
|
||||||
void* h = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL);
|
void* h = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL);
|
||||||
if (!h) continue;
|
if (!h) {
|
||||||
|
const char* err = dlerror();
|
||||||
|
if (err && *err) lastDlopenError = err;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
libHandle_ = h;
|
libHandle_ = h;
|
||||||
#endif
|
#endif
|
||||||
loadedLibraryPath_ = path;
|
loadedLibraryPath_ = path;
|
||||||
loadPathKind_ = LoadPathKind::Official;
|
loadPathKind_ = LoadPathKind::Official;
|
||||||
|
LOG_INFO("FSR3 runtime: opened library candidate ", loadedLibraryPath_);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!libHandle_) {
|
if (!libHandle_) {
|
||||||
lastError_ = "no official runtime (Path A) found";
|
lastError_ = "no official runtime (Path A) found";
|
||||||
|
#if !defined(_WIN32)
|
||||||
|
if (!lastDlopenError.empty()) {
|
||||||
|
lastError_ += " dlopen error: ";
|
||||||
|
lastError_ += lastDlopenError;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -223,16 +282,127 @@ bool AmdFsr3Runtime::initialize(const AmdFsr3RuntimeInitDesc& desc) {
|
||||||
fns_->fsr3ConfigureFrameGeneration = reinterpret_cast<decltype(fns_->fsr3ConfigureFrameGeneration)>(resolveSym("ffxFsr3ConfigureFrameGeneration"));
|
fns_->fsr3ConfigureFrameGeneration = reinterpret_cast<decltype(fns_->fsr3ConfigureFrameGeneration)>(resolveSym("ffxFsr3ConfigureFrameGeneration"));
|
||||||
fns_->fsr3DispatchFrameGeneration = reinterpret_cast<decltype(fns_->fsr3DispatchFrameGeneration)>(resolveSym("ffxFsr3DispatchFrameGeneration"));
|
fns_->fsr3DispatchFrameGeneration = reinterpret_cast<decltype(fns_->fsr3DispatchFrameGeneration)>(resolveSym("ffxFsr3DispatchFrameGeneration"));
|
||||||
fns_->fsr3ContextDestroy = reinterpret_cast<decltype(fns_->fsr3ContextDestroy)>(resolveSym("ffxFsr3ContextDestroy"));
|
fns_->fsr3ContextDestroy = reinterpret_cast<decltype(fns_->fsr3ContextDestroy)>(resolveSym("ffxFsr3ContextDestroy"));
|
||||||
|
fns_->createContext = reinterpret_cast<decltype(fns_->createContext)>(resolveSym("ffxCreateContext"));
|
||||||
|
fns_->destroyContext = reinterpret_cast<decltype(fns_->destroyContext)>(resolveSym("ffxDestroyContext"));
|
||||||
|
fns_->configure = reinterpret_cast<decltype(fns_->configure)>(resolveSym("ffxConfigure"));
|
||||||
|
fns_->dispatch = reinterpret_cast<decltype(fns_->dispatch)>(resolveSym("ffxDispatch"));
|
||||||
|
|
||||||
if (!fns_->getScratchMemorySizeVK || !fns_->getDeviceVK || !fns_->getInterfaceVK ||
|
const bool hasLegacyApi = (fns_->getScratchMemorySizeVK && fns_->getDeviceVK && fns_->getInterfaceVK &&
|
||||||
!fns_->getCommandListVK || !fns_->getResourceVK || !fns_->fsr3ContextCreate || !fns_->fsr3ContextDispatchUpscale ||
|
fns_->getCommandListVK && fns_->getResourceVK && fns_->fsr3ContextCreate &&
|
||||||
!fns_->fsr3ContextDestroy) {
|
fns_->fsr3ContextDispatchUpscale && fns_->fsr3ContextDestroy);
|
||||||
LOG_WARNING("FSR3 runtime: required symbols not found in ", loadedLibraryPath_);
|
const bool hasGenericApi = (fns_->createContext && fns_->destroyContext && fns_->configure && fns_->dispatch);
|
||||||
lastError_ = "missing required Vulkan FSR3 symbols in runtime library";
|
|
||||||
|
if (!hasLegacyApi && !hasGenericApi) {
|
||||||
|
LOG_WARNING("FSR3 runtime: required symbols not found in ", loadedLibraryPath_,
|
||||||
|
" (need legacy ffxFsr3* or generic ffxCreateContext/ffxDispatch)");
|
||||||
|
lastError_ = "missing required Vulkan FSR3 symbols in runtime library (legacy and generic APIs unavailable)";
|
||||||
shutdown();
|
shutdown();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apiMode_ = hasLegacyApi ? ApiMode::LegacyFsr3 : ApiMode::GenericApi;
|
||||||
|
if (apiMode_ == ApiMode::GenericApi) {
|
||||||
|
ffxConfigureDescGlobalDebug1 globalDebug{};
|
||||||
|
globalDebug.header.type = FFX_API_CONFIGURE_DESC_TYPE_GLOBALDEBUG1;
|
||||||
|
globalDebug.header.pNext = nullptr;
|
||||||
|
globalDebug.fpMessage = &ffxApiLogMessage;
|
||||||
|
globalDebug.debugLevel = FFX_API_CONFIGURE_GLOBALDEBUG_LEVEL_VERBOSE;
|
||||||
|
(void)fns_->configure(nullptr, reinterpret_cast<ffxConfigureDescHeader*>(&globalDebug));
|
||||||
|
|
||||||
|
ffxCreateBackendVKDesc backendDesc{};
|
||||||
|
backendDesc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_VK;
|
||||||
|
backendDesc.header.pNext = nullptr;
|
||||||
|
backendDesc.vkDevice = desc.device;
|
||||||
|
backendDesc.vkPhysicalDevice = desc.physicalDevice;
|
||||||
|
|
||||||
|
ffxCreateContextDescUpscaleVersion upVerDesc{};
|
||||||
|
upVerDesc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE_VERSION;
|
||||||
|
upVerDesc.header.pNext = nullptr;
|
||||||
|
upVerDesc.version = FFX_UPSCALER_VERSION;
|
||||||
|
|
||||||
|
ffxCreateContextDescUpscale upDesc{};
|
||||||
|
upDesc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE;
|
||||||
|
upDesc.header.pNext = reinterpret_cast<ffxApiHeader*>(&backendDesc);
|
||||||
|
upDesc.flags = FFX_UPSCALE_ENABLE_AUTO_EXPOSURE | FFX_UPSCALE_ENABLE_MOTION_VECTORS_JITTER_CANCELLATION;
|
||||||
|
upDesc.flags |= FFX_UPSCALE_ENABLE_DEBUG_CHECKING;
|
||||||
|
if (desc.hdrInput) upDesc.flags |= FFX_UPSCALE_ENABLE_HIGH_DYNAMIC_RANGE;
|
||||||
|
if (desc.depthInverted) upDesc.flags |= FFX_UPSCALE_ENABLE_DEPTH_INVERTED;
|
||||||
|
upDesc.maxRenderSize.width = desc.maxRenderWidth;
|
||||||
|
upDesc.maxRenderSize.height = desc.maxRenderHeight;
|
||||||
|
upDesc.maxUpscaleSize.width = desc.displayWidth;
|
||||||
|
upDesc.maxUpscaleSize.height = desc.displayHeight;
|
||||||
|
upDesc.fpMessage = &ffxApiLogMessage;
|
||||||
|
backendDesc.header.pNext = reinterpret_cast<ffxApiHeader*>(&upVerDesc);
|
||||||
|
|
||||||
|
ffxContext upscaleCtx = nullptr;
|
||||||
|
const ffxReturnCode_t upCreateRc =
|
||||||
|
fns_->createContext(&upscaleCtx, reinterpret_cast<ffxCreateContextDescHeader*>(&upDesc), nullptr);
|
||||||
|
if (upCreateRc != FFX_API_RETURN_OK) {
|
||||||
|
const std::string loadedPath = loadedLibraryPath_;
|
||||||
|
lastError_ = "ffxCreateContext (upscale) failed rc=" + std::to_string(upCreateRc) +
|
||||||
|
" (" + ffxApiReturnCodeName(upCreateRc) + "), runtimeLib=" + loadedPath;
|
||||||
|
shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
genericUpscaleContext_ = upscaleCtx;
|
||||||
|
backendDesc.header.pNext = nullptr;
|
||||||
|
|
||||||
|
if (desc.enableFrameGeneration) {
|
||||||
|
ffxCreateContextDescFrameGenerationVersion fgVerDesc{};
|
||||||
|
fgVerDesc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_FRAMEGENERATION_VERSION;
|
||||||
|
fgVerDesc.header.pNext = nullptr;
|
||||||
|
fgVerDesc.version = FFX_FRAMEGENERATION_VERSION;
|
||||||
|
|
||||||
|
ffxCreateContextDescFrameGeneration fgDesc{};
|
||||||
|
fgDesc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_FRAMEGENERATION;
|
||||||
|
fgDesc.header.pNext = reinterpret_cast<ffxApiHeader*>(&backendDesc);
|
||||||
|
fgDesc.flags = FFX_FRAMEGENERATION_ENABLE_MOTION_VECTORS_JITTER_CANCELLATION;
|
||||||
|
fgDesc.flags |= FFX_FRAMEGENERATION_ENABLE_DEBUG_CHECKING;
|
||||||
|
if (desc.hdrInput) fgDesc.flags |= FFX_FRAMEGENERATION_ENABLE_HIGH_DYNAMIC_RANGE;
|
||||||
|
if (desc.depthInverted) fgDesc.flags |= FFX_FRAMEGENERATION_ENABLE_DEPTH_INVERTED;
|
||||||
|
fgDesc.displaySize.width = desc.displayWidth;
|
||||||
|
fgDesc.displaySize.height = desc.displayHeight;
|
||||||
|
fgDesc.maxRenderSize.width = desc.maxRenderWidth;
|
||||||
|
fgDesc.maxRenderSize.height = desc.maxRenderHeight;
|
||||||
|
fgDesc.backBufferFormat = ffxApiGetSurfaceFormatVK(desc.colorFormat);
|
||||||
|
backendDesc.header.pNext = reinterpret_cast<ffxApiHeader*>(&fgVerDesc);
|
||||||
|
|
||||||
|
ffxContext fgCtx = nullptr;
|
||||||
|
const ffxReturnCode_t fgCreateRc =
|
||||||
|
fns_->createContext(&fgCtx, reinterpret_cast<ffxCreateContextDescHeader*>(&fgDesc), nullptr);
|
||||||
|
if (fgCreateRc != FFX_API_RETURN_OK) {
|
||||||
|
const std::string loadedPath = loadedLibraryPath_;
|
||||||
|
lastError_ = "ffxCreateContext (framegeneration) failed rc=" + std::to_string(fgCreateRc) +
|
||||||
|
" (" + ffxApiReturnCodeName(fgCreateRc) + "), runtimeLib=" + loadedPath;
|
||||||
|
shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
genericFramegenContext_ = fgCtx;
|
||||||
|
backendDesc.header.pNext = nullptr;
|
||||||
|
|
||||||
|
ffxConfigureDescFrameGeneration fgCfg{};
|
||||||
|
fgCfg.header.type = FFX_API_CONFIGURE_DESC_TYPE_FRAMEGENERATION;
|
||||||
|
fgCfg.header.pNext = nullptr;
|
||||||
|
fgCfg.frameGenerationEnabled = true;
|
||||||
|
fgCfg.allowAsyncWorkloads = false;
|
||||||
|
fgCfg.flags = FFX_FRAMEGENERATION_FLAG_NO_SWAPCHAIN_CONTEXT_NOTIFY;
|
||||||
|
fgCfg.onlyPresentGenerated = false;
|
||||||
|
fgCfg.frameID = genericFrameId_;
|
||||||
|
if (fns_->configure(reinterpret_cast<ffxContext*>(&genericFramegenContext_),
|
||||||
|
reinterpret_cast<ffxConfigureDescHeader*>(&fgCfg)) != FFX_API_RETURN_OK) {
|
||||||
|
lastError_ = "ffxConfigure (framegeneration) failed";
|
||||||
|
shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
frameGenerationReady_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ready_ = true;
|
||||||
|
LOG_INFO("FSR3 runtime: loaded generic API from ", loadedLibraryPath_,
|
||||||
|
" framegenReady=", frameGenerationReady_ ? "yes" : "no");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
scratchBufferSize_ = fns_->getScratchMemorySizeVK(FFX_FSR3_CONTEXT_COUNT);
|
scratchBufferSize_ = fns_->getScratchMemorySizeVK(FFX_FSR3_CONTEXT_COUNT);
|
||||||
if (scratchBufferSize_ == 0) {
|
if (scratchBufferSize_ == 0) {
|
||||||
LOG_WARNING("FSR3 runtime: scratch buffer size query returned 0.");
|
LOG_WARNING("FSR3 runtime: scratch buffer size query returned 0.");
|
||||||
|
|
@ -344,6 +514,49 @@ bool AmdFsr3Runtime::dispatchUpscale(const AmdFsr3RuntimeDispatchDesc& desc) {
|
||||||
lastError_ = "invalid upscale dispatch resources";
|
lastError_ = "invalid upscale dispatch resources";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (apiMode_ == ApiMode::GenericApi) {
|
||||||
|
if (!genericUpscaleContext_ || !fns_->dispatch) {
|
||||||
|
lastError_ = "generic API upscale context unavailable";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ffxDispatchDescUpscale up{};
|
||||||
|
up.header.type = FFX_API_DISPATCH_DESC_TYPE_UPSCALE;
|
||||||
|
up.header.pNext = nullptr;
|
||||||
|
up.commandList = reinterpret_cast<void*>(desc.commandBuffer);
|
||||||
|
up.color = ffxApiGetResourceVK(desc.colorImage, desc.colorFormat, desc.renderWidth, desc.renderHeight, FFX_API_RESOURCE_STATE_COMPUTE_READ);
|
||||||
|
up.depth = ffxApiGetResourceVK(desc.depthImage, desc.depthFormat, desc.renderWidth, desc.renderHeight, FFX_API_RESOURCE_STATE_COMPUTE_READ);
|
||||||
|
up.motionVectors = ffxApiGetResourceVK(desc.motionVectorImage, desc.motionVectorFormat, desc.renderWidth, desc.renderHeight, FFX_API_RESOURCE_STATE_COMPUTE_READ);
|
||||||
|
up.exposure = FfxApiResource{};
|
||||||
|
up.reactive = FfxApiResource{};
|
||||||
|
up.transparencyAndComposition = FfxApiResource{};
|
||||||
|
up.output = ffxApiGetResourceVK(desc.outputImage, desc.outputFormat, desc.outputWidth, desc.outputHeight, FFX_API_RESOURCE_STATE_UNORDERED_ACCESS);
|
||||||
|
up.jitterOffset.x = desc.jitterX;
|
||||||
|
up.jitterOffset.y = desc.jitterY;
|
||||||
|
up.motionVectorScale.x = desc.motionScaleX;
|
||||||
|
up.motionVectorScale.y = desc.motionScaleY;
|
||||||
|
up.renderSize.width = desc.renderWidth;
|
||||||
|
up.renderSize.height = desc.renderHeight;
|
||||||
|
up.upscaleSize.width = desc.outputWidth;
|
||||||
|
up.upscaleSize.height = desc.outputHeight;
|
||||||
|
up.enableSharpening = false;
|
||||||
|
up.sharpness = 0.0f;
|
||||||
|
up.frameTimeDelta = std::max(0.001f, desc.frameTimeDeltaMs);
|
||||||
|
up.preExposure = 1.0f;
|
||||||
|
up.reset = desc.reset;
|
||||||
|
up.cameraNear = desc.cameraNear;
|
||||||
|
up.cameraFar = desc.cameraFar;
|
||||||
|
up.cameraFovAngleVertical = desc.cameraFovYRadians;
|
||||||
|
up.viewSpaceToMetersFactor = 1.0f;
|
||||||
|
up.flags = 0;
|
||||||
|
if (fns_->dispatch(reinterpret_cast<ffxContext*>(&genericUpscaleContext_),
|
||||||
|
reinterpret_cast<ffxDispatchDescHeader*>(&up)) != FFX_API_RETURN_OK) {
|
||||||
|
lastError_ = "ffxDispatch (upscale) failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
lastError_.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!contextStorage_ || !fns_->fsr3ContextDispatchUpscale) {
|
if (!contextStorage_ || !fns_->fsr3ContextDispatchUpscale) {
|
||||||
lastError_ = "official runtime upscale context unavailable";
|
lastError_ = "official runtime upscale context unavailable";
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -413,6 +626,67 @@ bool AmdFsr3Runtime::dispatchFrameGeneration(const AmdFsr3RuntimeDispatchDesc& d
|
||||||
lastError_ = "invalid frame generation dispatch resources";
|
lastError_ = "invalid frame generation dispatch resources";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (apiMode_ == ApiMode::GenericApi) {
|
||||||
|
if (!genericFramegenContext_ || !fns_->dispatch) {
|
||||||
|
lastError_ = "generic API frame generation context unavailable";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ffxDispatchDescFrameGenerationPrepareV2 prep{};
|
||||||
|
prep.header.type = FFX_API_DISPATCH_DESC_TYPE_FRAMEGENERATION_PREPARE_V2;
|
||||||
|
prep.header.pNext = nullptr;
|
||||||
|
prep.frameID = genericFrameId_;
|
||||||
|
prep.flags = 0;
|
||||||
|
prep.commandList = reinterpret_cast<void*>(desc.commandBuffer);
|
||||||
|
prep.renderSize.width = desc.renderWidth;
|
||||||
|
prep.renderSize.height = desc.renderHeight;
|
||||||
|
prep.jitterOffset.x = desc.jitterX;
|
||||||
|
prep.jitterOffset.y = desc.jitterY;
|
||||||
|
prep.motionVectorScale.x = desc.motionScaleX;
|
||||||
|
prep.motionVectorScale.y = desc.motionScaleY;
|
||||||
|
prep.frameTimeDelta = std::max(0.001f, desc.frameTimeDeltaMs);
|
||||||
|
prep.reset = desc.reset;
|
||||||
|
prep.cameraNear = desc.cameraNear;
|
||||||
|
prep.cameraFar = desc.cameraFar;
|
||||||
|
prep.cameraFovAngleVertical = desc.cameraFovYRadians;
|
||||||
|
prep.viewSpaceToMetersFactor = 1.0f;
|
||||||
|
prep.depth = ffxApiGetResourceVK(desc.depthImage, desc.depthFormat, desc.renderWidth, desc.renderHeight, FFX_API_RESOURCE_STATE_COMPUTE_READ);
|
||||||
|
prep.motionVectors = ffxApiGetResourceVK(desc.motionVectorImage, desc.motionVectorFormat, desc.renderWidth, desc.renderHeight, FFX_API_RESOURCE_STATE_COMPUTE_READ);
|
||||||
|
prep.cameraPosition[0] = prep.cameraPosition[1] = prep.cameraPosition[2] = 0.0f;
|
||||||
|
prep.cameraUp[0] = 0.0f; prep.cameraUp[1] = 1.0f; prep.cameraUp[2] = 0.0f;
|
||||||
|
prep.cameraRight[0] = 1.0f; prep.cameraRight[1] = 0.0f; prep.cameraRight[2] = 0.0f;
|
||||||
|
prep.cameraForward[0] = 0.0f; prep.cameraForward[1] = 0.0f; prep.cameraForward[2] = -1.0f;
|
||||||
|
if (fns_->dispatch(reinterpret_cast<ffxContext*>(&genericFramegenContext_),
|
||||||
|
reinterpret_cast<ffxDispatchDescHeader*>(&prep)) != FFX_API_RETURN_OK) {
|
||||||
|
lastError_ = "ffxDispatch (framegeneration prepare) failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ffxDispatchDescFrameGeneration fg{};
|
||||||
|
fg.header.type = FFX_API_DISPATCH_DESC_TYPE_FRAMEGENERATION;
|
||||||
|
fg.header.pNext = nullptr;
|
||||||
|
fg.commandList = reinterpret_cast<void*>(desc.commandBuffer);
|
||||||
|
fg.presentColor = ffxApiGetResourceVK(desc.outputImage, desc.outputFormat, desc.outputWidth, desc.outputHeight, FFX_API_RESOURCE_STATE_COMPUTE_READ);
|
||||||
|
fg.outputs[0] = ffxApiGetResourceVK(desc.frameGenOutputImage, desc.outputFormat, desc.outputWidth, desc.outputHeight, FFX_API_RESOURCE_STATE_UNORDERED_ACCESS);
|
||||||
|
fg.numGeneratedFrames = 1;
|
||||||
|
fg.reset = desc.reset;
|
||||||
|
fg.backbufferTransferFunction = FFX_API_BACKBUFFER_TRANSFER_FUNCTION_SRGB;
|
||||||
|
fg.minMaxLuminance[0] = 0.0f;
|
||||||
|
fg.minMaxLuminance[1] = 1.0f;
|
||||||
|
fg.generationRect.left = 0;
|
||||||
|
fg.generationRect.top = 0;
|
||||||
|
fg.generationRect.width = desc.outputWidth;
|
||||||
|
fg.generationRect.height = desc.outputHeight;
|
||||||
|
fg.frameID = genericFrameId_;
|
||||||
|
if (fns_->dispatch(reinterpret_cast<ffxContext*>(&genericFramegenContext_),
|
||||||
|
reinterpret_cast<ffxDispatchDescHeader*>(&fg)) != FFX_API_RETURN_OK) {
|
||||||
|
lastError_ = "ffxDispatch (framegeneration) failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
++genericFrameId_;
|
||||||
|
lastError_.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!contextStorage_ || !fns_->fsr3DispatchFrameGeneration) {
|
if (!contextStorage_ || !fns_->fsr3DispatchFrameGeneration) {
|
||||||
lastError_ = "official runtime frame generation context unavailable";
|
lastError_ = "official runtime frame generation context unavailable";
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -449,7 +723,18 @@ bool AmdFsr3Runtime::dispatchFrameGeneration(const AmdFsr3RuntimeDispatchDesc& d
|
||||||
|
|
||||||
void AmdFsr3Runtime::shutdown() {
|
void AmdFsr3Runtime::shutdown() {
|
||||||
#if WOWEE_HAS_AMD_FSR3_FRAMEGEN
|
#if WOWEE_HAS_AMD_FSR3_FRAMEGEN
|
||||||
if (contextStorage_ && fns_ && fns_->fsr3ContextDestroy) {
|
if (apiMode_ == ApiMode::GenericApi && fns_ && fns_->destroyContext) {
|
||||||
|
if (genericFramegenContext_) {
|
||||||
|
auto ctx = reinterpret_cast<ffxContext*>(&genericFramegenContext_);
|
||||||
|
fns_->destroyContext(ctx, nullptr);
|
||||||
|
genericFramegenContext_ = nullptr;
|
||||||
|
}
|
||||||
|
if (genericUpscaleContext_) {
|
||||||
|
auto ctx = reinterpret_cast<ffxContext*>(&genericUpscaleContext_);
|
||||||
|
fns_->destroyContext(ctx, nullptr);
|
||||||
|
genericUpscaleContext_ = nullptr;
|
||||||
|
}
|
||||||
|
} else if (contextStorage_ && fns_ && fns_->fsr3ContextDestroy) {
|
||||||
fns_->fsr3ContextDestroy(reinterpret_cast<FfxFsr3Context*>(contextStorage_));
|
fns_->fsr3ContextDestroy(reinterpret_cast<FfxFsr3Context*>(contextStorage_));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -476,6 +761,8 @@ void AmdFsr3Runtime::shutdown() {
|
||||||
libHandle_ = nullptr;
|
libHandle_ = nullptr;
|
||||||
loadedLibraryPath_.clear();
|
loadedLibraryPath_.clear();
|
||||||
loadPathKind_ = LoadPathKind::None;
|
loadPathKind_ = LoadPathKind::None;
|
||||||
|
apiMode_ = ApiMode::LegacyFsr3;
|
||||||
|
genericFrameId_ = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace wowee::rendering
|
} // namespace wowee::rendering
|
||||||
|
|
|
||||||
|
|
@ -751,14 +751,22 @@ void M2Renderer::destroyModelGPU(M2ModelGPU& model) {
|
||||||
|
|
||||||
void M2Renderer::destroyInstanceBones(M2Instance& inst) {
|
void M2Renderer::destroyInstanceBones(M2Instance& inst) {
|
||||||
if (!vkCtx_) return;
|
if (!vkCtx_) return;
|
||||||
|
VkDevice device = vkCtx_->getDevice();
|
||||||
VmaAllocator alloc = vkCtx_->getAllocator();
|
VmaAllocator alloc = vkCtx_->getAllocator();
|
||||||
for (int i = 0; i < 2; i++) {
|
for (int i = 0; i < 2; i++) {
|
||||||
|
// Free bone descriptor set so the pool slot is immediately reusable.
|
||||||
|
// Without this, the pool fills up over a play session as tiles stream
|
||||||
|
// in/out, eventually causing vkAllocateDescriptorSets to fail and
|
||||||
|
// making animated instances invisible (perceived as flickering).
|
||||||
|
if (inst.boneSet[i] != VK_NULL_HANDLE) {
|
||||||
|
vkFreeDescriptorSets(device, boneDescPool_, 1, &inst.boneSet[i]);
|
||||||
|
inst.boneSet[i] = VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
if (inst.boneBuffer[i]) {
|
if (inst.boneBuffer[i]) {
|
||||||
vmaDestroyBuffer(alloc, inst.boneBuffer[i], inst.boneAlloc[i]);
|
vmaDestroyBuffer(alloc, inst.boneBuffer[i], inst.boneAlloc[i]);
|
||||||
inst.boneBuffer[i] = VK_NULL_HANDLE;
|
inst.boneBuffer[i] = VK_NULL_HANDLE;
|
||||||
inst.boneMapped[i] = nullptr;
|
inst.boneMapped[i] = nullptr;
|
||||||
}
|
}
|
||||||
// boneSet freed when pool is reset/destroyed
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -201,7 +201,7 @@ void PerformanceHUD::render(const Renderer* renderer, const Camera* camera) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (renderer->isFSR2Enabled()) {
|
if (renderer->isFSR2Enabled()) {
|
||||||
ImGui::TextColored(ImVec4(0.4f, 0.9f, 1.0f, 1.0f), "FSR 2.2: ON");
|
ImGui::TextColored(ImVec4(0.4f, 0.9f, 1.0f, 1.0f), "FSR 3 Upscale: ON");
|
||||||
ImGui::Text(" JitterSign=%.2f", renderer->getFSR2JitterSign());
|
ImGui::Text(" JitterSign=%.2f", renderer->getFSR2JitterSign());
|
||||||
const bool fgEnabled = renderer->isAmdFsr3FramegenEnabled();
|
const bool fgEnabled = renderer->isAmdFsr3FramegenEnabled();
|
||||||
const bool fgReady = renderer->isAmdFsr3FramegenRuntimeReady();
|
const bool fgReady = renderer->isAmdFsr3FramegenRuntimeReady();
|
||||||
|
|
|
||||||
|
|
@ -706,9 +706,12 @@ bool Renderer::initialize(core::Window* win) {
|
||||||
lightingManager = std::make_unique<LightingManager>();
|
lightingManager = std::make_unique<LightingManager>();
|
||||||
[[maybe_unused]] auto* assetManager = core::Application::getInstance().getAssetManager();
|
[[maybe_unused]] auto* assetManager = core::Application::getInstance().getAssetManager();
|
||||||
|
|
||||||
// Create zone manager
|
// Create zone manager; enrich music paths from DBC if available
|
||||||
zoneManager = std::make_unique<game::ZoneManager>();
|
zoneManager = std::make_unique<game::ZoneManager>();
|
||||||
zoneManager->initialize();
|
zoneManager->initialize();
|
||||||
|
if (assetManager) {
|
||||||
|
zoneManager->enrichFromDBC(assetManager);
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize AudioEngine (singleton)
|
// Initialize AudioEngine (singleton)
|
||||||
if (!audio::AudioEngine::instance().initialize()) {
|
if (!audio::AudioEngine::instance().initialize()) {
|
||||||
|
|
@ -727,9 +730,6 @@ bool Renderer::initialize(core::Window* win) {
|
||||||
spellSoundManager = std::make_unique<audio::SpellSoundManager>();
|
spellSoundManager = std::make_unique<audio::SpellSoundManager>();
|
||||||
movementSoundManager = std::make_unique<audio::MovementSoundManager>();
|
movementSoundManager = std::make_unique<audio::MovementSoundManager>();
|
||||||
|
|
||||||
// TODO Phase 6: Vulkan underwater overlay, post-process, and shadow map
|
|
||||||
// GL versions stubbed during migration
|
|
||||||
|
|
||||||
// Create secondary command buffer resources for multithreaded rendering
|
// Create secondary command buffer resources for multithreaded rendering
|
||||||
if (!createSecondaryCommandResources()) {
|
if (!createSecondaryCommandResources()) {
|
||||||
LOG_WARNING("Failed to create secondary command buffers — falling back to single-threaded rendering");
|
LOG_WARNING("Failed to create secondary command buffers — falling back to single-threaded rendering");
|
||||||
|
|
@ -3037,9 +3037,12 @@ void Renderer::update(float deltaTime) {
|
||||||
|
|
||||||
// Update zone detection and music
|
// Update zone detection and music
|
||||||
if (zoneManager && musicManager && terrainManager && camera) {
|
if (zoneManager && musicManager && terrainManager && camera) {
|
||||||
// First check tile-based zone
|
// Prefer server-authoritative zone ID (from SMSG_INIT_WORLD_STATES);
|
||||||
|
// fall back to tile-based lookup for single-player / offline mode.
|
||||||
|
const auto* gh = core::Application::getInstance().getGameHandler();
|
||||||
|
uint32_t serverZoneId = gh ? gh->getWorldStateZoneId() : 0;
|
||||||
auto tile = terrainManager->getCurrentTile();
|
auto tile = terrainManager->getCurrentTile();
|
||||||
uint32_t zoneId = zoneManager->getZoneId(tile.x, tile.y);
|
uint32_t zoneId = (serverZoneId != 0) ? serverZoneId : zoneManager->getZoneId(tile.x, tile.y);
|
||||||
|
|
||||||
bool insideTavern = false;
|
bool insideTavern = false;
|
||||||
bool insideBlacksmith = false;
|
bool insideBlacksmith = false;
|
||||||
|
|
@ -3149,6 +3152,10 @@ void Renderer::update(float deltaTime) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Update ambient sound manager zone type
|
||||||
|
if (ambientSoundManager) {
|
||||||
|
ambientSoundManager->setZoneId(zoneId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
musicManager->update(deltaTime);
|
musicManager->update(deltaTime);
|
||||||
|
|
@ -3899,7 +3906,9 @@ bool Renderer::initFSR2Resources() {
|
||||||
fsr2_.amdFsr3RuntimePath = "Path C";
|
fsr2_.amdFsr3RuntimePath = "Path C";
|
||||||
fsr2_.amdFsr3RuntimeLastError = fsr2_.amdFsr3Runtime->lastError();
|
fsr2_.amdFsr3RuntimeLastError = fsr2_.amdFsr3Runtime->lastError();
|
||||||
LOG_WARNING("FSR3 framegen toggle is enabled, but runtime initialization failed. ",
|
LOG_WARNING("FSR3 framegen toggle is enabled, but runtime initialization failed. ",
|
||||||
"Set WOWEE_FFX_SDK_RUNTIME_LIB to the SDK runtime binary path.");
|
"path=", fsr2_.amdFsr3RuntimePath,
|
||||||
|
" error=", fsr2_.amdFsr3RuntimeLastError.empty() ? "(none)" : fsr2_.amdFsr3RuntimeLastError,
|
||||||
|
" runtimeLib=", fsr2_.amdFsr3Runtime->loadedLibraryPath().empty() ? "(not loaded)" : fsr2_.amdFsr3Runtime->loadedLibraryPath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -4986,6 +4995,11 @@ bool Renderer::initializeRenderers(pipeline::AssetManager* assetManager, const s
|
||||||
terrainRenderer.reset();
|
terrainRenderer.reset();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (shadowRenderPass != VK_NULL_HANDLE) {
|
||||||
|
terrainRenderer->initializeShadow(shadowRenderPass);
|
||||||
|
}
|
||||||
|
} else if (!terrainRenderer->hasShadowPipeline() && shadowRenderPass != VK_NULL_HANDLE) {
|
||||||
|
terrainRenderer->initializeShadow(shadowRenderPass);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create water renderer if not already created
|
// Create water renderer if not already created
|
||||||
|
|
@ -5154,6 +5168,11 @@ bool Renderer::initializeRenderers(pipeline::AssetManager* assetManager, const s
|
||||||
}
|
}
|
||||||
|
|
||||||
cachedAssetManager = assetManager;
|
cachedAssetManager = assetManager;
|
||||||
|
|
||||||
|
// Enrich zone music from DBC if not already done (e.g. asset manager was null at init).
|
||||||
|
if (zoneManager && assetManager) {
|
||||||
|
zoneManager->enrichFromDBC(assetManager);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snap camera to ground
|
// Snap camera to ground
|
||||||
|
|
@ -5707,6 +5726,9 @@ void Renderer::renderShadowPass() {
|
||||||
|
|
||||||
// Phase 7/8: render shadow casters
|
// Phase 7/8: render shadow casters
|
||||||
const float shadowCullRadius = shadowDistance_ * 1.35f;
|
const float shadowCullRadius = shadowDistance_ * 1.35f;
|
||||||
|
if (terrainRenderer) {
|
||||||
|
terrainRenderer->renderShadow(currentCmd, lightSpaceMatrix, shadowCenter, shadowCullRadius);
|
||||||
|
}
|
||||||
if (wmoRenderer) {
|
if (wmoRenderer) {
|
||||||
wmoRenderer->renderShadow(currentCmd, lightSpaceMatrix, shadowCenter, shadowCullRadius);
|
wmoRenderer->renderShadow(currentCmd, lightSpaceMatrix, shadowCenter, shadowCullRadius);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -314,6 +314,13 @@ void TerrainRenderer::shutdown() {
|
||||||
if (materialDescPool) { vkDestroyDescriptorPool(device, materialDescPool, nullptr); materialDescPool = VK_NULL_HANDLE; }
|
if (materialDescPool) { vkDestroyDescriptorPool(device, materialDescPool, nullptr); materialDescPool = VK_NULL_HANDLE; }
|
||||||
if (materialSetLayout) { vkDestroyDescriptorSetLayout(device, materialSetLayout, nullptr); materialSetLayout = VK_NULL_HANDLE; }
|
if (materialSetLayout) { vkDestroyDescriptorSetLayout(device, materialSetLayout, nullptr); materialSetLayout = VK_NULL_HANDLE; }
|
||||||
|
|
||||||
|
// Shadow pipeline cleanup
|
||||||
|
if (shadowPipeline_) { vkDestroyPipeline(device, shadowPipeline_, nullptr); shadowPipeline_ = VK_NULL_HANDLE; }
|
||||||
|
if (shadowPipelineLayout_) { vkDestroyPipelineLayout(device, shadowPipelineLayout_, nullptr); shadowPipelineLayout_ = VK_NULL_HANDLE; }
|
||||||
|
if (shadowParamsPool_) { vkDestroyDescriptorPool(device, shadowParamsPool_, nullptr); shadowParamsPool_ = VK_NULL_HANDLE; shadowParamsSet_ = VK_NULL_HANDLE; }
|
||||||
|
if (shadowParamsLayout_) { vkDestroyDescriptorSetLayout(device, shadowParamsLayout_, nullptr); shadowParamsLayout_ = VK_NULL_HANDLE; }
|
||||||
|
if (shadowParamsUBO_) { vmaDestroyBuffer(allocator, shadowParamsUBO_, shadowParamsAlloc_); shadowParamsUBO_ = VK_NULL_HANDLE; shadowParamsAlloc_ = VK_NULL_HANDLE; }
|
||||||
|
|
||||||
vkCtx = nullptr;
|
vkCtx = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -784,8 +791,198 @@ void TerrainRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, c
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TerrainRenderer::renderShadow(VkCommandBuffer /*cmd*/, const glm::vec3& /*shadowCenter*/, float /*halfExtent*/) {
|
bool TerrainRenderer::initializeShadow(VkRenderPass shadowRenderPass) {
|
||||||
// Phase 6 stub
|
if (!vkCtx || shadowRenderPass == VK_NULL_HANDLE) return false;
|
||||||
|
if (shadowPipeline_ != VK_NULL_HANDLE) return true; // already initialised
|
||||||
|
VkDevice device = vkCtx->getDevice();
|
||||||
|
VmaAllocator allocator = vkCtx->getAllocator();
|
||||||
|
|
||||||
|
// ShadowParams UBO — terrain uses no bones, no texture, no alpha test
|
||||||
|
struct ShadowParamsUBO {
|
||||||
|
int32_t useBones = 0;
|
||||||
|
int32_t useTexture = 0;
|
||||||
|
int32_t alphaTest = 0;
|
||||||
|
int32_t foliageSway = 0;
|
||||||
|
float windTime = 0.0f;
|
||||||
|
float foliageMotionDamp = 1.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
VkBufferCreateInfo bufCI{};
|
||||||
|
bufCI.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||||
|
bufCI.size = sizeof(ShadowParamsUBO);
|
||||||
|
bufCI.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
|
||||||
|
VmaAllocationCreateInfo allocCI{};
|
||||||
|
allocCI.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
|
||||||
|
allocCI.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
|
||||||
|
VmaAllocationInfo allocInfo{};
|
||||||
|
if (vmaCreateBuffer(allocator, &bufCI, &allocCI,
|
||||||
|
&shadowParamsUBO_, &shadowParamsAlloc_, &allocInfo) != VK_SUCCESS) {
|
||||||
|
LOG_ERROR("TerrainRenderer: failed to create shadow params UBO");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ShadowParamsUBO defaultParams{};
|
||||||
|
std::memcpy(allocInfo.pMappedData, &defaultParams, sizeof(defaultParams));
|
||||||
|
|
||||||
|
// Descriptor set layout: binding 0 = combined sampler (unused), binding 1 = ShadowParams UBO
|
||||||
|
VkDescriptorSetLayoutBinding layoutBindings[2]{};
|
||||||
|
layoutBindings[0].binding = 0;
|
||||||
|
layoutBindings[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||||
|
layoutBindings[0].descriptorCount = 1;
|
||||||
|
layoutBindings[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||||
|
layoutBindings[1].binding = 1;
|
||||||
|
layoutBindings[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||||
|
layoutBindings[1].descriptorCount = 1;
|
||||||
|
layoutBindings[1].stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||||
|
|
||||||
|
VkDescriptorSetLayoutCreateInfo layoutCI{};
|
||||||
|
layoutCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
||||||
|
layoutCI.bindingCount = 2;
|
||||||
|
layoutCI.pBindings = layoutBindings;
|
||||||
|
if (vkCreateDescriptorSetLayout(device, &layoutCI, nullptr, &shadowParamsLayout_) != VK_SUCCESS) {
|
||||||
|
LOG_ERROR("TerrainRenderer: failed to create shadow params set layout");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkDescriptorPoolSize poolSizes[2]{};
|
||||||
|
poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||||
|
poolSizes[0].descriptorCount = 1;
|
||||||
|
poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||||
|
poolSizes[1].descriptorCount = 1;
|
||||||
|
VkDescriptorPoolCreateInfo poolCI{};
|
||||||
|
poolCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
||||||
|
poolCI.maxSets = 1;
|
||||||
|
poolCI.poolSizeCount = 2;
|
||||||
|
poolCI.pPoolSizes = poolSizes;
|
||||||
|
if (vkCreateDescriptorPool(device, &poolCI, nullptr, &shadowParamsPool_) != VK_SUCCESS) {
|
||||||
|
LOG_ERROR("TerrainRenderer: failed to create shadow params pool");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkDescriptorSetAllocateInfo setAlloc{};
|
||||||
|
setAlloc.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
||||||
|
setAlloc.descriptorPool = shadowParamsPool_;
|
||||||
|
setAlloc.descriptorSetCount = 1;
|
||||||
|
setAlloc.pSetLayouts = &shadowParamsLayout_;
|
||||||
|
if (vkAllocateDescriptorSets(device, &setAlloc, &shadowParamsSet_) != VK_SUCCESS) {
|
||||||
|
LOG_ERROR("TerrainRenderer: failed to allocate shadow params set");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write descriptors — sampler uses whiteTexture as dummy (useTexture=0 so never sampled)
|
||||||
|
VkDescriptorBufferInfo bufInfo{ shadowParamsUBO_, 0, sizeof(ShadowParamsUBO) };
|
||||||
|
VkDescriptorImageInfo imgInfo{};
|
||||||
|
imgInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||||
|
imgInfo.imageView = whiteTexture->getImageView();
|
||||||
|
imgInfo.sampler = whiteTexture->getSampler();
|
||||||
|
|
||||||
|
VkWriteDescriptorSet writes[2]{};
|
||||||
|
writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||||
|
writes[0].dstSet = shadowParamsSet_;
|
||||||
|
writes[0].dstBinding = 0;
|
||||||
|
writes[0].descriptorCount = 1;
|
||||||
|
writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||||
|
writes[0].pImageInfo = &imgInfo;
|
||||||
|
writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||||
|
writes[1].dstSet = shadowParamsSet_;
|
||||||
|
writes[1].dstBinding = 1;
|
||||||
|
writes[1].descriptorCount = 1;
|
||||||
|
writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||||
|
writes[1].pBufferInfo = &bufInfo;
|
||||||
|
vkUpdateDescriptorSets(device, 2, writes, 0, nullptr);
|
||||||
|
|
||||||
|
// Pipeline layout: set 0 = shadowParamsLayout_, push 128 bytes (lightSpaceMatrix + model)
|
||||||
|
VkPushConstantRange pc{};
|
||||||
|
pc.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
||||||
|
pc.offset = 0;
|
||||||
|
pc.size = 128;
|
||||||
|
shadowPipelineLayout_ = createPipelineLayout(device, {shadowParamsLayout_}, {pc});
|
||||||
|
if (!shadowPipelineLayout_) {
|
||||||
|
LOG_ERROR("TerrainRenderer: failed to create shadow pipeline layout");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkShaderModule vertShader, fragShader;
|
||||||
|
if (!vertShader.loadFromFile(device, "assets/shaders/shadow.vert.spv")) {
|
||||||
|
LOG_ERROR("TerrainRenderer: failed to load shadow vertex shader");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!fragShader.loadFromFile(device, "assets/shaders/shadow.frag.spv")) {
|
||||||
|
LOG_ERROR("TerrainRenderer: failed to load shadow fragment shader");
|
||||||
|
vertShader.destroy();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terrain vertex layout: pos(0,off0) normal(1,off12) texCoord(2,off24) layerUV(3,off32)
|
||||||
|
// stride = sizeof(TerrainVertex) = 44 bytes
|
||||||
|
// Shadow shader expects: aPos(loc0), aTexCoord(loc1), aBoneWeights(loc2), aBoneIndicesF(loc3)
|
||||||
|
// Alias unused bone attrs to position (offset 0); useBones=0 so they are never read.
|
||||||
|
const uint32_t stride = static_cast<uint32_t>(sizeof(pipeline::TerrainVertex));
|
||||||
|
VkVertexInputBindingDescription vertBind{};
|
||||||
|
vertBind.binding = 0;
|
||||||
|
vertBind.stride = stride;
|
||||||
|
vertBind.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||||
|
std::vector<VkVertexInputAttributeDescription> vertAttrs = {
|
||||||
|
{0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0}, // aPos -> position
|
||||||
|
{1, 0, VK_FORMAT_R32G32_SFLOAT, 24}, // aTexCoord -> texCoord (unused)
|
||||||
|
{2, 0, VK_FORMAT_R32G32B32A32_SFLOAT, 0}, // aBoneWeights -> position (unused)
|
||||||
|
{3, 0, VK_FORMAT_R32G32B32A32_SFLOAT, 0}, // aBoneIndices -> position (unused)
|
||||||
|
};
|
||||||
|
|
||||||
|
shadowPipeline_ = PipelineBuilder()
|
||||||
|
.setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||||
|
fragShader.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||||
|
.setVertexInput({vertBind}, vertAttrs)
|
||||||
|
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||||
|
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||||
|
.setDepthTest(true, true, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||||
|
.setDepthBias(0.05f, 0.20f)
|
||||||
|
.setNoColorAttachment()
|
||||||
|
.setLayout(shadowPipelineLayout_)
|
||||||
|
.setRenderPass(shadowRenderPass)
|
||||||
|
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
||||||
|
.build(device);
|
||||||
|
|
||||||
|
vertShader.destroy();
|
||||||
|
fragShader.destroy();
|
||||||
|
|
||||||
|
if (!shadowPipeline_) {
|
||||||
|
LOG_ERROR("TerrainRenderer: failed to create shadow pipeline");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LOG_INFO("TerrainRenderer shadow pipeline initialized");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TerrainRenderer::renderShadow(VkCommandBuffer cmd, const glm::mat4& lightSpaceMatrix,
|
||||||
|
const glm::vec3& shadowCenter, float shadowRadius) {
|
||||||
|
if (!shadowPipeline_ || !shadowParamsSet_) return;
|
||||||
|
if (chunks.empty()) return;
|
||||||
|
|
||||||
|
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, shadowPipeline_);
|
||||||
|
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, shadowPipelineLayout_,
|
||||||
|
0, 1, &shadowParamsSet_, 0, nullptr);
|
||||||
|
|
||||||
|
// Identity model matrix — terrain vertices are already in world space
|
||||||
|
static const glm::mat4 identity(1.0f);
|
||||||
|
struct ShadowPush { glm::mat4 lightSpaceMatrix; glm::mat4 model; };
|
||||||
|
ShadowPush push{ lightSpaceMatrix, identity };
|
||||||
|
vkCmdPushConstants(cmd, shadowPipelineLayout_, VK_SHADER_STAGE_VERTEX_BIT,
|
||||||
|
0, 128, &push);
|
||||||
|
|
||||||
|
for (const auto& chunk : chunks) {
|
||||||
|
if (!chunk.isValid()) continue;
|
||||||
|
|
||||||
|
// Sphere-cull chunk against shadow region
|
||||||
|
glm::vec3 diff = chunk.boundingSphereCenter - shadowCenter;
|
||||||
|
float distSq = glm::dot(diff, diff);
|
||||||
|
float combinedRadius = shadowRadius + chunk.boundingSphereRadius;
|
||||||
|
if (distSq > combinedRadius * combinedRadius) continue;
|
||||||
|
|
||||||
|
VkDeviceSize offset = 0;
|
||||||
|
vkCmdBindVertexBuffers(cmd, 0, 1, &chunk.vertexBuffer, &offset);
|
||||||
|
vkCmdBindIndexBuffer(cmd, chunk.indexBuffer, 0, VK_INDEX_TYPE_UINT16);
|
||||||
|
vkCmdDrawIndexed(cmd, chunk.indexCount, 1, 0, 0, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TerrainRenderer::removeTile(int tileX, int tileY) {
|
void TerrainRenderer::removeTile(int tileX, int tileY) {
|
||||||
|
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
#include "rendering/texture.hpp"
|
|
||||||
#include "core/logger.hpp"
|
|
||||||
|
|
||||||
// Stub implementation - would use stb_image or similar
|
|
||||||
namespace wowee {
|
|
||||||
namespace rendering {
|
|
||||||
|
|
||||||
Texture::~Texture() {
|
|
||||||
if (textureID) {
|
|
||||||
glDeleteTextures(1, &textureID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Texture::loadFromFile(const std::string& path) {
|
|
||||||
// TODO: Implement with stb_image or BLP loader
|
|
||||||
LOG_WARNING("Texture loading not yet implemented: ", path);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Texture::loadFromMemory(const unsigned char* data, int w, int h, int channels) {
|
|
||||||
width = w;
|
|
||||||
height = h;
|
|
||||||
|
|
||||||
glGenTextures(1, &textureID);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, textureID);
|
|
||||||
|
|
||||||
GLenum format = (channels == 4) ? GL_RGBA : GL_RGB;
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
|
|
||||||
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
||||||
|
|
||||||
glGenerateMipmap(GL_TEXTURE_2D);
|
|
||||||
applyAnisotropicFiltering();
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Texture::bind(GLuint unit) const {
|
|
||||||
glActiveTexture(GL_TEXTURE0 + unit);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, textureID);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Texture::unbind() const {
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void applyAnisotropicFiltering() {
|
|
||||||
static float maxAniso = -1.0f;
|
|
||||||
if (maxAniso < 0.0f) {
|
|
||||||
if (GLEW_EXT_texture_filter_anisotropic) {
|
|
||||||
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAniso);
|
|
||||||
if (maxAniso < 1.0f) maxAniso = 1.0f;
|
|
||||||
} else {
|
|
||||||
maxAniso = 0.0f; // Extension not available
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (maxAniso > 0.0f) {
|
|
||||||
float desired = 16.0f;
|
|
||||||
float clamped = (desired < maxAniso) ? desired : maxAniso;
|
|
||||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, clamped);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace rendering
|
|
||||||
} // namespace wowee
|
|
||||||
|
|
@ -942,13 +942,10 @@ void WaterRenderer::loadFromWMO([[maybe_unused]] const pipeline::WMOLiquid& liqu
|
||||||
|
|
||||||
if (surface.origin.z > 2000.0f || surface.origin.z < -500.0f) return;
|
if (surface.origin.z > 2000.0f || surface.origin.z < -500.0f) return;
|
||||||
|
|
||||||
// Build tile mask from MLIQ flags and per-vertex heights
|
// Build tile mask from MLIQ flags
|
||||||
size_t tileCount = static_cast<size_t>(surface.width) * static_cast<size_t>(surface.height);
|
size_t tileCount = static_cast<size_t>(surface.width) * static_cast<size_t>(surface.height);
|
||||||
size_t maskBytes = (tileCount + 7) / 8;
|
size_t maskBytes = (tileCount + 7) / 8;
|
||||||
surface.mask.assign(maskBytes, 0x00);
|
surface.mask.assign(maskBytes, 0x00);
|
||||||
const float baseZ = liquid.basePosition.z;
|
|
||||||
const bool hasHeights = !liquid.heights.empty() &&
|
|
||||||
liquid.heights.size() >= static_cast<size_t>(vertexCount);
|
|
||||||
for (size_t t = 0; t < tileCount; t++) {
|
for (size_t t = 0; t < tileCount; t++) {
|
||||||
bool hasLiquid = true;
|
bool hasLiquid = true;
|
||||||
int tx = static_cast<int>(t) % surface.width;
|
int tx = static_cast<int>(t) % surface.width;
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1090,6 +1090,11 @@ void InventoryScreen::renderCharacterScreen(game::GameHandler& gameHandler) {
|
||||||
ImGui::EndTabItem();
|
ImGui::EndTabItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginTabItem("Reputation")) {
|
||||||
|
renderReputationPanel(gameHandler);
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
if (ImGui::BeginTabItem("Skills")) {
|
if (ImGui::BeginTabItem("Skills")) {
|
||||||
const auto& skills = gameHandler.getPlayerSkills();
|
const auto& skills = gameHandler.getPlayerSkills();
|
||||||
if (skills.empty()) {
|
if (skills.empty()) {
|
||||||
|
|
@ -1171,6 +1176,89 @@ void InventoryScreen::renderCharacterScreen(game::GameHandler& gameHandler) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InventoryScreen::renderReputationPanel(game::GameHandler& gameHandler) {
|
||||||
|
const auto& standings = gameHandler.getFactionStandings();
|
||||||
|
if (standings.empty()) {
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::TextDisabled("No reputation data received yet.");
|
||||||
|
ImGui::TextDisabled("Reputation updates as you kill enemies and complete quests.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// WoW reputation tier breakpoints (cumulative from floor -42000)
|
||||||
|
// Tier name, threshold for next rank, bar color
|
||||||
|
struct RepTier {
|
||||||
|
const char* name;
|
||||||
|
int32_t floor; // raw value where this tier begins
|
||||||
|
int32_t ceiling; // raw value where the next tier begins
|
||||||
|
ImVec4 color;
|
||||||
|
};
|
||||||
|
static const RepTier tiers[] = {
|
||||||
|
{ "Hated", -42000, -6001, ImVec4(0.6f, 0.1f, 0.1f, 1.0f) },
|
||||||
|
{ "Hostile", -6000, -3001, ImVec4(0.8f, 0.2f, 0.1f, 1.0f) },
|
||||||
|
{ "Unfriendly", -3000, -1, ImVec4(0.9f, 0.5f, 0.1f, 1.0f) },
|
||||||
|
{ "Neutral", 0, 2999, ImVec4(0.8f, 0.8f, 0.2f, 1.0f) },
|
||||||
|
{ "Friendly", 3000, 8999, ImVec4(0.2f, 0.7f, 0.2f, 1.0f) },
|
||||||
|
{ "Honored", 9000, 20999, ImVec4(0.2f, 0.8f, 0.5f, 1.0f) },
|
||||||
|
{ "Revered", 21000, 41999, ImVec4(0.3f, 0.6f, 1.0f, 1.0f) },
|
||||||
|
{ "Exalted", 42000, 42000, ImVec4(1.0f, 0.84f, 0.0f, 1.0f) },
|
||||||
|
};
|
||||||
|
|
||||||
|
auto getTier = [&](int32_t val) -> const RepTier& {
|
||||||
|
for (int i = 6; i >= 0; --i) {
|
||||||
|
if (val >= tiers[i].floor) return tiers[i];
|
||||||
|
}
|
||||||
|
return tiers[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
ImGui::BeginChild("##ReputationList", ImVec2(0, 0), true);
|
||||||
|
|
||||||
|
// Sort factions alphabetically by name
|
||||||
|
std::vector<std::pair<uint32_t, int32_t>> sortedFactions(standings.begin(), standings.end());
|
||||||
|
std::sort(sortedFactions.begin(), sortedFactions.end(),
|
||||||
|
[&](const auto& a, const auto& b) {
|
||||||
|
const std::string& na = gameHandler.getFactionNamePublic(a.first);
|
||||||
|
const std::string& nb = gameHandler.getFactionNamePublic(b.first);
|
||||||
|
return na < nb;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const auto& [factionId, standing] : sortedFactions) {
|
||||||
|
const RepTier& tier = getTier(standing);
|
||||||
|
|
||||||
|
const std::string& factionName = gameHandler.getFactionNamePublic(factionId);
|
||||||
|
const char* displayName = factionName.empty() ? "Unknown Faction" : factionName.c_str();
|
||||||
|
|
||||||
|
// Faction name + tier label on same line
|
||||||
|
ImGui::TextColored(tier.color, "[%s]", tier.name);
|
||||||
|
ImGui::SameLine(90.0f);
|
||||||
|
ImGui::Text("%s", displayName);
|
||||||
|
|
||||||
|
// Progress bar showing position within current tier
|
||||||
|
float ratio = 0.0f;
|
||||||
|
char overlay[64] = "";
|
||||||
|
if (tier.floor == 42000) {
|
||||||
|
// Exalted — full bar
|
||||||
|
ratio = 1.0f;
|
||||||
|
snprintf(overlay, sizeof(overlay), "Exalted");
|
||||||
|
} else {
|
||||||
|
int32_t tierRange = tier.ceiling - tier.floor + 1;
|
||||||
|
int32_t inTier = standing - tier.floor;
|
||||||
|
ratio = static_cast<float>(inTier) / static_cast<float>(tierRange);
|
||||||
|
ratio = std::max(0.0f, std::min(1.0f, ratio));
|
||||||
|
snprintf(overlay, sizeof(overlay), "%d / %d",
|
||||||
|
inTier < 0 ? 0 : inTier, tierRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, tier.color);
|
||||||
|
ImGui::SetNextItemWidth(-1.0f);
|
||||||
|
ImGui::ProgressBar(ratio, ImVec2(0, 12.0f), overlay);
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::Spacing();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
void InventoryScreen::renderEquipmentPanel(game::Inventory& inventory) {
|
void InventoryScreen::renderEquipmentPanel(game::Inventory& inventory) {
|
||||||
ImGui::TextColored(ImVec4(1.0f, 0.84f, 0.0f, 1.0f), "Equipment");
|
ImGui::TextColored(ImVec4(1.0f, 0.84f, 0.0f, 1.0f), "Equipment");
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
|
||||||
202
tools/generate_ffx_sdk_vk_permutations.sh
Executable file
202
tools/generate_ffx_sdk_vk_permutations.sh
Executable file
|
|
@ -0,0 +1,202 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
SDK_ROOT="${1:-$ROOT_DIR/extern/FidelityFX-SDK}"
|
||||||
|
KITS_DIR="$SDK_ROOT/Kits/FidelityFX"
|
||||||
|
FFX_SC="$KITS_DIR/tools/ffx_sc/ffx_sc.py"
|
||||||
|
OUT_DIR="$KITS_DIR/framegeneration/fsr3/internal/permutations/vk"
|
||||||
|
SHADER_DIR="$KITS_DIR/upscalers/fsr3/internal/shaders"
|
||||||
|
|
||||||
|
if [[ ! -f "$FFX_SC" ]]; then
|
||||||
|
echo "Missing ffx_sc.py at $FFX_SC" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
required_headers=(
|
||||||
|
"$OUT_DIR/ffx_fsr2_accumulate_pass_wave64_16bit_permutations.h"
|
||||||
|
"$OUT_DIR/ffx_fsr3upscaler_accumulate_pass_wave64_16bit_permutations.h"
|
||||||
|
"$OUT_DIR/ffx_fsr3upscaler_autogen_reactive_pass_permutations.h"
|
||||||
|
)
|
||||||
|
if [[ "${WOWEE_FORCE_REGEN_PERMS:-0}" != "1" ]]; then
|
||||||
|
missing=0
|
||||||
|
for h in "${required_headers[@]}"; do
|
||||||
|
[[ -f "$h" ]] || missing=1
|
||||||
|
done
|
||||||
|
if [[ $missing -eq 0 ]]; then
|
||||||
|
echo "FidelityFX VK permutation headers already present."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${DXC:-}" ]]; then
|
||||||
|
if [[ -x /tmp/dxc/bin/dxc ]]; then
|
||||||
|
export DXC=/tmp/dxc/bin/dxc
|
||||||
|
elif command -v dxc >/dev/null 2>&1; then
|
||||||
|
export DXC="$(command -v dxc)"
|
||||||
|
elif [[ "$(uname -s)" == "Linux" ]]; then
|
||||||
|
_arch="$(uname -m)"
|
||||||
|
if [[ "$_arch" == "aarch64" || "$_arch" == "arm64" ]]; then
|
||||||
|
echo "Linux aarch64: no official arm64 DXC release available." >&2
|
||||||
|
echo "Install 'directx-shader-compiler' via apt or set DXC=/path/to/dxc to regenerate." >&2
|
||||||
|
echo "Skipping VK permutation codegen (permutations may be pre-built in the SDK checkout)."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "DXC not found; downloading Linux DXC release to /tmp/dxc ..."
|
||||||
|
tmp_json="$(mktemp)"
|
||||||
|
curl -sS https://api.github.com/repos/microsoft/DirectXShaderCompiler/releases/latest > "$tmp_json"
|
||||||
|
dxc_url="$(python3 - << 'PY' "$tmp_json"
|
||||||
|
import json, sys
|
||||||
|
with open(sys.argv[1], 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
for a in data.get('assets', []):
|
||||||
|
name = a.get('name', '')
|
||||||
|
if name.startswith('linux_dxc_') and name.endswith('.x86_64.tar.gz'):
|
||||||
|
print(a.get('browser_download_url', ''))
|
||||||
|
break
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
rm -f "$tmp_json"
|
||||||
|
if [[ -z "$dxc_url" ]]; then
|
||||||
|
echo "Failed to locate Linux DXC release asset URL." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
rm -rf /tmp/dxc /tmp/linux_dxc.tar.gz
|
||||||
|
curl -L --fail "$dxc_url" -o /tmp/linux_dxc.tar.gz
|
||||||
|
mkdir -p /tmp/dxc
|
||||||
|
tar -xzf /tmp/linux_dxc.tar.gz -C /tmp/dxc --strip-components=1
|
||||||
|
export DXC=/tmp/dxc/bin/dxc
|
||||||
|
elif [[ "$(uname -s)" =~ MINGW|MSYS|CYGWIN ]]; then
|
||||||
|
echo "DXC not found; downloading Windows DXC release to /tmp/dxc ..."
|
||||||
|
tmp_json="$(mktemp)"
|
||||||
|
curl -sS https://api.github.com/repos/microsoft/DirectXShaderCompiler/releases/latest > "$tmp_json"
|
||||||
|
dxc_url="$(python3 - << 'PY' "$tmp_json"
|
||||||
|
import json, sys
|
||||||
|
with open(sys.argv[1], 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
for a in data.get('assets', []):
|
||||||
|
name = a.get('name', '')
|
||||||
|
if name.startswith('dxc_') and name.endswith('.zip'):
|
||||||
|
print(a.get('browser_download_url', ''))
|
||||||
|
break
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
rm -f "$tmp_json"
|
||||||
|
if [[ -z "$dxc_url" ]]; then
|
||||||
|
echo "Failed to locate Windows DXC release asset URL." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
rm -rf /tmp/dxc /tmp/dxc_win.zip
|
||||||
|
curl -L --fail "$dxc_url" -o /tmp/dxc_win.zip
|
||||||
|
mkdir -p /tmp/dxc
|
||||||
|
unzip -q /tmp/dxc_win.zip -d /tmp/dxc
|
||||||
|
if [[ -x /tmp/dxc/bin/x64/dxc.exe ]]; then
|
||||||
|
export DXC=/tmp/dxc/bin/x64/dxc.exe
|
||||||
|
elif [[ -x /tmp/dxc/bin/x86/dxc.exe ]]; then
|
||||||
|
export DXC=/tmp/dxc/bin/x86/dxc.exe
|
||||||
|
else
|
||||||
|
echo "DXC download succeeded, but dxc.exe was not found." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "DXC not found. Set DXC=/path/to/dxc or install to /tmp/dxc/bin/dxc" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
|
||||||
|
# First generate frame interpolation + optical flow permutations via SDK script.
|
||||||
|
(
|
||||||
|
cd "$SDK_ROOT"
|
||||||
|
./generate_vk_permutations.sh
|
||||||
|
)
|
||||||
|
|
||||||
|
BASE_ARGS=(-reflection -embed-arguments -E CS -Wno-for-redefinition -Wno-ambig-lit-shift -DFFX_GPU=1 -DFFX_HLSL=1 -DFFX_IMPLICIT_SHADER_REGISTER_BINDING_HLSL=0)
|
||||||
|
WAVE32=(-DFFX_HLSL_SM=62 -T cs_6_2)
|
||||||
|
WAVE64=("-DFFX_PREFER_WAVE64=[WaveSize(64)]" -DFFX_HLSL_SM=66 -T cs_6_6)
|
||||||
|
BIT16=(-DFFX_HALF=1 -enable-16bit-types)
|
||||||
|
|
||||||
|
compile_shader() {
|
||||||
|
local file="$1"; shift
|
||||||
|
local name="$1"; shift
|
||||||
|
python3 "$FFX_SC" "${BASE_ARGS[@]}" "$@" -name="$name" -output="$OUT_DIR" "$file"
|
||||||
|
}
|
||||||
|
|
||||||
|
# FSR2 (for upscalers/fsr3/internal/ffx_fsr2_shaderblobs.cpp)
|
||||||
|
FSR2_COMMON=(
|
||||||
|
-DFFX_FSR2_EMBED_ROOTSIG=0
|
||||||
|
-DFFX_FSR2_OPTION_UPSAMPLE_SAMPLERS_USE_DATA_HALF=0
|
||||||
|
-DFFX_FSR2_OPTION_ACCUMULATE_SAMPLERS_USE_DATA_HALF=0
|
||||||
|
-DFFX_FSR2_OPTION_REPROJECT_SAMPLERS_USE_DATA_HALF=1
|
||||||
|
-DFFX_FSR2_OPTION_POSTPROCESSLOCKSTATUS_SAMPLERS_USE_DATA_HALF=0
|
||||||
|
-DFFX_FSR2_OPTION_UPSAMPLE_USE_LANCZOS_TYPE=2
|
||||||
|
"-DFFX_FSR2_OPTION_REPROJECT_USE_LANCZOS_TYPE={0,1}"
|
||||||
|
"-DFFX_FSR2_OPTION_HDR_COLOR_INPUT={0,1}"
|
||||||
|
"-DFFX_FSR2_OPTION_LOW_RESOLUTION_MOTION_VECTORS={0,1}"
|
||||||
|
"-DFFX_FSR2_OPTION_JITTERED_MOTION_VECTORS={0,1}"
|
||||||
|
"-DFFX_FSR2_OPTION_INVERTED_DEPTH={0,1}"
|
||||||
|
"-DFFX_FSR2_OPTION_APPLY_SHARPENING={0,1}"
|
||||||
|
-I "$KITS_DIR/api/internal/include/gpu"
|
||||||
|
-I "$KITS_DIR/upscalers/fsr3/include/gpu"
|
||||||
|
)
|
||||||
|
FSR2_SHADERS=(
|
||||||
|
ffx_fsr2_autogen_reactive_pass
|
||||||
|
ffx_fsr2_accumulate_pass
|
||||||
|
ffx_fsr2_compute_luminance_pyramid_pass
|
||||||
|
ffx_fsr2_depth_clip_pass
|
||||||
|
ffx_fsr2_lock_pass
|
||||||
|
ffx_fsr2_reconstruct_previous_depth_pass
|
||||||
|
ffx_fsr2_rcas_pass
|
||||||
|
ffx_fsr2_tcr_autogen_pass
|
||||||
|
)
|
||||||
|
|
||||||
|
for shader in "${FSR2_SHADERS[@]}"; do
|
||||||
|
file="$SHADER_DIR/$shader.hlsl"
|
||||||
|
[[ -f "$file" ]] || continue
|
||||||
|
compile_shader "$file" "$shader" -DFFX_HALF=0 "${WAVE32[@]}" "${FSR2_COMMON[@]}"
|
||||||
|
compile_shader "$file" "${shader}_wave64" -DFFX_HALF=0 "${WAVE64[@]}" "${FSR2_COMMON[@]}"
|
||||||
|
compile_shader "$file" "${shader}_16bit" "${BIT16[@]}" "${WAVE32[@]}" "${FSR2_COMMON[@]}"
|
||||||
|
compile_shader "$file" "${shader}_wave64_16bit" "${BIT16[@]}" "${WAVE64[@]}" "${FSR2_COMMON[@]}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# FSR3 upscaler (for upscalers/fsr3/internal/ffx_fsr3upscaler_shaderblobs.cpp)
|
||||||
|
FSR3_COMMON=(
|
||||||
|
-DFFX_FSR3UPSCALER_EMBED_ROOTSIG=0
|
||||||
|
-DFFX_FSR3UPSCALER_OPTION_UPSAMPLE_SAMPLERS_USE_DATA_HALF=0
|
||||||
|
-DFFX_FSR3UPSCALER_OPTION_ACCUMULATE_SAMPLERS_USE_DATA_HALF=0
|
||||||
|
-DFFX_FSR3UPSCALER_OPTION_REPROJECT_SAMPLERS_USE_DATA_HALF=1
|
||||||
|
-DFFX_FSR3UPSCALER_OPTION_POSTPROCESSLOCKSTATUS_SAMPLERS_USE_DATA_HALF=0
|
||||||
|
-DFFX_FSR3UPSCALER_OPTION_UPSAMPLE_USE_LANCZOS_TYPE=2
|
||||||
|
"-DFFX_FSR3UPSCALER_OPTION_REPROJECT_USE_LANCZOS_TYPE={0,1}"
|
||||||
|
"-DFFX_FSR3UPSCALER_OPTION_HDR_COLOR_INPUT={0,1}"
|
||||||
|
"-DFFX_FSR3UPSCALER_OPTION_LOW_RESOLUTION_MOTION_VECTORS={0,1}"
|
||||||
|
"-DFFX_FSR3UPSCALER_OPTION_JITTERED_MOTION_VECTORS={0,1}"
|
||||||
|
"-DFFX_FSR3UPSCALER_OPTION_INVERTED_DEPTH={0,1}"
|
||||||
|
"-DFFX_FSR3UPSCALER_OPTION_APPLY_SHARPENING={0,1}"
|
||||||
|
-I "$KITS_DIR/api/internal/gpu"
|
||||||
|
-I "$KITS_DIR/upscalers/fsr3/include/gpu"
|
||||||
|
)
|
||||||
|
FSR3_SHADERS=(
|
||||||
|
ffx_fsr3upscaler_autogen_reactive_pass
|
||||||
|
ffx_fsr3upscaler_accumulate_pass
|
||||||
|
ffx_fsr3upscaler_luma_pyramid_pass
|
||||||
|
ffx_fsr3upscaler_prepare_reactivity_pass
|
||||||
|
ffx_fsr3upscaler_prepare_inputs_pass
|
||||||
|
ffx_fsr3upscaler_shading_change_pass
|
||||||
|
ffx_fsr3upscaler_rcas_pass
|
||||||
|
ffx_fsr3upscaler_shading_change_pyramid_pass
|
||||||
|
ffx_fsr3upscaler_luma_instability_pass
|
||||||
|
ffx_fsr3upscaler_debug_view_pass
|
||||||
|
)
|
||||||
|
|
||||||
|
for shader in "${FSR3_SHADERS[@]}"; do
|
||||||
|
file="$SHADER_DIR/$shader.hlsl"
|
||||||
|
[[ -f "$file" ]] || continue
|
||||||
|
compile_shader "$file" "$shader" -DFFX_HALF=0 "${WAVE32[@]}" "${FSR3_COMMON[@]}"
|
||||||
|
compile_shader "$file" "${shader}_wave64" -DFFX_HALF=0 "${WAVE64[@]}" "${FSR3_COMMON[@]}"
|
||||||
|
compile_shader "$file" "${shader}_16bit" "${BIT16[@]}" "${WAVE32[@]}" "${FSR3_COMMON[@]}"
|
||||||
|
compile_shader "$file" "${shader}_wave64_16bit" "${BIT16[@]}" "${WAVE64[@]}" "${FSR3_COMMON[@]}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Generated VK permutation headers in $OUT_DIR"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue