// 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 (this->IsClient()) { act_power = CastToClient()->GetFocusEffect(focusPetPower, spell_id);//Client only act_power = CastToClient()->mod_pet_power(act_power, 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(!database.GetPoweredPetEntry(pettype, act_power, &record)) { Message(13, "Unable to find data for pet %s", pettype); Log.Out(Logs::General, Logs::Error, "Unable to find data for pet %s, check pets table.", pettype); return; } //find the NPC data for the specified NPC type const NPCType *base = database.LoadNPCTypesData(record.npc_type); if(base == nullptr) { Message(13, "Unable to load NPC data for pet %s", pettype); Log.Out(Logs::General, Logs::Error, "Unable to load NPC data for pet %s (NPC ID %d), 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 NPCType *npc_type = new NPCType; memcpy(npc_type, base, sizeof(NPCType)); // If pet power is set to -1 in the DB, use stat scaling if (this->IsClient() && record.petpower == -1) { float scale_power = (float)act_power / 100.0f; if(scale_power > 0) { npc_type->max_hp *= (1 + scale_power); npc_type->cur_hp = npc_type->max_hp; npc_type->AC *= (1 + scale_power); npc_type->level += 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 *= (1 + (scale_power / 2)); } record.petpower = act_power; } //Live AA - Elemental Durability int16 MaxHP = aabonuses.PetMaxHP + itembonuses.PetMaxHP + spellbonuses.PetMaxHP; if (MaxHP){ npc_type->max_hp += (npc_type->max_hp*MaxHP)/100; npc_type->cur_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 if (petname != nullptr) { // Name was provided, use it. strn0cpy(npc_type->name, petname, 64); } else if (record.petnaming == 0) { strcpy(npc_type->name, this->GetCleanName()); npc_type->name[25] = '\0'; strcat(npc_type->name, "`s_pet"); } else if (record.petnaming == 1) { strcpy(npc_type->name, this->GetName()); npc_type->name[19] = '\0'; strcat(npc_type->name, "`s_familiar"); } else if (record.petnaming == 2) { strcpy(npc_type->name, this->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()) { strcpy(npc_type->name, GetRandPetName()); } else { strcpy(npc_type->name, this->GetCleanName()); npc_type->name[25] = '\0'; strcat(npc_type->name, "`s_pet"); } //handle beastlord pet appearance if(record.petnaming == 2) { switch(GetBaseRace()) { case VAHSHIR: npc_type->race = TIGER; npc_type->size *= 0.8f; break; case TROLL: npc_type->race = ALLIGATOR; npc_type->size *= 2.5f; break; case OGRE: npc_type->race = BEAR; npc_type->texture = 3; npc_type->gender = 2; break; case BARBARIAN: npc_type->race = WOLF; npc_type->texture = 2; break; case IKSAR: npc_type->race = WOLF; npc_type->texture = 0; npc_type->gender = 1; npc_type->size *= 2.0f; npc_type->luclinface = 0; break; default: npc_type->race = WOLF; npc_type->texture = 0; } } // 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 = database.QueryDatabase(query); if (!results.Success()) { 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 = database.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 Log.Out(Logs::General, Logs::Error, "Error loading NPC data for monster summoning pet (NPC ID %d)", monsterid); } //this takes ownership of the npc_type data Pet *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[EmuConstants::EQUIPMENT_SIZE]; memset(petinv, 0, sizeof(petinv)); const Item_Struct *item = 0; if (database.GetBasePetItems(record.equipmentset, petinv)) { for (int i = 0; i<EmuConstants::EQUIPMENT_SIZE; i++) if (petinv[i]) { item = database.GetItem(petinv[i]); npc->AddLootDrop(item, &npc->itemlist, 0, 1, 127, true, 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* target = GetTarget(); if (target){ npc->AddToHateList(target, 1); npc->SetPetTargetLockID(target->GetID()); npc->SetSpecialAbility(IMMUNE_AGGRO, 1); } else npc->Kill(); //On live casts spell 892 Unsummon (Kayen - Too limiting to use that for emu since pet can have more than 20k HP) } }