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:
Kargatum
2021-12-31 04:45:13 +07:00
committed by GitHub
parent 24ab99919a
commit e12494d993
24 changed files with 1173 additions and 1208 deletions

View File

@@ -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)