mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Show online player equipment
This commit is contained in:
parent
6af9d6ba2d
commit
d3211f5493
4 changed files with 345 additions and 0 deletions
|
|
@ -8,6 +8,7 @@
|
|||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <array>
|
||||
|
||||
namespace wowee {
|
||||
|
||||
|
|
@ -96,6 +97,9 @@ private:
|
|||
uint32_t appearanceBytes,
|
||||
uint8_t facialFeatures,
|
||||
float x, float y, float z, float orientation);
|
||||
void setOnlinePlayerEquipment(uint64_t guid,
|
||||
const std::array<uint32_t, 19>& displayInfoIds,
|
||||
const std::array<uint8_t, 19>& inventoryTypes);
|
||||
void despawnOnlinePlayer(uint64_t guid);
|
||||
void buildCreatureDisplayLookups();
|
||||
std::string getModelPathForDisplayId(uint32_t displayId) const;
|
||||
|
|
@ -228,6 +232,18 @@ private:
|
|||
|
||||
// Online player instances (separate from creatures so we can apply per-player skin/hair textures).
|
||||
std::unordered_map<uint64_t, uint32_t> playerInstances_; // guid → render instanceId
|
||||
struct OnlinePlayerAppearanceState {
|
||||
uint32_t instanceId = 0;
|
||||
uint32_t modelId = 0;
|
||||
uint8_t raceId = 0;
|
||||
uint8_t genderId = 0;
|
||||
uint32_t appearanceBytes = 0;
|
||||
uint8_t facialFeatures = 0;
|
||||
std::string bodySkinPath;
|
||||
std::vector<std::string> underwearPaths;
|
||||
};
|
||||
std::unordered_map<uint64_t, OnlinePlayerAppearanceState> onlinePlayerAppearance_;
|
||||
std::unordered_map<uint64_t, std::pair<std::array<uint32_t, 19>, std::array<uint8_t, 19>>> pendingOnlinePlayerEquipment_;
|
||||
// Cache base player model geometry by (raceId, genderId)
|
||||
std::unordered_map<uint32_t, uint32_t> playerModelCache_; // key=(race<<8)|gender → modelId
|
||||
struct PlayerTextureSlots { int skin = -1; int hair = -1; int underwear = -1; };
|
||||
|
|
|
|||
|
|
@ -494,6 +494,14 @@ public:
|
|||
using PlayerDespawnCallback = std::function<void(uint64_t guid)>;
|
||||
void setPlayerDespawnCallback(PlayerDespawnCallback cb) { playerDespawnCallback_ = std::move(cb); }
|
||||
|
||||
// Online player equipment visuals callback.
|
||||
// Sends a best-effort view of equipped items for players in view using ItemDisplayInfo IDs.
|
||||
// Arrays are indexed by EquipSlot (0..18). Values are 0 when unknown/unavailable.
|
||||
using PlayerEquipmentCallback = std::function<void(uint64_t guid,
|
||||
const std::array<uint32_t, 19>& displayInfoIds,
|
||||
const std::array<uint8_t, 19>& inventoryTypes)>;
|
||||
void setPlayerEquipmentCallback(PlayerEquipmentCallback cb) { playerEquipmentCallback_ = std::move(cb); }
|
||||
|
||||
// GameObject spawn callback (online mode - triggered when gameobject enters view)
|
||||
// Parameters: guid, entry, displayId, x, y, z (canonical), orientation
|
||||
using GameObjectSpawnCallback = std::function<void(uint64_t guid, uint32_t entry, uint32_t displayId, float x, float y, float z, float orientation)>;
|
||||
|
|
@ -831,6 +839,10 @@ private:
|
|||
void handleItemQueryResponse(network::Packet& packet);
|
||||
void queryItemInfo(uint32_t entry, uint64_t guid);
|
||||
void rebuildOnlineInventory();
|
||||
void maybeDetectVisibleItemLayout();
|
||||
void updateOtherPlayerVisibleItems(uint64_t guid, const std::map<uint16_t, uint32_t>& fields);
|
||||
void emitOtherPlayerEquipment(uint64_t guid);
|
||||
void emitAllOtherPlayerEquipment();
|
||||
void detectInventorySlotBases(const std::map<uint16_t, uint32_t>& fields);
|
||||
bool applyInventoryFields(const std::map<uint16_t, uint32_t>& fields);
|
||||
uint64_t resolveOnlineItemGuid(uint32_t itemId) const;
|
||||
|
|
@ -1065,6 +1077,13 @@ private:
|
|||
std::map<uint16_t, uint32_t> lastPlayerFields_;
|
||||
bool onlineEquipDirty_ = false;
|
||||
|
||||
// Visible equipment for other players: detect the update-field layout (base + stride)
|
||||
// using the local player's own equipped items, then decode other players by index.
|
||||
int visibleItemEntryBase_ = -1;
|
||||
int visibleItemStride_ = 2;
|
||||
std::unordered_map<uint64_t, std::array<uint32_t, 19>> otherPlayerVisibleItemEntries_;
|
||||
std::unordered_set<uint64_t> otherPlayerVisibleDirty_;
|
||||
|
||||
// ---- Phase 2: Combat ----
|
||||
bool autoAttacking = false;
|
||||
uint64_t autoAttackTarget = 0;
|
||||
|
|
@ -1081,6 +1100,7 @@ private:
|
|||
CreatureDespawnCallback creatureDespawnCallback_;
|
||||
PlayerSpawnCallback playerSpawnCallback_;
|
||||
PlayerDespawnCallback playerDespawnCallback_;
|
||||
PlayerEquipmentCallback playerEquipmentCallback_;
|
||||
CreatureMoveCallback creatureMoveCallback_;
|
||||
TransportMoveCallback transportMoveCallback_;
|
||||
TransportSpawnCallback transportSpawnCallback_;
|
||||
|
|
|
|||
|
|
@ -1214,6 +1214,13 @@ void Application::setupUICallbacks() {
|
|||
pendingPlayerSpawnGuids_.insert(guid);
|
||||
});
|
||||
|
||||
// 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) {
|
||||
setOnlinePlayerEquipment(guid, displayInfoIds, inventoryTypes);
|
||||
});
|
||||
|
||||
// Creature despawn callback (online mode) - remove creature models
|
||||
gameHandler->setCreatureDespawnCallback([this](uint64_t guid) {
|
||||
despawnOnlineCreature(guid);
|
||||
|
|
@ -3742,6 +3749,172 @@ void Application::spawnOnlinePlayer(uint64_t guid,
|
|||
|
||||
charRenderer->playAnimation(instanceId, 0, true);
|
||||
playerInstances_[guid] = instanceId;
|
||||
|
||||
OnlinePlayerAppearanceState st;
|
||||
st.instanceId = instanceId;
|
||||
st.modelId = modelId;
|
||||
st.raceId = raceId;
|
||||
st.genderId = genderId;
|
||||
st.appearanceBytes = appearanceBytes;
|
||||
st.facialFeatures = facialFeatures;
|
||||
st.bodySkinPath = bodySkinPath;
|
||||
st.underwearPaths = underwearPaths;
|
||||
onlinePlayerAppearance_[guid] = std::move(st);
|
||||
}
|
||||
|
||||
void Application::setOnlinePlayerEquipment(uint64_t guid,
|
||||
const std::array<uint32_t, 19>& displayInfoIds,
|
||||
const std::array<uint8_t, 19>& inventoryTypes) {
|
||||
if (!renderer || !renderer->getCharacterRenderer() || !assetManager || !assetManager->isInitialized()) return;
|
||||
|
||||
// If the player isn't spawned yet, store equipment until spawn.
|
||||
if (!playerInstances_.count(guid) || !onlinePlayerAppearance_.count(guid)) {
|
||||
pendingOnlinePlayerEquipment_[guid] = {displayInfoIds, inventoryTypes};
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = onlinePlayerAppearance_.find(guid);
|
||||
if (it == onlinePlayerAppearance_.end()) return;
|
||||
const OnlinePlayerAppearanceState& st = it->second;
|
||||
|
||||
auto* charRenderer = renderer->getCharacterRenderer();
|
||||
if (!charRenderer) return;
|
||||
if (st.instanceId == 0 || st.modelId == 0) return;
|
||||
|
||||
if (st.bodySkinPath.empty()) return;
|
||||
|
||||
auto displayInfoDbc = assetManager->loadDBC("ItemDisplayInfo.dbc");
|
||||
if (!displayInfoDbc) return;
|
||||
const auto* idiL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("ItemDisplayInfo") : nullptr;
|
||||
|
||||
auto getGeosetGroup = [&](uint32_t displayInfoId, uint32_t fieldIdx) -> uint32_t {
|
||||
if (displayInfoId == 0) return 0;
|
||||
int32_t recIdx = displayInfoDbc->findRecordById(displayInfoId);
|
||||
if (recIdx < 0) return 0;
|
||||
return displayInfoDbc->getUInt32(static_cast<uint32_t>(recIdx), fieldIdx);
|
||||
};
|
||||
|
||||
auto findDisplayIdByInvType = [&](std::initializer_list<uint8_t> types) -> uint32_t {
|
||||
for (int s = 0; s < 19; s++) {
|
||||
uint8_t inv = inventoryTypes[s];
|
||||
if (inv == 0 || displayInfoIds[s] == 0) continue;
|
||||
for (uint8_t t : types) {
|
||||
if (inv == t) return displayInfoIds[s];
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
auto hasInvType = [&](std::initializer_list<uint8_t> types) -> bool {
|
||||
for (int s = 0; s < 19; s++) {
|
||||
uint8_t inv = inventoryTypes[s];
|
||||
if (inv == 0) continue;
|
||||
for (uint8_t t : types) {
|
||||
if (inv == t) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// --- Geosets ---
|
||||
std::unordered_set<uint16_t> geosets;
|
||||
for (uint16_t i = 0; i <= 18; i++) geosets.insert(i);
|
||||
|
||||
uint8_t hairStyleId = static_cast<uint8_t>((st.appearanceBytes >> 16) & 0xFF);
|
||||
geosets.insert(static_cast<uint16_t>(100 + hairStyleId + 1));
|
||||
geosets.insert(static_cast<uint16_t>(200 + st.facialFeatures + 1));
|
||||
geosets.insert(701);
|
||||
|
||||
const uint32_t geosetGroup1Field = idiL ? (*idiL)["GeosetGroup1"] : 7;
|
||||
const uint32_t geosetGroup3Field = idiL ? (*idiL)["GeosetGroup3"] : 9;
|
||||
|
||||
// Chest/Shirt/Robe (invType 4,5,20)
|
||||
{
|
||||
uint32_t did = findDisplayIdByInvType({4, 5, 20});
|
||||
uint32_t gg1 = getGeosetGroup(did, geosetGroup1Field);
|
||||
geosets.insert(static_cast<uint16_t>(gg1 > 0 ? 501 + gg1 : 501));
|
||||
|
||||
uint32_t gg3 = getGeosetGroup(did, geosetGroup3Field);
|
||||
if (gg3 > 0) geosets.insert(static_cast<uint16_t>(1301 + gg3));
|
||||
}
|
||||
|
||||
// Legs (invType 7)
|
||||
{
|
||||
uint32_t did = findDisplayIdByInvType({7});
|
||||
uint32_t gg1 = getGeosetGroup(did, geosetGroup1Field);
|
||||
if (geosets.count(1302) == 0 && geosets.count(1303) == 0) {
|
||||
geosets.insert(static_cast<uint16_t>(gg1 > 0 ? 1301 + gg1 : 1301));
|
||||
}
|
||||
}
|
||||
|
||||
// Feet (invType 8)
|
||||
{
|
||||
uint32_t did = findDisplayIdByInvType({8});
|
||||
uint32_t gg1 = getGeosetGroup(did, geosetGroup1Field);
|
||||
geosets.insert(static_cast<uint16_t>(gg1 > 0 ? 401 + gg1 : 401));
|
||||
}
|
||||
|
||||
// Hands (invType 10)
|
||||
{
|
||||
uint32_t did = findDisplayIdByInvType({10});
|
||||
uint32_t gg1 = getGeosetGroup(did, geosetGroup1Field);
|
||||
geosets.insert(static_cast<uint16_t>(gg1 > 0 ? 301 + gg1 : 301));
|
||||
}
|
||||
|
||||
// Back/Cloak (invType 16)
|
||||
geosets.insert(hasInvType({16}) ? 1502 : 1501);
|
||||
// Tabard (invType 19)
|
||||
if (hasInvType({19})) geosets.insert(1201);
|
||||
|
||||
charRenderer->setActiveGeosets(st.instanceId, geosets);
|
||||
|
||||
// --- Textures (skin atlas compositing) ---
|
||||
static const char* componentDirs[] = {
|
||||
"ArmUpperTexture",
|
||||
"ArmLowerTexture",
|
||||
"HandTexture",
|
||||
"TorsoUpperTexture",
|
||||
"TorsoLowerTexture",
|
||||
"LegUpperTexture",
|
||||
"LegLowerTexture",
|
||||
"FootTexture",
|
||||
};
|
||||
|
||||
std::vector<std::pair<int, std::string>> regionLayers;
|
||||
const bool isFemale = (st.genderId == 1);
|
||||
|
||||
for (int s = 0; s < 19; s++) {
|
||||
uint32_t did = displayInfoIds[s];
|
||||
if (did == 0) continue;
|
||||
int32_t recIdx = displayInfoDbc->findRecordById(did);
|
||||
if (recIdx < 0) continue;
|
||||
|
||||
for (int region = 0; region < 8; region++) {
|
||||
std::string texName = displayInfoDbc->getString(static_cast<uint32_t>(recIdx), 14 + region);
|
||||
if (texName.empty()) texName = displayInfoDbc->getString(static_cast<uint32_t>(recIdx), 15 + region);
|
||||
if (texName.empty()) continue;
|
||||
|
||||
std::string base = "Item\\TextureComponents\\" + std::string(componentDirs[region]) + "\\" + texName;
|
||||
std::string genderPath = base + (isFemale ? "_F.blp" : "_M.blp");
|
||||
std::string unisexPath = base + "_U.blp";
|
||||
std::string fullPath;
|
||||
if (assetManager->fileExists(genderPath)) fullPath = genderPath;
|
||||
else if (assetManager->fileExists(unisexPath)) fullPath = unisexPath;
|
||||
else fullPath = base + ".blp";
|
||||
|
||||
regionLayers.emplace_back(region, fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
const auto slotsIt = playerTextureSlotsByModelId_.find(st.modelId);
|
||||
if (slotsIt == playerTextureSlotsByModelId_.end()) return;
|
||||
const PlayerTextureSlots& slots = slotsIt->second;
|
||||
if (slots.skin < 0) return;
|
||||
|
||||
GLuint newTex = charRenderer->compositeWithRegions(st.bodySkinPath, st.underwearPaths, regionLayers);
|
||||
if (newTex != 0) {
|
||||
charRenderer->setTextureSlotOverride(st.instanceId, static_cast<uint16_t>(slots.skin), newTex);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::despawnOnlinePlayer(uint64_t guid) {
|
||||
|
|
@ -3750,6 +3923,8 @@ void Application::despawnOnlinePlayer(uint64_t guid) {
|
|||
if (it == playerInstances_.end()) return;
|
||||
renderer->getCharacterRenderer()->removeInstance(it->second);
|
||||
playerInstances_.erase(it);
|
||||
onlinePlayerAppearance_.erase(guid);
|
||||
pendingOnlinePlayerEquipment_.erase(guid);
|
||||
}
|
||||
|
||||
void Application::spawnOnlineGameObject(uint64_t guid, uint32_t entry, uint32_t displayId, float x, float y, float z, float orientation) {
|
||||
|
|
@ -4099,6 +4274,12 @@ void Application::processPlayerSpawnQueue() {
|
|||
}
|
||||
|
||||
spawnOnlinePlayer(s.guid, s.raceId, s.genderId, s.appearanceBytes, s.facialFeatures, s.x, s.y, s.z, s.orientation);
|
||||
// Apply any equipment updates that arrived before the player was spawned.
|
||||
auto pit = pendingOnlinePlayerEquipment_.find(s.guid);
|
||||
if (pit != pendingOnlinePlayerEquipment_.end()) {
|
||||
setOnlinePlayerEquipment(s.guid, pit->second.first, pit->second.second);
|
||||
pendingOnlinePlayerEquipment_.erase(pit);
|
||||
}
|
||||
processed++;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2844,6 +2844,8 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
creatureDespawnCallback_(guid);
|
||||
} else if (entity->getType() == ObjectType::PLAYER && playerDespawnCallback_) {
|
||||
playerDespawnCallback_(guid);
|
||||
otherPlayerVisibleItemEntries_.erase(guid);
|
||||
otherPlayerVisibleDirty_.erase(guid);
|
||||
} else if (entity->getType() == ObjectType::GAMEOBJECT && gameObjectDespawnCallback_) {
|
||||
gameObjectDespawnCallback_(guid);
|
||||
}
|
||||
|
|
@ -2949,6 +2951,9 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
// Auto-query names (Phase 1)
|
||||
if (block.objectType == ObjectType::PLAYER) {
|
||||
queryPlayerName(block.guid);
|
||||
if (block.guid != playerGuid) {
|
||||
updateOtherPlayerVisibleItems(block.guid, entity->getFields());
|
||||
}
|
||||
} else if (block.objectType == ObjectType::UNIT) {
|
||||
auto it = block.fields.find(fieldIndex(UF::OBJECT_FIELD_ENTRY));
|
||||
if (it != block.fields.end() && it->second != 0) {
|
||||
|
|
@ -3287,6 +3292,10 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
entity->setField(field.first, field.second);
|
||||
}
|
||||
|
||||
if (entity->getType() == ObjectType::PLAYER && block.guid != playerGuid) {
|
||||
updateOtherPlayerVisibleItems(block.guid, entity->getFields());
|
||||
}
|
||||
|
||||
// Update cached health/mana/power values (Phase 2) — single pass
|
||||
if (entity->getType() == ObjectType::UNIT || entity->getType() == ObjectType::PLAYER) {
|
||||
auto unit = std::static_pointer_cast<Unit>(entity);
|
||||
|
|
@ -3425,6 +3434,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
lastPlayerFields_[key] = val;
|
||||
}
|
||||
maybeDetectCoinageIndex(oldFieldsSnapshot, lastPlayerFields_);
|
||||
maybeDetectVisibleItemLayout();
|
||||
detectInventorySlotBases(block.fields);
|
||||
bool slotsChanged = false;
|
||||
const uint16_t ufPlayerXp = fieldIndex(UF::PLAYER_XP);
|
||||
|
|
@ -4966,6 +4976,8 @@ void GameHandler::handleItemQueryResponse(network::Packet& packet) {
|
|||
if (data.valid) {
|
||||
itemInfoCache_[data.entry] = data;
|
||||
rebuildOnlineInventory();
|
||||
maybeDetectVisibleItemLayout();
|
||||
emitAllOtherPlayerEquipment();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -5180,6 +5192,122 @@ void GameHandler::rebuildOnlineInventory() {
|
|||
}());
|
||||
}
|
||||
|
||||
void GameHandler::maybeDetectVisibleItemLayout() {
|
||||
if (visibleItemEntryBase_ >= 0) return;
|
||||
if (lastPlayerFields_.empty()) return;
|
||||
|
||||
std::array<uint32_t, 19> equipEntries{};
|
||||
int nonZero = 0;
|
||||
for (int i = 0; i < 19; i++) {
|
||||
const auto& slot = inventory.getEquipSlot(static_cast<EquipSlot>(i));
|
||||
equipEntries[i] = slot.empty() ? 0u : slot.item.itemId;
|
||||
if (equipEntries[i] != 0) nonZero++;
|
||||
}
|
||||
if (nonZero < 2) return;
|
||||
|
||||
const uint16_t maxKey = lastPlayerFields_.rbegin()->first;
|
||||
int bestBase = -1;
|
||||
int bestStride = 0;
|
||||
int bestMatches = 0;
|
||||
|
||||
const int strides[] = {2, 3, 4, 1};
|
||||
for (int stride : strides) {
|
||||
for (const auto& [baseIdxU16, _v] : lastPlayerFields_) {
|
||||
const int base = static_cast<int>(baseIdxU16);
|
||||
if (base + 18 * stride > static_cast<int>(maxKey)) continue;
|
||||
|
||||
int matches = 0;
|
||||
for (int s = 0; s < 19; s++) {
|
||||
uint32_t want = equipEntries[s];
|
||||
if (want == 0) continue;
|
||||
const uint16_t idx = static_cast<uint16_t>(base + s * stride);
|
||||
auto it = lastPlayerFields_.find(idx);
|
||||
if (it != lastPlayerFields_.end() && it->second == want) matches++;
|
||||
}
|
||||
|
||||
if (matches > bestMatches || (matches == bestMatches && matches > 0 && base < bestBase)) {
|
||||
bestMatches = matches;
|
||||
bestBase = base;
|
||||
bestStride = stride;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestMatches < 2 || bestBase < 0 || bestStride <= 0) return;
|
||||
|
||||
visibleItemEntryBase_ = bestBase;
|
||||
visibleItemStride_ = bestStride;
|
||||
LOG_INFO("Detected PLAYER_VISIBLE_ITEM entry layout: base=", visibleItemEntryBase_,
|
||||
" stride=", visibleItemStride_, " (matches=", bestMatches, ")");
|
||||
|
||||
// Backfill existing player entities already in view.
|
||||
for (const auto& [guid, ent] : entityManager.getEntities()) {
|
||||
if (!ent || ent->getType() != ObjectType::PLAYER) continue;
|
||||
if (guid == playerGuid) continue;
|
||||
updateOtherPlayerVisibleItems(guid, ent->getFields());
|
||||
}
|
||||
}
|
||||
|
||||
void GameHandler::updateOtherPlayerVisibleItems(uint64_t guid, const std::map<uint16_t, uint32_t>& fields) {
|
||||
if (guid == 0 || guid == playerGuid) return;
|
||||
if (visibleItemEntryBase_ < 0 || visibleItemStride_ <= 0) return;
|
||||
|
||||
std::array<uint32_t, 19> newEntries{};
|
||||
for (int s = 0; s < 19; s++) {
|
||||
uint16_t idx = static_cast<uint16_t>(visibleItemEntryBase_ + s * visibleItemStride_);
|
||||
auto it = fields.find(idx);
|
||||
if (it != fields.end()) newEntries[s] = it->second;
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
auto& old = otherPlayerVisibleItemEntries_[guid];
|
||||
if (old != newEntries) {
|
||||
old = newEntries;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Request item templates for any new visible entries.
|
||||
for (uint32_t entry : newEntries) {
|
||||
if (entry == 0) continue;
|
||||
if (!itemInfoCache_.count(entry) && !pendingItemQueries_.count(entry)) {
|
||||
queryItemInfo(entry, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
otherPlayerVisibleDirty_.insert(guid);
|
||||
emitOtherPlayerEquipment(guid);
|
||||
}
|
||||
}
|
||||
|
||||
void GameHandler::emitOtherPlayerEquipment(uint64_t guid) {
|
||||
if (!playerEquipmentCallback_) return;
|
||||
auto it = otherPlayerVisibleItemEntries_.find(guid);
|
||||
if (it == otherPlayerVisibleItemEntries_.end()) return;
|
||||
|
||||
std::array<uint32_t, 19> displayIds{};
|
||||
std::array<uint8_t, 19> invTypes{};
|
||||
|
||||
for (int s = 0; s < 19; s++) {
|
||||
uint32_t entry = it->second[s];
|
||||
if (entry == 0) continue;
|
||||
auto infoIt = itemInfoCache_.find(entry);
|
||||
if (infoIt == itemInfoCache_.end()) continue;
|
||||
displayIds[s] = infoIt->second.displayInfoId;
|
||||
invTypes[s] = static_cast<uint8_t>(infoIt->second.inventoryType);
|
||||
}
|
||||
|
||||
playerEquipmentCallback_(guid, displayIds, invTypes);
|
||||
otherPlayerVisibleDirty_.erase(guid);
|
||||
}
|
||||
|
||||
void GameHandler::emitAllOtherPlayerEquipment() {
|
||||
if (!playerEquipmentCallback_) return;
|
||||
for (const auto& [guid, _] : otherPlayerVisibleItemEntries_) {
|
||||
emitOtherPlayerEquipment(guid);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Phase 2: Combat
|
||||
// ============================================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue