feat: propagate OBJECT_FIELD_SCALE_X through creature and GO spawn pipeline

Reads OBJECT_FIELD_SCALE_X (field 4, cross-expansion) from CREATE_OBJECT
update fields and passes it through the full creature and game object spawn
chain: game_handler callbacks → pending spawn structs → async load results
→ createInstance() calls. This gives boss giants, gnomes, children, and
other non-unit-scale NPCs correct visual size, and ensures scaled GOs
(e.g. large treasure chests, oversized plants) render at the server-specified
scale rather than always at 1.0.

- Added OBJECT_FIELD_SCALE_X to UF enum and all expansion update_fields.json
- Added float scale to CreatureSpawnCallback and GameObjectSpawnCallback
- Propagated scale through PendingCreatureSpawn, PreparedCreatureModel,
  PendingGameObjectSpawn, PreparedGameObjectWMO
- Used scale in charRenderer/m2Renderer/wmoRenderer createInstance() calls
- Sanity-clamped raw float to [0.01, 100.0] range before use
This commit is contained in:
Kelsi 2026-03-10 22:45:47 -07:00
parent b658743e94
commit d95abfb607
10 changed files with 85 additions and 24 deletions

View file

@ -1,5 +1,6 @@
{ {
"OBJECT_FIELD_ENTRY": 3, "OBJECT_FIELD_ENTRY": 3,
"OBJECT_FIELD_SCALE_X": 4,
"UNIT_FIELD_TARGET_LO": 16, "UNIT_FIELD_TARGET_LO": 16,
"UNIT_FIELD_TARGET_HI": 17, "UNIT_FIELD_TARGET_HI": 17,
"UNIT_FIELD_BYTES_0": 36, "UNIT_FIELD_BYTES_0": 36,

View file

@ -1,5 +1,6 @@
{ {
"OBJECT_FIELD_ENTRY": 3, "OBJECT_FIELD_ENTRY": 3,
"OBJECT_FIELD_SCALE_X": 4,
"UNIT_FIELD_TARGET_LO": 16, "UNIT_FIELD_TARGET_LO": 16,
"UNIT_FIELD_TARGET_HI": 17, "UNIT_FIELD_TARGET_HI": 17,
"UNIT_FIELD_BYTES_0": 36, "UNIT_FIELD_BYTES_0": 36,

View file

@ -1,5 +1,6 @@
{ {
"OBJECT_FIELD_ENTRY": 3, "OBJECT_FIELD_ENTRY": 3,
"OBJECT_FIELD_SCALE_X": 4,
"UNIT_FIELD_TARGET_LO": 16, "UNIT_FIELD_TARGET_LO": 16,
"UNIT_FIELD_TARGET_HI": 17, "UNIT_FIELD_TARGET_HI": 17,
"UNIT_FIELD_BYTES_0": 36, "UNIT_FIELD_BYTES_0": 36,

View file

@ -1,5 +1,6 @@
{ {
"OBJECT_FIELD_ENTRY": 3, "OBJECT_FIELD_ENTRY": 3,
"OBJECT_FIELD_SCALE_X": 4,
"UNIT_FIELD_TARGET_LO": 6, "UNIT_FIELD_TARGET_LO": 6,
"UNIT_FIELD_TARGET_HI": 7, "UNIT_FIELD_TARGET_HI": 7,
"UNIT_FIELD_BYTES_0": 23, "UNIT_FIELD_BYTES_0": 23,

View file

@ -98,7 +98,7 @@ private:
void loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float z); void loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float z);
void buildFactionHostilityMap(uint8_t playerRace); void buildFactionHostilityMap(uint8_t playerRace);
pipeline::M2Model loadCreatureM2Sync(const std::string& m2Path); pipeline::M2Model loadCreatureM2Sync(const std::string& m2Path);
void spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x, float y, float z, float orientation); void spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x, float y, float z, float orientation, float scale = 1.0f);
void despawnOnlineCreature(uint64_t guid); void despawnOnlineCreature(uint64_t guid);
bool tryAttachCreatureVirtualWeapons(uint64_t guid, uint32_t instanceId); bool tryAttachCreatureVirtualWeapons(uint64_t guid, uint32_t instanceId);
void spawnOnlinePlayer(uint64_t guid, void spawnOnlinePlayer(uint64_t guid,
@ -113,7 +113,7 @@ private:
void despawnOnlinePlayer(uint64_t guid); void despawnOnlinePlayer(uint64_t guid);
void buildCreatureDisplayLookups(); void buildCreatureDisplayLookups();
std::string getModelPathForDisplayId(uint32_t displayId) const; std::string getModelPathForDisplayId(uint32_t displayId) const;
void spawnOnlineGameObject(uint64_t guid, uint32_t entry, uint32_t displayId, float x, float y, float z, float orientation); void spawnOnlineGameObject(uint64_t guid, uint32_t entry, uint32_t displayId, float x, float y, float z, float orientation, float scale = 1.0f);
void despawnOnlineGameObject(uint64_t guid); void despawnOnlineGameObject(uint64_t guid);
void buildGameObjectDisplayLookups(); void buildGameObjectDisplayLookups();
std::string getGameObjectModelPathForDisplayId(uint32_t displayId) const; std::string getGameObjectModelPathForDisplayId(uint32_t displayId) const;
@ -214,6 +214,7 @@ private:
uint32_t displayId; uint32_t displayId;
uint32_t modelId; uint32_t modelId;
float x, y, z, orientation; float x, y, z, orientation;
float scale = 1.0f;
std::shared_ptr<pipeline::M2Model> model; // parsed on background thread std::shared_ptr<pipeline::M2Model> model; // parsed on background thread
std::unordered_map<std::string, pipeline::BLPImage> predecodedTextures; // decoded on bg thread std::unordered_map<std::string, pipeline::BLPImage> predecodedTextures; // decoded on bg thread
bool valid = false; bool valid = false;
@ -300,6 +301,7 @@ private:
uint64_t guid; uint64_t guid;
uint32_t displayId; uint32_t displayId;
float x, y, z, orientation; float x, y, z, orientation;
float scale = 1.0f;
}; };
std::deque<PendingCreatureSpawn> pendingCreatureSpawns_; std::deque<PendingCreatureSpawn> pendingCreatureSpawns_;
static constexpr int MAX_SPAWNS_PER_FRAME = 3; static constexpr int MAX_SPAWNS_PER_FRAME = 3;
@ -393,6 +395,7 @@ private:
uint32_t entry; uint32_t entry;
uint32_t displayId; uint32_t displayId;
float x, y, z, orientation; float x, y, z, orientation;
float scale = 1.0f;
}; };
std::vector<PendingGameObjectSpawn> pendingGameObjectSpawns_; std::vector<PendingGameObjectSpawn> pendingGameObjectSpawns_;
void processGameObjectSpawnQueue(); void processGameObjectSpawnQueue();
@ -403,6 +406,7 @@ private:
uint32_t entry; uint32_t entry;
uint32_t displayId; uint32_t displayId;
float x, y, z, orientation; float x, y, z, orientation;
float scale = 1.0f;
std::shared_ptr<pipeline::WMOModel> wmoModel; std::shared_ptr<pipeline::WMOModel> wmoModel;
std::unordered_map<std::string, pipeline::BLPImage> predecodedTextures; // decoded on bg thread std::unordered_map<std::string, pipeline::BLPImage> predecodedTextures; // decoded on bg thread
bool valid = false; bool valid = false;

View file

@ -735,8 +735,8 @@ public:
void setHearthstonePreloadCallback(HearthstonePreloadCallback cb) { hearthstonePreloadCallback_ = std::move(cb); } void setHearthstonePreloadCallback(HearthstonePreloadCallback cb) { hearthstonePreloadCallback_ = std::move(cb); }
// Creature spawn callback (online mode - triggered when creature enters view) // Creature spawn callback (online mode - triggered when creature enters view)
// Parameters: guid, displayId, x, y, z (canonical), orientation // Parameters: guid, displayId, x, y, z (canonical), orientation, scale (OBJECT_FIELD_SCALE_X)
using CreatureSpawnCallback = std::function<void(uint64_t guid, uint32_t displayId, float x, float y, float z, float orientation)>; using CreatureSpawnCallback = std::function<void(uint64_t guid, uint32_t displayId, float x, float y, float z, float orientation, float scale)>;
void setCreatureSpawnCallback(CreatureSpawnCallback cb) { creatureSpawnCallback_ = std::move(cb); } void setCreatureSpawnCallback(CreatureSpawnCallback cb) { creatureSpawnCallback_ = std::move(cb); }
// Creature despawn callback (online mode - triggered when creature leaves view) // Creature despawn callback (online mode - triggered when creature leaves view)
@ -766,8 +766,8 @@ public:
void setPlayerEquipmentCallback(PlayerEquipmentCallback cb) { playerEquipmentCallback_ = std::move(cb); } void setPlayerEquipmentCallback(PlayerEquipmentCallback cb) { playerEquipmentCallback_ = std::move(cb); }
// GameObject spawn callback (online mode - triggered when gameobject enters view) // GameObject spawn callback (online mode - triggered when gameobject enters view)
// Parameters: guid, entry, displayId, x, y, z (canonical), orientation // Parameters: guid, entry, displayId, x, y, z (canonical), orientation, scale (OBJECT_FIELD_SCALE_X)
using GameObjectSpawnCallback = std::function<void(uint64_t guid, uint32_t entry, uint32_t displayId, float x, float y, float z, float orientation)>; using GameObjectSpawnCallback = std::function<void(uint64_t guid, uint32_t entry, uint32_t displayId, float x, float y, float z, float orientation, float scale)>;
void setGameObjectSpawnCallback(GameObjectSpawnCallback cb) { gameObjectSpawnCallback_ = std::move(cb); } void setGameObjectSpawnCallback(GameObjectSpawnCallback cb) { gameObjectSpawnCallback_ = std::move(cb); }
// GameObject move callback (online mode - triggered when gameobject position updates) // GameObject move callback (online mode - triggered when gameobject position updates)

View file

@ -14,6 +14,7 @@ namespace game {
enum class UF : uint16_t { enum class UF : uint16_t {
// Object fields // Object fields
OBJECT_FIELD_ENTRY, OBJECT_FIELD_ENTRY,
OBJECT_FIELD_SCALE_X,
// Unit fields // Unit fields
UNIT_FIELD_TARGET_LO, UNIT_FIELD_TARGET_LO,

View file

@ -985,6 +985,18 @@ void Application::update(float deltaTime) {
retrySpawn.y = unit->getY(); retrySpawn.y = unit->getY();
retrySpawn.z = unit->getZ(); retrySpawn.z = unit->getZ();
retrySpawn.orientation = unit->getOrientation(); retrySpawn.orientation = unit->getOrientation();
{
using game::fieldIndex; using game::UF;
uint16_t si = fieldIndex(UF::OBJECT_FIELD_SCALE_X);
if (si != 0xFFFF) {
uint32_t raw = unit->getField(si);
if (raw != 0) {
float s2 = 1.0f;
std::memcpy(&s2, &raw, sizeof(float));
if (s2 > 0.01f && s2 < 100.0f) retrySpawn.scale = s2;
}
}
}
pendingCreatureSpawns_.push_back(retrySpawn); pendingCreatureSpawns_.push_back(retrySpawn);
pendingCreatureSpawnGuids_.insert(guid); pendingCreatureSpawnGuids_.insert(guid);
} }
@ -2198,12 +2210,12 @@ void Application::setupUICallbacks() {
// Faction hostility map is built in buildFactionHostilityMap() when character enters world // Faction hostility map is built in buildFactionHostilityMap() when character enters world
// Creature spawn callback (online mode) - spawn creature models // 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) { 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. // Queue spawns to avoid hanging when many creatures appear at once.
// Deduplicate so repeated updates don't flood pending queue. // Deduplicate so repeated updates don't flood pending queue.
if (creatureInstances_.count(guid)) return; if (creatureInstances_.count(guid)) return;
if (pendingCreatureSpawnGuids_.count(guid)) return; if (pendingCreatureSpawnGuids_.count(guid)) return;
pendingCreatureSpawns_.push_back({guid, displayId, x, y, z, orientation}); pendingCreatureSpawns_.push_back({guid, displayId, x, y, z, orientation, scale});
pendingCreatureSpawnGuids_.insert(guid); pendingCreatureSpawnGuids_.insert(guid);
}); });
@ -2249,8 +2261,8 @@ void Application::setupUICallbacks() {
}); });
// GameObject spawn callback (online mode) - spawn static models (mailboxes, etc.) // 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) { gameHandler->setGameObjectSpawnCallback([this](uint64_t guid, uint32_t entry, uint32_t displayId, float x, float y, float z, float orientation, float scale) {
pendingGameObjectSpawns_.push_back({guid, entry, displayId, x, y, z, orientation}); pendingGameObjectSpawns_.push_back({guid, entry, displayId, x, y, z, orientation, scale});
}); });
// GameObject despawn callback (online mode) - remove static models // GameObject despawn callback (online mode) - remove static models
@ -4754,7 +4766,7 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
// Process ALL pending game object spawns. // Process ALL pending game object spawns.
while (!pendingGameObjectSpawns_.empty()) { while (!pendingGameObjectSpawns_.empty()) {
auto& s = pendingGameObjectSpawns_.front(); auto& s = pendingGameObjectSpawns_.front();
spawnOnlineGameObject(s.guid, s.entry, s.displayId, s.x, s.y, s.z, s.orientation); spawnOnlineGameObject(s.guid, s.entry, s.displayId, s.x, s.y, s.z, s.orientation, s.scale);
pendingGameObjectSpawns_.erase(pendingGameObjectSpawns_.begin()); pendingGameObjectSpawns_.erase(pendingGameObjectSpawns_.begin());
} }
@ -5285,7 +5297,7 @@ pipeline::M2Model Application::loadCreatureM2Sync(const std::string& m2Path) {
return model; return model;
} }
void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x, float y, float z, float orientation) { void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x, float y, float z, float orientation, float scale) {
if (!renderer || !renderer->getCharacterRenderer() || !assetManager) return; if (!renderer || !renderer->getCharacterRenderer() || !assetManager) return;
// Skip if lookups not yet built (asset manager not ready) // Skip if lookups not yet built (asset manager not ready)
@ -5722,9 +5734,9 @@ void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x
// Convert canonical WoW orientation (0=north) -> render yaw (0=west) // Convert canonical WoW orientation (0=north) -> render yaw (0=west)
float renderYaw = orientation + glm::radians(90.0f); float renderYaw = orientation + glm::radians(90.0f);
// Create instance // Create instance (apply server-provided scale from OBJECT_FIELD_SCALE_X)
uint32_t instanceId = charRenderer->createInstance(modelId, renderPos, uint32_t instanceId = charRenderer->createInstance(modelId, renderPos,
glm::vec3(0.0f, 0.0f, renderYaw), 1.0f); glm::vec3(0.0f, 0.0f, renderYaw), scale);
if (instanceId == 0) { if (instanceId == 0) {
LOG_WARNING("Failed to create creature instance for guid 0x", std::hex, guid, std::dec); LOG_WARNING("Failed to create creature instance for guid 0x", std::hex, guid, std::dec);
@ -7035,7 +7047,7 @@ void Application::despawnOnlinePlayer(uint64_t guid) {
creatureWasWalking_.erase(guid); creatureWasWalking_.erase(guid);
} }
void Application::spawnOnlineGameObject(uint64_t guid, uint32_t entry, uint32_t displayId, float x, float y, float z, float orientation) { void Application::spawnOnlineGameObject(uint64_t guid, uint32_t entry, uint32_t displayId, float x, float y, float z, float orientation, float scale) {
if (!renderer || !assetManager) return; if (!renderer || !assetManager) return;
if (!gameObjectLookupsBuilt_) { if (!gameObjectLookupsBuilt_) {
@ -7192,7 +7204,7 @@ void Application::spawnOnlineGameObject(uint64_t guid, uint32_t entry, uint32_t
if (loadedAsWmo) { if (loadedAsWmo) {
uint32_t instanceId = wmoRenderer->createInstance(modelId, renderPos, uint32_t instanceId = wmoRenderer->createInstance(modelId, renderPos,
glm::vec3(0.0f, 0.0f, renderYawWmo), 1.0f); glm::vec3(0.0f, 0.0f, renderYawWmo), scale);
if (instanceId == 0) { if (instanceId == 0) {
LOG_WARNING("Failed to create gameobject WMO instance for guid 0x", std::hex, guid, std::dec); LOG_WARNING("Failed to create gameobject WMO instance for guid 0x", std::hex, guid, std::dec);
return; return;
@ -7300,7 +7312,7 @@ void Application::spawnOnlineGameObject(uint64_t guid, uint32_t entry, uint32_t
} }
uint32_t instanceId = m2Renderer->createInstance(modelId, renderPos, uint32_t instanceId = m2Renderer->createInstance(modelId, renderPos,
glm::vec3(0.0f, 0.0f, renderYawM2go), 1.0f); glm::vec3(0.0f, 0.0f, renderYawM2go), scale);
if (instanceId == 0) { if (instanceId == 0) {
LOG_WARNING("Failed to create gameobject instance for guid 0x", std::hex, guid, std::dec); LOG_WARNING("Failed to create gameobject instance for guid 0x", std::hex, guid, std::dec);
return; return;
@ -7418,6 +7430,7 @@ void Application::processAsyncCreatureResults(bool unlimited) {
s.y = result.y; s.y = result.y;
s.z = result.z; s.z = result.z;
s.orientation = result.orientation; s.orientation = result.orientation;
s.scale = result.scale;
pendingCreatureSpawns_.push_back(s); pendingCreatureSpawns_.push_back(s);
pendingCreatureSpawnGuids_.insert(result.guid); pendingCreatureSpawnGuids_.insert(result.guid);
} }
@ -7732,6 +7745,7 @@ void Application::processCreatureSpawnQueue(bool unlimited) {
result.y = s.y; result.y = s.y;
result.z = s.z; result.z = s.z;
result.orientation = s.orientation; result.orientation = s.orientation;
result.scale = s.scale;
auto m2Data = am->readFile(m2Path); auto m2Data = am->readFile(m2Path);
if (m2Data.empty()) { if (m2Data.empty()) {
@ -7810,7 +7824,7 @@ void Application::processCreatureSpawnQueue(bool unlimited) {
// Cached model — spawn is fast (no file I/O, just instance creation + texture setup) // Cached model — spawn is fast (no file I/O, just instance creation + texture setup)
{ {
auto spawnStart = std::chrono::steady_clock::now(); auto spawnStart = std::chrono::steady_clock::now();
spawnOnlineCreature(s.guid, s.displayId, s.x, s.y, s.z, s.orientation); spawnOnlineCreature(s.guid, s.displayId, s.x, s.y, s.z, s.orientation, s.scale);
auto spawnEnd = std::chrono::steady_clock::now(); auto spawnEnd = std::chrono::steady_clock::now();
float spawnMs = std::chrono::duration<float, std::milli>(spawnEnd - spawnStart).count(); float spawnMs = std::chrono::duration<float, std::milli>(spawnEnd - spawnStart).count();
if (spawnMs > 100.0f) { if (spawnMs > 100.0f) {
@ -8015,7 +8029,7 @@ void Application::processAsyncGameObjectResults() {
if (!result.valid || !result.isWmo || !result.wmoModel) { if (!result.valid || !result.isWmo || !result.wmoModel) {
// Fallback: spawn via sync path (likely an M2 or failed WMO) // Fallback: spawn via sync path (likely an M2 or failed WMO)
spawnOnlineGameObject(result.guid, result.entry, result.displayId, spawnOnlineGameObject(result.guid, result.entry, result.displayId,
result.x, result.y, result.z, result.orientation); result.x, result.y, result.z, result.orientation, result.scale);
continue; continue;
} }
@ -8042,7 +8056,7 @@ void Application::processAsyncGameObjectResults() {
glm::vec3 renderPos = core::coords::canonicalToRender( glm::vec3 renderPos = core::coords::canonicalToRender(
glm::vec3(result.x, result.y, result.z)); glm::vec3(result.x, result.y, result.z));
uint32_t instanceId = wmoRenderer->createInstance( uint32_t instanceId = wmoRenderer->createInstance(
modelId, renderPos, glm::vec3(0.0f, 0.0f, result.orientation), 1.0f); modelId, renderPos, glm::vec3(0.0f, 0.0f, result.orientation), result.scale);
if (instanceId == 0) continue; if (instanceId == 0) continue;
gameObjectInstances_[result.guid] = {modelId, instanceId, true}; gameObjectInstances_[result.guid] = {modelId, instanceId, true};
@ -8129,6 +8143,7 @@ void Application::processGameObjectSpawnQueue() {
result.y = capture.y; result.y = capture.y;
result.z = capture.z; result.z = capture.z;
result.orientation = capture.orientation; result.orientation = capture.orientation;
result.scale = capture.scale;
result.modelPath = capturePath; result.modelPath = capturePath;
result.isWmo = true; result.isWmo = true;
@ -8194,7 +8209,7 @@ void Application::processGameObjectSpawnQueue() {
} }
// Cached WMO or M2 — spawn synchronously (cheap) // Cached WMO or M2 — spawn synchronously (cheap)
spawnOnlineGameObject(s.guid, s.entry, s.displayId, s.x, s.y, s.z, s.orientation); spawnOnlineGameObject(s.guid, s.entry, s.displayId, s.x, s.y, s.z, s.orientation, s.scale);
pendingGameObjectSpawns_.erase(pendingGameObjectSpawns_.begin()); pendingGameObjectSpawns_.erase(pendingGameObjectSpawns_.begin());
} }
} }

View file

@ -8009,8 +8009,19 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
LOG_DEBUG("[Spawn] UNIT guid=0x", std::hex, block.guid, std::dec, LOG_DEBUG("[Spawn] UNIT guid=0x", std::hex, block.guid, std::dec,
" displayId=", unit->getDisplayId(), " at (", " displayId=", unit->getDisplayId(), " at (",
unit->getX(), ",", unit->getY(), ",", unit->getZ(), ")"); unit->getX(), ",", unit->getY(), ",", unit->getZ(), ")");
float unitScale = 1.0f;
{
uint16_t scaleIdx = fieldIndex(UF::OBJECT_FIELD_SCALE_X);
if (scaleIdx != 0xFFFF) {
uint32_t raw = entity->getField(scaleIdx);
if (raw != 0) {
std::memcpy(&unitScale, &raw, sizeof(float));
if (unitScale <= 0.01f || unitScale > 100.0f) unitScale = 1.0f;
}
}
}
creatureSpawnCallback_(block.guid, unit->getDisplayId(), creatureSpawnCallback_(block.guid, unit->getDisplayId(),
unit->getX(), unit->getY(), unit->getZ(), unit->getOrientation()); unit->getX(), unit->getY(), unit->getZ(), unit->getOrientation(), unitScale);
if (unitInitiallyDead && npcDeathCallback_) { if (unitInitiallyDead && npcDeathCallback_) {
npcDeathCallback_(block.guid); npcDeathCallback_(block.guid);
} }
@ -8060,8 +8071,19 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
// Note: TransportSpawnCallback will be invoked from Application after WMO instance is created // Note: TransportSpawnCallback will be invoked from Application after WMO instance is created
} }
if (go->getDisplayId() != 0 && gameObjectSpawnCallback_) { if (go->getDisplayId() != 0 && gameObjectSpawnCallback_) {
float goScale = 1.0f;
{
uint16_t scaleIdx = fieldIndex(UF::OBJECT_FIELD_SCALE_X);
if (scaleIdx != 0xFFFF) {
uint32_t raw = entity->getField(scaleIdx);
if (raw != 0) {
std::memcpy(&goScale, &raw, sizeof(float));
if (goScale <= 0.01f || goScale > 100.0f) goScale = 1.0f;
}
}
}
gameObjectSpawnCallback_(block.guid, go->getEntry(), go->getDisplayId(), gameObjectSpawnCallback_(block.guid, go->getEntry(), go->getDisplayId(),
go->getX(), go->getY(), go->getZ(), go->getOrientation()); go->getX(), go->getY(), go->getZ(), go->getOrientation(), goScale);
} }
// Fire transport move callback for transports (position update on re-creation) // Fire transport move callback for transports (position update on re-creation)
if (transportGuids_.count(block.guid) && transportMoveCallback_) { if (transportGuids_.count(block.guid) && transportMoveCallback_) {
@ -8366,8 +8388,19 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
} }
} }
} else if (creatureSpawnCallback_) { } else if (creatureSpawnCallback_) {
float unitScale2 = 1.0f;
{
uint16_t scaleIdx = fieldIndex(UF::OBJECT_FIELD_SCALE_X);
if (scaleIdx != 0xFFFF) {
uint32_t raw = entity->getField(scaleIdx);
if (raw != 0) {
std::memcpy(&unitScale2, &raw, sizeof(float));
if (unitScale2 <= 0.01f || unitScale2 > 100.0f) unitScale2 = 1.0f;
}
}
}
creatureSpawnCallback_(block.guid, unit->getDisplayId(), creatureSpawnCallback_(block.guid, unit->getDisplayId(),
unit->getX(), unit->getY(), unit->getZ(), unit->getOrientation()); unit->getX(), unit->getY(), unit->getZ(), unit->getOrientation(), unitScale2);
bool isDeadNow = (unit->getHealth() == 0) || bool isDeadNow = (unit->getHealth() == 0) ||
((unit->getDynamicFlags() & (UNIT_DYNFLAG_DEAD | UNIT_DYNFLAG_LOOTABLE)) != 0); ((unit->getDynamicFlags() & (UNIT_DYNFLAG_DEAD | UNIT_DYNFLAG_LOOTABLE)) != 0);
if (isDeadNow && !npcDeathNotified && npcDeathCallback_) { if (isDeadNow && !npcDeathNotified && npcDeathCallback_) {

View file

@ -19,6 +19,7 @@ struct UFNameEntry {
static const UFNameEntry kUFNames[] = { static const UFNameEntry kUFNames[] = {
{"OBJECT_FIELD_ENTRY", UF::OBJECT_FIELD_ENTRY}, {"OBJECT_FIELD_ENTRY", UF::OBJECT_FIELD_ENTRY},
{"OBJECT_FIELD_SCALE_X", UF::OBJECT_FIELD_SCALE_X},
{"UNIT_FIELD_TARGET_LO", UF::UNIT_FIELD_TARGET_LO}, {"UNIT_FIELD_TARGET_LO", UF::UNIT_FIELD_TARGET_LO},
{"UNIT_FIELD_TARGET_HI", UF::UNIT_FIELD_TARGET_HI}, {"UNIT_FIELD_TARGET_HI", UF::UNIT_FIELD_TARGET_HI},
{"UNIT_FIELD_BYTES_0", UF::UNIT_FIELD_BYTES_0}, {"UNIT_FIELD_BYTES_0", UF::UNIT_FIELD_BYTES_0},
@ -52,6 +53,9 @@ static const UFNameEntry kUFNames[] = {
{"PLAYER_EXPLORED_ZONES_START", UF::PLAYER_EXPLORED_ZONES_START}, {"PLAYER_EXPLORED_ZONES_START", UF::PLAYER_EXPLORED_ZONES_START},
{"GAMEOBJECT_DISPLAYID", UF::GAMEOBJECT_DISPLAYID}, {"GAMEOBJECT_DISPLAYID", UF::GAMEOBJECT_DISPLAYID},
{"ITEM_FIELD_STACK_COUNT", UF::ITEM_FIELD_STACK_COUNT}, {"ITEM_FIELD_STACK_COUNT", UF::ITEM_FIELD_STACK_COUNT},
{"ITEM_FIELD_DURABILITY", UF::ITEM_FIELD_DURABILITY},
{"ITEM_FIELD_MAXDURABILITY", UF::ITEM_FIELD_MAXDURABILITY},
{"PLAYER_REST_STATE_EXPERIENCE", UF::PLAYER_REST_STATE_EXPERIENCE},
{"CONTAINER_FIELD_NUM_SLOTS", UF::CONTAINER_FIELD_NUM_SLOTS}, {"CONTAINER_FIELD_NUM_SLOTS", UF::CONTAINER_FIELD_NUM_SLOTS},
{"CONTAINER_FIELD_SLOT_1", UF::CONTAINER_FIELD_SLOT_1}, {"CONTAINER_FIELD_SLOT_1", UF::CONTAINER_FIELD_SLOT_1},
}; };