mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 21:01:29 +00:00
* rebase\tidy up to address commends * I blame git for this one * last typo * spaces * formating fixes I think? * Repository fixes * Cleanup --------- Co-authored-by: Akkadius <akkadius1@gmail.com>
686 lines
22 KiB
C++
686 lines
22 KiB
C++
/* EQEMu: Everquest Server Emulator
|
|
Copyright (C) 2001-2004 EQEMu Development Team (http://eqemu.org)
|
|
|
|
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; version 2 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY except by those people which sell it, which
|
|
are required to give you total support for your newly bought product;
|
|
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, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "../common/global_define.h"
|
|
#include "../common/spdat.h"
|
|
#include "../common/strings.h"
|
|
|
|
#include "../common/repositories/pets_repository.h"
|
|
#include "../common/repositories/pets_beastlord_data_repository.h"
|
|
#include "../common/repositories/character_pet_name_repository.h"
|
|
|
|
#include "entity.h"
|
|
#include "client.h"
|
|
#include "mob.h"
|
|
|
|
#include "pets.h"
|
|
#include "zonedb.h"
|
|
|
|
#include <string>
|
|
|
|
#include "bot.h"
|
|
|
|
#ifndef WIN32
|
|
#include <stdlib.h>
|
|
#include "../common/unix.h"
|
|
#endif
|
|
|
|
|
|
// need to pass in a char array of 64 chars
|
|
void GetRandPetName(char *name)
|
|
{
|
|
std::string temp;
|
|
temp.reserve(64);
|
|
// note these orders are used to make the exclusions cheap :P
|
|
static const char *part1[] = {"G", "J", "K", "L", "V", "X", "Z"};
|
|
static const char *part2[] = {nullptr, "ab", "ar", "as", "eb", "en", "ib", "ob", "on"};
|
|
static const char *part3[] = {nullptr, "an", "ar", "ek", "ob"};
|
|
static const char *part4[] = {"er", "ab", "n", "tik"};
|
|
|
|
const char *first = part1[zone->random.Int(0, (sizeof(part1) / sizeof(const char *)) - 1)];
|
|
const char *second = part2[zone->random.Int(0, (sizeof(part2) / sizeof(const char *)) - 1)];
|
|
const char *third = part3[zone->random.Int(0, (sizeof(part3) / sizeof(const char *)) - 1)];
|
|
const char *fourth = part4[zone->random.Int(0, (sizeof(part4) / sizeof(const char *)) - 1)];
|
|
|
|
// if both of these are empty, we would get an illegally short name
|
|
if (second == nullptr && third == nullptr)
|
|
fourth = part4[(sizeof(part4) / sizeof(const char *)) - 1];
|
|
|
|
// "ektik" isn't allowed either I guess?
|
|
if (third == part3[3] && fourth == part4[3])
|
|
fourth = part4[zone->random.Int(0, (sizeof(part4) / sizeof(const char *)) - 2)];
|
|
|
|
// "Laser" isn't allowed either I guess?
|
|
if (first == part1[3] && second == part2[3] && third == nullptr && fourth == part4[0])
|
|
fourth = part4[zone->random.Int(1, (sizeof(part4) / sizeof(const char *)) - 2)];
|
|
|
|
temp += first;
|
|
if (second != nullptr)
|
|
temp += second;
|
|
if (third != nullptr)
|
|
temp += third;
|
|
temp += fourth;
|
|
|
|
strn0cpy(name, temp.c_str(), 64);
|
|
}
|
|
|
|
void Mob::MakePet(uint16 spell_id, const char* pettype, const char *petname) {
|
|
// petpower of -1 is used to get the petpower based on whichever focus is currently
|
|
// equipped. This should replicate the old functionality for the most part.
|
|
MakePoweredPet(spell_id, pettype, -1, petname);
|
|
}
|
|
|
|
// Split from the basic MakePet to allow backward compatiblity with existing code while also
|
|
// making it possible for petpower to be retained without the focus item having to
|
|
// stay equipped when the character zones. petpower of -1 means that the currently equipped petfocus
|
|
// of a client is searched for and used instead.
|
|
void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower,
|
|
const char *petname, float in_size) {
|
|
// Sanity and early out checking first.
|
|
if(HasPet() || pettype == nullptr)
|
|
return;
|
|
|
|
int16 act_power = 0; // The actual pet power we'll use.
|
|
if (petpower == -1) {
|
|
if (IsClient()) {
|
|
act_power = CastToClient()->GetFocusEffect(focusPetPower, spell_id);//Client only
|
|
}
|
|
else if (IsBot())
|
|
act_power = CastToBot()->GetFocusEffect(focusPetPower, spell_id);
|
|
}
|
|
else if (petpower > 0)
|
|
act_power = petpower;
|
|
|
|
// optional rule: classic style variance in pets. Achieve this by
|
|
// adding a random 0-4 to pet power, since it only comes in increments
|
|
// of five from focus effects.
|
|
|
|
//lookup our pets table record for this type
|
|
PetRecord record;
|
|
if(!content_db.GetPoweredPetEntry(pettype, act_power, &record)) {
|
|
Message(Chat::Red, "Unable to find data for pet %s", pettype);
|
|
LogError("Unable to find data for pet [{}], check pets table", pettype);
|
|
return;
|
|
}
|
|
|
|
//find the NPC data for the specified NPC type
|
|
const NPCType *base = content_db.LoadNPCTypesData(record.npc_type);
|
|
if(base == nullptr) {
|
|
Message(Chat::Red, "Unable to load NPC data for pet %s", pettype);
|
|
LogError("Unable to load NPC data for pet [{}] (NPC ID [{}]), check pets and npc_types tables", pettype, record.npc_type);
|
|
return;
|
|
}
|
|
|
|
//we copy the npc_type data because we need to edit it a bit
|
|
auto npc_type = new NPCType;
|
|
memcpy(npc_type, base, sizeof(NPCType));
|
|
|
|
// If pet power is set to -1 in the DB, use stat scaling
|
|
if ((IsClient() || IsBot()) && record.petpower == -1)
|
|
{
|
|
float scale_power = (float)act_power / 100.0f;
|
|
if(scale_power > 0)
|
|
{
|
|
npc_type->max_hp *= (1 + scale_power);
|
|
npc_type->current_hp = npc_type->max_hp;
|
|
npc_type->AC *= (1 + scale_power);
|
|
npc_type->level += 1 + ((int)act_power / 25) > npc_type->level + RuleR(Pets, PetPowerLevelCap) ? RuleR(Pets, PetPowerLevelCap) : 1 + ((int)act_power / 25); // gains an additional level for every 25 pet power
|
|
npc_type->min_dmg = (npc_type->min_dmg * (1 + (scale_power / 2)));
|
|
npc_type->max_dmg = (npc_type->max_dmg * (1 + (scale_power / 2)));
|
|
npc_type->size = npc_type->size * (1 + (scale_power / 2)) > npc_type->size * 3 ? npc_type->size * 3 : npc_type-> size * (1 + (scale_power / 2));
|
|
}
|
|
record.petpower = act_power;
|
|
}
|
|
|
|
//Live AA - Elemental Durability
|
|
int64 MaxHP = aabonuses.PetMaxHP + itembonuses.PetMaxHP + spellbonuses.PetMaxHP;
|
|
|
|
if (MaxHP){
|
|
npc_type->max_hp += (npc_type->max_hp*MaxHP)/100;
|
|
npc_type->current_hp = npc_type->max_hp;
|
|
}
|
|
|
|
//TODO: think about regen (engaged vs. not engaged)
|
|
|
|
// Pet naming:
|
|
// 0 - `s pet
|
|
// 1 - `s familiar
|
|
// 2 - `s Warder
|
|
// 3 - Random name if client, `s pet for others
|
|
// 4 - Keep DB name
|
|
// 5 - `s ward
|
|
|
|
if (IsClient() && !petname) {
|
|
const auto vanity_name = CharacterPetNameRepository::FindOne(database, CastToClient()->CharacterID());
|
|
if (!vanity_name.name.empty()) {
|
|
petname = vanity_name.name.c_str();
|
|
}
|
|
}
|
|
|
|
if (petname != nullptr) {
|
|
// Name was provided, use it.
|
|
strn0cpy(npc_type->name, petname, 64);
|
|
EntityList::RemoveNumbers(npc_type->name);
|
|
entity_list.MakeNameUnique(npc_type->name);
|
|
} else if (record.petnaming == 0) {
|
|
strcpy(npc_type->name, GetCleanName());
|
|
npc_type->name[25] = '\0';
|
|
strcat(npc_type->name, "`s_pet");
|
|
} else if (record.petnaming == 1) {
|
|
strcpy(npc_type->name, GetName());
|
|
npc_type->name[19] = '\0';
|
|
strcat(npc_type->name, "`s_familiar");
|
|
} else if (record.petnaming == 2) {
|
|
strcpy(npc_type->name, GetName());
|
|
npc_type->name[21] = 0;
|
|
strcat(npc_type->name, "`s_Warder");
|
|
} else if (record.petnaming == 4) {
|
|
// Keep the DB name
|
|
} else if (record.petnaming == 3 && IsClient()) {
|
|
GetRandPetName(npc_type->name);
|
|
} else if (record.petnaming == 5 && IsClient()) {
|
|
strcpy(npc_type->name, GetName());
|
|
npc_type->name[24] = '\0';
|
|
strcat(npc_type->name, "`s_ward");
|
|
} else {
|
|
strcpy(npc_type->name, GetCleanName());
|
|
npc_type->name[25] = '\0';
|
|
strcat(npc_type->name, "`s_pet");
|
|
}
|
|
|
|
// Beastlord Pets
|
|
if (record.petnaming == 2) {
|
|
uint16 race_id = GetBaseRace();
|
|
|
|
auto d = content_db.GetBeastlordPetData(race_id);
|
|
|
|
npc_type->race = d.race_id;
|
|
npc_type->texture = d.texture;
|
|
npc_type->helmtexture = d.helm_texture;
|
|
npc_type->gender = d.gender;
|
|
npc_type->luclinface = d.face;
|
|
|
|
npc_type->size *= d.size_modifier;
|
|
}
|
|
|
|
// handle monster summoning pet appearance
|
|
if(record.monsterflag) {
|
|
|
|
uint32 monsterid = 0;
|
|
|
|
// get a random npc id from the spawngroups assigned to this zone
|
|
auto query = StringFormat("SELECT npcID "
|
|
"FROM (spawnentry INNER JOIN spawn2 ON spawn2.spawngroupID = spawnentry.spawngroupID) "
|
|
"INNER JOIN npc_types ON npc_types.id = spawnentry.npcID "
|
|
"WHERE spawn2.zone = '%s' AND npc_types.bodytype NOT IN (11, 33, 66, 67) "
|
|
"AND npc_types.race NOT IN (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 44, "
|
|
"55, 67, 71, 72, 73, 77, 78, 81, 90, 92, 93, 94, 106, 112, 114, 127, 128, "
|
|
"130, 139, 141, 183, 236, 237, 238, 239, 254, 266, 329, 330, 378, 379, "
|
|
"380, 381, 382, 383, 404, 522) "
|
|
"ORDER BY RAND() LIMIT 1", zone->GetShortName());
|
|
auto results = content_db.QueryDatabase(query);
|
|
if (!results.Success()) {
|
|
safe_delete(npc_type);
|
|
return;
|
|
}
|
|
|
|
if (results.RowCount() != 0) {
|
|
auto row = results.begin();
|
|
monsterid = Strings::ToInt(row[0]);
|
|
}
|
|
|
|
// since we don't have any monsters, just make it look like an earth pet for now
|
|
if (monsterid == 0)
|
|
monsterid = 567;
|
|
|
|
// give the summoned pet the attributes of the monster we found
|
|
const NPCType* monster = content_db.LoadNPCTypesData(monsterid);
|
|
if(monster) {
|
|
npc_type->race = monster->race;
|
|
npc_type->size = monster->size;
|
|
npc_type->texture = monster->texture;
|
|
npc_type->gender = monster->gender;
|
|
npc_type->luclinface = monster->luclinface;
|
|
npc_type->helmtexture = monster->helmtexture;
|
|
npc_type->herosforgemodel = monster->herosforgemodel;
|
|
} else
|
|
LogError("Error loading NPC data for monster summoning pet (NPC ID [{}])", monsterid);
|
|
|
|
}
|
|
|
|
//this takes ownership of the npc_type data
|
|
auto npc = new Pet(npc_type, this, (PetType)record.petcontrol, spell_id, record.petpower);
|
|
|
|
// Now that we have an actual object to interact with, load
|
|
// the base items for the pet. These are always loaded
|
|
// so that a rank 1 suspend minion does not kill things
|
|
// like the special back items some focused pets may receive.
|
|
uint32 petinv[EQ::invslot::EQUIPMENT_COUNT];
|
|
memset(petinv, 0, sizeof(petinv));
|
|
const EQ::ItemData *item = nullptr;
|
|
|
|
if (content_db.GetBasePetItems(record.equipmentset, petinv)) {
|
|
for (int i = EQ::invslot::EQUIPMENT_BEGIN; i <= EQ::invslot::EQUIPMENT_END; i++)
|
|
if (petinv[i]) {
|
|
item = database.GetItem(petinv[i]);
|
|
npc->AddLootDrop(item, LootdropEntriesRepository::NewNpcEntity(), true);
|
|
}
|
|
}
|
|
|
|
npc->UpdateEquipmentLight();
|
|
|
|
// finally, override size if one was provided
|
|
if (in_size > 0.0f)
|
|
npc->size = in_size;
|
|
|
|
entity_list.AddNPC(npc, true, true);
|
|
SetPetID(npc->GetID());
|
|
// We need to handle PetType 5 (petHatelist), add the current target to the hatelist of the pet
|
|
|
|
if (record.petcontrol == petTargetLock)
|
|
{
|
|
Mob* m_target = GetTarget();
|
|
|
|
bool activiate_pet = false;
|
|
if (m_target && m_target->GetID() != GetID()) {
|
|
|
|
if (spells[spell_id].target_type == ST_Self) {
|
|
float distance = CalculateDistance(m_target->GetX(), m_target->GetY(), m_target->GetZ());
|
|
if (distance <= 200) { //Live distance on targetlock pets that self cast. No message is given if not in range.
|
|
activiate_pet = true;
|
|
}
|
|
}
|
|
else {
|
|
activiate_pet = true;
|
|
}
|
|
}
|
|
|
|
if (activiate_pet){
|
|
npc->AddToHateList(m_target, 1);
|
|
npc->SetPetTargetLockID(m_target->GetID());
|
|
npc->SetSpecialAbility(SpecialAbility::AggroImmunity, 1);
|
|
}
|
|
else {
|
|
npc->CastSpell(SPELL_UNSUMMON_SELF, npc->GetID()); //Live like behavior, damages self for 20K
|
|
if (!npc->HasDied()) {
|
|
npc->Kill(); //Ensure pet dies if over 20k HP.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void NPC::TryDepopTargetLockedPets(Mob* current_target) {
|
|
|
|
if (!current_target || (current_target && (current_target->GetID() != GetPetTargetLockID()) || current_target->IsCorpse())) {
|
|
|
|
//Use when swarmpets are set to auto lock from quest or rule
|
|
if (GetSwarmInfo() && GetSwarmInfo()->target) {
|
|
Mob* owner = entity_list.GetMobID(GetSwarmInfo()->owner_id);
|
|
if (owner) {
|
|
owner->SetTempPetCount(owner->GetTempPetCount() - 1);
|
|
}
|
|
Depop();
|
|
return;
|
|
}
|
|
//Use when pets are given petype 5
|
|
if (IsPet() && GetPetType() == petTargetLock && GetPetTargetLockID()) {
|
|
CastSpell(SPELL_UNSUMMON_SELF, GetID()); //Live like behavior, damages self for 20K
|
|
if (!HasDied()) {
|
|
Kill(); //Ensure pet dies if over 20k HP.
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* This is why the pets ghost - pets were being spawned too far away from its npc owner and some
|
|
into walls or objects (+10), this sometimes creates the "ghost" effect. I changed to +2 (as close as I
|
|
could get while it still looked good). I also noticed this can happen if an NPC is spawned on the same spot of another or in a related bad spot.*/
|
|
Pet::Pet(NPCType *type_data, Mob *owner, PetType type, uint16 spell_id, int16 power)
|
|
: NPC(type_data, 0, owner->GetPosition() + glm::vec4(2.0f, 2.0f, 0.0f, 0.0f), GravityBehavior::Water)
|
|
{
|
|
GiveNPCTypeData(type_data);
|
|
SetPetType(type);
|
|
SetPetPower(power);
|
|
SetOwnerID(owner ? owner->GetID() : 0);
|
|
SetPetSpellID(spell_id);
|
|
|
|
// All pets start at false on newer clients. The client
|
|
// turns it on and tracks the state.
|
|
SetTaunting(false);
|
|
|
|
// Older clients didn't track state, and default taunting is on (per @mackal)
|
|
// Familiar and animation pets don't get taunt until an AA.
|
|
if (owner && owner->IsClient()) {
|
|
if (!(owner->CastToClient()->ClientVersionBit() & EQ::versions::maskUFAndLater)) {
|
|
if (
|
|
(GetPetType() != petFamiliar && GetPetType() != petAnimation) ||
|
|
aabonuses.PetCommands[PET_TAUNT]
|
|
) {
|
|
SetTaunting(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Class should use npc constructor to set light properties
|
|
}
|
|
|
|
bool ZoneDatabase::GetPetEntry(const std::string& pet_type, PetRecord *p)
|
|
{
|
|
return GetPoweredPetEntry(pet_type, 0, p);
|
|
}
|
|
|
|
bool ZoneDatabase::GetPoweredPetEntry(const std::string& pet_type, int16 pet_power, PetRecord* r)
|
|
{
|
|
const auto& l = PetsRepository::GetWhere(
|
|
content_db,
|
|
fmt::format(
|
|
"`type` = '{}' AND `petpower` <= {} ORDER BY `petpower` DESC LIMIT 1",
|
|
pet_type,
|
|
pet_power <= 0 ? 0 : pet_power
|
|
)
|
|
);
|
|
|
|
if (l.empty()) {
|
|
return false;
|
|
}
|
|
|
|
auto &e = l.front();
|
|
|
|
r->npc_type = e.npcID;
|
|
r->temporary = e.temp;
|
|
r->petpower = e.petpower;
|
|
r->petcontrol = e.petcontrol;
|
|
r->petnaming = e.petnaming;
|
|
r->monsterflag = e.monsterflag;
|
|
r->equipmentset = e.equipmentset;
|
|
|
|
return true;
|
|
}
|
|
|
|
Mob* Mob::GetPet() {
|
|
if (!GetPetID()) {
|
|
return nullptr;
|
|
}
|
|
|
|
const auto m = entity_list.GetMob(GetPetID());
|
|
if (!m) {
|
|
SetPetID(0);
|
|
return nullptr;
|
|
}
|
|
|
|
if (m->GetOwnerID() != GetID()) {
|
|
SetPetID(0);
|
|
return nullptr;
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
bool Mob::HasPet() const {
|
|
if (GetPetID() == 0) {
|
|
return false;
|
|
}
|
|
|
|
const auto m = entity_list.GetMob(GetPetID());
|
|
if (!m) {
|
|
return false;
|
|
}
|
|
|
|
if (m->GetOwnerID() != GetID()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Mob::SetPet(Mob* newpet) {
|
|
Mob* oldpet = GetPet();
|
|
if (oldpet) {
|
|
oldpet->SetOwnerID(0);
|
|
}
|
|
if (newpet == nullptr) {
|
|
SetPetID(0);
|
|
} else {
|
|
SetPetID(newpet->GetID());
|
|
Mob* oldowner = entity_list.GetMob(newpet->GetOwnerID());
|
|
if (oldowner)
|
|
oldowner->SetPetID(0);
|
|
newpet->SetOwnerID(GetID());
|
|
}
|
|
}
|
|
|
|
void Mob::SetPetID(uint16 NewPetID) {
|
|
if (NewPetID == GetID() && NewPetID != 0)
|
|
return;
|
|
petid = NewPetID;
|
|
|
|
if(IsClient())
|
|
{
|
|
Mob* NewPet = entity_list.GetMob(NewPetID);
|
|
CastToClient()->UpdateXTargetType(MyPet, NewPet);
|
|
}
|
|
}
|
|
|
|
void NPC::GetPetState(SpellBuff_Struct *pet_buffs, uint32 *items, char *name) {
|
|
//save the pet name
|
|
strn0cpy(name, GetName(), 64);
|
|
|
|
//save their items, we only care about what they are actually wearing
|
|
memcpy(items, equipment, sizeof(uint32) * EQ::invslot::EQUIPMENT_COUNT);
|
|
|
|
//save their buffs.
|
|
for (int i=EQ::invslot::EQUIPMENT_BEGIN; i < GetPetMaxTotalSlots(); i++) {
|
|
if (IsValidSpell(buffs[i].spellid)) {
|
|
pet_buffs[i].spellid = buffs[i].spellid;
|
|
pet_buffs[i].effect_type = i+1;
|
|
pet_buffs[i].duration = buffs[i].ticsremaining;
|
|
pet_buffs[i].level = buffs[i].casterlevel;
|
|
pet_buffs[i].bard_modifier = 10;
|
|
pet_buffs[i].counters = buffs[i].counters;
|
|
pet_buffs[i].bard_modifier = buffs[i].instrument_mod;
|
|
}
|
|
else {
|
|
pet_buffs[i].spellid = SPELL_UNKNOWN;
|
|
pet_buffs[i].duration = 0;
|
|
pet_buffs[i].level = 0;
|
|
pet_buffs[i].bard_modifier = 10;
|
|
pet_buffs[i].counters = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void NPC::SetPetState(SpellBuff_Struct *pet_buffs, uint32 *items) {
|
|
//restore their buffs...
|
|
|
|
int i;
|
|
for (i = 0; i < GetPetMaxTotalSlots(); i++) {
|
|
for(int z = 0; z < GetPetMaxTotalSlots(); z++) {
|
|
// check for duplicates
|
|
if(IsValidSpell(buffs[z].spellid) && buffs[z].spellid == pet_buffs[i].spellid) {
|
|
buffs[z].spellid = SPELL_UNKNOWN;
|
|
pet_buffs[i].spellid = 0xFFFFFFFF;
|
|
}
|
|
}
|
|
|
|
if (pet_buffs[i].spellid <= (uint32)SPDAT_RECORDS && pet_buffs[i].spellid != 0 && (pet_buffs[i].duration > 0 || pet_buffs[i].duration == -1)) {
|
|
if(pet_buffs[i].level == 0 || pet_buffs[i].level > 100)
|
|
pet_buffs[i].level = 1;
|
|
buffs[i].spellid = pet_buffs[i].spellid;
|
|
buffs[i].ticsremaining = pet_buffs[i].duration;
|
|
buffs[i].casterlevel = pet_buffs[i].level;
|
|
buffs[i].casterid = 0;
|
|
buffs[i].counters = pet_buffs[i].counters;
|
|
buffs[i].hit_number = spells[pet_buffs[i].spellid].hit_number;
|
|
buffs[i].instrument_mod = pet_buffs[i].bard_modifier;
|
|
}
|
|
else {
|
|
buffs[i].spellid = SPELL_UNKNOWN;
|
|
pet_buffs[i].spellid = 0xFFFFFFFF;
|
|
pet_buffs[i].effect_type = 0;
|
|
pet_buffs[i].level = 0;
|
|
pet_buffs[i].duration = 0;
|
|
pet_buffs[i].bard_modifier = 0;
|
|
}
|
|
}
|
|
for (int j1=0; j1 < GetPetMaxTotalSlots(); j1++) {
|
|
if (buffs[j1].spellid <= (uint32)SPDAT_RECORDS) {
|
|
for (int x1=0; x1 < EFFECT_COUNT; x1++) {
|
|
switch (spells[buffs[j1].spellid].effect_id[x1]) {
|
|
case SE_AddMeleeProc:
|
|
case SE_WeaponProc:
|
|
// We need to reapply buff based procs
|
|
// We need to do this here so suspended pets also regain their procs.
|
|
AddProcToWeapon(GetProcID(buffs[j1].spellid,x1), false, 100+spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, buffs[j1].casterlevel, GetSpellProcLimitTimer(buffs[j1].spellid, ProcType::MELEE_PROC));
|
|
break;
|
|
case SE_DefensiveProc:
|
|
AddDefensiveProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, GetSpellProcLimitTimer(buffs[j1].spellid, ProcType::DEFENSIVE_PROC));
|
|
break;
|
|
case SE_RangedProc:
|
|
AddRangedProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, GetSpellProcLimitTimer(buffs[j1].spellid, ProcType::RANGED_PROC));
|
|
break;
|
|
case SE_Charm:
|
|
case SE_Rune:
|
|
case SE_NegateAttacks:
|
|
case SE_Illusion:
|
|
buffs[j1].spellid = SPELL_UNKNOWN;
|
|
pet_buffs[j1].spellid = SPELLBOOK_UNKNOWN;
|
|
pet_buffs[j1].effect_type = 0;
|
|
pet_buffs[j1].level = 0;
|
|
pet_buffs[j1].duration = 0;
|
|
pet_buffs[j1].bard_modifier = 0;
|
|
x1 = EFFECT_COUNT;
|
|
break;
|
|
// We can't send appearance packets yet, put down at CompleteConnect
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//restore their equipment...
|
|
for (i = EQ::invslot::EQUIPMENT_BEGIN; i <= EQ::invslot::EQUIPMENT_END; i++) {
|
|
if (items[i] == 0) {
|
|
continue;
|
|
}
|
|
|
|
const EQ::ItemData *item2 = database.GetItem(items[i]);
|
|
|
|
if (item2) {
|
|
bool noDrop = (item2->NoDrop == 0); // Field is reverse logic
|
|
bool petCanHaveNoDrop = (RuleB(Pets, CanTakeNoDrop) && _CLIENTPET(this) && GetPetType() <= petOther);
|
|
|
|
if (!noDrop || petCanHaveNoDrop) {
|
|
AddLootDrop(item2, LootdropEntriesRepository::NewNpcEntity(), true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load the equipmentset from the DB. Might be worthwhile to load these into
|
|
// shared memory at some point due to the number of queries needed to load a
|
|
// nested set.
|
|
bool ZoneDatabase::GetBasePetItems(int32 equipmentset, uint32 *items) {
|
|
if (equipmentset < 0 || items == nullptr)
|
|
return false;
|
|
|
|
// Equipment sets can be nested. We start with the top-most one and
|
|
// add all items in it to the items array. Referenced equipmentsets
|
|
// are loaded after that, up to a max depth of 5. (Arbitrary limit
|
|
// so we don't go into an endless loop if the DB data is cyclic for
|
|
// some reason.)
|
|
// A slot will only get an item put in it if it is empty. That way
|
|
// an equipmentset can overload a slot for the set(s) it includes.
|
|
|
|
int depth = 0;
|
|
int32 curset = equipmentset;
|
|
int32 nextset = -1;
|
|
uint32 slot;
|
|
|
|
// outline:
|
|
// get equipmentset from DB. (Mainly check if we exist and get the
|
|
// nested ID)
|
|
// query pets_equipmentset_entries with the set_id and loop over
|
|
// all of the result rows. Check if we have something in the slot
|
|
// already. If no, add the item id to the equipment array.
|
|
while (curset >= 0 && depth < 5) {
|
|
std::string query = StringFormat("SELECT nested_set FROM pets_equipmentset WHERE set_id = '%d'", curset);
|
|
auto results = QueryDatabase(query);
|
|
if (!results.Success()) {
|
|
return false;
|
|
}
|
|
|
|
if (results.RowCount() != 1) {
|
|
// invalid set reference, it doesn't exist
|
|
LogError("Error in GetBasePetItems equipment set [{}] does not exist", curset);
|
|
return false;
|
|
}
|
|
|
|
auto row = results.begin();
|
|
nextset = Strings::ToInt(row[0]);
|
|
|
|
query = StringFormat("SELECT slot, item_id FROM pets_equipmentset_entries WHERE set_id='%d'", curset);
|
|
results = QueryDatabase(query);
|
|
if (results.Success()) {
|
|
for (row = results.begin(); row != results.end(); ++row)
|
|
{
|
|
slot = Strings::ToInt(row[0]);
|
|
|
|
if (slot > EQ::invslot::EQUIPMENT_END)
|
|
continue;
|
|
|
|
if (items[slot] == 0)
|
|
items[slot] = Strings::ToInt(row[1]);
|
|
}
|
|
}
|
|
|
|
curset = nextset;
|
|
depth++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Pet::CheckSpellLevelRestriction(Mob *caster, uint16 spell_id)
|
|
{
|
|
auto owner = GetOwner();
|
|
if (owner)
|
|
return owner->CheckSpellLevelRestriction(caster, spell_id);
|
|
return true;
|
|
}
|
|
|
|
BeastlordPetData::PetStruct ZoneDatabase::GetBeastlordPetData(uint16 race_id) {
|
|
BeastlordPetData::PetStruct d;
|
|
|
|
const auto& e = PetsBeastlordDataRepository::FindOne(*this, race_id);
|
|
|
|
if (!e.player_race) {
|
|
return d;
|
|
}
|
|
|
|
d.race_id = e.pet_race;
|
|
d.texture = e.texture;
|
|
d.helm_texture = e.helm_texture;
|
|
d.gender = e.gender;
|
|
d.size_modifier = e.size_modifier;
|
|
d.face = e.face;
|
|
|
|
return d;
|
|
}
|