eqemu-server/zone/pets.cpp
Alex King 3335cacac1
[Bots] Cleanup and remove preprocessors. (#2757)
* [Bots] Cleanup and remove preprocessors.

- Removes every `#ifdef BOTS` we have and locks bots behind `Bots:AllowBots` rule.
- Bot updates are now done by default similar to regular database updates.
- Modify `CMakeLists.txt`, `.drone.yml`, and `BUILD.md` to match the removal of `EQEMU_ENABLE_BOTS`.

* Cleanup

- Add SQL for enabling bots for servers with bots.
- Add message that tells players/operators bots are disabled.

* Suggested changes.

* Bot injection stuff

* Change SQL to bot SQL.

* Tweaks

* Remove `is_bot`

* Update version.h

* Update main.cpp

* Update database.cpp

* Fix name availability crash

* Remove bots from update script

Co-authored-by: Akkadius <akkadius1@gmail.com>
2023-01-20 12:35:33 -06:00

780 lines
24 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/types.h"
#include "entity.h"
#include "client.h"
#include "mob.h"
#include "pets.h"
#include "zonedb.h"
#include "../common/zone_store.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);
}
//not used anymore
/*int CalcPetHp(int levelb, int classb, int STA)
{
int multiplier = 0;
int base_hp = 0;
switch(classb) {
case WARRIOR:{
if (levelb < 20)
multiplier = 22;
else if (levelb < 30)
multiplier = 23;
else if (levelb < 40)
multiplier = 25;
else if (levelb < 53)
multiplier = 27;
else if (levelb < 57)
multiplier = 28;
else
multiplier = 30;
break;
}
case DRUID:
case CLERIC:
case SHAMAN:{
multiplier = 15;
break;
}
case PALADIN:
case SHADOWKNIGHT:{
if (levelb < 35)
multiplier = 21;
else if (levelb < 45)
multiplier = 22;
else if (levelb < 51)
multiplier = 23;
else if (levelb < 56)
multiplier = 24;
else if (levelb < 60)
multiplier = 25;
else
multiplier = 26;
break;
}
case MONK:
case BARD:
case ROGUE:
case BEASTLORD:{
if (levelb < 51)
multiplier = 18;
else if (levelb < 58)
multiplier = 19;
else
multiplier = 20;
break;
}
case RANGER:{
if (levelb < 58)
multiplier = 20;
else
multiplier = 21;
break;
}
case MAGICIAN:
case WIZARD:
case NECROMANCER:
case ENCHANTER:{
multiplier = 12;
break;
}
default:{
if (levelb < 35)
multiplier = 21;
else if (levelb < 45)
multiplier = 22;
else if (levelb < 51)
multiplier = 23;
else if (levelb < 56)
multiplier = 24;
else if (levelb < 60)
multiplier = 25;
else
multiplier = 26;
break;
}
}
if (multiplier == 0)
{
LogFile->write(EQEMuLog::Error, "Multiplier == 0 in CalcPetHp,using Generic....");;
multiplier=12;
}
base_hp = 5 + (multiplier*levelb) + ((multiplier*levelb*STA) + 1)/300;
return base_hp;
}
*/
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
act_power = CastToClient()->mod_pet_power(act_power, spell_id);
}
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 (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 beastlord_pet_data = content_db.GetBeastlordPetData(race_id);
npc_type->race = beastlord_pet_data.race_id;
npc_type->texture = beastlord_pet_data.texture;
npc_type->helmtexture = beastlord_pet_data.helm_texture;
npc_type->gender = beastlord_pet_data.gender;
npc_type->size *= beastlord_pet_data.size_modifier;
npc_type->luclinface = beastlord_pet_data.face;
}
// 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 = atoi(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, &npc->itemlist, NPC::NewLootDropEntry(), 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(IMMUNE_AGGRO, 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);
typeofpet = type;
petpower = power;
SetOwnerID(owner->GetID());
SetPetSpellID(spell_id);
// All pets start at false on newer clients. The client
// turns it on and tracks the state.
taunting=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 ((typeofpet != petFamiliar && typeofpet != petAnimation) ||
aabonuses.PetCommands[PET_TAUNT]) {
taunting=true;
}
}
}
// Class should use npc constructor to set light properties
}
bool ZoneDatabase::GetPetEntry(const char *pet_type, PetRecord *into) {
return GetPoweredPetEntry(pet_type, 0, into);
}
bool ZoneDatabase::GetPoweredPetEntry(const char *pet_type, int16 petpower, PetRecord *into) {
std::string query;
if (petpower <= 0)
query = StringFormat("SELECT npcID, temp, petpower, petcontrol, petnaming, monsterflag, equipmentset "
"FROM pets WHERE type='%s' AND petpower<=0", pet_type);
else
query = StringFormat("SELECT npcID, temp, petpower, petcontrol, petnaming, monsterflag, equipmentset "
"FROM pets WHERE type='%s' AND petpower<=%d ORDER BY petpower DESC LIMIT 1",
pet_type, petpower);
auto results = QueryDatabase(query);
if (!results.Success()) {
return false;
}
if (results.RowCount() != 1)
return false;
auto row = results.begin();
into->npc_type = atoi(row[0]);
into->temporary = atoi(row[1]);
into->petpower = atoi(row[2]);
into->petcontrol = atoi(row[3]);
into->petnaming = atoi(row[4]);
into->monsterflag = atoi(row[5]);
into->equipmentset = atoi(row[6]);
return true;
}
Mob* Mob::GetPet() {
if(GetPetID() == 0)
return(nullptr);
Mob* tmp = entity_list.GetMob(GetPetID());
if(tmp == nullptr) {
SetPetID(0);
return(nullptr);
}
if(tmp->GetOwnerID() != GetID()) {
SetPetID(0);
return(nullptr);
}
return(tmp);
}
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 (buffs[i].spellid != SPELL_UNKNOWN) {
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(buffs[z].spellid != SPELL_UNKNOWN && 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, GetProcLimitTimer(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, GetProcLimitTimer(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, GetProcLimitTimer(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, &itemlist, NPC::NewLootDropEntry(), 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 = atoi(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 = atoi(row[0]);
if (slot > EQ::invslot::EQUIPMENT_END)
continue;
if (items[slot] == 0)
items[slot] = atoi(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 beastlord_pet_data;
std::string query = fmt::format(
SQL(
SELECT
`pet_race`, `texture`, `helm_texture`, `gender`, `size_modifier`, `face`
FROM `pets_beastlord_data`
WHERE `player_race` = {}
),
race_id
);
auto results = QueryDatabase(query);
if (!results.Success() || results.RowCount() != 1) {
return beastlord_pet_data;
}
auto row = results.begin();
beastlord_pet_data.race_id = atoi(row[0]);
beastlord_pet_data.texture = atoi(row[1]);
beastlord_pet_data.helm_texture = atoi(row[2]);
beastlord_pet_data.gender = atoi(row[3]);
beastlord_pet_data.size_modifier = atof(row[4]);
beastlord_pet_data.face = atoi(row[5]);
return beastlord_pet_data;
}