diff --git a/Source/Framework/Constants/PetConst.cs b/Source/Framework/Constants/PetConst.cs
index f89a9c57b..dc0e6557d 100644
--- a/Source/Framework/Constants/PetConst.cs
+++ b/Source/Framework/Constants/PetConst.cs
@@ -34,11 +34,13 @@ namespace Framework.Constants
public enum PetSaveMode
{
- NotInSlot = -1, // for avoid conflict with stable size grow will use negative value
AsDeleted = -2, // not saved in fact
- AsCurrent = 0, // in current slot (with player)
- FirstStableSlot = 1,
- LastStableSlot = SharedConst.MaxPetStables, // last in DB stable slot index (including), all higher have same meaning as PET_SAVE_NOT_IN_SLOT
+ AsCurrent = -3, // in current slot (with player)
+ FirstActiveSlot = 0,
+ LastActiveSlot = FirstActiveSlot + SharedConst.MaxActivePets,
+ FirstStableSlot = 5,
+ LastStableSlot = FirstStableSlot + SharedConst.MaxPetStables, // last in DB stable slot index
+ NotInSlot = -1, // for avoid conflict with stable size grow will use negative value
}
public enum PetSpellState
diff --git a/Source/Framework/Constants/SharedConst.cs b/Source/Framework/Constants/SharedConst.cs
index 404433b3a..e917c0172 100644
--- a/Source/Framework/Constants/SharedConst.cs
+++ b/Source/Framework/Constants/SharedConst.cs
@@ -219,7 +219,9 @@ namespace Framework.Constants
///
/// Pet Const
///
- public const int MaxPetStables = 4;
+ public const int MaxActivePets = 5;
+ public const int MaxPetStables = 200;
+ public const uint CallPetSpellId = 883;
public const float PetFollowDist = 1.0f;
public const float PetFollowAngle = MathF.PI;
public const int MaxSpellCharm = 4;
@@ -434,6 +436,16 @@ namespace Framework.Constants
{
return raceId < Race.Max && raceBits[(int)raceId] >= 0 && raceBits[(int)raceId] < 64 ? (1 << raceBits[(int)raceId]) : 0;
}
+
+ public static bool IsActivePetSlot(PetSaveMode slot)
+ {
+ return slot >= PetSaveMode.FirstActiveSlot && slot < PetSaveMode.LastActiveSlot;
+ }
+
+ public static bool IsStabledPetSlot(PetSaveMode slot)
+ {
+ return slot >= PetSaveMode.FirstStableSlot && slot < PetSaveMode.LastStableSlot;
+ }
}
public enum Locale
diff --git a/Source/Framework/Database/Databases/CharacterDatabase.cs b/Source/Framework/Database/Databases/CharacterDatabase.cs
index cd87c0cf1..5e24dbc7f 100644
--- a/Source/Framework/Database/Databases/CharacterDatabase.cs
+++ b/Source/Framework/Database/Databases/CharacterDatabase.cs
@@ -721,7 +721,6 @@ namespace Framework.Database
PrepareStatement(CharStatements.UPD_CHAR_PET_NAME, "UPDATE character_pet SET name = ?, renamed = 1 WHERE owner = ? AND id = ?");
PrepareStatement(CharStatements.UPD_CHAR_PET_SLOT_BY_ID, "UPDATE character_pet SET slot = ? WHERE owner = ? AND id = ?");
PrepareStatement(CharStatements.DEL_CHAR_PET_BY_ID, "DELETE FROM character_pet WHERE id = ?");
- PrepareStatement(CharStatements.DEL_CHAR_PET_BY_SLOT, "DELETE FROM character_pet WHERE owner = ? AND slot IN (?, ?)");
PrepareStatement(CharStatements.DEL_ALL_PET_SPELLS_BY_OWNER, "DELETE FROM pet_spell WHERE guid in (SELECT id FROM character_pet WHERE owner=?)");
PrepareStatement(CharStatements.UPD_PET_SPECS_BY_OWNER, "UPDATE character_pet SET specialization = 0 WHERE owner=?");
PrepareStatement(CharStatements.INS_PET, "INSERT INTO character_pet (id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType, specialization) " +
@@ -1353,7 +1352,6 @@ namespace Framework.Database
UPD_CHAR_PET_NAME,
UPD_CHAR_PET_SLOT_BY_ID,
DEL_CHAR_PET_BY_ID,
- DEL_CHAR_PET_BY_SLOT,
DEL_ALL_PET_SPELLS_BY_OWNER,
UPD_PET_SPECS_BY_OWNER,
INS_PET,
diff --git a/Source/Game/Entities/Pet.cs b/Source/Game/Entities/Pet.cs
index e76ac4c44..5e39c4a37 100644
--- a/Source/Game/Entities/Pet.cs
+++ b/Source/Game/Entities/Pet.cs
@@ -96,13 +96,14 @@ namespace Game.Entities
}
}
- public static Tuple GetLoadPetInfo(PetStable stable, uint petEntry, uint petnumber, bool current)
+ public static Tuple GetLoadPetInfo(PetStable stable, uint petEntry, uint petnumber, PetSaveMode? slot)
{
if (petnumber != 0)
{
// Known petnumber entry
- if (stable.CurrentPet != null && stable.CurrentPet.PetNumber == petnumber)
- return Tuple.Create(stable.CurrentPet, PetSaveMode.AsCurrent);
+ for (var activeSlot = 0; activeSlot < stable.ActivePets.Length; ++activeSlot)
+ if (stable.ActivePets[activeSlot] != null && stable.ActivePets[activeSlot].PetNumber == petnumber)
+ return Tuple.Create(stable.ActivePets[activeSlot], PetSaveMode.FirstActiveSlot + activeSlot);
for (var stableSlot = 0; stableSlot < stable.StabledPets.Length; ++stableSlot)
if (stable.StabledPets[stableSlot] != null && stable.StabledPets[stableSlot].PetNumber == petnumber)
@@ -112,17 +113,24 @@ namespace Game.Entities
if (pet.PetNumber == petnumber)
return Tuple.Create(pet, PetSaveMode.NotInSlot);
}
- else if (current)
+ else if (slot.HasValue)
{
- // Current pet (slot 0)
- if (stable.CurrentPet != null)
- return Tuple.Create(stable.CurrentPet, PetSaveMode.AsCurrent);
+ // Current pet
+ if (slot == PetSaveMode.AsCurrent)
+ if (stable.GetCurrentActivePetIndex().HasValue && stable.ActivePets[stable.GetCurrentActivePetIndex().Value] != null)
+ return Tuple.Create(stable.ActivePets[stable.GetCurrentActivePetIndex().Value], (PetSaveMode)stable.GetCurrentActivePetIndex());
+
+ if (slot >= PetSaveMode.FirstActiveSlot && slot < PetSaveMode.LastActiveSlot)
+ if (stable.ActivePets[(int)slot.Value] != null)
+ return Tuple.Create(stable.ActivePets[(int)slot.Value], slot.Value);
+
+ if (slot >= PetSaveMode.FirstStableSlot && slot < PetSaveMode.LastStableSlot)
+ if (stable.StabledPets[(int)slot.Value] != null)
+ return Tuple.Create(stable.StabledPets[(int)slot.Value], slot.Value);
}
else if (petEntry != 0)
{
// known petEntry entry (unique for summoned pet, but non unique for hunter pet (only from current or not stabled pets)
- if (stable.CurrentPet != null && stable.CurrentPet.CreatureId == petEntry)
- return Tuple.Create(stable.CurrentPet, PetSaveMode.AsCurrent);
foreach (var pet in stable.UnslottedPets)
if (pet.CreatureId == petEntry)
@@ -131,8 +139,8 @@ namespace Game.Entities
else
{
// Any current or other non-stabled pet (for hunter "call pet")
- if (stable.CurrentPet != null)
- return Tuple.Create(stable.CurrentPet, PetSaveMode.AsCurrent);
+ if (stable.ActivePets[0] != null)
+ return Tuple.Create(stable.ActivePets[0], PetSaveMode.FirstActiveSlot);
if (!stable.UnslottedPets.Empty())
return Tuple.Create(stable.UnslottedPets.First(), PetSaveMode.NotInSlot);
@@ -141,22 +149,22 @@ namespace Game.Entities
return Tuple.Create(null, PetSaveMode.AsDeleted);
}
- public bool LoadPetFromDB(Player owner, uint petEntry = 0, uint petnumber = 0, bool current = false)
+ public bool LoadPetFromDB(Player owner, uint petEntry = 0, uint petnumber = 0, bool current = false, PetSaveMode? forcedSlot = null)
{
m_loading = true;
PetStable petStable = owner.GetPetStable();
ulong ownerid = owner.GetGUID().GetCounter();
- (PetStable.PetInfo petInfo, PetSaveMode slot) = GetLoadPetInfo(petStable, petEntry, petnumber, current);
- if (petInfo == null)
+ (PetStable.PetInfo petInfo, PetSaveMode slot) = GetLoadPetInfo(petStable, petEntry, petnumber, forcedSlot);
+ if (petInfo == null || (slot >= PetSaveMode.FirstStableSlot && slot < PetSaveMode.LastStableSlot))
{
m_loading = false;
return false;
}
// Don't try to reload the current pet
- if (petStable.CurrentPet != null && owner.GetPet() != null && petStable.CurrentPet.PetNumber == petInfo.PetNumber)
+ if (petStable.GetCurrentPet() != null && owner.GetPet() != null && petStable.GetCurrentPet().PetNumber == petInfo.PetNumber)
return false;
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(petInfo.CreatedBySpellId, owner.GetMap().GetDifficultyID());
@@ -273,36 +281,28 @@ namespace Game.Entities
}
// set current pet as current
- // 0=current
- // 1..MAX_PET_STABLES in stable slot
- // PET_SAVE_NOT_IN_SLOT(100) = not stable slot (summoning))
+ // 0-4=current
+ // PET_SAVE_NOT_IN_SLOT(-1) = not stable slot (summoning))
if (slot == PetSaveMode.NotInSlot)
{
uint petInfoNumber = petInfo.PetNumber;
- if (petStable.CurrentPet != null)
+ if (petStable.CurrentPetIndex != 0)
owner.RemovePet(null, PetSaveMode.NotInSlot);
- var unslottedPetInfo = petStable.UnslottedPets.Find(unslottedPet => unslottedPet.PetNumber == petInfoNumber);
- Cypher.Assert(petStable.CurrentPet == null);
- Cypher.Assert(unslottedPetInfo != null);
+ var unslottedPetIndex = petStable.UnslottedPets.FindIndex(unslottedPet => unslottedPet.PetNumber == petInfoNumber);
+ Cypher.Assert(petStable.CurrentPetIndex == 0);
+ Cypher.Assert(unslottedPetIndex != -1);
- petStable.CurrentPet = unslottedPetInfo;
- petStable.UnslottedPets.Remove(unslottedPetInfo);
-
- // old petInfo is no longer valid, refresh it
- petInfo = petStable.CurrentPet;
+ petStable.SetCurrentUnslottedPetIndex((uint)unslottedPetIndex);
}
- else if (PetSaveMode.FirstStableSlot <= slot && slot <= PetSaveMode.LastStableSlot)
+ else if (PetSaveMode.FirstActiveSlot <= slot && slot <= PetSaveMode.LastActiveSlot)
{
- var index = Array.FindIndex(petStable.StabledPets, pet => pet?.PetNumber == petnumber);
- var stabledPet = petStable.StabledPets[index];
- Cypher.Assert(index != -1);
+ var activePetIndex = Array.FindIndex(petStable.ActivePets, pet => pet?.PetNumber == petnumber);
- petStable.StabledPets[index] = petStable.CurrentPet;
- petStable.CurrentPet = stabledPet;
+ Cypher.Assert(petStable.CurrentPetIndex == 0);
+ Cypher.Assert(activePetIndex != -1);
- // old petInfo pointer is no longer valid, refresh it
- petInfo = petStable.CurrentPet;
+ petStable.SetCurrentActivePetIndex((uint)activePetIndex);
}
// Send fake summon spell cast - this is needed for correct cooldown application for spells
@@ -437,8 +437,15 @@ namespace Game.Entities
// save auras before possibly removing them
_SaveAuras(trans);
+ if (mode == PetSaveMode.AsCurrent)
+ {
+ var activeSlot = owner.GetPetStable().GetCurrentActivePetIndex();
+ if (activeSlot.HasValue)
+ mode = (PetSaveMode)activeSlot;
+ }
+
// stable and not in slot saves
- if (mode > PetSaveMode.AsCurrent)
+ if (mode < PetSaveMode.FirstActiveSlot || mode >= PetSaveMode.LastActiveSlot)
RemoveAllAuras();
_SaveSpells(trans);
@@ -446,7 +453,7 @@ namespace Game.Entities
DB.Characters.CommitTransaction(trans);
// current/stable/not_in_slot
- if (mode >= PetSaveMode.AsCurrent)
+ if (mode != PetSaveMode.AsDeleted)
{
ulong ownerLowGUID = GetOwnerGUID().GetCounter();
trans = new SQLTransaction();
@@ -456,21 +463,11 @@ namespace Game.Entities
stmt.AddValue(0, GetCharmInfo().GetPetNumber());
trans.Append(stmt);
- // prevent existence another hunter pet in PET_SAVE_AS_CURRENT and PET_SAVE_NOT_IN_SLOT
- if (GetPetType() == PetType.Hunter && (mode == PetSaveMode.AsCurrent || mode == PetSaveMode.LastStableSlot))
- {
- stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_PET_BY_SLOT);
- stmt.AddValue(0, ownerLowGUID);
- stmt.AddValue(1, (short)mode);
- stmt.AddValue(2, (short)PetSaveMode.NotInSlot);
- trans.Append(stmt);
- }
-
// save pet
string actionBar = GenerateActionBarData();
- Cypher.Assert(owner.GetPetStable().CurrentPet != null && owner.GetPetStable().CurrentPet.PetNumber == GetCharmInfo().GetPetNumber());
- FillPetInfo(owner.GetPetStable().CurrentPet);
+ Cypher.Assert(owner.GetPetStable().GetCurrentPet() != null && owner.GetPetStable().GetCurrentPet().PetNumber == GetCharmInfo().GetPetNumber());
+ FillPetInfo(owner.GetPetStable().GetCurrentPet());
stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_PET);
stmt.AddValue(0, GetCharmInfo().GetPetNumber());
@@ -480,7 +477,7 @@ namespace Game.Entities
stmt.AddValue(4, GetLevel());
stmt.AddValue(5, m_unitData.PetExperience);
stmt.AddValue(6, (byte)GetReactState());
- stmt.AddValue(7, (byte)mode);
+ stmt.AddValue(7, (owner.GetPetStable().GetCurrentActivePetIndex().HasValue ? (short)owner.GetPetStable().GetCurrentActivePetIndex().Value : (short)PetSaveMode.NotInSlot));
stmt.AddValue(8, GetName());
stmt.AddValue(9, HasPetFlag(UnitPetFlags.CanBeRenamed) ? 0 : 1);
stmt.AddValue(10, curhealth);
@@ -1676,6 +1673,8 @@ namespace Game.Entities
public class PetStable
{
+ static uint UnslottedPetIndexMask = 0x80000000;
+
public class PetInfo
{
public string Name;
@@ -1695,14 +1694,31 @@ namespace Game.Entities
public bool WasRenamed;
}
- public PetInfo CurrentPet; // PET_SAVE_AS_CURRENT
+ public uint? CurrentPetIndex; // index into ActivePets or UnslottedPets if highest bit is set
+ public PetInfo[] ActivePets = new PetInfo[SharedConst.MaxActivePets]; // PET_SAVE_FIRST_ACTIVE_SLOT - PET_SAVE_LAST_ACTIVE_SLOT
public PetInfo[] StabledPets = new PetInfo[SharedConst.MaxPetStables]; // PET_SAVE_FIRST_STABLE_SLOT - PET_SAVE_LAST_STABLE_SLOT
public List UnslottedPets = new(); // PET_SAVE_NOT_IN_SLOT
- public PetInfo GetUnslottedHunterPet()
+ public PetInfo GetCurrentPet()
{
- return UnslottedPets.Count == 1 && UnslottedPets[0].Type == PetType.Hunter ? UnslottedPets[0] : null;
+ if (!CurrentPetIndex.HasValue)
+ return null;
+
+ uint? activePetIndex = GetCurrentActivePetIndex();
+ if (activePetIndex.HasValue)
+ return ActivePets[activePetIndex.Value] != null ? ActivePets[activePetIndex.Value] : null;
+
+ uint? unslottedPetIndex = GetCurrentUnslottedPetIndex();
+ if (unslottedPetIndex.HasValue)
+ return unslottedPetIndex < UnslottedPets.Count ? UnslottedPets[(int)unslottedPetIndex.Value] : null;
+
+ return null;
}
+
+ public uint? GetCurrentActivePetIndex() { return CurrentPetIndex.HasValue && ((CurrentPetIndex & UnslottedPetIndexMask) == 0) ? CurrentPetIndex : null; }
+ public void SetCurrentActivePetIndex(uint index) { CurrentPetIndex = index; }
+ uint? GetCurrentUnslottedPetIndex() { return CurrentPetIndex.HasValue && ((CurrentPetIndex & UnslottedPetIndexMask) != 0) ? (CurrentPetIndex & ~UnslottedPetIndexMask) : null; }
+ public void SetCurrentUnslottedPetIndex(uint index) { CurrentPetIndex = index | UnslottedPetIndexMask; }
}
public enum ActiveStates
diff --git a/Source/Game/Entities/Player/Player.DB.cs b/Source/Game/Entities/Player/Player.DB.cs
index 7516f8508..79426eea9 100644
--- a/Source/Game/Entities/Player/Player.DB.cs
+++ b/Source/Game/Entities/Player/Player.DB.cs
@@ -1538,7 +1538,7 @@ namespace Game.Entities
m_bgData.taxiPath[1] = result.Read(8);
m_bgData.mountSpell = result.Read(9);
}
- void _LoadPetStable(SQLResult result)
+ void _LoadPetStable(uint summonedPetNumber, SQLResult result)
{
if (result.IsEmpty())
return;
@@ -1568,15 +1568,18 @@ namespace Game.Entities
petInfo.CreatedBySpellId = result.Read(13);
petInfo.Type = (PetType)result.Read(14);
petInfo.SpecializationId = result.Read(15);
- if (slot == PetSaveMode.AsCurrent)
- m_petStable.CurrentPet = petInfo;
- else if (slot >= PetSaveMode.FirstStableSlot && slot <= PetSaveMode.LastStableSlot)
- m_petStable.StabledPets[(int)slot - 1] = petInfo;
+ if (slot >= PetSaveMode.FirstActiveSlot && slot < PetSaveMode.LastActiveSlot)
+ m_petStable.ActivePets[(int)slot] = petInfo;
+ else if (slot >= PetSaveMode.FirstStableSlot && slot < PetSaveMode.LastStableSlot)
+ m_petStable.StabledPets[slot - PetSaveMode.FirstStableSlot] = petInfo;
else if (slot == PetSaveMode.NotInSlot)
m_petStable.UnslottedPets.Add(petInfo);
} while (result.NextRow());
}
+
+ if (Pet.GetLoadPetInfo(m_petStable, 0, summonedPetNumber, null).Item1 != null)
+ m_temporaryUnsummonedPetNumber = summonedPetNumber;
}
@@ -3080,8 +3083,7 @@ namespace Game.Entities
m_taxi.LoadTaxiMask(taximask); // must be before InitTaxiNodesForLevel
- _LoadPetStable(holder.GetResult(PlayerLoginQueryLoad.PetSlots));
- m_temporaryUnsummonedPetNumber = summonedPetNumber;
+ _LoadPetStable(summonedPetNumber, holder.GetResult(PlayerLoginQueryLoad.PetSlots));
// Honor system
// Update Honor kills data
@@ -3590,7 +3592,7 @@ namespace Game.Entities
stmt.AddValue(index++, (ushort)m_ExtraFlags);
PetStable petStable = GetPetStable();
if (petStable != null)
- stmt.AddValue(index++, petStable.CurrentPet != null && petStable.CurrentPet.Health > 0 ? petStable.CurrentPet.PetNumber : 0); // summonedPetNumber
+ stmt.AddValue(index++, petStable.GetCurrentPet() != null && petStable.GetCurrentPet().Health > 0 ? petStable.GetCurrentPet().PetNumber : 0); // summonedPetNumber
else
stmt.AddValue(index++, 0); // summonedPetNumber
stmt.AddValue(index++, (ushort)atLoginFlags);
diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs
index d3799f816..a7feb2134 100644
--- a/Source/Game/Entities/Player/Player.cs
+++ b/Source/Game/Entities/Player/Player.cs
@@ -4701,12 +4701,19 @@ namespace Game.Entities
return null;
}
- public Pet SummonPet(uint entry, float x, float y, float z, float ang, PetType petType, uint duration)
+ public Pet SummonPet(uint entry, PetSaveMode? slot, float x, float y, float z, float ang, uint duration)
{
+ return SummonPet(entry, slot, x, y, z, ang, duration, out _);
+ }
+
+ public Pet SummonPet(uint entry, PetSaveMode? slot, float x, float y, float z, float ang, uint duration, out bool isNew)
+ {
+ isNew = false;
+
PetStable petStable = GetOrInitPetStable();
- Pet pet = new(this, petType);
- if (petType == PetType.Summon && pet.LoadPetFromDB(this, entry, 0, false))
+ Pet pet = new(this, PetType.Summon);
+ if (pet.LoadPetFromDB(this, entry, 0, false, slot))
{
if (duration > 0)
pet.SetDuration(duration);
@@ -4718,6 +4725,8 @@ namespace Game.Entities
if (entry == 0)
return null;
+ // only SUMMON_PET are handled here
+
pet.Relocate(x, y, z, ang);
if (!pet.IsPositionValid())
{
@@ -4734,7 +4743,7 @@ namespace Game.Entities
return null;
}
- if (petType == PetType.Summon && petStable.CurrentPet != null)
+ if (petStable.GetCurrentPet() != null)
RemovePet(null, PetSaveMode.NotInSlot);
PhasingHandler.InheritPhaseShift(pet, this);
@@ -4747,44 +4756,34 @@ namespace Game.Entities
SetMinion(pet, true);
- Cypher.Assert(petStable.CurrentPet == null && (petType != PetType.Hunter || petStable.GetUnslottedHunterPet() == null));
- petStable.CurrentPet = new();
- pet.FillPetInfo(petStable.CurrentPet);
-
- switch (petType)
- {
- case PetType.Summon:
- // this enables pet details window (Shift+P)
- pet.GetCharmInfo().SetPetNumber(petNumber, true);
- pet.SetClass(Class.Mage);
- pet.SetPetExperience(0);
- pet.SetPetNextLevelExperience(1000);
- pet.SetFullHealth();
- pet.SetFullPower(PowerType.Mana);
- pet.SetPetNameTimestamp((uint)GameTime.GetGameTime());
- break;
- default:
- break;
- }
+ // this enables pet details window (Shift+P)
+ pet.GetCharmInfo().SetPetNumber(petNumber, true);
+ pet.SetClass(Class.Mage);
+ pet.SetPetExperience(0);
+ pet.SetPetNextLevelExperience(1000);
+ pet.SetFullHealth();
+ pet.SetFullPower(PowerType.Mana);
+ pet.SetPetNameTimestamp((uint)GameTime.GetGameTime());
map.AddToMap(pet.ToCreature());
- switch (petType)
- {
- case PetType.Summon:
- pet.InitPetCreateSpells();
- pet.SavePetToDB(PetSaveMode.AsCurrent);
- PetSpellInitialize();
- break;
- default:
- break;
- }
+ Cypher.Assert(petStable.CurrentPetIndex == 0);
+ petStable.SetCurrentUnslottedPetIndex((uint)petStable.UnslottedPets.Count);
+ PetStable.PetInfo petInfo = new();
+ pet.FillPetInfo(petInfo);
+ petStable.UnslottedPets.Add(petInfo);
+
+ pet.InitPetCreateSpells();
+ pet.SavePetToDB(PetSaveMode.AsCurrent);
+ PetSpellInitialize();
if (duration > 0)
pet.SetDuration(duration);
//ObjectAccessor.UpdateObjectVisibility(pet);
+ isNew = true;
+
return pet;
}
@@ -4829,49 +4828,22 @@ namespace Game.Entities
if (pet == null)
{
- if (mode == PetSaveMode.NotInSlot && m_petStable?.CurrentPet != null)
- {
- // Handle removing pet while it is in "temporarily unsummoned" state, for example on mount
- PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_PET_SLOT_BY_ID);
- stmt.AddValue(0, (short)PetSaveMode.NotInSlot);
- stmt.AddValue(1, GetGUID().GetCounter());
- stmt.AddValue(2, m_petStable.CurrentPet.PetNumber);
- DB.Characters.Execute(stmt);
-
- m_petStable.UnslottedPets.Add(m_petStable.CurrentPet);
- m_petStable.CurrentPet = null;
- }
+ // Handle removing pet while it is in "temporarily unsummoned" state, for example on mount
+ if (mode == PetSaveMode.NotInSlot && m_petStable != null && m_petStable.CurrentPetIndex.HasValue)
+ m_petStable.CurrentPetIndex = null;
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 = PetSaveMode.NotInSlot;
- break;
- }
- }
-
// only if current pet in slot
pet.SavePetToDB(mode);
- Cypher.Assert(m_petStable.CurrentPet != null && m_petStable.CurrentPet.PetNumber == pet.GetCharmInfo().GetPetNumber());
- if (mode == PetSaveMode.NotInSlot)
- {
- m_petStable.UnslottedPets.Add(m_petStable.CurrentPet);
- m_petStable.CurrentPet = null;
- }
- else if (mode == PetSaveMode.AsDeleted)
- m_petStable.CurrentPet = null;
+ PetStable.PetInfo currentPet = m_petStable.GetCurrentPet();
+ Cypher.Assert(currentPet != null && currentPet.PetNumber == pet.GetCharmInfo().GetPetNumber());
+ if (mode == PetSaveMode.NotInSlot || mode == PetSaveMode.AsDeleted)
+ m_petStable.CurrentPetIndex = null;
// else if (stable slots) handled in opcode handlers due to required swaps
// else (current pet) doesnt need to do anything
diff --git a/Source/Game/Entities/Unit/Unit.Pets.cs b/Source/Game/Entities/Unit/Unit.Pets.cs
index d1fa62988..1635eaaf8 100644
--- a/Source/Game/Entities/Unit/Unit.Pets.cs
+++ b/Source/Game/Entities/Unit/Unit.Pets.cs
@@ -761,7 +761,9 @@ namespace Game.Entities
{
Player player = ToPlayer();
PetStable petStable = player.GetOrInitPetStable();
- if (petStable.CurrentPet != null || petStable.GetUnslottedHunterPet() != null)
+
+ var freeActiveSlot = Array.FindIndex(petStable.ActivePets, petInfo => petInfo == null);
+ if (freeActiveSlot == -1)
return false;
pet.SetCreatorGUID(GetGUID());
@@ -784,8 +786,11 @@ namespace Game.Entities
pet.InitPetCreateSpells();
pet.SetFullHealth();
- petStable.CurrentPet = new();
- pet.FillPetInfo(petStable.CurrentPet);
+ petStable.SetCurrentActivePetIndex((uint)freeActiveSlot);
+
+ PetStable.PetInfo petInfo = new();
+ pet.FillPetInfo(petInfo);
+ petStable.ActivePets[freeActiveSlot] = petInfo;
return true;
}
diff --git a/Source/Game/Handlers/CharacterHandler.cs b/Source/Game/Handlers/CharacterHandler.cs
index 6c9b9acd7..301460128 100644
--- a/Source/Game/Handlers/CharacterHandler.cs
+++ b/Source/Game/Handlers/CharacterHandler.cs
@@ -792,6 +792,10 @@ namespace Game
pCurrChar.SetGuildLevel(0);
}
+ // Send stable contents to display icons on Call Pet spells
+ if (pCurrChar.HasSpell(SharedConst.CallPetSpellId))
+ SendStablePet(ObjectGuid.Empty);
+
pCurrChar.GetSession().GetBattlePetMgr().SendJournalLockStatus();
pCurrChar.SendInitialPacketsBeforeAddToMap();
diff --git a/Source/Game/Handlers/NPCHandler.cs b/Source/Game/Handlers/NPCHandler.cs
index 1911487bd..bd570834c 100644
--- a/Source/Game/Handlers/NPCHandler.cs
+++ b/Source/Game/Handlers/NPCHandler.cs
@@ -324,7 +324,7 @@ namespace Game
GetPlayer().PlayerTalkClass.SendCloseGossip();
}
- [WorldPacketHandler(ClientOpcodes.RequestStabledPets)]
+ [WorldPacketHandler(ClientOpcodes.RequestStabledPets, Processing = PacketProcessing.Inplace)]
void HandleRequestStabledPets(RequestStabledPets packet)
{
if (!CheckStableMaster(packet.StableMaster))
@@ -353,58 +353,40 @@ namespace Game
return;
}
- uint petSlot = 0;
- if (petStable.CurrentPet != null)
+ for (uint petSlot = 0; petSlot < petStable.ActivePets.Length; ++petSlot)
{
- PetStable.PetInfo pet = petStable.CurrentPet;
+ if (petStable.ActivePets[petSlot] == null)
+ continue;
+ PetStable.PetInfo pet = petStable.ActivePets[petSlot];
PetStableInfo stableEntry;
- stableEntry.PetSlot = petSlot;
+ stableEntry.PetSlot = petSlot + (int)PetSaveMode.FirstActiveSlot;
stableEntry.PetNumber = pet.PetNumber;
stableEntry.CreatureID = pet.CreatureId;
stableEntry.DisplayID = pet.DisplayId;
stableEntry.ExperienceLevel = pet.Level;
stableEntry.PetFlags = PetStableinfo.Active;
stableEntry.PetName = pet.Name;
- ++petSlot;
packet.Pets.Add(stableEntry);
}
- else
+
+ for (uint petSlot = 0; petSlot < petStable.StabledPets.Length; ++petSlot)
{
- PetStable.PetInfo pet = petStable.GetUnslottedHunterPet();
- if (pet != null)
- {
- PetStableInfo stableEntry;
- stableEntry.PetSlot = petSlot;
- stableEntry.PetNumber = pet.PetNumber;
- stableEntry.CreatureID = pet.CreatureId;
- stableEntry.DisplayID = pet.DisplayId;
- stableEntry.ExperienceLevel = pet.Level;
- stableEntry.PetFlags = PetStableinfo.Active;
- stableEntry.PetName = pet.Name;
+ if (petStable.StabledPets[petSlot] == null)
+ continue;
- ++petSlot;
- packet.Pets.Add(stableEntry);
- }
- }
+ PetStable.PetInfo pet = petStable.StabledPets[petSlot];
+ PetStableInfo stableEntry;
+ stableEntry.PetSlot = petSlot + (int)PetSaveMode.FirstStableSlot;
+ stableEntry.PetNumber = pet.PetNumber;
+ stableEntry.CreatureID = pet.CreatureId;
+ stableEntry.DisplayID = pet.DisplayId;
+ stableEntry.ExperienceLevel = pet.Level;
+ stableEntry.PetFlags = PetStableinfo.Inactive;
+ stableEntry.PetName = pet.Name;
- foreach (var pet in petStable.StabledPets)
- {
- if (pet != null)
- {
- PetStableInfo stableEntry;
- stableEntry.PetSlot = petSlot;
- stableEntry.PetNumber = pet.PetNumber;
- stableEntry.CreatureID = pet.CreatureId;
- stableEntry.DisplayID = pet.DisplayId;
- stableEntry.ExperienceLevel = pet.Level;
- stableEntry.PetFlags = PetStableinfo.Inactive;
- stableEntry.PetName = pet.Name;
-
- ++petSlot;
- packet.Pets.Add(stableEntry);
- }
+ packet.Pets.Add(stableEntry);
}
SendPacket(packet);
@@ -417,6 +399,152 @@ namespace Game
SendPacket(petStableResult);
}
+ [WorldPacketHandler(ClientOpcodes.SetPetSlot)]
+ void HandleSetPetSlot(SetPetSlot setPetSlot)
+ {
+ if (!CheckStableMaster(setPetSlot.StableMaster) || setPetSlot.DestSlot >= (byte)PetSaveMode.LastStableSlot)
+ {
+ SendPetStableResult(StableResult.InternalError);
+ return;
+ }
+
+ GetPlayer().RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.Interacting);
+
+ PetStable petStable = GetPlayer().GetPetStable();
+ if (petStable == null)
+ {
+ SendPetStableResult(StableResult.InternalError);
+ return;
+ }
+
+ (PetStable.PetInfo srcPet, PetSaveMode srcPetSlot) = Pet.GetLoadPetInfo(petStable, 0, setPetSlot.PetNumber, null);
+ PetSaveMode dstPetSlot = (PetSaveMode)setPetSlot.DestSlot;
+ PetStable.PetInfo dstPet = Pet.GetLoadPetInfo(petStable, 0, 0, dstPetSlot).Item1;
+
+ if (srcPet == null || srcPet.Type != PetType.Hunter)
+ {
+ SendPetStableResult(StableResult.InternalError);
+ return;
+ }
+
+ if (dstPet != null && dstPet.Type != PetType.Hunter)
+ {
+ SendPetStableResult(StableResult.InternalError);
+ return;
+ }
+
+ PetStable.PetInfo src = null;
+ PetStable.PetInfo dst = null;
+ PetSaveMode? newActivePetIndex = null;
+
+ if (SharedConst.IsActivePetSlot(srcPetSlot) && SharedConst.IsActivePetSlot(dstPetSlot))
+ {
+ // active<.active: only swap ActivePets and CurrentPetIndex (do not despawn pets)
+ src = petStable.ActivePets[srcPetSlot - PetSaveMode.FirstActiveSlot];
+ dst = petStable.ActivePets[dstPetSlot - PetSaveMode.FirstActiveSlot];
+
+ if (petStable.GetCurrentActivePetIndex().Value == (uint)srcPetSlot)
+ newActivePetIndex = dstPetSlot;
+ else if (petStable.GetCurrentActivePetIndex().Value == (uint)dstPetSlot)
+ newActivePetIndex = srcPetSlot;
+ }
+ else if (SharedConst.IsStabledPetSlot(srcPetSlot) && SharedConst.IsStabledPetSlot(dstPetSlot))
+ {
+ // stabled<.stabled: only swap StabledPets
+ src = petStable.StabledPets[srcPetSlot - PetSaveMode.FirstStableSlot];
+ dst = petStable.StabledPets[dstPetSlot - PetSaveMode.FirstStableSlot];
+ }
+ else if (SharedConst.IsActivePetSlot(srcPetSlot) && SharedConst.IsStabledPetSlot(dstPetSlot))
+ {
+ // active<.stabled: swap petStable contents and despawn active pet if it is involved in swap
+ if (petStable.CurrentPetIndex.Value == (uint)srcPetSlot)
+ {
+ Pet oldPet = _player.GetPet();
+ if (oldPet != null && !oldPet.IsAlive())
+ {
+ SendPetStableResult(StableResult.InternalError);
+ return;
+ }
+
+ _player.RemovePet(oldPet, PetSaveMode.NotInSlot);
+ }
+
+ if (dstPet != null)
+ {
+ CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate(dstPet.CreatureId);
+ if (creatureInfo == null || !creatureInfo.IsTameable(_player.CanTameExoticPets()))
+ {
+ SendPetStableResult(StableResult.CantControlExotic);
+ return;
+ }
+ }
+
+ src = petStable.ActivePets[srcPetSlot - PetSaveMode.FirstActiveSlot];
+ dst = petStable.StabledPets[dstPetSlot - PetSaveMode.FirstStableSlot];
+ }
+ else if (SharedConst.IsStabledPetSlot(srcPetSlot) && SharedConst.IsActivePetSlot(dstPetSlot))
+ {
+ // stabled<.active: swap petStable contents and despawn active pet if it is involved in swap
+ if (petStable.CurrentPetIndex.Value == (uint)dstPetSlot)
+ {
+ Pet oldPet = _player.GetPet();
+ if (oldPet != null && !oldPet.IsAlive())
+ {
+ SendPetStableResult(StableResult.InternalError);
+ return;
+ }
+
+ _player.RemovePet(oldPet, PetSaveMode.NotInSlot);
+ }
+
+ CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate(srcPet.CreatureId);
+ if (creatureInfo == null || !creatureInfo.IsTameable(_player.CanTameExoticPets()))
+ {
+ SendPetStableResult(StableResult.CantControlExotic);
+ return;
+ }
+
+ src = petStable.StabledPets[srcPetSlot - PetSaveMode.FirstStableSlot];
+ dst = petStable.ActivePets[dstPetSlot - PetSaveMode.FirstActiveSlot];
+ }
+
+ SQLTransaction trans = new();
+
+ PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_PET_SLOT_BY_ID);
+ stmt.AddValue(0, (short)dstPetSlot);
+ stmt.AddValue(1, _player.GetGUID().GetCounter());
+ stmt.AddValue(2, srcPet.PetNumber);
+ trans.Append(stmt);
+
+ if (dstPet != null)
+ {
+ stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_PET_SLOT_BY_ID);
+ stmt.AddValue(0, (short)srcPetSlot);
+ stmt.AddValue(1, _player.GetGUID().GetCounter());
+ stmt.AddValue(2, dstPet.PetNumber);
+ trans.Append(stmt);
+ }
+
+ AddTransactionCallback(DB.Characters.AsyncCommitTransaction(trans)).AfterComplete(success =>
+ {
+ var currentPlayerGuid = _player.GetGUID();
+ if (_player && _player.GetGUID() == currentPlayerGuid)
+ {
+ if (success)
+ {
+ Extensions.Swap(ref src, ref dst);
+ if (newActivePetIndex.HasValue)
+ GetPlayer().GetPetStable().SetCurrentActivePetIndex((uint)newActivePetIndex.Value);
+ SendPetStableResult(StableResult.StableSuccess);
+ }
+ else
+ {
+ SendPetStableResult(StableResult.InternalError);
+ }
+ }
+ });
+ }
+
[WorldPacketHandler(ClientOpcodes.RepairItem, Processing = PacketProcessing.Inplace)]
void HandleRepairItem(RepairItem packet)
{
diff --git a/Source/Game/Handlers/PetHandler.cs b/Source/Game/Handlers/PetHandler.cs
index 61ab785bc..1f770d538 100644
--- a/Source/Game/Handlers/PetHandler.cs
+++ b/Source/Game/Handlers/PetHandler.cs
@@ -541,7 +541,7 @@ namespace Game
// check it!
if (!pet || !pet.IsPet() || pet.ToPet().GetPetType() != PetType.Hunter || !pet.HasPetFlag(UnitPetFlags.CanBeRenamed) ||
pet.GetOwnerGUID() != _player.GetGUID() || pet.GetCharmInfo() == null ||
- petStable == null || petStable.CurrentPet == null || petStable.CurrentPet.PetNumber != pet.GetCharmInfo().GetPetNumber())
+ petStable == null || petStable.GetCurrentPet() == null || petStable.GetCurrentPet().PetNumber != pet.GetCharmInfo().GetPetNumber())
return;
PetNameInvalidReason res = ObjectManager.CheckPetName(name);
@@ -561,8 +561,8 @@ namespace Game
pet.SetGroupUpdateFlag(GroupUpdatePetFlags.Name);
pet.RemovePetFlag(UnitPetFlags.CanBeRenamed);
- petStable.CurrentPet.Name = name;
- petStable.CurrentPet.WasRenamed = true;
+ petStable.GetCurrentPet().Name = name;
+ petStable.GetCurrentPet().WasRenamed = true;
PreparedStatement stmt;
SQLTransaction trans = new();
diff --git a/Source/Game/Networking/Packets/NPCPackets.cs b/Source/Game/Networking/Packets/NPCPackets.cs
index 2cdf3c23e..cbdf90a07 100644
--- a/Source/Game/Networking/Packets/NPCPackets.cs
+++ b/Source/Game/Networking/Packets/NPCPackets.cs
@@ -292,6 +292,22 @@ namespace Game.Networking.Packets
public ObjectGuid StableMaster;
}
+ class SetPetSlot : ClientPacket
+ {
+ public SetPetSlot(WorldPacket packet) : base(packet) { }
+
+ public override void Read()
+ {
+ PetNumber = _worldPacket.ReadUInt32();
+ DestSlot = _worldPacket.ReadUInt8();
+ StableMaster = _worldPacket.ReadPackedGuid();
+ }
+
+ public ObjectGuid StableMaster;
+ public uint PetNumber;
+ public byte DestSlot;
+ }
+
//Structs
public struct TreasureItem
{
diff --git a/Source/Game/Spells/Spell.cs b/Source/Game/Spells/Spell.cs
index 7446791cd..a11cb3f2a 100644
--- a/Source/Game/Spells/Spell.cs
+++ b/Source/Game/Spells/Spell.cs
@@ -5148,13 +5148,20 @@ namespace Game.Spells
}
case SpellEffectName.ResurrectPet:
{
- if (unitCaster == null)
+ Player playerCaster = m_caster.ToPlayer();
+ if (playerCaster == null || playerCaster.GetPetStable() == null)
return SpellCastResult.BadTargets;
- Creature pet = unitCaster.GetGuardianPet();
+ Pet pet = playerCaster.GetPet();
if (pet != null && pet.IsAlive())
return SpellCastResult.AlreadyHaveSummon;
+ PetStable petStable = playerCaster.GetPetStable();
+ var deadPetInfo = petStable.ActivePets.FirstOrDefault(petInfo => petInfo?.Health == 0);
+
+ if (deadPetInfo == null)
+ return SpellCastResult.BadTargets;
+
break;
}
// This is generic summon effect
@@ -5221,17 +5228,27 @@ namespace Game.Spells
Player playerCaster = unitCaster.ToPlayer();
if (playerCaster != null && playerCaster.GetPetStable() != null)
{
- var info = Pet.GetLoadPetInfo(playerCaster.GetPetStable(), (uint)spellEffectInfo.MiscValue, 0, false);
- if (info.Item1 != null)
+ PetSaveMode? petSlot = null;
+ if (spellEffectInfo.MiscValue == 0)
{
- if (info.Item1.Type == PetType.Hunter)
+ petSlot = (PetSaveMode)spellEffectInfo.CalcValue();
+
+ // No pet can be summoned if any pet is dead
+ foreach (var activePet in playerCaster.GetPetStable().ActivePets)
{
- if (info.Item1.Health == 0)
+ if (activePet?.Health == 0)
{
playerCaster.SendTameFailure(PetTameResult.Dead);
return SpellCastResult.DontReport;
}
+ }
+ }
+ var info = Pet.GetLoadPetInfo(playerCaster.GetPetStable(), (uint)spellEffectInfo.MiscValue, 0, petSlot);
+ if (info.Item1 != null)
+ {
+ if (info.Item1.Type == PetType.Hunter)
+ {
CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate(info.Item1.CreatureId);
if (creatureInfo == null || !creatureInfo.IsTameable(playerCaster.CanTameExoticPets()))
{
@@ -5253,6 +5270,21 @@ namespace Game.Spells
}
break;
}
+ case SpellEffectName.DismissPet:
+ {
+ Player playerCaster = m_caster.ToPlayer();
+ if (playerCaster == null)
+ return SpellCastResult.BadTargets;
+
+ Pet pet = playerCaster.GetPet();
+ if (pet == null)
+ return SpellCastResult.NoPet;
+
+ if (!pet.IsAlive())
+ return SpellCastResult.TargetsDead;
+
+ break;
+ }
case SpellEffectName.SummonPlayer:
{
if (!m_caster.IsTypeId(TypeId.Player))
diff --git a/Source/Game/Spells/SpellEffects.cs b/Source/Game/Spells/SpellEffects.cs
index 38d06c2b2..895cc5719 100644
--- a/Source/Game/Spells/SpellEffects.cs
+++ b/Source/Game/Spells/SpellEffects.cs
@@ -2298,27 +2298,34 @@ namespace Game.Spells
return;
}
+ PetSaveMode? petSlot = null;
+ if (petentry == 0)
+ petSlot = (PetSaveMode)damage;
+
float x, y, z;
owner.GetClosePoint(out x, out y, out z, owner.GetCombatReach());
- Pet pet = owner.SummonPet(petentry, x, y, z, owner.Orientation, PetType.Summon, 0);
- if (!pet)
+ Pet pet = owner.SummonPet(petentry, petSlot, x, y, z, owner.Orientation, 0, out bool isNew);
+ if (pet == null)
return;
- if (m_caster.IsTypeId(TypeId.Unit))
+ if (isNew)
{
- if (m_caster.ToCreature().IsTotem())
- pet.SetReactState(ReactStates.Aggressive);
- else
- pet.SetReactState(ReactStates.Defensive);
+ if (m_caster.IsCreature())
+ {
+ if (m_caster.ToCreature().IsTotem())
+ pet.SetReactState(ReactStates.Aggressive);
+ else
+ pet.SetReactState(ReactStates.Defensive);
+ }
+
+ pet.SetCreatedBySpell(m_spellInfo.Id);
+
+ // generate new name for summon pet
+ string new_name = Global.ObjectMgr.GeneratePetName(petentry);
+ if (!string.IsNullOrEmpty(new_name))
+ pet.SetName(new_name);
}
- pet.SetCreatedBySpell(m_spellInfo.Id);
-
- // generate new name for summon pet
- string new_name = Global.ObjectMgr.GeneratePetName(petentry);
- if (!string.IsNullOrEmpty(new_name))
- pet.SetName(new_name);
-
ExecuteLogEffectSummonObject(effectInfo.Effect, pet);
}
@@ -4012,42 +4019,68 @@ namespace Game.Spells
}
[SpellEffectHandler(SpellEffectName.ResurrectPet)]
- void EffectSummonDeadPet()
+ void EffectResurrectPet()
{
if (effectHandleMode != SpellEffectHandleMode.Hit)
return;
+ if (damage < 0)
+ return;
+
Player player = m_caster.ToPlayer();
if (player == null)
return;
- Pet pet = player.GetPet();
- if (pet != null && pet.IsAlive())
- return;
+ // Maybe player dismissed dead pet or pet despawned?
+ bool hadPet = true;
- if (damage < 0)
- return;
-
- float x, y, z;
- player.GetPosition(out x, out y, out z);
- if (pet == null)
+ if (player.GetPet() == null)
{
- player.SummonPet(0, x, y, z, player.Orientation, PetType.Summon, 0);
- pet = player.GetPet();
+ PetStable petStable = player.GetPetStable();
+ var deadPetIndex = Array.FindIndex(petStable.ActivePets, petInfo => petInfo?.Health == 0);
+
+ PetSaveMode slot = (PetSaveMode)deadPetIndex;
+
+ player.SummonPet(0, slot, 0f, 0f, 0f, 0f, 0);
+ hadPet = false;
}
- if (pet == null)
+
+ // 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 == null || pet.IsAlive())
return;
- player.GetMap().CreatureRelocation(pet, x, y, z, player.GetOrientation());
+ // 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
+ // We can use a different, more accurate version of GetClosePoint() since we have a pet
+ // Will be used later to reposition the pet if we have one
+ player.GetClosePoint(out float x, out float y, out float z, pet.GetCombatReach(), SharedConst.PetFollowDist, pet.GetFollowAngle());
+ pet.NearTeleportTo(x, y, z, player.GetOrientation());
+ pet.Relocate(x, y, z, player.GetOrientation()); // This is needed so SaveStayPosition() will get the proper coords.
+ }
- pet.SetDynamicFlags(UnitDynFlags.HideModel);
+ pet.SetDynamicFlags(UnitDynFlags.None);
pet.RemoveUnitFlag(UnitFlags.Skinnable);
pet.SetDeathState(DeathState.Alive);
pet.ClearUnitState(UnitState.AllErasable);
pet.SetHealth(pet.CountPctFromMaxHealth(damage));
- pet.InitializeAI();
- player.PetSpellInitialize();
+ // Reset things for when the AI to takes over
+ CharmInfo ci = pet.GetCharmInfo();
+ if (ci != null)
+ {
+ // In case the pet was at stay, we don't want it running back
+ ci.SaveStayPosition();
+ ci.SetIsAtStay(ci.HasCommandState(CommandStates.Stay));
+
+ ci.SetIsFollowing(false);
+ ci.SetIsCommandAttack(false);
+ ci.SetIsCommandFollow(false);
+ ci.SetIsReturning(false);
+ }
+
pet.SavePetToDB(PetSaveMode.AsCurrent);
}
diff --git a/Source/Scripts/Spells/Hunter.cs b/Source/Scripts/Spells/Hunter.cs
index 17c92fe87..dfc17237a 100644
--- a/Source/Scripts/Spells/Hunter.cs
+++ b/Source/Scripts/Spells/Hunter.cs
@@ -405,6 +405,8 @@ namespace Scripts.Spells.Hunter
[Script]
class spell_hun_tame_beast : SpellScript
{
+ static uint[] CallPetSpellIds = { 883, 83242, 83243, 83244, 83245, };
+
SpellCastResult CheckCast()
{
Player caster = GetCaster().ToPlayer();
@@ -427,10 +429,18 @@ namespace Scripts.Spells.Hunter
PetStable petStable = caster.GetPetStable();
if (petStable != null)
{
- if (petStable.CurrentPet != null)
+ if (petStable.CurrentPetIndex.HasValue)
return SpellCastResult.AlreadyHaveSummon;
- if (petStable.GetUnslottedHunterPet() != null)
+ var freeSlotIndex = Array.FindIndex(petStable.ActivePets, petInfo => petInfo == null);
+ if (freeSlotIndex == -1)
+ {
+ caster.SendTameFailure(PetTameResult.TooMany);
+ return SpellCastResult.DontReport;
+ }
+
+ // Check for known Call Pet X spells
+ if (!caster.HasSpell(CallPetSpellIds[freeSlotIndex]))
{
caster.SendTameFailure(PetTameResult.TooMany);
return SpellCastResult.DontReport;