Add gameobject interaction and taxi activation

This commit is contained in:
Kelsi 2026-02-07 19:44:03 -08:00
parent a71902a571
commit e5c48dc9b7
8 changed files with 361 additions and 58 deletions

View file

@ -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

View file

@ -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;

View file

@ -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,

View file

@ -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:

View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

@ -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
}