Kelsidavis-WoWee/src/core/entity_spawn_callback_handler.cpp

192 lines
9.2 KiB
C++
Raw Normal View History

refactor(core): decompose Application::setupUICallbacks() into 7 domain handlers Extract ~1,700 lines / 60+ inline [this]-capturing lambdas from the monolithic Application::setupUICallbacks() into 7 focused callback handler classes following the ToastManager/ChatPanel::setupCallbacks() pattern already in the codebase. New handlers (include/core/ + src/core/): - NPCInteractionCallbackHandler NPC greeting/farewell/vendor/aggro voice - AudioCallbackHandler Music, positional sound, level-up, achievement, LFG - EntitySpawnCallbackHandler Creature/player/GO spawn, despawn, move, state - AnimationCallbackHandler Death, respawn, combat, emotes, charge, sprint, vehicle - TransportCallbackHandler Mount, taxi, transport spawn/move - WorldEntryCallbackHandler World entry, unstuck, hearthstone, bind point - UIScreenCallbackHandler Auth, realm selection, char selection/creation/deletion application.cpp: 4,462 → 2,791 lines (−1,671) setupUICallbacks: ~1,700 → ~50 lines (thin orchestrator) Deduplication: resolveSoundEntryPath() — was 3× copy-paste of SoundEntries.dbc lookup resolveNpcVoiceType() — was 4× copy-paste of display-ID→voice detection precacheNearbyTiles() — was 3× copy-paste of 17×17 tile loop 4 helper lambdas — promoted to private methods on WorldEntryCallbackHandler State migration out of Application: charge* (6 vars) → AnimationCallbackHandler hearth*/worldEntry*/taxi* → WorldEntryCallbackHandler pendingCreatedCharacterName_ → UIScreenCallbackHandler Bug fixes: - Duplicate `namespace core {` in application.hpp caused wowee::std pollution - AppState forward decl in ui_screen_callback_handler.hpp was at wrong scope - world_loader.cpp accessed moved member vars directly via friend; now uses handler API
2026-04-05 16:48:17 +03:00
#include "core/entity_spawn_callback_handler.hpp"
#include "core/entity_spawner.hpp"
#include "core/coordinates.hpp"
#include "core/logger.hpp"
#include "rendering/renderer.hpp"
#include "rendering/character_renderer.hpp"
#include "rendering/m2_renderer.hpp"
#include "rendering/wmo_renderer.hpp"
#include "rendering/animation/animation_ids.hpp"
#include "game/game_handler.hpp"
namespace wowee { namespace core {
EntitySpawnCallbackHandler::EntitySpawnCallbackHandler(
EntitySpawner& entitySpawner,
rendering::Renderer& renderer,
game::GameHandler& gameHandler,
std::function<bool(uint64_t)> isLocalPlayerGuid)
: entitySpawner_(entitySpawner)
, renderer_(renderer)
, gameHandler_(gameHandler)
, isLocalPlayerGuid_(std::move(isLocalPlayerGuid))
{
}
void EntitySpawnCallbackHandler::setupCallbacks() {
// Creature spawn callback (online mode) - spawn creature models
gameHandler_.setCreatureSpawnCallback([this](uint64_t guid, uint32_t displayId, float x, float y, float z, float orientation, float scale) {
// Queue spawns to avoid hanging when many creatures appear at once.
// Deduplicate so repeated updates don't flood pending queue.
if (entitySpawner_.isCreatureSpawned(guid)) return;
if (entitySpawner_.isCreaturePending(guid)) return;
entitySpawner_.queueCreatureSpawn(guid, displayId, x, y, z, orientation, scale);
});
// Player spawn callback (online mode) - spawn player models with correct textures
gameHandler_.setPlayerSpawnCallback([this](uint64_t guid,
uint32_t /*displayId*/,
uint8_t raceId,
uint8_t genderId,
uint32_t appearanceBytes,
uint8_t facialFeatures,
float x, float y, float z, float orientation) {
LOG_DEBUG("playerSpawnCallback: guid=0x", std::hex, guid, std::dec,
refactor(core): decompose Application::setupUICallbacks() into 7 domain handlers Extract ~1,700 lines / 60+ inline [this]-capturing lambdas from the monolithic Application::setupUICallbacks() into 7 focused callback handler classes following the ToastManager/ChatPanel::setupCallbacks() pattern already in the codebase. New handlers (include/core/ + src/core/): - NPCInteractionCallbackHandler NPC greeting/farewell/vendor/aggro voice - AudioCallbackHandler Music, positional sound, level-up, achievement, LFG - EntitySpawnCallbackHandler Creature/player/GO spawn, despawn, move, state - AnimationCallbackHandler Death, respawn, combat, emotes, charge, sprint, vehicle - TransportCallbackHandler Mount, taxi, transport spawn/move - WorldEntryCallbackHandler World entry, unstuck, hearthstone, bind point - UIScreenCallbackHandler Auth, realm selection, char selection/creation/deletion application.cpp: 4,462 → 2,791 lines (−1,671) setupUICallbacks: ~1,700 → ~50 lines (thin orchestrator) Deduplication: resolveSoundEntryPath() — was 3× copy-paste of SoundEntries.dbc lookup resolveNpcVoiceType() — was 4× copy-paste of display-ID→voice detection precacheNearbyTiles() — was 3× copy-paste of 17×17 tile loop 4 helper lambdas — promoted to private methods on WorldEntryCallbackHandler State migration out of Application: charge* (6 vars) → AnimationCallbackHandler hearth*/worldEntry*/taxi* → WorldEntryCallbackHandler pendingCreatedCharacterName_ → UIScreenCallbackHandler Bug fixes: - Duplicate `namespace core {` in application.hpp caused wowee::std pollution - AppState forward decl in ui_screen_callback_handler.hpp was at wrong scope - world_loader.cpp accessed moved member vars directly via friend; now uses handler API
2026-04-05 16:48:17 +03:00
" race=", static_cast<int>(raceId), " gender=", static_cast<int>(genderId),
" pos=(", x, ",", y, ",", z, ")");
// Skip local player — already spawned as the main character
if (isLocalPlayerGuid_(guid)) return;
if (entitySpawner_.isPlayerSpawned(guid)) return;
if (entitySpawner_.isPlayerPending(guid)) return;
entitySpawner_.queuePlayerSpawn(guid, raceId, genderId, appearanceBytes, facialFeatures, x, y, z, orientation);
});
// Online player equipment callback - apply armor geosets/skin overlays per player instance.
gameHandler_.setPlayerEquipmentCallback([this](uint64_t guid,
const std::array<uint32_t, 19>& displayInfoIds,
const std::array<uint8_t, 19>& inventoryTypes) {
// Queue equipment compositing instead of doing it immediately —
// compositeWithRegions is expensive (file I/O + CPU blit + GPU upload)
// and causes frame stutters if multiple players update at once.
entitySpawner_.queuePlayerEquipment(guid, displayInfoIds, inventoryTypes);
});
// Creature despawn callback (online mode) - remove creature models
gameHandler_.setCreatureDespawnCallback([this](uint64_t guid) {
entitySpawner_.despawnCreature(guid);
});
gameHandler_.setPlayerDespawnCallback([this](uint64_t guid) {
entitySpawner_.despawnPlayer(guid);
});
// GameObject spawn callback (online mode) - spawn static models (mailboxes, etc.)
gameHandler_.setGameObjectSpawnCallback([this](uint64_t guid, uint32_t entry, uint32_t displayId, float x, float y, float z, float orientation, float scale) {
entitySpawner_.queueGameObjectSpawn(guid, entry, displayId, x, y, z, orientation, scale);
});
// GameObject despawn callback (online mode) - remove static models
gameHandler_.setGameObjectDespawnCallback([this](uint64_t guid) {
entitySpawner_.despawnGameObject(guid);
});
// GameObject custom animation callback (e.g. chest opening)
gameHandler_.setGameObjectCustomAnimCallback([this](uint64_t guid, uint32_t animId) {
auto& goInstances = entitySpawner_.getGameObjectInstances();
auto it = goInstances.find(guid);
if (it == goInstances.end()) return;
auto& info = it->second;
if (!info.isWmo) {
if (auto* m2r = renderer_.getM2Renderer()) {
// Play the custom animation as a one-shot if model supports it
if (m2r->hasAnimation(info.instanceId, animId))
m2r->setInstanceAnimation(info.instanceId, animId, false);
else
m2r->setInstanceAnimationFrozen(info.instanceId, false);
}
}
});
// GameObject state change callback — animate doors/chests opening/closing/destroying
gameHandler_.setGameObjectStateCallback([this](uint64_t guid, uint8_t goState) {
auto& goInstances = entitySpawner_.getGameObjectInstances();
auto it = goInstances.find(guid);
if (it == goInstances.end()) return;
auto& info = it->second;
if (info.isWmo) return; // WMOs don't have M2 animation sequences
auto* m2r = renderer_.getM2Renderer();
if (!m2r) return;
uint32_t instId = info.instanceId;
// GO states: 0=READY(closed), 1=OPEN, 2=DESTROYED/ACTIVE
if (goState == 1) {
// Opening: play OPEN(148) one-shot, fall back to unfreezing
if (m2r->hasAnimation(instId, 148))
m2r->setInstanceAnimation(instId, 148, false);
else
m2r->setInstanceAnimationFrozen(instId, false);
} else if (goState == 2) {
// Destroyed: play DESTROY(149) one-shot
if (m2r->hasAnimation(instId, 149))
m2r->setInstanceAnimation(instId, 149, false);
} else {
// Closed: play CLOSE(146) one-shot, else freeze
if (m2r->hasAnimation(instId, 146))
m2r->setInstanceAnimation(instId, 146, false);
else
m2r->setInstanceAnimationFrozen(instId, true);
}
});
// Creature move callback (online mode) - update creature positions
gameHandler_.setCreatureMoveCallback([this](uint64_t guid, float x, float y, float z, uint32_t durationMs) {
if (!renderer_.getCharacterRenderer()) return;
uint32_t instanceId = 0;
bool isPlayer = false;
instanceId = entitySpawner_.getPlayerInstanceId(guid);
if (instanceId != 0) { isPlayer = true; }
else {
instanceId = entitySpawner_.getCreatureInstanceId(guid);
}
if (instanceId != 0) {
glm::vec3 renderPos = core::coords::canonicalToRender(glm::vec3(x, y, z));
float durationSec = static_cast<float>(durationMs) / 1000.0f;
renderer_.getCharacterRenderer()->moveInstanceTo(instanceId, renderPos, durationSec);
// Play Run animation (anim 5) for the duration of the spline move.
// WoW M2 animation IDs: 4=Walk, 5=Run.
// Don't override Death animation (1). The per-frame sync loop will return to
// Stand when movement stops.
if (durationMs > 0) {
// Player animation is managed by the local renderer state machine —
// don't reset it here or every server movement packet restarts the
// run cycle from frame 0, causing visible stutter.
if (!isPlayer) {
uint32_t curAnimId = 0; float curT = 0.0f, curDur = 0.0f;
auto* cr = renderer_.getCharacterRenderer();
bool gotState = cr->getAnimationState(instanceId, curAnimId, curT, curDur);
// Only start Run if not already running and not in Death animation.
if (!gotState || (curAnimId != rendering::anim::DEATH && curAnimId != rendering::anim::RUN)) {
cr->playAnimation(instanceId, rendering::anim::RUN, /*loop=*/true);
}
entitySpawner_.getCreatureWasMoving()[guid] = true;
}
}
}
});
gameHandler_.setGameObjectMoveCallback([this](uint64_t guid, float x, float y, float z, float orientation) {
auto& goInstMap = entitySpawner_.getGameObjectInstances();
auto it = goInstMap.find(guid);
if (it == goInstMap.end()) {
return;
}
glm::vec3 renderPos = core::coords::canonicalToRender(glm::vec3(x, y, z));
auto& info = it->second;
if (info.isWmo) {
if (auto* wr = renderer_.getWMORenderer()) {
glm::mat4 transform(1.0f);
transform = glm::translate(transform, renderPos);
transform = glm::rotate(transform, orientation, glm::vec3(0, 0, 1));
wr->setInstanceTransform(info.instanceId, transform);
}
} else {
if (auto* mr = renderer_.getM2Renderer()) {
glm::mat4 transform(1.0f);
transform = glm::translate(transform, renderPos);
mr->setInstanceTransform(info.instanceId, transform);
}
}
});
}
}} // namespace wowee::core