mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
fix: sync player appearance after barber shop or polymorph
PLAYER_BYTES and PLAYER_BYTES_2 changes in SMSG_UPDATE_OBJECT now update the Character struct's appearanceBytes and facialFeatures, and fire an appearance-changed callback that resets the inventory screen preview so it reloads with the new hair/face values.
This commit is contained in:
parent
2e134b686d
commit
801f29f043
4 changed files with 40 additions and 2 deletions
|
|
@ -949,6 +949,10 @@ public:
|
|||
using StandStateCallback = std::function<void(uint8_t standState)>;
|
||||
void setStandStateCallback(StandStateCallback cb) { standStateCallback_ = std::move(cb); }
|
||||
|
||||
// Appearance changed callback — fired when PLAYER_BYTES or facial features update (barber shop, etc.)
|
||||
using AppearanceChangedCallback = std::function<void()>;
|
||||
void setAppearanceChangedCallback(AppearanceChangedCallback cb) { appearanceChangedCallback_ = std::move(cb); }
|
||||
|
||||
// Ghost state callback — fired when player enters or leaves ghost (spirit) form
|
||||
using GhostStateCallback = std::function<void(bool isGhost)>;
|
||||
void setGhostStateCallback(GhostStateCallback cb) { ghostStateCallback_ = std::move(cb); }
|
||||
|
|
@ -3348,6 +3352,7 @@ private:
|
|||
NpcAggroCallback npcAggroCallback_;
|
||||
NpcRespawnCallback npcRespawnCallback_;
|
||||
StandStateCallback standStateCallback_;
|
||||
AppearanceChangedCallback appearanceChangedCallback_;
|
||||
GhostStateCallback ghostStateCallback_;
|
||||
MeleeSwingCallback meleeSwingCallback_;
|
||||
uint64_t lastMeleeSwingMs_ = 0; // system_clock ms at last player auto-attack swing
|
||||
|
|
|
|||
|
|
@ -665,6 +665,7 @@ private:
|
|||
float resurrectFlashTimer_ = 0.0f;
|
||||
static constexpr float kResurrectFlashDuration = 3.0f;
|
||||
bool ghostStateCallbackSet_ = false;
|
||||
bool appearanceCallbackSet_ = false;
|
||||
bool ghostOpacityStateKnown_ = false;
|
||||
bool ghostOpacityLastState_ = false;
|
||||
uint32_t ghostOpacityLastInstanceId_ = 0;
|
||||
|
|
|
|||
|
|
@ -12177,6 +12177,7 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem
|
|||
const uint16_t ufCoinage = fieldIndex(UF::PLAYER_FIELD_COINAGE);
|
||||
const uint16_t ufPlayerFlags = fieldIndex(UF::PLAYER_FLAGS);
|
||||
const uint16_t ufArmor = fieldIndex(UF::UNIT_FIELD_RESISTANCES);
|
||||
const uint16_t ufPBytesV = fieldIndex(UF::PLAYER_BYTES);
|
||||
const uint16_t ufPBytes2v = fieldIndex(UF::PLAYER_BYTES_2);
|
||||
const uint16_t ufChosenTitle = fieldIndex(UF::PLAYER_CHOSEN_TITLE);
|
||||
const uint16_t ufStatsV[5] = {
|
||||
|
|
@ -12227,15 +12228,38 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem
|
|||
else if (ufArmor != 0xFFFF && key > ufArmor && key <= ufArmor + 6) {
|
||||
playerResistances_[key - ufArmor - 1] = static_cast<int32_t>(val);
|
||||
}
|
||||
else if (ufPBytesV != 0xFFFF && key == ufPBytesV) {
|
||||
// PLAYER_BYTES changed (barber shop, polymorph, etc.)
|
||||
// Update the Character struct so inventory preview refreshes
|
||||
for (auto& ch : characters) {
|
||||
if (ch.guid == playerGuid) {
|
||||
ch.appearanceBytes = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (appearanceChangedCallback_)
|
||||
appearanceChangedCallback_();
|
||||
}
|
||||
else if (ufPBytes2v != 0xFFFF && key == ufPBytes2v) {
|
||||
// Byte 0 (bits 0-7): facial hair / piercings
|
||||
uint8_t facialHair = static_cast<uint8_t>(val & 0xFF);
|
||||
for (auto& ch : characters) {
|
||||
if (ch.guid == playerGuid) {
|
||||
ch.facialFeatures = facialHair;
|
||||
break;
|
||||
}
|
||||
}
|
||||
uint8_t bankBagSlots = static_cast<uint8_t>((val >> 16) & 0xFF);
|
||||
LOG_WARNING("PLAYER_BYTES_2 (VALUES): raw=0x", std::hex, val, std::dec,
|
||||
" bankBagSlots=", static_cast<int>(bankBagSlots));
|
||||
LOG_DEBUG("PLAYER_BYTES_2 (VALUES): raw=0x", std::hex, val, std::dec,
|
||||
" bankBagSlots=", static_cast<int>(bankBagSlots),
|
||||
" facial=", static_cast<int>(facialHair));
|
||||
inventory.setPurchasedBankBagSlots(bankBagSlots);
|
||||
// Byte 3 (bits 24-31): REST_STATE
|
||||
// 0 = not resting, 1 = REST_TYPE_IN_TAVERN, 2 = REST_TYPE_IN_CITY
|
||||
uint8_t restStateByte = static_cast<uint8_t>((val >> 24) & 0xFF);
|
||||
isResting_ = (restStateByte != 0);
|
||||
if (appearanceChangedCallback_)
|
||||
appearanceChangedCallback_();
|
||||
}
|
||||
else if (ufChosenTitle != 0xFFFF && key == ufChosenTitle) {
|
||||
chosenTitleBit_ = static_cast<int32_t>(val);
|
||||
|
|
|
|||
|
|
@ -401,6 +401,14 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
|||
ghostStateCallbackSet_ = true;
|
||||
}
|
||||
|
||||
// Set up appearance-changed callback to refresh inventory preview (barber shop, etc.)
|
||||
if (!appearanceCallbackSet_) {
|
||||
gameHandler.setAppearanceChangedCallback([this]() {
|
||||
inventoryScreenCharGuid_ = 0; // force preview re-sync on next frame
|
||||
});
|
||||
appearanceCallbackSet_ = true;
|
||||
}
|
||||
|
||||
// Set up UI error frame callback (once)
|
||||
if (!uiErrorCallbackSet_) {
|
||||
gameHandler.setUIErrorCallback([this](const std::string& msg) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue