typo
This commit is contained in:
@@ -883,6 +883,10 @@ set(PLAYERBOT_LIFECYCLE_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Lifecycle/PopulationLifecycleController.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Lifecycle/PopulationLifecycleController.h
|
||||
|
||||
# Startup Cleanup System
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Lifecycle/PlayerbotStartupCleanup.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Lifecycle/PlayerbotStartupCleanup.h
|
||||
|
||||
# Instance Bot Pool System
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Lifecycle/Instance/PoolSlotState.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Lifecycle/Instance/InstanceBotSlot.h
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
|
||||
#ifdef BUILD_PLAYERBOT
|
||||
|
||||
#include "PlayerbotStartupCleanup.h"
|
||||
#include "Database/PlayerbotDatabase.h"
|
||||
#include "Chat/BotChatManager.h"
|
||||
#include "Log.h"
|
||||
#include "Player.h"
|
||||
#include "ObjectAccessor.h"
|
||||
|
||||
namespace Playerbot
|
||||
{
|
||||
// Initialize static members
|
||||
PlayerbotStartupCleanup::CleanupStats PlayerbotStartupCleanup::_stats;
|
||||
|
||||
bool PlayerbotStartupCleanup::RunStartupCleanup()
|
||||
{
|
||||
TC_LOG_INFO("module.playerbot", "PlayerbotStartupCleanup: Starting comprehensive startup cleanup...");
|
||||
|
||||
// Reset statistics
|
||||
_stats = CleanupStats();
|
||||
|
||||
// Run all cleanup procedures
|
||||
// Each procedure handles its own error recovery
|
||||
CleanupOrphanedJITBots();
|
||||
CleanupStaleLFGAssignments();
|
||||
CleanupOldConversations();
|
||||
CleanupOrphanedAccounts();
|
||||
CleanupInactiveData();
|
||||
|
||||
// Log final statistics
|
||||
LogCleanupStatistics();
|
||||
|
||||
TC_LOG_INFO("module.playerbot", "PlayerbotStartupCleanup: Startup cleanup completed");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlayerbotStartupCleanup::CleanupOrphanedJITBots()
|
||||
{
|
||||
try
|
||||
{
|
||||
TC_LOG_INFO("module.playerbot", "PlayerbotStartupCleanup: Cleaning up orphaned JIT bot characters...");
|
||||
|
||||
// Query for orphaned JIT bots from tracking table
|
||||
QueryResult result = sPlayerbotDatabase->Query(
|
||||
"SELECT jb.bot_guid, jb.account_id FROM playerbot_jit_bots jb");
|
||||
|
||||
if (!result)
|
||||
{
|
||||
TC_LOG_DEBUG("module.playerbot", "PlayerbotStartupCleanup: No orphaned JIT bots found");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::pair<ObjectGuid, uint32>> botsToDelete;
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
ObjectGuid::LowType guidLow = fields[0].GetUInt64();
|
||||
uint32 accountId = fields[1].GetUInt32();
|
||||
ObjectGuid botGuid = ObjectGuid::Create<HighGuid::Player>(guidLow);
|
||||
botsToDelete.push_back({botGuid, accountId});
|
||||
} while (result->NextRow());
|
||||
|
||||
TC_LOG_INFO("module.playerbot", "PlayerbotStartupCleanup: Found {} orphaned JIT bots to delete", botsToDelete.size());
|
||||
|
||||
// Delete each orphaned bot
|
||||
for (auto const& [botGuid, accountId] : botsToDelete)
|
||||
{
|
||||
try
|
||||
{
|
||||
Player::DeleteFromDB(botGuid, accountId, false, true);
|
||||
++_stats.jitBotsDeleted;
|
||||
TC_LOG_DEBUG("module.playerbot", "PlayerbotStartupCleanup: Deleted orphaned JIT bot {}", botGuid.ToString());
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
TC_LOG_WARN("module.playerbot", "PlayerbotStartupCleanup: Failed to delete JIT bot {}: {}",
|
||||
botGuid.ToString(), e.what());
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the JIT bots tracking table
|
||||
sPlayerbotDatabase->Execute("TRUNCATE TABLE playerbot_jit_bots");
|
||||
|
||||
TC_LOG_INFO("module.playerbot", "PlayerbotStartupCleanup: Successfully cleaned up {} orphaned JIT bots",
|
||||
_stats.jitBotsDeleted);
|
||||
return true;
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
TC_LOG_ERROR("module.playerbot", "PlayerbotStartupCleanup: Exception during JIT bot cleanup: {}", e.what());
|
||||
return true; // Don't fail startup on cleanup errors
|
||||
}
|
||||
}
|
||||
|
||||
bool PlayerbotStartupCleanup::CleanupStaleLFGAssignments()
|
||||
{
|
||||
try
|
||||
{
|
||||
TC_LOG_INFO("module.playerbot", "PlayerbotStartupCleanup: Cleaning up stale LFG assignments...");
|
||||
|
||||
// NOTE: LFG assignments are stored in-memory and not persisted to database
|
||||
// They are automatically cleared on server restart, so no database cleanup needed
|
||||
// However, we verify that LFGBotManager is ready to start fresh
|
||||
|
||||
TC_LOG_DEBUG("module.playerbot", "PlayerbotStartupCleanup: LFG assignments are in-memory and will be cleared at startup");
|
||||
return true;
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
TC_LOG_WARN("module.playerbot", "PlayerbotStartupCleanup: Exception during LFG cleanup: {}", e.what());
|
||||
return true; // Don't fail startup on cleanup errors
|
||||
}
|
||||
}
|
||||
|
||||
bool PlayerbotStartupCleanup::CleanupOldConversations()
|
||||
{
|
||||
try
|
||||
{
|
||||
TC_LOG_INFO("module.playerbot", "PlayerbotStartupCleanup: Cleaning up old AI conversations...");
|
||||
|
||||
// Check if BotChatManager is available and call its cleanup
|
||||
if (Playerbot::BotChatManager::instance())
|
||||
{
|
||||
Playerbot::BotChatManager::instance()->Cleanup();
|
||||
TC_LOG_INFO("module.playerbot", "PlayerbotStartupCleanup: Old conversations cleaned up");
|
||||
return true;
|
||||
}
|
||||
|
||||
TC_LOG_DEBUG("module.playerbot", "PlayerbotStartupCleanup: BotChatManager not available, skipping conversation cleanup");
|
||||
return true;
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
TC_LOG_ERROR("module.playerbot", "PlayerbotStartupCleanup: Exception during conversation cleanup: {}", e.what());
|
||||
return true; // Don't fail startup on cleanup errors
|
||||
}
|
||||
}
|
||||
|
||||
bool PlayerbotStartupCleanup::CleanupOrphanedAccounts()
|
||||
{
|
||||
try
|
||||
{
|
||||
TC_LOG_INFO("module.playerbot", "PlayerbotStartupCleanup: Cleaning up orphaned bot accounts...");
|
||||
|
||||
// Find bot accounts that:
|
||||
// 1. Have no characters in the characters table, OR
|
||||
// 2. Have been inactive for > 30 days
|
||||
QueryResult result = sPlayerbotDatabase->Query(
|
||||
"SELECT pa.account_id, pa.created_at "
|
||||
"FROM playerbot_accounts pa "
|
||||
"LEFT JOIN characters c ON pa.account_id = c.account "
|
||||
"WHERE c.guid IS NULL "
|
||||
"OR (pa.is_active = 1 AND DATE_ADD(pa.last_login, INTERVAL 30 DAY) < NOW())");
|
||||
|
||||
if (!result)
|
||||
{
|
||||
TC_LOG_DEBUG("module.playerbot", "PlayerbotStartupCleanup: No orphaned bot accounts found");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<uint32> orphanedAccounts;
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
uint32 accountId = fields[0].GetUInt32();
|
||||
orphanedAccounts.push_back(accountId);
|
||||
} while (result->NextRow());
|
||||
|
||||
TC_LOG_INFO("module.playerbot", "PlayerbotStartupCleanup: Found {} orphaned bot accounts", orphanedAccounts.size());
|
||||
|
||||
// Mark orphaned accounts as inactive
|
||||
for (uint32 accountId : orphanedAccounts)
|
||||
{
|
||||
try
|
||||
{
|
||||
sPlayerbotDatabase->Execute(
|
||||
"UPDATE playerbot_accounts SET is_active = 0 WHERE account_id = {}", accountId);
|
||||
++_stats.orphanedAccountsDeleted;
|
||||
TC_LOG_DEBUG("module.playerbot", "PlayerbotStartupCleanup: Marked orphaned account {} as inactive", accountId);
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
TC_LOG_WARN("module.playerbot", "PlayerbotStartupCleanup: Failed to mark account {} as inactive: {}",
|
||||
accountId, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
TC_LOG_INFO("module.playerbot", "PlayerbotStartupCleanup: Deactivated {} orphaned bot accounts",
|
||||
_stats.orphanedAccountsDeleted);
|
||||
return true;
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
TC_LOG_ERROR("module.playerbot", "PlayerbotStartupCleanup: Exception during account cleanup: {}", e.what());
|
||||
return true; // Don't fail startup on cleanup errors
|
||||
}
|
||||
}
|
||||
|
||||
bool PlayerbotStartupCleanup::CleanupInactiveData()
|
||||
{
|
||||
try
|
||||
{
|
||||
TC_LOG_INFO("module.playerbot", "PlayerbotStartupCleanup: Cleaning up inactive bot data...");
|
||||
|
||||
// Clean up old pool statistics (keep last 7 days)
|
||||
if (sPlayerbotDatabase->Query("SELECT 1 FROM information_schema.TABLES WHERE TABLE_NAME='playerbot_pool_statistics' LIMIT 1"))
|
||||
{
|
||||
sPlayerbotDatabase->Execute(
|
||||
"DELETE FROM playerbot_pool_statistics WHERE recorded_at < DATE_SUB(NOW(), INTERVAL 7 DAY)");
|
||||
TC_LOG_DEBUG("module.playerbot", "PlayerbotStartupCleanup: Cleaned old pool statistics");
|
||||
}
|
||||
|
||||
// Clean up old template statistics (keep last 30 days)
|
||||
if (sPlayerbotDatabase->Query("SELECT 1 FROM information_schema.TABLES WHERE TABLE_NAME='playerbot_template_statistics' LIMIT 1"))
|
||||
{
|
||||
sPlayerbotDatabase->Execute(
|
||||
"DELETE FROM playerbot_template_statistics WHERE last_updated < DATE_SUB(NOW(), INTERVAL 30 DAY)");
|
||||
TC_LOG_DEBUG("module.playerbot", "PlayerbotStartupCleanup: Cleaned old template statistics");
|
||||
}
|
||||
|
||||
TC_LOG_INFO("module.playerbot", "PlayerbotStartupCleanup: Inactive data cleanup completed");
|
||||
return true;
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
TC_LOG_WARN("module.playerbot", "PlayerbotStartupCleanup: Exception during inactive data cleanup: {}", e.what());
|
||||
return true; // Don't fail startup on cleanup errors
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerbotStartupCleanup::LogCleanupStatistics()
|
||||
{
|
||||
TC_LOG_INFO("module.playerbot", "");
|
||||
TC_LOG_INFO("module.playerbot", "=== Playerbot Startup Cleanup Statistics ===");
|
||||
TC_LOG_INFO("module.playerbot", " JIT Bots Deleted: {}", _stats.jitBotsDeleted);
|
||||
TC_LOG_INFO("module.playerbot", " Stale LFG Removed: {}", _stats.staleLFGsRemoved);
|
||||
TC_LOG_INFO("module.playerbot", " Orphaned Accounts: {}", _stats.orphanedAccountsDeleted);
|
||||
TC_LOG_INFO("module.playerbot", "============================================");
|
||||
TC_LOG_INFO("module.playerbot", "");
|
||||
}
|
||||
|
||||
} // namespace Playerbot
|
||||
|
||||
#endif // BUILD_PLAYERBOT
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef PLAYERBOT_STARTUP_CLEANUP_H
|
||||
#define PLAYERBOT_STARTUP_CLEANUP_H
|
||||
|
||||
#include "Define.h"
|
||||
#include <string>
|
||||
|
||||
namespace Playerbot
|
||||
{
|
||||
/**
|
||||
* @brief Startup cleanup manager for playerbot accounts, characters, and data
|
||||
*
|
||||
* This manager handles comprehensive cleanup of playerbot data during server startup:
|
||||
* - Orphaned bot characters from crashed sessions
|
||||
* - Stale LFG assignments
|
||||
* - Old AI chat conversations
|
||||
* - Orphaned playerbot accounts
|
||||
* - Inactive playerbot data
|
||||
*/
|
||||
class TC_GAME_API PlayerbotStartupCleanup
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Run all startup cleanup procedures
|
||||
* @return true if all cleanups completed successfully, false if critical error occurred
|
||||
*/
|
||||
static bool RunStartupCleanup();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Clean up orphaned JIT bot characters from previous runs
|
||||
*
|
||||
* These are bot characters that were created by JITBotFactory but not
|
||||
* properly deleted due to server crashes or unclean shutdowns.
|
||||
*/
|
||||
static bool CleanupOrphanedJITBots();
|
||||
|
||||
/**
|
||||
* @brief Clean up stale LFG bot assignments
|
||||
*
|
||||
* Removes bots that are still marked as queued but no longer exist
|
||||
* in the world, or whose queue time has expired.
|
||||
*/
|
||||
static bool CleanupStaleLFGAssignments();
|
||||
|
||||
/**
|
||||
* @brief Clean up old AI chat conversations
|
||||
*
|
||||
* Removes cached conversations older than the configured retention period
|
||||
* to free up memory and database space.
|
||||
*/
|
||||
static bool CleanupOldConversations();
|
||||
|
||||
/**
|
||||
* @brief Clean up orphaned playerbot accounts
|
||||
*
|
||||
* Removes bot accounts that have no associated characters or have been
|
||||
* inactive for an extended period.
|
||||
*/
|
||||
static bool CleanupOrphanedAccounts();
|
||||
|
||||
/**
|
||||
* @brief Clean up inactive bot data
|
||||
*
|
||||
* Removes stale bot statistics, performance metrics, and other
|
||||
* accumulated data from bots that are no longer active.
|
||||
*/
|
||||
static bool CleanupInactiveData();
|
||||
|
||||
/**
|
||||
* @brief Log cleanup statistics
|
||||
*/
|
||||
static void LogCleanupStatistics();
|
||||
|
||||
// Statistics tracking
|
||||
struct CleanupStats
|
||||
{
|
||||
uint32 jitBotsDeleted = 0;
|
||||
uint32 staleLFGsRemoved = 0;
|
||||
uint32 conversationsDeleted = 0;
|
||||
uint32 orphanedAccountsDeleted = 0;
|
||||
uint64 bytesFreed = 0;
|
||||
};
|
||||
|
||||
static CleanupStats _stats;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // PLAYERBOT_STARTUP_CLEANUP_H
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "Lifecycle/Demand/PlayerActivityTracker.h"
|
||||
#include "Lifecycle/Demand/DemandCalculator.h"
|
||||
#include "Lifecycle/PopulationLifecycleController.h"
|
||||
#include "Lifecycle/PlayerbotStartupCleanup.h"
|
||||
|
||||
// Instance Bot Pool System (Hybrid Warm Pool + Elastic Overflow)
|
||||
#include "Lifecycle/Instance/InstanceBotPool.h"
|
||||
@@ -172,6 +173,11 @@ bool PlayerbotModule::Initialize()
|
||||
// This ensures schema synchronization between builds
|
||||
PlayerbotMigrationMgr::instance()->CheckVersionMismatch();
|
||||
|
||||
// Run startup cleanup to remove orphaned bots and stale data
|
||||
TC_LOG_INFO("module.playerbot", "Running playerbot startup cleanup...");
|
||||
Playerbot::PlayerbotStartupCleanup::RunStartupCleanup();
|
||||
TC_LOG_INFO("module.playerbot", "Playerbot startup cleanup completed");
|
||||
|
||||
// Initialize all sub-managers
|
||||
if (!InitializeManagers())
|
||||
{
|
||||
|
||||
@@ -4147,6 +4147,8 @@ void GameObject::ValuesUpdateForPlayerWithMaskSender::operator()(Player const* p
|
||||
void GameObject::ClearValuesChangesMask()
|
||||
{
|
||||
m_values.ClearChangesMask(&GameObject::m_gameObjectData);
|
||||
m_values.ClearChangesMask(&GameObject::m_housingCornerstoneData);
|
||||
m_values.ClearChangesMask(&GameObject::m_mirroredPositionData);
|
||||
WorldObject::ClearValuesChangesMask();
|
||||
}
|
||||
|
||||
@@ -4678,3 +4680,135 @@ SpellInfo const* GameObject::GetSpellForLock(Player const* player) const
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
void GameObject::InitHousingCornerstoneData(uint64 cost, int32 plotIndex)
|
||||
{
|
||||
if (m_housingCornerstoneData.has_value())
|
||||
return;
|
||||
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&GameObject::m_housingCornerstoneData, 0)
|
||||
.ModifyValue(&UF::HousingCornerstoneData::Cost), cost);
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&GameObject::m_housingCornerstoneData, 0)
|
||||
.ModifyValue(&UF::HousingCornerstoneData::PlotIndex), plotIndex);
|
||||
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FJamHousingCornerstone_C, IsInWorld(),
|
||||
WowCS::GetRawFragmentData(m_housingCornerstoneData));
|
||||
|
||||
TC_LOG_DEBUG("housing", "GameObject::InitHousingCornerstoneData: entry={} guid={} cost={} plotIndex={} "
|
||||
"isInWorld={} fragmentCount={} updateableCount={}",
|
||||
GetEntry(), GetGUID().ToString(), cost, plotIndex,
|
||||
IsInWorld(), m_entityFragments.Count, m_entityFragments.UpdateableCount);
|
||||
}
|
||||
|
||||
void GameObject::InitHousingDecorData(ObjectGuid decorGuid, ObjectGuid houseGuid,
|
||||
uint8 flags, ObjectGuid attachParent /*= ObjectGuid::Empty*/,
|
||||
uint8 sourceType /*= 0*/, std::string sourceValue /*= {}*/)
|
||||
{
|
||||
if (m_housingDecorData.has_value())
|
||||
return;
|
||||
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Object::m_housingDecorData, 0)
|
||||
.ModifyValue(&UF::HousingDecorData::DecorGUID), decorGuid);
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Object::m_housingDecorData, 0)
|
||||
.ModifyValue(&UF::HousingDecorData::AttachParentGUID), attachParent);
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Object::m_housingDecorData, 0)
|
||||
.ModifyValue(&UF::HousingDecorData::Flags), flags);
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Object::m_housingDecorData, 0)
|
||||
.ModifyValue(&UF::HousingDecorData::TargetGameObjectGUID), GetGUID());
|
||||
|
||||
// Set persisted data (house ownership + source tracking)
|
||||
auto persistedRef = m_values.ModifyValue(&Object::m_housingDecorData, 0)
|
||||
.ModifyValue(&UF::HousingDecorData::PersistedData, 0);
|
||||
SetUpdateFieldValue(persistedRef.ModifyValue(&UF::DecorStoragePersistedData::HouseGUID), houseGuid);
|
||||
SetUpdateFieldValue(persistedRef.ModifyValue(&UF::DecorStoragePersistedData::SourceType), sourceType);
|
||||
if (!sourceValue.empty())
|
||||
SetUpdateFieldValue(persistedRef.ModifyValue(&UF::DecorStoragePersistedData::SourceValue), std::move(sourceValue));
|
||||
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FHousingDecor_C, IsInWorld(),
|
||||
WowCS::GetRawFragmentData(m_housingDecorData));
|
||||
|
||||
// 12.0.5 added Tag_HousingDecorProxyGameObject (=226) to mark a GameObject that is
|
||||
// serving as a housing-decor proxy (chair/chest/mailbox/etc. placed as decor).
|
||||
// Attach it alongside FHousingDecor_C so the client treats this entity as housing
|
||||
// decor in addition to its normal GO behavior.
|
||||
m_entityFragments.Add(WowCS::EntityFragment::Tag_HousingDecorProxyGameObject, IsInWorld());
|
||||
|
||||
TC_LOG_DEBUG("housing", "GameObject::InitHousingDecorData: entry={} goGuid={} decorGuid={} houseGuid={} flags={} "
|
||||
"isInWorld={} fragmentCount={}",
|
||||
GetEntry(), GetGUID().ToString(), decorGuid.ToString(), houseGuid.ToString(), flags,
|
||||
IsInWorld(), m_entityFragments.Count);
|
||||
}
|
||||
|
||||
void GameObject::InitHousingDecorMirroredPosition(Position const& localPos, QuaternionData const& localRot,
|
||||
float localScale, ObjectGuid attachParent, uint8 attachFlags /*= 3*/)
|
||||
{
|
||||
// Retail sniff-verified: GameObject decor carries FMirroredPositionData_C fragment
|
||||
// with AttachParent=room entity and local-space position.
|
||||
auto posData = m_values.ModifyValue(&GameObject::m_mirroredPositionData)
|
||||
.ModifyValue(&UF::MirroredPositionData::PositionData);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::AttachParentGUID), attachParent);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::PositionLocalSpace),
|
||||
TaggedPosition<Position::XYZ>(localPos.GetPositionX(), localPos.GetPositionY(), localPos.GetPositionZ()));
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::RotationLocalSpace), localRot);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::ScaleLocalSpace), localScale);
|
||||
SetUpdateFieldValue(posData.ModifyValue(&UF::MirroredMeshObjectData::AttachmentFlags), attachFlags);
|
||||
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FMirroredPositionData_C, IsInWorld(),
|
||||
WowCS::GetRawFragmentData(m_mirroredPositionData));
|
||||
|
||||
TC_LOG_DEBUG("housing", "GameObject::InitHousingDecorMirroredPosition: entry={} goGuid={} "
|
||||
"localPos=({:.2f},{:.2f},{:.2f}) attachParent={} attachFlags={}",
|
||||
GetEntry(), GetGUID().ToString(),
|
||||
localPos.GetPositionX(), localPos.GetPositionY(), localPos.GetPositionZ(),
|
||||
attachParent.ToString(), attachFlags);
|
||||
}
|
||||
|
||||
void GameObject::InitHousingFixtureData(ObjectGuid houseGuid, int32 exteriorComponentID, int32 houseExteriorWmoDataID,
|
||||
uint8 exteriorComponentType /*= 9*/, uint8 houseSize /*= 2*/, int32 exteriorComponentHookID /*= -1*/)
|
||||
{
|
||||
if (m_housingFixtureData.has_value())
|
||||
return;
|
||||
|
||||
// Sniff-verified field values (11.2 retail MeshObject with FHousingFixture_C):
|
||||
// ExteriorComponentID: 141 (Stucco Base, small Human house)
|
||||
// HouseExteriorWmoDataID: 9 (Human/Generic theme, NOT 32)
|
||||
// ExteriorComponentHookID: -1 (base piece, no hook)
|
||||
// ExteriorComponentType: 9 (Base)
|
||||
// Field_59: 1
|
||||
// Size: 2 (small)
|
||||
// GameObjectGUID: 0 (empty)
|
||||
// Guid: MeshObject GUID (we use Housing GUID as safe substitute)
|
||||
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Object::m_housingFixtureData, 0)
|
||||
.ModifyValue(&UF::HousingFixtureData::ExteriorComponentID), exteriorComponentID);
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Object::m_housingFixtureData, 0)
|
||||
.ModifyValue(&UF::HousingFixtureData::HouseExteriorWmoDataID), houseExteriorWmoDataID);
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Object::m_housingFixtureData, 0)
|
||||
.ModifyValue(&UF::HousingFixtureData::ExteriorComponentHookID), exteriorComponentHookID);
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Object::m_housingFixtureData, 0)
|
||||
.ModifyValue(&UF::HousingFixtureData::HouseGUID), houseGuid);
|
||||
// Guid must be a Housing-type GUID (HighGuid::Housing, type 55). Client GUID resolver
|
||||
// crashes if it receives a non-Housing, non-null GUID here (e.g. HighGuid::GameObject = 11)
|
||||
// because it enters a conversion path that returns null, then dereferences at +0x64.
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Object::m_housingFixtureData, 0)
|
||||
.ModifyValue(&UF::HousingFixtureData::Guid), houseGuid);
|
||||
// GameObjectGUID: sniff confirms 0x0 (empty) for all fixture pieces
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Object::m_housingFixtureData, 0)
|
||||
.ModifyValue(&UF::HousingFixtureData::ExteriorComponentType), exteriorComponentType);
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Object::m_housingFixtureData, 0)
|
||||
.ModifyValue(&UF::HousingFixtureData::Field_59), uint8(1)); // sniff: always 1
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Object::m_housingFixtureData, 0)
|
||||
.ModifyValue(&UF::HousingFixtureData::Size), houseSize);
|
||||
|
||||
m_entityFragments.Add(WowCS::EntityFragment::FHousingFixture_C, IsInWorld(),
|
||||
WowCS::GetRawFragmentData(m_housingFixtureData));
|
||||
|
||||
TC_LOG_DEBUG("housing", "GameObject::InitHousingFixtureData: entry={} goGuid={} houseGuid={} "
|
||||
"exteriorComponentID={} wmoDataID={} hookID={} componentType={} size={} field59=1 "
|
||||
"isInWorld={} fragmentCount={}",
|
||||
GetEntry(), GetGUID().ToString(), houseGuid.ToString(),
|
||||
exteriorComponentID, houseExteriorWmoDataID, exteriorComponentHookID,
|
||||
exteriorComponentType, houseSize,
|
||||
IsInWorld(), m_entityFragments.Count);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user