eqemu-server/zone/mob_appearance.cpp
Alex King fa3a5c7a72
[Feature] Make ornamentations work with any augment type (#3281)
* [Feature] Make ornamentations work with any augment type

# Notes
- On Live there are augments that are not type 20/21 and are ornamentations.
- We also only allow a singular augment type to be ornamentation augment types, this will allow you to use any augment type as an ornamentation if it has a proper Hero's Forge model or a non-IT63/non-IT64 idfile.

* Update ruletypes.h

* Update client_packet.cpp

* Update item_instance.cpp

* Cleanup.
2023-04-16 10:26:19 -04:00

567 lines
16 KiB
C++

/**
* EQEmulator: Everquest Server Emulator
* Copyright (C) 2001-2018 EQEmulator Development Team (https://github.com/EQEmu/Server)
*
* 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/data_verification.h"
#include "../common/eqemu_logsys.h"
#include "../common/item_data.h"
#include "../common/spdat.h"
#include "../common/strings.h"
#include "mob.h"
#include "quest_parser_collection.h"
#include "zonedb.h"
#include "bot.h"
/**
* Stores internal representation of mob texture by material slot
*
* @param material_slot
* @param texture
* @param color
* @param hero_forge_model
*/
void Mob::SetMobTextureProfile(uint8 material_slot, uint16 texture, uint32 color, uint32 hero_forge_model)
{
Log(Logs::Detail, Logs::MobAppearance,
"[%s] material_slot: %u texture: %u color: %u hero_forge_model: %u",
GetCleanName(),
material_slot,
texture,
color,
hero_forge_model
);
switch (material_slot) {
case EQ::textures::armorHead:
mob_texture_profile.Head.Material = texture;
mob_texture_profile.Head.HerosForgeModel = hero_forge_model;
mob_texture_profile.Head.Color = color;
break;
case EQ::textures::armorChest:
mob_texture_profile.Chest.Material = texture;
mob_texture_profile.Chest.HerosForgeModel = hero_forge_model;
mob_texture_profile.Chest.Color = color;
break;
case EQ::textures::armorArms:
mob_texture_profile.Arms.Material = texture;
mob_texture_profile.Arms.HerosForgeModel = hero_forge_model;
mob_texture_profile.Arms.Color = color;
break;
case EQ::textures::armorWrist:
mob_texture_profile.Wrist.Material = texture;
mob_texture_profile.Wrist.HerosForgeModel = hero_forge_model;
mob_texture_profile.Wrist.Color = color;
break;
case EQ::textures::armorHands:
mob_texture_profile.Hands.Material = texture;
mob_texture_profile.Hands.HerosForgeModel = hero_forge_model;
mob_texture_profile.Hands.Color = color;
break;
case EQ::textures::armorLegs:
mob_texture_profile.Legs.Material = texture;
mob_texture_profile.Legs.HerosForgeModel = hero_forge_model;
mob_texture_profile.Legs.Color = color;
break;
case EQ::textures::armorFeet:
mob_texture_profile.Feet.Material = texture;
mob_texture_profile.Feet.HerosForgeModel = hero_forge_model;
mob_texture_profile.Feet.Color = color;
break;
case EQ::textures::weaponPrimary:
mob_texture_profile.Primary.Material = texture;
mob_texture_profile.Primary.HerosForgeModel = hero_forge_model;
mob_texture_profile.Primary.Color = color;
break;
case EQ::textures::weaponSecondary:
mob_texture_profile.Secondary.Material = texture;
mob_texture_profile.Secondary.HerosForgeModel = hero_forge_model;
mob_texture_profile.Secondary.Color = color;
break;
default:
return;
}
}
/**
* Returns internal representation of mob texture by material
*
* @param material_slot
* @return
*/
int32 Mob::GetTextureProfileMaterial(uint8 material_slot) const
{
switch (material_slot) {
case EQ::textures::armorHead:
return mob_texture_profile.Head.Material;
case EQ::textures::armorChest:
return mob_texture_profile.Chest.Material;
case EQ::textures::armorArms:
return mob_texture_profile.Arms.Material;
case EQ::textures::armorWrist:
return mob_texture_profile.Wrist.Material;
case EQ::textures::armorHands:
return mob_texture_profile.Hands.Material;
case EQ::textures::armorLegs:
return mob_texture_profile.Legs.Material;
case EQ::textures::armorFeet:
return mob_texture_profile.Feet.Material;
case EQ::textures::weaponPrimary:
return mob_texture_profile.Primary.Material;
case EQ::textures::weaponSecondary:
return mob_texture_profile.Secondary.Material;
default:
return 0;
}
}
/**
* Returns internal representation of mob texture by color
*
* @param material_slot
* @return
*/
int32 Mob::GetTextureProfileColor(uint8 material_slot) const
{
switch (material_slot) {
case EQ::textures::armorHead:
return mob_texture_profile.Head.Color;
case EQ::textures::armorChest:
return mob_texture_profile.Chest.Color;
case EQ::textures::armorArms:
return mob_texture_profile.Arms.Color;
case EQ::textures::armorWrist:
return mob_texture_profile.Wrist.Color;
case EQ::textures::armorHands:
return mob_texture_profile.Hands.Color;
case EQ::textures::armorLegs:
return mob_texture_profile.Legs.Color;
case EQ::textures::armorFeet:
return mob_texture_profile.Feet.Color;
case EQ::textures::weaponPrimary:
return mob_texture_profile.Primary.Color;
case EQ::textures::weaponSecondary:
return mob_texture_profile.Secondary.Color;
default:
return 0;
}
}
/**
* Returns internal representation of mob texture by HerosForgeModel
*
* @param material_slot
* @return
*/
int32 Mob::GetTextureProfileHeroForgeModel(uint8 material_slot) const
{
switch (material_slot) {
case EQ::textures::armorHead:
return mob_texture_profile.Head.HerosForgeModel;
case EQ::textures::armorChest:
return mob_texture_profile.Chest.HerosForgeModel;
case EQ::textures::armorArms:
return mob_texture_profile.Arms.HerosForgeModel;
case EQ::textures::armorWrist:
return mob_texture_profile.Wrist.HerosForgeModel;
case EQ::textures::armorHands:
return mob_texture_profile.Hands.HerosForgeModel;
case EQ::textures::armorLegs:
return mob_texture_profile.Legs.HerosForgeModel;
case EQ::textures::armorFeet:
return mob_texture_profile.Feet.HerosForgeModel;
case EQ::textures::weaponPrimary:
return mob_texture_profile.Primary.HerosForgeModel;
case EQ::textures::weaponSecondary:
return mob_texture_profile.Secondary.HerosForgeModel;
default:
return 0;
}
}
/**
* Gets the material or texture for a slot (leather / plate etc.)
*
* @param material_slot
* @return
*/
int32 Mob::GetEquipmentMaterial(uint8 material_slot) const
{
uint32 equipment_material = 0;
int32 texture_profile_material = GetTextureProfileMaterial(material_slot);
LogMobAppearance(
"[{}] material_slot: {} texture_profile_material: {}",
clean_name,
material_slot,
texture_profile_material
);
if (texture_profile_material > 0) {
return texture_profile_material;
}
auto item = database.GetItem(GetEquippedItemFromTextureSlot(material_slot));
if (item) {
const auto is_equipped_weapon = EQ::ValueWithin(material_slot, EQ::textures::weaponPrimary, EQ::textures::weaponSecondary);
if (is_equipped_weapon) {
if (IsClient()) {
const auto inventory_slot = EQ::InventoryProfile::CalcSlotFromMaterial(material_slot);
if (inventory_slot == INVALID_INDEX) {
return 0;
}
const auto* inst = CastToClient()->m_inv[inventory_slot];
if (inst) {
const auto augment = inst->GetOrnamentationAugment();
if (augment) {
item = augment->GetItem();
if (item && strlen(item->IDFile) > 2 && Strings::IsNumber(&item->IDFile[2])) {
equipment_material = Strings::ToInt(&item->IDFile[2]);
}
} else if (inst->GetOrnamentationIDFile()) {
equipment_material = inst->GetOrnamentationIDFile();
}
}
}
if (!equipment_material && strlen(item->IDFile) > 2 && Strings::IsNumber(&item->IDFile[2])) {
equipment_material = Strings::ToInt(&item->IDFile[2]);
}
}
else {
equipment_material = item->Material;
}
}
return equipment_material;
}
uint8 Mob::GetEquipmentType(uint8 material_slot) const
{
auto item_type = static_cast<uint8>(EQ::item::ItemType2HBlunt);
auto item = database.GetItem(GetEquippedItemFromTextureSlot(material_slot));
if (item) {
const auto is_equipped_weapon = EQ::ValueWithin(material_slot, EQ::textures::weaponPrimary, EQ::textures::weaponSecondary);
if (is_equipped_weapon) {
if (IsClient()) {
const auto inventory_slot = EQ::InventoryProfile::CalcSlotFromMaterial(material_slot);
if (inventory_slot == INVALID_INDEX) {
return item_type;
}
const auto* inst = CastToClient()->m_inv[inventory_slot];
if (inst) {
item_type = inst->GetItemType();
}
}
}
}
return item_type;
}
/**
* @param material_slot
* @return
*/
uint32 Mob::GetEquipmentColor(uint8 material_slot) const
{
const EQ::ItemData *item = nullptr;
if (armor_tint.Slot[material_slot].Color) {
return armor_tint.Slot[material_slot].Color;
}
item = database.GetItem(GetEquippedItemFromTextureSlot(material_slot));
if (item != nullptr) {
return item->Color;
}
return 0;
}
/**
* @param material_slot
* @return
*/
int32 Mob::GetHerosForgeModel(uint8 material_slot) const
{
uint32 hero_model = 0;
if (EQ::ValueWithin(material_slot, 0, EQ::textures::weaponPrimary)) {
const EQ::ItemData *item = database.GetItem(GetEquippedItemFromTextureSlot(material_slot));
const auto slot = EQ::InventoryProfile::CalcSlotFromMaterial(material_slot);
if (item && slot != INVALID_INDEX) {
if (IsClient()) {
const auto inst = CastToClient()->m_inv[slot];
if (inst) {
const auto augment = inst->GetOrnamentationAugment();
if (augment) {
item = augment->GetItem();
hero_model = item->HerosForgeModel;
} else if (inst->GetOrnamentHeroModel()) {
hero_model = inst->GetOrnamentHeroModel();
}
}
}
if (hero_model == 0) {
hero_model = item->HerosForgeModel;
}
}
if (IsNPC()) {
hero_model = CastToNPC()->GetHeroForgeModel();
/**
* Robes require full model number, and should only be sent to chest, arms, wrists, and legs slots
*/
if (
hero_model > 1000 &&
material_slot != EQ::textures::armorChest &&
material_slot != EQ::textures::armorArms &&
material_slot != EQ::textures::armorWrist &&
material_slot != EQ::textures::armorLegs
) {
hero_model = 0;
}
}
}
/**
* Auto-Convert Hero Model to match the slot
*
* Otherwise, use the exact Model if model is > 999
* Robes for example are 11607 to 12107 in RoF
*/
if (EQ::ValueWithin(hero_model, 1, 999)) {
hero_model *= 100;
hero_model += material_slot;
}
return hero_model;
}
uint32 NPC::GetEquippedItemFromTextureSlot(uint8 material_slot) const
{
if (material_slot > 8) {
return 0;
}
int16 inventory_slot = EQ::InventoryProfile::CalcSlotFromMaterial(material_slot);
if (inventory_slot == INVALID_INDEX) {
return 0;
}
return equipment[inventory_slot];
}
/**
* NPCs typically use this function for sending appearance
* @param one_client
*/
void Mob::SendArmorAppearance(Client *one_client)
{
/**
* one_client of 0 means sent to all clients
*
* Despite the fact that OP_NewSpawn and OP_ZoneSpawns include the
* armor being worn and its mats, the client doesn't update the display
* on arrival of these packets reliably.
*
* Send Wear changes if mob is a PC race and item is an armor slot.
* The other packets work for primary/secondary.
*/
LogMobAppearance("[{}]", GetCleanName());
if (IsPlayerRace(race)) {
if (!IsClient()) {
for (uint8 i = 0; i <= EQ::textures::materialCount; ++i) {
const EQ::ItemData *item = database.GetItem(GetEquippedItemFromTextureSlot(i));
if (item != nullptr) {
SendWearChange(i, one_client);
}
}
}
}
for (uint8 i = 0; i <= EQ::textures::materialCount; ++i) {
if (GetTextureProfileMaterial(i)) {
SendWearChange(i, one_client);
}
}
}
/**
* @param material_slot
* @param one_client
*/
void Mob::SendWearChange(uint8 material_slot, Client *one_client)
{
auto packet = new EQApplicationPacket(OP_WearChange, sizeof(WearChange_Struct));
auto *wear_change = (WearChange_Struct *) packet->pBuffer;
Log(Logs::Detail, Logs::MobAppearance, "[%s]",
GetCleanName()
);
wear_change->spawn_id = GetID();
wear_change->material = static_cast<uint32>(GetEquipmentMaterial(material_slot));
wear_change->elite_material = IsEliteMaterialItem(material_slot);
wear_change->hero_forge_model = static_cast<uint32>(GetHerosForgeModel(material_slot));
if (IsBot()) {
auto item_inst = CastToBot()->GetBotItem(EQ::InventoryProfile::CalcSlotFromMaterial(material_slot));
if (item_inst)
wear_change->color.Color = item_inst->GetColor();
else
wear_change->color.Color = 0;
}
else {
wear_change->color.Color = GetEquipmentColor(material_slot);
}
wear_change->wear_slot_id = material_slot;
// Part of a bug fix to ensure heroforge models send to other clients in zone.
queue_wearchange_slot = wear_change->hero_forge_model ? material_slot : -1;
if (!one_client) {
entity_list.QueueClients(this, packet);
}
else {
one_client->QueuePacket(packet, false, Client::CLIENT_CONNECTED);
}
safe_delete(packet);
}
/**
*
* @param slot
* @param texture
* @param hero_forge_model
* @param elite_material
* @param unknown06
* @param unknown18
*/
void Mob::SendTextureWC(
uint8 slot,
uint16 texture,
uint32 hero_forge_model,
uint32 elite_material,
uint32 unknown06,
uint32 unknown18
)
{
auto outapp = new EQApplicationPacket(OP_WearChange, sizeof(WearChange_Struct));
auto *wear_change = (WearChange_Struct *) outapp->pBuffer;
if (IsClient()) {
wear_change->color.Color = GetEquipmentColor(slot);
}
else {
wear_change->color.Color = GetArmorTint(slot);
}
wear_change->spawn_id = GetID();
wear_change->material = texture;
wear_change->wear_slot_id = slot;
wear_change->unknown06 = unknown06;
wear_change->elite_material = elite_material;
wear_change->hero_forge_model = hero_forge_model;
wear_change->unknown18 = unknown18;
SetMobTextureProfile(slot, texture, wear_change->color.Color, hero_forge_model);
entity_list.QueueClients(this, outapp);
safe_delete(outapp);
}
/**
* @param material_slot
* @param red_tint
* @param green_tint
* @param blue_tint
*/
void Mob::SetSlotTint(uint8 material_slot, uint8 red_tint, uint8 green_tint, uint8 blue_tint)
{
uint32 color;
color = (red_tint & 0xFF) << 16;
color |= (green_tint & 0xFF) << 8;
color |= (blue_tint & 0xFF);
color |= (color) ? (0xFF << 24) : 0;
armor_tint.Slot[material_slot].Color = color;
auto outapp = new EQApplicationPacket(OP_WearChange, sizeof(WearChange_Struct));
auto *wc = (WearChange_Struct *) outapp->pBuffer;
wc->spawn_id = GetID();
wc->material = GetEquipmentMaterial(material_slot);
wc->hero_forge_model = GetHerosForgeModel(material_slot);
wc->color.Color = color;
wc->wear_slot_id = material_slot;
SetMobTextureProfile(material_slot, texture, color);
entity_list.QueueClients(this, outapp);
safe_delete(outapp);
}
/**
* @param material_slot
* @param texture
* @param color
* @param hero_forge_model
*/
void Mob::WearChange(uint8 material_slot, uint16 texture, uint32 color, uint32 hero_forge_model)
{
armor_tint.Slot[material_slot].Color = color;
/**
* Change internal values
*/
SetMobTextureProfile(material_slot, texture, color, hero_forge_model);
/**
* Packet update
*/
auto outapp = new EQApplicationPacket(OP_WearChange, sizeof(WearChange_Struct));
auto *wear_change = (WearChange_Struct *) outapp->pBuffer;
wear_change->spawn_id = GetID();
wear_change->material = texture;
wear_change->hero_forge_model = hero_forge_model;
wear_change->color.Color = color;
wear_change->wear_slot_id = material_slot;
entity_list.QueueClients(this, outapp);
safe_delete(outapp);
}