From f98a6c3c6ead9237e771f576dfec24721e6b3842 Mon Sep 17 00:00:00 2001 From: Ovahlord Date: Thu, 21 Jul 2022 00:31:59 +0200 Subject: [PATCH] Core/Creatures: re-implement temporary summons (WIP) - replaced all temporary summon classes with new ones written from scratch to support all recently leaked informations in a clean and proper manner - controlled summons will no longer use the m_controlled container for that one is meant to be used by charmed units only - re-implemented pets from the ground up and handle their creation, updating and saving in a fast and efficient way - allow guardians to have passive auras and stats as well - defined and implemented PLAYER_FIELD_BYTE_HIDE_PET_BAR - converted most pet packets to packet class - made summon accessing type and moron safe - dropped a bazillion of hacks and tempfixes --- .../Implementation/CharacterDatabase.cpp | 36 +- .../Implementation/CharacterDatabase.h | 23 +- src/server/game/AI/CoreAI/PetAI.cpp | 8 +- src/server/game/AI/CreatureAI.cpp | 56 +- .../game/Battlegrounds/Battleground.cpp | 4 +- src/server/game/DataStores/DBCEnums.h | 65 +- src/server/game/DataStores/DBCStores.cpp | 4 - src/server/game/DataStores/DBCStructure.h | 16 +- src/server/game/DataStores/DBCfmt.h | 4 +- .../game/Entities/Creature/Creature.cpp | 22 +- .../Entities/Creature/TemporarySummon.cpp | 58 +- .../game/Entities/Creature/TemporarySummon.h | 3 - .../Creature/TemporarySummon/NewGuardian.cpp | 131 +++ .../Creature/TemporarySummon/NewGuardian.h | 51 + .../Creature/TemporarySummon/NewPet.cpp | 644 +++++++++++ .../Creature/TemporarySummon/NewPet.h | 108 ++ .../TemporarySummon/NewTemporarySummon.cpp | 307 +++++ .../TemporarySummon/NewTemporarySummon.h | 78 ++ src/server/game/Entities/Object/Object.cpp | 99 ++ .../game/Entities/Object/ObjectDefines.h | 3 +- src/server/game/Entities/Pet/Pet.cpp | 176 +-- src/server/game/Entities/Pet/Pet.h | 19 +- src/server/game/Entities/Pet/PetDefines.h | 68 +- src/server/game/Entities/Player/Player.cpp | 1010 +++++------------ src/server/game/Entities/Player/Player.h | 119 +- src/server/game/Entities/Totem/Totem.cpp | 13 - src/server/game/Entities/Unit/StatSystem.cpp | 221 ++++ src/server/game/Entities/Unit/Unit.cpp | 777 +++++-------- src/server/game/Entities/Unit/Unit.h | 107 +- src/server/game/Entities/Unit/UnitDefines.h | 22 +- src/server/game/Entities/Vehicle/Vehicle.cpp | 4 +- src/server/game/Globals/ObjectAccessor.cpp | 21 +- src/server/game/Globals/ObjectAccessor.h | 3 +- src/server/game/Globals/ObjectMgr.cpp | 2 +- src/server/game/Handlers/CharacterHandler.cpp | 16 +- src/server/game/Handlers/MiscHandler.cpp | 1 - src/server/game/Handlers/MovementHandler.cpp | 4 +- src/server/game/Handlers/NPCHandler.cpp | 148 +-- src/server/game/Handlers/PetHandler.cpp | 805 ++++--------- src/server/game/Handlers/SpellHandler.cpp | 21 +- src/server/game/Maps/Map.cpp | 17 +- src/server/game/Maps/Map.h | 10 +- src/server/game/Maps/MapScripts.cpp | 10 +- src/server/game/Phasing/PhasingHandler.cpp | 2 + src/server/game/Server/Packets/AllPackets.h | 1 + .../game/Server/Packets/CharacterPackets.cpp | 13 +- .../game/Server/Packets/PartyPackets.cpp | 4 + src/server/game/Server/Packets/PetPackets.cpp | 154 +++ src/server/game/Server/Packets/PetPackets.h | 195 +++- .../game/Server/Packets/QueryPackets.cpp | 14 + src/server/game/Server/Packets/QueryPackets.h | 14 + .../game/Server/Packets/TalentPackets.cpp | 51 + .../game/Server/Packets/TalentPackets.h | 66 ++ src/server/game/Server/WorldSession.cpp | 2 +- src/server/game/Server/WorldSession.h | 45 +- .../game/Spells/Auras/SpellAuraEffects.cpp | 21 +- src/server/game/Spells/Spell.cpp | 190 +++- src/server/game/Spells/Spell.h | 1 + src/server/game/Spells/SpellEffects.cpp | 422 +++---- src/server/game/Spells/SpellHistory.cpp | 30 + src/server/game/Spells/SpellHistory.h | 8 +- src/server/game/Spells/SpellInfo.cpp | 9 +- src/server/game/Tools/PlayerDump.cpp | 8 +- src/server/scripts/Commands/cs_npc.cpp | 13 +- src/server/scripts/Commands/cs_pet.cpp | 11 +- .../the_scarlet_enclave_chapter_1.cpp | 3 - .../boss_blackheart_the_inciter.cpp | 8 +- src/server/scripts/Spells/spell_dk.cpp | 12 +- src/server/scripts/Spells/spell_druid.cpp | 93 +- src/server/scripts/Spells/spell_generic.cpp | 58 +- src/server/scripts/Spells/spell_hunter.cpp | 74 -- src/server/scripts/Spells/spell_item.cpp | 2 +- src/server/scripts/Spells/spell_mage.cpp | 4 +- src/server/scripts/Spells/spell_paladin.cpp | 6 +- src/server/scripts/Spells/spell_shaman.cpp | 6 +- src/server/scripts/Spells/spell_warlock.cpp | 2 +- src/server/scripts/World/npcs_special.cpp | 75 +- 77 files changed, 3812 insertions(+), 3119 deletions(-) create mode 100644 src/server/game/Entities/Creature/TemporarySummon/NewGuardian.cpp create mode 100644 src/server/game/Entities/Creature/TemporarySummon/NewGuardian.h create mode 100644 src/server/game/Entities/Creature/TemporarySummon/NewPet.cpp create mode 100644 src/server/game/Entities/Creature/TemporarySummon/NewPet.h create mode 100644 src/server/game/Entities/Creature/TemporarySummon/NewTemporarySummon.cpp create mode 100644 src/server/game/Entities/Creature/TemporarySummon/NewTemporarySummon.h create mode 100644 src/server/game/Server/Packets/TalentPackets.cpp create mode 100644 src/server/game/Server/Packets/TalentPackets.h diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 209149dca5f..081e2b7a6fc 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -43,12 +43,12 @@ void CharacterDatabaseConnection::DoPrepareStatements() "subject, deliver_time, expire_time, money, has_items FROM mail WHERE receiver = ? ", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_MAIL_LIST_ITEMS, "SELECT itemEntry,count FROM item_instance WHERE guid = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_ENUM, "SELECT c.guid, c.name, c.race, c.class, c.gender, c.skin, c.face, c.hairStyle, c.hairColor, c.facialStyle, c.level, c.zone, c.map, c.position_x, c.position_y, c.position_z, " - "gm.guildid, c.playerFlags, c.at_login, cp.entry, cp.modelid, cp.level, c.equipmentCache, cb.guid, c.slot " - "FROM characters AS c LEFT JOIN character_pet AS cp ON c.guid = cp.owner AND cp.active = 1 LEFT JOIN guild_member AS gm ON c.guid = gm.guid " + "gm.guildid, c.playerFlags, c.at_login, cp.CreatureId, cp.TamedCreatureId, cp.DisplayId, c.equipmentCache, cb.guid, c.slot " + "FROM characters AS c LEFT JOIN character_pet AS cp ON c.guid = cp.Guid AND cp.IsActive = 1 LEFT JOIN guild_member AS gm ON c.guid = gm.guid " "LEFT JOIN character_banned AS cb ON c.guid = cb.guid AND cb.active = 1 WHERE c.account = ? AND c.deleteInfos_Name IS NULL", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_ENUM_DECLINED_NAME, "SELECT c.guid, c.name, c.race, c.class, c.gender, c.skin, c.face, c.hairStyle, c.hairColor, c.facialStyle, c.level, c.zone, c.map, " - "c.position_x, c.position_y, c.position_z, gm.guildid, c.playerFlags, c.at_login, cp.entry, cp.modelid, cp.level, c.equipmentCache, " - "cb.guid, c.slot, cd.genitive FROM characters AS c LEFT JOIN character_pet AS cp ON c.guid = cp.owner AND cp.active = 1 " + "c.position_x, c.position_y, c.position_z, gm.guildid, c.playerFlags, c.at_login, cp.CreatureId, cp.TamedCreatureId, cp.DisplayId, c.equipmentCache, " + "cb.guid, c.slot, cd.genitive FROM characters AS c LEFT JOIN character_pet AS cp ON c.guid = cp.Guid AND cp.IsActive = 1 " "LEFT JOIN character_declinedname AS cd ON c.guid = cd.guid LEFT JOIN guild_member AS gm ON c.guid = gm.guid " "LEFT JOIN character_banned AS cb ON c.guid = cb.guid AND cb.active = 1 WHERE c.account = ? AND c.deleteInfos_Name IS NULL", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_FREE_NAME, "SELECT guid, name, at_login FROM characters WHERE guid = ? AND account = ? AND NOT EXISTS (SELECT NULL FROM characters WHERE name = ?)", CONNECTION_ASYNC); @@ -611,19 +611,13 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_DEL_CALENDAR_INVITE, "DELETE FROM calendar_invites WHERE id = ?", CONNECTION_ASYNC); // Pet - PrepareStatement(CHAR_SEL_PET_SLOTS, "SELECT owner, slot FROM character_pet WHERE owner = ? AND slot >= ? AND slot <= ? ORDER BY slot", CONNECTION_ASYNC); - PrepareStatement(CHAR_SEL_PET_SLOTS_DETAIL, "SELECT slot, id, entry, modelid, level, name FROM character_pet WHERE owner = ? AND slot >= ? AND slot <= ? ORDER BY slot", CONNECTION_ASYNC); - PrepareStatement(CHAR_SEL_PET_ENTRY, "SELECT entry FROM character_pet WHERE owner = ? AND id = ? AND slot >= ? AND slot <= ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_SEL_PET_SLOT_BY_ID, "SELECT slot, id, entry FROM character_pet WHERE owner = ? AND id = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_SEL_PET_SPELL_LIST, "SELECT DISTINCT pet_spell.spell FROM pet_spell, character_pet WHERE character_pet.owner = ? AND character_pet.id = pet_spell.guid AND character_pet.id <> ?", CONNECTION_SYNCH); - PrepareStatement(CHAR_SEL_CHAR_PETS, "SELECT id FROM character_pet WHERE owner = ?", CONNECTION_SYNCH); - PrepareStatement(CHAR_DEL_CHAR_PET_DECLINEDNAME_BY_OWNER, "DELETE FROM character_pet_declinedname WHERE owner = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_DEL_CHAR_PET_DECLINEDNAME, "DELETE FROM character_pet_declinedname WHERE id = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_INS_CHAR_PET_DECLINEDNAME, "INSERT INTO character_pet_declinedname (id, owner, genitive, dative, accusative, instrumental, prepositional) VALUES (?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); + //PrepareStatement(CHAR_DEL_CHAR_PET_DECLINEDNAME_BY_OWNER, "DELETE FROM character_pet_declinedname WHERE Guid = ?", CONNECTION_ASYNC); + //PrepareStatement(CHAR_DEL_CHAR_PET_DECLINEDNAME, "DELETE FROM character_pet_declinedname WHERE PetNumber = ?", CONNECTION_ASYNC); + //PrepareStatement(CHAR_INS_CHAR_PET_DECLINEDNAME, "INSERT INTO character_pet_declinedname (PetNumber, Guid, genitive, dative, accusative, instrumental, prepositional) VALUES (?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_PET_AURA, "SELECT casterGuid, spell, effectMask, recalculateMask, stackCount, amount0, amount1, amount2, base_amount0, base_amount1, base_amount2, maxDuration, remainTime, remainCharges, critChance, applyResilience FROM pet_aura WHERE guid = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_PET_SPELL, "SELECT spell, active FROM pet_spell WHERE guid = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_PET_SPELL_COOLDOWN, "SELECT spell, time, categoryId, categoryEnd FROM pet_spell_cooldown WHERE guid = ? AND time > UNIX_TIMESTAMP()", CONNECTION_SYNCH); - PrepareStatement(CHAR_SEL_PET_DECLINED_NAME, "SELECT genitive, dative, accusative, instrumental, prepositional FROM character_pet_declinedname WHERE owner = ? AND id = ?", CONNECTION_SYNCH); + PrepareStatement(CHAR_SEL_PET_DECLINED_NAME, "SELECT genitive, dative, accusative, instrumental, prepositional FROM character_pet_declinedname WHERE Guid = ? AND PetNumber = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_DEL_PET_AURAS, "DELETE FROM pet_aura WHERE guid = ?", CONNECTION_BOTH); PrepareStatement(CHAR_DEL_PET_SPELLS, "DELETE FROM pet_spell WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_PET_SPELL_COOLDOWNS, "DELETE FROM pet_spell_cooldown WHERE guid = ?", CONNECTION_BOTH); @@ -632,14 +626,12 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_INS_PET_SPELL, "INSERT INTO pet_spell (guid, spell, active) VALUES (?, ?, ?)", CONNECTION_BOTH); PrepareStatement(CHAR_INS_PET_AURA, "INSERT INTO pet_aura (guid, casterGuid, spell, effectMask, recalculateMask, stackCount, amount0, amount1, amount2, " "base_amount0, base_amount1, base_amount2, maxDuration, remainTime, remainCharges, critChance, applyResilience) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_BOTH); - PrepareStatement(CHAR_DEL_CHAR_PET_BY_OWNER, "DELETE FROM character_pet WHERE owner = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_UPD_CHAR_PET_NAME, "UPDATE character_pet SET name = ?, renamed = 1 WHERE owner = ? AND id = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_UPD_CHAR_PET_SLOT_BY_SLOT, "UPDATE character_pet SET slot = ? WHERE owner = ? AND slot = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID, "UPDATE character_pet SET slot = ? WHERE owner = ? AND id = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_DEL_CHAR_PET_BY_ID, "DELETE FROM character_pet WHERE id = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_INS_PET, "INSERT INTO character_pet (id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, active, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType) " - "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); - PrepareStatement(CHAR_SEL_CHAR_ALL_PETS_DETAIL, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, active, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ? ORDER BY slot", CONNECTION_ASYNC); + + PrepareStatement(CHAR_DEL_CHAR_PETS, "DELETE FROM character_pet WHERE Guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_CHAR_PET, "DELETE FROM character_pet WHERE PetNumber = ?", CONNECTION_BOTH); + PrepareStatement(CHAR_SEL_CHAR_PET, "SELECT PetNumber, CreatureId, TamedCreatureId, DisplayId, SavedHealth, SavedPower, CreatedBySpellId, LastSaveTime, ReactState, Slot, HasBeenRenamed, IsActive, `Name`, ActionBar, Talents FROM character_pet WHERE Guid = ? ORDER BY Slot", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_CHAR_PET, "UPDATE character_pet SET SavedHealth = ?, SavedPower = ?, LastSaveTime = ?, ReactState = ?, Slot = ?, HasBeenRenamed = ?, IsActive = ?, `Name` = ?, ActionBar = ?, Talents = ? WHERE PetNumber = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_CHAR_PET, "INSERT INTO character_pet (Guid, PetNumber, CreatureId, TamedCreatureId, DisplayId, SavedHealth, SavedPower, CreatedBySpellId, LastSaveTime, ReactState, Slot, HasBeenRenamed, IsActive, `Name`, ActionBar, Talents) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_BOTH); // PvPstats PrepareStatement(CHAR_SEL_PVPSTATS_MAXID, "SELECT MAX(id) FROM pvpstats_battlegrounds", CONNECTION_SYNCH); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index db746cf3a59..3e8f7cf53b6 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -529,24 +529,13 @@ enum CharacterDatabaseStatements : uint32 CHAR_DEL_PET_SPELL_BY_SPELL, CHAR_INS_PET_SPELL, CHAR_INS_PET_AURA, - CHAR_DEL_PET_SPELLS, - CHAR_DEL_CHAR_PET_BY_OWNER, - CHAR_DEL_CHAR_PET_DECLINEDNAME_BY_OWNER, - CHAR_SEL_PET_SLOTS, - CHAR_SEL_PET_SLOTS_DETAIL, - CHAR_SEL_PET_ENTRY, - CHAR_SEL_PET_SLOT_BY_ID, - CHAR_SEL_PET_SPELL_LIST, - CHAR_SEL_CHAR_PETS, - CHAR_DEL_CHAR_PET_DECLINEDNAME, - CHAR_INS_CHAR_PET_DECLINEDNAME, - CHAR_UPD_CHAR_PET_NAME, - CHAR_UPD_CHAR_PET_SLOT_BY_SLOT, - CHAR_UPD_CHAR_PET_SLOT_BY_ID, - CHAR_DEL_CHAR_PET_BY_ID, - CHAR_INS_PET, - CHAR_SEL_CHAR_ALL_PETS_DETAIL, + + CHAR_INS_CHAR_PET, + CHAR_DEL_CHAR_PET, + CHAR_DEL_CHAR_PETS, + CHAR_UPD_CHAR_PET, + CHAR_SEL_CHAR_PET, CHAR_SEL_ITEMCONTAINER_ITEMS, CHAR_DEL_ITEMCONTAINER_ITEMS, diff --git a/src/server/game/AI/CoreAI/PetAI.cpp b/src/server/game/AI/CoreAI/PetAI.cpp index 8416b5300a7..3cf39b99e15 100644 --- a/src/server/game/AI/CoreAI/PetAI.cpp +++ b/src/server/game/AI/CoreAI/PetAI.cpp @@ -33,12 +33,8 @@ int32 PetAI::Permissible(Creature const* creature) { - if (creature->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN)) - { - if (reinterpret_cast(creature)->GetOwner()->GetTypeId() == TYPEID_PLAYER) - return PERMIT_BASE_PROACTIVE; - return PERMIT_BASE_REACTIVE; - } + if (creature->IsPet()) + return PERMIT_BASE_PROACTIVE; return PERMIT_BASE_NO; } diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp index da7d4cf170d..2c9d012617c 100644 --- a/src/server/game/AI/CreatureAI.cpp +++ b/src/server/game/AI/CreatureAI.cpp @@ -31,6 +31,7 @@ #include "SpellMgr.h" #include "SpellHistory.h" #include "TemporarySummon.h" +#include "NewTemporarySummon.h" #include "Vehicle.h" //Disable CreatureAI when charmed @@ -92,9 +93,6 @@ void CreatureAI::DoZoneInCombat(Creature* creature /*= nullptr*/) creature->EngageWithTarget(player); - for (Unit* pet : player->m_Controlled) - creature->EngageWithTarget(pet); - if (Unit* vehicle = player->GetVehicleBase()) creature->EngageWithTarget(vehicle); } @@ -154,56 +152,20 @@ void CreatureAI::TriggerAlert(Unit const* who) const me->SetFacingTo(me->GetAngle(who)); } -// adapted from logic in Spell:EFfectSummonType before commit 8499434 -static bool ShouldFollowOnSpawn(SummonPropertiesEntry const* properties) -{ - // Summons without SummonProperties are generally scripted summons that don't belong to any owner - if (!properties) - return false; - - switch (properties->Control) - { - case SUMMON_CATEGORY_PET: - return true; - case SUMMON_CATEGORY_WILD: - case SUMMON_CATEGORY_ALLY: - case SUMMON_CATEGORY_UNK: - if (properties->Flags & 512) - return true; - - // Guides. They have their own movement - if (properties->Flags & SUMMON_PROP_FLAG_UNK14) - return false; - - switch (SummonTitle(properties->Title)) - { - case SummonTitle::Pet: - case SummonTitle::Guardian: - case SummonTitle::Runeblade: - case SummonTitle::Minion: - case SummonTitle::Companion: - return true; - default: - return false; - } - default: - return false; - } -} void CreatureAI::JustAppeared() { if (!IsEngaged()) { - if (TempSummon* summon = me->ToTempSummon()) + if (!me->IsSummon()) + return; + + NewTemporarySummon* summon = me->ToTemporarySummon(); + if (summon->ShouldJoinSummonerSpawnGroupAfterCreation() || summon->ShouldFollowSummonerAfterCreation() && !summon->GetVehicle()) { - // Only apply this to specific types of summons - if (!summon->GetVehicle() && ShouldFollowOnSpawn(summon->m_Properties)) + if (Unit* summoner = summon->GetSummoner()) { - if (Unit* owner = summon->GetCharmerOrOwner()) - { - summon->GetMotionMaster()->Clear(); - summon->FollowTarget(owner); - } + summon->GetMotionMaster()->Clear(); + summon->FollowTarget(summoner); // @todo: ShouldJoinSummonerSpawnGroupAfterCreation should actually make the creature join the target's formation } } } diff --git a/src/server/game/Battlegrounds/Battleground.cpp b/src/server/game/Battlegrounds/Battleground.cpp index 61671b35350..eb5b3ce3a39 100644 --- a/src/server/game/Battlegrounds/Battleground.cpp +++ b/src/server/game/Battlegrounds/Battleground.cpp @@ -983,7 +983,7 @@ void Battleground::RemovePlayerAtLeave(ObjectGuid guid, bool Transport, bool Sen bgTypeId = BATTLEGROUND_AA; // set the bg type to all arenas (it will be used for queue refreshing) // unsummon current and summon old pet if there was one and there isn't a current pet - player->RemovePet(nullptr, PET_SAVE_DISMISS); player->ResummonPetTemporaryUnSummonedIfAny(); + //player->RemovePet(nullptr, PET_SAVE_DISMISS); player->ResummonPetTemporaryUnSummonedIfAny(); } if (SendPacket) @@ -1121,7 +1121,7 @@ void Battleground::AddPlayer(Player* player) { player->RemoveArenaEnchantments(TEMP_ENCHANTMENT_SLOT); player->DestroyConjuredItems(true); - player->UnsummonPetTemporaryIfAny(); + //player->UnsummonPetTemporaryIfAny(); if (GetStatus() == STATUS_WAIT_JOIN) // not started yet { diff --git a/src/server/game/DataStores/DBCEnums.h b/src/server/game/DataStores/DBCEnums.h index bcde40339fb..119bfc1cd2a 100644 --- a/src/server/game/DataStores/DBCEnums.h +++ b/src/server/game/DataStores/DBCEnums.h @@ -632,9 +632,9 @@ enum class SpellItemEnchantmentFlags : uint32 DEFINE_ENUM_FLAG(SpellItemEnchantmentFlags); -#define MAX_TALENT_RANK 5 -#define MAX_PET_TALENT_RANK 3 // use in calculations, expected <= MAX_TALENT_RANK -#define MAX_TALENT_TABS 3 +static constexpr uint8 MAX_TALENT_RANK = 3; +static constexpr uint8 MAX_PET_TALENT_RANK = 3; // use in calculations, expected <= MAX_TALENT_RANK +static constexpr uint8 MAX_TALENT_TABS = 3; enum class SpellShapeshiftFormFlags : int32 { @@ -713,31 +713,30 @@ enum class SummonPropertiesSlot : int8 AnyAvailableTotem = -1 }; -// SummonProperties.dbc, col 5 enum class SummonPropertiesFlags : uint32 { None = 0x000000, - AttackSummoner = 0x000001, - HelpWhenSummonedInCombat = 0x000002, - UseLevelOffset = 0x000004, - DespawnOnSummonerDeath = 0x000008, - OnlyVisibleToSummoner = 0x000010, - CannotDismissPet = 0x000020, - UseDemonTimeout = 0x000040, - UnlimitedSummons = 0x000080, - UseCreatureLevel = 0x000100, - JoinSummonerSpawnGroup = 0x000200, - DoNotToggle = 0x000400, - DespawnWhenExpired = 0x000800, - UseSummonerFaction = 0x001000, - DoNotFollowMountedSummoner = 0x002000, - SavePetAutocast = 0x004000, - IgnoreSummonerPhase = 0x008000, - OnlyVisibleToSummonerGroup = 0x010000, - DespawnOnSummonerLogout = 0x020000, - CastRideVehicleSpellOnSummoner = 0x040000, - GuardianActsLikePet = 0x080000, - DontSnapSessileToGround = 0x100000 + AttackSummoner = 0x000001, // Implemented in TemporarySummon::HandlePostSummonActions + HelpWhenSummonedInCombat = 0x000002, // Implemented in TemporarySummon::HandlePostSummonActions + UseLevelOffset = 0x000004, // NYI + DespawnOnSummonerDeath = 0x000008, // Implemented in Unit::UnsummonAllSummonsDueToDeath + OnlyVisibleToSummoner = 0x000010, // Implemented in Spell::EffectSummonType + CannotDismissPet = 0x000020, // Implemented in PetHandler.cpp HandlePetActionHelper + UseDemonTimeout = 0x000040, // NYI + UnlimitedSummons = 0x000080, // NYI + UseCreatureLevel = 0x000100, // Implemented in TemporarySummon::HandlePreSummonActions + JoinSummonerSpawnGroup = 0x000200, // Implemented in CreatureAI::JustAppeared + DoNotToggle = 0x000400, // NYI + DespawnWhenExpired = 0x000800, // Implemented in TemporarySummon::Update + UseSummonerFaction = 0x001000, // Implemented in TemporarySummon::HandlePreSummonActions + DoNotFollowMountedSummoner = 0x002000, // NYI + SavePetAutocast = 0x004000, // Implemented in Pet::Dismiss + IgnoreSummonerPhase = 0x008000, // Wild Only - Implemented in Map::SummonCreature + OnlyVisibleToSummonerGroup = 0x010000, // Implemented in Spell::EffectSummonType + DespawnOnSummonerLogout = 0x020000, // Implemented in Unit::UnsummonAllSummonsOnLogout + CastRideVehicleSpellOnSummoner = 0x040000, // NYI + GuardianActsLikePet = 0x080000, // NYI - unused 4.3.4.15595 + DontSnapSessileToGround = 0x100000 // NYI }; DEFINE_ENUM_FLAG(SummonPropertiesFlags); @@ -890,4 +889,20 @@ enum class CurrencyTypeFlags : uint32 DEFINE_ENUM_FLAG(CurrencyTypeFlags); +enum class ChrClassesFlags : uint32 +{ + None = 0x000, + PlayerClass = 0x001, + UseLoincloth = 0x002, + DisplayPet = 0x004, + Unused = 0x008, + CanWearScalingStatMail = 0x010, + CanWearScalingStatPlate = 0x020, + BindStartingArea = 0x040, + PetBarInitiallyHidden = 0x080, + SendStableAtLogin = 0x100 +}; + +DEFINE_ENUM_FLAG(ChrClassesFlags); + #endif diff --git a/src/server/game/DataStores/DBCStores.cpp b/src/server/game/DataStores/DBCStores.cpp index 6542d5dc9c2..f0340366402 100644 --- a/src/server/game/DataStores/DBCStores.cpp +++ b/src/server/game/DataStores/DBCStores.cpp @@ -637,10 +637,6 @@ void DBCManager::LoadStores(const std::string& dataPath, uint32 defaultLocale) if (!spellInfo) continue; - SpellLevelsEntry const* levels = sSpellLevelsStore.LookupEntry(spellInfo->LevelsID); - if (spellInfo->LevelsID && (!levels || levels->SpellLevel)) - continue; - if (spellInfo && spellInfo->Attributes & SPELL_ATTR0_PASSIVE) { for (CreatureFamilyEntry const* cFamily : sCreatureFamilyStore) diff --git a/src/server/game/DataStores/DBCStructure.h b/src/server/game/DataStores/DBCStructure.h index 361a4dcbaf7..489f4d172a1 100644 --- a/src/server/game/DataStores/DBCStructure.h +++ b/src/server/game/DataStores/DBCStructure.h @@ -398,12 +398,14 @@ struct ChrClassesEntry //char* Name_male; // 5 //char* Filename // 6 uint32 SpellClassSet; // 7 - //uint32 Flags; // 8 (0x08 HasRelicSlot) + uint32 Flags; // 8 uint32 CinematicSequenceID; // 9 uint32 Required_expansion; // 10 uint32 AttackPowerPerStrength; // 11 uint32 AttackPowerPerAgility; // 12 uint32 RangedAttackPowerPerAgility; // 13 + + EnumFlag GetFlags() const { return static_cast(Flags); } }; struct ChrRacesEntry @@ -2009,12 +2011,12 @@ struct TalentEntry uint32 TabID; // 1 index in TalentTab.dbc (TalentTabEntry) uint32 TierID; // 2 uint32 ColumnIndex; // 3 - uint32 SpellRank[MAX_TALENT_RANK]; // 4-8 - uint32 PrereqTalent[3]; // 9 - 11 (Talent.dbc) - uint32 PrereqRank[3]; // 12 - 14 part of prev field - //uint32 Flags; // 15 also need disable higest ranks on reset talent tree - //uint32 RequiredSpellID; // 16 - //uint64 CategoryMask[2]; // 17 - 18 its a 64 bit mask for pet 1 << m_categoryEnumID in CreatureFamily.dbc + uint32 SpellRank[MAX_TALENT_RANK]; // 4-6 + uint32 PrereqTalent[MAX_TALENT_RANK]; // 7 - 9 (Talent.dbc) + uint32 PrereqRank[MAX_TALENT_RANK]; // 10 - 12 part of prev field + uint32 Flags; // 13 also need disable higest ranks on reset talent tree + uint32 RequiredSpellID; // 14 + uint64 CategoryMask[2]; // 15 - 6 its a 64 bit mask for pet 1 << m_categoryEnumID in CreatureFamily.dbc }; #define MAX_MASTERY_SPELLS 2 diff --git a/src/server/game/DataStores/DBCfmt.h b/src/server/game/DataStores/DBCfmt.h index 74b96ea6492..9be7baae887 100644 --- a/src/server/game/DataStores/DBCfmt.h +++ b/src/server/game/DataStores/DBCfmt.h @@ -40,7 +40,7 @@ char const CharStartOutfitEntryfmt[] = "dbbbXiiiiiiiiiiiiiiiiiiiiiiiixxxxxxxxxxx char const CharSectionsEntryfmt[] = "diiixxxiii"; char const CharTitlesEntryfmt[] = "nxssix"; char const ChatChannelsEntryfmt[] = "nixsx"; -char const ChrClassesEntryfmt[] = "nixsxxxixiiiii"; +char const ChrClassesEntryfmt[] = "nixsxxxiiiiiii"; char const ChrRacesEntryfmt[] = "niixiixixxxxixsxxxxxixxx"; char const ChrClassesXPowerTypesfmt[] = "nii"; char const CinematicCameraEntryfmt[] = "nsiffff"; @@ -181,7 +181,7 @@ char const SpellVisualfmt[] = "dxxxxxxiixxxxxxxxxxxxxxxxxxxxxxxi"; char const SpellVisualKitfmt[] = "niiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii"; char const StableSlotPricesfmt[] = "ni"; char const SummonPropertiesfmt[] = "niiiii"; -char const TalentEntryfmt[] = "niiiiiiiiiiiiiixxxx"; +char const TalentEntryfmt[] = "niiiiiiiiiiiiiiiiii"; char const TalentTabEntryfmt[] = "nxxiiixxxii"; char const TalentTreePrimarySpellsfmt[] = "diix"; char const TaxiNodesEntryfmt[] = "nifffsiiiff"; diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index da81949dc6f..f273e9e81d6 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -50,6 +50,7 @@ #include "SpellAuraEffects.h" #include "SpellMgr.h" #include "TemporarySummon.h" +#include "NewTemporarySummon.h" #include "Transport.h" #include "Util.h" #include "Vehicle.h" @@ -454,6 +455,12 @@ void Creature::RemoveCorpse(bool setSpawnTime, bool destroyForNearbyPlayers) SaveRespawnTime(); } + if (NewTemporarySummon* summon = ToTemporarySummon()) + { + summon->Unsummon(); + return; + } + if (TempSummon* summon = ToTempSummon()) summon->UnSummon(); else @@ -1083,15 +1090,6 @@ Unit* Creature::SelectVictim() target = owner->getAttackerForHelper(); if (!target) { - for (ControlList::const_iterator itr = owner->m_Controlled.begin(); itr != owner->m_Controlled.end(); ++itr) - { - if ((*itr)->IsInCombat()) - { - target = (*itr)->getAttackerForHelper(); - if (target) - break; - } - } } } } @@ -2163,10 +2161,6 @@ void Creature::LoadTemplateImmunities() for (uint32 i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; ++i) ApplySpellImmune(placeholderSpellId, IMMUNITY_SCHOOL, 1 << i, false); - // don't inherit immunities for hunter pets - if (GetOwnerOrCreatorGUID().IsPlayer() && IsHunterPet()) - return; - if (uint32 mask = GetCreatureTemplate()->MechanicImmuneMask) { for (uint32 i = MECHANIC_NONE + 1; i < MAX_MECHANIC; ++i) @@ -2593,7 +2587,7 @@ bool Creature::LoadCreaturesAddon() SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, creatureAddon->pvpFlags); // These fields must only be handled by core internals and must not be modified via scripts/DB data - SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PET_FLAGS, 0); + //SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PET_FLAGS, 0); SetShapeshiftForm(FORM_NONE); if (creatureAddon->emote != 0) diff --git a/src/server/game/Entities/Creature/TemporarySummon.cpp b/src/server/game/Entities/Creature/TemporarySummon.cpp index b644a69faf1..1c5b90fa7ca 100644 --- a/src/server/game/Entities/Creature/TemporarySummon.cpp +++ b/src/server/game/Entities/Creature/TemporarySummon.cpp @@ -31,7 +31,7 @@ m_timer(0), m_lifetime(0) if (owner) m_summonerGUID = owner->GetGUID(); - m_unitTypeMask |= UNIT_MASK_SUMMON; + m_unitTypeMask |= UNIT_MASK_SUMMON_OLD; } Unit* TempSummon::GetSummoner() const @@ -183,16 +183,6 @@ void TempSummon::InitStats(uint32 duration) if (owner) { int32 slot = m_Properties->Slot; - if (slot > 0) - { - if (owner->m_SummonSlot[slot] && owner->m_SummonSlot[slot] != GetGUID()) - { - Creature* oldSummon = GetMap()->GetCreature(owner->m_SummonSlot[slot]); - if (oldSummon && oldSummon->IsSummon()) - oldSummon->ToTempSummon()->UnSummon(); - } - owner->m_SummonSlot[slot] = GetGUID(); - } if (m_Properties->Control != SUMMON_CATEGORY_WILD) { @@ -213,9 +203,6 @@ void TempSummon::InitStats(uint32 duration) } } } - - if (owner->IsTotem()) - owner->m_Controlled.insert(this); } // If property has a faction defined, use it. @@ -257,7 +244,7 @@ void TempSummon::UnSummon(uint32 msTime) //ASSERT(!IsPet()); if (IsPet()) { - ((Pet*)this)->Remove(PET_SAVE_DISMISS); + //((Pet*)this)->Remove(PET_SAVE_DISMISS); ASSERT(!IsInWorld()); return; } @@ -285,11 +272,6 @@ void TempSummon::RemoveFromWorld() if (m_Properties) { - int32 slot = m_Properties->Slot; - if (slot > 0) - if (Unit* owner = GetSummoner()) - if (owner->m_SummonSlot[slot] == GetGUID()) - owner->m_SummonSlot[slot].Clear(); } //if (GetOwnerGUID()) @@ -309,20 +291,6 @@ void Minion::InitStats(uint32 duration) { TempSummon::InitStats(duration); SetReactState(REACT_PASSIVE); - - // Controlable guardians and minions shall receive a summoner guid - if ((IsMinion() || IsControlableGuardian()) && !IsTotem() && !IsVehicle()) - GetOwner()->SetMinion(this, true); - else if (!IsPet() && !IsHunterPet()) - { - GetOwner()->m_Controlled.insert(this); - - // Store the totem elementals in players controlled list as well to trigger aggro mechanics - if (GetOwner()->IsTotem()) - if (Unit* totemOwner = GetOwner()->GetOwner()) - totemOwner->m_Controlled.insert(this); - } - if (m_Properties && m_Properties->Slot == SUMMON_SLOT_MINIPET) { SelectLevel(); // some summoned creaters have different from 1 DB data for level/hp @@ -338,21 +306,6 @@ void Minion::RemoveFromWorld() Unit* owner = GetOwner(); - if ((IsMinion() || IsControlableGuardian()) && !IsTotem() && !IsVehicle()) - owner->SetMinion(this, false); - else if (!IsPet() && !IsHunterPet()) - { - if (owner->m_Controlled.find(this) != owner->m_Controlled.end()) - owner->m_Controlled.erase(this); - else - TC_LOG_FATAL("entities.unit", "Minion::RemoveFromWorld: Owner %s tried to remove a non-existing controlled unit %s from controlled unit set.", owner->GetGUID().ToString().c_str(), GetGUID().ToString().c_str()); - - if (owner->IsTotem()) - if (Unit* totemOwner = owner->GetOwner()) - if (totemOwner->m_Controlled.find(this) != totemOwner->m_Controlled.end()) - totemOwner->m_Controlled.erase(this); - } - TempSummon::RemoveFromWorld(); } @@ -403,13 +356,6 @@ void Guardian::InitStats(uint32 duration) void Guardian::InitSummon() { TempSummon::InitSummon(); - - if (GetOwner()->GetTypeId() == TYPEID_PLAYER - && GetOwner()->GetMinionGUID() == GetGUID() - && !GetOwner()->GetCharmedGUID()) - { - GetOwner()->ToPlayer()->CharmSpellInitialize(); - } } Puppet::Puppet(SummonPropertiesEntry const* properties, Unit* owner) diff --git a/src/server/game/Entities/Creature/TemporarySummon.h b/src/server/game/Entities/Creature/TemporarySummon.h index 39de736d898..0939aacee8e 100644 --- a/src/server/game/Entities/Creature/TemporarySummon.h +++ b/src/server/game/Entities/Creature/TemporarySummon.h @@ -63,9 +63,6 @@ enum PlayerPetSpells // Risen Ghoul SPELL_PET_RISEN_GHOUL_SPAWN_IN = 47448, SPELL_PET_RISEN_GHOUL_SELF_STUN = 47466, - - // Hunter Pets - SPELL_PET_ENERGIZE = 99289 }; class TC_GAME_API TempSummon : public Creature diff --git a/src/server/game/Entities/Creature/TemporarySummon/NewGuardian.cpp b/src/server/game/Entities/Creature/TemporarySummon/NewGuardian.cpp new file mode 100644 index 00000000000..b6dc1ce5829 --- /dev/null +++ b/src/server/game/Entities/Creature/TemporarySummon/NewGuardian.cpp @@ -0,0 +1,131 @@ + +#include "NewGuardian.h" +#include "DBCStores.h" +#include "ObjectMgr.h" +#include "NewPet.h" +#include "SpellMgr.h" +#include "SpellInfo.h" + +NewGuardian::NewGuardian(SummonPropertiesEntry const* properties, Unit* summoner, bool isWorldObject) : + NewTemporarySummon(properties, summoner, isWorldObject), _isUsingRealStats(false) +{ + m_unitTypeMask |= UNIT_MASK_GUARDIAN; +} + +void NewGuardian::AddToWorld() +{ + // Setting the guid before adding to world to reduce building unnecessary object update packets + SetCreatorGUID(GetInternalSummonerGUID()); + + NewTemporarySummon::AddToWorld(); +} + +bool NewGuardian::HandlePreSummonActions(Unit const* summoner, uint8 creatureLevel, uint8 maxSummons) +{ + if (!NewTemporarySummon::HandlePreSummonActions(summoner, creatureLevel, maxSummons)) + return false; + + // Guardians inherit their summoner's faction and level unless a flag forbids it or the properties override it + if (summoner) + { + if (!_summonProperties || _summonProperties->Faction == 0) + SetFaction(summoner->GetFaction()); + + if (!_summonProperties || !_summonProperties->GetFlags().HasFlag(SummonPropertiesFlags::UseCreatureLevel)) + SetLevel(summoner->getLevel()); + } + + InitializeStats(); + + return true; +} + +void NewGuardian::InitializeStats() +{ + CreatureTemplate const* creatureInfo = GetCreatureTemplate(); + if (!creatureInfo) + return; + + SetMeleeDamageSchool(SpellSchools(creatureInfo->dmgschool)); + + uint32 creatureIdForStats = creatureInfo->Entry; + if (IsPet() && ToNewPet()->IsHunterPet()) + { + creatureIdForStats = 1; // hunter pet level stats are stored under creatureId 1 in pet_levelstats + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_CLASS, CLASS_WARRIOR); + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_GENDER, GENDER_NONE); + SetAttackTime(BASE_ATTACK, BASE_ATTACK_TIME); + SetAttackTime(OFF_ATTACK, BASE_ATTACK_TIME); + SetAttackTime(RANGED_ATTACK, BASE_ATTACK_TIME); + SetSheath(SHEATH_STATE_MELEE); + SetPowerType(POWER_FOCUS); + } + else + { + SetAttackTime(BASE_ATTACK, creatureInfo->BaseAttackTime); + SetAttackTime(OFF_ATTACK, creatureInfo->BaseAttackTime); + SetAttackTime(RANGED_ATTACK, creatureInfo->RangeAttackTime); + } + + if (PetLevelInfo const* petLevelInfo = sObjectMgr->GetPetLevelInfo(creatureIdForStats, getLevel())) + { + for (uint8 i = 0; i < MAX_STATS; ++i) + SetCreateStat(Stats(i), float(petLevelInfo->stats[i])); + + if (petLevelInfo->armor > 0) + SetStatFlatModifier(UNIT_MOD_ARMOR, BASE_VALUE, float(petLevelInfo->armor)); + + SetMaxHealth(petLevelInfo->health); + SetCreateHealth(petLevelInfo->health); + SetCreateMana(petLevelInfo->mana); + + _isUsingRealStats = true; + } + else + { + // Guardian has no pet level data, fall back to default creature behavior of Creature::UpdateEntry + uint32 previousHealth = GetHealth(); + UpdateLevelDependantStats(); + if (previousHealth > 0) + SetHealth(previousHealth); + + SetMeleeDamageSchool(SpellSchools(creatureInfo->dmgschool)); + SetStatFlatModifier(UNIT_MOD_RESISTANCE_HOLY, BASE_VALUE, float(creatureInfo->resistance[SPELL_SCHOOL_HOLY])); + SetStatFlatModifier(UNIT_MOD_RESISTANCE_FIRE, BASE_VALUE, float(creatureInfo->resistance[SPELL_SCHOOL_FIRE])); + SetStatFlatModifier(UNIT_MOD_RESISTANCE_NATURE, BASE_VALUE, float(creatureInfo->resistance[SPELL_SCHOOL_NATURE])); + SetStatFlatModifier(UNIT_MOD_RESISTANCE_FROST, BASE_VALUE, float(creatureInfo->resistance[SPELL_SCHOOL_FROST])); + SetStatFlatModifier(UNIT_MOD_RESISTANCE_SHADOW, BASE_VALUE, float(creatureInfo->resistance[SPELL_SCHOOL_SHADOW])); + SetStatFlatModifier(UNIT_MOD_RESISTANCE_ARCANE, BASE_VALUE, float(creatureInfo->resistance[SPELL_SCHOOL_ARCANE])); + + SetCanModifyStats(true); + } + + UpdateAllStats(); +} + +void NewGuardian::HandlePostSummonActions() +{ + NewTemporarySummon::HandlePostSummonActions(); + + //CastPassiveAuras(); +} + +void NewGuardian::CastPassiveAuras() +{ + CreatureTemplate const* creatureInfo = GetCreatureTemplate(); + if (!creatureInfo) + return; + + CreatureFamilyEntry const* creatureFamilyEntry = sCreatureFamilyStore.LookupEntry(creatureInfo->family); + if (!creatureFamilyEntry) + return; + + PetFamilySpellsStore::const_iterator petSpellStore = sPetFamilySpellsStore.find(creatureFamilyEntry->ID); + if (petSpellStore == sPetFamilySpellsStore.end()) + return; + + for (uint32 spellId : petSpellStore->second) + if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId)) + if (spellInfo->IsPassive()) + CastSpell(this, spellId); +} diff --git a/src/server/game/Entities/Creature/TemporarySummon/NewGuardian.h b/src/server/game/Entities/Creature/TemporarySummon/NewGuardian.h new file mode 100644 index 00000000000..6150dbe9b94 --- /dev/null +++ b/src/server/game/Entities/Creature/TemporarySummon/NewGuardian.h @@ -0,0 +1,51 @@ +/* + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef Guardian_h__ +#define Guardian_h__ + +#include "NewTemporarySummon.h" + +class TC_GAME_API NewGuardian : public NewTemporarySummon +{ +public: + explicit NewGuardian(SummonPropertiesEntry const* properties, Unit* owner, bool isWorldObject); + + void AddToWorld() override; + + // Stats handling + bool UpdateAllStats() override; + bool UpdateStats(Stats stat) override; + void UpdateResistances(uint32 school) override; + void UpdateArmor() override; + void UpdateMaxHealth() override; + void UpdateMaxPower(Powers power) override; + void UpdateAttackPowerAndDamage(bool ranged = false) override; + void UpdateDamagePhysical(WeaponAttackType attType) override; + + bool HandlePreSummonActions(Unit const* summoner, uint8 creatureLevel, uint8 maxSummons) override; + void HandlePostSummonActions() override; + bool IsUsingRealStats() const { return _isUsingRealStats; } + void InitializeStats(); + +protected: + void CastPassiveAuras(); + + bool _isUsingRealStats; +}; + +#endif // Guardian_h__ diff --git a/src/server/game/Entities/Creature/TemporarySummon/NewPet.cpp b/src/server/game/Entities/Creature/TemporarySummon/NewPet.cpp new file mode 100644 index 00000000000..047d254c197 --- /dev/null +++ b/src/server/game/Entities/Creature/TemporarySummon/NewPet.cpp @@ -0,0 +1,644 @@ + +#include "NewPet.h" +#include "TalentPackets.h" +#include "DBCStores.h" +#include "GameTime.h" +#include "Map.h" +#include "ObjectMgr.h" +#include "PetPackets.h" +#include "Player.h" +#include "SpellMgr.h" +#include "SpellInfo.h" + +NewPet::NewPet(SummonPropertiesEntry const* properties, Unit* summoner, bool isWorldObject, bool isClassPet, uint32 creatureId, uint8 slot) : + NewGuardian(properties, summoner, isWorldObject), _isClassPet(isClassPet) +{ + m_unitTypeMask |= UNIT_MASK_PET; + InitCharmInfo(); + + // Create or load player pet data for summons which can be controlled + if (summoner && summoner->IsPlayer() && (IsClassPet() || properties->GetFlags().HasFlag(SummonPropertiesFlags::SavePetAutocast))) + { + Player* player = summoner->ToPlayer(); + if (PlayerPetData* playerPetData = GetOrCreatePlayerPetData(player, slot, creatureId)) + SetPlayerPetDataKey(slot, creatureId); + } +} + +void NewPet::AddToWorld() +{ + // Setting the guid before adding to world to reduce building unnecessary object update packets + SetSummonerGUID(GetInternalSummonerGUID()); + + NewGuardian::AddToWorld(); + + if (Unit* summoner = GetInternalSummoner()) + summoner->SetActivelyControlledSummon(this, true); +} + +void NewPet::RemoveFromWorld() +{ + if (Unit* summoner = GetInternalSummoner()) + summoner->SetActivelyControlledSummon(this, false); + + NewGuardian::RemoveFromWorld(); +} + +bool NewPet::HandlePreSummonActions(Unit const* summoner, uint8 creatureLevel, uint8 maxSummons) +{ + if (!NewGuardian::HandlePreSummonActions(summoner, creatureLevel, maxSummons)) + return false; + + // Pets are initialized with REACT_ASSIST by default. Player pet data will override this in Unit::SummonPet + SetReactState(REACT_ASSIST); + + // Initialize the action bar after initializing the stats + m_charmInfo->InitPetActionBar(); + LearnAvailableSpellsForCurrentLevel(); + + // initialize pet behavior + if (_playerPetDataKey.has_value() && summoner && summoner->IsPlayer()) + { + PlayerPetData* playerPetData = summoner->ToPlayer()->GetPlayerPetData(_playerPetDataKey->first, _playerPetDataKey->second); + if (!playerPetData) + return false; + + playerPetData->DisplayId = GetNativeDisplayId(); + playerPetData->CreatedBySpellId = GetUInt32Value(UNIT_CREATED_BY_SPELL); + + GetCharmInfo()->SetPetNumber(playerPetData->PetNumber, IsClassPet()); + + if (IsHunterPet()) + LoadTalents(playerPetData->Talents); + + GetCharmInfo()->LoadPetActionBar(playerPetData->ActionBar, this); + + if (IsClassPet()) + SetName(playerPetData->Name); + + SetReactState(playerPetData->ReactState); + if (playerPetData->Status != PlayerPetDataStatus::New) + { + if (IsHunterPet()) + { + if (playerPetData->SavedHealth != 0) + SetHealth(playerPetData->SavedHealth); + else + setDeathState(JUST_DIED); + } + else + SetFullHealth(); + } + else + SetFullHealth(); + + if (IsHunterPet()) + { + // @todo: This is a workarround for us not properly supporting permanent pets yet. Hunter pet's are meant to last permanently until resurrected or the player leaves the world. + SetCorpseDelay(DAY); + + // Hunter pets have some special settings + SetByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PET_FLAGS, UNIT_CAN_BE_ABANDONED); + if (!playerPetData->HasBeenRenamed) + SetByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PET_FLAGS, UNIT_CAN_BE_RENAMED); + } + + } + else + SetFullHealth(); + + // Set the pet's visbility distance to normal (100yds) which is the threshold when a pet is being dismissed due to being too far away + SetVisibilityDistanceOverride(VisibilityDistanceType::Normal); + + return true; +} + +void NewPet::HandlePostSummonActions() +{ + NewGuardian::HandlePostSummonActions(); + + if (IsHunterPet()) + { + SendTalentsInfoUpdateToSummoner(); + CastSpell(nullptr, SPELL_PET_ENERGIZE); + } + else + CastSpell(nullptr, SPELL_SUMMON_HEAL); +} + +bool NewPet::CanBeDismissed() const +{ + if (_summonProperties && _summonProperties->GetFlags().HasFlag(SummonPropertiesFlags::CannotDismissPet)) + return false; + + return true; +} + +void NewPet::Dismiss(bool permanent /*= true*/) +{ + if (IsClassPet() || _playerPetDataKey.has_value()) + { + Unit* summoner = GetInternalSummoner(); + if (summoner && summoner->IsPlayer()) + { + Player* player = summoner->ToPlayer(); + // Update the pet data before unsummoning the pet + if (_playerPetDataKey.has_value()) + UpdatePlayerPetData(player->GetPlayerPetData(_playerPetDataKey->first, _playerPetDataKey->second)); + + // If the pet has been despawned while dead, we will mark the pet as inactive + if (IsClassPet()) + { + if (permanent) + player->SetActiveClassPetDataKey(std::nullopt); + else + player->SetActiveClassPetDataKey(_playerPetDataKey); + } + + // Warlock pets do play dismiss sounds when releasing them permanently + if (IsClassPet() && permanent && player->getClass() == CLASS_WARLOCK) + { + if (CreatureDisplayInfoEntry const* creatureDisplay = sCreatureDisplayInfoStore.LookupEntry(GetDisplayId())) + { + WorldPackets::Pet::PetDismissSound dismissSound; + dismissSound.ModelID = creatureDisplay->ModelID; + dismissSound.ModelPosition = GetPosition(); + player->SendDirectMessage(dismissSound.Write()); + } + } + } + } + + NewGuardian::Unsummon(); +} + +void NewPet::Unsummon(Milliseconds timeUntilDespawn /*= 0ms*/) +{ + if (timeUntilDespawn > 0ms) + { + m_Events.AddEventAtOffset([&]() { Unsummon(); }, timeUntilDespawn); + return; + } + + Dismiss(); +} + +PlayerPetData* NewPet::GetOrCreatePlayerPetData(Player* summoner, uint8 slot, uint32 creatureId) +{ + if (!summoner) + return nullptr; + + PlayerPetData* petData = summoner->GetPlayerPetData(slot, creatureId); + if (!petData && creatureId) // only create new pet data for non-hunter pets. Hunter pets do not have a creatureId so consequently we do not want to use it here. + petData = summoner->CreatePlayerPetData(slot, creatureId); + + return petData; +} + +bool NewPet::Create(ObjectGuid::LowType guidlow, Map* map, uint32 entry, uint32 petNumber) +{ + ASSERT(map); + SetMap(map); + + Object::_Create(guidlow, petNumber, HighGuid::Pet); + + m_spawnId = guidlow; + m_originalEntry = entry; + + if (!InitEntry(entry)) + return false; + + // Force regen flag for player pets, just like we do for players themselves + SetFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_REGENERATE_POWER); + SetSheath(SHEATH_STATE_MELEE); + SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE); + SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, GameTime::GetGameTime()); + + // Patch 4.1.0 (2011-04-26): Pets will now level with hunters in the same way warlock pets currently do. + SetInt32Value(UNIT_FIELD_PETEXPERIENCE, 0); + SetInt32Value(UNIT_FIELD_PETNEXTLEVELEXP, -1); + + GetThreatManager().Initialize(); + + return true; +} + +void NewPet::SynchronizeLevelWithSummoner() +{ + if (!IsClassPet()) + return; + + Unit* summoner = GetInternalSummoner(); + if (!summoner || getLevel() == summoner->getLevel()) + return; + + SetLevel(summoner->getLevel()); + InitializeStats(); + + LearnAvailableSpellsForCurrentLevel(); + if (summoner->IsPlayer()) + summoner->ToPlayer()->SendPetSpellsMessage(this); + + if (IsHunterPet()) + SendTalentsInfoUpdateToSummoner(); +} + +bool NewPet::HasSpell(uint32 spellId) const +{ + // Class pets use a own spell container which is being updated during runtime + auto itr = _spells.find(spellId); + return itr != _spells.end() && itr->second.State != PETSPELL_REMOVED; +} + +void NewPet::ToggleAutocast(SpellInfo const* spellInfo, bool apply) +{ + if (!spellInfo || !spellInfo->IsAutocastable()) + return; + + auto itr = _spells.find(spellInfo->Id); + if (itr == _spells.end()) + return; + + if (apply) + { + if (itr->second.Active != ACT_ENABLED) + { + itr->second.Active = ACT_ENABLED; + if (itr->second.State != PETSPELL_NEW) + itr->second.State = PETSPELL_CHANGED; + } + } + else + { + if (itr->second.Active != ACT_DISABLED) + { + itr->second.Active = ACT_DISABLED; + if (itr->second.State != PETSPELL_NEW) + itr->second.State = PETSPELL_CHANGED; + } + } +} + +void NewPet::SetPlayerPetDataKey(uint8 slot, uint32 creatureId) +{ + _playerPetDataKey = std::make_pair(slot, creatureId); +} + +void NewPet::UpdatePlayerPetData(PlayerPetData* petData) +{ + if (!petData) + return; + + petData->SavedHealth = GetHealth(); + petData->SavedPower = GetPower(GetPowerType()); + petData->LastSaveTime = static_cast(GameTime::GetGameTime()); + petData->ReactState = GetReactState(); + petData->Slot = _playerPetDataKey->first; + petData->Name = GetName(); + petData->ActionBar = GenerateActionBarDataString(); + petData->Talents = GenenerateTalentsDataString(); + + if (petData->Status != PlayerPetDataStatus::New) + petData->Status = PlayerPetDataStatus::Changed; +} + +void NewPet::LearnTalent(uint32 talentId, uint32 rank) +{ + if (rank >= MAX_TALENT_RANK) + return; + + TalentEntry const* talentEntry = sTalentStore.LookupEntry(talentId); + if (!talentEntry) + return; + + TalentTabEntry const* talentTabEntry = sTalentTabStore.LookupEntry(talentEntry->TabID); + if (!talentTabEntry || talentTabEntry->ClassMask != 0) + return; + + _talents[talentId] = rank; + + LearnSpell(talentEntry->SpellRank[rank]); +} + +void NewPet::LoadTalents(std::string const& dataString) +{ + if (dataString.empty()) + return; + + Tokenizer tokens(dataString, ' '); + + // Talents are stored in pairs (Id and rank) so ensure that there is always a pair + if (tokens.size() == 0 || (tokens.size() % 2) != 0) + return; + + for (Tokenizer::const_iterator itr = tokens.begin(); itr != tokens.end();) + { + uint32 talentId = atoi(*itr); + ++itr; + uint32 rank = atoi(*itr); + + if (rank <= MAX_PET_TALENT_RANK) + LearnTalent(talentId, rank); + + ++itr; + } +} + +void NewPet::SendTalentsInfoUpdateToSummoner() +{ + Unit const* summoner = GetInternalSummoner(); + if (!summoner || !summoner->IsPlayer()) + return; + + WorldPackets::Talent::TalentInfoUpdate packet; + packet.PetTalents = true; + packet.UnspentPoints = CalculateTalentPointsForLevel() - GetSpentTalentPoints(); + for (auto const& learnedTalents : _talents) + { + WorldPackets::Talent::TalentInfo& talent = packet.PetTalent.emplace_back(); + talent.TalentID = learnedTalents.first; + talent.Rank = learnedTalents.second; + } + + summoner->ToPlayer()->SendDirectMessage(packet.Write()); +} + +// ------------- private methods + +void NewPet::SendSpellLearnedToSummoner(uint32 spellId) +{ + Unit const* summoner = GetInternalSummoner(); + if (!summoner || !summoner->IsPlayer()) + return; + + summoner->ToPlayer()->SendDirectMessage(WorldPackets::Pet::PetLearnedSpell(spellId).Write()); +} + +void NewPet::SendSpellUnlearnedToSummoner(uint32 spellId) +{ + Unit const* summoner = GetInternalSummoner(); + if (!summoner || !summoner->IsPlayer()) + return; + + summoner->ToPlayer()->SendDirectMessage(WorldPackets::Pet::PetUnlearnedSpell(spellId).Write()); +} + +void NewPet::LearnAvailableSpellsForCurrentLevel() +{ + if (!IsClassPet()) + { + // Non-class pets do have their spells stored in creature_template so we draw our data from there + for (uint32 spell : m_spells) + if (spell != 0) + LearnSpell(spell); + return; + } + + if (PetLevelupSpellSet const* levelupSpells = GetCreatureTemplate()->family ? sSpellMgr->GetPetLevelupSpellList(GetCreatureTemplate()->family) : nullptr) + { + // PetLevelupSpellSet ordered by levels + for (auto const& itr : *levelupSpells) + { + if (itr.first > getLevel()) + UnlearnSpell(itr.second, true); + else + LearnSpell(itr.second); + } + } + + int32 petSpellsId = GetCreatureTemplate()->PetSpellDataId ? -(int32)GetCreatureTemplate()->PetSpellDataId : GetEntry(); + + // default spells + if (PetDefaultSpellsEntry const* defSpells = sSpellMgr->GetPetDefaultSpellsEntry(petSpellsId)) + { + for (uint32 spellId : defSpells->spellid) + { + if (!spellId) + continue; + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); + if (!spellInfo) + continue; + + if (spellInfo->SpellLevel > getLevel()) + UnlearnSpell(spellInfo->Id, true); + else + LearnSpell(spellInfo->Id); + } + } + +} + +bool NewPet::LearnSpell(uint32 spellId) +{ + if (!AddSpell(spellId)) + return false; + + if (IsInWorld()) + SendSpellLearnedToSummoner(spellId); + + return true; +} + +bool NewPet::UnlearnSpell(uint32 spellId, bool learnPreviousRank, bool clearActionbar /*= true*/) +{ + if (!RemoveSpell(spellId, learnPreviousRank, clearActionbar)) + return false; + + if (IsInWorld()) + SendSpellUnlearnedToSummoner(spellId); + + return true; +} + +bool NewPet::AddSpell(uint32 spellId, ActiveStates active /*= ACT_DECIDE*/, PetSpellState state /*= PETSPELL_NEW*/, PetSpellType type /*= PETSPELL_NORMAL*/) +{ + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); + if (!spellInfo) + return false; + + auto const itr = _spells.find(spellId); + if (itr != _spells.end()) + { + if (itr->second.State == PETSPELL_REMOVED) + state = PETSPELL_CHANGED; + else + { + if (state == PETSPELL_UNCHANGED && itr->second.State != PETSPELL_UNCHANGED) + { + // can be in case spell loading but learned at some previous spell loading + itr->second.State = PETSPELL_UNCHANGED; + + if (active == ACT_ENABLED) + ToggleAutocast(spellInfo, true); + else if (active == ACT_DISABLED) + ToggleAutocast(spellInfo, false); + } + + return false; + } + } + + PetSpell newspell; + newspell.State = state; + newspell.Type = type; + + if (active == ACT_DECIDE) // active was not used before, so we save it's autocast/passive state here + { + if (spellInfo->IsAutocastable()) + newspell.Active = ACT_ENABLED; + else + newspell.Active = ACT_PASSIVE; + } + else + newspell.Active = active; + + // talent: unlearn all other talent ranks (high and low) + if (TalentSpellPos const* talentPos = sDBCManager.GetTalentSpellPos(spellId)) + { + if (TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentPos->talent_id)) + { + for (uint8 i = 0; i < MAX_TALENT_RANK; ++i) + { + // skip learning spell and no rank spell case + uint32 rankSpellId = talentInfo->SpellRank[i]; + if (!rankSpellId || rankSpellId == spellId) + continue; + + // skip unknown ranks + if (!HasSpell(rankSpellId)) + continue; + RemoveSpell(rankSpellId, false, false); + } + } + } + else if (spellInfo->IsRanked()) + { + for (auto const& itr : _spells) + { + if (itr.second.State == PETSPELL_REMOVED) + continue; + + SpellInfo const* oldRankSpellInfo = sSpellMgr->GetSpellInfo(itr.first); + + if (!oldRankSpellInfo) + continue; + + if (spellInfo->IsDifferentRankOf(oldRankSpellInfo)) + { + // replace by new high rank + if (spellInfo->IsHighRankOf(oldRankSpellInfo)) + { + newspell.Active = itr.second.Active; + + if (newspell.Active == ACT_ENABLED) + ToggleAutocast(oldRankSpellInfo, false); + + UnlearnSpell(itr.first, false, false); + break; + } + // ignore new lesser rank + else + return false; + } + } + } + + // Store pet scaling auras in a own vector to handle the updating more efficient + //if (spellInfo->HasAttribute(SPELL_ATTR4_OWNER_POWER_SCALING)) + //{ + // m_petScalingAuras.push_back(spellInfo->Id); + // return true; + //} + + _spells[spellId] = newspell; + + if (spellInfo->IsPassive() && (!spellInfo->CasterAuraState || HasAuraState(AuraStateType(spellInfo->CasterAuraState)))) + CastSpell(this, spellId, true); + else if (!spellInfo->HasAttribute(SPELL_ATTR4_NOT_IN_SPELLBOOK)) + m_charmInfo->AddSpellToActionBar(spellInfo); + + if (newspell.Active == ACT_ENABLED) + ToggleAutocast(spellInfo, true); + + return true; +} + +bool NewPet::RemoveSpell(uint32 spellId, bool learnPreviousRank, bool clearActionbar /*= true*/) +{ + auto itr = _spells.find(spellId); + if (itr == _spells.end()) + return false; + + if (itr->second.State == PETSPELL_REMOVED) + return false; + + if (itr->second.State == PETSPELL_NEW) + _spells.erase(itr); + else + itr->second.State = PETSPELL_REMOVED; + + RemoveAurasDueToSpell(spellId); + + if (learnPreviousRank) + { + if (uint32 prevSpellId = sSpellMgr->GetPrevSpellInChain(spellId)) + LearnSpell(prevSpellId); + else + learnPreviousRank = false; + } + + // if remove last rank or non-ranked then update action bar at server and client if need + if (clearActionbar && !learnPreviousRank && m_charmInfo->RemoveSpellFromActionBar(spellId)) + { + if (Unit* summoner = GetInternalSummoner()) + if (summoner->IsPlayer()) + summoner->ToPlayer()->SendPetSpellsMessage(this); + } + + return true; +} + +std::string NewPet::GenerateActionBarDataString() const +{ + std::ostringstream ss; + + for (uint32 i = ACTION_BAR_INDEX_START; i < ACTION_BAR_INDEX_END; ++i) + { + ss << uint32(m_charmInfo->GetActionBarEntry(i)->GetType()) << ' ' + << uint32(m_charmInfo->GetActionBarEntry(i)->GetAction()) << ' '; + } + + return ss.str(); +} + +std::string NewPet::GenenerateTalentsDataString() const +{ + std::ostringstream ss; + + for (auto const& talent : _talents) + ss << uint32(talent.first) << ' ' << uint32(talent.second) << ' '; + + return ss.str(); +} + +uint32 NewPet::CalculateTalentPointsForLevel() const +{ + uint32 points = 0; + if (getLevel() >= 20) + points = 1 + ((getLevel() - 20) / 4); + + if (Unit* summoner = GetInternalSummoner()) + points += summoner->GetTotalAuraModifier(SPELL_AURA_MOD_PET_TALENT_POINTS); + + return points; +} + +uint32 NewPet::GetSpentTalentPoints() const +{ + uint32 points = 0; + for (auto const& pair : _talents) + points += pair.second + 1; + + return points; +} diff --git a/src/server/game/Entities/Creature/TemporarySummon/NewPet.h b/src/server/game/Entities/Creature/TemporarySummon/NewPet.h new file mode 100644 index 00000000000..fa60ced68bd --- /dev/null +++ b/src/server/game/Entities/Creature/TemporarySummon/NewPet.h @@ -0,0 +1,108 @@ +/* + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef NewPet_h__ +#define NewPet_h__ + +#include "NewGuardian.h" +#include "Optional.h" +#include "PetDefines.h" + +struct PlayerPetData; + +struct PetSpell +{ + ActiveStates Active = ACT_DECIDE; + PetSpellState State = PETSPELL_NEW; + PetSpellType Type = PETSPELL_NORMAL; +}; + +using PetSpellMap = std::unordered_map; +using PetTalentMap = std::unordered_map; + +class TC_GAME_API NewPet final : public NewGuardian +{ +public: + explicit NewPet(SummonPropertiesEntry const* properties, Unit* owner, bool isWorldObject, bool isClassPet, uint32 creatureId, uint8 slot); + + void AddToWorld() override; + void RemoveFromWorld() override; + + bool HandlePreSummonActions(Unit const* summoner, uint8 creatureLevel, uint8 maxSummons) override; + void HandlePostSummonActions() override; + + bool ShouldDespawnOnSummonerLogout() const override { return true; } + bool ShouldJoinSummonerSpawnGroupAfterCreation() const override { return true; } + + // Returns true if the pet is belongs to a specific class (Hunter Pets, Mage Water Elementals, DK Ghouls and Warlock Minions) + bool IsClassPet() const { return _isClassPet; } + // Returns true if the pet is a hunter class pet. This is the case when the pet has player pet data and is stored under creatureId = 0 + bool IsHunterPet() const { return _playerPetDataKey.has_value() && _playerPetDataKey->second == 0; } + // Returns true if the pet has a player pet data key to acces a player's pet data + bool HasPlayerPetDataKey() { return _playerPetDataKey.has_value(); } + // Returns the player pet data map key for the element that is stored in summoner's player class + Optional const& GetPlayerPetDataKey() const { return _playerPetDataKey; } + // Returns true if the summoner is allowed to dismiss the pet via pet action + bool CanBeDismissed() const; + // Unsummons the pet. If permanent is set to true, some pets will play a dismiss sound (such as Warlock pets) + void Dismiss(bool permanent = true); + // Overriden method of TemporarySummon::Unsummon to ensure that Pet::Dismiss is always called. + void Unsummon(Milliseconds timeUntilDespawn = 0ms) override; + // Returns or creates player pet data. Does return nullptr when the summon is a hunter pet and no pet data is available + PlayerPetData* GetOrCreatePlayerPetData(Player* summoner, uint8 slot, uint32 creatureId); + + bool Create(ObjectGuid::LowType guidlow, Map* map, uint32 entry, uint32 petNumber); + + // Synchronizes the pet's level with its summoner, updates the stats for the new level and learns new spells if available + void SynchronizeLevelWithSummoner(); + // Returns true when the pet knows the given spell + bool HasSpell(uint32 spellID) const override; + // Enables or disables auto casting for the given spell + void ToggleAutocast(SpellInfo const* spellInfo, bool apply); + // Returns a map of a pet's known spells + PetSpellMap const& GetSpells() const { return _spells; } + // Sets the PlayerPetData map key for accessing the summoner's player pet data at any given time + void SetPlayerPetDataKey(uint8 slot, uint32 creatureId); + // Updates the PlayerPetData values of this pet for the specified summoner + void UpdatePlayerPetData(PlayerPetData* petData); + // Learns the specified talent for the given rank and learns the according spell/passive aura + void LearnTalent(uint32 talentId, uint32 rank); + // Learns all talents that have been saved in the database + void LoadTalents(std::string const& dataString); + // Sends a pet talent info update packet to the summoner + void SendTalentsInfoUpdateToSummoner(); + +private: + void SendSpellLearnedToSummoner(uint32 spellId); + void SendSpellUnlearnedToSummoner(uint32 spellId); + void LearnAvailableSpellsForCurrentLevel(); + bool LearnSpell(uint32 spellId); + bool UnlearnSpell(uint32 spellId, bool learnPreviousRank, bool clearActionbar = true); + bool AddSpell(uint32 spellId, ActiveStates active = ACT_DECIDE, PetSpellState state = PETSPELL_NEW, PetSpellType type = PETSPELL_NORMAL); + bool RemoveSpell(uint32 spellId, bool learnPreviousRank, bool clearActionbar = true); + std::string GenerateActionBarDataString() const; + std::string GenenerateTalentsDataString() const; + uint32 CalculateTalentPointsForLevel() const; + uint32 GetSpentTalentPoints() const; + + bool _isClassPet; + PetSpellMap _spells; + Optional _playerPetDataKey; + PetTalentMap _talents; +}; + +#endif // NewPet_h__ diff --git a/src/server/game/Entities/Creature/TemporarySummon/NewTemporarySummon.cpp b/src/server/game/Entities/Creature/TemporarySummon/NewTemporarySummon.cpp new file mode 100644 index 00000000000..bbcfd253028 --- /dev/null +++ b/src/server/game/Entities/Creature/TemporarySummon/NewTemporarySummon.cpp @@ -0,0 +1,307 @@ +/* + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "NewTemporarySummon.h" +#include "DBCStores.h" +#include "CreatureAI.h" +#include "Log.h" +#include "ObjectAccessor.h" +#include "Player.h" +#include "TotemPackets.h" + +NewTemporarySummon::NewTemporarySummon(SummonPropertiesEntry const* properties, Unit* summoner, bool isWorldObject) : + Creature(isWorldObject), _summonProperties(properties), _summonDuration(0ms), _isPermanentSummon(false), _summonSlot(SummonPropertiesSlot::None) +{ + if (summoner) + _internalSummonerGUID = summoner->GetGUID(); + + m_unitTypeMask |= UNIT_MASK_SUMMON; +} + +void NewTemporarySummon::AddToWorld() +{ + if (IsInWorld()) + return; + + Creature::AddToWorld(); + + Unit* summoner = GetInternalSummoner(); + if (summoner) + { + summoner->AddSummonGUID(GetGUID()); + if (_summonSlot != SummonPropertiesSlot::None) + { + // Unsummon previous summon in the slot that we are about to occupy + if (NewTemporarySummon* summon = summoner->GetSummonInSlot(_summonSlot)) + summon->Unsummon(); + + summoner->AddSummonGUIDToSlot(GetGUID(), _summonSlot); + + if (_summonSlot == SummonPropertiesSlot::Critter) + summoner->SetCritterGUID(GetGUID()); + } + } +} + +void NewTemporarySummon::RemoveFromWorld() +{ + if (!IsInWorld()) + return; + + if (_summonProperties) + { + if (Unit* summoner = GetInternalSummoner()) + { + summoner->RemoveSummonGUID(GetGUID()); + if (_summonSlot != SummonPropertiesSlot::None) + { + summoner->RemoveSummonGUIDFromSlot(GetGUID(), _summonSlot); + if (_summonSlot == SummonPropertiesSlot::Critter && summoner->GetCritterGUID() == GetGUID()) + summoner->SetCritterGUID(ObjectGuid::Empty); + } + } + } + + Creature::RemoveFromWorld(); +} + +void NewTemporarySummon::Update(uint32 diff) +{ + Creature::Update(diff); + + if (_summonDuration >= Milliseconds(diff)) + _summonDuration -= Milliseconds(diff); + else + _summonDuration = 0s; + + // Make sure that the summon is within the summoner's distance when its following the player + if (_summonDistanceCheckTimer.has_value()) + { + if (_summonDistanceCheckTimer <= Milliseconds(diff)) + { + Unit* summoner = GetInternalSummoner(); + if (!summoner || GetExactDist(summoner) > MAX_SUMMON_DISTANCE) + { + Unsummon(); + return; + } + + _summonDistanceCheckTimer = 1s; + } + else + *_summonDistanceCheckTimer -= Milliseconds(diff); + } + + // The summon has been marked as permanent so we will not proceed with any of the expiration mechanics + if (_isPermanentSummon) + return; + + if (_summonDuration <= 0s) + { + // When a summon expires it usually dies unless the summon property flags expects us to despawn the summon right away. + if (_summonProperties && _summonProperties->GetFlags().HasFlag(SummonPropertiesFlags::DespawnWhenExpired)) + { + Unsummon(); + return; + } + + KillSelf(false); + } +} + +bool NewTemporarySummon::HandlePreSummonActions(Unit const* summoner, uint8 creatureLevel, uint8 maxSummons) +{ + if (!_summonProperties) + return true; + + if (_summonProperties->Faction != 0) + SetFaction(_summonProperties->Faction); + + if (!_summonProperties->GetFlags().HasFlag(SummonPropertiesFlags::UseCreatureLevel) && creatureLevel > 0) + SetLevel(creatureLevel); + + if (!summoner) + return true; + + SetGuidValue(OBJECT_FIELD_DATA, summoner->GetGuidValue(OBJECT_FIELD_DATA)); + + if (_summonProperties->GetFlags().HasFlag(SummonPropertiesFlags::UseSummonerFaction)) + SetFaction(summoner->GetFaction()); + + _summonSlot = static_cast(_summonProperties->Slot); + + // Only players are allowed to summon creatures in quest slots + if (_summonSlot == SummonPropertiesSlot::Quest && !summoner->IsPlayer()) + return false; + + if (_summonSlot == SummonPropertiesSlot::AnyAvailableTotem) + { + // We have to select an unoccupied totem slot that can be used. As of 4.3.4.15595 this mechanic is only being used by Wild Mushrooms + // If there is no empty slot, we are going to select the eldest summon of our kind and replace it + constexpr int8 firstTotemSlot = AsUnderlyingType(SummonPropertiesSlot::Totem1); + // first attempt: check for empty totem slots within the allowed range + for (uint8 i = 0; i < maxSummons; ++i) + { + SummonPropertiesSlot slot = static_cast(i + firstTotemSlot); + if (!summoner->HasSummonInSlot(slot)) + { + _summonSlot = slot; + break; + } + } + + // all eligible slots are occupied. So try to find a related summon and replace it if possible + if (_summonSlot == SummonPropertiesSlot::AnyAvailableTotem) + { + Optional> fallbackSlot; + for (uint8 i = 0; i < maxSummons; ++i) + { + SummonPropertiesSlot slot = static_cast(i + firstTotemSlot); + + NewTemporarySummon const* summon = summoner->GetSummonInSlot(slot); + if (summon->GetUInt32Value(UNIT_CREATED_BY_SPELL) == GetUInt32Value(UNIT_CREATED_BY_SPELL)) + if (!fallbackSlot.has_value() || fallbackSlot->second >= summon->GetRemainingSummonDuration()) + fallbackSlot = std::make_pair(slot, summon->GetRemainingSummonDuration()); + } + + if (fallbackSlot.has_value()) + _summonSlot = fallbackSlot->first; + else // There is no slot that we can use right now so skip summon creation + return false; + } + } + + // The summon slot is now fully initialized so we can proceed with the slot specific actions + switch (_summonSlot) + { + case SummonPropertiesSlot::Totem1: + case SummonPropertiesSlot::Totem2: + case SummonPropertiesSlot::Totem3: + case SummonPropertiesSlot::Totem4: + if (summoner->IsPlayer()) + { + // SMSG_TOTEM_CREATED must be sent to the client before adding the summon to world and destroying other totems + WorldPackets::Totem::TotemCreated totemCreated; + totemCreated.Duration = int32(_summonDuration.count()); + totemCreated.Slot = uint8(_summonProperties->Slot - AsUnderlyingType(SummonPropertiesSlot::Totem1)); + totemCreated.SpellID = GetUInt32Value(UNIT_CREATED_BY_SPELL); + totemCreated.Totem = GetGUID(); + summoner->ToPlayer()->SendDirectMessage(totemCreated.Write()); + } + // There are some creatures which also go into totem slot but are no real totems. In this case we do not want to override the display Id + if (uint32 totemModel = summoner->GetModelForTotem(PlayerTotemType(_summonProperties->ID))) + SetDisplayId(totemModel); + break; + default: + break; + } + + return true; +} + +void NewTemporarySummon::HandlePostSummonActions() +{ + if (Unit* summoner = GetInternalSummoner()) + { + if (summoner->IsCreature() && summoner->IsAIEnabled()) + if (CreatureAI* ai = summoner->ToCreature()->AI()) + ai->JustSummoned(this); + + if (IsAIEnabled()) + AI()->IsSummonedBy(summoner); + + if (_summonProperties) + { + if (_summonProperties->GetFlags().HasFlag(SummonPropertiesFlags::AttackSummoner)) + EngageWithTarget(summoner); + + if (_summonProperties->GetFlags().HasFlag(SummonPropertiesFlags::HelpWhenSummonedInCombat) && summoner->IsInCombat()) + { + Unit* victim = nullptr; + if (summoner->IsPlayer()) + victim = summoner->GetVictim(); + else if (summoner->IsCreature() && summoner->IsAIEnabled()) + victim = summoner->GetThreatManager().GetCurrentVictim(); + + if (victim) + EngageWithTarget(victim); + } + } + } + + // If a summon is suposed to follow its summoner, make sure that it stays within its distance + if (ShouldJoinSummonerSpawnGroupAfterCreation() || ShouldFollowSummonerAfterCreation()) + _summonDistanceCheckTimer = 1s; + + // Mark all temporary summons as active to keep updating duration and distance checks + // @todo: research possible exploits or performance issues because of that + setActive(true); +} + +void NewTemporarySummon::Unsummon(Milliseconds timeUntilDespawn /*= 0ms*/) +{ + if (timeUntilDespawn > 0ms) + { + m_Events.AddEventAtOffset([&]() { Unsummon(); }, timeUntilDespawn); + return; + } + + if (Unit* summoner = GetInternalSummoner()) + if (summoner->IsCreature() && summoner->IsAIEnabled()) + if (CreatureAI* ai = summoner->ToCreature()->AI()) + ai->SummonedCreatureDespawn(this); + + AddObjectToRemoveList(); +} + +Unit* NewTemporarySummon::GetInternalSummoner() const +{ + if (_internalSummonerGUID.IsEmpty()) + return nullptr; + + return ObjectAccessor::GetUnit(*this, _internalSummonerGUID); +} + +bool NewTemporarySummon::ShouldDespawnOnSummonerDeath() const +{ + return _summonProperties && _summonProperties->GetFlags().HasFlag(SummonPropertiesFlags::DespawnOnSummonerDeath); +} + +bool NewTemporarySummon::ShouldDespawnOnSummonerLogout() const +{ + if (_summonProperties && _summonProperties->GetFlags().HasFlag(SummonPropertiesFlags::DespawnOnSummonerLogout)) + return true; + + // Summons that are stored in a slot will be despawned when the summoner logs out + return _summonSlot != SummonPropertiesSlot::None; +} + +bool NewTemporarySummon::ShouldJoinSummonerSpawnGroupAfterCreation() const +{ + if (!_summonProperties) + return false; + + if (_summonProperties->GetFlags().HasFlag(SummonPropertiesFlags::JoinSummonerSpawnGroup)) + return true; + + return false; +} + +bool NewTemporarySummon::ShouldFollowSummonerAfterCreation() const +{ + return _summonSlot == SummonPropertiesSlot::Critter; +} diff --git a/src/server/game/Entities/Creature/TemporarySummon/NewTemporarySummon.h b/src/server/game/Entities/Creature/TemporarySummon/NewTemporarySummon.h new file mode 100644 index 00000000000..ac92e65d4b3 --- /dev/null +++ b/src/server/game/Entities/Creature/TemporarySummon/NewTemporarySummon.h @@ -0,0 +1,78 @@ +/* + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef TempoarySummon_h__ +#define TempoarySummon_h__ + +#include "Creature.h" + +struct SummonPropertiesEntry; + +// This time sepcifies when a summon will despawn after dying from expiration +static constexpr Milliseconds SummonExpirationCorpseDespawnTime = 15s; + +class TC_GAME_API NewTemporarySummon : public Creature +{ +public: + explicit NewTemporarySummon(SummonPropertiesEntry const* properties, Unit* summoner, bool isWorldObject); + virtual ~NewTemporarySummon() { } + + // Overriden methods of Creature class + void AddToWorld() override; + void RemoveFromWorld() override; + void Update(uint32 diff) override; + + // Handles everything that needs to be done before the summon is being added to the world (assigning slot, selecting level, etc) + virtual bool HandlePreSummonActions(Unit const* summoner, uint8 creatureLevel, uint8 maxSummons); + // Handles everything that needs to be done after the summon has been added to the world (casting passive auras, calling AI hooks, etc) + virtual void HandlePostSummonActions(); + + // Sets the summon duration before it will expire and either die or despawn + void SetSummonDuration(Milliseconds duration) { _summonDuration = duration; } + // Returns the remaining duration in milliseconds until the summon expires and dies or despawns + Milliseconds GetRemainingSummonDuration() const { return _summonDuration; } + + // Marks the summon as permanent so it will longer die or despawn upon expiration + void SetPermanentSummon(bool apply) { _isPermanentSummon = apply; } + + // Unsummons the summon after a specified amount of time. + virtual void Unsummon(Milliseconds timeUntilDespawn = 0ms); + + // Returns a pointer to the internally stored summoner that has summoned this creature. Unrelated to UNIT_FIELD_SUMMONEDBY. If the creature has not been added to a map yet this method will return nullptr! + Unit* GetInternalSummoner() const; + // Returns the internally stored ObjectGuid of the summoner. Unrelated to UNIT_FIELD_SUMMONEDBY + ObjectGuid GetInternalSummonerGUID() const { return _internalSummonerGUID; } + + // Returns true if the summon is suposed to despawn when its summoner has died + bool ShouldDespawnOnSummonerDeath() const; + // Returns true if the summon is suposed to despawn when its summoner is a player and has logged out + virtual bool ShouldDespawnOnSummonerLogout() const; + // Returns true if the summon is suposed to follow the summoner. SpawnGroups are Blizzard's internal term for our CreatureGroups which are currently not supported for players + virtual bool ShouldJoinSummonerSpawnGroupAfterCreation() const; + // Returns true if the summon is suposed to follow the summoner without joining its SpawnGroup. This mechanic is often being used by companions (formerly known as minipets) + bool ShouldFollowSummonerAfterCreation() const; + +protected: + SummonPropertiesEntry const* _summonProperties; + ObjectGuid _internalSummonerGUID; + Milliseconds _summonDuration; + bool _isPermanentSummon; + SummonPropertiesSlot _summonSlot; + Optional _summonDistanceCheckTimer; +}; + +#endif // PhasingHandler_h__ diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index 8de5ab7dfc4..4377c69e46c 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -46,6 +46,9 @@ #include "SpellDefines.h" #include "SpellMgr.h" #include "TemporarySummon.h" +#include "NewTemporarySummon.h" +#include "NewGuardian.h" +#include "NewPet.h" #include "Totem.h" #include "Transport.h" #include "Unit.h" @@ -2122,6 +2125,102 @@ TempSummon* Map::SummonCreature(uint32 entry, Position const& pos, SummonCreatur return summon; } +NewTemporarySummon* Map::SummonCreatureNew(uint32 entry, Position const& pos, SummonCreatureExtraArgs const& summonArgs /*= { }*/) +{ + NewTemporarySummon* summon = nullptr; + if (summonArgs.SummonProperties) + { + switch (SummonPropertiesControl(summonArgs.SummonProperties->Control)) + { + case SummonPropertiesControl::None: + summon = new NewTemporarySummon(summonArgs.SummonProperties, summonArgs.Summoner, false); + break; + case SummonPropertiesControl::Guardian: + summon = new NewGuardian(summonArgs.SummonProperties, summonArgs.Summoner, false); + break; + case SummonPropertiesControl::Pet: + summon = new NewPet(summonArgs.SummonProperties, summonArgs.Summoner, false, false, entry, 0); + break; + default: + break; + } + } + else + summon = new NewTemporarySummon(summonArgs.SummonProperties, summonArgs.Summoner, false); + + if (!summon) + return nullptr; + + // Create creature entity + if (!summon->Create(GenerateLowGuid(), this, entry, pos, nullptr, summonArgs.VehicleRecID, true)) + { + delete summon; + return nullptr; + } + + // Inherit summoner's Phaseshift + bool ignorePhaseShift = false; + if (summonArgs.SummonProperties && summonArgs.SummonProperties->GetFlags().HasFlag(SummonPropertiesFlags::IgnoreSummonerPhase) + && SummonPropertiesControl(summonArgs.SummonProperties->Control) == SummonPropertiesControl::None) + ignorePhaseShift = true; + + if (!ignorePhaseShift && summonArgs.Summoner) + PhasingHandler::InheritPhaseShift(summon, summonArgs.Summoner); + + TransportBase* transport = summonArgs.Summoner ? summonArgs.Summoner->GetTransport() : nullptr; + if (transport) + { + float x, y, z, o; + pos.GetPosition(x, y, z, o); + transport->CalculatePassengerOffset(x, y, z, &o); + summon->m_movementInfo.transport.pos.Relocate(x, y, z, o); + + // This object must be added to transport before adding to map for the client to properly display it + transport->AddPassenger(summon); + } + + // Initialize tempsummon fields + summon->SetUInt32Value(UNIT_CREATED_BY_SPELL, summonArgs.SummonSpellId); + summon->SetHomePosition(pos); + summon->SetPrivateObjectOwner(summonArgs.PrivateObjectOwner); + + if (summonArgs.SummonDuration >= 0) + summon->SetSummonDuration(Milliseconds(summonArgs.SummonDuration)); + else + summon->SetPermanentSummon(true); + + if (!summon->HandlePreSummonActions(summonArgs.Summoner, summonArgs.CreatureLevel, summonArgs.MaxSummons)) + { + delete summon; + return nullptr; + } + + // Handle health argument + if (summonArgs.SummonHealth > 0) + { + summon->SetMaxHealth(summonArgs.SummonHealth); + summon->SetHealth(summonArgs.SummonHealth); + } + + if (!AddToMap(summon->ToCreature())) + { + // Returning false will cause the object to be deleted - remove from transport + if (transport) + transport->RemovePassenger(summon); + + delete summon; + return nullptr; + } + + summon->HandlePostSummonActions(); + + // call MoveInLineOfSight for nearby creatures + Trinity::AIRelocationNotifier notifier(*summon); + Cell::VisitAllObjects(summon, notifier, GetVisibilityRange()); + + return summon; +} + /** * Summons group of creatures. * diff --git a/src/server/game/Entities/Object/ObjectDefines.h b/src/server/game/Entities/Object/ObjectDefines.h index 28ab831bac7..884a18e4c8b 100644 --- a/src/server/game/Entities/Object/ObjectDefines.h +++ b/src/server/game/Entities/Object/ObjectDefines.h @@ -63,7 +63,8 @@ enum TempSummonType TEMPSUMMON_CORPSE_DESPAWN = 5, // despawns instantly after death TEMPSUMMON_CORPSE_TIMED_DESPAWN = 6, // despawns after a specified time after death TEMPSUMMON_DEAD_DESPAWN = 7, // despawns when the creature disappears - TEMPSUMMON_MANUAL_DESPAWN = 8 // despawns when UnSummon() is called + TEMPSUMMON_MANUAL_DESPAWN = 8, // despawns when UnSummon() is called + TEMPSUMMON_DIE_UPON_EXPIRE = 9 // dies after a specified time. Corpse will despawn just like any regular creature. }; enum PhaseMasks diff --git a/src/server/game/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp index 54b71c2cc5c..c9055eab2f9 100644 --- a/src/server/game/Entities/Pet/Pet.cpp +++ b/src/server/game/Entities/Pet/Pet.cpp @@ -96,6 +96,7 @@ void Pet::RemoveFromWorld() bool Pet::LoadPetData(Player* owner, uint32 petEntry, uint32 petnumber, bool current) { + /** m_loading = true; PlayerPetData* playerPetData; @@ -269,7 +270,6 @@ bool Pet::LoadPetData(Player* owner, uint32 petEntry, uint32 petnumber, bool cur owner->SendMessageToSet(&data, true); } - owner->SetMinion(this, true); map->AddToMap(ToCreature()); InitTalentForLevel(); // set original talents points before spell loading @@ -344,134 +344,14 @@ bool Pet::LoadPetData(Player* owner, uint32 petEntry, uint32 petnumber, bool cur // must be after SetMinion (owner guid check) LoadTemplateImmunities(); m_loading = false; + */ return true; } -void Pet::SavePetToDB(PetSaveMode mode) -{ - if (!GetEntry()) - return; - - // save only fully controlled creature - if (!isControlled()) - return; - - // not save not player pets - if (!GetOwnerOrCreatorGUID().IsPlayer()) - return; - - uint32 curhealth = GetHealth(); - uint32 curmana = GetPower(POWER_MANA); - - CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); - // save auras before possibly removing them - _SaveAuras(trans); - - _SaveSpells(trans); - GetSpellHistory()->SaveToDB(trans); - CharacterDatabase.CommitTransaction(trans); - - PlayerPetData* playerPetData = GetOwner()->GetPlayerPetDataById(m_charmInfo->GetPetNumber()); - - // save as new if no data for Pet in PlayerPetDataStore - if (mode < PET_SAVE_NEW_PET && !playerPetData) - mode = PET_SAVE_NEW_PET; - - if (mode == PET_SAVE_NEW_PET) - { - Optional slot = IsHunterPet() ? GetOwner()->GetFirstUnusedActivePetSlot() : GetOwner()->GetFirstUnusedPetSlot(); - - if (slot) - { - SetSlot(*slot); - playerPetData = new PlayerPetData(); - } - else - mode = PET_SAVE_AS_DELETED; - } - - if (mode == PET_SAVE_DISMISS || mode == PET_SAVE_LOGOUT) - RemoveAllAuras(); - - // whole pet is saved to DB - if (mode >= PET_SAVE_CURRENT_STATE) - { - ObjectGuid::LowType ownerLowGUID = GetOwnerOrCreatorGUID().GetCounter(); - std::string name = m_name; - CharacterDatabase.EscapeString(name); - trans = CharacterDatabase.BeginTransaction(); - // remove current data - - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PET_BY_ID); - stmt->setUInt32(0, m_charmInfo->GetPetNumber()); - trans->Append(stmt); - - uint32 petId = m_charmInfo->GetPetNumber(); - - // save pet - stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PET); - stmt->setUInt32(0, petId); - stmt->setUInt32(1, GetEntry()); - stmt->setUInt64(2, ownerLowGUID); - stmt->setUInt32(3, GetNativeDisplayId()); - stmt->setUInt8(4, getLevel()); - stmt->setUInt32(5, GetUInt32Value(UNIT_FIELD_PETEXPERIENCE)); - stmt->setUInt8(6, GetReactState()); - stmt->setInt16(7, m_petSlot); - stmt->setString(8, m_name); - stmt->setUInt8(9, HasByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PET_FLAGS, UNIT_CAN_BE_RENAMED) ? 0 : 1); - stmt->setUInt8(10, mode == PET_SAVE_DISMISS ? 0 : 1); - stmt->setUInt32(11, curhealth); - stmt->setUInt32(12, curmana); - - stmt->setString(13, GenerateActionBarData()); - - stmt->setUInt32(14, GameTime::GetGameTime()); // unsure about this - stmt->setUInt32(15, GetUInt32Value(UNIT_CREATED_BY_SPELL)); - stmt->setUInt8(16, getPetType()); - trans->Append(stmt); - - CharacterDatabase.CommitTransaction(trans); - - if (m_petSlot > PET_SLOT_LAST) - TC_LOG_ERROR("sql.sql", "Pet::SavePetToDB: bad slot %u for pet %u!", m_petSlot, petId); - - playerPetData->PetId = petId; - playerPetData->CreatureId = GetEntry(); - playerPetData->Owner = ownerLowGUID; - playerPetData->DisplayId = GetNativeDisplayId(); - playerPetData->Petlevel = getLevel(); - playerPetData->PetExp = GetUInt32Value(UNIT_FIELD_PETEXPERIENCE); - playerPetData->Reactstate = GetReactState(); - playerPetData->Slot = m_petSlot; - playerPetData->Name = m_name; - playerPetData->Renamed = HasByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PET_FLAGS, UNIT_CAN_BE_RENAMED) ? 0 : 1; - playerPetData->Active = mode == PET_SAVE_DISMISS ? 0 : 1; - playerPetData->SavedHealth = curhealth; - playerPetData->SavedMana = curmana; - playerPetData->Actionbar = GenerateActionBarData(); - playerPetData->Timediff = GameTime::GetGameTime(); - playerPetData->SummonSpellId = GetUInt32Value(UNIT_CREATED_BY_SPELL); - playerPetData->Type = getPetType(); - - if (mode == PET_SAVE_NEW_PET) - GetOwner()->AddToPlayerPetDataStore(playerPetData); - - } - // delete - else - { - RemoveAllAuras(); - DeleteFromDB(m_charmInfo->GetPetNumber()); - - GetOwner()->DeleteFromPlayerPetDataStore(m_charmInfo->GetPetNumber()); - GetOwner()->GetSession()->SendStablePet(ObjectGuid::Empty); - } -} - void Pet::DeleteFromDB(ObjectGuid::LowType guidlow) { + /* CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PET_BY_ID); @@ -495,6 +375,7 @@ void Pet::DeleteFromDB(ObjectGuid::LowType guidlow) trans->Append(stmt); CharacterDatabase.CommitTransaction(trans); + */ } void Pet::setDeathState(DeathState s) // overwrite virtual Creature::setDeathState and Unit::setDeathState @@ -530,9 +411,9 @@ void Pet::Update(uint32 diff) { case CORPSE: { - if (!IsHunterPet() || m_corpseRemoveTime <= GameTime::GetGameTime()) + //if (!IsHunterPet() || m_corpseRemoveTime <= GameTime::GetGameTime()) { - Remove(PET_SAVE_DISMISS); //hunters' pets never get removed because of death, NEVER! + //Remove(PET_SAVE_DISMISS); //hunters' pets never get removed because of death, NEVER! return; } break; @@ -541,19 +422,19 @@ void Pet::Update(uint32 diff) { // unsummon pet that lost owner Player* owner = GetOwner(); - if ((!IsWithinDistInMap(owner, GetMap()->GetVisibilityRange()) && !isPossessed()) || (isControlled() && !owner->GetPetGUID())) + if ((!IsWithinDistInMap(owner, GetMap()->GetVisibilityRange()) && !isPossessed()) || (isControlled() && !owner->GetSummonGUID())) //if (!owner || (!IsWithinDistInMap(owner, GetMap()->GetVisibilityDistance()) && (owner->GetCharmGUID() && (owner->GetCharmGUID() != GetGUID()))) || (isControlled() && !owner->GetPetGUID())) { - Remove(PET_SAVE_DISMISS, true); + //Remove(PET_SAVE_DISMISS, true); return; } if (isControlled()) { - if (owner->GetPetGUID() != GetGUID()) + if (owner->GetSummonGUID() != GetGUID()) { TC_LOG_ERROR("entities.pet", "Pet %u is not pet of owner %s, removed", GetEntry(), GetOwner()->GetName().c_str()); - Remove(IsHunterPet() ? PET_SAVE_AS_DELETED : PET_SAVE_DISMISS); + //Remove(IsHunterPet() ? PET_SAVE_AS_DELETED : PET_SAVE_DISMISS); return; } } @@ -564,7 +445,7 @@ void Pet::Update(uint32 diff) m_duration -= diff; else { - Remove(getPetType() != SUMMON_PET ? PET_SAVE_AS_DELETED : PET_SAVE_DISMISS); + //Remove(getPetType() != SUMMON_PET ? PET_SAVE_AS_DELETED : PET_SAVE_DISMISS); return; } } @@ -577,15 +458,10 @@ void Pet::Update(uint32 diff) Creature::Update(diff); } -void Pet::Remove(PetSaveMode mode, bool returnreagent) -{ - GetOwner()->RemovePet(this, mode, returnreagent); -} - void Pet::GivePetXP(uint32 xp) { - if (!IsHunterPet()) - return; + //if (!IsHunterPet()) + // return; if (xp < 1) return; @@ -624,7 +500,7 @@ void Pet::GivePetLevel(uint8 level) if (!level || level == getLevel()) return; - if (!IsHunterPet()) + //if (!IsHunterPet()) { SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, 0); SetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP, uint32(sObjectMgr->GetXPForLevel(level)*PET_XP_FACTOR)); @@ -753,7 +629,7 @@ bool Guardian::InitStatsForLevel(uint8 petlevel) // Resistance // Hunters pets should not inherit resistances from creature_template, they have separate auras for that - if (!IsHunterPet()) + //if (!IsHunterPet()) for (uint8 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i) SetStatFlatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + i), BASE_VALUE, float(cinfo->resistance[i])); @@ -1071,7 +947,7 @@ void Pet::_LoadSpells() void Pet::_SaveSpells(CharacterDatabaseTransaction& trans) { - for (PetSpellMap::iterator itr = m_spells.begin(), next = m_spells.begin(); itr != m_spells.end(); itr = next) + for (PetSpellMapOld::iterator itr = m_spells.begin(), next = m_spells.begin(); itr != m_spells.end(); itr = next) { ++next; @@ -1284,7 +1160,7 @@ bool Pet::addSpell(uint32 spellId, ActiveStates active /*= ACT_DECIDE*/, PetSpel return false; } - PetSpellMap::iterator itr = m_spells.find(spellId); + PetSpellMapOld::iterator itr = m_spells.find(spellId); if (itr != m_spells.end()) { if (itr->second.state == PETSPELL_REMOVED) @@ -1306,7 +1182,7 @@ bool Pet::addSpell(uint32 spellId, ActiveStates active /*= ACT_DECIDE*/, PetSpel } } - PetSpell newspell; + PetSpellOld newspell; newspell.state = state; newspell.type = type; @@ -1341,7 +1217,7 @@ bool Pet::addSpell(uint32 spellId, ActiveStates active /*= ACT_DECIDE*/, PetSpel } else if (spellInfo->IsRanked()) { - for (PetSpellMap::const_iterator itr2 = m_spells.begin(); itr2 != m_spells.end(); ++itr2) + for (PetSpellMapOld::const_iterator itr2 = m_spells.begin(); itr2 != m_spells.end(); ++itr2) { if (itr2->second.state == PETSPELL_REMOVED) continue; @@ -1415,7 +1291,6 @@ bool Pet::learnSpell(uint32 spell_id) WorldPacket data(SMSG_PET_LEARNED_SPELL, 4); data << uint32(spell_id); GetOwner()->SendDirectMessage(&data); - GetOwner()->PetSpellInitialize(); } return true; } @@ -1476,7 +1351,7 @@ bool Pet::unlearnSpell(uint32 spell_id, bool learn_prev, bool clear_ab) bool Pet::removeSpell(uint32 spell_id, bool learn_prev, bool clear_ab) { - PetSpellMap::iterator itr = m_spells.find(spell_id); + PetSpellMapOld::iterator itr = m_spells.find(spell_id); if (itr == m_spells.end()) return false; @@ -1513,8 +1388,6 @@ bool Pet::removeSpell(uint32 spell_id, bool learn_prev, bool clear_ab) // if remove last rank or non-ranked then update action bar at server and client if need if (clear_ab && !learn_prev && m_charmInfo->RemoveSpellFromActionBar(spell_id)) { - if (!m_loading) - GetOwner()->PetSpellInitialize(); // need update action bar for last removed rank } return true; @@ -1591,7 +1464,7 @@ bool Pet::resetTalents() for (uint8 j = 0; j < MAX_TALENT_RANK; ++j) { - for (PetSpellMap::const_iterator itr = m_spells.begin(); itr != m_spells.end();) + for (PetSpellMapOld::const_iterator itr = m_spells.begin(); itr != m_spells.end();) { if (itr->second.state == PETSPELL_REMOVED) { @@ -1615,9 +1488,6 @@ bool Pet::resetTalents() } SetFreeTalentPoints(talentPointsForLevel); - - if (!m_loading) - player->PetSpellInitialize(); return true; } @@ -1729,7 +1599,7 @@ void Pet::ToggleAutocast(SpellInfo const* spellInfo, bool apply) uint32 spellid = spellInfo->Id; - PetSpellMap::iterator itr = m_spells.find(spellid); + PetSpellMapOld::iterator itr = m_spells.find(spellid); if (itr == m_spells.end()) return; @@ -1818,7 +1688,7 @@ bool Pet::Create(ObjectGuid::LowType guidlow, Map* map, uint32 Entry, uint32 pet bool Pet::HasSpell(uint32 spell) const { - PetSpellMap::const_iterator itr = m_spells.find(spell); + PetSpellMapOld::const_iterator itr = m_spells.find(spell); return itr != m_spells.end() && itr->second.state != PETSPELL_REMOVED; } diff --git a/src/server/game/Entities/Pet/Pet.h b/src/server/game/Entities/Pet/Pet.h index 796ee3aa3cb..b29d31a824e 100644 --- a/src/server/game/Entities/Pet/Pet.h +++ b/src/server/game/Entities/Pet/Pet.h @@ -21,31 +21,20 @@ #include "PetDefines.h" #include "TemporarySummon.h" -enum StableResultCode -{ - STABLE_ERR_MONEY = 0x01, // "you don't have enough money" - STABLE_ERR_INVALID_SLOT = 0x03, // "That slot is locked" - STABLE_SUCCESS_STABLE = 0x08, // stable success - STABLE_SUCCESS_UNSTABLE = 0x09, // unstable/swap success - STABLE_SUCCESS_BUY_SLOT = 0x0A, // buy slot success - STABLE_ERR_EXOTIC = 0x0B, // "you are unable to control exotic creatures" - STABLE_ERR_STABLE = 0x0C // "Internal pet error" -}; - enum PetStableInfo { PET_STABLE_ACTIVE = 1, PET_STABLE_INACTIVE = 2 }; -struct PetSpell +struct PetSpellOld { ActiveStates active; PetSpellState state; PetSpellType type; }; -typedef std::unordered_map PetSpellMap; +typedef std::unordered_map PetSpellMapOld; typedef std::vector AutoSpellList; typedef std::vector PetScalingAuraList; @@ -77,8 +66,6 @@ class TC_GAME_API Pet : public Guardian bool CreateBaseAtTamed(CreatureTemplate const* cinfo, Map* map); bool LoadPetData(Player* owner, uint32 petentry = 0, uint32 petnumber = 0, bool current = false); bool IsLoading() const override { return m_loading;} - void SavePetToDB(PetSaveMode mode); - void Remove(PetSaveMode mode, bool returnreagent = false); static void DeleteFromDB(ObjectGuid::LowType guidlow); void setDeathState(DeathState s) override; // overwrite virtual Creature::setDeathState and Unit::setDeathState @@ -137,7 +124,7 @@ class TC_GAME_API Pet : public Guardian void CleanupActionBar(); std::string GenerateActionBarData() const; - PetSpellMap m_spells; + PetSpellMapOld m_spells; AutoSpellList m_autospells; PetScalingAuraList m_petScalingAuras; diff --git a/src/server/game/Entities/Pet/PetDefines.h b/src/server/game/Entities/Pet/PetDefines.h index 8eba4649f30..430bea551bb 100644 --- a/src/server/game/Entities/Pet/PetDefines.h +++ b/src/server/game/Entities/Pet/PetDefines.h @@ -25,28 +25,28 @@ enum PetType MAX_PET_TYPE = 4 }; -#define MAX_PET_STABLES 20 +static constexpr uint8 MAX_PET_STABLES = 20; -// stored in character_pet.slot -enum PetSaveMode +enum PetCallSpells { - PET_SAVE_AS_DELETED = -1, // removes pet from DB and erases from playerPetDataStore - PET_SAVE_UPADTE_SLOT = 0, // not used yet - PET_SAVE_CURRENT_STATE = 1, // Saves everything like it is atm, current = true - PET_SAVE_DISMISS = 2, // Saves everything like it is atm, removes auras and current = false - PET_SAVE_LOGOUT = 3, // Saves everything like it is atm, removes auras and current = true - PET_SAVE_NEW_PET = 4, // Saves everything like it is atm, current = true and pushes new into playerPetDataStore - PET_SAVE_TEMP_UNSUMMON = PET_SAVE_LOGOUT + SPELL_CALL_PET_1 = 883, + SPELL_CALL_PET_2 = 83242, + SPELL_CALL_PET_3 = 83243, + SPELL_CALL_PET_4 = 83244, + SPELL_CALL_PET_5 = 83245 }; -enum PetStableSlot +static constexpr std::array PetCallSpellsArray = { SPELL_CALL_PET_1, SPELL_CALL_PET_2, SPELL_CALL_PET_3, SPELL_CALL_PET_4, SPELL_CALL_PET_5 }; + +enum PetStableSlot : uint8 { - PET_SLOT_FIRST = 0, - PET_SLOT_LAST = 20, - PET_SLOT_FIRST_ACTIVE_SLOT = PET_SLOT_FIRST, - PET_SLOT_LAST_ACTIVE_SLOT = 4, - PET_SLOT_FIRST_STABLE_SLOT = 5, - PET_SLOT_LAST_STABLE_SLOT = PET_SLOT_LAST + PET_SLOT_FIRST = 0, + PET_SLOT_LAST = 25, + PET_SLOT_FIRST_ACTIVE_SLOT = PET_SLOT_FIRST, + PET_SLOT_LAST_ACTIVE_SLOT = 4, + PET_SLOT_FIRST_STABLE_SLOT = 5, + PET_SLOT_LAST_STABLE_SLOT = PET_SLOT_LAST, + PET_SLOT_INACTIVE_STABLE_SLOTS = 20 }; enum PetSpellState @@ -66,10 +66,11 @@ enum PetSpellType enum ActionFeedback { - FEEDBACK_NONE = 0, - FEEDBACK_PET_DEAD = 1, - FEEDBACK_NOTHING_TO_ATT = 2, - FEEDBACK_CANT_ATT_TARGET = 3 + FEEDBACK_NONE = 0, + FEEDBACK_PET_DEAD = 1, + FEEDBACK_NOTHING_TO_ATT = 2, + FEEDBACK_CANT_ATT_TARGET = 3, + FEEBDACK_CHARGE_HAS_NO_PATH = 4 }; enum PetTalk @@ -78,9 +79,34 @@ enum PetTalk PET_TALK_ATTACK = 1 }; +enum class PetStableResultCode : uint8 +{ + NotEnoughMoney = 1, + InvalidSlot = 3, + StabledSucccessfully = 8, + UnstabledSucccessfully = 9, + UnlockedStableSlot = 10, + CannotControlExoticPets = 11, + InternalError = 12 +}; + +enum PetSummonSpellIds +{ + // Hunter Pets + SPELL_PET_ENERGIZE = 99289, + + // Pets + SPELL_SUMMON_HEAL = 36492 // serverside Spell +}; + // Used by companions (minipets) and quest slot summons constexpr float DEFAULT_FOLLOW_DISTANCE = 2.5f; constexpr float DEFAULT_FOLLOW_DISTANCE_PET = 3.f; constexpr float DEFAULT_FOLLOW_ANGLE = float(M_PI); +// The maximum distance that can be between a following summon and the player. If the player crosses this threshold the summon must be unsummoned/dismissed +constexpr float MAX_SUMMON_DISTANCE = 100.f; + +using PlayerPetDataKey = std::pair; + #endif diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 0fefd124563..60d77217d30 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -79,8 +79,11 @@ #include "Opcodes.h" #include "OutdoorPvP.h" #include "OutdoorPvPMgr.h" +#include "NewPet.h" #include "Pet.h" #include "PetPackets.h" +#include "NewPet.h" +#include "PetPackets.h" #include "PetitionMgr.h" #include "PhasingHandler.h" #include "PoolMgr.h" @@ -251,13 +254,6 @@ Player::Player(WorldSession* session): Unit(true) m_canTitanGrip = false; m_titanGripPenaltySpellId = 0; - m_temporaryUnsummonedPetNumber = 0; - //cache for UNIT_CREATED_BY_SPELL to allow - //returning reagents for temporarily removed pets - //when dying/logging out - m_oldpetspell = 0; - m_lastpetnumber = 0; - ////////////////////Rest System///////////////////// _restTime = 0; inn_triggerId = 0; @@ -354,8 +350,9 @@ Player::Player(WorldSession* session): Unit(true) m_reputationMgr = std::make_unique(this); _hasValidLFGLeavePoint = false; _archaeology = std::make_unique(this); - m_petScalingSynchTimer.Reset(1000); m_groupUpdateTimer.Reset(5000); + + _canControlClassPets = false; } Player::~Player() @@ -380,9 +377,6 @@ Player::~Player() for (size_t i = 0; i < _voidStorageItems.size(); ++i) delete _voidStorageItems[i]; - for (uint8 i = 0; i < PlayerPetDataStore.size(); i++) - delete PlayerPetDataStore[i]; - ClearResurrectRequestData(); sWorld->DecreasePlayerCount(); @@ -1276,23 +1270,6 @@ void Player::Update(uint32 p_time) m_groupUpdateTimer.Reset(5000); } - Pet* pet = GetPet(); - if (pet && !pet->IsWithinDistInMap(this, GetMap()->GetVisibilityRange()) && !pet->isPossessed()) - //if (pet && !pet->IsWithinDistInMap(this, GetMap()->GetVisibilityDistance()) && (GetCharmGUID() && (pet->GetGUID() != GetCharmGUID()))) - RemovePet(pet, PET_SAVE_DISMISS, true); - - m_petScalingSynchTimer.Update(p_time); - if (m_petScalingSynchTimer.Passed()) - { - if (pet) - { - pet->UpdatePetScalingAuras(); - pet->UpdateAllStats(); - } - - m_petScalingSynchTimer.Reset(1000); - } - if (IsAlive()) { if (m_hostileReferenceCheckTimer <= p_time) @@ -1333,9 +1310,6 @@ void Player::setDeathState(DeathState s) ClearResurrectRequestData(); - //FIXME: is pet dismissed at dying or releasing spirit? if second, add setDeathState(DEAD) to HandleRepopRequestOpcode and define pet unsummon here with (s == DEAD) - RemovePet(nullptr, PET_SAVE_DISMISS, true); - // save value before aura remove in Unit::setDeathState ressSpellId = GetUInt32Value(PLAYER_SELF_RES_SPELL); @@ -1410,9 +1384,6 @@ bool Player::TeleportTo(uint32 mapid, float x, float y, float z, float orientati return false; } - // preparing unsummon pet if lost (we must get pet before teleportation or will not find it later) - Pet* pet = GetPet(); - MapEntry const* mEntry = sMapStore.LookupEntry(mapid); // don't let enter battlegrounds without assigned battleground id (for example through areatrigger)... @@ -1477,13 +1448,6 @@ bool Player::TeleportTo(uint32 mapid, float x, float y, float z, float orientati return true; } - if (!(options & TELE_TO_NOT_UNSUMMON_PET)) - { - //same map, only remove pet if out of range for new position - if (pet && !pet->IsWithinDist3d(x, y, z, GetMap()->GetVisibilityRange())) - UnsummonPetTemporaryIfAny(); - } - if (!(options & TELE_TO_NOT_LEAVE_COMBAT)) CombatStop(); @@ -1577,14 +1541,8 @@ bool Player::TeleportTo(uint32 mapid, float x, float y, float z, float orientati { RemoveArenaSpellCooldowns(true); RemoveArenaAuras(); - if (pet) - pet->RemoveArenaAuras(); } - // remove pet on map change - if (pet) - UnsummonPetTemporaryIfAny(); - // remove all dyn objects RemoveAllDynObjects(); @@ -1747,10 +1705,10 @@ void Player::RemoveFromWorld() // cleanup if (IsInWorld()) { - ///- Release charmed creatures, unsummon totems and remove pets/guardians + ///- Release charmed creatures StopCastingCharm(); StopCastingBindSight(); - UnsummonPetTemporaryIfAny(); + UnsummonAllSummonsOnLogout(); ClearComboPoints(); ClearComboPointHolders(); ObjectGuid lootGuid = GetLootGUID(); @@ -2084,9 +2042,6 @@ void Player::SetGameMaster(bool on) SetFlag(PLAYER_FLAGS, PLAYER_FLAGS_GM); SetFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_ALLOW_CHEAT_SPELLS); - if (Pet* pet = GetPet()) - pet->SetFaction(FACTION_FRIENDLY); - RemoveByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, UNIT_BYTE2_FLAG_FFA_PVP); ResetContestedPvP(); @@ -2104,9 +2059,6 @@ void Player::SetGameMaster(bool on) RemoveFlag(PLAYER_FLAGS, PLAYER_FLAGS_GM); RemoveFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_ALLOW_CHEAT_SPELLS); - if (Pet* pet = GetPet()) - pet->SetFaction(GetFaction()); - // restore FFA PvP Server state if (sWorld->IsFFAPvPRealm()) SetByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, UNIT_BYTE2_FLAG_FFA_PVP); @@ -2325,10 +2277,6 @@ void Player::GiveLevel(uint8 level) SetFullHealth(); SetFullPower(POWER_MANA); - // update level to hunter/summon pet - if (Pet* pet = GetPet()) - pet->SynchronizeLevelWithOwner(); - //Update QuestGivers SendQuestGiverStatusMultiple(); @@ -2354,6 +2302,10 @@ void Player::GiveLevel(uint8 level) SetByteFlag(PLAYER_FIELD_BYTES, PLAYER_FIELD_BYTES_OFFSET_RAF_GRANTABLE_LEVEL, 0x01); } + if (NewPet* pet = GetActivelyControlledSummon()) + if (pet->IsClassPet()) + pet->SynchronizeLevelWithSummoner(); + sScriptMgr->OnPlayerLevelChanged(this, oldLevel); } @@ -2575,10 +2527,6 @@ void Player::InitStatsForLevel(bool reapplyMods) SetFullPower(POWER_RAGE); SetFullPower(POWER_FOCUS); SetPower(POWER_RUNIC_POWER, 0); - - // update level to hunter/summon pet - if (Pet* pet = GetPet()) - pet->SynchronizeLevelWithOwner(); } void Player::SendKnownSpells(bool firstLogin /*= false*/) @@ -3179,9 +3127,6 @@ void Player::LearnSpell(uint32 spell_id, bool dependent, uint32 fromSkill /*= 0* } } - if (CanControlPet(spell_id) && GetPet()) - PetSpellInitialize(); - if (Guild* guild = GetGuild()) guild->UpdateMemberData(this, GUILD_MEMBER_DATA_PROFESSIONS, 0); } @@ -3385,11 +3330,6 @@ void Player::RemoveArenaSpellCooldowns(bool removeActivePetCooldowns) && spellInfo->CategoryRecoveryTime < 10 * MINUTE * IN_MILLISECONDS && !spellInfo->HasAttribute(SPELL_ATTR6_DO_NOT_RESET_COOLDOWN_IN_ARENA); }, true); - - // pet cooldowns - if (removeActivePetCooldowns) - if (Pet* pet = GetPet()) - pet->GetSpellHistory()->ResetAllCooldowns(); } uint32 Player::GetNextResetTalentsCost() const @@ -3454,8 +3394,6 @@ bool Player::ResetTalents(bool no_cost) } } - RemovePet(nullptr, PET_SAVE_DISMISS, true); - for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId) { TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId); @@ -3767,21 +3705,6 @@ void Player::DeleteFromDB(ObjectGuid playerguid, uint32 accountId, bool updateRe while (resultMail->NextRow()); } - // Unsummon and delete for pets in world is not required: player deleted from CLI or character list with not loaded pet. - // NOW we can finally clear other DB data related to character - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PETS); - stmt->setUInt32(0, guid); - PreparedQueryResult resultPets = CharacterDatabase.Query(stmt); - - if (resultPets) - { - do - { - ObjectGuid::LowType petguidlow = (*resultPets)[0].GetUInt32(); - Pet::DeleteFromDB(petguidlow); - } while (resultPets->NextRow()); - } - // Delete char from social list of online chars stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_SOCIAL); stmt->setUInt32(0, guid); @@ -3903,13 +3826,13 @@ void Player::DeleteFromDB(ObjectGuid playerguid, uint32 accountId, bool updateRe stmt->setUInt32(0, guid); trans->Append(stmt); - stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PET_BY_OWNER); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PETS); stmt->setUInt32(0, guid); trans->Append(stmt); - stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PET_DECLINEDNAME_BY_OWNER); - stmt->setUInt32(0, guid); - trans->Append(stmt); + //stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PET_DECLINEDNAME_BY_OWNER); + //stmt->setUInt32(0, guid); + //trans->Append(stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACHIEVEMENTS); stmt->setUInt32(0, guid); @@ -7047,8 +6970,8 @@ void Player::UpdateZone(uint32 newZone, uint32 newArea) if (GetGroup()) { SetGroupUpdateFlag(GROUP_UPDATE_FULL); - if (GetPet()) - SetGroupUpdateFlag(GROUP_UPDATE_PET); + //if (GetPet()) + // SetGroupUpdateFlag(GROUP_UPDATE_PET); } // zone changed, so area changed as well, update it @@ -7280,12 +7203,12 @@ void Player::DuelComplete(DuelCompleteType type) // cleanup combo points if (GetComboTarget() == duel->opponent->GetGUID()) ClearComboPoints(); - else if (GetComboTarget() == duel->opponent->GetPetGUID()) + else if (GetComboTarget() == duel->opponent->GetSummonGUID()) ClearComboPoints(); if (duel->opponent->GetComboTarget() == GetGUID()) duel->opponent->ClearComboPoints(); - else if (duel->opponent->GetComboTarget() == GetPetGUID()) + else if (duel->opponent->GetComboTarget() == GetSummonGUID()) duel->opponent->ClearComboPoints(); //cleanups @@ -8760,21 +8683,6 @@ void Player::SendTalentWipeConfirm(ObjectGuid guid) const void Player::ResetPetTalents() { - // This needs another gossip option + NPC text as a confirmation. - // The confirmation gossip listid has the text: "Yes, please do." - Pet* pet = GetPet(); - - if (!pet || pet->getPetType() != HUNTER_PET || pet->m_usedTalentCount == 0) - return; - - CharmInfo* charmInfo = pet->GetCharmInfo(); - if (!charmInfo) - { - TC_LOG_ERROR("entities.player", "Object (GUID: %u TypeId: %u) is considered pet-like, but doesn't have charm info!", pet->GetGUID().GetCounter(), pet->GetTypeId()); - return; - } - pet->resetTalents(); - SendTalentsInfoData(true); } /*********************************************************/ @@ -13334,8 +13242,8 @@ void Player::PrepareGossipMenu(WorldObject* source, uint32 menuId /*= 0*/, bool canTalk = false; break; case GOSSIP_OPTION_UNLEARNPETTALENTS: - if (!GetPet() || GetPet()->getPetType() != HUNTER_PET || GetPet()->m_spells.size() <= 1 || !creature->CanResetTalents(this)) - canTalk = false; + //if (!GetPet() || GetPet()->getPetType() != HUNTER_PET || GetPet()->m_spells.size() <= 1 || !creature->CanResetTalents(this)) + // canTalk = false; break; case GOSSIP_OPTION_TAXIVENDOR: if (GetSession()->SendLearnNewTaxiNode(creature)) @@ -13534,7 +13442,10 @@ void Player::OnGossipSelect(WorldObject* source, uint32 gossipListId, uint32 men GetSession()->SendListInventory(guid); break; case GOSSIP_OPTION_STABLEPET: - GetSession()->SendStablePet(guid); + GetSession()->SendPetStableList(guid); + // the client needs at least one pet stable result packet to initialize an internal byte that allows using the stable. + // Due to the abscence of sniff data for the pet stable, we have to use a fake result for the time being + GetSession()->SendStableResult(PetStableResultCode(0)); break; case GOSSIP_OPTION_TRAINER: GetSession()->SendTrainerList(source->ToCreature(), sObjectMgr->GetCreatureTrainerForGossipOption(source->GetEntry(), menuId, gossipListId)); @@ -16948,7 +16859,7 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol SetUInt32Value(UNIT_CHANNEL_SPELL, 0); // clear charm/summon related fields - SetOwnerGUID(ObjectGuid::Empty); + SetSummonerGUID(ObjectGuid::Empty); SetGuidValue(UNIT_FIELD_CHARMEDBY, ObjectGuid::Empty); SetGuidValue(UNIT_FIELD_CHARM, ObjectGuid::Empty); SetGuidValue(UNIT_FIELD_SUMMON, ObjectGuid::Empty); @@ -17010,6 +16921,8 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) m_deathState = DEAD; + _LoadPets(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_ALL_PETS)); + // after spell load, learn rewarded spell if need also _LoadQuestStatus(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_QUEST_STATUS)); _LoadQuestStatusRewarded(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_QUEST_STATUS_REW)); @@ -17835,143 +17748,243 @@ void Player::_LoadMail(PreparedQueryResult mailsResult, PreparedQueryResult mail UpdateNextMailTimeAndUnreads(); } -void Player::LoadPet() -{ - //fixme: the pet should still be loaded if the player is not in world - // just not added to the map - if (IsInWorld()) - { - Pet* pet = new Pet(this); - if (!pet->LoadPetData(this, 0, 0, true)) - delete pet; - } -} - -void Player::LoadPetsFromDB(PreparedQueryResult result) +void Player::_LoadPets(PreparedQueryResult result) { + // "SELECT PetNumber, CreatureId, TamedCreatureId, DisplayId, SavedHealth, SavedPower, CreatedBySpellId, LastSaveTime, ReactState, Slot, HasBeenRenamed, IsActive, `Name`, ActionBar, Talents FROM character_pet WHERE Guid = ? ORDER BY Slot" if (!result) return; do { Field* fields = result->Fetch(); + std::unique_ptr petData = std::make_unique(); + + petData->PetNumber = fields[0].GetUInt32(); + petData->CreatureId = fields[1].GetUInt32(); + petData->TamedCreatureId = fields[2].GetUInt32(); + petData->DisplayId = fields[3].GetUInt32(); + petData->SavedHealth = fields[4].GetUInt32(); + petData->SavedPower = fields[5].GetUInt32(); + petData->CreatedBySpellId = fields[6].GetUInt32(); + petData->LastSaveTime = fields[7].GetUInt32(); + petData->ReactState = static_cast(fields[8].GetUInt8()); + petData->Slot = fields[9].GetUInt8(); + petData->HasBeenRenamed = fields[10].GetBool(); + petData->IsActive = fields[11].GetBool(); + petData->Name = fields[12].GetString(); + petData->ActionBar = fields[13].GetString(); + petData->Talents = fields[14].GetString(); + petData->Status = PlayerPetDataStatus::UpToDate; + + std::pair key = std::make_pair(petData->Slot, petData->CreatureId); + + if (petData->IsActive) + SetActiveClassPetDataKey(key); + + _playerPetDataMap.emplace(key, std::move(petData)); - PlayerPetData* playerPetData = new PlayerPetData(); + } while (result->NextRow()); +} + +void Player::SendPetSpellsMessage(NewPet* pet, bool remove /*= false*/) +{ + CharmInfo* charmInfo = pet->GetCharmInfo(); + if (!charmInfo) + { + TC_LOG_ERROR("entities.pet", "Attempted to send pet spells message for a pet without charmInfo."); + return; + } - uint8 slot = fields[7].GetUInt8(); - uint32 petId = fields[0].GetUInt32(); + WorldPackets::Pet::PetSpellsMessage packet; + if (!remove) + { + packet.PetGUID = pet->GetGUID(); + packet._CreatureFamily = pet->GetCreatureTemplate()->family; // creature family (required for pet talents) + packet.TimeLimit = pet->GetRemainingSummonDuration().count(); + packet.ReactState = pet->GetReactState(); + packet.CommandState = charmInfo->GetCommandState(); + packet.Flag = 0; - if (slot > PET_SLOT_LAST) + charmInfo->BuildActionBar(packet.ActionButtons); + if (pet->IsClassPet()) { - TC_LOG_ERROR("sql.sql", "Player::LoadPetsFromDB: bad slot %u for pet %u!", slot, petId); - continue; + for (auto const& itr : pet->GetSpells()) + { + if (itr.second.State == PETSPELL_REMOVED) + continue; + + packet.Actions.push_back(MAKE_UNIT_ACTION_BUTTON(itr.first, itr.second.Active)); + } + + // Cooldowns + pet->GetSpellHistory()->WritePetSpellHistory(packet); } + } - playerPetData->PetId = petId; - playerPetData->CreatureId = fields[1].GetUInt32(); - playerPetData->Owner = fields[2].GetUInt64(); - playerPetData->DisplayId = fields[3].GetUInt32(); - playerPetData->Petlevel = fields[4].GetUInt16(); - playerPetData->PetExp = fields[5].GetUInt32(); - playerPetData->Reactstate = ReactStates(fields[6].GetUInt8()); - playerPetData->Slot = slot; - playerPetData->Name = fields[8].GetString(); - playerPetData->Renamed = fields[9].GetBool(); - playerPetData->Active = fields[10].GetBool(); - playerPetData->SavedHealth = fields[11].GetUInt32(); - playerPetData->SavedMana = fields[12].GetUInt32(); - playerPetData->Actionbar = fields[13].GetString(); - playerPetData->Timediff = fields[14].GetUInt32(); - playerPetData->SummonSpellId = fields[15].GetUInt32(); - playerPetData->Type = PetType(fields[16].GetUInt8()); - - PlayerPetDataStore.push_back(playerPetData); + SendDirectMessage(packet.Write()); +} - } while (result->NextRow()); +void Player::SetCanControlClassPets() +{ + _canControlClassPets = true; + RemoveByteFlag(PLAYER_FIELD_BYTES, PLAYER_FIELD_BYTES_OFFSET_FLAGS, PLAYER_FIELD_BYTE_HIDE_PET_BAR); } -PlayerPetData* Player::GetPlayerPetDataById(uint32 petId) +bool Player::CanControlClassPets() const { - for (PlayerPetData* p : PlayerPetDataStore) - if (p->PetId == petId) - return p; + if (ChrClassesEntry const* clsEntry = sChrClassesStore.LookupEntry(getClass())) + if (clsEntry->GetFlags().HasFlag(ChrClassesFlags::PetBarInitiallyHidden)) + return _canControlClassPets; - return nullptr; + return true; } -PlayerPetData* Player::GetPlayerPetDataBySlot(uint8 slot) +PlayerPetData* Player::GetPlayerPetData(uint8 slot, uint32 creatureId) const { - for (PlayerPetData* p : PlayerPetDataStore) - if (p->Slot == slot) - return p; + auto itr = _playerPetDataMap.find(std::make_pair(slot, creatureId)); + if (itr == _playerPetDataMap.end()) + return nullptr; - return nullptr; + return itr->second.get(); } -PlayerPetData* Player::GetPlayerPetDataByCreatureId(uint32 creatureId) +PlayerPetData* Player::GetPlayerPetDataByPetNumber(uint32 petNumber) { - for (PlayerPetData* p : PlayerPetDataStore) - if (p->CreatureId == creatureId) - return p; + for (auto const& pair : _playerPetDataMap) + if (pair.second->PetNumber == petNumber) + return pair.second.get(); return nullptr; } -PlayerPetData* Player::GetPlayerPetDataCurrent() +PlayerPetData* Player::CreatePlayerPetData(uint8 slot, uint32 creatureId, Optional tamedCreatureId /*= {}*/) { - for (PlayerPetData* p : PlayerPetDataStore) - if (p->Active == true) - return p; + // Make sure that we do not attempt to create data for already existing pets + if (_playerPetDataMap.find(std::make_pair(slot, creatureId)) != _playerPetDataMap.end()) + { + TC_LOG_ERROR("entities.player", "Player::CreatePlayerPetData: Player(GUID: % u) tried to create pet data (slot %u, creatureId %u) that already exsist.", GetGUID().GetCounter(), slot, creatureId); + return nullptr; + } - return nullptr; + CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(tamedCreatureId.value_or(creatureId)); + if (!cInfo) + { + TC_LOG_ERROR("entities.player", "Player::CreatePlayerPetData: Player(GUID: % u) tried to create pet data (slot %u, creatureId %u) which does not have a creature_template entry.", GetGUID().GetCounter(), slot, creatureId); + return nullptr; + } + + std::unique_ptr petData = std::make_unique(); + petData->Slot = slot; + petData->CreatureId = creatureId; + petData->PetNumber = sObjectMgr->GeneratePetNumber(); + petData->Name = sObjectMgr->GeneratePetName(tamedCreatureId.value_or(creatureId)); + petData->Status = PlayerPetDataStatus::New; + petData->ReactState = REACT_ASSIST; + petData->TamedCreatureId = tamedCreatureId.value_or(0); + + return _playerPetDataMap.emplace(std::make_pair(slot, creatureId), std::move(petData)).first->second.get(); } -Optional Player::GetFirstUnusedActivePetSlot() +Optional Player::GetUnusedActivePetSlot(bool checkForUnlocked /*= true*/) { - std::set unusedActiveSlot = { 0, 1, 2, 3, 4 }; //unfiltered + for (uint8 i = PET_SLOT_FIRST; i <= PET_SLOT_LAST_ACTIVE_SLOT; ++i) + { + if (i >= PetCallSpellsArray.size()) + break; - for (PlayerPetData* p : PlayerPetDataStore) - if (unusedActiveSlot.find(p->Slot) != unusedActiveSlot.end()) - unusedActiveSlot.erase(p->Slot); + // Only allow tamed pets to be created for unlocked active slots + if (checkForUnlocked && !HasSpell(PetCallSpellsArray[i])) + continue; - if (!unusedActiveSlot.empty()) - return *unusedActiveSlot.begin(); + if (_playerPetDataMap.find(std::make_pair(i, 0)) == _playerPetDataMap.end()) + return i; + } - return Optional{}; + return std::nullopt; } -Optional Player::GetFirstUnusedPetSlot() +void Player::AbandonPet() { - std::set unusedSlot; - - for (uint8 i = 0; i < PET_SLOT_LAST; i++) // 4 MAX StableSlots (256 theoretically) - unusedSlot.insert(i); + NewPet* pet = GetActivelyControlledSummon(); + if (!pet) + return; - for (PlayerPetData* p : PlayerPetDataStore) - if (unusedSlot.find(p->Slot) != unusedSlot.end()) - unusedSlot.erase(p->Slot); + Optional const& key = pet->GetPlayerPetDataKey(); + if (!key.has_value()) + return; - if (!unusedSlot.empty()) - return *unusedSlot.begin(); + PlayerPetDataMap::const_iterator itr = _playerPetDataMap.find(std::make_pair(key->first, key->second)); + if (itr == _playerPetDataMap.end() || itr->second->PetNumber != pet->GetUInt32Value(UNIT_FIELD_PETNUMBER)) + return; - return Optional{}; + pet->Unsummon(); + _deletedPlayerPetDataSet.insert(itr->second->PetNumber); + _playerPetDataMap.erase(itr); + _activeClassPetDataKey.reset(); } -void Player::DeleteFromPlayerPetDataStore(uint32 petNumber) +void Player::ResummonActiveClassPet() { - for (uint8 i = 0; i < PlayerPetDataStore.size(); ++i) + if (!GetSummonGUID().IsEmpty() || !_activeClassPetDataKey.has_value()) + return; + + PlayerPetData* petData = GetPlayerPetData(_activeClassPetDataKey->first, _activeClassPetDataKey->second); + if (!petData) { - if (PlayerPetDataStore[i]->PetId == petNumber) - { - delete PlayerPetDataStore[i]; - PlayerPetDataStore.erase(PlayerPetDataStore.begin() + (i--)); - } + _activeClassPetDataKey.reset(); + return; } + + uint32 creatureId = petData->TamedCreatureId != 0 ? 0 : petData->CreatureId; + SummonPet(creatureId, petData->Slot, 0, true, GetPosition()); } -void Player::AddToPlayerPetDataStore(PlayerPetData* playerPetData) +void Player::SetPetSlot(uint32 petNumber, uint8 destSlot) { - PlayerPetDataStore.push_back(playerPetData); + auto itr = std::find_if(_playerPetDataMap.begin(), _playerPetDataMap.end(), [petNumber](auto const& pair) { return pair.second->PetNumber == petNumber; }); + if (itr == _playerPetDataMap.end()) + { + TC_LOG_ERROR("entities.player", "Player::SetPetSlot: Player (%s, guid: %s) tried to change the pet slot of a non-existing pet. Possible cheater.", GetName().c_str(), GetGUID().ToString().c_str()); + return; + } + + auto it = _playerPetDataMap.find(std::make_pair(destSlot, 0)); + + // Either our source or target pet is still active. We have to unsummon it first + if (NewPet* pet = GetActivelyControlledSummon()) + { + uint32 petNumber = pet->GetUInt32Value(UNIT_FIELD_PETNUMBER); + if ((itr->second->PetNumber == petNumber) || (it != _playerPetDataMap.end() && it->second->PetNumber == petNumber)) + pet->Unsummon(); + } + + bool unstabled = itr->second->Slot >= PET_SLOT_FIRST_STABLE_SLOT && destSlot <= PET_SLOT_LAST_ACTIVE_SLOT; + // If we have a pet that must be swapped with another + if (it != _playerPetDataMap.end()) + { + it->second->Slot = itr->second->Slot; + itr->second->Slot = destSlot; + + if (it->second->Status != PlayerPetDataStatus::New) + it->second->Status = PlayerPetDataStatus::Changed; + + if (itr->second->Status != PlayerPetDataStatus::New) + itr->second->Status = PlayerPetDataStatus::Changed; + + // Swap the smart pointers + it->second.swap(itr->second); + } + else + { + itr->second->Slot = destSlot; + if (itr->second->Status != PlayerPetDataStatus::New) + itr->second->Status = PlayerPetDataStatus::Changed; + + _playerPetDataMap.emplace(std::make_pair(destSlot, 0), std::move(itr->second)); + _playerPetDataMap.erase(itr); + } + + GetSession()->SendStableResult(unstabled ? PetStableResultCode::UnstabledSucccessfully : PetStableResultCode::StabledSucccessfully); } void Player::_LoadQuestStatus(PreparedQueryResult result) @@ -19163,15 +19176,12 @@ void Player::SaveToDB(CharacterDatabaseTransaction trans, bool create /* = false _SaveInstanceTimeRestrictions(trans); _SaveCurrency(trans); _SaveCUFProfiles(trans); + _SavePets(trans); // check if stats should only be saved on logout // save stats can be out of transaction if (m_session->isLogingOut() || !sWorld->getBoolConfig(CONFIG_STATS_SAVE_ONLY_ON_LOGOUT)) _SaveStats(trans); - - // save pet (hunter pet level and experience and all type pets health/mana). - if (Pet* pet = GetPet()) - pet->SavePetToDB(PET_SAVE_CURRENT_STATE); } // fast save function for item/money cheating preventing - save only inventory and money state @@ -19508,7 +19518,6 @@ void Player::_SaveCUFProfiles(CharacterDatabaseTransaction& trans) } } - void Player::_SaveMail(CharacterDatabaseTransaction& trans) { CharacterDatabasePreparedStatement* stmt; @@ -19919,6 +19928,85 @@ void Player::_SaveStats(CharacterDatabaseTransaction& trans) const trans->Append(stmt); } +void Player::_SavePets(CharacterDatabaseTransaction& trans) +{ + CharacterDatabasePreparedStatement* stmt = nullptr; + uint32 lowGuid = GetGUID().GetCounter(); + + // Update the pet data of our active summon before saving + if (NewPet* pet = GetActivelyControlledSummon()) + { + if (pet->HasPlayerPetDataKey()) + { + Optional const& key = pet->GetPlayerPetDataKey(); + pet->UpdatePlayerPetData(GetPlayerPetData(key->first, key->second)); + } + } + + // Insert new pets and update existing pet data + for (auto const& pair : _playerPetDataMap) + { + PlayerPetData* petData = pair.second.get(); + if (petData->Status == PlayerPetDataStatus::UpToDate) + continue; + + bool isActive = petData->SavedHealth != 0 && + _activeClassPetDataKey.has_value() && _activeClassPetDataKey->first == pair.first.first && + _activeClassPetDataKey->second == pair.first.second; + + if (petData->Status == PlayerPetDataStatus::New) + { + // INSERT INTO character_pet (Guid, PetNumber, CreatureId, TamedCreatureId, DisplayId, SavedHealth, SavedPower, CreatedBySpellId, LastSaveTime, ReactState, Slot, HasBeenRenamed, IsActive, `Name`, ActionBar, Talents) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_PET); + stmt->setUInt32(0, lowGuid); + stmt->setUInt32(1, petData->PetNumber); + stmt->setUInt32(2, petData->CreatureId); + stmt->setUInt32(3, petData->TamedCreatureId); + stmt->setUInt32(4, petData->DisplayId); + stmt->setUInt32(5, petData->SavedHealth); + stmt->setUInt32(6, petData->SavedPower); + stmt->setUInt32(7, petData->CreatedBySpellId); + stmt->setUInt32(8, petData->LastSaveTime); + stmt->setUInt8(9, petData->ReactState); + stmt->setUInt8(10, petData->Slot); + stmt->setUInt8(11, petData->HasBeenRenamed); + stmt->setUInt8(12, isActive); + stmt->setString(13, petData->Name); + stmt->setString(14, petData->ActionBar); + stmt->setString(15, petData->Talents); + } + else if (petData->Status == PlayerPetDataStatus::Changed) + { + // UPDATE character_pet SET SavedHealth = ?, SavedPower = ?, LastSaveTime = ?, ReactState = ?, Slot = ?, HasBeenRenamed = ?, IsActive = ?, `Name` = ?, ActionBar = ?, Talents = ? WHERE PetNumber = ? + stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET); + stmt->setUInt32(0, petData->SavedHealth); + stmt->setUInt32(1, petData->SavedPower); + stmt->setUInt32(2, petData->LastSaveTime); + stmt->setUInt8(3, petData->ReactState); + stmt->setUInt8(4, petData->Slot); + stmt->setUInt8(5, petData->HasBeenRenamed); + stmt->setUInt8(6, isActive); + stmt->setString(7, petData->Name); + stmt->setString(8, petData->ActionBar); + stmt->setString(9, petData->Talents); + stmt->setUInt32(10, petData->PetNumber); + } + + petData->Status = PlayerPetDataStatus::UpToDate; + + trans->Append(stmt); + } + + for (uint32 deletedPetNumber : _deletedPlayerPetDataSet) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PET); + stmt->setUInt32(0, deletedPetNumber); + trans->Append(stmt); + } + + _deletedPlayerPetDataSet.clear(); +} + void Player::outDebugValues() const { if (!sLog->ShouldLog("entities.unit", LOG_LEVEL_DEBUG)) @@ -20190,15 +20278,6 @@ void Player::SetContestedPvP(Player* attackedPlayer) Trinity::AIRelocationNotifier notifier(*this); Cell::VisitWorldObjects(this, notifier, GetVisibilityRange()); } - for (Unit* unit : m_Controlled) - { - if (!unit->HasUnitState(UNIT_STATE_ATTACK_PLAYER)) - { - unit->AddUnitState(UNIT_STATE_ATTACK_PLAYER); - Trinity::AIRelocationNotifier notifier(*unit); - Cell::VisitWorldObjects(this, notifier, GetVisibilityRange()); - } - } } void Player::UpdateContestedPvP(uint32 diff) @@ -20246,129 +20325,18 @@ void Player::UpdateDuelFlag(time_t currTime) duel->opponent->duel->startTime = currTime; } -Pet* Player::GetPet() const -{ - if (ObjectGuid pet_guid = GetPetGUID()) - { - if (!pet_guid.IsPet()) - return nullptr; - - Pet* pet = ObjectAccessor::GetPet(*this, pet_guid); - - if (!pet) - return nullptr; - - if (IsInWorld()) - return pet; - - // there may be a guardian in this slot - //TC_LOG_ERROR("entities.player", "Player::GetPet: Pet %u does not exist.", GUID_LOPART(pet_guid)); - //const_cast(this)->SetPetGUID(0); - } - - return nullptr; -} - -void Player::RemovePet(Pet* pet, PetSaveMode mode, bool returnreagent) -{ - if (!pet) - pet = GetPet(); - - if (pet) - { - TC_LOG_DEBUG("entities.pet", "Player::RemovePet: Player '%s' (%s), Pet (Entry: %u, Mode: %u, ReturnReagent: %u)", - GetName().c_str(), GetGUID().ToString().c_str(), pet->GetEntry(), mode, returnreagent); - - if (pet->m_removed) - return; - } - - if (returnreagent && (pet || m_temporaryUnsummonedPetNumber) && !InBattleground()) - { - //returning of reagents only for players, so best done here - uint32 spellId = pet ? pet->GetUInt32Value(UNIT_CREATED_BY_SPELL) : m_oldpetspell; - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); - - if (spellInfo) - { - for (uint32 i = 0; i < MAX_SPELL_REAGENTS; ++i) - { - if (spellInfo->Reagent[i] > 0) - { - ItemPosCountVec dest; //for succubus, voidwalker, felhunter and felguard credit soulshard when despawn reason other than death (out of range, logout) - InventoryResult msg = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, spellInfo->Reagent[i], spellInfo->ReagentCount[i]); - if (msg == EQUIP_ERR_OK) - { - Item* item = StoreNewItem(dest, spellInfo->Reagent[i], true); - if (IsInWorld()) - SendNewItem(item, spellInfo->ReagentCount[i], true, false); - } - } - } - } - m_temporaryUnsummonedPetNumber = 0; - } - - if (!pet || pet->GetOwnerOrCreatorGUID() != GetGUID()) - return; - - pet->CombatStop(); - - if (returnreagent) - { - switch (pet->GetEntry()) - { - //warlock pets except imp are removed(?) when logging out - case 1860: - case 1863: - case 417: - case 17252: - mode = PET_SAVE_DISMISS; - break; - } - } - - pet->SavePetToDB(mode); - - SetMinion(pet, false); - - pet->AddObjectToRemoveList(); - pet->m_removed = true; - - if (pet->isControlled()) - { - WorldPacket data(SMSG_PET_SPELLS, 8); - data << uint64(0); - SendDirectMessage(&data); - - if (GetGroup()) - SetGroupUpdateFlag(GROUP_UPDATE_PET); - - if (mode == PET_SAVE_DISMISS && getClass() == CLASS_WARLOCK) - { - if (CreatureDisplayInfoEntry const* creatureDisplay = sCreatureDisplayInfoStore.LookupEntry(pet->GetDisplayId())) - { - WorldPackets::Pet::PetDismissSound dismissSound; - dismissSound.ModelID = creatureDisplay->ModelID; - dismissSound.ModelPosition = pet->GetPosition(); - pet->SendMessageToSet(dismissSound.Write(), false); - } - } - } -} - void Player::AddPetAura(PetAura const* petSpell) { m_petAuras.insert(petSpell); - if (Pet* pet = GetPet()) - pet->CastPetAura(petSpell); + //if (Pet* pet = GetPet()) + // pet->CastPetAura(petSpell); } void Player::RemovePetAura(PetAura const* petSpell) { m_petAuras.erase(petSpell); - if (Pet* pet = GetPet()) - pet->RemoveAurasDueToSpell(petSpell->GetAura(pet->GetEntry())); + //if (Pet* pet = GetPet()) + // pet->RemoveAurasDueToSpell(petSpell->GetAura(pet->GetEntry())); } void Player::StopCastingCharm() @@ -20549,87 +20517,6 @@ void Player::SendOnCancelExpectedVehicleRideAura() const SendDirectMessage(&data); } -bool Player::CanControlPet(uint32 spellId) const -{ - // Hunters and Warlocks cannot control their pets until they learned their spell - if (spellId) - { - switch (spellId) - { - case 93321: // Control Pet - return getClass() == CLASS_HUNTER; - case 93375: // Control Demon - return getClass() == CLASS_WARLOCK; - } - } - else - { - if (getClass() == CLASS_HUNTER && !HasAura(93321)) - return false; - if (getClass() == CLASS_WARLOCK && !HasAura(93375)) - return false; - } - - return true; -} - -void Player::PetSpellInitialize() -{ - // Do not send the pet action bar when Hunters or Warlocks cannot control their pet - if (!CanControlPet()) - return; - - Pet* pet = GetPet(); - - if (!pet) - return; - - CharmInfo* charmInfo = pet->GetCharmInfo(); - - WorldPacket data(SMSG_PET_SPELLS, 8 + 2 + 4 + 4 + 4 * MAX_UNIT_ACTION_BAR_INDEX + 1 + 1); - data << uint64(pet->GetGUID()); - data << uint16(pet->GetCreatureTemplate()->family); // creature family (required for pet talents) - data << uint32(pet->GetDuration()); - data << uint8(pet->GetReactState()); - data << uint8(charmInfo->GetCommandState()); - data << uint16(0); // Flags, mostly unknown - - TC_LOG_DEBUG("entities.pet", "Player::PetspellInitialize: Creating spellgroups for summoned pet"); - - // action bar loop - charmInfo->BuildActionBar(&data); - - size_t spellsCountPos = data.wpos(); - - // spells count - uint8 addlist = 0; - data << uint8(addlist); // placeholder - - if (pet->IsPermanentPetFor(this)) - { - // spells loop - for (PetSpellMap::iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr) - { - if (itr->second.state == PETSPELL_REMOVED) - continue; - - // Do not send this spells, they are used indirectly - if (sSpellMgr->GetSpellInfo(itr->first)->HasAttribute(SPELL_ATTR4_NOT_IN_SPELLBOOK)) - continue; - - data << uint32(MAKE_UNIT_ACTION_BUTTON(itr->first, itr->second.active)); - ++addlist; - } - } - - data.put(spellsCountPos, addlist); - - //Cooldowns - pet->GetSpellHistory()->WritePacket(data); - - SendDirectMessage(&data); -} - void Player::PossessSpellInitialize() { Unit* charm = GetCharmed(); @@ -20711,7 +20598,7 @@ void Player::VehicleSpellInitialize() void Player::CharmSpellInitialize() { - Unit* charm = GetFirstControlled(); + Unit* charm = nullptr; if (!charm) return; @@ -21921,15 +21808,11 @@ void Player::UpdatePvPState(bool onlyFFA) if (!IsFFAPvP()) { SetByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, UNIT_BYTE2_FLAG_FFA_PVP); - for (ControlList::iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr) - (*itr)->SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, UNIT_BYTE2_FLAG_FFA_PVP); } } else if (IsFFAPvP()) { RemoveByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, UNIT_BYTE2_FLAG_FFA_PVP); - for (ControlList::iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr) - (*itr)->RemoveByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, UNIT_BYTE2_FLAG_FFA_PVP); } if (onlyFFA) @@ -21950,8 +21833,6 @@ void Player::UpdatePvPState(bool onlyFFA) void Player::SetPvP(bool state) { Unit::SetPvP(state); - for (ControlList::iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr) - (*itr)->SetPvP(state); } void Player::UpdatePvP(bool state, bool _override) @@ -22421,22 +22302,12 @@ inline void UpdateVisibilityOf_helper(GuidUnorderedSet& s64, Player* target, std template inline void BeforeVisibilityDestroy(T* /*t*/, Player* /*p*/) { } -template<> -inline void BeforeVisibilityDestroy(Creature* t, Player* p) -{ - if (p->GetPetGUID() == t->GetGUID() && t->IsPet()) - t->ToPet()->Remove(PET_SAVE_DISMISS, true); -} - void Player::UpdateVisibilityOf(WorldObject* target) { if (HaveAtClient(target)) { if (!CanSeeOrDetect(target, false, true)) { - if (target->GetTypeId() == TYPEID_UNIT) - BeforeVisibilityDestroy(target->ToCreature(), this); - if (!target->IsDestroyedObject()) target->SendOutOfRangeForPlayer(this); else @@ -22852,8 +22723,8 @@ void Player::SendUpdateToOutOfRangeGroupMembers() m_groupUpdateMask = GROUP_UPDATE_FLAG_NONE; m_auraRaidUpdateMask = 0; - if (Pet* pet = GetPet()) - pet->ResetAuraUpdateMaskForRaid(); + //if (Pet* pet = GetPet()) + // pet->ResetAuraUpdateMaskForRaid(); } void Player::SendTransferAborted(uint32 mapid, TransferAbortReason reason, uint8 arg) const @@ -25489,137 +25360,6 @@ bool Player::LearnTalent(uint32 talentId, uint32 talentRank) void Player::LearnPetTalent(ObjectGuid petGuid, uint32 talentId, uint32 talentRank) { - Pet* pet = GetPet(); - - if (!pet) - return; - - if (petGuid != pet->GetGUID()) - return; - - uint32 CurTalentPoints = pet->GetFreeTalentPoints(); - - if (CurTalentPoints == 0) - return; - - if (talentRank >= MAX_PET_TALENT_RANK) - return; - - TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId); - - if (!talentInfo) - return; - - TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TabID); - - if (!talentTabInfo) - return; - - CreatureTemplate const* ci = pet->GetCreatureTemplate(); - - if (!ci) - return; - - CreatureFamilyEntry const* pet_family = sCreatureFamilyStore.LookupEntry(ci->family); - - if (!pet_family) - return; - - if (pet_family->PetTalentType < 0) // not hunter pet - return; - - // prevent learn talent for different family (cheating) - if (!((1 << pet_family->PetTalentType) & talentTabInfo->CategoryEnumID)) - return; - - // find current max talent rank (0~5) - uint8 curtalent_maxrank = 0; // 0 = not learned any rank - for (int8 rank = MAX_TALENT_RANK-1; rank >= 0; --rank) - { - if (talentInfo->SpellRank[rank] && pet->HasSpell(talentInfo->SpellRank[rank])) - { - curtalent_maxrank = (rank + 1); - break; - } - } - - // we already have same or higher talent rank learned - if (curtalent_maxrank >= (talentRank + 1)) - return; - - // check if we have enough talent points - if (CurTalentPoints < (talentRank - curtalent_maxrank + 1)) - return; - - // Check if it requires another talent - if (talentInfo->PrereqTalent[0] > 0) - { - if (TalentEntry const* depTalentInfo = sTalentStore.LookupEntry(talentInfo->PrereqTalent[0])) - { - bool hasEnoughRank = false; - for (uint8 rank = talentInfo->PrereqRank[0]; rank < MAX_TALENT_RANK; rank++) - { - if (depTalentInfo->SpellRank[rank] != 0) - if (pet->HasSpell(depTalentInfo->SpellRank[rank])) - hasEnoughRank = true; - } - if (!hasEnoughRank) - return; - } - } - - // Find out how many points we have in this field - uint32 spentPoints = 0; - - uint32 tTab = talentInfo->TabID; - if (talentInfo->TierID > 0) - { - uint32 numRows = sTalentStore.GetNumRows(); - for (uint32 i = 0; i < numRows; ++i) // Loop through all talents. - { - // Someday, someone needs to revamp - TalentEntry const* tmpTalent = sTalentStore.LookupEntry(i); - if (tmpTalent) // the way talents are tracked - { - if (tmpTalent->TabID == tTab) - { - for (uint8 rank = 0; rank < MAX_TALENT_RANK; rank++) - { - if (tmpTalent->SpellRank[rank] != 0) - { - if (pet->HasSpell(tmpTalent->SpellRank[rank])) - { - spentPoints += (rank + 1); - } - } - } - } - } - } - } - - // not have required min points spent in talent tree - if (spentPoints < (talentInfo->TierID * MAX_PET_TALENT_RANK)) - return; - - // spell not set in talent.dbc - uint32 spellid = talentInfo->SpellRank[talentRank]; - if (spellid == 0) - { - TC_LOG_ERROR("entities.player", "Talent.dbc contains talent: %u Rank: %u spell id = 0", talentId, talentRank); - return; - } - - // already known - if (pet->HasSpell(spellid)) - return; - - // learn! (other talent ranks will unlearned at learning) - pet->learnSpell(spellid); - TC_LOG_DEBUG("entities.player", "PetTalentID: %u Rank: %u Spell: %u\n", talentId, talentRank, spellid); - - // update free talent points - pet->SetFreeTalentPoints(CurTalentPoints - (talentRank - curtalent_maxrank + 1)); } void Player::AddKnownCurrency(uint32 itemId) @@ -25634,45 +25374,6 @@ void Player::UpdateFallInformationIfNeed(MovementInfo const& minfo, uint16 opcod SetFallInformation(minfo.jump.fallTime, minfo.pos.GetPositionZ()); } -void Player::UnsummonPetTemporaryIfAny() -{ - Pet* pet = GetPet(); - if (!pet) - return; - - if (!m_temporaryUnsummonedPetNumber && pet->isControlled() && !pet->isTemporarySummoned()) - { - m_temporaryUnsummonedPetNumber = pet->GetCharmInfo()->GetPetNumber(); - m_oldpetspell = pet->GetUInt32Value(UNIT_CREATED_BY_SPELL); - } - - RemovePet(pet, PET_SAVE_TEMP_UNSUMMON); -} - -void Player::ResummonPetTemporaryUnSummonedIfAny() -{ - if (!m_temporaryUnsummonedPetNumber) - return; - - // not resummon in not appropriate state - if (IsPetNeedBeTemporaryUnsummoned()) - return; - - if (GetPetGUID()) - return; - - Pet* NewPet = new Pet(this); - if (!NewPet->LoadPetData(this, 0, m_temporaryUnsummonedPetNumber, false)) - delete NewPet; - - m_temporaryUnsummonedPetNumber = 0; -} - -bool Player::IsPetNeedBeTemporaryUnsummoned() const -{ - return !IsInWorld() || !IsAlive() || IsMounted() /*+in flight*/; -} - bool Player::CanSeeSpellClickOn(Creature const* c) const { if (!c->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPELLCLICK)) @@ -25777,6 +25478,7 @@ void Player::BuildPlayerTalentsInfoData(WorldPacket* data) void Player::BuildPetTalentsInfoData(WorldPacket* data) { + /* uint32 unspentTalentPoints = 0; size_t pointsPos = data->wpos(); *data << uint32(unspentTalentPoints); // [PH], unspentTalentPoints @@ -25845,6 +25547,7 @@ void Player::BuildPetTalentsInfoData(WorldPacket* data) break; } + */ } void Player::SendTalentsInfoData(bool pet) @@ -26238,14 +25941,8 @@ void Player::ActivateSpec(uint8 spec) _SaveActions(trans); CharacterDatabase.CommitTransaction(trans); - // TO-DO: We need more research to know what happens with warlock's reagent - if (Pet* pet = GetPet()) - RemovePet(pet, PET_SAVE_DISMISS); - ClearAllReactives(); - UnsummonAllTotems(); ExitVehicle(); - RemoveAllControlled(); // remove limited target auras at other targets AuraList& scAuras = GetSingleCastAuras(); @@ -26261,10 +25958,6 @@ void Player::ActivateSpec(uint8 spec) ++iter; } - /*RemoveAllAurasOnDeath(); - if (GetPet()) - GetPet()->RemoveAllAurasOnDeath();*/ - //RemoveAllAuras(GetGUID(), nullptr, false, true); // removes too many auras //ExitVehicle(); // should be impossible to switch specs from inside a vehicle.. @@ -27088,103 +26781,6 @@ Guild* Player::GetGuild() return guildId ? sGuildMgr->GetGuildById(guildId) : nullptr; } -Pet* Player::SummonPet(uint32 entry, float x, float y, float z, float ang, PetType petType, uint32 duration) -{ - Pet* pet = new Pet(this, petType); - - pet->Relocate(x, y, z, ang); - if (!pet->IsPositionValid()) - { - TC_LOG_ERROR("misc", "Player::SummonPet: Pet (%s, Entry: %d) not summoned. Suggested coordinates aren't valid (X: %f Y: %f)", pet->GetGUID().ToString().c_str(), pet->GetEntry(), pet->GetPositionX(), pet->GetPositionY()); - delete pet; - return nullptr; - } - - bool hasPetData = pet->LoadPetData(this, entry); - - if (!hasPetData && petType == HUNTER_PET) - { - delete pet; - return nullptr; - } - - if (!hasPetData) - { - Map* map = GetMap(); - uint32 pet_number = sObjectMgr->GeneratePetNumber(); - if (!pet->Create(map->GenerateLowGuid(), map, entry, pet_number)) - { - TC_LOG_ERROR("misc", "Player::SummonPet: No such creature entry %u", entry); - delete pet; - return nullptr; - } - - // generate new name for summon pet - std::string new_name = sObjectMgr->GeneratePetName(entry); - if (!new_name.empty()) - pet->SetName(new_name); - - pet->SetCreatorGUID(GetGUID()); - pet->SetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE, GetFaction()); - - pet->SetUInt64Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE); - pet->SetUInt32Value(UNIT_FIELD_BYTES_1, 0); - pet->InitStatsForLevel(getLevel()); - pet->SetReactState(REACT_ASSIST); - - SetMinion(pet, true); - - switch (petType) - { - case SUMMON_PET: - pet->GetCharmInfo()->SetPetNumber(pet_number, true); - pet->SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_CLASS, CLASS_MAGE); - pet->SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, 0); - pet->SetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP, 1000); - pet->SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, uint32(GameTime::GetGameTime())); // cast can't be helped in this case - break; - default: - break; - } - - map->AddToMap(pet->ToCreature()); - - switch (petType) - { - case SUMMON_PET: - pet->InitPetCreateSpells(); - pet->SavePetToDB(PET_SAVE_NEW_PET); - PetSpellInitialize(); - GetSession()->SendPetAdded(pet->GetSlot(), pet->GetCharmInfo()->GetPetNumber(), pet->GetEntry(), pet->getLevel(), pet->GetName()); - break; - default: - break; - } - - // Update all stats after we have applied pet scaling auras to make sure we have all fields initialized properly - pet->UpdateAllStats(); - pet->SetFullHealth(); - pet->SetPower(POWER_MANA, pet->GetMaxPower(POWER_MANA)); - - if (pet->IsHunterPet()) - pet->CastSpell(pet, SPELL_PET_ENERGIZE, true); - else if (pet->IsPetGhoul()) - { - pet->CastSpell(pet, SPELL_PET_RISEN_GHOUL_SPAWN_IN, true); - pet->CastSpell(pet, SPELL_PET_RISEN_GHOUL_SELF_STUN, true); - } - } - - PhasingHandler::InheritPhaseShift(pet, this); - - if (duration > 0) - pet->SetDuration(duration); - - //ObjectAccessor::UpdateObjectVisibility(pet); - - return pet; -} - bool Player::CanUseMastery() const { return HasSpell(MasterySpells[getClass()]); diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 156ba5c4ba6..a42c97f57f1 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -471,7 +471,8 @@ enum PlayerFieldByteFlags { PLAYER_FIELD_BYTE_TRACK_STEALTHED = 0x00000002, PLAYER_FIELD_BYTE_RELEASE_TIMER = 0x00000008, // Display time till auto release spirit - PLAYER_FIELD_BYTE_NO_RELEASE_WINDOW = 0x00000010 // Display no "release spirit" window at all + PLAYER_FIELD_BYTE_NO_RELEASE_WINDOW = 0x00000010, // Display no "release spirit" window at all + PLAYER_FIELD_BYTE_HIDE_PET_BAR = 0x00000020 // Hides the control bar of pets }; // used in PLAYER_FIELD_BYTES2 values @@ -890,27 +891,38 @@ enum PlayerLogXPReason : uint8 LOG_XP_REASON_NO_KILL = 1 }; +enum class PlayerPetDataStatus : uint8 +{ + UpToDate = 0, + New = 1, + Changed = 2, + Deleted = 3 +}; + struct PlayerPetData { - uint32 PetId; - uint32 CreatureId; - uint64 Owner; - uint32 DisplayId; - uint32 Petlevel; - uint32 PetExp; - ReactStates Reactstate; - uint8 Slot; + PlayerPetData() { } + + uint32 PetNumber = 0; + uint32 CreatureId = 0; + uint32 TamedCreatureId = 0; + uint32 DisplayId = 0; + uint32 SavedHealth = 0; + uint32 SavedPower = 0; + uint32 CreatedBySpellId = 0; + uint32 LastSaveTime = 0; + ReactStates ReactState = REACT_ASSIST; + uint8 Slot = 0; + bool HasBeenRenamed = false; + bool IsActive = false; std::string Name; - bool Renamed; - bool Active; - uint32 SavedHealth; - uint32 SavedMana; - std::string Actionbar; - uint32 Timediff; - uint32 SummonSpellId; - PetType Type; + std::string ActionBar; + std::string Talents; + PlayerPetDataStatus Status = PlayerPetDataStatus::New; }; +using PlayerPetDataMap = std::unordered_map>; + class Player; /// Holder for Battleground data @@ -1124,14 +1136,11 @@ class TC_GAME_API Player : public Unit, public GridObject uint32 GetXPRestBonus(uint32 xp); uint32 GetInnTriggerId() const { return inn_triggerId; } - Pet* GetPet() const; - Pet* SummonPet(uint32 entry, float x, float y, float z, float ang, PetType petType, uint32 despwtime); - void RemovePet(Pet* pet, PetSaveMode mode, bool returnreagent = false); - // pet auras std::unordered_set m_petAuras; void AddPetAura(PetAura const* petSpell); void RemovePetAura(PetAura const* petSpell); + Pet* GetPet() { return nullptr; } /// Handles said message in regular chat based on declared language and in config pre-defined Range. void Say(std::string const& text, Language language, WorldObject const* = nullptr) override; @@ -1303,8 +1312,6 @@ class TC_GAME_API Player : public Unit, public GridObject void RemoveItemDurations(Item* item); void SendItemDurations(); void LoadCorpse(PreparedQueryResult result); - void LoadPet(); - void LoadPetsFromDB(PreparedQueryResult result); bool AddItem(uint32 itemId, uint32 count); @@ -1556,9 +1563,9 @@ class TC_GAME_API Player : public Unit, public GridObject void AddMItem(Item* it); bool RemoveMItem(uint32 id); + public: void SendOnCancelExpectedVehicleRideAura() const; - bool CanControlPet(uint32 spellId = 0) const; - void PetSpellInitialize(); + void CharmSpellInitialize(); void PossessSpellInitialize(); void VehicleSpellInitialize(); @@ -2212,13 +2219,6 @@ class TC_GAME_API Player : public Unit, public GridObject bool HasValidLFGLeavePoint(uint32 mapid); void SetLFGLeavePoint(); - // Temporarily removed pet cache - uint32 GetTemporaryUnsummonedPetNumber() const { return m_temporaryUnsummonedPetNumber; } - void SetTemporaryUnsummonedPetNumber(uint32 petnumber) { m_temporaryUnsummonedPetNumber = petnumber; } - void UnsummonPetTemporaryIfAny(); - void ResummonPetTemporaryUnSummonedIfAny(); - bool IsPetNeedBeTemporaryUnsummoned() const; - void SendCinematicStart(uint32 cinematicId); void SendMovieStart(uint32 movieId); @@ -2253,10 +2253,6 @@ class TC_GAME_API Player : public Unit, public GridObject bool CheckInstanceCount(uint32 instanceId) const; void AddInstanceEnterTime(uint32 instanceId, time_t enterTime); - // last used pet number (for BG's) - uint32 GetLastPetNumber() const { return m_lastpetnumber; } - void SetLastPetNumber(uint32 petnumber) { m_lastpetnumber = petnumber; } - /*********************************************************/ /*** GROUP SYSTEM ***/ /*********************************************************/ @@ -2378,14 +2374,34 @@ class TC_GAME_API Player : public Unit, public GridObject VoidStorageItem* GetVoidStorageItem(uint64 id, uint8& slot) const; // Pets - PlayerPetData* GetPlayerPetDataById(uint32 petId); - PlayerPetData* GetPlayerPetDataBySlot(uint8 slot); - PlayerPetData* GetPlayerPetDataByCreatureId(uint32 creatureId); - PlayerPetData* GetPlayerPetDataCurrent(); - Optional GetFirstUnusedActivePetSlot(); - Optional GetFirstUnusedPetSlot(); - void DeleteFromPlayerPetDataStore(uint32 petNumber); - void AddToPlayerPetDataStore(PlayerPetData* playerPetData); + // Sends the pet spells message packet to the player, containing all action bar and spell information about the pet + void SendPetSpellsMessage(NewPet* pet, bool remove = false); + // Enables pet controls for classes who have to learn their control pet spell first + void SetCanControlClassPets(); + // Returns true when the player is allowed to perform pet actions with their pet + bool CanControlClassPets() const; + // Returns the PlayerPetData of a pet by slot and creatureId. Hunter pets are stored under creatureId = 0, everything else has a valid creatureId. + PlayerPetData* GetPlayerPetData(uint8 slot, uint32 creatureId) const; + // Returns the PlayerPetData of a pet by pet number. This operation is rather expensive so please consider using Player::GetPlayerPetData(uint8 slot, uint32 creatureId) if possible. + PlayerPetData* GetPlayerPetDataByPetNumber(uint32 petNumber); + // Creates a new PlayerPetData entry for the given creatureId and slot. + PlayerPetData* CreatePlayerPetData(uint8 slot, uint32 creatureId, Optional tamedCreatureId = {}); + // Returns a available active Hunter Pet slot if there is any. Used for taming and creating new pets. If checkForUnlocked is set to true, empty slots will skipped when the slot is not unlocked via Call Pet spell. + Optional GetUnusedActivePetSlot(bool checkForUnlocked = true); + // Returns a reference to the entire player pet data map which holds data for all pets the player has + PlayerPetDataMap const& GetPlayerPetDataMap() const { return _playerPetDataMap; } + // Unsummons the currently active pet and removes the player pet data from its container. The database data will be erased on the next save cycle. + void AbandonPet(); + // Sets the pet data key for the class pet that will be un- and resummoned by several actions + void SetActiveClassPetDataKey(Optional const& key) { _activeClassPetDataKey = key; } + // Summons the temporarily dismissed pet at the player's location + void ResummonActiveClassPet(); + // Moves the data of a pet to another slot and swaps it if another pet occupies this slot. This method is ony meant to be used by stabe master mechanics for Hunter pets. + void SetPetSlot(uint32 petNumber, uint8 destSlot); + // Returns a reference to the class pet player pet data key + Optional const& GetActiveClassPetDataKey() const { return _activeClassPetDataKey; } + private: + Optional _activeClassPetDataKey; protected: // Gamemaster whisper whitelist @@ -2475,6 +2491,7 @@ class TC_GAME_API Player : public Unit, public GridObject void _LoadCurrency(PreparedQueryResult result); void _LoadCUFProfiles(PreparedQueryResult result); void _LoadLFGRewardStatus(PreparedQueryResult result); + void _LoadPets(PreparedQueryResult result); /*********************************************************/ /*** SAVE SYSTEM ***/ @@ -2501,6 +2518,7 @@ class TC_GAME_API Player : public Unit, public GridObject void _SaveCurrency(CharacterDatabaseTransaction& trans); void _SaveCUFProfiles(CharacterDatabaseTransaction& trans); void _SaveLFGRewardStatus(CharacterDatabaseTransaction& trans); + void _SavePets(CharacterDatabaseTransaction& trans); /*********************************************************/ /*** ENVIRONMENTAL SYSTEM ***/ @@ -2647,9 +2665,6 @@ class TC_GAME_API Player : public Unit, public GridObject uint64 m_auraRaidUpdateMask; bool m_bPassOnGroupLoot; - // last used pet number (for BG's) - uint32 m_lastpetnumber; - // Player summoning time_t m_summon_expire; WorldLocation m_summon_location; @@ -2729,10 +2744,6 @@ class TC_GAME_API Player : public Unit, public GridObject bool m_bCanDelayTeleport; bool m_bHasDelayedTeleport; - // Temporary removed pet cache - uint32 m_temporaryUnsummonedPetNumber; - uint32 m_oldpetspell; - std::unique_ptr> m_achievementMgr; std::unique_ptr m_reputationMgr; @@ -2755,10 +2766,12 @@ class TC_GAME_API Player : public Unit, public GridObject std::unique_ptr _archaeology; - std::vector PlayerPetDataStore; + PlayerPetDataMap _playerPetDataMap; + std::unordered_set _deletedPlayerPetDataSet; - TimeTrackerSmall m_petScalingSynchTimer; TimeTrackerSmall m_groupUpdateTimer; + + bool _canControlClassPets; }; TC_GAME_API void AddItemsSetItem(Player* player, Item* item); diff --git a/src/server/game/Entities/Totem/Totem.cpp b/src/server/game/Entities/Totem/Totem.cpp index 0913356517e..75ef6fc399a 100644 --- a/src/server/game/Entities/Totem/Totem.cpp +++ b/src/server/game/Entities/Totem/Totem.cpp @@ -104,16 +104,6 @@ void Totem::UnSummon(uint32 msTime) CombatStop(); RemoveAurasDueToSpell(GetSpell(), GetGUID()); - // clear owner's totem slot - for (uint8 i = SUMMON_SLOT_TOTEM_FIRE; i < MAX_TOTEM_SLOT; ++i) - { - if (GetOwner()->m_SummonSlot[i] == GetGUID()) - { - GetOwner()->m_SummonSlot[i].Clear(); - break; - } - } - GetOwner()->RemoveAurasDueToSpell(GetSpell(), GetGUID()); // remove aura all party members too @@ -135,9 +125,6 @@ void Totem::UnSummon(uint32 msTime) } } - // Despawn elementals - RemoveAllControlled(); - // any totem unsummon look like as totem kill, req. for proper animation if (IsAlive()) setDeathState(DEAD); diff --git a/src/server/game/Entities/Unit/StatSystem.cpp b/src/server/game/Entities/Unit/StatSystem.cpp index 35763d1978d..668210fea6d 100644 --- a/src/server/game/Entities/Unit/StatSystem.cpp +++ b/src/server/game/Entities/Unit/StatSystem.cpp @@ -18,6 +18,7 @@ #include "Unit.h" #include "Creature.h" #include "DBCStores.h" +#include "NewGuardian.h" #include "Item.h" #include "Pet.h" #include "Player.h" @@ -1286,3 +1287,223 @@ void Guardian::SetBonusDamage(int32 damage) if (GetOwner()->GetTypeId() == TYPEID_PLAYER) GetOwner()->SetUInt32Value(PLAYER_PET_SPELL_POWER, damage); } + +bool NewGuardian::UpdateAllStats() +{ + // Guardians without real level data use regular stat updates + if (!IsUsingRealStats()) + return Creature::UpdateAllStats(); + + UpdateMaxHealth(); + + for (uint8 i = STAT_STRENGTH; i < MAX_STATS; ++i) + UpdateStats(Stats(i)); + + for (uint8 i = POWER_MANA; i < MAX_POWERS; ++i) + UpdateMaxPower(Powers(i)); + + UpdateAllResistances(); + + return true; +} + +bool NewGuardian::UpdateStats(Stats stat) +{ + if (stat >= MAX_STATS) + return false; + + // Guardians without real level data use regular stat updates + if (!IsUsingRealStats()) + return Creature::UpdateStats(stat); + + float value = GetTotalStatValue(stat); + + SetStat(stat, int32(value)); + UpdateStatBuffMod(stat); + + switch (stat) + { + case STAT_STRENGTH: + UpdateAttackPowerAndDamage(); + break; + case STAT_AGILITY: + UpdateArmor(); + break; + case STAT_STAMINA: + UpdateMaxHealth(); + break; + case STAT_INTELLECT: + UpdateMaxPower(POWER_MANA); + break; + case STAT_SPIRIT: + break; + default: + break; + } + + return true; +} + +void NewGuardian::UpdateResistances(uint32 school) +{ + if (!IsUsingRealStats()) + { + Creature::UpdateResistances(school); + return; + } + + if (school > SPELL_SCHOOL_NORMAL) + { + float value = GetTotalAuraModValue(UnitMods(UNIT_MOD_RESISTANCE_START + school)); + SetResistance(SpellSchools(school), int32(value)); + } + else + UpdateArmor(); +} + +void NewGuardian::UpdateArmor() +{ + if (!IsUsingRealStats()) + { + Creature::UpdateArmor(); + return; + } + + float value = 0.0f; + UnitMods unitMod = UNIT_MOD_ARMOR; + + value = GetFlatModifierValue(unitMod, BASE_VALUE); + value *= GetPctModifierValue(unitMod, BASE_PCT); + value += GetFlatModifierValue(unitMod, TOTAL_VALUE); + value *= GetPctModifierValue(unitMod, TOTAL_PCT); + + SetArmor(int32(value)); +} + +void NewGuardian::UpdateMaxHealth() +{ + if (!IsUsingRealStats()) + { + Creature::UpdateMaxHealth(); + return; + } + + UnitMods unitMod = UNIT_MOD_HEALTH; + float stamina = GetStat(STAT_STAMINA) - GetCreateStat(STAT_STAMINA); + float multiplicator = 10.0f; + + float value = GetFlatModifierValue(unitMod, BASE_VALUE) + GetCreateHealth(); + value *= GetPctModifierValue(unitMod, BASE_PCT); + value += GetFlatModifierValue(unitMod, TOTAL_VALUE) + stamina * multiplicator; + value *= GetPctModifierValue(unitMod, TOTAL_PCT); + + SetMaxHealth((uint32)value); +} + +void NewGuardian::UpdateMaxPower(Powers power) +{ + if (GetPowerIndex(power) == MAX_POWERS) + return; + + if (!IsUsingRealStats()) + { + Creature::UpdateMaxPower(power); + return; + } + + UnitMods unitMod = UnitMods(UNIT_MOD_POWER_START + AsUnderlyingType(power)); + + float addValue = (power == POWER_MANA) ? GetStat(STAT_INTELLECT) - GetCreateStat(STAT_INTELLECT) : 0.0f; + float multiplicator = 15.0f; + + float value = GetFlatModifierValue(unitMod, BASE_VALUE) + GetCreatePowers(power); + value *= GetPctModifierValue(unitMod, BASE_PCT); + value += GetFlatModifierValue(unitMod, TOTAL_VALUE) + addValue * multiplicator; + value *= GetPctModifierValue(unitMod, TOTAL_PCT); + + SetMaxPower(power, int32(value)); +} + +void NewGuardian::UpdateAttackPowerAndDamage(bool ranged /*= false*/) +{ + if (ranged) + return; + + if (!IsUsingRealStats()) + { + Creature::UpdateAttackPowerAndDamage(ranged); + return; + } + + float ap_per_strength = 2.0f; + float val = GetStat(STAT_STRENGTH) - 20.0f; + + val *= ap_per_strength; + + UnitMods unitMod = UNIT_MOD_ATTACK_POWER; + + SetStatFlatModifier(UNIT_MOD_ATTACK_POWER, BASE_VALUE, val); + + // in BASE_VALUE of UNIT_MOD_ATTACK_POWER for creatures we store data of meleeattackpower field in DB + float base_attPower = GetFlatModifierValue(unitMod, BASE_VALUE) * GetPctModifierValue(unitMod, BASE_PCT); + float attPowerMod = GetFlatModifierValue(unitMod, TOTAL_VALUE); + float attPowerMultiplier = GetPctModifierValue(unitMod, TOTAL_PCT) - 1.0f; + + // UNIT_FIELD_(RANGED)_ATTACK_POWER field + SetInt32Value(UNIT_FIELD_ATTACK_POWER, int32(base_attPower)); + // UNIT_FIELD_(RANGED)_ATTACK_POWER_MOD_POS field + SetInt32Value(UNIT_FIELD_ATTACK_POWER_MOD_POS, int32(attPowerMod > 0.f ? attPowerMod : 0.f)); + // UNIT_FIELD_(RANGED)_ATTACK_POWER_MOD_NEG field + SetInt32Value(UNIT_FIELD_ATTACK_POWER_MOD_NEG, int32(attPowerMod < 0.f ? -attPowerMod : 0.f)); + // UNIT_FIELD_(RANGED)_ATTACK_POWER_MULTIPLIER field + SetFloatValue(UNIT_FIELD_ATTACK_POWER_MULTIPLIER, attPowerMultiplier); + + // automatically update weapon damage after attack power modification + UpdateDamagePhysical(BASE_ATTACK); +} + +void NewGuardian::UpdateDamagePhysical(WeaponAttackType attType) +{ + if (attType > BASE_ATTACK) + return; + + if (!IsUsingRealStats()) + { + Creature::UpdateDamagePhysical(attType); + return; + } + + UnitMods unitMod = UNIT_MOD_DAMAGE_MAINHAND; + + float att_speed = float(GetAttackTime(BASE_ATTACK)) / 1000.0f; + + float base_value = GetFlatModifierValue(unitMod, BASE_VALUE) + GetTotalAttackPowerValue(attType) / 14.0f * att_speed; + float base_pct = GetPctModifierValue(unitMod, BASE_PCT); + float total_value = GetFlatModifierValue(unitMod, TOTAL_VALUE); + float total_pct = GetPctModifierValue(unitMod, TOTAL_PCT); + + float weapon_mindamage = GetWeaponDamageRange(BASE_ATTACK, MINDAMAGE); + float weapon_maxdamage = GetWeaponDamageRange(BASE_ATTACK, MAXDAMAGE); + + float mindamage = ((base_value + weapon_mindamage) * base_pct + total_value) * total_pct; + float maxdamage = ((base_value + weapon_maxdamage) * base_pct + total_value) * total_pct; + + /// @todo: remove this + Unit::AuraEffectList const& mDummy = GetAuraEffectsByType(SPELL_AURA_MOD_ATTACKSPEED); + for (Unit::AuraEffectList::const_iterator itr = mDummy.begin(); itr != mDummy.end(); ++itr) + { + switch ((*itr)->GetSpellInfo()->Id) + { + case 61682: + case 61683: + AddPct(mindamage, -(*itr)->GetAmount()); + AddPct(maxdamage, -(*itr)->GetAmount()); + break; + default: + break; + } + } + + SetStatFloatValue(UNIT_FIELD_MINDAMAGE, mindamage); + SetStatFloatValue(UNIT_FIELD_MAXDAMAGE, maxdamage); +} diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index ae0da6495ea..003da5d55c9 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -59,6 +59,7 @@ #include "Opcodes.h" #include "OutdoorPvP.h" #include "PassiveAI.h" +#include "NewPet.h" #include "Pet.h" #include "PetPackets.h" #include "PetAI.h" @@ -77,6 +78,7 @@ #include "SpellMgr.h" #include "SpellPackets.h" #include "TemporarySummon.h" +#include "NewTemporarySummon.h" #include "Totem.h" #include "Transport.h" #include "UnitAI.h" @@ -397,7 +399,6 @@ Unit::~Unit() ASSERT(!m_attacking); ASSERT(m_attackers.empty()); ASSERT(m_sharedVision.empty()); - ASSERT(m_Controlled.empty()); ASSERT(m_appliedAuras.empty()); ASSERT(m_ownedAuras.empty()); ASSERT(m_removedAuras.empty()); @@ -754,19 +755,8 @@ bool Unit::HasBreakableByDamageCrowdControlAura(Unit* excludeCasterChannel) cons sScriptMgr->OnDamage(attacker, victim, damage); if (victim->GetTypeId() == TYPEID_PLAYER) - { - // Signal to pets that their owner was attacked - except when DOT. - if (attacker != victim && damagetype != DOT) - { - for (Unit* controlled : victim->m_Controlled) - if (Creature* cControlled = controlled->ToCreature()) - if (CreatureAI* controlledAI = cControlled->AI()) - controlledAI->OwnerAttackedBy(attacker); - } - if (victim->ToPlayer()->GetCommandStatus(CHEAT_GOD)) return 0; - } if (damagetype != NODAMAGE) { @@ -5192,6 +5182,7 @@ void Unit::SetPowerType(Powers new_powertype) if (ToPlayer()->GetGroup()) ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_POWER_TYPE); } + /* else if (Pet* pet = ToCreature()->ToPet()) { if (pet->isControlled()) @@ -5200,7 +5191,7 @@ void Unit::SetPowerType(Powers new_powertype) if (owner && (owner->GetTypeId() == TYPEID_PLAYER) && owner->ToPlayer()->GetGroup()) owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_POWER_TYPE); } - } + }*/ // Update max power UpdateMaxPower(new_powertype); @@ -5214,9 +5205,6 @@ void Unit::SetPowerType(Powers new_powertype) case POWER_RAGE: // Reset to zero SetPower(POWER_RAGE, 0); break; - case POWER_FOCUS: // Make it full - SetFullPower(new_powertype); - break; default: break; } @@ -5255,13 +5243,6 @@ void Unit::UpdateDisplayPower() else if (getClass() == CLASS_ROGUE) displayPower = POWER_ENERGY; } - else if (Pet* pet = ToPet()) - { - if (pet->getPetType() == HUNTER_PET) // Hunter pets have focus - displayPower = POWER_FOCUS; - else if (pet->IsPetGhoul() || pet->IsRisenAlly()) // DK pets have energy - displayPower = POWER_ENERGY; - } } break; } @@ -5411,16 +5392,6 @@ bool Unit::Attack(Unit* victim, bool meleeAttack) if (meleeAttack) SendMeleeAttackStart(victim); - // Let the pet know we've started attacking someting. Handles melee attacks only - // Spells such as auto-shot and others handled in WorldSession::HandleCastSpellOpcode - if (GetTypeId() == TYPEID_PLAYER) - { - for (Unit* controlled : m_Controlled) - if (Creature* cControlled = controlled->ToCreature()) - if (CreatureAI* controlledAI = cControlled->AI()) - controlledAI->OwnerAttacked(victim); - } - return true; } @@ -5490,9 +5461,6 @@ void Unit::CombatStop(bool includingCast, bool mutualPvP) void Unit::CombatStopWithPets(bool includingCast) { CombatStop(includingCast); - - for (Unit* minion : m_Controlled) - minion->CombatStop(includingCast); } bool Unit::isAttackingPlayer() const @@ -5500,16 +5468,6 @@ bool Unit::isAttackingPlayer() const if (HasUnitState(UNIT_STATE_ATTACK_PLAYER)) return true; - for (ControlList::const_iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr) - if ((*itr)->isAttackingPlayer()) - return true; - - for (uint8 i = 0; i < MAX_SUMMON_SLOT; ++i) - if (m_SummonSlot[i]) - if (Creature* summon = GetMap()->GetCreature(m_SummonSlot[i])) - if (summon->isAttackingPlayer()) - return true; - return false; } @@ -5547,6 +5505,7 @@ void Unit::ModifyAuraState(AuraStateType flag, bool apply) CastSpell(this, itr->first, true); } } + /* else if (Pet* pet = ToCreature()->ToPet()) { for (PetSpellMap::const_iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr) @@ -5560,6 +5519,7 @@ void Unit::ModifyAuraState(AuraStateType flag, bool apply) CastSpell(this, itr->first, true); } } + */ } } else @@ -5619,29 +5579,35 @@ bool Unit::HasAuraState(AuraStateType flag, SpellInfo const* spellProto, Unit co return HasFlag(UNIT_FIELD_AURASTATE, 1 << (flag - 1)); } -void Unit::SetOwnerGUID(ObjectGuid owner) +void Unit::SetActivelyControlledSummon(NewPet* pet, bool apply) { - if (GetOwnerGUID() == owner) + if (apply) + { + if (GetSummonGUID() == pet->GetGUID()) + return; + } + else if (GetSummonGUID() != pet->GetGUID()) return; - SetGuidValue(UNIT_FIELD_SUMMONEDBY, owner); - if (!owner) - return; + SetSummonGUID(apply ? pet->GetGUID() : ObjectGuid::Empty); - // Update owner dependent fields - Player* player = ObjectAccessor::GetPlayer(*this, owner); - if (!player || !player->HaveAtClient(this)) // if player cannot see this unit yet, he will receive needed data with create object - return; + if (Player* player = ToPlayer()) + { + if (apply) + pet->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + else + pet->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); - SetFieldNotifyFlag(UF_FLAG_OWNER); + bool canControlPet = true; + if (pet->IsClassPet() && !player->CanControlClassPets()) + canControlPet = false; - UpdateData udata(GetMapId()); - WorldPacket packet; - BuildValuesUpdateBlockForPlayer(&udata, player); - udata.BuildPacket(&packet); - player->SendDirectMessage(&packet); + player->ApplyModByteFlag(PLAYER_FIELD_BYTES, PLAYER_FIELD_BYTES_OFFSET_FLAGS, PLAYER_FIELD_BYTE_HIDE_PET_BAR, (apply && !canControlPet)); + player->SendPetSpellsMessage(pet, !apply); - RemoveFieldNotifyFlag(UF_FLAG_OWNER); + if (pet->IsClassPet() && apply) + player->SetActiveClassPetDataKey(pet->GetPlayerPetDataKey()); + } } Player* Unit::GetControllingPlayer() const @@ -5656,196 +5622,6 @@ Player* Unit::GetControllingPlayer() const return const_cast(ToPlayer()); } -Minion* Unit::GetFirstMinion() const -{ - if (ObjectGuid pet_guid = GetMinionGUID()) - { - if (Creature* pet = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, pet_guid)) - if (pet->HasUnitTypeMask(UNIT_MASK_MINION)) - return (Minion*)pet; - - TC_LOG_ERROR("entities.unit", "Unit::GetFirstMinion: Minion %s not exist.", pet_guid.ToString().c_str()); - const_cast(this)->SetMinionGUID(ObjectGuid::Empty); - } - - return nullptr; -} - -Guardian* Unit::GetGuardianPet() const -{ - if (ObjectGuid pet_guid = GetPetGUID()) - { - if (Creature* pet = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, pet_guid)) - if (pet->HasUnitTypeMask(UNIT_MASK_GUARDIAN)) - return (Guardian*)pet; - - TC_LOG_FATAL("entities.unit", "Unit::GetGuardianPet: Guardian %s not exist.", pet_guid.ToString().c_str()); - const_cast(this)->SetPetGUID(ObjectGuid::Empty); - } - - return nullptr; -} - -void Unit::SetMinion(Minion* minion, bool apply) -{ - if (!minion) - { - TC_LOG_ERROR("entities.unit", "Unit::SetMinion: Unit %s tried to reference a non existing minion", GetGUID().ToString().c_str()); - return; - } - - TC_LOG_DEBUG("entities.unit", "SetMinion %u for %u, apply %u", minion->GetEntry(), GetEntry(), apply); - - if (apply) - { - if (minion->GetOwnerGUID()) - { - TC_LOG_FATAL("entities.unit", "SetMinion: Minion %u is not the minion of owner %u", minion->GetEntry(), GetEntry()); - return; - } - - minion->SetOwnerGUID(GetGUID()); - - m_Controlled.insert(minion); - - if (GetTypeId() == TYPEID_PLAYER) - { - minion->m_ControlledByPlayer = true; - minion->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); - } - - // Can only have one pet. If a new one is summoned, dismiss the old one. - if (minion->IsGuardianPet()) - { - if (Guardian* oldPet = GetGuardianPet()) - { - if (oldPet != minion && (oldPet->IsPet() || minion->IsPet() || oldPet->GetEntry() != minion->GetEntry())) - { - // remove existing minion pet - if (oldPet->IsPet()) - ((Pet*)oldPet)->Remove(PET_SAVE_DISMISS); - else - oldPet->UnSummon(); - SetPetGUID(minion->GetGUID()); - SetMinionGUID(ObjectGuid::Empty); - } - } - else - { - SetPetGUID(minion->GetGUID()); - SetMinionGUID(ObjectGuid::Empty); - } - } - - if (minion->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN)) - AddGuidValue(UNIT_FIELD_SUMMON, minion->GetGUID()); - - if (minion->m_Properties && SummonTitle(minion->m_Properties->Title) == SummonTitle::Companion) - SetCritterGUID(minion->GetGUID()); - - // PvP, FFAPvP - minion->SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, GetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG)); - - // Send infinity cooldown - client does that automatically but after relog cooldown needs to be set again - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(minion->GetUInt32Value(UNIT_CREATED_BY_SPELL)); - - if (spellInfo && spellInfo->IsCooldownStartedOnEvent()) - GetSpellHistory()->StartCooldown(spellInfo, 0, nullptr, true); - } - else - { - if (minion->GetOwnerGUID() != GetGUID()) - { - TC_LOG_FATAL("entities.unit", "SetMinion: Minion %u is not the minion of owner %u", minion->GetEntry(), GetEntry()); - return; - } - - if (m_Controlled.find(minion) != m_Controlled.end()) - m_Controlled.erase(minion); - - if (minion->m_Properties && SummonTitle(minion->m_Properties->Title) == SummonTitle::Companion) - if (GetCritterGUID() == minion->GetGUID()) - SetCritterGUID(ObjectGuid::Empty); - - if (minion->IsGuardianPet()) - { - if (GetPetGUID() == minion->GetGUID()) - SetPetGUID(ObjectGuid::Empty); - } - - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(minion->GetUInt32Value(UNIT_CREATED_BY_SPELL)); - // Remove infinity cooldown - if (spellInfo && (spellInfo->IsCooldownStartedOnEvent())) - GetSpellHistory()->SendCooldownEvent(spellInfo); - - //if (minion->HasUnitTypeMask(UNIT_MASK_GUARDIAN)) - { - if (RemoveGuidValue(UNIT_FIELD_SUMMON, minion->GetGUID())) - { - // Check if there is another minion - for (Unit const* controlled : m_Controlled) - { - // do not use this check, creature do not have charm guid - //if (GetCharmedGUID() == (*itr)->GetGUID()) - if (GetGUID() == controlled->GetCharmerGUID()) - continue; - - //ASSERT((*itr)->GetOwnerGUID() == GetGUID()); - if (controlled->GetOwnerOrCreatorGUID() != GetGUID()) - { - OutDebugInfo(); - controlled->OutDebugInfo(); - ABORT(); - } - ASSERT(controlled->IsCreature()); - - if (!controlled->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN)) - continue; - - if (AddGuidValue(UNIT_FIELD_SUMMON, controlled->GetGUID())) - { - // show another pet bar if there is no charm bar - if (IsPlayer() && !GetCharmedGUID()) - { - if (controlled->IsPet()) - ToPlayer()->PetSpellInitialize(); - else - ToPlayer()->CharmSpellInitialize(); - } - } - break; - } - } - } - } - UpdatePetCombatState(); -} - -void Unit::GetAllMinionsByEntry(std::list& Minions, uint32 entry) -{ - for (Unit::ControlList::iterator itr = m_Controlled.begin(); itr != m_Controlled.end();) - { - Unit* unit = *itr; - ++itr; - if (unit->GetEntry() == entry && unit->GetTypeId() == TYPEID_UNIT - && unit->IsSummon()) // minion, actually - Minions.push_back(unit->ToCreature()); - } -} - -void Unit::RemoveAllMinionsByEntry(uint32 entry) -{ - for (Unit::ControlList::iterator itr = m_Controlled.begin(); itr != m_Controlled.end();) - { - Unit* unit = *itr; - ++itr; - if (unit->GetEntry() == entry && unit->GetTypeId() == TYPEID_UNIT - && unit->IsSummon()) // minion, actually - unit->ToTempSummon()->UnSummon(); - // i think this is safe because i have never heard that a despawned minion will trigger a same minion - } -} - void Unit::SetCharm(Unit* charm, bool apply) { if (apply) @@ -5874,7 +5650,7 @@ void Unit::SetCharm(Unit* charm, bool apply) if (_isWalkingBeforeCharm) charm->SetWalk(false); - m_Controlled.insert(charm); + //m_Controlled.insert(charm); } else { @@ -5917,7 +5693,7 @@ void Unit::SetCharm(Unit* charm, bool apply) || !charm->ToCreature()->HasUnitTypeMask(UNIT_MASK_MINION) || charm->GetOwnerOrCreatorGUID() != GetGUID()) { - m_Controlled.erase(charm); + //m_Controlled.erase(charm); } } UpdatePetCombatState(); @@ -6001,44 +5777,6 @@ Unit* Unit::GetMeleeHitRedirectTarget(Unit* victim, SpellInfo const* spellInfo) return victim; } -Unit* Unit::GetFirstControlled() const -{ - // Sequence: charmed, pet, other guardians - Unit* unit = GetCharmed(); - if (!unit) - if (ObjectGuid guid = GetMinionGUID()) - unit = ObjectAccessor::GetUnit(*this, guid); - - return unit; -} - -void Unit::RemoveAllControlled() -{ - // possessed pet and vehicle - if (GetTypeId() == TYPEID_PLAYER) - ToPlayer()->StopCastingCharm(); - - while (!m_Controlled.empty()) - { - Unit* target = *m_Controlled.begin(); - m_Controlled.erase(m_Controlled.begin()); - if (target->GetCharmerGUID() == GetGUID()) - target->RemoveCharmAuras(); - else if (target->GetOwnerOrCreatorGUID() == GetGUID() && target->IsSummon()) - target->ToTempSummon()->UnSummon(); - else - TC_LOG_ERROR("entities.unit", "Unit %u is trying to release unit %u which is neither charmed nor owned by it", GetEntry(), target->GetEntry()); - } - if (GetPetGUID()) - TC_LOG_FATAL("entities.unit", "Unit %u is not able to release its pet %s", GetEntry(), GetPetGUID().ToString().c_str()); - if (GetMinionGUID()) - TC_LOG_FATAL("entities.unit", "Unit %u is not able to release its minion %s", GetEntry(), GetMinionGUID().ToString().c_str()); - if (GetCharmedGUID()) - TC_LOG_FATAL("entities.unit", "Unit %u is not able to release its charm %s", GetEntry(), GetCharmedGUID().ToString().c_str()); - if (!IsPet()) // pets don't use the flag for this - RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT); // m_controlled is now empty, so we know none of our minions are in combat -} - bool Unit::isPossessedByPlayer() const { return HasUnitState(UNIT_STATE_POSSESSED) && GetCharmerGUID().IsPlayer(); @@ -6065,56 +5803,6 @@ Unit* Unit::GetCharmerOrSelf() const return const_cast(this); } -Unit* Unit::GetNextRandomRaidMemberOrPet(float radius) -{ - Player* player = nullptr; - if (GetTypeId() == TYPEID_PLAYER) - player = ToPlayer(); - // Should we enable this also for charmed units? - else if (GetTypeId() == TYPEID_UNIT && IsPet()) - player = GetOwner()->ToPlayer(); - - if (!player) - return nullptr; - Group* group = player->GetGroup(); - // When there is no group check pet presence - if (!group) - { - // We are pet now, return owner - if (player != this) - return IsWithinDistInMap(player, radius) ? player : nullptr; - Unit* pet = GetGuardianPet(); - // No pet, no group, nothing to return - if (!pet) - return nullptr; - // We are owner now, return pet - return IsWithinDistInMap(pet, radius) ? pet : nullptr; - } - - std::vector nearMembers; - // reserve place for players and pets because resizing vector every unit push is unefficient (vector is reallocated then) - nearMembers.reserve(group->GetMembersCount() * 2); - - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - if (Player* Target = itr->GetSource()) - { - // IsHostileTo check duel and controlled by enemy - if (Target != this && IsWithinDistInMap(Target, radius) && Target->IsAlive() && !IsHostileTo(Target)) - nearMembers.push_back(Target); - - // Push player's pet to vector - if (Unit* pet = Target->GetGuardianPet()) - if (pet != this && IsWithinDistInMap(pet, radius) && pet->IsAlive() && !IsHostileTo(pet)) - nearMembers.push_back(pet); - } - - if (nearMembers.empty()) - return nullptr; - - uint32 randTarget = urand(0, nearMembers.size()-1); - return nearMembers[randTarget]; -} - // only called in Player::SetSeer // so move it to Player? void Unit::AddPlayerToVision(Player* player) @@ -6150,19 +5838,6 @@ void Unit::RemoveCharmAuras() RemoveAurasByType(SPELL_AURA_AOE_CHARM); } -void Unit::UnsummonAllTotems() -{ - for (uint8 i = 0; i < MAX_SUMMON_SLOT; ++i) - { - if (!m_SummonSlot[i]) - continue; - - if (Creature* OldTotem = GetMap()->GetCreature(m_SummonSlot[i])) - if (OldTotem->IsSummon()) - OldTotem->ToTempSummon()->UnSummon(); - } -} - void Unit::SendHealSpellLog(HealInfo& healInfo, bool critical /*= false*/) { // we guess size @@ -7841,18 +7516,6 @@ void Unit::Mount(uint32 mount, uint32 VehicleId, uint32 creatureEntry) } } - // unsummon pet - Pet* pet = player->GetPet(); - if (pet) - { - Battleground* bg = ToPlayer()->GetBattleground(); - // don't unsummon pet in arena but SetFlag UNIT_FLAG_STUNNED to disable pet's interface - if (bg && bg->isArena()) - pet->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED); - else - player->UnsummonPetTemporaryIfAny(); - } - // if we have charmed npc, stun him also (everywhere) if (Unit* charm = player->GetCharmed()) if (charm->GetTypeId() == TYPEID_UNIT) @@ -7891,14 +7554,6 @@ void Unit::Dismount() // (it could probably happen when logging in after a previous crash) if (Player* player = ToPlayer()) { - if (Pet* pPet = player->GetPet()) - { - if (pPet->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED) && !pPet->HasUnitState(UNIT_STATE_STUNNED)) - pPet->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED); - } - else - player->ResummonPetTemporaryUnSummonedIfAny(); - // if we have charmed npc, remove stun also if (Unit* charm = player->GetCharmed()) if (charm->GetTypeId() == TYPEID_UNIT && charm->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED) && !charm->HasUnitState(UNIT_STATE_STUNNED)) @@ -8418,7 +8073,7 @@ void Unit::UpdateSpeed(UnitMoveType mtype) if (float minSpeedMod = (float)GetMaxPositiveAuraModifier(SPELL_AURA_MOD_MINIMUM_SPEED)) { float baseMinSpeed = 1.0f; - if (!GetOwnerOrCreatorGUID().IsPlayer() && !IsHunterPet() && IsCreature()) + if (!GetOwnerOrCreatorGUID().IsPlayer() && !IsPet() && IsCreature()) { CreatureMovementInfo const& movementInfo = ToCreature()->GetCreatureMovementInfo(); baseMinSpeed = movementInfo.HasRunSpeedOverriden ? (movementInfo.RunSpeed / baseMoveSpeed[MOVE_RUN]) : ToCreature()->GetCreatureTemplate()->speed_run; @@ -8476,10 +8131,6 @@ void Unit::SetSpeedRate(UnitMoveType mtype, float rate) void Unit::SetSpeedRateReal(UnitMoveType mtype, float rate) { - if (!IsInCombat() && ToPlayer()) - if (Pet* pet = ToPlayer()->GetPet()) - pet->SetSpeedRate(mtype, rate); - m_speed_rate[mtype] = rate; PropagateSpeedChange(); } @@ -8497,35 +8148,25 @@ void Unit::FollowTarget(Unit* target) bool catchUpToTarget = false; // unit will allign to the target speed and catches up to the target automatically bool faceTarget = false; // unit will face its target with every spline float distance = DEFAULT_FOLLOW_DISTANCE_PET; + float angle = DEFAULT_FOLLOW_ANGLE; - if (TempSummon* summon = ToTempSummon()) + if (IsSummon()) { - if (SummonPropertiesEntry const* properties = summon->m_Properties) + if (NewTemporarySummon* summon = ToTemporarySummon()) { - // Allied summons, pet summons join a formation unless the following exceptions are being met. - if (properties->Control == SUMMON_CATEGORY_ALLY || properties->Control == SUMMON_CATEGORY_PET) - joinFormation = true; + joinFormation = summon->ShouldJoinSummonerSpawnGroupAfterCreation(); + catchUpToTarget = joinFormation; + if (joinFormation) + angle = DEFAULT_FOLLOW_ANGLE / 2.f; - // Companion minipets will always be able to catch up to their target - if (properties->Slot == SUMMON_SLOT_MINIPET) + if (summon->ShouldFollowSummonerAfterCreation()) { - joinFormation = false; catchUpToTarget = true; faceTarget = true; distance = DEFAULT_FOLLOW_DISTANCE; } - - // Quest npcs follow their target outside of formations - if (properties->Slot == SUMMON_SLOT_QUEST) - { - joinFormation = false; - distance = DEFAULT_FOLLOW_DISTANCE; - } } - - // Pets and minions alwys move in a formation of their target - if (summon->IsPet()) - joinFormation = true; + } else if (IsCharmed()) joinFormation = true; @@ -8534,7 +8175,7 @@ void Unit::FollowTarget(Unit* target) if (joinFormation && target->HasFormationFollower(this)) return; - GetMotionMaster()->MoveFollow(target, distance, DEFAULT_FOLLOW_ANGLE, joinFormation, catchUpToTarget, faceTarget); + GetMotionMaster()->MoveFollow(target, distance, angle, joinFormation, catchUpToTarget, faceTarget); } void Unit::RemoveFormationFollower(Unit* follower) @@ -8589,9 +8230,8 @@ void Unit::setDeathState(DeathState s) ExitVehicle(); // Exit vehicle before calling RemoveAllControlled // vehicles use special type of charm that is not removed by the next function // triggering an assert - UnsummonAllTotems(); - RemoveAllControlled(); RemoveAllAurasOnDeath(); + UnsummonAllSummonsDueToDeath(); } if (s == JUST_DIED) @@ -8628,6 +8268,48 @@ void Unit::setDeathState(DeathState s) //====================================================================== +Unit* Unit::GetCreator() const +{ + if (GetCreatorGUID().IsEmpty()) + return nullptr; + + return ObjectAccessor::GetUnit(*this, GetCreatorGUID()); +} + +Unit* Unit::GetSummoner() const +{ + if (GetSummonerGUID().IsEmpty()) + return nullptr; + + return ObjectAccessor::GetUnit(*this, GetSummonerGUID()); +} + +NewTemporarySummon* Unit::GetCritter() const +{ + if (GetCritterGUID().IsEmpty()) + return nullptr; + + Creature* creature = ObjectAccessor::GetCreature(*this, GetCritterGUID()); + if (!creature || !creature->IsSummon()) + return nullptr; + + return creature->ToTemporarySummon(); +} + +NewPet* Unit::GetActivelyControlledSummon() const +{ + if (GetSummonGUID().IsEmpty()) + return nullptr; + + Creature* creature = ObjectAccessor::GetCreature(*this, GetSummonGUID()); + if (!creature || !creature->IsPet()) + return nullptr; + + return creature->ToNewPet(); +} + +//====================================================================== + void Unit::AtEnterCombat() { if (Spell* spell = m_currentSpells[CURRENT_GENERIC_SPELL]) @@ -8665,20 +8347,6 @@ void Unit::AtTargetAttacked(Unit* target, bool canInitialAggro) void Unit::UpdatePetCombatState() { - ASSERT(!IsPet()); // player pets do not use UNIT_FLAG_PET_IN_COMBAT for this purpose - but player pets should also never have minions of their own to call this - - bool state = false; - for (Unit* minion : m_Controlled) - if (minion->IsInCombat()) - { - state = true; - break; - } - - if (state) - SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT); - else - RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT); } //====================================================================== @@ -9264,14 +8932,11 @@ void Unit::SetMaxHealth(uint32 val) if (ToPlayer()->GetGroup()) ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_MAX_HP); } - else if (Pet* pet = ToCreature()->ToPet()) + else if (NewPet* pet = GetActivelyControlledSummon()) { - if (pet->isControlled()) - { - Unit* owner = GetOwner(); - if (owner && (owner->GetTypeId() == TYPEID_PLAYER) && owner->ToPlayer()->GetGroup()) - owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_MAX_HP); - } + Unit* owner = pet->GetInternalSummoner(); + if (owner && owner->IsPlayer() && owner->ToPlayer()->GetGroup()) + owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_MAX_HP); } if (val < health) @@ -9371,9 +9036,7 @@ int32 Unit::GetCreatePowers(Powers power) const case POWER_RAGE: return 1000; case POWER_FOCUS: - if (GetTypeId() == TYPEID_PLAYER && getClass() == CLASS_HUNTER) - return 100; - return (GetTypeId() == TYPEID_PLAYER || !((Creature const*)this)->IsPet() || ((Pet const*)this)->getPetType() != HUNTER_PET ? 0 : 100); + return 100; case POWER_ENERGY: return 100; case POWER_RUNIC_POWER: @@ -9596,8 +9259,6 @@ void Unit::RemoveFromWorld() RemoveAllDynObjects(); ExitVehicle(); // Remove applied auras with SPELL_AURA_CONTROL_VEHICLE - UnsummonAllTotems(); - RemoveAllControlled(); RemoveAreaAurasDueToLeaveWorld(); @@ -9609,16 +9270,6 @@ void Unit::RemoveFromWorld() RemoveCharmedBy(nullptr); ASSERT(!GetCharmedGUID(), "Unit %u has charmed guid when removed from world", GetEntry()); - ASSERT(!GetCharmerGUID(), "Unit %u has charmer guid when removed from world", GetEntry()); - - if (Unit* owner = GetOwner()) - { - if (owner->m_Controlled.find(this) != owner->m_Controlled.end()) - { - TC_LOG_FATAL("entities.unit", "Unit %u is in controlled list of %u when removed from world", GetEntry(), owner->GetEntry()); - ABORT(); - } - } WorldObject::RemoveFromWorld(); m_duringRemoveFromWorld = false; @@ -9934,15 +9585,15 @@ void CharmInfo::SetPetNumber(uint32 petnumber, bool statwindow) _unit->SetUInt32Value(UNIT_FIELD_PETNUMBER, 0); } -void CharmInfo::LoadPetActionBar(const std::string& data) +void CharmInfo::LoadPetActionBar(const std::string& data, NewPet const* pet) { - InitPetActionBar(); - Tokenizer tokens(data, ' '); if (tokens.size() != (ACTION_BAR_INDEX_END-ACTION_BAR_INDEX_START) * 2) return; // non critical, will reset to default + InitPetActionBar(); + uint8 index = ACTION_BAR_INDEX_START; Tokenizer::const_iterator iter = tokens.begin(); for (; index < ACTION_BAR_INDEX_END; ++iter, ++index) @@ -9957,10 +9608,10 @@ void CharmInfo::LoadPetActionBar(const std::string& data) // check correctness if (PetActionBar[index].IsActionBarForSpell()) { - SpellInfo const* spelInfo = sSpellMgr->GetSpellInfo(PetActionBar[index].GetAction()); - if (!spelInfo) + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(PetActionBar[index].GetAction()); + if (!spellInfo || !pet->HasSpell(spellInfo->Id)) SetActionBar(index, 0, ACT_PASSIVE); - else if (!spelInfo->IsAutocastable()) + else if (!spellInfo->IsAutocastable()) SetActionBar(index, PetActionBar[index].GetAction(), ACT_PASSIVE); } } @@ -9972,6 +9623,12 @@ void CharmInfo::BuildActionBar(WorldPacket* data) *data << uint32(PetActionBar[i].packedData); } +void CharmInfo::BuildActionBar(std::array& actionButtons) +{ + for (uint8 i = 0; i < MAX_UNIT_ACTION_BAR_INDEX; ++i) + actionButtons[i] = PetActionBar[i].packedData; +} + void CharmInfo::SetSpellAutocast(SpellInfo const* spellInfo, bool state) { for (uint8 i = 0; i < MAX_UNIT_ACTION_BAR_INDEX; ++i) @@ -10908,42 +10565,12 @@ float Unit::GetAPMultiplier(WeaponAttackType attType, bool normalized) const Pet* Unit::CreateTamedPetFrom(Creature* creatureTarget, uint32 spell_id) { - if (GetTypeId() != TYPEID_PLAYER) - return nullptr; - - Pet* pet = new Pet(ToPlayer(), HUNTER_PET); - - if (!pet->CreateBaseAtCreature(creatureTarget)) - { - delete pet; - return nullptr; - } - - uint8 level = getLevel(); - - InitTamedPet(pet, level, spell_id); - - return pet; + return nullptr; } Pet* Unit::CreateTamedPetFrom(uint32 creatureEntry, uint32 spell_id) { - if (GetTypeId() != TYPEID_PLAYER) - return nullptr; - - CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(creatureEntry); - if (!creatureInfo) - return nullptr; - - Pet* pet = new Pet(ToPlayer(), HUNTER_PET); - - if (!pet->CreateBaseAtCreatureInfo(creatureInfo, this) || !InitTamedPet(pet, getLevel(), spell_id)) - { - delete pet; - return nullptr; - } - - return pet; + return nullptr; } bool Unit::InitTamedPet(Pet* pet, uint8 level, uint32 spell_id) @@ -11137,21 +10764,6 @@ void Unit::PlayOneShotAnimKitId(uint16 animKitId) TC_LOG_DEBUG("entities.unit", "SET JUST_DIED"); victim->setDeathState(JUST_DIED); - // Inform pets (if any) when player kills target) - // MUST come after victim->setDeathState(JUST_DIED); or pet next target - // selection will get stuck on same target and break pet react state - if (player) - { - Pet* pet = player->GetPet(); - if (pet && pet->IsAlive() && pet->isControlled()) - { - if (pet->IsAIEnabled()) - pet->AI()->KilledUnit(victim); - else - TC_LOG_ERROR("entities.unit", "Pet doesn't have any AI in Unit::Kill(). %s", pet->GetGUID().ToString().c_str()); - } - } - // 10% durability loss on death if (Player* plrVictim = victim->ToPlayer()) { @@ -11757,8 +11369,8 @@ void Unit::RemoveCharmedBy(Unit* charmer) EngageWithTarget(charmer); - if (playerCharmer && this != charmer->GetFirstControlled()) - playerCharmer->SendRemoveControlBar(); + //if (playerCharmer && this != charmer->GetFirstControlled()) + // playerCharmer->SendRemoveControlBar(); // a guardian should always have charminfo if (!IsGuardian()) @@ -11922,10 +11534,6 @@ void Unit::GetPartyMembers(std::list &TagUnitMap) { if (Target->IsAlive()) TagUnitMap.push_back(Target); - - if (Guardian* pet = Target->GetGuardianPet()) - if (pet->IsAlive()) - TagUnitMap.push_back(pet); } } } @@ -11933,9 +11541,6 @@ void Unit::GetPartyMembers(std::list &TagUnitMap) { if ((owner == this || IsInMap(owner)) && owner->IsAlive()) TagUnitMap.push_back(owner); - if (Guardian* pet = owner->GetGuardianPet()) - if ((pet == this || IsInMap(pet)) && pet->IsAlive()) - TagUnitMap.push_back(pet); } } @@ -12573,7 +12178,7 @@ uint32 Unit::GetModelForForm(ShapeshiftForm form, uint32 spellId) const return modelid; } -uint32 Unit::GetModelForTotem(PlayerTotemType totemType) +uint32 Unit::GetModelForTotem(PlayerTotemType totemType) const { switch (getRace()) { @@ -12963,9 +12568,6 @@ void Unit::_ExitVehicle(Position const* exitPosition) init.SetTransportExit(); GetMotionMaster()->LaunchMoveSpline(std::move(init), EVENT_VEHICLE_EXIT, MOTION_SLOT_CONTROLLED); - if (player) - player->ResummonPetTemporaryUnSummonedIfAny(); - if (vehicle->GetBase()->HasUnitTypeMask(UNIT_MASK_MINION) && vehicle->GetBase()->GetTypeId() == TYPEID_UNIT) if (((Minion*)vehicle->GetBase())->GetOwner() == this) vehicle->GetBase()->ToCreature()->DespawnOrUnsummon(1); @@ -13504,34 +13106,17 @@ void Unit::StopAttackFaction(uint32 faction_id) refsToEnd.push_back(pair.second); for (CombatReference* ref : refsToEnd) ref->EndCombat(); - - for (Unit* minion : m_Controlled) - minion->StopAttackFaction(faction_id); } void Unit::OutDebugInfo() const { TC_LOG_ERROR("entities.unit", "Unit::OutDebugInfo"); TC_LOG_DEBUG("entities.unit", "%s name %s", GetGUID().ToString().c_str(), GetName().c_str()); - TC_LOG_DEBUG("entities.unit", "Owner %s, Minion %s, Charmer %s, Charmed %s", GetOwnerOrCreatorGUID().ToString().c_str(), GetMinionGUID().ToString().c_str(), GetCharmerGUID().ToString().c_str(), GetCharmedGUID().ToString().c_str()); TC_LOG_DEBUG("entities.unit", "In world %u, unit type mask %u", (uint32)(IsInWorld() ? 1 : 0), m_unitTypeMask); if (IsInWorld()) TC_LOG_DEBUG("entities.unit", "Mapid %u", GetMapId()); std::ostringstream o; - o << "Summon Slot: "; - for (uint32 i = 0; i < MAX_SUMMON_SLOT; ++i) - o << m_SummonSlot[i].ToString() << ", "; - - TC_LOG_DEBUG("entities.unit", "%s", o.str().c_str()); - o.str(""); - - o << "Controlled List: "; - for (ControlList::const_iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr) - o << (*itr)->GetGUID().ToString() << ", "; - TC_LOG_DEBUG("entities.unit", "%s", o.str().c_str()); - o.str(""); - o << "Aura List: "; for (AuraApplicationMap::const_iterator itr = m_appliedAuras.begin(); itr != m_appliedAuras.end(); ++itr) o << itr->first << ", "; @@ -14594,3 +14179,153 @@ void Unit::SetGameClientMovingMe(GameClient* gameClientMovingMe) { _gameClientMovingMe = gameClientMovingMe; } + +void Unit::AddSummonGUIDToSlot(ObjectGuid summonGuid, SummonPropertiesSlot slot) +{ + _summonGUIDsInSlot[AsUnderlyingType(slot)] = summonGuid; +} + +void Unit::AddSummonGUID(ObjectGuid summonGuid) +{ + _summonGUIDs.insert(summonGuid); +} + +void Unit::RemoveSummonGUIDFromSlot(ObjectGuid summonGuid, SummonPropertiesSlot slot) +{ + if (_summonGUIDsInSlot[AsUnderlyingType(slot)] == summonGuid) + _summonGUIDsInSlot[AsUnderlyingType(slot)] = ObjectGuid::Empty; +} + +void Unit::RemoveSummonGUID(ObjectGuid summonGuid) +{ + _summonGUIDs.erase(summonGuid); +} + +bool Unit::HasSummonInSlot(SummonPropertiesSlot slot) const +{ + return _summonGUIDsInSlot[AsUnderlyingType(slot)] != ObjectGuid::Empty; +} + +void Unit::UnsummonAllSummonsDueToDeath() +{ + std::unordered_set summons = _summonGUIDs; + for (std::unordered_set::const_iterator itr = summons.begin(); itr != summons.end(); ++itr) + if (NewTemporarySummon* summon = GetSummonByGUID(*itr)) + if (summon->ShouldDespawnOnSummonerDeath()) + summon->Unsummon(); +} + +void Unit::UnsummonAllSummonsOnLogout() +{ + std::unordered_set summons = _summonGUIDs; + for (std::unordered_set::const_iterator itr = summons.begin(); itr != summons.end(); ++itr) + if (NewTemporarySummon* summon = GetSummonByGUID(*itr)) + if (summon->ShouldDespawnOnSummonerLogout()) + summon->Unsummon(); +} + +NewTemporarySummon* Unit::GetSummonInSlot(SummonPropertiesSlot slot) const +{ + if (_summonGUIDsInSlot[AsUnderlyingType(slot)].IsEmpty()) + return nullptr; + + Creature* summon = ObjectAccessor::GetCreature(*this, _summonGUIDsInSlot[AsUnderlyingType(slot)]); + if (!summon || !summon->IsSummon()) + return nullptr; + + return summon->ToTemporarySummon(); +} + +NewTemporarySummon* Unit::GetSummonByGUID(ObjectGuid guid) const +{ + if (_summonGUIDs.empty() || _summonGUIDs.find(guid) == _summonGUIDs.end()) + return nullptr; + + Creature* summon = ObjectAccessor::GetCreature(*this, guid); + if (!summon || !summon->IsSummon()) + return nullptr; + + return summon->ToTemporarySummon(); +} + +NewPet* Unit::SummonPet(uint32 creatureId, uint8 slot, uint32 spellId, bool asClassPet, Position const& position) +{ + NewPet* pet = new NewPet(nullptr, this, false, asClassPet, creatureId, slot); + pet->Relocate(position); + if (!pet->IsPositionValid() || (pet->IsClassPet() && !pet->HasPlayerPetDataKey())) + { + delete pet; + return nullptr; + } + + uint32 creatureTemplateEntry = creatureId; + uint32 petNumber = 0; + if (pet->HasPlayerPetDataKey() && IsPlayer()) + { + PlayerPetDataKey const& key = *pet->GetPlayerPetDataKey(); + if (PlayerPetData* petData = ToPlayer()->GetPlayerPetData(key.first, key.second)) + { + if (petData->TamedCreatureId) + creatureTemplateEntry = petData->TamedCreatureId; + + petNumber = petData->PetNumber; + } + } + + Map* map = GetMap(); + if (!pet->Create(map->GenerateLowGuid(), map, creatureTemplateEntry, petNumber)) + { + delete pet; + return nullptr; + } + + TransportBase* transport = GetTransport(); + if (transport) + { + float x, y, z, o; + position.GetPosition(x, y, z, o); + transport->CalculatePassengerOffset(x, y, z, &o); + pet->m_movementInfo.transport.pos.Relocate(x, y, z, o); + + // This object must be added to transport before adding to map for the client to properly display it + transport->AddPassenger(pet); + } + + PhasingHandler::InheritPhaseShift(pet, this); + + pet->SetUInt32Value(UNIT_CREATED_BY_SPELL, spellId); + pet->SetPermanentSummon(true); + + bool successfullyCreated = [&]() + { + if (!pet->HandlePreSummonActions(this, 0, 0)) + return false; + + return map->AddToMap(pet->ToCreature()); + }(); + + if (!successfullyCreated) + { + // Returning false will cause the object to be deleted - remove from transport + if (transport) + transport->RemovePassenger(pet); + + delete pet; + return nullptr; + } + + pet->HandlePostSummonActions(); + + if (Player* player = ToPlayer()) + { + WorldPackets::Pet::PetGuids petGuids; + petGuids.PetGUIDs.push_back(pet->GetGUID()); + player->SendDirectMessage(petGuids.Write()); + } + + // call MoveInLineOfSight for nearby creatures + Trinity::AIRelocationNotifier notifier(*pet); + Cell::VisitAllObjects(pet, notifier, GetVisibilityRange()); + + return pet; +} diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 40886c09435..17d4638b617 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -81,10 +81,14 @@ class Item; class Minion; class MotionMaster; class Pet; +class NewPet; +class Player; +class NewPet; class Spell; class SpellCastTargets; class SpellHistory; class SpellInfo; +class NewTemporarySummon; class Totem; class Transport; class TransportBase; @@ -94,6 +98,7 @@ class Vehicle; class VehicleJoinEvent; enum ZLiquidStatus : uint32; +enum class SummonPropertiesSlot : int8; namespace Movement { @@ -364,16 +369,20 @@ enum DamageEffectType : uint8 enum UnitTypeMask { UNIT_MASK_NONE = 0x00000000, - UNIT_MASK_SUMMON = 0x00000001, - UNIT_MASK_MINION = 0x00000002, - UNIT_MASK_GUARDIAN = 0x00000004, - UNIT_MASK_TOTEM = 0x00000008, - UNIT_MASK_PET = 0x00000010, - UNIT_MASK_VEHICLE = 0x00000020, - UNIT_MASK_PUPPET = 0x00000040, - UNIT_MASK_HUNTER_PET = 0x00000080, - UNIT_MASK_CONTROLABLE_GUARDIAN = 0x00000100, - UNIT_MASK_ACCESSORY = 0x00000200 + UNIT_MASK_SUMMON = 0x00000001, // SummonPropertiesControl = 0 + UNIT_MASK_GUARDIAN = 0x00000002, // SummonPropertiesControl = 1 + UNIT_MASK_PET = 0x00000004, // SummonPropertiesControl = 2 + UNIT_MASK_POSSESSED = 0x00000008, // SummonPropertiesControl = 3 + UNIT_MASK_POSSESSED_VEHICLE = 0x00000010, // SummonPropertiesControl = 4 + UNIT_MASK_VEHICLE = 0x00000020, // SummonPropertiesControl = 5 and Wild entities + + UNIT_MASK_MINION = 0x00000040, + UNIT_MASK_TOTEM = 0x00000080, + UNIT_MASK_PUPPET = 0x00000200, + UNIT_MASK_HUNTER_PET = 0x00000400, + UNIT_MASK_CONTROLABLE_GUARDIAN = 0x00000800, + UNIT_MASK_ACCESSORY = 0x00001000, + UNIT_MASK_SUMMON_OLD = 0x00002000, }; struct DiminishingReturn @@ -692,8 +701,10 @@ struct TC_GAME_API CharmInfo //return true if successful bool AddSpellToActionBar(SpellInfo const* spellInfo, ActiveStates newstate = ACT_DECIDE, uint8 preferredSlot = 0); bool RemoveSpellFromActionBar(uint32 spell_id); - void LoadPetActionBar(const std::string& data); + void LoadPetActionBar(const std::string& data, NewPet const* pet); void BuildActionBar(WorldPacket* data); + void BuildActionBar(std::array& actionButtons); + void SetSpellAutocast(SpellInfo const* spellInfo, bool state); void SetActionBar(uint8 index, uint32 spellOrAction, ActiveStates type) { @@ -780,7 +791,6 @@ class TC_GAME_API Unit : public WorldObject friend class WorldSession; public: typedef std::set AttackerSet; - typedef std::set ControlList; typedef std::vector UnitVector; typedef std::multimap AuraMap; @@ -892,7 +902,7 @@ class TC_GAME_API Unit : public WorldObject bool IsMinion() const { return (m_unitTypeMask & UNIT_MASK_MINION) != 0; } bool IsGuardian() const { return (m_unitTypeMask & UNIT_MASK_GUARDIAN) != 0; } bool IsPet() const { return (m_unitTypeMask & UNIT_MASK_PET) != 0; } - bool IsHunterPet() const{ return (m_unitTypeMask & UNIT_MASK_HUNTER_PET) != 0; } + //bool IsHunterPet() const{ return (m_unitTypeMask & UNIT_MASK_HUNTER_PET) != 0; } bool IsTotem() const { return (m_unitTypeMask & UNIT_MASK_TOTEM) != 0; } bool IsVehicle() const { return (m_unitTypeMask & UNIT_MASK_VEHICLE) != 0; } bool IsControlableGuardian() const { return (m_unitTypeMask & UNIT_MASK_CONTROLABLE_GUARDIAN) != 0; } @@ -1201,16 +1211,28 @@ class TC_GAME_API Unit : public WorldObject DeathState getDeathState() const { return m_deathState; } virtual void setDeathState(DeathState s); // overwrited in Creature/Player/Pet - ObjectGuid GetOwnerGUID() const override { return GetGuidValue(UNIT_FIELD_SUMMONEDBY); } - void SetOwnerGUID(ObjectGuid owner); ObjectGuid GetCreatorGUID() const { return GetGuidValue(UNIT_FIELD_CREATEDBY); } - void SetCreatorGUID(ObjectGuid creator) { SetGuidValue(UNIT_FIELD_CREATEDBY, creator); } - ObjectGuid GetMinionGUID() const { return GetGuidValue(UNIT_FIELD_SUMMON); } - void SetMinionGUID(ObjectGuid guid) { SetGuidValue(UNIT_FIELD_SUMMON, guid); } - void SetPetGUID(ObjectGuid guid) { m_SummonSlot[SUMMON_SLOT_PET] = guid; } - ObjectGuid GetPetGUID() const { return m_SummonSlot[SUMMON_SLOT_PET]; } - void SetCritterGUID(ObjectGuid guid) { SetGuidValue(UNIT_FIELD_CRITTER, guid); } + void SetCreatorGUID(ObjectGuid guid) { SetGuidValue(UNIT_FIELD_CREATEDBY, guid); } + // Returns the unit that has been referenced by the guid value of UNIT_FIELD_CREATEDBY + Unit* GetCreator() const; + + ObjectGuid GetSummonerGUID() const { return GetGuidValue(UNIT_FIELD_SUMMONEDBY); } + void SetSummonerGUID(ObjectGuid guid) { SetGuidValue(UNIT_FIELD_SUMMONEDBY, guid); } + // Returns the unit that has been referenced by the guid value of UNIT_FIELD_SUMMONEDBY + Unit* GetSummoner() const; + ObjectGuid GetCritterGUID() const { return GetGuidValue(UNIT_FIELD_CRITTER); } + void SetCritterGUID(ObjectGuid guid) { SetGuidValue(UNIT_FIELD_CRITTER, guid); } + // Returns the temporary summon that has been referenced by the guid value of UNIT_FIELD_CRITTER + NewTemporarySummon* GetCritter() const; + + ObjectGuid GetSummonGUID() const { return GetGuidValue(UNIT_FIELD_SUMMON); } + void SetSummonGUID(ObjectGuid guid) { SetGuidValue(UNIT_FIELD_SUMMON, guid); } + NewPet* GetActivelyControlledSummon() const; + + void SetActivelyControlledSummon(NewPet* pet, bool apply); + + ObjectGuid GetOwnerGUID() const override { return GetGuidValue(UNIT_FIELD_SUMMONEDBY); } ObjectGuid GetOwnerOrCreatorGUID() const { return GetOwnerGUID() ? GetOwnerGUID() : GetCreatorGUID(); } ObjectGuid GetCharmerGUID() const { return GetGuidValue(UNIT_FIELD_CHARMEDBY); } @@ -1224,23 +1246,13 @@ class TC_GAME_API Unit : public WorldObject ObjectGuid GetCharmerOrOwnerGUID() const override { return IsCharmed() ? GetCharmerGUID() : GetOwnerGUID(); } bool IsCharmedOwnedByPlayerOrPlayer() const { return GetCharmerOrOwnerOrOwnGUID().IsPlayer(); } - Guardian* GetGuardianPet() const; - Minion* GetFirstMinion() const; Unit* GetCharmerOrOwner() const { return IsCharmed() ? GetCharmer() : GetOwner(); } - void SetMinion(Minion *minion, bool apply); - void GetAllMinionsByEntry(std::list& Minions, uint32 entry); - void RemoveAllMinionsByEntry(uint32 entry); void SetCharm(Unit* target, bool apply); - Unit* GetNextRandomRaidMemberOrPet(float radius); bool SetCharmedBy(Unit* charmer, CharmType type, AuraApplication const* aurApp = nullptr); void RemoveCharmedBy(Unit* charmer); void RestoreFaction(); - ControlList m_Controlled; - Unit* GetFirstControlled() const; - void RemoveAllControlled(); - bool IsCharmed() const { return !GetCharmerGUID().IsEmpty(); } bool IsCharming() const { return !GetCharmedGUID().IsEmpty(); } bool isPossessed() const { return HasUnitState(UNIT_STATE_POSSESSED); } @@ -1446,7 +1458,6 @@ class TC_GAME_API Unit : public WorldObject SpellHistory* GetSpellHistory() { return m_spellHistory.get(); } SpellHistory const* GetSpellHistory() const { return m_spellHistory.get(); } - std::array m_SummonSlot; std::array m_ObjectSlot; ShapeshiftForm GetShapeshiftForm() const { return ShapeshiftForm(GetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_SHAPESHIFT_FORM)); } @@ -1563,7 +1574,6 @@ class TC_GAME_API Unit : public WorldObject void ModifyAuraState(AuraStateType flag, bool apply); uint32 BuildAuraStateUpdateForTarget(Unit* target) const; bool HasAuraState(AuraStateType flag, SpellInfo const* spellProto = nullptr, Unit const* Caster = nullptr) const; - void UnsummonAllTotems(); bool IsMagnet() const; Unit* GetMeleeHitRedirectTarget(Unit* victim, SpellInfo const* spellInfo = nullptr); @@ -1686,7 +1696,7 @@ class TC_GAME_API Unit : public WorldObject void SetCantProc(bool apply); uint32 GetModelForForm(ShapeshiftForm form, uint32 spellId) const; - uint32 GetModelForTotem(PlayerTotemType totemType); + uint32 GetModelForTotem(PlayerTotemType totemType) const; friend class VehicleJoinEvent; ObjectGuid LastCharmerGUID; @@ -1751,6 +1761,12 @@ class TC_GAME_API Unit : public WorldObject TempSummon* ToTempSummon() { if (IsSummon()) return reinterpret_cast(this); else return nullptr; } TempSummon const* ToTempSummon() const { if (IsSummon()) return reinterpret_cast(this); else return nullptr; } + NewTemporarySummon* ToTemporarySummon() { if (IsSummon()) return reinterpret_cast(this); else return nullptr; } + NewTemporarySummon const* ToTemporarySummon() const { if (IsSummon()) return reinterpret_cast(this); else return nullptr; } + + NewPet* ToNewPet() { if (IsPet()) return reinterpret_cast(this); else return nullptr; } + NewPet const* ToNewPet() const { if (IsPet()) return reinterpret_cast(this); else return nullptr; } + ObjectGuid GetTarget() const { return GetGuidValue(UNIT_FIELD_TARGET); } virtual void SetTarget(ObjectGuid /*guid*/) = 0; @@ -1945,6 +1961,29 @@ class TC_GAME_API Unit : public WorldObject std::unordered_map m_pendingMovementChanges; /* Player Movement fields END*/ + + /*Temporary Summons*/ + std::array _summonGUIDsInSlot; + std::unordered_set _summonGUIDs; + + public: + void AddSummonGUIDToSlot(ObjectGuid summonGuid, SummonPropertiesSlot slot); + void AddSummonGUID(ObjectGuid summonGuid); + void RemoveSummonGUIDFromSlot(ObjectGuid summonGuid, SummonPropertiesSlot slot); + void RemoveSummonGUID(ObjectGuid summonGuid); + bool HasSummonInSlot(SummonPropertiesSlot slot) const; + // Despawns all summons that are meant to be despawned when the unit has died + void UnsummonAllSummonsDueToDeath(); + // Despawns all summons that are meant to be despawned when the player leaves the world. Despite its name and summon property flag handling, this method is meant to trigger on world leaving instead of logouts only + void UnsummonAllSummonsOnLogout(); // we are using this method in the unit class to restrict direct access of the _summonGUIDs container + std::unordered_set const& GetSummonGUIDs() const { return _summonGUIDs; } + + NewTemporarySummon* GetSummonInSlot(SummonPropertiesSlot slot) const; + NewTemporarySummon* GetSummonByGUID(ObjectGuid guid) const; + + // Summons a pet that is being summoned by SPELL_EFFECT_SUMMON_PET. If creatureId is 0, we assume that the player attempts to summon a hunter pet. + NewPet* SummonPet(uint32 creatureId, uint8 slot, uint32 spellId, bool asClassPet, Position const& position); + /*End of Tempoary Summons*/ }; namespace Trinity diff --git a/src/server/game/Entities/Unit/UnitDefines.h b/src/server/game/Entities/Unit/UnitDefines.h index 2a6c4871bdf..097587b21a8 100644 --- a/src/server/game/Entities/Unit/UnitDefines.h +++ b/src/server/game/Entities/Unit/UnitDefines.h @@ -354,7 +354,7 @@ struct DeclinedName std::string name[MAX_DECLINED_NAME_CASES]; }; -enum ActiveStates +enum ActiveStates : uint8 { ACT_PASSIVE = 0x01, // 0x01 - passive ACT_DISABLED = 0x81, // 0x80 - castable @@ -364,7 +364,7 @@ enum ActiveStates ACT_DECIDE = 0x00 // custom }; -enum ReactStates +enum ReactStates : uint8 { REACT_PASSIVE = 0, REACT_DEFENSIVE = 1, @@ -381,4 +381,22 @@ enum CommandStates : uint8 COMMAND_MOVE_TO = 4 }; +enum class PetModeFlags : uint16 +{ + Unknown1 = 0x001, + Unknown2 = 0x002, + Unknown3 = 0x004, + Unknown4 = 0x008, + Unknown5 = 0x010, + Unknown6 = 0x020, + Unknown7 = 0x040, + Unknown8 = 0x080, + Unknown9 = 0x100, + Unknown10 = 0x200, + Unknown11 = 0x400, + DisableActions = 0x800 +}; + +DEFINE_ENUM_FLAG(PetModeFlags); + #endif // UnitDefines_h__ diff --git a/src/server/game/Entities/Vehicle/Vehicle.cpp b/src/server/game/Entities/Vehicle/Vehicle.cpp index 32c052bc210..e99854d956f 100644 --- a/src/server/game/Entities/Vehicle/Vehicle.cpp +++ b/src/server/game/Entities/Vehicle/Vehicle.cpp @@ -922,8 +922,8 @@ bool VehicleJoinEvent::Execute(uint64, uint32) player->StopCastingCharm(); player->StopCastingBindSight(); player->SendOnCancelExpectedVehicleRideAura(); - if (!veSeat->HasFlag(VEHICLE_SEAT_FLAG_B_KEEP_PET)) - player->UnsummonPetTemporaryIfAny(); + //if (!veSeat->HasFlag(VEHICLE_SEAT_FLAG_B_KEEP_PET)) + // player->UnsummonPetTemporaryIfAny(); } if (veSeat->HasFlag(VEHICLE_SEAT_FLAG_DISABLE_GRAVITY) || Target->GetBase()->CanFly()) diff --git a/src/server/game/Globals/ObjectAccessor.cpp b/src/server/game/Globals/ObjectAccessor.cpp index e2472099043..592a60253ab 100644 --- a/src/server/game/Globals/ObjectAccessor.cpp +++ b/src/server/game/Globals/ObjectAccessor.cpp @@ -26,6 +26,7 @@ #include "ObjectDefines.h" #include "ObjectMgr.h" #include "Pet.h" +#include "NewPet.h" #include "Player.h" #include "Transport.h" #include "World.h" @@ -114,8 +115,8 @@ WorldObject* ObjectAccessor::GetWorldObject(WorldObject const& p, ObjectGuid con case HighGuid::Mo_Transport: case HighGuid::GameObject: return GetGameObject(p, guid); case HighGuid::Vehicle: - case HighGuid::Unit: return GetCreature(p, guid); - case HighGuid::Pet: return GetPet(p, guid); + case HighGuid::Unit: + case HighGuid::Pet: return GetCreature(p, guid); case HighGuid::DynamicObject: return GetDynamicObject(p, guid); case HighGuid::AreaTrigger: return GetAreaTrigger(p, guid); case HighGuid::Corpse: return GetCorpse(p, guid); @@ -143,12 +144,9 @@ Object* ObjectAccessor::GetObjectByTypeMask(WorldObject const& p, ObjectGuid con break; case HighGuid::Unit: case HighGuid::Vehicle: - if (typemask & TYPEMASK_UNIT) - return GetCreature(p, guid); - break; case HighGuid::Pet: if (typemask & TYPEMASK_UNIT) - return GetPet(p, guid); + return GetCreature(p, guid); break; case HighGuid::DynamicObject: if (typemask & TYPEMASK_DYNAMICOBJECT) @@ -197,9 +195,6 @@ Unit* ObjectAccessor::GetUnit(WorldObject const& u, ObjectGuid const& guid) if (guid.IsPlayer()) return GetPlayer(u, guid); - if (guid.IsPet()) - return GetPet(u, guid); - return GetCreature(u, guid); } @@ -208,11 +203,6 @@ Creature* ObjectAccessor::GetCreature(WorldObject const& u, ObjectGuid const& gu return u.GetMap()->GetCreature(guid); } -Pet* ObjectAccessor::GetPet(WorldObject const& u, ObjectGuid const& guid) -{ - return u.GetMap()->GetPet(guid); -} - Player* ObjectAccessor::GetPlayer(Map const* m, ObjectGuid const& guid) { if (Player* player = HashMapHolder::Find(guid)) @@ -229,9 +219,6 @@ Player* ObjectAccessor::GetPlayer(WorldObject const& u, ObjectGuid const& guid) Creature* ObjectAccessor::GetCreatureOrPetOrVehicle(WorldObject const& u, ObjectGuid const& guid) { - if (guid.IsPet()) - return GetPet(u, guid); - if (guid.IsCreatureOrVehicle()) return GetCreature(u, guid); diff --git a/src/server/game/Globals/ObjectAccessor.h b/src/server/game/Globals/ObjectAccessor.h index 521009529c9..1dd43cb8ead 100644 --- a/src/server/game/Globals/ObjectAccessor.h +++ b/src/server/game/Globals/ObjectAccessor.h @@ -29,7 +29,7 @@ class DynamicObject; class GameObject; class Map; class Object; -class Pet; +class NewPet; class Player; class Transport; class MapTransport; @@ -68,7 +68,6 @@ namespace ObjectAccessor TC_GAME_API AreaTrigger* GetAreaTrigger(WorldObject const& u, ObjectGuid const& guid); TC_GAME_API Unit* GetUnit(WorldObject const&, ObjectGuid const& guid); TC_GAME_API Creature* GetCreature(WorldObject const& u, ObjectGuid const& guid); - TC_GAME_API Pet* GetPet(WorldObject const&, ObjectGuid const& guid); TC_GAME_API Player* GetPlayer(Map const*, ObjectGuid const& guid); TC_GAME_API Player* GetPlayer(WorldObject const&, ObjectGuid const& guid); TC_GAME_API Creature* GetCreatureOrPetOrVehicle(WorldObject const&, ObjectGuid const&); diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index 1c88ae219f3..a77ef8cb6d2 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -7377,7 +7377,7 @@ void ObjectMgr::LoadPetNumber() { uint32 oldMSTime = getMSTime(); - QueryResult result = CharacterDatabase.Query("SELECT MAX(id) FROM character_pet"); + QueryResult result = CharacterDatabase.Query("SELECT MAX(PetNumber) FROM character_pet"); if (result) { Field* fields = result->Fetch(); diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index 6e75fa7063e..94c64718f35 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -229,8 +229,8 @@ bool LoginQueryHolder::Initialize() stmt->setUInt32(0, lowGuid); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_CORPSE_LOCATION, stmt); - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_ALL_PETS_DETAIL); - stmt->setUInt64(0, lowGuid); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PET); + stmt->setUInt32(0, lowGuid); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_ALL_PETS, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_REWARDSTATUS_LFG); @@ -969,11 +969,6 @@ void WorldSession::HandlePlayerLogin(LoginQueryHolder const& holder) if (pCurrChar->HasAtLoginFlag(AT_LOGIN_RESET_PET_TALENTS)) Pet::resetTalentsForAllPetsOf(pCurrChar); - pCurrChar->LoadPetsFromDB(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_ALL_PETS)); - - // Load pet if any (if player not alive and in taxi flight or another then pet will remember as temporary unsummoned) - pCurrChar->LoadPet(); - // Set FFA PvP for non GM in non-rest mode if (sWorld->IsFFAPvPRealm() && !pCurrChar->IsGameMaster() && !pCurrChar->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING)) pCurrChar->SetByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, UNIT_BYTE2_FLAG_FFA_PVP); @@ -995,6 +990,10 @@ void WorldSession::HandlePlayerLogin(LoginQueryHolder const& holder) SendNotification(LANG_RESET_TALENTS); } + if (ChrClassesEntry const* clsEntry = sChrClassesStore.LookupEntry(pCurrChar->getClass())) + if (clsEntry->GetFlags().HasFlag(ChrClassesFlags::SendStableAtLogin)) + SendPetStableList(ObjectGuid::Empty); + bool firstLogin = pCurrChar->HasAtLoginFlag(AT_LOGIN_FIRST); if (firstLogin) { @@ -1083,8 +1082,7 @@ void WorldSession::HandlePlayerLogin(LoginQueryHolder const& holder) } } - if (pCurrChar->getClass() == CLASS_HUNTER) - pCurrChar->GetSession()->SendStablePet(ObjectGuid::Empty); + pCurrChar->ResummonActiveClassPet(); // show time before shutdown if shutdown planned. if (sWorld->IsShuttingDown()) diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp index b1dd6602ac5..8372b662c29 100644 --- a/src/server/game/Handlers/MiscHandler.cpp +++ b/src/server/game/Handlers/MiscHandler.cpp @@ -88,7 +88,6 @@ void WorldSession::HandleRepopRequestOpcode(WorldPacket& recvData) } //this is spirit release confirm? - GetPlayer()->RemovePet(nullptr, PET_SAVE_DISMISS, true); GetPlayer()->BuildPlayerRepop(); GetPlayer()->RepopAtGraveyard(); } diff --git a/src/server/game/Handlers/MovementHandler.cpp b/src/server/game/Handlers/MovementHandler.cpp index 1294c99f207..9f4377d6483 100644 --- a/src/server/game/Handlers/MovementHandler.cpp +++ b/src/server/game/Handlers/MovementHandler.cpp @@ -210,7 +210,7 @@ void WorldSession::HandleMoveWorldportAck() GetPlayer()->UpdatePvP(false, false); // resummon pet - GetPlayer()->ResummonPetTemporaryUnSummonedIfAny(); + //GetPlayer()->ResummonPetTemporaryUnSummonedIfAny(); //lets process all delayed operations on successful teleport GetPlayer()->ProcessDelayedOperations(); @@ -274,7 +274,7 @@ void WorldSession::HandleMoveTeleportAck(WorldPackets::Movement::MoveTeleportAck } // resummon pet - GetPlayer()->ResummonPetTemporaryUnSummonedIfAny(); + //GetPlayer()->ResummonPetTemporaryUnSummonedIfAny(); //lets process all delayed operations on successful teleport GetPlayer()->ProcessDelayedOperations(); diff --git a/src/server/game/Handlers/NPCHandler.cpp b/src/server/game/Handlers/NPCHandler.cpp index 4d555cd4272..c7f0c4717b4 100644 --- a/src/server/game/Handlers/NPCHandler.cpp +++ b/src/server/game/Handlers/NPCHandler.cpp @@ -32,6 +32,7 @@ #include "ObjectMgr.h" #include "Opcodes.h" #include "Pet.h" +#include "PetPackets.h" #include "Player.h" #include "QueryCallback.h" #include "ReputationMgr.h" @@ -296,153 +297,6 @@ void WorldSession::SendBindPoint(Creature* npc) _player->PlayerTalkClass->SendCloseGossip(); } -void WorldSession::HandleListStabledPetsOpcode(WorldPacket& recvData) -{ - TC_LOG_DEBUG("network", "WORLD: Recv MSG_LIST_STABLED_PETS"); - ObjectGuid npcGUID; - - recvData >> npcGUID; - - if (!CheckStableMaster(npcGUID)) - return; - - // remove fake death - if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) - GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - - // remove mounts this fix bug where getting pet from stable while mounted deletes pet. - if (GetPlayer()->IsMounted()) - GetPlayer()->RemoveAurasByType(SPELL_AURA_MOUNTED); - - SendStablePet(npcGUID); -} - -void WorldSession::SendStablePet(ObjectGuid guid) -{ - WorldPacket data(MSG_LIST_STABLED_PETS, 200); - - data << uint64(guid); // Stablemaster - data << uint8(_player->PlayerPetDataStore.size()); - data << uint8(PET_SLOT_LAST_STABLE_SLOT); // Stable Slots - - for (PlayerPetData* p : _player->PlayerPetDataStore) - { - uint32 petSlot = p->Slot; - uint8 flags = PET_STABLE_ACTIVE; - - if (petSlot > PET_SLOT_LAST_ACTIVE_SLOT) - flags |= PET_STABLE_INACTIVE; - - data << int32(petSlot); - data << uint32(p->PetId); - data << uint32(p->CreatureId); - data << uint32(p->Petlevel); - data << p->Name; - data << uint8(flags); - } - - SendPacket(&data); -} - -void WorldSession::SendStableResult(uint8 res) -{ - WorldPacket data(SMSG_STABLE_RESULT, 1); - data << uint8(res); - SendPacket(&data); -} - -void WorldSession::HandleSetPetSlot(WorldPacket& recvData) -{ - TC_LOG_DEBUG("network", "WORLD: Recv CMSG_STABLE_PET"); - ObjectGuid guid; - uint32 petId; - uint8 new_slot; - - recvData >> petId >> new_slot; - - guid[3] = recvData.ReadBit(); - guid[2] = recvData.ReadBit(); - guid[0] = recvData.ReadBit(); - guid[7] = recvData.ReadBit(); - guid[5] = recvData.ReadBit(); - guid[6] = recvData.ReadBit(); - guid[1] = recvData.ReadBit(); - guid[4] = recvData.ReadBit(); - - recvData.ReadByteSeq(guid[5]); - recvData.ReadByteSeq(guid[3]); - recvData.ReadByteSeq(guid[1]); - recvData.ReadByteSeq(guid[7]); - recvData.ReadByteSeq(guid[4]); - recvData.ReadByteSeq(guid[0]); - recvData.ReadByteSeq(guid[6]); - recvData.ReadByteSeq(guid[2]); - - if (!GetPlayer()->IsAlive()) - { - SendStableResult(STABLE_ERR_STABLE); - return; - } - - if (!CheckStableMaster(guid)) - { - SendStableResult(STABLE_ERR_STABLE); - return; - } - - if (new_slot > PET_SLOT_LAST_STABLE_SLOT) - { - SendStableResult(STABLE_ERR_STABLE); - return; - } - PlayerPetData* playerPetData = _player->GetPlayerPetDataById(petId); - CreatureTemplate const* creatureInfo = nullptr; - - if (playerPetData) - creatureInfo = sObjectMgr->GetCreatureTemplate(playerPetData->CreatureId); - - if (!creatureInfo || !creatureInfo->IsTameable(true)) - { - SendStableResult(STABLE_ERR_STABLE); - return; - } - - if (!creatureInfo->IsTameable(_player->CanTameExoticPets()) && new_slot <= PET_SLOT_LAST_ACTIVE_SLOT) - { - SendStableResult(STABLE_ERR_EXOTIC); - return; - } - - // remove fake death 2 - if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) - GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - - Pet* pet = _player->GetPet(); - - // can't place in stable dead pet - if (pet) - { - if (pet->GetCharmInfo()->GetPetNumber() == petId) - { - if (!pet->IsAlive() || !pet->IsHunterPet()) - { - SendStableResult(STABLE_ERR_STABLE); - return; - } - } - } - - if (playerPetData) - { - UpdatePetSlot(petId, playerPetData->Slot, new_slot); - } -} - -void WorldSession::HandleStableRevivePet(WorldPacket &/* recvData */) -{ - TC_LOG_DEBUG("network", "HandleStableRevivePet: Not implemented"); -} - void WorldSession::HandleRepairItemOpcode(WorldPacket& recvData) { TC_LOG_DEBUG("network", "WORLD: CMSG_REPAIR_ITEM"); diff --git a/src/server/game/Handlers/PetHandler.cpp b/src/server/game/Handlers/PetHandler.cpp index 1a3d67269b3..f5c24dc5060 100644 --- a/src/server/game/Handlers/PetHandler.cpp +++ b/src/server/game/Handlers/PetHandler.cpp @@ -26,143 +26,57 @@ #include "ObjectMgr.h" #include "Opcodes.h" #include "Pet.h" +#include "NewPet.h" #include "PetPackets.h" #include "PetAI.h" #include "Player.h" +#include "QueryPackets.h" #include "Spell.h" #include "SpellHistory.h" #include "SpellInfo.h" #include "SpellMgr.h" +#include "TalentPackets.h" #include "Util.h" #include "WorldPacket.h" -void WorldSession::HandleDismissCritter(WorldPacket& recvData) +void WorldSession::HandleDismissCritter(WorldPackets::Pet::DismissCritter& packet) { - ObjectGuid guid; - recvData >> guid; - - TC_LOG_DEBUG("network.opcode", "WORLD: Received CMSG_DISMISS_CRITTER for %s", guid.ToString().c_str()); - - Unit* pet = ObjectAccessor::GetCreatureOrPetOrVehicle(*_player, guid); - - if (!pet) - { - TC_LOG_DEBUG("entities.pet", "Vanitypet (%s) does not exist - player '%s' (guid: %u / account: %u) attempted to dismiss it (possibly lagged out)", - guid.ToString().c_str(), GetPlayer()->GetName().c_str(), GetPlayer()->GetGUID().GetCounter(), GetAccountId()); + if (_player->GetCritterGUID() != packet.CritterGUID) return; - } - if (_player->GetCritterGUID() == pet->GetGUID()) - { - if (pet->GetTypeId() == TYPEID_UNIT && pet->IsSummon()) - pet->ToTempSummon()->UnSummon(); - } + if (NewTemporarySummon* summon = _player->GetSummonInSlot(SummonPropertiesSlot::Critter)) + if (summon->GetGUID() == packet.CritterGUID) + summon->Unsummon(); } -void WorldSession::HandlePetAction(WorldPacket& recvData) +inline void SendPetActionFeedbackToPlayer(Player* player, ActionFeedback response, uint32 spellId) { - ObjectGuid guid1; - uint32 data; - ObjectGuid guid2; - float x, y, z; - recvData >> guid1; //pet guid - recvData >> data; - recvData >> guid2; //tag guid - // Position - recvData >> x; - recvData >> y; - recvData >> z; - - uint32 spellid = UNIT_ACTION_BUTTON_ACTION(data); - uint8 flag = UNIT_ACTION_BUTTON_TYPE(data); //delete = 0x07 CastSpell = C1 - - // used also for charmed creature - Unit* pet = ObjectAccessor::GetUnit(*_player, guid1); - TC_LOG_DEBUG("entities.pet", "HandlePetAction: %s - flag: %u, spellid: %u, target: %s.", guid1.ToString().c_str(), uint32(flag), spellid, guid2.ToString().c_str()); - - if (!pet) - { - TC_LOG_DEBUG("entities.pet", "HandlePetAction: %s doesn't exist for %s %s", guid1.ToString().c_str(), GetPlayer()->GetGUID().ToString().c_str(), GetPlayer()->GetName().c_str()); - return; - } - - if (pet != GetPlayer()->GetFirstControlled()) - { - TC_LOG_DEBUG("entities.pet", "HandlePetAction: %s does not belong to %s %s", guid1.ToString().c_str(), GetPlayer()->GetGUID().ToString().c_str(), GetPlayer()->GetName().c_str()); - return; - } - - if (!pet->IsAlive()) - { - SpellInfo const* spell = (flag == ACT_ENABLED || flag == ACT_PASSIVE) ? sSpellMgr->GetSpellInfo(spellid) : nullptr; - if (!spell) - return; - if (!spell->HasAttribute(SPELL_ATTR0_ALLOW_CAST_WHILE_DEAD)) - return; - } - - /// @todo allow control charmed player? - if (pet->GetTypeId() == TYPEID_PLAYER && !(flag == ACT_COMMAND && spellid == COMMAND_ATTACK)) - return; - - if (GetPlayer()->m_Controlled.size() == 1) - HandlePetActionHelper(pet, guid1, spellid, flag, guid2, x, y, z); - else - { - //If a pet is dismissed, m_Controlled will change - std::vector controlled; - for (Unit::ControlList::iterator itr = GetPlayer()->m_Controlled.begin(); itr != GetPlayer()->m_Controlled.end(); ++itr) - if ((*itr)->GetEntry() == pet->GetEntry() && (*itr)->IsAlive()) - controlled.push_back(*itr); - for (std::vector::iterator itr = controlled.begin(); itr != controlled.end(); ++itr) - HandlePetActionHelper(*itr, guid1, spellid, flag, guid2, x, y, z); - } + WorldPackets::Pet::PetActionFeedback actionFeedback; + actionFeedback.Response = response; + actionFeedback.SpellID = spellId; + player->SendDirectMessage(actionFeedback.Write()); } -void WorldSession::HandlePetStopAttack(WorldPacket &recvData) +inline void SendPetActionSoundToPlayer(Player* player, ObjectGuid const& petGuid, PetTalk response) { - ObjectGuid guid; - recvData >> guid; - - TC_LOG_DEBUG("network.opcode", "WORLD: Received CMSG_PET_STOP_ATTACK for %s", guid.ToString().c_str()); - - Unit* pet = ObjectAccessor::GetCreatureOrPetOrVehicle(*_player, guid); - - if (!pet) - { - TC_LOG_ERROR("entities.pet", "HandlePetStopAttack: %s does not exist", guid.ToString().c_str()); - return; - } - - if (pet != GetPlayer()->GetPet() && pet != GetPlayer()->GetCharmed()) - { - TC_LOG_ERROR("entities.pet", "HandlePetStopAttack: %s isn't a pet or charmed creature of player %s", - guid.ToString().c_str(), GetPlayer()->GetName().c_str()); - return; - } - - if (!pet->IsAlive()) - return; - - pet->AttackStop(); + WorldPackets::Pet::PetActionSound actionSound; + actionSound.Action = response; + actionSound.UnitGUID = petGuid; + player->SendDirectMessage(actionSound.Write()); } -void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spellid, uint16 flag, ObjectGuid guid2, float x, float y, float z) +void HandlePetActionHelper(NewPet* pet, Player* owner, ObjectGuid targetGuid, uint32 actionValue, uint16 actionFlag, Position const& actionPosition) { CharmInfo* charmInfo = pet->GetCharmInfo(); if (!charmInfo) - { - TC_LOG_DEBUG("entities.pet", "WorldSession::HandlePetAction(petGuid: %s, tagGuid: %s, spellId: %u, flag: %u): object (GUID: %u Entry: %u TypeId: %u) is considered pet-like but doesn't have a charminfo!", - guid1.ToString().c_str(), guid2.ToString().c_str(), spellid, flag, pet->GetGUID().GetCounter(), pet->GetEntry(), pet->GetTypeId()); return; - } - switch (flag) + switch (actionFlag) { - case ACT_COMMAND: //0x07 - switch (spellid) + case ACT_COMMAND: + switch (actionValue) { - case COMMAND_STAY: //flat=1792 //STAY + case COMMAND_STAY: if (pet->GetMotionMaster()->GetCurrentSlot() != MOTION_SLOT_CONTROLLED) pet->StopMoving(); @@ -178,102 +92,32 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe charmInfo->SetIsReturning(false); charmInfo->SaveStayPosition(); break; - case COMMAND_FOLLOW: //spellid=1792 //FOLLOW + case COMMAND_FOLLOW: pet->AttackStop(); pet->InterruptNonMeleeSpells(false); - pet->FollowTarget(_player); + pet->FollowTarget(pet->GetSummoner()); charmInfo->SetCommandState(COMMAND_FOLLOW); - charmInfo->SetIsCommandAttack(false); charmInfo->SetIsAtStay(false); charmInfo->SetIsReturning(true); charmInfo->SetIsCommandFollow(true); charmInfo->SetIsFollowing(false); break; - case COMMAND_ATTACK: //spellid=1792 //ATTACK - { - // Can't attack if owner is pacified - if (_player->HasAuraType(SPELL_AURA_MOD_PACIFY)) - { - //pet->SendPetCastFail(spellid, SPELL_FAILED_PACIFIED); - /// @todo Send proper error message to client - return; - } - - // only place where pet can be player - Unit* TargetUnit = ObjectAccessor::GetUnit(*_player, guid2); - if (!TargetUnit) - return; - - if (Unit* owner = pet->GetOwner()) - if (!owner->IsValidAttackTarget(TargetUnit)) - return; - - pet->ClearUnitState(UNIT_STATE_FOLLOW); - // This is true if pet has no target or has target but targets differs. - if (pet->GetVictim() != TargetUnit || !pet->GetCharmInfo()->IsCommandAttack()) - { - if (pet->GetVictim()) - pet->AttackStop(); - - if (pet->GetTypeId() != TYPEID_PLAYER && pet->ToCreature()->IsAIEnabled()) - { - charmInfo->SetIsCommandAttack(true); - charmInfo->SetIsAtStay(false); - charmInfo->SetIsFollowing(false); - charmInfo->SetIsCommandFollow(false); - charmInfo->SetIsReturning(false); - - CreatureAI* AI = pet->ToCreature()->AI(); - if (PetAI* petAI = dynamic_cast(AI)) - petAI->_AttackStart(TargetUnit); // force target switch - else - AI->AttackStart(TargetUnit); - - //10% chance to play special pet attack talk, else growl - if (pet->IsPet() && ((Pet*)pet)->getPetType() == SUMMON_PET && pet != TargetUnit && urand(0, 100) < 10) - pet->SendPetActionSound((uint32)PET_TALK_ATTACK); - else - { - // 90% chance for pet and 100% chance for charmed creature - pet->SendPetAIReaction(guid1); - } - } - else // charmed player - { - charmInfo->SetIsCommandAttack(true); - charmInfo->SetIsAtStay(false); - charmInfo->SetIsFollowing(false); - charmInfo->SetIsCommandFollow(false); - charmInfo->SetIsReturning(false); - - pet->Attack(TargetUnit, true); - pet->SendPetAIReaction(guid1); - } - } + case COMMAND_ATTACK: + printf("COMMAND_ATTACK -- %u\n", actionValue); break; - } - case COMMAND_ABANDON: // abandon (hunter pet) or dismiss (summoned pet) - if (pet->GetCharmerGUID() == GetPlayer()->GetGUID()) - _player->StopCastingCharm(); - else if (pet->GetOwnerOrCreatorGUID() == GetPlayer()->GetGUID()) - { - ASSERT(pet->GetTypeId() == TYPEID_UNIT); - if (pet->IsPet()) - { - if (((Pet*)pet)->IsHunterPet()) - GetPlayer()->RemovePet((Pet*)pet, PET_SAVE_AS_DELETED); - else - GetPlayer()->RemovePet((Pet*)pet, PET_SAVE_DISMISS); - } - else if (pet->HasUnitTypeMask(UNIT_MASK_MINION)) - ((Minion*)pet)->UnSummon(); - } + case COMMAND_ABANDON: + // Despite its enum name, abandoning pets is done via extra Opcode. This here is merely dismissing pets + if (pet->CanBeDismissed()) + pet->Dismiss(); break; case COMMAND_MOVE_TO: - pet->GetMotionMaster()->Clear(MOTION_SLOT_IDLE); - pet->GetMotionMaster()->MoveIdle(); - pet->GetMotionMaster()->MovePoint(0, x, y, z); + if (pet->IsAlive()) + { + pet->GetMotionMaster()->Clear(MOTION_SLOT_IDLE); + pet->GetMotionMaster()->MoveIdle(); + pet->GetMotionMaster()->MovePoint(0, actionPosition); + } charmInfo->SetCommandState(COMMAND_MOVE_TO); charmInfo->SetIsCommandAttack(false); charmInfo->SetIsAtStay(true); @@ -282,141 +126,106 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe charmInfo->SetIsReturning(false); charmInfo->SaveStayPosition(); break; - default: - TC_LOG_ERROR("entities.pet", "WORLD: unknown PET flag Action %i and spellid %i.", uint32(flag), spellid); } break; - case ACT_REACTION: // 0x6 - switch (spellid) + case ACT_REACTION: + switch (actionValue) { - case REACT_PASSIVE: //passive - pet->AttackStop(); - [[fallthrough]]; - case REACT_DEFENSIVE: //recovery - case REACT_AGGRESSIVE: //activete + case REACT_PASSIVE: + case REACT_DEFENSIVE: case REACT_ASSIST: - if (pet->GetTypeId() == TYPEID_UNIT) - pet->ToCreature()->SetReactState(ReactStates(spellid)); + pet->SetReactState(static_cast(actionValue)); + break; + // case REACT_AGGRESSIVE: // no longer available since 4.x + default: break; } break; - case ACT_DISABLED: // 0x81 spell (disabled), ignore - case ACT_PASSIVE: // 0x01 - case ACT_ENABLED: // 0xC1 spell + case ACT_DISABLED: + case ACT_PASSIVE: + case ACT_ENABLED: { - Unit* unit_target = nullptr; - - if (guid2) - unit_target = ObjectAccessor::GetUnit(*_player, guid2); - - // do not cast unknown spells - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellid); - if (!spellInfo) + if (!pet->IsAlive()) { - TC_LOG_ERROR("spells.pet", "WORLD: unknown PET spell id %i", spellid); + SendPetActionFeedbackToPlayer(owner, FEEDBACK_PET_DEAD, 0); return; } - for (uint32 i = 0; i < MAX_SPELL_EFFECTS; ++i) - { - if (spellInfo->Effects[i].TargetA.GetTarget() == TARGET_UNIT_SRC_AREA_ENEMY || spellInfo->Effects[i].TargetA.GetTarget() == TARGET_UNIT_DEST_AREA_ENEMY || spellInfo->Effects[i].TargetA.GetTarget() == TARGET_DEST_DYNOBJ_ENEMY) - return; - } + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(actionValue); + if (!spellInfo) + return; - // do not cast not learned spells - if (!pet->HasSpell(spellid) || spellInfo->IsPassive()) + if (!pet->HasSpell(actionValue) || spellInfo->IsPassive()) return; - // Clear the flags as if owner clicked 'attack'. AI will reset them - // after AttackStart, even if spell failed - if (pet->GetCharmInfo()) - { - pet->GetCharmInfo()->SetIsAtStay(false); - pet->GetCharmInfo()->SetIsCommandAttack(true); - pet->GetCharmInfo()->SetIsReturning(false); - pet->GetCharmInfo()->SetIsFollowing(false); - } + Unit* spellTarget = nullptr; - Spell* spell = new Spell(pet, spellInfo, TRIGGERED_NONE); + if (targetGuid) + spellTarget = ObjectAccessor::GetUnit(*owner, targetGuid); - SpellCastResult result = spell->CheckPetCast(unit_target); - //auto turn to target unless possessed - if (result == SPELL_FAILED_UNIT_NOT_INFRONT && !pet->isPossessed() && !pet->IsVehicle()) + Spell* spell = new Spell(pet, spellInfo, TRIGGERED_NONE); + SpellCastResult result = spell->CheckPetCast(spellTarget); + + if (result == SPELL_FAILED_BAD_IMPLICIT_TARGETS) { - if (unit_target) - { - pet->SetOrientationTowards(unit_target); - if (Player* player = unit_target->ToPlayer()) - pet->SendUpdateToPlayer(player); - } - else if (Unit* unit_target2 = spell->m_targets.GetUnitTarget()) - { - pet->SetOrientationTowards(unit_target2); - if (Player* player = unit_target2->ToPlayer()) - pet->SendUpdateToPlayer(player); - } - - if (Unit* powner = pet->GetCharmerOrOwner()) - if (Player* player = powner->ToPlayer()) - pet->SendUpdateToPlayer(player); - - result = SPELL_CAST_OK; + SendPetActionFeedbackToPlayer(owner, FEEDBACK_NOTHING_TO_ATT, actionValue); + return; } if (result == SPELL_CAST_OK) { - unit_target = spell->m_targets.GetUnitTarget(); + if (pet->IsClassPet() && actionFlag == ACT_PASSIVE) + SendPetActionSoundToPlayer(owner, pet->GetGUID(), PET_TALK_SPECIAL_SPELL); + + result = spell->prepare(spell->m_targets); + if (result == SPELL_FAILED_NOPATH) + SendPetActionFeedbackToPlayer(owner, FEEBDACK_CHARGE_HAS_NO_PATH, actionValue); + else if (result != SPELL_CAST_OK) + SendPetActionFeedbackToPlayer(owner, FEEDBACK_CANT_ATT_TARGET, actionValue); + } + break; + } + default: + break; + } +} - //10% chance to play special pet attack talk, else growl - //actually this only seems to happen on special spells, fire shield for imp, torment for voidwalker, but it's stupid to check every spell - if (pet->IsPet() && (((Pet*)pet)->getPetType() == SUMMON_PET) && (pet != unit_target) && (urand(0, 100) < 10)) - pet->SendPetActionSound(PET_TALK_SPECIAL_SPELL); - else - { - pet->SendPetAIReaction(guid1); - } - - if (unit_target && !GetPlayer()->IsFriendlyTo(unit_target) && !pet->isPossessed() && !pet->IsVehicle()) - { - // This is true if pet has no target or has target but targets differs. - if (pet->GetVictim() != unit_target) - { - pet->GetMotionMaster()->Clear(); - if (CreatureAI* AI = pet->ToCreature()->AI()) - { - if (PetAI* petAI = dynamic_cast(AI)) - petAI->_AttackStart(unit_target); // force victim switch - else - AI->AttackStart(unit_target); - } - } - } +void WorldSession::HandlePetAction(WorldPackets::Pet::PetAction& packet) +{ + if (packet.PetGUID.IsEmpty() || packet.Action == 0) + return; - spell->prepare(spell->m_targets); - } - else - { - if (pet->isPossessed() || pet->IsVehicle()) /// @todo: confirm this check - Spell::SendCastResult(GetPlayer(), spellInfo, 0, result); - else - spell->SendPetCastResult(result); + uint32 actionValue = packet.Action & 0xFFFFFF; + uint8 actionFlags = (packet.Action >> 24) & 0xFF; - if (!pet->GetSpellHistory()->HasCooldown(spellid)) - pet->GetSpellHistory()->ResetCooldown(spellid, true); + NewTemporarySummon* pet = _player->GetSummonByGUID(packet.PetGUID); + if (!pet || !pet->IsPet()) + return; - spell->finish(false); - delete spell; + // There are pet summons that consist of more than a single summon (Force of Nature for example) so we make sure that all related summons perform the action + std::vector pets; + pets.push_back(pet->ToNewPet()); + for (ObjectGuid const& guid : _player->GetSummonGUIDs()) + { + NewTemporarySummon* summon = _player->GetSummonByGUID(guid); + if (!summon || !summon->IsPet() || summon == pet || summon->GetUInt32Value(UNIT_CREATED_BY_SPELL) != pet->GetUInt32Value(UNIT_CREATED_BY_SPELL)) + continue; - // reset specific flags in case of spell fail. AI will reset other flags - if (pet->GetCharmInfo()) - pet->GetCharmInfo()->SetIsCommandAttack(false); - } - break; - } - default: - TC_LOG_ERROR("entities.pet", "WORLD: unknown PET flag Action %i and spellid %i.", uint32(flag), spellid); + pets.push_back(summon->ToNewPet()); } + + for (NewPet* actionPet : pets) + HandlePetActionHelper(actionPet, _player, packet.TargetGUID, actionValue, actionFlags, packet.ActionPosition); +} + +void WorldSession::HandlePetStopAttack(WorldPackets::Pet::PetStopAttack& packet) +{ + NewPet* pet = _player->GetActivelyControlledSummon(); + if (!pet || pet->GetGUID() != packet.PetGUID || !pet->IsAlive()) + return; + + pet->AttackStop(); } void WorldSession::HandlePetNameQuery(WorldPacket& recvData) @@ -432,25 +241,22 @@ void WorldSession::HandlePetNameQuery(WorldPacket& recvData) SendPetNameQuery(petguid, petnumber); } -void WorldSession::SendPetNameQuery(ObjectGuid petguid, uint32 petnumber) +void WorldSession::SendPetNameQuery(ObjectGuid petGuid, uint32 petNumber) { - Creature* pet = ObjectAccessor::GetCreatureOrPetOrVehicle(*_player, petguid); - if (!pet) + WorldPackets::Query::QueryPetNameResponse packet; + packet.PetID = petNumber; + + NewPet* pet = _player->GetActivelyControlledSummon(); + if (!pet || pet->GetGUID() != petGuid || pet->GetUInt32Value(UNIT_FIELD_PETNUMBER) != petNumber) { - WorldPacket data(SMSG_PET_NAME_QUERY_RESPONSE, (4+1+4+1)); - data << uint32(petnumber); - data << uint8(0); - data << uint32(0); - data << uint8(0); - _player->SendDirectMessage(&data); + _player->SendDirectMessage(packet.Write()); return; } - WorldPacket data(SMSG_PET_NAME_QUERY_RESPONSE, (4+4+pet->GetName().size()+1)); - data << uint32(petnumber); - data << pet->GetName(); - data << uint32(pet->GetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP)); + packet.Timestamp = pet->GetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP); + packet.Name = pet->GetName(); + /* if (pet->IsPet() && ((Pet*)pet)->GetDeclinedNames()) { data << uint8(1); @@ -459,8 +265,9 @@ void WorldSession::SendPetNameQuery(ObjectGuid petguid, uint32 petnumber) } else data << uint8(0); + */ - _player->SendDirectMessage(&data); + _player->SendDirectMessage(packet.Write()); } bool WorldSession::CheckStableMaster(ObjectGuid guid) @@ -486,119 +293,54 @@ bool WorldSession::CheckStableMaster(ObjectGuid guid) return true; } -void WorldSession::HandlePetSetAction(WorldPacket& recvData) +void WorldSession::HandlePetSetAction(WorldPackets::Pet::PetSetAction& packet) { - TC_LOG_DEBUG("network.opcode", "WORLD: Received CMSG_PET_SET_ACTION"); - - ObjectGuid petguid; - uint8 count; - - recvData >> petguid; - - Unit* pet = ObjectAccessor::GetUnit(*_player, petguid); - - if (!pet || pet != _player->GetFirstControlled()) - { - TC_LOG_ERROR("entities.pet", "HandlePetSetAction: Unknown %s or owner (%s)", petguid.ToString().c_str(), _player->GetGUID().ToString().c_str()); + NewPet* pet = _player->GetActivelyControlledSummon(); + if (!pet || pet->GetGUID() != packet.PetGUID) return; - } - - CharmInfo* charmInfo = pet->GetCharmInfo(); - if (!charmInfo) - { - TC_LOG_ERROR("entities.pet", "WorldSession::HandlePetSetAction: object (GUID: %u TypeId: %u) is considered pet-like but doesn't have a charminfo!", pet->GetGUID().GetCounter(), pet->GetTypeId()); - return; - } - count = (recvData.size() == 24) ? 2 : 1; - - uint32 position[2]; - uint32 data[2]; - bool move_command = false; - - for (uint8 i = 0; i < count; ++i) + std::vector pets = { pet }; + for (ObjectGuid const& guid : _player->GetSummonGUIDs()) { - recvData >> position[i]; - recvData >> data[i]; + if (guid == packet.PetGUID) + continue; - uint8 act_state = UNIT_ACTION_BUTTON_TYPE(data[i]); + NewTemporarySummon* summon = _player->GetSummonByGUID(guid); + if (!summon || !summon->IsPet() || summon == pet || summon->GetUInt32Value(UNIT_CREATED_BY_SPELL) != pet->GetUInt32Value(UNIT_CREATED_BY_SPELL)) + continue; - //ignore invalid position - if (position[i] >= MAX_UNIT_ACTION_BAR_INDEX) - return; + pets.push_back(summon->ToNewPet()); + } - // in the normal case, command and reaction buttons can only be moved, not removed - // at moving count == 2, at removing count == 1 - // ignore attempt to remove command|reaction buttons (not possible at normal case) - if (act_state == ACT_COMMAND || act_state == ACT_REACTION) - { - if (count == 1) - return; + uint32 position = packet.Index; + uint32 actionData = packet.Action; - move_command = true; - } - } + uint32 spell_id = UNIT_ACTION_BUTTON_ACTION(actionData); + uint8 act_state = UNIT_ACTION_BUTTON_TYPE(actionData); - // check swap (at command->spell swap client remove spell first in another packet, so check only command move correctness) - if (move_command) + for (NewPet* petControlled : pets) { - uint8 act_state_0 = UNIT_ACTION_BUTTON_TYPE(data[0]); - if (act_state_0 == ACT_COMMAND || act_state_0 == ACT_REACTION) + CharmInfo* charmInfo = petControlled->GetCharmInfo(); + if (!charmInfo) { - uint32 spell_id_0 = UNIT_ACTION_BUTTON_ACTION(data[0]); - UnitActionBarEntry const* actionEntry_1 = charmInfo->GetActionBarEntry(position[1]); - if (!actionEntry_1 || spell_id_0 != actionEntry_1->GetAction() || - act_state_0 != actionEntry_1->GetType()) - return; + TC_LOG_ERROR("entities.pet", "WorldSession::HandlePetSetAction: object (GUID: %u TypeId: %u) is considered pet-like but doesn't have a charminfo!", petControlled->GetGUID().GetCounter(), petControlled->GetTypeId()); + continue; } - uint8 act_state_1 = UNIT_ACTION_BUTTON_TYPE(data[1]); - if (act_state_1 == ACT_COMMAND || act_state_1 == ACT_REACTION) - { - uint32 spell_id_1 = UNIT_ACTION_BUTTON_ACTION(data[1]); - UnitActionBarEntry const* actionEntry_0 = charmInfo->GetActionBarEntry(position[0]); - if (!actionEntry_0 || spell_id_1 != actionEntry_0->GetAction() || - act_state_1 != actionEntry_0->GetType()) - return; - } - } - - for (uint8 i = 0; i < count; ++i) - { - uint32 spell_id = UNIT_ACTION_BUTTON_ACTION(data[i]); - uint8 act_state = UNIT_ACTION_BUTTON_TYPE(data[i]); - - TC_LOG_DEBUG("entities.pet", "Player %s has changed pet spell action. Position: %u, Spell: %u, State: 0x%X", - _player->GetName().c_str(), position[i], spell_id, uint32(act_state)); - //if it's act for spell (en/disable/cast) and there is a spell given (0 = remove spell) which pet doesn't know, don't add - if (!((act_state == ACT_ENABLED || act_state == ACT_DISABLED || act_state == ACT_PASSIVE) && spell_id && !pet->HasSpell(spell_id))) + if (!((act_state == ACT_ENABLED || act_state == ACT_DISABLED || act_state == ACT_PASSIVE) && spell_id && !petControlled->HasSpell(spell_id))) { if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell_id)) { //sign for autocast if (act_state == ACT_ENABLED) - { - if (pet->GetTypeId() == TYPEID_UNIT && pet->IsPet()) - ((Pet*)pet)->ToggleAutocast(spellInfo, true); - else - for (Unit::ControlList::iterator itr = GetPlayer()->m_Controlled.begin(); itr != GetPlayer()->m_Controlled.end(); ++itr) - if ((*itr)->GetEntry() == pet->GetEntry()) - (*itr)->GetCharmInfo()->ToggleCreatureAutocast(spellInfo, true); - } + petControlled->ToggleAutocast(spellInfo, true); //sign for no/turn off autocast else if (act_state == ACT_DISABLED) - { - if (pet->GetTypeId() == TYPEID_UNIT && pet->IsPet()) - ((Pet*)pet)->ToggleAutocast(spellInfo, false); - else - for (Unit::ControlList::iterator itr = GetPlayer()->m_Controlled.begin(); itr != GetPlayer()->m_Controlled.end(); ++itr) - if ((*itr)->GetEntry() == pet->GetEntry()) - (*itr)->GetCharmInfo()->ToggleCreatureAutocast(spellInfo, false); - } + petControlled->ToggleAutocast(spellInfo, false); } - charmInfo->SetActionBar(position[i], spell_id, ActiveStates(act_state)); + charmInfo->SetActionBar(position, spell_id, ActiveStates(act_state)); } } } @@ -617,6 +359,9 @@ void WorldSession::HandlePetRename(WorldPacket& recvData) recvData >> name; recvData >> isdeclined; + return; + + /* Pet* pet = ObjectAccessor::GetPet(*_player, petguid); // check it! if (!pet || !pet->IsPet() || !((Pet*)pet)->IsHunterPet() || @@ -662,6 +407,7 @@ void WorldSession::HandlePetRename(WorldPacket& recvData) } } + /* CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); if (isdeclined) { @@ -687,74 +433,41 @@ void WorldSession::HandlePetRename(WorldPacket& recvData) CharacterDatabase.CommitTransaction(trans); pet->SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, uint32(GameTime::GetGameTime())); // cast can't be helped + */ } -void WorldSession::HandlePetAbandon(WorldPacket& recvData) +void WorldSession::HandlePetAbandon(WorldPackets::Pet::PetAbandon& packet) { - ObjectGuid guid; - recvData >> guid; //pet guid - TC_LOG_DEBUG("network.opcode", "WORLD: Received CMSG_PET_ABANDON %s", guid.ToString().c_str()); + if (packet.Pet.IsEmpty() || !packet.Pet.IsPet() || !_player->IsInWorld()) + return; - if (!_player->IsInWorld()) + NewPet* pet = _player->GetActivelyControlledSummon(); + if (!pet || pet->GetGUID() != packet.Pet) return; - // pet/charmed - Creature* pet = ObjectAccessor::GetCreatureOrPetOrVehicle(*_player, guid); - if (pet) - { - if (pet->IsPet()) - _player->RemovePet((Pet*)pet, PET_SAVE_AS_DELETED); - else if (pet->GetGUID() == _player->GetCharmedGUID()) - _player->StopCastingCharm(); - } + _player->AbandonPet(); } -void WorldSession::HandlePetSpellAutocastOpcode(WorldPacket& recvPacket) +void WorldSession::HandlePetSpellAutocastOpcode(WorldPackets::Pet::PetSpellAutocast& packet) { - TC_LOG_DEBUG("network.opcode", "WORLD: Received CMSG_PET_SPELL_AUTOCAST"); - ObjectGuid guid; - uint32 spellid; - uint8 state; //1 for on, 0 for off - recvPacket >> guid >> spellid >> state; - - if (!_player->GetGuardianPet() && !_player->GetCharmed()) - return; - - if (guid.IsPlayer()) - return; - - Creature* pet=ObjectAccessor::GetCreatureOrPetOrVehicle(*_player, guid); - - if (!pet || (pet != _player->GetGuardianPet() && pet != _player->GetCharmed())) - { - TC_LOG_ERROR("entities.pet", "HandlePetSpellAutocastOpcode. %s isn't pet of player %s (%s).", guid.ToString().c_str(), GetPlayer()->GetName().c_str(), GetPlayer()->GetGUID().ToString().c_str()); + if (packet.PetGUID.IsEmpty() || packet.PetGUID.IsPlayer()) return; - } - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellid); - if (!spellInfo) - { - TC_LOG_ERROR("spells.pet", "WORLD: unknown PET spell id %u", spellid); + NewPet* pet = _player->GetActivelyControlledSummon(); + if (!pet || pet->GetGUID() != packet.PetGUID) return; - } + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(packet.SpellID); // do not add not learned spells/ passive spells - if (!pet->HasSpell(spellid) || !spellInfo->IsAutocastable()) + if (!spellInfo || spellInfo->IsAutocastable() || !pet->HasSpell(spellInfo->Id)) return; CharmInfo* charmInfo = pet->GetCharmInfo(); if (!charmInfo) - { - TC_LOG_ERROR("entities.pet", "WorldSession::HandlePetSpellAutocastOpcod: object (GUID: %u TypeId: %u) is considered pet-like but doesn't have a charminfo!", pet->GetGUID().GetCounter(), pet->GetTypeId()); return; - } - - if (pet->IsPet()) - ((Pet*)pet)->ToggleAutocast(spellInfo, state != 0); - else - pet->GetCharmInfo()->ToggleCreatureAutocast(spellInfo, state != 0); - charmInfo->SetSpellAutocast(spellInfo, state != 0); + pet->ToggleAutocast(spellInfo, packet.AutocastEnabled); + charmInfo->SetSpellAutocast(spellInfo, packet.AutocastEnabled); } void WorldSession::HandlePetCastSpellOpcode(WorldPacket& recvPacket) @@ -770,18 +483,8 @@ void WorldSession::HandlePetCastSpellOpcode(WorldPacket& recvPacket) TC_LOG_DEBUG("entities.pet", "WORLD: CMSG_PET_CAST_SPELL, %s, castCount: %u, spellId %u, castFlags %u", guid.ToString().c_str(), castCount, spellId, castFlags); - // This opcode is also sent from charmed and possessed units (players and creatures) - if (!_player->GetGuardianPet() && !_player->GetCharmed()) - return; - Unit* caster = ObjectAccessor::GetUnit(*_player, guid); - if (!caster || (caster != _player->GetGuardianPet() && caster != _player->GetCharmed())) - { - TC_LOG_ERROR("entities.pet", "HandlePetCastSpellOpcode: %s isn't pet of player %s (%s).", guid.ToString().c_str(), GetPlayer()->GetName().c_str(), GetPlayer()->GetGUID().ToString().c_str()); - return; - } - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) { @@ -849,111 +552,27 @@ void WorldSession::SendPetNameInvalid(uint32 error, const std::string& name, Dec void WorldSession::HandlePetLearnTalent(WorldPacket& recvData) { - TC_LOG_DEBUG("network.opcode", "WORLD: Received CMSG_PET_LEARN_TALENT"); - ObjectGuid guid; uint32 talentId, requestedRank; recvData >> guid >> talentId >> requestedRank; - _player->LearnPetTalent(guid, talentId, requestedRank); - _player->SendTalentsInfoData(true); -} - -void WorldSession::HandleLearnPreviewTalentsPet(WorldPacket& recvData) -{ - TC_LOG_DEBUG("network.opcode", "WORLD: Received CMSG_LEARN_PREVIEW_TALENTS_PET"); - - ObjectGuid guid; - recvData >> guid; - - uint32 talentsCount; - recvData >> talentsCount; - - uint32 talentId, talentRank; - - // Client has max 19 talents, rounded up : 25 - uint32 const MaxTalentsCount = 25; - - for (uint32 i = 0; i < talentsCount && i < MaxTalentsCount; ++i) - { - recvData >> talentId >> talentRank; - - _player->LearnPetTalent(guid, talentId, talentRank); - } - - _player->SendTalentsInfoData(true); - - recvData.rfinish(); -} - -void WorldSession::UpdatePetSlot(uint32 petNumberA, uint8 oldPetSlot, uint8 newPetSlot) -{ - uint32 petNumberB = 0; - Pet* pet = _player->GetPet(); - - // first check check new PetSlot if another pet already exists there - PlayerPetData* playerPetDataA = _player->GetPlayerPetDataById(petNumberA); - PlayerPetData* playerPetDataB = _player->GetPlayerPetDataBySlot(newPetSlot); - - CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); - - // If Slot is already in use - if (playerPetDataB) - { - petNumberB = playerPetDataB->PetId; - - // Check if current pet is the pet to swap - if (pet && pet->GetCharmInfo()->GetPetNumber() == petNumberB) - { - pet->SetSlot(oldPetSlot); - _player->RemovePet(pet, PET_SAVE_DISMISS); - } - else - { - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_SLOT); - stmt->setUInt32(0, oldPetSlot); - stmt->setUInt64(1, _player->GetGUID().GetCounter()); - stmt->setUInt32(2, newPetSlot); - trans->Append(stmt); - - } - - playerPetDataB->Slot = oldPetSlot; - } - - // Check if current pet is the pet to swap - if (pet && pet->GetCharmInfo()->GetPetNumber() == petNumberA) - { - pet->SetSlot(newPetSlot); - _player->RemovePet(pet, PET_SAVE_DISMISS); - } - else - { - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID); - stmt->setUInt32(0, newPetSlot); - stmt->setUInt64(1, _player->GetGUID().GetCounter()); - stmt->setUInt32(2, petNumberA); - trans->Append(stmt); - } - - playerPetDataA->Slot = newPetSlot; - - CharacterDatabase.CommitTransaction(trans); - - SendPetSlotUpdated(petNumberA, newPetSlot, petNumberB, oldPetSlot); - SendStableResult(STABLE_SUCCESS_STABLE); + if (NewPet* pet = _player->GetActivelyControlledSummon()) + if (pet->GetGUID() == guid) + pet->LearnTalent(talentId, requestedRank); } -void WorldSession::SendPetSlotUpdated(int32 petNumberA, int32 petSlotA, int32 petNumberB, int32 petSlotB) +void WorldSession::HandleLearnPreviewTalentsPet(WorldPackets::Talent::LeanPreviewTalentsPet& packet) { - WorldPacket data(SMSG_PET_SLOT_UPDATED, 4 + 4 + 4 + 4); + NewPet* pet = _player->GetActivelyControlledSummon(); + if (!pet || pet->GetGUID() != packet.PetGUID) + return; - data << uint32(petNumberA); - data << uint32(petSlotA); - data << uint32(petNumberB); - data << uint32(petSlotB); + for (WorldPackets::Talent::TalentInfo& talent : packet.Talents) + pet->LearnTalent(talent.TalentID, talent.Rank); - SendPacket(&data); + pet->SendTalentsInfoUpdateToSummoner(); + // After learning talents, we have to send out another pet spells message to show the added spells + _player->SendPetSpellsMessage(pet); } void WorldSession::SendPetAdded(int32 petSlot, int32 petNumber, int32 creatureID, int32 level, std::string name) @@ -979,8 +598,8 @@ void WorldSession::HandleRequestPetInfoOpcode(WorldPacket& /*recvData*/) * You're possessing a unit (Player::PossessSpellInitialize) */ - if (_player->GetPet() && _player->CanControlPet()) - _player->PetSpellInitialize(); + if (NewPet* pet = _player->GetActivelyControlledSummon()) + _player->SendPetSpellsMessage(pet); if (Unit* charm = _player->GetCharmed()) { @@ -992,3 +611,71 @@ void WorldSession::HandleRequestPetInfoOpcode(WorldPacket& /*recvData*/) _player->CharmSpellInitialize(); } } + +void WorldSession::HandleListStabledPetsOpcode(WorldPackets::Pet::CPetStableList& packet) +{ + if (!CheckStableMaster(packet.StableMaster)) + return; + + // remove fake death + if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) + GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); + + // remove mounts this fix bug where getting pet from stable while mounted deletes pet. + if (GetPlayer()->IsMounted()) + GetPlayer()->RemoveAurasByType(SPELL_AURA_MOUNTED); + + SendPetStableList(packet.StableMaster); +} + +void WorldSession::SendPetStableList(ObjectGuid stableMasterGuid) +{ + WorldPackets::Pet::SPetStableList packet; + packet.StableMaster = stableMasterGuid; + packet.StableSlots = PET_SLOT_INACTIVE_STABLE_SLOTS; + + for (auto const& pair : _player->GetPlayerPetDataMap()) + { + WorldPackets::Pet::PetStableInfo& stableInfo = packet.Pets.emplace_back(); + stableInfo.CreatureID = pair.second->TamedCreatureId; + stableInfo.DisplayID = pair.second->DisplayId; + stableInfo.ExperienceLevel = _player->getLevel(); + stableInfo.PetName = pair.second->Name; + stableInfo.PetNumber = pair.second->PetNumber; + stableInfo.PetSlot = pair.second->Slot; + stableInfo.PetFlags = (pair.second->Slot <= PET_SLOT_LAST_ACTIVE_SLOT ? PET_STABLE_ACTIVE : (PET_STABLE_ACTIVE | PET_STABLE_INACTIVE)); + } + + SendPacket(packet.Write()); +} + +void WorldSession::SendStableResult(PetStableResultCode result) +{ + WorldPackets::Pet::PetStableResult packet; + packet.Result = AsUnderlyingType(result); + SendPacket(packet.Write()); +} + +void WorldSession::SendPetSlotUpdated(int32 petNumberA, int32 petSlotA, int32 petNumberB, int32 petSlotB) +{ + WorldPackets::Pet::PetSlotUpdated packet; + packet.PetNumberA = petNumberA; + packet.PetSlotA = petSlotA; + packet.PetNumberB = petNumberB; + packet.PetSlotB = petSlotB; + + SendPacket(packet.Write()); +} + +void WorldSession::HandleSetPetSlot(WorldPackets::Pet::SetPetSlot& packet) +{ + if (!CheckStableMaster(packet.StableMaster) || packet.DestSlot >= PET_SLOT_LAST_STABLE_SLOT) + return; + + _player->SetPetSlot(packet.PetNumber, packet.DestSlot); +} + +void WorldSession::HandleStableRevivePet(WorldPacket &/* recvData */) +{ + TC_LOG_DEBUG("network", "HandleStableRevivePet: Not implemented"); +} diff --git a/src/server/game/Handlers/SpellHandler.cpp b/src/server/game/Handlers/SpellHandler.cpp index f92da7b84ff..51911927941 100644 --- a/src/server/game/Handlers/SpellHandler.cpp +++ b/src/server/game/Handlers/SpellHandler.cpp @@ -19,6 +19,7 @@ #include "Archaeology.h" #include "Common.h" #include "Config.h" +#include "Creature.h" #include "DatabaseEnv.h" #include "DBCStores.h" #include "GameClient.h" @@ -39,7 +40,9 @@ #include "SpellCastRequest.h" #include "SpellMgr.h" #include "SpellPackets.h" -#include "Totem.h" +#include "TemporarySummon.h" +#include "NewTemporarySummon.h" +#include "TotemPackets.h" #include "TotemPackets.h" #include "World.h" #include "WorldPacket.h" @@ -367,12 +370,6 @@ void WorldSession::HandlePetCancelAuraOpcode(WorldPacket& recvPacket) return; } - if (pet != GetPlayer()->GetGuardianPet() && pet != GetPlayer()->GetCharmed()) - { - TC_LOG_ERROR("network", "HandlePetCancelAura: %s is not a pet of player '%s'", guid.ToString().c_str(), GetPlayer()->GetName().c_str()); - return; - } - if (!pet->IsAlive()) { pet->SendPetActionFeedback(FEEDBACK_PET_DEAD); @@ -423,15 +420,15 @@ void WorldSession::HandleTotemDestroyed(WorldPackets::Totem::TotemDestroyed& pac if (_player->IsCharming()) return; - if (packet.Slot +1 >= MAX_TOTEM_SLOT) + SummonPropertiesSlot slot = SummonPropertiesSlot(packet.Slot + 1); + if (slot < SummonPropertiesSlot::Totem1 || slot > SummonPropertiesSlot::Totem4) return; - if (!_player->m_SummonSlot[packet.Slot + 1]) + NewTemporarySummon* totem = _player->GetSummonInSlot(slot); + if (!totem || totem->GetGUID() != packet.TotemGUID) return; - Creature* totem = ObjectAccessor::GetCreature(*GetPlayer(), _player->m_SummonSlot[packet.Slot + 1]); - if (totem && totem->IsTotem() && totem->GetGUID() == packet.TotemGUID) - totem->ToTotem()->UnSummon(); + totem->Unsummon(); } void WorldSession::HandleSelfResOpcode(WorldPacket& /*recvData*/) diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index 7e299fb7512..991fd7a77f8 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -40,6 +40,7 @@ #include "ObjectMgr.h" #include "OutdoorPvPMgr.h" #include "Pet.h" +#include "NewPet.h" #include "PoolMgr.h" #include "PhasingHandler.h" #include "ScriptMgr.h" @@ -1150,16 +1151,7 @@ void Map::MoveAllCreaturesInMoveList() #ifdef TRINITY_DEBUG TC_LOG_DEBUG("maps", "Creature (GUID: %u Entry: %u) cannot be move to unloaded respawn grid.", c->GetGUID().GetCounter(), c->GetEntry()); #endif - //AddObjectToRemoveList(Pet*) should only be called in Pet::Remove - //This may happen when a player just logs in and a pet moves to a nearby unloaded cell - //To avoid this, we can load nearby cells when player log in - //But this check is always needed to ensure safety - /// @todo pets will disappear if this is outside CreatureRespawnRelocation - //need to check why pet is frequently relocated to an unloaded cell - if (c->IsPet()) - ((Pet*)c)->Remove(PET_SAVE_DISMISS, true); - else - AddObjectToRemoveList(c); + AddObjectToRemoveList(c); } } } @@ -3191,11 +3183,6 @@ GameObject* Map::GetGameObject(ObjectGuid const& guid) return _objectsStore.Find(guid); } -Pet* Map::GetPet(ObjectGuid const& guid) -{ - return _objectsStore.Find(guid); -} - Transport* Map::GetTransport(ObjectGuid const& guid) { if (!guid.IsMOTransport()) diff --git a/src/server/game/Maps/Map.h b/src/server/game/Maps/Map.h index 1ce522d517c..f7165109277 100644 --- a/src/server/game/Maps/Map.h +++ b/src/server/game/Maps/Map.h @@ -48,10 +48,12 @@ class InstanceMap; class InstanceSave; class InstanceScript; class Object; +class NewPet; class PhaseShift; class Player; class SpawnedPoolData; class TempSummon; +class NewTemporarySummon; class TerrainInfo; class Transport; class Unit; @@ -149,17 +151,18 @@ struct TC_GAME_API SummonCreatureExtraArgs public: SummonCreatureExtraArgs() { } - SummonCreatureExtraArgs& SetSummonDuration(uint32 duration) { SummonDuration = duration; return *this; } + SummonCreatureExtraArgs& SetSummonDuration(int32 duration) { SummonDuration = duration; return *this; } SummonPropertiesEntry const* SummonProperties = nullptr; Unit* Summoner = nullptr; - uint32 SummonDuration = 0; + int32 SummonDuration = 0; uint32 SummonSpellId = 0; uint32 VehicleRecID = 0; uint32 SummonHealth = 0; uint32 RideSpell = 0; uint8 SeatNumber = 0; uint8 CreatureLevel = 0; + uint8 MaxSummons = 0; ObjectGuid PrivateObjectOwner; }; @@ -360,6 +363,8 @@ class TC_GAME_API Map : public GridRefManager void UpdateIteratorBack(Player* player); TempSummon* SummonCreature(uint32 entry, Position const& pos, SummonCreatureExtraArgs const& summonArgs = { }); + NewTemporarySummon* SummonCreatureNew(uint32 entry, Position const& pos, SummonCreatureExtraArgs const& summonArgs = { }); + void SummonCreatureGroup(uint8 group, std::list* list = nullptr); Player* GetPlayer(ObjectGuid const& guid); AreaTrigger* GetAreaTrigger(ObjectGuid const& guid); @@ -381,7 +386,6 @@ class TC_GAME_API Map : public GridRefManager return nullptr; } } - Pet* GetPet(ObjectGuid const& guid); Transport* GetTransport(ObjectGuid const& guid); MapStoredObjectTypesContainer& GetObjectsStore() { return _objectsStore; } diff --git a/src/server/game/Maps/MapScripts.cpp b/src/server/game/Maps/MapScripts.cpp index 7d31d49becd..85c70b50a49 100644 --- a/src/server/game/Maps/MapScripts.cpp +++ b/src/server/game/Maps/MapScripts.cpp @@ -24,7 +24,7 @@ #include "MapManager.h" #include "MotionMaster.h" #include "ObjectMgr.h" -#include "Pet.h" +#include "NewPet.h" #include "Transport.h" #include "WaypointManager.h" #include "World.h" @@ -298,10 +298,8 @@ void Map::ScriptsProcess() break; case HighGuid::Unit: case HighGuid::Vehicle: - source = GetCreature(step.sourceGUID); - break; case HighGuid::Pet: - source = GetPet(step.sourceGUID); + source = GetCreature(step.sourceGUID); break; case HighGuid::Player: source = GetPlayer(step.sourceGUID); @@ -330,10 +328,8 @@ void Map::ScriptsProcess() { case HighGuid::Unit: case HighGuid::Vehicle: - target = GetCreature(step.targetGUID); - break; case HighGuid::Pet: - target = GetPet(step.targetGUID); + target = GetCreature(step.targetGUID); break; case HighGuid::Player: // empty GUID case also target = GetPlayer(step.targetGUID); diff --git a/src/server/game/Phasing/PhasingHandler.cpp b/src/server/game/Phasing/PhasingHandler.cpp index 84bf8db57f5..47676193ade 100644 --- a/src/server/game/Phasing/PhasingHandler.cpp +++ b/src/server/game/Phasing/PhasingHandler.cpp @@ -52,6 +52,7 @@ inline PhaseFlags GetPhaseFlags(uint32 phaseId) template inline void ForAllControlled(Unit* unit, Func&& func) { + /* for (Unit* controlled : unit->m_Controlled) if (controlled->GetTypeId() != TYPEID_PLAYER) func(controlled); @@ -60,6 +61,7 @@ inline void ForAllControlled(Unit* unit, Func&& func) if (!unit->m_SummonSlot[i].IsEmpty()) if (Creature* summon = ObjectAccessor::GetCreature(*unit, unit->m_SummonSlot[i])) func(summon); + */ } } diff --git a/src/server/game/Server/Packets/AllPackets.h b/src/server/game/Server/Packets/AllPackets.h index af6b2ae3bcb..2b9736fe63b 100644 --- a/src/server/game/Server/Packets/AllPackets.h +++ b/src/server/game/Server/Packets/AllPackets.h @@ -42,6 +42,7 @@ #include "ReforgePackets.h" #include "SpellPackets.h" #include "SystemPackets.h" +#include "TalentPackets.h" #include "TicketPackets.h" #include "TotemPackets.h" #include "TradePackets.h" diff --git a/src/server/game/Server/Packets/CharacterPackets.cpp b/src/server/game/Server/Packets/CharacterPackets.cpp index 4d64e561789..14cbcfdd419 100644 --- a/src/server/game/Server/Packets/CharacterPackets.cpp +++ b/src/server/game/Server/Packets/CharacterPackets.cpp @@ -28,8 +28,8 @@ WorldPackets::Character::EnumCharactersResult::CharacterInfo::CharacterInfo(Fiel // SELECT characters.guid, characters.name, characters.race, characters.class, characters.gender, characters.skin, characters.face, characters.hairStyle, // 8 9 10 11 12 13 14 15 // characters.hairColor, characters.facialStyle, characters.level, characters.zone, characters.map, characters.position_x, characters.position_y, characters.position_z, - // 16 17 18 19 20 21 22 - // guild_member.guildid, characters.playerFlags, characters.at_login, character_pet.entry, character_pet.modelid, character_pet.level, characters.data, + // 16 17 18 19 20 21 22 + // guild_member.guildid, characters.playerFlags, characters.at_login, character_pet.CreatureId, character_pet.TamedCreatureId character_pet.DisplayId, characters.data, // 23 24 25 // character_banned.guid, characters.slot, character_declinedname.genitive @@ -87,10 +87,13 @@ WorldPackets::Character::EnumCharactersResult::CharacterInfo::CharacterInfo(Fiel // show pet at selection character in character list only for non-ghost character if (!(playerFlags & PLAYER_FLAGS_GHOST) && (ClassID == CLASS_WARLOCK || ClassID == CLASS_HUNTER || ClassID == CLASS_DEATH_KNIGHT)) { - if (CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(fields[19].GetUInt32())) + uint32 creatureId = fields[19].GetUInt32(); + uint32 tamedCreatureId = fields[20].GetUInt32(); + + if (CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(creatureId != 0 ? creatureId : tamedCreatureId)) { - PetCreatureDisplayID = fields[20].GetUInt32(); - PetExperienceLevel = fields[21].GetUInt16(); + PetCreatureDisplayID = fields[21].GetUInt32(); + PetExperienceLevel = ExperienceLevel; PetCreatureFamilyID = creatureInfo->family; } } diff --git a/src/server/game/Server/Packets/PartyPackets.cpp b/src/server/game/Server/Packets/PartyPackets.cpp index 841ca66bd9a..caa27e1c081 100644 --- a/src/server/game/Server/Packets/PartyPackets.cpp +++ b/src/server/game/Server/Packets/PartyPackets.cpp @@ -44,10 +44,12 @@ void WorldPackets::Party::PartyMemberState::Initialize(Player const* player) if (player->GetPowerType() != POWER_MANA) ChangeMask |= GROUP_UPDATE_FLAG_POWER_TYPE; + /* if (player->GetPet()) ChangeMask |= GROUP_UPDATE_FLAG_PET_GUID | GROUP_UPDATE_FLAG_PET_NAME | GROUP_UPDATE_FLAG_PET_MODEL_ID | GROUP_UPDATE_FLAG_PET_CUR_HP | GROUP_UPDATE_FLAG_PET_MAX_HP | GROUP_UPDATE_FLAG_PET_POWER_TYPE | GROUP_UPDATE_FLAG_PET_CUR_POWER | GROUP_UPDATE_FLAG_PET_MAX_POWER; + */ if (player->GetVehicle()) ChangeMask |= GROUP_UPDATE_FLAG_VEHICLE_SEAT; @@ -144,6 +146,7 @@ void WorldPackets::Party::PartyMemberState::Initialize(Player const* player) PhasingHandler::FillPartyMemberPhase(&MemberStats.Phases, player->GetPhaseShift()); // Pet + /* if (player->GetPet()) { ::Pet* pet = player->GetPet(); @@ -190,6 +193,7 @@ void WorldPackets::Party::PartyMemberState::Initialize(Player const* player) MemberStats.PetStats->Auras.push_back(aura); } } + */ } ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Party::PartyMemberPhaseStates const& phases) diff --git a/src/server/game/Server/Packets/PetPackets.cpp b/src/server/game/Server/Packets/PetPackets.cpp index 3469659fcbd..06dc46fcdc5 100644 --- a/src/server/game/Server/Packets/PetPackets.cpp +++ b/src/server/game/Server/Packets/PetPackets.cpp @@ -80,3 +80,157 @@ WorldPacket const* WorldPackets::Pet::PetAdded::Write() return &_worldPacket; } + +WorldPacket const* WorldPackets::Pet::PetSpellsMessage::Write() +{ + _worldPacket << PetGUID; + if (PetGUID.IsEmpty()) + return &_worldPacket; + + _worldPacket << uint16(_CreatureFamily); + _worldPacket << uint32(TimeLimit); + _worldPacket << uint8(ReactState); + _worldPacket << uint8(CommandState); + _worldPacket << uint16(Flag); + + for (uint32 button : ActionButtons) + _worldPacket << uint32(button); + + _worldPacket << uint8(Actions.size()); + for (uint32 action : Actions) + _worldPacket << uint32(action); + + _worldPacket << uint8(Cooldowns.size()); + for (PetSpellCooldown const& cooldown : Cooldowns) + { + _worldPacket << uint32(cooldown.SpellID); + _worldPacket << uint16(cooldown.Category); + _worldPacket << int32(cooldown.Duration); + _worldPacket << int32(cooldown.CategoryDuration); + } + + return &_worldPacket; +} + +void WorldPackets::Pet::PetAction::Read() +{ + _worldPacket >> PetGUID; + _worldPacket >> Action; + _worldPacket >> TargetGUID; + _worldPacket >> ActionPosition; +} + +void WorldPackets::Pet::DismissCritter::Read() +{ + _worldPacket >> CritterGUID; +} + +WorldPacket const* WorldPackets::Pet::PetLearnedSpell::Write() +{ + _worldPacket << uint32(SpellID); + + return &_worldPacket; +} + +WorldPacket const* WorldPackets::Pet::PetUnlearnedSpell::Write() +{ + _worldPacket << uint32(SpellID); + + return &_worldPacket; +} + +WorldPacket const* WorldPackets::Pet::SPetStableList::Write() +{ + _worldPacket << StableMaster; + _worldPacket << uint8(Pets.size()); + _worldPacket << uint8(StableSlots); + + for (PetStableInfo const& pet : Pets) + { + _worldPacket << uint32(pet.PetSlot); + _worldPacket << uint32(pet.PetNumber); + _worldPacket << uint32(pet.CreatureID); + _worldPacket << uint32(pet.ExperienceLevel); + _worldPacket << pet.PetName; + _worldPacket << uint8(pet.PetFlags); + } + + return &_worldPacket; +} + +void WorldPackets::Pet::CPetStableList::Read() +{ + _worldPacket >> StableMaster; +} + +WorldPacket const* WorldPackets::Pet::PetStableResult::Write() +{ + _worldPacket << uint8(Result); + + return &_worldPacket; +} + +void WorldPackets::Pet::SetPetSlot::Read() +{ + _worldPacket >> PetNumber; + _worldPacket >> DestSlot; + + StableMaster[3] = _worldPacket.ReadBit(); + StableMaster[2] = _worldPacket.ReadBit(); + StableMaster[0] = _worldPacket.ReadBit(); + StableMaster[7] = _worldPacket.ReadBit(); + StableMaster[5] = _worldPacket.ReadBit(); + StableMaster[6] = _worldPacket.ReadBit(); + StableMaster[1] = _worldPacket.ReadBit(); + StableMaster[4] = _worldPacket.ReadBit(); + + _worldPacket.ReadByteSeq(StableMaster[5]); + _worldPacket.ReadByteSeq(StableMaster[3]); + _worldPacket.ReadByteSeq(StableMaster[1]); + _worldPacket.ReadByteSeq(StableMaster[7]); + _worldPacket.ReadByteSeq(StableMaster[4]); + _worldPacket.ReadByteSeq(StableMaster[0]); + _worldPacket.ReadByteSeq(StableMaster[6]); + _worldPacket.ReadByteSeq(StableMaster[2]); +} + +void WorldPackets::Pet::PetAbandon::Read() +{ + _worldPacket >> Pet; +} + +void WorldPackets::Pet::PetSetAction::Read() +{ + _worldPacket >> PetGUID; + _worldPacket >> Index; + _worldPacket >> Action; +} + +WorldPacket const* WorldPackets::Pet::PetTameFailure::Write() +{ + _worldPacket << uint8(Result); + + return &_worldPacket; +} + +void WorldPackets::Pet::PetSpellAutocast::Read() +{ + _worldPacket >> PetGUID; + _worldPacket >> SpellID; + _worldPacket >> AutocastEnabled; +} + +WorldPacket const* WorldPackets::Pet::PetSlotUpdated::Write() +{ + _worldPacket << int32(PetNumberA); + _worldPacket << int32(PetSlotA); + _worldPacket << int32(PetNumberB); + _worldPacket << int32(PetSlotB); + + return &_worldPacket; +} + +void WorldPackets::Pet::PetStopAttack::Read() +{ + _worldPacket >> PetGUID; +} diff --git a/src/server/game/Server/Packets/PetPackets.h b/src/server/game/Server/Packets/PetPackets.h index bd9f9989eff..19608396bb4 100644 --- a/src/server/game/Server/Packets/PetPackets.h +++ b/src/server/game/Server/Packets/PetPackets.h @@ -40,7 +40,7 @@ namespace WorldPackets class PetGuids final : public ServerPacket { public: - PetGuids() : ServerPacket(SMSG_PET_GUIDS, 1) { } + PetGuids() : ServerPacket(SMSG_PET_GUIDS, 4) { } WorldPacket const* Write() override; @@ -94,6 +94,199 @@ namespace WorldPackets uint32 PetNumber = 0; uint8 Flags = 0; }; + + struct PetSpellCooldown + { + int32 SpellID = 0; + int32 Duration = 0; + int32 CategoryDuration = 0; + uint16 Category = 0; + }; + + class PetSpellsMessage final : public ServerPacket + { + public: + PetSpellsMessage() : ServerPacket(SMSG_PET_SPELLS) { } + + WorldPacket const* Write() override; + + ObjectGuid PetGUID; + uint16 _CreatureFamily = 0; ///< @see enum CreatureFamily + uint32 TimeLimit = 0; + uint8 ReactState = 0; + uint8 CommandState = 0; + uint8 Flag = 0; + + std::array ActionButtons = { }; + std::vector Actions; + std::vector Cooldowns; + }; + + class PetAction final : public ClientPacket + { + public: + PetAction(WorldPacket&& packet) : ClientPacket(CMSG_PET_ACTION, std::move(packet)) { } + + void Read() override; + + ObjectGuid PetGUID; + ObjectGuid TargetGUID; + TaggedPosition ActionPosition; + uint32 Action = 0; + }; + + + class DismissCritter final : public ClientPacket + { + public: + DismissCritter(WorldPacket&& packet) : ClientPacket(CMSG_DISMISS_CRITTER, std::move(packet)) { } + + void Read() override; + + ObjectGuid CritterGUID; + }; + + class PetLearnedSpell final : public ServerPacket + { + public: + PetLearnedSpell(uint32 spellId) : ServerPacket(SMSG_PET_LEARNED_SPELL, 4), SpellID(spellId) { } + + WorldPacket const* Write() override; + + uint32 SpellID = 0; + }; + + class PetUnlearnedSpell final : public ServerPacket + { + public: + PetUnlearnedSpell(uint32 spellId) : ServerPacket(SMSG_PET_REMOVED_SPELL, 4), SpellID(spellId) { } + + WorldPacket const* Write() override; + + uint32 SpellID = 0; + }; + + struct PetStableInfo + { + uint32 PetSlot = 0; + uint32 PetNumber = 0; + uint32 CreatureID = 0; + uint32 DisplayID = 0; + uint32 ExperienceLevel = 0; + std::string PetName; + uint8 PetFlags = 0; + }; + + class SPetStableList final : public ServerPacket + { + public: + SPetStableList() : ServerPacket(OpcodeServer(MSG_LIST_STABLED_PETS)) { } + + WorldPacket const* Write() override; + + std::vector Pets; + ObjectGuid StableMaster; + uint8 StableSlots = 0; + }; + + class CPetStableList final : public ClientPacket + { + public: + CPetStableList(WorldPacket&& packet) : ClientPacket(OpcodeClient(MSG_LIST_STABLED_PETS), std::move(packet)) { } + + void Read() override; + + ObjectGuid StableMaster; + }; + + class PetStableResult final : public ServerPacket + { + public: + PetStableResult() : ServerPacket(SMSG_STABLE_RESULT, 1) { } + + WorldPacket const* Write() override; + + uint8 Result = 0; + }; + + class SetPetSlot final : public ClientPacket + { + public: + SetPetSlot(WorldPacket&& packet) : ClientPacket(CMSG_SET_PET_SLOT, std::move(packet)) { } + + void Read() override; + + ObjectGuid StableMaster; + uint32 PetNumber = 0; + uint8 DestSlot = 0; + }; + + class PetAbandon final : public ClientPacket + { + public: + PetAbandon(WorldPacket&& packet) : ClientPacket(CMSG_PET_ABANDON, std::move(packet)) { } + + void Read() override; + + ObjectGuid Pet; + }; + + class PetSetAction final : public ClientPacket + { + public: + PetSetAction(WorldPacket&& packet) : ClientPacket(CMSG_PET_SET_ACTION, std::move(packet)) { } + + void Read() override; + + ObjectGuid PetGUID; + uint32 Action = 0; + uint32 Index = 0; + }; + + class PetTameFailure final : public ServerPacket + { + public: + PetTameFailure() : ServerPacket(SMSG_PET_TAME_FAILURE, 1) { } + + WorldPacket const* Write() override; + + uint8 Result = 0; + }; + + class PetSpellAutocast final : public ClientPacket + { + public: + PetSpellAutocast(WorldPacket&& packet) : ClientPacket(CMSG_PET_SPELL_AUTOCAST, std::move(packet)) { } + + void Read() override; + + ObjectGuid PetGUID; + int32 SpellID = 0; + bool AutocastEnabled = false; + }; + + class PetSlotUpdated final : public ServerPacket + { + public: + PetSlotUpdated() : ServerPacket(SMSG_PET_SLOT_UPDATED, 4 + 4 + 4 + 4) { } + + WorldPacket const* Write() override; + + int32 PetNumberA = 0; + int32 PetSlotA = 0; + int32 PetNumberB = 0; + int32 PetSlotB = 0; + }; + + class PetStopAttack final : public ClientPacket + { + public: + PetStopAttack(WorldPacket&& packet) : ClientPacket(CMSG_PET_STOP_ATTACK, std::move(packet)) { } + + void Read() override; + + ObjectGuid PetGUID; + }; } } diff --git a/src/server/game/Server/Packets/QueryPackets.cpp b/src/server/game/Server/Packets/QueryPackets.cpp index 2772de5d823..633c6e92345 100644 --- a/src/server/game/Server/Packets/QueryPackets.cpp +++ b/src/server/game/Server/Packets/QueryPackets.cpp @@ -217,3 +217,17 @@ WorldPacket const* WorldPackets::Query::QueryPlayerNameResponse::Write() return &_worldPacket; } + +WorldPacket const* WorldPackets::Query::QueryPetNameResponse::Write() +{ + _worldPacket << uint32(PetID); + _worldPacket << Name; + _worldPacket << uint32(Timestamp); + _worldPacket << uint8(HasDeclined); + + if (HasDeclined) + for (std::string declinedName : DeclinedNames) + _worldPacket << declinedName; + + return &_worldPacket; +} diff --git a/src/server/game/Server/Packets/QueryPackets.h b/src/server/game/Server/Packets/QueryPackets.h index c2e2b22969b..aad485b4a64 100644 --- a/src/server/game/Server/Packets/QueryPackets.h +++ b/src/server/game/Server/Packets/QueryPackets.h @@ -188,6 +188,20 @@ namespace WorldPackets uint8 Result = 0; // 0 - full packet, != 0 - only guid PlayerGuidLookupData Data; }; + + class QueryPetNameResponse final : public ServerPacket + { + public: + QueryPetNameResponse() : ServerPacket(SMSG_PET_NAME_QUERY_RESPONSE) { } + + WorldPacket const* Write() override; + + uint32 PetID = 0; + uint32 Timestamp = 0; + bool HasDeclined = false; + std::array DeclinedNames = { }; + std::string Name; + }; } } diff --git a/src/server/game/Server/Packets/TalentPackets.cpp b/src/server/game/Server/Packets/TalentPackets.cpp new file mode 100644 index 00000000000..c81c8aa5640 --- /dev/null +++ b/src/server/game/Server/Packets/TalentPackets.cpp @@ -0,0 +1,51 @@ +/* + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "TalentPackets.h" + +void WorldPackets::Talent::LeanPreviewTalentsPet::Read() +{ + _worldPacket >> PetGUID; + + Talents.resize(_worldPacket.read()); + for (TalentInfo& talent : Talents) + { + _worldPacket >> talent.TalentID; + _worldPacket >> talent.Rank; + } +} + +WorldPacket const* WorldPackets::Talent::TalentInfoUpdate::Write() +{ + _worldPacket << bool(PetTalents); + _worldPacket << uint32(UnspentPoints); + if (PetTalents) + { + _worldPacket << uint8(PetTalent.size()); + for (TalentInfo const& talentData : PetTalent) + { + _worldPacket << uint32(talentData.TalentID); + _worldPacket << uint8(talentData.Rank); + } + } + else + { + + } + + return &_worldPacket; +} diff --git a/src/server/game/Server/Packets/TalentPackets.h b/src/server/game/Server/Packets/TalentPackets.h new file mode 100644 index 00000000000..20cde1b9444 --- /dev/null +++ b/src/server/game/Server/Packets/TalentPackets.h @@ -0,0 +1,66 @@ +/* + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef TalentPackets_h__ +#define TalentPackets_h__ + +#include "Packet.h" + +namespace WorldPackets +{ + namespace Talent + { + struct TalentInfo + { + uint32 TalentID = 0; + uint32 Rank = 0; + }; + + class LeanPreviewTalentsPet final : public ClientPacket + { + public: + LeanPreviewTalentsPet(WorldPacket&& packet) : ClientPacket(CMSG_LEARN_PREVIEW_TALENTS_PET, std::move(packet)) { } + + ObjectGuid PetGUID; + Array Talents; + + void Read() override; + }; + + struct TalentGroupInfo + { + int32 SpecID = 0; + uint16 GlyphIDs[6] = { }; + std::vector TalentIDs; + }; + + class TalentInfoUpdate final : public ServerPacket + { + public: + TalentInfoUpdate() : ServerPacket(SMSG_TALENTS_INFO, 4) { } + + WorldPacket const* Write() override; + + bool PetTalents = false; + uint32 UnspentPoints = 0; + std::vector TalentGroups; + std::vector PetTalent; + }; + } +} + +#endif // TalentPackets_h__ diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index d076f5c2fa0..980b815598a 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -604,7 +604,7 @@ void WorldSession::LogoutPlayer(bool save) guild->HandleMemberLogout(this); ///- Remove pet - _player->RemovePet(nullptr, PET_SAVE_LOGOUT, true); + //_player->RemovePet(nullptr, PET_SAVE_LOGOUT, true); ///- Clear whisper whitelist _player->ClearWhisperWhiteList(); diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index c810df8ab5a..5b9ae8e1427 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -198,6 +198,18 @@ namespace WorldPackets class PartyInviteResponse; } + namespace Pet + { + class DismissCritter; + class PetAction; + class CPetStableList; + class SetPetSlot; + class PetAbandon; + class PetSetAction; + class PetSpellAutocast; + class PetStopAttack; + } + namespace Quest { class QuestGiverAcceptQuest; @@ -234,6 +246,11 @@ namespace WorldPackets class UpdateMissileTrajectory; } + namespace Talent + { + class LeanPreviewTalentsPet; + } + namespace Ticket { class Complaint; @@ -280,6 +297,8 @@ enum AccountDataType PER_CHARACTER_CHAT_CACHE = 7 // 0x80 p }; +enum class PetStableResultCode : uint8; + #define NUM_ACCOUNT_DATA_TYPES 8 #define GLOBAL_CACHE_MASK 0x15 @@ -571,11 +590,10 @@ class TC_GAME_API WorldSession // Pet void SendPetNameQuery(ObjectGuid guid, uint32 petnumber); - void SendStablePet(ObjectGuid guid); - void SendStableResult(uint8 guid); - bool CheckStableMaster(ObjectGuid guid); - void UpdatePetSlot(uint32 petNumber, uint8 oldPetSlot, uint8 newPetSlot); + void SendPetStableList(ObjectGuid stableMasterGuid); + void SendStableResult(PetStableResultCode result); void SendPetSlotUpdated(int32 petNumberA, int32 petSlotA, int32 petNumberB, int32 petSlotB); + bool CheckStableMaster(ObjectGuid guid); void SendPetAdded(int32 petSlot, int32 petNumber, int32 creatureID, int32 level, std::string name); // Account Data @@ -915,8 +933,8 @@ class TC_GAME_API WorldSession void HandleSpiritHealerActivateOpcode(WorldPacket& recvPacket); void HandleNpcTextQueryOpcode(WorldPacket& recvPacket); void HandleBinderActivateOpcode(WorldPackets::NPC::Hello& packet); - void HandleListStabledPetsOpcode(WorldPacket& recvPacket); - void HandleSetPetSlot(WorldPacket& recvPacket); + void HandleListStabledPetsOpcode(WorldPackets::Pet::CPetStableList& packet); + void HandleSetPetSlot(WorldPackets::Pet::SetPetSlot& packet); void HandleStableRevivePet(WorldPacket& recvPacket); void HandleDuelAcceptedOpcode(WorldPacket& recvPacket); @@ -1059,23 +1077,22 @@ class TC_GAME_API WorldSession void HandleTutorialReset(WorldPacket& recvData); //Pet - void HandlePetAction(WorldPacket& recvData); - void HandlePetStopAttack(WorldPacket& recvData); - void HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spellid, uint16 flag, ObjectGuid guid2, float x, float y, float z); + void HandlePetAction(WorldPackets::Pet::PetAction& packet); + void HandlePetStopAttack(WorldPackets::Pet::PetStopAttack& packet); void HandlePetNameQuery(WorldPacket& recvData); - void HandlePetSetAction(WorldPacket& recvData); - void HandlePetAbandon(WorldPacket& recvData); + void HandlePetSetAction(WorldPackets::Pet::PetSetAction& packet); + void HandlePetAbandon(WorldPackets::Pet::PetAbandon& packet); void HandlePetRename(WorldPacket& recvData); void HandlePetCancelAuraOpcode(WorldPacket& recvPacket); - void HandlePetSpellAutocastOpcode(WorldPacket& recvPacket); + void HandlePetSpellAutocastOpcode(WorldPackets::Pet::PetSpellAutocast& packet); void HandlePetCastSpellOpcode(WorldPacket& recvPacket); void HandlePetLearnTalent(WorldPacket& recvPacket); - void HandleLearnPreviewTalentsPet(WorldPacket& recvPacket); + void HandleLearnPreviewTalentsPet(WorldPackets::Talent::LeanPreviewTalentsPet& packet); void HandleSetActionBarToggles(WorldPacket& recvData); void HandleTotemDestroyed(WorldPackets::Totem::TotemDestroyed& packet); - void HandleDismissCritter(WorldPacket& recvData); + void HandleDismissCritter(WorldPackets::Pet::DismissCritter& packet); //Battleground void HandleBattlemasterHelloOpcode(WorldPacket& recvData); diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index a131a1a124b..adbfbeb9aa2 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -4361,9 +4361,6 @@ void AuraEffect::HandleModDamageDone(AuraApplication const* aurApp, uint8 mode, for (uint16 i = 0; i < MAX_SPELL_SCHOOL; ++i) if (GetMiscValue() & (1 << i)) target->ApplyModUInt32Value(baseField + i, GetAmount(), apply); - - if (Guardian* pet = target->ToPlayer()->GetGuardianPet()) - pet->UpdateAttackPowerAndDamage(); } } @@ -4561,23 +4558,13 @@ void AuraEffect::HandleAuraDummy(AuraApplication const* aurApp, uint8 mode, bool break; case 34026: // kill command { - Unit* pet = target->GetGuardianPet(); - if (!pet) - break; - target->CastSpell(target, 34027, this); // set 3 stacks and 3 charges (to make all auras not disappear at once) Aura* owner_aura = target->GetAura(34027, GetCasterGUID()); - Aura* pet_aura = pet->GetAura(58914, GetCasterGUID()); if (owner_aura) { owner_aura->SetStackAmount(owner_aura->GetSpellInfo()->StackAmount); - if (pet_aura) - { - pet_aura->SetCharges(0); - pet_aura->SetStackAmount(owner_aura->GetSpellInfo()->StackAmount); - } } break; } @@ -5141,7 +5128,7 @@ void AuraEffect::HandleAuraOpenStable(AuraApplication const* aurApp, uint8 mode, return; if (apply) - target->ToPlayer()->GetSession()->SendStablePet(target->GetGUID()); + target->ToPlayer()->GetSession()->SendPetStableList(target->GetGUID()); // client auto close stable dialog at !apply aura } @@ -6100,6 +6087,7 @@ void AuraEffect::HandlePeriodicManaLeechAuraTick(Unit* target, Unit* caster) con } // Drain Mana - Mana Feed effect + /* if (caster->GetGuardianPet() && m_spellInfo->SpellFamilyName == SPELLFAMILY_WARLOCK && m_spellInfo->SpellFamilyFlags[0] & 0x00000010) { int32 manaFeedVal = 0; @@ -6115,6 +6103,7 @@ void AuraEffect::HandlePeriodicManaLeechAuraTick(Unit* target, Unit* caster) con caster->CastSpell(caster, 32554, args); } } + */ } void AuraEffect::HandleObsModPowerAuraTick(Unit* target, Unit* caster) const @@ -6343,12 +6332,14 @@ void AuraEffect::HandleRaidProcFromChargeAuraProc(AuraApplication* aurApp, ProcE { float radius = GetSpellInfo()->Effects[GetEffIndex()].CalcRadius(caster); + /* if (Unit* triggerTarget = target->GetNextRandomRaidMemberOrPet(radius)) { target->CastSpell(triggerTarget, GetId(), { this, GetCasterGUID() }); if (Aura* aura = triggerTarget->GetAura(GetId(), GetCasterGUID())) aura->SetCharges(jumps); } + */ } } @@ -6385,12 +6376,14 @@ void AuraEffect::HandleRaidProcFromChargeWithValueAuraProc(AuraApplication* aurA { float radius = GetSpellInfo()->Effects[GetEffIndex()].CalcRadius(caster); + /* if (Unit* triggerTarget = target->GetNextRandomRaidMemberOrPet(radius)) { target->CastSpell(triggerTarget, GetId(), args); if (Aura* aura = triggerTarget->GetAura(GetId(), GetCasterGUID())) aura->SetCharges(jumps); } + */ } } diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 534df9f50e9..ad809520c8b 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -41,6 +41,7 @@ #include "Opcodes.h" #include "PathGenerator.h" #include "Pet.h" +#include "NewPet.h" #include "Player.h" #include "ScriptMgr.h" #include "SharedDefines.h" @@ -1670,12 +1671,12 @@ void Spell::SelectImplicitCasterObjectTargets(SpellEffIndex effIndex, SpellImpli break; case TARGET_UNIT_PET: if (Unit* unitCaster = m_caster->ToUnit()) - target = unitCaster->GetGuardianPet(); + target = unitCaster->GetActivelyControlledSummon(); break; case TARGET_UNIT_SUMMONER: if (Unit* unitCaster = m_caster->ToUnit()) if (unitCaster->IsSummon()) - target = unitCaster->ToTempSummon()->GetSummoner(); + target = unitCaster->GetSummoner(); break; case TARGET_UNIT_VEHICLE: if (Unit* unitCaster = m_caster->ToUnit()) @@ -3538,16 +3539,6 @@ void Spell::_cast(bool skipCheck) // now that we've done the basic check, now run the scripts // should be done before the spell is actually executed sScriptMgr->OnPlayerSpellCast(playerCaster, this, skipCheck); - - // As of 3.0.2 pets begin attacking their owner's target immediately - // Let any pets know we've attacked something. Check DmgClass for harmful spells only - // This prevents spells such as Hunter's Mark from triggering pet attack - if (this->GetSpellInfo()->DmgClass != SPELL_DAMAGE_CLASS_NONE) - if (Unit* unitTarget = m_targets.GetUnitTarget()) - for (Unit* controlled : playerCaster->m_Controlled) - if (Creature* cControlled = controlled->ToCreature()) - if (CreatureAI* controlledAI = cControlled->AI()) - controlledAI->OwnerAttacked(unitTarget); } SetExecutedCurrently(true); @@ -3650,11 +3641,11 @@ void Spell::_cast(bool skipCheck) return; } - if (Unit* unitCaster = m_caster->ToUnit()) - if (m_spellInfo->HasAttribute(SPELL_ATTR1_DISMISS_PET_FIRST)) - if (Creature* pet = ObjectAccessor::GetCreature(*m_caster, unitCaster->GetPetGUID())) - pet->DespawnOrUnsummon(); - + if (m_spellInfo->HasAttribute(SPELL_ATTR1_DISMISS_PET_FIRST)) + if (Unit* unitCaster = m_caster->ToUnit()) + if (NewPet* pet = unitCaster->GetActivelyControlledSummon()) + pet->Unsummon(); + PrepareTriggersExecutedOnHit(); CallScriptOnCastHandlers(); @@ -5854,14 +5845,14 @@ SpellCastResult Spell::CheckCast(bool strict, uint32* param1 /*= nullptr*/, uint if (Unit* unitCaster = m_caster->ToUnit()) { if (m_spellInfo->HasAttribute(SPELL_ATTR2_NO_ACTIVE_PETS)) - if (!unitCaster->GetPetGUID().IsEmpty()) + if (!unitCaster->GetSummonGUID().IsEmpty()) return SPELL_FAILED_ALREADY_HAVE_PET; for (uint8 j = 0; j < MAX_SPELL_EFFECTS; ++j) { if (m_spellInfo->Effects[j].TargetA.GetTarget() == TARGET_UNIT_PET) { - if (!unitCaster->GetGuardianPet()) + if (!unitCaster->GetActivelyControlledSummon()) { if (m_triggeredByAuraSpell) // not report pet not existence for triggered spells return SPELL_FAILED_DONT_REPORT; @@ -5873,6 +5864,63 @@ SpellCastResult Spell::CheckCast(bool strict, uint32* param1 /*= nullptr*/, uint } } + // check pet taming eligibility + if (m_spellInfo->HasAttribute(SPELL_ATTR2_SPECIAL_TAMING_FLAG)) + { + Player* player = m_caster->ToPlayer(); + if (!player) + return SPELL_FAILED_DONT_REPORT; + + if (player->GetSummonGUID()) + { + player->SendTamePetFailure(PET_TAME_FAILURE_ACTIVE_SUMMON); + return SPELL_FAILED_DONT_REPORT; + } + + Optional slot = player->GetUnusedActivePetSlot(false); + if (!slot.has_value()) + { + player->SendTamePetFailure(PET_TAME_FAILURE_TOO_MANY_PETS); + return SPELL_FAILED_DONT_REPORT; + } + + slot = player->GetUnusedActivePetSlot(true); + if (!slot.has_value()) + { + player->SendTamePetFailure(PET_TAME_FAILURE_SLOT_LOCKED); + return SPELL_FAILED_DONT_REPORT; + } + + Unit* unit = m_targets.GetUnitTarget(); + if (!unit || !unit->IsCreature()) + return SPELL_FAILED_BAD_TARGETS; + + Creature* creature = unit->ToCreature(); + if (creature->IsSummon() || creature->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) + { + player->SendTamePetFailure(PET_TAME_FAILURE_CREATURE_CONTROLLED); + return SPELL_FAILED_DONT_REPORT; + } + + if (!creature->HasStaticFlag(CREATURE_STATIC_FLAG_TAMEABLE) && !creature->HasStaticFlag(CREATURE_STATIC_FLAG_3_TAMEABLE_EXOTIC)) + { + player->SendTamePetFailure(PET_TAME_FAILURE_NOT_TAMEABLE); + return SPELL_FAILED_DONT_REPORT; + } + + if (creature->HasStaticFlag(CREATURE_STATIC_FLAG_3_TAMEABLE_EXOTIC) && !player->CanTameExoticPets()) + { + player->SendTamePetFailure(PET_TAME_FAILURE_CANNOT_TAME_EXOTIC); + return SPELL_FAILED_DONT_REPORT; + } + + if (creature->getLevel() > player->getLevel()) + { + player->SendTamePetFailure(PET_TAME_FAILURE_TOO_HIGH_LEVEL); + return SPELL_FAILED_DONT_REPORT; + } + } + // Spell cast only in battleground if (m_spellInfo->HasAttribute(SPELL_ATTR3_ONLY_BATTLEGROUNDS)) if (!m_caster->GetMap()->IsBattleground()) @@ -6219,10 +6267,56 @@ SpellCastResult Spell::CheckCast(bool strict, uint32* param1 /*= nullptr*/, uint if (!unitCaster) return SPELL_FAILED_BAD_TARGETS; - Creature* pet = unitCaster->GetGuardianPet(); + Player* player = unitCaster->ToPlayer(); + + NewPet* pet = unitCaster->GetActivelyControlledSummon(); if (pet && pet->IsAlive()) + { + if (player) + { + player->SendTamePetFailure(PET_TAME_FAILURE_PET_NOT_DEAD); + return SPELL_FAILED_DONT_REPORT; + } + return SPELL_FAILED_ALREADY_HAVE_SUMMON; + } + + if (!pet && player) + { + Optional const& key = player->GetActiveClassPetDataKey(); + if (!key.has_value()) + { + player->SendTamePetFailure(PET_TAME_FAILURE_NO_PET_TO_SUMMON); + return SPELL_FAILED_DONT_REPORT; + } + + PlayerPetData* petData = player->GetPlayerPetData(key->first, key->second); + if (!petData) + { + player->SendTamePetFailure(PET_TAME_FAILURE_NO_PET_TO_SUMMON); + return SPELL_FAILED_DONT_REPORT; + } + if (petData->SavedHealth != 0) + { + player->SendTamePetFailure(PET_TAME_FAILURE_PET_NOT_DEAD); + return SPELL_FAILED_DONT_REPORT; + } + } + break; + } + case SPELL_EFFECT_DISMISS_PET: + { + Unit* unitCaster = m_caster->ToUnit(); + if (!unitCaster) + return SPELL_FAILED_BAD_TARGETS; + + NewPet* pet = unitCaster->GetActivelyControlledSummon(); + if (!pet) + return SPELL_FAILED_BAD_TARGETS; + + if (!pet->IsAlive()) + return SPELL_FAILED_TARGETS_DEAD; break; } // This is generic summon effect @@ -6232,19 +6326,21 @@ SpellCastResult Spell::CheckCast(bool strict, uint32* param1 /*= nullptr*/, uint if (!unitCaster) break; - SummonPropertiesEntry const* SummonProperties = sSummonPropertiesStore.LookupEntry(m_spellInfo->Effects[i].MiscValueB); - if (!SummonProperties) - break; - switch (SummonProperties->Control) + if (SummonPropertiesEntry const* summonProperties = sSummonPropertiesStore.LookupEntry(m_spellInfo->Effects[i].MiscValueB)) { - case SUMMON_CATEGORY_PET: - if (!m_spellInfo->HasAttribute(SPELL_ATTR1_DISMISS_PET_FIRST) && unitCaster->GetPetGUID()) - return SPELL_FAILED_ALREADY_HAVE_SUMMON; - [[fallthrough]]; // check both GetPetGUID() and GetCharmGUID for SUMMON_CATEGORY_PET*/ - case SUMMON_CATEGORY_PUPPET: - if (unitCaster->GetCharmedGUID()) - return SPELL_FAILED_ALREADY_HAVE_CHARM; - break; + switch (static_cast(summonProperties->Control)) + { + case SummonPropertiesControl::Pet: + if (!m_spellInfo->HasAttribute(SPELL_ATTR1_DISMISS_PET_FIRST) && unitCaster->GetSummonGUID()) + return SPELL_FAILED_ALREADY_HAVE_SUMMON; + [[fallthrough]]; // check both GetSummonGUID() and GetCharmGUID for SUMMON_CATEGORY_PET*/ + case SummonPropertiesControl::Possessed: + if (unitCaster->GetCharmedGUID()) + return SPELL_FAILED_ALREADY_HAVE_CHARM; + break; + default: + break; + } } break; } @@ -6254,7 +6350,7 @@ SpellCastResult Spell::CheckCast(bool strict, uint32* param1 /*= nullptr*/, uint { if (m_targets.GetUnitTarget()->GetTypeId() != TYPEID_PLAYER) return SPELL_FAILED_BAD_TARGETS; - if (!m_spellInfo->HasAttribute(SPELL_ATTR1_DISMISS_PET_FIRST) && m_targets.GetUnitTarget()->GetPetGUID()) + if (!m_spellInfo->HasAttribute(SPELL_ATTR1_DISMISS_PET_FIRST) && m_targets.GetUnitTarget()->GetSummonGUID()) return SPELL_FAILED_ALREADY_HAVE_SUMMON; } break; @@ -6265,22 +6361,22 @@ SpellCastResult Spell::CheckCast(bool strict, uint32* param1 /*= nullptr*/, uint if (!unitCaster) return SPELL_FAILED_BAD_TARGETS; - if (unitCaster->GetPetGUID()) //let warlock do a replacement summon + if (unitCaster->GetSummonGUID() && !m_spellInfo->HasAttribute(SPELL_ATTR1_DISMISS_PET_FIRST)) + return SPELL_FAILED_ALREADY_HAVE_SUMMON; + + if (unitCaster->GetCharmedGUID()) + return SPELL_FAILED_ALREADY_HAVE_CHARM; + + // Hunter Pets perform a special check + if (m_spellInfo->Effects[i].MiscValue == 0 && unitCaster->IsPlayer()) { - if (unitCaster->GetTypeId() == TYPEID_PLAYER) + Player* player = unitCaster->ToPlayer(); + if (!player->GetPlayerPetData(m_spellInfo->Effects[i].BasePoints, m_spellInfo->Effects[i].MiscValue)) { - if (strict) //starting cast, trigger pet stun (cast by pet so it doesn't attack player) - if (Pet* pet = unitCaster->ToPlayer()->GetPet()) - pet->CastSpell(pet, 32752, CastSpellExtraArgs(TRIGGERED_FULL_MASK) - .SetOriginalCaster(pet->GetGUID()) - .SetTriggeringSpell(this)); + player->SendTamePetFailure(PET_TAME_FAILURE_NO_PET_TO_SUMMON); + return SPELL_FAILED_DONT_REPORT; } - else if (!m_spellInfo->HasAttribute(SPELL_ATTR1_DISMISS_PET_FIRST)) - return SPELL_FAILED_ALREADY_HAVE_SUMMON; } - - if (unitCaster->GetCharmedGUID()) - return SPELL_FAILED_ALREADY_HAVE_CHARM; break; } case SPELL_EFFECT_SUMMON_PLAYER: @@ -6405,7 +6501,7 @@ SpellCastResult Spell::CheckCast(bool strict, uint32* param1 /*= nullptr*/, uint if (m_spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_MOD_CHARM || m_spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_MOD_POSSESS) { - if (!m_spellInfo->HasAttribute(SPELL_ATTR1_DISMISS_PET_FIRST) && unitCaster->GetPetGUID()) + if (!m_spellInfo->HasAttribute(SPELL_ATTR1_DISMISS_PET_FIRST) && unitCaster->GetSummonGUID()) return SPELL_FAILED_ALREADY_HAVE_SUMMON; if (unitCaster->GetCharmedGUID()) @@ -8749,7 +8845,7 @@ bool WorldObjectSpellTargetCheck::operator()(WorldObject* target) const case TARGET_CHECK_SUMMONED: if (!unitTarget->IsSummon()) return false; - if (unitTarget->ToTempSummon()->GetSummonerGUID() != _caster->GetGUID()) + if (unitTarget->GetSummonerGUID() != _caster->GetGUID()) return false; break; case TARGET_CHECK_THREAT: diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h index 8ab0b132bde..202eb65c04d 100644 --- a/src/server/game/Spells/Spell.h +++ b/src/server/game/Spells/Spell.h @@ -409,6 +409,7 @@ class TC_GAME_API Spell void EffectResurrectWithAura(SpellEffIndex effIndex); void EffectCreateAreaTrigger(SpellEffIndex effIndex); void EffectUpdatePlayerPhase(SpellEffIndex effIndex); + void EffectAllowControlPet(SpellEffIndex effIndex); void EffectUpdateZoneAurasAndPhases(SpellEffIndex effIndex); typedef std::unordered_set UsedSpellMods; diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 8fec5edec6f..b34887e4294 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -44,13 +44,14 @@ #include "MapManager.h" #include "MiscPackets.h" #include "MotionMaster.h" +#include "NewPet.h" #include "ObjectAccessor.h" #include "ObjectMgr.h" #include "Opcodes.h" #include "OutdoorPvPMgr.h" #include "PartyPackets.h" #include "PathGenerator.h" -#include "Pet.h" +#include "PetPackets.h" #include "PhasingHandler.h" #include "Player.h" #include "QuestDef.h" @@ -64,6 +65,7 @@ #include "SpellHistory.h" #include "SpellMgr.h" #include "TemporarySummon.h" +#include "NewTemporarySummon.h" #include "Totem.h" #include "Transport.h" #include "Unit.h" @@ -194,7 +196,7 @@ SpellEffectHandlerFn SpellEffectHandlers[TOTAL_SPELL_EFFECTS] = &Spell::EffectSkinPlayerCorpse, //116 SPELL_EFFECT_SKIN_PLAYER_CORPSE one spell: Remove Insignia, bg usage, required special corpse flags... &Spell::EffectSpiritHeal, //117 SPELL_EFFECT_SPIRIT_HEAL one spell: Spirit Heal &Spell::EffectSkill, //118 SPELL_EFFECT_SKILL professions and more - &Spell::EffectUnused , //119 SPELL_EFFECT_APPLY_AREA_AURA_PET + &Spell::EffectUnused, //119 SPELL_EFFECT_APPLY_AREA_AURA_PET &Spell::EffectUnused, //120 SPELL_EFFECT_TELEPORT_GRAVEYARD one spell: Graveyard Teleport Test &Spell::EffectWeaponDmg, //121 SPELL_EFFECT_NORMALIZED_WEAPON_DMG &Spell::EffectUnused, //122 SPELL_EFFECT_122 unused @@ -243,7 +245,7 @@ SpellEffectHandlerFn SpellEffectHandlers[TOTAL_SPELL_EFFECTS] = &Spell::EffectDamageFromMaxHealthPCT, //165 SPELL_EFFECT_DAMAGE_FROM_MAX_HEALTH_PCT &Spell::EffectGiveCurrency, //166 SPELL_EFFECT_GIVE_CURRENCY &Spell::EffectUpdatePlayerPhase, //167 SPELL_EFFECT_UPDATE_PLAYER_PHASE - &Spell::EffectNULL, //168 SPELL_EFFECT_168 + &Spell::EffectAllowControlPet, //168 SPELL_EFFECT_ALLOW_CONTROL_PET &Spell::EffectNULL, //169 SPELL_EFFECT_DESTROY_ITEM &Spell::EffectUpdateZoneAurasAndPhases, //170 SPELL_EFFECT_UPDATE_ZONE_AURAS_AND_PHASES &Spell::EffectSummonPersonalGameObject, //171 SPELL_EFFECT_SUMMON_PERSONAL_GAMEOBJECT @@ -1942,13 +1944,13 @@ void Spell::EffectSummonType(SpellEffIndex effIndex) ObjectGuid privateObjectOwner = [&]() { - if (!(properties->Flags & (SUMMON_PROP_FLAG_PERSONAL_SPAWN | SUMMON_PROP_FLAG_PERSONAL_GROUP_SPAWN))) + if (!(properties->GetFlags().HasFlag(SummonPropertiesFlags::OnlyVisibleToSummoner | SummonPropertiesFlags::OnlyVisibleToSummonerGroup))) return ObjectGuid::Empty; if (caster->IsPrivateObject()) return caster->GetPrivateObjectOwner(); - if (properties->Flags & SUMMON_PROP_FLAG_PERSONAL_GROUP_SPAWN) + if (properties->GetFlags().HasFlag(SummonPropertiesFlags::OnlyVisibleToSummonerGroup)) if (caster->IsPlayer() && caster->ToPlayer()->GetGroup()) return caster->ToPlayer()->GetGroup()->GetGUID(); @@ -1997,7 +1999,8 @@ void Spell::EffectSummonType(SpellEffIndex effIndex) case SummonPropertiesParamType::CreatureLevel: extraArgs.CreatureLevel = damage; break; - case SummonPropertiesParamType::MaxSummons: // @todo: handle and research (number of allowed units in one summon slot?) + case SummonPropertiesParamType::MaxSummons: // restricts the amount of totem slots that may be checked for SummonPropertiesSlot::AnyAvailableTotem + extraArgs.MaxSummons = damage; break; case SummonPropertiesParamType::NumUnitsMax: // Fails if less than 1 if (damage <= 0) @@ -2019,7 +2022,7 @@ void Spell::EffectSummonType(SpellEffIndex effIndex) dest = caster->GetRandomPoint(*destTarget, radius); } - if (TempSummon* summon = caster->GetMap()->SummonCreature(entry, *destTarget, extraArgs)) + if (NewTemporarySummon* summon = caster->GetMap()->SummonCreatureNew(entry, *destTarget, extraArgs)) { ExecuteLogEffectSummonObject(effIndex, summon); @@ -2530,62 +2533,27 @@ void Spell::EffectTameCreature(SpellEffIndex /*effIndex*/) if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET) return; - if (!unitCaster || unitCaster->GetPetGUID()) + if (!unitCaster || !unitCaster->IsPlayer() || unitCaster->GetSummonGUID() || unitCaster->getClass() != CLASS_HUNTER) return; - if (!unitTarget) + if (!unitTarget || !unitTarget->IsCreature() || unitTarget->IsSummon()) return; - if (unitTarget->GetTypeId() != TYPEID_UNIT) - return; - - Creature* creatureTarget = unitTarget->ToCreature(); - - if (creatureTarget->IsPet()) - return; + Creature* creature = unitTarget->ToCreature(); + Player* player = unitCaster->ToPlayer(); - if (unitCaster->getClass() != CLASS_HUNTER) + Optional slot = player->GetUnusedActivePetSlot(); + if (!slot.has_value()) return; - // cast finish successfully - //SendChannelUpdate(0); - finish(); - - Pet* pet = unitCaster->CreateTamedPetFrom(creatureTarget, m_spellInfo->Id); - if (!pet) // in very specific state like near world end/etc. + if (!player->CreatePlayerPetData(*slot, 0, creature->GetEntry())) return; - uint8 level = unitCaster->getLevel(); - - // prepare visual effect for levelup - pet->SetUInt32Value(UNIT_FIELD_LEVEL, level - 1); + NewPet* pet = player->SummonPet(0, *slot, 0, true, creature->GetPosition()); + player->GetSession()->SendPetAdded(*slot, pet->GetCharmInfo()->GetPetNumber(), creature->GetEntry(), pet->getLevel(), pet->GetName()); - // add to world - pet->GetMap()->AddToMap(pet->ToCreature()); - - // caster have pet now - unitCaster->SetMinion(pet, true); - - pet->InitTalentForLevel(); - if (unitCaster->GetTypeId() == TYPEID_PLAYER) - { - pet->SavePetToDB(PET_SAVE_NEW_PET); - if (pet->GetSlot() <= PET_SLOT_LAST_ACTIVE_SLOT) - { - unitCaster->ToPlayer()->GetSession()->SendPetAdded(pet->GetSlot(), pet->GetCharmInfo()->GetPetNumber(), creatureTarget->GetEntry(), creatureTarget->getLevel(), pet->GetName()); - unitCaster->ToPlayer()->PetSpellInitialize(); - } - else - { - pet->Remove(PET_SAVE_AS_DELETED); - return; - } - } - // despawn tame target - creatureTarget->DespawnOrUnsummon(); - - // visual effect for levelup - pet->SetUInt32Value(UNIT_FIELD_LEVEL, level); + // Officially, the tamed creature itself should become the pet and the whole summon API should be a layer on top of that. But since we don't support that (yet), we gotta create a new creature. + creature->DespawnOrUnsummon(); } void Spell::EffectSummonPet(SpellEffIndex effIndex) @@ -2593,95 +2561,85 @@ void Spell::EffectSummonPet(SpellEffIndex effIndex) if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT) return; - Player* owner = nullptr; - if (unitCaster) - { - owner = unitCaster->ToPlayer(); - if (!owner && unitCaster->IsTotem()) - owner = unitCaster->GetCharmerOrOwnerPlayerOrPlayerItself(); - } + int32 petCreatureId = m_spellInfo->Effects[effIndex].MiscValue; + int32 petSlotIndex = m_spellInfo->Effects[effIndex].BasePoints; - // SUMMON_PET SummonPet's entries are at MiscValue, HunterPetSlot at BasePoints - uint32 petentry = (m_spellInfo->Effects[effIndex].MiscValue == 0 && m_spellInfo->Effects[effIndex].BasePoints <= PET_SLOT_LAST_ACTIVE_SLOT) ? - m_spellInfo->Effects[effIndex].BasePoints : m_spellInfo->Effects[effIndex].MiscValue; + if (!unitCaster) + return; - PetType petType = (m_spellInfo->Effects[effIndex].MiscValue == 0 && m_spellInfo->Effects[effIndex].BasePoints <= PET_SLOT_LAST_ACTIVE_SLOT) ? HUNTER_PET : SUMMON_PET; + bool isClassPet = m_spellInfo->SpellFamilyName != SPELLFAMILY_GENERIC || petCreatureId == 0; - // Pet is summoned by a npc - if (!owner) + // Hunter pet handling + if (petCreatureId == 0 && unitCaster->IsPlayer()) { - if (SummonPropertiesEntry const* properties = sSummonPropertiesStore.LookupEntry(67)) + Player* player = unitCaster->ToPlayer(); + Optional const& key = player->GetActiveClassPetDataKey(); + if (!key.has_value()) { - SummonCreatureExtraArgs extraArgs; - extraArgs.SummonProperties = properties; - extraArgs.SummonDuration = m_spellInfo->GetDuration(); - extraArgs.Summoner = m_originalCaster; - extraArgs.SummonSpellId = m_spellInfo->Id; - - if (TempSummon* summon = m_caster->GetMap()->SummonCreature(petentry, *destTarget, extraArgs)) - ExecuteLogEffectSummonObject(effIndex, summon); + // We don't have an active pet right now. Try to find a dead pet and mark it as active instead. + for (uint8 i = 0; i <= PET_SLOT_LAST_ACTIVE_SLOT; ++i) + { + if (PlayerPetData* petData = player->GetPlayerPetData(i, 0)) + { + if (petData->SavedHealth == 0) + { + player->SetActiveClassPetDataKey(std::make_pair(petData->Slot, 0)); + break; + } + } + } } - return; - } - Pet* OldSummon = owner->GetPet(); - - // if pet requested type already exist - if (OldSummon) - { - if (petentry == 0 || OldSummon->GetEntry() == petentry) + // We have an active summon right now. Check if we can actually summon it + if (key.has_value()) { - // pet in corpse state can't be summoned - if (OldSummon->isDead()) - return; - - ASSERT(OldSummon->GetMap() == owner->GetMap()); - - //OldSummon->GetMap()->Remove(OldSummon->ToCreature(), false); - - float px, py, pz; - owner->GetClosePoint(px, py, pz, OldSummon->GetCombatReach()); + if (PlayerPetData* petData = player->GetPlayerPetData(key->first, key->second)) + { + if (petData->TamedCreatureId) + { + CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(petData->TamedCreatureId); + if (cInfo->StaticFlags.HasFlag(CREATURE_STATIC_FLAG_3_TAMEABLE_EXOTIC) && !player->CanTameExoticPets()) + { + player->GetSession()->SendStableResult(PetStableResultCode::CannotControlExoticPets); + player->GetSpellHistory()->ResetCooldown(m_spellInfo->Id, true); + return; + } + } - OldSummon->NearTeleportTo(px, py, pz, OldSummon->GetOrientation()); - //OldSummon->Relocate(px, py, pz, OldSummon->GetOrientation()); - //OldSummon->SetMap(owner->GetMap()); - //owner->GetMap()->Add(OldSummon->ToCreature()); - if (OldSummon->getPetType() == SUMMON_PET) + if (petData->SavedHealth == 0) + { + player->SendTamePetFailure(PET_TAME_FAILURE_DEAD_PET); + player->GetSpellHistory()->ResetCooldown(m_spellInfo->Id, true); + return; + } + } + else { - OldSummon->SetHealth(OldSummon->GetMaxHealth()); - OldSummon->SetPower(OldSummon->GetPowerType(), - OldSummon->GetMaxPower(OldSummon->GetPowerType())); + player->SendTamePetFailure(PET_TAME_FAILURE_NO_PET_TO_SUMMON); + player->GetSpellHistory()->ResetCooldown(m_spellInfo->Id, true); + return; } - - if (owner->GetTypeId() == TYPEID_PLAYER && OldSummon->isControlled()) - owner->ToPlayer()->PetSpellInitialize(); - - return; } - - if (owner->GetTypeId() == TYPEID_PLAYER) - owner->ToPlayer()->RemovePet(OldSummon, (OldSummon->getPetType() == HUNTER_PET ? PET_SAVE_AS_DELETED : PET_SAVE_DISMISS), false); - else - return; - } - - float x, y, z; - owner->GetClosePoint(x, y, z, owner->GetCombatReach()); - Pet* pet = owner->SummonPet(petentry, x, y, z, owner->GetOrientation(), petType, 0); - if (!pet) - return; - - if (m_caster->GetTypeId() == TYPEID_UNIT) - { - if (m_caster->ToCreature()->IsTotem()) - pet->SetReactState(REACT_AGGRESSIVE); else - pet->SetReactState(REACT_DEFENSIVE); + { + if (PlayerPetData* petData = player->GetPlayerPetData(petSlotIndex, 0)) + { + if (petData->TamedCreatureId) + { + CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(petData->TamedCreatureId); + if (cInfo->StaticFlags.HasFlag(CREATURE_STATIC_FLAG_3_TAMEABLE_EXOTIC) && !player->CanTameExoticPets()) + { + player->GetSession()->SendStableResult(PetStableResultCode::CannotControlExoticPets); + player->GetSpellHistory()->ResetCooldown(m_spellInfo->Id, true); + return; + } + } + } + } } - pet->SetUInt32Value(UNIT_CREATED_BY_SPELL, m_spellInfo->Id); - - ExecuteLogEffectSummonObject(effIndex, pet); + if (NewPet* summon = unitCaster->SummonPet(petCreatureId, petSlotIndex, m_spellInfo->Id, isClassPet, *destTarget)) + ExecuteLogEffectSummonObject(effIndex, summon); } void Spell::EffectLearnPetSpell(SpellEffIndex effIndex) @@ -2705,9 +2663,11 @@ void Spell::EffectLearnPetSpell(SpellEffIndex effIndex) if (!learn_spellproto) return; + /* pet->learnSpell(learn_spellproto->Id); pet->SavePetToDB(PET_SAVE_CURRENT_STATE); pet->GetOwner()->PetSpellInitialize(); + */ } void Spell::EffectTaunt(SpellEffIndex /*effIndex*/) @@ -3490,41 +3450,6 @@ void Spell::EffectScriptEffect(SpellEffIndex effIndex) return; } - // Stoneclaw Totem - case 55328: // Rank 1 - case 55329: // Rank 2 - case 55330: // Rank 3 - case 55332: // Rank 4 - case 55333: // Rank 5 - case 55335: // Rank 6 - case 55278: // Rank 7 - case 58589: // Rank 8 - case 58590: // Rank 9 - case 58591: // Rank 10 - { - // Cast Absorb on totems - for (uint8 slot = SUMMON_SLOT_TOTEM_FIRE; slot < MAX_TOTEM_SLOT; ++slot) - { - if (!unitTarget->m_SummonSlot[slot]) - continue; - - Creature* totem = unitTarget->GetMap()->GetCreature(unitTarget->m_SummonSlot[slot]); - if (totem && totem->IsTotem()) - { - CastSpellExtraArgs args(TRIGGERED_FULL_MASK); - args.AddSpellMod(SPELLVALUE_BASE_POINT0, damage); - m_caster->CastSpell(totem, 55277, args); - } - } - // Glyph of Stoneclaw Totem - if (AuraEffect* aur=unitTarget->GetAuraEffect(63298, 0)) - { - CastSpellExtraArgs args(TRIGGERED_FULL_MASK); - args.AddSpellMod(SPELLVALUE_BASE_POINT0, damage* aur->GetAmount()); - m_caster->CastSpell(unitTarget, 55277, args); - } - break; - } case 45668: // Ultra-Advanced Proto-Typical Shortening Blaster { if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) @@ -4053,6 +3978,7 @@ void Spell::EffectFeedPet(SpellEffIndex effIndex) if (!pet) return; + /* if (!pet->IsAlive()) return; @@ -4068,6 +3994,7 @@ void Spell::EffectFeedPet(SpellEffIndex effIndex) CastSpellExtraArgs args(TRIGGERED_FULL_MASK); args.AddSpellMod(SPELLVALUE_BASE_POINT0, benefit); m_caster->CastSpell(pet, m_spellInfo->Effects[effIndex].TriggerSpell, args); + */ } void Spell::EffectDismissPet(SpellEffIndex effIndex) @@ -4075,13 +4002,11 @@ void Spell::EffectDismissPet(SpellEffIndex effIndex) if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET) return; - if (!unitTarget || !unitTarget->IsPet()) + if (!m_caster->IsPlayer() || !unitTarget || !unitTarget->IsPet()) return; - Pet* pet = unitTarget->ToPet(); - - ExecuteLogEffectUnsummonObject(effIndex, pet); - pet->GetOwner()->RemovePet(pet, PET_SAVE_DISMISS); + ExecuteLogEffectUnsummonObject(effIndex, unitTarget); + unitTarget->ToNewPet()->Dismiss(); } void Spell::EffectSummonObject(SpellEffIndex effIndex) @@ -4717,99 +4642,80 @@ void Spell::EffectDispelMechanic(SpellEffIndex effIndex) void Spell::EffectResurrectPet(SpellEffIndex /*effIndex*/) { - if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT) + if (effectHandleMode != SPELL_EFFECT_HANDLE_LAUNCH) return; - if (damage < 0) + if (damage < 0 || !m_targets.HasDst()) return; Player* player = m_caster->ToPlayer(); if (!player) return; - // Maybe player dismissed dead pet or pet despawned? - bool hadPet = true; - - if (!player->GetPet()) + NewPet* pet = player->GetActivelyControlledSummon(); + if (pet && !pet->IsAlive()) { - // Position passed to SummonPet is irrelevant with current implementation, - // pet will be relocated without using these coords in Pet::LoadPetData - player->SummonPet(0, 0.0f, 0.0f, 0.0f, 0.0f, SUMMON_PET, 0); - hadPet = false; - } + pet->NearTeleportTo(m_targets.GetDstPos()->GetPosition()); + pet->Relocate(m_targets.GetDstPos()->GetPosition()); // This is needed so SaveStayPosition() will get the proper coords. + pet->SetUInt32Value(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_NONE); + pet->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE); + pet->setDeathState(ALIVE); + pet->ClearUnitState(UNIT_STATE_ALL_ERASABLE); + pet->SetHealth(pet->CountPctFromMaxHealth(damage)); - // TODO: Better to fail Hunter's "Revive Pet" at cast instead of here when casting ends - Pet* pet = player->GetPet(); // Attempt to get current pet - if (!pet || pet->IsAlive()) - return; + // Reset things for when the AI to takes over + if (CharmInfo* charmInfo = pet->GetCharmInfo()) + { + if (charmInfo->HasCommandState(COMMAND_FOLLOW)) + pet->FollowTarget(player); - // If player did have a pet before reviving, teleport it - if (hadPet) - { - // Reposition the pet's corpse before reviving so as not to grab aggro - Position pos = player->GetPosition(); - player->MovePositionToFirstCollision(pos, DEFAULT_FOLLOW_DISTANCE_PET, float(M_PI_2)); - pet->NearTeleportTo(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), player->GetOrientation()); - pet->Relocate(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), player->GetOrientation()); // This is needed so SaveStayPosition() will get the proper coords. + if (charmInfo->HasCommandState(COMMAND_STAY)) + charmInfo->SaveStayPosition(); + } } - - pet->SetUInt32Value(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_NONE); - pet->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE); - pet->setDeathState(ALIVE); - pet->ClearUnitState(UNIT_STATE_ALL_ERASABLE); - pet->SetHealth(pet->CountPctFromMaxHealth(damage)); - - // Reset things for when the AI to takes over - CharmInfo *ci = pet->GetCharmInfo(); - if (ci) + else if (!pet) { - // In case the pet was at stay, we don't want it running back - ci->SaveStayPosition(); - ci->SetIsAtStay(ci->HasCommandState(COMMAND_STAY)); + Optional const& key = player->GetActiveClassPetDataKey(); + if (!key.has_value()) + return; - ci->SetIsFollowing(false); - ci->SetIsCommandAttack(false); - ci->SetIsCommandFollow(false); - ci->SetIsReturning(false); + PlayerPetData* petData = player->GetPlayerPetData(key->first, key->second); + if (petData && petData->SavedHealth == 0) + { + if (pet = player->SummonPet(key->second, key->first, m_spellInfo->Id, true, m_targets.GetDstPos()->GetPosition())) + { + pet->Relocate(m_targets.GetDstPos()->GetPosition()); // This is needed so SaveStayPosition() will get the proper coords. + pet->SetUInt32Value(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_NONE); + pet->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE); + pet->setDeathState(ALIVE); + pet->ClearUnitState(UNIT_STATE_ALL_ERASABLE); + pet->SetHealth(pet->CountPctFromMaxHealth(damage)); + } + } } - - pet->SavePetToDB(PET_SAVE_CURRENT_STATE); } +static constexpr uint32 SPELL_TOTEMIC_RECALL_ENERGIZE = 39104; void Spell::EffectDestroyAllTotems(SpellEffIndex /*effIndex*/) { - if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT) + if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT || !m_caster->IsUnit()) return; - if (!unitCaster) - return; - - int32 mana = 0; - for (uint8 slot = SUMMON_SLOT_TOTEM_FIRE; slot < MAX_TOTEM_SLOT; ++slot) + int32 refundedMana = 0; + for (uint8 i = AsUnderlyingType(SummonPropertiesSlot::Totem1); i <= AsUnderlyingType(SummonPropertiesSlot::Totem4); ++i) { - if (!unitCaster->m_SummonSlot[slot]) + NewTemporarySummon* summon = m_caster->ToUnit()->GetSummonInSlot(SummonPropertiesSlot(i)); + if (!summon) continue; - Creature* totem = m_caster->GetMap()->GetCreature(unitCaster->m_SummonSlot[slot]); - if (totem && totem->IsTotem()) - { - uint32 spell_id = totem->GetUInt32Value(UNIT_CREATED_BY_SPELL); - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell_id); - if (spellInfo) - { - mana += spellInfo->ManaCost; - mana += int32(CalculatePct(unitCaster->GetCreateMana(), spellInfo->ManaCostPercentage)); - } - totem->ToTotem()->UnSummon(); - } - } - ApplyPct(mana, damage); - if (mana) - { - CastSpellExtraArgs args(TRIGGERED_FULL_MASK); - args.AddSpellMod(SPELLVALUE_BASE_POINT0, mana); - unitCaster->CastSpell(unitCaster, 39104, args); + if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(summon->GetUInt32Value(UNIT_CREATED_BY_SPELL))) + refundedMana += CalculatePct(spellInfo->CalcPowerCost(m_caster, m_spellInfo->GetSchoolMask()), damage); + + summon->Unsummon(); } + + if (refundedMana > 0) + m_caster->CastSpell(m_caster, SPELL_TOTEMIC_RECALL_ENERGIZE, CastSpellExtraArgs(TRIGGERED_FULL_MASK).AddSpellBP0(refundedMana)); } void Spell::EffectDurabilityDamage(SpellEffIndex effIndex) @@ -5336,32 +5242,26 @@ void Spell::EffectCreateTamedPet(SpellEffIndex effIndex) if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET) return; - if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER || unitTarget->GetPetGUID() || unitTarget->getClass() != CLASS_HUNTER) + if (!unitTarget || !unitTarget->IsPlayer() || unitTarget->getClass() != CLASS_HUNTER) return; uint32 creatureEntry = m_spellInfo->Effects[effIndex].MiscValue; - Pet* pet = unitTarget->CreateTamedPetFrom(creatureEntry, m_spellInfo->Id); - if (!pet) + if (!sObjectMgr->GetCreatureTemplate(creatureEntry)) return; - // relocate - Position pos = unitTarget->GetPosition(); - unitTarget->MovePositionToFirstCollision(pos, DEFAULT_FOLLOW_DISTANCE_PET, float(M_PI_2)); - pet->Relocate(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), unitTarget->GetOrientation()); - - // add to world - pet->GetMap()->AddToMap(pet->ToCreature()); + Player* player = unitTarget->ToPlayer(); + Optional slot = player->GetUnusedActivePetSlot(); + if (!slot.has_value()) + return; - // unitTarget has pet now - unitTarget->SetMinion(pet, true); + if (!player->CreatePlayerPetData(*slot, 0, creatureEntry)) + return; - pet->InitTalentForLevel(); + Position pos = unitTarget->GetPosition(); + unitTarget->MovePositionToFirstCollision(pos, DEFAULT_FOLLOW_DISTANCE_PET, float(M_PI_2)); + NewPet* pet = player->SummonPet(0, *slot, 0, true, pos); + player->GetSession()->SendPetAdded(*slot, pet->GetCharmInfo()->GetPetNumber(), creatureEntry, pet->getLevel(), pet->GetName()); - if (unitTarget->GetTypeId() == TYPEID_PLAYER) - { - pet->SavePetToDB(PET_SAVE_NEW_PET); - unitTarget->ToPlayer()->PetSpellInitialize(); - } } void Spell::EffectDiscoverTaxi(SpellEffIndex effIndex) @@ -5439,11 +5339,13 @@ void Spell::EffectRenamePet(SpellEffIndex /*effIndex*/) if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET) return; + /* if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT || !unitTarget->IsPet() || ((Pet*)unitTarget)->getPetType() != HUNTER_PET) return; unitTarget->SetByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PET_FLAGS, UNIT_CAN_BE_RENAMED); + */ } void Spell::EffectPlayMusic(SpellEffIndex effIndex) @@ -5806,6 +5708,20 @@ void Spell::EffectUpdatePlayerPhase(SpellEffIndex /*effIndex*/) PhasingHandler::OnConditionChange(unitTarget); } +void Spell::EffectAllowControlPet(SpellEffIndex /*effIndex*/) +{ + if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET) + return; + + if (!unitTarget || !unitTarget->IsPlayer()) + return; + + Player* player = unitTarget->ToPlayer(); + player->SetCanControlClassPets(); + if (NewPet* pet = player->GetActivelyControlledSummon()) + player->SendPetSpellsMessage(pet); +} + void Spell::EffectUpdateZoneAurasAndPhases(SpellEffIndex /*effIndex*/) { if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET) diff --git a/src/server/game/Spells/SpellHistory.cpp b/src/server/game/Spells/SpellHistory.cpp index 61ee3ad4d8f..e10392458cf 100644 --- a/src/server/game/Spells/SpellHistory.cpp +++ b/src/server/game/Spells/SpellHistory.cpp @@ -22,6 +22,7 @@ #include "ObjectMgr.h" #include "Opcodes.h" #include "Pet.h" +#include "PetPackets.h" #include "Player.h" #include "Spell.h" #include "SpellInfo.h" @@ -238,6 +239,35 @@ void SpellHistory::WritePacket(WorldPacket& packet) const } } +void SpellHistory::WritePetSpellHistory(WorldPackets::Pet::PetSpellsMessage& petSpellsMessage) const +{ + Clock::time_point now = GameTime::GetGameTimeSystemPoint(); + + petSpellsMessage.Cooldowns.reserve(_spellCooldowns.size()); + for (auto const& p : _spellCooldowns) + { + WorldPackets::Pet::PetSpellCooldown petSpellCooldown; + petSpellCooldown.SpellID = p.first; + petSpellCooldown.Category = p.second.CategoryId; + + if (!p.second.OnHold) + { + Milliseconds cooldownDuration = std::chrono::duration_cast(p.second.CooldownEnd - now); + if (cooldownDuration.count() <= 0) + continue; + + petSpellCooldown.Duration = uint32(cooldownDuration.count()); + Milliseconds categoryDuration = std::chrono::duration_cast(p.second.CategoryEnd - now); + if (categoryDuration.count() > 0) + petSpellCooldown.CategoryDuration = uint32(categoryDuration.count()); + } + else + petSpellCooldown.CategoryDuration = 0x80000000; + + petSpellsMessage.Cooldowns.push_back(petSpellCooldown); + } +} + template<> void SpellHistory::WriteSpellHistoryEntries(std::vector& spellHistoryEntries) const { diff --git a/src/server/game/Spells/SpellHistory.h b/src/server/game/Spells/SpellHistory.h index 142245cc162..48212a17d52 100644 --- a/src/server/game/Spells/SpellHistory.h +++ b/src/server/game/Spells/SpellHistory.h @@ -34,6 +34,11 @@ struct SpellCategoryEntry; namespace WorldPackets { + namespace Pet + { + class PetSpellsMessage; + } + namespace Spells { struct SpellHistoryEntry; @@ -81,7 +86,8 @@ class TC_GAME_API SpellHistory void HandleCooldowns(SpellInfo const* spellInfo, uint32 itemID, Spell* spell = nullptr); bool IsReady(SpellInfo const* spellInfo, uint32 itemId = 0, bool ignoreCategoryCooldown = false) const; template - void WritePacket(WorldPacket& packet) const; + void WritePacket(WorldPacket& data) const; + void WritePetSpellHistory(WorldPackets::Pet::PetSpellsMessage& petSpellsMessage) const; template void WriteSpellHistoryEntries(std::vector& spellHistoryEntries) const; diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp index c608a87157e..5d4b930441f 100644 --- a/src/server/game/Spells/SpellInfo.cpp +++ b/src/server/game/Spells/SpellInfo.cpp @@ -1960,9 +1960,6 @@ SpellCastResult SpellInfo::CheckTarget(WorldObject const* caster, WorldObject co { if (HasAttribute(SPELL_ATTR3_ONLY_ON_PLAYER)) return SPELL_FAILED_TARGET_NOT_PLAYER; - - if (HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER_CONTROLLED_NPC) && unitTarget->IsControlledByPlayer()) - return SPELL_FAILED_TARGET_IS_PLAYER_CONTROLLED; } else if (HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER)) return SPELL_FAILED_TARGET_IS_PLAYER; @@ -1974,10 +1971,8 @@ SpellCastResult SpellInfo::CheckTarget(WorldObject const* caster, WorldObject co if (implicit && HasAttribute(SPELL_ATTR6_DO_NOT_CHAIN_TO_CROWD_CONTROLLED_TARGETS) && !unitTarget->CanFreeMove()) return SPELL_FAILED_BAD_TARGETS; - // Do not allow pet or guardian targets if the spell is a raid buff or may not target pets at all - if ((HasAttribute(SPELL_ATTR7_CONSOLIDATED_RAID_BUFF) || HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER_CONTROLLED_NPC)) && (caster->IsCreature() && !caster->ToCreature()->IsPet() && !caster->ToCreature()->IsGuardian())) - if ((unitTarget->IsPet() || unitTarget->IsGuardian())) - return SPELL_FAILED_BAD_TARGETS; + if (HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER_CONTROLLED_NPC) && unitTarget->IsCreature() && unitTarget->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) + return SPELL_FAILED_BAD_TARGETS; // checked in Unit::IsValidAttack/AssistTarget, shouldn't be checked for ENTRY targets //if (!HasAttribute(SPELL_ATTR6_CAN_TARGET_UNTARGETABLE) && target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE)) diff --git a/src/server/game/Tools/PlayerDump.cpp b/src/server/game/Tools/PlayerDump.cpp index 28b65c2a660..f36dc9df23d 100644 --- a/src/server/game/Tools/PlayerDump.cpp +++ b/src/server/game/Tools/PlayerDump.cpp @@ -69,7 +69,7 @@ struct BaseTable BaseTable const BaseTables[] = { - { "character_pet", "id", "owner", GUID_TYPE_PET }, + { "character_pet", "PetNumber", "Guid", GUID_TYPE_PET }, { "mail", "id", "receiver", GUID_TYPE_MAIL }, { "item_instance", "guid", "owner_guid", GUID_TYPE_ITEM }, @@ -346,10 +346,10 @@ void PlayerDump::InitializeTables() MarkDependentColumn(t, "item_guid", GUID_TYPE_ITEM); break; case DTT_PET: - MarkWhereField(t, "owner"); + MarkWhereField(t, "Guid"); - MarkDependentColumn(t, "id", GUID_TYPE_PET); - MarkDependentColumn(t, "owner", GUID_TYPE_CHAR); + MarkDependentColumn(t, "PetNumber", GUID_TYPE_PET); + MarkDependentColumn(t, "Guid", GUID_TYPE_CHAR); break; case DTT_PET_TABLE: MarkWhereField(t, "guid"); diff --git a/src/server/scripts/Commands/cs_npc.cpp b/src/server/scripts/Commands/cs_npc.cpp index d2bdb4a7adc..25c6d4a59b1 100644 --- a/src/server/scripts/Commands/cs_npc.cpp +++ b/src/server/scripts/Commands/cs_npc.cpp @@ -1483,13 +1483,6 @@ class npc_commandscript : public CommandScript Player* player = handler->GetSession()->GetPlayer(); - if (player->GetPetGUID()) - { - handler->SendSysMessage (LANG_YOU_ALREADY_HAVE_PET); - handler->SetSentErrorMessage (true); - return false; - } - CreatureTemplate const* cInfo = creatureTarget->GetCreatureTemplate(); if (!cInfo->IsTameable (player->CanTameExoticPets())) @@ -1528,11 +1521,7 @@ class npc_commandscript : public CommandScript // visual effect for levelup pet->SetUInt32Value(UNIT_FIELD_LEVEL, level); - // caster have pet now - player->SetMinion(pet, true); - - pet->SavePetToDB(PET_SAVE_NEW_PET); - player->PetSpellInitialize(); + //pet->SavePetToDB(PET_SAVE_NEW_PET); return true; } diff --git a/src/server/scripts/Commands/cs_pet.cpp b/src/server/scripts/Commands/cs_pet.cpp index f6937dff699..134389cda96 100644 --- a/src/server/scripts/Commands/cs_pet.cpp +++ b/src/server/scripts/Commands/cs_pet.cpp @@ -83,13 +83,6 @@ class pet_commandscript : public CommandScript return false; } - if (player->GetPetGUID()) - { - handler->PSendSysMessage("You already have a pet"); - handler->SetSentErrorMessage(true); - return false; - } - // Everything looks OK, create new pet Pet* pet = new Pet(player, HUNTER_PET); if (!pet->CreateBaseAtCreature(creatureTarget)) @@ -126,9 +119,7 @@ class pet_commandscript : public CommandScript // visual effect for levelup pet->SetUInt32Value(UNIT_FIELD_LEVEL, creatureTarget->getLevel()); - player->SetMinion(pet, true); - pet->SavePetToDB(PET_SAVE_NEW_PET); - player->PetSpellInitialize(); + //pet->SavePetToDB(PET_SAVE_NEW_PET); return true; } diff --git a/src/server/scripts/EasternKingdoms/ScarletEnclave/the_scarlet_enclave_chapter_1.cpp b/src/server/scripts/EasternKingdoms/ScarletEnclave/the_scarlet_enclave_chapter_1.cpp index 959e072d13d..a6c81304180 100644 --- a/src/server/scripts/EasternKingdoms/ScarletEnclave/the_scarlet_enclave_chapter_1.cpp +++ b/src/server/scripts/EasternKingdoms/ScarletEnclave/the_scarlet_enclave_chapter_1.cpp @@ -951,8 +951,6 @@ class npc_dkc1_gothik : public CreatureScript // and dig into the ground. creature->DespawnOrUnsummon(); - if (player->GetQuestStatus(12698) == QUEST_STATUS_COMPLETE) - owner->RemoveAllMinionsByEntry(NPC_GHOSTS); } } } @@ -980,7 +978,6 @@ struct npc_scarlet_ghoul : public ScriptedAI void FindMinions(Unit* owner) { std::list MinionList; - owner->GetAllMinionsByEntry(MinionList, NPC_GHOULS); if (!MinionList.empty()) { diff --git a/src/server/scripts/Outland/Auchindoun/ShadowLabyrinth/boss_blackheart_the_inciter.cpp b/src/server/scripts/Outland/Auchindoun/ShadowLabyrinth/boss_blackheart_the_inciter.cpp index cab3678f7ee..60d46b7ce73 100644 --- a/src/server/scripts/Outland/Auchindoun/ShadowLabyrinth/boss_blackheart_the_inciter.cpp +++ b/src/server/scripts/Outland/Auchindoun/ShadowLabyrinth/boss_blackheart_the_inciter.cpp @@ -177,18 +177,12 @@ struct boss_blackheart_the_inciter_mc_dummy : public NullCreatureAI { me->GetThreatManager().AddThreat(trigger, 0.0f); trigger->GetThreatManager().AddThreat(who, 0.0f); - for (Unit* other : trigger->m_Controlled) - { - me->GetThreatManager().AddThreat(other, 0.0f); - other->GetThreatManager().AddThreat(who, 0.0f); - } } } void UpdateAI(uint32 /*diff*/) override { - if (me->m_Controlled.empty()) - me->DespawnOrUnsummon(); } + PlayerAI* GetAIForCharmedPlayer(Player* player) override { return new BlackheartCharmedPlayerAI(player); diff --git a/src/server/scripts/Spells/spell_dk.cpp b/src/server/scripts/Spells/spell_dk.cpp index fd4cc4d6128..1e8ebdbea11 100644 --- a/src/server/scripts/Spells/spell_dk.cpp +++ b/src/server/scripts/Spells/spell_dk.cpp @@ -415,16 +415,6 @@ class spell_dk_death_pact : public SpellScript { SpellCastResult CheckCast() { - // Check if we have valid targets, otherwise skip spell casting here - if (Player* player = GetCaster()->ToPlayer()) - for (Unit::ControlList::const_iterator itr = player->m_Controlled.begin(); itr != player->m_Controlled.end(); ++itr) - if (Creature* undeadPet = (*itr)->ToCreature()) - if (undeadPet->IsAlive() && - undeadPet->GetOwnerOrCreatorGUID() == player->GetGUID() && - undeadPet->GetCreatureType() == CREATURE_TYPE_UNDEAD && - undeadPet->IsWithinDist(player, 100.0f, false)) - return SPELL_CAST_OK; - return SPELL_FAILED_NO_PET; } @@ -1784,7 +1774,7 @@ class spell_dk_dancing_rune_weapon : public AuraScript { PreventDefaultAction(); std::list runeWeapon; - GetTarget()->GetAllMinionsByEntry(runeWeapon, GetSpellInfo()->Effects[EFFECT_0].MiscValue); + //GetTarget()->GetAllMinionsByEntry(runeWeapon, GetSpellInfo()->Effects[EFFECT_0].MiscValue); if (runeWeapon.empty()) return; diff --git a/src/server/scripts/Spells/spell_druid.cpp b/src/server/scripts/Spells/spell_druid.cpp index 55916d8d476..96c1245a7f3 100644 --- a/src/server/scripts/Spells/spell_druid.cpp +++ b/src/server/scripts/Spells/spell_druid.cpp @@ -33,6 +33,7 @@ #include "SpellMgr.h" #include "SpellScript.h" #include "TemporarySummon.h" +#include "NewTemporarySummon.h" enum DruidSpells { @@ -63,8 +64,6 @@ enum DruidSpells SPELL_DRUID_FERAL_SWIFTNESS = 17002, SPELL_DRUID_FERAL_SWIFTNESS_CLEAR_ROAR = 97993, SPELL_DRUID_FERAL_SWIFTNESS_CLEAR_CAT = 97985, - SPELL_DRUID_FUNGAL_GROWTH_R1 = 78788, - SPELL_DRUID_FUNGAL_GROWTH_R2 = 78789, SPELL_DRUID_FUNGAL_GROWTH_SUMMON_R1 = 81291, SPELL_DRUID_FUNGAL_GROWTH_SUMMON_R2 = 81283, SPELL_DRUID_FRENZIED_REGENERATION_HEAL = 22845, @@ -120,8 +119,8 @@ enum DruidSpells SPELL_DRUID_T13_FERAL_2P_BONUS = 105725, SPELL_DRUID_WILD_MUSHROOM = 88747, SPELL_DRUID_WILD_MUSHROOM_DAMAGE = 78777, - SPELL_DRUID_WILD_MUSHROOM_SUICIDE = 92853, - SPELL_DRUID_WILD_MUSHROOM_VISUAL = 92701, + SPELL_DRUID_WILD_MUSHROOM_DETONATE_SUICIDE = 92853, + SPELL_DRUID_WILD_MUSHROOM_DETONATE_DEATH_VISUAL = 92701, SPELL_DRUID_FIREBLOOM = 99017 }; @@ -136,7 +135,8 @@ enum DruidSpellIconIds SPELL_ICON_ID_GLYPH_OF_FEROCIOUS_BITE = 1680, SPELL_ICON_ID_GLYPH_OF_FRENZIED_REGENERATION = 50, SPELL_ICON_ID_GIFT_OF_THE_EARTHMOTHER = 3186, - SPELL_ICON_ID_STAMPEDE = 3930 + SPELL_ICON_ID_STAMPEDE = 3930, + SPELL_ICON_ID_FUNGAL_GROWTH = 2681 }; enum MiscSpells @@ -1323,46 +1323,7 @@ class spell_dru_leader_of_the_pack : public AuraScript } }; -class spell_dru_wild_mushroom : public SpellScript -{ - void HandleSummon() - { - Unit* caster = GetCaster(); - if (!caster) - return; - - std::list mushrooms; - caster->GetAllMinionsByEntry(mushrooms, GetSpellInfo()->Effects[EFFECT_0].MiscValue); - if (mushrooms.empty()) - return; - - TempSummon* oldestMushroom = nullptr; - - if (mushrooms.size() > uint32(GetSpellInfo()->Effects[EFFECT_0].BasePoints)) - { - for (auto itr : mushrooms) - { - if (TempSummon* mushroomToCheck = itr->ToTempSummon()) - { - if (!oldestMushroom) - oldestMushroom = mushroomToCheck; - else - { - if (mushroomToCheck->GetTimer() < oldestMushroom->GetTimer()) - oldestMushroom = mushroomToCheck; - } - } - } - if (oldestMushroom) - oldestMushroom->UnSummon(); - } - } - - void Register() override - { - AfterCast.Register(&spell_dru_wild_mushroom::HandleSummon); - } -}; +static constexpr uint32 const NPC_WILD_MUSHROOM = 47649; class spell_dru_wild_mushroom_detonate : public SpellScript { @@ -1370,12 +1331,9 @@ class spell_dru_wild_mushroom_detonate : public SpellScript { return ValidateSpellInfo( { - SPELL_DRUID_WILD_MUSHROOM, SPELL_DRUID_WILD_MUSHROOM_DAMAGE, - SPELL_DRUID_WILD_MUSHROOM_SUICIDE, - SPELL_DRUID_WILD_MUSHROOM_VISUAL, - SPELL_DRUID_FUNGAL_GROWTH_R1, - SPELL_DRUID_FUNGAL_GROWTH_R2, + SPELL_DRUID_WILD_MUSHROOM_DETONATE_SUICIDE, + SPELL_DRUID_WILD_MUSHROOM_DETONATE_DEATH_VISUAL, SPELL_DRUID_FUNGAL_GROWTH_SUMMON_R1, SPELL_DRUID_FUNGAL_GROWTH_SUMMON_R2 }); @@ -1383,33 +1341,19 @@ class spell_dru_wild_mushroom_detonate : public SpellScript void HandleDummy(SpellEffIndex /*effIndex*/) { - Unit* caster = GetCaster(); - if (!caster) - return; - SpellInfo const* spell = sSpellMgr->GetSpellInfo(SPELL_DRUID_WILD_MUSHROOM); - if (!spell) - return; + for (SummonPropertiesSlot slot : { SummonPropertiesSlot::Totem1, SummonPropertiesSlot::Totem2, SummonPropertiesSlot::Totem3, SummonPropertiesSlot::Totem4 }) + { + NewTemporarySummon* summon = GetHitUnit()->GetSummonInSlot(slot); + if (!summon || summon->GetEntry() != NPC_WILD_MUSHROOM) + continue; - std::list mushrooms; - caster->GetAllMinionsByEntry(mushrooms, spell->Effects[EFFECT_0].MiscValue); - if (mushrooms.empty()) - return; + if (AuraEffect const* fungalGrowth = GetHitUnit()->GetDummyAuraEffect(SPELLFAMILY_DRUID, SPELL_ICON_ID_FUNGAL_GROWTH, EFFECT_0)) + GetHitUnit()->CastSpell(summon, fungalGrowth->GetSpellInfo()->GetRank() == 1 ? SPELL_DRUID_FUNGAL_GROWTH_SUMMON_R1 : SPELL_DRUID_FUNGAL_GROWTH_SUMMON_R2, fungalGrowth); - for (auto itr : mushrooms) - { - if (TempSummon* mushroom = itr->ToTempSummon()) - { - if (caster->HasAura(SPELL_DRUID_FUNGAL_GROWTH_R1)) - caster->CastSpell(mushroom, SPELL_DRUID_FUNGAL_GROWTH_SUMMON_R1, true); - else if (caster->HasAura(SPELL_DRUID_FUNGAL_GROWTH_R2)) - caster->CastSpell(mushroom, SPELL_DRUID_FUNGAL_GROWTH_SUMMON_R2, true); - - caster->CastSpell(mushroom, SPELL_DRUID_WILD_MUSHROOM_DAMAGE, true); - mushroom->CastSpell(mushroom, SPELL_DRUID_WILD_MUSHROOM_VISUAL, true); - mushroom->CastSpell(mushroom, SPELL_DRUID_WILD_MUSHROOM_SUICIDE, true); - mushroom->UnSummon(1500); - } + GetHitUnit()->CastSpell(summon, SPELL_DRUID_WILD_MUSHROOM_DAMAGE, true); + summon->CastSpell(nullptr, SPELL_DRUID_WILD_MUSHROOM_DETONATE_DEATH_VISUAL); + summon->CastSpell(nullptr, SPELL_DRUID_WILD_MUSHROOM_DETONATE_SUICIDE); } } @@ -2240,7 +2184,6 @@ void AddSC_druid_spell_scripts() RegisterSpellScript(spell_dru_t12_restoration_4p_bonus); RegisterSpellScript(spell_dru_item_t11_feral_4p_bonus); RegisterSpellScript(spell_dru_wild_growth); - RegisterSpellScript(spell_dru_wild_mushroom); RegisterSpellScript(spell_dru_wild_mushroom_detonate); RegisterSpellScript(spell_dru_stampeding_roar); RegisterSpellScript(spell_dru_feral_swiftness_clear); diff --git a/src/server/scripts/Spells/spell_generic.cpp b/src/server/scripts/Spells/spell_generic.cpp index e9164254483..8fd8986f6ee 100644 --- a/src/server/scripts/Spells/spell_generic.cpp +++ b/src/server/scripts/Spells/spell_generic.cpp @@ -1704,7 +1704,7 @@ class spell_ethereal_pet_aura : public AuraScript PreventDefaultAction(); std::list minionList; - GetUnitOwner()->GetAllMinionsByEntry(minionList, NPC_ETHEREAL_SOUL_TRADER); + //GetUnitOwner()->GetAllMinionsByEntry(minionList, NPC_ETHEREAL_SOUL_TRADER); for (Creature* minion : minionList) { if (minion->IsAIEnabled()) @@ -2650,6 +2650,7 @@ class spell_gen_pet_summoned : public SpellScriptLoader void HandleScript(SpellEffIndex /*effIndex*/) { + /* Player* player = GetCaster()->ToPlayer(); if (player->GetLastPetNumber()) { @@ -2677,6 +2678,7 @@ class spell_gen_pet_summoned : public SpellScriptLoader else delete newPet; } + */ } void Register() override @@ -3167,10 +3169,6 @@ class spell_gen_summon_elemental : public SpellScriptLoader void AfterRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) { - if (GetCaster()) - if (Unit* owner = GetCaster()->GetOwner()) - if (owner->GetTypeId() == TYPEID_PLAYER) /// @todo this check is maybe wrong - owner->ToPlayer()->RemovePet(nullptr, PET_SAVE_DISMISS, true); } void Register() override @@ -3782,10 +3780,10 @@ class spell_gen_gm_freeze : public SpellScriptLoader { if (Pet* pet = player->GetPet()) { - pet->SavePetToDB(PET_SAVE_CURRENT_STATE); + //pet->SavePetToDB(PET_SAVE_CURRENT_STATE); // not let dismiss dead pet - if (pet->IsAlive()) - player->RemovePet(pet, PET_SAVE_DISMISS); + //if (pet->IsAlive()) + // player->RemovePet(pet, PET_SAVE_DISMISS); } } } @@ -5609,6 +5607,48 @@ class spell_gen_dalaran_shop_keeper_greeting_ping : public SpellScript } }; +enum ControlPet +{ + SPELL_CONTROL_DEMON_EFFECT = 93376, + SPELL_CONTROL_PET_EFFECT = 93322, +}; + +// 93321 - Control Pet (Passive) +// 93375 - Control Demon (Passive) +class spell_gen_control_pet : public AuraScript +{ +public: + spell_gen_control_pet(uint32 effectSpellId) : AuraScript(), _effectSpellId(effectSpellId) { } + + bool Load() override + { + return GetCaster()->IsPlayer(); + } + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ _effectSpellId }); + } + + void AfterApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + Player* player = GetTarget()->ToPlayer(); + if (!player) + return; + + if (!player->CanControlClassPets()) + player->CastSpell(nullptr, _effectSpellId, TRIGGERED_FULL_MASK); + } + + void Register() override + { + AfterEffectApply.Register(&spell_gen_control_pet::AfterApply, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL_OR_REAPPLY_MASK); + } + +private: + uint32 _effectSpellId = 0; +}; + void AddSC_generic_spell_scripts() { new spell_gen_absorb0_hitlimit1(); @@ -5751,4 +5791,6 @@ void AddSC_generic_spell_scripts() RegisterSpellScript(spell_gen_shadowmeld); RegisterSpellScript(spell_gen_vehicle_control_link); RegisterSpellScript(spell_gen_polymorph_cast_visual); + RegisterSpellScriptWithArgs(spell_gen_control_pet, "spell_gen_control_demon", SPELL_CONTROL_DEMON_EFFECT); + RegisterSpellScriptWithArgs(spell_gen_control_pet, "spell_gen_control_pet", SPELL_CONTROL_PET_EFFECT); } diff --git a/src/server/scripts/Spells/spell_hunter.cpp b/src/server/scripts/Spells/spell_hunter.cpp index 843c72557b8..92d785cf3f0 100644 --- a/src/server/scripts/Spells/spell_hunter.cpp +++ b/src/server/scripts/Spells/spell_hunter.cpp @@ -34,11 +34,6 @@ enum HunterSpells { SPELL_HUNTER_AIMED_SHOT = 19434, SPELL_HUNTER_BESTIAL_WRATH = 19574, - SPELL_HUNTER_CALL_PET_1 = 883, - SPELL_HUNTER_CALL_PET_2 = 83242, - SPELL_HUNTER_CALL_PET_3 = 83243, - SPELL_HUNTER_CALL_PET_4 = 83244, - SPELL_HUNTER_CALL_PET_5 = 83245, SPELL_HUNTER_CAMOUFLAGE_DURATION = 51755, SPELL_HUNTER_CAMOUFLAGE_PERIODIC = 80326, SPELL_HUNTER_CAMOUFLAGE_PERIODIC_TRIGGERED = 80325, @@ -711,74 +706,6 @@ class spell_hun_improved_steady_shot : public AuraScript uint8 _steadyShotCounter = 0; }; -uint32 callPetSpellIdBySlot[] = -{ - SPELL_HUNTER_CALL_PET_1, - SPELL_HUNTER_CALL_PET_2, - SPELL_HUNTER_CALL_PET_3, - SPELL_HUNTER_CALL_PET_4, - SPELL_HUNTER_CALL_PET_5 -}; - -// 1515 - Tame Beast -class spell_hun_tame_beast : public SpellScript -{ - SpellCastResult SendTameFailResult(PetTameFailureReason reason) - { - Player* player = GetCaster()->ToPlayer(); - if (!player) - return SPELL_FAILED_DONT_REPORT; - - player->SendTamePetFailure(reason); - - return SPELL_FAILED_DONT_REPORT; - } - - SpellCastResult CheckCast() - { - Player* player = GetCaster()->ToPlayer(); - if (!player) - return SPELL_FAILED_DONT_REPORT; - - if (!GetExplTargetUnit()) - return SPELL_FAILED_BAD_IMPLICIT_TARGETS; - - if (player->getClass() != CLASS_HUNTER) - return SendTameFailResult(PET_TAME_FAILURE_CANNOT_TAME_CREATURES); - - if (!player->GetFirstUnusedActivePetSlot()) - return SendTameFailResult(PET_TAME_FAILURE_TOO_MANY_PETS); - - if (Optional slot = player->GetFirstUnusedActivePetSlot()) - if (!player->HasSpell(callPetSpellIdBySlot[*slot])) - return SendTameFailResult(PET_TAME_FAILURE_SLOT_LOCKED); - - if (Creature* target = GetExplTargetUnit()->ToCreature()) - { - if (target->getLevel() > player->getLevel()) - return SendTameFailResult(PET_TAME_FAILURE_TOO_HIGH_LEVEL); - - if (!target->GetCreatureTemplate()->IsTameable(player->ToPlayer()->CanTameExoticPets())) - return SendTameFailResult(PET_TAME_FAILURE_CANNOT_TAME_EXOTIC); - - if (player->GetPetGUID()) - return SendTameFailResult(PET_TAME_FAILURE_ACTIVE_SUMMON); - - if (player->GetCharmedGUID()) - return SendTameFailResult(PET_TAME_FAILURE_CREATURE_CONTROLLED); - } - else - return SendTameFailResult(PET_TAME_FAILURE_NOT_TAMEABLE); - - return SPELL_CAST_OK; - } - - void Register() override - { - OnCheckCast.Register(&spell_hun_tame_beast::CheckCast); - } -}; - // 53434 - Call of the Wild class spell_hun_target_only_pet_and_owner : public SpellScript { @@ -1474,7 +1401,6 @@ void AddSC_hunter_spell_scripts() RegisterSpellScript(spell_hun_sniper_training); RegisterSpellScript(spell_hun_steady_shot); RegisterSpellScript(spell_hun_improved_steady_shot); - RegisterSpellScript(spell_hun_tame_beast); RegisterSpellScript(spell_hun_target_only_pet_and_owner); RegisterSpellScript(spell_hun_thrill_of_the_hunt); RegisterSpellScript(spell_hun_tnt); diff --git a/src/server/scripts/Spells/spell_item.cpp b/src/server/scripts/Spells/spell_item.cpp index 578e7259cac..ef99587d674 100644 --- a/src/server/scripts/Spells/spell_item.cpp +++ b/src/server/scripts/Spells/spell_item.cpp @@ -2491,7 +2491,7 @@ class spell_item_gift_of_the_harvester : public SpellScriptLoader SpellCastResult CheckRequirement() { std::list ghouls; - GetCaster()->GetAllMinionsByEntry(ghouls, NPC_GHOUL); + //GetCaster()->GetAllMinionsByEntry(ghouls, NPC_GHOUL); if (ghouls.size() >= MAX_GHOULS) { SetCustomCastResultMessage(SPELL_CUSTOM_ERROR_TOO_MANY_GHOULS); diff --git a/src/server/scripts/Spells/spell_mage.cpp b/src/server/scripts/Spells/spell_mage.cpp index f4cb972bd18..6e6f122c91f 100644 --- a/src/server/scripts/Spells/spell_mage.cpp +++ b/src/server/scripts/Spells/spell_mage.cpp @@ -827,7 +827,7 @@ class spell_mage_permafrost : public AuraScript bool DoCheck(ProcEventInfo& eventInfo) { - return GetTarget()->GetGuardianPet() && eventInfo.GetDamageInfo()->GetDamage() && eventInfo.GetProcTarget(); + return false; } void HandleEffectProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) @@ -967,7 +967,7 @@ class spell_mage_ring_of_frost : public AuraScript void Apply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) { std::list MinionList; - GetTarget()->GetAllMinionsByEntry(MinionList, GetSpellInfo()->Effects[EFFECT_0].MiscValue); + //GetTarget()->GetAllMinionsByEntry(MinionList, GetSpellInfo()->Effects[EFFECT_0].MiscValue); // Get the last summoned RoF, save it and despawn older ones for (std::list::iterator itr = MinionList.begin(); itr != MinionList.end(); itr++) diff --git a/src/server/scripts/Spells/spell_paladin.cpp b/src/server/scripts/Spells/spell_paladin.cpp index 62d5c570c10..e3360c324a3 100644 --- a/src/server/scripts/Spells/spell_paladin.cpp +++ b/src/server/scripts/Spells/spell_paladin.cpp @@ -1428,9 +1428,9 @@ class spell_pal_ancient_healer : public AuraScript int32 bp1 = CalculatePct(heal->GetEffectiveHeal(), 10); - for (Unit* guardian : GetTarget()->m_Controlled) - if (guardian->GetUInt32Value(UNIT_CREATED_BY_SPELL) == SPELL_PALADIN_GUARDIAN_OF_ANCIENT_KINGS_HOLY) - guardian->CastSpell(heal->GetTarget(), SPELL_PALADIN_LIGHT_OF_THE_ANCIENT_KINGS, CastSpellExtraArgs(aurEff).AddSpellBP0(bp0).AddSpellMod(SPELLVALUE_BASE_POINT1, bp1)); + //for (Unit* guardian : GetTarget()->m_Controlled) + // if (guardian->GetUInt32Value(UNIT_CREATED_BY_SPELL) == SPELL_PALADIN_GUARDIAN_OF_ANCIENT_KINGS_HOLY) + // guardian->CastSpell(heal->GetTarget(), SPELL_PALADIN_LIGHT_OF_THE_ANCIENT_KINGS, CastSpellExtraArgs(aurEff).AddSpellBP0(bp0).AddSpellMod(SPELLVALUE_BASE_POINT1, bp1)); _procCount++; } diff --git a/src/server/scripts/Spells/spell_shaman.cpp b/src/server/scripts/Spells/spell_shaman.cpp index f430f78b095..9ab711f4fad 100644 --- a/src/server/scripts/Spells/spell_shaman.cpp +++ b/src/server/scripts/Spells/spell_shaman.cpp @@ -1013,9 +1013,9 @@ class spell_sha_totemic_mastery : public AuraScript void HandleDummy(AuraEffect const* /*aurEff*/) { Unit* target = GetTarget(); - for (uint8 i = SUMMON_SLOT_TOTEM_FIRE; i < MAX_TOTEM_SLOT; ++i) - if (!target->m_SummonSlot[i]) - return; + //for (uint8 i = SUMMON_SLOT_TOTEM_FIRE; i < MAX_TOTEM_SLOT; ++i) + // if (!target->m_SummonSlot[i]) + // return; target->CastSpell(target, SPELL_SHAMAN_TOTEMIC_MASTERY, true); PreventDefaultAction(); diff --git a/src/server/scripts/Spells/spell_warlock.cpp b/src/server/scripts/Spells/spell_warlock.cpp index 8b3b0b08de8..3445ad0f25e 100644 --- a/src/server/scripts/Spells/spell_warlock.cpp +++ b/src/server/scripts/Spells/spell_warlock.cpp @@ -507,7 +507,7 @@ class spell_warl_fel_synergy : public AuraScript bool CheckProc(ProcEventInfo& eventInfo) { - return GetTarget()->GetGuardianPet() && eventInfo.GetDamageInfo()->GetDamage(); + return false; } void OnProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) diff --git a/src/server/scripts/World/npcs_special.cpp b/src/server/scripts/World/npcs_special.cpp index 96bbd979c9c..4997bd494fc 100644 --- a/src/server/scripts/World/npcs_special.cpp +++ b/src/server/scripts/World/npcs_special.cpp @@ -2450,64 +2450,6 @@ enum StableMasters STABLE_MASTER_GOSSIP_SUB_MENU = 9820 }; -class npc_stable_master : public CreatureScript -{ - public: - npc_stable_master() : CreatureScript("npc_stable_master") { } - - struct npc_stable_masterAI : public ScriptedAI - { - npc_stable_masterAI(Creature* creature) : ScriptedAI(creature) { } - - bool GossipSelect(Player* player, uint32 menuId, uint32 gossipListId) override - { - if (menuId == STABLE_MASTER_GOSSIP_SUB_MENU) - { - switch (gossipListId) - { - case 0: - player->CastSpell(player, SPELL_MINIWING, false); - break; - case 1: - player->CastSpell(player, SPELL_JUBLING, false); - break; - case 2: - player->CastSpell(player, SPELL_DARTER, false); - break; - case 3: - player->CastSpell(player, SPELL_WORG, false); - break; - case 4: - player->CastSpell(player, SPELL_SMOLDERWEB, false); - break; - case 5: - player->CastSpell(player, SPELL_CHIKEN, false); - break; - case 6: - player->CastSpell(player, SPELL_WOLPERTINGER, false); - break; - default: - return false; - } - } - else - { - if (gossipListId == 0) - { - player->GetSession()->SendStablePet(me->GetGUID()); - player->PlayerTalkClass->SendCloseGossip(); - } - } - return false; - } - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return new npc_stable_masterAI(creature); - } -}; - enum TrainWrecker { GO_TOY_TRAIN = 193963, @@ -3030,6 +2972,21 @@ class npc_druid_treant : public CreatureScript } }; +enum WildMushroom +{ + SPELL_WILD_MUSHROOM_BIRTH_VISUAL = 94081 +}; + +struct npc_druid_wild_mushroom : public NullCreatureAI +{ + npc_druid_wild_mushroom(Creature* creature) : NullCreatureAI(creature) { } + + void JustAppeared() override + { + DoCastSelf(SPELL_WILD_MUSHROOM_BIRTH_VISUAL); + } +}; + enum WhackAGnoll { NPC_DARKMOON_FAIRE_GNOLL = 54444, @@ -3166,11 +3123,11 @@ void AddSC_npcs_special() new npc_firework(); new npc_spring_rabbit(); new npc_imp_in_a_ball(); - new npc_stable_master(); new npc_train_wrecker(); new npc_argent_squire_gruntling(); new npc_bountiful_table(); RegisterCreatureAI(npc_mage_orb); new npc_druid_treant(); + RegisterCreatureAI(npc_druid_wild_mushroom); RegisterCreatureAI(npc_darkmoon_island_gnoll); }