Core/Pets: Pet management refactoring

Port From (https://github.com/TrinityCore/TrinityCore/commit/ca92686b44cc8c53a8991bf02d6e8534797fc115)
This commit is contained in:
hondacrx
2022-03-10 09:39:55 -05:00
parent 9d5dd66215
commit 718302fa18
14 changed files with 549 additions and 378 deletions
@@ -693,13 +693,7 @@ namespace Framework.Database
PrepareStatement(CharStatements.DEL_CALENDAR_INVITE, "DELETE FROM calendar_invites WHERE InviteID = ?");
// Pet
PrepareStatement(CharStatements.SEL_PET_SLOTS, "SELECT owner, slot FROM character_pet WHERE owner = ? AND slot >= ? AND slot <= ? ORDER BY slot");
PrepareStatement(CharStatements.SEL_PET_SLOTS_DETAIL, "SELECT owner, id, entry, level, name, modelid FROM character_pet WHERE owner = ? AND slot >= ? AND slot <= ? ORDER BY slot");
PrepareStatement(CharStatements.SEL_PET_ENTRY, "SELECT entry FROM character_pet WHERE owner = ? AND id = ? AND slot >= ? AND slot <= ?");
PrepareStatement(CharStatements.SEL_PET_SLOT_BY_ID, "SELECT slot, entry FROM character_pet WHERE owner = ? AND id = ?");
PrepareStatement(CharStatements.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 <> ?");
PrepareStatement(CharStatements.SEL_CHAR_PET, "SELECT id FROM character_pet WHERE owner = ? AND id <> ?");
PrepareStatement(CharStatements.SEL_CHAR_PETS, "SELECT id FROM character_pet WHERE owner = ?");
PrepareStatement(CharStatements.SEL_CHAR_PET_IDS, "SELECT id FROM character_pet WHERE owner = ?");
PrepareStatement(CharStatements.DEL_CHAR_PET_DECLINEDNAME_BY_OWNER, "DELETE FROM character_pet_declinedname WHERE owner = ?");
PrepareStatement(CharStatements.DEL_CHAR_PET_DECLINEDNAME, "DELETE FROM character_pet_declinedname WHERE id = ?");
PrepareStatement(CharStatements.INS_CHAR_PET_DECLINEDNAME, "INSERT INTO character_pet_declinedname (id, owner, genitive, dative, accusative, instrumental, prepositional) VALUES (?, ?, ?, ?, ?, ?, ?)");
@@ -722,14 +716,9 @@ namespace Framework.Database
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
PrepareStatement(CharStatements.INS_PET_AURA_EFFECT, "INSERT INTO pet_aura_effect (guid, casterGuid, spell, effectMask, effectIndex, amount, baseAmount) " +
"VALUES (?, ?, ?, ?, ?, ?, ?)");
PrepareStatement(CharStatements.SEL_CHAR_PET_BY_ENTRY, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType, specialization FROM character_pet WHERE owner = ? AND id = ?");
PrepareStatement(CharStatements.SEL_CHAR_PET_BY_ENTRY_AND_SLOT_2, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType, specialization FROM character_pet WHERE owner = ? AND entry = ? AND (slot = ? OR slot > ?)");
PrepareStatement(CharStatements.SEL_CHAR_PET_BY_SLOT, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType, specialization FROM character_pet WHERE owner = ? AND (slot = ? OR slot > ?) ");
PrepareStatement(CharStatements.SEL_CHAR_PET_BY_ENTRY_AND_SLOT, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType, specialization FROM character_pet WHERE owner = ? AND slot = ?");
PrepareStatement(CharStatements.SEL_CHAR_PETS, "SELECT id, entry, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType, specialization FROM character_pet WHERE owner = ?");
PrepareStatement(CharStatements.DEL_CHAR_PET_BY_OWNER, "DELETE FROM character_pet WHERE owner = ?");
PrepareStatement(CharStatements.UPD_CHAR_PET_NAME, "UPDATE character_pet SET name = ?, renamed = 1 WHERE owner = ? AND id = ?");
PrepareStatement(CharStatements.UPD_CHAR_PET_SLOT_BY_SLOT_EXCLUDE_ID, "UPDATE character_pet SET slot = ? WHERE owner = ? AND slot = ? AND id <> ?");
PrepareStatement(CharStatements.UPD_CHAR_PET_SLOT_BY_SLOT, "UPDATE character_pet SET slot = ? WHERE owner = ? AND slot = ?");
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 = ? OR slot > ?)");
@@ -1356,23 +1345,12 @@ namespace Framework.Database
DEL_PET_SPELLS,
DEL_CHAR_PET_BY_OWNER,
DEL_CHAR_PET_DECLINEDNAME_BY_OWNER,
SEL_CHAR_PET_BY_ENTRY_AND_SLOT,
SEL_PET_SLOTS,
SEL_PET_SLOTS_DETAIL,
SEL_PET_ENTRY,
SEL_PET_SLOT_BY_ID,
SEL_PET_SPELL_LIST,
SEL_CHAR_PET,
SEL_CHAR_PET_IDS,
SEL_CHAR_PETS,
INS_CHAR_PET,
SEL_CHAR_PET_BY_ENTRY,
SEL_CHAR_PET_BY_ENTRY_AND_SLOT_2,
SEL_CHAR_PET_BY_SLOT,
DEL_CHAR_PET_DECLINEDNAME,
INS_CHAR_PET_DECLINEDNAME,
UPD_CHAR_PET_NAME,
UPD_CHAR_PET_SLOT_BY_SLOT_EXCLUDE_ID,
UPD_CHAR_PET_SLOT_BY_SLOT,
UPD_CHAR_PET_SLOT_BY_ID,
DEL_CHAR_PET_BY_ID,
DEL_CHAR_PET_BY_SLOT,
+8 -26
View File
@@ -52,41 +52,23 @@ namespace Game.Chat
}
// Everything looks OK, create new pet
Pet pet = new(player, PetType.Hunter);
if (!pet.CreateBaseAtCreature(creatureTarget))
{
handler.SendSysMessage("Error 1");
return false;
}
Pet pet = player.CreateTamedPetFrom(creatureTarget);
creatureTarget.SetDeathState(DeathState.JustDied);
creatureTarget.RemoveCorpse();
creatureTarget.SetHealth(0); // just for nice GM-mode view
pet.SetCreatorGUID(player.GetGUID());
pet.SetFaction(player.GetFaction());
if (!pet.InitStatsForLevel(creatureTarget.GetLevel()))
{
Log.outError(LogFilter.ChatSystem, "InitStatsForLevel() in EffectTameCreature failed! Pet deleted.");
handler.SendSysMessage("Error 2");
return false;
}
// "kill" original creature
creatureTarget.DespawnOrUnsummon();
// prepare visual effect for levelup
pet.SetLevel(creatureTarget.GetLevel() - 1);
pet.GetCharmInfo().SetPetNumber(Global.ObjectMgr.GeneratePetNumber(), true);
// this enables pet details window (Shift+P)
pet.InitPetCreateSpells();
pet.SetFullHealth();
pet.SetLevel(player.GetLevel() - 1);
// add to world
pet.GetMap().AddToMap(pet.ToCreature());
// visual effect for levelup
pet.SetLevel(creatureTarget.GetLevel());
pet.SetLevel(player.GetLevel());
// caster have pet now
player.SetMinion(pet, true);
pet.SavePetToDB(PetSaveMode.AsCurrent);
player.PetSpellInitialize();
+338 -258
View File
@@ -96,89 +96,95 @@ namespace Game.Entities
}
}
public bool LoadPetFromDB(Player owner, uint petEntry = 0, uint petnumber = 0, bool current = false)
public static Tuple<PetStable.PetInfo, PetSaveMode> GetLoadPetInfo(PetStable stable, uint petEntry, uint petnumber, bool current)
{
m_loading = true;
ulong ownerid = owner.GetGUID().GetCounter();
PreparedStatement stmt;
if (petnumber != 0)
{
// Known petnumber entry
stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_PET_BY_ENTRY);
stmt.AddValue(0, ownerid);
stmt.AddValue(1, petnumber);
if (stable.CurrentPet != null && stable.CurrentPet.PetNumber == petnumber)
return Tuple.Create(stable.CurrentPet, PetSaveMode.AsCurrent);
for (var stableSlot = 0; stableSlot < stable.StabledPets.Length; ++stableSlot)
if (stable.StabledPets[stableSlot] != null && stable.StabledPets[stableSlot].PetNumber == petnumber)
return Tuple.Create(stable.StabledPets[stableSlot], PetSaveMode.FirstStableSlot + stableSlot);
foreach (var pet in stable.UnslottedPets)
if (pet.CreatureId == petEntry)
return Tuple.Create(pet, PetSaveMode.NotInSlot);
}
else if (current)
{
// Current pet (slot 0)
stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_PET_BY_ENTRY_AND_SLOT);
stmt.AddValue(0, ownerid);
stmt.AddValue(1, (byte)PetSaveMode.AsCurrent);
if (stable.CurrentPet != null)
return Tuple.Create(stable.CurrentPet, PetSaveMode.AsCurrent);
}
else if (petEntry != 0)
{
// known petEntry entry (unique for summoned pet, but non unique for hunter pet (only from current or not stabled pets)
stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_PET_BY_ENTRY_AND_SLOT_2);
stmt.AddValue(0, ownerid);
stmt.AddValue(1, petEntry);
stmt.AddValue(2, (byte)PetSaveMode.AsCurrent);
stmt.AddValue(3, (byte)PetSaveMode.LastStableSlot);
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)
return Tuple.Create(pet, PetSaveMode.NotInSlot);
}
else
{
// Any current or other non-stabled pet (for hunter "call pet")
stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_PET_BY_SLOT);
stmt.AddValue(0, ownerid);
stmt.AddValue(1, (byte)PetSaveMode.AsCurrent);
stmt.AddValue(2, (byte)PetSaveMode.LastStableSlot);
if (stable.CurrentPet != null)
return Tuple.Create(stable.CurrentPet, PetSaveMode.AsCurrent);
if (!stable.UnslottedPets.Empty())
return Tuple.Create(stable.UnslottedPets.First(), PetSaveMode.NotInSlot);
}
SQLResult result = DB.Characters.Query(stmt);
if (result.IsEmpty())
return Tuple.Create<PetStable.PetInfo, PetSaveMode>(null, PetSaveMode.AsDeleted);
}
public bool LoadPetFromDB(Player owner, uint petEntry = 0, uint petnumber = 0, bool current = false)
{
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)
{
m_loading = false;
return false;
}
// update for case of current pet "slot = 0"
petEntry = result.Read<uint>(1);
if (petEntry == 0)
return false;
uint summonSpellId = result.Read<uint>(14);
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(summonSpellId, owner.GetMap().GetDifficultyID());
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(petInfo.CreatedBySpellId, owner.GetMap().GetDifficultyID());
bool isTemporarySummon = spellInfo != null && spellInfo.GetDuration() > 0;
if (current && isTemporarySummon)
return false;
PetType petType = (PetType)result.Read<byte>(15);
if (petType == PetType.Hunter)
if (petInfo.Type == PetType.Hunter)
{
CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate(petEntry);
CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate(petInfo.CreatureId);
if (creatureInfo == null || !creatureInfo.IsTameable(owner.CanTameExoticPets()))
return false;
}
uint petId = result.Read<uint>(0);
if (current && owner.IsPetNeedBeTemporaryUnsummoned())
{
owner.SetTemporaryUnsummonedPetNumber(petId);
owner.SetTemporaryUnsummonedPetNumber(petInfo.PetNumber);
return false;
}
Map map = owner.GetMap();
if (!Create(map.GenerateLowGuid(HighGuid.Pet), map, petEntry))
ulong guid = map.GenerateLowGuid(HighGuid.Pet);
if (!Create(guid, map, petInfo.CreatureId, petInfo.PetNumber))
return false;
PhasingHandler.InheritPhaseShift(this, owner);
SetPetType(petType);
SetPetType(petInfo.Type);
SetFaction(owner.GetFaction());
SetCreatedBySpell(summonSpellId);
SetCreatedBySpell(petInfo.CreatedBySpellId);
float px, py, pz;
if (IsCritter())
@@ -197,14 +203,14 @@ namespace Game.Entities
return true;
}
GetCharmInfo().SetPetNumber(petId, IsPermanentPetFor(owner));
GetCharmInfo().SetPetNumber(petInfo.PetNumber, IsPermanentPetFor(owner));
SetDisplayId(result.Read<uint>(3));
SetNativeDisplayId(result.Read<uint>(3));
uint petlevel = result.Read<ushort>(4);
SetDisplayId(petInfo.DisplayId);
SetNativeDisplayId(petInfo.DisplayId);
uint petlevel = petInfo.Level;
SetNpcFlags(NPCFlags.None);
SetNpcFlags2(NPCFlags2.None);
SetName(result.Read<string>(8));
SetName(petInfo.Name);
switch (GetPetType())
{
@@ -218,7 +224,7 @@ namespace Game.Entities
SetClass(Class.Warrior);
SetGender(Gender.None);
SetSheath(SheathState.Melee);
SetPetFlags(result.Read<bool>(9) ? UnitPetFlags.CanBeAbandoned : UnitPetFlags.CanBeRenamed | UnitPetFlags.CanBeAbandoned);
SetPetFlags(petInfo.WasRenamed ? UnitPetFlags.CanBeAbandoned : UnitPetFlags.CanBeRenamed | UnitPetFlags.CanBeAbandoned);
SetUnitFlags(UnitFlags.PlayerControlled); // this enables popup window (pet abandon, cancel)
break;
default:
@@ -231,7 +237,7 @@ namespace Game.Entities
SetCreatorGUID(owner.GetGUID());
InitStatsForLevel(petlevel);
SetPetExperience(result.Read<uint>(5));
SetPetExperience(petInfo.Experience);
SynchronizeLevelWithOwner();
@@ -244,15 +250,15 @@ namespace Game.Entities
return false;
}
SetReactState((ReactStates)result.Read<byte>(6));
SetReactState(petInfo.ReactState);
SetCanModifyStats(true);
if (GetPetType() == PetType.Summon && !current) //all (?) summon pets come with full health when called, but not when they are current
SetFullPower(PowerType.Mana);
else
{
uint savedhealth = result.Read<uint>(10);
uint savedmana = result.Read<uint>(11);
uint savedhealth = petInfo.Health;
uint savedmana = petInfo.Mana;
if (savedhealth == 0 && GetPetType() == PetType.Hunter)
SetDeathState(DeathState.JustDied);
else
@@ -266,102 +272,108 @@ namespace Game.Entities
// 0=current
// 1..MAX_PET_STABLES in stable slot
// PET_SAVE_NOT_IN_SLOT(100) = not stable slot (summoning))
if (result.Read<byte>(7) != 0)
if (slot == PetSaveMode.NotInSlot)
{
stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_PET_SLOT_BY_SLOT_EXCLUDE_ID);
stmt.AddValue(0, (byte)PetSaveMode.NotInSlot);
stmt.AddValue(1, ownerid);
stmt.AddValue(2, (byte)PetSaveMode.AsCurrent);
stmt.AddValue(3, GetCharmInfo().GetPetNumber());
DB.Characters.Execute(stmt);
var unslottedPetInfo = petStable.UnslottedPets.Find(unslottedPet => unslottedPet.PetNumber == petInfo.PetNumber);
Cypher.Assert(petStable.CurrentPet == null);
Cypher.Assert(unslottedPetInfo != null);
stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_PET_SLOT_BY_ID);
stmt.AddValue(0, (byte)PetSaveMode.AsCurrent);
stmt.AddValue(1, ownerid);
stmt.AddValue(2, GetCharmInfo().GetPetNumber());
DB.Characters.Execute(stmt);
petStable.CurrentPet = unslottedPetInfo;
petStable.UnslottedPets.Remove(unslottedPetInfo);
}
// Send fake summon spell cast - this is needed for correct cooldown application for spells
// Example: 46584 - without this cooldown (which should be set always when pet is loaded) isn't set clientside
// @todo pets should be summoned from real cast instead of just faking it?
if (summonSpellId != 0)
if (petInfo.CreatedBySpellId != 0)
{
SpellGo spellGo = new();
SpellCastData castData = spellGo.Cast;
castData.CasterGUID = owner.GetGUID();
castData.CasterUnit = owner.GetGUID();
castData.CastID = ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, owner.GetMapId(), summonSpellId, map.GenerateLowGuid(HighGuid.Cast));
castData.SpellID = (int)summonSpellId;
castData.CastID = ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, owner.GetMapId(), petInfo.CreatedBySpellId, map.GenerateLowGuid(HighGuid.Cast));
castData.SpellID = (int)petInfo.CreatedBySpellId;
castData.CastFlags = SpellCastFlags.Unk9;
castData.CastTime = Time.GetMSTime();
owner.SendMessageToSet(spellGo, true);
}
owner.SetMinion(this, true);
if (!isTemporarySummon)
GetCharmInfo().LoadPetActionBar(petInfo.ActionBar);
map.AddToMap(ToCreature());
uint timediff = (uint)(GameTime.GetGameTime() - result.Read<uint>(13));
_LoadAuras(timediff);
// load action bar, if data broken will fill later by default spells.
if (!isTemporarySummon)
{
GetCharmInfo().LoadPetActionBar(result.Read<string>(12));
_LoadSpells();
_LoadSpellCooldowns();
LearnPetPassives();
InitLevelupSpellsForLevel();
if (map.IsBattleArena())
RemoveArenaAuras();
CastPetAuras(current);
}
Log.outDebug(LogFilter.Pet, "New Pet has guid {0}", GetGUID().ToString());
ushort specId = result.Read<ushort>(16);
ChrSpecializationRecord petSpec = CliDB.ChrSpecializationStorage.LookupByKey(specId);
if (petSpec != null)
specId = (ushort)Global.DB2Mgr.GetChrSpecializationByIndex(owner.HasAuraType(AuraType.OverridePetSpecs) ? Class.Max : 0, petSpec.OrderIndex).Id;
SetSpecialization(specId);
// The SetSpecialization function will run these functions if the pet's spec is not 0
if (GetSpecialization() == 0)
{
CleanupActionBar(); // remove unknown spells from action bar after load
owner.PetSpellInitialize();
}
SetGroupUpdateFlag(GroupUpdatePetFlags.Full);
if (GetPetType() == PetType.Hunter)
{
stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PET_DECLINED_NAME);
stmt.AddValue(0, owner.GetGUID().GetCounter());
stmt.AddValue(1, GetCharmInfo().GetPetNumber());
result = DB.Characters.Query(stmt);
if (!result.IsEmpty())
{
_declinedname = new DeclinedName();
for (byte i = 0; i < SharedConst.MaxDeclinedNameCases; ++i)
{
_declinedname.name[i] = result.Read<string>(i);
}
}
}
//set last used pet number (for use in BG's)
if (owner.IsTypeId(TypeId.Player) && IsControlled() && !IsTemporarySummoned() && (GetPetType() == PetType.Summon || GetPetType() == PetType.Hunter))
owner.ToPlayer().SetLastPetNumber(petId);
if (owner.IsPlayer() && IsControlled() && !IsTemporarySummoned() && (GetPetType() == PetType.Summon || GetPetType() == PetType.Hunter))
owner.ToPlayer().SetLastPetNumber(petInfo.PetNumber);
// must be after SetMinion (owner guid check)
LoadTemplateImmunities();
m_loading = false;
owner.GetSession().AddQueryHolderCallback(DB.Characters.DelayQueryHolder(new PetLoadQueryHolder(ownerid, petInfo.PetNumber)))
.AfterComplete(holder =>
{
var session = owner.GetSession();
var lastSaveTime = petInfo.LastSaveTime;
var specializationId = petInfo.SpecializationId;
if (session.GetPlayer() != owner || owner.GetPet() != this)
return;
// passing previous checks ensure that 'this' is still valid
if (m_removed)
return;
uint timediff = (uint)(GameTime.GetGameTime() - lastSaveTime);
_LoadAuras(holder.GetResult(PetLoginQueryLoad.Auras), holder.GetResult(PetLoginQueryLoad.AuraEffects), timediff);
// load action bar, if data broken will fill later by default spells.
if (!isTemporarySummon)
{
_LoadSpells(holder.GetResult(PetLoginQueryLoad.Spells));
GetSpellHistory().LoadFromDB<Pet>(holder.GetResult(PetLoginQueryLoad.Cooldowns), holder.GetResult(PetLoginQueryLoad.Charges));
LearnPetPassives();
InitLevelupSpellsForLevel();
if (GetMap().IsBattleArena())
RemoveArenaAuras();
CastPetAuras(current);
}
Log.outDebug(LogFilter.Pet, $"New Pet has {GetGUID()}");
ushort specId = specializationId;
var petSpec = CliDB.ChrSpecializationStorage.LookupByKey(specId);
if (petSpec != null)
specId = (ushort)Global.DB2Mgr.GetChrSpecializationByIndex(owner.HasAuraType(AuraType.OverridePetSpecs) ? Class.Max : 0, petSpec.OrderIndex).Id;
SetSpecialization(specId);
// The SetSpecialization function will run these functions if the pet's spec is not 0
if (GetSpecialization() == 0)
{
CleanupActionBar(); // remove unknown spells from action bar after load
owner.PetSpellInitialize();
}
SetGroupUpdateFlag(GroupUpdatePetFlags.Full);
if (GetPetType() == PetType.Hunter)
{
SQLResult result = holder.GetResult(PetLoginQueryLoad.DeclinedNames);
if (!result.IsEmpty())
{
_declinedname = new();
for (byte i = 0; i < SharedConst.MaxDeclinedNameCases; ++i)
_declinedname.name[i] = result.Read<string>(i);
}
}
// must be after SetMinion (owner guid check)
LoadTemplateImmunities();
m_loading = false;
});
return true;
}
@@ -421,16 +433,6 @@ namespace Game.Entities
stmt.AddValue(0, GetCharmInfo().GetPetNumber());
trans.Append(stmt);
// prevent duplicate using slot (except PET_SAVE_NOT_IN_SLOT)
if (mode <= PetSaveMode.LastStableSlot)
{
stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_PET_SLOT_BY_SLOT);
stmt.AddValue(0, (byte)PetSaveMode.NotInSlot);
stmt.AddValue(1, ownerLowGUID);
stmt.AddValue(2, (byte)mode);
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))
{
@@ -442,6 +444,11 @@ namespace Game.Entities
}
// save pet
string actionBar = GenerateActionBarData();
Cypher.Assert(owner.GetPetStable().CurrentPet != null && owner.GetPetStable().CurrentPet.PetNumber == GetCharmInfo().GetPetNumber());
FillPetInfo(owner.GetPetStable().CurrentPet);
stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_PET);
stmt.AddValue(0, GetCharmInfo().GetPetNumber());
stmt.AddValue(1, GetEntry());
@@ -455,11 +462,13 @@ namespace Game.Entities
stmt.AddValue(9, HasPetFlag(UnitPetFlags.CanBeRenamed) ? 0 : 1);
stmt.AddValue(10, curhealth);
stmt.AddValue(11, curmana);
stmt.AddValue(12, GenerateActionBarData());
stmt.AddValue(12, actionBar);
stmt.AddValue(13, GameTime.GetGameTime());
stmt.AddValue(14, m_unitData.CreatedBySpell);
stmt.AddValue(15, (byte)GetPetType());
stmt.AddValue(16, m_petSpecialization);
stmt.AddValue(16, GetSpecialization());
trans.Append(stmt);
DB.Characters.CommitTransaction(trans);
@@ -472,36 +481,55 @@ namespace Game.Entities
}
}
public static void DeleteFromDB(uint guidlow)
public void FillPetInfo(PetStable.PetInfo petInfo)
{
petInfo.PetNumber = GetCharmInfo().GetPetNumber();
petInfo.CreatureId = GetEntry();
petInfo.DisplayId = GetNativeDisplayId();
petInfo.Level = (byte)GetLevel();
petInfo.Experience = m_unitData.PetExperience;
petInfo.ReactState = GetReactState();
petInfo.Name = GetName();
petInfo.WasRenamed = !HasPetFlag(UnitPetFlags.CanBeRenamed);
petInfo.Health = (uint)GetHealth();
petInfo.Mana = (uint)GetPower(PowerType.Mana);
petInfo.ActionBar = GenerateActionBarData();
petInfo.LastSaveTime = (uint)GameTime.GetGameTime();
petInfo.CreatedBySpellId = m_unitData.CreatedBySpell;
petInfo.Type = GetPetType();
petInfo.SpecializationId = GetSpecialization();
}
public static void DeleteFromDB(uint petNumber)
{
SQLTransaction trans = new();
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_PET_BY_ID);
stmt.AddValue(0, guidlow);
stmt.AddValue(0, petNumber);
trans.Append(stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_PET_DECLINEDNAME);
stmt.AddValue(0, guidlow);
stmt.AddValue(0, petNumber);
trans.Append(stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PET_AURA_EFFECTS);
stmt.AddValue(0, guidlow);
stmt.AddValue(0, petNumber);
trans.Append(stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PET_AURAS);
stmt.AddValue(0, guidlow);
stmt.AddValue(0, petNumber);
trans.Append(stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PET_SPELLS);
stmt.AddValue(0, guidlow);
stmt.AddValue(0, petNumber);
trans.Append(stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PET_SPELL_COOLDOWNS);
stmt.AddValue(0, guidlow);
stmt.AddValue(0, petNumber);
trans.Append(stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PET_SPELL_CHARGES);
stmt.AddValue(0, guidlow);
stmt.AddValue(0, petNumber);
trans.Append(stmt);
DB.Characters.CommitTransaction(trans);
@@ -536,72 +564,73 @@ namespace Game.Entities
switch (m_deathState)
{
case DeathState.Corpse:
{
if (GetPetType() != PetType.Hunter || m_corpseRemoveTime <= GameTime.GetGameTime())
{
if (GetPetType() != PetType.Hunter || m_corpseRemoveTime <= GameTime.GetGameTime())
{
Remove(PetSaveMode.NotInSlot); //hunters' pets never get removed because of death, NEVER!
return;
}
break;
Remove(PetSaveMode.NotInSlot); //hunters' pets never get removed because of death, NEVER!
return;
}
break;
}
case DeathState.Alive:
{
// unsummon pet that lost owner
Player owner = GetOwner();
if (owner == null || (!IsWithinDistInMap(owner, GetMap().GetVisibilityRange()) && !IsPossessed()) || (IsControlled() && owner.GetPetGUID().IsEmpty()))
{
// unsummon pet that lost owner
Player owner = GetOwner();
if (owner == null || (!IsWithinDistInMap(owner, GetMap().GetVisibilityRange()) && !IsPossessed()) || (IsControlled() && owner.GetPetGUID().IsEmpty()))
Remove(PetSaveMode.NotInSlot, true);
return;
}
if (IsControlled())
{
if (owner.GetPetGUID() != GetGUID())
{
Remove(PetSaveMode.NotInSlot, true);
Log.outError(LogFilter.Pet, $"Pet {GetEntry()} is not pet of owner {GetOwner().GetName()}, removed");
Cypher.Assert(GetPetType() != PetType.Hunter, $"Unexpected unlinked pet found for owner {owner.GetSession().GetPlayerInfo()}");
Remove(PetSaveMode.NotInSlot);
return;
}
if (IsControlled())
{
if (owner.GetPetGUID() != GetGUID())
{
Log.outError(LogFilter.Pet, "Pet {0} is not pet of owner {1}, removed", GetEntry(), GetOwner().GetName());
Remove(GetPetType() == PetType.Hunter ? PetSaveMode.AsDeleted : PetSaveMode.NotInSlot);
return;
}
}
if (m_duration > 0)
{
if (m_duration > diff)
m_duration -= (int)diff;
else
{
Remove(GetPetType() != PetType.Summon ? PetSaveMode.AsDeleted : PetSaveMode.NotInSlot);
return;
}
}
//regenerate focus for hunter pets or energy for deathknight's ghoul
if (m_focusRegenTimer != 0)
{
if (m_focusRegenTimer > diff)
m_focusRegenTimer -= diff;
else
{
switch (GetPowerType())
{
case PowerType.Focus:
Regenerate(PowerType.Focus);
m_focusRegenTimer += PetFocusRegenInterval - diff;
if (m_focusRegenTimer == 0)
++m_focusRegenTimer;
// Reset if large diff (lag) causes focus to get 'stuck'
if (m_focusRegenTimer > PetFocusRegenInterval)
m_focusRegenTimer = PetFocusRegenInterval;
break;
default:
m_focusRegenTimer = 0;
break;
}
}
}
break;
}
if (m_duration > 0)
{
if (m_duration > diff)
m_duration -= (int)diff;
else
{
Remove(GetPetType() != PetType.Summon ? PetSaveMode.AsDeleted : PetSaveMode.NotInSlot);
return;
}
}
//regenerate focus for hunter pets or energy for deathknight's ghoul
if (m_focusRegenTimer != 0)
{
if (m_focusRegenTimer > diff)
m_focusRegenTimer -= diff;
else
{
switch (GetPowerType())
{
case PowerType.Focus:
Regenerate(PowerType.Focus);
m_focusRegenTimer += PetFocusRegenInterval - diff;
if (m_focusRegenTimer == 0)
++m_focusRegenTimer;
// Reset if large diff (lag) causes focus to get 'stuck'
if (m_focusRegenTimer > PetFocusRegenInterval)
m_focusRegenTimer = PetFocusRegenInterval;
break;
default:
m_focusRegenTimer = 0;
break;
}
}
}
break;
}
default:
break;
}
@@ -714,7 +743,7 @@ namespace Game.Entities
bool CreateBaseAtTamed(CreatureTemplate cinfo, Map map)
{
Log.outDebug(LogFilter.Pet, "CreateBaseForTamed");
if (!Create(map.GenerateLowGuid(HighGuid.Pet), map, cinfo.Entry))
if (!Create(map.GenerateLowGuid(HighGuid.Pet), map, cinfo.Entry, Global.ObjectMgr.GeneratePetNumber()))
return false;
SetPetNameTimestamp(0);
@@ -753,24 +782,8 @@ namespace Game.Entities
return diet.HasAnyFlag(FoodMask);
}
void _LoadSpellCooldowns()
void _LoadSpells(SQLResult result)
{
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PET_SPELL_COOLDOWN);
stmt.AddValue(0, GetCharmInfo().GetPetNumber());
SQLResult cooldownsResult = DB.Characters.Query(stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PET_SPELL_CHARGES);
SQLResult chargesResult = DB.Characters.Query(stmt);
GetSpellHistory().LoadFromDB<Pet>(cooldownsResult, chargesResult);
}
void _LoadSpells()
{
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PET_SPELL);
stmt.AddValue(0, GetCharmInfo().GetPetNumber());
SQLResult result = DB.Characters.Query(stmt);
if (!result.IsEmpty())
{
do
@@ -827,58 +840,52 @@ namespace Game.Entities
}
}
void _LoadAuras(uint timediff)
void _LoadAuras(SQLResult auraResult, SQLResult effectResult, uint timediff)
{
Log.outDebug(LogFilter.Pet, "Loading auras for {0}", GetGUID().ToString());
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PET_AURA_EFFECT);
stmt.AddValue(0, GetCharmInfo().GetPetNumber());
ObjectGuid casterGuid = new();
ObjectGuid itemGuid = new();
ObjectGuid casterGuid = default;
ObjectGuid itemGuid = default;
Dictionary<AuraKey, AuraLoadEffectInfo> effectInfo = new();
SQLResult result = DB.Characters.Query(stmt);
if (!result.IsEmpty())
if (!effectResult.IsEmpty())
{
do
{
uint effectIndex = result.Read<byte>(3);
uint effectIndex = effectResult.Read<byte>(3);
if (effectIndex < SpellConst.MaxEffects)
{
casterGuid.SetRawValue(result.Read<byte[]>(0));
casterGuid.SetRawValue(effectResult.Read<byte[]>(0));
if (casterGuid.IsEmpty())
casterGuid = GetGUID();
AuraKey key = new(casterGuid, itemGuid, result.Read<uint>(1), result.Read<uint>(2));
AuraKey key = new(casterGuid, itemGuid, effectResult.Read<uint>(1), effectResult.Read<uint>(2));
if (!effectInfo.ContainsKey(key))
effectInfo[key] = new AuraLoadEffectInfo();
var info = effectInfo[key];
info.Amounts[effectIndex] = result.Read<int>(4);
info.BaseAmounts[effectIndex] = result.Read<int>(5);
info.Amounts[effectIndex] = effectResult.Read<int>(4);
info.BaseAmounts[effectIndex] = effectResult.Read<int>(5);
}
} while (result.NextRow());
} while (effectResult.NextRow());
}
stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PET_AURA);
stmt.AddValue(0, GetCharmInfo().GetPetNumber());
result = DB.Characters.Query(stmt);
if (!result.IsEmpty())
if (!auraResult.IsEmpty())
{
do
{
// NULL guid stored - pet is the caster of the spell - see Pet._SaveAuras
casterGuid.SetRawValue(result.Read<byte[]>(0));
casterGuid.SetRawValue(auraResult.Read<byte[]>(0));
if (casterGuid.IsEmpty())
casterGuid = GetGUID();
AuraKey key = new(casterGuid, itemGuid, result.Read<uint>(1), result.Read<uint>(2));
uint recalculateMask = result.Read<uint>(3);
Difficulty difficulty = (Difficulty)result.Read<byte>(4);
byte stackCount = result.Read<byte>(5);
int maxDuration = result.Read<int>(6);
int remainTime = result.Read<int>(7);
byte remainCharges = result.Read<byte>(8);
AuraKey key = new(casterGuid, itemGuid, auraResult.Read<uint>(1), auraResult.Read<uint>(2));
uint recalculateMask = auraResult.Read<uint>(3);
Difficulty difficulty = (Difficulty)auraResult.Read<byte>(4);
byte stackCount = auraResult.Read<byte>(5);
int maxDuration = auraResult.Read<int>(6);
int remainTime = auraResult.Read<int>(7);
byte remainCharges = auraResult.Read<byte>(8);
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(key.SpellId, difficulty);
if (spellInfo == null)
@@ -931,7 +938,7 @@ namespace Game.Entities
Log.outInfo(LogFilter.Pet, "Added aura spellid {0}, effectmask {1}", spellInfo.Id, key.EffectMask);
}
}
while (result.NextRow());
while (auraResult.NextRow());
}
}
@@ -1152,9 +1159,9 @@ namespace Game.Entities
PetDefaultSpellsEntry defSpells = Global.SpellMgr.GetPetDefaultSpellsEntry((int)GetEntry());
if (defSpells != null)
{
for (byte i = 0; i < SharedConst.MaxCreatureSpellDataSlots; ++i)
foreach (uint spellId in defSpells.spellid)
{
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(defSpells.spellid[i], Difficulty.None);
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId, Difficulty.None);
if (spellInfo == null)
continue;
@@ -1334,17 +1341,18 @@ namespace Game.Entities
}
}
public bool Create(ulong guidlow, Map map, uint Entry)
public bool Create(ulong guidlow, Map map, uint entry, uint petNumber)
{
Cypher.Assert(map);
SetMap(map);
_Create(ObjectGuid.Create(HighGuid.Pet, map.GetId(), Entry, guidlow));
// TODO: counter should be constructed as (summon_count << 32) | petNumber
_Create(ObjectGuid.Create(HighGuid.Pet, map.GetId(), entry, guidlow));
m_spawnId = guidlow;
m_originalEntry = Entry;
m_originalEntry = entry;
if (!InitEntry(Entry))
if (!InitEntry(entry))
return false;
// Force regen flag for player pets, just like we do for players themselves
@@ -1379,8 +1387,8 @@ namespace Game.Entities
// For general hunter pets skill 270
// Passive 01~10, Passive 00 (20782, not used), Ferocious Inspiration (34457)
// Scale 01~03 (34902~34904, bonus from owner, not used)
foreach (var petSet in petStore)
AddSpell(petSet, ActiveStates.Decide, PetSpellState.New, PetSpellType.Family);
foreach (var spellId in petStore)
AddSpell(spellId, ActiveStates.Decide, PetSpellState.New, PetSpellType.Family);
}
}
@@ -1420,10 +1428,9 @@ namespace Game.Entities
// if the owner has that pet aura, return true
foreach (var petAura in owner.m_petAuras)
{
if (petAura.GetAura(GetEntry()) == aura.GetId())
return true;
}
return false;
}
@@ -1476,7 +1483,7 @@ namespace Game.Entities
return base.GetNativeObjectScale();
}
public override void SetDisplayId(uint modelId, float displayScale = 1f)
{
base.SetDisplayId(modelId, displayScale);
@@ -1619,9 +1626,9 @@ namespace Game.Entities
public override string GetDebugInfo()
{
return $"{base.GetDebugInfo()}\nPetType: {GetPetType()}";
return $"{base.GetDebugInfo()}\nPetType: {GetPetType()} PetNumber: {GetCharmInfo().GetPetNumber()}";
}
public DeclinedName GetDeclinedNames() { return _declinedname; }
public new Dictionary<uint, PetSpell> m_spells = new();
@@ -1644,6 +1651,38 @@ namespace Game.Entities
public PetSpellType type;
}
public class PetStable
{
public class PetInfo
{
public string Name;
public string ActionBar;
public uint PetNumber;
public uint CreatureId;
public uint DisplayId;
public uint Experience;
public uint Health;
public uint Mana;
public uint LastSaveTime;
public uint CreatedBySpellId;
public ushort SpecializationId;
public byte Level = 0;
public ReactStates ReactState;
public PetType Type = PetType.Max;
public bool WasRenamed;
}
public PetInfo CurrentPet; // PET_SAVE_AS_CURRENT
public PetInfo[] StabledPets = new PetInfo[SharedConst.MaxPetStables]; // PET_SAVE_FIRST_STABLE_SLOT - PET_SAVE_LAST_STABLE_SLOT
public uint MaxStabledPets = 0;
public List<PetInfo> UnslottedPets = new(); // PET_SAVE_NOT_IN_SLOT
public PetInfo GetUnslottedHunterPet()
{
return UnslottedPets.Count == 1 && UnslottedPets[0].Type == PetType.Hunter ? UnslottedPets[0] : null;
}
}
public enum ActiveStates
{
Passive = 0x01, // 0x01 - passive
@@ -1653,4 +1692,45 @@ namespace Game.Entities
Reaction = 0x06, // 0x02 | 0x04
Decide = 0x00 // custom
}
enum PetLoginQueryLoad
{
DeclinedNames,
Auras,
AuraEffects,
Spells,
Cooldowns,
Charges
}
class PetLoadQueryHolder : SQLQueryHolder<PetLoginQueryLoad>
{
public PetLoadQueryHolder(ulong ownerGuid, uint petNumber)
{
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PET_DECLINED_NAME);
stmt.AddValue(0, ownerGuid);
stmt.AddValue(1, petNumber);
SetQuery(PetLoginQueryLoad.DeclinedNames, stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PET_AURA);
stmt.AddValue(0, petNumber);
SetQuery(PetLoginQueryLoad.Auras, stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PET_AURA_EFFECT);
stmt.AddValue(0, petNumber);
SetQuery(PetLoginQueryLoad.AuraEffects, stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PET_SPELL);
stmt.AddValue(0, petNumber);
SetQuery(PetLoginQueryLoad.Spells, stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PET_SPELL_COOLDOWN);
stmt.AddValue(0, petNumber);
SetQuery(PetLoginQueryLoad.Cooldowns, stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PET_SPELL_CHARGES);
stmt.AddValue(0, petNumber);
SetQuery(PetLoginQueryLoad.Charges, stmt);
}
}
}
+51 -9
View File
@@ -1538,6 +1538,53 @@ namespace Game.Entities
m_bgData.taxiPath[1] = result.Read<uint>(8);
m_bgData.mountSpell = result.Read<uint>(9);
}
void _LoadPetStable(byte petStableSlots, SQLResult result)
{
if (petStableSlots == 0 && result.IsEmpty())
return;
m_petStable = new();
m_petStable.MaxStabledPets = petStableSlots;
if (m_petStable.MaxStabledPets > SharedConst.MaxPetStables)
{
Log.outError(LogFilter.Player, $"Player::LoadFromDB: Player ({GetGUID()}) can't have more stable slots than {SharedConst.MaxPetStables}, but has {m_petStable.MaxStabledPets} in DB");
m_petStable.MaxStabledPets = SharedConst.MaxPetStables;
}
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// SELECT id, entry, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType, specialization FROM character_pet WHERE owner = ?
if (!result.IsEmpty())
{
do
{
PetStable.PetInfo petInfo = new();
petInfo.PetNumber = result.Read<uint>(0);
petInfo.CreatureId = result.Read<uint>(1);
petInfo.DisplayId = result.Read<uint>(2);
petInfo.Level = result.Read<byte>(3);
petInfo.Experience = result.Read<uint>(4);
petInfo.ReactState = (ReactStates)result.Read<byte>(5);
PetSaveMode slot = (PetSaveMode)result.Read<byte>(6);
petInfo.Name = result.Read<string>(7);
petInfo.WasRenamed = result.Read<bool>(8);
petInfo.Health = result.Read<uint>(9);
petInfo.Mana = result.Read<uint>(10);
petInfo.ActionBar = result.Read<string>(11);
petInfo.LastSaveTime = result.Read<uint>(12);
petInfo.CreatedBySpellId = result.Read<uint>(13);
petInfo.Type = (PetType)result.Read<byte>(14);
petInfo.SpecializationId = result.Read<ushort>(15);
if (slot == PetSaveMode.AsCurrent)
m_petStable.CurrentPet = petInfo;
else if (slot >= PetSaveMode.FirstStableSlot && slot <= PetSaveMode.LastStableSlot)
m_petStable.StabledPets[(int)slot - 1] = petInfo;
else if (slot == PetSaveMode.NotInSlot)
m_petStable.UnslottedPets.Add(petInfo);
} while (result.NextRow());
}
}
void _SaveInventory(SQLTransaction trans)
{
@@ -3039,12 +3086,7 @@ namespace Game.Entities
m_taxi.LoadTaxiMask(taximask); // must be before InitTaxiNodesForLevel
m_stableSlots = stable_slots;
if (m_stableSlots > 4)
{
Log.outError(LogFilter.Player, "Player can have not more {0} stable slots, but have in DB {1}", 4, m_stableSlots);
m_stableSlots = 4;
}
_LoadPetStable(stable_slots, holder.GetResult(PlayerLoginQueryLoad.PetSlots));
// Honor system
// Update Honor kills data
@@ -3411,7 +3453,7 @@ namespace Game.Entities
stmt.AddValue(index++, GetTalentResetTime());
stmt.AddValue(index++, GetPrimarySpecialization());
stmt.AddValue(index++, (ushort)m_ExtraFlags);
stmt.AddValue(index++, m_stableSlots);
stmt.AddValue(index++, m_petStable != null ? m_petStable.MaxStabledPets : 0);
stmt.AddValue(index++, (ushort)atLoginFlags);
stmt.AddValue(index++, m_deathExpireTime);
@@ -3551,7 +3593,7 @@ namespace Game.Entities
stmt.AddValue(index++, GetNumRespecs());
stmt.AddValue(index++, GetPrimarySpecialization());
stmt.AddValue(index++, (ushort)m_ExtraFlags);
stmt.AddValue(index++, m_stableSlots);
stmt.AddValue(index++, m_petStable != null ? m_petStable.MaxStabledPets : 0);
stmt.AddValue(index++, (ushort)atLoginFlags);
stmt.AddValue(index++, GetZoneId());
stmt.AddValue(index++, m_deathExpireTime);
@@ -3937,7 +3979,7 @@ namespace Game.Entities
// 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 = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_PETS);
stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_PET_IDS);
stmt.AddValue(0, guid);
SQLResult resultPets = DB.Characters.Query(stmt);
+1 -1
View File
@@ -130,8 +130,8 @@ namespace Game.Entities
public bool m_mailsUpdated;
//Pets
PetStable m_petStable;
public List<PetAura> m_petAuras = new();
public uint m_stableSlots;
uint m_temporaryUnsummonedPetNumber;
uint m_lastpetnumber;
+34 -15
View File
@@ -884,7 +884,16 @@ namespace Game.Entities
public void SetCommandStatusOff(PlayerCommandStates command) { _activeCheats &= ~command; }
//Pet - Summons - Vehicles
public PetStable GetPetStable() { return m_petStable; }
public PetStable GetOrInitPetStable()
{
if (m_petStable == null)
m_petStable = new();
return m_petStable;
}
// last used pet number (for BG's)
public uint GetLastPetNumber() { return m_lastpetnumber; }
public void SetLastPetNumber(uint petnumber) { m_lastpetnumber = petnumber; }
@@ -892,7 +901,7 @@ namespace Game.Entities
{
//fixme: the pet should still be loaded if the player is not in world
// just not added to the map
if (IsInWorld)
if (m_petStable != null && IsInWorld)
{
Pet pet = new(this);
pet.LoadPetFromDB(this, 0, 0, true);
@@ -4702,18 +4711,13 @@ namespace Game.Entities
return null;
}
public Pet SummonPet(uint entry, float x, float y, float z, float ang, PetType petType, uint duration, bool aliveOnly = false)
public Pet SummonPet(uint entry, float x, float y, float z, float ang, PetType petType, uint duration)
{
Pet pet = new(this, petType);
if (petType == PetType.Summon && pet.LoadPetFromDB(this, entry))
{
if (aliveOnly && !pet.IsAlive())
{
pet.DespawnOrUnsummon();
SendTameFailure(PetTameResult.Dead);
return null;
}
PetStable petStable = GetOrInitPetStable();
Pet pet = new(this, petType);
if (petType == PetType.Summon && pet.LoadPetFromDB(this, entry, 0, false))
{
if (duration > 0)
pet.SetDuration(duration);
@@ -4733,8 +4737,8 @@ namespace Game.Entities
}
Map map = GetMap();
uint pet_number = Global.ObjectMgr.GeneratePetNumber();
if (!pet.Create(map.GenerateLowGuid(HighGuid.Pet), map, entry))
uint petNumber = Global.ObjectMgr.GeneratePetNumber();
if (!pet.Create(map.GenerateLowGuid(HighGuid.Pet), map, entry, petNumber))
{
Log.outError(LogFilter.Server, "no such creature entry {0}", entry);
return null;
@@ -4750,11 +4754,15 @@ 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(pet_number, true);
pet.GetCharmInfo().SetPetNumber(petNumber, true);
pet.SetClass(Class.Mage);
pet.SetPetExperience(0);
pet.SetPetNextLevelExperience(1000);
@@ -4848,6 +4856,17 @@ namespace Game.Entities
// 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;
// else if (stable slots) handled in opcode handlers due to required swaps
// else (current pet) doesnt need to do anything
SetMinion(pet, false);
pet.AddObjectToRemoveList();
@@ -4862,7 +4881,7 @@ namespace Game.Entities
}
}
void SendTameFailure(PetTameResult result)
public void SendTameFailure(PetTameResult result)
{
PetTameFailure petTameFailure = new();
petTameFailure.Result = (byte)result;
+13 -1
View File
@@ -730,7 +730,11 @@ namespace Game.Entities
uint level = creatureTarget.GetLevelForTarget(this) + 5 < GetLevel() ? (GetLevel() - 5) : creatureTarget.GetLevelForTarget(this);
InitTamedPet(pet, level, spell_id);
if (!InitTamedPet(pet, level, spell_id))
{
pet.Dispose();
return null;
}
return pet;
}
@@ -754,6 +758,11 @@ namespace Game.Entities
bool InitTamedPet(Pet pet, uint level, uint spell_id)
{
Player player = ToPlayer();
PetStable petStable = player.GetOrInitPetStable();
if (petStable.CurrentPet != null || petStable.GetUnslottedHunterPet() != null)
return false;
pet.SetCreatorGUID(GetGUID());
pet.SetFaction(GetFaction());
pet.SetCreatedBySpell(spell_id);
@@ -773,6 +782,9 @@ namespace Game.Entities
// this enables pet details window (Shift+P)
pet.InitPetCreateSpells();
pet.SetFullHealth();
petStable.CurrentPet = new();
pet.FillPetInfo(petStable.CurrentPet);
return true;
}
+5
View File
@@ -2820,6 +2820,10 @@ namespace Game
stmt.AddValue(0, lowGuid);
SetQuery(PlayerLoginQueryLoad.CorpseLocation, stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_PETS);
stmt.AddValue(0, lowGuid);
SetQuery(PlayerLoginQueryLoad.PetSlots, stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_GARRISON);
stmt.AddValue(0, lowGuid);
SetQuery(PlayerLoginQueryLoad.Garrison, stmt);
@@ -2938,6 +2942,7 @@ namespace Game
Currency,
CufProfiles,
CorpseLocation,
PetSlots,
Garrison,
GarrisonBlueprints,
GarrisonBuildings,
+43 -34
View File
@@ -343,59 +343,68 @@ namespace Game
public void SendStablePet(ObjectGuid guid)
{
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PET_SLOTS_DETAIL);
stmt.AddValue(0, guid.GetCounter());
stmt.AddValue(1, (byte)PetSaveMode.FirstStableSlot);
stmt.AddValue(2, (byte)PetSaveMode.LastStableSlot);
_queryProcessor.AddCallback(DB.Characters.AsyncQuery(stmt).WithCallback(SendStablePetCallback, guid));
}
void SendStablePetCallback(ObjectGuid guid, SQLResult result)
{
if (!GetPlayer())
return;
PetStableList packet = new();
packet.StableMaster = guid;
Pet pet = GetPlayer().GetPet();
PetStable petStable = GetPlayer().GetPetStable();
if (petStable == null)
{
SendPacket(packet);
return;
}
uint petSlot = 0;
// not let move dead pet in slot
if (pet && pet.IsAlive() && pet.GetPetType() == PetType.Hunter)
if (petStable.CurrentPet != null)
{
PetStableInfo stableEntry;// = new PetStableInfo();
PetStable.PetInfo pet = petStable.CurrentPet;
PetStableInfo stableEntry;
stableEntry.PetSlot = petSlot;
stableEntry.PetNumber = pet.GetCharmInfo().GetPetNumber();
stableEntry.CreatureID = pet.GetEntry();
stableEntry.DisplayID = pet.GetDisplayId();
stableEntry.ExperienceLevel = pet.GetLevel();
stableEntry.PetNumber = pet.PetNumber;
stableEntry.CreatureID = pet.CreatureId;
stableEntry.DisplayID = pet.DisplayId;
stableEntry.ExperienceLevel = pet.Level;
stableEntry.PetFlags = PetStableinfo.Active;
stableEntry.PetName = pet.GetName();
stableEntry.PetName = pet.Name;
++petSlot;
packet.Pets.Add(stableEntry);
}
if (!result.IsEmpty())
else
{
do
PetStable.PetInfo pet = petStable.GetUnslottedHunterPet();
if (pet != null)
{
PetStableInfo stableEntry;// = new PetStableInfo();
PetStableInfo stableEntry;
stableEntry.PetSlot = petSlot;
stableEntry.PetNumber = result.Read<uint>(1); // petnumber
stableEntry.CreatureID = result.Read<uint>(2); // creature entry
stableEntry.DisplayID = result.Read<uint>(5); // creature displayid
stableEntry.ExperienceLevel = result.Read<ushort>(3); // level
stableEntry.PetFlags = PetStableinfo.Inactive;
stableEntry.PetName = result.Read<string>(4); // Name
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);
}
}
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);
}
while (result.NextRow());
}
SendPacket(packet);
+6 -1
View File
@@ -536,10 +536,12 @@ namespace Game
bool isdeclined = packet.RenameData.HasDeclinedNames;
string name = packet.RenameData.NewName;
PetStable petStable = _player.GetPetStable();
Pet pet = ObjectAccessor.GetPet(GetPlayer(), petguid);
// check it!
if (!pet || !pet.IsPet() || pet.ToPet().GetPetType() != PetType.Hunter || !pet.HasPetFlag(UnitPetFlags.CanBeRenamed) ||
pet.GetOwnerGUID() != GetPlayer().GetGUID() || pet.GetCharmInfo() == null)
pet.GetOwnerGUID() != _player.GetGUID() || pet.GetCharmInfo() == null ||
petStable == null || petStable.CurrentPet == null || petStable.CurrentPet.PetNumber != pet.GetCharmInfo().GetPetNumber())
return;
PetNameInvalidReason res = ObjectManager.CheckPetName(name);
@@ -559,6 +561,9 @@ namespace Game
pet.SetGroupUpdateFlag(GroupUpdatePetFlags.Name);
pet.RemovePetFlag(UnitPetFlags.CanBeRenamed);
petStable.CurrentPet.Name = name;
petStable.CurrentPet.WasRenamed = true;
PreparedStatement stmt;
SQLTransaction trans = new();
if (isdeclined)
+1 -1
View File
@@ -713,7 +713,7 @@ namespace Game
return _transactionCallbacks.AddCallback(callback);
}
SQLQueryHolderCallback<R> AddQueryHolderCallback<R>(SQLQueryHolderCallback<R> callback)
public SQLQueryHolderCallback<R> AddQueryHolderCallback<R>(SQLQueryHolderCallback<R> callback)
{
return (SQLQueryHolderCallback<R>)_queryHolderProcessor.AddCallback(callback);
}
+29
View File
@@ -5217,6 +5217,35 @@ namespace Game.Spells
if (!unitCaster.GetCharmedGUID().IsEmpty())
return SpellCastResult.AlreadyHaveCharm;
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)
{
playerCaster.SendTameFailure(PetTameResult.NoPetAvailable);
return SpellCastResult.DontReport;
}
if (info.Item1.Type == PetType.Hunter && info.Item1.Health == 0)
{
playerCaster.SendTameFailure(PetTameResult.Dead);
return SpellCastResult.DontReport;
}
CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate(info.Item1.CreatureId);
if (creatureInfo == null || !creatureInfo.IsTameable(playerCaster.CanTameExoticPets()))
{
// if problem in exotic pet
if (creatureInfo != null && creatureInfo.IsTameable(true))
playerCaster.SendTameFailure(PetTameResult.CantControlExotic);
else
playerCaster.SendTameFailure(PetTameResult.NoPetAvailable);
return SpellCastResult.DontReport;
}
}
break;
}
case SpellEffectName.SummonPlayer:
+2 -2
View File
@@ -2293,14 +2293,14 @@ namespace Game.Spells
}
if (owner.IsTypeId(TypeId.Player))
owner.ToPlayer().RemovePet(OldSummon, (OldSummon.GetPetType() == PetType.Hunter ? PetSaveMode.AsDeleted : PetSaveMode.NotInSlot), false);
owner.ToPlayer().RemovePet(OldSummon, PetSaveMode.NotInSlot, false);
else
return;
}
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, true);
Pet pet = owner.SummonPet(petentry, x, y, z, owner.Orientation, PetType.Summon, 0);
if (!pet)
return;
+15 -5
View File
@@ -407,8 +407,8 @@ namespace Scripts.Spells.Hunter
{
SpellCastResult CheckCast()
{
Unit caster = GetCaster();
if (!caster.IsTypeId(TypeId.Player))
Player caster = GetCaster().ToPlayer();
if (caster == null)
return SpellCastResult.DontReport;
if (!GetExplTargetUnit())
@@ -421,11 +421,21 @@ namespace Scripts.Spells.Hunter
return SpellCastResult.Highlevel;
// use SMSG_PET_TAME_FAILURE?
if (!target.GetCreatureTemplate().IsTameable(caster.ToPlayer().CanTameExoticPets()))
if (!target.GetCreatureTemplate().IsTameable(caster.CanTameExoticPets()))
return SpellCastResult.BadTargets;
if (!caster.GetPetGUID().IsEmpty())
return SpellCastResult.AlreadyHaveSummon;
PetStable petStable = caster.GetPetStable();
if (petStable != null)
{
if (petStable.CurrentPet != null)
return SpellCastResult.AlreadyHaveSummon;
if (petStable.GetUnslottedHunterPet() != null)
{
caster.SendTameFailure(PetTameResult.TooMany);
return SpellCastResult.DontReport;
}
}
if (!caster.GetCharmedGUID().IsEmpty())
return SpellCastResult.AlreadyHaveCharm;