mirror of
https://github.com/mod-playerbots/azerothcore-wotlk.git
synced 2026-01-16 10:30:27 +00:00
feat(Core/Pets): Management refactoring (#9712)
* feat(Core/Pets): rework managment * 1 * 2 * 3 * 4 * 5 * cs pet * check before ressurect * pet DECLINED_NAMES * display - https://github.com/azerothcore/azerothcore-wotlk/issues/9297 * ArenaSpectator * 1
This commit is contained in:
@@ -35,355 +35,6 @@
|
||||
#include "WorldPacket.h"
|
||||
#include "WorldSession.h"
|
||||
|
||||
class LoadPetFromDBQueryHolder : public CharacterDatabaseQueryHolder
|
||||
{
|
||||
public:
|
||||
LoadPetFromDBQueryHolder(uint32 petNumber, bool current, uint32 diffTime, std::string&& actionBar, uint32 health, uint32 mana)
|
||||
: _petNumber(petNumber),
|
||||
_current(current),
|
||||
_diffTime(diffTime),
|
||||
_actionBar(std::move(actionBar)),
|
||||
_savedHealth(health),
|
||||
_savedMana(mana) { }
|
||||
|
||||
uint32 GetPetNumber() const { return _petNumber; }
|
||||
uint32 GetDiffTime() const { return _diffTime; }
|
||||
bool GetCurrent() const { return _current; }
|
||||
uint32 GetSavedHealth() const { return _savedHealth; }
|
||||
uint32 GetSavedMana() const { return _savedMana; }
|
||||
std::string GetActionBar() const { return _actionBar; }
|
||||
|
||||
bool Initialize();
|
||||
private:
|
||||
enum
|
||||
{
|
||||
AURAS,
|
||||
SPELLS,
|
||||
COOLDOWNS,
|
||||
|
||||
MAX
|
||||
};
|
||||
|
||||
const uint32 _petNumber;
|
||||
const bool _current;
|
||||
const uint32 _diffTime;
|
||||
const std::string _actionBar;
|
||||
const uint32 _savedHealth;
|
||||
const uint32 _savedMana;
|
||||
};
|
||||
|
||||
bool LoadPetFromDBQueryHolder::Initialize()
|
||||
{
|
||||
SetSize(MAX);
|
||||
|
||||
bool res = true;
|
||||
CharacterDatabasePreparedStatement* stmt = nullptr;
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_AURA);
|
||||
stmt->setUInt32(0, _petNumber);
|
||||
res &= SetPreparedQuery(AURAS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SPELL);
|
||||
stmt->setUInt32(0, _petNumber);
|
||||
res &= SetPreparedQuery(SPELLS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SPELL_COOLDOWN);
|
||||
stmt->setUInt32(0, _petNumber);
|
||||
res &= SetPreparedQuery(COOLDOWNS, stmt);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
uint8 WorldSession::HandleLoadPetFromDBFirstCallback(PreparedQueryResult result, uint8 asynchLoadType, AsynchPetSummon* info)
|
||||
{
|
||||
if (!result)
|
||||
return PET_LOAD_NO_RESULT;
|
||||
|
||||
Player* owner = GetPlayer();
|
||||
if (!owner || owner->GetPet() || owner->GetVehicle() || owner->IsSpectator() || owner->IsBeingTeleportedFar())
|
||||
{
|
||||
return PET_LOAD_ERROR;
|
||||
}
|
||||
|
||||
Field* fields = result->Fetch();
|
||||
|
||||
// Xinef: this can happen if fetch is called twice, impossibru.
|
||||
if (!fields)
|
||||
return PET_LOAD_ERROR;
|
||||
|
||||
// update for case of current pet "slot = 0"
|
||||
uint32 petentry = fields[1].GetUInt32();
|
||||
if (!petentry)
|
||||
return PET_LOAD_NO_RESULT;
|
||||
|
||||
uint8 petSlot = fields[7].GetUInt8();
|
||||
bool current = petSlot == PET_SAVE_AS_CURRENT;
|
||||
uint32 summon_spell_id = fields[15].GetUInt32();
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(summon_spell_id); // CANT BE nullptr
|
||||
bool is_temporary_summoned = spellInfo && spellInfo->GetDuration() > 0;
|
||||
uint32 pet_number = fields[0].GetUInt32();
|
||||
uint32 savedhealth = fields[10].GetUInt32();
|
||||
uint32 savedmana = fields[11].GetUInt32();
|
||||
PetType pet_type = PetType(fields[16].GetUInt8());
|
||||
|
||||
// xinef: BG resurrect, overwrite saved value
|
||||
if (asynchLoadType == PET_LOAD_BG_RESURRECT)
|
||||
savedhealth = 1;
|
||||
|
||||
if (pet_type == HUNTER_PET && savedhealth == 0 && asynchLoadType != PET_LOAD_SUMMON_DEAD_PET)
|
||||
{
|
||||
WorldPacket data(SMSG_CAST_FAILED, 1 + 4 + 1 + 4);
|
||||
data << uint8(0);
|
||||
data << uint32(883);
|
||||
data << uint8(SPELL_FAILED_TARGETS_DEAD);
|
||||
SendPacket(&data);
|
||||
owner->RemoveSpellCooldown(883, false);
|
||||
return PET_LOAD_ERROR;
|
||||
}
|
||||
|
||||
// check temporary summoned pets like mage water elemental
|
||||
if (current && is_temporary_summoned)
|
||||
return PET_LOAD_ERROR;
|
||||
|
||||
if (pet_type == HUNTER_PET)
|
||||
{
|
||||
CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(petentry);
|
||||
if (!creatureInfo || !creatureInfo->IsTameable(owner->CanTameExoticPets()))
|
||||
return PET_LOAD_ERROR;
|
||||
}
|
||||
|
||||
Map* map = owner->GetMap();
|
||||
ObjectGuid::LowType guid = map->GenerateLowGuid<HighGuid::Pet>();
|
||||
Pet* pet = new Pet(owner, pet_type);
|
||||
if (!pet->Create(guid, map, owner->GetPhaseMask(), petentry, pet_number))
|
||||
{
|
||||
delete pet;
|
||||
return PET_LOAD_ERROR;
|
||||
}
|
||||
|
||||
std::shared_ptr<LoadPetFromDBQueryHolder> holder = std::make_shared<LoadPetFromDBQueryHolder>(pet_number, current, uint32(time(nullptr) - fields[14].GetUInt32()), fields[13].GetString(), savedhealth, savedmana);
|
||||
if (!holder->Initialize())
|
||||
{
|
||||
delete pet;
|
||||
return PET_LOAD_ERROR;
|
||||
}
|
||||
|
||||
float px, py, pz;
|
||||
owner->GetClosePoint(px, py, pz, pet->GetObjectSize(), PET_FOLLOW_DIST, pet->GetFollowAngle());
|
||||
if (!pet->IsPositionValid())
|
||||
{
|
||||
LOG_ERROR("network.opcode", "Pet (%s, entry %d) not loaded. Suggested coordinates isn't valid (X: %f Y: %f)",
|
||||
pet->GetGUID().ToString().c_str(), pet->GetEntry(), pet->GetPositionX(), pet->GetPositionY());
|
||||
delete pet;
|
||||
return PET_LOAD_ERROR;
|
||||
}
|
||||
|
||||
pet->SetLoading(true);
|
||||
pet->Relocate(px, py, pz, owner->GetOrientation());
|
||||
pet->setPetType(pet_type);
|
||||
pet->SetFaction(owner->GetFaction());
|
||||
pet->SetUInt32Value(UNIT_CREATED_BY_SPELL, summon_spell_id);
|
||||
|
||||
if (pet->IsCritter())
|
||||
{
|
||||
pet->UpdatePositionData();
|
||||
map->AddToMap(pet->ToCreature(), true);
|
||||
pet->SetLoading(false); // xinef, mine
|
||||
return PET_LOAD_OK;
|
||||
}
|
||||
|
||||
if (pet->getPetType() == HUNTER_PET || pet->GetCreatureTemplate()->type == CREATURE_TYPE_DEMON || pet->GetCreatureTemplate()->type == CREATURE_TYPE_UNDEAD)
|
||||
pet->GetCharmInfo()->SetPetNumber(pet_number, pet->IsPermanentPetFor(owner)); // Show pet details tab (Shift+P) only for hunter pets, demons or undead
|
||||
else
|
||||
pet->GetCharmInfo()->SetPetNumber(pet_number, false);
|
||||
|
||||
pet->SetDisplayId(fields[3].GetUInt32());
|
||||
pet->UpdatePositionData();
|
||||
pet->SetNativeDisplayId(fields[3].GetUInt32());
|
||||
pet->SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE);
|
||||
pet->SetName(fields[8].GetString());
|
||||
uint32 petlevel = fields[4].GetUInt16();
|
||||
|
||||
switch (pet->getPetType())
|
||||
{
|
||||
case SUMMON_PET:
|
||||
petlevel = owner->getLevel();
|
||||
|
||||
if (pet->IsPetGhoul())
|
||||
pet->SetUInt32Value(UNIT_FIELD_BYTES_0, 0x400); // class = rogue
|
||||
else
|
||||
pet->SetUInt32Value(UNIT_FIELD_BYTES_0, 0x800); // class = mage
|
||||
|
||||
pet->SetUInt32Value(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED);
|
||||
// this enables popup window (pet dismiss, cancel)
|
||||
break;
|
||||
case HUNTER_PET:
|
||||
pet->SetUInt32Value(UNIT_FIELD_BYTES_0, 0x02020100); // class = warrior, gender = none, power = focus
|
||||
pet->SetSheath(SHEATH_STATE_MELEE);
|
||||
pet->SetByteFlag(UNIT_FIELD_BYTES_2, 2, fields[9].GetBool() ? UNIT_CAN_BE_ABANDONED : UNIT_CAN_BE_RENAMED | UNIT_CAN_BE_ABANDONED);
|
||||
pet->SetUInt32Value(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED);
|
||||
// this enables popup window (pet abandon, cancel)
|
||||
pet->SetMaxPower(POWER_HAPPINESS, pet->GetCreatePowers(POWER_HAPPINESS));
|
||||
pet->SetPower(POWER_HAPPINESS, fields[12].GetUInt32());
|
||||
pet->setPowerType(POWER_FOCUS);
|
||||
break;
|
||||
default:
|
||||
if (!pet->IsPetGhoul())
|
||||
LOG_ERROR("network.opcode", "Pet have incorrect type (%u) for pet loading.", pet->getPetType());
|
||||
break;
|
||||
}
|
||||
|
||||
pet->SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, uint32(time(nullptr))); // cast can't be helped here
|
||||
pet->SetCreatorGUID(owner->GetGUID());
|
||||
owner->SetMinion(pet, true);
|
||||
|
||||
pet->InitStatsForLevel(petlevel);
|
||||
pet->SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, fields[5].GetUInt32());
|
||||
pet->SynchronizeLevelWithOwner();
|
||||
pet->SetReactState(ReactStates(fields[6].GetUInt8()));
|
||||
pet->SetCanModifyStats(true);
|
||||
|
||||
// set current pet as current
|
||||
// 0=current
|
||||
// 1..MAX_PET_STABLES in stable slot
|
||||
// PET_SAVE_NOT_IN_SLOT(100) = not stable slot (summoning))
|
||||
if (petSlot)
|
||||
{
|
||||
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UDP_CHAR_PET_SLOT_BY_SLOT_EXCLUDE_ID);
|
||||
stmt->setUInt8(0, uint8(PET_SAVE_NOT_IN_SLOT));
|
||||
stmt->setUInt32(1, owner->GetGUID().GetCounter());
|
||||
stmt->setUInt8(2, uint8(PET_SAVE_AS_CURRENT));
|
||||
stmt->setUInt32(3, pet_number);
|
||||
trans->Append(stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID);
|
||||
stmt->setUInt8(0, uint8(PET_SAVE_AS_CURRENT));
|
||||
stmt->setUInt32(1, owner->GetGUID().GetCounter());
|
||||
stmt->setUInt32(2, pet_number);
|
||||
trans->Append(stmt);
|
||||
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
}
|
||||
|
||||
// Send fake summon spell cast - this is needed for correct cooldown application for spells
|
||||
// Example: 46584 - without this cooldown (which should be set always when pet is loaded) isn't set clientside
|
||||
// TODO: pets should be summoned from real cast instead of just faking it?
|
||||
if (summon_spell_id)
|
||||
{
|
||||
WorldPacket data(SMSG_SPELL_GO, (8 + 8 + 4 + 4 + 2));
|
||||
data << owner->GetPackGUID();
|
||||
data << owner->GetPackGUID();
|
||||
data << uint8(0);
|
||||
data << uint32(summon_spell_id);
|
||||
data << uint32(256); // CAST_FLAG_UNKNOWN3
|
||||
data << uint32(0);
|
||||
owner->SendMessageToSet(&data, true);
|
||||
}
|
||||
|
||||
// do it as early as possible!
|
||||
pet->InitTalentForLevel(); // set original talents points before spell loading
|
||||
|
||||
if (!is_temporary_summoned)
|
||||
pet->GetCharmInfo()->InitPetActionBar();
|
||||
|
||||
map->AddToMap(pet->ToCreature(), true);
|
||||
|
||||
if (pet->getPetType() == SUMMON_PET && !current) //all (?) summon pets come with full health when called, but not when they are current
|
||||
pet->SetPower(POWER_MANA, pet->GetMaxPower(POWER_MANA));
|
||||
else
|
||||
{
|
||||
pet->SetHealth(savedhealth > pet->GetMaxHealth() ? pet->GetMaxHealth() : savedhealth);
|
||||
pet->SetPower(POWER_MANA, savedmana > pet->GetMaxPower(POWER_MANA) ? pet->GetMaxPower(POWER_MANA) : savedmana);
|
||||
}
|
||||
|
||||
pet->SetAsynchLoadType(asynchLoadType);
|
||||
|
||||
AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder)).AfterComplete([this, info](SQLQueryHolderBase const& holder)
|
||||
{
|
||||
HandleLoadPetFromDBSecondCallback(static_cast<LoadPetFromDBQueryHolder const&>(holder), info);
|
||||
});
|
||||
|
||||
return PET_LOAD_OK;
|
||||
}
|
||||
|
||||
void WorldSession::HandleLoadPetFromDBSecondCallback(LoadPetFromDBQueryHolder const& holder, AsynchPetSummon* info)
|
||||
{
|
||||
if (!GetPlayer())
|
||||
return;
|
||||
|
||||
Player* owner = GetPlayer();
|
||||
Pet* pet = owner->GetPet();
|
||||
if (!pet)
|
||||
return;
|
||||
|
||||
pet->_LoadAuras(holder.GetPreparedResult(PET_LOAD_QUERY_LOADAURAS), holder.GetDiffTime());
|
||||
bool current = holder.GetCurrent();
|
||||
uint32 summon_spell_id = pet->GetUInt32Value(UNIT_CREATED_BY_SPELL);
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(summon_spell_id); // CANT BE nullptr
|
||||
bool is_temporary_summoned = spellInfo && spellInfo->GetDuration() > 0;
|
||||
|
||||
// load action bar, if data broken will fill later by default spells.
|
||||
if (!is_temporary_summoned)
|
||||
{
|
||||
pet->GetCharmInfo()->LoadPetActionBar(holder.GetActionBar()); // action bar stored in already read string
|
||||
pet->_LoadSpells(holder.GetPreparedResult(PET_LOAD_QUERY_LOADSPELLS));
|
||||
pet->InitTalentForLevel(); // re-init to check talent count
|
||||
pet->_LoadSpellCooldowns(holder.GetPreparedResult(PET_LOAD_QUERY_LOADSPELLCOOLDOWN));
|
||||
pet->LearnPetPassives();
|
||||
pet->InitLevelupSpellsForLevel();
|
||||
pet->CastPetAuras(current);
|
||||
}
|
||||
|
||||
pet->CleanupActionBar(); // remove unknown spells from action bar after load
|
||||
owner->PetSpellInitialize();
|
||||
owner->SendTalentsInfoData(true);
|
||||
|
||||
if (owner->GetGroup())
|
||||
owner->SetGroupUpdateFlag(GROUP_UPDATE_PET);
|
||||
|
||||
//set last used pet number (for use in BG's)
|
||||
if (owner->GetTypeId() == TYPEID_PLAYER && pet->isControlled() && !pet->isTemporarySummoned() && (pet->getPetType() == SUMMON_PET || pet->getPetType() == HUNTER_PET))
|
||||
{
|
||||
owner->ToPlayer()->SetLastPetNumber(holder.GetPetNumber());
|
||||
owner->SetLastPetSpell(pet->GetUInt32Value(UNIT_CREATED_BY_SPELL));
|
||||
}
|
||||
|
||||
if (pet->getPetType() == SUMMON_PET && !current) //all (?) summon pets come with full health when called, but not when they are current
|
||||
{
|
||||
pet->SetPower(POWER_MANA, pet->GetMaxPower(POWER_MANA));
|
||||
pet->SetHealth(pet->GetMaxHealth());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!holder.GetSavedHealth() && pet->getPetType() == HUNTER_PET && pet->GetAsynchLoadType() != PET_LOAD_SUMMON_DEAD_PET)
|
||||
pet->setDeathState(JUST_DIED);
|
||||
else
|
||||
{
|
||||
pet->SetHealth(holder.GetSavedHealth() > pet->GetMaxHealth() ? pet->GetMaxHealth() : holder.GetSavedHealth());
|
||||
pet->SetPower(POWER_MANA, holder.GetSavedMana() > pet->GetMaxPower(POWER_MANA) ? pet->GetMaxPower(POWER_MANA) : holder.GetSavedMana());
|
||||
}
|
||||
}
|
||||
|
||||
pet->SetLoading(false);
|
||||
owner->SetTemporaryUnsummonedPetNumber(0); // clear this only if pet is loaded successfuly
|
||||
|
||||
// current
|
||||
if (current && owner->IsPetNeedBeTemporaryUnsummoned())
|
||||
{
|
||||
owner->UnsummonPetTemporaryIfAny();
|
||||
return;
|
||||
}
|
||||
|
||||
pet->HandleAsynchLoadSucceed();
|
||||
|
||||
if (info && info->m_healthPct)
|
||||
{
|
||||
pet->SetHealth(pet->CountPctFromMaxHealth(info->m_healthPct));
|
||||
}
|
||||
}
|
||||
|
||||
void WorldSession::HandleDismissCritter(WorldPacket& recvData)
|
||||
{
|
||||
ObjectGuid guid;
|
||||
@@ -1193,12 +844,18 @@ void WorldSession::HandlePetRename(WorldPacket& recvData)
|
||||
recvData >> name;
|
||||
recvData >> isdeclined;
|
||||
|
||||
PetStable* petStable = _player->GetPetStable();
|
||||
|
||||
Pet* pet = ObjectAccessor::GetPet(*_player, petguid);
|
||||
|
||||
// check it!
|
||||
if (!pet || !pet->IsPet() || ((Pet*)pet)->getPetType() != HUNTER_PET ||
|
||||
!pet->HasByteFlag(UNIT_FIELD_BYTES_2, 2, UNIT_CAN_BE_RENAMED) ||
|
||||
pet->GetOwnerGUID() != _player->GetGUID() || !pet->GetCharmInfo())
|
||||
!pet->HasByteFlag(UNIT_FIELD_BYTES_2, 2, UNIT_CAN_BE_RENAMED) ||
|
||||
pet->GetOwnerGUID() != _player->GetGUID() || !pet->GetCharmInfo() ||
|
||||
!petStable || !petStable->CurrentPet || petStable->CurrentPet->PetNumber != pet->GetCharmInfo()->GetPetNumber())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PetNameInvalidReason res = ObjectMgr::CheckPetName(name);
|
||||
if (res != PET_NAME_SUCCESS)
|
||||
@@ -1221,6 +878,9 @@ void WorldSession::HandlePetRename(WorldPacket& recvData)
|
||||
|
||||
pet->RemoveByteFlag(UNIT_FIELD_BYTES_2, 2, UNIT_CAN_BE_RENAMED);
|
||||
|
||||
petStable->CurrentPet->Name = name;
|
||||
petStable->CurrentPet->WasRenamed = true;
|
||||
|
||||
if (isdeclined)
|
||||
{
|
||||
for (uint8 i = 0; i < MAX_DECLINED_NAME_CASES; ++i)
|
||||
|
||||
Reference in New Issue
Block a user