mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Add gameobject interaction and taxi activation
This commit is contained in:
parent
a71902a571
commit
e5c48dc9b7
8 changed files with 361 additions and 58 deletions
|
|
@ -85,6 +85,10 @@ private:
|
|||
void despawnOnlineCreature(uint64_t guid);
|
||||
void buildCreatureDisplayLookups();
|
||||
std::string getModelPathForDisplayId(uint32_t displayId) const;
|
||||
void spawnOnlineGameObject(uint64_t guid, uint32_t displayId, float x, float y, float z, float orientation);
|
||||
void despawnOnlineGameObject(uint64_t guid);
|
||||
void buildGameObjectDisplayLookups();
|
||||
std::string getGameObjectModelPathForDisplayId(uint32_t displayId) const;
|
||||
|
||||
static Application* instance;
|
||||
|
||||
|
|
@ -151,6 +155,20 @@ private:
|
|||
std::unordered_map<uint32_t, uint32_t> displayIdModelCache_; // displayId → modelId (model caching)
|
||||
uint32_t nextCreatureModelId_ = 5000; // Model IDs for online creatures
|
||||
|
||||
// Online gameobject model spawning
|
||||
struct GameObjectInstanceInfo {
|
||||
uint32_t modelId = 0;
|
||||
uint32_t instanceId = 0;
|
||||
bool isWmo = false;
|
||||
};
|
||||
std::unordered_map<uint32_t, std::string> gameObjectDisplayIdToPath_;
|
||||
std::unordered_map<uint32_t, uint32_t> gameObjectDisplayIdModelCache_; // displayId → M2 modelId
|
||||
std::unordered_map<uint32_t, uint32_t> gameObjectDisplayIdWmoCache_; // displayId → WMO modelId
|
||||
std::unordered_map<uint64_t, GameObjectInstanceInfo> gameObjectInstances_; // guid → instance info
|
||||
uint32_t nextGameObjectModelId_ = 20000;
|
||||
uint32_t nextGameObjectWmoModelId_ = 40000;
|
||||
bool gameObjectLookupsBuilt_ = false;
|
||||
|
||||
// Mount model tracking
|
||||
uint32_t mountInstanceId_ = 0;
|
||||
uint32_t mountModelId_ = 0;
|
||||
|
|
@ -167,6 +185,14 @@ private:
|
|||
std::vector<PendingCreatureSpawn> pendingCreatureSpawns_;
|
||||
static constexpr int MAX_SPAWNS_PER_FRAME = 2;
|
||||
void processCreatureSpawnQueue();
|
||||
|
||||
struct PendingGameObjectSpawn {
|
||||
uint64_t guid;
|
||||
uint32_t displayId;
|
||||
float x, y, z, orientation;
|
||||
};
|
||||
std::vector<PendingGameObjectSpawn> pendingGameObjectSpawns_;
|
||||
void processGameObjectSpawnQueue();
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
|
|
|
|||
|
|
@ -378,6 +378,15 @@ public:
|
|||
using CreatureDespawnCallback = std::function<void(uint64_t guid)>;
|
||||
void setCreatureDespawnCallback(CreatureDespawnCallback cb) { creatureDespawnCallback_ = std::move(cb); }
|
||||
|
||||
// GameObject spawn callback (online mode - triggered when gameobject enters view)
|
||||
// Parameters: guid, displayId, x, y, z (canonical), orientation
|
||||
using GameObjectSpawnCallback = std::function<void(uint64_t guid, uint32_t displayId, float x, float y, float z, float orientation)>;
|
||||
void setGameObjectSpawnCallback(GameObjectSpawnCallback cb) { gameObjectSpawnCallback_ = std::move(cb); }
|
||||
|
||||
// GameObject despawn callback (online mode - triggered when gameobject leaves view)
|
||||
using GameObjectDespawnCallback = std::function<void(uint64_t guid)>;
|
||||
void setGameObjectDespawnCallback(GameObjectDespawnCallback cb) { gameObjectDespawnCallback_ = std::move(cb); }
|
||||
|
||||
// Faction hostility map (populated from FactionTemplate.dbc by Application)
|
||||
void setFactionHostileMap(std::unordered_map<uint32_t, bool> map) { factionHostileMap_ = std::move(map); }
|
||||
|
||||
|
|
@ -417,6 +426,7 @@ public:
|
|||
|
||||
// NPC Gossip
|
||||
void interactWithNpc(uint64_t guid);
|
||||
void interactWithGameObject(uint64_t guid);
|
||||
void selectGossipOption(uint32_t optionId);
|
||||
void selectGossipQuest(uint32_t questId);
|
||||
void acceptQuest();
|
||||
|
|
@ -792,6 +802,8 @@ private:
|
|||
CreatureSpawnCallback creatureSpawnCallback_;
|
||||
CreatureDespawnCallback creatureDespawnCallback_;
|
||||
CreatureMoveCallback creatureMoveCallback_;
|
||||
GameObjectSpawnCallback gameObjectSpawnCallback_;
|
||||
GameObjectDespawnCallback gameObjectDespawnCallback_;
|
||||
std::vector<uint32_t> knownSpells;
|
||||
std::unordered_map<uint32_t, float> spellCooldowns; // spellId -> remaining seconds
|
||||
uint8_t castCount = 0;
|
||||
|
|
|
|||
|
|
@ -193,6 +193,9 @@ enum class Opcode : uint16_t {
|
|||
SMSG_LOOT_MONEY_NOTIFY = 0x163,
|
||||
SMSG_LOOT_CLEAR_MONEY = 0x165,
|
||||
|
||||
// ---- Phase 5: Taxi / Flight Paths ----
|
||||
CMSG_ACTIVATETAXI = 0x19D,
|
||||
|
||||
// ---- Phase 5: NPC Gossip ----
|
||||
CMSG_GOSSIP_HELLO = 0x17B,
|
||||
CMSG_GOSSIP_SELECT_OPTION = 0x17C,
|
||||
|
|
@ -200,6 +203,9 @@ enum class Opcode : uint16_t {
|
|||
SMSG_GOSSIP_COMPLETE = 0x17E,
|
||||
SMSG_NPC_TEXT_UPDATE = 0x180,
|
||||
|
||||
// ---- Phase 5: GameObject ----
|
||||
CMSG_GAMEOBJECT_USE = 0x01B,
|
||||
|
||||
// ---- Phase 5: Quests ----
|
||||
CMSG_QUESTGIVER_STATUS_QUERY = 0x182,
|
||||
SMSG_QUESTGIVER_STATUS = 0x183,
|
||||
|
|
|
|||
|
|
@ -1665,8 +1665,10 @@ struct ShowTaxiNodesData {
|
|||
uint32_t nearestNode = 0; // Taxi node player is at
|
||||
uint32_t nodeMask[TLK_TAXI_MASK_SIZE] = {};
|
||||
bool isNodeKnown(uint32_t nodeId) const {
|
||||
uint32_t idx = nodeId / 32;
|
||||
uint32_t bit = nodeId % 32;
|
||||
if (nodeId == 0) return false;
|
||||
uint32_t bitIndex = nodeId - 1;
|
||||
uint32_t idx = bitIndex / 32;
|
||||
uint32_t bit = bitIndex % 32;
|
||||
return idx < TLK_TAXI_MASK_SIZE && (nodeMask[idx] & (1u << bit));
|
||||
}
|
||||
};
|
||||
|
|
@ -1694,6 +1696,18 @@ public:
|
|||
static network::Packet build(uint64_t npcGuid, const std::vector<uint32_t>& pathNodes);
|
||||
};
|
||||
|
||||
/** CMSG_ACTIVATETAXI packet builder */
|
||||
class ActivateTaxiPacket {
|
||||
public:
|
||||
static network::Packet build(uint64_t npcGuid, uint32_t srcNode, uint32_t destNode);
|
||||
};
|
||||
|
||||
/** CMSG_GAMEOBJECT_USE packet builder */
|
||||
class GameObjectUsePacket {
|
||||
public:
|
||||
static network::Packet build(uint64_t guid);
|
||||
};
|
||||
|
||||
/** CMSG_REPOP_REQUEST packet builder */
|
||||
class RepopRequestPacket {
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
#include "rendering/weather.hpp"
|
||||
#include "rendering/character_renderer.hpp"
|
||||
#include "rendering/wmo_renderer.hpp"
|
||||
#include "rendering/m2_renderer.hpp"
|
||||
#include "rendering/minimap.hpp"
|
||||
#include "rendering/loading_screen.hpp"
|
||||
#include "audio/music_manager.hpp"
|
||||
|
|
@ -39,6 +40,7 @@
|
|||
#include <cstdlib>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cctype>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <set>
|
||||
|
|
@ -385,6 +387,7 @@ void Application::update(float deltaTime) {
|
|||
}
|
||||
// Process deferred online creature spawns (throttled)
|
||||
processCreatureSpawnQueue();
|
||||
processGameObjectSpawnQueue();
|
||||
processPendingMount();
|
||||
if (npcManager && renderer && renderer->getCharacterRenderer()) {
|
||||
npcManager->update(deltaTime, renderer->getCharacterRenderer());
|
||||
|
|
@ -560,6 +563,16 @@ void Application::setupUICallbacks() {
|
|||
despawnOnlineCreature(guid);
|
||||
});
|
||||
|
||||
// GameObject spawn callback (online mode) - spawn static models (mailboxes, etc.)
|
||||
gameHandler->setGameObjectSpawnCallback([this](uint64_t guid, uint32_t displayId, float x, float y, float z, float orientation) {
|
||||
pendingGameObjectSpawns_.push_back({guid, displayId, x, y, z, orientation});
|
||||
});
|
||||
|
||||
// GameObject despawn callback (online mode) - remove static models
|
||||
gameHandler->setGameObjectDespawnCallback([this](uint64_t guid) {
|
||||
despawnOnlineGameObject(guid);
|
||||
});
|
||||
|
||||
// Mount callback (online mode) - defer heavy model load to next frame
|
||||
gameHandler->setMountCallback([this](uint32_t mountDisplayId) {
|
||||
if (mountDisplayId == 0) {
|
||||
|
|
@ -1663,6 +1676,40 @@ std::string Application::getModelPathForDisplayId(uint32_t displayId) const {
|
|||
return itPath->second;
|
||||
}
|
||||
|
||||
void Application::buildGameObjectDisplayLookups() {
|
||||
if (gameObjectLookupsBuilt_ || !assetManager || !assetManager->isInitialized()) return;
|
||||
|
||||
LOG_INFO("Building gameobject display lookups from DBC files");
|
||||
|
||||
// GameObjectDisplayInfo.dbc structure (3.3.5a):
|
||||
// Col 0: ID (displayId)
|
||||
// Col 1: ModelName
|
||||
if (auto godi = assetManager->loadDBC("GameObjectDisplayInfo.dbc"); godi && godi->isLoaded()) {
|
||||
for (uint32_t i = 0; i < godi->getRecordCount(); i++) {
|
||||
uint32_t displayId = godi->getUInt32(i, 0);
|
||||
std::string modelName = godi->getString(i, 1);
|
||||
if (modelName.empty()) continue;
|
||||
if (modelName.size() >= 4) {
|
||||
std::string ext = modelName.substr(modelName.size() - 4);
|
||||
for (char& c : ext) c = static_cast<char>(std::tolower(c));
|
||||
if (ext == ".mdx") {
|
||||
modelName = modelName.substr(0, modelName.size() - 4) + ".m2";
|
||||
}
|
||||
}
|
||||
gameObjectDisplayIdToPath_[displayId] = modelName;
|
||||
}
|
||||
LOG_INFO("Loaded ", gameObjectDisplayIdToPath_.size(), " gameobject display mappings");
|
||||
}
|
||||
|
||||
gameObjectLookupsBuilt_ = true;
|
||||
}
|
||||
|
||||
std::string Application::getGameObjectModelPathForDisplayId(uint32_t displayId) const {
|
||||
auto it = gameObjectDisplayIdToPath_.find(displayId);
|
||||
if (it == gameObjectDisplayIdToPath_.end()) return "";
|
||||
return it->second;
|
||||
}
|
||||
|
||||
bool Application::getRenderBoundsForGuid(uint64_t guid, glm::vec3& outCenter, float& outRadius) const {
|
||||
if (!renderer || !renderer->getCharacterRenderer()) return false;
|
||||
uint32_t instanceId = 0;
|
||||
|
|
@ -2142,6 +2189,144 @@ void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x
|
|||
" displayId=", displayId, " at (", x, ", ", y, ", ", z, ")");
|
||||
}
|
||||
|
||||
void Application::spawnOnlineGameObject(uint64_t guid, uint32_t displayId, float x, float y, float z, float orientation) {
|
||||
if (!renderer || !assetManager) return;
|
||||
|
||||
if (!gameObjectLookupsBuilt_) {
|
||||
buildGameObjectDisplayLookups();
|
||||
}
|
||||
if (!gameObjectLookupsBuilt_) return;
|
||||
|
||||
if (gameObjectInstances_.count(guid)) return;
|
||||
|
||||
std::string modelPath = getGameObjectModelPathForDisplayId(displayId);
|
||||
if (modelPath.empty()) {
|
||||
LOG_WARNING("No model path for gameobject displayId ", displayId, " (guid 0x", std::hex, guid, std::dec, ")");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string lowerPath = modelPath;
|
||||
std::transform(lowerPath.begin(), lowerPath.end(), lowerPath.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
bool isWmo = lowerPath.size() >= 4 && lowerPath.substr(lowerPath.size() - 4) == ".wmo";
|
||||
|
||||
glm::vec3 renderPos = core::coords::canonicalToRender(glm::vec3(x, y, z));
|
||||
float renderYaw = orientation + glm::radians(90.0f);
|
||||
|
||||
if (isWmo) {
|
||||
auto* wmoRenderer = renderer->getWMORenderer();
|
||||
if (!wmoRenderer) return;
|
||||
|
||||
uint32_t modelId = 0;
|
||||
auto itCache = gameObjectDisplayIdWmoCache_.find(displayId);
|
||||
if (itCache != gameObjectDisplayIdWmoCache_.end()) {
|
||||
modelId = itCache->second;
|
||||
} else {
|
||||
modelId = nextGameObjectWmoModelId_++;
|
||||
auto wmoData = assetManager->readFile(modelPath);
|
||||
if (wmoData.empty()) {
|
||||
LOG_WARNING("Failed to read gameobject WMO: ", modelPath);
|
||||
return;
|
||||
}
|
||||
|
||||
pipeline::WMOModel wmoModel = pipeline::WMOLoader::load(wmoData);
|
||||
if (wmoModel.nGroups > 0) {
|
||||
std::string basePath = modelPath;
|
||||
std::string extension;
|
||||
if (basePath.size() > 4) {
|
||||
extension = basePath.substr(basePath.size() - 4);
|
||||
std::string extLower = extension;
|
||||
for (char& c : extLower) c = static_cast<char>(std::tolower(c));
|
||||
if (extLower == ".wmo") {
|
||||
basePath = basePath.substr(0, basePath.size() - 4);
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t gi = 0; gi < wmoModel.nGroups; gi++) {
|
||||
char groupSuffix[16];
|
||||
snprintf(groupSuffix, sizeof(groupSuffix), "_%03u%s", gi, extension.c_str());
|
||||
std::string groupPath = basePath + groupSuffix;
|
||||
std::vector<uint8_t> groupData = assetManager->readFile(groupPath);
|
||||
if (groupData.empty()) {
|
||||
snprintf(groupSuffix, sizeof(groupSuffix), "_%03u.wmo", gi);
|
||||
groupData = assetManager->readFile(basePath + groupSuffix);
|
||||
}
|
||||
if (groupData.empty()) {
|
||||
snprintf(groupSuffix, sizeof(groupSuffix), "_%03u.WMO", gi);
|
||||
groupData = assetManager->readFile(basePath + groupSuffix);
|
||||
}
|
||||
if (!groupData.empty()) {
|
||||
pipeline::WMOLoader::loadGroup(groupData, wmoModel, gi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!wmoRenderer->loadModel(wmoModel, modelId)) {
|
||||
LOG_WARNING("Failed to load gameobject WMO: ", modelPath);
|
||||
return;
|
||||
}
|
||||
gameObjectDisplayIdWmoCache_[displayId] = modelId;
|
||||
}
|
||||
|
||||
uint32_t instanceId = wmoRenderer->createInstance(modelId, renderPos,
|
||||
glm::vec3(0.0f, 0.0f, renderYaw), 1.0f);
|
||||
if (instanceId == 0) {
|
||||
LOG_WARNING("Failed to create gameobject WMO instance for guid 0x", std::hex, guid, std::dec);
|
||||
return;
|
||||
}
|
||||
|
||||
gameObjectInstances_[guid] = {modelId, instanceId, true};
|
||||
} else {
|
||||
auto* m2Renderer = renderer->getM2Renderer();
|
||||
if (!m2Renderer) return;
|
||||
|
||||
uint32_t modelId = 0;
|
||||
auto itCache = gameObjectDisplayIdModelCache_.find(displayId);
|
||||
if (itCache != gameObjectDisplayIdModelCache_.end()) {
|
||||
modelId = itCache->second;
|
||||
} else {
|
||||
modelId = nextGameObjectModelId_++;
|
||||
|
||||
auto m2Data = assetManager->readFile(modelPath);
|
||||
if (m2Data.empty()) {
|
||||
LOG_WARNING("Failed to read gameobject M2: ", modelPath);
|
||||
return;
|
||||
}
|
||||
|
||||
pipeline::M2Model model = pipeline::M2Loader::load(m2Data);
|
||||
if (model.vertices.empty()) {
|
||||
LOG_WARNING("Failed to parse gameobject M2: ", modelPath);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string skinPath = modelPath.substr(0, modelPath.size() - 3) + "00.skin";
|
||||
auto skinData = assetManager->readFile(skinPath);
|
||||
if (!skinData.empty()) {
|
||||
pipeline::M2Loader::loadSkin(skinData, model);
|
||||
}
|
||||
|
||||
if (!m2Renderer->loadModel(model, modelId)) {
|
||||
LOG_WARNING("Failed to load gameobject model: ", modelPath);
|
||||
return;
|
||||
}
|
||||
|
||||
gameObjectDisplayIdModelCache_[displayId] = modelId;
|
||||
}
|
||||
|
||||
uint32_t instanceId = m2Renderer->createInstance(modelId, renderPos,
|
||||
glm::vec3(0.0f, 0.0f, renderYaw), 1.0f);
|
||||
if (instanceId == 0) {
|
||||
LOG_WARNING("Failed to create gameobject instance for guid 0x", std::hex, guid, std::dec);
|
||||
return;
|
||||
}
|
||||
|
||||
gameObjectInstances_[guid] = {modelId, instanceId, false};
|
||||
}
|
||||
|
||||
LOG_INFO("Spawned gameobject: guid=0x", std::hex, guid, std::dec,
|
||||
" displayId=", displayId, " at (", x, ", ", y, ", ", z, ")");
|
||||
}
|
||||
|
||||
void Application::processCreatureSpawnQueue() {
|
||||
if (pendingCreatureSpawns_.empty()) return;
|
||||
|
||||
|
|
@ -2154,6 +2339,18 @@ void Application::processCreatureSpawnQueue() {
|
|||
}
|
||||
}
|
||||
|
||||
void Application::processGameObjectSpawnQueue() {
|
||||
if (pendingGameObjectSpawns_.empty()) return;
|
||||
|
||||
int spawned = 0;
|
||||
while (!pendingGameObjectSpawns_.empty() && spawned < MAX_SPAWNS_PER_FRAME) {
|
||||
auto& s = pendingGameObjectSpawns_.front();
|
||||
spawnOnlineGameObject(s.guid, s.displayId, s.x, s.y, s.z, s.orientation);
|
||||
pendingGameObjectSpawns_.erase(pendingGameObjectSpawns_.begin());
|
||||
spawned++;
|
||||
}
|
||||
}
|
||||
|
||||
void Application::processPendingMount() {
|
||||
if (pendingMountDisplayId_ == 0) return;
|
||||
uint32_t mountDisplayId = pendingMountDisplayId_;
|
||||
|
|
@ -2312,5 +2509,26 @@ void Application::despawnOnlineCreature(uint64_t guid) {
|
|||
LOG_INFO("Despawned creature: guid=0x", std::hex, guid, std::dec);
|
||||
}
|
||||
|
||||
void Application::despawnOnlineGameObject(uint64_t guid) {
|
||||
auto it = gameObjectInstances_.find(guid);
|
||||
if (it == gameObjectInstances_.end()) return;
|
||||
|
||||
if (renderer) {
|
||||
if (it->second.isWmo) {
|
||||
if (auto* wmoRenderer = renderer->getWMORenderer()) {
|
||||
wmoRenderer->removeInstance(it->second.instanceId);
|
||||
}
|
||||
} else {
|
||||
if (auto* m2Renderer = renderer->getM2Renderer()) {
|
||||
m2Renderer->removeInstance(it->second.instanceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gameObjectInstances_.erase(it);
|
||||
|
||||
LOG_INFO("Despawned gameobject: guid=0x", std::hex, guid, std::dec);
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace wowee
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ void GameHandler::update(float deltaTime) {
|
|||
}
|
||||
}
|
||||
|
||||
// Close vendor/gossip window if player walks too far from NPC
|
||||
// Close vendor/gossip/taxi window if player walks too far from NPC
|
||||
if (vendorWindowOpen && currentVendorItems.vendorGuid != 0) {
|
||||
auto npc = entityManager.getEntity(currentVendorItems.vendorGuid);
|
||||
if (npc) {
|
||||
|
|
@ -216,6 +216,30 @@ void GameHandler::update(float deltaTime) {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (gossipWindowOpen && currentGossip.npcGuid != 0) {
|
||||
auto npc = entityManager.getEntity(currentGossip.npcGuid);
|
||||
if (npc) {
|
||||
float dx = movementInfo.x - npc->getX();
|
||||
float dy = movementInfo.y - npc->getY();
|
||||
float dist = std::sqrt(dx * dx + dy * dy);
|
||||
if (dist > 15.0f) {
|
||||
closeGossip();
|
||||
LOG_INFO("Gossip closed: walked too far from NPC");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (taxiWindowOpen_ && taxiNpcGuid_ != 0) {
|
||||
auto npc = entityManager.getEntity(taxiNpcGuid_);
|
||||
if (npc) {
|
||||
float dx = movementInfo.x - npc->getX();
|
||||
float dy = movementInfo.y - npc->getY();
|
||||
float dist = std::sqrt(dx * dx + dy * dy);
|
||||
if (dist > 15.0f) {
|
||||
closeTaxi();
|
||||
LOG_INFO("Taxi window closed: walked too far from NPC");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update entity movement interpolation (keeps targeting in sync with visuals)
|
||||
for (auto& [guid, entity] : entityManager.getEntities()) {
|
||||
|
|
@ -1231,9 +1255,14 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
for (uint64_t guid : data.outOfRangeGuids) {
|
||||
if (entityManager.hasEntity(guid)) {
|
||||
LOG_INFO("Entity went out of range: 0x", std::hex, guid, std::dec);
|
||||
// Trigger creature despawn callback before removing entity
|
||||
if (creatureDespawnCallback_) {
|
||||
creatureDespawnCallback_(guid);
|
||||
// Trigger despawn callbacks before removing entity
|
||||
auto entity = entityManager.getEntity(guid);
|
||||
if (entity) {
|
||||
if (entity->getType() == ObjectType::UNIT && creatureDespawnCallback_) {
|
||||
creatureDespawnCallback_(guid);
|
||||
} else if (entity->getType() == ObjectType::GAMEOBJECT && gameObjectDespawnCallback_) {
|
||||
gameObjectDespawnCallback_(guid);
|
||||
}
|
||||
}
|
||||
entityManager.removeEntity(guid);
|
||||
}
|
||||
|
|
@ -1355,6 +1384,18 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Extract displayId for gameobjects (3.3.5a: GAMEOBJECT_DISPLAYID = field 8)
|
||||
if (block.objectType == ObjectType::GAMEOBJECT) {
|
||||
auto go = std::static_pointer_cast<GameObject>(entity);
|
||||
auto itDisp = block.fields.find(8);
|
||||
if (itDisp != block.fields.end()) {
|
||||
go->setDisplayId(itDisp->second);
|
||||
}
|
||||
if (go->getDisplayId() != 0 && gameObjectSpawnCallback_) {
|
||||
gameObjectSpawnCallback_(block.guid, go->getDisplayId(),
|
||||
go->getX(), go->getY(), go->getZ(), go->getOrientation());
|
||||
}
|
||||
}
|
||||
// Track online item objects
|
||||
if (block.objectType == ObjectType::ITEM) {
|
||||
auto entryIt = block.fields.find(3); // OBJECT_FIELD_ENTRY
|
||||
|
|
@ -3524,6 +3565,12 @@ void GameHandler::interactWithNpc(uint64_t guid) {
|
|||
socket->send(packet);
|
||||
}
|
||||
|
||||
void GameHandler::interactWithGameObject(uint64_t guid) {
|
||||
if (state != WorldState::IN_WORLD || !socket) return;
|
||||
auto packet = GameObjectUsePacket::build(guid);
|
||||
socket->send(packet);
|
||||
}
|
||||
|
||||
void GameHandler::selectGossipOption(uint32_t optionId) {
|
||||
if (state != WorldState::IN_WORLD || !socket || !gossipWindowOpen) return;
|
||||
auto packet = GossipSelectOptionPacket::build(currentGossip.npcGuid, currentGossip.menuId, optionId);
|
||||
|
|
@ -4069,7 +4116,7 @@ void GameHandler::handleShowTaxiNodes(network::Packet& packet) {
|
|||
if (newBits == 0) continue;
|
||||
for (uint32_t bit = 0; bit < 32; ++bit) {
|
||||
if (newBits & (1u << bit)) {
|
||||
uint32_t nodeId = i * 32 + bit;
|
||||
uint32_t nodeId = i * 32 + bit + 1;
|
||||
auto it = taxiNodes_.find(nodeId);
|
||||
if (it != taxiNodes_.end()) {
|
||||
addSystemChatMessage("Discovered flight path: " + it->second.name);
|
||||
|
|
@ -4119,13 +4166,11 @@ void GameHandler::buildTaxiCostMap() {
|
|||
uint32_t startNode = currentTaxiData_.nearestNode;
|
||||
if (startNode == 0) return;
|
||||
|
||||
// Build adjacency list with costs from known edges
|
||||
// Build adjacency list with costs from all edges (path may traverse unknown nodes)
|
||||
struct AdjEntry { uint32_t node; uint32_t cost; };
|
||||
std::unordered_map<uint32_t, std::vector<AdjEntry>> adj;
|
||||
for (const auto& edge : taxiPathEdges_) {
|
||||
if (currentTaxiData_.isNodeKnown(edge.fromNode) && currentTaxiData_.isNodeKnown(edge.toNode)) {
|
||||
adj[edge.fromNode].push_back({edge.toNode, edge.cost});
|
||||
}
|
||||
adj[edge.fromNode].push_back({edge.toNode, edge.cost});
|
||||
}
|
||||
|
||||
// BFS from startNode, accumulating costs along the path
|
||||
|
|
@ -4156,51 +4201,7 @@ void GameHandler::activateTaxi(uint32_t destNodeId) {
|
|||
uint32_t startNode = currentTaxiData_.nearestNode;
|
||||
if (startNode == 0 || destNodeId == 0 || startNode == destNodeId) return;
|
||||
|
||||
// BFS to find path from startNode to destNodeId through known nodes
|
||||
// Build adjacency list from edges where both nodes are known
|
||||
std::unordered_map<uint32_t, std::vector<uint32_t>> adj;
|
||||
for (const auto& edge : taxiPathEdges_) {
|
||||
if (currentTaxiData_.isNodeKnown(edge.fromNode) && currentTaxiData_.isNodeKnown(edge.toNode)) {
|
||||
adj[edge.fromNode].push_back(edge.toNode);
|
||||
}
|
||||
}
|
||||
|
||||
// BFS
|
||||
std::unordered_map<uint32_t, uint32_t> parent;
|
||||
std::deque<uint32_t> queue;
|
||||
queue.push_back(startNode);
|
||||
parent[startNode] = startNode;
|
||||
|
||||
bool found = false;
|
||||
while (!queue.empty()) {
|
||||
uint32_t cur = queue.front();
|
||||
queue.pop_front();
|
||||
if (cur == destNodeId) { found = true; break; }
|
||||
for (uint32_t next : adj[cur]) {
|
||||
if (parent.find(next) == parent.end()) {
|
||||
parent[next] = cur;
|
||||
queue.push_back(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
LOG_WARNING("No taxi path found from node ", startNode, " to ", destNodeId);
|
||||
addSystemChatMessage("No flight path available to that destination.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Reconstruct path
|
||||
std::vector<uint32_t> path;
|
||||
for (uint32_t n = destNodeId; n != startNode; n = parent[n]) {
|
||||
path.push_back(n);
|
||||
}
|
||||
path.push_back(startNode);
|
||||
std::reverse(path.begin(), path.end());
|
||||
|
||||
LOG_INFO("Taxi path: ", path.size(), " nodes, from ", startNode, " to ", destNodeId);
|
||||
|
||||
auto pkt = ActivateTaxiExpressPacket::build(taxiNpcGuid_, path);
|
||||
auto pkt = ActivateTaxiPacket::build(taxiNpcGuid_, startNode, destNodeId);
|
||||
socket->send(pkt);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2634,5 +2634,19 @@ network::Packet ActivateTaxiExpressPacket::build(uint64_t npcGuid, const std::ve
|
|||
return packet;
|
||||
}
|
||||
|
||||
network::Packet ActivateTaxiPacket::build(uint64_t npcGuid, uint32_t srcNode, uint32_t destNode) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_ACTIVATETAXI));
|
||||
packet.writeUInt64(npcGuid);
|
||||
packet.writeUInt32(srcNode);
|
||||
packet.writeUInt32(destNode);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet GameObjectUsePacket::build(uint64_t guid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_GAMEOBJECT_USE));
|
||||
packet.writeUInt64(guid);
|
||||
return packet;
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
} // namespace wowee
|
||||
|
|
|
|||
|
|
@ -608,7 +608,9 @@ void GameScreen::processTargetInput(game::GameHandler& gameHandler) {
|
|||
const uint64_t myGuid = gameHandler.getPlayerGuid();
|
||||
for (const auto& [guid, entity] : gameHandler.getEntityManager().getEntities()) {
|
||||
auto t = entity->getType();
|
||||
if (t != game::ObjectType::UNIT && t != game::ObjectType::PLAYER) continue;
|
||||
if (t != game::ObjectType::UNIT &&
|
||||
t != game::ObjectType::PLAYER &&
|
||||
t != game::ObjectType::GAMEOBJECT) continue;
|
||||
if (guid == myGuid) continue; // Don't target self
|
||||
|
||||
glm::vec3 hitCenter;
|
||||
|
|
@ -625,6 +627,9 @@ void GameScreen::processTargetInput(game::GameHandler& gameHandler) {
|
|||
hitRadius = 0.5f;
|
||||
heightOffset = 0.3f;
|
||||
}
|
||||
} else if (t == game::ObjectType::GAMEOBJECT) {
|
||||
hitRadius = 1.2f;
|
||||
heightOffset = 0.8f;
|
||||
}
|
||||
hitCenter = core::coords::canonicalToRender(glm::vec3(entity->getX(), entity->getY(), entity->getZ()));
|
||||
hitCenter.z += heightOffset;
|
||||
|
|
@ -669,7 +674,9 @@ void GameScreen::processTargetInput(game::GameHandler& gameHandler) {
|
|||
const uint64_t myGuid = gameHandler.getPlayerGuid();
|
||||
for (const auto& [guid, entity] : gameHandler.getEntityManager().getEntities()) {
|
||||
auto t = entity->getType();
|
||||
if (t != game::ObjectType::UNIT && t != game::ObjectType::PLAYER) continue;
|
||||
if (t != game::ObjectType::UNIT &&
|
||||
t != game::ObjectType::PLAYER &&
|
||||
t != game::ObjectType::GAMEOBJECT) continue;
|
||||
if (guid == myGuid) continue;
|
||||
glm::vec3 hitCenter;
|
||||
float hitRadius = 0.0f;
|
||||
|
|
@ -683,6 +690,9 @@ void GameScreen::processTargetInput(game::GameHandler& gameHandler) {
|
|||
hitRadius = 0.5f;
|
||||
heightOffset = 0.3f;
|
||||
}
|
||||
} else if (t == game::ObjectType::GAMEOBJECT) {
|
||||
hitRadius = 1.2f;
|
||||
heightOffset = 0.8f;
|
||||
}
|
||||
hitCenter = core::coords::canonicalToRender(
|
||||
glm::vec3(entity->getX(), entity->getY(), entity->getZ()));
|
||||
|
|
@ -717,6 +727,8 @@ void GameScreen::processTargetInput(game::GameHandler& gameHandler) {
|
|||
gameHandler.interactWithNpc(target->getGuid());
|
||||
}
|
||||
}
|
||||
} else if (target->getType() == game::ObjectType::GAMEOBJECT) {
|
||||
gameHandler.interactWithGameObject(target->getGuid());
|
||||
} else if (target->getType() == game::ObjectType::PLAYER) {
|
||||
// Right-click another player could start attack in PvP context
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue