Housing: Fix tutorial mode blocking editor features via account CVar injection

The client's housing editor UI checks FrameTutorialAccount flags stored in the
closedInfoFramesAccountWide CVar bitfield (GLOBAL_CONFIG_CACHE account data),
which is completely separate from the 256-bit server tutorial flags sent via
SMSG_TUTORIAL_FLAGS. Without bit 38 (HousingModesUnlocked) set in this CVar,
expert/cleanup/layout/customize modes remain locked.

Fix: inject closedInfoFramesAccountWide (all bits set) and housingTutorialsEnabled=0
into the GLOBAL_CONFIG_CACHE account data during player login and house purchase.
The code handles both fresh and existing account data, replacing or appending CVars
as needed. Account data timestamps are bumped so the client re-requests the config.

Added TODO markers for adapting this when the housing tutorial questline is
implemented (quest-driven FrameTutorialAccount bit progression).
This commit is contained in:
luis
2026-03-10 13:32:34 -03:00
parent 19c91b9f3c
commit 1e768c19b2
@@ -1085,12 +1085,62 @@ void WorldSession::HandleNeighborhoodBuyHouse(WorldPackets::Neighborhood::Neighb
static constexpr uint32 NPC_KILL_CREDIT_BUY_HOME = 248858;
player->KilledMonsterCredit(NPC_KILL_CREDIT_BUY_HOME);
// Mark all tutorials as seen (retail sniff: all 256 bits = 0xFF).
// Without this, the client gates cleanup/expert modes behind FrameTutorialAccount bits.
// TODO: Replace with quest-driven tutorial progression when the housing tutorial
// questline is implemented. See Player.cpp LoadFromDB for full explanation.
// Mark all server tutorial flags as seen (retail sniff: all 256 bits = 0xFF).
for (uint8 i = 0; i < MAX_ACCOUNT_TUTORIAL_VALUES; ++i)
SetTutorialInt(i, 0xFFFFFFFF);
SendTutorialsData();
// Also inject FrameTutorialAccount CVars into GLOBAL_CONFIG_CACHE.
// The client's housing UI checks closedInfoFramesAccountWide bit 38
// (HousingModesUnlocked) separately from the 256-bit server tutorial flags.
{
AccountData const* configCache = GetAccountData(GLOBAL_CONFIG_CACHE);
std::string configData = configCache ? configCache->Data : "";
bool modified = false;
auto ensureCVar = [&](std::string_view cvarName, std::string_view value)
{
std::string setPrefix = std::string("SET ") + std::string(cvarName) + " \"";
size_t pos = configData.find(setPrefix);
if (pos != std::string::npos)
{
size_t valStart = pos + setPrefix.size();
size_t valEnd = configData.find('"', valStart);
if (valEnd != std::string::npos)
{
std::string oldVal = configData.substr(valStart, valEnd - valStart);
if (oldVal != value)
{
configData.replace(valStart, valEnd - valStart, value);
modified = true;
}
}
}
else
{
if (!configData.empty() && configData.back() != '\n')
configData += '\n';
configData += "SET ";
configData += cvarName;
configData += " \"";
configData += value;
configData += "\"\n";
modified = true;
}
};
ensureCVar("closedInfoFramesAccountWide", "4294967295 4294967295");
ensureCVar("housingTutorialsEnabled", "0");
if (modified)
{
SetAccountData(GLOBAL_CONFIG_CACHE, GameTime::GetGameTime(), configData);
SendAccountDataTimes(player->GetGUID(), GLOBAL_CACHE_MASK);
}
}
// Retail sequence: FirstTimeDecorAcquisition ? BuyHouseResponse ? LevelFavor updates
// 1a. Populate the server-side decor catalog with starter items (so edit mode works)