diff --git a/Server b/Server new file mode 160000 index 000000000..9d78eec48 --- /dev/null +++ b/Server @@ -0,0 +1 @@ +Subproject commit 9d78eec485fb73c8d61ce035590474556390783e diff --git a/common/shareddbx.cpp b/common/shareddbx.cpp new file mode 100644 index 000000000..60e29cccb --- /dev/null +++ b/common/shareddbx.cpp @@ -0,0 +1,2162 @@ +#include +#include +#include + +#include "shareddb.h" +#include "mysql.h" +#include "Item.h" +#include "classes.h" +#include "rulesys.h" +#include "seperator.h" +#include "StringUtil.h" +#include "eq_packet_structs.h" +#include "guilds.h" +#include "extprofile.h" +#include "memory_mapped_file.h" +#include "ipc_mutex.h" +#include "eqemu_exception.h" +#include "loottable.h" +#include "faction.h" +#include "features.h" + +SharedDatabase::SharedDatabase() +: Database(), skill_caps_mmf(nullptr), items_mmf(nullptr), items_hash(nullptr), faction_mmf(nullptr), faction_hash(nullptr), + loot_table_mmf(nullptr), loot_table_hash(nullptr), loot_drop_mmf(nullptr), loot_drop_hash(nullptr), base_data_mmf(nullptr) +{ +} + +SharedDatabase::SharedDatabase(const char* host, const char* user, const char* passwd, const char* database, uint32 port) +: Database(host, user, passwd, database, port), skill_caps_mmf(nullptr), items_mmf(nullptr), items_hash(nullptr), + faction_mmf(nullptr), faction_hash(nullptr), loot_table_mmf(nullptr), loot_table_hash(nullptr), loot_drop_mmf(nullptr), + loot_drop_hash(nullptr), base_data_mmf(nullptr) +{ +} + +SharedDatabase::~SharedDatabase() { + safe_delete(skill_caps_mmf); + safe_delete(items_mmf); + safe_delete(items_hash); + safe_delete(faction_mmf); + safe_delete(faction_hash); + safe_delete(loot_table_mmf); + safe_delete(loot_drop_mmf); + safe_delete(loot_table_hash); + safe_delete(loot_drop_hash); + safe_delete(base_data_mmf); +} + +bool SharedDatabase::SetHideMe(uint32 account_id, uint8 hideme) +{ + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + + if (!RunQuery(query, MakeAnyLenString(&query, "UPDATE account SET hideme = %i where id = %i", hideme, account_id), errbuf)) { + std::cerr << "Error in SetGMSpeed query '" << query << "' " << errbuf << std::endl; + safe_delete_array(query); + return false; + } + + safe_delete_array(query); + return true; + +} + +uint8 SharedDatabase::GetGMSpeed(uint32 account_id) +{ + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + if (RunQuery(query, MakeAnyLenString(&query, "SELECT gmspeed FROM account where id='%i'", account_id), errbuf, &result)) { + safe_delete_array(query); + if (mysql_num_rows(result) == 1) + { + row = mysql_fetch_row(result); + uint8 gmspeed = atoi(row[0]); + mysql_free_result(result); + return gmspeed; + } + else + { + mysql_free_result(result); + return 0; + } + mysql_free_result(result); + } + else + { + + std::cerr << "Error in GetGMSpeed query '" << query << "' " << errbuf << std::endl; + safe_delete_array(query); + return false; + } + + return 0; + + +} + +bool SharedDatabase::SetGMSpeed(uint32 account_id, uint8 gmspeed) +{ + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + + if (!RunQuery(query, MakeAnyLenString(&query, "UPDATE account SET gmspeed = %i where id = %i", gmspeed, account_id), errbuf)) { + std::cerr << "Error in SetGMSpeed query '" << query << "' " << errbuf << std::endl; + safe_delete_array(query); + return false; + } + + safe_delete_array(query); + return true; + +} + +uint32 SharedDatabase::GetTotalTimeEntitledOnAccount(uint32 AccountID) { + + uint32 EntitledTime = 0; + + const char *EntitledQuery = "select sum(ascii(substring(profile, 237, 1)) + (ascii(substring(profile, 238, 1)) * 256) +" + "(ascii(substring(profile, 239, 1)) * 65536) + (ascii(substring(profile, 240, 1)) * 16777216))" + "from character_ where account_id = %i"; + + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + if (RunQuery(query, MakeAnyLenString(&query, EntitledQuery, AccountID), errbuf, &result)) { + + if (mysql_num_rows(result) == 1) { + + row = mysql_fetch_row(result); + + EntitledTime = atoi(row[0]); + } + + mysql_free_result(result); + } + + safe_delete_array(query); + + return EntitledTime; +} + +bool SharedDatabase::SaveCursor(uint32 char_id, std::list::const_iterator &start, std::list::const_iterator &end) +{ +iter_queue it; +int i; +bool ret=true; + char errbuf[MYSQL_ERRMSG_SIZE]; + char* query = 0; + // Delete cursor items + if ((ret = RunQuery(query, MakeAnyLenString(&query, "DELETE FROM inventory WHERE charid=%i AND ( (slotid >=8000 and slotid<=8999) or slotid=30 or (slotid>=331 and slotid<=340))", char_id), errbuf))) { + for(it=start,i=8000;it!=end;++it,i++) { + ItemInst *inst=*it; + if (!(ret=SaveInventory(char_id,inst,(i==8000) ? 30 : i))) + break; + } + } else { + std::cout << "Clearing cursor failed: " << errbuf << std::endl; + } + safe_delete_array(query); + + return ret; +} + +bool SharedDatabase::VerifyInventory(uint32 account_id, int16 slot_id, const ItemInst* inst) +{ + char errbuf[MYSQL_ERRMSG_SIZE]; + char* query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + // Delete cursor items + if (!RunQuery(query, MakeAnyLenString(&query, + "SELECT itemid,charges FROM sharedbank " + "WHERE acctid=%d AND slotid=%d", + account_id, slot_id), errbuf, &result)) { + LogFile->write(EQEMuLog::Error, "Error runing inventory verification query '%s': %s", query, errbuf); + safe_delete_array(query); + //returning true is less harmful in the face of a query error + return(true); + } + safe_delete_array(query); + + row = mysql_fetch_row(result); + bool found = false; + if(row) { + uint32 id = atoi(row[0]); + uint16 charges = atoi(row[1]); + + uint16 expect_charges = 0; + if(inst->GetCharges() >= 0) + expect_charges = inst->GetCharges(); + else + expect_charges = 0x7FFF; + + if(id == inst->GetItem()->ID && charges == expect_charges) + found = true; + } + mysql_free_result(result); + return(found); +} + +bool SharedDatabase::SaveInventory(uint32 char_id, const ItemInst* inst, int16 slot_id) { + char errbuf[MYSQL_ERRMSG_SIZE]; + char* query = 0; + bool ret = false; + uint32 augslot[5] = { 0, 0, 0, 0, 0 }; + + //never save tribute slots: + if(slot_id >= 400 && slot_id <= 404) + return(true); + + if (inst && inst->IsType(ItemClassCommon)) { + for(int i=0;i<5;i++) { + ItemInst *auginst=inst->GetItem(i); + augslot[i]=(auginst && auginst->GetItem()) ? auginst->GetItem()->ID : 0; + } + } + + if (slot_id>=2500 && slot_id<=2600) { // Shared bank inventory + if (!inst) { + // Delete item + uint32 account_id = GetAccountIDByChar(char_id); + uint32 len_query = MakeAnyLenString(&query, "DELETE FROM sharedbank WHERE acctid=%i AND slotid=%i", + account_id, slot_id); + + ret = RunQuery(query, len_query, errbuf); + + // Delete bag slots, if need be + if (ret && Inventory::SupportsContainers(slot_id)) { + safe_delete_array(query); + int16 base_slot_id = Inventory::CalcSlotId(slot_id, 0); + ret = RunQuery(query, MakeAnyLenString(&query, "DELETE FROM sharedbank WHERE acctid=%i AND slotid>=%i AND slotid<%i", + account_id, base_slot_id, (base_slot_id+10)), errbuf); + } + + // @merth: need to delete augments here + } + else { + // Update/Insert item + uint32 account_id = GetAccountIDByChar(char_id); + uint16 charges = 0; + if(inst->GetCharges() >= 0) + charges = inst->GetCharges(); + else + charges = 0x7FFF; + + uint32 len_query = MakeAnyLenString(&query, + "REPLACE INTO sharedbank " + " (acctid,slotid,itemid,charges,custom_data," + " augslot1,augslot2,augslot3,augslot4,augslot5)" + " VALUES(%lu,%lu,%lu,%lu,'%s'," + " %lu,%lu,%lu,%lu,%lu)", + (unsigned long)account_id, (unsigned long)slot_id, (unsigned long)inst->GetItem()->ID, (unsigned long)charges, + inst->GetCustomDataString().c_str(), + (unsigned long)augslot[0],(unsigned long)augslot[1],(unsigned long)augslot[2],(unsigned long)augslot[3],(unsigned long)augslot[4]); + + + ret = RunQuery(query, len_query, errbuf); + } + } + else { // All other inventory + if (!inst) { + // Delete item + ret = RunQuery(query, MakeAnyLenString(&query, "DELETE FROM inventory WHERE charid=%i AND slotid=%i", + char_id, slot_id), errbuf); + + // Delete bag slots, if need be + if (ret && Inventory::SupportsContainers(slot_id)) { + safe_delete_array(query); + int16 base_slot_id = Inventory::CalcSlotId(slot_id, 0); + ret = RunQuery(query, MakeAnyLenString(&query, "DELETE FROM inventory WHERE charid=%i AND slotid>=%i AND slotid<%i", + char_id, base_slot_id, (base_slot_id+10)), errbuf); + } + + // @merth: need to delete augments here + } + else { + uint16 charges = 0; + if(inst->GetCharges() >= 0) + charges = inst->GetCharges(); + else + charges = 0x7FFF; + // Update/Insert item + uint32 len_query = MakeAnyLenString(&query, + "REPLACE INTO inventory " + " (charid,slotid,itemid,charges,instnodrop,custom_data,color," + " augslot1,augslot2,augslot3,augslot4,augslot5)" + " VALUES(%lu,%lu,%lu,%lu,%lu,'%s',%lu," + " %lu,%lu,%lu,%lu,%lu)", + (unsigned long)char_id, (unsigned long)slot_id, (unsigned long)inst->GetItem()->ID, (unsigned long)charges, + (unsigned long)(inst->IsInstNoDrop() ? 1:0),inst->GetCustomDataString().c_str(),(unsigned long)inst->GetColor(), + (unsigned long)augslot[0],(unsigned long)augslot[1],(unsigned long)augslot[2],(unsigned long)augslot[3],(unsigned long)augslot[4] ); + + ret = RunQuery(query, len_query, errbuf); + } + } + + if (!ret) + LogFile->write(EQEMuLog::Error, "SaveInventory query '%s': %s", query, errbuf); + safe_delete_array(query); + + // Save bag contents, if slot supports bag contents + if (inst && inst->IsType(ItemClassContainer) && Inventory::SupportsContainers(slot_id)) { + for (uint8 idx=0; idx<10; idx++) { + const ItemInst* baginst = inst->GetItem(idx); + SaveInventory(char_id, baginst, Inventory::CalcSlotId(slot_id, idx)); + } + } + + // @merth: need to save augments here + + return ret; +} + +int32 SharedDatabase::GetSharedPlatinum(uint32 account_id) +{ + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + if (RunQuery(query, MakeAnyLenString(&query, "SELECT sharedplat FROM account WHERE id='%i'", account_id), errbuf, &result)) { + safe_delete_array(query); + if (mysql_num_rows(result) == 1) + { + row = mysql_fetch_row(result); + uint32 shared_platinum = atoi(row[0]); + mysql_free_result(result); + return shared_platinum; + } + else + { + mysql_free_result(result); + return 0; + } + mysql_free_result(result); + } + else + { + std::cerr << "Error in GetSharedPlatinum query '" << query << "' " << errbuf << std::endl; + safe_delete_array(query); + return false; + } + + return 0; +} + +bool SharedDatabase::SetSharedPlatinum(uint32 account_id, int32 amount_to_add) +{ + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + + if (!RunQuery(query, MakeAnyLenString(&query, "UPDATE account SET sharedplat = sharedplat + %i WHERE id = %i", amount_to_add, account_id), errbuf)) { + std::cerr << "Error in SetSharedPlatinum query '" << query << "' " << errbuf << std::endl; + safe_delete_array(query); + return false; + } + + safe_delete_array(query); + return true; +} + +bool SharedDatabase::SetStartingItems(PlayerProfile_Struct* pp, Inventory* inv, uint32 si_race, uint32 si_class, uint32 si_deity, uint32 si_current_zone, char* si_name, int admin_level) +{ + char errbuf[MYSQL_ERRMSG_SIZE]; + char* query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + const Item_Struct* myitem; + + RunQuery + ( + query, + MakeAnyLenString + ( + &query, + "SELECT itemid, item_charges, slot FROM starting_items " + "WHERE (race = %i or race = 0) AND (class = %i or class = 0) AND " + "(deityid = %i or deityid=0) AND (zoneid = %i or zoneid = 0) AND " + "gm <= %i ORDER BY id", + si_race, si_class, si_deity, si_current_zone, admin_level + ), + errbuf, + &result + ); + safe_delete_array(query); + + while((row = mysql_fetch_row(result))) { + int itemid = atoi(row[0]); + int charges = atoi(row[1]); + int slot = atoi(row[2]); + myitem = GetItem(itemid); + if(!myitem) + continue; + ItemInst* myinst = CreateBaseItem(myitem, charges); + if(slot < 0) + slot = inv->FindFreeSlot(0,0); + inv->PutItem(slot, *myinst); + safe_delete(myinst); + } + + if(result) mysql_free_result(result); + + return true; +} + + +// Retrieve shared bank inventory based on either account or character +bool SharedDatabase::GetSharedBank(uint32 id, Inventory* inv, bool is_charid) { + char errbuf[MYSQL_ERRMSG_SIZE]; + char* query = 0; + uint32 len_query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + bool ret = false; + + if (is_charid) { + len_query = MakeAnyLenString(&query, + "SELECT sb.slotid,sb.itemid,sb.charges,sb.augslot1,sb.augslot2,sb.augslot3,sb.augslot4,sb.augslot5,sb.custom_data from sharedbank sb " + "INNER JOIN character_ ch ON ch.account_id=sb.acctid " + "WHERE ch.id=%i", id); + } + else { + len_query = MakeAnyLenString(&query, + "SELECT slotid,itemid,charges,augslot1,augslot2,augslot3,augslot4,augslot5,custom_data from sharedbank WHERE acctid=%i", id); + } + + if (RunQuery(query, len_query, errbuf, &result)) { + while ((row = mysql_fetch_row(result))) { + int16 slot_id = (int16)atoi(row[0]); + uint32 item_id = (uint32)atoi(row[1]); + int8 charges = (int8)atoi(row[2]); + uint32 aug[5]; + aug[0] = (uint32)atoi(row[3]); + aug[1] = (uint32)atoi(row[4]); + aug[2] = (uint32)atoi(row[5]); + aug[3] = (uint32)atoi(row[6]); + aug[4] = (uint32)atoi(row[7]); + const Item_Struct* item = GetItem(item_id); + + if (item) { + int16 put_slot_id = INVALID_INDEX; + + ItemInst* inst = CreateBaseItem(item, charges); + if (item->ItemClass == ItemClassCommon) { + for(int i=0;i<5;i++) { + if (aug[i]) { + inst->PutAugment(this, i, aug[i]); + } + } + } + if(row[8]) { + std::string data_str(row[8]); + std::string id; + std::string value; + bool use_id = true; + + for(int i = 0; i < data_str.length(); ++i) { + if(data_str[i] == '^') { + if(!use_id) { + inst->SetCustomData(id, value); + id.clear(); + value.clear(); + } + use_id = !use_id; + } + else { + char v = data_str[i]; + if(use_id) { + id.push_back(v); + } else { + value.push_back(v); + } + } + } + } + + put_slot_id = inv->PutItem(slot_id, *inst); + safe_delete(inst); + + // Save ptr to item in inventory + if (put_slot_id == INVALID_INDEX) { + LogFile->write(EQEMuLog::Error, + "Warning: Invalid slot_id for item in shared bank inventory: %s=%i, item_id=%i, slot_id=%i", + ((is_charid==true) ? "charid" : "acctid"), id, item_id, slot_id); + + if(is_charid) + SaveInventory(id,nullptr,slot_id); + } + } + else { + LogFile->write(EQEMuLog::Error, + "Warning: %s %i has an invalid item_id %i in inventory slot %i", + ((is_charid==true) ? "charid" : "acctid"), id, item_id, slot_id); + } + } + + mysql_free_result(result); + ret = true; + } + else { + LogFile->write(EQEMuLog::Error, "Database::GetSharedBank(uint32 account_id): %s", errbuf); + } + + safe_delete_array(query); + return ret; +} + + +// Overloaded: Retrieve character inventory based on character id +bool SharedDatabase::GetInventory(uint32 char_id, Inventory* inv) { + char errbuf[MYSQL_ERRMSG_SIZE]; + char* query = 0; + MYSQL_RES* result; + MYSQL_ROW row; + bool ret = false; + + // Retrieve character inventory + if (RunQuery(query, MakeAnyLenString(&query, "SELECT slotid,itemid,charges,color,augslot1,augslot2,augslot3,augslot4,augslot5," + "instnodrop,custom_data FROM inventory WHERE charid=%i ORDER BY slotid", char_id), errbuf, &result)) { + + while ((row = mysql_fetch_row(result))) { + int16 slot_id = atoi(row[0]); + uint32 item_id = atoi(row[1]); + uint16 charges = atoi(row[2]); + uint32 color = atoul(row[3]); + uint32 aug[5]; + aug[0] = (uint32)atoul(row[4]); + aug[1] = (uint32)atoul(row[5]); + aug[2] = (uint32)atoul(row[6]); + aug[3] = (uint32)atoul(row[7]); + aug[4] = (uint32)atoul(row[8]); + bool instnodrop = (row[9] && (uint16)atoi(row[9])) ? true : false; + + const Item_Struct* item = GetItem(item_id); + + if (item) { + int16 put_slot_id = INVALID_INDEX; + + ItemInst* inst = CreateBaseItem(item, charges); + + if(row[10]) { + std::string data_str(row[10]); + std::string id; + std::string value; + bool use_id = true; + + for(int i = 0; i < data_str.length(); ++i) { + if(data_str[i] == '^') { + if(!use_id) { + inst->SetCustomData(id, value); + id.clear(); + value.clear(); + } + use_id = !use_id; + } + else { + char v = data_str[i]; + if(use_id) { + id.push_back(v); + } else { + value.push_back(v); + } + } + } + } + + if (instnodrop || (slot_id >= 0 && slot_id <= 21 && inst->GetItem()->Attuneable)) + inst->SetInstNoDrop(true); + if (color > 0) + inst->SetColor(color); + if(charges==0x7FFF) + inst->SetCharges(-1); + else + inst->SetCharges(charges); + + if (item->ItemClass == ItemClassCommon) { + for(int i=0;i<5;i++) { + if (aug[i]) { + inst->PutAugment(this, i, aug[i]); + } + } + } + + if (slot_id>=8000 && slot_id <= 8999) + put_slot_id = inv->PushCursor(*inst); + else + put_slot_id = inv->PutItem(slot_id, *inst); + safe_delete(inst); + + // Save ptr to item in inventory + if (put_slot_id == INVALID_INDEX) { + LogFile->write(EQEMuLog::Error, + "Warning: Invalid slot_id for item in inventory: charid=%i, item_id=%i, slot_id=%i", + char_id, item_id, slot_id); + } + } + else { + LogFile->write(EQEMuLog::Error, + "Warning: charid %i has an invalid item_id %i in inventory slot %i", + char_id, item_id, slot_id); + } + } + mysql_free_result(result); + + // Retrieve shared inventory + ret = GetSharedBank(char_id, inv, true); + } + else { + LogFile->write(EQEMuLog::Error, "GetInventory query '%s' %s", query, errbuf); + LogFile->write(EQEMuLog::Error, "If you got an error related to the 'instnodrop' field, run the following SQL Queries:\nalter table inventory add instnodrop tinyint(1) unsigned default 0 not null;\n"); + } + + safe_delete_array(query); + return ret; +} + +// Overloaded: Retrieve character inventory based on account_id and character name +bool SharedDatabase::GetInventory(uint32 account_id, char* name, Inventory* inv) { + char errbuf[MYSQL_ERRMSG_SIZE]; + char* query = 0; + MYSQL_RES* result; + MYSQL_ROW row; + bool ret = false; + + // Retrieve character inventory + if (RunQuery(query, MakeAnyLenString(&query, "SELECT slotid,itemid,charges,color,augslot1,augslot2,augslot3,augslot4,augslot5," + "instnodrop,custom_data FROM inventory INNER JOIN character_ ch ON ch.id=charid WHERE ch.name='%s' AND ch.account_id=%i ORDER BY slotid", + name, account_id), errbuf, &result)) + { + while ((row = mysql_fetch_row(result))) { + int16 slot_id = atoi(row[0]); + uint32 item_id = atoi(row[1]); + int8 charges = atoi(row[2]); + uint32 color = atoul(row[3]); + uint32 aug[5]; + aug[0] = (uint32)atoi(row[4]); + aug[1] = (uint32)atoi(row[5]); + aug[2] = (uint32)atoi(row[6]); + aug[3] = (uint32)atoi(row[7]); + aug[4] = (uint32)atoi(row[8]); + bool instnodrop = (row[9] && (uint16)atoi(row[9])) ? true : false; + const Item_Struct* item = GetItem(item_id); + int16 put_slot_id = INVALID_INDEX; + if(!item) + continue; + + ItemInst* inst = CreateBaseItem(item, charges); + inst->SetInstNoDrop(instnodrop); + + if(row[10]) { + std::string data_str(row[10]); + std::string id; + std::string value; + bool use_id = true; + + for(int i = 0; i < data_str.length(); ++i) { + if(data_str[i] == '^') { + if(!use_id) { + inst->SetCustomData(id, value); + id.clear(); + value.clear(); + } + use_id = !use_id; + } + else { + char v = data_str[i]; + if(use_id) { + id.push_back(v); + } else { + value.push_back(v); + } + } + } + } + + if (color > 0) + inst->SetColor(color); + inst->SetCharges(charges); + + if (item->ItemClass == ItemClassCommon) { + for(int i=0;i<5;i++) { + if (aug[i]) { + inst->PutAugment(this, i, aug[i]); + } + } + } + if (slot_id>=8000 && slot_id <= 8999) + put_slot_id = inv->PushCursor(*inst); + else + put_slot_id = inv->PutItem(slot_id, *inst); + safe_delete(inst); + + // Save ptr to item in inventory + if (put_slot_id == INVALID_INDEX) { + LogFile->write(EQEMuLog::Error, + "Warning: Invalid slot_id for item in inventory: name=%s, acctid=%i, item_id=%i, slot_id=%i", + name, account_id, item_id, slot_id); + } + } + mysql_free_result(result); + + // Retrieve shared inventory + ret = GetSharedBank(account_id, inv, false); + } + else { + LogFile->write(EQEMuLog::Error, "GetInventory query '%s' %s", query, errbuf); + LogFile->write(EQEMuLog::Error, "If you got an error related to the 'instnodrop' field, run the following SQL Queries:\nalter table inventory add instnodrop tinyint(1) unsigned default 0 not null;\n"); + } + + safe_delete_array(query); + return ret; +} + + +void SharedDatabase::GetItemsCount(int32 &item_count, uint32 &max_id) { + char errbuf[MYSQL_ERRMSG_SIZE]; + MYSQL_RES *result; + MYSQL_ROW row; + item_count = -1; + max_id = 0; + + char query[] = "SELECT MAX(id), count(*) FROM items"; + if (RunQuery(query, static_cast(strlen(query)), errbuf, &result)) { + row = mysql_fetch_row(result); + if (row != nullptr && row[1] != 0) { + item_count = atoi(row[1]); + if(row[0]) + max_id = atoi(row[0]); + } + mysql_free_result(result); + } + else { + LogFile->write(EQEMuLog::Error, "Error in GetItemsCount '%s': '%s'", query, errbuf); + } +} + +bool SharedDatabase::LoadItems() { + if(items_mmf) { + return true; + } + + try { + EQEmu::IPCMutex mutex("items"); + mutex.Lock(); + items_mmf = new EQEmu::MemoryMappedFile("shared/items"); + + int32 items = -1; + uint32 max_item = 0; + GetItemsCount(items, max_item); + if(items == -1) { + EQ_EXCEPT("SharedDatabase", "Database returned no result"); + } + uint32 size = static_cast(EQEmu::FixedMemoryHashSet::estimated_size(items, max_item)); + if(items_mmf->Size() != size) { + EQ_EXCEPT("SharedDatabase", "Couldn't load items because items_mmf->Size() != size"); + } + + items_hash = new EQEmu::FixedMemoryHashSet(reinterpret_cast(items_mmf->Get()), size); + mutex.Unlock(); + } catch(std::exception& ex) { + LogFile->write(EQEMuLog::Error, "Error Loading Items: %s", ex.what()); + return false; + } + + return true; +} + +void SharedDatabase::LoadItems(void *data, uint32 size, int32 items, uint32 max_item_id) { + EQEmu::FixedMemoryHashSet hash(reinterpret_cast(data), size, items, max_item_id); + char errbuf[MYSQL_ERRMSG_SIZE]; + MYSQL_RES *result; + MYSQL_ROW row; + + char ndbuffer[4]; + bool disableNoRent = false; + if(GetVariable("disablenorent", ndbuffer, 4)) { + if(ndbuffer[0] == '1' && ndbuffer[1] == '\0') { + disableNoRent = true; + } + } + bool disableNoDrop = false; + if(GetVariable("disablenodrop", ndbuffer, 4)) { + if(ndbuffer[0] == '1' && ndbuffer[1] == '\0') { + disableNoDrop = true; + } + } + bool disableLoreGroup = false; + if(GetVariable("disablelore", ndbuffer, 4)) { + if(ndbuffer[0] == '1' && ndbuffer[1] == '\0') { + disableLoreGroup = true; + } + } + bool disableNoTransfer = false; + if(GetVariable("disablenotransfer", ndbuffer, 4)) { + if(ndbuffer[0] == '1' && ndbuffer[1] == '\0') { + disableNoTransfer = true; + } + } + + char query[] = "select source," +#define F(x) "`"#x"`," +#include "item_fieldlist.h" +#undef F + "updated" + " from items order by id"; + Item_Struct item; + if(RunQuery(query, sizeof(query), errbuf, &result)) { + while((row = mysql_fetch_row(result))) { + memset(&item, 0, sizeof(Item_Struct)); + + item.ItemClass = (uint8)atoi(row[ItemField::itemclass]); + strcpy(item.Name,row[ItemField::name]); + strcpy(item.Lore,row[ItemField::lore]); + strcpy(item.IDFile,row[ItemField::idfile]); + item.ID = (uint32)atoul(row[ItemField::id]); + item.Weight = (uint8)atoi(row[ItemField::weight]); + item.NoRent = disableNoRent ? (uint8)atoi("255") : (uint8)atoi(row[ItemField::norent]); + item.NoDrop = disableNoDrop ? (uint8)atoi("255") : (uint8)atoi(row[ItemField::nodrop]); + item.Size = (uint8)atoi(row[ItemField::size]); + item.Slots = (uint32)atoul(row[ItemField::slots]); + item.Price = (uint32)atoul(row[ItemField::price]); + item.Icon = (uint32)atoul(row[ItemField::icon]); + item.BenefitFlag = (atoul(row[ItemField::benefitflag]) != 0); + item.Tradeskills = (atoi(row[ItemField::tradeskills])==0) ? false : true; + item.CR = (int8)atoi(row[ItemField::cr]); + item.DR = (int8)atoi(row[ItemField::dr]); + item.PR = (int8)atoi(row[ItemField::pr]); + item.MR = (int8)atoi(row[ItemField::mr]); + item.FR = (int8)atoi(row[ItemField::fr]); + item.AStr = (int8)atoi(row[ItemField::astr]); + item.ASta = (int8)atoi(row[ItemField::asta]); + item.AAgi = (int8)atoi(row[ItemField::aagi]); + item.ADex = (int8)atoi(row[ItemField::adex]); + item.ACha = (int8)atoi(row[ItemField::acha]); + item.AInt = (int8)atoi(row[ItemField::aint]); + item.AWis = (int8)atoi(row[ItemField::awis]); + item.HP = (int32)atoul(row[ItemField::hp]); + item.Mana = (int32)atoul(row[ItemField::mana]); + item.AC = (int32)atoul(row[ItemField::ac]); + item.Deity = (uint32)atoul(row[ItemField::deity]); + item.SkillModValue = (int32)atoul(row[ItemField::skillmodvalue]); + //item.Unk033 = (int32)atoul(row[ItemField::UNK033]); + item.SkillModType = (uint32)atoul(row[ItemField::skillmodtype]); + item.BaneDmgRace = (uint32)atoul(row[ItemField::banedmgrace]); + item.BaneDmgAmt = (int8)atoi(row[ItemField::banedmgamt]); + item.BaneDmgBody = (uint32)atoul(row[ItemField::banedmgbody]); + item.Magic = (atoi(row[ItemField::magic])==0) ? false : true; + item.CastTime_ = (int32)atoul(row[ItemField::casttime_]); + item.ReqLevel = (uint8)atoi(row[ItemField::reqlevel]); + item.BardType = (uint32)atoul(row[ItemField::bardtype]); + item.BardValue = (int32)atoul(row[ItemField::bardvalue]); + item.Light = (int8)atoi(row[ItemField::light]); + item.Delay = (uint8)atoi(row[ItemField::delay]); + item.RecLevel = (uint8)atoi(row[ItemField::reclevel]); + item.RecSkill = (uint8)atoi(row[ItemField::recskill]); + item.ElemDmgType = (uint8)atoi(row[ItemField::elemdmgtype]); + item.ElemDmgAmt = (uint8)atoi(row[ItemField::elemdmgamt]); + item.Range = (uint8)atoi(row[ItemField::range]); + item.Damage = (uint32)atoi(row[ItemField::damage]); + item.Color = (uint32)atoul(row[ItemField::color]); + item.Classes = (uint32)atoul(row[ItemField::classes]); + item.Races = (uint32)atoul(row[ItemField::races]); + //item.Unk054 = (uint32)atoul(row[ItemField::UNK054]); + item.MaxCharges = (int16)atoi(row[ItemField::maxcharges]); + item.ItemType = (uint8)atoi(row[ItemField::itemtype]); + item.Material = (uint8)atoi(row[ItemField::material]); + item.SellRate = (float)atof(row[ItemField::sellrate]); + //item.Unk059 = (uint32)atoul(row[ItemField::UNK059]); + item.CastTime = (uint32)atoul(row[ItemField::casttime]); + item.EliteMaterial = (uint32)atoul(row[ItemField::elitematerial]); + item.ProcRate = (int32)atoi(row[ItemField::procrate]); + item.CombatEffects = (int8)atoi(row[ItemField::combateffects]); + item.Shielding = (int8)atoi(row[ItemField::shielding]); + item.StunResist = (int8)atoi(row[ItemField::stunresist]); + item.StrikeThrough = (int8)atoi(row[ItemField::strikethrough]); + item.ExtraDmgSkill = (uint32)atoul(row[ItemField::extradmgskill]); + item.ExtraDmgAmt = (uint32)atoul(row[ItemField::extradmgamt]); + item.SpellShield = (int8)atoi(row[ItemField::spellshield]); + item.Avoidance = (int8)atoi(row[ItemField::avoidance]); + item.Accuracy = (int8)atoi(row[ItemField::accuracy]); + item.CharmFileID = (uint32)atoul(row[ItemField::charmfileid]); + item.FactionMod1 = (int32)atoul(row[ItemField::factionmod1]); + item.FactionMod2 = (int32)atoul(row[ItemField::factionmod2]); + item.FactionMod3 = (int32)atoul(row[ItemField::factionmod3]); + item.FactionMod4 = (int32)atoul(row[ItemField::factionmod4]); + item.FactionAmt1 = (int32)atoul(row[ItemField::factionamt1]); + item.FactionAmt2 = (int32)atoul(row[ItemField::factionamt2]); + item.FactionAmt3 = (int32)atoul(row[ItemField::factionamt3]); + item.FactionAmt4 = (int32)atoul(row[ItemField::factionamt4]); + strcpy(item.CharmFile,row[ItemField::charmfile]); + item.AugType = (uint32)atoul(row[ItemField::augtype]); + item.AugSlotType[0] = (uint8)atoi(row[ItemField::augslot1type]); + item.AugSlotVisible[0] = (uint8)atoi(row[ItemField::augslot1visible]); + item.AugSlotUnk2[0] = 0; + item.AugSlotType[1] = (uint8)atoi(row[ItemField::augslot2type]); + item.AugSlotVisible[1] = (uint8)atoi(row[ItemField::augslot2visible]); + item.AugSlotUnk2[1] = 0; + item.AugSlotType[2] = (uint8)atoi(row[ItemField::augslot3type]); + item.AugSlotVisible[2] = (uint8)atoi(row[ItemField::augslot3visible]); + item.AugSlotUnk2[2] = 0; + item.AugSlotType[3] = (uint8)atoi(row[ItemField::augslot4type]); + item.AugSlotVisible[3] = (uint8)atoi(row[ItemField::augslot4visible]); + item.AugSlotUnk2[3] = 0; + item.AugSlotType[4] = (uint8)atoi(row[ItemField::augslot5type]); + item.AugSlotVisible[4] = (uint8)atoi(row[ItemField::augslot5visible]); + item.AugSlotUnk2[4] = 0; + item.LDoNTheme = (uint32)atoul(row[ItemField::ldontheme]); + item.LDoNPrice = (uint32)atoul(row[ItemField::ldonprice]); + item.LDoNSold = (uint32)atoul(row[ItemField::ldonsold]); + item.BagType = (uint8)atoi(row[ItemField::bagtype]); + item.BagSlots = (uint8)atoi(row[ItemField::bagslots]); + item.BagSize = (uint8)atoi(row[ItemField::bagsize]); + item.BagWR = (uint8)atoi(row[ItemField::bagwr]); + item.Book = (uint8)atoi(row[ItemField::book]); + item.BookType = (uint32)atoul(row[ItemField::booktype]); + strcpy(item.Filename,row[ItemField::filename]); + item.BaneDmgRaceAmt = (uint32)atoul(row[ItemField::banedmgraceamt]); + item.AugRestrict = (uint32)atoul(row[ItemField::augrestrict]); + item.LoreGroup = disableLoreGroup ? (uint8)atoi("0") : atoi(row[ItemField::loregroup]); + item.LoreFlag = item.LoreGroup!=0; + item.PendingLoreFlag = (atoi(row[ItemField::pendingloreflag])==0) ? false : true; + item.ArtifactFlag = (atoi(row[ItemField::artifactflag])==0) ? false : true; + item.SummonedFlag = (atoi(row[ItemField::summonedflag])==0) ? false : true; + item.Favor = (uint32)atoul(row[ItemField::favor]); + item.FVNoDrop = (atoi(row[ItemField::fvnodrop])==0) ? false : true; + item.Endur = (uint32)atoul(row[ItemField::endur]); + item.DotShielding = (uint32)atoul(row[ItemField::dotshielding]); + item.Attack = (uint32)atoul(row[ItemField::attack]); + item.Regen = (uint32)atoul(row[ItemField::regen]); + item.ManaRegen = (uint32)atoul(row[ItemField::manaregen]); + item.EnduranceRegen = (uint32)atoul(row[ItemField::enduranceregen]); + item.Haste = (uint32)atoul(row[ItemField::haste]); + item.DamageShield = (uint32)atoul(row[ItemField::damageshield]); + item.RecastDelay = (uint32)atoul(row[ItemField::recastdelay]); + item.RecastType = (uint32)atoul(row[ItemField::recasttype]); + item.GuildFavor = (uint32)atoul(row[ItemField::guildfavor]); + item.AugDistiller = (uint32)atoul(row[ItemField::augdistiller]); + item.Attuneable = (atoi(row[ItemField::attuneable])==0) ? false : true; + item.NoPet = (atoi(row[ItemField::nopet])==0) ? false : true; + item.PointType = (uint32)atoul(row[ItemField::pointtype]); + item.PotionBelt = (atoi(row[ItemField::potionbelt])==0) ? false : true; + item.PotionBeltSlots = (atoi(row[ItemField::potionbeltslots])==0) ? false : true; + item.StackSize = (uint16)atoi(row[ItemField::stacksize]); + item.NoTransfer = disableNoTransfer ? false : (atoi(row[ItemField::notransfer])==0) ? false : true; + item.Stackable = (atoi(row[ItemField::stackable])==0) ? false : true; + item.Click.Effect = (uint32)atoul(row[ItemField::clickeffect]); + item.Click.Type = (uint8)atoul(row[ItemField::clicktype]); + item.Click.Level = (uint8)atoul(row[ItemField::clicklevel]); + item.Click.Level2 = (uint8)atoul(row[ItemField::clicklevel2]); + strcpy(item.CharmFile,row[ItemField::charmfile]); + item.Proc.Effect = (uint16)atoul(row[ItemField::proceffect]); + item.Proc.Type = (uint8)atoul(row[ItemField::proctype]); + item.Proc.Level = (uint8)atoul(row[ItemField::proclevel]); + item.Proc.Level2 = (uint8)atoul(row[ItemField::proclevel2]); + item.Worn.Effect = (uint16)atoul(row[ItemField::worneffect]); + item.Worn.Type = (uint8)atoul(row[ItemField::worntype]); + item.Worn.Level = (uint8)atoul(row[ItemField::wornlevel]); + item.Worn.Level2 = (uint8)atoul(row[ItemField::wornlevel2]); + item.Focus.Effect = (uint16)atoul(row[ItemField::focuseffect]); + item.Focus.Type = (uint8)atoul(row[ItemField::focustype]); + item.Focus.Level = (uint8)atoul(row[ItemField::focuslevel]); + item.Focus.Level2 = (uint8)atoul(row[ItemField::focuslevel2]); + item.Scroll.Effect = (uint16)atoul(row[ItemField::scrolleffect]); + item.Scroll.Type = (uint8)atoul(row[ItemField::scrolltype]); + item.Scroll.Level = (uint8)atoul(row[ItemField::scrolllevel]); + item.Scroll.Level2 = (uint8)atoul(row[ItemField::scrolllevel2]); + item.Bard.Effect = (uint16)atoul(row[ItemField::bardeffect]); + item.Bard.Type = (uint8)atoul(row[ItemField::bardtype]); + item.Bard.Level = (uint8)atoul(row[ItemField::bardlevel]); + item.Bard.Level2 = (uint8)atoul(row[ItemField::bardlevel2]); + item.QuestItemFlag = (atoi(row[ItemField::questitemflag])==0) ? false : true; + item.SVCorruption = (int32)atoi(row[ItemField::svcorruption]); + item.Purity = (uint32)atoul(row[ItemField::purity]); + item.BackstabDmg = (uint32)atoul(row[ItemField::backstabdmg]); + item.DSMitigation = (uint32)atoul(row[ItemField::dsmitigation]); + item.HeroicStr = (int32)atoi(row[ItemField::heroic_str]); + item.HeroicInt = (int32)atoi(row[ItemField::heroic_int]); + item.HeroicWis = (int32)atoi(row[ItemField::heroic_wis]); + item.HeroicAgi = (int32)atoi(row[ItemField::heroic_agi]); + item.HeroicDex = (int32)atoi(row[ItemField::heroic_dex]); + item.HeroicSta = (int32)atoi(row[ItemField::heroic_sta]); + item.HeroicCha = (int32)atoi(row[ItemField::heroic_cha]); + item.HeroicMR = (int32)atoi(row[ItemField::heroic_mr]); + item.HeroicFR = (int32)atoi(row[ItemField::heroic_fr]); + item.HeroicCR = (int32)atoi(row[ItemField::heroic_cr]); + item.HeroicDR = (int32)atoi(row[ItemField::heroic_dr]); + item.HeroicPR = (int32)atoi(row[ItemField::heroic_pr]); + item.HeroicSVCorrup = (int32)atoi(row[ItemField::heroic_svcorrup]); + item.HealAmt = (int32)atoi(row[ItemField::healamt]); + item.SpellDmg = (int32)atoi(row[ItemField::spelldmg]); + item.LDoNSellBackRate = (uint32)atoul(row[ItemField::ldonsellbackrate]); + item.ScriptFileID = (uint32)atoul(row[ItemField::scriptfileid]); + item.ExpendableArrow = (uint16)atoul(row[ItemField::expendablearrow]); + item.Clairvoyance = (uint32)atoul(row[ItemField::clairvoyance]); + strcpy(item.ClickName,row[ItemField::clickname]); + strcpy(item.ProcName,row[ItemField::procname]); + strcpy(item.WornName,row[ItemField::wornname]); + strcpy(item.FocusName,row[ItemField::focusname]); + strcpy(item.ScrollName,row[ItemField::scrollname]); + + try { + hash.insert(item.ID, item); + } catch(std::exception &ex) { + LogFile->write(EQEMuLog::Error, "Database::LoadItems: %s", ex.what()); + break; + } + } + + mysql_free_result(result); + } + else { + LogFile->write(EQEMuLog::Error, "LoadItems '%s', %s", query, errbuf); + } +} + +const Item_Struct* SharedDatabase::GetItem(uint32 id) { + if(!items_hash || id > items_hash->max_key()) { + return nullptr; + } + + if(items_hash->exists(id)) { + return &(items_hash->at(id)); + } + + return nullptr; +} + +const Item_Struct* SharedDatabase::IterateItems(uint32* id) { + if(!items_hash || !id) { + return nullptr; + } + + for(;;) { + if(*id > items_hash->max_key()) { + break; + } + + if(items_hash->exists(*id)) { + return &(items_hash->at((*id)++)); + } else { + ++(*id); + } + } + + return nullptr; +} + +std::string SharedDatabase::GetBook(const char *txtfile) +{ + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + char txtfile2[20]; + std::string txtout; + strcpy(txtfile2,txtfile); + if (!RunQuery(query, MakeAnyLenString(&query, "SELECT txtfile FROM books where name='%s'", txtfile2), errbuf, &result)) { + std::cerr << "Error in GetBook query '" << query << "' " << errbuf << std::endl; + if (query != 0) + safe_delete_array(query); + txtout.assign(" ",1); + return txtout; + } + else { + safe_delete_array(query); + if (mysql_num_rows(result) == 0) { + mysql_free_result(result); + LogFile->write(EQEMuLog::Error, "No book to send, (%s)", txtfile); + txtout.assign(" ",1); + return txtout; + } + else { + row = mysql_fetch_row(result); + txtout.assign(row[0],strlen(row[0])); + mysql_free_result(result); + return txtout; + } + } +} + +void SharedDatabase::GetFactionListInfo(uint32 &list_count, uint32 &max_lists) { + list_count = 0; + max_lists = 0; + const char *query = "SELECT COUNT(*), MAX(id) FROM npc_faction"; + char errbuf[MYSQL_ERRMSG_SIZE]; + MYSQL_RES *result; + MYSQL_ROW row; + + if(RunQuery(query, strlen(query), errbuf, &result)) { + if(row = mysql_fetch_row(result)) { + list_count = static_cast(atoul(row[0])); + max_lists = static_cast(atoul(row[1])); + } + mysql_free_result(result); + } else { + LogFile->write(EQEMuLog::Error, "Error getting npc faction info from database: %s, %s", query, errbuf); + } +} + +const NPCFactionList* SharedDatabase::GetNPCFactionEntry(uint32 id) { + if(!faction_hash) { + return nullptr; + } + + if(faction_hash->exists(id)) { + return &(faction_hash->at(id)); + } + + return nullptr; +} + +void SharedDatabase::LoadNPCFactionLists(void *data, uint32 size, uint32 list_count, uint32 max_lists) { + EQEmu::FixedMemoryHashSet hash(reinterpret_cast(data), size, list_count, max_lists); + const char *query = "SELECT npc_faction.id, npc_faction.primaryfaction, npc_faction.ignore_primary_assist, " + "npc_faction_entries.faction_id, npc_faction_entries.value, npc_faction_entries.npc_value, npc_faction_entries.temp " + "FROM npc_faction LEFT JOIN npc_faction_entries ON npc_faction.id = npc_faction_entries.npc_faction_id ORDER BY " + "npc_faction.id;"; + + char errbuf[MYSQL_ERRMSG_SIZE]; + MYSQL_RES *result; + MYSQL_ROW row; + NPCFactionList faction; + + if(RunQuery(query, strlen(query), errbuf, &result)) { + uint32 current_id = 0; + uint32 current_entry = 0; + while(row = mysql_fetch_row(result)) { + uint32 id = static_cast(atoul(row[0])); + if(id != current_id) { + if(current_id != 0) { + hash.insert(current_id, faction); + } + + memset(&faction, 0, sizeof(faction)); + current_entry = 0; + current_id = id; + faction.id = id; + faction.primaryfaction = static_cast(atoul(row[1])); + faction.assistprimaryfaction = (atoi(row[2]) == 0); + } + + if(!row[3]) { + continue; + } + + if(current_entry >= MAX_NPC_FACTIONS) { + continue; + } + + faction.factionid[current_entry] = static_cast(atoul(row[3])); + faction.factionvalue[current_entry] = static_cast(atoi(row[4])); + faction.factionnpcvalue[current_entry] = static_cast(atoi(row[5])); + faction.factiontemp[current_entry] = static_cast(atoi(row[6])); + ++current_entry; + } + + if(current_id != 0) { + hash.insert(current_id, faction); + } + + mysql_free_result(result); + } else { + LogFile->write(EQEMuLog::Error, "Error getting npc faction info from database: %s, %s", query, errbuf); +} +} + +bool SharedDatabase::LoadNPCFactionLists() { + if(faction_hash) { + return true; + } + + try { + EQEmu::IPCMutex mutex("faction"); + mutex.Lock(); + faction_mmf = new EQEmu::MemoryMappedFile("shared/faction"); + + uint32 list_count = 0; + uint32 max_lists = 0; + GetFactionListInfo(list_count, max_lists); + if(list_count == 0) { + EQ_EXCEPT("SharedDatabase", "Database returned no result"); + } + uint32 size = static_cast(EQEmu::FixedMemoryHashSet::estimated_size( + list_count, max_lists)); + + if(faction_mmf->Size() != size) { + EQ_EXCEPT("SharedDatabase", "Couldn't load npc factions because faction_mmf->Size() != size"); + } + + faction_hash = new EQEmu::FixedMemoryHashSet(reinterpret_cast(faction_mmf->Get()), size); + mutex.Unlock(); + } catch(std::exception& ex) { + LogFile->write(EQEMuLog::Error, "Error Loading npc factions: %s", ex.what()); + return false; + } + + return true; +} + +// Get the player profile and inventory for the given account "account_id" and +// character name "name". Return true if the character was found, otherwise false. +// False will also be returned if there is a database error. +bool SharedDatabase::GetPlayerProfile(uint32 account_id, char* name, PlayerProfile_Struct* pp, Inventory* inv, ExtendedProfile_Struct *ext, char* current_zone, uint32 *current_instance) { + char errbuf[MYSQL_ERRMSG_SIZE]; + char* query = 0; + MYSQL_RES* result; + MYSQL_ROW row; + bool ret = false; + + unsigned long* lengths; + + if (RunQuery(query, MakeAnyLenString(&query, "SELECT profile,zonename,x,y,z,extprofile,instanceid FROM character_ WHERE account_id=%i AND name='%s'", account_id, name), errbuf, &result)) { + if (mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + lengths = mysql_fetch_lengths(result); + if (lengths[0] == sizeof(PlayerProfile_Struct)) { + memcpy(pp, row[0], sizeof(PlayerProfile_Struct)); + + if (current_zone) + strcpy(current_zone, row[1]); + pp->zone_id = GetZoneID(row[1]); + pp->x = atof(row[2]); + pp->y = atof(row[3]); + pp->z = atof(row[4]); + pp->zoneInstance = atoi(row[6]); + if (pp->x == -1 && pp->y == -1 && pp->z == -1) + GetSafePoints(pp->zone_id, GetInstanceVersion(pp->zoneInstance), &pp->x, &pp->y, &pp->z); + + if(current_instance) + *current_instance = pp->zoneInstance; + + if(ext) { + //SetExtendedProfile handles any conversion + SetExtendedProfile(ext, row[5], lengths[5]); + } + + // Retrieve character inventory + ret = GetInventory(account_id, name, inv); + } + else { + LogFile->write(EQEMuLog::Error, "Player profile length mismatch in GetPlayerProfile. Found: %i, Expected: %i", + lengths[0], sizeof(PlayerProfile_Struct)); + } + } + + mysql_free_result(result); + } + else { + LogFile->write(EQEMuLog::Error, "GetPlayerProfile query '%s' %s", query, errbuf); + } + + safe_delete_array(query); + return ret; +} + +bool SharedDatabase::SetPlayerProfile(uint32 account_id, uint32 charid, PlayerProfile_Struct* pp, Inventory* inv, ExtendedProfile_Struct *ext, uint32 current_zone, uint32 current_instance, uint8 MaxXTargets) { + char errbuf[MYSQL_ERRMSG_SIZE]; + char* query = 0; + uint32 affected_rows = 0; + bool ret = false; + + if (RunQuery(query, SetPlayerProfile_MQ(&query, account_id, charid, pp, inv, ext, current_zone, current_instance, MaxXTargets), errbuf, 0, &affected_rows)) { + ret = (affected_rows != 0); + } + + if (!ret) { + LogFile->write(EQEMuLog::Error, "SetPlayerProfile query '%s' %s", query, errbuf); + } + + safe_delete_array(query); + return ret; +} + +// Generate SQL for updating player profile +uint32 SharedDatabase::SetPlayerProfile_MQ(char** query, uint32 account_id, uint32 charid, PlayerProfile_Struct* pp, Inventory* inv, ExtendedProfile_Struct *ext, uint32 current_zone, uint32 current_instance, uint8 MaxXTargets) { + *query = new char[396 + sizeof(PlayerProfile_Struct)*2 + sizeof(ExtendedProfile_Struct)*2 + 4]; + char* end = *query; + if (!current_zone) + current_zone = pp->zone_id; + + if (!current_instance) + current_instance = pp->zoneInstance; + + if(strlen(pp->name) == 0) // Sanity check in case pp never loaded + return false; + + end += sprintf(end, "UPDATE character_ SET timelaston=unix_timestamp(now()),name=\'%s\', zonename=\'%s\', zoneid=%u, instanceid=%u, x = %f, y = %f, z = %f, profile=\'", pp->name, GetZoneName(current_zone), current_zone, current_instance, pp->x, pp->y, pp->z); + end += DoEscapeString(end, (char*)pp, sizeof(PlayerProfile_Struct)); + end += sprintf(end,"\', extprofile=\'"); + end += DoEscapeString(end, (char*)ext, sizeof(ExtendedProfile_Struct)); + end += sprintf(end,"\',class=%d,level=%d,xtargets=%u WHERE id=%u", pp->class_, pp->level, MaxXTargets, charid); + + return (uint32) (end - (*query)); +} + + + +// Create appropriate ItemInst class +ItemInst* SharedDatabase::CreateItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, uint32 aug3, uint32 aug4, uint32 aug5) +{ + const Item_Struct* item = nullptr; + ItemInst* inst = nullptr; + item = GetItem(item_id); + if (item) { + inst = CreateBaseItem(item, charges); + inst->PutAugment(this, 0, aug1); + inst->PutAugment(this, 1, aug2); + inst->PutAugment(this, 2, aug3); + inst->PutAugment(this, 3, aug4); + inst->PutAugment(this, 4, aug5); + } + + return inst; +} + + +// Create appropriate ItemInst class +ItemInst* SharedDatabase::CreateItem(const Item_Struct* item, int16 charges, uint32 aug1, uint32 aug2, uint32 aug3, uint32 aug4, uint32 aug5) +{ + ItemInst* inst = nullptr; + if (item) { + inst = CreateBaseItem(item, charges); + inst->PutAugment(this, 0, aug1); + inst->PutAugment(this, 1, aug2); + inst->PutAugment(this, 2, aug3); + inst->PutAugment(this, 3, aug4); + inst->PutAugment(this, 4, aug5); + } + + return inst; +} + +ItemInst* SharedDatabase::CreateBaseItem(const Item_Struct* item, int16 charges) { + ItemInst* inst = nullptr; + if (item) { + // if maxcharges is -1 that means it is an unlimited use item. + // set it to 1 charge so that it is usable on creation + if (charges == 0 && item->MaxCharges == -1) + charges = 1; + + inst = new ItemInst(item, charges); + + if(item->CharmFileID != 0 || (item->LoreGroup >= 1000 && item->LoreGroup != -1)) { + inst->Initialize(this); + } + } + return inst; +} + +int32 SharedDatabase::DeleteStalePlayerCorpses() { + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + uint32 affected_rows = 0; + + if(RuleB(Zone, EnableShadowrest)) + { + if (!RunQuery(query, MakeAnyLenString(&query, "UPDATE player_corpses SET IsBurried = 1 WHERE IsBurried=0 and " + "(UNIX_TIMESTAMP() - UNIX_TIMESTAMP(timeofdeath)) > %d and not timeofdeath=0", + (RuleI(Character, CorpseDecayTimeMS) / 1000)), errbuf, 0, &affected_rows)) + { + safe_delete_array(query); + return -1; + } + } + else + { + if (!RunQuery(query, MakeAnyLenString(&query, "Delete from player_corpses where (UNIX_TIMESTAMP() - " + "UNIX_TIMESTAMP(timeofdeath)) > %d and not timeofdeath=0", (RuleI(Character, CorpseDecayTimeMS) / 1000)), + errbuf, 0, &affected_rows)) + { + safe_delete_array(query); + return -1; + } + } + + safe_delete_array(query); + return affected_rows; +} + +int32 SharedDatabase::DeleteStalePlayerBackups() { + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + uint32 affected_rows = 0; + + // 1209600 seconds = 2 weeks + if (!RunQuery(query, MakeAnyLenString(&query, "Delete from player_corpses_backup where (UNIX_TIMESTAMP() - UNIX_TIMESTAMP(timeofdeath)) > 1209600"), errbuf, 0, &affected_rows)) { + safe_delete_array(query); + return -1; + } + safe_delete_array(query); + + return affected_rows; +} + +bool SharedDatabase::GetCommandSettings(std::map &commands) { + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + query = new char[256]; + strcpy(query, "SELECT command,access from commands"); + commands.clear(); + if (RunQuery(query, strlen(query), errbuf, &result)) { + safe_delete_array(query); + while((row = mysql_fetch_row(result))) { + commands[row[0]]=atoi(row[1]); + } + mysql_free_result(result); + return true; + } else { + std::cerr << "Error in GetCommands query '" << query << "' " << errbuf << std::endl; + safe_delete_array(query); + return false; + } + + return false; +} + +bool SharedDatabase::LoadSkillCaps() { + if(skill_caps_mmf) + return true; + + uint32 class_count = PLAYER_CLASS_COUNT; + uint32 skill_count = HIGHEST_SKILL + 1; + uint32 level_count = HARD_LEVEL_CAP + 1; + uint32 size = (class_count * skill_count * level_count * sizeof(uint16)); + + try { + EQEmu::IPCMutex mutex("skill_caps"); + mutex.Lock(); + skill_caps_mmf = new EQEmu::MemoryMappedFile("shared/skill_caps"); + if(skill_caps_mmf->Size() != size) { + EQ_EXCEPT("SharedDatabase", "Unable to load skill caps: skill_caps_mmf->Size() != size"); + } + + mutex.Unlock(); + } catch(std::exception &ex) { + LogFile->write(EQEMuLog::Error, "Error loading skill caps: %s", ex.what()); + return false; + } + + return true; +} + +void SharedDatabase::LoadSkillCaps(void *data) { + uint32 class_count = PLAYER_CLASS_COUNT; + uint32 skill_count = HIGHEST_SKILL + 1; + uint32 level_count = HARD_LEVEL_CAP + 1; + uint16 *skill_caps_table = reinterpret_cast(data); + + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + if(RunQuery(query, MakeAnyLenString(&query, + "SELECT skillID, class, level, cap FROM skill_caps ORDER BY skillID, class, level"), + errbuf, &result)) { + safe_delete_array(query); + + while((row = mysql_fetch_row(result))) { + uint8 skillID = atoi(row[0]); + uint8 class_ = atoi(row[1]) - 1; + uint8 level = atoi(row[2]); + uint16 cap = atoi(row[3]); + if(skillID >= skill_count || class_ >= class_count || level >= level_count) + continue; + + uint32 index = (((class_ * skill_count) + skillID) * level_count) + level; + skill_caps_table[index] = cap; + } + mysql_free_result(result); + } else { + LogFile->write(EQEMuLog::Error, "Error loading skill caps from database: %s", errbuf); + safe_delete_array(query); + } +} + +uint16 SharedDatabase::GetSkillCap(uint8 Class_, SkillUseTypes Skill, uint8 Level) { + if(!skill_caps_mmf) { + return 0; + } + + if(Class_ == 0) + return 0; + + int SkillMaxLevel = RuleI(Character, SkillCapMaxLevel); + if(SkillMaxLevel < 1) { + SkillMaxLevel = RuleI(Character, MaxLevel); + } + + uint32 class_count = PLAYER_CLASS_COUNT; + uint32 skill_count = HIGHEST_SKILL + 1; + uint32 level_count = HARD_LEVEL_CAP + 1; + if(Class_ > class_count || static_cast(Skill) > skill_count || Level > level_count) { + return 0; + } + + if(Level > static_cast(SkillMaxLevel)){ + Level = static_cast(SkillMaxLevel); + } + + uint32 index = ((((Class_ - 1) * skill_count) + Skill) * level_count) + Level; + uint16 *skill_caps_table = reinterpret_cast(skill_caps_mmf->Get()); + return skill_caps_table[index]; +} + +uint8 SharedDatabase::GetTrainLevel(uint8 Class_, SkillUseTypes Skill, uint8 Level) { + if(!skill_caps_mmf) { + return 0; + } + + if(Class_ == 0) + return 0; + + int SkillMaxLevel = RuleI(Character, SkillCapMaxLevel); + if (SkillMaxLevel < 1) { + SkillMaxLevel = RuleI(Character, MaxLevel); + } + + uint32 class_count = PLAYER_CLASS_COUNT; + uint32 skill_count = HIGHEST_SKILL + 1; + uint32 level_count = HARD_LEVEL_CAP + 1; + if(Class_ > class_count || static_cast(Skill) > skill_count || Level > level_count) { + return 0; + } + + uint8 ret = 0; + if(Level > static_cast(SkillMaxLevel)) { + uint32 index = ((((Class_ - 1) * skill_count) + Skill) * level_count); + uint16 *skill_caps_table = reinterpret_cast(skill_caps_mmf->Get()); + for(uint8 x = 0; x < Level; x++){ + if(skill_caps_table[index + x]){ + ret = x; + break; + } + } + } + else + { + uint32 index = ((((Class_ - 1) * skill_count) + Skill) * level_count); + uint16 *skill_caps_table = reinterpret_cast(skill_caps_mmf->Get()); + for(int x = 0; x < SkillMaxLevel; x++){ + if(skill_caps_table[index + x]){ + ret = x; + break; + } + } + } + + if(ret > GetSkillCap(Class_, Skill, Level)) + ret = static_cast(GetSkillCap(Class_, Skill, Level)); + + return ret; +} + +void SharedDatabase::LoadDamageShieldTypes(SPDat_Spell_Struct* sp, int32 iMaxSpellID) { + + const char *DSQuery = "SELECT `spellid`, `type` from `damageshieldtypes` WHERE `spellid` > 0 " + "AND `spellid` <= %i"; + + const char *ERR_MYSQLERROR = "Error in LoadDamageShieldTypes: %s %s"; + + char errbuf[MYSQL_ERRMSG_SIZE]; + char* query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + + if(RunQuery(query,MakeAnyLenString(&query,DSQuery,iMaxSpellID),errbuf,&result)) { + + while((row = mysql_fetch_row(result))) { + + int SpellID = atoi(row[0]); + if((SpellID > 0) && (SpellID <= iMaxSpellID)) { + sp[SpellID].DamageShieldType = atoi(row[1]); + } + } + mysql_free_result(result); + safe_delete_array(query); + } + else { + LogFile->write(EQEMuLog::Error, ERR_MYSQLERROR, query, errbuf); + safe_delete_array(query); + } +} + +const EvolveInfo* SharedDatabase::GetEvolveInfo(uint32 loregroup) { + return nullptr; // nothing here for now... database and/or sharemem pulls later +} + +int SharedDatabase::GetMaxSpellID() { + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = nullptr; + MYSQL_RES *result; + MYSQL_ROW row; + int32 ret = 0; + if(RunQuery(query, MakeAnyLenString(&query, "SELECT MAX(id) FROM spells_new"), + errbuf, &result)) { + safe_delete_array(query); + row = mysql_fetch_row(result); + ret = atoi(row[0]); + mysql_free_result(result); + } else { + _log(SPELLS__LOAD_ERR, "Error in GetMaxSpellID query '%s' %s", query, errbuf); + safe_delete_array(query); + ret = -1; + } + return ret; +} + +void SharedDatabase::LoadSpells(void *data, int max_spells) { + SPDat_Spell_Struct *sp = reinterpret_cast(data); + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + + if(RunQuery(query, MakeAnyLenString(&query, + "SELECT * FROM spells_new ORDER BY id ASC"), + errbuf, &result)) { + safe_delete_array(query); + + int tempid = 0; + int counter = 0; + while (row = mysql_fetch_row(result)) { + tempid = atoi(row[0]); + if(tempid >= max_spells) { + _log(SPELLS__LOAD_ERR, "Non fatal error: spell.id >= max_spells, ignoring."); + continue; + } + + ++counter; + sp[tempid].id = tempid; + strn0cpy(sp[tempid].name, row[1], sizeof(sp[tempid].name)); + strn0cpy(sp[tempid].player_1, row[2], sizeof(sp[tempid].player_1)); + strn0cpy(sp[tempid].teleport_zone, row[3], sizeof(sp[tempid].teleport_zone)); + strn0cpy(sp[tempid].you_cast, row[4], sizeof(sp[tempid].you_cast)); + strn0cpy(sp[tempid].other_casts, row[5], sizeof(sp[tempid].other_casts)); + strn0cpy(sp[tempid].cast_on_you, row[6], sizeof(sp[tempid].cast_on_you)); + strn0cpy(sp[tempid].cast_on_other, row[7], sizeof(sp[tempid].cast_on_other)); + strn0cpy(sp[tempid].spell_fades, row[8], sizeof(sp[tempid].spell_fades)); + + sp[tempid].range=static_cast(atof(row[9])); + sp[tempid].aoerange=static_cast(atof(row[10])); + sp[tempid].pushback=static_cast(atof(row[11])); + sp[tempid].pushup=static_cast(atof(row[12])); + sp[tempid].cast_time=atoi(row[13]); + sp[tempid].recovery_time=atoi(row[14]); + sp[tempid].recast_time=atoi(row[15]); + sp[tempid].buffdurationformula=atoi(row[16]); + sp[tempid].buffduration=atoi(row[17]); + sp[tempid].AEDuration=atoi(row[18]); + sp[tempid].mana=atoi(row[19]); + + int y=0; + for(y=0; y< EFFECT_COUNT;y++) + sp[tempid].base[y]=atoi(row[20+y]); // effect_base_value + for(y=0; y < EFFECT_COUNT; y++) + sp[tempid].base2[y]=atoi(row[32+y]); // effect_limit_value + for(y=0; y< EFFECT_COUNT;y++) + sp[tempid].max[y]=atoi(row[44+y]); + + for(y=0; y< 4;y++) + sp[tempid].components[y]=atoi(row[58+y]); + + for(y=0; y< 4;y++) + sp[tempid].component_counts[y]=atoi(row[62+y]); + + for(y=0; y< 4;y++) + sp[tempid].NoexpendReagent[y]=atoi(row[66+y]); + + for(y=0; y< EFFECT_COUNT;y++) + sp[tempid].formula[y]=atoi(row[70+y]); + + sp[tempid].goodEffect=atoi(row[83]); + sp[tempid].Activated=atoi(row[84]); + sp[tempid].resisttype=atoi(row[85]); + + for(y=0; y< EFFECT_COUNT;y++) + sp[tempid].effectid[y]=atoi(row[86+y]); + + sp[tempid].targettype = (SpellTargetType) atoi(row[98]); + sp[tempid].basediff=atoi(row[99]); + int tmp_skill = atoi(row[100]);; + if(tmp_skill < 0 || tmp_skill > HIGHEST_SKILL) + sp[tempid].skill = SkillBegging; /* not much better we can do. */ // can probably be changed to client-based 'SkillNone' once activated + else + sp[tempid].skill = (SkillUseTypes) tmp_skill; + sp[tempid].zonetype=atoi(row[101]); + sp[tempid].EnvironmentType=atoi(row[102]); + sp[tempid].TimeOfDay=atoi(row[103]); + + for(y=0; y < PLAYER_CLASS_COUNT;y++) + sp[tempid].classes[y]=atoi(row[104+y]); + + sp[tempid].CastingAnim=atoi(row[120]); + sp[tempid].SpellAffectIndex=atoi(row[123]); + sp[tempid].disallow_sit=atoi(row[124]); + + for (y = 0; y < 16; y++) + sp[tempid].deities[y]=atoi(row[126+y]); + + sp[tempid].uninterruptable=atoi(row[146]); + sp[tempid].ResistDiff=atoi(row[147]); + sp[tempid].dot_stacking_exempt=atoi(row[148]); + sp[tempid].RecourseLink = atoi(row[150]); + sp[tempid].no_partial_resist = atoi(row[151]) != 0; + + sp[tempid].short_buff_box = atoi(row[154]); + sp[tempid].descnum = atoi(row[155]); + sp[tempid].effectdescnum = atoi(row[157]); + + sp[tempid].reflectable = atoi(row[161]) != 0; + sp[tempid].bonushate=atoi(row[162]); + + sp[tempid].EndurCost=atoi(row[166]); + sp[tempid].EndurTimerIndex=atoi(row[167]); + sp[tempid].HateAdded=atoi(row[173]); + sp[tempid].EndurUpkeep=atoi(row[174]); + sp[tempid].numhitstype = atoi(row[175]); + sp[tempid].numhits = atoi(row[176]); + sp[tempid].pvpresistbase=atoi(row[177]); + sp[tempid].pvpresistcalc=atoi(row[178]); + sp[tempid].pvpresistcap=atoi(row[179]); + sp[tempid].spell_category=atoi(row[180]); + sp[tempid].can_mgb=atoi(row[185]); + sp[tempid].dispel_flag = atoi(row[186]); + sp[tempid].MinResist = atoi(row[189]); + sp[tempid].MaxResist = atoi(row[190]); + sp[tempid].viral_targets = atoi(row[191]); + sp[tempid].viral_timer = atoi(row[192]); + sp[tempid].NimbusEffect = atoi(row[193]); + sp[tempid].directional_start = (float)atoi(row[194]); + sp[tempid].directional_end = (float)atoi(row[195]); + sp[tempid].not_extendable = atoi(row[197]) != 0; + sp[tempid].suspendable = atoi(row[200]) != 0; + sp[tempid].spellgroup=atoi(row[207]); + sp[tempid].powerful_flag=atoi(row[209]); + sp[tempid].CastRestriction = atoi(row[211]); + sp[tempid].AllowRest = atoi(row[212]) != 0; + sp[tempid].NotOutofCombat = atoi(row[213]) != 0; + sp[tempid].NotInCombat = atoi(row[214]) != 0; + sp[tempid].aemaxtargets = atoi(row[218]); + sp[tempid].maxtargets = atoi(row[219]); + sp[tempid].persistdeath = atoi(row[224]) != 0; + sp[tempid].DamageShieldType = 0; + } + mysql_free_result(result); + + LoadDamageShieldTypes(sp, max_spells); + } else { + _log(SPELLS__LOAD_ERR, "Error in LoadSpells query '%s' %s", query, errbuf); + safe_delete_array(query); + } +} + +int SharedDatabase::GetMaxBaseDataLevel() { + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = "SELECT MAX(level) FROM base_data"; + MYSQL_RES *result; + MYSQL_ROW row; + int32 ret = 0; + if(RunQuery(query, strlen(query), errbuf, &result)) { + row = mysql_fetch_row(result); + if(row) { + ret = atoi(row[0]); + mysql_free_result(result); + } else { + ret = -1; + mysql_free_result(result); + } + } else { + LogFile->write(EQEMuLog::Error, "Error in GetMaxBaseDataLevel query '%s' %s", query, errbuf); + ret = -1; + } + return ret; +} + +bool SharedDatabase::LoadBaseData() { + if(base_data_mmf) { + return true; + } + + try { + EQEmu::IPCMutex mutex("base_data"); + mutex.Lock(); + base_data_mmf = new EQEmu::MemoryMappedFile("shared/base_data"); + + int size = 16 * (GetMaxBaseDataLevel() + 1) * sizeof(BaseDataStruct); + if(size == 0) { + EQ_EXCEPT("SharedDatabase", "Base Data size is zero"); + } + + if(base_data_mmf->Size() != size) { + EQ_EXCEPT("SharedDatabase", "Couldn't load base data because base_data_mmf->Size() != size"); + } + + mutex.Unlock(); + } catch(std::exception& ex) { + LogFile->write(EQEMuLog::Error, "Error Loading Base Data: %s", ex.what()); + return false; + } + + return true; +} + +void SharedDatabase::LoadBaseData(void *data, int max_level) { + char *base_ptr = reinterpret_cast(data); + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = "SELECT * FROM base_data ORDER BY level, class ASC"; + MYSQL_RES *result; + MYSQL_ROW row; + + if(RunQuery(query, strlen(query), errbuf, &result)) { + + int lvl = 0; + int cl = 0; + while (row = mysql_fetch_row(result)) { + lvl = atoi(row[0]); + cl = atoi(row[1]); + if(lvl <= 0) { + LogFile->write(EQEMuLog::Error, "Non fatal error: base_data.level <= 0, ignoring."); + continue; + } + + if(lvl >= max_level) { + LogFile->write(EQEMuLog::Error, "Non fatal error: base_data.level >= max_level, ignoring."); + continue; + } + + if(cl <= 0) { + LogFile->write(EQEMuLog::Error, "Non fatal error: base_data.cl <= 0, ignoring."); + continue; + } + + if(cl > 16) { + LogFile->write(EQEMuLog::Error, "Non fatal error: base_data.class > 16, ignoring."); + continue; + } + + BaseDataStruct *bd = reinterpret_cast(base_ptr + (((16 * (lvl - 1)) + (cl - 1)) * sizeof(BaseDataStruct))); + bd->base_hp = atof(row[2]); + bd->base_mana = atof(row[3]); + bd->base_end = atof(row[4]); + bd->unk1 = atof(row[5]); + bd->unk2 = atof(row[6]); + bd->hp_factor = atof(row[7]); + bd->mana_factor = atof(row[8]); + bd->endurance_factor = atof(row[9]); + } + mysql_free_result(result); + } else { + LogFile->write(EQEMuLog::Error, "Error in LoadBaseData query '%s' %s", query, errbuf); + safe_delete_array(query); + } +} + +const BaseDataStruct* SharedDatabase::GetBaseData(int lvl, int cl) { + if(!base_data_mmf) { + return nullptr; + } + + if(lvl <= 0) { + return nullptr; + } + + if(cl <= 0) { + return nullptr; + } + + if(cl > 16) { + return nullptr; + } + + char *base_ptr = reinterpret_cast(base_data_mmf->Get()); + + uint32 offset = ((16 * (lvl - 1)) + (cl - 1)) * sizeof(BaseDataStruct); + + if(offset >= base_data_mmf->Size()) { + return nullptr; + } + + BaseDataStruct *bd = reinterpret_cast(base_ptr + offset); + return bd; +} + +void SharedDatabase::GetLootTableInfo(uint32 &loot_table_count, uint32 &max_loot_table, uint32 &loot_table_entries) { + loot_table_count = 0; + max_loot_table = 0; + loot_table_entries = 0; + const char *query = "SELECT COUNT(*), MAX(id), (SELECT COUNT(*) FROM loottable_entries) FROM loottable"; + char errbuf[MYSQL_ERRMSG_SIZE]; + MYSQL_RES *result; + MYSQL_ROW row; + + if(RunQuery(query, strlen(query), errbuf, &result)) { + if(row = mysql_fetch_row(result)) { + loot_table_count = static_cast(atoul(row[0])); + max_loot_table = static_cast(atoul(row[1])); + loot_table_entries = static_cast(atoul(row[2])); + } + mysql_free_result(result); + } else { + LogFile->write(EQEMuLog::Error, "Error getting loot table info from database: %s, %s", query, errbuf); + } +} + +void SharedDatabase::GetLootDropInfo(uint32 &loot_drop_count, uint32 &max_loot_drop, uint32 &loot_drop_entries) { + loot_drop_count = 0; + max_loot_drop = 0; + loot_drop_entries = 0; + const char *query = "SELECT COUNT(*), MAX(id), (SELECT COUNT(*) FROM lootdrop_entries) FROM lootdrop"; + char errbuf[MYSQL_ERRMSG_SIZE]; + MYSQL_RES *result; + MYSQL_ROW row; + + if(RunQuery(query, strlen(query), errbuf, &result)) { + if(row = mysql_fetch_row(result)) { + loot_drop_count = static_cast(atoul(row[0])); + max_loot_drop = static_cast(atoul(row[1])); + loot_drop_entries = static_cast(atoul(row[2])); + } + mysql_free_result(result); + } else { + LogFile->write(EQEMuLog::Error, "Error getting loot table info from database: %s, %s", query, errbuf); + } +} + +void SharedDatabase::LoadLootTables(void *data, uint32 size) { + EQEmu::FixedMemoryVariableHashSet hash(reinterpret_cast(data), size); + const char *query = "SELECT loottable.id, loottable.mincash, loottable.maxcash, loottable.avgcoin," + " loottable_entries.lootdrop_id, loottable_entries.multiplier, loottable_entries.droplimit, " + "loottable_entries.mindrop, loottable_entries.probability FROM loottable LEFT JOIN loottable_entries" + " ON loottable.id = loottable_entries.loottable_id ORDER BY id"; + char errbuf[MYSQL_ERRMSG_SIZE]; + MYSQL_RES *result; + MYSQL_ROW row; + uint8 loot_table[sizeof(LootTable_Struct) + (sizeof(LootTableEntries_Struct) * 128)]; + LootTable_Struct *lt = reinterpret_cast(loot_table); + + if(RunQuery(query, strlen(query), errbuf, &result)) { + uint32 current_id = 0; + uint32 current_entry = 0; + while(row = mysql_fetch_row(result)) { + uint32 id = static_cast(atoul(row[0])); + if(id != current_id) { + if(current_id != 0) { + hash.insert(current_id, loot_table, (sizeof(LootTable_Struct) + + (sizeof(LootTableEntries_Struct) * lt->NumEntries))); + } + + memset(loot_table, 0, sizeof(LootTable_Struct) + (sizeof(LootTableEntries_Struct) * 128)); + current_entry = 0; + current_id = id; + lt->mincash = static_cast(atoul(row[1])); + lt->maxcash = static_cast(atoul(row[2])); + lt->avgcoin = static_cast(atoul(row[3])); + } + + if(current_entry > 128) { + continue; + } + + if(!row[4]) { + continue; + } + + lt->Entries[current_entry].lootdrop_id = static_cast(atoul(row[4])); + lt->Entries[current_entry].multiplier = static_cast(atoi(row[5])); + lt->Entries[current_entry].droplimit = static_cast(atoi(row[6])); + lt->Entries[current_entry].mindrop = static_cast(atoi(row[7])); + lt->Entries[current_entry].probability = static_cast(atof(row[8])); + + ++(lt->NumEntries); + ++current_entry; + } + if(current_id != 0) { + hash.insert(current_id, loot_table, (sizeof(LootTable_Struct) + + (sizeof(LootTableEntries_Struct) * lt->NumEntries))); + } + + mysql_free_result(result); + } else { + LogFile->write(EQEMuLog::Error, "Error getting loot table info from database: %s, %s", query, errbuf); + } +} + +void SharedDatabase::LoadLootDrops(void *data, uint32 size) { + EQEmu::FixedMemoryVariableHashSet hash(reinterpret_cast(data), size); + const char *query = "SELECT lootdrop.id, lootdrop_entries.item_id, lootdrop_entries.item_charges, " + "lootdrop_entries.equip_item, lootdrop_entries.chance, lootdrop_entries.minlevel, " + "lootdrop_entries.maxlevel, lootdrop_entries.multiplier FROM lootdrop JOIN lootdrop_entries " + "ON lootdrop.id = lootdrop_entries.lootdrop_id ORDER BY lootdrop_id"; + char errbuf[MYSQL_ERRMSG_SIZE]; + MYSQL_RES *result; + MYSQL_ROW row; + + uint8 loot_drop[sizeof(LootDrop_Struct) + (sizeof(LootDropEntries_Struct) * 1260)]; + LootDrop_Struct *ld = reinterpret_cast(loot_drop); + if(RunQuery(query, strlen(query), errbuf, &result)) { + uint32 current_id = 0; + uint32 current_entry = 0; + while(row = mysql_fetch_row(result)) { + uint32 id = static_cast(atoul(row[0])); + if(id != current_id) { + if(current_id != 0) { + hash.insert(current_id, loot_drop, (sizeof(LootDrop_Struct) + + (sizeof(LootDropEntries_Struct) * ld->NumEntries))); + } + + memset(loot_drop, 0, sizeof(LootDrop_Struct) + (sizeof(LootDropEntries_Struct) * 1260)); + current_entry = 0; + current_id = id; + } + + if(current_entry >= 1260) { + continue; + } + + ld->Entries[current_entry].item_id = static_cast(atoul(row[1])); + ld->Entries[current_entry].item_charges = static_cast(atoi(row[2])); + ld->Entries[current_entry].equip_item = static_cast(atoi(row[3])); + ld->Entries[current_entry].chance = static_cast(atof(row[4])); + ld->Entries[current_entry].minlevel = static_cast(atoi(row[5])); + ld->Entries[current_entry].maxlevel = static_cast(atoi(row[6])); + ld->Entries[current_entry].multiplier = static_cast(atoi(row[7])); + + ++(ld->NumEntries); + ++current_entry; + } + if(current_id != 0) { + hash.insert(current_id, loot_drop, (sizeof(LootDrop_Struct) + + (sizeof(LootDropEntries_Struct) * ld->NumEntries))); + } + + mysql_free_result(result); + } else { + LogFile->write(EQEMuLog::Error, "Error getting loot drop info from database: %s, %s", query, errbuf); + } +} + +bool SharedDatabase::LoadLoot() { + if(loot_table_mmf || loot_drop_mmf) + return true; + + try { + EQEmu::IPCMutex mutex("loot"); + mutex.Lock(); + loot_table_mmf = new EQEmu::MemoryMappedFile("shared/loot_table"); + loot_table_hash = new EQEmu::FixedMemoryVariableHashSet( + reinterpret_cast(loot_table_mmf->Get()), + loot_table_mmf->Size()); + loot_drop_mmf = new EQEmu::MemoryMappedFile("shared/loot_drop"); + loot_drop_hash = new EQEmu::FixedMemoryVariableHashSet( + reinterpret_cast(loot_drop_mmf->Get()), + loot_drop_mmf->Size()); + mutex.Unlock(); + } catch(std::exception &ex) { + LogFile->write(EQEMuLog::Error, "Error loading loot: %s", ex.what()); + return false; + } + + return true; +} + +const LootTable_Struct* SharedDatabase::GetLootTable(uint32 loottable_id) { + if(!loot_table_hash) + return nullptr; + + try { + if(loot_table_hash->exists(loottable_id)) { + return &loot_table_hash->at(loottable_id); + } + } catch(std::exception &ex) { + LogFile->write(EQEMuLog::Error, "Could not get loot table: %s", ex.what()); + } + return nullptr; +} + +const LootDrop_Struct* SharedDatabase::GetLootDrop(uint32 lootdrop_id) { + if(!loot_drop_hash) + return nullptr; + + try { + if(loot_drop_hash->exists(lootdrop_id)) { + return &loot_drop_hash->at(lootdrop_id); + } + } catch(std::exception &ex) { + LogFile->write(EQEMuLog::Error, "Could not get loot drop: %s", ex.what()); + } + return nullptr; +} + +void SharedDatabase::GetPlayerInspectMessage(char* playername, InspectMessage_Struct* message) { + + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + + if (RunQuery(query, MakeAnyLenString(&query, "SELECT inspectmessage FROM character_ WHERE name='%s'", playername), errbuf, &result)) { + safe_delete_array(query); + + if (mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + memcpy(message, row[0], sizeof(InspectMessage_Struct)); + } + + mysql_free_result(result); + } + else { + std::cerr << "Error in GetPlayerInspectMessage query '" << query << "' " << errbuf << std::endl; + safe_delete_array(query); + } +} + +void SharedDatabase::SetPlayerInspectMessage(char* playername, const InspectMessage_Struct* message) { + + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + + if (!RunQuery(query, MakeAnyLenString(&query, "UPDATE character_ SET inspectmessage='%s' WHERE name='%s'", message->text, playername), errbuf)) { + std::cerr << "Error in SetPlayerInspectMessage query '" << query << "' " << errbuf << std::endl; + } + + safe_delete_array(query); +} + +void SharedDatabase::GetBotInspectMessage(uint32 botid, InspectMessage_Struct* message) { + + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + + if (RunQuery(query, MakeAnyLenString(&query, "SELECT BotInspectMessage FROM bots WHERE BotID=%i", botid), errbuf, &result)) { + safe_delete_array(query); + + if (mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + memcpy(message, row[0], sizeof(InspectMessage_Struct)); + } + + mysql_free_result(result); + } + else { + std::cerr << "Error in GetBotInspectMessage query '" << query << "' " << errbuf << std::endl; + safe_delete_array(query); + } +} + +void SharedDatabase::SetBotInspectMessage(uint32 botid, const InspectMessage_Struct* message) { + + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + + if (!RunQuery(query, MakeAnyLenString(&query, "UPDATE bots SET BotInspectMessage='%s' WHERE BotID=%i", message->text, botid), errbuf)) { + std::cerr << "Error in SetBotInspectMessage query '" << query << "' " << errbuf << std::endl; + } + + safe_delete_array(query); +} diff --git a/common/spdat.h b/common/spdat.h index 0e9096f1a..5bb29b8c4 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -529,7 +529,7 @@ typedef enum { #define SE_CastOnFadeEffectAlways 373 // implemented - Triggers if fades after natural duration OR from rune/numhits fades. #define SE_ApplyEffect 374 // implemented #define SE_DotCritDmgIncrease 375 // implemented - Increase damage of DoT critical amount -//#define SE_Fling 376 // *not implemented - used in 2 test spells (12945 | Movement Test Spell 1) +#define SE_Fling 376 // *not implemented - used in 2 test spells (12945 | Movement Test Spell 1) #define SE_CastOnFadeEffectNPC 377 // implemented - Triggers only if fades after natural duration (On live these are usually players spells that effect an NPC). #define SE_SpellEffectResistChance 378 // implemented - Increase chance to resist specific spell effect (base1=value, base2=spell effect id) #define SE_ShadowStepDirectional 379 // implemented - handled by client diff --git a/utils/sql/git/required/2014_02_12_spells_new_update.sql b/utils/sql/git/required/2014_02_12_spells_new_update.sql new file mode 100644 index 000000000..ba552e1ed --- /dev/null +++ b/utils/sql/git/required/2014_02_12_spells_new_update.sql @@ -0,0 +1,13 @@ +ALTER TABLE `spells_new` CHANGE `field161` `not_reflectable` INT(11) NOT NULL DEFAULT '0'; +ALTER TABLE `spells_new` CHANGE `field151` `no_partial_resist` INT(11) NOT NULL DEFAULT '0'; +ALTER TABLE `spells_new` CHANGE `field189` `MinResist` INT(11) NOT NULL DEFAULT '0'; +ALTER TABLE `spells_new` CHANGE `field190` `MaxResist` INT(11) NOT NULL DEFAULT '0'; +ALTER TABLE `spells_new` CHANGE `field194` `ConeStartAngle` INT(11) NOT NULL DEFAULT '0'; +ALTER TABLE `spells_new` CHANGE `field195` `ConeStopAngle` INT(11) NOT NULL DEFAULT '0'; +ALTER TABLE `spells_new` CHANGE `field208` `rank` INT(11) NOT NULL DEFAULT '0'; +ALTER TABLE `spells_new` CHANGE `field159` `npc_no_los` INT(11) NOT NULL DEFAULT '0'; +ALTER TABLE `spells_new` CHANGE `field213` `NotOutofCombat` INT(11) NOT NULL DEFAULT '0'; +ALTER TABLE `spells_new` CHANGE `field214` `NotInCombat` INT(11) NOT NULL DEFAULT '0'; +ALTER TABLE `spells_new` CHANGE `field168` `IsDiscipline` INT(11) NOT NULL DEFAULT '0'; +ALTER TABLE `spells_new` CHANGE `field211` `CastRestrict` INT(11) NOT NULL DEFAULT '0'; + diff --git a/utils/sql/git/required/2014_04_10_No_Target_With_Hotkey - Copy.sql b/utils/sql/git/required/2014_04_10_No_Target_With_Hotkey - Copy.sql new file mode 100644 index 000000000..12e9963a5 --- /dev/null +++ b/utils/sql/git/required/2014_04_10_No_Target_With_Hotkey - Copy.sql @@ -0,0 +1,3 @@ +ALTER TABLE `npc_types` ADD `no_target_hotkey` tinyint( 1 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `healscale`; + + diff --git a/utils/sql/git/required/2014_06_22_MetabolismAAs.sql b/utils/sql/git/required/2014_06_22_MetabolismAAs.sql new file mode 100644 index 000000000..3a957edb2 --- /dev/null +++ b/utils/sql/git/required/2014_06_22_MetabolismAAs.sql @@ -0,0 +1,6 @@ +-- Innate Metabolism +INSERT INTO `aa_effects` (`aaid`, `slot`, `effectid`, `base1`, `base2`) VALUES ('68', '1', '233', '110', '0'); +INSERT INTO `aa_effects` (`aaid`, `slot`, `effectid`, `base1`, `base2`) VALUES ('69', '1', '233', '125', '0'); +INSERT INTO `aa_effects` (`aaid`, `slot`, `effectid`, `base1`, `base2`) VALUES ('70', '1', '233', '150', '0'); + + diff --git a/zone/AA_base.cpp b/zone/AA_base.cpp new file mode 100644 index 000000000..8566c09b5 --- /dev/null +++ b/zone/AA_base.cpp @@ -0,0 +1,1990 @@ +/* EQEMu: Everquest Server Emulator +Copyright (C) 2001-2004 EQEMu Development Team (http://eqemulator.net) + + 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 +*/ + +// Test 1 + +#include "../common/debug.h" +#include "AA.h" +#include "mob.h" +#include "client.h" +#include "groups.h" +#include "raids.h" +#include "../common/spdat.h" +#include "object.h" +#include "doors.h" +#include "beacon.h" +#include "corpse.h" +#include "titles.h" +#include "../common/races.h" +#include "../common/classes.h" +#include "../common/eq_packet_structs.h" +#include "../common/packet_dump.h" +#include "../common/StringUtil.h" +#include "../common/logsys.h" +#include "zonedb.h" +#include "StringIDs.h" + +//static data arrays, really not big enough to warrant shared mem. +AA_DBAction AA_Actions[aaHighestID][MAX_AA_ACTION_RANKS]; //[aaid][rank] +std::mapaas_send; +std::map > aa_effects; //stores the effects from the aa_effects table in memory +std::map AARequiredLevelAndCost; + +/* + + +Schema: + +spell_id is spell to cast, SPELL_UNKNOWN == no spell +nonspell_action is action to preform on activation which is not a spell, 0=none +nonspell_mana is mana that the nonspell action consumes +nonspell_duration is a duration which may be used by the nonspell action +redux_aa is the aa which reduces the reuse timer of the skill +redux_rate is the multiplier of redux_aa, as a percentage of total rate (10 == 10% faster) + +CREATE TABLE aa_actions ( + aaid mediumint unsigned not null, + rank tinyint unsigned not null, + reuse_time mediumint unsigned not null, + spell_id mediumint unsigned not null, + target tinyint unsigned not null, + nonspell_action tinyint unsigned not null, + nonspell_mana mediumint unsigned not null, + nonspell_duration mediumint unsigned not null, + redux_aa mediumint unsigned not null, + redux_rate tinyint not null, + + PRIMARY KEY(aaid, rank) +); + +CREATE TABLE aa_swarmpets ( + spell_id mediumint unsigned not null, + count tinyint unsigned not null, + npc_id int not null, + duration mediumint unsigned not null, + PRIMARY KEY(spell_id) +); +*/ + +/* + +Credits for this function: + -FatherNitwit: Structure and mechanism + -Wiz: Initial set of AAs, original function contents + -Branks: Much updated info and a bunch of higher-numbered AAs + +*/ +int Client::GetAATimerID(aaID activate) +{ + SendAA_Struct* aa2 = zone->FindAA(activate); + + if(!aa2) + { + for(int i = 1;i < MAX_AA_ACTION_RANKS; ++i) + { + int a = activate - i; + + if(a <= 0) + break; + + aa2 = zone->FindAA(a); + + if(aa2 != nullptr) + break; + } + } + + if(aa2) + return aa2->spell_type; + + return 0; +} + +int Client::CalcAAReuseTimer(const AA_DBAction *caa) { + + if(!caa) + return 0; + + int ReuseTime = caa->reuse_time; + + if(ReuseTime > 0) + { + int ReductionPercentage; + + if(caa->redux_aa > 0 && caa->redux_aa < aaHighestID) + { + ReductionPercentage = GetAA(caa->redux_aa) * caa->redux_rate; + + if(caa->redux_aa2 > 0 && caa->redux_aa2 < aaHighestID) + ReductionPercentage += (GetAA(caa->redux_aa2) * caa->redux_rate2); + + ReuseTime = caa->reuse_time * (100 - ReductionPercentage) / 100; + } + + } + return ReuseTime; +} + +void Client::ActivateAA(aaID activate){ + if(activate < 0 || activate >= aaHighestID) + return; + if(IsStunned() || IsFeared() || IsMezzed() || IsSilenced() || IsPet() || IsSitting() || GetFeigned()) + return; + + int AATimerID = GetAATimerID(activate); + + SendAA_Struct* aa2 = nullptr; + aaID aaid = activate; + uint8 activate_val = GetAA(activate); + //this wasn't taking into acct multi tiered act talents before... + if(activate_val == 0){ + aa2 = zone->FindAA(activate); + if(!aa2){ + int i; + int a; + for(i=1;iFindAA(a); + if(aa2 != nullptr) + break; + } + } + if(aa2){ + aaid = (aaID) aa2->id; + activate_val = GetAA(aa2->id); + } + } + + if (activate_val == 0){ + return; + } + + if(aa2) + { + if(aa2->account_time_required) + { + if((Timer::GetTimeSeconds() + account_creation) < aa2->account_time_required) + { + return; + } + } + } + + if(!p_timers.Expired(&database, AATimerID + pTimerAAStart)) + { + uint32 aaremain = p_timers.GetRemainingTime(AATimerID + pTimerAAStart); + uint32 aaremain_hr = aaremain / (60 * 60); + uint32 aaremain_min = (aaremain / 60) % 60; + uint32 aaremain_sec = aaremain % 60; + + if(aa2) { + if (aaremain_hr >= 1) //1 hour or more + Message(13, "You can use the ability %s again in %u hour(s) %u minute(s) %u seconds", + aa2->name, aaremain_hr, aaremain_min, aaremain_sec); + else //less than an hour + Message(13, "You can use the ability %s again in %u minute(s) %u seconds", + aa2->name, aaremain_min, aaremain_sec); + } else { + if (aaremain_hr >= 1) //1 hour or more + Message(13, "You can use this ability again in %u hour(s) %u minute(s) %u seconds", + aaremain_hr, aaremain_min, aaremain_sec); + else //less than an hour + Message(13, "You can use this ability again in %u minute(s) %u seconds", + aaremain_min, aaremain_sec); + } + return; + } + + if(activate_val > MAX_AA_ACTION_RANKS) + activate_val = MAX_AA_ACTION_RANKS; + activate_val--; //to get array index. + + //get our current node, now that the indices are well bounded + const AA_DBAction *caa = &AA_Actions[aaid][activate_val]; + + if((aaid == aaImprovedHarmTouch || aaid == aaLeechTouch) && !p_timers.Expired(&database, pTimerHarmTouch)){ + Message(13,"Ability recovery time not yet met."); + return; + } + + //everything should be configured out now + + uint16 target_id = 0; + + //figure out our target + switch(caa->target) { + case aaTargetUser: + case aaTargetGroup: + target_id = GetID(); + break; + case aaTargetCurrent: + case aaTargetCurrentGroup: + if(GetTarget() == nullptr) { + Message_StringID(MT_DefaultText, AA_NO_TARGET); //You must first select a target for this ability! + p_timers.Clear(&database, AATimerID + pTimerAAStart); + return; + } + target_id = GetTarget()->GetID(); + break; + case aaTargetPet: + if(GetPet() == nullptr) { + Message(0, "A pet is required for this skill."); + return; + } + target_id = GetPetID(); + break; + } + + //handle non-spell action + if(caa->action != aaActionNone) { + if(caa->mana_cost > 0) { + if(GetMana() < caa->mana_cost) { + Message_StringID(13, INSUFFICIENT_MANA); + return; + } + SetMana(GetMana() - caa->mana_cost); + } + if(caa->reuse_time > 0) + { + uint32 timer_base = CalcAAReuseTimer(caa); + if(activate == aaImprovedHarmTouch || activate == aaLeechTouch) + { + p_timers.Start(pTimerHarmTouch, HarmTouchReuseTime); + } + p_timers.Start(AATimerID + pTimerAAStart, timer_base); + SendAATimer(AATimerID, 0, 0); + } + HandleAAAction(aaid); + } + + //cast the spell, if we have one + if(caa->spell_id > 0 && caa->spell_id < SPDAT_RECORDS) { + + if(caa->reuse_time > 0) + { + uint32 timer_base = CalcAAReuseTimer(caa); + SendAATimer(AATimerID, 0, 0); + p_timers.Start(AATimerID + pTimerAAStart, timer_base); + if(activate == aaImprovedHarmTouch || activate == aaLeechTouch) + { + p_timers.Start(pTimerHarmTouch, HarmTouchReuseTime); + } + // Bards can cast instant cast AAs while they are casting another song + if (spells[caa->spell_id].cast_time == 0 && GetClass() == BARD && IsBardSong(casting_spell_id)) { + if(!SpellFinished(caa->spell_id, entity_list.GetMob(target_id), 10, -1, -1, spells[caa->spell_id].ResistDiff, false)) { + //Reset on failed cast + SendAATimer(AATimerID, 0, 0xFFFFFF); + Message_StringID(15,ABILITY_FAILED); + p_timers.Clear(&database, AATimerID + pTimerAAStart); + return; + } + } else { + if(!CastSpell(caa->spell_id, target_id, 10, -1, -1, 0, -1, AATimerID + pTimerAAStart, timer_base, 1)) { + //Reset on failed cast + SendAATimer(AATimerID, 0, 0xFFFFFF); + Message_StringID(15,ABILITY_FAILED); + p_timers.Clear(&database, AATimerID + pTimerAAStart); + return; + } + } + } + else + { + if(!CastSpell(caa->spell_id, target_id)) + return; + } + } + // Check if AA is expendable + if (aas_send[activate - activate_val]->special_category == 7) + { + // Add the AA cost to the extended profile to track overall total + m_epp.expended_aa += aas_send[activate]->cost; + SetAA(activate, 0); + + Save(); + SendAA(activate); + SendAATable(); + } +} + +void Client::HandleAAAction(aaID activate) { + if(activate < 0 || activate >= aaHighestID) + return; + + uint8 activate_val = GetAA(activate); + + if (activate_val == 0) + return; + + if(activate_val > MAX_AA_ACTION_RANKS) + activate_val = MAX_AA_ACTION_RANKS; + activate_val--; //to get array index. + + //get our current node, now that the indices are well bounded + const AA_DBAction *caa = &AA_Actions[activate][activate_val]; + + uint16 timer_id = 0; + uint16 timer_duration = caa->duration; + aaTargetType target = aaTargetUser; + + uint16 spell_id = SPELL_UNKNOWN; //gets cast at the end if not still unknown + + switch(caa->action) { + case aaActionAETaunt: + entity_list.AETaunt(this); + break; + + case aaActionMassBuff: + EnableAAEffect(aaEffectMassGroupBuff, 3600); + Message_StringID(MT_Disciplines, MGB_STRING); //The next group buff you cast will hit all targets in range. + break; + + case aaActionFlamingArrows: + //toggle it + if(CheckAAEffect(aaEffectFlamingArrows)) + EnableAAEffect(aaEffectFlamingArrows); + else + DisableAAEffect(aaEffectFlamingArrows); + break; + + case aaActionFrostArrows: + if(CheckAAEffect(aaEffectFrostArrows)) + EnableAAEffect(aaEffectFrostArrows); + else + DisableAAEffect(aaEffectFrostArrows); + break; + + case aaActionRampage: + EnableAAEffect(aaEffectRampage, 10); + break; + + case aaActionSharedHealth: + if(CheckAAEffect(aaEffectSharedHealth)) + EnableAAEffect(aaEffectSharedHealth); + else + DisableAAEffect(aaEffectSharedHealth); + break; + + case aaActionCelestialRegen: { + //special because spell_id depends on a different AA + switch (GetAA(aaCelestialRenewal)) { + case 1: + spell_id = 3250; + break; + case 2: + spell_id = 3251; + break; + default: + spell_id = 2740; + break; + } + target = aaTargetCurrent; + break; + } + + case aaActionDireCharm: { + //special because spell_id depends on class + switch (GetClass()) + { + case DRUID: + spell_id = 2760; //2644? + break; + case NECROMANCER: + spell_id = 2759; //2643? + break; + case ENCHANTER: + spell_id = 2761; //2642? + break; + } + target = aaTargetCurrent; + break; + } + + case aaActionImprovedFamiliar: { + //Spell IDs might be wrong... + if (GetAA(aaAllegiantFamiliar)) + spell_id = 3264; //1994? + else + spell_id = 2758; //2155? + break; + } + + case aaActionActOfValor: + if(GetTarget() != nullptr) { + int curhp = GetTarget()->GetHP(); + target = aaTargetCurrent; + GetTarget()->HealDamage(curhp, this); + Death(this, 0, SPELL_UNKNOWN, SkillHandtoHand); + } + break; + + case aaActionSuspendedMinion: + if (GetPet()) { + target = aaTargetPet; + switch (GetAA(aaSuspendedMinion)) { + case 1: + spell_id = 3248; + break; + case 2: + spell_id = 3249; + break; + } + //do we really need to cast a spell? + + Message(0,"You call your pet to your side."); + GetPet()->WipeHateList(); + GetPet()->GMMove(GetX(),GetY(),GetZ()); + if (activate_val > 1) + entity_list.ClearFeignAggro(GetPet()); + } else { + Message(0,"You have no pet to call."); + } + break; + + case aaActionProjectIllusion: + EnableAAEffect(aaEffectProjectIllusion, 3600); + Message(10, "The power of your next illusion spell will flow to your grouped target in your place."); + break; + + + case aaActionEscape: + Escape(); + break; + + // Don't think this code is used any longer for Bestial Alignment as the AA has a spell_id and no nonspell_action. + case aaActionBeastialAlignment: + switch(GetBaseRace()) { + case BARBARIAN: + spell_id = AA_Choose3(activate_val, 4521, 4522, 4523); + break; + case TROLL: + spell_id = AA_Choose3(activate_val, 4524, 4525, 4526); + break; + case OGRE: + spell_id = AA_Choose3(activate_val, 4527, 4527, 4529); + break; + case IKSAR: + spell_id = AA_Choose3(activate_val, 4530, 4531, 4532); + break; + case VAHSHIR: + spell_id = AA_Choose3(activate_val, 4533, 4534, 4535); + break; + } + + case aaActionLeechTouch: + target = aaTargetCurrent; + spell_id = SPELL_HARM_TOUCH2; + EnableAAEffect(aaEffectLeechTouch, 1000); + break; + + case aaActionFadingMemories: + // Do nothing since spell effect works correctly, but mana isn't used. + break; + + default: + LogFile->write(EQEMuLog::Error, "Unknown AA nonspell action type %d", caa->action); + return; + } + + + uint16 target_id = 0; + //figure out our target + switch(target) { + case aaTargetUser: + case aaTargetGroup: + target_id = GetID(); + break; + case aaTargetCurrent: + case aaTargetCurrentGroup: + if(GetTarget() == nullptr) { + Message_StringID(MT_DefaultText, AA_NO_TARGET); //You must first select a target for this ability! + p_timers.Clear(&database, timer_id + pTimerAAEffectStart); + return; + } + target_id = GetTarget()->GetID(); + break; + case aaTargetPet: + if(GetPet() == nullptr) { + Message(0, "A pet is required for this skill."); + return; + } + target_id = GetPetID(); + break; + } + + //cast the spell, if we have one + if(IsValidSpell(spell_id)) { + int aatid = GetAATimerID(activate); + if(!CastSpell(spell_id, target_id , 10, -1, -1, 0, -1, pTimerAAStart + aatid , CalcAAReuseTimer(caa), 1)) { + SendAATimer(aatid, 0, 0xFFFFFF); + Message_StringID(15,ABILITY_FAILED); + p_timers.Clear(&database, pTimerAAStart + aatid); + return; + } + } + + //handle the duration timer if we have one. + if(timer_id > 0 && timer_duration > 0) { + p_timers.Start(pTimerAAEffectStart + timer_id, timer_duration); + } +} + + +//Originally written by Branks +//functionality rewritten by Father Nitwit +void Mob::TemporaryPets(uint16 spell_id, Mob *targ, const char *name_override, uint32 duration_override) { + + //It might not be a bad idea to put these into the database, eventually.. + + //Dook- swarms and wards + + PetRecord record; + if(!database.GetPetEntry(spells[spell_id].teleport_zone, &record)) + { + LogFile->write(EQEMuLog::Error, "Unknown swarm pet spell id: %d, check pets table", spell_id); + Message(13, "Unable to find data for pet %s", spells[spell_id].teleport_zone); + return; + } + + AA_SwarmPet pet; + pet.count = 1; + pet.duration = 1; + + for(int x = 0; x < 12; x++) + { + if(spells[spell_id].effectid[x] == SE_TemporaryPets) + { + pet.count = spells[spell_id].base[x]; + pet.duration = spells[spell_id].max[x]; + } + } + + if(IsClient()) + pet.duration += (CastToClient()->GetFocusEffect(focusSwarmPetDuration, spell_id) / 1000); + + pet.npc_id = record.npc_type; + + NPCType *made_npc = nullptr; + + const NPCType *npc_type = database.GetNPCType(pet.npc_id); + if(npc_type == nullptr) { + //log write + LogFile->write(EQEMuLog::Error, "Unknown npc type for swarm pet spell id: %d", spell_id); + Message(0,"Unable to find pet!"); + return; + } + + if(name_override != nullptr) { + //we have to make a custom NPC type for this name change + made_npc = new NPCType; + memcpy(made_npc, npc_type, sizeof(NPCType)); + strcpy(made_npc->name, name_override); + npc_type = made_npc; + } + + int summon_count = 0; + summon_count = pet.count; + + if(summon_count > MAX_SWARM_PETS) + summon_count = MAX_SWARM_PETS; + + static const float swarm_pet_x[MAX_SWARM_PETS] = { 5, -5, 5, -5, + 10, -10, 10, -10, + 8, -8, 8, -8 }; + static const float swarm_pet_y[MAX_SWARM_PETS] = { 5, 5, -5, -5, + 10, 10, -10, -10, + 8, 8, -8, -8 }; + TempPets(true); + + while(summon_count > 0) { + int pet_duration = pet.duration; + if(duration_override > 0) + pet_duration = duration_override; + + //this is a little messy, but the only way to do it right + //it would be possible to optimize out this copy for the last pet, but oh well + NPCType *npc_dup = nullptr; + if(made_npc != nullptr) { + npc_dup = new NPCType; + memcpy(npc_dup, made_npc, sizeof(NPCType)); + } + + NPC* npca = new NPC( + (npc_dup!=nullptr)?npc_dup:npc_type, //make sure we give the NPC the correct data pointer + 0, + GetX()+swarm_pet_x[summon_count], GetY()+swarm_pet_y[summon_count], + GetZ(), GetHeading(), FlyMode3); + + if((spell_id == 6882) || (spell_id == 6884)) + npca->SetFollowID(GetID()); + + if(!npca->GetSwarmInfo()){ + AA_SwarmPetInfo* nSI = new AA_SwarmPetInfo; + npca->SetSwarmInfo(nSI); + npca->GetSwarmInfo()->duration = new Timer(pet_duration*1000); + } + else{ + npca->GetSwarmInfo()->duration->Start(pet_duration*1000); + } + + //removing this prevents the pet from attacking + npca->GetSwarmInfo()->owner_id = GetID(); + + //give the pets somebody to "love" + if(targ != nullptr){ + npca->AddToHateList(targ, 1000, 1000); + npca->GetSwarmInfo()->target = targ->GetID(); + } + + //we allocated a new NPC type object, give the NPC ownership of that memory + if(npc_dup != nullptr) + npca->GiveNPCTypeData(npc_dup); + + entity_list.AddNPC(npca, true, true); + summon_count--; + } + + //the target of these swarm pets will take offense to being cast on... + if(targ != nullptr) + targ->AddToHateList(this, 1, 0); +} + +void Mob::TypesTemporaryPets(uint32 typesid, Mob *targ, const char *name_override, uint32 duration_override, bool followme) { + + AA_SwarmPet pet; + pet.count = 1; + pet.duration = 1; + + pet.npc_id = typesid; + + NPCType *made_npc = nullptr; + + const NPCType *npc_type = database.GetNPCType(typesid); + if(npc_type == nullptr) { + //log write + LogFile->write(EQEMuLog::Error, "Unknown npc type for swarm pet type id: %d", typesid); + Message(0,"Unable to find pet!"); + return; + } + + if(name_override != nullptr) { + //we have to make a custom NPC type for this name change + made_npc = new NPCType; + memcpy(made_npc, npc_type, sizeof(NPCType)); + strcpy(made_npc->name, name_override); + npc_type = made_npc; + } + + int summon_count = 0; + summon_count = pet.count; + + if(summon_count > MAX_SWARM_PETS) + summon_count = MAX_SWARM_PETS; + + static const float swarm_pet_x[MAX_SWARM_PETS] = { 5, -5, 5, -5, + 10, -10, 10, -10, + 8, -8, 8, -8 }; + static const float swarm_pet_y[MAX_SWARM_PETS] = { 5, 5, -5, -5, + 10, 10, -10, -10, + 8, 8, -8, -8 }; + TempPets(true); + + while(summon_count > 0) { + int pet_duration = pet.duration; + if(duration_override > 0) + pet_duration = duration_override; + + //this is a little messy, but the only way to do it right + //it would be possible to optimize out this copy for the last pet, but oh well + NPCType *npc_dup = nullptr; + if(made_npc != nullptr) { + npc_dup = new NPCType; + memcpy(npc_dup, made_npc, sizeof(NPCType)); + } + + NPC* npca = new NPC( + (npc_dup!=nullptr)?npc_dup:npc_type, //make sure we give the NPC the correct data pointer + 0, + GetX()+swarm_pet_x[summon_count], GetY()+swarm_pet_y[summon_count], + GetZ(), GetHeading(), FlyMode3); + + if(!npca->GetSwarmInfo()){ + AA_SwarmPetInfo* nSI = new AA_SwarmPetInfo; + npca->SetSwarmInfo(nSI); + npca->GetSwarmInfo()->duration = new Timer(pet_duration*1000); + } + else{ + npca->GetSwarmInfo()->duration->Start(pet_duration*1000); + } + + //removing this prevents the pet from attacking + npca->GetSwarmInfo()->owner_id = GetID(); + + //give the pets somebody to "love" + if(targ != nullptr){ + npca->AddToHateList(targ, 1000, 1000); + npca->GetSwarmInfo()->target = targ->GetID(); + } + + //we allocated a new NPC type object, give the NPC ownership of that memory + if(npc_dup != nullptr) + npca->GiveNPCTypeData(npc_dup); + + entity_list.AddNPC(npca, true, true); + summon_count--; + } +} + +void Mob::WakeTheDead(uint16 spell_id, Mob *target, uint32 duration) +{ + Corpse *CorpseToUse = nullptr; + CorpseToUse = entity_list.GetClosestCorpse(this, nullptr); + + if(!CorpseToUse) + return; + + //assuming we have pets in our table; we take the first pet as a base type. + const NPCType *base_type = database.GetNPCType(500); + NPCType *make_npc = new NPCType; + memcpy(make_npc, base_type, sizeof(NPCType)); + + //combat stats + make_npc->AC = ((GetLevel() * 7) + 550); + make_npc->ATK = GetLevel(); + make_npc->max_dmg = (GetLevel() * 4) + 2; + make_npc->min_dmg = 1; + + //base stats + make_npc->cur_hp = (GetLevel() * 55); + make_npc->max_hp = (GetLevel() * 55); + make_npc->STR = 85 + (GetLevel() * 3); + make_npc->STA = 85 + (GetLevel() * 3); + make_npc->DEX = 85 + (GetLevel() * 3); + make_npc->AGI = 85 + (GetLevel() * 3); + make_npc->INT = 85 + (GetLevel() * 3); + make_npc->WIS = 85 + (GetLevel() * 3); + make_npc->CHA = 85 + (GetLevel() * 3); + make_npc->MR = 25; + make_npc->FR = 25; + make_npc->CR = 25; + make_npc->DR = 25; + make_npc->PR = 25; + + //level class and gender + make_npc->level = GetLevel(); + make_npc->class_ = CorpseToUse->class_; + make_npc->race = CorpseToUse->race; + make_npc->gender = CorpseToUse->gender; + make_npc->loottable_id = 0; + //name + char NewName[64]; + sprintf(NewName, "%s`s Animated Corpse", GetCleanName()); + strcpy(make_npc->name, NewName); + + //appearance + make_npc->beard = CorpseToUse->beard; + make_npc->beardcolor = CorpseToUse->beardcolor; + make_npc->eyecolor1 = CorpseToUse->eyecolor1; + make_npc->eyecolor2 = CorpseToUse->eyecolor2; + make_npc->haircolor = CorpseToUse->haircolor; + make_npc->hairstyle = CorpseToUse->hairstyle; + make_npc->helmtexture = CorpseToUse->helmtexture; + make_npc->luclinface = CorpseToUse->luclinface; + make_npc->size = CorpseToUse->size; + make_npc->texture = CorpseToUse->texture; + + //cast stuff.. based off of PEQ's if you want to change + //it you'll have to mod this code, but most likely + //most people will be using PEQ style for the first + //part of their spell list; can't think of any smooth + //way to do this + //some basic combat mods here too since it's convienent + switch(CorpseToUse->class_) + { + case CLERIC: + make_npc->npc_spells_id = 1; + break; + case WIZARD: + make_npc->npc_spells_id = 2; + break; + case NECROMANCER: + make_npc->npc_spells_id = 3; + break; + case MAGICIAN: + make_npc->npc_spells_id = 4; + break; + case ENCHANTER: + make_npc->npc_spells_id = 5; + break; + case SHAMAN: + make_npc->npc_spells_id = 6; + break; + case DRUID: + make_npc->npc_spells_id = 7; + break; + case PALADIN: + //SPECATK_TRIPLE + strcpy(make_npc->special_abilities, "6,1"); + make_npc->cur_hp = make_npc->cur_hp * 150 / 100; + make_npc->max_hp = make_npc->max_hp * 150 / 100; + make_npc->npc_spells_id = 8; + break; + case SHADOWKNIGHT: + strcpy(make_npc->special_abilities, "6,1"); + make_npc->cur_hp = make_npc->cur_hp * 150 / 100; + make_npc->max_hp = make_npc->max_hp * 150 / 100; + make_npc->npc_spells_id = 9; + break; + case RANGER: + strcpy(make_npc->special_abilities, "7,1"); + make_npc->cur_hp = make_npc->cur_hp * 135 / 100; + make_npc->max_hp = make_npc->max_hp * 135 / 100; + make_npc->npc_spells_id = 10; + break; + case BARD: + strcpy(make_npc->special_abilities, "6,1"); + make_npc->cur_hp = make_npc->cur_hp * 110 / 100; + make_npc->max_hp = make_npc->max_hp * 110 / 100; + make_npc->npc_spells_id = 11; + break; + case BEASTLORD: + strcpy(make_npc->special_abilities, "7,1"); + make_npc->cur_hp = make_npc->cur_hp * 110 / 100; + make_npc->max_hp = make_npc->max_hp * 110 / 100; + make_npc->npc_spells_id = 12; + break; + case ROGUE: + strcpy(make_npc->special_abilities, "7,1"); + make_npc->max_dmg = make_npc->max_dmg * 150 /100; + make_npc->cur_hp = make_npc->cur_hp * 110 / 100; + make_npc->max_hp = make_npc->max_hp * 110 / 100; + break; + case MONK: + strcpy(make_npc->special_abilities, "7,1"); + make_npc->max_dmg = make_npc->max_dmg * 150 /100; + make_npc->cur_hp = make_npc->cur_hp * 135 / 100; + make_npc->max_hp = make_npc->max_hp * 135 / 100; + break; + case WARRIOR: + case BERSERKER: + strcpy(make_npc->special_abilities, "7,1"); + make_npc->max_dmg = make_npc->max_dmg * 150 /100; + make_npc->cur_hp = make_npc->cur_hp * 175 / 100; + make_npc->max_hp = make_npc->max_hp * 175 / 100; + break; + default: + make_npc->npc_spells_id = 0; + break; + } + + make_npc->loottable_id = 0; + make_npc->merchanttype = 0; + make_npc->d_meele_texture1 = 0; + make_npc->d_meele_texture2 = 0; + + TempPets(true); + + NPC* npca = new NPC(make_npc, 0, GetX(), GetY(), GetZ(), GetHeading(), FlyMode3); + + if(!npca->GetSwarmInfo()){ + AA_SwarmPetInfo* nSI = new AA_SwarmPetInfo; + npca->SetSwarmInfo(nSI); + npca->GetSwarmInfo()->duration = new Timer(duration*1000); + } + else{ + npca->GetSwarmInfo()->duration->Start(duration*1000); + } + + npca->GetSwarmInfo()->owner_id = GetID(); + + //give the pet somebody to "love" + if(target != nullptr){ + npca->AddToHateList(target, 100000); + npca->GetSwarmInfo()->target = target->GetID(); + } + + //gear stuff, need to make sure there's + //no situation where this stuff can be duped + for(int x = 0; x < 21; x++) + { + uint32 sitem = 0; + sitem = CorpseToUse->GetWornItem(x); + if(sitem){ + const Item_Struct * itm = database.GetItem(sitem); + npca->AddLootDrop(itm, &npca->itemlist, 1, 1, 127, true, true); + } + } + + //we allocated a new NPC type object, give the NPC ownership of that memory + if(make_npc != nullptr) + npca->GiveNPCTypeData(make_npc); + + entity_list.AddNPC(npca, true, true); + + //the target of these swarm pets will take offense to being cast on... + if(target != nullptr) + target->AddToHateList(this, 1, 0); +} + +//turn on an AA effect +//duration == 0 means no time limit, used for one-shot deals, etc.. +void Client::EnableAAEffect(aaEffectType type, uint32 duration) { + if(type > 32) + return; //for now, special logic needed. + m_epp.aa_effects |= 1 << (type-1); + + if(duration > 0) { + p_timers.Start(pTimerAAEffectStart + type, duration); + } else { + p_timers.Clear(&database, pTimerAAEffectStart + type); + } +} + +void Client::DisableAAEffect(aaEffectType type) { + if(type > 32) + return; //for now, special logic needed. + uint32 bit = 1 << (type-1); + if(m_epp.aa_effects & bit) { + m_epp.aa_effects ^= bit; + } + p_timers.Clear(&database, pTimerAAEffectStart + type); +} + +/* +By default an AA effect is a one shot deal, unless +a duration timer is set. +*/ +bool Client::CheckAAEffect(aaEffectType type) { + if(type > 32) + return(false); //for now, special logic needed. + if(m_epp.aa_effects & (1 << (type-1))) { //is effect enabled? + //has our timer expired? + if(p_timers.Expired(&database, pTimerAAEffectStart + type)) { + DisableAAEffect(type); + return(false); + } + return(true); + } + return(false); +} + +void Client::SendAAStats() { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AAExpUpdate, sizeof(AltAdvStats_Struct)); + AltAdvStats_Struct *aps = (AltAdvStats_Struct *)outapp->pBuffer; + aps->experience = m_pp.expAA; + aps->experience = (uint32)(((float)330.0f * (float)m_pp.expAA) / (float)max_AAXP); + aps->unspent = m_pp.aapoints; + aps->percentage = m_epp.perAA; + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::BuyAA(AA_Action* action) +{ + mlog(AA__MESSAGE, "Starting to buy AA %d", action->ability); + + //find the AA information from the database + SendAA_Struct* aa2 = zone->FindAA(action->ability); + if(!aa2) { + //hunt for a lower level... + int i; + int a; + for(i=1;iability - i; + if(a <= 0) + break; + mlog(AA__MESSAGE, "Could not find AA %d, trying potential parent %d", action->ability, a); + aa2 = zone->FindAA(a); + if(aa2 != nullptr) + break; + } + } + if(aa2 == nullptr) + return; //invalid ability... + + if(aa2->special_category == 1 || aa2->special_category == 2) + return; // Not purchasable progression style AAs + + if(aa2->special_category == 8 && aa2->cost == 0) + return; // Not purchasable racial AAs(set a cost to make them purchasable) + + uint32 cur_level = GetAA(aa2->id); + if((aa2->id + cur_level) != action->ability) { //got invalid AA + mlog(AA__ERROR, "Unable to find or match AA %d (found %d + lvl %d)", action->ability, aa2->id, cur_level); + return; + } + + if(aa2->account_time_required) + { + if((Timer::GetTimeSeconds() - account_creation) < aa2->account_time_required) + { + return; + } + } + + uint32 real_cost; + std::map::iterator RequiredLevel = AARequiredLevelAndCost.find(action->ability); + + if(RequiredLevel != AARequiredLevelAndCost.end()) + { + real_cost = RequiredLevel->second.Cost; + } + else + real_cost = aa2->cost + (aa2->cost_inc * cur_level); + + if(m_pp.aapoints >= real_cost && cur_level < aa2->max_level) { + SetAA(aa2->id, cur_level+1); + + mlog(AA__MESSAGE, "Set AA %d to level %d", aa2->id, cur_level+1); + + m_pp.aapoints -= real_cost; + + Save(); + if ((RuleB(AA, Stacking) && (GetClientVersionBit() >= 4) && (aa2->hotkey_sid == 4294967295u)) + && ((aa2->max_level == (cur_level+1)) && aa2->sof_next_id)){ + SendAA(aa2->id); + SendAA(aa2->sof_next_id); + } + else + SendAA(aa2->id); + + SendAATable(); + + //we are building these messages ourself instead of using the stringID to work around patch discrepencies + //these are AA_GAIN_ABILITY (410) & AA_IMPROVE (411), respectively, in both Titanium & SoF. not sure about 6.2 + if(cur_level<1) + Message(15,"You have gained the ability \"%s\" at a cost of %d ability %s.", aa2->name, real_cost, (real_cost>1)?"points":"point"); + else + Message(15,"You have improved %s %d at a cost of %d ability %s.", aa2->name, cur_level+1, real_cost, (real_cost>1)?"points":"point"); + + + SendAAStats(); + + CalcBonuses(); + if(title_manager.IsNewAATitleAvailable(m_pp.aapoints_spent, GetBaseClass())) + NotifyNewTitlesAvailable(); + } +} + +void Client::SendAATimer(uint32 ability, uint32 begin, uint32 end) { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AAAction,sizeof(UseAA_Struct)); + UseAA_Struct* uaaout = (UseAA_Struct*)outapp->pBuffer; + uaaout->ability = ability; + uaaout->begin = begin; + uaaout->end = end; + QueuePacket(outapp); + safe_delete(outapp); +} + +//sends all AA timers. +void Client::SendAATimers() { + //we dont use SendAATimer because theres no reason to allocate the EQApplicationPacket every time + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AAAction,sizeof(UseAA_Struct)); + UseAA_Struct* uaaout = (UseAA_Struct*)outapp->pBuffer; + + PTimerList::iterator c,e; + c = p_timers.begin(); + e = p_timers.end(); + for(; c != e; ++c) { + PersistentTimer *cur = c->second; + if(cur->GetType() < pTimerAAStart || cur->GetType() > pTimerAAEnd) + continue; //not an AA timer + //send timer + uaaout->begin = cur->GetStartTime(); + uaaout->end = static_cast(time(nullptr)); + uaaout->ability = cur->GetType() - pTimerAAStart; // uuaaout->ability is really a shared timer number + QueuePacket(outapp); + } + + safe_delete(outapp); +} + +void Client::SendAATable() { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_RespondAA, sizeof(AATable_Struct)); + + AATable_Struct* aa2 = (AATable_Struct *)outapp->pBuffer; + aa2->aa_spent = GetAAPointsSpent(); + + uint32 i; + for(i=0;i < MAX_PP_AA_ARRAY;i++){ + aa2->aa_list[i].aa_skill = aa[i]->AA; + aa2->aa_list[i].aa_value = aa[i]->value; + aa2->aa_list[i].unknown08 = 0; + } + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::SendPreviousAA(uint32 id, int seq){ + uint32 value=0; + SendAA_Struct* saa2 = nullptr; + if(id==0) + saa2 = zone->GetAABySequence(seq); + else + saa2 = zone->FindAA(id); + if(!saa2) + return; + int size=sizeof(SendAA_Struct)+sizeof(AA_Ability)*saa2->total_abilities; + uchar* buffer = new uchar[size]; + SendAA_Struct* saa=(SendAA_Struct*)buffer; + value = GetAA(saa2->id); + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SendAATable); + outapp->size=size; + outapp->pBuffer=(uchar*)saa; + value--; + memcpy(saa,saa2,size); + + if(value>0){ + if(saa->spellid==0) + saa->spellid=0xFFFFFFFF; + saa->id+=value; + saa->next_id=saa->id+1; + if(value==1) + saa->last_id=saa2->id; + else + saa->last_id=saa->id-1; + saa->current_level=value+1; + saa->cost2 = 0; //cost 2 is what the client uses to calc how many points we've spent, so we have to add up the points in order + for(uint32 i = 0; i < (value+1); i++) { + saa->cost2 += saa->cost + (saa->cost_inc * i); + } + } + + database.FillAAEffects(saa); + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::SendAA(uint32 id, int seq) { + + uint32 value=0; + SendAA_Struct* saa2 = nullptr; + SendAA_Struct* qaa = nullptr; + SendAA_Struct* saa_pp = nullptr; + bool IsBaseLevel = true; + bool aa_stack = false; + + if(id==0) + saa2 = zone->GetAABySequence(seq); + else + saa2 = zone->FindAA(id); + if(!saa2) + return; + + uint16 classes = saa2->classes; + if(!(classes & (1 << GetClass())) && (GetClass()!=BERSERKER || saa2->berserker==0)){ + return; + } + + if(saa2->account_time_required) + { + if((Timer::GetTimeSeconds() - account_creation) < saa2->account_time_required) + { + return; + } + } + + // Hide Quest/Progression AAs unless player has been granted the first level using $client->IncrementAA(skill_id). + if (saa2->special_category == 1 || saa2->special_category == 2 ) { + if(GetAA(saa2->id) == 0) + return; + // For Quest line AA(demiplane AEs) where only 1 is visible at a time, check to make sure only the highest level obtained is shown + if(saa2->aa_expansion > 0) { + qaa = zone->FindAA(saa2->id+1); + if(qaa && (saa2->aa_expansion == qaa->aa_expansion) && GetAA(qaa->id) > 0) + return; + } + } + +/* Beginning of Shroud AAs, these categories are for Passive and Active Shroud AAs + Eventually with a toggle we could have it show player list or shroud list + if (saa2->special_category == 3 || saa2->special_category == 4) + return; +*/ + // Check for racial/Drakkin blood line AAs + if (saa2->special_category == 8) + { + uint32 client_race = this->GetBaseRace(); + + // Drakkin Bloodlines + if (saa2->aa_expansion > 522) + { + if (client_race != 522) + return; // Check for Drakkin Race + + int heritage = this->GetDrakkinHeritage() + 523; // 523 = Drakkin Race(522) + Bloodline + + if (heritage != saa2->aa_expansion) + return; + } + // Racial AAs + else if (client_race != saa2->aa_expansion) + { + return; + } + } + + /* + AA stacking on SoF+ clients. + + Note: There were many ways to achieve this effect - The method used proved to be the most straight forward and consistent. + Stacking does not currently work ideally for AA's that use hotkeys, therefore they will be excluded at this time. + + TODO: Problem with AA hotkeys - When you reach max rank of an AA tier (ie 5/5), it automatically displays the next AA in + the series and you can not transfer the hotkey to the next AA series. To the best of the my ability and through many + different variations of coding I could not find an ideal solution to this issue. + + How stacking works: + Utilizes two new fields: sof_next_id (which is the next id in the series), sof_current_level (ranks the AA's as the current level) + 1) If no AA's purchased only display the base levels of each AA series. + 2) When you purchase an AA and its rank is maxed it sends the packet for the completed AA, and the packet + for the next aa in the series. The previous tier is removed from your window, and the new AA is displayed. + 3) When you zone/buy your player profile will be checked and determine what AA can be displayed base on what you have already. + */ + + if (RuleB(AA, Stacking) && (GetClientVersionBit() >= 4) && (saa2->hotkey_sid == 4294967295u)) + aa_stack = true; + + if (aa_stack){ + uint32 aa_AA = 0; + uint32 aa_value = 0; + for (int i = 0; i < MAX_PP_AA_ARRAY; i++) { + if (aa[i]) { + aa_AA = aa[i]->AA; + aa_value = aa[i]->value; + + if (aa_AA){ + + if (aa_value > 0) + aa_AA -= aa_value-1; + + saa_pp = zone->FindAA(aa_AA); + + if (saa_pp){ + + if (saa_pp->sof_next_skill == saa2->sof_next_skill){ + + if (saa_pp->id == saa2->id) + break; //You already have this in the player profile. + else if ((saa_pp->sof_current_level < saa2->sof_current_level) && (aa_value < saa_pp->max_level)) + return; //DISABLE DISPLAY HIGHER - You have not reached max level yet of your current AA. + else if ((saa_pp->sof_current_level < saa2->sof_current_level) && (aa_value == saa_pp->max_level) && (saa_pp->sof_next_id == saa2->id)) + IsBaseLevel = false; //ALLOW DISPLAY HIGHER + } + } + } + } + } + } + + //Hide higher tiers of multi tiered AA's if the base level is not fully purchased. + if (aa_stack && IsBaseLevel && saa2->sof_current_level > 0) + return; + + int size=sizeof(SendAA_Struct)+sizeof(AA_Ability)*saa2->total_abilities; + + if(size == 0) + return; + + uchar* buffer = new uchar[size]; + SendAA_Struct* saa=(SendAA_Struct*)buffer; + memcpy(saa,saa2,size); + + if(saa->spellid==0) + saa->spellid=0xFFFFFFFF; + + value=GetAA(saa->id); + uint32 orig_val = value; + + if(value && saa->id){ + + if(value < saa->max_level){ + saa->id+=value; + saa->next_id=saa->id+1; + value++; + } + + else if (aa_stack && saa->sof_next_id){ + saa->id+=value-1; + saa->next_id=saa->sof_next_id; + + //Prevent removal of previous AA from window if next AA belongs to a higher client version. + SendAA_Struct* saa_next = nullptr; + saa_next = zone->FindAA(saa->sof_next_id); + if (saa_next && + (((GetClientVersionBit() == 4) && (saa_next->clientver > 4)) + || ((GetClientVersionBit() == 8) && (saa_next->clientver > 5)) + || ((GetClientVersionBit() == 16) && (saa_next->clientver > 6)))){ + saa->next_id=0xFFFFFFFF; + } + } + + else{ + saa->id+=value-1; + saa->next_id=0xFFFFFFFF; + } + + uint32 current_level_mod = 0; + if (aa_stack) + current_level_mod = saa->sof_current_level; + + saa->last_id=saa->id-1; + saa->current_level=value+(current_level_mod); + saa->cost = saa2->cost + (saa2->cost_inc*(value-1)); + saa->cost2 = 0; + for(uint32 i = 0; i < value; i++) { + saa->cost2 += saa2->cost + (saa2->cost_inc * i); + } + saa->class_type = saa2->class_type + (saa2->level_inc*(value-1)); + } + + if (aa_stack){ + + if (saa->sof_current_level >= 1 && value == 0) + saa->current_level = saa->sof_current_level+1; + + saa->max_level = saa->sof_max_level; + } + + database.FillAAEffects(saa); + + if(value > 0) + { + // AA_Action stores the base ID + const AA_DBAction *caa = &AA_Actions[saa->id - value + 1][value - 1]; + + if(caa && caa->reuse_time > 0) + saa->spell_refresh = CalcAAReuseTimer(caa); + } + + //You can now use the level_inc field in the altadv_vars table to accomplish this, though still needed + //for special cases like LOH/HT due to inability to implement correct stacking of AA's that use hotkeys. + std::map::iterator RequiredLevel = AARequiredLevelAndCost.find(saa->id); + + if(RequiredLevel != AARequiredLevelAndCost.end()) + { + saa->class_type = RequiredLevel->second.Level; + saa->cost = RequiredLevel->second.Cost; + } + + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SendAATable); + outapp->size=size; + outapp->pBuffer=(uchar*)saa; + if(id==0 && value && (orig_val < saa->max_level)) //send previous AA only on zone in + SendPreviousAA(id, seq); + + QueuePacket(outapp); + safe_delete(outapp); + //will outapp delete the buffer for us even though it didnt make it? --- Yes, it should +} + +void Client::SendAAList(){ + int total = zone->GetTotalAAs(); + for(int i=0;i < total;i++){ + SendAA(0,i); + } +} + +uint32 Client::GetAA(uint32 aa_id) const { + std::map::const_iterator res; + res = aa_points.find(aa_id); + if(res != aa_points.end()) { + return(res->second); + } + return(0); +} + +bool Client::SetAA(uint32 aa_id, uint32 new_value) { + aa_points[aa_id] = new_value; + uint32 cur; + for(cur=0;cur < MAX_PP_AA_ARRAY;cur++){ + if((aa[cur]->value > 1) && ((aa[cur]->AA - aa[cur]->value + 1)== aa_id)){ + aa[cur]->value = new_value; + if(new_value > 0) + aa[cur]->AA++; + else + aa[cur]->AA = 0; + return true; + } + else if((aa[cur]->value == 1) && (aa[cur]->AA == aa_id)){ + aa[cur]->value = new_value; + if(new_value > 0) + aa[cur]->AA++; + else + aa[cur]->AA = 0; + return true; + } + else if(aa[cur]->AA==0){ //end of list + aa[cur]->AA = aa_id; + aa[cur]->value = new_value; + return true; + } + } + return false; +} + +SendAA_Struct* Zone::FindAA(uint32 id) { + return aas_send[id]; +} + +void Zone::LoadAAs() { + LogFile->write(EQEMuLog::Status, "Loading AA information..."); + totalAAs = database.CountAAs(); + if(totalAAs == 0) { + LogFile->write(EQEMuLog::Error, "Failed to load AAs!"); + aas = nullptr; + return; + } + aas = new SendAA_Struct *[totalAAs]; + + database.LoadAAs(aas); + + int i; + for(i=0; i < totalAAs; i++){ + SendAA_Struct* aa = aas[i]; + aas_send[aa->id] = aa; + } + + //load AA Effects into aa_effects + LogFile->write(EQEMuLog::Status, "Loading AA Effects..."); + if (database.LoadAAEffects2()) + LogFile->write(EQEMuLog::Status, "Loaded %d AA Effects.", aa_effects.size()); + else + LogFile->write(EQEMuLog::Error, "Failed to load AA Effects!"); +} + +bool ZoneDatabase::LoadAAEffects2() { + aa_effects.clear(); //start fresh + + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + if (RunQuery(query, MakeAnyLenString(&query, "SELECT aaid, slot, effectid, base1, base2 FROM aa_effects ORDER BY aaid ASC, slot ASC"), errbuf, &result)) { + int count = 0; + while((row = mysql_fetch_row(result))!= nullptr) { + int aaid = atoi(row[0]); + int slot = atoi(row[1]); + int effectid = atoi(row[2]); + int base1 = atoi(row[3]); + int base2 = atoi(row[4]); + aa_effects[aaid][slot].skill_id = effectid; + aa_effects[aaid][slot].base1 = base1; + aa_effects[aaid][slot].base2 = base2; + aa_effects[aaid][slot].slot = slot; //not really needed, but we'll populate it just in case + count++; + } + mysql_free_result(result); + if (count < 1) //no results + LogFile->write(EQEMuLog::Error, "Error loading AA Effects, none found in the database."); + } else { + LogFile->write(EQEMuLog::Error, "Error in ZoneDatabase::LoadAAEffects2 query: '%s': %s", query, errbuf); + return false; + } + safe_delete_array(query); + return true; +} +void Client::ResetAA(){ + uint32 i; + for(i=0;iAA = 0; + aa[i]->value = 0; + } + std::map::iterator itr; + for(itr=aa_points.begin();itr!=aa_points.end();++itr) + aa_points[itr->first] = 0; + + for(int i = 0; i < _maxLeaderAA; ++i) + m_pp.leader_abilities.ranks[i] = 0; + + m_pp.group_leadership_points = 0; + m_pp.raid_leadership_points = 0; + m_pp.group_leadership_exp = 0; + m_pp.raid_leadership_exp = 0; +} + +int Client::GroupLeadershipAAHealthEnhancement() +{ + Group *g = GetGroup(); + + if(!g || (g->GroupCount() < 3)) + return 0; + + switch(g->GetLeadershipAA(groupAAHealthEnhancement)) + { + case 0: + return 0; + case 1: + return 30; + case 2: + return 60; + case 3: + return 100; + } + + return 0; +} + +int Client::GroupLeadershipAAManaEnhancement() +{ + Group *g = GetGroup(); + + if(!g || (g->GroupCount() < 3)) + return 0; + + switch(g->GetLeadershipAA(groupAAManaEnhancement)) + { + case 0: + return 0; + case 1: + return 30; + case 2: + return 60; + case 3: + return 100; + } + + return 0; +} + +int Client::GroupLeadershipAAHealthRegeneration() +{ + Group *g = GetGroup(); + + if(!g || (g->GroupCount() < 3)) + return 0; + + switch(g->GetLeadershipAA(groupAAHealthRegeneration)) + { + case 0: + return 0; + case 1: + return 4; + case 2: + return 6; + case 3: + return 8; + } + + return 0; +} + +int Client::GroupLeadershipAAOffenseEnhancement() +{ + Group *g = GetGroup(); + + if(!g || (g->GroupCount() < 3)) + return 0; + + switch(g->GetLeadershipAA(groupAAOffenseEnhancement)) + { + case 0: + return 0; + case 1: + return 10; + case 2: + return 19; + case 3: + return 28; + case 4: + return 34; + case 5: + return 40; + } + return 0; +} + +void Client::InspectBuffs(Client* Inspector, int Rank) +{ + if(!Inspector || (Rank == 0)) return; + + Inspector->Message_StringID(0, CURRENT_SPELL_EFFECTS, GetName()); + uint32 buff_count = GetMaxTotalSlots(); + for (uint32 i = 0; i < buff_count; ++i) + { + if (buffs[i].spellid != SPELL_UNKNOWN) + { + if(Rank == 1) + Inspector->Message(0, "%s", spells[buffs[i].spellid].name); + else + { + if (spells[buffs[i].spellid].buffdurationformula == DF_Permanent) + Inspector->Message(0, "%s (Permanent)", spells[buffs[i].spellid].name); + else { + char *TempString = nullptr; + MakeAnyLenString(&TempString, "%.1f", static_cast(buffs[i].ticsremaining) / 10.0f); + Inspector->Message_StringID(0, BUFF_MINUTES_REMAINING, spells[buffs[i].spellid].name, TempString); + safe_delete_array(TempString); + } + } + } + } +} + +//this really need to be renamed to LoadAAActions() +bool ZoneDatabase::LoadAAEffects() { + char errbuf[MYSQL_ERRMSG_SIZE]; + MYSQL_RES *result; + MYSQL_ROW row; + + memset(AA_Actions, 0, sizeof(AA_Actions)); //I hope the compiler is smart about this size... + + const char *query = "SELECT aaid,rank,reuse_time,spell_id,target,nonspell_action,nonspell_mana,nonspell_duration," + "redux_aa,redux_rate,redux_aa2,redux_rate2 FROM aa_actions"; + + if(RunQuery(query, static_cast(strlen(query)), errbuf, &result)) { + //safe_delete_array(query); + int r; + while ((row = mysql_fetch_row(result))) { + r = 0; + int aaid = atoi(row[r++]); + int rank = atoi(row[r++]); + if(aaid < 0 || aaid >= aaHighestID || rank < 0 || rank >= MAX_AA_ACTION_RANKS) + continue; + AA_DBAction *caction = &AA_Actions[aaid][rank]; + + caction->reuse_time = atoi(row[r++]); + caction->spell_id = atoi(row[r++]); + caction->target = (aaTargetType) atoi(row[r++]); + caction->action = (aaNonspellAction) atoi(row[r++]); + caction->mana_cost = atoi(row[r++]); + caction->duration = atoi(row[r++]); + caction->redux_aa = (aaID) atoi(row[r++]); + caction->redux_rate = atoi(row[r++]); + caction->redux_aa2 = (aaID) atoi(row[r++]); + caction->redux_rate2 = atoi(row[r++]); + + } + mysql_free_result(result); + } + else { + LogFile->write(EQEMuLog::Error, "Error in LoadAAEffects query '%s': %s", query, errbuf);; + //safe_delete_array(query); + return false; + } + + return true; +} + +//Returns the number effects an AA has when we send them to the client +//For the purposes of sizing a packet because every skill does not +//have the same number effects, they can range from none to a few depending on AA. +//counts the # of effects by counting the different slots of an AAID in the DB. + +//AndMetal: this may now be obsolete since we have Zone::GetTotalAALevels() +uint8 ZoneDatabase::GetTotalAALevels(uint32 skill_id) { + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + int total=0; + if (RunQuery(query, MakeAnyLenString(&query, "SELECT count(slot) from aa_effects where aaid=%i", skill_id), errbuf, &result)) { + safe_delete_array(query); + if (mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + total=atoi(row[0]); + } + mysql_free_result(result); + } else { + LogFile->write(EQEMuLog::Error, "Error in GetTotalAALevels '%s: %s", query, errbuf); + safe_delete_array(query); + } + return total; +} + +//this will allow us to count the number of effects for an AA by pulling the info from memory instead of the database. hopefully this will same some CPU cycles +uint8 Zone::GetTotalAALevels(uint32 skill_id) { + size_t sz = aa_effects[skill_id].size(); + return sz >= 255 ? 255 : static_cast(sz); +} + +/* +Every AA can send the client effects, which are purely for client side effects. +Essentially it's like being able to attach a very simple version of a spell to +Any given AA, it has 4 fields: +skill_id = spell effect id +slot = ID slot, doesn't appear to have any impact on stacking like real spells, just needs to be unique. +base1 = the base field of a spell +base2 = base field 2 of a spell, most AAs do not utilize this +example: + skill_id = SE_STA + slot = 1 + base1 = 15 + This would if you filled the abilities struct with this make the client show if it had + that AA an additional 15 stamina on the client's stats +*/ +void ZoneDatabase::FillAAEffects(SendAA_Struct* aa_struct){ + if(!aa_struct) + return; + + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + if (RunQuery(query, MakeAnyLenString(&query, "SELECT effectid, base1, base2, slot from aa_effects where aaid=%i order by slot asc", aa_struct->id), errbuf, &result)) { + int ndx=0; + while((row = mysql_fetch_row(result))!=nullptr) { + aa_struct->abilities[ndx].skill_id=atoi(row[0]); + aa_struct->abilities[ndx].base1=atoi(row[1]); + aa_struct->abilities[ndx].base2=atoi(row[2]); + aa_struct->abilities[ndx].slot=atoi(row[3]); + ndx++; + } + mysql_free_result(result); + } else { + LogFile->write(EQEMuLog::Error, "Error in Client::FillAAEffects query: '%s': %s", query, errbuf); + } + safe_delete_array(query); +} + +uint32 ZoneDatabase::CountAAs(){ + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + int count=0; + if (RunQuery(query, MakeAnyLenString(&query, "SELECT count(title_sid) from altadv_vars"), errbuf, &result)) { + if((row = mysql_fetch_row(result))!=nullptr) + count = atoi(row[0]); + mysql_free_result(result); + } else { + LogFile->write(EQEMuLog::Error, "Error in ZoneDatabase::CountAAs query '%s': %s", query, errbuf); + } + safe_delete_array(query); + return count; +} + +uint32 ZoneDatabase::CountAAEffects(){ + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + int count=0; + if (RunQuery(query, MakeAnyLenString(&query, "SELECT count(id) from aa_effects"), errbuf, &result)) { + if((row = mysql_fetch_row(result))!=nullptr){ + count = atoi(row[0]); + } + mysql_free_result(result); + } else { + LogFile->write(EQEMuLog::Error, "Error in ZoneDatabase::CountAALevels query '%s': %s", query, errbuf); + } + safe_delete_array(query); + return count; +} + +uint32 ZoneDatabase::GetSizeAA(){ + int size=CountAAs()*sizeof(SendAA_Struct); + if(size>0) + size+=CountAAEffects()*sizeof(AA_Ability); + return size; +} + +void ZoneDatabase::LoadAAs(SendAA_Struct **load){ + if(!load) + return; + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + if (RunQuery(query, MakeAnyLenString(&query, "SELECT skill_id from altadv_vars order by skill_id"), errbuf, &result)) { + int skill=0,ndx=0; + while((row = mysql_fetch_row(result))!=nullptr) { + skill=atoi(row[0]); + load[ndx] = GetAASkillVars(skill); + load[ndx]->seq = ndx+1; + ndx++; + } + mysql_free_result(result); + } else { + LogFile->write(EQEMuLog::Error, "Error in ZoneDatabase::LoadAAs query '%s': %s", query, errbuf); + } + safe_delete_array(query); + + AARequiredLevelAndCost.clear(); + + if (RunQuery(query, MakeAnyLenString(&query, "SELECT skill_id, level, cost from aa_required_level_cost order by skill_id"), errbuf, &result)) + { + AALevelCost_Struct aalcs; + while((row = mysql_fetch_row(result))!=nullptr) + { + aalcs.Level = atoi(row[1]); + aalcs.Cost = atoi(row[2]); + AARequiredLevelAndCost[atoi(row[0])] = aalcs; + } + mysql_free_result(result); + } + else + LogFile->write(EQEMuLog::Error, "Error in ZoneDatabase::LoadAAs query '%s': %s", query, errbuf); + + safe_delete_array(query); +} + +SendAA_Struct* ZoneDatabase::GetAASkillVars(uint32 skill_id) +{ + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + SendAA_Struct* sendaa = nullptr; + uchar* buffer; + if (RunQuery(query, MakeAnyLenString(&query, "SET @row = 0"), errbuf)) { //initialize "row" variable in database for next query + safe_delete_array(query); + MYSQL_RES *result; //we don't really need these unless we get to this point, so why bother? + MYSQL_ROW row; + + if (RunQuery(query, MakeAnyLenString(&query, + "SELECT " + "a.cost, " + "a.max_level, " + "a.hotkey_sid, " + "a.hotkey_sid2, " + "a.title_sid, " + "a.desc_sid, " + "a.type, " + "COALESCE(" //so we can return 0 if it's null + "(" //this is our derived table that has the row # that we can SELECT from, because the client is stupid + "SELECT " + "p.prereq_index_num " + "FROM " + "(" + "SELECT " + "a2.skill_id, " + "@row := @row + 1 AS prereq_index_num " + "FROM " + "altadv_vars a2" + ") AS p " + "WHERE " + "p.skill_id = a.prereq_skill" + "), " + "0) AS prereq_skill_index, " + "a.prereq_minpoints, " + "a.spell_type, " + "a.spell_refresh, " + "a.classes, " + "a.berserker, " + "a.spellid, " + "a.class_type, " + "a.name, " + "a.cost_inc, " + "a.aa_expansion, " + "a.special_category, " + "a.sof_type, " + "a.sof_cost_inc, " + "a.sof_max_level, " + "a.sof_next_skill, " + "a.clientver, " // Client Version 0 = None, 1 = All, 2 = Titanium/6.2, 4 = SoF 5 = SOD 6 = UF + "a.account_time_required, " + "a.sof_current_level," + "a.sof_next_id, " + "a.level_inc " + " FROM altadv_vars a WHERE skill_id=%i", skill_id), errbuf, &result)) { + safe_delete_array(query); + if (mysql_num_rows(result) == 1) { + int total_abilities = GetTotalAALevels(skill_id); //eventually we'll want to use zone->GetTotalAALevels(skill_id) since it should save queries to the DB + int totalsize = total_abilities * sizeof(AA_Ability) + sizeof(SendAA_Struct); + + buffer = new uchar[totalsize]; + memset(buffer,0,totalsize); + sendaa = (SendAA_Struct*)buffer; + + row = mysql_fetch_row(result); + + //ATOI IS NOT UNISGNED LONG-SAFE!!! + + sendaa->cost = atoul(row[0]); + sendaa->cost2 = sendaa->cost; + sendaa->max_level = atoul(row[1]); + sendaa->hotkey_sid = atoul(row[2]); + sendaa->id = skill_id; + sendaa->hotkey_sid2 = atoul(row[3]); + sendaa->title_sid = atoul(row[4]); + sendaa->desc_sid = atoul(row[5]); + sendaa->type = atoul(row[6]); + sendaa->prereq_skill = atoul(row[7]); + sendaa->prereq_minpoints = atoul(row[8]); + sendaa->spell_type = atoul(row[9]); + sendaa->spell_refresh = atoul(row[10]); + sendaa->classes = static_cast(atoul(row[11])); + sendaa->berserker = static_cast(atoul(row[12])); + sendaa->last_id = 0xFFFFFFFF; + sendaa->current_level=1; + sendaa->spellid = atoul(row[13]); + sendaa->class_type = atoul(row[14]); + strcpy(sendaa->name,row[15]); + + sendaa->total_abilities=total_abilities; + if(sendaa->max_level > 1) + sendaa->next_id=skill_id+1; + else + sendaa->next_id=0xFFFFFFFF; + + sendaa->cost_inc = atoi(row[16]); + // Begin SoF Specific/Adjusted AA Fields + sendaa->aa_expansion = atoul(row[17]); + sendaa->special_category = atoul(row[18]); + sendaa->sof_type = atoul(row[19]); + sendaa->sof_cost_inc = atoi(row[20]); + sendaa->sof_max_level = atoul(row[21]); + sendaa->sof_next_skill = atoul(row[22]); + sendaa->clientver = atoul(row[23]); + sendaa->account_time_required = atoul(row[24]); + //Internal use only - not sent to client + sendaa->sof_current_level = atoul(row[25]); + sendaa->sof_next_id = atoul(row[26]); + sendaa->level_inc = static_cast(atoul(row[27])); + } + mysql_free_result(result); + } else { + LogFile->write(EQEMuLog::Error, "Error in GetAASkillVars '%s': %s", query, errbuf); + safe_delete_array(query); + } + } else { + LogFile->write(EQEMuLog::Error, "Error in GetAASkillVars '%s': %s", query, errbuf); + safe_delete_array(query); + } + return sendaa; +} + +void Client::DurationRampage(uint32 duration) +{ + if(duration) { + m_epp.aa_effects |= 1 << (aaEffectRampage-1); + p_timers.Start(pTimerAAEffectStart + aaEffectRampage, duration); + } +} + +AA_SwarmPetInfo::AA_SwarmPetInfo() +{ + target = 0; + owner_id = 0; + duration = nullptr; +} + +AA_SwarmPetInfo::~AA_SwarmPetInfo() +{ + target = 0; + owner_id = 0; + safe_delete(duration); +} + +Mob *AA_SwarmPetInfo::GetOwner() +{ + return entity_list.GetMobID(owner_id); +} diff --git a/zone/AA_v1.cpp b/zone/AA_v1.cpp new file mode 100644 index 000000000..fb1413927 --- /dev/null +++ b/zone/AA_v1.cpp @@ -0,0 +1,2032 @@ +/* EQEMu: Everquest Server Emulator +Copyright (C) 2001-2004 EQEMu Development Team (http://eqemulator.net) + + 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 +*/ + +// Test 1 + +#include "../common/debug.h" +#include "AA.h" +#include "mob.h" +#include "client.h" +#include "groups.h" +#include "raids.h" +#include "../common/spdat.h" +#include "object.h" +#include "doors.h" +#include "beacon.h" +#include "corpse.h" +#include "titles.h" +#include "../common/races.h" +#include "../common/classes.h" +#include "../common/eq_packet_structs.h" +#include "../common/packet_dump.h" +#include "../common/StringUtil.h" +#include "../common/logsys.h" +#include "zonedb.h" +#include "StringIDs.h" + +//static data arrays, really not big enough to warrant shared mem. +AA_DBAction AA_Actions[aaHighestID][MAX_AA_ACTION_RANKS]; //[aaid][rank] +std::mapaas_send; +std::map > aa_effects; //stores the effects from the aa_effects table in memory +std::map AARequiredLevelAndCost; + +/* + + +Schema: + +spell_id is spell to cast, SPELL_UNKNOWN == no spell +nonspell_action is action to preform on activation which is not a spell, 0=none +nonspell_mana is mana that the nonspell action consumes +nonspell_duration is a duration which may be used by the nonspell action +redux_aa is the aa which reduces the reuse timer of the skill +redux_rate is the multiplier of redux_aa, as a percentage of total rate (10 == 10% faster) + +CREATE TABLE aa_actions ( + aaid mediumint unsigned not null, + rank tinyint unsigned not null, + reuse_time mediumint unsigned not null, + spell_id mediumint unsigned not null, + target tinyint unsigned not null, + nonspell_action tinyint unsigned not null, + nonspell_mana mediumint unsigned not null, + nonspell_duration mediumint unsigned not null, + redux_aa mediumint unsigned not null, + redux_rate tinyint not null, + + PRIMARY KEY(aaid, rank) +); + +CREATE TABLE aa_swarmpets ( + spell_id mediumint unsigned not null, + count tinyint unsigned not null, + npc_id int not null, + duration mediumint unsigned not null, + PRIMARY KEY(spell_id) +); +*/ + +/* + +Credits for this function: + -FatherNitwit: Structure and mechanism + -Wiz: Initial set of AAs, original function contents + -Branks: Much updated info and a bunch of higher-numbered AAs + +*/ +int Client::GetAATimerID(aaID activate) +{ + SendAA_Struct* aa2 = zone->FindAA(activate); + + if(!aa2) + { + for(int i = 1;i < MAX_AA_ACTION_RANKS; ++i) + { + int a = activate - i; + + if(a <= 0) + break; + + aa2 = zone->FindAA(a); + + if(aa2 != nullptr) + break; + } + } + + if(aa2) + return aa2->spell_type; + + return 0; +} + +int Client::CalcAAReuseTimer(const AA_DBAction *caa) { + + if(!caa) + return 0; + + int ReuseTime = caa->reuse_time; + + if(ReuseTime > 0) + { + int ReductionPercentage; + + if(caa->redux_aa > 0 && caa->redux_aa < aaHighestID) + { + ReductionPercentage = GetAA(caa->redux_aa) * caa->redux_rate; + + if(caa->redux_aa2 > 0 && caa->redux_aa2 < aaHighestID) + ReductionPercentage += (GetAA(caa->redux_aa2) * caa->redux_rate2); + + ReuseTime = caa->reuse_time * (100 - ReductionPercentage) / 100; + } + + } + return ReuseTime; +} + +void Client::ActivateAA(aaID activate){ + if(activate < 0 || activate >= aaHighestID) + return; + if(IsStunned() || IsFeared() || IsMezzed() || IsSilenced() || IsPet() || IsSitting() || GetFeigned()) + return; + + int AATimerID = GetAATimerID(activate); + + SendAA_Struct* aa2 = nullptr; + aaID aaid = activate; + uint8 activate_val = GetAA(activate); + //this wasn't taking into acct multi tiered act talents before... + if(activate_val == 0){ + aa2 = zone->FindAA(activate); + if(!aa2){ + int i; + int a; + for(i=1;iFindAA(a); + if(aa2 != nullptr) + break; + } + } + if(aa2){ + aaid = (aaID) aa2->id; + activate_val = GetAA(aa2->id); + } + } + + if (activate_val == 0){ + return; + } + + if(aa2) + { + if(aa2->account_time_required) + { + if((Timer::GetTimeSeconds() + account_creation) < aa2->account_time_required) + { + return; + } + } + } + + if(!p_timers.Expired(&database, AATimerID + pTimerAAStart)) + { + uint32 aaremain = p_timers.GetRemainingTime(AATimerID + pTimerAAStart); + uint32 aaremain_hr = aaremain / (60 * 60); + uint32 aaremain_min = (aaremain / 60) % 60; + uint32 aaremain_sec = aaremain % 60; + + if(aa2) { + if (aaremain_hr >= 1) //1 hour or more + Message(13, "You can use the ability %s again in %u hour(s) %u minute(s) %u seconds", + aa2->name, aaremain_hr, aaremain_min, aaremain_sec); + else //less than an hour + Message(13, "You can use the ability %s again in %u minute(s) %u seconds", + aa2->name, aaremain_min, aaremain_sec); + } else { + if (aaremain_hr >= 1) //1 hour or more + Message(13, "You can use this ability again in %u hour(s) %u minute(s) %u seconds", + aaremain_hr, aaremain_min, aaremain_sec); + else //less than an hour + Message(13, "You can use this ability again in %u minute(s) %u seconds", + aaremain_min, aaremain_sec); + } + return; + } + + if(activate_val > MAX_AA_ACTION_RANKS) + activate_val = MAX_AA_ACTION_RANKS; + activate_val--; //to get array index. + + //get our current node, now that the indices are well bounded + const AA_DBAction *caa = &AA_Actions[aaid][activate_val]; + + if((aaid == aaImprovedHarmTouch || aaid == aaLeechTouch) && !p_timers.Expired(&database, pTimerHarmTouch)){ + Message(13,"Ability recovery time not yet met."); + return; + } + Shout("spell id %i", caa->spell_id); + //everything should be configured out now + + uint16 target_id = 0; + + //figure out our target + switch(caa->target) { + case aaTargetUser: + case aaTargetGroup: + target_id = GetID(); + break; + case aaTargetCurrent: + case aaTargetCurrentGroup: + if(GetTarget() == nullptr) { + Message_StringID(MT_DefaultText, AA_NO_TARGET); //You must first select a target for this ability! + p_timers.Clear(&database, AATimerID + pTimerAAStart); + return; + } + target_id = GetTarget()->GetID(); + break; + case aaTargetPet: + if(GetPet() == nullptr) { + Message(0, "A pet is required for this skill."); + return; + } + target_id = GetPetID(); + break; + } + + //handle non-spell action + if(caa->action != aaActionNone) { + if(caa->mana_cost > 0) { + if(GetMana() < caa->mana_cost) { + Message_StringID(13, INSUFFICIENT_MANA); + return; + } + SetMana(GetMana() - caa->mana_cost); + } + if(caa->reuse_time > 0) + { + uint32 timer_base = CalcAAReuseTimer(caa); + if(activate == aaImprovedHarmTouch || activate == aaLeechTouch) + { + p_timers.Start(pTimerHarmTouch, HarmTouchReuseTime); + } + p_timers.Start(AATimerID + pTimerAAStart, timer_base); + SendAATimer(AATimerID, 0, 0); + } + HandleAAAction(aaid); + } + Shout("1 spell id %i", caa->spell_id); + //cast the spell, if we have one + if(caa->spell_id > 0 && caa->spell_id < SPDAT_RECORDS) { + Shout("2 spell id %i", caa->spell_id); + if(caa->reuse_time > 0) + { + uint32 timer_base = CalcAAReuseTimer(caa); + SendAATimer(AATimerID, 0, 0); + p_timers.Start(AATimerID + pTimerAAStart, timer_base); + if(activate == aaImprovedHarmTouch || activate == aaLeechTouch) + { + p_timers.Start(pTimerHarmTouch, HarmTouchReuseTime); + } + // Bards can cast instant cast AAs while they are casting another song + if (spells[caa->spell_id].cast_time == 0 && GetClass() == BARD && IsBardSong(casting_spell_id)) { + if(!SpellFinished(caa->spell_id, entity_list.GetMob(target_id), 10, -1, -1, spells[caa->spell_id].ResistDiff, false)) { + //Reset on failed cast + SendAATimer(AATimerID, 0, 0xFFFFFF); + Message_StringID(15,ABILITY_FAILED); + p_timers.Clear(&database, AATimerID + pTimerAAStart); + return; + } + } else { + if(!CastSpell(caa->spell_id, target_id, 10, -1, -1, 0, -1, AATimerID + pTimerAAStart, timer_base, 1)) { + //Reset on failed cast + SendAATimer(AATimerID, 0, 0xFFFFFF); + Message_StringID(15,ABILITY_FAILED); + p_timers.Clear(&database, AATimerID + pTimerAAStart); + return; + } + } + } + else + { + if(!CastSpell(caa->spell_id, target_id)) + return; + } + } + // Check if AA is expendable + if (aas_send[activate - activate_val]->special_category == 7) + { + // Add the AA cost to the extended profile to track overall total + m_epp.expended_aa += aas_send[activate]->cost; + SetAA(activate, 0); + + Save(); + SendAA(activate); + SendAATable(); + } +} + +void Client::HandleAAAction(aaID activate) { + if(activate < 0 || activate >= aaHighestID) + return; + + uint8 activate_val = GetAA(activate); + + if (activate_val == 0) + return; + + if(activate_val > MAX_AA_ACTION_RANKS) + activate_val = MAX_AA_ACTION_RANKS; + activate_val--; //to get array index. + + //get our current node, now that the indices are well bounded + const AA_DBAction *caa = &AA_Actions[activate][activate_val]; + + uint16 timer_id = 0; + uint16 timer_duration = caa->duration; + aaTargetType target = aaTargetUser; + + uint16 spell_id = SPELL_UNKNOWN; //gets cast at the end if not still unknown + + switch(caa->action) { + case aaActionAETaunt: + entity_list.AETaunt(this); + break; + + case aaActionMassBuff: + EnableAAEffect(aaEffectMassGroupBuff, 3600); + Message_StringID(MT_Disciplines, MGB_STRING); //The next group buff you cast will hit all targets in range. + break; + + case aaActionFlamingArrows: + //toggle it + if(CheckAAEffect(aaEffectFlamingArrows)) + EnableAAEffect(aaEffectFlamingArrows); + else + DisableAAEffect(aaEffectFlamingArrows); + break; + + case aaActionFrostArrows: + if(CheckAAEffect(aaEffectFrostArrows)) + EnableAAEffect(aaEffectFrostArrows); + else + DisableAAEffect(aaEffectFrostArrows); + break; + + case aaActionRampage: + EnableAAEffect(aaEffectRampage, 10); + break; + + case aaActionSharedHealth: + if(CheckAAEffect(aaEffectSharedHealth)) + EnableAAEffect(aaEffectSharedHealth); + else + DisableAAEffect(aaEffectSharedHealth); + break; + + case aaActionCelestialRegen: { + //special because spell_id depends on a different AA + switch (GetAA(aaCelestialRenewal)) { + case 1: + spell_id = 3250; + break; + case 2: + spell_id = 3251; + break; + default: + spell_id = 2740; + break; + } + target = aaTargetCurrent; + break; + } + + case aaActionDireCharm: { + //special because spell_id depends on class + switch (GetClass()) + { + case DRUID: + spell_id = 2760; //2644? + break; + case NECROMANCER: + spell_id = 2759; //2643? + break; + case ENCHANTER: + spell_id = 2761; //2642? + break; + } + target = aaTargetCurrent; + break; + } + + case aaActionImprovedFamiliar: { + //Spell IDs might be wrong... + if (GetAA(aaAllegiantFamiliar)) + spell_id = 3264; //1994? + else + spell_id = 2758; //2155? + break; + } + + case aaActionActOfValor: + if(GetTarget() != nullptr) { + int curhp = GetTarget()->GetHP(); + target = aaTargetCurrent; + GetTarget()->HealDamage(curhp, this); + Death(this, 0, SPELL_UNKNOWN, SkillHandtoHand); + } + break; + + case aaActionSuspendedMinion: + if (GetPet()) { + target = aaTargetPet; + switch (GetAA(aaSuspendedMinion)) { + case 1: + spell_id = 3248; + break; + case 2: + spell_id = 3249; + break; + } + //do we really need to cast a spell? + + Message(0,"You call your pet to your side."); + GetPet()->WipeHateList(); + GetPet()->GMMove(GetX(),GetY(),GetZ()); + if (activate_val > 1) + entity_list.ClearFeignAggro(GetPet()); + } else { + Message(0,"You have no pet to call."); + } + break; + + case aaActionProjectIllusion: + EnableAAEffect(aaEffectProjectIllusion, 3600); + Message(10, "The power of your next illusion spell will flow to your grouped target in your place."); + break; + + + case aaActionEscape: + Escape(); + break; + + // Don't think this code is used any longer for Bestial Alignment as the AA has a spell_id and no nonspell_action. + case aaActionBeastialAlignment: + switch(GetBaseRace()) { + case BARBARIAN: + spell_id = AA_Choose3(activate_val, 4521, 4522, 4523); + break; + case TROLL: + spell_id = AA_Choose3(activate_val, 4524, 4525, 4526); + break; + case OGRE: + spell_id = AA_Choose3(activate_val, 4527, 4527, 4529); + break; + case IKSAR: + spell_id = AA_Choose3(activate_val, 4530, 4531, 4532); + break; + case VAHSHIR: + spell_id = AA_Choose3(activate_val, 4533, 4534, 4535); + break; + } + + case aaActionLeechTouch: + target = aaTargetCurrent; + spell_id = SPELL_HARM_TOUCH2; + EnableAAEffect(aaEffectLeechTouch, 1000); + break; + + case aaActionFadingMemories: + // Do nothing since spell effect works correctly, but mana isn't used. + break; + + default: + LogFile->write(EQEMuLog::Error, "Unknown AA nonspell action type %d", caa->action); + return; + } + + + uint16 target_id = 0; + //figure out our target + switch(target) { + case aaTargetUser: + case aaTargetGroup: + target_id = GetID(); + break; + case aaTargetCurrent: + case aaTargetCurrentGroup: + if(GetTarget() == nullptr) { + Message_StringID(MT_DefaultText, AA_NO_TARGET); //You must first select a target for this ability! + p_timers.Clear(&database, timer_id + pTimerAAEffectStart); + return; + } + target_id = GetTarget()->GetID(); + break; + case aaTargetPet: + if(GetPet() == nullptr) { + Message(0, "A pet is required for this skill."); + return; + } + target_id = GetPetID(); + break; + } + + //cast the spell, if we have one + if(IsValidSpell(spell_id)) { + int aatid = GetAATimerID(activate); + if(!CastSpell(spell_id, target_id , 10, -1, -1, 0, -1, pTimerAAStart + aatid , CalcAAReuseTimer(caa), 1)) { + SendAATimer(aatid, 0, 0xFFFFFF); + Message_StringID(15,ABILITY_FAILED); + p_timers.Clear(&database, pTimerAAStart + aatid); + return; + } + } + + //handle the duration timer if we have one. + if(timer_id > 0 && timer_duration > 0) { + p_timers.Start(pTimerAAEffectStart + timer_id, timer_duration); + } +} + + +//Originally written by Branks +//functionality rewritten by Father Nitwit +void Mob::TemporaryPets(uint16 spell_id, Mob *targ, const char *name_override, uint32 duration_override) { + + //It might not be a bad idea to put these into the database, eventually.. + + //Dook- swarms and wards + + PetRecord record; + if(!database.GetPetEntry(spells[spell_id].teleport_zone, &record)) + { + LogFile->write(EQEMuLog::Error, "Unknown swarm pet spell id: %d, check pets table", spell_id); + Message(13, "Unable to find data for pet %s", spells[spell_id].teleport_zone); + return; + } + + AA_SwarmPet pet; + pet.count = 1; + pet.duration = 1; + + for(int x = 0; x < 12; x++) + { + if(spells[spell_id].effectid[x] == SE_TemporaryPets) + { + pet.count = spells[spell_id].base[x]; + pet.duration = spells[spell_id].max[x]; + } + } + + if(IsClient()) + pet.duration += (CastToClient()->GetFocusEffect(focusSwarmPetDuration, spell_id) / 1000); + + pet.npc_id = record.npc_type; + + NPCType *made_npc = nullptr; + + const NPCType *npc_type = database.GetNPCType(pet.npc_id); + if(npc_type == nullptr) { + //log write + LogFile->write(EQEMuLog::Error, "Unknown npc type for swarm pet spell id: %d", spell_id); + Message(0,"Unable to find pet!"); + return; + } + + if(name_override != nullptr) { + //we have to make a custom NPC type for this name change + made_npc = new NPCType; + memcpy(made_npc, npc_type, sizeof(NPCType)); + strcpy(made_npc->name, name_override); + npc_type = made_npc; + } + + int summon_count = 0; + summon_count = pet.count; + + if(summon_count > MAX_SWARM_PETS) + summon_count = MAX_SWARM_PETS; + + static const float swarm_pet_x[MAX_SWARM_PETS] = { 5, -5, 5, -5, + 10, -10, 10, -10, + 8, -8, 8, -8 }; + static const float swarm_pet_y[MAX_SWARM_PETS] = { 5, 5, -5, -5, + 10, 10, -10, -10, + 8, 8, -8, -8 }; + TempPets(true); + + while(summon_count > 0) { + int pet_duration = pet.duration; + if(duration_override > 0) + pet_duration = duration_override; + + //this is a little messy, but the only way to do it right + //it would be possible to optimize out this copy for the last pet, but oh well + NPCType *npc_dup = nullptr; + if(made_npc != nullptr) { + npc_dup = new NPCType; + memcpy(npc_dup, made_npc, sizeof(NPCType)); + } + + NPC* npca = new NPC( + (npc_dup!=nullptr)?npc_dup:npc_type, //make sure we give the NPC the correct data pointer + 0, + GetX()+swarm_pet_x[summon_count], GetY()+swarm_pet_y[summon_count], + GetZ(), GetHeading(), FlyMode3); + + if((spell_id == 6882) || (spell_id == 6884)) + npca->SetFollowID(GetID()); + + if(!npca->GetSwarmInfo()){ + AA_SwarmPetInfo* nSI = new AA_SwarmPetInfo; + npca->SetSwarmInfo(nSI); + npca->GetSwarmInfo()->duration = new Timer(pet_duration*1000); + } + else{ + npca->GetSwarmInfo()->duration->Start(pet_duration*1000); + } + + //removing this prevents the pet from attacking + npca->GetSwarmInfo()->owner_id = GetID(); + + //give the pets somebody to "love" + if(targ != nullptr){ + npca->AddToHateList(targ, 1000, 1000); + npca->GetSwarmInfo()->target = targ->GetID(); + } + + //we allocated a new NPC type object, give the NPC ownership of that memory + if(npc_dup != nullptr) + npca->GiveNPCTypeData(npc_dup); + + entity_list.AddNPC(npca, true, true); + summon_count--; + } + + //the target of these swarm pets will take offense to being cast on... + if(targ != nullptr) + targ->AddToHateList(this, 1, 0); +} + +void Mob::TypesTemporaryPets(uint32 typesid, Mob *targ, const char *name_override, uint32 duration_override, bool followme) { + + AA_SwarmPet pet; + pet.count = 1; + pet.duration = 1; + + pet.npc_id = typesid; + + NPCType *made_npc = nullptr; + + const NPCType *npc_type = database.GetNPCType(typesid); + if(npc_type == nullptr) { + //log write + LogFile->write(EQEMuLog::Error, "Unknown npc type for swarm pet type id: %d", typesid); + Message(0,"Unable to find pet!"); + return; + } + + if(name_override != nullptr) { + //we have to make a custom NPC type for this name change + made_npc = new NPCType; + memcpy(made_npc, npc_type, sizeof(NPCType)); + strcpy(made_npc->name, name_override); + npc_type = made_npc; + } + + int summon_count = 0; + summon_count = pet.count; + + if(summon_count > MAX_SWARM_PETS) + summon_count = MAX_SWARM_PETS; + + static const float swarm_pet_x[MAX_SWARM_PETS] = { 5, -5, 5, -5, + 10, -10, 10, -10, + 8, -8, 8, -8 }; + static const float swarm_pet_y[MAX_SWARM_PETS] = { 5, 5, -5, -5, + 10, 10, -10, -10, + 8, 8, -8, -8 }; + TempPets(true); + + while(summon_count > 0) { + int pet_duration = pet.duration; + if(duration_override > 0) + pet_duration = duration_override; + + //this is a little messy, but the only way to do it right + //it would be possible to optimize out this copy for the last pet, but oh well + NPCType *npc_dup = nullptr; + if(made_npc != nullptr) { + npc_dup = new NPCType; + memcpy(npc_dup, made_npc, sizeof(NPCType)); + } + + NPC* npca = new NPC( + (npc_dup!=nullptr)?npc_dup:npc_type, //make sure we give the NPC the correct data pointer + 0, + GetX()+swarm_pet_x[summon_count], GetY()+swarm_pet_y[summon_count], + GetZ(), GetHeading(), FlyMode3); + + if(!npca->GetSwarmInfo()){ + AA_SwarmPetInfo* nSI = new AA_SwarmPetInfo; + npca->SetSwarmInfo(nSI); + npca->GetSwarmInfo()->duration = new Timer(pet_duration*1000); + } + else{ + npca->GetSwarmInfo()->duration->Start(pet_duration*1000); + } + + //removing this prevents the pet from attacking + npca->GetSwarmInfo()->owner_id = GetID(); + + //give the pets somebody to "love" + if(targ != nullptr){ + npca->AddToHateList(targ, 1000, 1000); + npca->GetSwarmInfo()->target = targ->GetID(); + } + + //we allocated a new NPC type object, give the NPC ownership of that memory + if(npc_dup != nullptr) + npca->GiveNPCTypeData(npc_dup); + + entity_list.AddNPC(npca, true, true); + summon_count--; + } +} + +void Mob::WakeTheDead(uint16 spell_id, Mob *target, uint32 duration) +{ + Corpse *CorpseToUse = nullptr; + CorpseToUse = entity_list.GetClosestCorpse(this, nullptr); + + if(!CorpseToUse) + return; + + //assuming we have pets in our table; we take the first pet as a base type. + const NPCType *base_type = database.GetNPCType(500); + NPCType *make_npc = new NPCType; + memcpy(make_npc, base_type, sizeof(NPCType)); + + //combat stats + make_npc->AC = ((GetLevel() * 7) + 550); + make_npc->ATK = GetLevel(); + make_npc->max_dmg = (GetLevel() * 4) + 2; + make_npc->min_dmg = 1; + + //base stats + make_npc->cur_hp = (GetLevel() * 55); + make_npc->max_hp = (GetLevel() * 55); + make_npc->STR = 85 + (GetLevel() * 3); + make_npc->STA = 85 + (GetLevel() * 3); + make_npc->DEX = 85 + (GetLevel() * 3); + make_npc->AGI = 85 + (GetLevel() * 3); + make_npc->INT = 85 + (GetLevel() * 3); + make_npc->WIS = 85 + (GetLevel() * 3); + make_npc->CHA = 85 + (GetLevel() * 3); + make_npc->MR = 25; + make_npc->FR = 25; + make_npc->CR = 25; + make_npc->DR = 25; + make_npc->PR = 25; + + //level class and gender + make_npc->level = GetLevel(); + make_npc->class_ = CorpseToUse->class_; + make_npc->race = CorpseToUse->race; + make_npc->gender = CorpseToUse->gender; + make_npc->loottable_id = 0; + //name + char NewName[64]; + sprintf(NewName, "%s`s Animated Corpse", GetCleanName()); + strcpy(make_npc->name, NewName); + + //appearance + make_npc->beard = CorpseToUse->beard; + make_npc->beardcolor = CorpseToUse->beardcolor; + make_npc->eyecolor1 = CorpseToUse->eyecolor1; + make_npc->eyecolor2 = CorpseToUse->eyecolor2; + make_npc->haircolor = CorpseToUse->haircolor; + make_npc->hairstyle = CorpseToUse->hairstyle; + make_npc->helmtexture = CorpseToUse->helmtexture; + make_npc->luclinface = CorpseToUse->luclinface; + make_npc->size = CorpseToUse->size; + make_npc->texture = CorpseToUse->texture; + + //cast stuff.. based off of PEQ's if you want to change + //it you'll have to mod this code, but most likely + //most people will be using PEQ style for the first + //part of their spell list; can't think of any smooth + //way to do this + //some basic combat mods here too since it's convienent + switch(CorpseToUse->class_) + { + case CLERIC: + make_npc->npc_spells_id = 1; + break; + case WIZARD: + make_npc->npc_spells_id = 2; + break; + case NECROMANCER: + make_npc->npc_spells_id = 3; + break; + case MAGICIAN: + make_npc->npc_spells_id = 4; + break; + case ENCHANTER: + make_npc->npc_spells_id = 5; + break; + case SHAMAN: + make_npc->npc_spells_id = 6; + break; + case DRUID: + make_npc->npc_spells_id = 7; + break; + case PALADIN: + //SPECATK_TRIPLE + strcpy(make_npc->special_abilities, "6,1"); + make_npc->cur_hp = make_npc->cur_hp * 150 / 100; + make_npc->max_hp = make_npc->max_hp * 150 / 100; + make_npc->npc_spells_id = 8; + break; + case SHADOWKNIGHT: + strcpy(make_npc->special_abilities, "6,1"); + make_npc->cur_hp = make_npc->cur_hp * 150 / 100; + make_npc->max_hp = make_npc->max_hp * 150 / 100; + make_npc->npc_spells_id = 9; + break; + case RANGER: + strcpy(make_npc->special_abilities, "7,1"); + make_npc->cur_hp = make_npc->cur_hp * 135 / 100; + make_npc->max_hp = make_npc->max_hp * 135 / 100; + make_npc->npc_spells_id = 10; + break; + case BARD: + strcpy(make_npc->special_abilities, "6,1"); + make_npc->cur_hp = make_npc->cur_hp * 110 / 100; + make_npc->max_hp = make_npc->max_hp * 110 / 100; + make_npc->npc_spells_id = 11; + break; + case BEASTLORD: + strcpy(make_npc->special_abilities, "7,1"); + make_npc->cur_hp = make_npc->cur_hp * 110 / 100; + make_npc->max_hp = make_npc->max_hp * 110 / 100; + make_npc->npc_spells_id = 12; + break; + case ROGUE: + strcpy(make_npc->special_abilities, "7,1"); + make_npc->max_dmg = make_npc->max_dmg * 150 /100; + make_npc->cur_hp = make_npc->cur_hp * 110 / 100; + make_npc->max_hp = make_npc->max_hp * 110 / 100; + break; + case MONK: + strcpy(make_npc->special_abilities, "7,1"); + make_npc->max_dmg = make_npc->max_dmg * 150 /100; + make_npc->cur_hp = make_npc->cur_hp * 135 / 100; + make_npc->max_hp = make_npc->max_hp * 135 / 100; + break; + case WARRIOR: + case BERSERKER: + strcpy(make_npc->special_abilities, "7,1"); + make_npc->max_dmg = make_npc->max_dmg * 150 /100; + make_npc->cur_hp = make_npc->cur_hp * 175 / 100; + make_npc->max_hp = make_npc->max_hp * 175 / 100; + break; + default: + make_npc->npc_spells_id = 0; + break; + } + + make_npc->loottable_id = 0; + make_npc->merchanttype = 0; + make_npc->d_meele_texture1 = 0; + make_npc->d_meele_texture2 = 0; + + TempPets(true); + + NPC* npca = new NPC(make_npc, 0, GetX(), GetY(), GetZ(), GetHeading(), FlyMode3); + + if(!npca->GetSwarmInfo()){ + AA_SwarmPetInfo* nSI = new AA_SwarmPetInfo; + npca->SetSwarmInfo(nSI); + npca->GetSwarmInfo()->duration = new Timer(duration*1000); + } + else{ + npca->GetSwarmInfo()->duration->Start(duration*1000); + } + + npca->GetSwarmInfo()->owner_id = GetID(); + + //give the pet somebody to "love" + if(target != nullptr){ + npca->AddToHateList(target, 100000); + npca->GetSwarmInfo()->target = target->GetID(); + } + + //gear stuff, need to make sure there's + //no situation where this stuff can be duped + for(int x = 0; x < 21; x++) + { + uint32 sitem = 0; + sitem = CorpseToUse->GetWornItem(x); + if(sitem){ + const Item_Struct * itm = database.GetItem(sitem); + npca->AddLootDrop(itm, &npca->itemlist, 1, 1, 127, true, true); + } + } + + //we allocated a new NPC type object, give the NPC ownership of that memory + if(make_npc != nullptr) + npca->GiveNPCTypeData(make_npc); + + entity_list.AddNPC(npca, true, true); + + //the target of these swarm pets will take offense to being cast on... + if(target != nullptr) + target->AddToHateList(this, 1, 0); +} + +//turn on an AA effect +//duration == 0 means no time limit, used for one-shot deals, etc.. +void Client::EnableAAEffect(aaEffectType type, uint32 duration) { + if(type > 32) + return; //for now, special logic needed. + m_epp.aa_effects |= 1 << (type-1); + + if(duration > 0) { + p_timers.Start(pTimerAAEffectStart + type, duration); + } else { + p_timers.Clear(&database, pTimerAAEffectStart + type); + } +} + +void Client::DisableAAEffect(aaEffectType type) { + if(type > 32) + return; //for now, special logic needed. + uint32 bit = 1 << (type-1); + if(m_epp.aa_effects & bit) { + m_epp.aa_effects ^= bit; + } + p_timers.Clear(&database, pTimerAAEffectStart + type); +} + +/* +By default an AA effect is a one shot deal, unless +a duration timer is set. +*/ +bool Client::CheckAAEffect(aaEffectType type) { + if(type > 32) + return(false); //for now, special logic needed. + if(m_epp.aa_effects & (1 << (type-1))) { //is effect enabled? + //has our timer expired? + if(p_timers.Expired(&database, pTimerAAEffectStart + type)) { + DisableAAEffect(type); + return(false); + } + return(true); + } + return(false); +} + +void Client::SendAAStats() { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AAExpUpdate, sizeof(AltAdvStats_Struct)); + AltAdvStats_Struct *aps = (AltAdvStats_Struct *)outapp->pBuffer; + aps->experience = m_pp.expAA; + aps->experience = (uint32)(((float)330.0f * (float)m_pp.expAA) / (float)max_AAXP); + aps->unspent = m_pp.aapoints; + aps->percentage = m_epp.perAA; + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::BuyAA(AA_Action* action) +{ + mlog(AA__MESSAGE, "Starting to buy AA %d", action->ability); + + //find the AA information from the database + SendAA_Struct* aa2 = zone->FindAA(action->ability); + if(!aa2) { + //hunt for a lower level... + int i; + int a; + for(i=1;iability - i; + if(a <= 0) + break; + mlog(AA__MESSAGE, "Could not find AA %d, trying potential parent %d", action->ability, a); + aa2 = zone->FindAA(a); + if(aa2 != nullptr) + break; + } + } + if(aa2 == nullptr) + return; //invalid ability... + + if(aa2->special_category == 1 || aa2->special_category == 2) + return; // Not purchasable progression style AAs + + if(aa2->special_category == 8 && aa2->cost == 0) + return; // Not purchasable racial AAs(set a cost to make them purchasable) + + uint32 cur_level = GetAA(aa2->id); + if((aa2->id + cur_level) != action->ability) { //got invalid AA + mlog(AA__ERROR, "Unable to find or match AA %d (found %d + lvl %d)", action->ability, aa2->id, cur_level); + return; + } + + if(aa2->account_time_required) + { + if((Timer::GetTimeSeconds() - account_creation) < aa2->account_time_required) + { + return; + } + } + + uint32 real_cost; + std::map::iterator RequiredLevel = AARequiredLevelAndCost.find(action->ability); + + if(RequiredLevel != AARequiredLevelAndCost.end()) + { + real_cost = RequiredLevel->second.Cost; + } + else + real_cost = aa2->cost + (aa2->cost_inc * cur_level); + + if(m_pp.aapoints >= real_cost && cur_level < aa2->max_level) { + SetAA(aa2->id, cur_level+1); + + mlog(AA__MESSAGE, "Set AA %d to level %d", aa2->id, cur_level+1); + + m_pp.aapoints -= real_cost; + + Save(); + //if ((RuleB(AA, Stacking) && (GetClientVersionBit() >= 4) && (aa2->hotkey_sid == 4294967295u)) + if ((RuleB(AA, Stacking) && (GetClientVersionBit() >= 4)) + && ((aa2->max_level == (cur_level+1)) && aa2->sof_next_id)){ + SendAA(aa2->id); + SendAA(aa2->sof_next_id); + } + //else if ((RuleB(AA, Stacking) && (GetClientVersionBit() >= 4) && cur_level == 0 && aa2->hotkey_sid != 4294967295u)) + //{ + //Shout("Current lv 0 for AA hot key %i %i", cur_level, aa2->hotkey_sid); + //} + + else + SendAA(aa2->id); + + SendAATable(); + + //we are building these messages ourself instead of using the stringID to work around patch discrepencies + //these are AA_GAIN_ABILITY (410) & AA_IMPROVE (411), respectively, in both Titanium & SoF. not sure about 6.2 + if(cur_level<1) + Message(15,"You have gained the ability \"%s\" at a cost of %d ability %s.", aa2->name, real_cost, (real_cost>1)?"points":"point"); + else + Message(15,"You have improved %s %d at a cost of %d ability %s.", aa2->name, cur_level+1, real_cost, (real_cost>1)?"points":"point"); + + + SendAAStats(); + + //SendAAList(true); + //SendAATable(); + CalcBonuses(); + if(title_manager.IsNewAATitleAvailable(m_pp.aapoints_spent, GetBaseClass())) + NotifyNewTitlesAvailable(); + } +} + +void Client::SendAATimer(uint32 ability, uint32 begin, uint32 end) { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AAAction,sizeof(UseAA_Struct)); + UseAA_Struct* uaaout = (UseAA_Struct*)outapp->pBuffer; + uaaout->ability = ability; + uaaout->begin = begin; + uaaout->end = end; + QueuePacket(outapp); + safe_delete(outapp); +} + +//sends all AA timers. +void Client::SendAATimers() { + //we dont use SendAATimer because theres no reason to allocate the EQApplicationPacket every time + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AAAction,sizeof(UseAA_Struct)); + UseAA_Struct* uaaout = (UseAA_Struct*)outapp->pBuffer; + + PTimerList::iterator c,e; + c = p_timers.begin(); + e = p_timers.end(); + for(; c != e; ++c) { + PersistentTimer *cur = c->second; + if(cur->GetType() < pTimerAAStart || cur->GetType() > pTimerAAEnd) + continue; //not an AA timer + //send timer + uaaout->begin = cur->GetStartTime(); + uaaout->end = static_cast(time(nullptr)); + uaaout->ability = cur->GetType() - pTimerAAStart; // uuaaout->ability is really a shared timer number + QueuePacket(outapp); + } + + safe_delete(outapp); +} + +void Client::SendAATable() { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_RespondAA, sizeof(AATable_Struct)); + + AATable_Struct* aa2 = (AATable_Struct *)outapp->pBuffer; + aa2->aa_spent = GetAAPointsSpent(); + + uint32 i; + for(i=0;i < MAX_PP_AA_ARRAY;i++){ + aa2->aa_list[i].aa_skill = aa[i]->AA; + aa2->aa_list[i].aa_value = aa[i]->value; + aa2->aa_list[i].unknown08 = 0; + } + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::SendPreviousAA(uint32 id, int seq){ + uint32 value=0; + SendAA_Struct* saa2 = nullptr; + if(id==0) + saa2 = zone->GetAABySequence(seq); + else + saa2 = zone->FindAA(id); + if(!saa2) + return; + int size=sizeof(SendAA_Struct)+sizeof(AA_Ability)*saa2->total_abilities; + uchar* buffer = new uchar[size]; + SendAA_Struct* saa=(SendAA_Struct*)buffer; + value = GetAA(saa2->id); + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SendAATable); + outapp->size=size; + outapp->pBuffer=(uchar*)saa; + value--; + memcpy(saa,saa2,size); + + if(value>0){ + if(saa->spellid==0) + saa->spellid=0xFFFFFFFF; + saa->id+=value; + saa->next_id=saa->id+1; + if(value==1) + saa->last_id=saa2->id; + else + saa->last_id=saa->id-1; + saa->current_level=value+1; + saa->cost2 = 0; //cost 2 is what the client uses to calc how many points we've spent, so we have to add up the points in order + for(uint32 i = 0; i < (value+1); i++) { + saa->cost2 += saa->cost + (saa->cost_inc * i); + } + } + + database.FillAAEffects(saa); + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::SendAA(uint32 id, int seq, bool seqrest) { + + uint32 value=0; + SendAA_Struct* saa2 = nullptr; + SendAA_Struct* qaa = nullptr; + SendAA_Struct* saa_pp = nullptr; + bool IsBaseLevel = true; + bool aa_stack = false; + + Shout("Reset: %i", seqrest); + + if(id==0){ + saa2 = zone->GetAABySequence(seq); + //Shout("SAA2 %i x seq %i", GetAA(saa2->id), seq); + } + else + saa2 = zone->FindAA(id); + if(!saa2) + return; + + uint16 classes = saa2->classes; + if(!(classes & (1 << GetClass())) && (GetClass()!=BERSERKER || saa2->berserker==0)){ + return; + } + + if(saa2->account_time_required) + { + if((Timer::GetTimeSeconds() - account_creation) < saa2->account_time_required) + { + return; + } + } + + // Hide Quest/Progression AAs unless player has been granted the first level using $client->IncrementAA(skill_id). + if (saa2->special_category == 1 || saa2->special_category == 2 ) { + if(GetAA(saa2->id) == 0) + return; + // For Quest line AA(demiplane AEs) where only 1 is visible at a time, check to make sure only the highest level obtained is shown + if(saa2->aa_expansion > 0) { + qaa = zone->FindAA(saa2->id+1); + if(qaa && (saa2->aa_expansion == qaa->aa_expansion) && GetAA(qaa->id) > 0) + return; + } + } + +/* Beginning of Shroud AAs, these categories are for Passive and Active Shroud AAs + Eventually with a toggle we could have it show player list or shroud list + if (saa2->special_category == 3 || saa2->special_category == 4) + return; +*/ + // Check for racial/Drakkin blood line AAs + if (saa2->special_category == 8) + { + uint32 client_race = this->GetBaseRace(); + + // Drakkin Bloodlines + if (saa2->aa_expansion > 522) + { + if (client_race != 522) + return; // Check for Drakkin Race + + int heritage = this->GetDrakkinHeritage() + 523; // 523 = Drakkin Race(522) + Bloodline + + if (heritage != saa2->aa_expansion) + return; + } + // Racial AAs + else if (client_race != saa2->aa_expansion) + { + return; + } + } + + /* + AA stacking on SoF+ clients. + + Note: There were many ways to achieve this effect - The method used proved to be the most straight forward and consistent. + Stacking does not currently work ideally for AA's that use hotkeys, therefore they will be excluded at this time. + + TODO: Problem with AA hotkeys - When you reach max rank of an AA tier (ie 5/5), it automatically displays the next AA in + the series and you can not transfer the hotkey to the next AA series. To the best of the my ability and through many + different variations of coding I could not find an ideal solution to this issue. + + How stacking works: + Utilizes two new fields: sof_next_id (which is the next id in the series), sof_current_level (ranks the AA's as the current level) + 1) If no AA's purchased only display the base levels of each AA series. + 2) When you purchase an AA and its rank is maxed it sends the packet for the completed AA, and the packet + for the next aa in the series. The previous tier is removed from your window, and the new AA is displayed. + 3) When you zone/buy your player profile will be checked and determine what AA can be displayed base on what you have already. + */ + + //if (RuleB(AA, Stacking) && (GetClientVersionBit() >= 4) && (saa2->hotkey_sid == 4294967295u)) + if (RuleB(AA, Stacking) && (GetClientVersionBit() >= 4)) + aa_stack = true; + + if (aa_stack){ + uint32 aa_AA = 0; + uint32 aa_value = 0; + for (int i = 0; i < MAX_PP_AA_ARRAY; i++) { + if (aa[i]) { + aa_AA = aa[i]->AA; + aa_value = aa[i]->value; + + if (aa_AA){ + + if (aa_value > 0) + aa_AA -= aa_value-1; + + saa_pp = zone->FindAA(aa_AA); + + if (saa_pp){ + + if (saa_pp->sof_next_skill == saa2->sof_next_skill){ + + if (saa_pp->id == saa2->id) + break; //You already have this in the player profile. + else if ((saa_pp->sof_current_level < saa2->sof_current_level) && (aa_value < saa_pp->max_level)) + return; //DISABLE DISPLAY HIGHER - You have not reached max level yet of your current AA. + else if ((saa_pp->sof_current_level < saa2->sof_current_level) && (aa_value == saa_pp->max_level) && (saa_pp->sof_next_id == saa2->id)) + IsBaseLevel = false; //ALLOW DISPLAY HIGHER + } + } + } + } + } + } + + //Hide higher tiers of multi tiered AA's if the base level is not fully purchased. + if (aa_stack && IsBaseLevel && saa2->sof_current_level > 0) + return; + + int size=sizeof(SendAA_Struct)+sizeof(AA_Ability)*saa2->total_abilities; + + if(size == 0) + return; + + uchar* buffer = new uchar[size]; + SendAA_Struct* saa=(SendAA_Struct*)buffer; + memcpy(saa,saa2,size); + Shout("[AA ID %i] SEQ %i SEQLIVE %i",saa->id, saa->seq, saa->seqlive); + if(saa->spellid==0) + saa->spellid=0xFFFFFFFF; + + value=GetAA(saa->id); + uint32 orig_val = value; + + if(value && saa->id){ + + if(value < saa->max_level){ + saa->id+=value; + saa->next_id=saa->id+1; + value++; + } + + else if (aa_stack && saa->sof_next_id){ + //Max value of current tier reached. + saa->id+=value-1; + saa->next_id=saa->sof_next_id; + + //Prevent removal of previous AA from window if next AA belongs to a higher client version. + SendAA_Struct* saa_next = nullptr; + saa_next = zone->FindAA(saa->sof_next_id); + if (saa_next && + (((GetClientVersionBit() == 4) && (saa_next->clientver > 4)) + || ((GetClientVersionBit() == 8) && (saa_next->clientver > 5)) + || ((GetClientVersionBit() == 16) && (saa_next->clientver > 6)))){ + saa->next_id=0xFFFFFFFF; + } + + //if (saa->id == 131) { + // saa->seq = 1; + // Shout("SET SEQ 1"); + //} + Shout("AA Completed: Next"); + } + + else{ + saa->id+=value-1; + saa->next_id=0xFFFFFFFF; + Shout("AA Completed: Final"); + } + + uint32 current_level_mod = 0; + if (aa_stack) + current_level_mod = saa->sof_current_level; + + saa->last_id=saa->id-1; + Shout("1 Current level %i + Value %i",saa->sof_current_level, value); + saa->current_level=value+(current_level_mod); + saa->cost = saa2->cost + (saa2->cost_inc*(value-1)); + saa->cost2 = 0; + for(uint32 i = 0; i < value; i++) { + saa->cost2 += saa2->cost + (saa2->cost_inc * i); + } + saa->class_type = saa2->class_type + (saa2->level_inc*(value-1)); + + } + + if (aa_stack){ + Shout("2 Current level %i VALUE %i",saa->sof_current_level, value); + //After finishing an AA tier transfer over the current level to the next tier to display. + if (saa->sof_current_level >= 1 && value == 0){ + saa->current_level = saa->sof_current_level+1; + + Shout("value = 0 SET LAST AND SEQ"); + saa->last_id = 131; + //saa->seq = 38; + if (saa->seqlive) + saa->seq = saa->seqlive; + } + + saa->max_level = saa->sof_max_level; + } + + + if(seqrest) { + //saa->seq = 0; + saa->id+= saa->max_level-1; + saa->next_id=1; + saa->seq = 0; + Shout("reset"); + } + + database.FillAAEffects(saa); + + if(value > 0) + { + // AA_Action stores the base ID + const AA_DBAction *caa = &AA_Actions[saa->id - value + 1][value - 1]; + + if(caa && caa->reuse_time > 0) + saa->spell_refresh = CalcAAReuseTimer(caa); + } + + //You can now use the level_inc field in the altadv_vars table to accomplish this, though still needed + //for special cases like LOH/HT due to inability to implement correct stacking of AA's that use hotkeys. + std::map::iterator RequiredLevel = AARequiredLevelAndCost.find(saa->id); + + if(RequiredLevel != AARequiredLevelAndCost.end()) + { + saa->class_type = RequiredLevel->second.Level; + saa->cost = RequiredLevel->second.Cost; + } + + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SendAATable); + outapp->size=size; + outapp->pBuffer=(uchar*)saa; + if(id==0 && value && (orig_val < saa->max_level)) //send previous AA only on zone in + SendPreviousAA(id, seq); + + QueuePacket(outapp); + safe_delete(outapp); + //will outapp delete the buffer for us even though it didnt make it? --- Yes, it should +} + +void Client::SendAAList(bool seqrest){ + int total = zone->GetTotalAAs(); + for(int i=0;i < total;i++){ + SendAA(0,i, seqrest); + } +} + +uint32 Client::GetAA(uint32 aa_id) const { + std::map::const_iterator res; + res = aa_points.find(aa_id); + if(res != aa_points.end()) { + return(res->second); + } + return(0); +} + +bool Client::SetAA(uint32 aa_id, uint32 new_value) { + aa_points[aa_id] = new_value; + uint32 cur; + for(cur=0;cur < MAX_PP_AA_ARRAY;cur++){ + if((aa[cur]->value > 1) && ((aa[cur]->AA - aa[cur]->value + 1)== aa_id)){ + aa[cur]->value = new_value; + if(new_value > 0) + aa[cur]->AA++; + else + aa[cur]->AA = 0; + return true; + } + else if((aa[cur]->value == 1) && (aa[cur]->AA == aa_id)){ + aa[cur]->value = new_value; + if(new_value > 0) + aa[cur]->AA++; + else + aa[cur]->AA = 0; + return true; + } + else if(aa[cur]->AA==0){ //end of list + aa[cur]->AA = aa_id; + aa[cur]->value = new_value; + return true; + } + } + return false; +} + +SendAA_Struct* Zone::FindAA(uint32 id) { + return aas_send[id]; +} + +void Zone::LoadAAs() { + LogFile->write(EQEMuLog::Status, "Loading AA information..."); + totalAAs = database.CountAAs(); + if(totalAAs == 0) { + LogFile->write(EQEMuLog::Error, "Failed to load AAs!"); + aas = nullptr; + return; + } + aas = new SendAA_Struct *[totalAAs]; + + database.LoadAAs(aas); + + int i; + for(i=0; i < totalAAs; i++){ + SendAA_Struct* aa = aas[i]; + aas_send[aa->id] = aa; + } + + //load AA Effects into aa_effects + LogFile->write(EQEMuLog::Status, "Loading AA Effects..."); + if (database.LoadAAEffects2()) + LogFile->write(EQEMuLog::Status, "Loaded %d AA Effects.", aa_effects.size()); + else + LogFile->write(EQEMuLog::Error, "Failed to load AA Effects!"); +} + +bool ZoneDatabase::LoadAAEffects2() { + aa_effects.clear(); //start fresh + + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + if (RunQuery(query, MakeAnyLenString(&query, "SELECT aaid, slot, effectid, base1, base2 FROM aa_effects ORDER BY aaid ASC, slot ASC"), errbuf, &result)) { + int count = 0; + while((row = mysql_fetch_row(result))!= nullptr) { + int aaid = atoi(row[0]); + int slot = atoi(row[1]); + int effectid = atoi(row[2]); + int base1 = atoi(row[3]); + int base2 = atoi(row[4]); + aa_effects[aaid][slot].skill_id = effectid; + aa_effects[aaid][slot].base1 = base1; + aa_effects[aaid][slot].base2 = base2; + aa_effects[aaid][slot].slot = slot; //not really needed, but we'll populate it just in case + count++; + } + mysql_free_result(result); + if (count < 1) //no results + LogFile->write(EQEMuLog::Error, "Error loading AA Effects, none found in the database."); + } else { + LogFile->write(EQEMuLog::Error, "Error in ZoneDatabase::LoadAAEffects2 query: '%s': %s", query, errbuf); + return false; + } + safe_delete_array(query); + return true; +} +void Client::ResetAA(){ + uint32 i; + for(i=0;iAA = 0; + aa[i]->value = 0; + } + std::map::iterator itr; + for(itr=aa_points.begin();itr!=aa_points.end();++itr) + aa_points[itr->first] = 0; + + for(int i = 0; i < _maxLeaderAA; ++i) + m_pp.leader_abilities.ranks[i] = 0; + + m_pp.group_leadership_points = 0; + m_pp.raid_leadership_points = 0; + m_pp.group_leadership_exp = 0; + m_pp.raid_leadership_exp = 0; +} + +int Client::GroupLeadershipAAHealthEnhancement() +{ + Group *g = GetGroup(); + + if(!g || (g->GroupCount() < 3)) + return 0; + + switch(g->GetLeadershipAA(groupAAHealthEnhancement)) + { + case 0: + return 0; + case 1: + return 30; + case 2: + return 60; + case 3: + return 100; + } + + return 0; +} + +int Client::GroupLeadershipAAManaEnhancement() +{ + Group *g = GetGroup(); + + if(!g || (g->GroupCount() < 3)) + return 0; + + switch(g->GetLeadershipAA(groupAAManaEnhancement)) + { + case 0: + return 0; + case 1: + return 30; + case 2: + return 60; + case 3: + return 100; + } + + return 0; +} + +int Client::GroupLeadershipAAHealthRegeneration() +{ + Group *g = GetGroup(); + + if(!g || (g->GroupCount() < 3)) + return 0; + + switch(g->GetLeadershipAA(groupAAHealthRegeneration)) + { + case 0: + return 0; + case 1: + return 4; + case 2: + return 6; + case 3: + return 8; + } + + return 0; +} + +int Client::GroupLeadershipAAOffenseEnhancement() +{ + Group *g = GetGroup(); + + if(!g || (g->GroupCount() < 3)) + return 0; + + switch(g->GetLeadershipAA(groupAAOffenseEnhancement)) + { + case 0: + return 0; + case 1: + return 10; + case 2: + return 19; + case 3: + return 28; + case 4: + return 34; + case 5: + return 40; + } + return 0; +} + +void Client::InspectBuffs(Client* Inspector, int Rank) +{ + if(!Inspector || (Rank == 0)) return; + + Inspector->Message_StringID(0, CURRENT_SPELL_EFFECTS, GetName()); + uint32 buff_count = GetMaxTotalSlots(); + for (uint32 i = 0; i < buff_count; ++i) + { + if (buffs[i].spellid != SPELL_UNKNOWN) + { + if(Rank == 1) + Inspector->Message(0, "%s", spells[buffs[i].spellid].name); + else + { + if (spells[buffs[i].spellid].buffdurationformula == DF_Permanent) + Inspector->Message(0, "%s (Permanent)", spells[buffs[i].spellid].name); + else { + char *TempString = nullptr; + MakeAnyLenString(&TempString, "%.1f", static_cast(buffs[i].ticsremaining) / 10.0f); + Inspector->Message_StringID(0, BUFF_MINUTES_REMAINING, spells[buffs[i].spellid].name, TempString); + safe_delete_array(TempString); + } + } + } + } +} + +//this really need to be renamed to LoadAAActions() +bool ZoneDatabase::LoadAAEffects() { + char errbuf[MYSQL_ERRMSG_SIZE]; + MYSQL_RES *result; + MYSQL_ROW row; + + memset(AA_Actions, 0, sizeof(AA_Actions)); //I hope the compiler is smart about this size... + + const char *query = "SELECT aaid,rank,reuse_time,spell_id,target,nonspell_action,nonspell_mana,nonspell_duration," + "redux_aa,redux_rate,redux_aa2,redux_rate2 FROM aa_actions"; + + if(RunQuery(query, static_cast(strlen(query)), errbuf, &result)) { + //safe_delete_array(query); + int r; + while ((row = mysql_fetch_row(result))) { + r = 0; + int aaid = atoi(row[r++]); + int rank = atoi(row[r++]); + if(aaid < 0 || aaid >= aaHighestID || rank < 0 || rank >= MAX_AA_ACTION_RANKS) + continue; + AA_DBAction *caction = &AA_Actions[aaid][rank]; + + caction->reuse_time = atoi(row[r++]); + caction->spell_id = atoi(row[r++]); + caction->target = (aaTargetType) atoi(row[r++]); + caction->action = (aaNonspellAction) atoi(row[r++]); + caction->mana_cost = atoi(row[r++]); + caction->duration = atoi(row[r++]); + caction->redux_aa = (aaID) atoi(row[r++]); + caction->redux_rate = atoi(row[r++]); + caction->redux_aa2 = (aaID) atoi(row[r++]); + caction->redux_rate2 = atoi(row[r++]); + + } + mysql_free_result(result); + } + else { + LogFile->write(EQEMuLog::Error, "Error in LoadAAEffects query '%s': %s", query, errbuf);; + //safe_delete_array(query); + return false; + } + + return true; +} + +//Returns the number effects an AA has when we send them to the client +//For the purposes of sizing a packet because every skill does not +//have the same number effects, they can range from none to a few depending on AA. +//counts the # of effects by counting the different slots of an AAID in the DB. + +//AndMetal: this may now be obsolete since we have Zone::GetTotalAALevels() +uint8 ZoneDatabase::GetTotalAALevels(uint32 skill_id) { + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + int total=0; + if (RunQuery(query, MakeAnyLenString(&query, "SELECT count(slot) from aa_effects where aaid=%i", skill_id), errbuf, &result)) { + safe_delete_array(query); + if (mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + total=atoi(row[0]); + } + mysql_free_result(result); + } else { + LogFile->write(EQEMuLog::Error, "Error in GetTotalAALevels '%s: %s", query, errbuf); + safe_delete_array(query); + } + return total; +} + +//this will allow us to count the number of effects for an AA by pulling the info from memory instead of the database. hopefully this will same some CPU cycles +uint8 Zone::GetTotalAALevels(uint32 skill_id) { + size_t sz = aa_effects[skill_id].size(); + return sz >= 255 ? 255 : static_cast(sz); +} + +/* +Every AA can send the client effects, which are purely for client side effects. +Essentially it's like being able to attach a very simple version of a spell to +Any given AA, it has 4 fields: +skill_id = spell effect id +slot = ID slot, doesn't appear to have any impact on stacking like real spells, just needs to be unique. +base1 = the base field of a spell +base2 = base field 2 of a spell, most AAs do not utilize this +example: + skill_id = SE_STA + slot = 1 + base1 = 15 + This would if you filled the abilities struct with this make the client show if it had + that AA an additional 15 stamina on the client's stats +*/ +void ZoneDatabase::FillAAEffects(SendAA_Struct* aa_struct){ + if(!aa_struct) + return; + + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + if (RunQuery(query, MakeAnyLenString(&query, "SELECT effectid, base1, base2, slot from aa_effects where aaid=%i order by slot asc", aa_struct->id), errbuf, &result)) { + int ndx=0; + while((row = mysql_fetch_row(result))!=nullptr) { + aa_struct->abilities[ndx].skill_id=atoi(row[0]); + aa_struct->abilities[ndx].base1=atoi(row[1]); + aa_struct->abilities[ndx].base2=atoi(row[2]); + aa_struct->abilities[ndx].slot=atoi(row[3]); + ndx++; + } + mysql_free_result(result); + } else { + LogFile->write(EQEMuLog::Error, "Error in Client::FillAAEffects query: '%s': %s", query, errbuf); + } + safe_delete_array(query); +} + +uint32 ZoneDatabase::CountAAs(){ + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + int count=0; + if (RunQuery(query, MakeAnyLenString(&query, "SELECT count(title_sid) from altadv_vars"), errbuf, &result)) { + if((row = mysql_fetch_row(result))!=nullptr) + count = atoi(row[0]); + mysql_free_result(result); + } else { + LogFile->write(EQEMuLog::Error, "Error in ZoneDatabase::CountAAs query '%s': %s", query, errbuf); + } + safe_delete_array(query); + return count; +} + +uint32 ZoneDatabase::CountAAEffects(){ + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + int count=0; + if (RunQuery(query, MakeAnyLenString(&query, "SELECT count(id) from aa_effects"), errbuf, &result)) { + if((row = mysql_fetch_row(result))!=nullptr){ + count = atoi(row[0]); + } + mysql_free_result(result); + } else { + LogFile->write(EQEMuLog::Error, "Error in ZoneDatabase::CountAALevels query '%s': %s", query, errbuf); + } + safe_delete_array(query); + return count; +} + +uint32 ZoneDatabase::GetSizeAA(){ + int size=CountAAs()*sizeof(SendAA_Struct); + if(size>0) + size+=CountAAEffects()*sizeof(AA_Ability); + return size; +} + +void ZoneDatabase::LoadAAs(SendAA_Struct **load){ + if(!load) + return; + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + if (RunQuery(query, MakeAnyLenString(&query, "SELECT skill_id from altadv_vars order by skill_id"), errbuf, &result)) { + int skill=0,ndx=0; + while((row = mysql_fetch_row(result))!=nullptr) { + skill=atoi(row[0]); + load[ndx] = GetAASkillVars(skill); + load[ndx]->seq = ndx+1; + ndx++; + } + mysql_free_result(result); + } else { + LogFile->write(EQEMuLog::Error, "Error in ZoneDatabase::LoadAAs query '%s': %s", query, errbuf); + } + safe_delete_array(query); + + AARequiredLevelAndCost.clear(); + + if (RunQuery(query, MakeAnyLenString(&query, "SELECT skill_id, level, cost from aa_required_level_cost order by skill_id"), errbuf, &result)) + { + AALevelCost_Struct aalcs; + while((row = mysql_fetch_row(result))!=nullptr) + { + aalcs.Level = atoi(row[1]); + aalcs.Cost = atoi(row[2]); + AARequiredLevelAndCost[atoi(row[0])] = aalcs; + } + mysql_free_result(result); + } + else + LogFile->write(EQEMuLog::Error, "Error in ZoneDatabase::LoadAAs query '%s': %s", query, errbuf); + + safe_delete_array(query); +} + +SendAA_Struct* ZoneDatabase::GetAASkillVars(uint32 skill_id) +{ + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + SendAA_Struct* sendaa = nullptr; + uchar* buffer; + if (RunQuery(query, MakeAnyLenString(&query, "SET @row = 0"), errbuf)) { //initialize "row" variable in database for next query + safe_delete_array(query); + MYSQL_RES *result; //we don't really need these unless we get to this point, so why bother? + MYSQL_ROW row; + + if (RunQuery(query, MakeAnyLenString(&query, + "SELECT " + "a.cost, " + "a.max_level, " + "a.hotkey_sid, " + "a.hotkey_sid2, " + "a.title_sid, " + "a.desc_sid, " + "a.type, " + "COALESCE(" //so we can return 0 if it's null + "(" //this is our derived table that has the row # that we can SELECT from, because the client is stupid + "SELECT " + "p.prereq_index_num " + "FROM " + "(" + "SELECT " + "a2.skill_id, " + "@row := @row + 1 AS prereq_index_num " + "FROM " + "altadv_vars a2" + ") AS p " + "WHERE " + "p.skill_id = a.prereq_skill" + "), " + "0) AS prereq_skill_index, " + "a.prereq_minpoints, " + "a.spell_type, " + "a.spell_refresh, " + "a.classes, " + "a.berserker, " + "a.spellid, " + "a.class_type, " + "a.name, " + "a.cost_inc, " + "a.aa_expansion, " + "a.special_category, " + "a.sof_type, " + "a.sof_cost_inc, " + "a.sof_max_level, " + "a.sof_next_skill, " + "a.clientver, " // Client Version 0 = None, 1 = All, 2 = Titanium/6.2, 4 = SoF 5 = SOD 6 = UF + "a.account_time_required, " + "a.sof_current_level," + "a.sof_next_id, " + "a.level_inc, " + "a.seqlive " + " FROM altadv_vars a WHERE skill_id=%i", skill_id), errbuf, &result)) { + safe_delete_array(query); + if (mysql_num_rows(result) == 1) { + int total_abilities = GetTotalAALevels(skill_id); //eventually we'll want to use zone->GetTotalAALevels(skill_id) since it should save queries to the DB + int totalsize = total_abilities * sizeof(AA_Ability) + sizeof(SendAA_Struct); + + buffer = new uchar[totalsize]; + memset(buffer,0,totalsize); + sendaa = (SendAA_Struct*)buffer; + + row = mysql_fetch_row(result); + + //ATOI IS NOT UNISGNED LONG-SAFE!!! + + sendaa->cost = atoul(row[0]); + sendaa->cost2 = sendaa->cost; + sendaa->max_level = atoul(row[1]); + sendaa->hotkey_sid = atoul(row[2]); + sendaa->id = skill_id; + sendaa->hotkey_sid2 = atoul(row[3]); + sendaa->title_sid = atoul(row[4]); + sendaa->desc_sid = atoul(row[5]); + sendaa->type = atoul(row[6]); + sendaa->prereq_skill = atoul(row[7]); + sendaa->prereq_minpoints = atoul(row[8]); + sendaa->spell_type = atoul(row[9]); + sendaa->spell_refresh = atoul(row[10]); + sendaa->classes = static_cast(atoul(row[11])); + sendaa->berserker = static_cast(atoul(row[12])); + sendaa->last_id = 0xFFFFFFFF; + sendaa->current_level=1; + sendaa->spellid = atoul(row[13]); + sendaa->class_type = atoul(row[14]); + strcpy(sendaa->name,row[15]); + + sendaa->total_abilities=total_abilities; + if(sendaa->max_level > 1) + sendaa->next_id=skill_id+1; + else + sendaa->next_id=0xFFFFFFFF; + + sendaa->cost_inc = atoi(row[16]); + // Begin SoF Specific/Adjusted AA Fields + sendaa->aa_expansion = atoul(row[17]); + sendaa->special_category = atoul(row[18]); + sendaa->sof_type = atoul(row[19]); + sendaa->sof_cost_inc = atoi(row[20]); + sendaa->sof_max_level = atoul(row[21]); + sendaa->sof_next_skill = atoul(row[22]); + sendaa->clientver = atoul(row[23]); + sendaa->account_time_required = atoul(row[24]); + //Internal use only - not sent to client + sendaa->sof_current_level = atoul(row[25]); + sendaa->sof_next_id = atoul(row[26]); + sendaa->level_inc = static_cast(atoul(row[27])); + sendaa->seqlive = (atoul(row[28])); + } + mysql_free_result(result); + } else { + LogFile->write(EQEMuLog::Error, "Error in GetAASkillVars '%s': %s", query, errbuf); + safe_delete_array(query); + } + } else { + LogFile->write(EQEMuLog::Error, "Error in GetAASkillVars '%s': %s", query, errbuf); + safe_delete_array(query); + } + return sendaa; +} + +void Client::DurationRampage(uint32 duration) +{ + if(duration) { + m_epp.aa_effects |= 1 << (aaEffectRampage-1); + p_timers.Start(pTimerAAEffectStart + aaEffectRampage, duration); + } +} + +AA_SwarmPetInfo::AA_SwarmPetInfo() +{ + target = 0; + owner_id = 0; + duration = nullptr; +} + +AA_SwarmPetInfo::~AA_SwarmPetInfo() +{ + target = 0; + owner_id = 0; + safe_delete(duration); +} + +Mob *AA_SwarmPetInfo::GetOwner() +{ + return entity_list.GetMobID(owner_id); +} diff --git a/zone/MobAI_M.cpp b/zone/MobAI_M.cpp new file mode 100644 index 000000000..7574f19ad --- /dev/null +++ b/zone/MobAI_M.cpp @@ -0,0 +1,2760 @@ +/* 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/debug.h" +#include +#include +#include +#include +#include +#include "npc.h" +#include "masterentity.h" +#include "NpcAI.h" +#include "map.h" +#include "../common/moremath.h" +#include "StringIDs.h" +#include "../common/MiscFunctions.h" +#include "../common/StringUtil.h" +#include "../common/rulesys.h" +#include "../common/features.h" +#include "QuestParserCollection.h" +#include "watermap.h" + +extern EntityList entity_list; + +extern Zone *zone; + +#ifdef _EQDEBUG + #define MobAI_DEBUG_Spells -1 +#else + #define MobAI_DEBUG_Spells -1 +#endif +#define ABS(x) ((x)<0?-(x):(x)) + +//NOTE: do NOT pass in beneficial and detrimental spell types into the same call here! +bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint16 iSpellTypes) { + if (!tar) + return false; + + if (IsNoCast()) + return false; + + if(AI_HasSpells() == false) + return false; + + if (iChance < 100) { + if (MakeRandomInt(0, 100) >= iChance) + return false; + } + + float dist2; + + if (iSpellTypes & SpellType_Escape) { + dist2 = 0; //DistNoRoot(*this); //WTF was up with this... + } + else + dist2 = DistNoRoot(*tar); + + bool checked_los = false; //we do not check LOS until we are absolutely sure we need to, and we only do it once. + + float manaR = GetManaRatio(); + for (int i = static_cast(AIspells.size()) - 1; i >= 0; i--) { + if (AIspells[i].spellid <= 0 || AIspells[i].spellid >= SPDAT_RECORDS) { + // this is both to quit early to save cpu and to avoid casting bad spells + // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here + //return false; + continue; + } + if (iSpellTypes & AIspells[i].type) { + // manacost has special values, -1 is no mana cost, -2 is instant cast (no mana) + int32 mana_cost = AIspells[i].manacost; + if (mana_cost == -1) + mana_cost = spells[AIspells[i].spellid].mana; + else if (mana_cost == -2) + mana_cost = 0; + if ( + (( + (spells[AIspells[i].spellid].targettype==ST_AECaster || spells[AIspells[i].spellid].targettype==ST_AEBard) + && dist2 <= spells[AIspells[i].spellid].aoerange*spells[AIspells[i].spellid].aoerange + ) || + dist2 <= spells[AIspells[i].spellid].range*spells[AIspells[i].spellid].range + ) + && (mana_cost <= GetMana() || GetMana() == GetMaxMana()) + && (AIspells[i].time_cancast + (MakeRandomInt(0, 4) * 1000)) <= Timer::GetCurrentTime() //break up the spelling casting over a period of time. + ) { + +#if MobAI_DEBUG_Spells >= 21 + std::cout << "Mob::AICastSpell: Casting: spellid=" << AIspells[i].spellid + << ", tar=" << tar->GetName() + << ", dist2[" << dist2 << "]<=" << spells[AIspells[i].spellid].range *spells[AIspells[i].spellid].range + << ", mana_cost[" << mana_cost << "]<=" << GetMana() + << ", cancast[" << AIspells[i].time_cancast << "]<=" << Timer::GetCurrentTime() + << ", type=" << AIspells[i].type << std::endl; +#endif + + switch (AIspells[i].type) { + case SpellType_Heal: { + if ( + (spells[AIspells[i].spellid].targettype == ST_Target || tar == this) + && tar->DontHealMeBefore() < Timer::GetCurrentTime() + && !(tar->IsPet() && tar->GetOwner()->IsClient()) //no buffing PC's pets + ) { + uint8 hpr = (uint8)tar->GetHPRatio(); + + if(hpr <= 35 || (!IsEngaged() && hpr <= 50) || (tar->IsClient() && hpr <= 99)) { + uint32 tempTime = 0; + AIDoSpellCast(i, tar, mana_cost, &tempTime); + tar->SetDontHealMeBefore(tempTime); + return true; + } + } + break; + } + case SpellType_Root: { + Mob *rootee = GetHateRandom(); + if (rootee && !rootee->IsRooted() && MakeRandomInt(0, 99) < 50 + && rootee->DontRootMeBefore() < Timer::GetCurrentTime() + && rootee->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0 + ) { + if(!checked_los) { + if(!CheckLosFN(rootee)) + return(false); //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call + checked_los = true; + } + uint32 tempTime = 0; + AIDoSpellCast(i, rootee, mana_cost, &tempTime); + rootee->SetDontRootMeBefore(tempTime); + return true; + } + break; + } + case SpellType_Buff: { + if ( + (spells[AIspells[i].spellid].targettype == ST_Target || tar == this) + && tar->DontBuffMeBefore() < Timer::GetCurrentTime() + && !tar->IsImmuneToSpell(AIspells[i].spellid, this) + && tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0 + && !(tar->IsPet() && tar->GetOwner()->IsClient() && this != tar) //no buffing PC's pets, but they can buff themself + ) + { + if(!checked_los) { + if(!CheckLosFN(tar)) + return(false); + checked_los = true; + } + uint32 tempTime = 0; + AIDoSpellCast(i, tar, mana_cost, &tempTime); + tar->SetDontBuffMeBefore(tempTime); + return true; + } + break; + } + + case SpellType_InCombatBuff: { + if(MakeRandomInt(0, 99) < 50) + { + AIDoSpellCast(i, tar, mana_cost); + return true; + } + break; + } + + case SpellType_Escape: { + if (GetHPRatio() <= 5 ) + { + AIDoSpellCast(i, tar, mana_cost); + return true; + } + break; + } + case SpellType_Slow: + case SpellType_Debuff: { + Mob * debuffee = GetHateRandom(); + if (debuffee && manaR >= 10 && MakeRandomInt(0, 99 < 70) && + debuffee->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0) { + if (!checked_los) { + if (!CheckLosFN(debuffee)) + return false; + checked_los = true; + } + AIDoSpellCast(i, debuffee, mana_cost); + return true; + } + break; + } + case SpellType_Nuke: { + if ( + manaR >= 10 && MakeRandomInt(0, 99) < 70 + && tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0 + ) { + if(!checked_los) { + if(!CheckLosFN(tar)) + return(false); //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call + checked_los = true; + } + AIDoSpellCast(i, tar, mana_cost); + return true; + } + break; + } + case SpellType_Dispel: { + if(MakeRandomInt(0, 99) < 15) + { + if(!checked_los) { + if(!CheckLosFN(tar)) + return(false); //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call + checked_los = true; + } + if(tar->CountDispellableBuffs() > 0) + { + AIDoSpellCast(i, tar, mana_cost); + return true; + } + } + break; + } + case SpellType_Mez: { + if(MakeRandomInt(0, 99) < 20) + { + Mob * mezTar = nullptr; + mezTar = entity_list.GetTargetForMez(this); + + if(mezTar && mezTar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0) + { + AIDoSpellCast(i, mezTar, mana_cost); + return true; + } + } + break; + } + + case SpellType_Charm: + { + if(!IsPet() && MakeRandomInt(0, 99) < 20) + { + Mob * chrmTar = GetHateRandom(); + if(chrmTar && chrmTar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0) + { + AIDoSpellCast(i, chrmTar, mana_cost); + return true; + } + } + break; + } + + case SpellType_Pet: { + //keep mobs from recasting pets when they have them. + if (!IsPet() && !GetPetID() && MakeRandomInt(0, 99) < 25) { + AIDoSpellCast(i, tar, mana_cost); + return true; + } + break; + } + case SpellType_Lifetap: { + if (GetHPRatio() <= 95 + && MakeRandomInt(0, 99) < 50 + && tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0 + ) { + if(!checked_los) { + if(!CheckLosFN(tar)) + return(false); //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call + checked_los = true; + } + AIDoSpellCast(i, tar, mana_cost); + return true; + } + break; + } + case SpellType_Snare: { + if ( + !tar->IsRooted() + && MakeRandomInt(0, 99) < 50 + && tar->DontSnareMeBefore() < Timer::GetCurrentTime() + && tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0 + ) { + if(!checked_los) { + if(!CheckLosFN(tar)) + return(false); //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call + checked_los = true; + } + uint32 tempTime = 0; + AIDoSpellCast(i, tar, mana_cost, &tempTime); + tar->SetDontSnareMeBefore(tempTime); + return true; + } + break; + } + case SpellType_DOT: { + if ( + MakeRandomInt(0, 99) < 60 + && tar->DontDotMeBefore() < Timer::GetCurrentTime() + && tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0 + ) { + if(!checked_los) { + if(!CheckLosFN(tar)) + return(false); //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call + checked_los = true; + } + uint32 tempTime = 0; + AIDoSpellCast(i, tar, mana_cost, &tempTime); + tar->SetDontDotMeBefore(tempTime); + return true; + } + break; + } + default: { + std::cout << "Error: Unknown spell type in AICastSpell. caster:" << this->GetName() << " type:" << AIspells[i].type << " slot:" << i << std::endl; + break; + } + } + } +#if MobAI_DEBUG_Spells >= 21 + else { + std::cout << "Mob::AICastSpell: NotCasting: spellid=" << AIspells[i].spellid << ", tar=" << tar->GetName() << ", dist2[" << dist2 << "]<=" << spells[AIspells[i].spellid].range*spells[AIspells[i].spellid].range << ", mana_cost[" << mana_cost << "]<=" << GetMana() << ", cancast[" << AIspells[i].time_cancast << "]<=" << Timer::GetCurrentTime() << std::endl; + } +#endif + } + } + return false; +} + +bool NPC::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore) { +#if MobAI_DEBUG_Spells >= 1 + std::cout << "Mob::AIDoSpellCast: spellid=" << AIspells[i].spellid << ", tar=" << tar->GetName() << ", mana=" << mana_cost << ", Name: " << spells[AIspells[i].spellid].name << std::endl; +#endif + casting_spell_AIindex = i; + + //stop moving if were casting a spell and were not a bard... + if(!IsBardSong(AIspells[i].spellid)) { + SetRunAnimSpeed(0); + SendPosition(); + SetMoving(false); + } + + return CastSpell(AIspells[i].spellid, tar->GetID(), 1, AIspells[i].manacost == -2 ? 0 : -1, mana_cost, oDontDoAgainBefore, -1, -1, 0, 0, &(AIspells[i].resist_adjust)); +} + +bool EntityList::AICheckCloseBeneficialSpells(NPC* caster, uint8 iChance, float iRange, uint16 iSpellTypes) { + if((iSpellTypes&SpellTypes_Detrimental) != 0) { + //according to live, you can buff and heal through walls... + //now with PCs, this only applies if you can TARGET the target, but + // according to Rogean, Live NPCs will just cast through walls/floors, no problem.. + // + // This check was put in to address an idle-mob CPU issue + _log(AI__ERROR, "Error: detrimental spells requested from AICheckCloseBeneficialSpells!!"); + return(false); + } + + if(!caster) + return false; + + if(caster->AI_HasSpells() == false) + return false; + + if(caster->GetSpecialAbility(NPC_NO_BUFFHEAL_FRIENDS)) + return false; + + if (iChance < 100) { + uint8 tmp = MakeRandomInt(0, 99); + if (tmp >= iChance) + return false; + } + if (caster->GetPrimaryFaction() == 0 ) + return(false); // well, if we dont have a faction set, we're gonna be indiff to everybody + + float iRange2 = iRange*iRange; + + float t1, t2, t3; + + + //Only iterate through NPCs + for (auto it = npc_list.begin(); it != npc_list.end(); ++it) { + NPC* mob = it->second; + + //Since >90% of mobs will always be out of range, try to + //catch them with simple bounding box checks first. These + //checks are about 6X faster than DistNoRoot on my athlon 1Ghz + t1 = mob->GetX() - caster->GetX(); + t2 = mob->GetY() - caster->GetY(); + t3 = mob->GetZ() - caster->GetZ(); + //cheap ABS() + if(t1 < 0) + t1 = 0 - t1; + if(t2 < 0) + t2 = 0 - t2; + if(t3 < 0) + t3 = 0 - t3; + if (t1 > iRange + || t2 > iRange + || t3 > iRange + || mob->DistNoRoot(*caster) > iRange2 + //this call should seem backwards: + || !mob->CheckLosFN(caster) + || mob->GetReverseFactionCon(caster) >= FACTION_KINDLY + ) { + continue; + } + + //since we assume these are beneficial spells, which do not + //require LOS, we just go for it. + // we have a winner! + if((iSpellTypes & SpellType_Buff) && !RuleB(NPC, BuffFriends)){ + if (mob != caster) + iSpellTypes = SpellType_Heal; + } + + if (caster->AICastSpell(mob, 100, iSpellTypes)) + return true; + } + return false; +} + +void Mob::AI_Init() { + pAIControlled = false; + AIthink_timer = 0; + AIwalking_timer = 0; + AImovement_timer = 0; + AItarget_check_timer = 0; + AIfeignremember_timer = nullptr; + AIscanarea_timer = 0; + minLastFightingDelayMoving = RuleI(NPC, LastFightingDelayMovingMin); + maxLastFightingDelayMoving = RuleI(NPC, LastFightingDelayMovingMax); + + pDontHealMeBefore = 0; + pDontBuffMeBefore = 0; + pDontDotMeBefore = 0; + pDontRootMeBefore = 0; + pDontSnareMeBefore = 0; + pDontCureMeBefore = 0; +} + +void NPC::AI_Init() { + Mob::AI_Init(); + + AIautocastspell_timer = 0; + casting_spell_AIindex = static_cast(AIspells.size()); + + roambox_max_x = 0; + roambox_max_y = 0; + roambox_min_x = 0; + roambox_min_y = 0; + roambox_distance = 0; + roambox_movingto_x = 0; + roambox_movingto_y = 0; + roambox_min_delay = 2500; + roambox_delay = 2500; +} + +void Client::AI_Init() { + Mob::AI_Init(); + minLastFightingDelayMoving = CLIENT_LD_TIMEOUT; + maxLastFightingDelayMoving = CLIENT_LD_TIMEOUT; +} + +void Mob::AI_Start(uint32 iMoveDelay) { + if (pAIControlled) + return; + + if (iMoveDelay) + pLastFightingDelayMoving = Timer::GetCurrentTime() + iMoveDelay; + else + pLastFightingDelayMoving = 0; + + pAIControlled = true; + AIthink_timer = new Timer(AIthink_duration); + AIthink_timer->Trigger(); + AIwalking_timer = new Timer(0); + AImovement_timer = new Timer(AImovement_duration); + AItarget_check_timer = new Timer(AItarget_check_duration); + AIfeignremember_timer = new Timer(AIfeignremember_delay); + AIscanarea_timer = new Timer(AIscanarea_delay); +#ifdef REVERSE_AGGRO + if(IsNPC() && !CastToNPC()->WillAggroNPCs()) + AIscanarea_timer->Disable(); +#endif + + if (GetAggroRange() == 0) + pAggroRange = 70; + if (GetAssistRange() == 0) + pAssistRange = 70; + hate_list.Wipe(); + + delta_heading = 0; + delta_x = 0; + delta_y = 0; + delta_z = 0; + pRunAnimSpeed = 0; + pLastChange = Timer::GetCurrentTime(); +} + +void Client::AI_Start(uint32 iMoveDelay) { + Mob::AI_Start(iMoveDelay); + + if (!pAIControlled) + return; + + pClientSideTarget = GetTarget() ? GetTarget()->GetID() : 0; + SendAppearancePacket(AT_Anim, ANIM_FREEZE); // this freezes the client + SendAppearancePacket(AT_Linkdead, 1); // Sending LD packet so *LD* appears by the player name when charmed/feared -Kasai + SetAttackTimer(); + SetFeigned(false); +} + +void NPC::AI_Start(uint32 iMoveDelay) { + Mob::AI_Start(iMoveDelay); + if (!pAIControlled) + return; + + if (AIspells.size() == 0) { + AIautocastspell_timer = new Timer(1000); + AIautocastspell_timer->Disable(); + } else { + AIautocastspell_timer = new Timer(750); + AIautocastspell_timer->Start(RandomTimer(0, 15000), false); + } + + if (NPCTypedata) { + AI_AddNPCSpells(NPCTypedata->npc_spells_id); + ProcessSpecialAbilities(NPCTypedata->special_abilities); + AI_AddNPCSpellsEffects(NPCTypedata->npc_spells_effects_id); + } + + SendTo(GetX(), GetY(), GetZ()); + SetChanged(); + SaveGuardSpot(); +} + +void Mob::AI_Stop() { + if (!IsAIControlled()) + return; + pAIControlled = false; + safe_delete(AIthink_timer); + safe_delete(AIwalking_timer); + safe_delete(AImovement_timer); + safe_delete(AItarget_check_timer) + safe_delete(AIscanarea_timer); + safe_delete(AIfeignremember_timer); + hate_list.Wipe(); +} + +void NPC::AI_Stop() { + Waypoints.clear(); + safe_delete(AIautocastspell_timer); +} + +void Client::AI_Stop() { + Mob::AI_Stop(); + this->Message_StringID(13,PLAYER_REGAIN); + + EQApplicationPacket *app = new EQApplicationPacket(OP_Charm, sizeof(Charm_Struct)); + Charm_Struct *ps = (Charm_Struct*)app->pBuffer; + ps->owner_id = 0; + ps->pet_id = this->GetID(); + ps->command = 0; + entity_list.QueueClients(this, app); + safe_delete(app); + + SetTarget(entity_list.GetMob(pClientSideTarget)); + SendAppearancePacket(AT_Anim, GetAppearanceValue(GetAppearance())); + SendAppearancePacket(AT_Linkdead, 0); // Removing LD packet so *LD* no longer appears by the player name when charmed/feared -Kasai + if (!auto_attack) { + attack_timer.Disable(); + attack_dw_timer.Disable(); + } + if (IsLD()) + { + Save(); + Disconnect(); + } +} + +//todo: expand the logic here to cover: +//redundant debuffs +//buffing owner +//certain types of det spells that need special behavior. +void Client::AI_SpellCast() +{ + if(!charm_cast_timer.Check()) + return; + + Mob *targ = GetTarget(); + if(!targ) + return; + + float dist = DistNoRootNoZ(*targ); + + std::vector valid_spells; + std::vector slots; + + for(uint32 x = 0; x < 9; ++x) + { + uint32 current_spell = m_pp.mem_spells[x]; + if(!IsValidSpell(current_spell)) + continue; + + if(IsBeneficialSpell(current_spell)) + { + continue; + } + + if(dist > spells[current_spell].range*spells[current_spell].range) + { + continue; + } + + if(GetMana() < spells[current_spell].mana) + { + continue; + } + + if(IsEffectInSpell(current_spell, SE_Charm)) + { + continue; + } + + if(!GetPTimers().Expired(&database, pTimerSpellStart + current_spell, false)) + { + continue; + } + + if(targ->CanBuffStack(current_spell, GetLevel(), true) < 0) + { + continue; + } + + //bard songs cause trouble atm + if(IsBardSong(current_spell)) + continue; + + valid_spells.push_back(current_spell); + slots.push_back(x); + } + + uint32 spell_to_cast = 0xFFFFFFFF; + uint32 slot_to_use = 10; + if(valid_spells.size() == 1) + { + spell_to_cast = valid_spells[0]; + slot_to_use = slots[0]; + } + else if(valid_spells.size() == 0) + { + return; + } + else + { + uint32 idx = MakeRandomInt(0, (valid_spells.size()-1)); + spell_to_cast = valid_spells[idx]; + slot_to_use = slots[idx]; + } + + if(IsMezSpell(spell_to_cast) || IsFearSpell(spell_to_cast)) + { + Mob *tar = entity_list.GetTargetForMez(this); + if(!tar) + { + tar = GetTarget(); + if(tar && IsFearSpell(spell_to_cast)) + { + if(!IsBardSong(spell_to_cast)) + { + SetRunAnimSpeed(0); + SendPosition(); + SetMoving(false); + } + CastSpell(spell_to_cast, tar->GetID(), slot_to_use); + return; + } + } + } + else + { + Mob *tar = GetTarget(); + if(tar) + { + if(!IsBardSong(spell_to_cast)) + { + SetRunAnimSpeed(0); + SendPosition(); + SetMoving(false); + } + CastSpell(spell_to_cast, tar->GetID(), slot_to_use); + return; + } + } + + +} + +void Client::AI_Process() +{ + if (!IsAIControlled()) + return; + + if (!(AIthink_timer->Check() || attack_timer.Check(false))) + return; + + if (IsCasting()) + return; + + bool engaged = IsEngaged(); + + Mob *ow = GetOwner(); + if(!engaged) + { + if(ow) + { + if(ow->IsEngaged()) + { + Mob *tar = ow->GetTarget(); + if(tar) + { + AddToHateList(tar, 1, 0, false); + } + } + } + } + + if(!ow) + { + if(!IsFeared() && !IsLD()) + { + BuffFadeByEffect(SE_Charm); + return; + } + } + + if(RuleB(Combat, EnableFearPathing)){ + if(curfp) { + if(IsRooted()) { + //make sure everybody knows were not moving, for appearance sake + if(IsMoving()) + { + if(GetTarget()) + SetHeading(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY())); + SetRunAnimSpeed(0); + SendPosition(); + SetMoving(false); + moved=false; + } + //continue on to attack code, ensuring that we execute the engaged code + engaged = true; + } else { + if(AImovement_timer->Check()) { + animation = GetRunspeed() * 21; + // Check if we have reached the last fear point + if((ABS(GetX()-fear_walkto_x) < 0.1) && (ABS(GetY()-fear_walkto_y) <0.1)) { + // Calculate a new point to run to + CalculateNewFearpoint(); + } + if(!RuleB(Pathing, Fear) || !zone->pathing) + CalculateNewPosition2(fear_walkto_x, fear_walkto_y, fear_walkto_z, GetFearSpeed(), true); + else + { + bool WaypointChanged, NodeReached; + + VERTEX Goal = UpdatePath(fear_walkto_x, fear_walkto_y, fear_walkto_z, + GetFearSpeed(), WaypointChanged, NodeReached); + + if(WaypointChanged) + tar_ndx = 20; + + CalculateNewPosition2(Goal.x, Goal.y, Goal.z, GetFearSpeed()); + } + } + return; + } + } + } + + if (engaged) + { + if (IsRooted()) + SetTarget(hate_list.GetClosest(this)); + else + { + if(AItarget_check_timer->Check()) + { + SetTarget(hate_list.GetTop(this)); + } + } + + if (!GetTarget()) + return; + + if (GetTarget()->IsCorpse()) { + RemoveFromHateList(this); + return; + } + + if(DivineAura()) + return; + + bool is_combat_range = CombatRange(GetTarget()); + + if(is_combat_range) { + if(charm_class_attacks_timer.Check()) { + DoClassAttacks(GetTarget()); + } + + if (AImovement_timer->Check()) { + SetRunAnimSpeed(0); + } + if(IsMoving()) { + SetMoving(false); + moved=false; + SetHeading(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY())); + SendPosition(); + tar_ndx =0; + } + + if(GetTarget() && !IsStunned() && !IsMezzed() && !GetFeigned()) { + if(attack_timer.Check()) { + Attack(GetTarget(), 13); + if(GetTarget()) { + if(CheckDoubleAttack()) { + Attack(GetTarget(), 13); + if(GetTarget()) { + bool triple_attack_success = false; + if((((GetClass() == MONK || GetClass() == WARRIOR || GetClass() == RANGER || GetClass() == BERSERKER) + && GetLevel() >= 60) || GetSpecialAbility(SPECATK_TRIPLE)) + && CheckDoubleAttack(true)) + { + Attack(GetTarget(), 13, true); + triple_attack_success = true; + } + + if(GetTarget()) + { + //Live AA - Flurry, Rapid Strikes ect (Flurry does not require Triple Attack). + int16 flurrychance = aabonuses.FlurryChance + spellbonuses.FlurryChance + itembonuses.FlurryChance; + + if (flurrychance) + { + if(MakeRandomInt(0, 100) < flurrychance) + { + Message_StringID(MT_NPCFlurry, YOU_FLURRY); + Attack(GetTarget(), 13, false); + Attack(GetTarget(), 13, false); + } + } + + int16 ExtraAttackChanceBonus = spellbonuses.ExtraAttackChance + itembonuses.ExtraAttackChance + aabonuses.ExtraAttackChance; + + if (ExtraAttackChanceBonus && GetTarget()) { + ItemInst *wpn = GetInv().GetItem(SLOT_PRIMARY); + if(wpn){ + if(wpn->GetItem()->ItemType == ItemType2HSlash || + wpn->GetItem()->ItemType == ItemType2HBlunt || + wpn->GetItem()->ItemType == ItemType2HPiercing ) + { + if(MakeRandomInt(0, 100) < ExtraAttackChanceBonus) + { + Attack(GetTarget(), 13, false); + } + } + } + } + + if (GetClass() == WARRIOR || GetClass() == BERSERKER) + { + if(!dead && !berserk && this->GetHPRatio() < 30) + { + entity_list.MessageClose_StringID(this, false, 200, 0, BERSERK_START, GetName()); + berserk = true; + } + else if (berserk && this->GetHPRatio() > 30) + { + entity_list.MessageClose_StringID(this, false, 200, 0, BERSERK_END, GetName()); + berserk = false; + } + } + } + } + } + } + } + } + + if(CanThisClassDualWield() && attack_dw_timer.Check()) + { + if(GetTarget()) + { + float DualWieldProbability = 0.0f; + + int16 Ambidexterity = aabonuses.Ambidexterity + spellbonuses.Ambidexterity + itembonuses.Ambidexterity; + DualWieldProbability = (GetSkill(SkillDualWield) + GetLevel() + Ambidexterity) / 400.0f; // 78.0 max + int16 DWBonus = spellbonuses.DualWieldChance + itembonuses.DualWieldChance; + DualWieldProbability += DualWieldProbability*float(DWBonus)/ 100.0f; + + if(MakeRandomFloat(0.0, 1.0) < DualWieldProbability) + { + Attack(GetTarget(), 14); + if(CheckDoubleAttack()) + { + Attack(GetTarget(), 14); + } + + } + } + } + } + else + { + if(!IsRooted()) + { + animation = 21 * GetRunspeed(); + if(!RuleB(Pathing, Aggro) || !zone->pathing) + CalculateNewPosition2(GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ(), GetRunspeed()); + else + { + bool WaypointChanged, NodeReached; + VERTEX Goal = UpdatePath(GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ(), + GetRunspeed(), WaypointChanged, NodeReached); + + if(WaypointChanged) + tar_ndx = 20; + + CalculateNewPosition2(Goal.x, Goal.y, Goal.z, GetRunspeed()); + } + } + else if(IsMoving()) + { + SetHeading(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY())); + SetRunAnimSpeed(0); + SendPosition(); + SetMoving(false); + moved=false; + } + } + AI_SpellCast(); + } + else + { + if(AIfeignremember_timer->Check()) { + std::set::iterator RememberedCharID; + RememberedCharID = feign_memory_list.begin(); + while (RememberedCharID != feign_memory_list.end()) { + Client* remember_client = entity_list.GetClientByCharID(*RememberedCharID); + if (remember_client == nullptr) { + //they are gone now... + RememberedCharID = feign_memory_list.erase(RememberedCharID); + } else if (!remember_client->GetFeigned()) { + AddToHateList(remember_client->CastToMob(),1); + RememberedCharID = feign_memory_list.erase(RememberedCharID); + break; + } else { + //they are still feigned, carry on... + ++RememberedCharID; + } + } + } + + if(IsPet()) + { + Mob* owner = GetOwner(); + if(owner == nullptr) + return; + + float dist = DistNoRoot(*owner); + if (dist >= 100) + { + float speed = dist >= 225 ? GetRunspeed() : GetWalkspeed(); + animation = 21 * speed; + CalculateNewPosition2(owner->GetX(), owner->GetY(), owner->GetZ(), speed); + } + else + { + SetHeading(owner->GetHeading()); + if(moved) + { + moved=false; + SetMoving(false); + SendPosition(); + SetRunAnimSpeed(0); + } + } + } + } +} + +void Mob::AI_Process() { + if (!IsAIControlled()) + return; + + if (!(AIthink_timer->Check() || attack_timer.Check(false))) + return; + + if (IsCasting()) + return; + + bool engaged = IsEngaged(); + bool doranged = false; + + // Begin: Additions for Wiz Fear Code + // + if(RuleB(Combat, EnableFearPathing)){ + if(curfp) { + if(IsRooted()) { + //make sure everybody knows were not moving, for appearance sake + if(IsMoving()) + { + if(target) + SetHeading(CalculateHeadingToTarget(target->GetX(), target->GetY())); + SetRunAnimSpeed(0); + SendPosition(); + SetMoving(false); + moved=false; + } + //continue on to attack code, ensuring that we execute the engaged code + engaged = true; + } else { + if(AImovement_timer->Check()) { + // Check if we have reached the last fear point + if((ABS(GetX()-fear_walkto_x) < 0.1) && (ABS(GetY()-fear_walkto_y) <0.1)) { + // Calculate a new point to run to + CalculateNewFearpoint(); + } + if(!RuleB(Pathing, Fear) || !zone->pathing) + CalculateNewPosition2(fear_walkto_x, fear_walkto_y, fear_walkto_z, GetFearSpeed(), true); + else + { + bool WaypointChanged, NodeReached; + + VERTEX Goal = UpdatePath(fear_walkto_x, fear_walkto_y, fear_walkto_z, + GetFearSpeed(), WaypointChanged, NodeReached); + + if(WaypointChanged) + tar_ndx = 20; + + CalculateNewPosition2(Goal.x, Goal.y, Goal.z, GetFearSpeed()); + } + } + return; + } + } + } + + // trigger EVENT_SIGNAL if required + if(IsNPC()) { + CastToNPC()->CheckSignal(); + } + + if (engaged) + { + if (IsRooted()) + SetTarget(hate_list.GetClosest(this)); + else + { + if(AItarget_check_timer->Check()) + { + if (IsFocused()) { + if (!target) { + SetTarget(hate_list.GetTop(this)); + } + } else { + if (!ImprovedTaunt()) + SetTarget(hate_list.GetTop(this)); + } + + } + } + + if (!target) + return; + + if (target->IsCorpse()) + { + RemoveFromHateList(this); + return; + } + +#ifdef BOTS + if (IsPet() && GetOwner()->IsBot() && target == GetOwner()) + { + // this blocks all pet attacks against owner..bot pet test (copied above check) + RemoveFromHateList(this); + return; + } +#endif //BOTS + + if(DivineAura()) + return; + + if(GetSpecialAbility(TETHER)) { + float tether_range = static_cast(GetSpecialAbilityParam(TETHER, 0)); + tether_range = tether_range > 0.0f ? tether_range * tether_range : pAggroRange * pAggroRange; + + if(DistNoRootNoZ(CastToNPC()->GetSpawnPointX(), CastToNPC()->GetSpawnPointY()) > tether_range) { + GMMove(CastToNPC()->GetSpawnPointX(), CastToNPC()->GetSpawnPointY(), CastToNPC()->GetSpawnPointZ(), CastToNPC()->GetSpawnPointH()); + } + } else if(GetSpecialAbility(LEASH)) { + float leash_range = static_cast(GetSpecialAbilityParam(LEASH, 0)); + leash_range = leash_range > 0.0f ? leash_range * leash_range : pAggroRange * pAggroRange; + + if(DistNoRootNoZ(CastToNPC()->GetSpawnPointX(), CastToNPC()->GetSpawnPointY()) > leash_range) { + GMMove(CastToNPC()->GetSpawnPointX(), CastToNPC()->GetSpawnPointY(), CastToNPC()->GetSpawnPointZ(), CastToNPC()->GetSpawnPointH()); + SetHP(GetMaxHP()); + BuffFadeAll(); + WipeHateList(); + return; + } + } + + StartEnrage(); + + bool is_combat_range = CombatRange(target); + + if (is_combat_range) + { + if (AImovement_timer->Check()) + { + SetRunAnimSpeed(0); + } + if(IsMoving()) + { + SetMoving(false); + moved=false; + SetHeading(CalculateHeadingToTarget(target->GetX(), target->GetY())); + SendPosition(); + tar_ndx =0; + } + + //casting checked above... + if(target && !IsStunned() && !IsMezzed() && GetAppearance() != eaDead && !IsMeleeDisabled()) { + + //we should check to see if they die mid-attacks, previous + //crap of checking target for null was not gunna cut it + + //try main hand first + if(attack_timer.Check()) { + if(IsNPC()) { + int16 n_atk = CastToNPC()->GetNumberOfAttacks(); + if(n_atk <= 1) { + Attack(target, 13); + } else { + for(int i = 0; i < n_atk; ++i) { + Attack(target, 13); + } + } + } else { + Attack(target, 13); + } + + if (target) { + //we use this random value in three comparisons with different + //thresholds, and if its truely random, then this should work + //out reasonably and will save us compute resources. + int32 RandRoll = MakeRandomInt(0, 99); + if ((CanThisClassDoubleAttack() || GetSpecialAbility(SPECATK_TRIPLE) + || GetSpecialAbility(SPECATK_QUAD)) + //check double attack, this is NOT the same rules that clients use... + && RandRoll < (GetLevel() + NPCDualAttackModifier)) { + Attack(target, 13); + // lets see if we can do a triple attack with the main hand + //pets are excluded from triple and quads... + if ((GetSpecialAbility(SPECATK_TRIPLE) || GetSpecialAbility(SPECATK_QUAD)) + && !IsPet() && RandRoll < (GetLevel() + NPCTripleAttackModifier)) { + Attack(target, 13); + // now lets check the quad attack + if (GetSpecialAbility(SPECATK_QUAD) + && RandRoll < (GetLevel() + NPCQuadAttackModifier)) { + Attack(target, 13); + } + } + } + } + + if (GetSpecialAbility(SPECATK_FLURRY)) { + int flurry_chance = GetSpecialAbilityParam(SPECATK_FLURRY, 0); + flurry_chance = flurry_chance > 0 ? flurry_chance : RuleI(Combat, NPCFlurryChance); + + if (MakeRandomInt(0, 99) < flurry_chance) { + ExtraAttackOptions opts; + int cur = GetSpecialAbilityParam(SPECATK_FLURRY, 2); + if (cur > 0) + opts.damage_percent = cur / 100.0f; + + cur = GetSpecialAbilityParam(SPECATK_FLURRY, 3); + if (cur > 0) + opts.damage_flat = cur; + + cur = GetSpecialAbilityParam(SPECATK_FLURRY, 4); + if (cur > 0) + opts.armor_pen_percent = cur / 100.0f; + + cur = GetSpecialAbilityParam(SPECATK_FLURRY, 5); + if (cur > 0) + opts.armor_pen_flat = cur; + + cur = GetSpecialAbilityParam(SPECATK_FLURRY, 6); + if (cur > 0) + opts.crit_percent = cur / 100.0f; + + cur = GetSpecialAbilityParam(SPECATK_FLURRY, 7); + if (cur > 0) + opts.crit_flat = cur; + + Flurry(&opts); + } + } + + if (IsPet()) { + Mob *owner = GetOwner(); + if (owner) { + int16 flurry_chance = owner->aabonuses.PetFlurry + + owner->spellbonuses.PetFlurry + owner->itembonuses.PetFlurry; + if (flurry_chance && (MakeRandomInt(0, 99) < flurry_chance)) + Flurry(nullptr); + } + } + + if (GetSpecialAbility(SPECATK_RAMPAGE)) + { + int rampage_chance = GetSpecialAbilityParam(SPECATK_RAMPAGE, 0); + rampage_chance = rampage_chance > 0 ? rampage_chance : 20; + if(MakeRandomInt(0, 99) < rampage_chance) { + ExtraAttackOptions opts; + int cur = GetSpecialAbilityParam(SPECATK_RAMPAGE, 2); + if(cur > 0) { + opts.damage_percent = cur / 100.0f; + } + + cur = GetSpecialAbilityParam(SPECATK_RAMPAGE, 3); + if(cur > 0) { + opts.damage_flat = cur; + } + + cur = GetSpecialAbilityParam(SPECATK_RAMPAGE, 4); + if(cur > 0) { + opts.armor_pen_percent = cur / 100.0f; + } + + cur = GetSpecialAbilityParam(SPECATK_RAMPAGE, 5); + if(cur > 0) { + opts.armor_pen_flat = cur; + } + + cur = GetSpecialAbilityParam(SPECATK_RAMPAGE, 6); + if(cur > 0) { + opts.crit_percent = cur / 100.0f; + } + + cur = GetSpecialAbilityParam(SPECATK_RAMPAGE, 7); + if(cur > 0) { + opts.crit_flat = cur; + } + Rampage(&opts); + } + } + + if (GetSpecialAbility(SPECATK_AREA_RAMPAGE)) + { + int rampage_chance = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 0); + rampage_chance = rampage_chance > 0 ? rampage_chance : 20; + if(MakeRandomInt(0, 99) < rampage_chance) { + ExtraAttackOptions opts; + int cur = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 2); + if(cur > 0) { + opts.damage_percent = cur / 100.0f; + } + + cur = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 3); + if(cur > 0) { + opts.damage_flat = cur; + } + + cur = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 4); + if(cur > 0) { + opts.armor_pen_percent = cur / 100.0f; + } + + cur = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 5); + if(cur > 0) { + opts.armor_pen_flat = cur; + } + + cur = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 6); + if(cur > 0) { + opts.crit_percent = cur / 100.0f; + } + + cur = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 7); + if(cur > 0) { + opts.crit_flat = cur; + } + + AreaRampage(&opts); + } + } + } + + //now off hand + if (attack_dw_timer.Check() && CanThisClassDualWield()) + { + int myclass = GetClass(); + //can only dual wield without a weapon if your a monk + if(GetSpecialAbility(SPECATK_INNATE_DW) || (GetEquipment(MaterialSecondary) != 0 && GetLevel() > 29) || myclass == MONK || myclass == MONKGM) { + float DualWieldProbability = (GetSkill(SkillDualWield) + GetLevel()) / 400.0f; + if(MakeRandomFloat(0.0, 1.0) < DualWieldProbability) + { + Attack(target, 14); + if (CanThisClassDoubleAttack()) + { + int32 RandRoll = MakeRandomInt(0, 99); + if (RandRoll < (GetLevel() + 20)) + { + Attack(target, 14); + } + } + } + } + } + + //now special attacks (kick, etc) + if(IsNPC()) + CastToNPC()->DoClassAttacks(target); + } + AI_EngagedCastCheck(); + } //end is within combat range + else { + //we cannot reach our target... + //underwater stuff only works with water maps in the zone! + if(IsNPC() && CastToNPC()->IsUnderwaterOnly() && zone->HasWaterMap()) { + if(!zone->watermap->InLiquid(target->GetX(), target->GetY(), target->GetZ())) { + Mob *tar = hate_list.GetTop(this); + if(tar == target) { + WipeHateList(); + Heal(); + BuffFadeAll(); + AIwalking_timer->Start(100); + pLastFightingDelayMoving = Timer::GetCurrentTime(); + return; + } else if(tar != nullptr) { + SetTarget(tar); + return; + } + } + } + + // See if we can summon the mob to us + if (!HateSummon()) + { + //could not summon them, check ranged... + if(GetSpecialAbility(SPECATK_RANGED_ATK)) + doranged = true; + + // Now pursue + // TODO: Check here for another person on hate list with close hate value + if(AI_PursueCastCheck()){ + //we did something, so do not process movement. + } + else if (AImovement_timer->Check()) + { + if(!IsRooted()) { + mlog(AI__WAYPOINTS, "Pursuing %s while engaged.", target->GetName()); + if(!RuleB(Pathing, Aggro) || !zone->pathing) + CalculateNewPosition2(target->GetX(), target->GetY(), target->GetZ(), GetRunspeed()); + else + { + bool WaypointChanged, NodeReached; + + VERTEX Goal = UpdatePath(target->GetX(), target->GetY(), target->GetZ(), + GetRunspeed(), WaypointChanged, NodeReached); + + if(WaypointChanged) + tar_ndx = 20; + + CalculateNewPosition2(Goal.x, Goal.y, Goal.z, GetRunspeed()); + } + + } + else if(IsMoving()) { + SetHeading(CalculateHeadingToTarget(target->GetX(), target->GetY())); + SetRunAnimSpeed(0); + SendPosition(); + SetMoving(false); + moved=false; + + } + } + } + } + } + else + { + if(AIfeignremember_timer->Check()) { + // EverHood - 6/14/06 + // Improved Feign Death Memory + // check to see if any of our previous feigned targets have gotten up. + std::set::iterator RememberedCharID; + RememberedCharID = feign_memory_list.begin(); + while (RememberedCharID != feign_memory_list.end()) { + Client* remember_client = entity_list.GetClientByCharID(*RememberedCharID); + if (remember_client == nullptr) { + //they are gone now... + RememberedCharID = feign_memory_list.erase(RememberedCharID); + } else if (!remember_client->GetFeigned()) { + AddToHateList(remember_client->CastToMob(),1); + RememberedCharID = feign_memory_list.erase(RememberedCharID); + break; + } else { + //they are still feigned, carry on... + ++RememberedCharID; + } + } + } + if (AI_IdleCastCheck()) + { + //we processed a spell action, so do nothing else. + } + else if (AIscanarea_timer->Check()) + { + /* + * This is where NPCs look around to see if they want to attack anybody. + * + * if REVERSE_AGGRO is enabled, then this timer is disabled unless they + * have the npc_aggro flag on them, and aggro against clients is checked + * by the clients. + * + */ + + Mob* tmptar = entity_list.AICheckCloseAggro(this, GetAggroRange(), GetAssistRange()); + if (tmptar) + AddToHateList(tmptar); + } + else if (AImovement_timer->Check() && !IsRooted()) + { + SetRunAnimSpeed(0); + if (IsPet()) + { + // we're a pet, do as we're told + switch (pStandingPetOrder) + { + case SPO_Follow: + { + + Mob* owner = GetOwner(); + if(owner == nullptr) + break; + + //if(owner->IsClient()) + // printf("Pet start pos: (%f, %f, %f)\n", GetX(), GetY(), GetZ()); + + float dist = DistNoRoot(*owner); + if (dist >= 400) + { + float speed = GetWalkspeed(); + if (dist >= 5625) + speed = GetRunspeed(); + CalculateNewPosition2(owner->GetX(), owner->GetY(), owner->GetZ(), speed); + } + else + { + if(moved) + { + moved=false; + SetMoving(false); + SendPosition(); + } + } + + /* + //fix up Z + float zdiff = GetZ() - owner->GetZ(); + if(zdiff < 0) + zdiff = 0 - zdiff; + if(zdiff > 2.0f) { + SendTo(GetX(), GetY(), owner->GetZ()); + SendPosition(); + } + + if(owner->IsClient()) + printf("Pet pos: (%f, %f, %f)\n", GetX(), GetY(), GetZ()); + */ + + break; + } + case SPO_Sit: + { + SetAppearance(eaSitting, false); + break; + } + case SPO_Guard: + { + //only NPCs can guard stuff. (forced by where the guard movement code is in the AI) + if(IsNPC()) { + CastToNPC()->NextGuardPosition(); + } + break; + } + } + } + else if (GetFollowID()) + { + Mob* follow = entity_list.GetMob(GetFollowID()); + if (!follow) SetFollowID(0); + else + { + float dist2 = DistNoRoot(*follow); + int followdist = GetFollowDistance(); + + if (dist2 >= followdist) // Default follow distance is 100 + { + float speed = GetWalkspeed(); + if (dist2 >= followdist + 150) + speed = GetRunspeed(); + CalculateNewPosition2(follow->GetX(), follow->GetY(), follow->GetZ(), speed); + } + else + { + if(moved) + { + SendPosition(); + moved=false; + SetMoving(false); + } + } + } + } + else //not a pet, and not following somebody... + { + // dont move till a bit after you last fought + if (pLastFightingDelayMoving < Timer::GetCurrentTime()) + { + if (this->IsClient()) + { + // LD timer expired, drop out of world + if (this->CastToClient()->IsLD()) + this->CastToClient()->Disconnect(); + return; + } + + if(IsNPC()) + { + if(RuleB(NPC, SmartLastFightingDelayMoving) && !feign_memory_list.empty()) + { + minLastFightingDelayMoving = 0; + maxLastFightingDelayMoving = 0; + } + CastToNPC()->AI_DoMovement(); + } + } + + } + } // else if (AImovement_timer->Check()) + } + + //Do Ranged attack here + if(doranged) + { + int attacks = GetSpecialAbilityParam(SPECATK_RANGED_ATK, 0); + attacks = attacks > 0 ? attacks : 1; + for(int i = 0; i < attacks; ++i) { + RangedAttack(target); + } + } +} + +void NPC::AI_DoMovement() { + float walksp = GetMovespeed(); + if(walksp <= 0.0f) + return; //this is idle movement at walk speed, and we are unable to walk right now. + + if (roambox_distance > 0) { + if ( + roambox_movingto_x > roambox_max_x + || roambox_movingto_x < roambox_min_x + || roambox_movingto_y > roambox_max_y + || roambox_movingto_y < roambox_min_y + ) + { + float movedist = roambox_distance*roambox_distance; + float movex = MakeRandomFloat(0, movedist); + float movey = movedist - movex; + movex = sqrtf(movex); + movey = sqrtf(movey); + movex *= MakeRandomInt(0, 1) ? 1 : -1; + movey *= MakeRandomInt(0, 1) ? 1 : -1; + roambox_movingto_x = GetX() + movex; + roambox_movingto_y = GetY() + movey; + //Try to calculate new coord using distance. + if (roambox_movingto_x > roambox_max_x || roambox_movingto_x < roambox_min_x) + roambox_movingto_x -= movex * 2; + if (roambox_movingto_y > roambox_max_y || roambox_movingto_y < roambox_min_y) + roambox_movingto_y -= movey * 2; + //New coord is still invalid, ignore distance and just pick a new random coord. + //If we're here we may have a roambox where one side is shorter than the specified distance. Commons, Wkarana, etc. + if (roambox_movingto_x > roambox_max_x || roambox_movingto_x < roambox_min_x) + roambox_movingto_x = MakeRandomFloat(roambox_min_x+1,roambox_max_x-1); + if (roambox_movingto_y > roambox_max_y || roambox_movingto_y < roambox_min_y) + roambox_movingto_y = MakeRandomFloat(roambox_min_y+1,roambox_max_y-1); + } + + mlog(AI__WAYPOINTS, "Roam Box: d=%.3f (%.3f->%.3f,%.3f->%.3f): Go To (%.3f,%.3f)", + roambox_distance, roambox_min_x, roambox_max_x, roambox_min_y, roambox_max_y, roambox_movingto_x, roambox_movingto_y); + if (!CalculateNewPosition2(roambox_movingto_x, roambox_movingto_y, GetZ(), walksp, true)) + { + roambox_movingto_x = roambox_max_x + 1; // force update + pLastFightingDelayMoving = Timer::GetCurrentTime() + RandomTimer(roambox_min_delay, roambox_delay); + SetMoving(false); + SendPosition(); // makes mobs stop clientside + } + } + else if (roamer) + { + if (AIwalking_timer->Check()) + { + movetimercompleted=true; + AIwalking_timer->Disable(); + } + + + int16 gridno = CastToNPC()->GetGrid(); + + if (gridno > 0 || cur_wp==-2) { + if (movetimercompleted==true) { // time to pause at wp is over + + int32 spawn_id = this->GetSpawnPointID(); + LinkedListIterator iterator(zone->spawn2_list); + iterator.Reset(); + Spawn2 *found_spawn = nullptr; + + while(iterator.MoreElements()) + { + Spawn2* cur = iterator.GetData(); + iterator.Advance(); + if(cur->GetID() == spawn_id) + { + found_spawn = cur; + break; + } + } + + if (wandertype == 4 && cur_wp == CastToNPC()->GetMaxWp()) { + CastToNPC()->Depop(true); //depop and resart spawn timer + if(found_spawn) + found_spawn->SetNPCPointerNull(); + } + else if (wandertype == 6 && cur_wp == CastToNPC()->GetMaxWp()) { + CastToNPC()->Depop(false);//depop without spawn timer + if(found_spawn) + found_spawn->SetNPCPointerNull(); + } + else { + movetimercompleted=false; + + mlog(QUESTS__PATHING, "We are departing waypoint %d.", cur_wp); + + //if we were under quest control (with no grid), we are done now.. + if(cur_wp == -2) { + mlog(QUESTS__PATHING, "Non-grid quest mob has reached its quest ordered waypoint. Leaving pathing mode."); + roamer = false; + cur_wp = 0; + } + + if(GetAppearance() != eaStanding) + SetAppearance(eaStanding, false); + + entity_list.OpenDoorsNear(CastToNPC()); + + if(!DistractedFromGrid) { + //kick off event_waypoint depart + char temp[16]; + sprintf(temp, "%d", cur_wp); + parse->EventNPC(EVENT_WAYPOINT_DEPART, CastToNPC(), nullptr, temp, 0); + + //setup our next waypoint, if we are still on our normal grid + //remember that the quest event above could have done anything it wanted with our grid + if(gridno > 0) { + CastToNPC()->CalculateNewWaypoint(); + } + } + else { + DistractedFromGrid = false; + } + } + } // endif (movetimercompleted==true) + else if (!(AIwalking_timer->Enabled())) + { // currently moving + if (cur_wp_x == GetX() && cur_wp_y == GetY()) + { // are we there yet? then stop + mlog(AI__WAYPOINTS, "We have reached waypoint %d (%.3f,%.3f,%.3f) on grid %d", cur_wp, GetX(), GetY(), GetZ(), GetGrid()); + SetWaypointPause(); + if(GetAppearance() != eaStanding) + SetAppearance(eaStanding, false); + SetMoving(false); + if (cur_wp_heading >= 0.0) { + SetHeading(cur_wp_heading); + } + SendPosition(); + + //kick off event_waypoint arrive + char temp[16]; + sprintf(temp, "%d", cur_wp); + parse->EventNPC(EVENT_WAYPOINT_ARRIVE, CastToNPC(), nullptr, temp, 0); + + // wipe feign memory since we reached our first waypoint + if(cur_wp == 1) + ClearFeignMemory(); + } + else + { // not at waypoint yet, so keep moving + if(!RuleB(Pathing, AggroReturnToGrid) || !zone->pathing || (DistractedFromGrid == 0)) + CalculateNewPosition2(cur_wp_x, cur_wp_y, cur_wp_z, walksp, true); + else + { + bool WaypointChanged; + bool NodeReached; + VERTEX Goal = UpdatePath(cur_wp_x, cur_wp_y, cur_wp_z, walksp, WaypointChanged, NodeReached); + if(WaypointChanged) + tar_ndx = 20; + + if(NodeReached) + entity_list.OpenDoorsNear(CastToNPC()); + + CalculateNewPosition2(Goal.x, Goal.y, Goal.z, walksp, true); + } + + } + } + } // endif (gridno > 0) +// handle new quest grid command processing + else if (gridno < 0) + { // this mob is under quest control + if (movetimercompleted==true) + { // time to pause has ended + SetGrid( 0 - GetGrid()); // revert to AI control + mlog(QUESTS__PATHING, "Quest pathing is finished. Resuming on grid %d", GetGrid()); + + if(GetAppearance() != eaStanding) + SetAppearance(eaStanding, false); + + CalculateNewWaypoint(); + } + } + + } + else if (IsGuarding()) + { + bool CP2Moved; + if(!RuleB(Pathing, Guard) || !zone->pathing) + CP2Moved = CalculateNewPosition2(guard_x, guard_y, guard_z, walksp); + else + { + if(!((x_pos == guard_x) && (y_pos == guard_y) && (z_pos == guard_z))) + { + bool WaypointChanged, NodeReached; + VERTEX Goal = UpdatePath(guard_x, guard_y, guard_z, walksp, WaypointChanged, NodeReached); + if(WaypointChanged) + tar_ndx = 20; + + if(NodeReached) + entity_list.OpenDoorsNear(CastToNPC()); + + CP2Moved = CalculateNewPosition2(Goal.x, Goal.y, Goal.z, walksp); + } + else + CP2Moved = false; + + } + if (!CP2Moved) + { + if(moved) { + mlog(AI__WAYPOINTS, "Reached guard point (%.3f,%.3f,%.3f)", guard_x, guard_y, guard_z); + ClearFeignMemory(); + moved=false; + SetMoving(false); + if (GetTarget() == nullptr || DistNoRoot(*GetTarget()) >= 5*5 ) + { + SetHeading(guard_heading); + } else { + FaceTarget(GetTarget()); + } + SendPosition(); + SetAppearance(GetGuardPointAnim()); + } + } + } +} + +// Note: Mob that caused this may not get added to the hate list until after this function call completes +void Mob::AI_Event_Engaged(Mob* attacker, bool iYellForHelp) { + if (!IsAIControlled()) + return; + + if(GetAppearance() != eaStanding) + { + SetAppearance(eaStanding); + } + + if (iYellForHelp) { + if(IsPet()) { + GetOwner()->AI_Event_Engaged(attacker, iYellForHelp); + } else { + entity_list.AIYellForHelp(this, attacker); + } + } + + if(IsNPC()) + { + if(CastToNPC()->GetGrid() > 0) + { + DistractedFromGrid = true; + } + if(attacker && !attacker->IsCorpse()) + { + //Because sometimes the AIYellForHelp triggers another engaged and then immediately a not engaged + //if the target dies before it goes off + if(attacker->GetHP() > 0) + { + if(!CastToNPC()->GetCombatEvent() && GetHP() > 0) + { + parse->EventNPC(EVENT_COMBAT, CastToNPC(), attacker, "1", 0); + uint16 emoteid = GetEmoteID(); + if(emoteid != 0) + CastToNPC()->DoNPCEmote(ENTERCOMBAT,emoteid); + CastToNPC()->SetCombatEvent(true); + } + } + } + } +} + +// Note: Hate list may not be actually clear until after this function call completes +void Mob::AI_Event_NoLongerEngaged() { + if (!IsAIControlled()) + return; + this->AIwalking_timer->Start(RandomTimer(3000,20000)); + pLastFightingDelayMoving = Timer::GetCurrentTime(); + if (minLastFightingDelayMoving == maxLastFightingDelayMoving) + pLastFightingDelayMoving += minLastFightingDelayMoving; + else + pLastFightingDelayMoving += MakeRandomInt(minLastFightingDelayMoving, maxLastFightingDelayMoving); + // EverHood - So mobs don't keep running as a ghost until AIwalking_timer fires + // if they were moving prior to losing all hate + if(IsMoving()){ + SetRunAnimSpeed(0); + SetMoving(false); + SendPosition(); + } + ClearRampage(); + + if(IsNPC()) + { + if(CastToNPC()->GetCombatEvent() && GetHP() > 0) + { + if(entity_list.GetNPCByID(this->GetID())) + { + uint16 emoteid = CastToNPC()->GetEmoteID(); + parse->EventNPC(EVENT_COMBAT, CastToNPC(), nullptr, "0", 0); + if(emoteid != 0) + CastToNPC()->DoNPCEmote(LEAVECOMBAT,emoteid); + CastToNPC()->SetCombatEvent(false); + } + } + } +} + +//this gets called from InterruptSpell() for failure or SpellFinished() for success +void NPC::AI_Event_SpellCastFinished(bool iCastSucceeded, uint8 slot) { + if (slot == 1) { + uint32 recovery_time = 0; + if (iCastSucceeded) { + if (casting_spell_AIindex < AIspells.size()) { + recovery_time += spells[AIspells[casting_spell_AIindex].spellid].recovery_time; + if (AIspells[casting_spell_AIindex].recast_delay >= 0) + { + if (AIspells[casting_spell_AIindex].recast_delay < 10000) + AIspells[casting_spell_AIindex].time_cancast = Timer::GetCurrentTime() + (AIspells[casting_spell_AIindex].recast_delay*1000); + } + else + AIspells[casting_spell_AIindex].time_cancast = Timer::GetCurrentTime() + spells[AIspells[casting_spell_AIindex].spellid].recast_time; + } + if (recovery_time < AIautocastspell_timer->GetSetAtTrigger()) + recovery_time = AIautocastspell_timer->GetSetAtTrigger(); + AIautocastspell_timer->Start(recovery_time, false); + } + else + AIautocastspell_timer->Start(800, false); + casting_spell_AIindex = AIspells.size(); + } +} + + +bool NPC::AI_EngagedCastCheck() { + if (AIautocastspell_timer->Check(false)) { + AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting. + + mlog(AI__SPELLS, "Engaged autocast check triggered. Trying to cast healing spells then maybe offensive spells."); + Shout("KAYEN"); + // try casting a heal or gate + if (!AICastSpell(this, 100, SpellType_Heal | SpellType_Escape | SpellType_InCombatBuff)) { + // try casting a heal on nearby + if (!entity_list.AICheckCloseBeneficialSpells(this, 25, MobAISpellRange, SpellType_Heal)) { + //nobody to heal, try some detrimental spells. + if(!AICastSpell(GetTarget(), 20, SpellType_Nuke | SpellType_Lifetap | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Slow | SpellType_Debuff | SpellType_Charm | SpellType_Root)) { + //no spell to cast, try again soon. + AIautocastspell_timer->Start(RandomTimer(500, 1000), false); + } + } + } + return(true); + } + + return(false); +} + +bool NPC::AI_PursueCastCheck() { + if (AIautocastspell_timer->Check(false)) { + AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting. + + mlog(AI__SPELLS, "Engaged (pursuing) autocast check triggered. Trying to cast offensive spells."); + if(!AICastSpell(GetTarget(), 90, SpellType_Root | SpellType_Nuke | SpellType_Lifetap | SpellType_Snare | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Slow | SpellType_Debuff)) { + //no spell cast, try again soon. + AIautocastspell_timer->Start(RandomTimer(500, 2000), false); + } //else, spell casting finishing will reset the timer. + return(true); + } + return(false); +} + +bool NPC::AI_IdleCastCheck() { + if (AIautocastspell_timer->Check(false)) { +#if MobAI_DEBUG_Spells >= 25 + std::cout << "Non-Engaged autocast check triggered: " << this->GetName() << std::endl; +#endif + AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting. + if (!AICastSpell(this, 100, SpellType_Heal | SpellType_Buff | SpellType_Pet)) { + if(!entity_list.AICheckCloseBeneficialSpells(this, 33, MobAISpellRange, SpellType_Heal | SpellType_Buff)) { + //if we didnt cast any spells, our autocast timer just resets to the + //last duration it was set to... try to put up a more reasonable timer... + AIautocastspell_timer->Start(RandomTimer(1000, 5000), false); + } //else, spell casting finishing will reset the timer. + } //else, spell casting finishing will reset the timer. + return(true); + } + return(false); +} + +void Mob::StartEnrage() +{ + // dont continue if already enraged + if (bEnraged) + return; + + if(!GetSpecialAbility(SPECATK_ENRAGE)) + return; + + int hp_ratio = GetSpecialAbilityParam(SPECATK_ENRAGE, 0); + hp_ratio = hp_ratio > 0 ? hp_ratio : RuleI(NPC, StartEnrageValue); + if(GetHPRatio() > static_cast(hp_ratio)) { + return; + } + + if(RuleB(NPC, LiveLikeEnrage) && !((IsPet() && !IsCharmed() && GetOwner() && GetOwner()->IsClient()) || + (CastToNPC()->GetSwarmOwner() && entity_list.GetMob(CastToNPC()->GetSwarmOwner())->IsClient()))) { + return; + } + + Timer *timer = GetSpecialAbilityTimer(SPECATK_ENRAGE); + if (timer && !timer->Check()) + return; + + int enraged_duration = GetSpecialAbilityParam(SPECATK_ENRAGE, 1); + enraged_duration = enraged_duration > 0 ? enraged_duration : EnragedDurationTimer; + StartSpecialAbilityTimer(SPECATK_ENRAGE, enraged_duration); + + // start the timer. need to call IsEnraged frequently since we dont have callback timers :-/ + bEnraged = true; + entity_list.MessageClose_StringID(this, true, 200, MT_NPCEnrage, NPC_ENRAGE_START, GetCleanName()); +} + +void Mob::ProcessEnrage(){ + if(IsEnraged()){ + Timer *timer = GetSpecialAbilityTimer(SPECATK_ENRAGE); + if(timer && timer->Check()){ + entity_list.MessageClose_StringID(this, true, 200, MT_NPCEnrage, NPC_ENRAGE_END, GetCleanName()); + + int enraged_cooldown = GetSpecialAbilityParam(SPECATK_ENRAGE, 2); + enraged_cooldown = enraged_cooldown > 0 ? enraged_cooldown : EnragedTimer; + StartSpecialAbilityTimer(SPECATK_ENRAGE, enraged_cooldown); + bEnraged = false; + } + } +} + +bool Mob::IsEnraged() +{ + return bEnraged; +} + +bool Mob::Flurry(ExtraAttackOptions *opts) +{ + // this is wrong, flurry is extra attacks on the current target + Mob *target = GetTarget(); + if (target) { + if (!IsPet()) { + entity_list.MessageClose_StringID(this, true, 200, MT_NPCFlurry, NPC_FLURRY, GetCleanName(), target->GetCleanName()); + } else { + entity_list.MessageClose_StringID(this, true, 200, MT_PetFlurry, NPC_FLURRY, GetCleanName(), target->GetCleanName()); + } + + int num_attacks = GetSpecialAbilityParam(SPECATK_FLURRY, 1); + num_attacks = num_attacks > 0 ? num_attacks : RuleI(Combat, MaxFlurryHits); + for (int i = 0; i < num_attacks; i++) + Attack(target, 13, false, false, false, opts); + } + return true; +} + +bool Mob::AddRampage(Mob *mob) +{ + if (!mob) + return false; + + if (!GetSpecialAbility(SPECATK_RAMPAGE)) + return false; + + for (int i = 0; i < RampageArray.size(); i++) { + // if Entity ID is already on the list don't add it again + if (mob->GetID() == RampageArray[i]) + return false; + } + RampageArray.push_back(mob->GetID()); + return true; +} + +void Mob::ClearRampage() +{ + RampageArray.clear(); +} + +bool Mob::Rampage(ExtraAttackOptions *opts) +{ + int index_hit = 0; + if (!IsPet()) + entity_list.MessageClose_StringID(this, true, 200, MT_NPCRampage, NPC_RAMPAGE, GetCleanName()); + else + entity_list.MessageClose_StringID(this, true, 200, MT_PetFlurry, NPC_RAMPAGE, GetCleanName()); + + int rampage_targets = GetSpecialAbilityParam(SPECATK_RAMPAGE, 1); + if (rampage_targets == 0) // if set to 0 or not set in the DB + rampage_targets = RuleI(Combat, DefaultRampageTargets); + if (rampage_targets > RuleI(Combat, MaxRampageTargets)) + rampage_targets = RuleI(Combat, MaxRampageTargets); + for (int i = 0; i < RampageArray.size(); i++) { + if (index_hit >= rampage_targets) + break; + // range is important + Mob *m_target = entity_list.GetMob(RampageArray[i]); + if (m_target) { + if (m_target == GetTarget()) + continue; + if (CombatRange(m_target)) { + Attack(m_target, 13, false, false, false, opts); + index_hit++; + } + } + } + + if (RuleB(Combat, RampageHitsTarget) && index_hit < rampage_targets) + Attack(GetTarget(), 13, false, false, false, opts); + + return true; +} + +void Mob::AreaRampage(ExtraAttackOptions *opts) +{ + int index_hit = 0; + if (!IsPet()) { // do not know every pet AA so thought it safer to add this + entity_list.MessageClose_StringID(this, true, 200, MT_NPCRampage, AE_RAMPAGE, GetCleanName()); + } else { + entity_list.MessageClose_StringID(this, true, 200, MT_PetFlurry, AE_RAMPAGE, GetCleanName()); + } + + int rampage_targets = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 1); + rampage_targets = rampage_targets > 0 ? rampage_targets : 1; + index_hit = hate_list.AreaRampage(this, GetTarget(), rampage_targets, opts); + + if(index_hit == 0) { + Attack(GetTarget(), 13, false, false, false, opts); + } +} + +uint32 Mob::GetLevelCon(uint8 mylevel, uint8 iOtherLevel) { + int16 diff = iOtherLevel - mylevel; + uint32 conlevel=0; + + if (diff == 0) + return CON_WHITE; + else if (diff >= 1 && diff <= 2) + return CON_YELLOW; + else if (diff >= 3) + return CON_RED; + + if (mylevel <= 8) + { + if (diff <= -4) + conlevel = CON_GREEN; + else + conlevel = CON_BLUE; + } + else if (mylevel <= 9) + { + if (diff <= -6) + conlevel = CON_GREEN; + else if (diff <= -4) + conlevel = CON_LIGHTBLUE; + else + conlevel = CON_BLUE; + } + else if (mylevel <= 13) + { + if (diff <= -7) + conlevel = CON_GREEN; + else if (diff <= -5) + conlevel = CON_LIGHTBLUE; + else + conlevel = CON_BLUE; + } + else if (mylevel <= 15) + { + if (diff <= -7) + conlevel = CON_GREEN; + else if (diff <= -5) + conlevel = CON_LIGHTBLUE; + else + conlevel = CON_BLUE; + } + else if (mylevel <= 17) + { + if (diff <= -8) + conlevel = CON_GREEN; + else if (diff <= -6) + conlevel = CON_LIGHTBLUE; + else + conlevel = CON_BLUE; + } + else if (mylevel <= 21) + { + if (diff <= -9) + conlevel = CON_GREEN; + else if (diff <= -7) + conlevel = CON_LIGHTBLUE; + else + conlevel = CON_BLUE; + } + else if (mylevel <= 25) + { + if (diff <= -10) + conlevel = CON_GREEN; + else if (diff <= -8) + conlevel = CON_LIGHTBLUE; + else + conlevel = CON_BLUE; + } + else if (mylevel <= 29) + { + if (diff <= -11) + conlevel = CON_GREEN; + else if (diff <= -9) + conlevel = CON_LIGHTBLUE; + else + conlevel = CON_BLUE; + } + else if (mylevel <= 31) + { + if (diff <= -12) + conlevel = CON_GREEN; + else if (diff <= -9) + conlevel = CON_LIGHTBLUE; + else + conlevel = CON_BLUE; + } + else if (mylevel <= 33) + { + if (diff <= -13) + conlevel = CON_GREEN; + else if (diff <= -10) + conlevel = CON_LIGHTBLUE; + else + conlevel = CON_BLUE; + } + else if (mylevel <= 37) + { + if (diff <= -14) + conlevel = CON_GREEN; + else if (diff <= -11) + conlevel = CON_LIGHTBLUE; + else + conlevel = CON_BLUE; + } + else if (mylevel <= 41) + { + if (diff <= -16) + conlevel = CON_GREEN; + else if (diff <= -12) + conlevel = CON_LIGHTBLUE; + else + conlevel = CON_BLUE; + } + else if (mylevel <= 45) + { + if (diff <= -17) + conlevel = CON_GREEN; + else if (diff <= -13) + conlevel = CON_LIGHTBLUE; + else + conlevel = CON_BLUE; + } + else if (mylevel <= 49) + { + if (diff <= -18) + conlevel = CON_GREEN; + else if (diff <= -14) + conlevel = CON_LIGHTBLUE; + else + conlevel = CON_BLUE; + } + else if (mylevel <= 53) + { + if (diff <= -19) + conlevel = CON_GREEN; + else if (diff <= -15) + conlevel = CON_LIGHTBLUE; + else + conlevel = CON_BLUE; + } + else if (mylevel <= 55) + { + if (diff <= -20) + conlevel = CON_GREEN; + else if (diff <= -15) + conlevel = CON_LIGHTBLUE; + else + conlevel = CON_BLUE; + } + else + { + if (diff <= -21) + conlevel = CON_GREEN; + else if (diff <= -16) + conlevel = CON_LIGHTBLUE; + else + conlevel = CON_BLUE; + } + return conlevel; +} + +void NPC::CheckSignal() { + if (!signal_q.empty()) { + int signal_id = signal_q.front(); + signal_q.pop_front(); + char buf[32]; + snprintf(buf, 31, "%d", signal_id); + buf[31] = '\0'; + parse->EventNPC(EVENT_SIGNAL, this, nullptr, buf, 0); + } +} + + + +/* +alter table npc_types drop column usedspells; +alter table npc_types add column npc_spells_id int(11) unsigned not null default 0 after merchant_id; +Create Table npc_spells ( + id int(11) unsigned not null auto_increment primary key, + name tinytext, + parent_list int(11) unsigned not null default 0, + attack_proc smallint(5) not null default -1, + proc_chance tinyint(3) not null default 3 + ); +create table npc_spells_entries ( + id int(11) unsigned not null auto_increment primary key, + npc_spells_id int(11) not null, + spellid smallint(5) not null default 0, + type smallint(5) unsigned not null default 0, + minlevel tinyint(3) unsigned not null default 0, + maxlevel tinyint(3) unsigned not null default 255, + manacost smallint(5) not null default '-1', + recast_delay int(11) not null default '-1', + priority smallint(5) not null default 0, + index npc_spells_id (npc_spells_id) + ); +*/ + +bool IsSpellInList(DBnpcspells_Struct* spell_list, int16 iSpellID); +bool IsSpellEffectInList(DBnpcspellseffects_Struct* spelleffect_list, uint16 iSpellEffectID, int32 base, int32 limit, int32 max); +bool Compare_AI_Spells(AISpells_Struct i, AISpells_Struct j); + +bool NPC::AI_AddNPCSpells(uint32 iDBSpellsID) { + // ok, this function should load the list, and the parent list then shove them into the struct and sort + npc_spells_id = iDBSpellsID; + AIspells.clear(); + if (iDBSpellsID == 0) { + AIautocastspell_timer->Disable(); + return false; + } + DBnpcspells_Struct* spell_list = database.GetNPCSpells(iDBSpellsID); + if (!spell_list) { + AIautocastspell_timer->Disable(); + return false; + } + DBnpcspells_Struct* parentlist = database.GetNPCSpells(spell_list->parent_list); + uint32 i; +#if MobAI_DEBUG_Spells >= 10 + std::cout << "Loading NPCSpells onto " << this->GetName() << ": dbspellsid=" << iDBSpellsID; + if (spell_list) { + std::cout << " (found, " << spell_list->numentries << "), parentlist=" << spell_list->parent_list; + if (spell_list->parent_list) { + if (parentlist) { + std::cout << " (found, " << parentlist->numentries << ")"; + } + else + std::cout << " (not found)"; + } + } + else + std::cout << " (not found)"; + std::cout << std::endl; +#endif + int16 attack_proc_spell = -1; + int8 proc_chance = 3; + if (parentlist) { + attack_proc_spell = parentlist->attack_proc; + proc_chance = parentlist->proc_chance; + for (i=0; inumentries; i++) { + if (GetLevel() >= parentlist->entries[i].minlevel && GetLevel() <= parentlist->entries[i].maxlevel && parentlist->entries[i].spellid > 0) { + if (!IsSpellInList(spell_list, parentlist->entries[i].spellid)) + { + AddSpellToNPCList(parentlist->entries[i].priority, + parentlist->entries[i].spellid, parentlist->entries[i].type, + parentlist->entries[i].manacost, parentlist->entries[i].recast_delay, + parentlist->entries[i].resist_adjust); + } + } + } + } + if (spell_list->attack_proc >= 0) { + attack_proc_spell = spell_list->attack_proc; + proc_chance = spell_list->proc_chance; + } + for (i=0; inumentries; i++) { + if (GetLevel() >= spell_list->entries[i].minlevel && GetLevel() <= spell_list->entries[i].maxlevel && spell_list->entries[i].spellid > 0) { + AddSpellToNPCList(spell_list->entries[i].priority, + spell_list->entries[i].spellid, spell_list->entries[i].type, + spell_list->entries[i].manacost, spell_list->entries[i].recast_delay, + spell_list->entries[i].resist_adjust); + } + } + std::sort(AIspells.begin(), AIspells.end(), Compare_AI_Spells); + + if (attack_proc_spell > 0) + AddProcToWeapon(attack_proc_spell, true, proc_chance); + + if (AIspells.size() == 0) + AIautocastspell_timer->Disable(); + else + AIautocastspell_timer->Trigger(); + return true; +} + +bool NPC::AI_AddNPCSpellsEffects(uint32 iDBSpellsEffectsID) { + + npc_spells_effects_id = iDBSpellsEffectsID; + AIspellsEffects.clear(); + + if (iDBSpellsEffectsID == 0) + return false; + + DBnpcspellseffects_Struct* spell_effects_list = database.GetNPCSpellsEffects(iDBSpellsEffectsID); + + if (!spell_effects_list) { + return false; + } + + DBnpcspellseffects_Struct* parentlist = database.GetNPCSpellsEffects(spell_effects_list->parent_list); + + uint32 i; +#if MobAI_DEBUG_Spells >= 10 + std::cout << "Loading NPCSpellsEffects onto " << this->GetName() << ": dbspellseffectsid=" << iDBSpellsEffectsID; + if (spell_effects_list) { + std::cout << " (found, " << spell_effects_list->numentries << "), parentlist=" << spell_effects)list->parent_list; + if (spell_effects_list->parent_list) { + if (parentlist) { + std::cout << " (found, " << parentlist->numentries << ")"; + } + else + std::cout << " (not found)"; + } + } + else + std::cout << " (not found)"; + std::cout << std::endl; +#endif + + if (parentlist) { + for (i=0; inumentries; i++) { + if (GetLevel() >= parentlist->entries[i].minlevel && GetLevel() <= parentlist->entries[i].maxlevel && parentlist->entries[i].spelleffectid > 0) { + if (!IsSpellEffectInList(spell_effects_list, parentlist->entries[i].spelleffectid, parentlist->entries[i].base, + parentlist->entries[i].limit, parentlist->entries[i].max)) + { + AddSpellEffectToNPCList(parentlist->entries[i].spelleffectid, + parentlist->entries[i].base, parentlist->entries[i].limit, + parentlist->entries[i].max); + } + } + } + } + + for (i=0; inumentries; i++) { + if (GetLevel() >= spell_effects_list->entries[i].minlevel && GetLevel() <= spell_effects_list->entries[i].maxlevel && spell_effects_list->entries[i].spelleffectid > 0) { + AddSpellEffectToNPCList(spell_effects_list->entries[i].spelleffectid, + spell_effects_list->entries[i].base, spell_effects_list->entries[i].limit, + spell_effects_list->entries[i].max); + } + } + + return true; +} + +void NPC::ApplyAISpellEffects(StatBonuses* newbon) +{ + if (!AI_HasSpellsEffects()) + return; + + + + for(int i=0; i < AIspellsEffects.size(); i++) + { + Shout("ApplyAISpellEffects %i %i %i %i", AIspellsEffects[i].spelleffectid, AIspellsEffects[i].base, AIspellsEffects[i].limit,AIspellsEffects[i].max); + ApplySpellsBonuses(0, 0, newbon, 0, false, 0,-1, + true, AIspellsEffects[i].spelleffectid, AIspellsEffects[i].base, AIspellsEffects[i].limit,AIspellsEffects[i].max); + } + + return; +} + +// adds a spell to the list, taking into account priority and resorting list as needed. +void NPC::AddSpellEffectToNPCList(uint16 iSpellEffectID, int32 base, int32 limit, int32 max) +{ + + if(!iSpellEffectID) + return; + + + HasAISpellEffects = true; + AISpellsEffects_Struct t; + + t.spelleffectid = iSpellEffectID; + t.base = base; + t.limit = limit; + t.max = max; + Shout("AddSpellEffectToNPCList %i %i %i %i", iSpellEffectID,base, limit, max ); + AIspellsEffects.push_back(t); +} + +bool IsSpellEffectInList(DBnpcspellseffects_Struct* spelleffect_list, uint16 iSpellEffectID, int32 base, int32 limit, int32 max) { + for (uint32 i=0; i < spelleffect_list->numentries; i++) { + if (spelleffect_list->entries[i].spelleffectid == iSpellEffectID && spelleffect_list->entries[i].base == base + && spelleffect_list->entries[i].limit == limit && spelleffect_list->entries[i].max == max) + return true; + } + return false; +} + +bool IsSpellInList(DBnpcspells_Struct* spell_list, int16 iSpellID) { + for (uint32 i=0; i < spell_list->numentries; i++) { + if (spell_list->entries[i].spellid == iSpellID) + return true; + } + return false; +} + +bool Compare_AI_Spells(AISpells_Struct i, AISpells_Struct j) +{ + return(i.priority > j.priority); +} + +// adds a spell to the list, taking into account priority and resorting list as needed. +void NPC::AddSpellToNPCList(int16 iPriority, int16 iSpellID, uint16 iType, + int16 iManaCost, int32 iRecastDelay, int16 iResistAdjust) +{ + + if(!IsValidSpell(iSpellID)) + return; + + HasAISpell = true; + AISpells_Struct t; + + t.priority = iPriority; + t.spellid = iSpellID; + t.type = iType; + t.manacost = iManaCost; + t.recast_delay = iRecastDelay; + t.time_cancast = 0; + t.resist_adjust = iResistAdjust; + + AIspells.push_back(t); +} + +void NPC::RemoveSpellFromNPCList(int16 spell_id) +{ + std::vector::iterator iter = AIspells.begin(); + while(iter != AIspells.end()) + { + if((*iter).spellid == spell_id) + { + iter = AIspells.erase(iter); + continue; + } + ++iter; + } +} + +void NPC::AISpellsList(Client *c) +{ + if (!c) + return; + + for (std::vector::iterator it = AIspells.begin(); it != AIspells.end(); ++it) + c->Message(0, "%s (%d): Type %d, Priority %d", + spells[it->spellid].name, it->spellid, it->type, it->priority); + + return; +} + +DBnpcspells_Struct* ZoneDatabase::GetNPCSpells(uint32 iDBSpellsID) { + if (iDBSpellsID == 0) + return 0; + + if (!npc_spells_cache) { + npc_spells_maxid = GetMaxNPCSpellsID(); + npc_spells_cache = new DBnpcspells_Struct*[npc_spells_maxid+1]; + npc_spells_loadtried = new bool[npc_spells_maxid+1]; + for (uint32 i=0; i<=npc_spells_maxid; i++) { + npc_spells_cache[i] = 0; + npc_spells_loadtried[i] = false; + } + } + + if (iDBSpellsID > npc_spells_maxid) + return 0; + if (npc_spells_cache[iDBSpellsID]) { // it's in the cache, easy =) + return npc_spells_cache[iDBSpellsID]; + } + + else if (!npc_spells_loadtried[iDBSpellsID]) { // no reason to ask the DB again if we have failed once already + npc_spells_loadtried[iDBSpellsID] = true; + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + + if (RunQuery(query, MakeAnyLenString(&query, "SELECT id, parent_list, attack_proc, proc_chance from npc_spells where id=%d", iDBSpellsID), errbuf, &result)) { + safe_delete_array(query); + if (mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + uint32 tmpparent_list = atoi(row[1]); + int16 tmpattack_proc = atoi(row[2]); + uint8 tmpproc_chance = atoi(row[3]); + mysql_free_result(result); + if (RunQuery(query, MakeAnyLenString(&query, "SELECT spellid, type, minlevel, maxlevel, manacost, recast_delay, priority, resist_adjust from npc_spells_entries where npc_spells_id=%d ORDER BY minlevel", iDBSpellsID), errbuf, &result)) { + safe_delete_array(query); + uint32 tmpSize = sizeof(DBnpcspells_Struct) + (sizeof(DBnpcspells_entries_Struct) * mysql_num_rows(result)); + npc_spells_cache[iDBSpellsID] = (DBnpcspells_Struct*) new uchar[tmpSize]; + memset(npc_spells_cache[iDBSpellsID], 0, tmpSize); + npc_spells_cache[iDBSpellsID]->parent_list = tmpparent_list; + npc_spells_cache[iDBSpellsID]->attack_proc = tmpattack_proc; + npc_spells_cache[iDBSpellsID]->proc_chance = tmpproc_chance; + npc_spells_cache[iDBSpellsID]->numentries = mysql_num_rows(result); + int j = 0; + while ((row = mysql_fetch_row(result))) { + int spell_id = atoi(row[0]); + npc_spells_cache[iDBSpellsID]->entries[j].spellid = spell_id; + npc_spells_cache[iDBSpellsID]->entries[j].type = atoi(row[1]); + npc_spells_cache[iDBSpellsID]->entries[j].minlevel = atoi(row[2]); + npc_spells_cache[iDBSpellsID]->entries[j].maxlevel = atoi(row[3]); + npc_spells_cache[iDBSpellsID]->entries[j].manacost = atoi(row[4]); + npc_spells_cache[iDBSpellsID]->entries[j].recast_delay = atoi(row[5]); + npc_spells_cache[iDBSpellsID]->entries[j].priority = atoi(row[6]); + if(row[7]) + { + npc_spells_cache[iDBSpellsID]->entries[j].resist_adjust = atoi(row[7]); + } + else + { + if(IsValidSpell(spell_id)) + { + npc_spells_cache[iDBSpellsID]->entries[j].resist_adjust = spells[spell_id].ResistDiff; + } + } + j++; + } + mysql_free_result(result); + return npc_spells_cache[iDBSpellsID]; + } + else { + std::cerr << "Error in AddNPCSpells query1 '" << query << "' " << errbuf << std::endl; + safe_delete_array(query); + return 0; + } + } + else { + mysql_free_result(result); + } + } + else { + std::cerr << "Error in AddNPCSpells query1 '" << query << "' " << errbuf << std::endl; + safe_delete_array(query); + return 0; + } + + return 0; + } + return 0; +} + +uint32 ZoneDatabase::GetMaxNPCSpellsID() { + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + + if (RunQuery(query, MakeAnyLenString(&query, "SELECT max(id) from npc_spells"), errbuf, &result)) { + safe_delete_array(query); + if (mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + uint32 ret = 0; + if (row[0]) + ret = atoi(row[0]); + mysql_free_result(result); + return ret; + } + mysql_free_result(result); + } + else { + std::cerr << "Error in GetMaxNPCSpellsID query '" << query << "' " << errbuf << std::endl; + safe_delete_array(query); + return 0; + } + + return 0; +} + +DBnpcspellseffects_Struct* ZoneDatabase::GetNPCSpellsEffects(uint32 iDBSpellsEffectsID) { + if (iDBSpellsEffectsID == 0) + return 0; + + if (!npc_spellseffects_cache) { + npc_spellseffects_maxid = GetMaxNPCSpellsEffectsID(); + npc_spellseffects_cache = new DBnpcspellseffects_Struct*[npc_spellseffects_maxid+1]; + npc_spellseffects_loadtried = new bool[npc_spellseffects_maxid+1]; + for (uint32 i=0; i<=npc_spellseffects_maxid; i++) { + npc_spellseffects_cache[i] = 0; + npc_spellseffects_loadtried[i] = false; + } + } + + if (iDBSpellsEffectsID > npc_spellseffects_maxid) + return 0; + if (npc_spellseffects_cache[iDBSpellsEffectsID]) { // it's in the cache, easy =) + return npc_spellseffects_cache[iDBSpellsEffectsID]; + } + + else if (!npc_spellseffects_loadtried[iDBSpellsEffectsID]) { // no reason to ask the DB again if we have failed once already + npc_spellseffects_loadtried[iDBSpellsEffectsID] = true; + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + + if (RunQuery(query, MakeAnyLenString(&query, "SELECT id, parent_list from npc_spells_effects where id=%d", iDBSpellsEffectsID), errbuf, &result)) { + safe_delete_array(query); + if (mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + uint32 tmpparent_list = atoi(row[1]); + mysql_free_result(result); + if (RunQuery(query, MakeAnyLenString(&query, "SELECT spell_effect_id, minlevel, maxlevel,se_base, se_limit, se_max from npc_spells_effects_entries where npc_spells_effects_id=%d ORDER BY minlevel", iDBSpellsEffectsID), errbuf, &result)) { + safe_delete_array(query); + uint32 tmpSize = sizeof(DBnpcspellseffects_Struct) + (sizeof(DBnpcspellseffects_Struct) * mysql_num_rows(result)); + npc_spellseffects_cache[iDBSpellsEffectsID] = (DBnpcspellseffects_Struct*) new uchar[tmpSize]; + memset(npc_spellseffects_cache[iDBSpellsEffectsID], 0, tmpSize); + npc_spellseffects_cache[iDBSpellsEffectsID]->parent_list = tmpparent_list; + npc_spellseffects_cache[iDBSpellsEffectsID]->numentries = mysql_num_rows(result); + int j = 0; + while ((row = mysql_fetch_row(result))) { + int spell_effect_id = atoi(row[0]); + npc_spellseffects_cache[iDBSpellsEffectsID]->entries[j].spelleffectid = spell_effect_id; + npc_spellseffects_cache[iDBSpellsEffectsID]->entries[j].minlevel = atoi(row[1]); + npc_spellseffects_cache[iDBSpellsEffectsID]->entries[j].maxlevel = atoi(row[2]); + npc_spellseffects_cache[iDBSpellsEffectsID]->entries[j].base = atoi(row[3]); + npc_spellseffects_cache[iDBSpellsEffectsID]->entries[j].limit = atoi(row[4]); + npc_spellseffects_cache[iDBSpellsEffectsID]->entries[j].max = atoi(row[5]); + j++; + } + mysql_free_result(result); + return npc_spellseffects_cache[iDBSpellsEffectsID]; + } + else { + std::cerr << "Error in AddNPCSpells query1 '" << query << "' " << errbuf << std::endl; + safe_delete_array(query); + return 0; + } + } + else { + mysql_free_result(result); + } + } + else { + std::cerr << "Error in AddNPCSpells query1 '" << query << "' " << errbuf << std::endl; + safe_delete_array(query); + return 0; + } + return 0; + } + return 0; +} + +uint32 ZoneDatabase::GetMaxNPCSpellsEffectsID() { + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + + if (RunQuery(query, MakeAnyLenString(&query, "SELECT max(id) from npc_spells_effects"), errbuf, &result)) { + safe_delete_array(query); + if (mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + uint32 ret = 0; + if (row[0]) + ret = atoi(row[0]); + mysql_free_result(result); + return ret; + } + mysql_free_result(result); + } + else { + std::cerr << "Error in GetMaxNPCSpellsEffectsID query '" << query << "' " << errbuf << std::endl; + safe_delete_array(query); + return 0; + } + + return 0; +} + diff --git a/zone/attackx.cpp b/zone/attackx.cpp new file mode 100644 index 000000000..3f96399be --- /dev/null +++ b/zone/attackx.cpp @@ -0,0 +1,4663 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2002 EQEMu Development Team (http://eqemulator.net) + + 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 +*/ + +#if EQDEBUG >= 5 +//#define ATTACK_DEBUG 20 +#endif + +#include "../common/debug.h" +#include +#include +#include +#include +#include +#include + +#include "masterentity.h" +#include "NpcAI.h" +#include "../common/packet_dump.h" +#include "../common/eq_packet_structs.h" +#include "../common/eq_constants.h" +#include "../common/skills.h" +#include "../common/spdat.h" +#include "zone.h" +#include "StringIDs.h" +#include "../common/StringUtil.h" +#include "../common/rulesys.h" +#include "QuestParserCollection.h" +#include "watermap.h" +#include "worldserver.h" +extern WorldServer worldserver; + +#ifdef _WINDOWS +#define snprintf _snprintf +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#endif + +extern EntityList entity_list; +extern Zone* zone; + +bool Mob::AttackAnimation(SkillUseTypes &skillinuse, int Hand, const ItemInst* weapon) +{ + // Determine animation + int type = 0; + if (weapon && weapon->IsType(ItemClassCommon)) { + const Item_Struct* item = weapon->GetItem(); +#if EQDEBUG >= 11 + LogFile->write(EQEMuLog::Debug, "Weapon skill:%i", item->ItemType); +#endif + switch (item->ItemType) + { + case ItemType1HSlash: // 1H Slashing + { + skillinuse = Skill1HSlashing; + type = anim1HWeapon; + break; + } + case ItemType2HSlash: // 2H Slashing + { + skillinuse = Skill2HSlashing; + type = anim2HSlashing; + break; + } + case ItemType1HPiercing: // Piercing + { + skillinuse = Skill1HPiercing; + type = animPiercing; + break; + } + case ItemType1HBlunt: // 1H Blunt + { + skillinuse = Skill1HBlunt; + type = anim1HWeapon; + break; + } + case ItemType2HBlunt: // 2H Blunt + { + skillinuse = Skill2HBlunt; + type = anim2HWeapon; + break; + } + case ItemType2HPiercing: // 2H Piercing + { + skillinuse = Skill1HPiercing; // change to Skill2HPiercing once activated + type = anim2HWeapon; + break; + } + case ItemTypeMartial: + { + skillinuse = SkillHandtoHand; + type = animHand2Hand; + break; + } + default: + { + skillinuse = SkillHandtoHand; + type = animHand2Hand; + break; + } + }// switch + } + else if(IsNPC()) { + + switch (skillinuse) + { + case Skill1HSlashing: // 1H Slashing + { + type = anim1HWeapon; + break; + } + case Skill2HSlashing: // 2H Slashing + { + type = anim2HSlashing; + break; + } + case Skill1HPiercing: // Piercing + { + type = animPiercing; + break; + } + case Skill1HBlunt: // 1H Blunt + { + type = anim1HWeapon; + break; + } + case Skill2HBlunt: // 2H Blunt + { + type = anim2HWeapon; + break; + } + case 99: // 2H Piercing // change to Skill2HPiercing once activated + { + type = anim2HWeapon; + break; + } + case SkillHandtoHand: + { + type = animHand2Hand; + break; + } + default: + { + type = animHand2Hand; + break; + } + }// switch + } + else { + skillinuse = SkillHandtoHand; + type = animHand2Hand; + } + + // If we're attacking with the secondary hand, play the dual wield anim + if (Hand == 14) // DW anim + type = animDualWield; + + DoAnim(type); + return true; +} + +// called when a mob is attacked, does the checks to see if it's a hit +// and does other mitigation checks. 'this' is the mob being attacked. +bool Mob::CheckHitChance(Mob* other, SkillUseTypes skillinuse, int Hand, int16 chance_mod) +{ +/*/ + //Reworked a lot of this code to achieve better balance at higher levels. + //The old code basically meant that any in high level (50+) combat, + //both parties always had 95% chance to hit the other one. +/*/ + + Mob *attacker=other; + Mob *defender=this; + float chancetohit = RuleR(Combat, BaseHitChance); + + if(attacker->IsNPC() && !attacker->IsPet()) + chancetohit += RuleR(Combat, NPCBonusHitChance); + +#if ATTACK_DEBUG>=11 + LogFile->write(EQEMuLog::Debug, "CheckHitChance(%s) attacked by %s", defender->GetName(), attacker->GetName()); +#endif + mlog(COMBAT__TOHIT,"CheckHitChance(%s) attacked by %s", defender->GetName(), attacker->GetName()); + + bool pvpmode = false; + if(IsClient() && other->IsClient()) + pvpmode = true; + + if (chance_mod >= 10000) + return true; + + float bonus; + + //////////////////////////////////////////////////////// + // To hit calcs go here + //////////////////////////////////////////////////////// + + uint8 attacker_level = attacker->GetLevel() ? attacker->GetLevel() : 1; + uint8 defender_level = defender->GetLevel() ? defender->GetLevel() : 1; + + //Calculate the level difference + + mlog(COMBAT__TOHIT, "Chance to hit before level diff calc %.2f", chancetohit); + double level_difference = attacker_level - defender_level; + double range = defender->GetLevel(); + range = ((range / 4) + 3); + + if(level_difference < 0) + { + if(level_difference >= -range) + { + chancetohit += (level_difference / range) * RuleR(Combat,HitFalloffMinor); //5 + } + else if (level_difference >= -(range+3.0)) + { + chancetohit -= RuleR(Combat,HitFalloffMinor); + chancetohit += ((level_difference+range) / (3.0)) * RuleR(Combat,HitFalloffModerate); //7 + } + else + { + chancetohit -= (RuleR(Combat,HitFalloffMinor) + RuleR(Combat,HitFalloffModerate)); + chancetohit += ((level_difference+range+3.0)/12.0) * RuleR(Combat,HitFalloffMajor); //50 + } + } + else + { + chancetohit += (RuleR(Combat,HitBonusPerLevel) * level_difference); + } + + mlog(COMBAT__TOHIT, "Chance to hit after level diff calc %.2f", chancetohit); + + chancetohit -= ((float)defender->GetAGI() * RuleR(Combat, AgiHitFactor)); + + mlog(COMBAT__TOHIT, "Chance to hit after agil calc %.2f", chancetohit); + + if(attacker->IsClient()) + { + chancetohit -= (RuleR(Combat,WeaponSkillFalloff) * (attacker->CastToClient()->MaxSkill(skillinuse) - attacker->GetSkill(skillinuse))); + mlog(COMBAT__TOHIT, "Chance to hit after weapon falloff calc (attack) %.2f", chancetohit); + } + + if(defender->IsClient()) + { + chancetohit += (RuleR(Combat,WeaponSkillFalloff) * (defender->CastToClient()->MaxSkill(SkillDefense) - defender->GetSkill(SkillDefense))); + mlog(COMBAT__TOHIT, "Chance to hit after weapon falloff calc (defense) %.2f", chancetohit); + } + + //I dont think this is 100% correct, but at least it does something... + if(attacker->spellbonuses.MeleeSkillCheckSkill == skillinuse || attacker->spellbonuses.MeleeSkillCheckSkill == 255) { + chancetohit += attacker->spellbonuses.MeleeSkillCheck; + mlog(COMBAT__TOHIT, "Applied spell melee skill bonus %d, yeilding %.2f", attacker->spellbonuses.MeleeSkillCheck, chancetohit); + } + if(attacker->itembonuses.MeleeSkillCheckSkill == skillinuse || attacker->itembonuses.MeleeSkillCheckSkill == 255) { + chancetohit += attacker->itembonuses.MeleeSkillCheck; + mlog(COMBAT__TOHIT, "Applied item melee skill bonus %d, yeilding %.2f", attacker->spellbonuses.MeleeSkillCheck, chancetohit); + } + + //subtract off avoidance by the defender. (Live AA - Combat Agility) + bonus = defender->spellbonuses.AvoidMeleeChance + defender->itembonuses.AvoidMeleeChance + (defender->aabonuses.AvoidMeleeChance * 10); + + //AA Live - Elemental Agility + if (IsPet()) { + Mob *owner = defender->GetOwner(); + if (!owner)return false; + bonus += (owner->aabonuses.PetAvoidance + owner->spellbonuses.PetAvoidance + owner->itembonuses.PetAvoidance)*10; + } + + if(bonus > 0) { + chancetohit -= ((bonus * chancetohit) / 1000); + mlog(COMBAT__TOHIT, "Applied avoidance chance %.2f/10, yeilding %.2f", bonus, chancetohit); + } + + if(attacker->IsNPC()) + chancetohit += (chancetohit * attacker->CastToNPC()->GetAccuracyRating() / 1000); + + mlog(COMBAT__TOHIT, "Chance to hit after accuracy rating calc %.2f", chancetohit); + + float hitBonus = 0; + + /* + Kayen: Unknown if the HitChance and Accuracy effect's should modify 'chancetohit' + cumulatively or successively. For now all hitBonuses are cumulative. + */ + + hitBonus += attacker->itembonuses.HitChanceEffect[skillinuse] + + attacker->spellbonuses.HitChanceEffect[skillinuse]+ + attacker->itembonuses.HitChanceEffect[HIGHEST_SKILL+1] + + attacker->spellbonuses.HitChanceEffect[HIGHEST_SKILL+1]; + + //Accuracy = Spell Effect , HitChance = 'Accuracy' from Item Effect + //Only AA derived accuracy can be skill limited. ie (Precision of the Pathfinder, Dead Aim) + hitBonus += (attacker->itembonuses.Accuracy[HIGHEST_SKILL+1] + + attacker->spellbonuses.Accuracy[HIGHEST_SKILL+1] + + attacker->aabonuses.Accuracy[HIGHEST_SKILL+1] + + attacker->aabonuses.Accuracy[skillinuse] + + attacker->itembonuses.HitChance) / 15.0f; + + hitBonus += chance_mod; //Modifier applied from casted/disc skill attacks. + + chancetohit += ((chancetohit * hitBonus) / 100.0f); + + if(skillinuse == SkillArchery) + chancetohit -= (chancetohit * RuleR(Combat, ArcheryHitPenalty)) / 100.0f; + + chancetohit = mod_hit_chance(chancetohit, skillinuse, attacker); + + // Chance to hit; Max 95%, Min 30% + if(chancetohit > 1000) { + //if chance to hit is crazy high, that means a discipline is in use, and let it stay there + } + else if(chancetohit > 95) { + chancetohit = 95; + } + else if(chancetohit < 5) { + chancetohit = 5; + } + + //I dont know the best way to handle a garunteed hit discipline being used + //agains a garunteed riposte (for example) discipline... for now, garunteed hit wins + + + #if EQDEBUG>=11 + LogFile->write(EQEMuLog::Debug, "3 FINAL calculated chance to hit is: %5.2f", chancetohit); + #endif + + // + // Did we hit? + // + + float tohit_roll = MakeRandomFloat(0, 100); + + mlog(COMBAT__TOHIT, "Final hit chance: %.2f%%. Hit roll %.2f", chancetohit, tohit_roll); + + return(tohit_roll <= chancetohit); +} + + +bool Mob::AvoidDamage(Mob* other, int32 &damage, bool CanRiposte) +{ + /* solar: called when a mob is attacked, does the checks to see if it's a hit + * and does other mitigation checks. 'this' is the mob being attacked. + * + * special return values: + * -1 - block + * -2 - parry + * -3 - riposte + * -4 - dodge + * + */ + float skill; + float bonus; + float RollTable[4] = {0,0,0,0}; + float roll; + Mob *attacker=other; + Mob *defender=this; + + //garunteed hit + bool ghit = false; + if((attacker->spellbonuses.MeleeSkillCheck + attacker->itembonuses.MeleeSkillCheck) > 500) + ghit = true; + + ////////////////////////////////////////////////////////// + // make enrage same as riposte + ///////////////////////////////////////////////////////// + if (IsEnraged() && other->InFrontMob(this, other->GetX(), other->GetY())) { + damage = -3; + mlog(COMBAT__DAMAGE, "I am enraged, riposting frontal attack."); + } + + ///////////////////////////////////////////////////////// + // riposte + ///////////////////////////////////////////////////////// + float riposte_chance = 0.0f; + if (CanRiposte && damage > 0 && CanThisClassRiposte() && other->InFrontMob(this, other->GetX(), other->GetY())) + { + riposte_chance = (100.0f + (float)defender->aabonuses.RiposteChance + (float)defender->spellbonuses.RiposteChance + (float)defender->itembonuses.RiposteChance) / 100.0f; + skill = GetSkill(SkillRiposte); + if (IsClient()) { + CastToClient()->CheckIncreaseSkill(SkillRiposte, other, -10); + } + + if (!ghit) { //if they are not using a garunteed hit discipline + bonus = 2.0 + skill/60.0 + (GetDEX()/200); + bonus *= riposte_chance; + bonus = mod_riposte_chance(bonus, attacker); + RollTable[0] = bonus + (itembonuses.HeroicDEX / 25); // 25 heroic = 1%, applies to ripo, parry, block + } + } + + /////////////////////////////////////////////////////// + // block + /////////////////////////////////////////////////////// + + bool bBlockFromRear = false; + bool bShieldBlockFromRear = false; + + if (this->IsClient()) { + int aaChance = 0; + + // a successful roll on this does not mean a successful block is forthcoming. only that a chance to block + // from a direction other than the rear is granted. + + //Live AA - HightenedAwareness + int BlockBehindChance = aabonuses.BlockBehind + spellbonuses.BlockBehind + itembonuses.BlockBehind; + + if (BlockBehindChance && (BlockBehindChance > MakeRandomInt(1, 100))){ + bBlockFromRear = true; + + if (spellbonuses.BlockBehind || itembonuses.BlockBehind) + bShieldBlockFromRear = true; //This bonus should allow a chance to Shield Block from behind. + } + } + + float block_chance = 0.0f; + if (damage > 0 && CanThisClassBlock() && (other->InFrontMob(this, other->GetX(), other->GetY()) || bBlockFromRear)) { + block_chance = (100.0f + (float)spellbonuses.IncreaseBlockChance + (float)itembonuses.IncreaseBlockChance) / 100.0f; + skill = CastToClient()->GetSkill(SkillBlock); + if (IsClient()) { + CastToClient()->CheckIncreaseSkill(SkillBlock, other, -10); + } + + if (!ghit) { //if they are not using a garunteed hit discipline + bonus = 2.0 + skill/35.0 + (GetDEX()/200); + bonus = mod_block_chance(bonus, attacker); + RollTable[1] = RollTable[0] + (bonus * block_chance); + } + } + else{ + RollTable[1] = RollTable[0]; + } + + if(damage > 0 && HasShieldEquiped() && (aabonuses.ShieldBlock || spellbonuses.ShieldBlock || itembonuses.ShieldBlock) + && (other->InFrontMob(this, other->GetX(), other->GetY()) || bShieldBlockFromRear)) { + + float bonusShieldBlock = 0.0f; + bonusShieldBlock = aabonuses.ShieldBlock + spellbonuses.ShieldBlock + itembonuses.ShieldBlock; + RollTable[1] += bonusShieldBlock; + } + + if(damage > 0 && (aabonuses.TwoHandBluntBlock || spellbonuses.TwoHandBluntBlock || itembonuses.TwoHandBluntBlock) + && (other->InFrontMob(this, other->GetX(), other->GetY()) || bShieldBlockFromRear)) { + bool equiped2 = CastToClient()->m_inv.GetItem(13); + if(equiped2) { + uint8 TwoHandBlunt = CastToClient()->m_inv.GetItem(13)->GetItem()->ItemType; + float bonusStaffBlock = 0.0f; + if(TwoHandBlunt == ItemType2HBlunt) { + + bonusStaffBlock = aabonuses.TwoHandBluntBlock + spellbonuses.TwoHandBluntBlock + itembonuses.TwoHandBluntBlock; + RollTable[1] += bonusStaffBlock; + } + } + } + + ////////////////////////////////////////////////////// + // parry + ////////////////////////////////////////////////////// + float parry_chance = 0.0f; + if (damage > 0 && CanThisClassParry() && other->InFrontMob(this, other->GetX(), other->GetY())) + { + parry_chance = (100.0f + (float)defender->spellbonuses.ParryChance + (float)defender->itembonuses.ParryChance) / 100.0f; + skill = CastToClient()->GetSkill(SkillParry); + if (IsClient()) { + CastToClient()->CheckIncreaseSkill(SkillParry, other, -10); + } + + if (!ghit) { //if they are not using a garunteed hit discipline + bonus = 2.0 + skill/60.0 + (GetDEX()/200); + bonus *= parry_chance; + bonus = mod_parry_chance(bonus, attacker); + RollTable[2] = RollTable[1] + bonus; + } + } + else{ + RollTable[2] = RollTable[1]; + } + + //////////////////////////////////////////////////////// + // dodge + //////////////////////////////////////////////////////// + float dodge_chance = 0.0f; + if (damage > 0 && CanThisClassDodge() && other->InFrontMob(this, other->GetX(), other->GetY())) + { + dodge_chance = (100.0f + (float)defender->spellbonuses.DodgeChance + (float)defender->itembonuses.DodgeChance) / 100.0f; + skill = CastToClient()->GetSkill(SkillDodge); + if (IsClient()) { + CastToClient()->CheckIncreaseSkill(SkillDodge, other, -10); + } + + if (!ghit) { //if they are not using a garunteed hit discipline + bonus = 2.0 + skill/60.0 + (GetAGI()/200); + bonus *= dodge_chance; + //DCBOOMKAR + bonus = mod_dodge_chance(bonus, attacker); + RollTable[3] = RollTable[2] + bonus - (itembonuses.HeroicDEX / 25) + (itembonuses.HeroicAGI / 25); + } + } + else{ + RollTable[3] = RollTable[2]; + } + + if(damage > 0){ + roll = MakeRandomFloat(0,100); + if(roll <= RollTable[0]){ + damage = -3; + } + else if(roll <= RollTable[1]){ + damage = -1; + } + else if(roll <= RollTable[2]){ + damage = -2; + } + else if(roll <= RollTable[3]){ + damage = -4; + } + } + + mlog(COMBAT__DAMAGE, "Final damage after all avoidances: %d", damage); + + if (damage < 0) + return true; + return false; +} + +void Mob::MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit, ExtraAttackOptions *opts) +{ + if (damage <= 0) + return; + + Mob* defender = this; + float aa_mit = (aabonuses.CombatStability + itembonuses.CombatStability + + spellbonuses.CombatStability) / 100.0f; + + if (RuleB(Combat, UseIntervalAC)) { + float softcap = (GetSkill(SkillDefense) + GetLevel()) * + RuleR(Combat, SoftcapFactor) * (1.0 + aa_mit); + float mitigation_rating = 0.0; + float attack_rating = 0.0; + int shield_ac = 0; + int armor = 0; + float weight = 0.0; + + float monkweight = RuleI(Combat, MonkACBonusWeight); + monkweight = mod_monk_weight(monkweight, attacker); + + if (IsClient()) { + armor = CastToClient()->GetRawACNoShield(shield_ac); + weight = (CastToClient()->CalcCurrentWeight() / 10.0); + } else if (IsNPC()) { + armor = CastToNPC()->GetRawAC(); + + if (!IsPet()) + armor = (armor / RuleR(Combat, NPCACFactor)); + + armor += spellbonuses.AC + itembonuses.AC + 1; + } + + if (opts) { + armor *= (1.0f - opts->armor_pen_percent); + armor -= opts->armor_pen_flat; + } + + if (RuleB(Combat, OldACSoftcapRules)) { + if (GetClass() == WIZARD || GetClass() == MAGICIAN || + GetClass() == NECROMANCER || GetClass() == ENCHANTER) + softcap = RuleI(Combat, ClothACSoftcap); + else if (GetClass() == MONK && weight <= monkweight) + softcap = RuleI(Combat, MonkACSoftcap); + else if(GetClass() == DRUID || GetClass() == BEASTLORD || GetClass() == MONK) + softcap = RuleI(Combat, LeatherACSoftcap); + else if(GetClass() == SHAMAN || GetClass() == ROGUE || + GetClass() == BERSERKER || GetClass() == RANGER) + softcap = RuleI(Combat, ChainACSoftcap); + else + softcap = RuleI(Combat, PlateACSoftcap); + } + + softcap += shield_ac; + armor += shield_ac; + if (RuleB(Combat, OldACSoftcapRules)) + softcap += (softcap * (aa_mit * RuleR(Combat, AAMitigationACFactor))); + if (armor > softcap) { + int softcap_armor = armor - softcap; + if (RuleB(Combat, OldACSoftcapRules)) { + if (GetClass() == WARRIOR) + softcap_armor = softcap_armor * RuleR(Combat, WarriorACSoftcapReturn); + else if (GetClass() == SHADOWKNIGHT || GetClass() == PALADIN || + (GetClass() == MONK && weight <= monkweight)) + softcap_armor = softcap_armor * RuleR(Combat, KnightACSoftcapReturn); + else if (GetClass() == CLERIC || GetClass() == BARD || + GetClass() == BERSERKER || GetClass() == ROGUE || + GetClass() == SHAMAN || GetClass() == MONK) + softcap_armor = softcap_armor * RuleR(Combat, LowPlateChainACSoftcapReturn); + else if (GetClass() == RANGER || GetClass() == BEASTLORD) + softcap_armor = softcap_armor * RuleR(Combat, LowChainLeatherACSoftcapReturn); + else if (GetClass() == WIZARD || GetClass() == MAGICIAN || + GetClass() == NECROMANCER || GetClass() == ENCHANTER || + GetClass() == DRUID) + softcap_armor = softcap_armor * RuleR(Combat, CasterACSoftcapReturn); + else + softcap_armor = softcap_armor * RuleR(Combat, MiscACSoftcapReturn); + } else { + if (GetClass() == WARRIOR) + softcap_armor *= RuleR(Combat, WarACSoftcapReturn); + else if (GetClass() == PALADIN || GetClass() == SHADOWKNIGHT) + softcap_armor *= RuleR(Combat, PalShdACSoftcapReturn); + else if (GetClass() == CLERIC || GetClass() == RANGER || + GetClass() == MONK || GetClass() == BARD) + softcap_armor *= RuleR(Combat, ClrRngMnkBrdACSoftcapReturn); + else if (GetClass() == DRUID || GetClass() == NECROMANCER || + GetClass() == WIZARD || GetClass() == ENCHANTER || + GetClass() == MAGICIAN) + softcap_armor *= RuleR(Combat, DruNecWizEncMagACSoftcapReturn); + else if (GetClass() == ROGUE || GetClass() == SHAMAN || + GetClass() == BEASTLORD || GetClass() == BERSERKER) + softcap_armor *= RuleR(Combat, RogShmBstBerACSoftcapReturn); + else + softcap_armor *= RuleR(Combat, MiscACSoftcapReturn); + } + armor = softcap + softcap_armor; + } + + if (GetClass() == WIZARD || GetClass() == MAGICIAN || + GetClass() == NECROMANCER || GetClass() == ENCHANTER) + mitigation_rating = ((GetSkill(SkillDefense) + itembonuses.HeroicAGI/10) / 4.0) + armor + 1; + else + mitigation_rating = ((GetSkill(SkillDefense) + itembonuses.HeroicAGI/10) / 3.0) + (armor * 1.333333) + 1; + mitigation_rating *= 0.847; + + mitigation_rating = mod_mitigation_rating(mitigation_rating, attacker); + + if (attacker->IsClient()) + attack_rating = (attacker->CastToClient()->CalcATK() + ((attacker->GetSTR()-66) * 0.9) + (attacker->GetSkill(SkillOffense)*1.345)); + else + attack_rating = (attacker->GetATK() + (attacker->GetSkill(SkillOffense)*1.345) + ((attacker->GetSTR()-66) * 0.9)); + + attack_rating = attacker->mod_attack_rating(attack_rating, this); + + damage = GetMeleeMitDmg(attacker, damage, minhit, mitigation_rating, attack_rating); + } else { + //////////////////////////////////////////////////////// + // Scorpious2k: Include AC in the calculation + // use serverop variables to set values + int myac = GetAC(); + if(opts) { + myac *= (1.0f - opts->armor_pen_percent); + myac -= opts->armor_pen_flat; + } + + if (damage > 0 && myac > 0) { + int acfail=1000; + char tmp[10]; + + if (database.GetVariable("ACfail", tmp, 9)) { + acfail = (int) (atof(tmp) * 100); + if (acfail>100) acfail=100; + } + + if (acfail<=0 || MakeRandomInt(0, 100)>acfail) { + float acreduction=1; + int acrandom=300; + if (database.GetVariable("ACreduction", tmp, 9)) + { + acreduction=atof(tmp); + if (acreduction>100) acreduction=100; + } + + if (database.GetVariable("ACrandom", tmp, 9)) + { + acrandom = (int) ((atof(tmp)+1) * 100); + if (acrandom>10100) acrandom=10100; + } + + if (acreduction>0) { + damage -= (int) (GetAC() * acreduction/100.0f); + } + if (acrandom>0) { + damage -= (myac * MakeRandomInt(0, acrandom) / 10000); + } + if (damage<1) damage=1; + mlog(COMBAT__DAMAGE, "AC Damage Reduction: fail chance %d%%. Failed. Reduction %.3f%%, random %d. Resulting damage %d.", acfail, acreduction, acrandom, damage); + } else { + mlog(COMBAT__DAMAGE, "AC Damage Reduction: fail chance %d%%. Did not fail.", acfail); + } + } + + damage -= (aa_mit * damage); + + if(damage != 0 && damage < minhit) + damage = minhit; + //reduce the damage from shielding item and aa based on the min dmg + //spells offer pure mitigation + damage -= (minhit * defender->itembonuses.MeleeMitigation / 100); + damage -= (damage * defender->spellbonuses.MeleeMitigation / 100); + } + + if (damage < 0) + damage = 0; +} + +// This is called when the Mob is the one being hit +int32 Mob::GetMeleeMitDmg(Mob *attacker, int32 damage, int32 minhit, + float mit_rating, float atk_rating) +{ + float d = 10.0; + float mit_roll = MakeRandomFloat(0, mit_rating); + float atk_roll = MakeRandomFloat(0, atk_rating); + + if (atk_roll > mit_roll) { + float a_diff = atk_roll - mit_roll; + float thac0 = atk_rating * RuleR(Combat, ACthac0Factor); + float thac0cap = attacker->GetLevel() * 9 + 20; + if (thac0 > thac0cap) + thac0 = thac0cap; + + d -= 10.0 * (a_diff / thac0); + } else if (mit_roll > atk_roll) { + float m_diff = mit_roll - atk_roll; + float thac20 = mit_rating * RuleR(Combat, ACthac20Factor); + float thac20cap = GetLevel() * 9 + 20; + if (thac20 > thac20cap) + thac20 = thac20cap; + + d += 10.0 * (m_diff / thac20); + } + + if (d < 0.0) + d = 0.0; + else if (d > 20.0) + d = 20.0; + + float interval = (damage - minhit) / 20.0; + damage -= ((int)d * interval); + + damage -= (minhit * itembonuses.MeleeMitigation / 100); + damage -= (damage * spellbonuses.MeleeMitigation / 100); + return damage; +} + +// This is called when the Client is the one being hit +int32 Client::GetMeleeMitDmg(Mob *attacker, int32 damage, int32 minhit, + float mit_rating, float atk_rating) +{ + if (!attacker->IsNPC() || RuleB(Combat, UseOldDamageIntervalRules)) + return Mob::GetMeleeMitDmg(attacker, damage, minhit, mit_rating, atk_rating); + int d = 10; + // floats for the rounding issues + float dmg_interval = (damage - minhit) / 19.0; + float dmg_bonus = minhit - dmg_interval; + float spellMeleeMit = spellbonuses.MeleeMitigation / 100.0; + if (GetClass() == WARRIOR) + spellMeleeMit += 0.05; + dmg_bonus -= dmg_bonus * (itembonuses.MeleeMitigation / 100.0); + dmg_interval -= dmg_interval * spellMeleeMit; + + float mit_roll = MakeRandomFloat(0, mit_rating); + float atk_roll = MakeRandomFloat(0, atk_rating); + + if (atk_roll > mit_roll) { + float a_diff = atk_roll - mit_roll; + float thac0 = atk_rating * RuleR(Combat, ACthac0Factor); + float thac0cap = attacker->GetLevel() * 9 + 20; + if (thac0 > thac0cap) + thac0 = thac0cap; + + d += 10 * (a_diff / thac0); + } else if (mit_roll > atk_roll) { + float m_diff = mit_roll - atk_roll; + float thac20 = mit_rating * RuleR(Combat, ACthac20Factor); + float thac20cap = GetLevel() * 9 + 20; + if (thac20 > thac20cap) + thac20 = thac20cap; + + d -= 10 * (m_diff / thac20); + } + + if (d < 1) + d = 1; + else if (d > 20) + d = 20; + + return static_cast((dmg_bonus + dmg_interval * d)); +} + +//Returns the weapon damage against the input mob +//if we cannot hit the mob with the current weapon we will get a value less than or equal to zero +//Else we know we can hit. +//GetWeaponDamage(mob*, const Item_Struct*) is intended to be used for mobs or any other situation where we do not have a client inventory item +//GetWeaponDamage(mob*, const ItemInst*) is intended to be used for situations where we have a client inventory item +int Mob::GetWeaponDamage(Mob *against, const Item_Struct *weapon_item) { + int dmg = 0; + int banedmg = 0; + + //can't hit invulnerable stuff with weapons. + if(against->GetInvul() || against->GetSpecialAbility(IMMUNE_MELEE)){ + return 0; + } + + //check to see if our weapons or fists are magical. + if(against->GetSpecialAbility(IMMUNE_MELEE_NONMAGICAL)){ + if(weapon_item){ + if(weapon_item->Magic){ + dmg = weapon_item->Damage; + + //this is more for non weapon items, ex: boots for kick + //they don't have a dmg but we should be able to hit magical + dmg = dmg <= 0 ? 1 : dmg; + } + else + return 0; + } + else{ + if((GetClass() == MONK || GetClass() == BEASTLORD) && GetLevel() >= 30){ + dmg = GetMonkHandToHandDamage(); + } + else if(GetOwner() && GetLevel() >= RuleI(Combat, PetAttackMagicLevel)){ + //pets wouldn't actually use this but... + //it gives us an idea if we can hit due to the dual nature of this function + dmg = 1; + } + else if(GetSpecialAbility(SPECATK_MAGICAL)) + { + dmg = 1; + } + else + return 0; + } + } + else{ + if(weapon_item){ + dmg = weapon_item->Damage; + + dmg = dmg <= 0 ? 1 : dmg; + } + else{ + if(GetClass() == MONK || GetClass() == BEASTLORD){ + dmg = GetMonkHandToHandDamage(); + } + else{ + dmg = 1; + } + } + } + + int eledmg = 0; + if(!against->GetSpecialAbility(IMMUNE_MAGIC)){ + if(weapon_item && weapon_item->ElemDmgAmt){ + //we don't check resist for npcs here + eledmg = weapon_item->ElemDmgAmt; + dmg += eledmg; + } + } + + if(against->GetSpecialAbility(IMMUNE_MELEE_EXCEPT_BANE)){ + if(weapon_item){ + if(weapon_item->BaneDmgBody == against->GetBodyType()){ + banedmg += weapon_item->BaneDmgAmt; + } + + if(weapon_item->BaneDmgRace == against->GetRace()){ + banedmg += weapon_item->BaneDmgRaceAmt; + } + } + + if(!eledmg && !banedmg){ + if(!GetSpecialAbility(SPECATK_BANE)) + return 0; + else + return 1; + } + else + dmg += banedmg; + } + else{ + if(weapon_item){ + if(weapon_item->BaneDmgBody == against->GetBodyType()){ + banedmg += weapon_item->BaneDmgAmt; + } + + if(weapon_item->BaneDmgRace == against->GetRace()){ + banedmg += weapon_item->BaneDmgRaceAmt; + } + } + + dmg += (banedmg + eledmg); + } + + if(dmg <= 0){ + return 0; + } + else + return dmg; +} + +int Mob::GetWeaponDamage(Mob *against, const ItemInst *weapon_item, uint32 *hate) +{ + int dmg = 0; + int banedmg = 0; + + if(!against || against->GetInvul() || against->GetSpecialAbility(IMMUNE_MELEE)){ + return 0; + } + + //check for items being illegally attained + if(weapon_item){ + const Item_Struct *mWeaponItem = weapon_item->GetItem(); + if(mWeaponItem){ + if(mWeaponItem->ReqLevel > GetLevel()){ + return 0; + } + + if(!weapon_item->IsEquipable(GetBaseRace(), GetClass())){ + return 0; + } + } + else{ + return 0; + } + } + + if(against->GetSpecialAbility(IMMUNE_MELEE_NONMAGICAL)){ + if(weapon_item){ + // check to see if the weapon is magic + bool MagicWeapon = false; + if(weapon_item->GetItem() && weapon_item->GetItem()->Magic) + MagicWeapon = true; + else { + if(spellbonuses.MagicWeapon || itembonuses.MagicWeapon) + MagicWeapon = true; + } + + if(MagicWeapon) { + + if(IsClient() && GetLevel() < weapon_item->GetItem()->RecLevel){ + dmg = CastToClient()->CalcRecommendedLevelBonus(GetLevel(), weapon_item->GetItem()->RecLevel, weapon_item->GetItem()->Damage); + } + else{ + dmg = weapon_item->GetItem()->Damage; + } + + for(int x = 0; x < MAX_AUGMENT_SLOTS; x++){ + if(weapon_item->GetAugment(x) && weapon_item->GetAugment(x)->GetItem()){ + dmg += weapon_item->GetAugment(x)->GetItem()->Damage; + if (hate) *hate += weapon_item->GetAugment(x)->GetItem()->Damage + weapon_item->GetAugment(x)->GetItem()->ElemDmgAmt; + } + } + dmg = dmg <= 0 ? 1 : dmg; + } + else + return 0; + } + else{ + if((GetClass() == MONK || GetClass() == BEASTLORD) && GetLevel() >= 30){ + dmg = GetMonkHandToHandDamage(); + if (hate) *hate += dmg; + } + else if(GetOwner() && GetLevel() >= RuleI(Combat, PetAttackMagicLevel)){ //pets wouldn't actually use this but... + dmg = 1; //it gives us an idea if we can hit + } + else if(GetSpecialAbility(SPECATK_MAGICAL)){ + dmg = 1; + } + else + return 0; + } + } + else{ + if(weapon_item){ + if(weapon_item->GetItem()){ + + if(IsClient() && GetLevel() < weapon_item->GetItem()->RecLevel){ + dmg = CastToClient()->CalcRecommendedLevelBonus(GetLevel(), weapon_item->GetItem()->RecLevel, weapon_item->GetItem()->Damage); + } + else{ + dmg = weapon_item->GetItem()->Damage; + } + + for(int x = 0; x < MAX_AUGMENT_SLOTS; x++){ + if(weapon_item->GetAugment(x) && weapon_item->GetAugment(x)->GetItem()){ + dmg += weapon_item->GetAugment(x)->GetItem()->Damage; + if (hate) *hate += weapon_item->GetAugment(x)->GetItem()->Damage + weapon_item->GetAugment(x)->GetItem()->ElemDmgAmt; + } + } + dmg = dmg <= 0 ? 1 : dmg; + } + } + else{ + if(GetClass() == MONK || GetClass() == BEASTLORD){ + dmg = GetMonkHandToHandDamage(); + if (hate) *hate += dmg; + } + else{ + dmg = 1; + } + } + } + + int eledmg = 0; + if(!against->GetSpecialAbility(IMMUNE_MAGIC)){ + if(weapon_item && weapon_item->GetItem() && weapon_item->GetItem()->ElemDmgAmt){ + if(IsClient() && GetLevel() < weapon_item->GetItem()->RecLevel){ + eledmg = CastToClient()->CalcRecommendedLevelBonus(GetLevel(), weapon_item->GetItem()->RecLevel, weapon_item->GetItem()->ElemDmgAmt); + } + else{ + eledmg = weapon_item->GetItem()->ElemDmgAmt; + } + + if(eledmg) + { + eledmg = (eledmg * against->ResistSpell(weapon_item->GetItem()->ElemDmgType, 0, this) / 100); + } + } + + if(weapon_item){ + for(int x = 0; x < MAX_AUGMENT_SLOTS; x++){ + if(weapon_item->GetAugment(x) && weapon_item->GetAugment(x)->GetItem()){ + if(weapon_item->GetAugment(x)->GetItem()->ElemDmgAmt) + eledmg += (weapon_item->GetAugment(x)->GetItem()->ElemDmgAmt * against->ResistSpell(weapon_item->GetAugment(x)->GetItem()->ElemDmgType, 0, this) / 100); + } + } + } + } + + if(against->GetSpecialAbility(IMMUNE_MELEE_EXCEPT_BANE)){ + if(weapon_item && weapon_item->GetItem()){ + if(weapon_item->GetItem()->BaneDmgBody == against->GetBodyType()){ + if(IsClient() && GetLevel() < weapon_item->GetItem()->RecLevel){ + banedmg += CastToClient()->CalcRecommendedLevelBonus(GetLevel(), weapon_item->GetItem()->RecLevel, weapon_item->GetItem()->BaneDmgAmt); + } + else{ + banedmg += weapon_item->GetItem()->BaneDmgAmt; + } + } + + if(weapon_item->GetItem()->BaneDmgRace == against->GetRace()){ + if(IsClient() && GetLevel() < weapon_item->GetItem()->RecLevel){ + banedmg += CastToClient()->CalcRecommendedLevelBonus(GetLevel(), weapon_item->GetItem()->RecLevel, weapon_item->GetItem()->BaneDmgRaceAmt); + } + else{ + banedmg += weapon_item->GetItem()->BaneDmgRaceAmt; + } + } + + for(int x = 0; x < MAX_AUGMENT_SLOTS; x++){ + if(weapon_item->GetAugment(x) && weapon_item->GetAugment(x)->GetItem()){ + if(weapon_item->GetAugment(x)->GetItem()->BaneDmgBody == against->GetBodyType()){ + banedmg += weapon_item->GetAugment(x)->GetItem()->BaneDmgAmt; + } + + if(weapon_item->GetAugment(x)->GetItem()->BaneDmgRace == against->GetRace()){ + banedmg += weapon_item->GetAugment(x)->GetItem()->BaneDmgRaceAmt; + } + } + } + } + + if(!eledmg && !banedmg) + { + if(!GetSpecialAbility(SPECATK_BANE)) + return 0; + else + return 1; + } + else { + dmg += (banedmg + eledmg); + if (hate) *hate += banedmg; + } + } + else{ + if(weapon_item && weapon_item->GetItem()){ + if(weapon_item->GetItem()->BaneDmgBody == against->GetBodyType()){ + if(IsClient() && GetLevel() < weapon_item->GetItem()->RecLevel){ + banedmg += CastToClient()->CalcRecommendedLevelBonus(GetLevel(), weapon_item->GetItem()->RecLevel, weapon_item->GetItem()->BaneDmgAmt); + } + else{ + banedmg += weapon_item->GetItem()->BaneDmgAmt; + } + } + + if(weapon_item->GetItem()->BaneDmgRace == against->GetRace()){ + if(IsClient() && GetLevel() < weapon_item->GetItem()->RecLevel){ + banedmg += CastToClient()->CalcRecommendedLevelBonus(GetLevel(), weapon_item->GetItem()->RecLevel, weapon_item->GetItem()->BaneDmgRaceAmt); + } + else{ + banedmg += weapon_item->GetItem()->BaneDmgRaceAmt; + } + } + + for(int x = 0; x < MAX_AUGMENT_SLOTS; x++){ + if(weapon_item->GetAugment(x) && weapon_item->GetAugment(x)->GetItem()){ + if(weapon_item->GetAugment(x)->GetItem()->BaneDmgBody == against->GetBodyType()){ + banedmg += weapon_item->GetAugment(x)->GetItem()->BaneDmgAmt; + } + + if(weapon_item->GetAugment(x)->GetItem()->BaneDmgRace == against->GetRace()){ + banedmg += weapon_item->GetAugment(x)->GetItem()->BaneDmgRaceAmt; + } + } + } + } + dmg += (banedmg + eledmg); + if (hate) *hate += banedmg; + } + + if(dmg <= 0){ + return 0; + } + else + return dmg; +} + +//note: throughout this method, setting `damage` to a negative is a way to +//stop the attack calculations +// IsFromSpell added to allow spell effects to use Attack. (Mainly for the Rampage AA right now.) +bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) +{ + if (!other) { + SetTarget(nullptr); + LogFile->write(EQEMuLog::Error, "A null Mob object was passed to Client::Attack() for evaluation!"); + return false; + } + + if(!GetTarget()) + SetTarget(other); + + mlog(COMBAT__ATTACKS, "Attacking %s with hand %d %s", other?other->GetName():"(nullptr)", Hand, bRiposte?"(this is a riposte)":""); + + //SetAttackTimer(); + if ( + (IsCasting() && GetClass() != BARD && !IsFromSpell) + || other == nullptr + || ((IsClient() && CastToClient()->dead) || (other->IsClient() && other->CastToClient()->dead)) + || (GetHP() < 0) + || (!IsAttackAllowed(other)) + ) { + mlog(COMBAT__ATTACKS, "Attack canceled, invalid circumstances."); + return false; // Only bards can attack while casting + } + + if(DivineAura() && !GetGM()) {//cant attack while invulnerable unless your a gm + mlog(COMBAT__ATTACKS, "Attack canceled, Divine Aura is in effect."); + Message_StringID(MT_DefaultText, DIVINE_AURA_NO_ATK); //You can't attack while invulnerable! + return false; + } + + if (GetFeigned()) + return false; // Rogean: How can you attack while feigned? Moved up from Aggro Code. + + + ItemInst* weapon; + if (Hand == 14){ // Kaiyodo - Pick weapon from the attacking hand + weapon = GetInv().GetItem(SLOT_SECONDARY); + OffHandAtk(true); + } + else{ + weapon = GetInv().GetItem(SLOT_PRIMARY); + OffHandAtk(false); + } + + if(weapon != nullptr) { + if (!weapon->IsWeapon()) { + mlog(COMBAT__ATTACKS, "Attack canceled, Item %s (%d) is not a weapon.", weapon->GetItem()->Name, weapon->GetID()); + return(false); + } + mlog(COMBAT__ATTACKS, "Attacking with weapon: %s (%d)", weapon->GetItem()->Name, weapon->GetID()); + } else { + mlog(COMBAT__ATTACKS, "Attacking without a weapon."); + } + + // calculate attack_skill and skillinuse depending on hand and weapon + // also send Packet to near clients + SkillUseTypes skillinuse; + AttackAnimation(skillinuse, Hand, weapon); + mlog(COMBAT__ATTACKS, "Attacking with %s in slot %d using skill %d", weapon?weapon->GetItem()->Name:"Fist", Hand, skillinuse); + + /// Now figure out damage + int damage = 0; + uint8 mylevel = GetLevel() ? GetLevel() : 1; + uint32 hate = 0; + if (weapon) hate = weapon->GetItem()->Damage + weapon->GetItem()->ElemDmgAmt; + int weapon_damage = GetWeaponDamage(other, weapon, &hate); + if (hate == 0 && weapon_damage > 1) hate = weapon_damage; + + //if weapon damage > 0 then we know we can hit the target with this weapon + //otherwise we cannot and we set the damage to -5 later on + if(weapon_damage > 0){ + + //Berserker Berserk damage bonus + if(IsBerserk() && GetClass() == BERSERKER){ + int bonus = 3 + GetLevel()/10; //unverified + weapon_damage = weapon_damage * (100+bonus) / 100; + mlog(COMBAT__DAMAGE, "Berserker damage bonus increases DMG to %d", weapon_damage); + } + + //try a finishing blow.. if successful end the attack + if(TryFinishingBlow(other, skillinuse)) + return (true); + + int min_hit = 1; + int max_hit = (2*weapon_damage*GetDamageTable(skillinuse)) / 100; + + if(GetLevel() < 10 && max_hit > RuleI(Combat, HitCapPre10)) + max_hit = (RuleI(Combat, HitCapPre10)); + else if(GetLevel() < 20 && max_hit > RuleI(Combat, HitCapPre20)) + max_hit = (RuleI(Combat, HitCapPre20)); + + CheckIncreaseSkill(skillinuse, other, -15); + CheckIncreaseSkill(SkillOffense, other, -15); + + + // *************************************************************** + // *** Calculate the damage bonus, if applicable, for this hit *** + // *************************************************************** + +#ifndef EQEMU_NO_WEAPON_DAMAGE_BONUS + + // If you include the preprocessor directive "#define EQEMU_NO_WEAPON_DAMAGE_BONUS", that indicates that you do not + // want damage bonuses added to weapon damage at all. This feature was requested by ChaosSlayer on the EQEmu Forums. + // + // This is not recommended for normal usage, as the damage bonus represents a non-trivial component of the DPS output + // of weapons wielded by higher-level melee characters (especially for two-handed weapons). + + int ucDamageBonus = 0; + + if( Hand == 13 && GetLevel() >= 28 && IsWarriorClass() ) + { + // Damage bonuses apply only to hits from the main hand (Hand == 13) by characters level 28 and above + // who belong to a melee class. If we're here, then all of these conditions apply. + + ucDamageBonus = GetWeaponDamageBonus( weapon ? weapon->GetItem() : (const Item_Struct*) nullptr ); + + min_hit += (int) ucDamageBonus; + max_hit += (int) ucDamageBonus; + hate += ucDamageBonus; + } +#endif + //Live AA - Sinister Strikes *Adds weapon damage bonus to offhand weapon. + if (Hand==14) { + if (aabonuses.SecondaryDmgInc || itembonuses.SecondaryDmgInc || spellbonuses.SecondaryDmgInc){ + + ucDamageBonus = GetWeaponDamageBonus( weapon ? weapon->GetItem() : (const Item_Struct*) nullptr ); + + min_hit += (int) ucDamageBonus; + max_hit += (int) ucDamageBonus; + hate += ucDamageBonus; + } + } + + min_hit += min_hit * GetMeleeMinDamageMod_SE(skillinuse) / 100; + + if(max_hit < min_hit) + max_hit = min_hit; + + if(RuleB(Combat, UseIntervalAC)) + damage = max_hit; + else + damage = MakeRandomInt(min_hit, max_hit); + + damage = mod_client_damage(damage, skillinuse, Hand, weapon, other); + + mlog(COMBAT__DAMAGE, "Damage calculated to %d (min %d, max %d, str %d, skill %d, DMG %d, lv %d)", + damage, min_hit, max_hit, GetSTR(), GetSkill(skillinuse), weapon_damage, mylevel); + + if(opts) { + damage *= opts->damage_percent; + damage += opts->damage_flat; + hate *= opts->hate_percent; + hate += opts->hate_flat; + } + + //check to see if we hit.. + if(!other->CheckHitChance(this, skillinuse, Hand)) { + mlog(COMBAT__ATTACKS, "Attack missed. Damage set to 0."); + damage = 0; + } else { //we hit, try to avoid it + other->AvoidDamage(this, damage); + other->MeleeMitigation(this, damage, min_hit, opts); + if(damage > 0) { + ApplyMeleeDamageBonus(skillinuse, damage); + damage += (itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse); + TryCriticalHit(other, skillinuse, damage, opts); + } + mlog(COMBAT__DAMAGE, "Final damage after all reductions: %d", damage); + } + + //riposte + bool slippery_attack = false; // Part of hack to allow riposte to become a miss, but still allow a Strikethrough chance (like on Live) + if (damage == -3) { + if (bRiposte) return false; + else { + if (Hand == 14) {// Do we even have it & was attack with mainhand? If not, don't bother with other calculations + //Live AA - SlipperyAttacks + //This spell effect most likely directly modifies the actual riposte chance when using offhand attack. + int16 OffhandRiposteFail = aabonuses.OffhandRiposteFail + itembonuses.OffhandRiposteFail + spellbonuses.OffhandRiposteFail; + OffhandRiposteFail *= -1; //Live uses a negative value for this. + + if (OffhandRiposteFail && + (OffhandRiposteFail > 99 || (MakeRandomInt(0, 100) < OffhandRiposteFail))) { + damage = 0; // Counts as a miss + slippery_attack = true; + } else + DoRiposte(other); + if (IsDead()) return false; + } + else + DoRiposte(other); + if (IsDead()) return false; + } + } + + if (((damage < 0) || slippery_attack) && !bRiposte && !IsStrikethrough) { // Hack to still allow Strikethrough chance w/ Slippery Attacks AA + int16 bonusStrikeThrough = itembonuses.StrikeThrough + spellbonuses.StrikeThrough + aabonuses.StrikeThrough; + + if(bonusStrikeThrough && (MakeRandomInt(0, 100) < bonusStrikeThrough)) { + Message_StringID(MT_StrikeThrough, STRIKETHROUGH_STRING); // You strike through your opponents defenses! + Attack(other, Hand, false, true); // Strikethrough only gives another attempted hit + return false; + } + } + } + else{ + damage = -5; + } + + // Hate Generation is on a per swing basis, regardless of a hit, miss, or block, its always the same. + // If we are this far, this means we are atleast making a swing. + + if (!bRiposte) // Ripostes never generate any aggro. + other->AddToHateList(this, hate); + + /////////////////////////////////////////////////////////// + ////// Send Attack Damage + /////////////////////////////////////////////////////////// + other->Damage(this, damage, SPELL_UNKNOWN, skillinuse); + + if (IsDead()) return false; + + MeleeLifeTap(damage); + + if (damage > 0) + CheckNumHitsRemaining(5); + + //break invis when you attack + if(invisible) { + mlog(COMBAT__ATTACKS, "Removing invisibility due to melee attack."); + BuffFadeByEffect(SE_Invisibility); + BuffFadeByEffect(SE_Invisibility2); + invisible = false; + } + if(invisible_undead) { + mlog(COMBAT__ATTACKS, "Removing invisibility vs. undead due to melee attack."); + BuffFadeByEffect(SE_InvisVsUndead); + BuffFadeByEffect(SE_InvisVsUndead2); + invisible_undead = false; + } + if(invisible_animals){ + mlog(COMBAT__ATTACKS, "Removing invisibility vs. animals due to melee attack."); + BuffFadeByEffect(SE_InvisVsAnimals); + invisible_animals = false; + } + + if(hidden || improved_hidden){ + hidden = false; + improved_hidden = false; + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); + SpawnAppearance_Struct* sa_out = (SpawnAppearance_Struct*)outapp->pBuffer; + sa_out->spawn_id = GetID(); + sa_out->type = 0x03; + sa_out->parameter = 0; + entity_list.QueueClients(this, outapp, true); + safe_delete(outapp); + } + + if(GetTarget()) + TriggerDefensiveProcs(weapon, other, Hand, damage); + + if (damage > 0) + return true; + + else + return false; +} + +//used by complete heal and #heal +void Mob::Heal() +{ + SetMaxHP(); + SendHPUpdate(); +} + +void Client::Damage(Mob* other, int32 damage, uint16 spell_id, SkillUseTypes attack_skill, bool avoidable, int8 buffslot, bool iBuffTic) +{ + if(dead || IsCorpse()) + return; + + if(spell_id==0) + spell_id = SPELL_UNKNOWN; + + if(spell_id!=0 && spell_id != SPELL_UNKNOWN && other && damage > 0) + { + if(other->IsNPC() && !other->IsPet()) + { + float npcspellscale = other->CastToNPC()->GetSpellScale(); + damage = ((float)damage * npcspellscale) / (float)100; + } + } + + // cut all PVP spell damage to 2/3 -solar + // EverHood - Blasting ourselfs is considered PvP + //Don't do PvP mitigation if the caster is damaging himself + if(other && other->IsClient() && (other != this) && damage > 0) { + int PvPMitigation = 100; + if(attack_skill == SkillArchery) + PvPMitigation = 80; + else + PvPMitigation = 67; + damage = (damage * PvPMitigation) / 100; + } + + if(!ClientFinishedLoading()) + damage = -5; + + //do a majority of the work... + CommonDamage(other, damage, spell_id, attack_skill, avoidable, buffslot, iBuffTic); + + if (damage > 0) { + + if (spell_id == SPELL_UNKNOWN) + CheckIncreaseSkill(SkillDefense, other, -15); + } +} + +bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes attack_skill) +{ + if(!ClientFinishedLoading()) + return false; + + if(dead) + return false; //cant die more than once... + + if(!spell) + spell = SPELL_UNKNOWN; + + char buffer[48] = { 0 }; + snprintf(buffer, 47, "%d %d %d %d", killerMob ? killerMob->GetID() : 0, damage, spell, static_cast(attack_skill)); + if(parse->EventPlayer(EVENT_DEATH, this, buffer, 0) != 0) { + if(GetHP() < 0) { + SetHP(0); + } + return false; + } + + if(killerMob && killerMob->IsClient() && (spell != SPELL_UNKNOWN) && damage > 0) { + char val1[20]={0}; + entity_list.MessageClose_StringID(this, false, 100, MT_NonMelee, HIT_NON_MELEE, + killerMob->GetCleanName(), GetCleanName(), ConvertArray(damage, val1)); + } + + int exploss = 0; + mlog(COMBAT__HITS, "Fatal blow dealt by %s with %d damage, spell %d, skill %d", killerMob ? killerMob->GetName() : "Unknown", damage, spell, attack_skill); + + // + // #1: Send death packet to everyone + // + uint8 killed_level = GetLevel(); + + SendLogoutPackets(); + + //make our become corpse packet, and queue to ourself before OP_Death. + EQApplicationPacket app2(OP_BecomeCorpse, sizeof(BecomeCorpse_Struct)); + BecomeCorpse_Struct* bc = (BecomeCorpse_Struct*)app2.pBuffer; + bc->spawn_id = GetID(); + bc->x = GetX(); + bc->y = GetY(); + bc->z = GetZ(); + QueuePacket(&app2); + + // make death packet + EQApplicationPacket app(OP_Death, sizeof(Death_Struct)); + Death_Struct* d = (Death_Struct*)app.pBuffer; + d->spawn_id = GetID(); + d->killer_id = killerMob ? killerMob->GetID() : 0; + d->corpseid=GetID(); + d->bindzoneid = m_pp.binds[0].zoneId; + d->spell_id = spell == SPELL_UNKNOWN ? 0xffffffff : spell; + d->attack_skill = spell != SPELL_UNKNOWN ? 0xe7 : attack_skill; + d->damage = damage; + app.priority = 6; + entity_list.QueueClients(this, &app); + + // + // #2: figure out things that affect the player dying and mark them dead + // + + InterruptSpell(); + SetPet(0); + SetHorseId(0); + dead = true; + + if(GetMerc()) { + GetMerc()->Suspend(); + } + + if (killerMob != nullptr) + { + if (killerMob->IsNPC()) { + parse->EventNPC(EVENT_SLAY, killerMob->CastToNPC(), this, "", 0); + + mod_client_death_npc(killerMob); + + uint16 emoteid = killerMob->GetEmoteID(); + if(emoteid != 0) + killerMob->CastToNPC()->DoNPCEmote(KILLEDPC,emoteid); + killerMob->TrySpellOnKill(killed_level,spell); + } + + if(killerMob->IsClient() && (IsDueling() || killerMob->CastToClient()->IsDueling())) { + SetDueling(false); + SetDuelTarget(0); + if (killerMob->IsClient() && killerMob->CastToClient()->IsDueling() && killerMob->CastToClient()->GetDuelTarget() == GetID()) + { + //if duel opponent killed us... + killerMob->CastToClient()->SetDueling(false); + killerMob->CastToClient()->SetDuelTarget(0); + entity_list.DuelMessage(killerMob,this,false); + + mod_client_death_duel(killerMob); + + } else { + //otherwise, we just died, end the duel. + Mob* who = entity_list.GetMob(GetDuelTarget()); + if(who && who->IsClient()) { + who->CastToClient()->SetDueling(false); + who->CastToClient()->SetDuelTarget(0); + } + } + } + } + + entity_list.RemoveFromTargets(this); + hate_list.RemoveEnt(this); + RemoveAutoXTargets(); + + + //remove ourself from all proximities + ClearAllProximities(); + + // + // #3: exp loss and corpse generation + // + + // figure out if they should lose exp + if(RuleB(Character, UseDeathExpLossMult)){ + float GetNum [] = {0.005f,0.015f,0.025f,0.035f,0.045f,0.055f,0.065f,0.075f,0.085f,0.095f,0.110f }; + int Num = RuleI(Character, DeathExpLossMultiplier); + if((Num < 0) || (Num > 10)) + Num = 3; + float loss = GetNum[Num]; + exploss=(int)((float)GetEXP() * (loss)); //loose % of total XP pending rule (choose 0-10) + } + + if(!RuleB(Character, UseDeathExpLossMult)){ + exploss = (int)(GetLevel() * (GetLevel() / 18.0) * 12000); + } + + if( (GetLevel() < RuleI(Character, DeathExpLossLevel)) || (GetLevel() > RuleI(Character, DeathExpLossMaxLevel)) || IsBecomeNPC() ) + { + exploss = 0; + } + else if( killerMob ) + { + if( killerMob->IsClient() ) + { + exploss = 0; + } + else if( killerMob->GetOwner() && killerMob->GetOwner()->IsClient() ) + { + exploss = 0; + } + } + + if(spell != SPELL_UNKNOWN) + { + uint32 buff_count = GetMaxTotalSlots(); + for(uint16 buffIt = 0; buffIt < buff_count; buffIt++) + { + if(buffs[buffIt].spellid == spell && buffs[buffIt].client) + { + exploss = 0; // no exp loss for pvp dot + break; + } + } + } + + bool LeftCorpse = false; + + // now we apply the exp loss, unmem their spells, and make a corpse + // unless they're a GM (or less than lvl 10 + if(!GetGM()) + { + if(exploss > 0) { + int32 newexp = GetEXP(); + if(exploss > newexp) { + //lost more than we have... wtf.. + newexp = 1; + } else { + newexp -= exploss; + } + SetEXP(newexp, GetAAXP()); + //m_epp.perAA = 0; //reset to no AA exp on death. + } + + //this generates a lot of 'updates' to the client that the client does not need + BuffFadeNonPersistDeath(); + if((GetClientVersionBit() & BIT_SoFAndLater) && RuleB(Character, RespawnFromHover)) + UnmemSpellAll(true); + else + UnmemSpellAll(false); + + if(RuleB(Character, LeaveCorpses) && GetLevel() >= RuleI(Character, DeathItemLossLevel) || RuleB(Character, LeaveNakedCorpses)) + { + // creating the corpse takes the cash/items off the player too + Corpse *new_corpse = new Corpse(this, exploss); + + char tmp[20]; + database.GetVariable("ServerType", tmp, 9); + if(atoi(tmp)==1 && killerMob != nullptr && killerMob->IsClient()){ + char tmp2[10] = {0}; + database.GetVariable("PvPreward", tmp, 9); + int reward = atoi(tmp); + if(reward==3){ + database.GetVariable("PvPitem", tmp2, 9); + int pvpitem = atoi(tmp2); + if(pvpitem>0 && pvpitem<200000) + new_corpse->SetPKItem(pvpitem); + } + else if(reward==2) + new_corpse->SetPKItem(-1); + else if(reward==1) + new_corpse->SetPKItem(1); + else + new_corpse->SetPKItem(0); + if(killerMob->CastToClient()->isgrouped) { + Group* group = entity_list.GetGroupByClient(killerMob->CastToClient()); + if(group != 0) + { + for(int i=0;i<6;i++) + { + if(group->members[i] != nullptr) + { + new_corpse->AllowMobLoot(group->members[i],i); + } + } + } + } + } + + entity_list.AddCorpse(new_corpse, GetID()); + SetID(0); + + //send the become corpse packet to everybody else in the zone. + entity_list.QueueClients(this, &app2, true); + + LeftCorpse = true; + } + +// if(!IsLD())//Todo: make it so an LDed client leaves corpse if its enabled +// MakeCorpse(exploss); + } else { + BuffFadeDetrimental(); + } + + // + // Finally, send em home + // + + // we change the mob variables, not pp directly, because Save() will copy + // from these and overwrite what we set in pp anyway + // + + if(LeftCorpse && (GetClientVersionBit() & BIT_SoFAndLater) && RuleB(Character, RespawnFromHover)) + { + ClearDraggedCorpses(); + + RespawnFromHoverTimer.Start(RuleI(Character, RespawnFromHoverTimer) * 1000); + + SendRespawnBinds(); + } + else + { + if(isgrouped) + { + Group *g = GetGroup(); + if(g) + g->MemberZoned(this); + } + + Raid* r = entity_list.GetRaidByClient(this); + + if(r) + r->MemberZoned(this); + + dead_timer.Start(5000, true); + + m_pp.zone_id = m_pp.binds[0].zoneId; + m_pp.zoneInstance = 0; + database.MoveCharacterToZone(this->CharacterID(), database.GetZoneName(m_pp.zone_id)); + + Save(); + + GoToDeath(); + } + + parse->EventPlayer(EVENT_DEATH_COMPLETE, this, buffer, 0); + return true; +} + +bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) +{ + int damage = 0; + + if (!other) { + SetTarget(nullptr); + LogFile->write(EQEMuLog::Error, "A null Mob object was passed to NPC::Attack() for evaluation!"); + return false; + } + + if(DivineAura()) + return(false); + + if(!GetTarget()) + SetTarget(other); + + //Check that we can attack before we calc heading and face our target + if (!IsAttackAllowed(other)) { + if (this->GetOwnerID()) + this->Say_StringID(NOT_LEGAL_TARGET); + if(other) { + if (other->IsClient()) + other->CastToClient()->RemoveXTarget(this, false); + RemoveFromHateList(other); + mlog(COMBAT__ATTACKS, "I am not allowed to attack %s", other->GetName()); + } + return false; + } + + FaceTarget(GetTarget()); + + SkillUseTypes skillinuse = SkillHandtoHand; + if (Hand == 13) { + skillinuse = static_cast(GetPrimSkill()); + OffHandAtk(false); + } + if (Hand == 14) { + skillinuse = static_cast(GetSecSkill()); + OffHandAtk(true); + } + + //figure out what weapon they are using, if any + const Item_Struct* weapon = nullptr; + if (Hand == 13 && equipment[SLOT_PRIMARY] > 0) + weapon = database.GetItem(equipment[SLOT_PRIMARY]); + else if (equipment[SLOT_SECONDARY]) + weapon = database.GetItem(equipment[SLOT_SECONDARY]); + + //We dont factor much from the weapon into the attack. + //Just the skill type so it doesn't look silly using punching animations and stuff while wielding weapons + if(weapon) { + mlog(COMBAT__ATTACKS, "Attacking with weapon: %s (%d) (too bad im not using it for much)", weapon->Name, weapon->ID); + + if(Hand == 14 && weapon->ItemType == ItemTypeShield){ + mlog(COMBAT__ATTACKS, "Attack with shield canceled."); + return false; + } + + switch(weapon->ItemType){ + case ItemType1HSlash: + skillinuse = Skill1HSlashing; + break; + case ItemType2HSlash: + skillinuse = Skill2HSlashing; + break; + case ItemType1HPiercing: + //skillinuse = Skill1HPiercing; + //break; + case ItemType2HPiercing: + skillinuse = Skill1HPiercing; // change to Skill2HPiercing once activated + break; + case ItemType1HBlunt: + skillinuse = Skill1HBlunt; + break; + case ItemType2HBlunt: + skillinuse = Skill2HBlunt; + break; + case ItemTypeBow: + skillinuse = SkillArchery; + break; + case ItemTypeLargeThrowing: + case ItemTypeSmallThrowing: + skillinuse = SkillThrowing; + break; + default: + skillinuse = SkillHandtoHand; + break; + } + } + + int weapon_damage = GetWeaponDamage(other, weapon); + + //do attack animation regardless of whether or not we can hit below + int16 charges = 0; + ItemInst weapon_inst(weapon, charges); + AttackAnimation(skillinuse, Hand, &weapon_inst); + + // Remove this once Skill2HPiercing is activated + //Work-around for there being no 2HP skill - We use 99 for the 2HB animation and 36 for pierce messages + if(skillinuse == 99) + skillinuse = static_cast(36); + + //basically "if not immune" then do the attack + if((weapon_damage) > 0) { + + //ele and bane dmg too + //NPCs add this differently than PCs + //if NPCs can't inheriently hit the target we don't add bane/magic dmg which isn't exactly the same as PCs + uint16 eleBane = 0; + if(weapon){ + if(weapon->BaneDmgBody == other->GetBodyType()){ + eleBane += weapon->BaneDmgAmt; + } + + if(weapon->BaneDmgRace == other->GetRace()){ + eleBane += weapon->BaneDmgRaceAmt; + } + + if(weapon->ElemDmgAmt){ + eleBane += (weapon->ElemDmgAmt * other->ResistSpell(weapon->ElemDmgType, 0, this) / 100); + } + } + + if(!RuleB(NPC, UseItemBonusesForNonPets)){ + if(!GetOwner()){ + eleBane = 0; + } + } + + uint8 otherlevel = other->GetLevel(); + uint8 mylevel = this->GetLevel(); + + otherlevel = otherlevel ? otherlevel : 1; + mylevel = mylevel ? mylevel : 1; + + //instead of calcing damage in floats lets just go straight to ints + if(RuleB(Combat, UseIntervalAC)) + damage = (max_dmg+eleBane); + else + damage = MakeRandomInt((min_dmg+eleBane),(max_dmg+eleBane)); + + + //check if we're hitting above our max or below it. + if((min_dmg+eleBane) != 0 && damage < (min_dmg+eleBane)) { + mlog(COMBAT__DAMAGE, "Damage (%d) is below min (%d). Setting to min.", damage, (min_dmg+eleBane)); + damage = (min_dmg+eleBane); + } + if((max_dmg+eleBane) != 0 && damage > (max_dmg+eleBane)) { + mlog(COMBAT__DAMAGE, "Damage (%d) is above max (%d). Setting to max.", damage, (max_dmg+eleBane)); + damage = (max_dmg+eleBane); + } + + damage = mod_npc_damage(damage, skillinuse, Hand, weapon, other); + + int32 hate = damage; + if(IsPet()) + { + hate = hate * 100 / GetDamageTable(skillinuse); + } + + if(other->IsClient() && other->CastToClient()->IsSitting()) { + mlog(COMBAT__DAMAGE, "Client %s is sitting. Hitting for max damage (%d).", other->GetName(), (max_dmg+eleBane)); + damage = (max_dmg+eleBane); + damage += (itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse); + + if(opts) { + damage *= opts->damage_percent; + damage += opts->damage_flat; + hate *= opts->hate_percent; + hate += opts->hate_flat; + } + + mlog(COMBAT__HITS, "Generating hate %d towards %s", hate, GetName()); + // now add done damage to the hate list + other->AddToHateList(this, hate); + + } else { + if(opts) { + damage *= opts->damage_percent; + damage += opts->damage_flat; + hate *= opts->hate_percent; + hate += opts->hate_flat; + } + + if(!other->CheckHitChance(this, skillinuse, Hand)) { + damage = 0; //miss + } else { //hit, check for damage avoidance + other->AvoidDamage(this, damage); + other->MeleeMitigation(this, damage, min_dmg+eleBane, opts); + if(damage > 0) { + ApplyMeleeDamageBonus(skillinuse, damage); + damage += (itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse); + TryCriticalHit(other, skillinuse, damage, opts); + } + mlog(COMBAT__HITS, "Generating hate %d towards %s", hate, GetName()); + // now add done damage to the hate list + if(damage > 0) + { + other->AddToHateList(this, hate); + } + else + other->AddToHateList(this, 0); + } + } + + mlog(COMBAT__DAMAGE, "Final damage against %s: %d", other->GetName(), damage); + + if(other->IsClient() && IsPet() && GetOwner()->IsClient()) { + //pets do half damage to clients in pvp + damage=damage/2; + } + } + else + damage = -5; + + //cant riposte a riposte + if (bRiposte && damage == -3) { + mlog(COMBAT__DAMAGE, "Riposte of riposte canceled."); + return false; + } + + int16 DeathHP = 0; + DeathHP = other->GetDelayDeath() * -1; + + if(GetHP() > 0 && other->GetHP() >= DeathHP) { + other->Damage(this, damage, SPELL_UNKNOWN, skillinuse, false); // Not avoidable client already had thier chance to Avoid + } else + return false; + + if (HasDied()) //killed by damage shield ect + return false; + + MeleeLifeTap(damage); + + if (damage > 0) + CheckNumHitsRemaining(5); + + //break invis when you attack + if(invisible) { + mlog(COMBAT__ATTACKS, "Removing invisibility due to melee attack."); + BuffFadeByEffect(SE_Invisibility); + BuffFadeByEffect(SE_Invisibility2); + invisible = false; + } + if(invisible_undead) { + mlog(COMBAT__ATTACKS, "Removing invisibility vs. undead due to melee attack."); + BuffFadeByEffect(SE_InvisVsUndead); + BuffFadeByEffect(SE_InvisVsUndead2); + invisible_undead = false; + } + if(invisible_animals){ + mlog(COMBAT__ATTACKS, "Removing invisibility vs. animals due to melee attack."); + BuffFadeByEffect(SE_InvisVsAnimals); + invisible_animals = false; + } + + if(hidden || improved_hidden) + { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); + SpawnAppearance_Struct* sa_out = (SpawnAppearance_Struct*)outapp->pBuffer; + sa_out->spawn_id = GetID(); + sa_out->type = 0x03; + sa_out->parameter = 0; + entity_list.QueueClients(this, outapp, true); + safe_delete(outapp); + } + + + hidden = false; + improved_hidden = false; + + //I doubt this works... + if (!GetTarget()) + return true; //We killed them + + if(!bRiposte && other->GetHP() > 0 ) { + TryWeaponProc(nullptr, weapon, other, Hand); //no weapon + TrySpellProc(nullptr, weapon, other, Hand); + } + + TriggerDefensiveProcs(nullptr, other, Hand, damage); + + // now check ripostes + if (damage == -3) { // riposting + DoRiposte(other); + } + + if (damage > 0) + return true; + + else + return false; +} + +void NPC::Damage(Mob* other, int32 damage, uint16 spell_id, SkillUseTypes attack_skill, bool avoidable, int8 buffslot, bool iBuffTic) { + if(spell_id==0) + spell_id = SPELL_UNKNOWN; + + //handle EVENT_ATTACK. Resets after we have not been attacked for 12 seconds + if(attacked_timer.Check()) + { + mlog(COMBAT__HITS, "Triggering EVENT_ATTACK due to attack by %s", other->GetName()); + parse->EventNPC(EVENT_ATTACK, this, other, "", 0); + } + attacked_timer.Start(CombatEventTimer_expire); + + if (!IsEngaged()) + zone->AddAggroMob(); + + if(GetClass() == LDON_TREASURE) + { + if(IsLDoNLocked() && GetLDoNLockedSkill() != LDoNTypeMechanical) + { + damage = -5; + } + else + { + if(IsLDoNTrapped()) + { + Message_StringID(13, LDON_ACCIDENT_SETOFF2); + SpellFinished(GetLDoNTrapSpellID(), other, 10, 0, -1, spells[GetLDoNTrapSpellID()].ResistDiff, false); + SetLDoNTrapSpellID(0); + SetLDoNTrapped(false); + SetLDoNTrapDetected(false); + } + } + } + + //do a majority of the work... + CommonDamage(other, damage, spell_id, attack_skill, avoidable, buffslot, iBuffTic); + + if(damage > 0) { + //see if we are gunna start fleeing + if(!IsPet()) CheckFlee(); + } +} + +bool NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes attack_skill) { + mlog(COMBAT__HITS, "Fatal blow dealt by %s with %d damage, spell %d, skill %d", killerMob->GetName(), damage, spell, attack_skill); + + Mob *oos = nullptr; + if(killerMob) { + oos = killerMob->GetOwnerOrSelf(); + + char buffer[48] = { 0 }; + snprintf(buffer, 47, "%d %d %d %d", killerMob ? killerMob->GetID() : 0, damage, spell, static_cast(attack_skill)); + if(parse->EventNPC(EVENT_DEATH, this, oos, buffer, 0) != 0) + { + if(GetHP() < 0) { + SetHP(0); + } + return false; + } + + if(killerMob && killerMob->IsClient() && (spell != SPELL_UNKNOWN) && damage > 0) { + char val1[20]={0}; + entity_list.MessageClose_StringID(this, false, 100, MT_NonMelee, HIT_NON_MELEE, + killerMob->GetCleanName(), GetCleanName(), ConvertArray(damage, val1)); + } + } else { + + char buffer[48] = { 0 }; + snprintf(buffer, 47, "%d %d %d %d", killerMob ? killerMob->GetID() : 0, damage, spell, static_cast(attack_skill)); + if(parse->EventNPC(EVENT_DEATH, this, nullptr, buffer, 0) != 0) + { + if(GetHP() < 0) { + SetHP(0); + } + return false; + } + } + + if (IsEngaged()) + { + zone->DelAggroMob(); +#if EQDEBUG >= 11 + LogFile->write(EQEMuLog::Debug,"NPC::Death() Mobs currently Aggro %i", zone->MobsAggroCount()); +#endif + } + SetHP(0); + SetPet(0); + Mob* killer = GetHateDamageTop(this); + + entity_list.RemoveFromTargets(this, p_depop); + + if(p_depop == true) + return false; + + BuffFadeAll(); + uint8 killed_level = GetLevel(); + + EQApplicationPacket* app= new EQApplicationPacket(OP_Death,sizeof(Death_Struct)); + Death_Struct* d = (Death_Struct*)app->pBuffer; + d->spawn_id = GetID(); + d->killer_id = killerMob ? killerMob->GetID() : 0; + d->bindzoneid = 0; + d->spell_id = spell == SPELL_UNKNOWN ? 0xffffffff : spell; + d->attack_skill = SkillDamageTypes[attack_skill]; + d->damage = damage; + app->priority = 6; + entity_list.QueueClients(killerMob, app, false); + + if(respawn2) { + respawn2->DeathReset(1); + } + + if (killerMob) { + if(GetClass() != LDON_TREASURE) + hate_list.Add(killerMob, damage); + } + + safe_delete(app); + + Mob *give_exp = hate_list.GetDamageTop(this); + + if(give_exp == nullptr) + give_exp = killer; + + if(give_exp && give_exp->HasOwner()) { + + bool ownerInGroup = false; + if((give_exp->HasGroup() && give_exp->GetGroup()->IsGroupMember(give_exp->GetUltimateOwner())) + || (give_exp->IsPet() && (give_exp->GetOwner()->IsClient() + || ( give_exp->GetOwner()->HasGroup() && give_exp->GetOwner()->GetGroup()->IsGroupMember(give_exp->GetOwner()->GetUltimateOwner()))))) + ownerInGroup = true; + + give_exp = give_exp->GetUltimateOwner(); + +#ifdef BOTS + if(!RuleB(Bots, BotGroupXP) && !ownerInGroup) { + give_exp = nullptr; + } +#endif //BOTS + } + + int PlayerCount = 0; // QueryServ Player Counting + + Client *give_exp_client = nullptr; + if(give_exp && give_exp->IsClient()) + give_exp_client = give_exp->CastToClient(); + + bool IsLdonTreasure = (this->GetClass() == LDON_TREASURE); + if (give_exp_client && !IsCorpse() && MerchantType == 0) + { + Group *kg = entity_list.GetGroupByClient(give_exp_client); + Raid *kr = entity_list.GetRaidByClient(give_exp_client); + + int32 finalxp = EXP_FORMULA; + finalxp = give_exp_client->mod_client_xp(finalxp, this); + + if(kr) + { + if(!IsLdonTreasure) { + kr->SplitExp((finalxp), this); + if(killerMob && (kr->IsRaidMember(killerMob->GetName()) || kr->IsRaidMember(killerMob->GetUltimateOwner()->GetName()))) + killerMob->TrySpellOnKill(killed_level,spell); + } + /* Send the EVENT_KILLED_MERIT event for all raid members */ + for (int i = 0; i < MAX_RAID_MEMBERS; i++) { + if (kr->members[i].member != nullptr) { // If Group Member is Client + parse->EventNPC(EVENT_KILLED_MERIT, this, kr->members[i].member, "killed", 0); + + mod_npc_killed_merit(kr->members[i].member); + + if(RuleB(TaskSystem, EnableTaskSystem)) + kr->members[i].member->UpdateTasksOnKill(GetNPCTypeID()); + PlayerCount++; + } + } + + // QueryServ Logging - Raid Kills + if(RuleB(QueryServ, PlayerLogNPCKills)){ + ServerPacket* pack = new ServerPacket(ServerOP_QSPlayerLogNPCKills, sizeof(QSPlayerLogNPCKill_Struct) + (sizeof(QSPlayerLogNPCKillsPlayers_Struct) * PlayerCount)); + PlayerCount = 0; + QSPlayerLogNPCKill_Struct* QS = (QSPlayerLogNPCKill_Struct*) pack->pBuffer; + QS->s1.NPCID = this->GetNPCTypeID(); + QS->s1.ZoneID = this->GetZoneID(); + QS->s1.Type = 2; // Raid Fight + for (int i = 0; i < MAX_RAID_MEMBERS; i++) { + if (kr->members[i].member != nullptr) { // If Group Member is Client + Client *c = kr->members[i].member; + QS->Chars[PlayerCount].char_id = c->CharacterID(); + PlayerCount++; + } + } + worldserver.SendPacket(pack); // Send Packet to World + safe_delete(pack); + } + // End QueryServ Logging + + } + else if (give_exp_client->IsGrouped() && kg != nullptr) + { + if(!IsLdonTreasure) { + kg->SplitExp((finalxp), this); + if(killerMob && (kg->IsGroupMember(killerMob->GetName()) || kg->IsGroupMember(killerMob->GetUltimateOwner()->GetName()))) + killerMob->TrySpellOnKill(killed_level,spell); + } + /* Send the EVENT_KILLED_MERIT event and update kill tasks + * for all group members */ + for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (kg->members[i] != nullptr && kg->members[i]->IsClient()) { // If Group Member is Client + Client *c = kg->members[i]->CastToClient(); + parse->EventNPC(EVENT_KILLED_MERIT, this, c, "killed", 0); + + mod_npc_killed_merit(c); + + if(RuleB(TaskSystem, EnableTaskSystem)) + c->UpdateTasksOnKill(GetNPCTypeID()); + + PlayerCount++; + } + } + + // QueryServ Logging - Group Kills + if(RuleB(QueryServ, PlayerLogNPCKills)){ + ServerPacket* pack = new ServerPacket(ServerOP_QSPlayerLogNPCKills, sizeof(QSPlayerLogNPCKill_Struct) + (sizeof(QSPlayerLogNPCKillsPlayers_Struct) * PlayerCount)); + PlayerCount = 0; + QSPlayerLogNPCKill_Struct* QS = (QSPlayerLogNPCKill_Struct*) pack->pBuffer; + QS->s1.NPCID = this->GetNPCTypeID(); + QS->s1.ZoneID = this->GetZoneID(); + QS->s1.Type = 1; // Group Fight + for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (kg->members[i] != nullptr && kg->members[i]->IsClient()) { // If Group Member is Client + Client *c = kg->members[i]->CastToClient(); + QS->Chars[PlayerCount].char_id = c->CharacterID(); + PlayerCount++; + } + } + worldserver.SendPacket(pack); // Send Packet to World + safe_delete(pack); + } + // End QueryServ Logging + } + else + { + if(!IsLdonTreasure) { + int conlevel = give_exp->GetLevelCon(GetLevel()); + if (conlevel != CON_GREEN) + { + if(GetOwner() && GetOwner()->IsClient()){ + } + else { + give_exp_client->AddEXP((finalxp), conlevel); // Pyro: Comment this if NPC death crashes zone + if(killerMob && (killerMob->GetID() == give_exp_client->GetID() || killerMob->GetUltimateOwner()->GetID() == give_exp_client->GetID())) + killerMob->TrySpellOnKill(killed_level,spell); + } + } + } + /* Send the EVENT_KILLED_MERIT event */ + parse->EventNPC(EVENT_KILLED_MERIT, this, give_exp_client, "killed", 0); + + mod_npc_killed_merit(give_exp_client); + + if(RuleB(TaskSystem, EnableTaskSystem)) + give_exp_client->UpdateTasksOnKill(GetNPCTypeID()); + + // QueryServ Logging - Solo + if(RuleB(QueryServ, PlayerLogNPCKills)){ + ServerPacket* pack = new ServerPacket(ServerOP_QSPlayerLogNPCKills, sizeof(QSPlayerLogNPCKill_Struct) + (sizeof(QSPlayerLogNPCKillsPlayers_Struct) * 1)); + QSPlayerLogNPCKill_Struct* QS = (QSPlayerLogNPCKill_Struct*) pack->pBuffer; + QS->s1.NPCID = this->GetNPCTypeID(); + QS->s1.ZoneID = this->GetZoneID(); + QS->s1.Type = 0; // Solo Fight + Client *c = give_exp_client; + QS->Chars[0].char_id = c->CharacterID(); + PlayerCount++; + worldserver.SendPacket(pack); // Send Packet to World + safe_delete(pack); + } + // End QueryServ Logging + } + } + + //do faction hits even if we are a merchant, so long as a player killed us + if(give_exp_client) + hate_list.DoFactionHits(GetNPCFactionID()); + + if (!HasOwner() && !IsMerc() && class_ != MERCHANT && class_ != ADVENTUREMERCHANT && !GetSwarmInfo() + && MerchantType == 0 && killer && (killer->IsClient() || (killer->HasOwner() && killer->GetUltimateOwner()->IsClient()) || + (killer->IsNPC() && killer->CastToNPC()->GetSwarmInfo() && killer->CastToNPC()->GetSwarmInfo()->GetOwner() && killer->CastToNPC()->GetSwarmInfo()->GetOwner()->IsClient()))) + { + if(killer != 0) + { + if(killer->GetOwner() != 0 && killer->GetOwner()->IsClient()) + killer = killer->GetOwner(); + + if(!killer->CastToClient()->GetGM() && killer->IsClient()) + this->CheckMinMaxLevel(killer); + } + entity_list.RemoveFromAutoXTargets(this); + uint16 emoteid = this->GetEmoteID(); + Corpse* corpse = new Corpse(this, &itemlist, GetNPCTypeID(), &NPCTypedata,level>54?RuleI(NPC,MajorNPCCorpseDecayTimeMS):RuleI(NPC,MinorNPCCorpseDecayTimeMS)); + entity_list.LimitRemoveNPC(this); + entity_list.AddCorpse(corpse, GetID()); + + entity_list.UnMarkNPC(GetID()); + entity_list.RemoveNPC(GetID()); + this->SetID(0); + if(killer != 0 && emoteid != 0) + corpse->CastToNPC()->DoNPCEmote(AFTERDEATH, emoteid); + if(killer != 0 && killer->IsClient()) { + corpse->AllowMobLoot(killer, 0); + if(killer->IsGrouped()) { + Group* group = entity_list.GetGroupByClient(killer->CastToClient()); + if(group != 0) { + for(int i=0;i<6;i++) { // Doesnt work right, needs work + if(group->members[i] != nullptr) { + corpse->AllowMobLoot(group->members[i],i); + } + } + } + } + else if(killer->IsRaidGrouped()){ + Raid* r = entity_list.GetRaidByClient(killer->CastToClient()); + if(r){ + int i = 0; + for(int x = 0; x < MAX_RAID_MEMBERS; x++) + { + switch(r->GetLootType()) + { + case 0: + case 1: + if(r->members[x].member && r->members[x].IsRaidLeader){ + corpse->AllowMobLoot(r->members[x].member, i); + i++; + } + break; + case 2: + if(r->members[x].member && r->members[x].IsRaidLeader){ + corpse->AllowMobLoot(r->members[x].member, i); + i++; + } + else if(r->members[x].member && r->members[x].IsGroupLeader){ + corpse->AllowMobLoot(r->members[x].member, i); + i++; + } + break; + case 3: + if(r->members[x].member && r->members[x].IsLooter){ + corpse->AllowMobLoot(r->members[x].member, i); + i++; + } + break; + case 4: + if(r->members[x].member) + { + corpse->AllowMobLoot(r->members[x].member, i); + i++; + } + break; + } + } + } + } + } + + if(zone && zone->adv_data) + { + ServerZoneAdventureDataReply_Struct *sr = (ServerZoneAdventureDataReply_Struct*)zone->adv_data; + if(sr->type == Adventure_Kill) + { + zone->DoAdventureCountIncrease(); + } + else if(sr->type == Adventure_Assassinate) + { + if(sr->data_id == GetNPCTypeID()) + { + zone->DoAdventureCountIncrease(); + } + else + { + zone->DoAdventureAssassinationCountIncrease(); + } + } + } + } + else + entity_list.RemoveFromXTargets(this); + + // Parse quests even if we're killed by an NPC + if(oos) { + mod_npc_killed(oos); + + uint16 emoteid = this->GetEmoteID(); + if(emoteid != 0) + this->DoNPCEmote(ONDEATH, emoteid); + if(oos->IsNPC()) + { + parse->EventNPC(EVENT_NPC_SLAY, oos->CastToNPC(), this, "", 0); + uint16 emoteid = oos->GetEmoteID(); + if(emoteid != 0) + oos->CastToNPC()->DoNPCEmote(KILLEDNPC, emoteid); + killerMob->TrySpellOnKill(killed_level, spell); + } + } + + WipeHateList(); + p_depop = true; + if(killerMob && killerMob->GetTarget() == this) //we can kill things without having them targeted + killerMob->SetTarget(nullptr); //via AE effects and such.. + + entity_list.UpdateFindableNPCState(this, true); + + char buffer[48] = { 0 }; + snprintf(buffer, 47, "%d %d %d %d", killerMob ? killerMob->GetID() : 0, damage, spell, static_cast(attack_skill)); + parse->EventNPC(EVENT_DEATH_COMPLETE, this, oos, buffer, 0); + return true; +} + +void Mob::AddToHateList(Mob* other, int32 hate, int32 damage, bool iYellForHelp, bool bFrenzy, bool iBuffTic) { + + assert(other != nullptr); + + if (other == this) + return; + + if(damage < 0){ + hate = 1; + } + + bool wasengaged = IsEngaged(); + Mob* owner = other->GetOwner(); + Mob* mypet = this->GetPet(); + Mob* myowner = this->GetOwner(); + Mob* targetmob = this->GetTarget(); + + if(other){ + AddRampage(other); + int hatemod = 100 + other->spellbonuses.hatemod + other->itembonuses.hatemod + other->aabonuses.hatemod; + + int16 shieldhatemod = other->spellbonuses.ShieldEquipHateMod + other->itembonuses.ShieldEquipHateMod + other->aabonuses.ShieldEquipHateMod; + + if (shieldhatemod && other->HasShieldEquiped()) + hatemod += shieldhatemod; + + if(hatemod < 1) + hatemod = 1; + hate = ((hate * (hatemod))/100); + } + + if(IsPet() && GetOwner() && GetOwner()->GetAA(aaPetDiscipline) && IsHeld() && !IsFocused()) { //ignore aggro if hold and !focus + return; + } + + if(IsPet() && GetOwner() && GetOwner()->GetAA(aaPetDiscipline) && IsHeld() && GetOwner()->GetAA(aaAdvancedPetDiscipline) >= 1 && IsFocused()) { + if (!targetmob) + return; + } + + if (other->IsNPC() && (other->IsPet() || other->CastToNPC()->GetSwarmOwner() > 0)) + TryTriggerOnValueAmount(false, false, false, true); + + if(IsClient() && !IsAIControlled()) + return; + + if(IsFamiliar() || GetSpecialAbility(IMMUNE_AGGRO)) + return; + + if (other == myowner) + return; + + if(other->GetSpecialAbility(IMMUNE_AGGRO_ON)) + return; + + if(GetSpecialAbility(NPC_TUNNELVISION)) { + int tv_mod = GetSpecialAbilityParam(NPC_TUNNELVISION, 0); + + Mob *top = GetTarget(); + if(top && top != other) { + if(tv_mod) { + float tv = tv_mod / 100.0f; + hate *= tv; + } else { + hate *= RuleR(Aggro, TunnelVisionAggroMod); + } + } + } + + if(IsNPC() && CastToNPC()->IsUnderwaterOnly() && zone->HasWaterMap()) { + if(!zone->watermap->InLiquid(other->GetX(), other->GetY(), other->GetZ())) { + return; + } + } + // first add self + + // The damage on the hate list is used to award XP to the killer. This check is to prevent Killstealing. + // e.g. Mob has 5000 hit points, Player A melees it down to 500 hp, Player B executes a headshot (10000 damage). + // If we add 10000 damage, Player B would get the kill credit, so we only award damage credit to player B of the + // amount of HP the mob had left. + // + if(damage > GetHP()) + damage = GetHP(); + + if (spellbonuses.ImprovedTaunt[1] && (GetLevel() < spellbonuses.ImprovedTaunt[0]) + && other && (buffs[spellbonuses.ImprovedTaunt[2]].casterid != other->GetID())) + hate = (hate*spellbonuses.ImprovedTaunt[1])/100; + + hate_list.Add(other, hate, damage, bFrenzy, !iBuffTic); + + if(other->IsClient()) + other->CastToClient()->AddAutoXTarget(this); + +#ifdef BOTS + // if other is a bot, add the bots client to the hate list + if(other->IsBot()) { + if(other->CastToBot()->GetBotOwner() && other->CastToBot()->GetBotOwner()->CastToClient()->GetFeigned()) { + AddFeignMemory(other->CastToBot()->GetBotOwner()->CastToClient()); + } + else { + if(!hate_list.IsOnHateList(other->CastToBot()->GetBotOwner())) + hate_list.Add(other->CastToBot()->GetBotOwner(), 0, 0, false, true); + } + } +#endif //BOTS + + + // if other is a merc, add the merc client to the hate list + if(other->IsMerc()) { + if(other->CastToMerc()->GetMercOwner() && other->CastToMerc()->GetMercOwner()->CastToClient()->GetFeigned()) { + AddFeignMemory(other->CastToMerc()->GetMercOwner()->CastToClient()); + } + else { + if(!hate_list.IsOnHateList(other->CastToMerc()->GetMercOwner())) + hate_list.Add(other->CastToMerc()->GetMercOwner(), 0, 0, false, true); + } + } //MERC + + // then add pet owner if there's one + if (owner) { // Other is a pet, add him and it + // EverHood 6/12/06 + // Can't add a feigned owner to hate list + if(owner->IsClient() && owner->CastToClient()->GetFeigned()) { + //they avoid hate due to feign death... + } else { + // cb:2007-08-17 + // owner must get on list, but he's not actually gained any hate yet + if(!owner->GetSpecialAbility(IMMUNE_AGGRO)) + { + hate_list.Add(owner, 0, 0, false, !iBuffTic); + if(owner->IsClient()) + owner->CastToClient()->AddAutoXTarget(this); + } + } + } + + if (mypet && (!(GetAA(aaPetDiscipline) && mypet->IsHeld()))) { // I have a pet, add other to it + if(!mypet->IsFamiliar() && !mypet->GetSpecialAbility(IMMUNE_AGGRO)) + mypet->hate_list.Add(other, 0, 0, bFrenzy); + } else if (myowner) { // I am a pet, add other to owner if it's NPC/LD + if (myowner->IsAIControlled() && !myowner->GetSpecialAbility(IMMUNE_AGGRO)) + myowner->hate_list.Add(other, 0, 0, bFrenzy); + } + if (!wasengaged) { + if(IsNPC() && other->IsClient() && other->CastToClient()) + parse->EventNPC(EVENT_AGGRO, this->CastToNPC(), other, "", 0); + AI_Event_Engaged(other, iYellForHelp); + } +} + +// solar: this is called from Damage() when 'this' is attacked by 'other. +// 'this' is the one being attacked +// 'other' is the attacker +// a damage shield causes damage (or healing) to whoever attacks the wearer +// a reverse ds causes damage to the wearer whenever it attack someone +// given this, a reverse ds must be checked each time the wearer is attacking +// and not when they're attacked +//a damage shield on a spell is a negative value but on an item it's a positive value so add the spell value and subtract the item value to get the end ds value +void Mob::DamageShield(Mob* attacker, bool spell_ds) { + + if(!attacker || this == attacker) + return; + + int DS = 0; + int rev_ds = 0; + uint16 spellid = 0; + + if(!spell_ds) + { + DS = spellbonuses.DamageShield; + rev_ds = attacker->spellbonuses.ReverseDamageShield; + + if(spellbonuses.DamageShieldSpellID != 0 && spellbonuses.DamageShieldSpellID != SPELL_UNKNOWN) + spellid = spellbonuses.DamageShieldSpellID; + } + else { + DS = spellbonuses.SpellDamageShield; + rev_ds = 0; + // This ID returns "you are burned", seemed most appropriate for spell DS + spellid = 2166; + } + + if(DS == 0 && rev_ds == 0) + return; + + mlog(COMBAT__HITS, "Applying Damage Shield of value %d to %s", DS, attacker->GetName()); + + //invert DS... spells yield negative values for a true damage shield + if(DS < 0) { + if(!spell_ds) { + + DS += aabonuses.DamageShield; //Live AA - coat of thistles. (negative value) + DS -= itembonuses.DamageShield; //+Damage Shield should only work when you already have a DS spell + + //Spell data for damage shield mitigation shows a negative value for spells for clients and positive + //value for spells that effect pets. Unclear as to why. For now will convert all positive to be consistent. + if (attacker->IsOffHandAtk()){ + int16 mitigation = attacker->itembonuses.DSMitigationOffHand + + attacker->spellbonuses.DSMitigationOffHand + + attacker->aabonuses.DSMitigationOffHand; + DS -= DS*mitigation/100; + } + DS -= DS * attacker->itembonuses.DSMitigation / 100; + } + attacker->Damage(this, -DS, spellid, SkillAbjuration/*hackish*/, false); + //we can assume there is a spell now + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Damage, sizeof(CombatDamage_Struct)); + CombatDamage_Struct* cds = (CombatDamage_Struct*)outapp->pBuffer; + cds->target = attacker->GetID(); + cds->source = GetID(); + cds->type = spellbonuses.DamageShieldType; + cds->spellid = 0x0; + cds->damage = DS; + entity_list.QueueCloseClients(this, outapp); + safe_delete(outapp); + } else if (DS > 0 && !spell_ds) { + //we are healing the attacker... + attacker->HealDamage(DS); + //TODO: send a packet??? + } + + //Reverse DS + //this is basically a DS, but the spell is on the attacker, not the attackee + //if we've gotten to this point, we know we know "attacker" hit "this" (us) for damage & we aren't invulnerable + uint16 rev_ds_spell_id = SPELL_UNKNOWN; + + if(spellbonuses.ReverseDamageShieldSpellID != 0 && spellbonuses.ReverseDamageShieldSpellID != SPELL_UNKNOWN) + rev_ds_spell_id = spellbonuses.ReverseDamageShieldSpellID; + + if(rev_ds < 0) { + mlog(COMBAT__HITS, "Applying Reverse Damage Shield of value %d to %s", rev_ds, attacker->GetName()); + attacker->Damage(this, -rev_ds, rev_ds_spell_id, SkillAbjuration/*hackish*/, false); //"this" (us) will get the hate, etc. not sure how this works on Live, but it'll works for now, and tanks will love us for this + //do we need to send a damage packet here also? + } +} + +uint8 Mob::GetWeaponDamageBonus( const Item_Struct *Weapon ) +{ + // This function calculates and returns the damage bonus for the weapon identified by the parameter "Weapon". + // Modified 9/21/2008 by Cantus + + + // Assert: This function should only be called for hits by the mainhand, as damage bonuses apply only to the + // weapon in the primary slot. Be sure to check that Hand == 13 before calling. + + // Assert: The caller should ensure that Weapon is actually a weapon before calling this function. + // The ItemInst::IsWeapon() method can be used to quickly determine this. + + // Assert: This function should not be called if the player's level is below 28, as damage bonuses do not begin + // to apply until level 28. + + // Assert: This function should not be called unless the player is a melee class, as casters do not receive a damage bonus. + + + if( Weapon == nullptr || Weapon->ItemType == ItemType1HSlash || Weapon->ItemType == ItemType1HBlunt || Weapon->ItemType == ItemTypeMartial || Weapon->ItemType == ItemType1HPiercing ) + { + // The weapon in the player's main (primary) hand is a one-handed weapon, or there is no item equipped at all. + // + // According to player posts on Allakhazam, 1H damage bonuses apply to bare fists (nothing equipped in the mainhand, + // as indicated by Weapon == nullptr). + // + // The following formula returns the correct damage bonus for all 1H weapons: + + return (uint8) ((GetLevel() - 25) / 3); + } + + // If we've gotten to this point, the weapon in the mainhand is a two-handed weapon. + // Calculating damage bonuses for 2H weapons is more complicated, as it's based on PC level AND the delay of the weapon. + // The formula to calculate 2H bonuses is HIDEOUS. It's a huge conglomeration of ternary operators and multiple operations. + // + // The following is a hybrid approach. In cases where the Level and Delay merit a formula that does not use many operators, + // the formula is used. In other cases, lookup tables are used for speed. + // Though the following code may look bloated and ridiculous, it's actually a very efficient way of calculating these bonuses. + + // Player Level is used several times in the code below, so save it into a variable. + // If GetLevel() were an ordinary function, this would DEFINITELY make sense, as it'd cut back on all of the function calling + // overhead involved with multiple calls to GetLevel(). But in this case, GetLevel() is a simple, inline accessor method. + // So it probably doesn't matter. If anyone knows for certain that there is no overhead involved with calling GetLevel(), + // as I suspect, then please feel free to delete the following line, and replace all occurences of "ucPlayerLevel" with "GetLevel()". + uint8 ucPlayerLevel = (uint8) GetLevel(); + + + // The following may look cleaner, and would certainly be easier to understand, if it was + // a simple 53x150 cell matrix. + // + // However, that would occupy 7,950 Bytes of memory (7.76 KB), and would likely result + // in "thrashing the cache" when performing lookups. + // + // Initially, I thought the best approach would be to reverse-engineer the formula used by + // Sony/Verant to calculate these 2H weapon damage bonuses. But the more than Reno and I + // worked on figuring out this formula, the more we're concluded that the formula itself ugly + // (that is, it contains so many operations and conditionals that it's fairly CPU intensive). + // Because of that, we're decided that, in most cases, a lookup table is the most efficient way + // to calculate these damage bonuses. + // + // The code below is a hybrid between a pure formulaic approach and a pure, brute-force + // lookup table. In cases where a formula is the best bet, I use a formula. In other places + // where a formula would be ugly, I use a lookup table in the interests of speed. + + + if( Weapon->Delay <= 27 ) + { + // Damage Bonuses for all 2H weapons with delays of 27 or less are identical. + // They are the same as the damage bonus would be for a corresponding 1H weapon, plus one. + // This formula applies to all levels 28-80, and will probably continue to apply if + + // the level cap on Live ever is increased beyond 80. + + return (ucPlayerLevel - 22) / 3; + } + + + if( ucPlayerLevel == 65 && Weapon->Delay <= 59 ) + { + // Consider these two facts: + // * Level 65 is the maximum level on many EQ Emu servers. + // * If you listed the levels of all characters logged on to a server, odds are that the number you'll + // see most frequently is level 65. That is, there are more level 65 toons than any other single level. + // + // Therefore, if we can optimize this function for level 65 toons, we're speeding up the server! + // + // With that goal in mind, I create an array of Damage Bonuses for level 65 characters wielding 2H weapons with + // delays between 28 and 59 (inclusive). I suspect that this one small lookup array will therefore handle + // many of the calls to this function. + + static const uint8 ucLevel65DamageBonusesForDelays28to59[] = {35, 35, 36, 36, 37, 37, 38, 38, 39, 39, 40, 40, 42, 42, 42, 45, 45, 47, 48, 49, 49, 51, 51, 52, 53, 54, 54, 56, 56, 57, 58, 59}; + + return ucLevel65DamageBonusesForDelays28to59[Weapon->Delay-28]; + } + + + if( ucPlayerLevel > 65 ) + { + if( ucPlayerLevel > 80 ) + { + // As level 80 is currently the highest achievable level on Live, we only include + // damage bonus information up to this level. + // + // If there is a custom EQEmu server that allows players to level beyond 80, the + // damage bonus for their 2H weapons will simply not increase beyond their damage + // bonus at level 80. + + ucPlayerLevel = 80; + } + + // Lucy does not list a chart of damage bonuses for players levels 66+, + // so my original version of this function just applied the level 65 damage + // bonus for level 66+ toons. That sucked for higher level toons, as their + // 2H weapons stopped ramping up in DPS as they leveled past 65. + // + // Thanks to the efforts of two guys, this is no longer the case: + // + // Janusd (Zetrakyl) ran a nifty query against the PEQ item database to list + // the name of an example 2H weapon that represents each possible unique 2H delay. + // + // Romai then wrote an excellent script to automatically look up each of those + // weapons, open the Lucy item page associated with it, and iterate through all + // levels in the range 66 - 80. He saved the damage bonus for that weapon for + // each level, and that forms the basis of the lookup tables below. + + if( Weapon->Delay <= 59 ) + { + static const uint8 ucDelay28to59Levels66to80[32][15]= + { + /* Level: */ + /* 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 */ + + {36, 37, 38, 39, 41, 42, 43, 44, 45, 47, 49, 49, 49, 50, 53}, /* Delay = 28 */ + {36, 38, 38, 39, 42, 43, 43, 45, 46, 48, 49, 50, 51, 52, 54}, /* Delay = 29 */ + {37, 38, 39, 40, 43, 43, 44, 46, 47, 48, 50, 51, 52, 53, 55}, /* Delay = 30 */ + {37, 39, 40, 40, 43, 44, 45, 46, 47, 49, 51, 52, 52, 52, 54}, /* Delay = 31 */ + {38, 39, 40, 41, 44, 45, 45, 47, 48, 48, 50, 52, 53, 55, 57}, /* Delay = 32 */ + {38, 40, 41, 41, 44, 45, 46, 48, 49, 50, 52, 53, 54, 56, 58}, /* Delay = 33 */ + {39, 40, 41, 42, 45, 46, 47, 48, 49, 51, 53, 54, 55, 57, 58}, /* Delay = 34 */ + {39, 41, 42, 43, 46, 46, 47, 49, 50, 52, 54, 55, 56, 57, 59}, /* Delay = 35 */ + {40, 41, 42, 43, 46, 47, 48, 50, 51, 53, 55, 55, 56, 58, 60}, /* Delay = 36 */ + {40, 42, 43, 44, 47, 48, 49, 50, 51, 53, 55, 56, 57, 59, 61}, /* Delay = 37 */ + {41, 42, 43, 44, 47, 48, 49, 51, 52, 54, 56, 57, 58, 60, 62}, /* Delay = 38 */ + {41, 43, 44, 45, 48, 49, 50, 52, 53, 55, 57, 58, 59, 61, 63}, /* Delay = 39 */ + {43, 45, 46, 47, 50, 51, 52, 54, 55, 57, 59, 60, 61, 63, 65}, /* Delay = 40 */ + {43, 45, 46, 47, 50, 51, 52, 54, 55, 57, 59, 60, 61, 63, 65}, /* Delay = 41 */ + {44, 46, 47, 48, 51, 52, 53, 55, 56, 58, 60, 61, 62, 64, 66}, /* Delay = 42 */ + {46, 48, 49, 50, 53, 54, 55, 58, 59, 61, 63, 64, 65, 67, 69}, /* Delay = 43 */ + {47, 49, 50, 51, 54, 55, 56, 58, 59, 61, 64, 65, 66, 68, 70}, /* Delay = 44 */ + {48, 50, 51, 52, 56, 57, 58, 60, 61, 63, 65, 66, 68, 70, 72}, /* Delay = 45 */ + {50, 52, 53, 54, 57, 58, 59, 62, 63, 65, 67, 68, 69, 71, 74}, /* Delay = 46 */ + {50, 52, 53, 55, 58, 59, 60, 62, 63, 66, 68, 69, 70, 72, 74}, /* Delay = 47 */ + {51, 53, 54, 55, 58, 60, 61, 63, 64, 66, 69, 69, 71, 73, 75}, /* Delay = 48 */ + {52, 54, 55, 57, 60, 61, 62, 65, 66, 68, 70, 71, 73, 75, 77}, /* Delay = 49 */ + {53, 55, 56, 57, 61, 62, 63, 65, 67, 69, 71, 72, 74, 76, 78}, /* Delay = 50 */ + {53, 55, 57, 58, 61, 62, 64, 66, 67, 69, 72, 73, 74, 77, 79}, /* Delay = 51 */ + {55, 57, 58, 59, 63, 64, 65, 68, 69, 71, 74, 75, 76, 78, 81}, /* Delay = 52 */ + {57, 55, 59, 60, 63, 65, 66, 68, 70, 72, 74, 76, 77, 79, 82}, /* Delay = 53 */ + {56, 58, 59, 61, 64, 65, 67, 69, 70, 73, 75, 76, 78, 80, 82}, /* Delay = 54 */ + {57, 59, 61, 62, 66, 67, 68, 71, 72, 74, 77, 78, 80, 82, 84}, /* Delay = 55 */ + {58, 60, 61, 63, 66, 68, 69, 71, 73, 75, 78, 79, 80, 83, 85}, /* Delay = 56 */ + + /* Important Note: Janusd's search for 2H weapons did not find */ + /* any 2H weapon with a delay of 57. Therefore the values below */ + /* are interpolated, not exact! */ + {59, 61, 62, 64, 67, 69, 70, 72, 74, 76, 77, 78, 81, 84, 86}, /* Delay = 57 INTERPOLATED */ + + {60, 62, 63, 65, 68, 70, 71, 74, 75, 78, 80, 81, 83, 85, 88}, /* Delay = 58 */ + + /* Important Note: Janusd's search for 2H weapons did not find */ + /* any 2H weapon with a delay of 59. Therefore the values below */ + /* are interpolated, not exact! */ + {60, 62, 64, 65, 69, 70, 72, 74, 76, 78, 81, 82, 84, 86, 89}, /* Delay = 59 INTERPOLATED */ + }; + + return ucDelay28to59Levels66to80[Weapon->Delay-28][ucPlayerLevel-66]; + } + else + { + // Delay is 60+ + + const static uint8 ucDelayOver59Levels66to80[6][15] = + { + /* Level: */ + /* 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 */ + + {61, 63, 65, 66, 70, 71, 73, 75, 77, 79, 82, 83, 85, 87, 90}, /* Delay = 60 */ + {65, 68, 69, 71, 75, 76, 78, 80, 82, 85, 87, 89, 91, 93, 96}, /* Delay = 65 */ + + /* Important Note: Currently, the only 2H weapon with a delay */ + /* of 66 is not player equippable (it's None/None). So I'm */ + /* leaving it commented out to keep this table smaller. */ + //{66, 68, 70, 71, 75, 77, 78, 81, 83, 85, 88, 90, 91, 94, 97}, /* Delay = 66 */ + + {70, 72, 74, 76, 80, 81, 83, 86, 88, 88, 90, 95, 97, 99, 102}, /* Delay = 70 */ + {82, 85, 87, 89, 89, 94, 98, 101, 103, 106, 109, 111, 114, 117, 120}, /* Delay = 85 */ + {90, 93, 96, 98, 103, 105, 107, 111, 113, 116, 120, 122, 125, 128, 131}, /* Delay = 95 */ + + /* Important Note: Currently, the only 2H weapons with delay */ + /* 100 are GM-only items purchased from vendors in Sunset Home */ + /* (cshome). Because they are highly unlikely to be used in */ + /* combat, I'm commenting it out to keep the table smaller. */ + //{95, 98, 101, 103, 108, 110, 113, 116, 119, 122, 126, 128, 131, 134, 138},/* Delay = 100 */ + + {136, 140, 144, 148, 154, 157, 161, 166, 170, 174, 179, 183, 187, 191, 196} /* Delay = 150 */ + }; + + if( Weapon->Delay < 65 ) + { + return ucDelayOver59Levels66to80[0][ucPlayerLevel-66]; + } + else if( Weapon->Delay < 70 ) + { + return ucDelayOver59Levels66to80[1][ucPlayerLevel-66]; + } + else if( Weapon->Delay < 85 ) + { + return ucDelayOver59Levels66to80[2][ucPlayerLevel-66]; + } + else if( Weapon->Delay < 95 ) + { + return ucDelayOver59Levels66to80[3][ucPlayerLevel-66]; + } + else if( Weapon->Delay < 150 ) + { + return ucDelayOver59Levels66to80[4][ucPlayerLevel-66]; + } + else + { + return ucDelayOver59Levels66to80[5][ucPlayerLevel-66]; + } + } + } + + + // If we've gotten to this point in the function without hitting a return statement, + // we know that the character's level is between 28 and 65, and that the 2H weapon's + // delay is 28 or higher. + + // The Damage Bonus values returned by this function (in the level 28-65 range) are + // based on a table of 2H Weapon Damage Bonuses provided by Lucy at the following address: + // http://lucy.allakhazam.com/dmgbonus.html + + if( Weapon->Delay <= 39 ) + { + if( ucPlayerLevel <= 53) + { + // The Damage Bonus for all 2H weapons with delays between 28 and 39 (inclusive) is the same for players level 53 and below... + static const uint8 ucDelay28to39LevelUnder54[] = {1, 1, 2, 3, 3, 3, 4, 5, 5, 6, 6, 6, 8, 8, 8, 9, 9, 10, 11, 11, 11, 12, 13, 14, 16, 17}; + + // As a note: The following formula accurately calculates damage bonuses for 2H weapons with delays in the range 28-39 (inclusive) + // for characters levels 28-50 (inclusive): + // return ( (ucPlayerLevel - 22) / 3 ) + ( (ucPlayerLevel - 25) / 5 ); + // + // However, the small lookup array used above is actually much faster. So we'll just use it instead of the formula + // + // (Thanks to Reno for helping figure out the above formula!) + + return ucDelay28to39LevelUnder54[ucPlayerLevel-28]; + } + else + { + // Use a matrix to look up the damage bonus for 2H weapons with delays between 28 and 39 wielded by characters level 54 and above. + static const uint8 ucDelay28to39Level54to64[12][11] = + { + /* Level: */ + /* 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 */ + + {17, 21, 21, 23, 25, 26, 28, 30, 31, 31, 33}, /* Delay = 28 */ + {17, 21, 22, 23, 25, 26, 29, 30, 31, 32, 34}, /* Delay = 29 */ + {18, 21, 22, 23, 25, 27, 29, 31, 32, 32, 34}, /* Delay = 30 */ + {18, 21, 22, 23, 25, 27, 29, 31, 32, 33, 34}, /* Delay = 31 */ + {18, 21, 22, 24, 26, 27, 30, 32, 32, 33, 35}, /* Delay = 32 */ + {18, 21, 22, 24, 26, 27, 30, 32, 33, 34, 35}, /* Delay = 33 */ + {18, 22, 22, 24, 26, 28, 30, 32, 33, 34, 36}, /* Delay = 34 */ + {18, 22, 23, 24, 26, 28, 31, 33, 34, 34, 36}, /* Delay = 35 */ + {18, 22, 23, 25, 27, 28, 31, 33, 34, 35, 37}, /* Delay = 36 */ + {18, 22, 23, 25, 27, 29, 31, 33, 34, 35, 37}, /* Delay = 37 */ + {18, 22, 23, 25, 27, 29, 32, 34, 35, 36, 38}, /* Delay = 38 */ + {18, 22, 23, 25, 27, 29, 32, 34, 35, 36, 38} /* Delay = 39 */ + }; + + return ucDelay28to39Level54to64[Weapon->Delay-28][ucPlayerLevel-54]; + } + } + else if( Weapon->Delay <= 59 ) + { + if( ucPlayerLevel <= 52 ) + { + if( Weapon->Delay <= 45 ) + { + static const uint8 ucDelay40to45Levels28to52[6][25] = + { + /* Level: */ + /* 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52 */ + + {2, 2, 3, 4, 4, 4, 5, 6, 6, 7, 7, 7, 9, 9, 9, 10, 10, 11, 12, 12, 12, 13, 14, 16, 18}, /* Delay = 40 */ + {2, 2, 3, 4, 4, 4, 5, 6, 6, 7, 7, 7, 9, 9, 9, 10, 10, 11, 12, 12, 12, 13, 14, 16, 18}, /* Delay = 41 */ + {2, 2, 3, 4, 4, 4, 5, 6, 6, 7, 7, 7, 9, 9, 9, 10, 10, 11, 12, 12, 12, 13, 14, 16, 18}, /* Delay = 42 */ + {4, 4, 5, 6, 6, 6, 7, 8, 8, 9, 9, 9, 11, 11, 11, 12, 12, 13, 14, 14, 14, 15, 16, 18, 20}, /* Delay = 43 */ + {4, 4, 5, 6, 6, 6, 7, 8, 8, 9, 9, 9, 11, 11, 11, 12, 12, 13, 14, 14, 14, 15, 16, 18, 20}, /* Delay = 44 */ + {5, 5, 6, 7, 7, 7, 8, 9, 9, 10, 10, 10, 12, 12, 12, 13, 13, 14, 15, 15, 15, 16, 17, 19, 21} /* Delay = 45 */ + }; + + return ucDelay40to45Levels28to52[Weapon->Delay-40][ucPlayerLevel-28]; + } + else + { + static const uint8 ucDelay46Levels28to52[] = {6, 6, 7, 8, 8, 8, 9, 10, 10, 11, 11, 11, 13, 13, 13, 14, 14, 15, 16, 16, 16, 17, 18, 20, 22}; + + return ucDelay46Levels28to52[ucPlayerLevel-28] + ((Weapon->Delay-46) / 3); + } + } + else + { + // Player is in the level range 53 - 64 + + // Calculating damage bonus for 2H weapons with a delay between 40 and 59 (inclusive) involves, unforunately, a brute-force matrix lookup. + static const uint8 ucDelay40to59Levels53to64[20][37] = + { + /* Level: */ + /* 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 */ + + {19, 20, 24, 25, 27, 29, 31, 34, 36, 37, 38, 40}, /* Delay = 40 */ + {19, 20, 24, 25, 27, 29, 31, 34, 36, 37, 38, 40}, /* Delay = 41 */ + {19, 20, 24, 25, 27, 29, 31, 34, 36, 37, 38, 40}, /* Delay = 42 */ + {21, 22, 26, 27, 29, 31, 33, 37, 39, 40, 41, 43}, /* Delay = 43 */ + {21, 22, 26, 27, 29, 32, 34, 37, 39, 40, 41, 43}, /* Delay = 44 */ + {22, 23, 27, 28, 31, 33, 35, 38, 40, 42, 43, 45}, /* Delay = 45 */ + {23, 24, 28, 30, 32, 34, 36, 40, 42, 43, 44, 46}, /* Delay = 46 */ + {23, 24, 29, 30, 32, 34, 37, 40, 42, 43, 44, 47}, /* Delay = 47 */ + {23, 24, 29, 30, 32, 35, 37, 40, 43, 44, 45, 47}, /* Delay = 48 */ + {24, 25, 30, 31, 34, 36, 38, 42, 44, 45, 46, 49}, /* Delay = 49 */ + {24, 26, 30, 31, 34, 36, 39, 42, 44, 46, 47, 49}, /* Delay = 50 */ + {24, 26, 30, 31, 34, 36, 39, 42, 45, 46, 47, 49}, /* Delay = 51 */ + {25, 27, 31, 33, 35, 38, 40, 44, 46, 47, 49, 51}, /* Delay = 52 */ + {25, 27, 31, 33, 35, 38, 40, 44, 46, 48, 49, 51}, /* Delay = 53 */ + {26, 27, 32, 33, 36, 38, 41, 44, 47, 48, 49, 52}, /* Delay = 54 */ + {27, 28, 33, 34, 37, 39, 42, 46, 48, 50, 51, 53}, /* Delay = 55 */ + {27, 28, 33, 34, 37, 40, 42, 46, 49, 50, 51, 54}, /* Delay = 56 */ + {27, 28, 33, 34, 37, 40, 43, 46, 49, 50, 52, 54}, /* Delay = 57 */ + {28, 29, 34, 36, 39, 41, 44, 48, 50, 52, 53, 56}, /* Delay = 58 */ + {28, 29, 34, 36, 39, 41, 44, 48, 51, 52, 54, 56} /* Delay = 59 */ + }; + + return ucDelay40to59Levels53to64[Weapon->Delay-40][ucPlayerLevel-53]; + } + } + else + { + // The following table allows us to look up Damage Bonuses for weapons with delays greater than or equal to 60. + // + // There aren't a lot of 2H weapons with a delay greater than 60. In fact, both a database and Lucy search run by janusd confirm + // that the only unique 2H delays greater than 60 are: 65, 70, 85, 95, and 150. + // + // To be fair, there are also weapons with delays of 66 and 100. But they are either not equippable (None/None), or are + // only available to GMs from merchants in Sunset Home (cshome). In order to keep this table "lean and mean", I will not + // include the values for delays 66 and 100. If they ever are wielded, the 66 delay weapon will use the 65 delay bonuses, + // and the 100 delay weapon will use the 95 delay bonuses. So it's not a big deal. + // + // Still, if someone in the future decides that they do want to include them, here are the tables for these two delays: + // + // {12, 12, 13, 14, 14, 14, 15, 16, 16, 17, 17, 17, 19, 19, 19, 20, 20, 21, 22, 22, 22, 23, 24, 26, 29, 30, 32, 37, 39, 42, 45, 48, 53, 55, 57, 59, 61, 64} /* Delay = 66 */ + // {24, 24, 25, 26, 26, 26, 27, 28, 28, 29, 29, 29, 31, 31, 31, 32, 32, 33, 34, 34, 34, 35, 36, 39, 43, 45, 48, 55, 57, 62, 66, 71, 77, 80, 83, 85, 89, 92} /* Delay = 100 */ + // + // In case there are 2H weapons added in the future with delays other than those listed above (and until the damage bonuses + // associated with that new delay are added to this function), this function is designed to do the following: + // + // For weapons with delays in the range 60-64, use the Damage Bonus that would apply to a 2H weapon with delay 60. + // For weapons with delays in the range 65-69, use the Damage Bonus that would apply to a 2H weapon with delay 65 + // For weapons with delays in the range 70-84, use the Damage Bonus that would apply to a 2H weapon with delay 70. + // For weapons with delays in the range 85-94, use the Damage Bonus that would apply to a 2H weapon with delay 85. + // For weapons with delays in the range 95-149, use the Damage Bonus that would apply to a 2H weapon with delay 95. + // For weapons with delays 150 or higher, use the Damage Bonus that would apply to a 2H weapon with delay 150. + + static const uint8 ucDelayOver59Levels28to65[6][38] = + { + /* Level: */ + /* 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64. 65 */ + + {10, 10, 11, 12, 12, 12, 13, 14, 14, 15, 15, 15, 17, 17, 17, 18, 18, 19, 20, 20, 20, 21, 22, 24, 27, 28, 30, 35, 36, 39, 42, 45, 49, 51, 53, 54, 57, 59}, /* Delay = 60 */ + {12, 12, 13, 14, 14, 14, 15, 16, 16, 17, 17, 17, 19, 19, 19, 20, 20, 21, 22, 22, 22, 23, 24, 26, 29, 30, 32, 37, 39, 42, 45, 48, 52, 55, 57, 58, 61, 63}, /* Delay = 65 */ + {14, 14, 15, 16, 16, 16, 17, 18, 18, 19, 19, 19, 21, 21, 21, 22, 22, 23, 24, 24, 24, 25, 26, 28, 31, 33, 35, 40, 42, 45, 48, 52, 56, 59, 61, 62, 65, 68}, /* Delay = 70 */ + {19, 19, 20, 21, 21, 21, 22, 23, 23, 24, 24, 24, 26, 26, 26, 27, 27, 28, 29, 29, 29, 30, 31, 34, 37, 39, 41, 47, 49, 54, 57, 61, 66, 69, 72, 74, 77, 80}, /* Delay = 85 */ + {22, 22, 23, 24, 24, 24, 25, 26, 26, 27, 27, 27, 29, 29, 29, 30, 30, 31, 32, 32, 32, 33, 34, 37, 40, 43, 45, 52, 54, 59, 62, 67, 73, 76, 79, 81, 84, 88}, /* Delay = 95 */ + {40, 40, 41, 42, 42, 42, 43, 44, 44, 45, 45, 45, 47, 47, 47, 48, 48, 49, 50, 50, 50, 51, 52, 56, 61, 65, 69, 78, 82, 89, 94, 102, 110, 115, 119, 122, 127, 132} /* Delay = 150 */ + }; + + if( Weapon->Delay < 65 ) + { + return ucDelayOver59Levels28to65[0][ucPlayerLevel-28]; + } + else if( Weapon->Delay < 70 ) + { + return ucDelayOver59Levels28to65[1][ucPlayerLevel-28]; + } + else if( Weapon->Delay < 85 ) + { + return ucDelayOver59Levels28to65[2][ucPlayerLevel-28]; + } + else if( Weapon->Delay < 95 ) + { + return ucDelayOver59Levels28to65[3][ucPlayerLevel-28]; + } + else if( Weapon->Delay < 150 ) + { + return ucDelayOver59Levels28to65[4][ucPlayerLevel-28]; + } + else + { + return ucDelayOver59Levels28to65[5][ucPlayerLevel-28]; + } + } +} + +int Mob::GetMonkHandToHandDamage(void) +{ + // Kaiyodo - Determine a monk's fist damage. Table data from www.monkly-business.com + // saved as static array - this should speed this function up considerably + static int damage[66] = { + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 + 99, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 9, 9, 9, 9, 9,10,10,10,10,10,11,11,11,11,11, + 12,12,12,12,12,13,13,13,13,13,14,14,14,14,14,14,14,14,14,14, + 14,14,15,15,15,15 }; + + // Have a look to see if we have epic fists on + + if (IsClient() && CastToClient()->GetItemIDAt(12) == 10652) + return(9); + else + { + int Level = GetLevel(); + if (Level > 65) + return(19); + else + return damage[Level]; + } +} + +int Mob::GetMonkHandToHandDelay(void) +{ + // Kaiyodo - Determine a monk's fist delay. Table data from www.monkly-business.com + // saved as static array - this should speed this function up considerably + static int delayshuman[66] = { + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 + 99,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36, + 36,36,36,36,36,35,35,35,35,35,34,34,34,34,34,33,33,33,33,33, + 32,32,32,32,32,31,31,31,31,31,30,30,30,29,29,29,28,28,28,27, + 26,24,22,20,20,20 }; + static int delaysiksar[66] = { + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 + 99,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36, + 36,36,36,36,36,36,36,36,36,36,35,35,35,35,35,34,34,34,34,34, + 33,33,33,33,33,32,32,32,32,32,31,31,31,30,30,30,29,29,29,28, + 27,24,22,20,20,20 }; + + // Have a look to see if we have epic fists on + if (IsClient() && CastToClient()->GetItemIDAt(12) == 10652) + return(16); + else + { + int Level = GetLevel(); + if (GetRace() == HUMAN) + { + if (Level > 65) + return(24); + else + return delayshuman[Level]; + } + else //heko: iksar table + { + if (Level > 65) + return(25); + else + return delaysiksar[Level]; + } + } +} + + +int32 Mob::ReduceDamage(int32 damage) +{ + if(damage <= 0) + return damage; + + int32 slot = -1; + bool DisableMeleeRune = false; + + if (spellbonuses.NegateAttacks[0]){ + slot = spellbonuses.NegateAttacks[1]; + if(slot >= 0) { + if(--buffs[slot].numhits == 0) { + + if(!TryFadeEffect(slot)) + BuffFadeBySlot(slot , true); + } + return -6; + } + } + + //Only mitigate if damage is above the minimium specified. + if (spellbonuses.MeleeThresholdGuard[0]){ + slot = spellbonuses.MeleeThresholdGuard[1]; + + if (slot >= 0 && (damage > spellbonuses.MeleeThresholdGuard[2])) + { + DisableMeleeRune = true; + int damage_to_reduce = damage * spellbonuses.MeleeThresholdGuard[0] / 100; + if(damage_to_reduce > buffs[slot].melee_rune) + { + mlog(SPELLS__EFFECT_VALUES, "Mob::ReduceDamage SE_MeleeThresholdGuard %d damage negated, %d" + " damage remaining, fading buff.", damage_to_reduce, buffs[slot].melee_rune); + damage -= damage_to_reduce; + if(!TryFadeEffect(slot)) + BuffFadeBySlot(slot); + } + else + { + mlog(SPELLS__EFFECT_VALUES, "Mob::ReduceDamage SE_MeleeThresholdGuard %d damage negated, %d" + " damage remaining.", damage_to_reduce, buffs[slot].melee_rune); + buffs[slot].melee_rune = (buffs[slot].melee_rune - damage_to_reduce); + damage -= damage_to_reduce; + } + } + } + + + if (spellbonuses.MitigateMeleeRune[0] && !DisableMeleeRune){ + slot = spellbonuses.MitigateMeleeRune[1]; + if(slot >= 0) + { + int damage_to_reduce = damage * spellbonuses.MitigateMeleeRune[0] / 100; + if(damage_to_reduce > buffs[slot].melee_rune) + { + mlog(SPELLS__EFFECT_VALUES, "Mob::ReduceDamage SE_MitigateMeleeDamage %d damage negated, %d" + " damage remaining, fading buff.", damage_to_reduce, buffs[slot].melee_rune); + damage -= damage_to_reduce; + if(!TryFadeEffect(slot)) + BuffFadeBySlot(slot); + } + else + { + mlog(SPELLS__EFFECT_VALUES, "Mob::ReduceDamage SE_MitigateMeleeDamage %d damage negated, %d" + " damage remaining.", damage_to_reduce, buffs[slot].melee_rune); + buffs[slot].melee_rune = (buffs[slot].melee_rune - damage_to_reduce); + damage -= damage_to_reduce; + } + } + } + + if (spellbonuses.TriggerMeleeThreshold[2]){ + slot = spellbonuses.TriggerMeleeThreshold[1]; + + if (slot >= 0) { + if(damage > buffs[slot].melee_rune) { + if(!TryFadeEffect(slot)) + BuffFadeBySlot(slot); + } + else{ + buffs[slot].melee_rune = (buffs[slot].melee_rune - damage); + } + } + } + + if(damage < 1) + return -6; + + if (spellbonuses.MeleeRune[0] && spellbonuses.MeleeRune[1] >= 0) + damage = RuneAbsorb(damage, SE_Rune); + + if(damage < 1) + return -6; + + return(damage); +} + +int32 Mob::AffectMagicalDamage(int32 damage, uint16 spell_id, const bool iBuffTic, Mob* attacker) +{ + if(damage <= 0) + return damage; + + bool DisableSpellRune = false; + int32 slot = -1; + + // See if we block the spell outright first + if (spellbonuses.NegateAttacks[0]){ + slot = spellbonuses.NegateAttacks[1]; + if(slot >= 0) { + if(--buffs[slot].numhits == 0) { + + if(!TryFadeEffect(slot)) + BuffFadeBySlot(slot , true); + } + return 0; + } + } + + // If this is a DoT, use DoT Shielding... + if(iBuffTic) { + damage -= (damage * itembonuses.DoTShielding / 100); + + if (spellbonuses.MitigateDotRune[0]){ + slot = spellbonuses.MitigateDotRune[1]; + if(slot >= 0) + { + int damage_to_reduce = damage * spellbonuses.MitigateDotRune[0] / 100; + if(damage_to_reduce > buffs[slot].dot_rune) + { + damage -= damage_to_reduce; + if(!TryFadeEffect(slot)) + BuffFadeBySlot(slot); + } + else + { + buffs[slot].dot_rune = (buffs[slot].dot_rune - damage_to_reduce); + damage -= damage_to_reduce; + } + } + } + } + + // This must be a DD then so lets apply Spell Shielding and runes. + else + { + // Reduce damage by the Spell Shielding first so that the runes don't take the raw damage. + damage -= (damage * itembonuses.SpellShield / 100); + + + //Only mitigate if damage is above the minimium specified. + if (spellbonuses.SpellThresholdGuard[0]){ + slot = spellbonuses.SpellThresholdGuard[1]; + + if (slot >= 0 && (damage > spellbonuses.MeleeThresholdGuard[2])) + { + DisableSpellRune = true; + int damage_to_reduce = damage * spellbonuses.SpellThresholdGuard[0] / 100; + if(damage_to_reduce > buffs[slot].magic_rune) + { + damage -= damage_to_reduce; + if(!TryFadeEffect(slot)) + BuffFadeBySlot(slot); + } + else + { + buffs[slot].melee_rune = (buffs[slot].magic_rune - damage_to_reduce); + damage -= damage_to_reduce; + } + } + } + + + // Do runes now. + if (spellbonuses.MitigateSpellRune[0] && !DisableSpellRune){ + slot = spellbonuses.MitigateSpellRune[1]; + if(slot >= 0) + { + int damage_to_reduce = damage * spellbonuses.MitigateSpellRune[0] / 100; + if(damage_to_reduce > buffs[slot].magic_rune) + { + mlog(SPELLS__EFFECT_VALUES, "Mob::ReduceDamage SE_MitigateSpellDamage %d damage negated, %d" + " damage remaining, fading buff.", damage_to_reduce, buffs[slot].magic_rune); + damage -= damage_to_reduce; + if(!TryFadeEffect(slot)) + BuffFadeBySlot(slot); + } + else + { + mlog(SPELLS__EFFECT_VALUES, "Mob::ReduceDamage SE_MitigateMeleeDamage %d damage negated, %d" + " damage remaining.", damage_to_reduce, buffs[slot].magic_rune); + buffs[slot].magic_rune = (buffs[slot].magic_rune - damage_to_reduce); + damage -= damage_to_reduce; + } + } + } + + if (spellbonuses.TriggerSpellThreshold[2]){ + slot = spellbonuses.TriggerSpellThreshold[1]; + + if (slot >= 0) { + if(damage > buffs[slot].magic_rune) { + if(!TryFadeEffect(slot)) + BuffFadeBySlot(slot); + } + else{ + buffs[slot].magic_rune = (buffs[slot].magic_rune - damage); + } + } + } + + if(damage < 1) + return 0; + + + if (spellbonuses.AbsorbMagicAtt[0] && spellbonuses.AbsorbMagicAtt[1] >= 0) + damage = RuneAbsorb(damage, SE_AbsorbMagicAtt); + + if(damage < 1) + return 0; + } + return damage; +} + +int32 Mob::ReduceAllDamage(int32 damage) +{ + if(damage <= 0) + return damage; + + if(spellbonuses.ManaAbsorbPercentDamage[0] && (GetMana() > damage * spellbonuses.ManaAbsorbPercentDamage[0] / 100)) { + damage -= (damage * spellbonuses.ManaAbsorbPercentDamage[0] / 100); + SetMana(GetMana() - damage); + TryTriggerOnValueAmount(false, true); + } + + CheckNumHitsRemaining(8); + + return(damage); +} + +bool Mob::HasProcs() const +{ + for (int i = 0; i < MAX_PROCS; i++) + if (PermaProcs[i].spellID != SPELL_UNKNOWN || SpellProcs[i].spellID != SPELL_UNKNOWN) + return true; + return false; +} + +bool Mob::HasDefensiveProcs() const +{ + for (int i = 0; i < MAX_PROCS; i++) + if (DefensiveProcs[i].spellID != SPELL_UNKNOWN) + return true; + return false; +} + +bool Mob::HasSkillProcs() const +{ + for (int i = 0; i < MAX_PROCS; i++) + if (SkillProcs[i].spellID != SPELL_UNKNOWN) + return true; + return false; +} + +bool Mob::HasRangedProcs() const +{ + for (int i = 0; i < MAX_PROCS; i++) + if (RangedProcs[i].spellID != SPELL_UNKNOWN) + return true; + return false; +} + +bool Client::CheckDoubleAttack(bool tripleAttack) { + + //Check for bonuses that give you a double attack chance regardless of skill (ie Bestial Frenzy/Harmonious Attack AA) + uint16 bonusGiveDA = aabonuses.GiveDoubleAttack + spellbonuses.GiveDoubleAttack + itembonuses.GiveDoubleAttack; + + if(!HasSkill(SkillDoubleAttack) && !bonusGiveDA) + return false; + + float chance = 0.0f; + + uint16 skill = GetSkill(SkillDoubleAttack); + + int16 bonusDA = aabonuses.DoubleAttackChance + spellbonuses.DoubleAttackChance + itembonuses.DoubleAttackChance; + + //Use skill calculations otherwise, if you only have AA applied GiveDoubleAttack chance then use that value as the base. + if (skill) + chance = (float(skill+GetLevel()) * (float(100.0f+bonusDA+bonusGiveDA) /100.0f)) /500.0f; + else + chance = (float(bonusGiveDA) * (float(100.0f+bonusDA)/100.0f) ) /100.0f; + + //Live now uses a static Triple Attack skill (lv 46 = 2% lv 60 = 20%) - We do not have this skill on EMU ATM. + //A reasonable forumla would then be TA = 20% * chance + //AA's can also give triple attack skill over cap. (ie Burst of Power) NOTE: Skill ID in spell data is 76 (Triple Attack) + //Kayen: Need to decide if we can implement triple attack skill before working in over the cap effect. + if(tripleAttack) { + // Only some Double Attack classes get Triple Attack [This is already checked in client_processes.cpp] + int16 triple_bonus = spellbonuses.TripleAttackChance + itembonuses.TripleAttackChance; + chance *= 0.2f; //Baseline chance is 20% of your double attack chance. + chance *= float(100.0f+triple_bonus)/100.0f; //Apply modifiers. + } + + if((MakeRandomFloat(0, 1) < chance)) + return true; + + return false; +} + +bool Client::CheckDoubleRangedAttack() { + + int16 chance = spellbonuses.DoubleRangedAttack + itembonuses.DoubleRangedAttack + aabonuses.DoubleRangedAttack; + + if(chance && (MakeRandomInt(0, 100) < chance)) + return true; + + return false; +} + +void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, const SkillUseTypes skill_used, bool &avoidable, const int8 buffslot, const bool iBuffTic) { + // This method is called with skill_used=ABJURE for Damage Shield damage. + bool FromDamageShield = (skill_used == SkillAbjuration); + + mlog(COMBAT__HITS, "Applying damage %d done by %s with skill %d and spell %d, avoidable? %s, is %sa buff tic in slot %d", + damage, attacker?attacker->GetName():"NOBODY", skill_used, spell_id, avoidable?"yes":"no", iBuffTic?"":"not ", buffslot); + + if (GetInvul() || DivineAura()) { + mlog(COMBAT__DAMAGE, "Avoiding %d damage due to invulnerability.", damage); + damage = -5; + } + + if( spell_id != SPELL_UNKNOWN || attacker == nullptr ) + avoidable = false; + + // only apply DS if physical damage (no spell damage) + // damage shield calls this function with spell_id set, so its unavoidable + if (attacker && damage > 0 && spell_id == SPELL_UNKNOWN && skill_used != SkillArchery && skill_used != SkillThrowing) { + DamageShield(attacker); + } + + if (spell_id == SPELL_UNKNOWN && skill_used) { + CheckNumHitsRemaining(1); //Incoming Hit Attempts + + if (attacker) + attacker->CheckNumHitsRemaining(2); //Outgoing Hit Attempts + } + + if(attacker){ + if(attacker->IsClient()){ + if(!RuleB(Combat, EXPFromDmgShield)) { + // Damage shield damage shouldn't count towards who gets EXP + if(!attacker->CastToClient()->GetFeigned() && !FromDamageShield) + AddToHateList(attacker, 0, damage, true, false, iBuffTic); + } + else { + if(!attacker->CastToClient()->GetFeigned()) + AddToHateList(attacker, 0, damage, true, false, iBuffTic); + } + } + else + AddToHateList(attacker, 0, damage, true, false, iBuffTic); + } + + if(damage > 0) { + //if there is some damage being done and theres an attacker involved + if(attacker) { + if(spell_id == SPELL_HARM_TOUCH2 && attacker->IsClient() && attacker->CastToClient()->CheckAAEffect(aaEffectLeechTouch)){ + int healed = damage; + healed = attacker->GetActSpellHealing(spell_id, healed); + attacker->HealDamage(healed); + entity_list.MessageClose(this, true, 300, MT_Emote, "%s beams a smile at %s", attacker->GetCleanName(), this->GetCleanName() ); + attacker->CastToClient()->DisableAAEffect(aaEffectLeechTouch); + } + + // if spell is lifetap add hp to the caster + if (spell_id != SPELL_UNKNOWN && IsLifetapSpell( spell_id )) { + int healed = damage; + + healed = attacker->GetActSpellHealing(spell_id, healed); + mlog(COMBAT__DAMAGE, "Applying lifetap heal of %d to %s", healed, attacker->GetName()); + attacker->HealDamage(healed); + + //we used to do a message to the client, but its gone now. + // emote goes with every one ... even npcs + entity_list.MessageClose(this, true, 300, MT_Emote, "%s beams a smile at %s", attacker->GetCleanName(), this->GetCleanName() ); + } + } //end `if there is some damage being done and theres anattacker person involved` + + Mob *pet = GetPet(); + if (pet && !pet->IsFamiliar() && !pet->GetSpecialAbility(IMMUNE_AGGRO) && !pet->IsEngaged() && attacker && attacker != this && !attacker->IsCorpse()) + { + if (!pet->IsHeld()) { + mlog(PETS__AGGRO, "Sending pet %s into battle due to attack.", pet->GetName()); + pet->AddToHateList(attacker, 1); + pet->SetTarget(attacker); + Message_StringID(10, PET_ATTACKING, pet->GetCleanName(), attacker->GetCleanName()); + } + } + + //see if any runes want to reduce this damage + if(spell_id == SPELL_UNKNOWN) { + damage = ReduceDamage(damage); + mlog(COMBAT__HITS, "Melee Damage reduced to %d", damage); + } else { + int32 origdmg = damage; + damage = AffectMagicalDamage(damage, spell_id, iBuffTic, attacker); + if (origdmg != damage && attacker && attacker->IsClient()) { + if(attacker->CastToClient()->GetFilter(FilterDamageShields) != FilterHide) + attacker->Message(15, "The Spellshield absorbed %d of %d points of damage", origdmg - damage, origdmg); + } + if (damage == 0 && attacker && origdmg != damage && IsClient()) { + //Kayen: Probably need to add a filter for this - Not sure if this msg is correct but there should be a message for spell negate/runes. + Message(263, "%s tries to cast on YOU, but YOUR magical skin absorbs the spell.",attacker->GetCleanName()); + } + + } + + if (skill_used) + CheckNumHitsRemaining(6); //Incomming Hit Success on Defender + + ReduceAllDamage(damage); + + if(IsClient() && CastToClient()->sneaking){ + CastToClient()->sneaking = false; + SendAppearancePacket(AT_Sneak, 0); + } + if(attacker && attacker->IsClient() && attacker->CastToClient()->sneaking){ + attacker->CastToClient()->sneaking = false; + attacker->SendAppearancePacket(AT_Sneak, 0); + } + + //final damage has been determined. + + SetHP(GetHP() - damage); + + if(HasDied()) { + bool IsSaved = false; + + if(TryDivineSave()) + IsSaved = true; + + if(!IsSaved && !TrySpellOnDeath()) { + SetHP(-500); + + if(Death(attacker, damage, spell_id, skill_used)) { + return; + } + } + } + else{ + if(GetHPRatio() < 16) + TryDeathSave(); + } + + TryTriggerOnValueAmount(true); + + //fade mez if we are mezzed + if (IsMezzed()) { + mlog(COMBAT__HITS, "Breaking mez due to attack."); + BuffFadeByEffect(SE_Mez); + } + + //check stun chances if bashing + if (damage > 0 && ((skill_used == SkillBash || skill_used == SkillKick) && attacker)) { + // NPCs can stun with their bash/kick as soon as they receive it. + // Clients can stun mobs under level 56 with their kick when they get level 55 or greater. + // Clients have a chance to stun if the mob is 56+ + + // Calculate the chance to stun + int stun_chance = 0; + if (!GetSpecialAbility(UNSTUNABLE)) { + if (attacker->IsNPC()) { + stun_chance = RuleI(Combat, NPCBashKickStunChance); + } else if (attacker->IsClient()) { + // Less than base immunity + // Client vs. Client always uses the chance + if (!IsClient() && GetLevel() <= RuleI(Spells, BaseImmunityLevel)) { + if (skill_used == SkillBash) // Bash always will + stun_chance = 100; + else if (attacker->GetLevel() >= RuleI(Combat, ClientStunLevel)) + stun_chance = 100; // only if you're over level 55 and using kick + } else { // higher than base immunity or Client vs. Client + // not sure on this number, use same as NPC for now + if (skill_used == SkillKick && attacker->GetLevel() < RuleI(Combat, ClientStunLevel)) + stun_chance = RuleI(Combat, NPCBashKickStunChance); + else if (skill_used == SkillBash) + stun_chance = RuleI(Combat, NPCBashKickStunChance) + + attacker->spellbonuses.StunBashChance + + attacker->itembonuses.StunBashChance + + attacker->aabonuses.StunBashChance; + } + } + } + + if (stun_chance && MakeRandomInt(0, 99) < stun_chance) { + // Passed stun, try to resist now + int stun_resist = itembonuses.StunResist + spellbonuses.StunResist; + int frontal_stun_resist = itembonuses.FrontalStunResist + spellbonuses.FrontalStunResist; + + mlog(COMBAT__HITS, "Stun passed, checking resists. Was %d chance.", stun_chance); + if (IsClient()) { + stun_resist += aabonuses.StunResist; + frontal_stun_resist += aabonuses.FrontalStunResist; + } + + // frontal stun check for ogres/bonuses + if (((GetBaseRace() == OGRE && IsClient()) || + (frontal_stun_resist && MakeRandomInt(0, 99) < frontal_stun_resist)) && + !attacker->BehindMob(this, attacker->GetX(), attacker->GetY())) { + mlog(COMBAT__HITS, "Frontal stun resisted. %d chance.", frontal_stun_resist); + } else { + // Normal stun resist check. + if (stun_resist && MakeRandomInt(0, 99) < stun_resist) { + if (IsClient()) + Message_StringID(MT_Stun, SHAKE_OFF_STUN); + mlog(COMBAT__HITS, "Stun Resisted. %d chance.", stun_resist); + } else { + mlog(COMBAT__HITS, "Stunned. %d resist chance.", stun_resist); + Stun(MakeRandomInt(0, 2) * 1000); // 0-2 seconds + } + } + } else { + mlog(COMBAT__HITS, "Stun failed. %d chance.", stun_chance); + } + } + + if(spell_id != SPELL_UNKNOWN && !iBuffTic) { + //see if root will break + if (IsRooted() && !FromDamageShield) // neotoyko: only spells cancel root + TryRootFadeByDamage(buffslot, attacker); + } + else if(spell_id == SPELL_UNKNOWN) + { + //increment chances of interrupting + if(IsCasting()) { //shouldnt interrupt on regular spell damage + attacked_count++; + mlog(COMBAT__HITS, "Melee attack while casting. Attack count %d", attacked_count); + } + } + + //send an HP update if we are hurt + if(GetHP() < GetMaxHP()) + SendHPUpdate(); + } //end `if damage was done` + + //send damage packet... + if(!iBuffTic) { //buff ticks do not send damage, instead they just call SendHPUpdate(), which is done below + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Damage, sizeof(CombatDamage_Struct)); + CombatDamage_Struct* a = (CombatDamage_Struct*)outapp->pBuffer; + a->target = GetID(); + if (attacker == nullptr) + a->source = 0; + else if (attacker->IsClient() && attacker->CastToClient()->GMHideMe()) + a->source = 0; + else + a->source = attacker->GetID(); + a->type = SkillDamageTypes[skill_used]; // was 0x1c + a->damage = damage; + a->spellid = spell_id; + + //Note: if players can become pets, they will not receive damage messages of their own + //this was done to simplify the code here (since we can only effectively skip one mob on queue) + eqFilterType filter; + Mob *skip = attacker; + if(attacker && attacker->GetOwnerID()) { + //attacker is a pet, let pet owners see their pet's damage + Mob* owner = attacker->GetOwner(); + if (owner && owner->IsClient()) { + if (((spell_id != SPELL_UNKNOWN) || (FromDamageShield)) && damage>0) { + //special crap for spell damage, looks hackish to me + char val1[20]={0}; + owner->Message_StringID(MT_NonMelee,OTHER_HIT_NONMELEE,GetCleanName(),ConvertArray(damage,val1)); + } else { + if(damage > 0) { + if(spell_id != SPELL_UNKNOWN) + filter = iBuffTic ? FilterDOT : FilterSpellDamage; + else + filter = FilterPetHits; + } else if(damage == -5) + filter = FilterNone; //cant filter invulnerable + else + filter = FilterPetMisses; + + if(!FromDamageShield) + owner->CastToClient()->QueuePacket(outapp,true,CLIENT_CONNECTED,filter); + } + } + skip = owner; + } else { + //attacker is not a pet, send to the attacker + + //if the attacker is a client, try them with the correct filter + if(attacker && attacker->IsClient()) { + if (((spell_id != SPELL_UNKNOWN)||(FromDamageShield)) && damage>0) { + //special crap for spell damage, looks hackish to me + char val1[20]={0}; + if (FromDamageShield) + { + if(!attacker->CastToClient()->GetFilter(FilterDamageShields) == FilterHide) + { + attacker->Message_StringID(MT_DS,OTHER_HIT_NONMELEE,GetCleanName(),ConvertArray(damage,val1)); + } + } + else + entity_list.MessageClose_StringID(this, true, 100, MT_NonMelee,HIT_NON_MELEE,attacker->GetCleanName(),GetCleanName(),ConvertArray(damage,val1)); + } else { + if(damage > 0) { + if(spell_id != SPELL_UNKNOWN) + filter = iBuffTic ? FilterDOT : FilterSpellDamage; + else + filter = FilterNone; //cant filter our own hits + } else if(damage == -5) + filter = FilterNone; //cant filter invulnerable + else + filter = FilterMyMisses; + + attacker->CastToClient()->QueuePacket(outapp, true, CLIENT_CONNECTED, filter); + } + } + skip = attacker; + } + + //send damage to all clients around except the specified skip mob (attacker or the attacker's owner) and ourself + if(damage > 0) { + if(spell_id != SPELL_UNKNOWN) + filter = iBuffTic ? FilterDOT : FilterSpellDamage; + else + filter = FilterOthersHit; + } else if(damage == -5) + filter = FilterNone; //cant filter invulnerable + else + filter = FilterOthersMiss; + //make attacker (the attacker) send the packet so we can skip them and the owner + //this call will send the packet to `this` as well (using the wrong filter) (will not happen until PC charm works) + // If this is Damage Shield damage, the correct OP_Damage packets will be sent from Mob::DamageShield, so + // we don't send them here. + if(!FromDamageShield) { + entity_list.QueueCloseClients(this, outapp, true, 200, skip, true, filter); + //send the damage to ourself if we are a client + if(IsClient()) { + //I dont think any filters apply to damage affecting us + CastToClient()->QueuePacket(outapp); + } + } + + safe_delete(outapp); + } else { + //else, it is a buff tic... + // Everhood - So we can see our dot dmg like live shows it. + if(spell_id != SPELL_UNKNOWN && damage > 0 && attacker && attacker != this && attacker->IsClient()) { + //might filter on (attack_skill>200 && attack_skill<250), but I dont think we need it + attacker->FilteredMessage_StringID(attacker, MT_DoTDamage, FilterDOT, + YOUR_HIT_DOT, GetCleanName(), itoa(damage), spells[spell_id].name); + // older clients don't have the below String ID, but it will be filtered + entity_list.FilteredMessageClose_StringID(attacker, true, 200, + MT_DoTDamage, FilterDOT, OTHER_HIT_DOT, GetCleanName(), + itoa(damage), attacker->GetCleanName(), spells[spell_id].name); + } + } //end packet sending + +} + + +void Mob::HealDamage(uint32 amount, Mob *caster, uint16 spell_id) +{ + int32 maxhp = GetMaxHP(); + int32 curhp = GetHP(); + uint32 acthealed = 0; + + if (caster && amount > 0) { + if (caster->IsNPC() && !caster->IsPet()) { + float npchealscale = caster->CastToNPC()->GetHealScale(); + amount = (static_cast(amount) * npchealscale) / 100.0f; + } + } + + if (amount > (maxhp - curhp)) + acthealed = (maxhp - curhp); + else + acthealed = amount; + + if (acthealed > 100) { + if (caster) { + if (IsBuffSpell(spell_id)) { // hots + // message to caster + if (caster->IsClient() && caster == this) { + if (caster->CastToClient()->GetClientVersionBit() & BIT_SoFAndLater) + FilteredMessage_StringID(caster, MT_NonMelee, FilterHealOverTime, + HOT_HEAL_SELF, itoa(acthealed), spells[spell_id].name); + else + FilteredMessage_StringID(caster, MT_NonMelee, FilterHealOverTime, + YOU_HEALED, GetCleanName(), itoa(acthealed)); + } else if (caster->IsClient() && caster != this) { + if (caster->CastToClient()->GetClientVersionBit() & BIT_SoFAndLater) + caster->FilteredMessage_StringID(caster, MT_NonMelee, FilterHealOverTime, + HOT_HEAL_OTHER, GetCleanName(), itoa(acthealed), + spells[spell_id].name); + else + caster->FilteredMessage_StringID(caster, MT_NonMelee, FilterHealOverTime, + YOU_HEAL, GetCleanName(), itoa(acthealed)); + } + // message to target + if (IsClient() && caster != this) { + if (CastToClient()->GetClientVersionBit() & BIT_SoFAndLater) + FilteredMessage_StringID(this, MT_NonMelee, FilterHealOverTime, + HOT_HEALED_OTHER, caster->GetCleanName(), + itoa(acthealed), spells[spell_id].name); + else + FilteredMessage_StringID(this, MT_NonMelee, FilterHealOverTime, + YOU_HEALED, caster->GetCleanName(), itoa(acthealed)); + } + } else { // normal heals + FilteredMessage_StringID(caster, MT_NonMelee, FilterSpellDamage, + YOU_HEALED, caster->GetCleanName(), itoa(acthealed)); + if (caster != this) + caster->FilteredMessage_StringID(caster, MT_NonMelee, FilterSpellDamage, + YOU_HEAL, GetCleanName(), itoa(acthealed)); + } + } else { + Message(MT_NonMelee, "You have been healed for %d points of damage.", acthealed); + } + } + + if (curhp < maxhp) { + if ((curhp + amount) > maxhp) + curhp = maxhp; + else + curhp += amount; + SetHP(curhp); + + SendHPUpdate(); + } +} + +//proc chance includes proc bonus +float Mob::GetProcChances(float ProcBonus, uint16 weapon_speed, uint16 hand) +{ + int mydex = GetDEX(); + float ProcChance = 0.0f; + + switch (hand) { + case 13: + weapon_speed = attack_timer.GetDuration(); + break; + case 14: + weapon_speed = attack_dw_timer.GetDuration(); + break; + case 11: + weapon_speed = ranged_timer.GetDuration(); + break; + } + + //calculate the weapon speed in ms, so we can use the rule to compare against. + // fast as a client can swing, so should be the floor of the proc chance + if (weapon_speed < RuleI(Combat, MinHastedDelay)) + weapon_speed = RuleI(Combat, MinHastedDelay); + + if (RuleB(Combat, AdjustProcPerMinute)) { + ProcChance = (static_cast(weapon_speed) * + RuleR(Combat, AvgProcsPerMinute) / 60000.0f); // compensate for weapon_speed being in ms + ProcBonus += static_cast(mydex) * RuleR(Combat, ProcPerMinDexContrib); + ProcChance += ProcChance * ProcBonus / 100.0f; + } else { + ProcChance = RuleR(Combat, BaseProcChance) + + static_cast(mydex) / RuleR(Combat, ProcDexDivideBy); + ProcChance += ProcChance * ProcBonus / 100.0f; + } + + mlog(COMBAT__PROCS, "Proc chance %.2f (%.2f from bonuses)", ProcChance, ProcBonus); + return ProcChance; +} + +float Mob::GetDefensiveProcChances(float &ProcBonus, float &ProcChance, uint16 weapon_speed, uint16 hand) { + int myagi = GetAGI(); + ProcBonus = 0; + ProcChance = 0; + + switch(hand){ + case 13: + weapon_speed = attack_timer.GetDuration(); + break; + case 14: + weapon_speed = attack_dw_timer.GetDuration(); + break; + case 11: + return 0; + break; + } + + //calculate the weapon speed in ms, so we can use the rule to compare against. + //weapon_speed = ((int)(weapon_speed*(100.0f+attack_speed)*PermaHaste)); + if(weapon_speed < RuleI(Combat, MinHastedDelay)) // fast as a client can swing, so should be the floor of the proc chance + weapon_speed = RuleI(Combat, MinHastedDelay); + + ProcChance = ((float)weapon_speed * RuleR(Combat, AvgDefProcsPerMinute) / 60000.0f); // compensate for weapon_speed being in ms + ProcBonus += float(myagi) * RuleR(Combat, DefProcPerMinAgiContrib) / 100.0f; + ProcChance = ProcChance + (ProcChance * ProcBonus); + + mlog(COMBAT__PROCS, "Defensive Proc chance %.2f (%.2f from bonuses)", ProcChance, ProcBonus); + return ProcChance; +} + +void Mob::TryDefensiveProc(const ItemInst* weapon, Mob *on, uint16 hand, int damage) { + + if (!on) { + SetTarget(nullptr); + LogFile->write(EQEMuLog::Error, "A null Mob object was passed to Mob::TryDefensiveProc for evaluation!"); + return; + } + + bool bSkillProc = HasSkillProcs(); + bool bDefensiveProc = HasDefensiveProcs(); + + if (!bDefensiveProc && !bSkillProc) + return; + + if (!bDefensiveProc && (bSkillProc && damage >= 0)) + return; + + float ProcChance, ProcBonus; + if(weapon!=nullptr) + on->GetDefensiveProcChances(ProcBonus, ProcChance, weapon->GetItem()->Delay, hand); + else + on->GetDefensiveProcChances(ProcBonus, ProcChance); + if(hand != 13) + ProcChance /= 2; + + if (bDefensiveProc){ + for (int i = 0; i < MAX_PROCS; i++) { + if (DefensiveProcs[i].spellID != SPELL_UNKNOWN) { + int chance = ProcChance * (DefensiveProcs[i].chance); + if ((MakeRandomInt(0, 100) < chance)) { + ExecWeaponProc(nullptr, DefensiveProcs[i].spellID, on); + CheckNumHitsRemaining(10,0,DefensiveProcs[i].base_spellID); + } + } + } + } + + if (bSkillProc && damage < 0){ + + if (damage == -1) + TrySkillProc(on, SkillBlock, ProcChance); + + if (damage == -2) + TrySkillProc(on, SkillParry, ProcChance); + + if (damage == -3) + TrySkillProc(on, SkillRiposte, ProcChance); + + if (damage == -4) + TrySkillProc(on, SkillDodge, ProcChance); + } +} + +void Mob::TryWeaponProc(const ItemInst* weapon_g, Mob *on, uint16 hand) { + if(!on) { + SetTarget(nullptr); + LogFile->write(EQEMuLog::Error, "A null Mob object was passed to Mob::TryWeaponProc for evaluation!"); + return; + } + + if (!IsAttackAllowed(on)) { + mlog(COMBAT__PROCS, "Preventing procing off of unattackable things."); + return; + } + + if(!weapon_g) { + TrySpellProc(nullptr, (const Item_Struct*)nullptr, on); + return; + } + + if(!weapon_g->IsType(ItemClassCommon)) { + TrySpellProc(nullptr, (const Item_Struct*)nullptr, on); + return; + } + + // Innate + aug procs from weapons + // TODO: powersource procs + TryWeaponProc(weapon_g, weapon_g->GetItem(), on, hand); + // Procs from Buffs and AA both melee and range + TrySpellProc(weapon_g, weapon_g->GetItem(), on, hand); + + return; +} + +void Mob::TryWeaponProc(const ItemInst *inst, const Item_Struct *weapon, Mob *on, uint16 hand) +{ + if (!weapon) + return; + uint16 skillinuse = 28; + int ourlevel = GetLevel(); + float ProcBonus = static_cast(aabonuses.ProcChanceSPA + + spellbonuses.ProcChanceSPA + itembonuses.ProcChanceSPA); + ProcBonus += static_cast(itembonuses.ProcChance) / 10.0f; // Combat Effects + float ProcChance = GetProcChances(ProcBonus, weapon->Delay, hand); + + if (hand != 13) //Is Archery intened to proc at 50% rate? + ProcChance /= 2; + + // Try innate proc on weapon + // We can proc once here, either weapon or one aug + bool proced = false; // silly bool to prevent augs from going if weapon does + skillinuse = GetSkillByItemType(weapon->ItemType); + if (weapon->Proc.Type == ET_CombatProc) { + float WPC = ProcChance * (100.0f + // Proc chance for this weapon + static_cast(weapon->ProcRate)) / 100.0f; + if (MakeRandomFloat(0, 1) <= WPC) { // 255 dex = 0.084 chance of proc. No idea what this number should be really. + if (weapon->Proc.Level > ourlevel) { + mlog(COMBAT__PROCS, + "Tried to proc (%s), but our level (%d) is lower than required (%d)", + weapon->Name, ourlevel, weapon->Proc.Level); + if (IsPet()) { + Mob *own = GetOwner(); + if (own) + own->Message_StringID(13, PROC_PETTOOLOW); + } else { + Message_StringID(13, PROC_TOOLOW); + } + } else { + mlog(COMBAT__PROCS, + "Attacking weapon (%s) successfully procing spell %d (%.2f percent chance)", + weapon->Name, weapon->Proc.Effect, WPC * 100); + ExecWeaponProc(inst, weapon->Proc.Effect, on); + proced = true; + } + } + } + + if (!proced && inst) { + for (int r = 0; r < MAX_AUGMENT_SLOTS; r++) { + const ItemInst *aug_i = inst->GetAugment(r); + if (!aug_i) // no aug, try next slot! + continue; + const Item_Struct *aug = aug_i->GetItem(); + if (!aug) + continue; + + if (aug->Proc.Type == ET_CombatProc) { + float APC = ProcChance * (100.0f + // Proc chance for this aug + static_cast(aug->ProcRate)) / 100.0f; + if (MakeRandomFloat(0, 1) <= APC) { + if (aug->Proc.Level > ourlevel) { + if (IsPet()) { + Mob *own = GetOwner(); + if (own) + own->Message_StringID(13, PROC_PETTOOLOW); + } else { + Message_StringID(13, PROC_TOOLOW); + } + } else { + ExecWeaponProc(aug_i, aug->Proc.Effect, on); + break; + } + } + } + } + } + // TODO: Powersource procs + if (HasSkillProcs()) + TrySkillProc(on, skillinuse, ProcChance); + + return; +} + +void Mob::TrySpellProc(const ItemInst *inst, const Item_Struct *weapon, Mob *on, uint16 hand) +{ + float ProcBonus = static_cast(spellbonuses.SpellProcChance + + itembonuses.SpellProcChance + aabonuses.SpellProcChance); + float ProcChance = 0.0f; + if (weapon) + ProcChance = GetProcChances(ProcBonus, weapon->Delay, hand); + else + ProcChance = GetProcChances(ProcBonus); + + if (hand != 13) //Is Archery intened to proc at 50% rate? + ProcChance /= 2; + + bool rangedattk = false; + if (weapon && hand == 11) { + if (weapon->ItemType == ItemTypeArrow || + weapon->ItemType == ItemTypeLargeThrowing || + weapon->ItemType == ItemTypeSmallThrowing || + weapon->ItemType == ItemTypeBow) + rangedattk = true; + } + + for (uint32 i = 0; i < MAX_PROCS; i++) { + if (IsPet() && hand != 13) //Pets can only proc spell procs from their primay hand (ie; beastlord pets) + continue; // If pets ever can proc from off hand, this will need to change + + // Not ranged + if (!rangedattk) { + // Perma procs (AAs) + if (PermaProcs[i].spellID != SPELL_UNKNOWN) { + if (MakeRandomInt(0, 99) < PermaProcs[i].chance) { // TODO: Do these get spell bonus? + mlog(COMBAT__PROCS, + "Permanent proc %d procing spell %d (%d percent chance)", + i, PermaProcs[i].spellID, PermaProcs[i].chance); + ExecWeaponProc(nullptr, PermaProcs[i].spellID, on); + } else { + mlog(COMBAT__PROCS, + "Permanent proc %d failed to proc %d (%d percent chance)", + i, PermaProcs[i].spellID, PermaProcs[i].chance); + } + } + + // Spell procs (buffs) + if (SpellProcs[i].spellID != SPELL_UNKNOWN) { + float chance = ProcChance * (SpellProcs[i].chance / 100.0f); + if (MakeRandomFloat(0, 1) <= chance) { + mlog(COMBAT__PROCS, + "Spell proc %d procing spell %d (%.2f percent chance)", + i, SpellProcs[i].spellID, chance); + ExecWeaponProc(nullptr, SpellProcs[i].spellID, on); + CheckNumHitsRemaining(11, 0, SpellProcs[i].base_spellID); + } else { + mlog(COMBAT__PROCS, + "Spell proc %d failed to proc %d (%.2f percent chance)", + i, SpellProcs[i].spellID, chance); + } + } + } else if (rangedattk) { // ranged only + // ranged spell procs (buffs) + if (RangedProcs[i].spellID != SPELL_UNKNOWN) { + float chance = ProcChance * (RangedProcs[i].chance / 100.0f); + if (MakeRandomFloat(0, 1) <= chance) { + mlog(COMBAT__PROCS, + "Ranged proc %d procing spell %d (%.2f percent chance)", + i, RangedProcs[i].spellID, chance); + ExecWeaponProc(nullptr, RangedProcs[i].spellID, on); + CheckNumHitsRemaining(11, 0, RangedProcs[i].base_spellID); + } else { + mlog(COMBAT__PROCS, + "Ranged proc %d failed to proc %d (%.2f percent chance)", + i, RangedProcs[i].spellID, chance); + } + } + } + } + + return; +} + +void Mob::TryPetCriticalHit(Mob *defender, uint16 skill, int32 &damage) +{ + if(damage < 1) + return; + + //Allows pets to perform critical hits. + //Each rank adds an additional 1% chance for any melee hit (primary, secondary, kick, bash, etc) to critical, + //dealing up to 63% more damage. http://www.magecompendium.com/aa-short-library.html + + Mob *owner = nullptr; + float critChance = 0.0f; + critChance += RuleI(Combat, MeleeBaseCritChance); + uint16 critMod = 163; + + if (damage < 1) //We can't critical hit if we don't hit. + return; + + if (!IsPet()) + return; + + owner = GetOwner(); + + if (!owner) + return; + + int16 CritPetChance = owner->aabonuses.PetCriticalHit + owner->itembonuses.PetCriticalHit + owner->spellbonuses.PetCriticalHit; + int16 CritChanceBonus = GetCriticalChanceBonus(skill); + + if (CritPetChance || critChance) { + + //For pets use PetCriticalHit for base chance, pets do not innately critical with without it + //even if buffed with a CritChanceBonus effects. + critChance += CritPetChance; + critChance += critChance*CritChanceBonus/100.0f; + } + + if(critChance > 0){ + + critChance /= 100; + + if(MakeRandomFloat(0, 1) < critChance) + { + critMod += GetCritDmgMob(skill) * 2; // To account for base crit mod being 200 not 100 + damage = (damage * critMod) / 100; + entity_list.FilteredMessageClose_StringID(this, false, 200, + MT_CritMelee, FilterMeleeCrits, CRITICAL_HIT, + GetCleanName(), itoa(damage)); + } + } +} + +void Mob::TryCriticalHit(Mob *defender, uint16 skill, int32 &damage, ExtraAttackOptions *opts) +{ + if(damage < 1) + return; + + // decided to branch this into it's own function since it's going to be duplicating a lot of the + // code in here, but could lead to some confusion otherwise + if (IsPet() && GetOwner()->IsClient()) { + TryPetCriticalHit(defender,skill,damage); + return; + } + +#ifdef BOTS + if (this->IsPet() && this->GetOwner()->IsBot()) { + this->TryPetCriticalHit(defender,skill,damage); + return; + } +#endif //BOTS + + + float critChance = 0.0f; + + //1: Try Slay Undead + if(defender && defender->GetBodyType() == BT_Undead || defender->GetBodyType() == BT_SummonedUndead || defender->GetBodyType() == BT_Vampire){ + + int16 SlayRateBonus = aabonuses.SlayUndead[0] + itembonuses.SlayUndead[0] + spellbonuses.SlayUndead[0]; + + if (SlayRateBonus) { + + critChance += (float(SlayRateBonus)/100.0f); + critChance /= 100.0f; + + if(MakeRandomFloat(0, 1) < critChance){ + int16 SlayDmgBonus = aabonuses.SlayUndead[1] + itembonuses.SlayUndead[1] + spellbonuses.SlayUndead[1]; + damage = (damage*SlayDmgBonus*2.25)/100; + entity_list.MessageClose(this, false, 200, MT_CritMelee, "%s cleanses %s target!(%d)", GetCleanName(), this->GetGender() == 0 ? "his" : this->GetGender() == 1 ? "her" : "its", damage); + return; + } + } + } + + //2: Try Melee Critical + + //Base critical rate for all classes is dervived from DEX stat, this rate is then augmented + //by item,spell and AA bonuses allowing you a chance to critical hit. If the following rules + //are defined you will have an innate chance to hit at Level 1 regardless of bonuses. + //Warning: Do not define these rules if you want live like critical hits. + critChance += RuleI(Combat, MeleeBaseCritChance); + + if (IsClient()) { + critChance += RuleI(Combat, ClientBaseCritChance); + + if ((GetClass() == WARRIOR || GetClass() == BERSERKER) && GetLevel() >= 12) { + if (IsBerserk()) + critChance += RuleI(Combat, BerserkBaseCritChance); + else + critChance += RuleI(Combat, WarBerBaseCritChance); + } + } + + int deadlyChance = 0; + int deadlyMod = 0; + if(skill == SkillArchery && GetClass() == RANGER && GetSkill(SkillArchery) >= 65) + critChance += 6; + + if (skill == SkillThrowing && GetClass() == ROGUE && GetSkill(SkillThrowing) >= 65) { + critChance += RuleI(Combat, RogueCritThrowingChance); + deadlyChance = RuleI(Combat, RogueDeadlyStrikeChance); + deadlyMod = RuleI(Combat, RogueDeadlyStrikeMod); + } + + int CritChanceBonus = GetCriticalChanceBonus(skill); + + if (CritChanceBonus || critChance) { + + //Get Base CritChance from Dex. (200 = ~1.6%, 255 = ~2.0%, 355 = ~2.20%) Fall off rate > 255 + //http://giline.versus.jp/shiden/su.htm , http://giline.versus.jp/shiden/damage_e.htm + if (GetDEX() <= 255) + critChance += (float(GetDEX()) / 125.0f); + else if (GetDEX() > 255) + critChance += (float(GetDEX()-255)/ 500.0f) + 2.0f; + critChance += critChance*(float)CritChanceBonus /100.0f; + } + + if(opts) { + critChance *= opts->crit_percent; + critChance += opts->crit_flat; + } + + if(critChance > 0) { + + critChance /= 100; + + if(MakeRandomFloat(0, 1) < critChance) + { + uint16 critMod = 200; + bool crip_success = false; + int16 CripplingBlowChance = GetCrippBlowChance(); + + //Crippling Blow Chance: The percent value of the effect is applied + //to the your Chance to Critical. (ie You have 10% chance to critical and you + //have a 200% Chance to Critical Blow effect, therefore you have a 20% Chance to Critical Blow. + if (CripplingBlowChance || IsBerserk()) { + if (!IsBerserk()) + critChance *= float(CripplingBlowChance)/100.0f; + + if (IsBerserk() || MakeRandomFloat(0, 1) < critChance) { + critMod = 400; + crip_success = true; + } + } + + critMod += GetCritDmgMob(skill) * 2; // To account for base crit mod being 200 not 100 + damage = damage * critMod / 100; + + bool deadlySuccess = false; + if (deadlyChance && MakeRandomFloat(0, 1) < static_cast(deadlyChance) / 100.0f) { + if (BehindMob(defender, GetX(), GetY())) { + damage *= deadlyMod; + deadlySuccess = true; + } + } + + if (crip_success) { + entity_list.FilteredMessageClose_StringID(this, false, 200, + MT_CritMelee, FilterMeleeCrits, CRIPPLING_BLOW, + GetCleanName(), itoa(damage)); + // Crippling blows also have a chance to stun + //Kayen: Crippling Blow would cause a chance to interrupt for npcs < 55, with a staggers message. + if (defender->GetLevel() <= 55 && !defender->GetSpecialAbility(IMMUNE_STUN)){ + defender->Emote("staggers."); + defender->Stun(0); + } + } else if (deadlySuccess) { + entity_list.FilteredMessageClose_StringID(this, false, 200, + MT_CritMelee, FilterMeleeCrits, DEADLY_STRIKE, + GetCleanName(), itoa(damage)); + } else { + entity_list.FilteredMessageClose_StringID(this, false, 200, + MT_CritMelee, FilterMeleeCrits, CRITICAL_HIT, + GetCleanName(), itoa(damage)); + } + } + } +} + + +bool Mob::TryFinishingBlow(Mob *defender, SkillUseTypes skillinuse) +{ + + if (!defender) + return false; + + if (aabonuses.FinishingBlow[1] && !defender->IsClient() && defender->GetHPRatio() < 10){ + + uint32 chance = aabonuses.FinishingBlow[0]/10; //500 = 5% chance. + uint32 damage = aabonuses.FinishingBlow[1]; + uint16 levelreq = aabonuses.FinishingBlowLvl[0]; + + if(defender->GetLevel() <= levelreq && (chance >= MakeRandomInt(0, 1000))){ + mlog(COMBAT__ATTACKS, "Landed a finishing blow: levelreq at %d, other level %d", levelreq , defender->GetLevel()); + entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FINISHING_BLOW, GetName()); + defender->Damage(this, damage, SPELL_UNKNOWN, skillinuse); + return true; + } + else + { + mlog(COMBAT__ATTACKS, "FAILED a finishing blow: levelreq at %d, other level %d", levelreq , defender->GetLevel()); + return false; + } + } + return false; +} + +void Mob::DoRiposte(Mob* defender) { + mlog(COMBAT__ATTACKS, "Preforming a riposte"); + + if (!defender) + return; + + defender->Attack(this, SLOT_PRIMARY, true); + if (HasDied()) return; + + int16 DoubleRipChance = defender->aabonuses.GiveDoubleRiposte[0] + + defender->spellbonuses.GiveDoubleRiposte[0] + + defender->itembonuses.GiveDoubleRiposte[0]; + + //Live AA - Double Riposte + if(DoubleRipChance && (DoubleRipChance >= MakeRandomInt(0, 100))) { + mlog(COMBAT__ATTACKS, "Preforming a double riposed (%d percent chance)", DoubleRipChance); + defender->Attack(this, SLOT_PRIMARY, true); + if (HasDied()) return; + } + + //Double Riposte effect, allows for a chance to do RIPOSTE with a skill specfic special attack (ie Return Kick). + //Coded narrowly: Limit to one per client. Limit AA only. [1 = Skill Attack Chance, 2 = Skill] + DoubleRipChance = defender->aabonuses.GiveDoubleRiposte[1]; + + if(DoubleRipChance && (DoubleRipChance >= MakeRandomInt(0, 100))) { + mlog(COMBAT__ATTACKS, "Preforming a return SPECIAL ATTACK (%d percent chance)", DoubleRipChance); + + if (defender->GetClass() == MONK) + defender->MonkSpecialAttack(this, defender->aabonuses.GiveDoubleRiposte[2]); + else if (defender->IsClient()) + defender->CastToClient()->DoClassAttacks(this,defender->aabonuses.GiveDoubleRiposte[2], true); + } +} + +void Mob::ApplyMeleeDamageBonus(uint16 skill, int32 &damage){ + + if(!RuleB(Combat, UseIntervalAC)){ + if(IsNPC()){ //across the board NPC damage bonuses. + //only account for STR here, assume their base STR was factored into their DB damages + int dmgbonusmod = 0; + dmgbonusmod += (100*(itembonuses.STR + spellbonuses.STR))/3; + dmgbonusmod += (100*(spellbonuses.ATK + itembonuses.ATK))/5; + mlog(COMBAT__DAMAGE, "Damage bonus: %d percent from ATK and STR bonuses.", (dmgbonusmod/100)); + damage += (damage*dmgbonusmod/10000); + } + } + + damage += damage * GetMeleeDamageMod_SE(skill) / 100; +} + +bool Mob::HasDied() { + bool Result = false; + int16 hp_below = 0; + + hp_below = (GetDelayDeath() * -1); + + if((GetHP()) <= (hp_below)) + Result = true; + + return Result; +} + +uint16 Mob::GetDamageTable(SkillUseTypes skillinuse) +{ + if(GetLevel() <= 51) + { + uint16 ret_table = 0; + int str_over_75 = 0; + if(GetSTR() > 75) + str_over_75 = GetSTR() - 75; + if(str_over_75 > 255) + ret_table = (GetSkill(skillinuse)+255)/2; + else + ret_table = (GetSkill(skillinuse)+str_over_75)/2; + + if(ret_table < 100) + return 100; + + return ret_table; + } + else if(GetLevel() >= 90) + { + if(GetClass() == MONK) + return 379; + else + return 345; + } + else + { + uint16 dmg_table[] = { + 275, 275, 275, 275, 275, + 280, 280, 280, 280, 285, + 285, 285, 290, 290, 295, + 295, 300, 300, 300, 305, + 305, 305, 310, 310, 315, + 315, 320, 320, 320, 325, + 325, 325, 330, 330, 335, + 335, 340, 340, 340, + }; + if(GetClass() == MONK) + return (dmg_table[GetLevel()-51]*(100+RuleI(Combat,MonkDamageTableBonus))/100); + else + return dmg_table[GetLevel()-51]; + } +} + +void Mob::TrySkillProc(Mob *on, uint16 skill, float chance) +{ + + if (!on) { + SetTarget(nullptr); + LogFile->write(EQEMuLog::Error, "A null Mob object was passed to Mob::TrySkillProc for evaluation!"); + return; + } + + for (int i = 0; i < MAX_PROCS; i++) { + if (SkillProcs[i].spellID != SPELL_UNKNOWN){ + if (PassLimitToSkill(SkillProcs[i].base_spellID,skill)){ + int ProcChance = chance * (float)SkillProcs[i].chance; + if ((MakeRandomInt(0, 100) < ProcChance)) { + ExecWeaponProc(nullptr, SkillProcs[i].spellID, on); + CheckNumHitsRemaining(11,0, SkillProcs[i].base_spellID); + } + } + } + } +} + +bool Mob::TryRootFadeByDamage(int buffslot, Mob* attacker) +{ + /*Dev Quote 2010: http://forums.station.sony.com/eq/posts/list.m?topic_id=161443 + The Viscid Roots AA does the following: Reduces the chance for root to break by X percent. + There is no distinction of any kind between the caster inflicted damage, or anyone + else's damage. There is also no distinction between Direct and DOT damage in the root code. + There is however, a provision that if the damage inflicted is greater than 500 per hit, the + chance to break root is increased. My guess is when this code was put in place, the devs at + the time couldn't imagine DOT damage getting that high. + */ + + /* General Mechanics + - Check buffslot to make sure damage from a root does not cancel the root + - If multiple roots on target, always and only checks first root slot and if broken only removes that slots root. + - Only roots on determental spells can be broken by damage. + - Root break chance values obtained from live parses. + */ + + if (!attacker || !spellbonuses.Root[0] || spellbonuses.Root[1] < 0) + return false; + + if (IsDetrimentalSpell(spellbonuses.Root[1]) && spellbonuses.Root[1] != buffslot){ + + int BreakChance = RuleI(Spells, RootBreakFromSpells); + BreakChance -= BreakChance*buffs[spellbonuses.Root[1]].RootBreakChance/100; + int level_diff = attacker->GetLevel() - GetLevel(); + + attacker->Shout("%i ATTACKER LV %i",attacker->GetLevel(), level_diff); + Shout("%i DEF LEVEL %i", GetLevel(), level_diff); + //Use baseline if level difference <= 1 (ie. If target is (1) level less than you, or equal or greater level) + + if (level_diff == 2) + BreakChance = (BreakChance * 80) /100; //Decrease by 20%; + + else if (level_diff >= 3 && level_diff <= 20) + BreakChance = (BreakChance * 60) /100; //Decrease by 40%; + + else if (level_diff > 21) + BreakChance = (BreakChance * 20) /100; //Decrease by 80%; + + Shout("Root Break Chance %i Lv diff %i base %i ", BreakChance, level_diff, RuleI(Spells, RootBreakFromSpells)); + + if (BreakChance < 1) + BreakChance = 1; + + if (MakeRandomInt(0, 99) < BreakChance) { + + if (!TryFadeEffect(spellbonuses.Root[1])) { + BuffFadeBySlot(spellbonuses.Root[1]); + mlog(COMBAT__HITS, "Spell broke root! BreakChance percent chance"); + return true; + } + } + } + + mlog(COMBAT__HITS, "Spell did not break root. BreakChance percent chance"); + return false; +} + +int32 Mob::RuneAbsorb(int32 damage, uint16 type) +{ + uint32 buff_max = GetMaxTotalSlots(); + if (type == SE_Rune){ + for(uint32 slot = 0; slot < buff_max; slot++) { + if(slot == spellbonuses.MeleeRune[1] && spellbonuses.MeleeRune[0] && buffs[slot].melee_rune && IsValidSpell(buffs[slot].spellid)){ + uint32 melee_rune_left = buffs[slot].melee_rune; + + if(melee_rune_left > damage) + { + melee_rune_left -= damage; + buffs[slot].melee_rune = melee_rune_left; + return -6; + } + + else + { + if(melee_rune_left > 0) + damage -= melee_rune_left; + + if(!TryFadeEffect(slot)) + BuffFadeBySlot(slot); + } + } + } + } + + + else{ + for(uint32 slot = 0; slot < buff_max; slot++) { + if(slot == spellbonuses.AbsorbMagicAtt[1] && spellbonuses.AbsorbMagicAtt[0] && buffs[slot].magic_rune && IsValidSpell(buffs[slot].spellid)){ + uint32 magic_rune_left = buffs[slot].magic_rune; + if(magic_rune_left > damage) + { + magic_rune_left -= damage; + buffs[slot].magic_rune = magic_rune_left; + return 0; + } + + else + { + if(magic_rune_left > 0) + damage -= magic_rune_left; + + if(!TryFadeEffect(slot)) + BuffFadeBySlot(slot); + } + } + } + } + + return damage; +} + diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index 413b8d6c6..f57e92247 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -1543,14 +1543,10 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne { int i, effect_value, base2, max, effectid; bool AdditiveWornBonus = false; - Mob *caster = nullptr; if(!IsAISpellEffect && !IsValidSpell(spell_id)) return; - if(casterId > 0) - caster = entity_list.GetMob(casterId); - for (i = 0; i < EFFECT_COUNT; i++) { //Buffs/Item effects @@ -1577,7 +1573,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne AdditiveWornBonus = true; effectid = spells[spell_id].effectid[i]; - effect_value = CalcSpellEffectValue(spell_id, i, casterlevel, instrument_mod, caster, ticsremaining); + effect_value = CalcSpellEffectValue(spell_id, i, casterlevel, instrument_mod, nullptr, ticsremaining, casterId); base2 = spells[spell_id].base2[i]; max = spells[spell_id].max[i]; } diff --git a/zone/bonusesxx.cpp b/zone/bonusesxx.cpp new file mode 100644 index 000000000..8e23eb5fb --- /dev/null +++ b/zone/bonusesxx.cpp @@ -0,0 +1,4337 @@ +/* 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/debug.h" +#include "../common/spdat.h" +#include "masterentity.h" +#include "../common/packet_dump.h" +#include "../common/moremath.h" +#include "../common/Item.h" +#include "worldserver.h" +#include "../common/skills.h" +#include "../common/bodytypes.h" +#include "../common/classes.h" +#include "../common/rulesys.h" +#include "QuestParserCollection.h" +#include +#include +#include +#ifndef WIN32 +#include +#include "../common/unix.h" +#endif + +#include "StringIDs.h" + +void Mob::CalcBonuses() +{ + CalcSpellBonuses(&spellbonuses); + CalcMaxHP(); + CalcMaxMana(); + SetAttackTimer(); + + rooted = FindType(SE_Root); +} + +void NPC::CalcBonuses() +{ + Mob::CalcBonuses(); + memset(&aabonuses, 0, sizeof(StatBonuses)); + + if(RuleB(NPC, UseItemBonusesForNonPets)){ + memset(&itembonuses, 0, sizeof(StatBonuses)); + CalcItemBonuses(&itembonuses); + } + else{ + if(GetOwner()){ + memset(&itembonuses, 0, sizeof(StatBonuses)); + CalcItemBonuses(&itembonuses); + } + } + + // This has to happen last, so we actually take the item bonuses into account. + Mob::CalcBonuses(); +} + +void Client::CalcBonuses() +{ + memset(&itembonuses, 0, sizeof(StatBonuses)); + CalcItemBonuses(&itembonuses); + CalcEdibleBonuses(&itembonuses); + + CalcSpellBonuses(&spellbonuses); + + _log(AA__BONUSES, "Calculating AA Bonuses for %s.", this->GetCleanName()); + CalcAABonuses(&aabonuses); //we're not quite ready for this + _log(AA__BONUSES, "Finished calculating AA Bonuses for %s.", this->GetCleanName()); + + RecalcWeight(); + + CalcAC(); + CalcATK(); + CalcHaste(); + + CalcSTR(); + CalcSTA(); + CalcDEX(); + CalcAGI(); + CalcINT(); + CalcWIS(); + CalcCHA(); + + CalcMR(); + CalcFR(); + CalcDR(); + CalcPR(); + CalcCR(); + CalcCorrup(); + + CalcMaxHP(); + CalcMaxMana(); + CalcMaxEndurance(); + + rooted = FindType(SE_Root); + + XPRate = 100 + spellbonuses.XPRateMod; +} + +int Client::CalcRecommendedLevelBonus(uint8 level, uint8 reclevel, int basestat) +{ + if( (reclevel > 0) && (level < reclevel) ) + { + int32 statmod = (level * 10000 / reclevel) * basestat; + + if( statmod < 0 ) + { + statmod -= 5000; + return (statmod/10000); + } + else + { + statmod += 5000; + return (statmod/10000); + } + } + + return 0; +} + +void Client::CalcItemBonuses(StatBonuses* newbon) { + //memset assumed to be done by caller. + + // Clear item faction mods + ClearItemFactionBonuses(); + ShieldEquiped(false); + + unsigned int i; + //should not include 21 (SLOT_AMMO) + for (i=0; i<21; i++) { + const ItemInst* inst = m_inv[i]; + if(inst == 0) + continue; + AddItemBonuses(inst, newbon); + + //Check if item is secondary slot is a 'shield'. Required for multiple spelll effects. + if (i == 14 && (m_inv.GetItem(14)->GetItem()->ItemType == ItemTypeShield)) + ShieldEquiped(true); + } + + //Power Source Slot + if (GetClientVersion() >= EQClientSoF) + { + const ItemInst* inst = m_inv[9999]; + if(inst) + AddItemBonuses(inst, newbon); + } + + //tribute items + for (i = 0; i < MAX_PLAYER_TRIBUTES; i++) { + const ItemInst* inst = m_inv[TRIBUTE_SLOT_START + i]; + if(inst == 0) + continue; + AddItemBonuses(inst, newbon, false, true); + } + // Caps + if(newbon->HPRegen > CalcHPRegenCap()) + newbon->HPRegen = CalcHPRegenCap(); + + if(newbon->ManaRegen > CalcManaRegenCap()) + newbon->ManaRegen = CalcManaRegenCap(); + + if(newbon->EnduranceRegen > CalcEnduranceRegenCap()) + newbon->EnduranceRegen = CalcEnduranceRegenCap(); + + SetAttackTimer(); +} + +void Client::AddItemBonuses(const ItemInst *inst, StatBonuses* newbon, bool isAug, bool isTribute) { + if(!inst || !inst->IsType(ItemClassCommon)) + { + return; + } + + if(inst->GetAugmentType()==0 && isAug == true) + { + return; + } + + const Item_Struct *item = inst->GetItem(); + + if(!isTribute && !inst->IsEquipable(GetBaseRace(),GetClass())) + { + if(item->ItemType != ItemTypeFood && item->ItemType != ItemTypeDrink) + return; + } + + if(GetLevel() < item->ReqLevel) + { + return; + } + + if(GetLevel() >= item->RecLevel) + { + newbon->AC += item->AC; + newbon->HP += item->HP; + newbon->Mana += item->Mana; + newbon->Endurance += item->Endur; + newbon->STR += (item->AStr + item->HeroicStr); + newbon->STA += (item->ASta + item->HeroicSta); + newbon->DEX += (item->ADex + item->HeroicDex); + newbon->AGI += (item->AAgi + item->HeroicAgi); + newbon->INT += (item->AInt + item->HeroicInt); + newbon->WIS += (item->AWis + item->HeroicWis); + newbon->CHA += (item->ACha + item->HeroicCha); + + newbon->MR += (item->MR + item->HeroicMR); + newbon->FR += (item->FR + item->HeroicFR); + newbon->CR += (item->CR + item->HeroicCR); + newbon->PR += (item->PR + item->HeroicPR); + newbon->DR += (item->DR + item->HeroicDR); + newbon->Corrup += (item->SVCorruption + item->HeroicSVCorrup); + + newbon->STRCapMod += item->HeroicStr; + newbon->STACapMod += item->HeroicSta; + newbon->DEXCapMod += item->HeroicDex; + newbon->AGICapMod += item->HeroicAgi; + newbon->INTCapMod += item->HeroicInt; + newbon->WISCapMod += item->HeroicWis; + newbon->CHACapMod += item->HeroicCha; + newbon->MRCapMod += item->HeroicMR; + newbon->CRCapMod += item->HeroicFR; + newbon->FRCapMod += item->HeroicCR; + newbon->PRCapMod += item->HeroicPR; + newbon->DRCapMod += item->HeroicDR; + newbon->CorrupCapMod += item->HeroicSVCorrup; + + newbon->HeroicSTR += item->HeroicStr; + newbon->HeroicSTA += item->HeroicSta; + newbon->HeroicDEX += item->HeroicDex; + newbon->HeroicAGI += item->HeroicAgi; + newbon->HeroicINT += item->HeroicInt; + newbon->HeroicWIS += item->HeroicWis; + newbon->HeroicCHA += item->HeroicCha; + newbon->HeroicMR += item->HeroicMR; + newbon->HeroicFR += item->HeroicFR; + newbon->HeroicCR += item->HeroicCR; + newbon->HeroicPR += item->HeroicPR; + newbon->HeroicDR += item->HeroicDR; + newbon->HeroicCorrup += item->HeroicSVCorrup; + + } + else + { + int lvl = GetLevel(); + int reclvl = item->RecLevel; + + newbon->AC += CalcRecommendedLevelBonus( lvl, reclvl, item->AC ); + newbon->HP += CalcRecommendedLevelBonus( lvl, reclvl, item->HP ); + newbon->Mana += CalcRecommendedLevelBonus( lvl, reclvl, item->Mana ); + newbon->Endurance += CalcRecommendedLevelBonus( lvl, reclvl, item->Endur ); + newbon->STR += CalcRecommendedLevelBonus( lvl, reclvl, (item->AStr + item->HeroicStr) ); + newbon->STA += CalcRecommendedLevelBonus( lvl, reclvl, (item->ASta + item->HeroicSta) ); + newbon->DEX += CalcRecommendedLevelBonus( lvl, reclvl, (item->ADex + item->HeroicDex) ); + newbon->AGI += CalcRecommendedLevelBonus( lvl, reclvl, (item->AAgi + item->HeroicAgi) ); + newbon->INT += CalcRecommendedLevelBonus( lvl, reclvl, (item->AInt + item->HeroicInt) ); + newbon->WIS += CalcRecommendedLevelBonus( lvl, reclvl, (item->AWis + item->HeroicWis) ); + newbon->CHA += CalcRecommendedLevelBonus( lvl, reclvl, (item->ACha + item->HeroicCha) ); + + newbon->MR += CalcRecommendedLevelBonus( lvl, reclvl, (item->MR + item->HeroicMR) ); + newbon->FR += CalcRecommendedLevelBonus( lvl, reclvl, (item->FR + item->HeroicFR) ); + newbon->CR += CalcRecommendedLevelBonus( lvl, reclvl, (item->CR + item->HeroicCR) ); + newbon->PR += CalcRecommendedLevelBonus( lvl, reclvl, (item->PR + item->HeroicPR) ); + newbon->DR += CalcRecommendedLevelBonus( lvl, reclvl, (item->DR + item->HeroicDR) ); + newbon->Corrup += CalcRecommendedLevelBonus( lvl, reclvl, (item->SVCorruption + item->HeroicSVCorrup) ); + + newbon->STRCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicStr ); + newbon->STACapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicSta ); + newbon->DEXCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicDex ); + newbon->AGICapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicAgi ); + newbon->INTCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicInt ); + newbon->WISCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicWis ); + newbon->CHACapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicCha ); + newbon->MRCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicMR ); + newbon->CRCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicFR ); + newbon->FRCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicCR ); + newbon->PRCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicPR ); + newbon->DRCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicDR ); + newbon->CorrupCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicSVCorrup ); + + newbon->HeroicSTR += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicStr ); + newbon->HeroicSTA += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicSta ); + newbon->HeroicDEX += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicDex ); + newbon->HeroicAGI += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicAgi ); + newbon->HeroicINT += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicInt ); + newbon->HeroicWIS += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicWis ); + newbon->HeroicCHA += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicCha ); + newbon->HeroicMR += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicMR ); + newbon->HeroicFR += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicFR ); + newbon->HeroicCR += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicCR ); + newbon->HeroicPR += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicPR ); + newbon->HeroicDR += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicDR ); + newbon->HeroicCorrup += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicSVCorrup ); + } + + //FatherNitwit: New style haste, shields, and regens + if(newbon->haste < (int16)item->Haste) { + newbon->haste = item->Haste; + } + if(item->Regen > 0) + newbon->HPRegen += item->Regen; + + if(item->ManaRegen > 0) + newbon->ManaRegen += item->ManaRegen; + + if(item->EnduranceRegen > 0) + newbon->EnduranceRegen += item->EnduranceRegen; + + if(item->Attack > 0) { + + int cap = RuleI(Character, ItemATKCap); + cap += itembonuses.ItemATKCap + spellbonuses.ItemATKCap + aabonuses.ItemATKCap; + + if((newbon->ATK + item->Attack) > cap) + newbon->ATK = RuleI(Character, ItemATKCap); + else + newbon->ATK += item->Attack; + } + if(item->DamageShield > 0) { + if((newbon->DamageShield + item->DamageShield) > RuleI(Character, ItemDamageShieldCap)) + newbon->DamageShield = RuleI(Character, ItemDamageShieldCap); + else + newbon->DamageShield += item->DamageShield; + } + if(item->SpellShield > 0) { + if((newbon->SpellShield + item->SpellShield) > RuleI(Character, ItemSpellShieldingCap)) + newbon->SpellShield = RuleI(Character, ItemSpellShieldingCap); + else + newbon->SpellShield += item->SpellShield; + } + if(item->Shielding > 0) { + if((newbon->MeleeMitigation + item->Shielding) > RuleI(Character, ItemShieldingCap)) + newbon->MeleeMitigation = RuleI(Character, ItemShieldingCap); + else + newbon->MeleeMitigation += item->Shielding; + } + if(item->StunResist > 0) { + if((newbon->StunResist + item->StunResist) > RuleI(Character, ItemStunResistCap)) + newbon->StunResist = RuleI(Character, ItemStunResistCap); + else + newbon->StunResist += item->StunResist; + } + if(item->StrikeThrough > 0) { + if((newbon->StrikeThrough + item->StrikeThrough) > RuleI(Character, ItemStrikethroughCap)) + newbon->StrikeThrough = RuleI(Character, ItemStrikethroughCap); + else + newbon->StrikeThrough += item->StrikeThrough; + } + if(item->Avoidance > 0) { + if((newbon->AvoidMeleeChance + item->Avoidance) > RuleI(Character, ItemAvoidanceCap)) + newbon->AvoidMeleeChance = RuleI(Character, ItemAvoidanceCap); + else + newbon->AvoidMeleeChance += item->Avoidance; + } + if(item->Accuracy > 0) { + if((newbon->HitChance + item->Accuracy) > RuleI(Character, ItemAccuracyCap)) + newbon->HitChance = RuleI(Character, ItemAccuracyCap); + else + newbon->HitChance += item->Accuracy; + } + if(item->CombatEffects > 0) { + if((newbon->ProcChance + item->CombatEffects) > RuleI(Character, ItemCombatEffectsCap)) + newbon->ProcChance = RuleI(Character, ItemCombatEffectsCap); + else + newbon->ProcChance += item->CombatEffects; + } + if(item->DotShielding > 0) { + if((newbon->DoTShielding + item->DotShielding) > RuleI(Character, ItemDoTShieldingCap)) + newbon->DoTShielding = RuleI(Character, ItemDoTShieldingCap); + else + newbon->DoTShielding += item->DotShielding; + } + + if(item->HealAmt > 0) { + if((newbon->HealAmt + item->HealAmt) > RuleI(Character, ItemHealAmtCap)) + newbon->HealAmt = RuleI(Character, ItemHealAmtCap); + else + newbon->HealAmt += item->HealAmt; + } + if(item->SpellDmg > 0) { + if((newbon->SpellDmg + item->SpellDmg) > RuleI(Character, ItemSpellDmgCap)) + newbon->SpellDmg = RuleI(Character, ItemSpellDmgCap); + else + newbon->SpellDmg += item->SpellDmg; + } + if(item->Clairvoyance > 0) { + if((newbon->Clairvoyance + item->Clairvoyance) > RuleI(Character, ItemClairvoyanceCap)) + newbon->Clairvoyance = RuleI(Character, ItemClairvoyanceCap); + else + newbon->Clairvoyance += item->Clairvoyance; + } + + if(item->DSMitigation > 0) { + if((newbon->DSMitigation + item->DSMitigation) > RuleI(Character, ItemDSMitigationCap)) + newbon->DSMitigation = RuleI(Character, ItemDSMitigationCap); + else + newbon->DSMitigation += item->DSMitigation; + } + if (item->Worn.Effect>0 && (item->Worn.Type == ET_WornEffect)) { // latent effects + ApplySpellsBonuses(item->Worn.Effect, item->Worn.Level, newbon, 0, true); + } + + if (item->Focus.Effect>0 && (item->Focus.Type == ET_Focus)) { // focus effects + ApplySpellsBonuses(item->Focus.Effect, item->Focus.Level, newbon, 0, true); + } + + switch(item->BardType) + { + case 51: /* All (e.g. Singing Short Sword) */ + { + if(item->BardValue > newbon->singingMod) + newbon->singingMod = item->BardValue; + if(item->BardValue > newbon->brassMod) + newbon->brassMod = item->BardValue; + if(item->BardValue > newbon->stringedMod) + newbon->stringedMod = item->BardValue; + if(item->BardValue > newbon->percussionMod) + newbon->percussionMod = item->BardValue; + if(item->BardValue > newbon->windMod) + newbon->windMod = item->BardValue; + break; + } + case 50: /* Singing */ + { + if(item->BardValue > newbon->singingMod) + newbon->singingMod = item->BardValue; + break; + } + case 23: /* Wind */ + { + if(item->BardValue > newbon->windMod) + newbon->windMod = item->BardValue; + break; + } + case 24: /* stringed */ + { + if(item->BardValue > newbon->stringedMod) + newbon->stringedMod = item->BardValue; + break; + } + case 25: /* brass */ + { + if(item->BardValue > newbon->brassMod) + newbon->brassMod = item->BardValue; + break; + } + case 26: /* Percussion */ + { + if(item->BardValue > newbon->percussionMod) + newbon->percussionMod = item->BardValue; + break; + } + } + + if (item->SkillModValue != 0 && item->SkillModType <= HIGHEST_SKILL){ + if ((item->SkillModValue > 0 && newbon->skillmod[item->SkillModType] < item->SkillModValue) || + (item->SkillModValue < 0 && newbon->skillmod[item->SkillModType] > item->SkillModValue)) + { + newbon->skillmod[item->SkillModType] = item->SkillModValue; + } + } + + // Add Item Faction Mods + if (item->FactionMod1) + { + if (item->FactionAmt1 > 0 && item->FactionAmt1 > GetItemFactionBonus(item->FactionMod1)) + { + AddItemFactionBonus(item->FactionMod1, item->FactionAmt1); + } + else if (item->FactionAmt1 < 0 && item->FactionAmt1 < GetItemFactionBonus(item->FactionMod1)) + { + AddItemFactionBonus(item->FactionMod1, item->FactionAmt1); + } + } + if (item->FactionMod2) + { + if (item->FactionAmt2 > 0 && item->FactionAmt2 > GetItemFactionBonus(item->FactionMod2)) + { + AddItemFactionBonus(item->FactionMod2, item->FactionAmt2); + } + else if (item->FactionAmt2 < 0 && item->FactionAmt2 < GetItemFactionBonus(item->FactionMod2)) + { + AddItemFactionBonus(item->FactionMod2, item->FactionAmt2); + } + } + if (item->FactionMod3) + { + if (item->FactionAmt3 > 0 && item->FactionAmt3 > GetItemFactionBonus(item->FactionMod3)) + { + AddItemFactionBonus(item->FactionMod3, item->FactionAmt3); + } + else if (item->FactionAmt3 < 0 && item->FactionAmt3 < GetItemFactionBonus(item->FactionMod3)) + { + AddItemFactionBonus(item->FactionMod3, item->FactionAmt3); + } + } + if (item->FactionMod4) + { + if (item->FactionAmt4 > 0 && item->FactionAmt4 > GetItemFactionBonus(item->FactionMod4)) + { + AddItemFactionBonus(item->FactionMod4, item->FactionAmt4); + } + else if (item->FactionAmt4 < 0 && item->FactionAmt4 < GetItemFactionBonus(item->FactionMod4)) + { + AddItemFactionBonus(item->FactionMod4, item->FactionAmt4); + } + } + + if (item->ExtraDmgSkill != 0 && item->ExtraDmgSkill <= HIGHEST_SKILL) { + if((newbon->SkillDamageAmount[item->ExtraDmgSkill] + item->ExtraDmgAmt) > RuleI(Character, ItemExtraDmgCap)) + newbon->SkillDamageAmount[item->ExtraDmgSkill] = RuleI(Character, ItemExtraDmgCap); + else + newbon->SkillDamageAmount[item->ExtraDmgSkill] += item->ExtraDmgAmt; + } + + if (!isAug) + { + int i; + for(i = 0; i < MAX_AUGMENT_SLOTS; i++) { + AddItemBonuses(inst->GetAugment(i),newbon,true); + } + } + +} + +void Client::CalcEdibleBonuses(StatBonuses* newbon) { +#if EQDEBUG >= 11 + std::cout<<"Client::CalcEdibleBonuses(StatBonuses* newbon)"<GetItem() && inst->IsType(ItemClassCommon)) { + const Item_Struct *item=inst->GetItem(); + if (item->ItemType == ItemTypeFood && !food) + food = true; + else if (item->ItemType == ItemTypeDrink && !drink) + drink = true; + else + continue; + AddItemBonuses(inst, newbon); + } + } + for (i = 251; i <= 330; i++) + { + if (food && drink) + break; + const ItemInst* inst = GetInv().GetItem(i); + if (inst && inst->GetItem() && inst->IsType(ItemClassCommon)) { + const Item_Struct *item=inst->GetItem(); + if (item->ItemType == ItemTypeFood && !food) + food = true; + else if (item->ItemType == ItemTypeDrink && !drink) + drink = true; + else + continue; + AddItemBonuses(inst, newbon); + } + } +} + +void Client::CalcAABonuses(StatBonuses* newbon) { + memset(newbon, 0, sizeof(StatBonuses)); //start fresh + + int i; + uint32 slots = 0; + uint32 aa_AA = 0; + uint32 aa_value = 0; + if(this->aa) { + for (i = 0; i < MAX_PP_AA_ARRAY; i++) { //iterate through all of the client's AAs + if (this->aa[i]) { // make sure aa exists or we'll crash zone + aa_AA = this->aa[i]->AA; //same as aaid from the aa_effects table + aa_value = this->aa[i]->value; //how many points in it + if (aa_AA > 0 || aa_value > 0) { //do we have the AA? if 1 of the 2 is set, we can assume we do + //slots = database.GetTotalAALevels(aa_AA); //find out how many effects from aa_effects table + slots = zone->GetTotalAALevels(aa_AA); //find out how many effects from aa_effects, which is loaded into memory + if (slots > 0) //and does it have any effects? may be able to put this above, not sure if it runs on each iteration + ApplyAABonuses(aa_AA, slots, newbon); //add the bonuses + } + } + } + } +} + + +//A lot of the normal spell functions (IsBlankSpellEffect, etc) are set for just spells (in common/spdat.h). +//For now, we'll just put them directly into the code and comment with the corresponding normal function +//Maybe we'll fix it later? :-D +void Client::ApplyAABonuses(uint32 aaid, uint32 slots, StatBonuses* newbon) +{ + if(slots == 0) //sanity check. why bother if no slots to fill? + return; + + //from AA_Ability struct + uint32 effect = 0; + int32 base1 = 0; + int32 base2 = 0; //only really used for SE_RaiseStatCap & SE_ReduceSkillTimer in aa_effects table + uint32 slot = 0; + + std::map >::const_iterator find_iter = aa_effects.find(aaid); + if(find_iter == aa_effects.end()) + { + return; + } + + for (std::map::const_iterator iter = aa_effects[aaid].begin(); iter != aa_effects[aaid].end(); ++iter) { + effect = iter->second.skill_id; + base1 = iter->second.base1; + base2 = iter->second.base2; + slot = iter->second.slot; + + //we default to 0 (SE_CurrentHP) for the effect, so if there aren't any base1/2 values, we'll just skip it + if (effect == 0 && base1 == 0 && base2 == 0) + continue; + + //IsBlankSpellEffect() + if (effect == SE_Blank || (effect == SE_CHA && base1 == 0) || effect == SE_StackingCommand_Block || effect == SE_StackingCommand_Overwrite) + continue; + + _log(AA__BONUSES, "Applying Effect %d from AA %u in slot %d (base1: %d, base2: %d) on %s", effect, aaid, slot, base1, base2, this->GetCleanName()); + + uint8 focus = IsFocusEffect(0, 0, true,effect); + if (focus) + { + newbon->FocusEffects[focus] = effect; + continue; + } + + switch (effect) + { + //Note: AA effects that use accuracy are skill limited, while spell effect is not. + case SE_Accuracy: + if ((base2 == -1) && (newbon->Accuracy[HIGHEST_SKILL+1] < base1)) + newbon->Accuracy[HIGHEST_SKILL+1] = base1; + else if (newbon->Accuracy[base2] < base1) + newbon->Accuracy[base2] += base1; + break; + case SE_CurrentHP: //regens + newbon->HPRegen += base1; + break; + case SE_CurrentEndurance: + newbon->EnduranceRegen += base1; + break; + case SE_MovementSpeed: + newbon->movementspeed += base1; //should we let these stack? + /*if (base1 > newbon->movementspeed) //or should we use a total value? + newbon->movementspeed = base1;*/ + break; + case SE_STR: + newbon->STR += base1; + break; + case SE_DEX: + newbon->DEX += base1; + break; + case SE_AGI: + newbon->AGI += base1; + break; + case SE_STA: + newbon->STA += base1; + break; + case SE_INT: + newbon->INT += base1; + break; + case SE_WIS: + newbon->WIS += base1; + break; + case SE_CHA: + newbon->CHA += base1; + break; + case SE_WaterBreathing: + //handled by client + break; + case SE_CurrentMana: + newbon->ManaRegen += base1; + break; + case SE_ItemManaRegenCapIncrease: + newbon->ItemManaRegenCap += base1; + break; + case SE_ResistFire: + newbon->FR += base1; + break; + case SE_ResistCold: + newbon->CR += base1; + break; + case SE_ResistPoison: + newbon->PR += base1; + break; + case SE_ResistDisease: + newbon->DR += base1; + break; + case SE_ResistMagic: + newbon->MR += base1; + break; + case SE_ResistCorruption: + newbon->Corrup += base1; + break; + case SE_IncreaseSpellHaste: + break; + case SE_IncreaseRange: + break; + case SE_MaxHPChange: + newbon->MaxHP += base1; + break; + case SE_Packrat: + newbon->Packrat += base1; + break; + case SE_TwoHandBash: + break; + case SE_SetBreathLevel: + break; + case SE_RaiseStatCap: + switch(base2) + { + //are these #define'd somewhere? + case 0: //str + newbon->STRCapMod += base1; + break; + case 1: //sta + newbon->STACapMod += base1; + break; + case 2: //agi + newbon->AGICapMod += base1; + break; + case 3: //dex + newbon->DEXCapMod += base1; + break; + case 4: //wis + newbon->WISCapMod += base1; + break; + case 5: //int + newbon->INTCapMod += base1; + break; + case 6: //cha + newbon->CHACapMod += base1; + break; + case 7: //mr + newbon->MRCapMod += base1; + break; + case 8: //cr + newbon->CRCapMod += base1; + break; + case 9: //fr + newbon->FRCapMod += base1; + break; + case 10: //pr + newbon->PRCapMod += base1; + break; + case 11: //dr + newbon->DRCapMod += base1; + break; + case 12: //corruption + newbon->CorrupCapMod += base1; + break; + } + break; + case SE_PetDiscipline2: + break; + case SE_SpellSlotIncrease: + break; + case SE_MysticalAttune: + newbon->BuffSlotIncrease += base1; + break; + case SE_TotalHP: + newbon->HP += base1; + break; + case SE_StunResist: + newbon->StunResist += base1; + break; + case SE_SpellCritChance: + newbon->CriticalSpellChance += base1; + break; + case SE_SpellCritDmgIncrease: + newbon->SpellCritDmgIncrease += base1; + break; + case SE_DotCritDmgIncrease: + newbon->DotCritDmgIncrease += base1; + break; + case SE_ResistSpellChance: + newbon->ResistSpellChance += base1; + break; + case SE_CriticalHealChance: + newbon->CriticalHealChance += base1; + break; + case SE_CriticalHealOverTime: + newbon->CriticalHealOverTime += base1; + break; + case SE_CriticalDoTChance: + newbon->CriticalDoTChance += base1; + break; + case SE_ReduceSkillTimer: + newbon->SkillReuseTime[base2] += base1; + break; + case SE_Fearless: + newbon->Fearless = true; + break; + case SE_PersistantCasting: + newbon->PersistantCasting += base1; + break; + case SE_DelayDeath: + newbon->DelayDeath += base1; + break; + case SE_FrontalStunResist: + newbon->FrontalStunResist += base1; + break; + case SE_ImprovedBindWound: + newbon->BindWound += base1; + break; + case SE_MaxBindWound: + newbon->MaxBindWound += base1; + break; + case SE_ExtraAttackChance: + newbon->ExtraAttackChance += base1; + break; + case SE_SeeInvis: + newbon->SeeInvis = base1; + break; + case SE_BaseMovementSpeed: + newbon->BaseMovementSpeed += base1; + break; + case SE_IncreaseRunSpeedCap: + newbon->IncreaseRunSpeedCap += base1; + break; + case SE_ConsumeProjectile: + newbon->ConsumeProjectile += base1; + break; + case SE_ForageAdditionalItems: + newbon->ForageAdditionalItems += base1; + break; + case SE_Salvage: + newbon->SalvageChance += base1; + break; + case SE_ArcheryDamageModifier: + newbon->ArcheryDamageModifier += base1; + break; + case SE_DoubleRangedAttack: + newbon->DoubleRangedAttack += base1; + break; + case SE_DamageShield: + newbon->DamageShield += base1; + break; + case SE_CharmBreakChance: + newbon->CharmBreakChance += base1; + break; + case SE_OffhandRiposteFail: + newbon->OffhandRiposteFail += base1; + break; + case SE_ItemAttackCapIncrease: + newbon->ItemATKCap += base1; + break; + case SE_GivePetGroupTarget: + newbon->GivePetGroupTarget = true; + break; + case SE_ItemHPRegenCapIncrease: + newbon->ItemHPRegenCap = +base1; + break; + case SE_Ambidexterity: + newbon->Ambidexterity += base1; + break; + case SE_PetMaxHP: + newbon->PetMaxHP += base1; + break; + case SE_AvoidMeleeChance: + newbon->AvoidMeleeChance += base1; + break; + case SE_CombatStability: + newbon->CombatStability += base1; + break; + case SE_AddSingingMod: + switch (base2) + { + case ItemTypeWindInstrument: + newbon->windMod += base1; + break; + case ItemTypeStringedInstrument: + newbon->stringedMod += base1; + break; + case ItemTypeBrassInstrument: + newbon->brassMod += base1; + break; + case ItemTypePercussionInstrument: + newbon->percussionMod += base1; + break; + case ItemTypeSinging: + newbon->singingMod += base1; + break; + } + break; + case SE_SongModCap: + newbon->songModCap += base1; + break; + case SE_PetCriticalHit: + newbon->PetCriticalHit += base1; + break; + case SE_PetAvoidance: + newbon->PetAvoidance += base1; + break; + case SE_ShieldBlock: + newbon->ShieldBlock += base1; + break; + case SE_ShieldEquipHateMod: + newbon->ShieldEquipHateMod += base1; + break; + case SE_ShieldEquipDmgMod: + newbon->ShieldEquipDmgMod[0] += base1; + newbon->ShieldEquipDmgMod[1] += base2; + break; + case SE_SecondaryDmgInc: + newbon->SecondaryDmgInc = true; + break; + case SE_ChangeAggro: + newbon->hatemod += base1; + break; + case SE_EndurancePool: + newbon->Endurance += base1; + break; + case SE_ChannelChanceItems: + newbon->ChannelChanceItems += base1; + break; + case SE_ChannelChanceSpells: + newbon->ChannelChanceSpells += base1; + break; + case SE_DoubleSpecialAttack: + newbon->DoubleSpecialAttack += base1; + break; + case SE_TripleBackstab: + newbon->TripleBackstab += base1; + break; + case SE_FrontalBackstabMinDmg: + newbon->FrontalBackstabMinDmg = true; + break; + case SE_FrontalBackstabChance: + newbon->FrontalBackstabChance += base1; + break; + case SE_BlockBehind: + newbon->BlockBehind += base1; + break; + + case SE_StrikeThrough: + case SE_StrikeThrough2: + newbon->StrikeThrough += base1; + break; + case SE_DoubleAttackChance: + newbon->DoubleAttackChance += base1; + break; + case SE_GiveDoubleAttack: + newbon->GiveDoubleAttack += base1; + break; + case SE_ProcChance: + newbon->ProcChanceSPA += base1; + break; + case SE_RiposteChance: + newbon->RiposteChance += base1; + break; + case SE_Flurry: + newbon->FlurryChance += base1; + break; + case SE_PetFlurry: + newbon->PetFlurry = base1; + break; + case SE_BardSongRange: + newbon->SongRange += base1; + break; + case SE_RootBreakChance: + newbon->RootBreakChance += base1; + break; + case SE_UnfailingDivinity: + newbon->UnfailingDivinity += base1; + break; + case SE_CrippBlowChance: + newbon->CrippBlowChance += base1; + break; + + case SE_ProcOnKillShot: + for(int i = 0; i < MAX_SPELL_TRIGGER*3; i+=3) + { + if(!newbon->SpellOnKill[i] || ((newbon->SpellOnKill[i] == base2) && (newbon->SpellOnKill[i+1] < base1))) + { + //base1 = chance, base2 = SpellID to be triggered, base3 = min npc level + newbon->SpellOnKill[i] = base2; + newbon->SpellOnKill[i+1] = base1; + + if (GetLevel() > 15) + newbon->SpellOnKill[i+2] = GetLevel() - 15; //AA specifiy "non-trivial" + else + newbon->SpellOnKill[i+2] = 0; + + break; + } + } + break; + + case SE_SpellOnDeath: + for(int i = 0; i < MAX_SPELL_TRIGGER*2; i+=2) + { + if(!newbon->SpellOnDeath[i]) + { + // base1 = SpellID to be triggered, base2 = chance to fire + newbon->SpellOnDeath[i] = base1; + newbon->SpellOnDeath[i+1] = base2; + break; + } + } + break; + + case SE_TriggerOnCast: + + for(int i = 0; i < MAX_SPELL_TRIGGER; i++) + { + if (newbon->SpellTriggers[i] == aaid) + break; + + if(!newbon->SpellTriggers[i]) + { + //Save the 'aaid' of each triggerable effect to an array + newbon->SpellTriggers[i] = aaid; + break; + } + } + break; + + case SE_CriticalHitChance: + { + if(base2 == -1) + newbon->CriticalHitChance[HIGHEST_SKILL+1] += base1; + else + newbon->CriticalHitChance[base2] += base1; + } + break; + + case SE_CriticalDamageMob: + { + // base1 = effect value, base2 = skill restrictions(-1 for all) + if(base2 == -1) + newbon->CritDmgMob[HIGHEST_SKILL+1] += base1; + else + newbon->CritDmgMob[base2] += base1; + break; + } + + case SE_CriticalSpellChance: + { + newbon->CriticalSpellChance += base1; + + if (base2 > newbon->SpellCritDmgIncNoStack) + newbon->SpellCritDmgIncNoStack = base2; + + break; + } + + case SE_ResistFearChance: + { + if(base1 == 100) // If we reach 100% in a single spell/item then we should be immune to negative fear resist effects until our immunity is over + newbon->Fearless = true; + + newbon->ResistFearChance += base1; // these should stack + break; + } + + case SE_SkillDamageAmount: + { + if(base2 == -1) + newbon->SkillDamageAmount[HIGHEST_SKILL+1] += base1; + else + newbon->SkillDamageAmount[base2] += base1; + break; + } + + case SE_SpecialAttackKBProc: + { + //You can only have one of these per client. [AA Dragon Punch] + newbon->SpecialAttackKBProc[0] = base1; //Chance base 100 = 25% proc rate + newbon->SpecialAttackKBProc[1] = base2; //Skill to KB Proc Off + break; + } + + case SE_DamageModifier: + { + if(base2 == -1) + newbon->DamageModifier[HIGHEST_SKILL+1] += base1; + else + newbon->DamageModifier[base2] += base1; + break; + } + + case SE_DamageModifier2: + { + if(base2 == -1) + newbon->DamageModifier2[HIGHEST_SKILL+1] += base1; + else + newbon->DamageModifier2[base2] += base1; + break; + } + + case SE_SlayUndead: + { + if(newbon->SlayUndead[1] < base1) + newbon->SlayUndead[0] = base1; // Rate + newbon->SlayUndead[1] = base2; // Damage Modifier + break; + } + + case SE_DoubleRiposte: + { + newbon->DoubleRiposte += base1; + } + + case SE_GiveDoubleRiposte: + { + //0=Regular Riposte 1=Skill Attack Riposte 2=Skill + if(base2 == 0){ + if(newbon->GiveDoubleRiposte[0] < base1) + newbon->GiveDoubleRiposte[0] = base1; + } + //Only for special attacks. + else if(base2 > 0 && (newbon->GiveDoubleRiposte[1] < base1)){ + newbon->GiveDoubleRiposte[1] = base1; + newbon->GiveDoubleRiposte[2] = base2; + } + + break; + } + + //Kayen: Not sure best way to implement this yet. + //Physically raises skill cap ie if 55/55 it will raise to 55/60 + case SE_RaiseSkillCap: + { + if(newbon->RaiseSkillCap[0] < base1){ + newbon->RaiseSkillCap[0] = base1; //value + newbon->RaiseSkillCap[1] = base2; //skill + } + break; + } + + case SE_MasteryofPast: + { + if(newbon->MasteryofPast < base1) + newbon->MasteryofPast = base1; + break; + } + + case SE_CastingLevel2: + case SE_CastingLevel: + { + newbon->effective_casting_level += base1; + break; + } + + case SE_DivineSave: + { + if(newbon->DivineSaveChance[0] < base1) + { + newbon->DivineSaveChance[0] = base1; + newbon->DivineSaveChance[1] = base2; + } + break; + } + + + case SE_SpellEffectResistChance: + { + for(int e = 0; e < MAX_RESISTABLE_EFFECTS*2; e+=2) + { + if(newbon->SEResist[e+1] && (newbon->SEResist[e] == base2) && (newbon->SEResist[e+1] < base1)){ + newbon->SEResist[e] = base2; //Spell Effect ID + newbon->SEResist[e+1] = base1; //Resist Chance + break; + } + else if (!newbon->SEResist[e+1]){ + newbon->SEResist[e] = base2; //Spell Effect ID + newbon->SEResist[e+1] = base1; //Resist Chance + break; + } + } + break; + } + + case SE_MitigateDamageShield: + { + if (base1 < 0) + base1 = base1*(-1); + + newbon->DSMitigationOffHand += base1; + break; + } + + case SE_FinishingBlow: + { + //base1 = chance, base2 = damage + if (newbon->FinishingBlow[1] < base2){ + newbon->FinishingBlow[0] = base1; + newbon->FinishingBlow[1] = base2; + } + break; + } + + case SE_FinishingBlowLvl: + { + //base1 = level, base2 = ??? (Set to 200 in AA data, possible proc rate mod?) + if (newbon->FinishingBlowLvl[0] < base1){ + newbon->FinishingBlowLvl[0] = base1; + newbon->FinishingBlowLvl[1] = base2; + } + break; + } + + case SE_StunBashChance: + newbon->StunBashChance += base1; + break; + + case SE_IncreaseChanceMemwipe: + newbon->IncreaseChanceMemwipe += base1; + break; + + case SE_CriticalMend: + newbon->CriticalMend += base1; + break; + + case SE_HealRate: + newbon->HealRate += base1; + break; + + case SE_MeleeLifetap: + { + + if((base1 < 0) && (newbon->MeleeLifetap > base1)) + newbon->MeleeLifetap = base1; + + else if(newbon->MeleeLifetap < base1) + newbon->MeleeLifetap = base1; + break; + } + + case SE_Vampirism: + newbon->Vampirism += base1; + break; + + case SE_FrenziedDevastation: + newbon->FrenziedDevastation += base2; + break; + + case SE_SpellProcChance: + newbon->SpellProcChance += base1; + break; + + case SE_Berserk: + newbon->BerserkSPA = true; + break; + + case SE_Metabolism: + newbon->Metabolism += base1; + break; + + case SE_ImprovedReclaimEnergy: + { + if((base1 < 0) && (newbon->ImprovedReclaimEnergy > base1)) + newbon->ImprovedReclaimEnergy = base1; + + else if(newbon->ImprovedReclaimEnergy < base1) + newbon->ImprovedReclaimEnergy = base1; + break; + } + + case SE_HeadShot: + { + if(newbon->HeadShot[1] < base2){ + newbon->HeadShot[0] = base1; + newbon->HeadShot[1] = base2; + } + break; + } + + case SE_HeadShotLevel: + { + if(newbon->HSLevel < base1) + newbon->HSLevel = base1; + break; + } + + case SE_Assassinate: + { + if(newbon->Assassinate[1] < base2){ + newbon->Assassinate[0] = base1; + newbon->Assassinate[1] = base2; + } + break; + } + + case SE_AssassinateLevel: + { + if(newbon->AssassinateLevel < base1) + newbon->AssassinateLevel = base1; + break; + } + + case SE_PetMeleeMitigation: + newbon->PetMeleeMitigation += base1; + break; + + } + } +} + +void Mob::CalcSpellBonuses(StatBonuses* newbon) +{ + int i; + + memset(newbon, 0, sizeof(StatBonuses)); + newbon->AggroRange = -1; + newbon->AssistRange = -1; + + uint32 buff_count = GetMaxTotalSlots(); + for(i = 0; i < buff_count; i++) { + if(buffs[i].spellid != SPELL_UNKNOWN){ + ApplySpellsBonuses(buffs[i].spellid, buffs[i].casterlevel, newbon, buffs[i].casterid, false, buffs[i].ticsremaining,i); + + if (buffs[i].numhits > 0) + Numhits(true); + } + } + + //Applies any perma NPC spell bonuses from npc_spells_effects table. + if (IsNPC()) + CastToNPC()->ApplyAISpellEffects(newbon); + + //Removes the spell bonuses that are effected by a 'negate' debuff. + if (spellbonuses.NegateEffects){ + for(i = 0; i < buff_count; i++) { + if( (buffs[i].spellid != SPELL_UNKNOWN) && (IsEffectInSpell(buffs[i].spellid, SE_NegateSpellEffect)) ) + NegateSpellsBonuses(buffs[i].spellid); + } + } + //this prolly suffer from roundoff error slightly... + newbon->AC = newbon->AC * 10 / 34; //ratio determined impirically from client. + if (GetClass() == BARD) newbon->ManaRegen = 0; // Bards do not get mana regen from spells. +} + +void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* newbon, uint16 casterId, bool item_bonus, uint32 ticsremaining, int buffslot, + bool IsAISpellEffect, uint16 effect_id, int32 se_base, int32 se_limit, int32 se_max) +{ + int i, effect_value, base2, max, effectid; + Mob *caster = nullptr; + + if(!IsAISpellEffect && !IsValidSpell(spell_id)) + return; + + if(casterId > 0) + caster = entity_list.GetMob(casterId); + + for (i = 0; i < EFFECT_COUNT; i++) + { + //Buffs/Item effects + if (!IsAISpellEffect) { + + if(IsBlankSpellEffect(spell_id, i)) + continue; + + uint8 focus = IsFocusEffect(spell_id, i); + if (focus) + { + newbon->FocusEffects[focus] = spells[spell_id].effectid[i]; + continue; + } + + + effectid = spells[spell_id].effectid[i]; + effect_value = CalcSpellEffectValue(spell_id, i, casterlevel, caster, ticsremaining); + base2 = spells[spell_id].base2[i]; + max = spells[spell_id].max[i]; + } + //Use AISpellEffects + else { + effectid = effect_id; + effect_value = se_base; + base2 = se_limit; + max = se_max; + i = EFFECT_COUNT; //End the loop + } + + switch (effectid) + { + case SE_CurrentHP: //regens + if(effect_value > 0) { + newbon->HPRegen += effect_value; + } + break; + + case SE_CurrentEndurance: + newbon->EnduranceRegen += effect_value; + break; + + case SE_ChangeFrenzyRad: + { + // redundant to have level check here + if(newbon->AggroRange == -1 || effect_value < newbon->AggroRange) + { + newbon->AggroRange = effect_value; + } + break; + } + + case SE_Harmony: + { + // neotokyo: Harmony effect as buff - kinda tricky + // harmony could stack with a lull spell, which has better aggro range + // take the one with less range in any case + if(newbon->AssistRange == -1 || effect_value < newbon->AssistRange) + { + newbon->AssistRange = effect_value; + } + break; + } + + case SE_AttackSpeed: + { + if ((effect_value - 100) > 0) { // Haste + if (newbon->haste < 0) break; // Slowed - Don't apply haste + if ((effect_value - 100) > newbon->haste) { + newbon->haste = effect_value - 100; + } + } + else if ((effect_value - 100) < 0) { // Slow + int real_slow_value = (100 - effect_value) * -1; + real_slow_value -= ((real_slow_value * GetSlowMitigation()/100)); + if (real_slow_value < newbon->haste) + newbon->haste = real_slow_value; + } + break; + } + + case SE_AttackSpeed2: + { + if ((effect_value - 100) > 0) { // Haste V2 - Stacks with V1 but does not Overcap + if (newbon->hastetype2 < 0) break; //Slowed - Don't apply haste2 + if ((effect_value - 100) > newbon->hastetype2) { + newbon->hastetype2 = effect_value - 100; + } + } + else if ((effect_value - 100) < 0) { // Slow + int real_slow_value = (100 - effect_value) * -1; + real_slow_value -= ((real_slow_value * GetSlowMitigation()/100)); + if (real_slow_value < newbon->hastetype2) + newbon->hastetype2 = real_slow_value; + } + break; + } + + case SE_AttackSpeed3: + { + if (effect_value < 0){ //Slow + effect_value -= ((effect_value * GetSlowMitigation()/100)); + if (effect_value < newbon->hastetype3) + newbon->hastetype3 = effect_value; + } + + else if (effect_value > 0) { // Haste V3 - Stacks and Overcaps + if (effect_value > newbon->hastetype3) { + newbon->hastetype3 = effect_value; + } + } + break; + } + + case SE_AttackSpeed4: + { + if (effect_value < 0) //A few spells use negative values(Descriptions all indicate it should be a slow) + effect_value = effect_value * -1; + + if (effect_value > 0 && effect_value > newbon->inhibitmelee) { + effect_value -= ((effect_value * GetSlowMitigation()/100)); + if (effect_value > newbon->inhibitmelee) + newbon->inhibitmelee = effect_value; + } + + break; + } + + case SE_TotalHP: + { + newbon->HP += effect_value; + break; + } + + case SE_ManaRegen_v2: + case SE_CurrentMana: + { + newbon->ManaRegen += effect_value; + break; + } + + case SE_ManaPool: + { + newbon->Mana += effect_value; + break; + } + + case SE_Stamina: + { + newbon->EnduranceReduction += effect_value; + break; + } + + case SE_ACv2: + case SE_ArmorClass: + { + newbon->AC += effect_value; + break; + } + + case SE_ATK: + { + newbon->ATK += effect_value; + break; + } + + case SE_STR: + { + newbon->STR += effect_value; + break; + } + + case SE_DEX: + { + newbon->DEX += effect_value; + break; + } + + case SE_AGI: + { + newbon->AGI += effect_value; + break; + } + + case SE_STA: + { + newbon->STA += effect_value; + break; + } + + case SE_INT: + { + newbon->INT += effect_value; + break; + } + + case SE_WIS: + { + newbon->WIS += effect_value; + break; + } + + case SE_CHA: + { + if (spells[spell_id].base[i] != 0) { + newbon->CHA += effect_value; + } + break; + } + + case SE_AllStats: + { + newbon->STR += effect_value; + newbon->DEX += effect_value; + newbon->AGI += effect_value; + newbon->STA += effect_value; + newbon->INT += effect_value; + newbon->WIS += effect_value; + newbon->CHA += effect_value; + break; + } + + case SE_ResistFire: + { + newbon->FR += effect_value; + break; + } + + case SE_ResistCold: + { + newbon->CR += effect_value; + break; + } + + case SE_ResistPoison: + { + newbon->PR += effect_value; + break; + } + + case SE_ResistDisease: + { + newbon->DR += effect_value; + break; + } + + case SE_ResistMagic: + { + newbon->MR += effect_value; + break; + } + + case SE_ResistAll: + { + newbon->MR += effect_value; + newbon->DR += effect_value; + newbon->PR += effect_value; + newbon->CR += effect_value; + newbon->FR += effect_value; + break; + } + + case SE_ResistCorruption: + { + newbon->Corrup += effect_value; + break; + } + + case SE_RaiseStatCap: + { + switch(spells[spell_id].base2[i]) + { + //are these #define'd somewhere? + case 0: //str + newbon->STRCapMod += effect_value; + break; + case 1: //sta + newbon->STACapMod += effect_value; + break; + case 2: //agi + newbon->AGICapMod += effect_value; + break; + case 3: //dex + newbon->DEXCapMod += effect_value; + break; + case 4: //wis + newbon->WISCapMod += effect_value; + break; + case 5: //int + newbon->INTCapMod += effect_value; + break; + case 6: //cha + newbon->CHACapMod += effect_value; + break; + case 7: //mr + newbon->MRCapMod += effect_value; + break; + case 8: //cr + newbon->CRCapMod += effect_value; + break; + case 9: //fr + newbon->FRCapMod += effect_value; + break; + case 10: //pr + newbon->PRCapMod += effect_value; + break; + case 11: //dr + newbon->DRCapMod += effect_value; + break; + case 12: // corruption + newbon->CorrupCapMod += effect_value; + break; + } + break; + } + + case SE_CastingLevel2: + case SE_CastingLevel: // Brilliance of Ro + { + newbon->effective_casting_level += effect_value; + break; + } + + case SE_MovementSpeed: + newbon->movementspeed += effect_value; + break; + + case SE_SpellDamageShield: + newbon->SpellDamageShield += effect_value; + break; + + case SE_DamageShield: + { + newbon->DamageShield += effect_value; + newbon->DamageShieldSpellID = spell_id; + //When using npc_spells_effects MAX value can be set to determine DS Type + if (IsAISpellEffect && max) + newbon->DamageShieldType = GetDamageShieldType(spell_id, max); + else + newbon->DamageShieldType = GetDamageShieldType(spell_id); + + break; + } + + case SE_ReverseDS: + { + newbon->ReverseDamageShield += effect_value; + newbon->ReverseDamageShieldSpellID = spell_id; + + if (IsAISpellEffect && max) + newbon->ReverseDamageShieldType = GetDamageShieldType(spell_id, max); + else + newbon->ReverseDamageShieldType = GetDamageShieldType(spell_id); + break; + } + + case SE_Reflect: + newbon->reflect_chance += effect_value; + break; + + case SE_Amplification: + newbon->Amplification += effect_value; + break; + + case SE_ChangeAggro: + newbon->hatemod += effect_value; + break; + + case SE_MeleeMitigation: + //for some reason... this value is negative for increased mitigation + newbon->MeleeMitigation -= effect_value; + break; + + case SE_CriticalHitChance: + { + if (RuleB(Spells, AdditiveBonusValues) && item_bonus) { + if(base2 == -1) + newbon->CriticalHitChance[HIGHEST_SKILL+1] += effect_value; + else + newbon->CriticalHitChance[base2] += effect_value; + } + + else if(effect_value < 0) { + + if(base2 == -1 && newbon->CriticalHitChance[HIGHEST_SKILL+1] > effect_value) + newbon->CriticalHitChance[HIGHEST_SKILL+1] = effect_value; + else if(base2 != -1 && newbon->CriticalHitChance[base2] > effect_value) + newbon->CriticalHitChance[base2] = effect_value; + } + + + else if(base2 == -1 && newbon->CriticalHitChance[HIGHEST_SKILL+1] < effect_value) + newbon->CriticalHitChance[HIGHEST_SKILL+1] = effect_value; + else if(base2 != -1 && newbon->CriticalHitChance[base2] < effect_value) + newbon->CriticalHitChance[base2] = effect_value; + + break; + } + + case SE_CrippBlowChance: + { + if (RuleB(Spells, AdditiveBonusValues) && item_bonus) + newbon->CrippBlowChance += effect_value; + + else if((effect_value < 0) && (newbon->CrippBlowChance > effect_value)) + newbon->CrippBlowChance = effect_value; + + else if(newbon->CrippBlowChance < effect_value) + newbon->CrippBlowChance = effect_value; + + break; + } + + case SE_AvoidMeleeChance: + { + //multiplier is to be compatible with item effects, watching for overflow too + effect_value = effect_value<3000? effect_value * 10 : 30000; + if (RuleB(Spells, AdditiveBonusValues) && item_bonus) + newbon->AvoidMeleeChance += effect_value; + + else if((effect_value < 0) && (newbon->AvoidMeleeChance > effect_value)) + newbon->AvoidMeleeChance = effect_value; + + else if(newbon->AvoidMeleeChance < effect_value) + newbon->AvoidMeleeChance = effect_value; + break; + } + + case SE_RiposteChance: + { + if (RuleB(Spells, AdditiveBonusValues) && item_bonus) + newbon->RiposteChance += effect_value; + + else if((effect_value < 0) && (newbon->RiposteChance > effect_value)) + newbon->RiposteChance = effect_value; + + else if(newbon->RiposteChance < effect_value) + newbon->RiposteChance = effect_value; + break; + } + + case SE_DodgeChance: + { + if (RuleB(Spells, AdditiveBonusValues) && item_bonus) + newbon->DodgeChance += effect_value; + + else if((effect_value < 0) && (newbon->DodgeChance > effect_value)) + newbon->DodgeChance = effect_value; + + if(newbon->DodgeChance < effect_value) + newbon->DodgeChance = effect_value; + break; + } + + case SE_ParryChance: + { + if (RuleB(Spells, AdditiveBonusValues) && item_bonus) + newbon->ParryChance += effect_value; + + else if((effect_value < 0) && (newbon->ParryChance > effect_value)) + newbon->ParryChance = effect_value; + + if(newbon->ParryChance < effect_value) + newbon->ParryChance = effect_value; + break; + } + + case SE_DualWieldChance: + { + if (RuleB(Spells, AdditiveBonusValues) && item_bonus) + newbon->DualWieldChance += effect_value; + + else if((effect_value < 0) && (newbon->DualWieldChance > effect_value)) + newbon->DualWieldChance = effect_value; + + if(newbon->DualWieldChance < effect_value) + newbon->DualWieldChance = effect_value; + break; + } + + case SE_DoubleAttackChance: + { + + if (RuleB(Spells, AdditiveBonusValues) && item_bonus) + newbon->DoubleAttackChance += effect_value; + + else if((effect_value < 0) && (newbon->DoubleAttackChance > effect_value)) + newbon->DoubleAttackChance = effect_value; + + if(newbon->DoubleAttackChance < effect_value) + newbon->DoubleAttackChance = effect_value; + break; + } + + case SE_TripleAttackChance: + { + + if (RuleB(Spells, AdditiveBonusValues) && item_bonus) + newbon->TripleAttackChance += effect_value; + + else if((effect_value < 0) && (newbon->TripleAttackChance > effect_value)) + newbon->TripleAttackChance = effect_value; + + if(newbon->TripleAttackChance < effect_value) + newbon->TripleAttackChance = effect_value; + break; + } + + case SE_MeleeLifetap: + { + if (RuleB(Spells, AdditiveBonusValues) && item_bonus) + newbon->MeleeLifetap += spells[spell_id].base[i]; + + else if((effect_value < 0) && (newbon->MeleeLifetap > effect_value)) + newbon->MeleeLifetap = effect_value; + + else if(newbon->MeleeLifetap < effect_value) + newbon->MeleeLifetap = effect_value; + break; + } + + case SE_Vampirism: + newbon->Vampirism += effect_value; + break; + + case SE_AllInstrumentMod: + { + if(effect_value > newbon->singingMod) + newbon->singingMod = effect_value; + if(effect_value > newbon->brassMod) + newbon->brassMod = effect_value; + if(effect_value > newbon->percussionMod) + newbon->percussionMod = effect_value; + if(effect_value > newbon->windMod) + newbon->windMod = effect_value; + if(effect_value > newbon->stringedMod) + newbon->stringedMod = effect_value; + break; + } + + case SE_ResistSpellChance: + newbon->ResistSpellChance += effect_value; + break; + + case SE_ResistFearChance: + { + if(effect_value == 100) // If we reach 100% in a single spell/item then we should be immune to negative fear resist effects until our immunity is over + newbon->Fearless = true; + + newbon->ResistFearChance += effect_value; // these should stack + break; + } + + case SE_Fearless: + newbon->Fearless = true; + break; + + case SE_HundredHands: + { + if (RuleB(Spells, AdditiveBonusValues) && item_bonus) + newbon->HundredHands += effect_value; + + if (effect_value > 0 && effect_value > newbon->HundredHands) + newbon->HundredHands = effect_value; //Increase Weapon Delay + else if (effect_value < 0 && effect_value < newbon->HundredHands) + newbon->HundredHands = effect_value; //Decrease Weapon Delay + break; + } + + case SE_MeleeSkillCheck: + { + if(newbon->MeleeSkillCheck < effect_value) { + newbon->MeleeSkillCheck = effect_value; + newbon->MeleeSkillCheckSkill = base2==-1?255:base2; + } + break; + } + + case SE_HitChance: + { + + if (RuleB(Spells, AdditiveBonusValues) && item_bonus){ + if(base2 == -1) + newbon->HitChanceEffect[HIGHEST_SKILL+1] += effect_value; + else + newbon->HitChanceEffect[base2] += effect_value; + } + + else if(base2 == -1){ + + if ((effect_value < 0) && (newbon->HitChanceEffect[HIGHEST_SKILL+1] > effect_value)) + newbon->HitChanceEffect[HIGHEST_SKILL+1] = effect_value; + + else if (!newbon->HitChanceEffect[HIGHEST_SKILL+1] || + ((newbon->HitChanceEffect[HIGHEST_SKILL+1] > 0) && (newbon->HitChanceEffect[HIGHEST_SKILL+1] < effect_value))) + newbon->HitChanceEffect[HIGHEST_SKILL+1] = effect_value; + } + + else { + + if ((effect_value < 0) && (newbon->HitChanceEffect[base2] > effect_value)) + newbon->HitChanceEffect[base2] = effect_value; + + else if (!newbon->HitChanceEffect[base2] || + ((newbon->HitChanceEffect[base2] > 0) && (newbon->HitChanceEffect[base2] < effect_value))) + newbon->HitChanceEffect[base2] = effect_value; + } + + break; + + } + + case SE_DamageModifier: + { + if(base2 == -1) + newbon->DamageModifier[HIGHEST_SKILL+1] += effect_value; + else + newbon->DamageModifier[base2] += effect_value; + break; + } + + case SE_DamageModifier2: + { + if(base2 == -1) + newbon->DamageModifier2[HIGHEST_SKILL+1] += effect_value; + else + newbon->DamageModifier2[base2] += effect_value; + break; + } + + case SE_MinDamageModifier: + { + if(base2 == -1) + newbon->MinDamageModifier[HIGHEST_SKILL+1] += effect_value; + else + newbon->MinDamageModifier[base2] += effect_value; + break; + } + + case SE_StunResist: + { + if(newbon->StunResist < effect_value) + newbon->StunResist = effect_value; + break; + } + + case SE_ProcChance: + { + if (RuleB(Spells, AdditiveBonusValues) && item_bonus) + newbon->ProcChanceSPA += effect_value; + + else if((effect_value < 0) && (newbon->ProcChanceSPA > effect_value)) + newbon->ProcChanceSPA = effect_value; + + if(newbon->ProcChanceSPA < effect_value) + newbon->ProcChanceSPA = effect_value; + + break; + } + + case SE_ExtraAttackChance: + newbon->ExtraAttackChance += effect_value; + break; + + case SE_PercentXPIncrease: + { + if(newbon->XPRateMod < effect_value) + newbon->XPRateMod = effect_value; + break; + } + + case SE_DeathSave: + { + if(newbon->DeathSave[0] < effect_value) + { + newbon->DeathSave[0] = effect_value; //1='Partial' 2='Full' + newbon->DeathSave[1] = buffslot; + //These are used in later expansion spell effects. + newbon->DeathSave[2] = base2;//Min level for HealAmt + newbon->DeathSave[3] = max;//HealAmt + } + break; + } + + case SE_DivineSave: + { + if (RuleB(Spells, AdditiveBonusValues) && item_bonus) { + newbon->DivineSaveChance[0] += effect_value; + newbon->DivineSaveChance[1] = 0; + } + + else if(newbon->DivineSaveChance[0] < effect_value) + { + newbon->DivineSaveChance[0] = effect_value; + newbon->DivineSaveChance[1] = base2; + //SetDeathSaveChance(true); + } + break; + } + + case SE_Flurry: + newbon->FlurryChance += effect_value; + break; + + case SE_Accuracy: + { + if ((effect_value < 0) && (newbon->Accuracy[HIGHEST_SKILL+1] > effect_value)) + newbon->Accuracy[HIGHEST_SKILL+1] = effect_value; + + else if (!newbon->Accuracy[HIGHEST_SKILL+1] || + ((newbon->Accuracy[HIGHEST_SKILL+1] > 0) && (newbon->Accuracy[HIGHEST_SKILL+1] < effect_value))) + newbon->Accuracy[HIGHEST_SKILL+1] = effect_value; + break; + } + + case SE_MaxHPChange: + newbon->MaxHPChange += effect_value; + break; + + case SE_EndurancePool: + newbon->Endurance += effect_value; + break; + + case SE_HealRate: + newbon->HealRate += effect_value; + break; + + case SE_SkillDamageTaken: + { + //When using npc_spells_effects if MAX value set, use stackable quest based modifier. + if (IsAISpellEffect && max){ + if(base2 == -1) + SkillDmgTaken_Mod[HIGHEST_SKILL+1] = effect_value; + else + SkillDmgTaken_Mod[base2] = effect_value; + } + else { + + if(base2 == -1) + newbon->SkillDmgTaken[HIGHEST_SKILL+1] += effect_value; + else + newbon->SkillDmgTaken[base2] += effect_value; + + } + break; + } + + case SE_TriggerOnCast: + { + for(int e = 0; e < MAX_SPELL_TRIGGER; e++) + { + if(!newbon->SpellTriggers[e]) + { + newbon->SpellTriggers[e] = spell_id; + break; + } + } + break; + } + + case SE_SpellCritChance: + newbon->CriticalSpellChance += effect_value; + break; + + case SE_CriticalSpellChance: + { + newbon->CriticalSpellChance += effect_value; + + if (base2 > newbon->SpellCritDmgIncNoStack) + newbon->SpellCritDmgIncNoStack = base2; + break; + } + + case SE_SpellCritDmgIncrease: + newbon->SpellCritDmgIncrease += effect_value; + break; + + case SE_DotCritDmgIncrease: + newbon->DotCritDmgIncrease += effect_value; + break; + + case SE_CriticalHealChance: + newbon->CriticalHealChance += effect_value; + break; + + case SE_CriticalHealOverTime: + newbon->CriticalHealOverTime += effect_value; + break; + + case SE_CriticalHealDecay: + newbon->CriticalHealDecay = true; + break; + + case SE_CriticalRegenDecay: + newbon->CriticalRegenDecay = true; + break; + + case SE_CriticalDotDecay: + newbon->CriticalDotDecay = true; + break; + + case SE_MitigateDamageShield: + { + if (effect_value < 0) + effect_value = effect_value*-1; + + newbon->DSMitigationOffHand += effect_value; + break; + } + + case SE_CriticalDoTChance: + newbon->CriticalDoTChance += effect_value; + break; + + case SE_ProcOnKillShot: + { + for(int e = 0; e < MAX_SPELL_TRIGGER*3; e+=3) + { + if(!newbon->SpellOnKill[e]) + { + // Base2 = Spell to fire | Base1 = % chance | Base3 = min level + newbon->SpellOnKill[e] = base2; + newbon->SpellOnKill[e+1] = effect_value; + newbon->SpellOnKill[e+2] = max; + break; + } + } + break; + } + + case SE_SpellOnDeath: + { + for(int e = 0; e < MAX_SPELL_TRIGGER; e+=2) + { + if(!newbon->SpellOnDeath[e]) + { + // Base2 = Spell to fire | Base1 = % chance + newbon->SpellOnDeath[e] = base2; + newbon->SpellOnDeath[e+1] = effect_value; + break; + } + } + break; + } + + case SE_CriticalDamageMob: + { + if(base2 == -1) + newbon->CritDmgMob[HIGHEST_SKILL+1] += effect_value; + else + newbon->CritDmgMob[base2] += effect_value; + break; + } + + case SE_ReduceSkillTimer: + { + if(newbon->SkillReuseTime[base2] < effect_value) + newbon->SkillReuseTime[base2] = effect_value; + break; + } + + case SE_SkillDamageAmount: + { + if(base2 == -1) + newbon->SkillDamageAmount[HIGHEST_SKILL+1] += effect_value; + else + newbon->SkillDamageAmount[base2] += effect_value; + break; + } + + case SE_GravityEffect: + newbon->GravityEffect = 1; + break; + + case SE_AntiGate: + newbon->AntiGate = true; + break; + + case SE_MagicWeapon: + newbon->MagicWeapon = true; + break; + + case SE_IncreaseBlockChance: + newbon->IncreaseBlockChance += effect_value; + break; + + case SE_PersistantCasting: + newbon->PersistantCasting += effect_value; + break; + + case SE_LimitHPPercent: + { + if(newbon->HPPercCap[0] != 0 && newbon->HPPercCap[0] > effect_value){ + newbon->HPPercCap[0] = effect_value; + newbon->HPPercCap[1] = base2; + } + else if(newbon->HPPercCap[0] == 0){ + newbon->HPPercCap[0] = effect_value; + newbon->HPPercCap[1] = base2; + } + break; + } + case SE_LimitManaPercent: + { + if(newbon->ManaPercCap[0] != 0 && newbon->ManaPercCap[0] > effect_value){ + newbon->ManaPercCap[0] = effect_value; + newbon->ManaPercCap[1] = base2; + } + else if(newbon->ManaPercCap[0] == 0) { + newbon->ManaPercCap[0] = effect_value; + newbon->ManaPercCap[1] = base2; + } + + break; + } + case SE_LimitEndPercent: + { + if(newbon->EndPercCap[0] != 0 && newbon->EndPercCap[0] > effect_value) { + newbon->EndPercCap[0] = effect_value; + newbon->EndPercCap[1] = base2; + } + + else if(newbon->EndPercCap[0] == 0){ + newbon->EndPercCap[0] = effect_value; + newbon->EndPercCap[1] = base2; + } + + break; + } + + case SE_BlockNextSpellFocus: + newbon->BlockNextSpell = true; + break; + + case SE_NegateSpellEffect: + newbon->NegateEffects = true; + break; + + case SE_ImmuneFleeing: + newbon->ImmuneToFlee = true; + break; + + case SE_DelayDeath: + newbon->DelayDeath += effect_value; + break; + + case SE_SpellProcChance: + newbon->SpellProcChance += effect_value; + break; + + case SE_CharmBreakChance: + newbon->CharmBreakChance += effect_value; + break; + + case SE_BardSongRange: + newbon->SongRange += effect_value; + break; + + case SE_HPToMana: + { + //Lower the ratio the more favorable + if((!newbon->HPToManaConvert) || (newbon->HPToManaConvert >= effect_value)) + newbon->HPToManaConvert = spells[spell_id].base[i]; + break; + } + + case SE_SkillDamageAmount2: + { + if(base2 == -1) + newbon->SkillDamageAmount2[HIGHEST_SKILL+1] += effect_value; + else + newbon->SkillDamageAmount2[base2] += effect_value; + break; + } + + case SE_NegateAttacks: + { + if (!newbon->NegateAttacks[0] || + ((newbon->NegateAttacks[0] && newbon->NegateAttacks[2]) && (newbon->NegateAttacks[2] < max))){ + newbon->NegateAttacks[0] = 1; + newbon->NegateAttacks[1] = buffslot; + newbon->NegateAttacks[2] = max; + } + break; + } + + case SE_MitigateMeleeDamage: + { + if (newbon->MitigateMeleeRune[0] < effect_value){ + newbon->MitigateMeleeRune[0] = effect_value; + newbon->MitigateMeleeRune[1] = buffslot; + newbon->MitigateMeleeRune[2] = base2; + newbon->MitigateMeleeRune[3] = max; + } + break; + } + + + case SE_MeleeThresholdGuard: + { + if (newbon->MeleeThresholdGuard[0] < effect_value){ + newbon->MeleeThresholdGuard[0] = effect_value; + newbon->MeleeThresholdGuard[1] = buffslot; + newbon->MeleeThresholdGuard[2] = base2; + } + break; + } + + case SE_SpellThresholdGuard: + { + if (newbon->SpellThresholdGuard[0] < effect_value){ + newbon->SpellThresholdGuard[0] = effect_value; + newbon->SpellThresholdGuard[1] = buffslot; + newbon->SpellThresholdGuard[2] = base2; + } + break; + } + + case SE_MitigateSpellDamage: + { + if (newbon->MitigateSpellRune[0] < effect_value){ + newbon->MitigateSpellRune[0] = effect_value; + newbon->MitigateSpellRune[1] = buffslot; + newbon->MitigateSpellRune[2] = base2; + newbon->MitigateSpellRune[3] = max; + } + break; + } + + case SE_MitigateDotDamage: + { + if (newbon->MitigateDotRune[0] < effect_value){ + newbon->MitigateDotRune[0] = effect_value; + newbon->MitigateDotRune[1] = buffslot; + newbon->MitigateDotRune[2] = base2; + newbon->MitigateDotRune[3] = max; + } + break; + } + + case SE_ManaAbsorbPercentDamage: + { + if (newbon->ManaAbsorbPercentDamage[0] < effect_value){ + newbon->ManaAbsorbPercentDamage[0] = effect_value; + newbon->ManaAbsorbPercentDamage[1] = buffslot; + } + break; + } + + case SE_TriggerMeleeThreshold: + { + if (newbon->TriggerMeleeThreshold[2] < base2){ + newbon->TriggerMeleeThreshold[0] = effect_value; + newbon->TriggerMeleeThreshold[1] = buffslot; + newbon->TriggerMeleeThreshold[2] = base2; + } + break; + } + + case SE_TriggerSpellThreshold: + { + if (newbon->TriggerSpellThreshold[2] < base2){ + newbon->TriggerSpellThreshold[0] = effect_value; + newbon->TriggerSpellThreshold[1] = buffslot; + newbon->TriggerSpellThreshold[2] = base2; + } + break; + } + + case SE_ShieldBlock: + newbon->ShieldBlock += effect_value; + break; + + case SE_ShieldEquipHateMod: + newbon->ShieldEquipHateMod += effect_value; + break; + + case SE_ShieldEquipDmgMod: + newbon->ShieldEquipDmgMod[0] += effect_value; + newbon->ShieldEquipDmgMod[1] += base2; + break; + + case SE_BlockBehind: + newbon->BlockBehind += effect_value; + break; + + case SE_Fear: + newbon->IsFeared = true; + break; + + //AA bonuses - implemented broadly into spell/item effects + case SE_FrontalStunResist: + newbon->FrontalStunResist += effect_value; + break; + + case SE_ImprovedBindWound: + newbon->BindWound += effect_value; + break; + + case SE_MaxBindWound: + newbon->MaxBindWound += effect_value; + break; + + case SE_BaseMovementSpeed: + newbon->BaseMovementSpeed += effect_value; + break; + + case SE_IncreaseRunSpeedCap: + newbon->IncreaseRunSpeedCap += effect_value; + break; + + case SE_DoubleSpecialAttack: + newbon->DoubleSpecialAttack += effect_value; + break; + + case SE_TripleBackstab: + newbon->TripleBackstab += effect_value; + break; + + case SE_FrontalBackstabMinDmg: + newbon->FrontalBackstabMinDmg = true; + break; + + case SE_FrontalBackstabChance: + newbon->FrontalBackstabChance += effect_value; + break; + + case SE_ConsumeProjectile: + newbon->ConsumeProjectile += effect_value; + break; + + case SE_ForageAdditionalItems: + newbon->ForageAdditionalItems += effect_value; + break; + + case SE_Salvage: + newbon->SalvageChance += effect_value; + break; + + case SE_ArcheryDamageModifier: + newbon->ArcheryDamageModifier += effect_value; + break; + + case SE_DoubleRangedAttack: + newbon->DoubleRangedAttack += effect_value; + break; + + case SE_SecondaryDmgInc: + newbon->SecondaryDmgInc = true; + break; + + case SE_StrikeThrough: + case SE_StrikeThrough2: + newbon->StrikeThrough += effect_value; + break; + + case SE_GiveDoubleAttack: + newbon->GiveDoubleAttack += effect_value; + break; + + case SE_PetCriticalHit: + newbon->PetCriticalHit += effect_value; + break; + + case SE_CombatStability: + newbon->CombatStability += effect_value; + break; + + case SE_AddSingingMod: + switch (base2) + { + case ItemTypeWindInstrument: + newbon->windMod += effect_value; + break; + case ItemTypeStringedInstrument: + newbon->stringedMod += effect_value; + break; + case ItemTypeBrassInstrument: + newbon->brassMod += effect_value; + break; + case ItemTypePercussionInstrument: + newbon->percussionMod += effect_value; + break; + case ItemTypeSinging: + newbon->singingMod += effect_value; + break; + } + break; + + case SE_SongModCap: + newbon->songModCap += effect_value; + break; + + case SE_PetAvoidance: + newbon->PetAvoidance += effect_value; + break; + + case SE_Ambidexterity: + newbon->Ambidexterity += effect_value; + break; + + case SE_PetMaxHP: + newbon->PetMaxHP += effect_value; + break; + + case SE_PetFlurry: + newbon->PetFlurry += effect_value; + break; + + case SE_GivePetGroupTarget: + newbon->GivePetGroupTarget = true; + break; + + case SE_RootBreakChance: + newbon->RootBreakChance += effect_value; + break; + + case SE_ChannelChanceItems: + newbon->ChannelChanceItems += effect_value; + break; + + case SE_ChannelChanceSpells: + newbon->ChannelChanceSpells += effect_value; + break; + + case SE_UnfailingDivinity: + newbon->UnfailingDivinity += effect_value; + break; + + + case SE_ItemHPRegenCapIncrease: + newbon->ItemHPRegenCap += effect_value; + break; + + case SE_OffhandRiposteFail: + newbon->OffhandRiposteFail += effect_value; + break; + + case SE_ItemAttackCapIncrease: + newbon->ItemATKCap += effect_value; + break; + + case SE_TwoHandBluntBlock: + newbon->TwoHandBluntBlock += effect_value; + break; + + case SE_StunBashChance: + newbon->StunBashChance += effect_value; + break; + + case SE_IncreaseChanceMemwipe: + newbon->IncreaseChanceMemwipe += effect_value; + break; + + case SE_CriticalMend: + newbon->CriticalMend += effect_value; + break; + + case SE_SpellEffectResistChance: + { + for(int e = 0; e < MAX_RESISTABLE_EFFECTS*2; e+=2) + { + if(newbon->SEResist[e+1] && (newbon->SEResist[e] == base2) && (newbon->SEResist[e+1] < effect_value)){ + newbon->SEResist[e] = base2; //Spell Effect ID + newbon->SEResist[e+1] = effect_value; //Resist Chance + break; + } + else if (!newbon->SEResist[e+1]){ + newbon->SEResist[e] = base2; //Spell Effect ID + newbon->SEResist[e+1] = effect_value; //Resist Chance + break; + } + } + break; + } + + case SE_MasteryofPast: + { + if(newbon->MasteryofPast < effect_value) + newbon->MasteryofPast = effect_value; + break; + } + + case SE_DoubleRiposte: + { + newbon->DoubleRiposte += effect_value; + } + + case SE_GiveDoubleRiposte: + { + //Only allow for regular double riposte chance. + if(newbon->GiveDoubleRiposte[base2] == 0){ + if(newbon->GiveDoubleRiposte[0] < effect_value) + newbon->GiveDoubleRiposte[0] = effect_value; + } + break; + } + + case SE_SlayUndead: + { + if(newbon->SlayUndead[1] < effect_value) + newbon->SlayUndead[0] = effect_value; // Rate + newbon->SlayUndead[1] = base2; // Damage Modifier + break; + } + + case SE_TriggerOnReqTarget: + case SE_TriggerOnReqCaster: + newbon->TriggerOnValueAmount = true; + break; + + case SE_DivineAura: + newbon->DivineAura = true; + break; + + case SE_ImprovedTaunt: + if (newbon->ImprovedTaunt[0] < effect_value) { + newbon->ImprovedTaunt[0] = effect_value; + newbon->ImprovedTaunt[1] = base2; + newbon->ImprovedTaunt[2] = buffslot; + } + break; + + + case SE_DistanceRemoval: + newbon->DistanceRemoval = true; + break; + + case SE_FrenziedDevastation: + newbon->FrenziedDevastation += base2; + break; + + case SE_Root: + if (newbon->Root[0] && (newbon->Root[1] > buffslot)){ + newbon->Root[0] = 1; + newbon->Root[1] = buffslot; + } + else if (!newbon->Root[0]){ + newbon->Root[0] = 1; + newbon->Root[1] = buffslot; + } + break; + + case SE_Rune: + + if (newbon->MeleeRune[0] && (newbon->MeleeRune[1] > buffslot)){ + + newbon->MeleeRune[0] = effect_value; + newbon->MeleeRune[1] = buffslot; + } + else if (!newbon->MeleeRune[0]){ + newbon->MeleeRune[0] = effect_value; + newbon->MeleeRune[1] = buffslot; + } + + break; + + case SE_AbsorbMagicAtt: + if (newbon->AbsorbMagicAtt[0] && (newbon->AbsorbMagicAtt[1] > buffslot)){ + newbon->AbsorbMagicAtt[0] = effect_value; + newbon->AbsorbMagicAtt[1] = buffslot; + } + else if (!newbon->AbsorbMagicAtt[0]){ + newbon->AbsorbMagicAtt[0] = effect_value; + newbon->AbsorbMagicAtt[1] = buffslot; + } + break; + + case SE_NegateIfCombat: + newbon->NegateIfCombat = true; + break; + + case SE_Screech: + newbon->Screech = effect_value; + break; + + case SE_AlterNPCLevel: + + if (IsNPC()){ + if (!newbon->AlterNPCLevel + || ((effect_value < 0) && (newbon->AlterNPCLevel > effect_value)) + || ((effect_value > 0) && (newbon->AlterNPCLevel < effect_value))) { + + int16 tmp_lv = GetOrigLevel() + effect_value; + if (tmp_lv < 1) + tmp_lv = 1; + else if (tmp_lv > 255) + tmp_lv = 255; + if ((GetLevel() != tmp_lv)){ + newbon->AlterNPCLevel = effect_value; + SetLevel(tmp_lv); + } + } + } + break; + + case SE_AStacker: + newbon->AStacker[0] = 1; + newbon->AStacker[1] = effect_value; + break; + + case SE_BStacker: + newbon->BStacker[0] = 1; + newbon->BStacker[1] = effect_value; + break; + + case SE_CStacker: + newbon->CStacker[0] = 1; + newbon->CStacker[1] = effect_value; + break; + + case SE_DStacker: + newbon->DStacker[0] = 1; + newbon->DStacker[1] = effect_value; + break; + + case SE_Berserk: + newbon->BerserkSPA = true; + break; + + + case SE_Metabolism: + newbon->Metabolism += effect_value; + break; + + case SE_ImprovedReclaimEnergy: + { + if((effect_value < 0) && (newbon->ImprovedReclaimEnergy > effect_value)) + newbon->ImprovedReclaimEnergy = effect_value; + + else if(newbon->ImprovedReclaimEnergy < effect_value) + newbon->ImprovedReclaimEnergy = effect_value; + break; + } + + case SE_HeadShot: + { + if(newbon->HeadShot[1] < base2){ + newbon->HeadShot[0] = effect_value; + newbon->HeadShot[1] = base2; + } + break; + } + + case SE_HeadShotLevel: + { + if(newbon->HSLevel < effect_value) + newbon->HSLevel = effect_value; + break; + } + + case SE_Assassinate: + { + if(newbon->Assassinate[1] < base2){ + newbon->Assassinate[0] = effect_value; + newbon->Assassinate[1] = base2; + } + break; + } + + case SE_AssassinateLevel: + { + if(newbon->AssassinateLevel < effect_value) + newbon->AssassinateLevel = effect_value; + break; + } + + case SE_FinishingBlow: + { + //base1 = chance, base2 = damage + if (newbon->FinishingBlow[1] < base2){ + newbon->FinishingBlow[0] = effect_value; + newbon->FinishingBlow[1] = base2; + } + break; + } + + case SE_FinishingBlowLvl: + { + //base1 = level, base2 = ??? (Set to 200 in AA data, possible proc rate mod?) + if (newbon->FinishingBlowLvl[0] < effect_value){ + newbon->FinishingBlowLvl[0] = effect_value; + newbon->FinishingBlowLvl[1] = base2; + } + break; + } + + case SE_PetMeleeMitigation: + newbon->PetMeleeMitigation += effect_value; + break; + + //Special custom cases for loading effects on to NPC from 'npc_spels_effects' table + if (IsAISpellEffect) { + + //Non-Focused Effect to modify incomming spell damage by resist type. + case SE_FcSpellVulnerability: + ModVulnerability(base2, effect_value); + break; + } + } + } +} + +void NPC::CalcItemBonuses(StatBonuses *newbon) +{ + if(newbon){ + + for(int i = 0; i < MAX_WORN_INVENTORY; i++){ + const Item_Struct *cur = database.GetItem(equipment[i]); + if(cur){ + //basic stats + newbon->AC += cur->AC; + newbon->HP += cur->HP; + newbon->Mana += cur->Mana; + newbon->Endurance += cur->Endur; + newbon->STR += (cur->AStr + cur->HeroicStr); + newbon->STA += (cur->ASta + cur->HeroicSta); + newbon->DEX += (cur->ADex + cur->HeroicDex); + newbon->AGI += (cur->AAgi + cur->HeroicAgi); + newbon->INT += (cur->AInt + cur->HeroicInt); + newbon->WIS += (cur->AWis + cur->HeroicWis); + newbon->CHA += (cur->ACha + cur->HeroicCha); + newbon->MR += (cur->MR + cur->HeroicMR); + newbon->FR += (cur->FR + cur->HeroicFR); + newbon->CR += (cur->CR + cur->HeroicCR); + newbon->PR += (cur->PR + cur->HeroicPR); + newbon->DR += (cur->DR + cur->HeroicDR); + newbon->Corrup += (cur->SVCorruption + cur->HeroicSVCorrup); + + //more complex stats + if(cur->Regen > 0) { + newbon->HPRegen += cur->Regen; + } + if(cur->ManaRegen > 0) { + newbon->ManaRegen += cur->ManaRegen; + } + if(cur->Attack > 0) { + newbon->ATK += cur->Attack; + } + if(cur->DamageShield > 0) { + newbon->DamageShield += cur->DamageShield; + } + if(cur->SpellShield > 0) { + newbon->SpellDamageShield += cur->SpellShield; + } + if(cur->Shielding > 0) { + newbon->MeleeMitigation += cur->Shielding; + } + if(cur->StunResist > 0) { + newbon->StunResist += cur->StunResist; + } + if(cur->StrikeThrough > 0) { + newbon->StrikeThrough += cur->StrikeThrough; + } + if(cur->Avoidance > 0) { + newbon->AvoidMeleeChance += cur->Avoidance; + } + if(cur->Accuracy > 0) { + newbon->HitChance += cur->Accuracy; + } + if(cur->CombatEffects > 0) { + newbon->ProcChance += cur->CombatEffects; + } + if (cur->Worn.Effect>0 && (cur->Worn.Type == ET_WornEffect)) { // latent effects + ApplySpellsBonuses(cur->Worn.Effect, cur->Worn.Level, newbon); + } + if (cur->Haste > newbon->haste) + newbon->haste = cur->Haste; + } + } + + } +} + +void Client::CalcItemScale() +{ + bool changed = false; + + if(CalcItemScale(0, 21)) + changed = true; + + if(CalcItemScale(22, 30)) + changed = true; + + if(CalcItemScale(251, 341)) + changed = true; + + if(CalcItemScale(400, 405)) + changed = true; + + //Power Source Slot + if (GetClientVersion() >= EQClientSoF) + { + if(CalcItemScale(9999, 10000)) + changed = true; + } + + if(changed) + { + CalcBonuses(); + } +} + +bool Client::CalcItemScale(uint32 slot_x, uint32 slot_y) +{ + bool changed = false; + int i; + for (i = slot_x; i < slot_y; i++) { + ItemInst* inst = m_inv.GetItem(i); + if(inst == 0) + continue; + + bool update_slot = false; + if(inst->IsScaling()) + { + uint16 oldexp = inst->GetExp(); + parse->EventItem(EVENT_SCALE_CALC, this, inst, nullptr, "", 0); + + if (inst->GetExp() != oldexp) { // if the scaling factor changed, rescale the item and update the client + inst->ScaleItem(); + changed = true; + update_slot = true; + } + } + + //iterate all augments + for(int x = 0; x < MAX_AUGMENT_SLOTS; ++x) + { + ItemInst * a_inst = inst->GetAugment(x); + if(!a_inst) + continue; + + if(a_inst->IsScaling()) + { + uint16 oldexp = a_inst->GetExp(); + parse->EventItem(EVENT_SCALE_CALC, this, a_inst, nullptr, "", 0); + + if (a_inst->GetExp() != oldexp) + { + a_inst->ScaleItem(); + changed = true; + update_slot = true; + } + } + } + + if(update_slot) + { + SendItemPacket(i, inst, ItemPacketCharmUpdate); + } + } + return changed; +} + +void Client::DoItemEnterZone() { + bool changed = false; + + if(DoItemEnterZone(0, 21)) + changed = true; + + if(DoItemEnterZone(22, 30)) + changed = true; + + if(DoItemEnterZone(251, 341)) + changed = true; + + if(DoItemEnterZone(400, 405)) + changed = true; + + //Power Source Slot + if (GetClientVersion() >= EQClientSoF) + { + if(DoItemEnterZone(9999, 10000)) + changed = true; + } + + if(changed) + { + CalcBonuses(); + } +} + +bool Client::DoItemEnterZone(uint32 slot_x, uint32 slot_y) { + bool changed = false; + for(int i = slot_x; i < slot_y; i++) { + ItemInst* inst = m_inv.GetItem(i); + if(!inst) + continue; + + bool update_slot = false; + if(inst->IsScaling()) + { + uint16 oldexp = inst->GetExp(); + + parse->EventItem(EVENT_ITEM_ENTER_ZONE, this, inst, nullptr, "", 0); + if(i < 22 || i == 9999) { + parse->EventItem(EVENT_EQUIP_ITEM, this, inst, nullptr, "", i); + } + + if (inst->GetExp() != oldexp) { // if the scaling factor changed, rescale the item and update the client + inst->ScaleItem(); + changed = true; + update_slot = true; + } + } else { + if(i < 22 || i == 9999) { + parse->EventItem(EVENT_EQUIP_ITEM, this, inst, nullptr, "", i); + } + + parse->EventItem(EVENT_ITEM_ENTER_ZONE, this, inst, nullptr, "", 0); + } + + //iterate all augments + for(int x = 0; x < MAX_AUGMENT_SLOTS; ++x) + { + ItemInst *a_inst = inst->GetAugment(x); + if(!a_inst) + continue; + + if(a_inst->IsScaling()) + { + uint16 oldexp = a_inst->GetExp(); + + parse->EventItem(EVENT_ITEM_ENTER_ZONE, this, a_inst, nullptr, "", 0); + + if (a_inst->GetExp() != oldexp) + { + a_inst->ScaleItem(); + changed = true; + update_slot = true; + } + } else { + parse->EventItem(EVENT_ITEM_ENTER_ZONE, this, a_inst, nullptr, "", 0); + } + } + + if(update_slot) + { + SendItemPacket(i, inst, ItemPacketCharmUpdate); + } + } + return changed; +} + +uint8 Mob::IsFocusEffect(uint16 spell_id,int effect_index, bool AA,uint32 aa_effect) +{ + uint16 effect = 0; + + if (!AA) + effect = spells[spell_id].effectid[effect_index]; + else + effect = aa_effect; + + switch (effect) + { + case SE_ImprovedDamage: + return focusImprovedDamage; + case SE_ImprovedHeal: + return focusImprovedHeal; + case SE_ReduceManaCost: + return focusManaCost; + case SE_IncreaseSpellHaste: + return focusSpellHaste; + case SE_IncreaseSpellDuration: + return focusSpellDuration; + case SE_SpellDurationIncByTic: + return focusSpellDurByTic; + case SE_SwarmPetDuration: + return focusSwarmPetDuration; + case SE_IncreaseRange: + return focusRange; + case SE_ReduceReagentCost: + return focusReagentCost; + case SE_PetPowerIncrease: + return focusPetPower; + case SE_SpellResistReduction: + return focusResistRate; + case SE_SpellHateMod: + return focusSpellHateMod; + case SE_ReduceReuseTimer: + return focusReduceRecastTime; + case SE_TriggerOnCast: + //return focusTriggerOnCast; + return 0; //This is calculated as an actual bonus + case SE_FcSpellVulnerability: + return focusSpellVulnerability; + case SE_BlockNextSpellFocus: + //return focusBlockNextSpell; + return 0; //This is calculated as an actual bonus + case SE_FcTwincast: + return focusTwincast; + case SE_SympatheticProc: + return focusSympatheticProc; + case SE_FcDamageAmt: + return focusFcDamageAmt; + case SE_FcDamageAmtCrit: + return focusFcDamageAmtCrit; + case SE_FcDamagePctCrit: + return focusFcDamagePctCrit; + case SE_FcDamageAmtIncoming: + return focusFcDamageAmtIncoming; + case SE_FcHealAmtIncoming: + return focusFcHealAmtIncoming; + case SE_FcHealPctIncoming: + return focusFcHealPctIncoming; + case SE_FcBaseEffects: + return focusFcBaseEffects; + case SE_FcIncreaseNumHits: + return focusIncreaseNumHits; + case SE_FcLimitUse: + return focusFcLimitUse; + case SE_FcMute: + return focusFcMute; + case SE_FcTimerRefresh: + return focusFcTimerRefresh; + case SE_FcStunTimeMod: + return focusFcStunTimeMod; + case SE_FcHealPctCritIncoming: + return focusFcHealPctCritIncoming; + case SE_FcHealAmt: + return focusFcHealAmt; + case SE_FcHealAmtCrit: + return focusFcHealAmtCrit; + } + return 0; +} + +void Mob::NegateSpellsBonuses(uint16 spell_id) +{ + if(!IsValidSpell(spell_id)) + return; + + int effect_value = 0; + + for (int i = 0; i < EFFECT_COUNT; i++) + { + if (spells[spell_id].effectid[i] == SE_NegateSpellEffect){ + + //Negate focus effects + for(int e = 0; e < HIGHEST_FOCUS+1; e++) + { + if (spellbonuses.FocusEffects[e] == spells[spell_id].base2[i]) + { + spellbonuses.FocusEffects[e] = effect_value; + continue; + } + } + + //Negate bonuses + switch (spells[spell_id].base2[i]) + { + case SE_CurrentHP: + if(spells[spell_id].base[i] == 1) { + spellbonuses.HPRegen = effect_value; + aabonuses.HPRegen = effect_value; + itembonuses.HPRegen = effect_value; + } + break; + + case SE_CurrentEndurance: + spellbonuses.EnduranceRegen = effect_value; + aabonuses.EnduranceRegen = effect_value; + itembonuses.EnduranceRegen = effect_value; + break; + + case SE_ChangeFrenzyRad: + spellbonuses.AggroRange = effect_value; + aabonuses.AggroRange = effect_value; + itembonuses.AggroRange = effect_value; + break; + + case SE_Harmony: + spellbonuses.AssistRange = effect_value; + aabonuses.AssistRange = effect_value; + itembonuses.AssistRange = effect_value; + break; + + case SE_AttackSpeed: + spellbonuses.haste = effect_value; + aabonuses.haste = effect_value; + itembonuses.haste = effect_value; + break; + + case SE_AttackSpeed2: + spellbonuses.hastetype2 = effect_value; + aabonuses.hastetype2 = effect_value; + itembonuses.hastetype2 = effect_value; + break; + + case SE_AttackSpeed3: + { + if (effect_value > 0) { + spellbonuses.hastetype3 = effect_value; + aabonuses.hastetype3 = effect_value; + itembonuses.hastetype3 = effect_value; + + } + break; + } + + case SE_AttackSpeed4: + spellbonuses.inhibitmelee = effect_value; + aabonuses.inhibitmelee = effect_value; + itembonuses.inhibitmelee = effect_value; + break; + + case SE_TotalHP: + spellbonuses.HP = effect_value; + aabonuses.HP = effect_value; + itembonuses.HP = effect_value; + break; + + case SE_ManaRegen_v2: + case SE_CurrentMana: + spellbonuses.ManaRegen = effect_value; + aabonuses.ManaRegen = effect_value; + itembonuses.ManaRegen = effect_value; + break; + + case SE_ManaPool: + spellbonuses.Mana = effect_value; + itembonuses.Mana = effect_value; + aabonuses.Mana = effect_value; + break; + + case SE_Stamina: + spellbonuses.EnduranceReduction = effect_value; + itembonuses.EnduranceReduction = effect_value; + aabonuses.EnduranceReduction = effect_value; + break; + + case SE_ACv2: + case SE_ArmorClass: + spellbonuses.AC = effect_value; + aabonuses.AC = effect_value; + itembonuses.AC = effect_value; + break; + + case SE_ATK: + spellbonuses.ATK = effect_value; + aabonuses.ATK = effect_value; + itembonuses.ATK = effect_value; + break; + + case SE_STR: + spellbonuses.STR = effect_value; + itembonuses.STR = effect_value; + aabonuses.STR = effect_value; + break; + + case SE_DEX: + spellbonuses.DEX = effect_value; + aabonuses.DEX = effect_value; + itembonuses.DEX = effect_value; + break; + + case SE_AGI: + itembonuses.AGI = effect_value; + aabonuses.AGI = effect_value; + spellbonuses.AGI = effect_value; + break; + + case SE_STA: + spellbonuses.STA = effect_value; + itembonuses.STA = effect_value; + aabonuses.STA = effect_value; + break; + + case SE_INT: + spellbonuses.INT = effect_value; + aabonuses.INT = effect_value; + itembonuses.INT = effect_value; + break; + + case SE_WIS: + spellbonuses.WIS = effect_value; + aabonuses.WIS = effect_value; + itembonuses.WIS = effect_value; + break; + + case SE_CHA: + itembonuses.CHA = effect_value; + spellbonuses.CHA = effect_value; + aabonuses.CHA = effect_value; + break; + + case SE_AllStats: + { + spellbonuses.STR = effect_value; + spellbonuses.DEX = effect_value; + spellbonuses.AGI = effect_value; + spellbonuses.STA = effect_value; + spellbonuses.INT = effect_value; + spellbonuses.WIS = effect_value; + spellbonuses.CHA = effect_value; + + itembonuses.STR = effect_value; + itembonuses.DEX = effect_value; + itembonuses.AGI = effect_value; + itembonuses.STA = effect_value; + itembonuses.INT = effect_value; + itembonuses.WIS = effect_value; + itembonuses.CHA = effect_value; + + aabonuses.STR = effect_value; + aabonuses.DEX = effect_value; + aabonuses.AGI = effect_value; + aabonuses.STA = effect_value; + aabonuses.INT = effect_value; + aabonuses.WIS = effect_value; + aabonuses.CHA = effect_value; + break; + } + + case SE_ResistFire: + spellbonuses.FR = effect_value; + itembonuses.FR = effect_value; + aabonuses.FR = effect_value; + break; + + case SE_ResistCold: + spellbonuses.CR = effect_value; + aabonuses.CR = effect_value; + itembonuses.CR = effect_value; + break; + + case SE_ResistPoison: + spellbonuses.PR = effect_value; + aabonuses.PR = effect_value; + itembonuses.PR = effect_value; + break; + + case SE_ResistDisease: + spellbonuses.DR = effect_value; + itembonuses.DR = effect_value; + aabonuses.DR = effect_value; + break; + + case SE_ResistMagic: + spellbonuses.MR = effect_value; + aabonuses.MR = effect_value; + itembonuses.MR = effect_value; + break; + + case SE_ResistAll: + { + spellbonuses.MR = effect_value; + spellbonuses.DR = effect_value; + spellbonuses.PR = effect_value; + spellbonuses.CR = effect_value; + spellbonuses.FR = effect_value; + + aabonuses.MR = effect_value; + aabonuses.DR = effect_value; + aabonuses.PR = effect_value; + aabonuses.CR = effect_value; + aabonuses.FR = effect_value; + + itembonuses.MR = effect_value; + itembonuses.DR = effect_value; + itembonuses.PR = effect_value; + itembonuses.CR = effect_value; + itembonuses.FR = effect_value; + break; + } + + case SE_ResistCorruption: + spellbonuses.Corrup = effect_value; + itembonuses.Corrup = effect_value; + aabonuses.Corrup = effect_value; + break; + + case SE_CastingLevel2: + case SE_CastingLevel: // Brilliance of Ro + spellbonuses.effective_casting_level = effect_value; + aabonuses.effective_casting_level = effect_value; + itembonuses.effective_casting_level = effect_value; + break; + + + case SE_MovementSpeed: + spellbonuses.movementspeed = effect_value; + aabonuses.movementspeed = effect_value; + itembonuses.movementspeed = effect_value; + break; + + case SE_SpellDamageShield: + spellbonuses.SpellDamageShield = effect_value; + aabonuses.SpellDamageShield = effect_value; + itembonuses.SpellDamageShield = effect_value; + break; + + case SE_DamageShield: + spellbonuses.DamageShield = effect_value; + aabonuses.DamageShield = effect_value; + itembonuses.DamageShield = effect_value; + break; + + case SE_ReverseDS: + spellbonuses.ReverseDamageShield = effect_value; + aabonuses.ReverseDamageShield = effect_value; + itembonuses.ReverseDamageShield = effect_value; + break; + + case SE_Reflect: + spellbonuses.reflect_chance = effect_value; + aabonuses.reflect_chance = effect_value; + itembonuses.reflect_chance = effect_value; + break; + + case SE_Amplification: + spellbonuses.Amplification = effect_value; + itembonuses.Amplification = effect_value; + aabonuses.Amplification = effect_value; + break; + + case SE_ChangeAggro: + spellbonuses.hatemod = effect_value; + itembonuses.hatemod = effect_value; + aabonuses.hatemod = effect_value; + break; + + case SE_MeleeMitigation: + spellbonuses.MeleeMitigation = effect_value; + itembonuses.MeleeMitigation = effect_value; + aabonuses.MeleeMitigation = effect_value; + break; + + case SE_CriticalHitChance: + { + for(int e = 0; e < HIGHEST_SKILL+1; e++) + { + spellbonuses.CriticalHitChance[e] = effect_value; + aabonuses.CriticalHitChance[e] = effect_value; + itembonuses.CriticalHitChance[e] = effect_value; + } + } + + case SE_CrippBlowChance: + spellbonuses.CrippBlowChance = effect_value; + aabonuses.CrippBlowChance = effect_value; + itembonuses.CrippBlowChance = effect_value; + break; + + case SE_AvoidMeleeChance: + spellbonuses.AvoidMeleeChance = effect_value; + aabonuses.AvoidMeleeChance = effect_value; + itembonuses.AvoidMeleeChance = effect_value; + break; + + case SE_RiposteChance: + spellbonuses.RiposteChance = effect_value; + aabonuses.RiposteChance = effect_value; + itembonuses.RiposteChance = effect_value; + break; + + case SE_DodgeChance: + spellbonuses.DodgeChance = effect_value; + aabonuses.DodgeChance = effect_value; + itembonuses.DodgeChance = effect_value; + break; + + case SE_ParryChance: + spellbonuses.ParryChance = effect_value; + aabonuses.ParryChance = effect_value; + itembonuses.ParryChance = effect_value; + break; + + case SE_DualWieldChance: + spellbonuses.DualWieldChance = effect_value; + aabonuses.DualWieldChance = effect_value; + itembonuses.DualWieldChance = effect_value; + break; + + case SE_DoubleAttackChance: + spellbonuses.DoubleAttackChance = effect_value; + aabonuses.DoubleAttackChance = effect_value; + itembonuses.DoubleAttackChance = effect_value; + break; + + case SE_TripleAttackChance: + spellbonuses.TripleAttackChance = effect_value; + aabonuses.TripleAttackChance = effect_value; + itembonuses.TripleAttackChance = effect_value; + break; + + case SE_MeleeLifetap: + spellbonuses.MeleeLifetap = effect_value; + aabonuses.MeleeLifetap = effect_value; + itembonuses.MeleeLifetap = effect_value; + break; + + case SE_AllInstrumentMod: + { + spellbonuses.singingMod = effect_value; + spellbonuses.brassMod = effect_value; + spellbonuses.percussionMod = effect_value; + spellbonuses.windMod = effect_value; + spellbonuses.stringedMod = effect_value; + + itembonuses.singingMod = effect_value; + itembonuses.brassMod = effect_value; + itembonuses.percussionMod = effect_value; + itembonuses.windMod = effect_value; + itembonuses.stringedMod = effect_value; + + aabonuses.singingMod = effect_value; + aabonuses.brassMod = effect_value; + aabonuses.percussionMod = effect_value; + aabonuses.windMod = effect_value; + aabonuses.stringedMod = effect_value; + break; + } + + case SE_ResistSpellChance: + spellbonuses.ResistSpellChance = effect_value; + aabonuses.ResistSpellChance = effect_value; + itembonuses.ResistSpellChance = effect_value; + break; + + case SE_ResistFearChance: + spellbonuses.Fearless = false; + spellbonuses.ResistFearChance = effect_value; + aabonuses.ResistFearChance = effect_value; + itembonuses.ResistFearChance = effect_value; + break; + + case SE_Fearless: + spellbonuses.Fearless = false; + aabonuses.Fearless = false; + itembonuses.Fearless = false; + break; + + case SE_HundredHands: + spellbonuses.HundredHands = effect_value; + aabonuses.HundredHands = effect_value; + itembonuses.HundredHands = effect_value; + break; + + case SE_MeleeSkillCheck: + { + spellbonuses.MeleeSkillCheck = effect_value; + spellbonuses.MeleeSkillCheckSkill = effect_value; + break; + } + + case SE_HitChance: + { + for(int e = 0; e < HIGHEST_SKILL+1; e++) + { + spellbonuses.HitChanceEffect[e] = effect_value; + aabonuses.HitChanceEffect[e] = effect_value; + itembonuses.HitChanceEffect[e] = effect_value; + } + break; + } + + case SE_DamageModifier: + { + for(int e = 0; e < HIGHEST_SKILL+1; e++) + { + spellbonuses.DamageModifier[e] = effect_value; + aabonuses.DamageModifier[e] = effect_value; + itembonuses.DamageModifier[e] = effect_value; + } + break; + } + + case SE_DamageModifier2: + { + for(int e = 0; e < HIGHEST_SKILL+1; e++) + { + spellbonuses.DamageModifier2[e] = effect_value; + aabonuses.DamageModifier2[e] = effect_value; + itembonuses.DamageModifier2[e] = effect_value; + } + break; + } + + case SE_MinDamageModifier: + { + for(int e = 0; e < HIGHEST_SKILL+1; e++) + { + spellbonuses.MinDamageModifier[e] = effect_value; + aabonuses.MinDamageModifier[e] = effect_value; + itembonuses.MinDamageModifier[e] = effect_value; + } + break; + } + + case SE_StunResist: + spellbonuses.StunResist = effect_value; + aabonuses.StunResist = effect_value; + itembonuses.StunResist = effect_value; + break; + + case SE_ProcChance: + spellbonuses.ProcChanceSPA = effect_value; + aabonuses.ProcChanceSPA = effect_value; + itembonuses.ProcChanceSPA = effect_value; + break; + + case SE_ExtraAttackChance: + spellbonuses.ExtraAttackChance = effect_value; + aabonuses.ExtraAttackChance = effect_value; + itembonuses.ExtraAttackChance = effect_value; + break; + + case SE_PercentXPIncrease: + spellbonuses.XPRateMod = effect_value; + aabonuses.XPRateMod = effect_value; + itembonuses.XPRateMod = effect_value; + break; + + case SE_Flurry: + spellbonuses.FlurryChance = effect_value; + aabonuses.FlurryChance = effect_value; + itembonuses.FlurryChance = effect_value; + break; + + case SE_Accuracy: + { + spellbonuses.Accuracy[HIGHEST_SKILL+1] = effect_value; + itembonuses.Accuracy[HIGHEST_SKILL+1] = effect_value; + + for(int e = 0; e < HIGHEST_SKILL+1; e++) + { + aabonuses.Accuracy[e] = effect_value; + } + break; + } + + case SE_MaxHPChange: + spellbonuses.MaxHPChange = effect_value; + aabonuses.MaxHPChange = effect_value; + itembonuses.MaxHPChange = effect_value; + break; + + case SE_EndurancePool: + spellbonuses.Endurance = effect_value; + aabonuses.Endurance = effect_value; + itembonuses.Endurance = effect_value; + break; + + case SE_HealRate: + spellbonuses.HealRate = effect_value; + aabonuses.HealRate = effect_value; + itembonuses.HealRate = effect_value; + break; + + case SE_SkillDamageTaken: + { + for(int e = 0; e < HIGHEST_SKILL+1; e++) + { + spellbonuses.SkillDmgTaken[e] = effect_value; + aabonuses.SkillDmgTaken[e] = effect_value; + itembonuses.SkillDmgTaken[e] = effect_value; + + } + break; + } + + case SE_TriggerOnCast: + { + for(int e = 0; e < MAX_SPELL_TRIGGER; e++) + { + spellbonuses.SpellTriggers[e] = effect_value; + aabonuses.SpellTriggers[e] = effect_value; + itembonuses.SpellTriggers[e] = effect_value; + } + break; + } + + case SE_SpellCritChance: + spellbonuses.CriticalSpellChance = effect_value; + aabonuses.CriticalSpellChance = effect_value; + itembonuses.CriticalSpellChance = effect_value; + break; + + case SE_CriticalSpellChance: + spellbonuses.CriticalSpellChance = effect_value; + spellbonuses.SpellCritDmgIncrease = effect_value; + aabonuses.CriticalSpellChance = effect_value; + aabonuses.SpellCritDmgIncrease = effect_value; + itembonuses.CriticalSpellChance = effect_value; + itembonuses.SpellCritDmgIncrease = effect_value; + break; + + case SE_SpellCritDmgIncrease: + spellbonuses.SpellCritDmgIncrease = effect_value; + aabonuses.SpellCritDmgIncrease = effect_value; + itembonuses.SpellCritDmgIncrease = effect_value; + break; + + case SE_DotCritDmgIncrease: + spellbonuses.DotCritDmgIncrease = effect_value; + aabonuses.DotCritDmgIncrease = effect_value; + itembonuses.DotCritDmgIncrease = effect_value; + break; + + case SE_CriticalHealChance: + spellbonuses.CriticalHealChance = effect_value; + aabonuses.CriticalHealChance = effect_value; + itembonuses.CriticalHealChance = effect_value; + break; + + case SE_CriticalHealOverTime: + spellbonuses.CriticalHealOverTime = effect_value; + aabonuses.CriticalHealOverTime = effect_value; + itembonuses.CriticalHealOverTime = effect_value; + break; + + case SE_MitigateDamageShield: + spellbonuses.DSMitigationOffHand = effect_value; + itembonuses.DSMitigationOffHand = effect_value; + aabonuses.DSMitigationOffHand = effect_value; + break; + + case SE_CriticalDoTChance: + spellbonuses.CriticalDoTChance = effect_value; + aabonuses.CriticalDoTChance = effect_value; + itembonuses.CriticalDoTChance = effect_value; + break; + + case SE_ProcOnKillShot: + { + for(int e = 0; e < MAX_SPELL_TRIGGER*3; e=3) + { + spellbonuses.SpellOnKill[e] = effect_value; + spellbonuses.SpellOnKill[e+1] = effect_value; + spellbonuses.SpellOnKill[e+2] = effect_value; + + aabonuses.SpellOnKill[e] = effect_value; + aabonuses.SpellOnKill[e+1] = effect_value; + aabonuses.SpellOnKill[e+2] = effect_value; + + itembonuses.SpellOnKill[e] = effect_value; + itembonuses.SpellOnKill[e+1] = effect_value; + itembonuses.SpellOnKill[e+2] = effect_value; + } + break; + } + + /* + case SE_SpellOnDeath: + { + for(int e = 0; e < MAX_SPELL_TRIGGER; e=2) + { + spellbonuses.SpellOnDeath[e] = SPELL_UNKNOWN; + spellbonuses.SpellOnDeath[e+1] = effect_value; + } + break; + } + */ + + case SE_CriticalDamageMob: + { + for(int e = 0; e < HIGHEST_SKILL+1; e++) + { + spellbonuses.CritDmgMob[e] = effect_value; + aabonuses.CritDmgMob[e] = effect_value; + itembonuses.CritDmgMob[e] = effect_value; + } + break; + } + + case SE_SkillDamageAmount: + { + for(int e = 0; e < HIGHEST_SKILL+1; e++) + { + spellbonuses.SkillDamageAmount[e] = effect_value; + aabonuses.SkillDamageAmount[e] = effect_value; + itembonuses.SkillDamageAmount[e] = effect_value; + } + break; + } + + case SE_IncreaseBlockChance: + spellbonuses.IncreaseBlockChance = effect_value; + aabonuses.IncreaseBlockChance = effect_value; + itembonuses.IncreaseBlockChance = effect_value; + break; + + case SE_PersistantCasting: + spellbonuses.PersistantCasting = effect_value; + itembonuses.PersistantCasting = effect_value; + aabonuses.PersistantCasting = effect_value; + break; + + case SE_ImmuneFleeing: + spellbonuses.ImmuneToFlee = false; + break; + + case SE_DelayDeath: + spellbonuses.DelayDeath = effect_value; + aabonuses.DelayDeath = effect_value; + itembonuses.DelayDeath = effect_value; + break; + + case SE_SpellProcChance: + spellbonuses.SpellProcChance = effect_value; + itembonuses.SpellProcChance = effect_value; + aabonuses.SpellProcChance = effect_value; + break; + + case SE_CharmBreakChance: + spellbonuses.CharmBreakChance = effect_value; + aabonuses.CharmBreakChance = effect_value; + itembonuses.CharmBreakChance = effect_value; + break; + + case SE_BardSongRange: + spellbonuses.SongRange = effect_value; + aabonuses.SongRange = effect_value; + itembonuses.SongRange = effect_value; + break; + + case SE_SkillDamageAmount2: + { + for(int e = 0; e < HIGHEST_SKILL+1; e++) + { + spellbonuses.SkillDamageAmount2[e] = effect_value; + aabonuses.SkillDamageAmount2[e] = effect_value; + itembonuses.SkillDamageAmount2[e] = effect_value; + } + break; + } + + case SE_NegateAttacks: + spellbonuses.NegateAttacks[0] = effect_value; + spellbonuses.NegateAttacks[1] = effect_value; + break; + + case SE_MitigateMeleeDamage: + spellbonuses.MitigateMeleeRune[0] = effect_value; + spellbonuses.MitigateMeleeRune[1] = -1; + break; + + case SE_MeleeThresholdGuard: + spellbonuses.MeleeThresholdGuard[0] = effect_value; + spellbonuses.MeleeThresholdGuard[1] = -1; + spellbonuses.MeleeThresholdGuard[1] = effect_value; + break; + + case SE_SpellThresholdGuard: + spellbonuses.SpellThresholdGuard[0] = effect_value; + spellbonuses.SpellThresholdGuard[1] = -1; + spellbonuses.SpellThresholdGuard[1] = effect_value; + break; + + case SE_MitigateSpellDamage: + spellbonuses.MitigateSpellRune[0] = effect_value; + spellbonuses.MitigateSpellRune[1] = -1; + break; + + case SE_MitigateDotDamage: + spellbonuses.MitigateDotRune[0] = effect_value; + spellbonuses.MitigateDotRune[1] = -1; + break; + + case SE_ManaAbsorbPercentDamage: + spellbonuses.ManaAbsorbPercentDamage[0] = effect_value; + spellbonuses.ManaAbsorbPercentDamage[1] = -1; + break; + + case SE_ShieldBlock: + spellbonuses.ShieldBlock = effect_value; + aabonuses.ShieldBlock = effect_value; + itembonuses.ShieldBlock = effect_value; + + case SE_BlockBehind: + spellbonuses.BlockBehind = effect_value; + aabonuses.BlockBehind = effect_value; + itembonuses.BlockBehind = effect_value; + break; + + case SE_Fear: + spellbonuses.IsFeared = false; + break; + + case SE_FrontalStunResist: + spellbonuses.FrontalStunResist = effect_value; + aabonuses.FrontalStunResist = effect_value; + itembonuses.FrontalStunResist = effect_value; + break; + + case SE_ImprovedBindWound: + aabonuses.BindWound = effect_value; + itembonuses.BindWound = effect_value; + spellbonuses.BindWound = effect_value; + break; + + case SE_MaxBindWound: + spellbonuses.MaxBindWound = effect_value; + aabonuses.MaxBindWound = effect_value; + itembonuses.MaxBindWound = effect_value; + break; + + case SE_BaseMovementSpeed: + spellbonuses.BaseMovementSpeed = effect_value; + aabonuses.BaseMovementSpeed = effect_value; + itembonuses.BaseMovementSpeed = effect_value; + break; + + case SE_IncreaseRunSpeedCap: + itembonuses.IncreaseRunSpeedCap = effect_value; + aabonuses.IncreaseRunSpeedCap = effect_value; + spellbonuses.IncreaseRunSpeedCap = effect_value; + break; + + case SE_DoubleSpecialAttack: + spellbonuses.DoubleSpecialAttack = effect_value; + aabonuses.DoubleSpecialAttack = effect_value; + itembonuses.DoubleSpecialAttack = effect_value; + break; + + case SE_TripleBackstab: + spellbonuses.TripleBackstab = effect_value; + aabonuses.TripleBackstab = effect_value; + itembonuses.TripleBackstab = effect_value; + break; + + case SE_FrontalBackstabMinDmg: + spellbonuses.FrontalBackstabMinDmg = false; + break; + + case SE_FrontalBackstabChance: + spellbonuses.FrontalBackstabChance = effect_value; + aabonuses.FrontalBackstabChance = effect_value; + itembonuses.FrontalBackstabChance = effect_value; + break; + + case SE_ConsumeProjectile: + spellbonuses.ConsumeProjectile = effect_value; + aabonuses.ConsumeProjectile = effect_value; + itembonuses.ConsumeProjectile = effect_value; + break; + + case SE_ForageAdditionalItems: + spellbonuses.ForageAdditionalItems = effect_value; + aabonuses.ForageAdditionalItems = effect_value; + itembonuses.ForageAdditionalItems = effect_value; + break; + + case SE_Salvage: + spellbonuses.SalvageChance = effect_value; + aabonuses.SalvageChance = effect_value; + itembonuses.SalvageChance = effect_value; + break; + + case SE_ArcheryDamageModifier: + spellbonuses.ArcheryDamageModifier = effect_value; + aabonuses.ArcheryDamageModifier = effect_value; + itembonuses.ArcheryDamageModifier = effect_value; + break; + + case SE_SecondaryDmgInc: + spellbonuses.SecondaryDmgInc = false; + aabonuses.SecondaryDmgInc = false; + itembonuses.SecondaryDmgInc = false; + break; + + case SE_StrikeThrough: + spellbonuses.StrikeThrough = effect_value; + aabonuses.StrikeThrough = effect_value; + itembonuses.StrikeThrough = effect_value; + break; + + case SE_StrikeThrough2: + spellbonuses.StrikeThrough = effect_value; + aabonuses.StrikeThrough = effect_value; + itembonuses.StrikeThrough = effect_value; + break; + + case SE_GiveDoubleAttack: + spellbonuses.GiveDoubleAttack = effect_value; + aabonuses.GiveDoubleAttack = effect_value; + itembonuses.GiveDoubleAttack = effect_value; + break; + + case SE_PetCriticalHit: + spellbonuses.PetCriticalHit = effect_value; + aabonuses.PetCriticalHit = effect_value; + itembonuses.PetCriticalHit = effect_value; + break; + + case SE_CombatStability: + spellbonuses.CombatStability = effect_value; + aabonuses.CombatStability = effect_value; + itembonuses.CombatStability = effect_value; + break; + + case SE_PetAvoidance: + spellbonuses.PetAvoidance = effect_value; + aabonuses.PetAvoidance = effect_value; + itembonuses.PetAvoidance = effect_value; + break; + + case SE_Ambidexterity: + spellbonuses.Ambidexterity = effect_value; + aabonuses.Ambidexterity = effect_value; + itembonuses.Ambidexterity = effect_value; + break; + + case SE_PetMaxHP: + spellbonuses.PetMaxHP = effect_value; + aabonuses.PetMaxHP = effect_value; + itembonuses.PetMaxHP = effect_value; + break; + + case SE_PetFlurry: + spellbonuses.PetFlurry = effect_value; + aabonuses.PetFlurry = effect_value; + itembonuses.PetFlurry = effect_value; + break; + + case SE_GivePetGroupTarget: + spellbonuses.GivePetGroupTarget = false; + aabonuses.GivePetGroupTarget = false; + itembonuses.GivePetGroupTarget = false; + break; + + case SE_PetMeleeMitigation: + spellbonuses.PetMeleeMitigation = effect_value; + itembonuses.PetMeleeMitigation = effect_value; + aabonuses.PetMeleeMitigation = effect_value; + break; + + case SE_RootBreakChance: + spellbonuses.RootBreakChance = effect_value; + aabonuses.RootBreakChance = effect_value; + itembonuses.RootBreakChance = effect_value; + break; + + case SE_ChannelChanceItems: + spellbonuses.ChannelChanceItems = effect_value; + aabonuses.ChannelChanceItems = effect_value; + itembonuses.ChannelChanceItems = effect_value; + break; + + case SE_ChannelChanceSpells: + spellbonuses.ChannelChanceSpells = effect_value; + aabonuses.ChannelChanceSpells = effect_value; + itembonuses.ChannelChanceSpells = effect_value; + break; + + case SE_UnfailingDivinity: + spellbonuses.UnfailingDivinity = effect_value; + aabonuses.UnfailingDivinity = effect_value; + itembonuses.UnfailingDivinity = effect_value; + break; + + case SE_ItemHPRegenCapIncrease: + spellbonuses.ItemHPRegenCap = effect_value; + aabonuses.ItemHPRegenCap = effect_value; + itembonuses.ItemHPRegenCap = effect_value; + break; + + case SE_OffhandRiposteFail: + spellbonuses.OffhandRiposteFail = effect_value; + aabonuses.OffhandRiposteFail = effect_value; + itembonuses.OffhandRiposteFail = effect_value; + break; + + case SE_ItemAttackCapIncrease: + aabonuses.ItemATKCap = effect_value; + itembonuses.ItemATKCap = effect_value; + spellbonuses.ItemATKCap = effect_value; + break; + + case SE_SpellEffectResistChance: + { + for(int e = 0; e < MAX_RESISTABLE_EFFECTS*2; e+=2) + { + spellbonuses.SEResist[e] = effect_value; + spellbonuses.SEResist[e+1] = effect_value; + } + break; + } + + case SE_MasteryofPast: + spellbonuses.MasteryofPast = effect_value; + aabonuses.MasteryofPast = effect_value; + itembonuses.MasteryofPast = effect_value; + break; + + case SE_DoubleRiposte: + spellbonuses.DoubleRiposte = effect_value; + itembonuses.DoubleRiposte = effect_value; + aabonuses.DoubleRiposte = effect_value; + break; + + case SE_GiveDoubleRiposte: + spellbonuses.GiveDoubleRiposte[0] = effect_value; + itembonuses.GiveDoubleRiposte[0] = effect_value; + aabonuses.GiveDoubleRiposte[0] = effect_value; + break; + + case SE_SlayUndead: + spellbonuses.SlayUndead[0] = effect_value; + spellbonuses.SlayUndead[1] = effect_value; + itembonuses.SlayUndead[0] = effect_value; + itembonuses.SlayUndead[1] = effect_value; + aabonuses.SlayUndead[0] = effect_value; + aabonuses.SlayUndead[1] = effect_value; + break; + + case SE_DoubleRangedAttack: + spellbonuses.DoubleRangedAttack = effect_value; + aabonuses.DoubleRangedAttack = effect_value; + itembonuses.DoubleRangedAttack = effect_value; + break; + + case SE_ShieldEquipHateMod: + spellbonuses.ShieldEquipHateMod = effect_value; + aabonuses.ShieldEquipHateMod = effect_value; + itembonuses.ShieldEquipHateMod = effect_value; + break; + + case SE_ShieldEquipDmgMod: + spellbonuses.ShieldEquipDmgMod[0] = effect_value; + spellbonuses.ShieldEquipDmgMod[1] = effect_value; + aabonuses.ShieldEquipDmgMod[0] = effect_value; + aabonuses.ShieldEquipDmgMod[1] = effect_value; + itembonuses.ShieldEquipDmgMod[0] = effect_value; + itembonuses.ShieldEquipDmgMod[1] = effect_value; + break; + + case SE_TriggerMeleeThreshold: + spellbonuses.TriggerMeleeThreshold[0] = effect_value; + spellbonuses.TriggerMeleeThreshold[1] = effect_value; + spellbonuses.TriggerMeleeThreshold[2] = effect_value; + break; + + case SE_TriggerSpellThreshold: + spellbonuses.TriggerSpellThreshold[0] = effect_value; + spellbonuses.TriggerSpellThreshold[1] = effect_value; + spellbonuses.TriggerSpellThreshold[2] = effect_value; + break; + + case SE_DivineAura: + spellbonuses.DivineAura = false; + break; + + case SE_StunBashChance: + spellbonuses.StunBashChance = effect_value; + itembonuses.StunBashChance = effect_value; + aabonuses.StunBashChance = effect_value; + break; + + case SE_IncreaseChanceMemwipe: + spellbonuses.IncreaseChanceMemwipe = effect_value; + itembonuses.IncreaseChanceMemwipe = effect_value; + aabonuses.IncreaseChanceMemwipe = effect_value; + break; + + case SE_CriticalMend: + spellbonuses.CriticalMend = effect_value; + itembonuses.CriticalMend = effect_value; + aabonuses.CriticalMend = effect_value; + break; + + case SE_DistanceRemoval: + spellbonuses.DistanceRemoval = effect_value; + break; + + case SE_ImprovedTaunt: + spellbonuses.ImprovedTaunt[0] = effect_value; + spellbonuses.ImprovedTaunt[1] = effect_value; + spellbonuses.ImprovedTaunt[2] = -1; + break; + + case SE_FrenziedDevastation: + spellbonuses.FrenziedDevastation = effect_value; + aabonuses.FrenziedDevastation = effect_value; + itembonuses.FrenziedDevastation = effect_value; + break; + + case SE_Root: + spellbonuses.Root[0] = effect_value; + spellbonuses.Root[1] = -1; + break; + + case SE_Rune: + spellbonuses.MeleeRune[0] = effect_value; + spellbonuses.MeleeRune[1] = -1; + break; + + case SE_AbsorbMagicAtt: + spellbonuses.AbsorbMagicAtt[0] = effect_value; + spellbonuses.AbsorbMagicAtt[1] = -1; + break; + + case SE_Berserk: + spellbonuses.BerserkSPA = false; + aabonuses.BerserkSPA = false; + itembonuses.BerserkSPA = false; + break; + + case SE_Vampirism: + spellbonuses.Vampirism = effect_value; + aabonuses.Vampirism = effect_value; + itembonuses.Vampirism = effect_value; + break; + + case SE_Metabolism: + spellbonuses.Metabolism = effect_value; + aabonuses.Metabolism = effect_value; + itembonuses.Metabolism = effect_value; + break; + + case SE_ImprovedReclaimEnergy: + spellbonuses.ImprovedReclaimEnergy = effect_value; + aabonuses.ImprovedReclaimEnergy = effect_value; + itembonuses.ImprovedReclaimEnergy = effect_value; + break; + + case SE_HeadShot: + spellbonuses.HeadShot[0] = effect_value; + aabonuses.HeadShot[0] = effect_value; + itembonuses.HeadShot[0] = effect_value; + spellbonuses.HeadShot[1] = effect_value; + aabonuses.HeadShot[1] = effect_value; + itembonuses.HeadShot[1] = effect_value; + break; + + case SE_HeadShotLevel: + spellbonuses.HSLevel = effect_value; + aabonuses.HSLevel = effect_value; + itembonuses.HSLevel = effect_value; + break; + + case SE_Assassinate: + spellbonuses.Assassinate[0] = effect_value; + aabonuses.Assassinate[0] = effect_value; + itembonuses.Assassinate[0] = effect_value; + spellbonuses.Assassinate[1] = effect_value; + aabonuses.Assassinate[1] = effect_value; + itembonuses.Assassinate[1] = effect_value; + break; + + case SE_AssassinateLevel: + spellbonuses.AssassinateLevel = effect_value; + aabonuses.AssassinateLevel = effect_value; + itembonuses.AssassinateLevel = effect_value; + break; + + case SE_FinishingBlow: + spellbonuses.FinishingBlow[0] = effect_value; + aabonuses.FinishingBlow[0] = effect_value; + itembonuses.FinishingBlow[0] = effect_value; + spellbonuses.FinishingBlow[1] = effect_value; + aabonuses.FinishingBlow[1] = effect_value; + itembonuses.FinishingBlow[1] = effect_value; + break; + + case SE_FinishingBlowLvl: + spellbonuses.FinishingBlowLvl[0] = effect_value; + aabonuses.FinishingBlowLvl[0] = effect_value; + itembonuses.FinishingBlowLvl[0] = effect_value; + spellbonuses.FinishingBlowLvl[1] = effect_value; + aabonuses.FinishingBlowLvl[1] = effect_value; + itembonuses.FinishingBlowLvl[1] = effect_value; + break; + + } + } + } +} + diff --git a/zone/groupsx.cpp b/zone/groupsx.cpp new file mode 100644 index 000000000..6aee5f38b --- /dev/null +++ b/zone/groupsx.cpp @@ -0,0 +1,2194 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2002 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/debug.h" +#include "masterentity.h" +#include "NpcAI.h" +#include "../common/packet_functions.h" +#include "../common/packet_dump.h" +#include "../common/StringUtil.h" +#include "worldserver.h" +extern EntityList entity_list; +extern WorldServer worldserver; + +// +// Xorlac: This will need proper synchronization to make it work correctly. +// Also, should investigate client ack for packet to ensure proper synch. +// + +/* + +note about how groups work: +A group contains 2 list, a list of pointers to members and a +list of member names. All members of a group should have their +name in the membername array, wether they are in the zone or not. +Only members in this zone will have non-null pointers in the +members array. + +*/ + +//create a group which should allready exist in the database +Group::Group(uint32 gid) +: GroupIDConsumer(gid) +{ + leader = nullptr; + memset(members,0,sizeof(Mob*) * MAX_GROUP_MEMBERS); + AssistTargetID = 0; + TankTargetID = 0; + PullerTargetID = 0; + + memset(&LeaderAbilities, 0, sizeof(GroupLeadershipAA_Struct)); + uint32 i; + for(i=0;iSetGrouped(true); + SetLeader(leader); + AssistTargetID = 0; + TankTargetID = 0; + PullerTargetID = 0; + memset(&LeaderAbilities, 0, sizeof(GroupLeadershipAA_Struct)); + uint32 i; + for(i=0;iGetName()); + + if(leader->IsClient()) + strcpy(leader->CastToClient()->GetPP().groupMembers[0],leader->GetName()); + + for(int i = 0; i < MAX_MARKED_NPCS; ++i) + MarkedNPCs[i] = 0; + + NPCMarkerID = 0; +} + +Group::~Group() +{ + for(int i = 0; i < MAX_MARKED_NPCS; ++i) + if(MarkedNPCs[i]) + { + Mob* m = entity_list.GetMob(MarkedNPCs[i]); + if(m) + m->IsTargeted(-1); + } +} + +//Cofruben:Split money used in OP_Split. +//Rewritten by Father Nitwit +void Group::SplitMoney(uint32 copper, uint32 silver, uint32 gold, uint32 platinum, Client *splitter) { + //avoid unneeded work + if(copper == 0 && silver == 0 && gold == 0 && platinum == 0) + return; + + uint32 i; + uint8 membercount = 0; + for (i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (members[i] != nullptr) { + membercount++; + } + } + + if (membercount == 0) + return; + + uint32 mod; + //try to handle round off error a little better + if(membercount > 1) { + mod = platinum % membercount; + if((mod) > 0) { + platinum -= mod; + gold += 10 * mod; + } + mod = gold % membercount; + if((mod) > 0) { + gold -= mod; + silver += 10 * mod; + } + mod = silver % membercount; + if((mod) > 0) { + silver -= mod; + copper += 10 * mod; + } + } + + //calculate the splits + //We can still round off copper pieces, but I dont care + uint32 sc; + uint32 cpsplit = copper / membercount; + sc = copper % membercount; + uint32 spsplit = silver / membercount; + uint32 gpsplit = gold / membercount; + uint32 ppsplit = platinum / membercount; + + char buf[128]; + buf[63] = '\0'; + std::string msg = "You receive"; + bool one = false; + + if(ppsplit > 0) { + snprintf(buf, 63, " %u platinum", ppsplit); + msg += buf; + one = true; + } + if(gpsplit > 0) { + if(one) + msg += ","; + snprintf(buf, 63, " %u gold", gpsplit); + msg += buf; + one = true; + } + if(spsplit > 0) { + if(one) + msg += ","; + snprintf(buf, 63, " %u silver", spsplit); + msg += buf; + one = true; + } + if(cpsplit > 0) { + if(one) + msg += ","; + //this message is not 100% accurate for the splitter + //if they are receiving any roundoff + snprintf(buf, 63, " %u copper", cpsplit); + msg += buf; + one = true; + } + msg += " as your split"; + + for (i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (members[i] != nullptr && members[i]->IsClient()) { // If Group Member is Client + Client *c = members[i]->CastToClient(); + //I could not get MoneyOnCorpse to work, so we use this + c->AddMoneyToPP(cpsplit, spsplit, gpsplit, ppsplit, true); + + c->Message(2, msg.c_str()); + } + } +} + +bool Group::AddMember(Mob* newmember, const char *NewMemberName, uint32 CharacterID) +{ + bool InZone = true; + bool ismerc = false; + + // This method should either be passed a Mob*, if the new member is in this zone, or a nullptr Mob* + // and the name and CharacterID of the new member, if they are out of zone. + // + if(!newmember && !NewMemberName) + return false; + + if(GroupCount() >= MAX_GROUP_MEMBERS) //Sanity check for merging groups together. + return false; + + if(!newmember) + InZone = false; + else + { + NewMemberName = newmember->GetCleanName(); + + if(newmember->IsClient()) + CharacterID = newmember->CastToClient()->CharacterID(); + if(newmember->IsMerc()) + { + Client* owner = newmember->CastToMerc()->GetMercOwner(); + if(owner) + { + CharacterID = owner->CastToClient()->CharacterID(); + NewMemberName = newmember->GetName(); + ismerc = true; + } + } + } + + uint32 i = 0; + + // See if they are already in the group + // + for (i = 0; i < MAX_GROUP_MEMBERS; ++i) + if(!strcasecmp(membername[i], NewMemberName)) + return false; + + // Put them in the group + for (i = 0; i < MAX_GROUP_MEMBERS; ++i) + { + if (membername[i][0] == '\0') + { + if(InZone) + members[i] = newmember; + + break; + } + } + + if (i == MAX_GROUP_MEMBERS) + return false; + + strcpy(membername[i], NewMemberName); + MemberRoles[i] = 0; + + int x=1; + + //build the template join packet + EQApplicationPacket* outapp = new EQApplicationPacket(OP_GroupUpdate,sizeof(GroupJoin_Struct)); + GroupJoin_Struct* gj = (GroupJoin_Struct*) outapp->pBuffer; + strcpy(gj->membername, NewMemberName); + gj->action = groupActJoin; + + gj->leader_aas = LeaderAbilities; + + for (i = 0;i < MAX_GROUP_MEMBERS; i++) { + if (members[i] != nullptr && members[i] != newmember) { + //fill in group join & send it + if(members[i]->IsMerc()) + { + strcpy(gj->yourname, members[i]->GetName()); + } + else + { + strcpy(gj->yourname, members[i]->GetCleanName()); + } + if(members[i]->IsClient()) { + members[i]->CastToClient()->QueuePacket(outapp); + + //put new member into existing person's list + strcpy(members[i]->CastToClient()->GetPP().groupMembers[this->GroupCount()-1], NewMemberName); + } + + //put this existing person into the new member's list + if(InZone && newmember->IsClient()) { + if(IsLeader(members[i])) + strcpy(newmember->CastToClient()->GetPP().groupMembers[0], members[i]->GetCleanName()); + else { + strcpy(newmember->CastToClient()->GetPP().groupMembers[x], members[i]->GetCleanName()); + x++; + } + } + } + } + + if(InZone) + { + //put new member in his own list. + newmember->SetGrouped(true); + + if(newmember->IsClient()) + { + strcpy(newmember->CastToClient()->GetPP().groupMembers[x], NewMemberName); + newmember->CastToClient()->Save(); + database.SetGroupID(NewMemberName, GetID(), newmember->CastToClient()->CharacterID(), false); + SendMarkedNPCsToMember(newmember->CastToClient()); + + NotifyMainTank(newmember->CastToClient(), 1); + NotifyMainAssist(newmember->CastToClient(), 1); + NotifyPuller(newmember->CastToClient(), 1); + } + + if(newmember->IsMerc()) + { + Client* owner = newmember->CastToMerc()->GetMercOwner(); + if(owner) + { + database.SetGroupID(newmember->GetName(), GetID(), owner->CharacterID(), true); + } + } +#ifdef BOTS + for (i = 0;i < MAX_GROUP_MEMBERS; i++) { + if (members[i] != nullptr && members[i]->IsBot()) { + members[i]->CastToBot()->CalcChanceToCast(); + } + } +#endif //BOTS + } + else + database.SetGroupID(NewMemberName, GetID(), CharacterID, ismerc); + + safe_delete(outapp); + + return true; +} + +void Group::AddMember(const char *NewMemberName) +{ + // This method should be called when both the new member and the group leader are in a different zone to this one. + // + for (uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) + if(!strcasecmp(membername[i], NewMemberName)) + { + return; + } + + for (uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) + { + if (membername[i][0] == '\0') + { + strcpy(membername[i], NewMemberName); + MemberRoles[i] = 0; + break; + } + } +} + + +void Group::QueuePacket(const EQApplicationPacket *app, bool ack_req) +{ + uint32 i; + for(i = 0; i < MAX_GROUP_MEMBERS; i++) + if(members[i] && members[i]->IsClient()) + members[i]->CastToClient()->QueuePacket(app, ack_req); +} + +// solar: sends the rest of the group's hps to member. this is useful when +// someone first joins a group, but otherwise there shouldn't be a need to +// call it +void Group::SendHPPacketsTo(Mob *member) +{ + if(member && member->IsClient()) + { + EQApplicationPacket hpapp; + EQApplicationPacket outapp(OP_MobManaUpdate, sizeof(MobManaUpdate_Struct)); + + for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) + { + if(members[i] && members[i] != member) + { + members[i]->CreateHPPacket(&hpapp); + member->CastToClient()->QueuePacket(&hpapp, false); + if(member->CastToClient()->GetClientVersion() >= EQClientSoD) + { + outapp.SetOpcode(OP_MobManaUpdate); + MobManaUpdate_Struct *mmus = (MobManaUpdate_Struct *)outapp.pBuffer; + mmus->spawn_id = members[i]->GetID(); + mmus->mana = members[i]->GetManaPercent(); + member->CastToClient()->QueuePacket(&outapp, false); + MobEnduranceUpdate_Struct *meus = (MobEnduranceUpdate_Struct *)outapp.pBuffer; + outapp.SetOpcode(OP_MobEnduranceUpdate); + meus->endurance = members[i]->GetEndurancePercent(); + member->CastToClient()->QueuePacket(&outapp, false); + } + } + } + } +} + +void Group::SendHPPacketsFrom(Mob *member) +{ + EQApplicationPacket hp_app; + if(!member) + return; + + member->CreateHPPacket(&hp_app); + EQApplicationPacket outapp(OP_MobManaUpdate, sizeof(MobManaUpdate_Struct)); + + uint32 i; + for(i = 0; i < MAX_GROUP_MEMBERS; i++) { + if(members[i] && members[i] != member && members[i]->IsClient()) + { + members[i]->CastToClient()->QueuePacket(&hp_app); + if(members[i]->CastToClient()->GetClientVersion() >= EQClientSoD) + { + outapp.SetOpcode(OP_MobManaUpdate); + MobManaUpdate_Struct *mmus = (MobManaUpdate_Struct *)outapp.pBuffer; + mmus->spawn_id = member->GetID(); + mmus->mana = member->GetManaPercent(); + members[i]->CastToClient()->QueuePacket(&outapp, false); + MobEnduranceUpdate_Struct *meus = (MobEnduranceUpdate_Struct *)outapp.pBuffer; + outapp.SetOpcode(OP_MobEnduranceUpdate); + meus->endurance = member->GetEndurancePercent(); + members[i]->CastToClient()->QueuePacket(&outapp, false); + } + } + } +} + +//updates a group member's client pointer when they zone in +//if the group was in the zone allready +bool Group::UpdatePlayer(Mob* update){ + + VerifyGroup(); + + uint32 i=0; + if(update->IsClient()) { + //update their player profile + PlayerProfile_Struct &pp = update->CastToClient()->GetPP(); + for (i = 0; i < MAX_GROUP_MEMBERS; i++) { + if(membername[i][0] == '\0') + memset(pp.groupMembers[i], 0, 64); + else + strn0cpy(pp.groupMembers[i], membername[i], 64); + } + if(IsNPCMarker(update->CastToClient())) + { + NPCMarkerID = update->GetID(); + SendLeadershipAAUpdate(); + } + } + + for (i = 0; i < MAX_GROUP_MEMBERS; i++) + { + if (!strcasecmp(membername[i],update->GetName())) + { + members[i] = update; + members[i]->SetGrouped(true); + return true; + } + } + return false; +} + + +void Group::MemberZoned(Mob* removemob) { + uint32 i; + + if (removemob == nullptr) + return; + + if(removemob == GetLeader()) + SetLeader(nullptr); + + for (i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (members[i] == removemob) { + members[i] = nullptr; + //should NOT clear the name, it is used for world communication. + break; + } +#ifdef BOTS + if (members[i] != nullptr && members[i]->IsBot()) { + members[i]->CastToBot()->CalcChanceToCast(); + } +#endif //BOTS + } + if(removemob->IsClient() && HasRole(removemob, RoleAssist)) + SetGroupAssistTarget(0); + + if(removemob->IsClient() && HasRole(removemob, RoleTank)) + SetGroupTankTarget(0); + + if(removemob->IsClient() && HasRole(removemob, RolePuller)) + SetGroupPullerTarget(0); +} + +bool Group::DelMemberOOZ(const char *Name) { + + if(!Name) return false; + + // If a member out of zone has disbanded, clear out their name. + // + for(unsigned int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if(!strcasecmp(Name, membername[i])) + // This shouldn't be called if the member is in this zone. + if(!members[i]) { + if(!strncmp(GetLeaderName(), Name, 64)) + { + //TODO: Transfer leadership if leader disbands OOZ. + UpdateGroupAAs(); + } + + memset(membername[i], 0, 64); + MemberRoles[i] = 0; + if(GroupCount() < 3) + { + UnDelegateMarkNPC(NPCMarkerName.c_str()); + if(GetLeader() && GetLeader()->IsClient() && GetLeader()->CastToClient()->GetClientVersion() < EQClientSoD) { + UnDelegateMainAssist(MainAssistName.c_str()); + } + ClearAllNPCMarks(); + } + return true; + } + } + + return false; +} + +bool Group::DelMember(Mob* oldmember,bool ignoresender) +{ + if (oldmember == nullptr){ + return false; + } + + for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (members[i] == oldmember) { + members[i] = nullptr; + membername[i][0] = '\0'; + memset(membername[i],0,64); + MemberRoles[i] = 0; + break; + } + } + + //handle leader quitting group gracefully + if (oldmember == GetLeader() && GroupCount() >= 2) { + for(uint32 nl = 0; nl < MAX_GROUP_MEMBERS; nl++) { + if(members[nl]) { + ChangeLeader(members[nl]); + break; + } + } + } + + ServerPacket* pack = new ServerPacket(ServerOP_GroupLeave, sizeof(ServerGroupLeave_Struct)); + ServerGroupLeave_Struct* gl = (ServerGroupLeave_Struct*)pack->pBuffer; + gl->gid = GetID(); + gl->zoneid = zone->GetZoneID(); + gl->instance_id = zone->GetInstanceID(); + strcpy(gl->member_name, oldmember->GetName()); + worldserver.SendPacket(pack); + safe_delete(pack); + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_GroupUpdate,sizeof(GroupJoin_Struct)); + GroupJoin_Struct* gu = (GroupJoin_Struct*) outapp->pBuffer; + gu->action = groupActLeave; + strcpy(gu->membername, oldmember->GetCleanName()); + strcpy(gu->yourname, oldmember->GetCleanName()); + + gu->leader_aas = LeaderAbilities; + + for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (members[i] == nullptr) { + //if (DEBUG>=5) LogFile->write(EQEMuLog::Debug, "Group::DelMember() null member at slot %i", i); + continue; + } + if (members[i] != oldmember) { + strcpy(gu->yourname, members[i]->GetCleanName()); + if(members[i]->IsClient()) + members[i]->CastToClient()->QueuePacket(outapp); + } +#ifdef BOTS + if (members[i] != nullptr && members[i]->IsBot()) { + members[i]->CastToBot()->CalcChanceToCast(); + } +#endif //BOTS + } + + if (!ignoresender) { + strcpy(gu->yourname,oldmember->GetCleanName()); + strcpy(gu->membername,oldmember->GetCleanName()); + gu->action = groupActLeave; + + if(oldmember->IsClient()) + oldmember->CastToClient()->QueuePacket(outapp); + } + + if(oldmember->IsClient()) + database.SetGroupID(oldmember->GetCleanName(), 0, oldmember->CastToClient()->CharacterID()); + + oldmember->SetGrouped(false); + disbandcheck = true; + + safe_delete(outapp); + + if(HasRole(oldmember, RoleTank)) + { + SetGroupTankTarget(0); + UnDelegateMainTank(oldmember->GetName()); + } + + if(HasRole(oldmember, RoleAssist)) + { + SetGroupAssistTarget(0); + UnDelegateMainAssist(oldmember->GetName()); + } + + if(HasRole(oldmember, RolePuller)) + { + SetGroupPullerTarget(0); + UnDelegatePuller(oldmember->GetName()); + } + + if(oldmember->IsClient()) + SendMarkedNPCsToMember(oldmember->CastToClient(), true); + + if(GroupCount() < 3) + { + UnDelegateMarkNPC(NPCMarkerName.c_str()); + if(GetLeader() && GetLeader()->IsClient() && GetLeader()->CastToClient()->GetClientVersion() < EQClientSoD) { + UnDelegateMainAssist(MainAssistName.c_str()); + } + ClearAllNPCMarks(); + } + + return true; +} + +// does the caster + group +void Group::CastGroupSpell(Mob* caster, uint16 spell_id) { + uint32 z; + float range, distance; + + if(!caster) + return; + + castspell = true; + range = caster->GetAOERange(spell_id); + + float range2 = range*range; + +// caster->SpellOnTarget(spell_id, caster); + + for(z=0; z < MAX_GROUP_MEMBERS; z++) + { + if(members[z] == caster) { + caster->SpellOnTarget(spell_id, caster); +#ifdef GROUP_BUFF_PETS + if(caster->GetPet() && caster->HasPetAffinity() && !caster->GetPet()->IsCharmed()) + caster->SpellOnTarget(spell_id, caster->GetPet()); +#endif + } + else if(members[z] != nullptr) + { + distance = caster->DistNoRoot(*members[z]); + if(distance <= range2) { + caster->SpellOnTarget(spell_id, members[z]); +#ifdef GROUP_BUFF_PETS + if(members[z]->GetPet() && members[z]->HasPetAffinity() && !members[z]->GetPet()->IsCharmed()) + caster->SpellOnTarget(spell_id, members[z]->GetPet()); +#endif + } else + _log(SPELLS__CASTING, "Group spell: %s is out of range %f at distance %f from %s", members[z]->GetName(), range, distance, caster->GetName()); + } + } + + castspell = false; + disbandcheck = true; +} + +// does the caster + group +void Group::GroupBardPulse(Mob* caster, uint16 spell_id) { + uint32 z; + float range, distance; + + if(!caster) + return; + + castspell = true; + range = caster->GetAOERange(spell_id); + + float range2 = range*range; + + for(z=0; z < MAX_GROUP_MEMBERS; z++) { + if(members[z] == caster) { + caster->BardPulse(spell_id, caster); +#ifdef GROUP_BUFF_PETS + if(caster->GetPet() && caster->HasPetAffinity() && !caster->GetPet()->IsCharmed()) + caster->BardPulse(spell_id, caster->GetPet()); +#endif + } + else if(members[z] != nullptr) + { + distance = caster->DistNoRoot(*members[z]); + if(distance <= range2) { + members[z]->BardPulse(spell_id, caster); +#ifdef GROUP_BUFF_PETS + if(members[z]->GetPet() && members[z]->HasPetAffinity() && !members[z]->GetPet()->IsCharmed()) + members[z]->GetPet()->BardPulse(spell_id, caster); +#endif + } else + _log(SPELLS__BARDS, "Group bard pulse: %s is out of range %f at distance %f from %s", members[z]->GetName(), range, distance, caster->GetName()); + } + } +} + +bool Group::IsGroupMember(Mob* client) +{ + bool Result = false; + + if(client) { + for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (members[i] == client) + Result = true; + } + } + + return Result; +} + +bool Group::IsGroupMember(const char *Name) +{ + if(Name) + for(uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) + if((strlen(Name) == strlen(membername[i])) && !strncmp(membername[i], Name, strlen(Name))) + return true; + + return false; +} + +void Group::GroupMessage(Mob* sender, uint8 language, uint8 lang_skill, const char* message) { + uint32 i; + for (i = 0; i < MAX_GROUP_MEMBERS; i++) { + if(!members[i]) + continue; + + if (members[i]->IsClient() && members[i]->CastToClient()->GetFilter(FilterGroupChat)!=0) + members[i]->CastToClient()->ChannelMessageSend(sender->GetName(),members[i]->GetName(),2,language,lang_skill,message); + } + + ServerPacket* pack = new ServerPacket(ServerOP_OOZGroupMessage, sizeof(ServerGroupChannelMessage_Struct) + strlen(message) + 1); + ServerGroupChannelMessage_Struct* gcm = (ServerGroupChannelMessage_Struct*)pack->pBuffer; + gcm->zoneid = zone->GetZoneID(); + gcm->groupid = GetID(); + gcm->instanceid = zone->GetInstanceID(); + strcpy(gcm->from, sender->GetName()); + strcpy(gcm->message, message); + worldserver.SendPacket(pack); + safe_delete(pack); +} + +uint32 Group::GetTotalGroupDamage(Mob* other) { + uint32 total = 0; + + uint32 i; + for (i = 0; i < MAX_GROUP_MEMBERS; i++) { + if(!members[i]) + continue; + if (other->CheckAggro(members[i])) + total += other->GetHateAmount(members[i],true); + } + return total; +} + +void Group::DisbandGroup() { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_GroupUpdate,sizeof(GroupUpdate_Struct)); + + GroupUpdate_Struct* gu = (GroupUpdate_Struct*) outapp->pBuffer; + gu->action = groupActDisband; + + Client *Leader = nullptr; + + uint32 i; + for (i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (members[i] == nullptr) { + continue; + } + + if (members[i]->IsClient()) { + if(IsLeader(members[i])) + Leader = members[i]->CastToClient(); + + strcpy(gu->yourname, members[i]->GetName()); + database.SetGroupID(members[i]->GetName(), 0, members[i]->CastToClient()->CharacterID()); + members[i]->CastToClient()->QueuePacket(outapp); + SendMarkedNPCsToMember(members[i]->CastToClient(), true); + + } + + members[i]->SetGrouped(false); + members[i] = nullptr; + membername[i][0] = '\0'; + } + + ClearAllNPCMarks(); + + ServerPacket* pack = new ServerPacket(ServerOP_DisbandGroup, sizeof(ServerDisbandGroup_Struct)); + ServerDisbandGroup_Struct* dg = (ServerDisbandGroup_Struct*)pack->pBuffer; + dg->zoneid = zone->GetZoneID(); + dg->groupid = GetID(); + dg->instance_id = zone->GetInstanceID(); + worldserver.SendPacket(pack); + safe_delete(pack); + + entity_list.RemoveGroup(GetID()); + if(GetID() != 0) + database.ClearGroup(GetID()); + + if(Leader && (Leader->IsLFP())) { + Leader->UpdateLFP(); + } + + safe_delete(outapp); +} + +bool Group::Process() { + if(disbandcheck && !GroupCount()) + return false; + else if(disbandcheck && GroupCount()) + disbandcheck = false; + return true; +} + +void Group::SendUpdate(uint32 type, Mob* member) +{ + if(!member->IsClient()) + return; + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupUpdate2_Struct)); + GroupUpdate2_Struct* gu = (GroupUpdate2_Struct*)outapp->pBuffer; + gu->action = type; + strcpy(gu->yourname,member->GetName()); + + int x = 0; + + gu->leader_aas = LeaderAbilities; + + for (uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) + if((members[i] != nullptr) && IsLeader(members[i])) + { + strcpy(gu->leadersname, members[i]->GetName()); + break; + } + + for (uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) + if (members[i] != nullptr && members[i] != member) + strcpy(gu->membername[x++], members[i]->GetName()); + + member->CastToClient()->QueuePacket(outapp); + + safe_delete(outapp); +} + +void Group::SendLeadershipAAUpdate() +{ + // This method updates other members of the group in the current zone with the Leader's group leadership AAs. + // + // It is called when the leader purchases a leadership AA or enters a zone. + // + // If a group member is not in the same zone as the leader when the leader purchases a new AA, they will not become + // aware of it until they are next in the same zone as the leader. + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_GroupUpdate,sizeof(GroupJoin_Struct)); + + GroupJoin_Struct* gu = (GroupJoin_Struct*)outapp->pBuffer; + + gu->action = groupActAAUpdate; + + uint32 i = 0; + + gu->leader_aas = LeaderAbilities; + + gu->NPCMarkerID = GetNPCMarkerID(); + + for (i = 0;i < MAX_GROUP_MEMBERS; ++i) + if(members[i] && members[i]->IsClient()) + { + strcpy(gu->yourname, members[i]->GetName()); + strcpy(gu->membername, members[i]->GetName()); + members[i]->CastToClient()->QueuePacket(outapp); + } + + safe_delete(outapp); +} + +uint8 Group::GroupCount() { + + uint8 MemberCount = 0; + + for(uint8 i = 0; i < MAX_GROUP_MEMBERS; ++i) + if(membername[i][0]) + ++MemberCount; + + return MemberCount; +} + +uint32 Group::GetHighestLevel() +{ +uint32 level = 1; +uint32 i; + for (i = 0; i < MAX_GROUP_MEMBERS; i++) + { + if (members[i]) + { + if(members[i]->GetLevel() > level) + level = members[i]->GetLevel(); + } + } + return level; +} +uint32 Group::GetLowestLevel() +{ +uint32 level = 255; +uint32 i; + for (i = 0; i < MAX_GROUP_MEMBERS; i++) + { + if (members[i]) + { + if(members[i]->GetLevel() < level) + level = members[i]->GetLevel(); + } + } + return level; +} + +void Group::TeleportGroup(Mob* sender, uint32 zoneID, uint16 instance_id, float x, float y, float z, float heading) +{ + uint32 i; + for (i = 0; i < MAX_GROUP_MEMBERS; i++) + { + if (members[i] != nullptr && members[i]->IsClient() && members[i] != sender) + { + members[i]->CastToClient()->MovePC(zoneID, instance_id, x, y, z, heading, 0, ZoneSolicited); + } + } +} + +bool Group::LearnMembers() { + char errbuf[MYSQL_ERRMSG_SIZE]; + char* query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + if (database.RunQuery(query,MakeAnyLenString(&query, "SELECT name FROM group_id WHERE groupid=%lu", (unsigned long)GetID()), + errbuf,&result)){ + safe_delete_array(query); + if(mysql_num_rows(result) < 1) { //could prolly be 2 + mysql_free_result(result); + LogFile->write(EQEMuLog::Error, "Error getting group members for group %lu: %s", (unsigned long)GetID(), errbuf); + return(false); + } + int i = 0; + while((row = mysql_fetch_row(result))) { + if(!row[0]) + continue; + members[i] = nullptr; + strn0cpy(membername[i], row[0], 64); + + i++; + } + mysql_free_result(result); + } + + return(true); +} + +void Group::VerifyGroup() { + /* + The purpose of this method is to make sure that a group + is in a valid state, to prevent dangling pointers. + Only called every once in a while (on member re-join for now). + */ + + uint32 i; + for (i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (membername[i][0] == '\0') { +#if EQDEBUG >= 7 +LogFile->write(EQEMuLog::Debug, "Group %lu: Verify %d: Empty.\n", (unsigned long)GetID(), i); +#endif + members[i] = nullptr; + continue; + } + + //it should be safe to use GetClientByName, but Group is trying + //to be generic, so we'll go for general Mob + Mob *them = entity_list.GetMob(membername[i]); + if(them == nullptr && members[i] != nullptr) { //they arnt here anymore.... +#if EQDEBUG >= 6 + LogFile->write(EQEMuLog::Debug, "Member of group %lu named '%s' has disappeared!!", (unsigned long)GetID(), membername[i]); +#endif + membername[i][0] = '\0'; + members[i] = nullptr; + continue; + } + + if(them != nullptr && members[i] != them) { //our pointer is out of date... not so good. +#if EQDEBUG >= 5 + LogFile->write(EQEMuLog::Debug, "Member of group %lu named '%s' had an out of date pointer!!", (unsigned long)GetID(), membername[i]); +#endif + members[i] = them; + continue; + } +#if EQDEBUG >= 8 + LogFile->write(EQEMuLog::Debug, "Member of group %lu named '%s' is valid.", (unsigned long)GetID(), membername[i]); +#endif + } +} + + +void Group::GroupMessage_StringID(Mob* sender, uint32 type, uint32 string_id, const char* message,const char* message2,const char* message3,const char* message4,const char* message5,const char* message6,const char* message7,const char* message8,const char* message9, uint32 distance) { + uint32 i; + for (i = 0; i < MAX_GROUP_MEMBERS; i++) { + if(members[i] == nullptr) + continue; + + if(members[i] == sender) + continue; + + members[i]->Message_StringID(type, string_id, message, message2, message3, message4, message5, message6, message7, message8, message9, 0); + } +} + + + +void Client::LeaveGroup() { + Group *g = GetGroup(); + + if(g) { + if(g->GroupCount() < 3) + g->DisbandGroup(); + else + g->DelMember(this); + } else { + //force things a little + database.SetGroupID(GetName(), 0, CharacterID()); + } + + isgrouped = false; +} + +void Group::HealGroup(uint32 heal_amt, Mob* caster, int32 range) +{ + if (!caster) + return; + + if (!range) + range = 200; + + float distance; + float range2 = range*range; + + + int numMem = 0; + unsigned int gi = 0; + for(; gi < MAX_GROUP_MEMBERS; gi++) + { + if(members[gi]){ + distance = caster->DistNoRoot(*members[gi]); + if(distance <= range2){ + numMem += 1; + } + } + } + + heal_amt /= numMem; + for(gi = 0; gi < MAX_GROUP_MEMBERS; gi++) + { + if(members[gi]){ + distance = caster->DistNoRoot(*members[gi]); + if(distance <= range2){ + members[gi]->HealDamage(heal_amt, caster); + members[gi]->SendHPUpdate(); + } + } + } +} + + +void Group::BalanceHP(int32 penalty, int32 range, Mob* caster, int32 limit) +{ + if (!caster) + return; + + if (!range) + range = 200; + + int dmgtaken = 0, numMem = 0, dmgtaken_tmp = 0; + + float distance; + float range2 = range*range; + + unsigned int gi = 0; + for(; gi < MAX_GROUP_MEMBERS; gi++) + { + if(members[gi]){ + distance = caster->DistNoRoot(*members[gi]); + if(distance <= range2){ + + dmgtaken_tmp = members[gi]->GetMaxHP() - members[gi]->GetHP(); + if (limit && (dmgtaken_tmp > limit)) + dmgtaken_tmp = limit; + + dmgtaken += (dmgtaken_tmp); + numMem += 1; + } + } + } + + dmgtaken += dmgtaken * penalty / 100; + dmgtaken /= numMem; + for(gi = 0; gi < MAX_GROUP_MEMBERS; gi++) + { + if(members[gi]){ + distance = caster->DistNoRoot(*members[gi]); + if(distance <= range2){ + if((members[gi]->GetMaxHP() - dmgtaken) < 1){ //this way the ability will never kill someone + members[gi]->SetHP(1); //but it will come darn close + members[gi]->SendHPUpdate(); + } + else{ + members[gi]->SetHP(members[gi]->GetMaxHP() - dmgtaken); + members[gi]->SendHPUpdate(); + } + } + } + } +} + +void Group::BalanceMana(int32 penalty, int32 range, Mob* caster, int32 limit) +{ + if (!caster) + return; + + if (!range) + range = 200; + + float distance; + float range2 = range*range; + + int manataken = 0, numMem = 0, manataken_tmp = 0; + unsigned int gi = 0; + for(; gi < MAX_GROUP_MEMBERS; gi++) + { + if(members[gi] && (members[gi]->GetMaxMana() > 0)){ + distance = caster->DistNoRoot(*members[gi]); + if(distance <= range2){ + + manataken_tmp = members[gi]->GetMaxMana() - members[gi]->GetMana(); + if (limit && (manataken_tmp > limit)) + manataken_tmp = limit; + + manataken += (manataken_tmp); + numMem += 1; + } + } + } + + manataken += manataken * penalty / 100; + manataken /= numMem; + + if (limit && (manataken > limit)) + manataken = limit; + + for(gi = 0; gi < MAX_GROUP_MEMBERS; gi++) + { + if(members[gi]){ + distance = caster->DistNoRoot(*members[gi]); + if(distance <= range2){ + if((members[gi]->GetMaxMana() - manataken) < 1){ + members[gi]->SetMana(1); + if (members[gi]->IsClient()) + members[gi]->CastToClient()->SendManaUpdate(); + } + else{ + members[gi]->SetMana(members[gi]->GetMaxMana() - manataken); + if (members[gi]->IsClient()) + members[gi]->CastToClient()->SendManaUpdate(); + } + } + } + } +} + +uint16 Group::GetAvgLevel() +{ + double levelHolder = 0; + uint8 i = 0; + uint8 numMem = 0; + while(i < MAX_GROUP_MEMBERS) + { + if (members[i]) + { + numMem++; + levelHolder = levelHolder + (members[i]->GetLevel()); + } + i++; + } + levelHolder = ((levelHolder/numMem)+.5); // total levels divided by num of characters + return (uint16(levelHolder)); +} + +void Group::MarkNPC(Mob* Target, int Number) +{ + // Send a packet to all group members in this zone causing the client to prefix the Target mob's name + // with the specified Number. + // + if(!Target || Target->IsClient()) + return; + + if((Number < 1) || (Number > MAX_MARKED_NPCS)) + return; + + bool AlreadyMarked = false; + + uint16 EntityID = Target->GetID(); + + for(int i = 0; i < MAX_MARKED_NPCS; ++i) + if(MarkedNPCs[i] == EntityID) + { + if(i == (Number - 1)) + return; + + UpdateXTargetMarkedNPC(i+1, nullptr); + MarkedNPCs[i] = 0; + + AlreadyMarked = true; + + break; + } + + if(!AlreadyMarked) + { + if(MarkedNPCs[Number - 1]) + { + Mob* m = entity_list.GetMob(MarkedNPCs[Number-1]); + if(m) + m->IsTargeted(-1); + + UpdateXTargetMarkedNPC(Number, nullptr); + } + + if(EntityID) + { + Mob* m = entity_list.GetMob(Target->GetID()); + if(m) + m->IsTargeted(1); + } + } + + MarkedNPCs[Number - 1] = EntityID; + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_MarkNPC, sizeof(MarkNPC_Struct)); + + MarkNPC_Struct* mnpcs = (MarkNPC_Struct *)outapp->pBuffer; + + mnpcs->TargetID = EntityID; + + mnpcs->Number = Number; + + Mob *m = entity_list.GetMob(EntityID); + + if(m) + sprintf(mnpcs->Name, "%s", m->GetCleanName()); + + QueuePacket(outapp); + + safe_delete(outapp); + + UpdateXTargetMarkedNPC(Number, m); +} + +void Group::DelegateMainTank(const char *NewMainTankName, uint8 toggle) +{ + // This method is called when the group leader Delegates the Main Tank role to a member of the group + // (or himself). All group members in the zone are notified of the new Main Tank and it is recorded + // in the group_leaders table so as to persist across zones. + // + + bool updateDB = false; + + if(!NewMainTankName) + return; + + Mob *m = entity_list.GetMob(NewMainTankName); + + if(!m) + return; + + if(MainTankName != NewMainTankName || !toggle) + updateDB = true; + + if(m->GetTarget()) + TankTargetID = m->GetTarget()->GetID(); + else + TankTargetID = 0; + + Mob *mtt = TankTargetID ? entity_list.GetMob(TankTargetID) : 0; + + SetMainTank(NewMainTankName); + + for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) + { + if(members[i] && members[i]->IsClient()) + { + NotifyMainTank(members[i]->CastToClient(), toggle); + members[i]->CastToClient()->UpdateXTargetType(GroupTank, m, NewMainTankName); + members[i]->CastToClient()->UpdateXTargetType(GroupTankTarget, mtt); + } + } + + if(updateDB) { + char errbuff[MYSQL_ERRMSG_SIZE]; + + char *Query = nullptr; + + if (!database.RunQuery(Query, MakeAnyLenString(&Query, "UPDATE group_leaders SET maintank='%s' WHERE gid=%i LIMIT 1", + MainTankName.c_str(), GetID()), errbuff)) + LogFile->write(EQEMuLog::Error, "Unable to set group main tank: %s\n", errbuff); + + safe_delete_array(Query); + } +} + +void Group::DelegateMainAssist(const char *NewMainAssistName, uint8 toggle) +{ + // This method is called when the group leader Delegates the Main Assist role to a member of the group + // (or himself). All group members in the zone are notified of the new Main Assist and it is recorded + // in the group_leaders table so as to persist across zones. + // + + bool updateDB = false; + + if(!NewMainAssistName) + return; + + Mob *m = entity_list.GetMob(NewMainAssistName); + + if(!m) + return; + + if(MainAssistName != NewMainAssistName || !toggle) + updateDB = true; + + if(m->GetTarget()) + AssistTargetID = m->GetTarget()->GetID(); + else + AssistTargetID = 0; + + SetMainAssist(NewMainAssistName); + + for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) { + if(members[i] && members[i]->IsClient()) + { + NotifyMainAssist(members[i]->CastToClient(), toggle); + members[i]->CastToClient()->UpdateXTargetType(GroupAssist, m, NewMainAssistName); + members[i]->CastToClient()->UpdateXTargetType(GroupAssistTarget, m->GetTarget()); + } + } + + if(updateDB) { + char errbuff[MYSQL_ERRMSG_SIZE]; + + char *Query = nullptr; + + if (!database.RunQuery(Query, MakeAnyLenString(&Query, "UPDATE group_leaders SET assist='%s' WHERE gid=%i LIMIT 1", + MainAssistName.c_str(), GetID()), errbuff)) + LogFile->write(EQEMuLog::Error, "Unable to set group main assist: %s\n", errbuff); + + safe_delete_array(Query); + } +} + +void Group::DelegatePuller(const char *NewPullerName, uint8 toggle) +{ + // This method is called when the group leader Delegates the Puller role to a member of the group + // (or himself). All group members in the zone are notified of the new Puller and it is recorded + // in the group_leaders table so as to persist across zones. + // + + bool updateDB = false; + + if(!NewPullerName) + return; + + Mob *m = entity_list.GetMob(NewPullerName); + + if(!m) + return; + + if(PullerName != NewPullerName || !toggle) + updateDB = true; + + if(m->GetTarget()) + PullerTargetID = m->GetTarget()->GetID(); + else + PullerTargetID = 0; + + SetPuller(NewPullerName); + + for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) { + if(members[i] && members[i]->IsClient()) + { + NotifyPuller(members[i]->CastToClient(), toggle); + members[i]->CastToClient()->UpdateXTargetType(Puller, m, NewPullerName); + members[i]->CastToClient()->UpdateXTargetType(PullerTarget, m->GetTarget()); + } + } + + if(updateDB) { + char errbuff[MYSQL_ERRMSG_SIZE]; + + char *Query = nullptr; + + if (!database.RunQuery(Query, MakeAnyLenString(&Query, "UPDATE group_leaders SET puller='%s' WHERE gid=%i LIMIT 1", + PullerName.c_str(), GetID()), errbuff)) + LogFile->write(EQEMuLog::Error, "Unable to set group main puller: %s\n", errbuff); + + safe_delete_array(Query); + } + +} + +void Group::NotifyMainTank(Client *c, uint8 toggle) +{ + // Send a packet to the specified Client notifying them who the new Main Tank is. This causes the client to display + // a message with the name of the Main Tank. + // + + if(!c) + return; + + if(!MainTankName.size()) + return; + + if(c->GetClientVersion() < EQClientSoD) + { + if(toggle) + c->Message(0, "%s is now Main Tank.", MainTankName.c_str()); + else + c->Message(0, "%s is no longer Main Tank.", MainTankName.c_str()); + } + else + { + EQApplicationPacket *outapp = new EQApplicationPacket(OP_GroupRoles, sizeof(GroupRole_Struct)); + + GroupRole_Struct *grs = (GroupRole_Struct*)outapp->pBuffer; + + strn0cpy(grs->Name1, MainTankName.c_str(), sizeof(grs->Name1)); + + strn0cpy(grs->Name2, GetLeaderName(), sizeof(grs->Name2)); + + grs->RoleNumber = 1; + + grs->Toggle = toggle; + + c->QueuePacket(outapp); + + safe_delete(outapp); + } + +} + +void Group::NotifyMainAssist(Client *c, uint8 toggle) +{ + // Send a packet to the specified Client notifying them who the new Main Assist is. This causes the client to display + // a message with the name of the Main Assist. + // + + if(!c) + return; + + if(!MainAssistName.size()) + return; + + if(c->GetClientVersion() < EQClientSoD) + { + EQApplicationPacket *outapp = new EQApplicationPacket(OP_DelegateAbility, sizeof(DelegateAbility_Struct)); + + DelegateAbility_Struct* das = (DelegateAbility_Struct*)outapp->pBuffer; + + das->DelegateAbility = 0; + + das->MemberNumber = 0; + + das->Action = 0; + + das->EntityID = 0; + + strn0cpy(das->Name, MainAssistName.c_str(), sizeof(das->Name)); + + c->QueuePacket(outapp); + + safe_delete(outapp); + } + else + { + EQApplicationPacket *outapp = new EQApplicationPacket(OP_GroupRoles, sizeof(GroupRole_Struct)); + + GroupRole_Struct *grs = (GroupRole_Struct*)outapp->pBuffer; + + strn0cpy(grs->Name1, MainAssistName.c_str(), sizeof(grs->Name1)); + + strn0cpy(grs->Name2, GetLeaderName(), sizeof(grs->Name2)); + + grs->RoleNumber = 2; + + grs->Toggle = toggle; + + c->QueuePacket(outapp); + + safe_delete(outapp); + } + + NotifyAssistTarget(c); + +} + +void Group::NotifyPuller(Client *c, uint8 toggle) +{ + // Send a packet to the specified Client notifying them who the new Puller is. This causes the client to display + // a message with the name of the Puller. + // + + if(!c) + return; + + if(!PullerName.size()) + return; + + if(c->GetClientVersion() < EQClientSoD) + { + if(toggle) + c->Message(0, "%s is now Puller.", PullerName.c_str()); + else + c->Message(0, "%s is no longer Puller.", PullerName.c_str()); + } + else + { + EQApplicationPacket *outapp = new EQApplicationPacket(OP_GroupRoles, sizeof(GroupRole_Struct)); + + GroupRole_Struct *grs = (GroupRole_Struct*)outapp->pBuffer; + + strn0cpy(grs->Name1, PullerName.c_str(), sizeof(grs->Name1)); + + strn0cpy(grs->Name2, GetLeaderName(), sizeof(grs->Name2)); + + grs->RoleNumber = 3; + + grs->Toggle = toggle; + + c->QueuePacket(outapp); + + safe_delete(outapp); + } + +} + +void Group::UnDelegateMainTank(const char *OldMainTankName, uint8 toggle) +{ + // Called when the group Leader removes the Main Tank delegation. Sends a packet to each group member in the zone + // informing them of the change and update the group_leaders table. + // + if(OldMainTankName == MainTankName) { + char errbuff[MYSQL_ERRMSG_SIZE]; + + char *Query = 0; + + if (!database.RunQuery(Query, MakeAnyLenString(&Query, "UPDATE group_leaders SET maintank='' WHERE gid=%i LIMIT 1", + GetID()), errbuff)) + LogFile->write(EQEMuLog::Error, "Unable to clear group main tank: %s\n", errbuff); + + safe_delete_array(Query); + + if(!toggle) { + for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) { + if(members[i] && members[i]->IsClient()) + { + NotifyMainTank(members[i]->CastToClient(), toggle); + members[i]->CastToClient()->UpdateXTargetType(GroupTank, nullptr, ""); + members[i]->CastToClient()->UpdateXTargetType(GroupTankTarget, nullptr); + } + } + } + + SetMainTank(""); + } +} + +void Group::UnDelegateMainAssist(const char *OldMainAssistName, uint8 toggle) +{ + // Called when the group Leader removes the Main Assist delegation. Sends a packet to each group member in the zone + // informing them of the change and update the group_leaders table. + // + if(OldMainAssistName == MainAssistName) { + EQApplicationPacket *outapp = new EQApplicationPacket(OP_DelegateAbility, sizeof(DelegateAbility_Struct)); + + DelegateAbility_Struct* das = (DelegateAbility_Struct*)outapp->pBuffer; + + das->DelegateAbility = 0; + + das->MemberNumber = 0; + + das->Action = 1; + + das->EntityID = 0; + + strn0cpy(das->Name, OldMainAssistName, sizeof(das->Name)); + + for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) + if(members[i] && members[i]->IsClient()) + { + members[i]->CastToClient()->QueuePacket(outapp); + members[i]->CastToClient()->UpdateXTargetType(GroupAssist, nullptr, ""); + } + + safe_delete(outapp); + + char errbuff[MYSQL_ERRMSG_SIZE]; + + char *Query = 0; + + if (!database.RunQuery(Query, MakeAnyLenString(&Query, "UPDATE group_leaders SET assist='' WHERE gid=%i LIMIT 1", + GetID()), errbuff)) + LogFile->write(EQEMuLog::Error, "Unable to clear group main assist: %s\n", errbuff); + + safe_delete_array(Query); + + if(!toggle) + { + for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) + { + if(members[i] && members[i]->IsClient()) + { + NotifyMainAssist(members[i]->CastToClient(), toggle); + members[i]->CastToClient()->UpdateXTargetType(GroupAssistTarget, nullptr); + } + } + } + + SetMainAssist(""); + } +} + +void Group::UnDelegatePuller(const char *OldPullerName, uint8 toggle) +{ + // Called when the group Leader removes the Puller delegation. Sends a packet to each group member in the zone + // informing them of the change and update the group_leaders table. + // + if(OldPullerName == PullerName) { + char errbuff[MYSQL_ERRMSG_SIZE]; + + char *Query = 0; + + if (!database.RunQuery(Query, MakeAnyLenString(&Query, "UPDATE group_leaders SET puller='' WHERE gid=%i LIMIT 1", + GetID()), errbuff)) + LogFile->write(EQEMuLog::Error, "Unable to clear group main puller: %s\n", errbuff); + + safe_delete_array(Query); + + if(!toggle) { + for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) { + if(members[i] && members[i]->IsClient()) + { + NotifyPuller(members[i]->CastToClient(), toggle); + members[i]->CastToClient()->UpdateXTargetType(Puller, nullptr, ""); + members[i]->CastToClient()->UpdateXTargetType(PullerTarget, nullptr); + } + } + } + + SetPuller(""); + } +} + +bool Group::IsNPCMarker(Client *c) +{ + // Returns true if the specified client has been delegated the NPC Marker Role + // + if(!c) + return false; + + if(NPCMarkerName.size()) + return(c->GetName() == NPCMarkerName); + + return false; + +} + +void Group::SetGroupAssistTarget(Mob *m) +{ + // Notify all group members in the zone of the new target the Main Assist has selected. + // + AssistTargetID = m ? m->GetID() : 0; + + for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) + { + if(members[i] && members[i]->IsClient()) + { + NotifyAssistTarget(members[i]->CastToClient()); + } + } +} + +void Group::SetGroupTankTarget(Mob *m) +{ + TankTargetID = m ? m->GetID() : 0; + + for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) + { + if(members[i] && members[i]->IsClient()) + { + members[i]->CastToClient()->UpdateXTargetType(GroupTankTarget, m); + } + } +} + +void Group::SetGroupPullerTarget(Mob *m) +{ + PullerTargetID = m ? m->GetID() : 0; + + for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) + { + if(members[i] && members[i]->IsClient()) + { + members[i]->CastToClient()->UpdateXTargetType(PullerTarget, m); + } + } +} + +void Group::NotifyAssistTarget(Client *c) +{ + // Send a packet to the specified client notifying them of the group target selected by the Main Assist. + + if(!c) + return; + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SetGroupTarget, sizeof(MarkNPC_Struct)); + + MarkNPC_Struct* mnpcs = (MarkNPC_Struct *)outapp->pBuffer; + + mnpcs->TargetID = AssistTargetID; + + mnpcs->Number = 0; + + c->QueuePacket(outapp); + + safe_delete(outapp); + + Mob *m = entity_list.GetMob(AssistTargetID); + + c->UpdateXTargetType(GroupAssistTarget, m); + +} + +void Group::NotifyTankTarget(Client *c) +{ + if(!c) + return; + + Mob *m = entity_list.GetMob(TankTargetID); + + c->UpdateXTargetType(GroupTankTarget, m); +} + +void Group::NotifyPullerTarget(Client *c) +{ + if(!c) + return; + + Mob *m = entity_list.GetMob(PullerTargetID); + + c->UpdateXTargetType(PullerTarget, m); +} + +void Group::DelegateMarkNPC(const char *NewNPCMarkerName) +{ + // Called when the group leader has delegated the Mark NPC ability to a group member. + // Notify all group members in the zone of the change and save the change in the group_leaders + // table to persist across zones. + // + if(NPCMarkerName.size() > 0) + UnDelegateMarkNPC(NPCMarkerName.c_str()); + + if(!NewNPCMarkerName) + return; + + SetNPCMarker(NewNPCMarkerName); + + for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) + if(members[i] && members[i]->IsClient()) + NotifyMarkNPC(members[i]->CastToClient()); + + char errbuff[MYSQL_ERRMSG_SIZE]; + + char *Query = 0; + + if (!database.RunQuery(Query, MakeAnyLenString(&Query, "UPDATE group_leaders SET marknpc='%s' WHERE gid=%i LIMIT 1", + NewNPCMarkerName, GetID()), errbuff)) + LogFile->write(EQEMuLog::Error, "Unable to set group mark npc: %s\n", errbuff); + + safe_delete_array(Query); + +} + +void Group::NotifyMarkNPC(Client *c) +{ + // Notify the specified client who the group member is who has been delgated the Mark NPC ability. + + if(!c) + return; + + if(!NPCMarkerName.size()) + return; + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_DelegateAbility, sizeof(DelegateAbility_Struct)); + + DelegateAbility_Struct* das = (DelegateAbility_Struct*)outapp->pBuffer; + + das->DelegateAbility = 1; + + das->MemberNumber = 0; + + das->Action = 0; + + das->EntityID = NPCMarkerID; + + strn0cpy(das->Name, NPCMarkerName.c_str(), sizeof(das->Name)); + + c->QueuePacket(outapp); + + safe_delete(outapp); + +} +void Group::SetNPCMarker(const char *NewNPCMarkerName) +{ + NPCMarkerName = NewNPCMarkerName; + + Client *m = entity_list.GetClientByName(NPCMarkerName.c_str()); + + if(!m) + NPCMarkerID = 0; + else + NPCMarkerID = m->GetID(); +} + +void Group::UnDelegateMarkNPC(const char *OldNPCMarkerName) +{ + // Notify all group members in the zone that the Mark NPC ability has been rescinded from the specified + // group member. + + if(!OldNPCMarkerName) + return; + + if(!NPCMarkerName.size()) + return; + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_DelegateAbility, sizeof(DelegateAbility_Struct)); + + DelegateAbility_Struct* das = (DelegateAbility_Struct*)outapp->pBuffer; + + das->DelegateAbility = 1; + + das->MemberNumber = 0; + + das->Action = 1; + + das->EntityID = 0; + + strn0cpy(das->Name, OldNPCMarkerName, sizeof(das->Name)); + + for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) + if(members[i] && members[i]->IsClient()) + members[i]->CastToClient()->QueuePacket(outapp); + + safe_delete(outapp); + + NPCMarkerName.clear(); + + char errbuff[MYSQL_ERRMSG_SIZE]; + + char *Query = 0; + + if (!database.RunQuery(Query, MakeAnyLenString(&Query, "UPDATE group_leaders SET marknpc='' WHERE gid=%i LIMIT 1", + GetID()), errbuff)) + LogFile->write(EQEMuLog::Error, "Unable to clear group marknpc: %s\n", errbuff); + + safe_delete_array(Query); +} + +void Group::SaveGroupLeaderAA() +{ + // Stores the Group Leaders Leadership AA data from the Player Profile as a blob in the group_leaders table. + // This is done so that group members not in the same zone as the Leader still have access to this information. + + char *Query = new char[200 + sizeof(GroupLeadershipAA_Struct)*2]; + + char *End = Query; + + End += sprintf(End, "UPDATE group_leaders SET leadershipaa='"); + + End += database.DoEscapeString(End, (char*)&LeaderAbilities, sizeof(GroupLeadershipAA_Struct)); + + End += sprintf(End,"' WHERE gid=%i LIMIT 1", GetID()); + + char errbuff[MYSQL_ERRMSG_SIZE]; + if (!database.RunQuery(Query, End - Query, errbuff)) + LogFile->write(EQEMuLog::Error, "Unable to store LeadershipAA: %s\n", errbuff); + + safe_delete_array(Query); +} + +void Group::UnMarkNPC(uint16 ID) +{ + // Called from entity_list when the mob with the specified ID is being destroyed. + // + // If the given mob has been marked by this group, it is removed from the list of marked NPCs. + // The primary reason for doing this is so that when a new group member joins or zones in, we + // send them correct details of which NPCs are currently marked. + + if(AssistTargetID == ID) + AssistTargetID = 0; + + + if(TankTargetID == ID) + TankTargetID = 0; + + if(PullerTargetID == ID) + PullerTargetID = 0; + + for(int i = 0; i < MAX_MARKED_NPCS; ++i) + { + if(MarkedNPCs[i] == ID) + { + MarkedNPCs[i] = 0; + UpdateXTargetMarkedNPC(i + 1, nullptr); + } + } +} + +void Group::SendMarkedNPCsToMember(Client *c, bool Clear) +{ + // Send the Entity IDs of the NPCs marked by the Group Leader or delegate to the specified client. + // If Clear == true, then tell the client to unmark the NPCs (when a member disbands). + // + // + if(!c) + return; + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_MarkNPC, sizeof(MarkNPC_Struct)); + + MarkNPC_Struct *mnpcs = (MarkNPC_Struct *)outapp->pBuffer; + + for(int i = 0; i < MAX_MARKED_NPCS; ++i) + { + if(MarkedNPCs[i]) + { + mnpcs->TargetID = MarkedNPCs[i]; + + Mob *m = entity_list.GetMob(MarkedNPCs[i]); + + if(m) + sprintf(mnpcs->Name, "%s", m->GetCleanName()); + + if(!Clear) + mnpcs->Number = i + 1; + else + mnpcs->Number = 0; + + c->QueuePacket(outapp); + c->UpdateXTargetType((mnpcs->Number == 1) ? GroupMarkTarget1 : ((mnpcs->Number == 2) ? GroupMarkTarget2 : GroupMarkTarget3), m); + } + } + + safe_delete(outapp); +} + +void Group::ClearAllNPCMarks() +{ + // This method is designed to be called when the number of members in the group drops below 3 and leadership AA + // may no longer be used. It removes all NPC marks. + // + for(uint8 i = 0; i < MAX_GROUP_MEMBERS; ++i) + if(members[i] && members[i]->IsClient()) + SendMarkedNPCsToMember(members[i]->CastToClient(), true); + + for(int i = 0; i < MAX_MARKED_NPCS; ++i) + { + if(MarkedNPCs[i]) + { + Mob* m = entity_list.GetMob(MarkedNPCs[i]); + + if(m) + m->IsTargeted(-1); + } + + MarkedNPCs[i] = 0; + } + +} + +int8 Group::GetNumberNeedingHealedInGroup(int8 hpr, bool includePets) { + int8 needHealed = 0; + + for( int i = 0; iqglobal) { + + if(members[i]->GetHPRatio() <= hpr) + needHealed++; + + if(includePets) { + if(members[i]->GetPet() && members[i]->GetPet()->GetHPRatio() <= hpr) { + needHealed++; + } + } + } + } + + + return needHealed; +} + +void Group::UpdateGroupAAs() +{ + // This method updates the Groups Leadership abilities from the Player Profile of the Leader. + // + Mob *m = GetLeader(); + + if(m && m->IsClient()) + m->CastToClient()->GetGroupAAs(&LeaderAbilities); + else + memset(&LeaderAbilities, 0, sizeof(GroupLeadershipAA_Struct)); + + SaveGroupLeaderAA(); +} + +void Group::QueueHPPacketsForNPCHealthAA(Mob* sender, const EQApplicationPacket* app) +{ + // Send a mobs HP packets to group members if the leader has the NPC Health AA and the mob is the + // target of the group's main assist, or is marked, and the member doesn't already have the mob targeted. + + if(!sender || !app || !GetLeadershipAA(groupAANPCHealth)) + return; + + uint16 SenderID = sender->GetID(); + + if(SenderID != AssistTargetID) + { + bool Marked = false; + + for(int i = 0; i < MAX_MARKED_NPCS; ++i) + { + if(MarkedNPCs[i] == SenderID) + { + Marked = true; + break; + } + } + + if(!Marked) + return; + + } + + for(unsigned int i = 0; i < MAX_GROUP_MEMBERS; ++i) + if(members[i] && members[i]->IsClient()) + { + if(!members[i]->GetTarget() || (members[i]->GetTarget()->GetID() != SenderID)) + { + members[i]->CastToClient()->QueuePacket(app); + } + } + +} + +void Group::ChangeLeader(Mob* newleader) +{ + // this changes the current group leader, notifies other members, and updates leadship AA + + // if the new leader is invalid, do nothing + if (!newleader) + return; + + Mob* oldleader = GetLeader(); + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_GroupUpdate,sizeof(GroupJoin_Struct)); + GroupJoin_Struct* gu = (GroupJoin_Struct*) outapp->pBuffer; + gu->action = groupActMakeLeader; + + strcpy(gu->membername, newleader->GetName()); + strcpy(gu->yourname, oldleader->GetName()); + SetLeader(newleader); + database.SetGroupLeaderName(GetID(), newleader->GetName()); + UpdateGroupAAs(); + gu->leader_aas = LeaderAbilities; + for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (members[i] && members[i]->IsClient()) + { + if(members[i]->CastToClient()->GetClientVersion() >= EQClientSoD) + members[i]->CastToClient()->SendGroupLeaderChangePacket(newleader->GetName()); + + members[i]->CastToClient()->QueuePacket(outapp); + } + } + safe_delete(outapp); +} + +const char *Group::GetClientNameByIndex(uint8 index) +{ + return membername[index]; +} + +void Group::UpdateXTargetMarkedNPC(uint32 Number, Mob *m) +{ + for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) + { + if(members[i] && members[i]->IsClient()) + { + members[i]->CastToClient()->UpdateXTargetType((Number == 1) ? GroupMarkTarget1 : ((Number == 2) ? GroupMarkTarget2 : GroupMarkTarget3), m); + } + } + +} + +void Group::SetMainTank(const char *NewMainTankName) +{ + MainTankName = NewMainTankName; + + for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) + { + if(!strncasecmp(membername[i], NewMainTankName, 64)) + MemberRoles[i] |= RoleTank; + else + MemberRoles[i] &= ~RoleTank; + } +} + +void Group::SetMainAssist(const char *NewMainAssistName) +{ + MainAssistName = NewMainAssistName; + + for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) + { + if(!strncasecmp(membername[i], NewMainAssistName, 64)) + MemberRoles[i] |= RoleAssist; + else + MemberRoles[i] &= ~RoleAssist; + } +} + +void Group::SetPuller(const char *NewPullerName) +{ + PullerName = NewPullerName; + + for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) + { + if(!strncasecmp(membername[i], NewPullerName, 64)) + MemberRoles[i] |= RolePuller; + else + MemberRoles[i] &= ~RolePuller; + } +} + +bool Group::HasRole(Mob *m, uint8 Role) +{ + if(!m) + return false; + + for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) + { + if((m == members[i]) && (MemberRoles[i] & Role)) + return true; + } + return false; +} + diff --git a/zone/mob.h b/zone/mob.h index 7083e45ca..97b75a4fb 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -662,7 +662,7 @@ public: bool TryReflectSpell(uint32 spell_id); bool CanBlockSpell() const { return(spellbonuses.BlockNextSpell); } bool DoHPToManaCovert(uint16 mana_cost = 0); - int32 ApplySpellEffectiveness(Mob* caster, int16 spell_id, int32 value, bool IsBard = false); + int32 ApplySpellEffectiveness(int16 spell_id, int32 value, bool IsBard = false, uint16 caster_id=0); int8 GetDecayEffectValue(uint16 spell_id, uint16 spelleffect); int32 GetExtraSpellAmt(uint16 spell_id, int32 extra_spell_amt, int32 base_spell_dmg); void MeleeLifeTap(int32 damage); @@ -898,7 +898,7 @@ public: virtual int32 CheckHealAggroAmount(uint16 spell_id, Mob *target, uint32 heal_possible = 0); uint32 GetInstrumentMod(uint16 spell_id) const; - int CalcSpellEffectValue(uint16 spell_id, int effect_id, int caster_level = 1, uint32 instrument_mod = 10, Mob *caster = nullptr, int ticsremaining = 0); + int CalcSpellEffectValue(uint16 spell_id, int effect_id, int caster_level = 1, uint32 instrument_mod = 10, Mob *caster = nullptr, int ticsremaining = 0,uint16 casterid=0); int CalcSpellEffectValue_formula(int formula, int base, int max, int caster_level, uint16 spell_id, int ticsremaining = 0); virtual int CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, int caster_level2, Mob* caster1 = nullptr, Mob* caster2 = nullptr, int buffslot = -1); uint32 GetCastedSpellInvSlot() const { return casting_spell_inventory_slot; } diff --git a/zone/mobx.cpp b/zone/mobx.cpp new file mode 100644 index 000000000..340dfdfea --- /dev/null +++ b/zone/mobx.cpp @@ -0,0 +1,5148 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2002 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/debug.h" +#include "masterentity.h" +#include "../common/spdat.h" +#include "StringIDs.h" +#include "worldserver.h" +#include "QuestParserCollection.h" +#include "../common/StringUtil.h" + +#include +#include +#include + +extern EntityList entity_list; + +extern Zone* zone; +extern WorldServer worldserver; + +Mob::Mob(const char* in_name, + const char* in_lastname, + int32 in_cur_hp, + int32 in_max_hp, + uint8 in_gender, + uint16 in_race, + uint8 in_class, + bodyType in_bodytype, + uint8 in_deity, + uint8 in_level, + uint32 in_npctype_id, + float in_size, + float in_runspeed, + float in_heading, + float in_x_pos, + float in_y_pos, + float in_z_pos, + + uint8 in_light, + uint8 in_texture, + uint8 in_helmtexture, + uint16 in_ac, + uint16 in_atk, + uint16 in_str, + uint16 in_sta, + uint16 in_dex, + uint16 in_agi, + uint16 in_int, + uint16 in_wis, + uint16 in_cha, + uint8 in_haircolor, + uint8 in_beardcolor, + uint8 in_eyecolor1, // the eyecolors always seem to be the same, maybe left and right eye? + uint8 in_eyecolor2, + uint8 in_hairstyle, + uint8 in_luclinface, + uint8 in_beard, + uint32 in_drakkin_heritage, + uint32 in_drakkin_tattoo, + uint32 in_drakkin_details, + uint32 in_armor_tint[_MaterialCount], + + uint8 in_aa_title, + uint8 in_see_invis, // see through invis/ivu + uint8 in_see_invis_undead, + uint8 in_see_hide, + uint8 in_see_improved_hide, + int32 in_hp_regen, + int32 in_mana_regen, + uint8 in_qglobal, + uint8 in_maxlevel, + uint32 in_scalerate + ) : + attack_timer(2000), + attack_dw_timer(2000), + ranged_timer(2000), + tic_timer(6000), + mana_timer(2000), + spellend_timer(0), + rewind_timer(30000), //Timer used for determining amount of time between actual player position updates for /rewind. + bindwound_timer(10000), + stunned_timer(0), + spun_timer(0), + bardsong_timer(6000), + gravity_timer(1000), + viral_timer(0), + flee_timer(FLEE_CHECK_TIMER) + +{ + targeted = 0; + tar_ndx=0; + tar_vector=0; + tar_vx=0; + tar_vy=0; + tar_vz=0; + tarx=0; + tary=0; + tarz=0; + fear_walkto_x = -999999; + fear_walkto_y = -999999; + fear_walkto_z = -999999; + curfp = false; + + AI_Init(); + SetMoving(false); + moved=false; + rewind_x = 0; //Stored x_pos for /rewind + rewind_y = 0; //Stored y_pos for /rewind + rewind_z = 0; //Stored z_pos for /rewind + move_tic_count = 0; + + _egnode = nullptr; + name[0]=0; + orig_name[0]=0; + clean_name[0]=0; + lastname[0]=0; + if(in_name) { + strn0cpy(name,in_name,64); + strn0cpy(orig_name,in_name,64); + } + if(in_lastname) + strn0cpy(lastname,in_lastname,64); + cur_hp = in_cur_hp; + max_hp = in_max_hp; + base_hp = in_max_hp; + gender = in_gender; + race = in_race; + base_gender = in_gender; + base_race = in_race; + class_ = in_class; + bodytype = in_bodytype; + orig_bodytype = in_bodytype; + deity = in_deity; + level = in_level; + orig_level = in_level; + npctype_id = in_npctype_id; + size = in_size; + base_size = size; + runspeed = in_runspeed; + + + // sanity check + if (runspeed < 0 || runspeed > 20) + runspeed = 1.25f; + + heading = in_heading; + x_pos = in_x_pos; + y_pos = in_y_pos; + z_pos = in_z_pos; + light = in_light; + texture = in_texture; + helmtexture = in_helmtexture; + haircolor = in_haircolor; + beardcolor = in_beardcolor; + eyecolor1 = in_eyecolor1; + eyecolor2 = in_eyecolor2; + hairstyle = in_hairstyle; + luclinface = in_luclinface; + beard = in_beard; + drakkin_heritage = in_drakkin_heritage; + drakkin_tattoo = in_drakkin_tattoo; + drakkin_details = in_drakkin_details; + attack_speed= 0; + slow_mitigation= 0; + findable = false; + trackable = true; + has_shieldequiped = false; + has_numhits = false; + has_MGB = false; + has_ProjectIllusion = false; + + if(in_aa_title>0) + aa_title = in_aa_title; + else + aa_title =0xFF; + AC = in_ac; + ATK = in_atk; + STR = in_str; + STA = in_sta; + DEX = in_dex; + AGI = in_agi; + INT = in_int; + WIS = in_wis; + CHA = in_cha; + MR = CR = FR = DR = PR = Corrup = 0; + + ExtraHaste = 0; + bEnraged = false; + + shield_target = nullptr; + cur_mana = 0; + max_mana = 0; + hp_regen = in_hp_regen; + mana_regen = in_mana_regen; + oocregen = RuleI(NPC, OOCRegen); //default Out of Combat Regen + maxlevel = in_maxlevel; + scalerate = in_scalerate; + invisible = false; + invisible_undead = false; + invisible_animals = false; + sneaking = false; + hidden = false; + improved_hidden = false; + invulnerable = false; + IsFullHP = (cur_hp == max_hp); + qglobal=0; + + InitializeBuffSlots(); + + // clear the proc arrays + int i; + int j; + for (j = 0; j < MAX_PROCS; j++) + { + PermaProcs[j].spellID = SPELL_UNKNOWN; + PermaProcs[j].chance = 0; + PermaProcs[j].base_spellID = SPELL_UNKNOWN; + SpellProcs[j].spellID = SPELL_UNKNOWN; + SpellProcs[j].chance = 0; + SpellProcs[j].base_spellID = SPELL_UNKNOWN; + DefensiveProcs[j].spellID = SPELL_UNKNOWN; + DefensiveProcs[j].chance = 0; + DefensiveProcs[j].base_spellID = SPELL_UNKNOWN; + RangedProcs[j].spellID = SPELL_UNKNOWN; + RangedProcs[j].chance = 0; + RangedProcs[j].base_spellID = SPELL_UNKNOWN; + SkillProcs[j].spellID = SPELL_UNKNOWN; + SkillProcs[j].chance = 0; + SkillProcs[j].base_spellID = SPELL_UNKNOWN; + } + + for (i = 0; i < _MaterialCount; i++) + { + if (in_armor_tint) + { + armor_tint[i] = in_armor_tint[i]; + } + else + { + armor_tint[i] = 0; + } + } + + delta_heading = 0; + delta_x = 0; + delta_y = 0; + delta_z = 0; + animation = 0; + + logging_enabled = false; + isgrouped = false; + israidgrouped = false; + islooting = false; + _appearance = eaStanding; + pRunAnimSpeed = 0; + + spellend_timer.Disable(); + bardsong_timer.Disable(); + bardsong = 0; + bardsong_target_id = 0; + casting_spell_id = 0; + casting_spell_timer = 0; + casting_spell_timer_duration = 0; + casting_spell_type = 0; + casting_spell_inventory_slot = 0; + target = 0; + + for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_spell_id[i] = 0; } + for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_target_id[i] = 0; } + for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_increment[i] = 0; } + for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_x[i] = 0; } + for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_y[i] = 0; } + for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_z[i] = 0; } + projectile_timer.Disable(); + + memset(&itembonuses, 0, sizeof(StatBonuses)); + memset(&spellbonuses, 0, sizeof(StatBonuses)); + memset(&aabonuses, 0, sizeof(StatBonuses)); + spellbonuses.AggroRange = -1; + spellbonuses.AssistRange = -1; + pLastChange = 0; + SetPetID(0); + SetOwnerID(0); + typeofpet = petCharmed; //default to charmed... + petpower = 0; + held = false; + nocast = false; + focused = false; + + attacked_count = 0; + mezzed = false; + stunned = false; + silenced = false; + amnesiad = false; + inWater = false; + int m; + for (m = 0; m < MAX_SHIELDERS; m++) + { + shielder[m].shielder_id = 0; + shielder[m].shielder_bonus = 0; + } + + destructibleobject = false; + wandertype=0; + pausetype=0; + cur_wp = 0; + cur_wp_x = 0; + cur_wp_y = 0; + cur_wp_z = 0; + cur_wp_pause = 0; + patrol=0; + follow=0; + follow_dist = 100; // Default Distance for Follow + flee_mode = false; + fear_walkto_x = -999999; + fear_walkto_y = -999999; + fear_walkto_z = -999999; + curfp = false; + flee_timer.Start(); + + permarooted = (runspeed > 0) ? false : true; + + movetimercompleted = false; + roamer = false; + rooted = false; + charmed = false; + has_virus = false; + for (i=0; iCharmed()) + GetPet()->BuffFadeByEffect(SE_Charm); + else + SetPet(0); + } + + EQApplicationPacket app; + CreateDespawnPacket(&app, !IsCorpse()); + Corpse* corpse = entity_list.GetCorpseByID(GetID()); + if(!corpse || (corpse && !corpse->IsPlayerCorpse())) + entity_list.QueueClients(this, &app, true); + + entity_list.RemoveFromTargets(this, true); + + if(trade) { + Mob *with = trade->With(); + if(with && with->IsClient()) { + with->CastToClient()->FinishTrade(with); + with->trade->Reset(); + } + delete trade; + } + + if(HadTempPets()){ + entity_list.DestroyTempPets(this); + } + entity_list.UnMarkNPC(GetID()); + safe_delete(PathingLOSCheckTimer); + safe_delete(PathingRouteUpdateTimerShort); + safe_delete(PathingRouteUpdateTimerLong); + UninitializeBuffSlots(); +} + +uint32 Mob::GetAppearanceValue(EmuAppearance iAppearance) { + switch (iAppearance) { + // 0 standing, 1 sitting, 2 ducking, 3 lieing down, 4 looting + case eaStanding: { + return ANIM_STAND; + } + case eaSitting: { + return ANIM_SIT; + } + case eaCrouching: { + return ANIM_CROUCH; + } + case eaDead: { + return ANIM_DEATH; + } + case eaLooting: { + return ANIM_LOOT; + } + //to shup up compiler: + case _eaMaxAppearance: + break; + } + return(ANIM_STAND); +} + +void Mob::SetInvisible(uint8 state) +{ + invisible = state; + SendAppearancePacket(AT_Invis, invisible); + // Invis and hide breaks charms + + if ((this->GetPetType() == petCharmed) && (invisible || hidden || improved_hidden)) + { + Mob* formerpet = this->GetPet(); + + if(formerpet) + formerpet->BuffFadeByEffect(SE_Charm); + } +} + +//check to see if `this` is invisible to `other` +bool Mob::IsInvisible(Mob* other) const +{ + if(!other) + return(false); + + uint8 SeeInvisBonus = 0; + if (IsClient()) + SeeInvisBonus = aabonuses.SeeInvis; + + //check regular invisibility + if (invisible && invisible > (other->SeeInvisible())) + return true; + + //check invis vs. undead + if (other->GetBodyType() == BT_Undead || other->GetBodyType() == BT_SummonedUndead) { + if(invisible_undead && !other->SeeInvisibleUndead()) + return true; + } + + //check invis vs. animals... + if (other->GetBodyType() == BT_Animal){ + if(invisible_animals && !other->SeeInvisible()) + return true; + } + + if(hidden){ + if(!other->see_hide && !other->see_improved_hide){ + return true; + } + } + + if(improved_hidden){ + if(!other->see_improved_hide){ + return true; + } + } + + //handle sneaking + if(sneaking) { + if(BehindMob(other, GetX(), GetY()) ) + return true; + } + + return(false); +} + +float Mob::_GetMovementSpeed(int mod) const +{ + // List of movement speed modifiers, including AAs & spells: + // http://everquest.allakhazam.com/db/item.html?item=1721;page=1;howmany=50#m10822246245352 + if (IsRooted()) + return 0.0f; + + float speed_mod = runspeed; + + // These two cases ignore the cap, be wise in the DB for horses. + if (IsClient()) { + if (CastToClient()->GetGMSpeed()) { + speed_mod = 3.125f; + if (mod != 0) + speed_mod += speed_mod * static_cast(mod) / 100.0f; + return speed_mod; + } else { + Mob *horse = entity_list.GetMob(CastToClient()->GetHorseId()); + if (horse) { + speed_mod = horse->GetBaseRunspeed(); + if (mod != 0) + speed_mod += speed_mod * static_cast(mod) / 100.0f; + return speed_mod; + } + } + } + + int aa_mod = 0; + int spell_mod = 0; + int runspeedcap = RuleI(Character,BaseRunSpeedCap); + int movemod = 0; + float frunspeedcap = 0.0f; + + runspeedcap += itembonuses.IncreaseRunSpeedCap + spellbonuses.IncreaseRunSpeedCap + aabonuses.IncreaseRunSpeedCap; + aa_mod += itembonuses.BaseMovementSpeed + spellbonuses.BaseMovementSpeed + aabonuses.BaseMovementSpeed; + spell_mod += spellbonuses.movementspeed + itembonuses.movementspeed; + + // hard cap + if (runspeedcap > 225) + runspeedcap = 225; + + if (spell_mod < 0) + movemod += spell_mod; + else if (spell_mod > aa_mod) + movemod = spell_mod; + else + movemod = aa_mod; + + // cap negative movemods from snares mostly + if (movemod < -85) + movemod = -85; + + if (movemod != 0) + speed_mod += speed_mod * static_cast(movemod) / 100.0f; + + // runspeed caps + frunspeedcap = static_cast(runspeedcap) / 100.0f; + if (IsClient() && speed_mod > frunspeedcap) + speed_mod = frunspeedcap; + + // apply final mod such as the -47 for walking + // use runspeed since it should stack with snares + // and if we get here, we know runspeed was the initial + // value before we applied movemod. + if (mod != 0) + speed_mod += runspeed * static_cast(mod) / 100.0f; + + if (speed_mod <= 0.0f) + speed_mod = IsClient() ? 0.0001f : 0.0f; + + return speed_mod; +} + +int32 Mob::CalcMaxMana() { + switch (GetCasterClass()) { + case 'I': + max_mana = (((GetINT()/2)+1) * GetLevel()) + spellbonuses.Mana + itembonuses.Mana; + break; + case 'W': + max_mana = (((GetWIS()/2)+1) * GetLevel()) + spellbonuses.Mana + itembonuses.Mana; + break; + case 'N': + default: + max_mana = 0; + break; + } + if (max_mana < 0) { + max_mana = 0; + } + + return max_mana; +} + +int32 Mob::CalcMaxHP() { + max_hp = (base_hp + itembonuses.HP + spellbonuses.HP); + max_hp += max_hp * ((aabonuses.MaxHPChange + spellbonuses.MaxHPChange + itembonuses.MaxHPChange) / 10000.0f); + return max_hp; +} + +int32 Mob::GetItemHPBonuses() { + int32 item_hp = 0; + item_hp = itembonuses.HP; + item_hp += item_hp * itembonuses.MaxHPChange / 10000; + return item_hp; +} + +int32 Mob::GetSpellHPBonuses() { + int32 spell_hp = 0; + spell_hp = spellbonuses.HP; + spell_hp += spell_hp * spellbonuses.MaxHPChange / 10000; + return spell_hp; +} + +char Mob::GetCasterClass() const { + switch(class_) + { + case CLERIC: + case PALADIN: + case RANGER: + case DRUID: + case SHAMAN: + case BEASTLORD: + case CLERICGM: + case PALADINGM: + case RANGERGM: + case DRUIDGM: + case SHAMANGM: + case BEASTLORDGM: + return 'W'; + break; + + case SHADOWKNIGHT: + case BARD: + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + case SHADOWKNIGHTGM: + case BARDGM: + case NECROMANCERGM: + case WIZARDGM: + case MAGICIANGM: + case ENCHANTERGM: + return 'I'; + break; + + default: + return 'N'; + break; + } +} + +uint8 Mob::GetArchetype() const { + switch(class_) + { + case PALADIN: + case RANGER: + case SHADOWKNIGHT: + case BARD: + case BEASTLORD: + case PALADINGM: + case RANGERGM: + case SHADOWKNIGHTGM: + case BARDGM: + case BEASTLORDGM: + return ARCHETYPE_HYBRID; + break; + case CLERIC: + case DRUID: + case SHAMAN: + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + case CLERICGM: + case DRUIDGM: + case SHAMANGM: + case NECROMANCERGM: + case WIZARDGM: + case MAGICIANGM: + case ENCHANTERGM: + return ARCHETYPE_CASTER; + break; + case WARRIOR: + case MONK: + case ROGUE: + case BERSERKER: + case WARRIORGM: + case MONKGM: + case ROGUEGM: + case BERSERKERGM: + return ARCHETYPE_MELEE; + break; + default: + return ARCHETYPE_HYBRID; + break; + } +} + +void Mob::CreateSpawnPacket(EQApplicationPacket* app, Mob* ForWho) { + app->SetOpcode(OP_NewSpawn); + app->size = sizeof(NewSpawn_Struct); + app->pBuffer = new uchar[app->size]; + memset(app->pBuffer, 0, app->size); + NewSpawn_Struct* ns = (NewSpawn_Struct*)app->pBuffer; + FillSpawnStruct(ns, ForWho); + + if(strlen(ns->spawn.lastName) == 0) { + switch(ns->spawn.class_) + { + case TRIBUTE_MASTER: + strcpy(ns->spawn.lastName, "Tribute Master"); + break; + case ADVENTURERECRUITER: + strcpy(ns->spawn.lastName, "Adventure Recruiter"); + break; + case BANKER: + strcpy(ns->spawn.lastName, "Banker"); + break; + case ADVENTUREMERCHANT: + strcpy(ns->spawn.lastName,"Adventure Merchant"); + break; + case WARRIORGM: + strcpy(ns->spawn.lastName, "GM Warrior"); + break; + case PALADINGM: + strcpy(ns->spawn.lastName, "GM Paladin"); + break; + case RANGERGM: + strcpy(ns->spawn.lastName, "GM Ranger"); + break; + case SHADOWKNIGHTGM: + strcpy(ns->spawn.lastName, "GM Shadowknight"); + break; + case DRUIDGM: + strcpy(ns->spawn.lastName, "GM Druid"); + break; + case BARDGM: + strcpy(ns->spawn.lastName, "GM Bard"); + break; + case ROGUEGM: + strcpy(ns->spawn.lastName, "GM Rogue"); + break; + case SHAMANGM: + strcpy(ns->spawn.lastName, "GM Shaman"); + break; + case NECROMANCERGM: + strcpy(ns->spawn.lastName, "GM Necromancer"); + break; + case WIZARDGM: + strcpy(ns->spawn.lastName, "GM Wizard"); + break; + case MAGICIANGM: + strcpy(ns->spawn.lastName, "GM Magician"); + break; + case ENCHANTERGM: + strcpy(ns->spawn.lastName, "GM Enchanter"); + break; + case BEASTLORDGM: + strcpy(ns->spawn.lastName, "GM Beastlord"); + break; + case BERSERKERGM: + strcpy(ns->spawn.lastName, "GM Berserker"); + break; + case MERCERNARY_MASTER: + strcpy(ns->spawn.lastName, "Mercenary Recruiter"); + break; + default: + break; + } + } +} + +void Mob::CreateSpawnPacket(EQApplicationPacket* app, NewSpawn_Struct* ns) { + app->SetOpcode(OP_NewSpawn); + app->size = sizeof(NewSpawn_Struct); + + app->pBuffer = new uchar[sizeof(NewSpawn_Struct)]; + + // Copy ns directly into packet + memcpy(app->pBuffer, ns, sizeof(NewSpawn_Struct)); + + // Custom packet data + NewSpawn_Struct* ns2 = (NewSpawn_Struct*)app->pBuffer; + strcpy(ns2->spawn.name, ns->spawn.name); + switch(ns->spawn.class_) + { + case TRIBUTE_MASTER: + strcpy(ns2->spawn.lastName, "Tribute Master"); + break; + case ADVENTURERECRUITER: + strcpy(ns2->spawn.lastName, "Adventure Recruiter"); + break; + case BANKER: + strcpy(ns2->spawn.lastName, "Banker"); + break; + case ADVENTUREMERCHANT: + strcpy(ns->spawn.lastName,"Adventure Merchant"); + break; + case WARRIORGM: + strcpy(ns2->spawn.lastName, "GM Warrior"); + break; + case PALADINGM: + strcpy(ns2->spawn.lastName, "GM Paladin"); + break; + case RANGERGM: + strcpy(ns2->spawn.lastName, "GM Ranger"); + break; + case SHADOWKNIGHTGM: + strcpy(ns2->spawn.lastName, "GM Shadowknight"); + break; + case DRUIDGM: + strcpy(ns2->spawn.lastName, "GM Druid"); + break; + case BARDGM: + strcpy(ns2->spawn.lastName, "GM Bard"); + break; + case ROGUEGM: + strcpy(ns2->spawn.lastName, "GM Rogue"); + break; + case SHAMANGM: + strcpy(ns2->spawn.lastName, "GM Shaman"); + break; + case NECROMANCERGM: + strcpy(ns2->spawn.lastName, "GM Necromancer"); + break; + case WIZARDGM: + strcpy(ns2->spawn.lastName, "GM Wizard"); + break; + case MAGICIANGM: + strcpy(ns2->spawn.lastName, "GM Magician"); + break; + case ENCHANTERGM: + strcpy(ns2->spawn.lastName, "GM Enchanter"); + break; + case BEASTLORDGM: + strcpy(ns2->spawn.lastName, "GM Beastlord"); + break; + case BERSERKERGM: + strcpy(ns2->spawn.lastName, "GM Berserker"); + break; + case MERCERNARY_MASTER: + strcpy(ns->spawn.lastName, "Mercenary Recruiter"); + break; + default: + strcpy(ns2->spawn.lastName, ns->spawn.lastName); + break; + } + + memset(&app->pBuffer[sizeof(Spawn_Struct)-7], 0xFF, 7); +} + +void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) +{ + int i; + + strcpy(ns->spawn.name, name); + if(IsClient()) { + strn0cpy(ns->spawn.lastName, lastname, sizeof(ns->spawn.lastName)); + } + + ns->spawn.heading = FloatToEQ19(heading); + ns->spawn.x = FloatToEQ19(x_pos);//((int32)x_pos)<<3; + ns->spawn.y = FloatToEQ19(y_pos);//((int32)y_pos)<<3; + ns->spawn.z = FloatToEQ19(z_pos);//((int32)z_pos)<<3; + ns->spawn.spawnId = GetID(); + ns->spawn.curHp = static_cast(GetHPRatio()); + ns->spawn.max_hp = 100; //this field needs a better name + ns->spawn.race = race; + ns->spawn.runspeed = runspeed; + ns->spawn.walkspeed = runspeed * 0.5f; + ns->spawn.class_ = class_; + ns->spawn.gender = gender; + ns->spawn.level = level; + ns->spawn.deity = deity; + ns->spawn.animation = 0; + ns->spawn.findable = findable?1:0; + ns->spawn.light = light; + ns->spawn.showhelm = 1; + + ns->spawn.invis = (invisible || hidden) ? 1 : 0; // TODO: load this before spawning players + ns->spawn.NPC = IsClient() ? 0 : 1; + ns->spawn.IsMercenary = (IsMerc() || no_target_hotkey) ? 1 : 0; + + ns->spawn.petOwnerId = ownerid; + + ns->spawn.haircolor = haircolor; + ns->spawn.beardcolor = beardcolor; + ns->spawn.eyecolor1 = eyecolor1; + ns->spawn.eyecolor2 = eyecolor2; + ns->spawn.hairstyle = hairstyle; + ns->spawn.face = luclinface; + ns->spawn.beard = beard; + ns->spawn.StandState = GetAppearanceValue(_appearance); + ns->spawn.drakkin_heritage = drakkin_heritage; + ns->spawn.drakkin_tattoo = drakkin_tattoo; + ns->spawn.drakkin_details = drakkin_details; + ns->spawn.equip_chest2 = texture; + +// ns->spawn.invis2 = 0xff;//this used to be labeled beard.. if its not FF it will turn mob invis + + if(helmtexture && helmtexture != 0xFF) + { + ns->spawn.helm=helmtexture; + } else { + ns->spawn.helm = 0; + } + + ns->spawn.guildrank = 0xFF; + ns->spawn.size = size; + ns->spawn.bodytype = bodytype; + // The 'flymode' settings have the following effect: + // 0 - Mobs in water sink like a stone to the bottom + // 1 - Same as #flymode 1 + // 2 - Same as #flymode 2 + // 3 - Mobs in water do not sink. A value of 3 in this field appears to be the default setting for all mobs + // (in water or not) according to 6.2 era packet collects. + if(IsClient()) + { + ns->spawn.flymode = FindType(SE_Levitate) ? 2 : 0; + } + else + ns->spawn.flymode = flymode; + + ns->spawn.lastName[0] = '\0'; + + strn0cpy(ns->spawn.lastName, lastname, sizeof(ns->spawn.lastName)); + + for(i = 0; i < _MaterialCount; i++) + { + ns->spawn.equipment[i] = GetEquipmentMaterial(i); + if (armor_tint[i]) + { + ns->spawn.colors[i].color = armor_tint[i]; + } + else + { + ns->spawn.colors[i].color = GetEquipmentColor(i); + } + } + + memset(ns->spawn.set_to_0xFF, 0xFF, sizeof(ns->spawn.set_to_0xFF)); + if(IsNPC() && IsDestructibleObject()) + { + ns->spawn.DestructibleObject = true; + + // Changing the first string made it vanish, so it has some significance. + if(lastname) + sprintf(ns->spawn.DestructibleModel, "%s", lastname); + // Changing the second string made no visible difference + sprintf(ns->spawn.DestructibleName2, "%s", ns->spawn.name); + // Putting a string in the final one that was previously empty had no visible effect. + sprintf(ns->spawn.DestructibleString, ""); + + // Sets damage appearance level of the object. + ns->spawn.DestructibleAppearance = luclinface; // Was 0x00000000 + //ns->spawn.DestructibleAppearance = static_cast(_appearance); + // #appearance 44 1 makes it jump but no visible damage + // #appearance 44 2 makes it look completely broken but still visible + // #appearnace 44 3 makes it jump but not visible difference to 3 + // #appearance 44 4 makes it disappear altogether + // #appearance 44 5 makes the client crash. + + ns->spawn.DestructibleUnk1 = 0x00000224; // Was 0x000001f5; + // These next 4 are mostly always sequential + // Originally they were 633, 634, 635, 636 + // Changing them all to 633 - no visible effect. + // Changing them all to 636 - no visible effect. + // Reversing the order of these four numbers and then using #appearance gain had no visible change. + // Setting these four ids to zero had no visible effect when the catapult spawned, nor when #appearance was used. + ns->spawn.DestructibleID1 = 1968; + ns->spawn.DestructibleID2 = 1969; + ns->spawn.DestructibleID3 = 1970; + ns->spawn.DestructibleID4 = 1971; + // Next one was originally 0x1ce45008, changing it to 0x00000000 made no visible difference + ns->spawn.DestructibleUnk2 = 0x13f79d00; + // Next one was originally 0x1a68fe30, changing it to 0x00000000 made no visible difference + ns->spawn.DestructibleUnk3 = 0x00000000; + // Next one was already 0x00000000 + ns->spawn.DestructibleUnk4 = 0x13f79d58; + // Next one was originally 0x005a69ec, changing it to 0x00000000 made no visible difference. + ns->spawn.DestructibleUnk5 = 0x13c55b00; + // Next one was originally 0x1a68fe30, changing it to 0x00000000 made no visible difference. + ns->spawn.DestructibleUnk6 = 0x00128860; + // Next one was originally 0x0059de6d, changing it to 0x00000000 made no visible difference. + ns->spawn.DestructibleUnk7 = 0x005a8f66; + // Next one was originally 0x00000201, changing it to 0x00000000 made no visible difference. + // For the Minohten tents, 0x00000000 had them up in the air, while 0x201 put them on the ground. + // Changing it it 0x00000001 makes the tent sink into the ground. + ns->spawn.DestructibleUnk8 = 0x01; // Needs to be 1 for tents? + ns->spawn.DestructibleUnk9 = 0x00000002; // Needs to be 2 for tents? + + ns->spawn.flymode = 0; + } +} + +void Mob::CreateDespawnPacket(EQApplicationPacket* app, bool Decay) +{ + app->SetOpcode(OP_DeleteSpawn); + app->size = sizeof(DeleteSpawn_Struct); + app->pBuffer = new uchar[app->size]; + memset(app->pBuffer, 0, app->size); + DeleteSpawn_Struct* ds = (DeleteSpawn_Struct*)app->pBuffer; + ds->spawn_id = GetID(); + // The next field only applies to corpses. If 0, they vanish instantly, otherwise they 'decay' + ds->Decay = Decay ? 1 : 0; +} + +void Mob::CreateHPPacket(EQApplicationPacket* app) +{ + this->IsFullHP=(cur_hp>=max_hp); + app->SetOpcode(OP_MobHealth); + app->size = sizeof(SpawnHPUpdate_Struct2); + app->pBuffer = new uchar[app->size]; + memset(app->pBuffer, 0, sizeof(SpawnHPUpdate_Struct2)); + SpawnHPUpdate_Struct2* ds = (SpawnHPUpdate_Struct2*)app->pBuffer; + + ds->spawn_id = GetID(); + // they don't need to know the real hp + ds->hp = (int)GetHPRatio(); + + // hp event + if (IsNPC() && (GetNextHPEvent() > 0)) + { + if (ds->hp < GetNextHPEvent()) + { + char buf[10]; + snprintf(buf, 9, "%i", GetNextHPEvent()); + buf[9] = '\0'; + SetNextHPEvent(-1); + parse->EventNPC(EVENT_HP, CastToNPC(), nullptr, buf, 0); + } + } + + if (IsNPC() && (GetNextIncHPEvent() > 0)) + { + if (ds->hp > GetNextIncHPEvent()) + { + char buf[10]; + snprintf(buf, 9, "%i", GetNextIncHPEvent()); + buf[9] = '\0'; + SetNextIncHPEvent(-1); + parse->EventNPC(EVENT_HP, CastToNPC(), nullptr, buf, 1); + } + } +} + +// sends hp update of this mob to people who might care +void Mob::SendHPUpdate() +{ + EQApplicationPacket hp_app; + Group *group; + + // destructor will free the pBuffer + CreateHPPacket(&hp_app); + + // send to people who have us targeted + entity_list.QueueClientsByTarget(this, &hp_app, false, 0, false, true, BIT_AllClients); + entity_list.QueueClientsByXTarget(this, &hp_app, false); + entity_list.QueueToGroupsForNPCHealthAA(this, &hp_app); + + // send to group + if(IsGrouped()) + { + group = entity_list.GetGroupByMob(this); + if(group) //not sure why this might be null, but it happens + group->SendHPPacketsFrom(this); + } + + if(IsClient()){ + Raid *r = entity_list.GetRaidByClient(CastToClient()); + if(r){ + r->SendHPPacketsFrom(this); + } + } + + // send to master + if(GetOwner() && GetOwner()->IsClient()) + { + GetOwner()->CastToClient()->QueuePacket(&hp_app, false); + group = entity_list.GetGroupByClient(GetOwner()->CastToClient()); + if(group) + group->SendHPPacketsFrom(this); + Raid *r = entity_list.GetRaidByClient(GetOwner()->CastToClient()); + if(r) + r->SendHPPacketsFrom(this); + } + + // send to pet + if(GetPet() && GetPet()->IsClient()) + { + GetPet()->CastToClient()->QueuePacket(&hp_app, false); + } + + // Update the damage state of destructible objects + if(IsNPC() && IsDestructibleObject()) + { + if (GetHPRatio() > 74) + { + if (GetAppearance() != eaStanding) + { + SendAppearancePacket(AT_DamageState, eaStanding); + _appearance = eaStanding; + } + } + else if (GetHPRatio() > 49) + { + if (GetAppearance() != eaSitting) + { + SendAppearancePacket(AT_DamageState, eaSitting); + _appearance = eaSitting; + } + } + else if (GetHPRatio() > 24) + { + if (GetAppearance() != eaCrouching) + { + SendAppearancePacket(AT_DamageState, eaCrouching); + _appearance = eaCrouching; + } + } + else if (GetHPRatio() > 0) + { + if (GetAppearance() != eaDead) + { + SendAppearancePacket(AT_DamageState, eaDead); + _appearance = eaDead; + } + } + else if (GetAppearance() != eaLooting) + { + SendAppearancePacket(AT_DamageState, eaLooting); + _appearance = eaLooting; + } + } + + // send to self - we need the actual hps here + if(IsClient()) + { + EQApplicationPacket* hp_app2 = new EQApplicationPacket(OP_HPUpdate,sizeof(SpawnHPUpdate_Struct)); + SpawnHPUpdate_Struct* ds = (SpawnHPUpdate_Struct*)hp_app2->pBuffer; + ds->cur_hp = CastToClient()->GetHP() - itembonuses.HP; + ds->spawn_id = GetID(); + ds->max_hp = CastToClient()->GetMaxHP() - itembonuses.HP; + CastToClient()->QueuePacket(hp_app2); + safe_delete(hp_app2); + } +} + +// this one just warps the mob to the current location +void Mob::SendPosition() +{ + EQApplicationPacket* app = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); + PlayerPositionUpdateServer_Struct* spu = (PlayerPositionUpdateServer_Struct*)app->pBuffer; + MakeSpawnUpdateNoDelta(spu); + move_tic_count = 0; + entity_list.QueueClients(this, app, true); + safe_delete(app); +} + +// this one is for mobs on the move, with deltas - this makes them walk +void Mob::SendPosUpdate(uint8 iSendToSelf) { + EQApplicationPacket* app = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); + PlayerPositionUpdateServer_Struct* spu = (PlayerPositionUpdateServer_Struct*)app->pBuffer; + MakeSpawnUpdate(spu); + + if (iSendToSelf == 2) { + if (this->IsClient()) + this->CastToClient()->FastQueuePacket(&app,false); + } + else + { + if(move_tic_count == RuleI(Zone, NPCPositonUpdateTicCount)) + { + entity_list.QueueClients(this, app, (iSendToSelf==0), false); + move_tic_count = 0; + } + else + { + entity_list.QueueCloseClients(this, app, (iSendToSelf==0), 800, nullptr, false); + move_tic_count++; + } + } + safe_delete(app); +} + +// this is for SendPosition() +void Mob::MakeSpawnUpdateNoDelta(PlayerPositionUpdateServer_Struct *spu){ + memset(spu,0xff,sizeof(PlayerPositionUpdateServer_Struct)); + spu->spawn_id = GetID(); + spu->x_pos = FloatToEQ19(x_pos); + spu->y_pos = FloatToEQ19(y_pos); + spu->z_pos = FloatToEQ19(z_pos); + spu->delta_x = NewFloatToEQ13(0); + spu->delta_y = NewFloatToEQ13(0); + spu->delta_z = NewFloatToEQ13(0); + spu->heading = FloatToEQ19(heading); + spu->animation = 0; + spu->delta_heading = NewFloatToEQ13(0); + spu->padding0002 =0; + spu->padding0006 =7; + spu->padding0014 =0x7f; + spu->padding0018 =0x5df27; + +} + +// this is for SendPosUpdate() +void Mob::MakeSpawnUpdate(PlayerPositionUpdateServer_Struct* spu) { + spu->spawn_id = GetID(); + spu->x_pos = FloatToEQ19(x_pos); + spu->y_pos = FloatToEQ19(y_pos); + spu->z_pos = FloatToEQ19(z_pos); + spu->delta_x = NewFloatToEQ13(delta_x); + spu->delta_y = NewFloatToEQ13(delta_y); + spu->delta_z = NewFloatToEQ13(delta_z); + spu->heading = FloatToEQ19(heading); + spu->padding0002 =0; + spu->padding0006 =7; + spu->padding0014 =0x7f; + spu->padding0018 =0x5df27; + if(this->IsClient()) + spu->animation = animation; + else + spu->animation = pRunAnimSpeed;//animation; + spu->delta_heading = NewFloatToEQ13(static_cast(delta_heading)); +} + +void Mob::ShowStats(Client* client) +{ + if (IsClient()) { + CastToClient()->SendStatsWindow(client, RuleB(Character, UseNewStatsWindow)); + } + else if (IsCorpse()) { + if (IsPlayerCorpse()) { + client->Message(0, " CharID: %i PlayerCorpse: %i", CastToCorpse()->GetCharID(), CastToCorpse()->GetDBID()); + } + else { + client->Message(0, " NPCCorpse", GetID()); + } + } + else { + client->Message(0, " Level: %i AC: %i Class: %i Size: %1.1f Haste: %i", GetLevel(), GetAC(), GetClass(), GetSize(), GetHaste()); + client->Message(0, " HP: %i Max HP: %i",GetHP(), GetMaxHP()); + client->Message(0, " Mana: %i Max Mana: %i", GetMana(), GetMaxMana()); + client->Message(0, " Total ATK: %i Worn/Spell ATK (Cap %i): %i", GetATK(), RuleI(Character, ItemATKCap), GetATKBonus()); + client->Message(0, " STR: %i STA: %i DEX: %i AGI: %i INT: %i WIS: %i CHA: %i", GetSTR(), GetSTA(), GetDEX(), GetAGI(), GetINT(), GetWIS(), GetCHA()); + client->Message(0, " MR: %i PR: %i FR: %i CR: %i DR: %i Corruption: %i", GetMR(), GetPR(), GetFR(), GetCR(), GetDR(), GetCorrup()); + client->Message(0, " Race: %i BaseRace: %i Texture: %i HelmTexture: %i Gender: %i BaseGender: %i", GetRace(), GetBaseRace(), GetTexture(), GetHelmTexture(), GetGender(), GetBaseGender()); + if (client->Admin() >= 100) + client->Message(0, " EntityID: %i PetID: %i OwnerID: %i AIControlled: %i Targetted: %i", GetID(), GetPetID(), GetOwnerID(), IsAIControlled(), targeted); + + if (IsNPC()) { + NPC *n = CastToNPC(); + uint32 spawngroupid = 0; + if(n->respawn2 != 0) + spawngroupid = n->respawn2->SpawnGroupID(); + client->Message(0, " NPCID: %u SpawnGroupID: %u Grid: %i LootTable: %u FactionID: %i SpellsID: %u ", GetNPCTypeID(),spawngroupid, n->GetGrid(), n->GetLoottableID(), n->GetNPCFactionID(), n->GetNPCSpellsID()); + client->Message(0, " Accuracy: %i MerchantID: %i EmoteID: %i Runspeed: %f Walkspeed: %f", n->GetAccuracyRating(), n->MerchantType, n->GetEmoteID(), n->GetRunspeed(), n->GetWalkspeed()); + n->QueryLoot(client); + } + if (IsAIControlled()) { + client->Message(0, " AggroRange: %1.0f AssistRange: %1.0f", GetAggroRange(), GetAssistRange()); + } + } +} + +void Mob::DoAnim(const int animnum, int type, bool ackreq, eqFilterType filter) { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Animation, sizeof(Animation_Struct)); + Animation_Struct* anim = (Animation_Struct*)outapp->pBuffer; + anim->spawnid = GetID(); + if(type == 0){ + anim->action = 10; + anim->value=animnum; + } + else{ + anim->action = animnum; + anim->value=type; + } + entity_list.QueueCloseClients(this, outapp, false, 200, 0, ackreq, filter); + safe_delete(outapp); +} + +void Mob::ShowBuffs(Client* client) { + if(SPDAT_RECORDS <= 0) + return; + client->Message(0, "Buffs on: %s", this->GetName()); + uint32 i; + uint32 buff_count = GetMaxTotalSlots(); + for (i=0; i < buff_count; i++) { + if (buffs[i].spellid != SPELL_UNKNOWN) { + if (spells[buffs[i].spellid].buffdurationformula == DF_Permanent) + client->Message(0, " %i: %s: Permanent", i, spells[buffs[i].spellid].name); + else + client->Message(0, " %i: %s: %i tics left", i, spells[buffs[i].spellid].name, buffs[i].ticsremaining); + + } + } + if (IsClient()){ + client->Message(0, "itembonuses:"); + client->Message(0, "Atk:%i Ac:%i HP(%i):%i Mana:%i", itembonuses.ATK, itembonuses.AC, itembonuses.HPRegen, itembonuses.HP, itembonuses.Mana); + client->Message(0, "Str:%i Sta:%i Dex:%i Agi:%i Int:%i Wis:%i Cha:%i", + itembonuses.STR,itembonuses.STA,itembonuses.DEX,itembonuses.AGI,itembonuses.INT,itembonuses.WIS,itembonuses.CHA); + client->Message(0, "SvMagic:%i SvFire:%i SvCold:%i SvPoison:%i SvDisease:%i", + itembonuses.MR,itembonuses.FR,itembonuses.CR,itembonuses.PR,itembonuses.DR); + client->Message(0, "DmgShield:%i Haste:%i", itembonuses.DamageShield, itembonuses.haste ); + client->Message(0, "spellbonuses:"); + client->Message(0, "Atk:%i Ac:%i HP(%i):%i Mana:%i", spellbonuses.ATK, spellbonuses.AC, spellbonuses.HPRegen, spellbonuses.HP, spellbonuses.Mana); + client->Message(0, "Str:%i Sta:%i Dex:%i Agi:%i Int:%i Wis:%i Cha:%i", + spellbonuses.STR,spellbonuses.STA,spellbonuses.DEX,spellbonuses.AGI,spellbonuses.INT,spellbonuses.WIS,spellbonuses.CHA); + client->Message(0, "SvMagic:%i SvFire:%i SvCold:%i SvPoison:%i SvDisease:%i", + spellbonuses.MR,spellbonuses.FR,spellbonuses.CR,spellbonuses.PR,spellbonuses.DR); + client->Message(0, "DmgShield:%i Haste:%i", spellbonuses.DamageShield, spellbonuses.haste ); + } +} + +void Mob::ShowBuffList(Client* client) { + if(SPDAT_RECORDS <= 0) + return; + + client->Message(0, "Buffs on: %s", this->GetCleanName()); + uint32 i; + uint32 buff_count = GetMaxTotalSlots(); + for (i=0; i < buff_count; i++) { + if (buffs[i].spellid != SPELL_UNKNOWN) { + if (spells[buffs[i].spellid].buffdurationformula == DF_Permanent) + client->Message(0, " %i: %s: Permanent", i, spells[buffs[i].spellid].name); + else + client->Message(0, " %i: %s: %i tics left", i, spells[buffs[i].spellid].name, buffs[i].ticsremaining); + } + } +} + +void Mob::GMMove(float x, float y, float z, float heading, bool SendUpdate) { + + Route.clear(); + + if(IsNPC()) { + entity_list.ProcessMove(CastToNPC(), x, y, z); + } + + x_pos = x; + y_pos = y; + z_pos = z; + if (heading != 0.01) + this->heading = heading; + if(IsNPC()) + CastToNPC()->SaveGuardSpot(true); + if(SendUpdate) + SendPosition(); +} + +void Mob::SendIllusionPacket(uint16 in_race, uint8 in_gender, uint8 in_texture, uint8 in_helmtexture, uint8 in_haircolor, uint8 in_beardcolor, uint8 in_eyecolor1, uint8 in_eyecolor2, uint8 in_hairstyle, uint8 in_luclinface, uint8 in_beard, uint8 in_aa_title, uint32 in_drakkin_heritage, uint32 in_drakkin_tattoo, uint32 in_drakkin_details, float in_size) { + + uint16 BaseRace = GetBaseRace(); + + if (in_race == 0) { + this->race = BaseRace; + if (in_gender == 0xFF) + this->gender = GetBaseGender(); + else + this->gender = in_gender; + } + else { + this->race = in_race; + if (in_gender == 0xFF) { + uint8 tmp = Mob::GetDefaultGender(this->race, gender); + if (tmp == 2) + gender = 2; + else if (gender == 2 && GetBaseGender() == 2) + gender = tmp; + else if (gender == 2) + gender = GetBaseGender(); + } + else + gender = in_gender; + } + if (in_texture == 0xFF) { + if (in_race <= 12 || in_race == 128 || in_race == 130 || in_race == 330 || in_race == 522) + this->texture = 0xFF; + else + this->texture = GetTexture(); + } + else + this->texture = in_texture; + + if (in_helmtexture == 0xFF) { + if (in_race <= 12 || in_race == 128 || in_race == 130 || in_race == 330 || in_race == 522) + this->helmtexture = 0xFF; + else if (in_texture != 0xFF) + this->helmtexture = in_texture; + else + this->helmtexture = GetHelmTexture(); + } + else + this->helmtexture = in_helmtexture; + + if (in_haircolor == 0xFF) + this->haircolor = GetHairColor(); + else + this->haircolor = in_haircolor; + + if (in_beardcolor == 0xFF) + this->beardcolor = GetBeardColor(); + else + this->beardcolor = in_beardcolor; + + if (in_eyecolor1 == 0xFF) + this->eyecolor1 = GetEyeColor1(); + else + this->eyecolor1 = in_eyecolor1; + + if (in_eyecolor2 == 0xFF) + this->eyecolor2 = GetEyeColor2(); + else + this->eyecolor2 = in_eyecolor2; + + if (in_hairstyle == 0xFF) + this->hairstyle = GetHairStyle(); + else + this->hairstyle = in_hairstyle; + + if (in_luclinface == 0xFF) + this->luclinface = GetLuclinFace(); + else + this->luclinface = in_luclinface; + + if (in_beard == 0xFF) + this->beard = GetBeard(); + else + this->beard = in_beard; + + this->aa_title = 0xFF; + + if (in_drakkin_heritage == 0xFFFFFFFF) + this->drakkin_heritage = GetDrakkinHeritage(); + else + this->drakkin_heritage = in_drakkin_heritage; + + if (in_drakkin_tattoo == 0xFFFFFFFF) + this->drakkin_tattoo = GetDrakkinTattoo(); + else + this->drakkin_tattoo = in_drakkin_tattoo; + + if (in_drakkin_details == 0xFFFFFFFF) + this->drakkin_details = GetDrakkinDetails(); + else + this->drakkin_details = in_drakkin_details; + + if (in_size == 0xFFFFFFFF) + this->size = GetSize(); + else + this->size = in_size; + + // Forces the feature information to be pulled from the Player Profile + if (this->IsClient() && in_race == 0) { + this->race = CastToClient()->GetBaseRace(); + this->gender = CastToClient()->GetBaseGender(); + this->texture = 0xFF; + this->helmtexture = 0xFF; + this->haircolor = CastToClient()->GetBaseHairColor(); + this->beardcolor = CastToClient()->GetBaseBeardColor(); + this->eyecolor1 = CastToClient()->GetBaseEyeColor(); + this->eyecolor2 = CastToClient()->GetBaseEyeColor(); + this->hairstyle = CastToClient()->GetBaseHairStyle(); + this->luclinface = CastToClient()->GetBaseFace(); + this->beard = CastToClient()->GetBaseBeard(); + this->aa_title = 0xFF; + this->drakkin_heritage = CastToClient()->GetBaseHeritage(); + this->drakkin_tattoo = CastToClient()->GetBaseTattoo(); + this->drakkin_details = CastToClient()->GetBaseDetails(); + switch(race){ + case OGRE: + this->size = 9; + break; + case TROLL: + this->size = 8; + break; + case VAHSHIR: + case BARBARIAN: + this->size = 7; + break; + case HALF_ELF: + case WOOD_ELF: + case DARK_ELF: + case FROGLOK: + this->size = 5; + break; + case DWARF: + this->size = 4; + break; + case HALFLING: + case GNOME: + this->size = 3; + break; + default: + this->size = 6; + break; + } + } + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Illusion, sizeof(Illusion_Struct)); + memset(outapp->pBuffer, 0, sizeof(outapp->pBuffer)); + Illusion_Struct* is = (Illusion_Struct*) outapp->pBuffer; + is->spawnid = this->GetID(); + strcpy(is->charname, GetCleanName()); + is->race = this->race; + is->gender = this->gender; + is->texture = this->texture; + is->helmtexture = this->helmtexture; + is->haircolor = this->haircolor; + is->beardcolor = this->beardcolor; + is->beard = this->beard; + is->eyecolor1 = this->eyecolor1; + is->eyecolor2 = this->eyecolor2; + is->hairstyle = this->hairstyle; + is->face = this->luclinface; + //is->aa_title = this->aa_title; + is->drakkin_heritage = this->drakkin_heritage; + is->drakkin_tattoo = this->drakkin_tattoo; + is->drakkin_details = this->drakkin_details; + is->size = this->size; + + entity_list.QueueClients(this, outapp); + safe_delete(outapp); + mlog(CLIENT__SPELLS, "Illusion: Race = %i, Gender = %i, Texture = %i, HelmTexture = %i, HairColor = %i, BeardColor = %i, EyeColor1 = %i, EyeColor2 = %i, HairStyle = %i, Face = %i, DrakkinHeritage = %i, DrakkinTattoo = %i, DrakkinDetails = %i, Size = %f", + this->race, this->gender, this->texture, this->helmtexture, this->haircolor, this->beardcolor, this->eyecolor1, this->eyecolor2, this->hairstyle, this->luclinface, this->drakkin_heritage, this->drakkin_tattoo, this->drakkin_details, this->size); +} + +uint8 Mob::GetDefaultGender(uint16 in_race, uint8 in_gender) { +//std::cout << "Gender in: " << (int)in_gender << std::endl; // undefined cout [CODEBUG] + if ((in_race > 0 && in_race <= GNOME ) + || in_race == IKSAR || in_race == VAHSHIR || in_race == FROGLOK || in_race == DRAKKIN + || in_race == 15 || in_race == 50 || in_race == 57 || in_race == 70 || in_race == 98 || in_race == 118) { + if (in_gender >= 2) { + // Female default for PC Races + return 1; + } + else + return in_gender; + } + else if (in_race == 44 || in_race == 52 || in_race == 55 || in_race == 65 || in_race == 67 || in_race == 88 || in_race == 117 || in_race == 127 || + in_race == 77 || in_race == 78 || in_race == 81 || in_race == 90 || in_race == 92 || in_race == 93 || in_race == 94 || in_race == 106 || in_race == 112 || in_race == 471) { + // Male only races + return 0; + + } + else if (in_race == 25 || in_race == 56) { + // Female only races + return 1; + } + else { + // Neutral default for NPC Races + return 2; + } +} + +void Mob::SendAppearancePacket(uint32 type, uint32 value, bool WholeZone, bool iIgnoreSelf, Client *specific_target) { + if (!GetID()) + return; + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); + SpawnAppearance_Struct* appearance = (SpawnAppearance_Struct*)outapp->pBuffer; + appearance->spawn_id = this->GetID(); + appearance->type = type; + appearance->parameter = value; + if (WholeZone) + entity_list.QueueClients(this, outapp, iIgnoreSelf); + else if(specific_target != nullptr) + specific_target->QueuePacket(outapp, false, Client::CLIENT_CONNECTED); + else if (this->IsClient()) + this->CastToClient()->QueuePacket(outapp, false, Client::CLIENT_CONNECTED); + safe_delete(outapp); +} + +void Mob::SendLevelAppearance(){ + EQApplicationPacket* outapp = new EQApplicationPacket(OP_LevelAppearance, sizeof(LevelAppearance_Struct)); + LevelAppearance_Struct* la = (LevelAppearance_Struct*)outapp->pBuffer; + la->parm1 = 0x4D; + la->parm2 = la->parm1 + 1; + la->parm3 = la->parm2 + 1; + la->parm4 = la->parm3 + 1; + la->parm5 = la->parm4 + 1; + la->spawn_id = GetID(); + la->value1a = 1; + la->value2a = 2; + la->value3a = 1; + la->value3b = 1; + la->value4a = 1; + la->value4b = 1; + la->value5a = 2; + entity_list.QueueCloseClients(this,outapp); + safe_delete(outapp); +} + +void Mob::SendStunAppearance() +{ + EQApplicationPacket* outapp = new EQApplicationPacket(OP_LevelAppearance, sizeof(LevelAppearance_Struct)); + LevelAppearance_Struct* la = (LevelAppearance_Struct*)outapp->pBuffer; + la->parm1 = 58; + la->parm2 = 60; + la->spawn_id = GetID(); + la->value1a = 2; + la->value1b = 0; + la->value2a = 2; + la->value2b = 0; + entity_list.QueueCloseClients(this,outapp); + safe_delete(outapp); +} + +void Mob::SendAppearanceEffect(uint32 parm1, uint32 parm2, uint32 parm3, uint32 parm4, uint32 parm5, Client *specific_target){ + EQApplicationPacket* outapp = new EQApplicationPacket(OP_LevelAppearance, sizeof(LevelAppearance_Struct)); + LevelAppearance_Struct* la = (LevelAppearance_Struct*)outapp->pBuffer; + la->spawn_id = GetID(); + la->parm1 = parm1; + la->parm2 = parm2; + la->parm3 = parm3; + la->parm4 = parm4; + la->parm5 = parm5; + // Note that setting the b values to 0 will disable the related effect from the corresponding parameter. + // Setting the a value appears to have no affect at all.s + la->value1a = 1; + la->value1b = 1; + la->value2a = 1; + la->value2b = 1; + la->value3a = 1; + la->value3b = 1; + la->value4a = 1; + la->value4b = 1; + la->value5a = 1; + la->value5b = 1; + if(specific_target == nullptr) { + entity_list.QueueClients(this,outapp); + } + else if (specific_target->IsClient()) { + specific_target->CastToClient()->QueuePacket(outapp, false); + } + safe_delete(outapp); +} + +void Mob::SendTargetable(bool on, Client *specific_target) { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Untargetable, sizeof(Untargetable_Struct)); + Untargetable_Struct *ut = (Untargetable_Struct*)outapp->pBuffer; + ut->id = GetID(); + ut->targetable_flag = on == true ? 1 : 0; + + if(specific_target == nullptr) { + entity_list.QueueClients(this, outapp); + } + else if (specific_target->IsClient()) { + specific_target->CastToClient()->QueuePacket(outapp, false); + } + safe_delete(outapp); +} + +void Mob::QuestReward(Client *c, uint32 silver, uint32 gold, uint32 platinum) { + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Sound, sizeof(QuestReward_Struct)); + memset(outapp->pBuffer, 0, sizeof(outapp->pBuffer)); + QuestReward_Struct* qr = (QuestReward_Struct*) outapp->pBuffer; + + qr->from_mob = GetID(); // Entity ID for the from mob name + qr->silver = silver; + qr->gold = gold; + qr->platinum = platinum; + + if(c) + c->QueuePacket(outapp, false, Client::CLIENT_CONNECTED); + + safe_delete(outapp); +} + +void Mob::CameraEffect(uint32 duration, uint32 intensity, Client *c, bool global) { + + + if(global == true) + { + ServerPacket* pack = new ServerPacket(ServerOP_CameraShake, sizeof(ServerCameraShake_Struct)); + memset(pack->pBuffer, 0, sizeof(pack->pBuffer)); + ServerCameraShake_Struct* scss = (ServerCameraShake_Struct*) pack->pBuffer; + scss->duration = duration; + scss->intensity = intensity; + worldserver.SendPacket(pack); + safe_delete(pack); + return; + } + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_CameraEffect, sizeof(Camera_Struct)); + memset(outapp->pBuffer, 0, sizeof(outapp->pBuffer)); + Camera_Struct* cs = (Camera_Struct*) outapp->pBuffer; + cs->duration = duration; // Duration in milliseconds + cs->intensity = ((intensity * 6710886) + 1023410176); // Intensity ranges from 1023410176 to 1090519040, so simplify it from 0 to 10. + + if(c) + c->QueuePacket(outapp, false, Client::CLIENT_CONNECTED); + else + entity_list.QueueClients(this, outapp); + + safe_delete(outapp); +} + +void Mob::SendSpellEffect(uint32 effectid, uint32 duration, uint32 finish_delay, bool zone_wide, uint32 unk020, bool perm_effect, Client *c) { + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpellEffect, sizeof(SpellEffect_Struct)); + memset(outapp->pBuffer, 0, sizeof(outapp->pBuffer)); + SpellEffect_Struct* se = (SpellEffect_Struct*) outapp->pBuffer; + se->EffectID = effectid; // ID of the Particle Effect + se->EntityID = GetID(); + se->EntityID2 = GetID(); // EntityID again + se->Duration = duration; // In Milliseconds + se->FinishDelay = finish_delay; // Seen 0 + se->Unknown020 = unk020; // Seen 3000 + se->Unknown024 = 1; // Seen 1 for SoD + se->Unknown025 = 1; // Seen 1 for Live + se->Unknown026 = 0; // Seen 1157 + + if(c) + c->QueuePacket(outapp, false, Client::CLIENT_CONNECTED); + else if(zone_wide) + entity_list.QueueClients(this, outapp); + else + entity_list.QueueCloseClients(this, outapp); + + safe_delete(outapp); + + if (perm_effect) { + if(!IsNimbusEffectActive(effectid)) { + SetNimbusEffect(effectid); + } + } + +} + +void Mob::TempName(const char *newname) +{ + char temp_name[64]; + char old_name[64]; + strn0cpy(old_name, GetName(), 64); + + if(newname) + strn0cpy(temp_name, newname, 64); + + // Reset the name to the original if left null. + if(!newname) { + strn0cpy(temp_name, GetOrigName(), 64); + SetName(temp_name); + //CleanMobName(GetName(), temp_name); + strn0cpy(temp_name, GetCleanName(), 64); + } + + // Make the new name unique and set it + strn0cpy(temp_name, entity_list.MakeNameUnique(temp_name), 64); + + + // Send the new name to all clients + EQApplicationPacket* outapp = new EQApplicationPacket(OP_MobRename, sizeof(MobRename_Struct)); + memset(outapp->pBuffer, 0, sizeof(outapp->pBuffer)); + MobRename_Struct* mr = (MobRename_Struct*) outapp->pBuffer; + strn0cpy(mr->old_name, old_name, 64); + strn0cpy(mr->old_name_again, old_name, 64); + strn0cpy(mr->new_name, temp_name, 64); + mr->unknown192 = 0; + mr->unknown196 = 1; + entity_list.QueueClients(this, outapp); + safe_delete(outapp); + + SetName(temp_name); +} + +void Mob::SetTargetable(bool on) { + if(m_targetable != on) { + m_targetable = on; + SendTargetable(on); + } +} + +const int32& Mob::SetMana(int32 amount) +{ + CalcMaxMana(); + int32 mmana = GetMaxMana(); + cur_mana = amount < 0 ? 0 : (amount > mmana ? mmana : amount); +/* + if(IsClient()) + LogFile->write(EQEMuLog::Debug, "Setting mana for %s to %d (%4.1f%%)", GetName(), amount, GetManaRatio()); +*/ + + return cur_mana; +} + + +void Mob::SetAppearance(EmuAppearance app, bool iIgnoreSelf) { + if (_appearance != app) { + _appearance = app; + SendAppearancePacket(AT_Anim, GetAppearanceValue(app), true, iIgnoreSelf); + if (this->IsClient() && this->IsAIControlled()) + SendAppearancePacket(AT_Anim, ANIM_FREEZE, false, false); + } +} + +void Mob::ChangeSize(float in_size = 0, bool bNoRestriction) { + // Size Code + if (!bNoRestriction) + { + if (this->IsClient() || this->petid != 0) + if (in_size < 3.0) + in_size = 3.0; + + + if (this->IsClient() || this->petid != 0) + if (in_size > 15.0) + in_size = 15.0; + } + + + if (in_size < 1.0) + in_size = 1.0; + + if (in_size > 255.0) + in_size = 255.0; + //End of Size Code + this->size = in_size; + SendAppearancePacket(AT_Size, (uint32) in_size); +} + +Mob* Mob::GetOwnerOrSelf() { + if (!GetOwnerID()) + return this; + Mob* owner = entity_list.GetMob(this->GetOwnerID()); + if (!owner) { + SetOwnerID(0); + return(this); + } + if (owner->GetPetID() == this->GetID()) { + return owner; + } + if(IsNPC() && CastToNPC()->GetSwarmInfo()){ + return (CastToNPC()->GetSwarmInfo()->GetOwner()); + } + SetOwnerID(0); + return this; +} + +Mob* Mob::GetOwner() { + Mob* owner = entity_list.GetMob(this->GetOwnerID()); + if (owner && owner->GetPetID() == this->GetID()) { + + return owner; + } + if(IsNPC() && CastToNPC()->GetSwarmInfo()){ + return (CastToNPC()->GetSwarmInfo()->GetOwner()); + } + SetOwnerID(0); + return 0; +} + +Mob* Mob::GetUltimateOwner() +{ + Mob* Owner = GetOwner(); + + if(!Owner) + return this; + + while(Owner && Owner->HasOwner()) + Owner = Owner->GetOwner(); + + return Owner ? Owner : this; +} + +void Mob::SetOwnerID(uint16 NewOwnerID) { + if (NewOwnerID == GetID() && NewOwnerID != 0) // ok, no charming yourself now =p + return; + ownerid = NewOwnerID; + if (ownerid == 0 && this->IsNPC() && this->GetPetType() != petCharmed) + this->Depop(); +} + +// used in checking for behind (backstab) and checking in front (melee LoS) +float Mob::MobAngle(Mob *other, float ourx, float oury) const { + if (!other || other == this) + return 0.0f; + + float angle, lengthb, vectorx, vectory, dotp; + float mobx = -(other->GetX()); // mob xloc (inverse because eq) + float moby = other->GetY(); // mob yloc + float heading = other->GetHeading(); // mob heading + heading = (heading * 360.0f) / 256.0f; // convert to degrees + if (heading < 270) + heading += 90; + else + heading -= 270; + + heading = heading * 3.1415f / 180.0f; // convert to radians + vectorx = mobx + (10.0f * cosf(heading)); // create a vector based on heading + vectory = moby + (10.0f * sinf(heading)); // of mob length 10 + + // length of mob to player vector + lengthb = (float) sqrtf(((-ourx - mobx) * (-ourx - mobx)) + ((oury - moby) * (oury - moby))); + + // calculate dot product to get angle + // Handle acos domain errors due to floating point rounding errors + dotp = ((vectorx - mobx) * (-ourx - mobx) + + (vectory - moby) * (oury - moby)) / (10 * lengthb); + // I haven't seen any errors that cause problems that weren't slightly + // larger/smaller than 1/-1, so only handle these cases for now + if (dotp > 1) + return 0.0f; + else if (dotp < -1) + return 180.0f; + + angle = acosf(dotp); + angle = angle * 180.0f / 3.1415f; + + return angle; +} + +void Mob::SetZone(uint32 zone_id, uint32 instance_id) +{ + if(IsClient()) + { + CastToClient()->GetPP().zone_id = zone_id; + CastToClient()->GetPP().zoneInstance = instance_id; + } + Save(); +} + +void Mob::Kill() { + Death(this, 0, SPELL_UNKNOWN, SkillHandtoHand); +} + +void Mob::SetAttackTimer() { + float PermaHaste; + if(GetHaste() > 0) + PermaHaste = 1 / (1 + (float)GetHaste()/100); + else if(GetHaste() < 0) + PermaHaste = 1 * (1 - (float)GetHaste()/100); + else + PermaHaste = 1.0f; + + //default value for attack timer in case they have + //an invalid weapon equipped: + attack_timer.SetAtTrigger(4000, true); + + Timer* TimerToUse = nullptr; + const Item_Struct* PrimaryWeapon = nullptr; + + for (int i=SLOT_RANGE; i<=SLOT_SECONDARY; i++) { + + //pick a timer + if (i == SLOT_PRIMARY) + TimerToUse = &attack_timer; + else if (i == SLOT_RANGE) + TimerToUse = &ranged_timer; + else if(i == SLOT_SECONDARY) + TimerToUse = &attack_dw_timer; + else //invalid slot (hands will always hit this) + continue; + + const Item_Struct* ItemToUse = nullptr; + + //find our item + if (IsClient()) { + ItemInst* ci = CastToClient()->GetInv().GetItem(i); + if (ci) + ItemToUse = ci->GetItem(); + } else if(IsNPC()) + { + //The code before here was fundementally flawed because equipment[] + //isn't the same as PC inventory and also: + //NPCs don't use weapon speed to dictate how fast they hit anyway. + ItemToUse = nullptr; + } + + //special offhand stuff + if(i == SLOT_SECONDARY) { + //if we have a 2H weapon in our main hand, no dual + if(PrimaryWeapon != nullptr) { + if( PrimaryWeapon->ItemClass == ItemClassCommon + && (PrimaryWeapon->ItemType == ItemType2HSlash + || PrimaryWeapon->ItemType == ItemType2HBlunt + || PrimaryWeapon->ItemType == ItemType2HPiercing)) { + attack_dw_timer.Disable(); + continue; + } + } + + //clients must have the skill to use it... + if(IsClient()) { + //if we cant dual wield, skip it + if (!CanThisClassDualWield()) { + attack_dw_timer.Disable(); + continue; + } + } else { + //NPCs get it for free at 13 + if(GetLevel() < 13) { + attack_dw_timer.Disable(); + continue; + } + } + } + + //see if we have a valid weapon + if(ItemToUse != nullptr) { + //check type and damage/delay + if(ItemToUse->ItemClass != ItemClassCommon + || ItemToUse->Damage == 0 + || ItemToUse->Delay == 0) { + //no weapon + ItemToUse = nullptr; + } + // Check to see if skill is valid + else if((ItemToUse->ItemType > ItemTypeLargeThrowing) && (ItemToUse->ItemType != ItemTypeMartial) && (ItemToUse->ItemType != ItemType2HPiercing)) { + //no weapon + ItemToUse = nullptr; + } + } + + int16 DelayMod = itembonuses.HundredHands + spellbonuses.HundredHands; + if (DelayMod < -99) + DelayMod = -99; + + //if we have no weapon.. + if (ItemToUse == nullptr) { + //above checks ensure ranged weapons do not fall into here + // Work out if we're a monk + if ((GetClass() == MONK) || (GetClass() == BEASTLORD)) { + //we are a monk, use special delay + int speed = (int)( (GetMonkHandToHandDelay()*(100+DelayMod)/100)*(100.0f+attack_speed)*PermaHaste); + // 1200 seemed too much, with delay 10 weapons available + if(speed < RuleI(Combat, MinHastedDelay)) //lower bound + speed = RuleI(Combat, MinHastedDelay); + TimerToUse->SetAtTrigger(speed, true); // Hand to hand, delay based on level or epic + } else { + //not a monk... using fist, regular delay + int speed = (int)((36 *(100+DelayMod)/100)*(100.0f+attack_speed)*PermaHaste); + if(speed < RuleI(Combat, MinHastedDelay) && IsClient()) //lower bound + speed = RuleI(Combat, MinHastedDelay); + TimerToUse->SetAtTrigger(speed, true); // Hand to hand, non-monk 2/36 + } + } else { + //we have a weapon, use its delay + // Convert weapon delay to timer resolution (milliseconds) + //delay * 100 + int speed = (int)((ItemToUse->Delay*(100+DelayMod)/100)*(100.0f+attack_speed)*PermaHaste); + if(speed < RuleI(Combat, MinHastedDelay)) + speed = RuleI(Combat, MinHastedDelay); + + if(ItemToUse && (ItemToUse->ItemType == ItemTypeBow || ItemToUse->ItemType == ItemTypeLargeThrowing)) + { + if(IsClient()) + { + float max_quiver = 0; + for(int r = SLOT_PERSONAL_BEGIN; r <= SLOT_PERSONAL_END; r++) + { + const ItemInst *pi = CastToClient()->GetInv().GetItem(r); + if(!pi) + continue; + if(pi->IsType(ItemClassContainer) && pi->GetItem()->BagType == BagTypeQuiver) + { + float temp_wr = ( pi->GetItem()->BagWR / RuleI(Combat, QuiverWRHasteDiv) ); + if(temp_wr > max_quiver) + { + max_quiver = temp_wr; + } + } + } + if(max_quiver > 0) + { + float quiver_haste = 1 / (1 + max_quiver / 100); + speed *= quiver_haste; + } + } + } + TimerToUse->SetAtTrigger(speed, true); + } + + if(i == SLOT_PRIMARY) + PrimaryWeapon = ItemToUse; + } + +} + +bool Mob::CanThisClassDualWield(void) const { + if(!IsClient()) { + return(GetSkill(SkillDualWield) > 0); + } + else if(CastToClient()->HasSkill(SkillDualWield)) { + const ItemInst* pinst = CastToClient()->GetInv().GetItem(SLOT_PRIMARY); + const ItemInst* sinst = CastToClient()->GetInv().GetItem(SLOT_SECONDARY); + + // 2HS, 2HB, or 2HP + if(pinst && pinst->IsWeapon()) { + const Item_Struct* item = pinst->GetItem(); + + if((item->ItemType == ItemType2HBlunt) || (item->ItemType == ItemType2HSlash) || (item->ItemType == ItemType2HPiercing)) + return false; + } + + // OffHand Weapon + if(sinst && !sinst->IsWeapon()) + return false; + + // Dual-Wielding Empty Fists + if(!pinst && !sinst) + if(class_ != MONK && class_ != MONKGM && class_ != BEASTLORD && class_ != BEASTLORDGM) + return false; + + return true; + } + + return false; +} + +bool Mob::CanThisClassDoubleAttack(void) const +{ + if(!IsClient()) { + return(GetSkill(SkillDoubleAttack) > 0); + } else { + if(aabonuses.GiveDoubleAttack || itembonuses.GiveDoubleAttack || spellbonuses.GiveDoubleAttack) { + return true; + } + return(CastToClient()->HasSkill(SkillDoubleAttack)); + } +} + +bool Mob::IsWarriorClass(void) const +{ + switch(GetClass()) + { + case WARRIOR: + case WARRIORGM: + case ROGUE: + case ROGUEGM: + case MONK: + case MONKGM: + case PALADIN: + case PALADINGM: + case SHADOWKNIGHT: + case SHADOWKNIGHTGM: + case RANGER: + case RANGERGM: + case BEASTLORD: + case BEASTLORDGM: + case BERSERKER: + case BERSERKERGM: + case BARD: + case BARDGM: + { + return true; + } + default: + { + return false; + } + } + +} + +bool Mob::CanThisClassParry(void) const +{ + if(!IsClient()) { + return(GetSkill(SkillParry) > 0); + } else { + return(CastToClient()->HasSkill(SkillParry)); + } +} + +bool Mob::CanThisClassDodge(void) const +{ + if(!IsClient()) { + return(GetSkill(SkillDodge) > 0); + } else { + return(CastToClient()->HasSkill(SkillDodge)); + } +} + +bool Mob::CanThisClassRiposte(void) const +{ + if(!IsClient()) { + return(GetSkill(SkillRiposte) > 0); + } else { + return(CastToClient()->HasSkill(SkillRiposte)); + } +} + +bool Mob::CanThisClassBlock(void) const +{ + if(!IsClient()) { + return(GetSkill(SkillBlock) > 0); + } else { + return(CastToClient()->HasSkill(SkillBlock)); + } +} + +float Mob::Dist(const Mob &other) const { + float xDiff = other.x_pos - x_pos; + float yDiff = other.y_pos - y_pos; + float zDiff = other.z_pos - z_pos; + + return sqrtf( (xDiff * xDiff) + + (yDiff * yDiff) + + (zDiff * zDiff) ); +} + +float Mob::DistNoZ(const Mob &other) const { + float xDiff = other.x_pos - x_pos; + float yDiff = other.y_pos - y_pos; + + return sqrtf( (xDiff * xDiff) + + (yDiff * yDiff) ); +} + +float Mob::DistNoRoot(const Mob &other) const { + float xDiff = other.x_pos - x_pos; + float yDiff = other.y_pos - y_pos; + float zDiff = other.z_pos - z_pos; + + return ( (xDiff * xDiff) + + (yDiff * yDiff) + + (zDiff * zDiff) ); +} + +float Mob::DistNoRoot(float x, float y, float z) const { + float xDiff = x - x_pos; + float yDiff = y - y_pos; + float zDiff = z - z_pos; + + return ( (xDiff * xDiff) + + (yDiff * yDiff) + + (zDiff * zDiff) ); +} + +float Mob::DistNoRootNoZ(float x, float y) const { + float xDiff = x - x_pos; + float yDiff = y - y_pos; + + return ( (xDiff * xDiff) + (yDiff * yDiff) ); +} + +float Mob::DistNoRootNoZ(const Mob &other) const { + float xDiff = other.x_pos - x_pos; + float yDiff = other.y_pos - y_pos; + + return ( (xDiff * xDiff) + (yDiff * yDiff) ); +} + +float Mob::GetReciprocalHeading(Mob* target) { + float Result = 0; + + if(target) { + // Convert to radians + float h = (target->GetHeading() / 256.0f) * 6.283184f; + + // Calculate the reciprocal heading in radians + Result = h + 3.141592f; + + // Convert back to eq heading from radians + Result = (Result / 6.283184f) * 256.0f; + } + + return Result; +} + +bool Mob::PlotPositionAroundTarget(Mob* target, float &x_dest, float &y_dest, float &z_dest, bool lookForAftArc) { + bool Result = false; + + if(target) { + float look_heading = 0; + + if(lookForAftArc) + look_heading = GetReciprocalHeading(target); + else + look_heading = target->GetHeading(); + + // Convert to sony heading to radians + look_heading = (look_heading / 256.0f) * 6.283184f; + + float tempX = 0; + float tempY = 0; + float tempZ = 0; + float tempSize = 0; + const float rangeCreepMod = 0.25; + const uint8 maxIterationsAllowed = 4; + uint8 counter = 0; + float rangeReduction= 0; + + tempSize = target->GetSize(); + rangeReduction = (tempSize * rangeCreepMod); + + while(tempSize > 0 && counter != maxIterationsAllowed) { + tempX = GetX() + (tempSize * static_cast(sin(double(look_heading)))); + tempY = GetY() + (tempSize * static_cast(cos(double(look_heading)))); + tempZ = target->GetZ(); + + if(!CheckLosFN(tempX, tempY, tempZ, tempSize)) { + tempSize -= rangeReduction; + } + else { + Result = true; + break; + } + + counter++; + } + + if(!Result) { + // Try to find an attack arc to position at from the opposite direction. + look_heading += (3.141592 / 2); + + tempSize = target->GetSize(); + counter = 0; + + while(tempSize > 0 && counter != maxIterationsAllowed) { + tempX = GetX() + (tempSize * static_cast(sin(double(look_heading)))); + tempY = GetY() + (tempSize * static_cast(cos(double(look_heading)))); + tempZ = target->GetZ(); + + if(!CheckLosFN(tempX, tempY, tempZ, tempSize)) { + tempSize -= rangeReduction; + } + else { + Result = true; + break; + } + + counter++; + } + } + + if(Result) { + x_dest = tempX; + y_dest = tempY; + z_dest = tempZ; + } + } + + return Result; +} + +bool Mob::HateSummon() { + // check if mob has ability to summon + // 97% is the offical % that summoning starts on live, not 94 + // if the mob can summon and is charmed, it can only summon mobs it has LoS to + Mob* mob_owner = nullptr; + if(GetOwnerID()) + mob_owner = entity_list.GetMob(GetOwnerID()); + + int summon_level = GetSpecialAbility(SPECATK_SUMMON); + if(summon_level == 1 || summon_level == 2) { + if(!GetTarget() || (mob_owner && mob_owner->IsClient() && !CheckLosFN(GetTarget()))) { + return false; + } + } else { + //unsupported summon level or OFF + return false; + } + + // validate hp + int hp_ratio = GetSpecialAbilityParam(SPECATK_SUMMON, 1); + hp_ratio = hp_ratio > 0 ? hp_ratio : 97; + if(GetHPRatio() > static_cast(hp_ratio)) { + return false; + } + + // now validate the timer + int summon_timer_duration = GetSpecialAbilityParam(SPECATK_SUMMON, 0); + summon_timer_duration = summon_timer_duration > 0 ? summon_timer_duration : 6000; + Timer *timer = GetSpecialAbilityTimer(SPECATK_SUMMON); + if (!timer) + { + StartSpecialAbilityTimer(SPECATK_SUMMON, summon_timer_duration); + } else { + if(!timer->Check()) + return false; + + timer->Start(summon_timer_duration); + } + + // get summon target + SetTarget(GetHateTop()); + if(target) + { + if(summon_level == 1) { + entity_list.MessageClose(this, true, 500, MT_Say, "%s says,'You will not evade me, %s!' ", GetCleanName(), target->GetCleanName() ); + + if (target->IsClient()) { + target->CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), x_pos, y_pos, z_pos, target->GetHeading(), 0, SummonPC); + } + else { +#ifdef BOTS + if(target && target->IsBot()) { + // set pre summoning info to return to (to get out of melee range for caster) + target->CastToBot()->SetHasBeenSummoned(true); + target->CastToBot()->SetPreSummonX(target->GetX()); + target->CastToBot()->SetPreSummonY(target->GetY()); + target->CastToBot()->SetPreSummonZ(target->GetZ()); + + } +#endif //BOTS + target->GMMove(x_pos, y_pos, z_pos, target->GetHeading()); + } + + return true; + } else if(summon_level == 2) { + entity_list.MessageClose(this, true, 500, MT_Say, "%s says,'You will not evade me, %s!'", GetCleanName(), target->GetCleanName()); + GMMove(target->GetX(), target->GetY(), target->GetZ()); + } + } + return false; +} + +void Mob::FaceTarget(Mob* MobToFace) { + Mob* facemob = MobToFace; + if(!facemob) { + if(!GetTarget()) { + return; + } + else { + facemob = GetTarget(); + } + } + + float oldheading = GetHeading(); + float newheading = CalculateHeadingToTarget(facemob->GetX(), facemob->GetY()); + if(oldheading != newheading) { + SetHeading(newheading); + if(moving) + SendPosUpdate(); + else + { + SendPosition(); + } + } + + if(IsNPC() && !IsEngaged()) { + CastToNPC()->GetRefaceTimer()->Start(15000); + CastToNPC()->GetRefaceTimer()->Enable(); + } +} + +bool Mob::RemoveFromHateList(Mob* mob) +{ + SetRunAnimSpeed(0); + bool bFound = false; + if(IsEngaged()) + { + bFound = hate_list.RemoveEnt(mob); + if(hate_list.IsEmpty()) + { + AI_Event_NoLongerEngaged(); + zone->DelAggroMob(); + } + } + if(GetTarget() == mob) + { + SetTarget(hate_list.GetTop(this)); + } + + return bFound; +} + +void Mob::WipeHateList() +{ + if(IsEngaged()) + { + hate_list.Wipe(); + AI_Event_NoLongerEngaged(); + } + else + { + hate_list.Wipe(); + } +} + +uint32 Mob::RandomTimer(int min,int max) { + int r = 14000; + if(min != 0 && max != 0 && min < max) + { + r = MakeRandomInt(min, max); + } + return r; +} + +uint32 NPC::GetEquipment(uint8 material_slot) const +{ + if(material_slot > 8) + return 0; + int invslot = Inventory::CalcSlotFromMaterial(material_slot); + if (invslot == -1) + return 0; + return equipment[invslot]; +} + +void Mob::SendWearChange(uint8 material_slot) +{ + EQApplicationPacket* outapp = new EQApplicationPacket(OP_WearChange, sizeof(WearChange_Struct)); + WearChange_Struct* wc = (WearChange_Struct*)outapp->pBuffer; + + wc->spawn_id = GetID(); + wc->material = GetEquipmentMaterial(material_slot); + wc->elite_material = IsEliteMaterialItem(material_slot); + wc->color.color = GetEquipmentColor(material_slot); + wc->wear_slot_id = material_slot; + + entity_list.QueueClients(this, outapp); + safe_delete(outapp); +} + +void Mob::SendTextureWC(uint8 slot, uint16 texture, uint32 hero_forge_model, uint32 elite_material, uint32 unknown06, uint32 unknown18) +{ + EQApplicationPacket* outapp = new EQApplicationPacket(OP_WearChange, sizeof(WearChange_Struct)); + WearChange_Struct* wc = (WearChange_Struct*)outapp->pBuffer; + + wc->spawn_id = this->GetID(); + wc->material = texture; + if (this->IsClient()) + wc->color.color = GetEquipmentColor(slot); + else + wc->color.color = this->GetArmorTint(slot); + wc->wear_slot_id = slot; + + wc->unknown06 = unknown06; + wc->elite_material = elite_material; + wc->hero_forge_model = hero_forge_model; + wc->unknown18 = unknown18; + + + entity_list.QueueClients(this, outapp); + safe_delete(outapp); +} + +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[material_slot] = color; + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_WearChange, sizeof(WearChange_Struct)); + WearChange_Struct* wc = (WearChange_Struct*)outapp->pBuffer; + + wc->spawn_id = this->GetID(); + wc->material = GetEquipmentMaterial(material_slot); + wc->color.color = color; + wc->wear_slot_id = material_slot; + + entity_list.QueueClients(this, outapp); + safe_delete(outapp); +} + +void Mob::WearChange(uint8 material_slot, uint16 texture, uint32 color) +{ + armor_tint[material_slot] = color; + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_WearChange, sizeof(WearChange_Struct)); + WearChange_Struct* wc = (WearChange_Struct*)outapp->pBuffer; + + wc->spawn_id = this->GetID(); + wc->material = texture; + wc->color.color = color; + wc->wear_slot_id = material_slot; + + entity_list.QueueClients(this, outapp); + safe_delete(outapp); +} + +int32 Mob::GetEquipmentMaterial(uint8 material_slot) const +{ + const Item_Struct *item; + + item = database.GetItem(GetEquipment(material_slot)); + if(item != 0) + { + if // for primary and secondary we need the model, not the material + ( + material_slot == MaterialPrimary || + material_slot == MaterialSecondary + ) + { + if(strlen(item->IDFile) > 2) + return atoi(&item->IDFile[2]); + else //may as well try this, since were going to 0 anyways + return item->Material; + } + else + { + return item->Material; + } + } + + return 0; +} + +uint32 Mob::GetEquipmentColor(uint8 material_slot) const +{ + const Item_Struct *item; + + item = database.GetItem(GetEquipment(material_slot)); + if(item != 0) + { + return item->Color; + } + + return 0; +} + +uint32 Mob::IsEliteMaterialItem(uint8 material_slot) const +{ + const Item_Struct *item; + + item = database.GetItem(GetEquipment(material_slot)); + if(item != 0) + { + return item->EliteMaterial; + } + + return 0; +} + +// works just like a printf +void Mob::Say(const char *format, ...) +{ + char buf[1000]; + va_list ap; + + va_start(ap, format); + vsnprintf(buf, 1000, format, ap); + va_end(ap); + + Mob* talker = this; + if(spellbonuses.VoiceGraft != 0) { + if(spellbonuses.VoiceGraft == GetPetID()) + talker = entity_list.GetMob(spellbonuses.VoiceGraft); + else + spellbonuses.VoiceGraft = 0; + } + + if(!talker) + talker = this; + + entity_list.MessageClose_StringID(talker, false, 200, 10, + GENERIC_SAY, GetCleanName(), buf); +} + +// +// solar: this is like the above, but the first parameter is a string id +// +void Mob::Say_StringID(uint32 string_id, const char *message3, const char *message4, const char *message5, const char *message6, const char *message7, const char *message8, const char *message9) +{ + char string_id_str[10]; + + snprintf(string_id_str, 10, "%d", string_id); + + entity_list.MessageClose_StringID(this, false, 200, 10, + GENERIC_STRINGID_SAY, GetCleanName(), string_id_str, message3, message4, message5, + message6, message7, message8, message9 + ); +} + +void Mob::Say_StringID(uint32 type, uint32 string_id, const char *message3, const char *message4, const char *message5, const char *message6, const char *message7, const char *message8, const char *message9) +{ + char string_id_str[10]; + + snprintf(string_id_str, 10, "%d", string_id); + + entity_list.MessageClose_StringID(this, false, 200, type, + GENERIC_STRINGID_SAY, GetCleanName(), string_id_str, message3, message4, message5, + message6, message7, message8, message9 + ); +} + +void Mob::Shout(const char *format, ...) +{ + char buf[1000]; + va_list ap; + + va_start(ap, format); + vsnprintf(buf, 1000, format, ap); + va_end(ap); + + entity_list.Message_StringID(this, false, MT_Shout, + GENERIC_SHOUT, GetCleanName(), buf); +} + +void Mob::Emote(const char *format, ...) +{ + char buf[1000]; + va_list ap; + + va_start(ap, format); + vsnprintf(buf, 1000, format, ap); + va_end(ap); + + entity_list.MessageClose_StringID(this, false, 200, 10, + GENERIC_EMOTE, GetCleanName(), buf); +} + +void Mob::QuestJournalledSay(Client *QuestInitiator, const char *str) +{ + entity_list.QuestJournalledSayClose(this, QuestInitiator, 200, GetCleanName(), str); +} + +const char *Mob::GetCleanName() +{ + if(!strlen(clean_name)) + { + CleanMobName(GetName(), clean_name); + } + + return clean_name; +} + +// hp event +void Mob::SetNextHPEvent( int hpevent ) +{ + nexthpevent = hpevent; +} + +void Mob::SetNextIncHPEvent( int inchpevent ) +{ + nextinchpevent = inchpevent; +} +//warp for quest function,from sandy +void Mob::Warp( float x, float y, float z ) +{ + if(IsNPC()) { + entity_list.ProcessMove(CastToNPC(), x, y, z); + } + + x_pos = x; + y_pos = y; + z_pos = z; + + Mob* target = GetTarget(); + if (target) { + FaceTarget( target ); + } + + SendPosition(); +} + +int16 Mob::GetResist(uint8 type) const +{ + if (IsNPC()) + { + if (type == 1) + return MR + spellbonuses.MR + itembonuses.MR; + else if (type == 2) + return FR + spellbonuses.FR + itembonuses.FR; + else if (type == 3) + return CR + spellbonuses.CR + itembonuses.CR; + else if (type == 4) + return PR + spellbonuses.PR + itembonuses.PR; + else if (type == 5) + return DR + spellbonuses.DR + itembonuses.DR; + } + else if (IsClient()) + { + if (type == 1) + return CastToClient()->GetMR(); + else if (type == 2) + return CastToClient()->GetFR(); + else if (type == 3) + return CastToClient()->GetCR(); + else if (type == 4) + return CastToClient()->GetPR(); + else if (type == 5) + return CastToClient()->GetDR(); + } + return 25; +} + +uint32 Mob::GetLevelHP(uint8 tlevel) +{ + //std::cout<<"Tlevel: "<<(int)tlevel<= 60 && casttime > 1000) + { + casttime = casttime / 2; + if (casttime < 1000) + casttime = 1000; + } else if (level >= 50 && casttime > 1000) { + int32 cast_deduction = (casttime*(level - 49))/5; + if (cast_deduction > casttime/2) + casttime /= 2; + else + casttime -= cast_deduction; + } + return(casttime); +} + +void Mob::ExecWeaponProc(const ItemInst *inst, uint16 spell_id, Mob *on) { + // Changed proc targets to look up based on the spells goodEffect flag. + // This should work for the majority of weapons. + if(spell_id == SPELL_UNKNOWN || on->GetSpecialAbility(NO_HARM_FROM_CLIENT)) { + //This is so 65535 doesn't get passed to the client message and to logs because it is not relavant information for debugging. + return; + } + + if (IsNoCast()) + return; + + if(!IsValidSpell(spell_id)) { // Check for a valid spell otherwise it will crash through the function + if(IsClient()){ + Message(0, "Invalid spell proc %u", spell_id); + mlog(CLIENT__SPELLS, "Player %s, Weapon Procced invalid spell %u", this->GetName(), spell_id); + } + return; + } + + if(inst && IsClient()) { + //const cast is dirty but it would require redoing a ton of interfaces at this point + //It should be safe as we don't have any truly const ItemInst floating around anywhere. + //So we'll live with it for now + int i = parse->EventItem(EVENT_WEAPON_PROC, CastToClient(), const_cast(inst), on, "", spell_id); + if(i != 0) { + return; + } + } + + bool twinproc = false; + int32 twinproc_chance = 0; + + if(IsClient()) + twinproc_chance = CastToClient()->GetFocusEffect(focusTwincast, spell_id); + + if(twinproc_chance && (MakeRandomInt(0,99) < twinproc_chance)) + twinproc = true; + + if (IsBeneficialSpell(spell_id)) { + SpellFinished(spell_id, this, 10, 0, -1, spells[spell_id].ResistDiff, true); + if(twinproc) + SpellOnTarget(spell_id, this, false, false, 0, true); + } + else if(!(on->IsClient() && on->CastToClient()->dead)) { //dont proc on dead clients + SpellFinished(spell_id, on, 10, 0, -1, spells[spell_id].ResistDiff, true); + if(twinproc) + SpellOnTarget(spell_id, on, false, false, 0, true); + } + return; +} + +uint32 Mob::GetZoneID() const { + return(zone->GetZoneID()); +} + +int Mob::GetHaste() { + int h = spellbonuses.haste + spellbonuses.hastetype2; + int cap = 0; + int overhaste = 0; + int level = GetLevel(); + + // 26+ no cap, 1-25 10 + if (level > 25) // 26+ + h += itembonuses.haste; + else // 1-25 + h += itembonuses.haste > 10 ? 10 : itembonuses.haste; + + // 60+ 100, 51-59 85, 1-50 level+25 + if (level > 59) // 60+ + cap = RuleI(Character, HasteCap); + else if (level > 50) // 51-59 + cap = 85; + else // 1-50 + cap = level + 25; + + if(h > cap) + h = cap; + + // 51+ 25 (despite there being higher spells...), 1-50 10 + if (level > 50) // 51+ + overhaste = spellbonuses.hastetype3 > 25 ? 25 : spellbonuses.hastetype3; + else // 1-50 + overhaste = spellbonuses.hastetype3 > 10 ? 10 : spellbonuses.hastetype3; + + h += overhaste; + h += ExtraHaste; //GM granted haste. + + if (spellbonuses.inhibitmelee) { + if (h >= 0) + h -= spellbonuses.inhibitmelee; + else + h -= ((100 + h) * spellbonuses.inhibitmelee / 100); + } + + return(h); +} + +void Mob::SetTarget(Mob* mob) { + if (target == mob) return; + target = mob; + entity_list.UpdateHoTT(this); + if(IsNPC()) + parse->EventNPC(EVENT_TARGET_CHANGE, CastToNPC(), mob, "", 0); + else if (IsClient()) + parse->EventPlayer(EVENT_TARGET_CHANGE, CastToClient(), "", 0); + + if(IsPet() && GetOwner() && GetOwner()->IsClient()) + GetOwner()->CastToClient()->UpdateXTargetType(MyPetTarget, mob); +} + +float Mob::FindGroundZ(float new_x, float new_y, float z_offset) +{ + float ret = -999999; + if (zone->zonemap != nullptr) + { + Map::Vertex me; + me.x = new_x; + me.y = new_y; + me.z = z_pos+z_offset; + Map::Vertex hit; + float best_z = zone->zonemap->FindBestZ(me, &hit); + if (best_z != -999999) + { + ret = best_z; + } + } + return ret; +} + +// Copy of above function that isn't protected to be exported to Perl::Mob +float Mob::GetGroundZ(float new_x, float new_y, float z_offset) +{ + float ret = -999999; + if (zone->zonemap != 0) + { + Map::Vertex me; + me.x = new_x; + me.y = new_y; + me.z = z_pos+z_offset; + Map::Vertex hit; + float best_z = zone->zonemap->FindBestZ(me, &hit); + if (best_z != -999999) + { + ret = best_z; + } + } + return ret; +} + +//helper function for npc AI; needs to be mob:: cause we need to be able to count buffs on other clients and npcs +int Mob::CountDispellableBuffs() +{ + int val = 0; + int buff_count = GetMaxTotalSlots(); + for(int x = 0; x < buff_count; x++) + { + if(!IsValidSpell(buffs[x].spellid)) + continue; + + if(buffs[x].counters) + continue; + + if(spells[buffs[x].spellid].goodEffect == 0) + continue; + + if(buffs[x].spellid != SPELL_UNKNOWN && spells[buffs[x].spellid].buffdurationformula != DF_Permanent) + val++; + } + return val; +} + +// Returns the % that a mob is snared (as a positive value). -1 means not snared +int Mob::GetSnaredAmount() +{ + int worst_snare = -1; + + int buff_count = GetMaxTotalSlots(); + for (int i = 0; i < buff_count; i++) + { + if (!IsValidSpell(buffs[i].spellid)) + continue; + + for(int j = 0; j < EFFECT_COUNT; j++) + { + if (spells[buffs[i].spellid].effectid[j] == SE_MovementSpeed) + { + int val = CalcSpellEffectValue_formula(spells[buffs[i].spellid].formula[j], spells[buffs[i].spellid].base[j], spells[buffs[i].spellid].max[j], buffs[i].casterlevel, buffs[i].spellid); + //int effect = CalcSpellEffectValue(buffs[i].spellid, spells[buffs[i].spellid].effectid[j], buffs[i].casterlevel); + if (val < 0 && abs(val) > worst_snare) + worst_snare = abs(val); + } + } + } + + return worst_snare; +} + +void Mob::TriggerDefensiveProcs(const ItemInst* weapon, Mob *on, uint16 hand, int damage) +{ + if (!on) + return; + + on->TryDefensiveProc(weapon, this, hand, damage); + + //Defensive Skill Procs + if (damage < 0 && damage >= -4) { + uint16 skillinuse = 0; + switch (damage) { + case (-1): + skillinuse = SkillBlock; + break; + + case (-2): + skillinuse = SkillParry; + break; + + case (-3): + skillinuse = SkillRiposte; + break; + + case (-4): + skillinuse = SkillDodge; + break; + } + + if (on->HasSkillProcs()) + on->TrySkillProc(this, skillinuse, 0, false, hand, true); + + if (on->HasSkillProcSuccess()) + on->TrySkillProc(this, skillinuse, 0, true, hand, true); + } +} + +void Mob::SetDeltas(float dx, float dy, float dz, float dh) { + delta_x = dx; + delta_y = dy; + delta_z = dz; + delta_heading = static_cast(dh); +} + +void Mob::SetEntityVariable(const char *id, const char *m_var) +{ + std::string n_m_var = m_var; + m_EntityVariables[id] = n_m_var; +} + +const char* Mob::GetEntityVariable(const char *id) +{ + std::map::iterator iter = m_EntityVariables.find(id); + if(iter != m_EntityVariables.end()) + { + return iter->second.c_str(); + } + return nullptr; +} + +bool Mob::EntityVariableExists(const char *id) +{ + std::map::iterator iter = m_EntityVariables.find(id); + if(iter != m_EntityVariables.end()) + { + return true; + } + return false; +} + +void Mob::SetFlyMode(uint8 flymode) +{ + if(IsClient() && flymode >= 0 && flymode < 3) + { + this->SendAppearancePacket(AT_Levitate, flymode); + } + else if(IsNPC() && flymode >= 0 && flymode <= 3) + { + this->SendAppearancePacket(AT_Levitate, flymode); + this->CastToNPC()->SetFlyMode(flymode); + } +} + +bool Mob::IsNimbusEffectActive(uint32 nimbus_effect) +{ + if(nimbus_effect1 == nimbus_effect || nimbus_effect2 == nimbus_effect || nimbus_effect3 == nimbus_effect) + { + return true; + } + return false; +} + +void Mob::SetNimbusEffect(uint32 nimbus_effect) +{ + if(nimbus_effect1 == 0) + { + nimbus_effect1 = nimbus_effect; + } + else if(nimbus_effect2 == 0) + { + nimbus_effect2 = nimbus_effect; + } + else + { + nimbus_effect3 = nimbus_effect; + } +} + +void Mob::TryTriggerOnCast(uint32 spell_id, bool aa_trigger) +{ + if(!IsValidSpell(spell_id)) + return; + + if (aabonuses.SpellTriggers[0] || spellbonuses.SpellTriggers[0] || itembonuses.SpellTriggers[0]){ + + for(int i = 0; i < MAX_SPELL_TRIGGER; i++){ + + if(aabonuses.SpellTriggers[i] && IsClient()) + TriggerOnCast(aabonuses.SpellTriggers[i], spell_id,1); + + if(spellbonuses.SpellTriggers[i]) + TriggerOnCast(spellbonuses.SpellTriggers[i], spell_id,0); + + if(itembonuses.SpellTriggers[i]) + TriggerOnCast(spellbonuses.SpellTriggers[i], spell_id,0); + } + } +} + + +void Mob::TriggerOnCast(uint32 focus_spell, uint32 spell_id, bool aa_trigger) +{ + if(!IsValidSpell(focus_spell) || !IsValidSpell(spell_id)) + return; + + uint32 trigger_spell_id = 0; + + if (aa_trigger && IsClient()){ + //focus_spell = aaid + trigger_spell_id = CastToClient()->CalcAAFocus(focusTriggerOnCast, focus_spell, spell_id); + + if(IsValidSpell(trigger_spell_id) && GetTarget()) + SpellFinished(trigger_spell_id, GetTarget(), 10, 0, -1, spells[trigger_spell_id].ResistDiff); + } + + else{ + trigger_spell_id = CalcFocusEffect(focusTriggerOnCast, focus_spell, spell_id); + + if(IsValidSpell(trigger_spell_id) && GetTarget()){ + SpellFinished(trigger_spell_id, GetTarget(),10, 0, -1, spells[trigger_spell_id].ResistDiff); + CheckNumHitsRemaining(NUMHIT_MatchingSpells,0, focus_spell); + } + } +} + +void Mob::TrySpellTrigger(Mob *target, uint32 spell_id) +{ + if(target == nullptr || !IsValidSpell(spell_id)) + { + return; + } + int spell_trig = 0; + // Count all the percentage chances to trigger for all effects + for(int i = 0; i < EFFECT_COUNT; i++) + { + if (spells[spell_id].effectid[i] == SE_SpellTrigger) + spell_trig += spells[spell_id].base[i]; + } + // If all the % add to 100, then only one of the effects can fire but one has to fire. + if (spell_trig == 100) + { + int trig_chance = 100; + for(int i = 0; i < EFFECT_COUNT; i++) + { + if (spells[spell_id].effectid[i] == SE_SpellTrigger) + { + if(MakeRandomInt(0, trig_chance) <= spells[spell_id].base[i]) + { + // If we trigger an effect then its over. + SpellFinished(spells[spell_id].base2[i], target, 10, 0, -1, spells[spell_id].ResistDiff); + break; + } + else + { + // Increase the chance to fire for the next effect, if all effects fail, the final effect will fire. + trig_chance -= spells[spell_id].base[i]; + } + } + + } + } + // if the chances don't add to 100, then each effect gets a chance to fire, chance for no trigger as well. + else + { + for(int i = 0; i < EFFECT_COUNT; i++) + { + if (spells[spell_id].effectid[i] == SE_SpellTrigger) + { + if(MakeRandomInt(0, 100) <= spells[spell_id].base[i]) + { + SpellFinished(spells[spell_id].base2[i], target, 10, 0, -1, spells[spell_id].ResistDiff); + } + } + } + } +} + +void Mob::TryApplyEffect(Mob *target, uint32 spell_id) +{ + if(target == nullptr || !IsValidSpell(spell_id)) + { + return; + } + + for(int i = 0; i < EFFECT_COUNT; i++) + { + if (spells[spell_id].effectid[i] == SE_ApplyEffect) + { + if(MakeRandomInt(0, 100) <= spells[spell_id].base[i]) + { + if(target) + SpellFinished(spells[spell_id].base2[i], target, 10, 0, -1, spells[spell_id].ResistDiff); + } + } + } +} + +void Mob::TryTriggerOnValueAmount(bool IsHP, bool IsMana, bool IsEndur, bool IsPet) +{ + /* + At present time there is no obvious difference between ReqTarget and ReqCaster + ReqTarget is typically used in spells cast on a target where the trigger occurs on that target. + ReqCaster is typically self only spells where the triggers on self. + Regardless both trigger on the owner of the buff. + */ + + /* + Base2 Range: 1004 = Below < 80% HP + Base2 Range: 500-520 = Below (base2 - 500)*5 HP + Base2 Range: 521 = Below (?) Mana UKNOWN - Will assume its 20% unless proven otherwise + Base2 Range: 522 = Below (40%) Endurance + Base2 Range: 523 = Below (40%) Mana + Base2 Range: 220-? = Number of pets on hatelist to trigger (base2 - 220) (Set at 30 pets max for now) + 38311 = < 10% mana; + */ + + if (!spellbonuses.TriggerOnValueAmount) + return; + + if (spellbonuses.TriggerOnValueAmount){ + + int buff_count = GetMaxTotalSlots(); + + for(int e = 0; e < buff_count; e++){ + + uint32 spell_id = buffs[e].spellid; + + if (IsValidSpell(spell_id)){ + + for(int i = 0; i < EFFECT_COUNT; i++){ + + if ((spells[spell_id].effectid[i] == SE_TriggerOnReqTarget) || (spells[spell_id].effectid[i] == SE_TriggerOnReqCaster)) { + + int base2 = spells[spell_id].base2[i]; + bool use_spell = false; + + if (IsHP){ + if ((base2 >= 500 && base2 <= 520) && GetHPRatio() < (base2 - 500)*5) + use_spell = true; + + else if (base2 = 1004 && GetHPRatio() < 80) + use_spell = true; + } + + else if (IsMana){ + if ( (base2 = 521 && GetManaRatio() < 20) || (base2 = 523 && GetManaRatio() < 40)) + use_spell = true; + + else if (base2 = 38311 && GetManaRatio() < 10) + use_spell = true; + } + + else if (IsEndur){ + if (base2 = 522 && GetEndurancePercent() < 40){ + use_spell = true; + } + } + + else if (IsPet){ + int count = hate_list.SummonedPetCount(this); + if ((base2 >= 220 && base2 <= 250) && count >= (base2 - 220)){ + use_spell = true; + } + } + + if (use_spell){ + SpellFinished(spells[spell_id].base[i], this, 10, 0, -1, spells[spell_id].ResistDiff); + + if(!TryFadeEffect(e)) + BuffFadeBySlot(e); + } + } + } + } + } + } +} + + +//Twincast Focus effects should stack across different types (Spell, AA - when implemented ect) +void Mob::TryTwincast(Mob *caster, Mob *target, uint32 spell_id) +{ + if(!IsValidSpell(spell_id)) + return; + + if(IsClient()) + { + int32 focus = CastToClient()->GetFocusEffect(focusTwincast, spell_id); + + if (focus > 0) + { + if(MakeRandomInt(0, 100) <= focus) + { + Message(MT_Spells,"You twincast %s!",spells[spell_id].name); + SpellFinished(spell_id, target, 10, 0, -1, spells[spell_id].ResistDiff); + } + } + } + + //Retains function for non clients + else if (spellbonuses.FocusEffects[focusTwincast] || itembonuses.FocusEffects[focusTwincast]) + { + int buff_count = GetMaxTotalSlots(); + for(int i = 0; i < buff_count; i++) + { + if(IsEffectInSpell(buffs[i].spellid, SE_FcTwincast)) + { + int32 focus = CalcFocusEffect(focusTwincast, buffs[i].spellid, spell_id); + if(focus > 0) + { + if(MakeRandomInt(0, 100) <= focus) + { + SpellFinished(spell_id, target, 10, 0, -1, spells[spell_id].ResistDiff); + } + } + } + } + } +} + +int32 Mob::GetVulnerability(Mob* caster, uint32 spell_id, uint32 ticsremaining) +{ + if (!IsValidSpell(spell_id)) + return 0; + + if (!caster) + return 0; + + int32 value = 0; + + //Apply innate vulnerabilities + if (Vulnerability_Mod[GetSpellResistType(spell_id)] != 0) + value = Vulnerability_Mod[GetSpellResistType(spell_id)]; + + + else if (Vulnerability_Mod[HIGHEST_RESIST+1] != 0) + value = Vulnerability_Mod[HIGHEST_RESIST+1]; + + //Apply spell derived vulnerabilities + if (spellbonuses.FocusEffects[focusSpellVulnerability]){ + + int32 tmp_focus = 0; + int tmp_buffslot = -1; + + int buff_count = GetMaxTotalSlots(); + for(int i = 0; i < buff_count; i++) { + + if((IsValidSpell(buffs[i].spellid) && IsEffectInSpell(buffs[i].spellid, SE_FcSpellVulnerability))){ + + int32 focus = caster->CalcFocusEffect(focusSpellVulnerability, buffs[i].spellid, spell_id); + + if (!focus) + continue; + + if (tmp_focus && focus > tmp_focus){ + tmp_focus = focus; + tmp_buffslot = i; + } + + else if (!tmp_focus){ + tmp_focus = focus; + tmp_buffslot = i; + } + + } + } + + if (tmp_focus < -99) + tmp_focus = -99; + + value += tmp_focus; + + if (tmp_buffslot >= 0) + CheckNumHitsRemaining(NUMHIT_MatchingSpells, tmp_buffslot); + } + return value; +} + +int16 Mob::GetSkillDmgTaken(const SkillUseTypes skill_used) +{ + int skilldmg_mod = 0; + + int16 MeleeVuln = spellbonuses.MeleeVulnerability + itembonuses.MeleeVulnerability + aabonuses.MeleeVulnerability; + + // All skill dmg mod + Skill specific + skilldmg_mod += itembonuses.SkillDmgTaken[HIGHEST_SKILL+1] + spellbonuses.SkillDmgTaken[HIGHEST_SKILL+1] + + itembonuses.SkillDmgTaken[skill_used] + spellbonuses.SkillDmgTaken[skill_used]; + + //Innate SetSkillDamgeTaken(skill,value) + if ((SkillDmgTaken_Mod[skill_used]) || (SkillDmgTaken_Mod[HIGHEST_SKILL+1])) + skilldmg_mod += SkillDmgTaken_Mod[skill_used] + SkillDmgTaken_Mod[HIGHEST_SKILL+1]; + + skilldmg_mod += MeleeVuln; + + if(skilldmg_mod < -100) + skilldmg_mod = -100; + + return skilldmg_mod; +} + +int16 Mob::GetHealRate(uint16 spell_id, Mob* caster) { + + int16 heal_rate = 0; + + heal_rate += itembonuses.HealRate + spellbonuses.HealRate + aabonuses.HealRate; + heal_rate += GetFocusIncoming(focusFcHealPctIncoming, SE_FcHealPctIncoming, caster, spell_id); + + if(heal_rate < -99) + heal_rate = -99; + + return heal_rate; +} + +bool Mob::TryFadeEffect(int slot) +{ + if(IsValidSpell(buffs[slot].spellid)) + { + for(int i = 0; i < EFFECT_COUNT; i++) + { + if (spells[buffs[slot].spellid].effectid[i] == SE_CastOnFadeEffectAlways || + spells[buffs[slot].spellid].effectid[i] == SE_CastOnRuneFadeEffect) + { + uint16 spell_id = spells[buffs[slot].spellid].base[i]; + BuffFadeBySlot(slot); + + if(spell_id) + { + + if(spell_id == SPELL_UNKNOWN) + return false; + + if(IsValidSpell(spell_id)) + { + if (IsBeneficialSpell(spell_id)) { + SpellFinished(spell_id, this, 10, 0, -1, spells[spell_id].ResistDiff); + } + else if(!(IsClient() && CastToClient()->dead)) { + SpellFinished(spell_id, this, 10, 0, -1, spells[spell_id].ResistDiff); + } + return true; + } + } + } + } + } + return false; +} + +void Mob::TrySympatheticProc(Mob *target, uint32 spell_id) +{ + if(target == nullptr || !IsValidSpell(spell_id)) + return; + + int focus_spell = CastToClient()->GetSympatheticFocusEffect(focusSympatheticProc,spell_id); + + if(IsValidSpell(focus_spell)){ + int focus_trigger = spells[focus_spell].base2[0]; + // For beneficial spells, if the triggered spell is also beneficial then proc it on the target + // if the triggered spell is detrimental, then it will trigger on the caster(ie cursed items) + if(IsBeneficialSpell(spell_id)) + { + if(IsBeneficialSpell(focus_trigger)) + SpellFinished(focus_trigger, target); + + else + SpellFinished(focus_trigger, this, 10, 0, -1, spells[focus_trigger].ResistDiff); + } + // For detrimental spells, if the triggered spell is beneficial, then it will land on the caster + // if the triggered spell is also detrimental, then it will land on the target + else + { + if(IsBeneficialSpell(focus_trigger)) + SpellFinished(focus_trigger, this); + + else + SpellFinished(focus_trigger, target, 10, 0, -1, spells[focus_trigger].ResistDiff); + } + + CheckNumHitsRemaining(NUMHIT_MatchingSpells, 0, focus_spell); + } +} + +uint32 Mob::GetItemStat(uint32 itemid, const char *identifier) +{ + const ItemInst* inst = database.CreateItem(itemid); + if (!inst) + return 0; + + const Item_Struct* item = inst->GetItem(); + if (!item) + return 0; + + if (!identifier) + return 0; + + uint32 stat = 0; + + std::string id = identifier; + for(int i = 0; i < id.length(); ++i) + { + id[i] = tolower(id[i]); + } + + if (id == "itemclass") + stat = uint32(item->ItemClass); + if (id == "id") + stat = uint32(item->ID); + if (id == "weight") + stat = uint32(item->Weight); + if (id == "norent") + stat = uint32(item->NoRent); + if (id == "nodrop") + stat = uint32(item->NoDrop); + if (id == "size") + stat = uint32(item->Size); + if (id == "slots") + stat = uint32(item->Slots); + if (id == "price") + stat = uint32(item->Price); + if (id == "icon") + stat = uint32(item->Icon); + if (id == "loregroup") + stat = uint32(item->LoreGroup); + if (id == "loreflag") + stat = uint32(item->LoreFlag); + if (id == "pendingloreflag") + stat = uint32(item->PendingLoreFlag); + if (id == "artifactflag") + stat = uint32(item->ArtifactFlag); + if (id == "summonedflag") + stat = uint32(item->SummonedFlag); + if (id == "fvnodrop") + stat = uint32(item->FVNoDrop); + if (id == "favor") + stat = uint32(item->Favor); + if (id == "guildfavor") + stat = uint32(item->GuildFavor); + if (id == "pointtype") + stat = uint32(item->PointType); + if (id == "bagtype") + stat = uint32(item->BagType); + if (id == "bagslots") + stat = uint32(item->BagSlots); + if (id == "bagsize") + stat = uint32(item->BagSize); + if (id == "bagwr") + stat = uint32(item->BagWR); + if (id == "benefitflag") + stat = uint32(item->BenefitFlag); + if (id == "tradeskills") + stat = uint32(item->Tradeskills); + if (id == "cr") + stat = uint32(item->CR); + if (id == "dr") + stat = uint32(item->DR); + if (id == "pr") + stat = uint32(item->PR); + if (id == "mr") + stat = uint32(item->MR); + if (id == "fr") + stat = uint32(item->FR); + if (id == "astr") + stat = uint32(item->AStr); + if (id == "asta") + stat = uint32(item->ASta); + if (id == "aagi") + stat = uint32(item->AAgi); + if (id == "adex") + stat = uint32(item->ADex); + if (id == "acha") + stat = uint32(item->ACha); + if (id == "aint") + stat = uint32(item->AInt); + if (id == "awis") + stat = uint32(item->AWis); + if (id == "hp") + stat = uint32(item->HP); + if (id == "mana") + stat = uint32(item->Mana); + if (id == "ac") + stat = uint32(item->AC); + if (id == "deity") + stat = uint32(item->Deity); + if (id == "skillmodvalue") + stat = uint32(item->SkillModValue); + if (id == "skillmodtype") + stat = uint32(item->SkillModType); + if (id == "banedmgrace") + stat = uint32(item->BaneDmgRace); + if (id == "banedmgamt") + stat = uint32(item->BaneDmgAmt); + if (id == "banedmgbody") + stat = uint32(item->BaneDmgBody); + if (id == "magic") + stat = uint32(item->Magic); + if (id == "casttime_") + stat = uint32(item->CastTime_); + if (id == "reqlevel") + stat = uint32(item->ReqLevel); + if (id == "bardtype") + stat = uint32(item->BardType); + if (id == "bardvalue") + stat = uint32(item->BardValue); + if (id == "light") + stat = uint32(item->Light); + if (id == "delay") + stat = uint32(item->Delay); + if (id == "reclevel") + stat = uint32(item->RecLevel); + if (id == "recskill") + stat = uint32(item->RecSkill); + if (id == "elemdmgtype") + stat = uint32(item->ElemDmgType); + if (id == "elemdmgamt") + stat = uint32(item->ElemDmgAmt); + if (id == "range") + stat = uint32(item->Range); + if (id == "damage") + stat = uint32(item->Damage); + if (id == "color") + stat = uint32(item->Color); + if (id == "classes") + stat = uint32(item->Classes); + if (id == "races") + stat = uint32(item->Races); + if (id == "maxcharges") + stat = uint32(item->MaxCharges); + if (id == "itemtype") + stat = uint32(item->ItemType); + if (id == "material") + stat = uint32(item->Material); + if (id == "casttime") + stat = uint32(item->CastTime); + if (id == "elitematerial") + stat = uint32(item->EliteMaterial); + if (id == "procrate") + stat = uint32(item->ProcRate); + if (id == "combateffects") + stat = uint32(item->CombatEffects); + if (id == "shielding") + stat = uint32(item->Shielding); + if (id == "stunresist") + stat = uint32(item->StunResist); + if (id == "strikethrough") + stat = uint32(item->StrikeThrough); + if (id == "extradmgskill") + stat = uint32(item->ExtraDmgSkill); + if (id == "extradmgamt") + stat = uint32(item->ExtraDmgAmt); + if (id == "spellshield") + stat = uint32(item->SpellShield); + if (id == "avoidance") + stat = uint32(item->Avoidance); + if (id == "accuracy") + stat = uint32(item->Accuracy); + if (id == "charmfileid") + stat = uint32(item->CharmFileID); + if (id == "factionmod1") + stat = uint32(item->FactionMod1); + if (id == "factionmod2") + stat = uint32(item->FactionMod2); + if (id == "factionmod3") + stat = uint32(item->FactionMod3); + if (id == "factionmod4") + stat = uint32(item->FactionMod4); + if (id == "factionamt1") + stat = uint32(item->FactionAmt1); + if (id == "factionamt2") + stat = uint32(item->FactionAmt2); + if (id == "factionamt3") + stat = uint32(item->FactionAmt3); + if (id == "factionamt4") + stat = uint32(item->FactionAmt4); + if (id == "augtype") + stat = uint32(item->AugType); + if (id == "ldontheme") + stat = uint32(item->LDoNTheme); + if (id == "ldonprice") + stat = uint32(item->LDoNPrice); + if (id == "ldonsold") + stat = uint32(item->LDoNSold); + if (id == "banedmgraceamt") + stat = uint32(item->BaneDmgRaceAmt); + if (id == "augrestrict") + stat = uint32(item->AugRestrict); + if (id == "endur") + stat = uint32(item->Endur); + if (id == "dotshielding") + stat = uint32(item->DotShielding); + if (id == "attack") + stat = uint32(item->Attack); + if (id == "regen") + stat = uint32(item->Regen); + if (id == "manaregen") + stat = uint32(item->ManaRegen); + if (id == "enduranceregen") + stat = uint32(item->EnduranceRegen); + if (id == "haste") + stat = uint32(item->Haste); + if (id == "damageshield") + stat = uint32(item->DamageShield); + if (id == "recastdelay") + stat = uint32(item->RecastDelay); + if (id == "recasttype") + stat = uint32(item->RecastType); + if (id == "augdistiller") + stat = uint32(item->AugDistiller); + if (id == "attuneable") + stat = uint32(item->Attuneable); + if (id == "nopet") + stat = uint32(item->NoPet); + if (id == "potionbelt") + stat = uint32(item->PotionBelt); + if (id == "stackable") + stat = uint32(item->Stackable); + if (id == "notransfer") + stat = uint32(item->NoTransfer); + if (id == "questitemflag") + stat = uint32(item->QuestItemFlag); + if (id == "stacksize") + stat = uint32(item->StackSize); + if (id == "potionbeltslots") + stat = uint32(item->PotionBeltSlots); + if (id == "book") + stat = uint32(item->Book); + if (id == "booktype") + stat = uint32(item->BookType); + if (id == "svcorruption") + stat = uint32(item->SVCorruption); + if (id == "purity") + stat = uint32(item->Purity); + if (id == "backstabdmg") + stat = uint32(item->BackstabDmg); + if (id == "dsmitigation") + stat = uint32(item->DSMitigation); + if (id == "heroicstr") + stat = uint32(item->HeroicStr); + if (id == "heroicint") + stat = uint32(item->HeroicInt); + if (id == "heroicwis") + stat = uint32(item->HeroicWis); + if (id == "heroicagi") + stat = uint32(item->HeroicAgi); + if (id == "heroicdex") + stat = uint32(item->HeroicDex); + if (id == "heroicsta") + stat = uint32(item->HeroicSta); + if (id == "heroiccha") + stat = uint32(item->HeroicCha); + if (id == "heroicmr") + stat = uint32(item->HeroicMR); + if (id == "heroicfr") + stat = uint32(item->HeroicFR); + if (id == "heroiccr") + stat = uint32(item->HeroicCR); + if (id == "heroicdr") + stat = uint32(item->HeroicDR); + if (id == "heroicpr") + stat = uint32(item->HeroicPR); + if (id == "heroicsvcorrup") + stat = uint32(item->HeroicSVCorrup); + if (id == "healamt") + stat = uint32(item->HealAmt); + if (id == "spelldmg") + stat = uint32(item->SpellDmg); + if (id == "ldonsellbackrate") + stat = uint32(item->LDoNSellBackRate); + if (id == "scriptfileid") + stat = uint32(item->ScriptFileID); + if (id == "expendablearrow") + stat = uint32(item->ExpendableArrow); + if (id == "clairvoyance") + stat = uint32(item->Clairvoyance); + // Begin Effects + if (id == "clickeffect") + stat = uint32(item->Click.Effect); + if (id == "clicktype") + stat = uint32(item->Click.Type); + if (id == "clicklevel") + stat = uint32(item->Click.Level); + if (id == "clicklevel2") + stat = uint32(item->Click.Level2); + if (id == "proceffect") + stat = uint32(item->Proc.Effect); + if (id == "proctype") + stat = uint32(item->Proc.Type); + if (id == "proclevel") + stat = uint32(item->Proc.Level); + if (id == "proclevel2") + stat = uint32(item->Proc.Level2); + if (id == "worneffect") + stat = uint32(item->Worn.Effect); + if (id == "worntype") + stat = uint32(item->Worn.Type); + if (id == "wornlevel") + stat = uint32(item->Worn.Level); + if (id == "wornlevel2") + stat = uint32(item->Worn.Level2); + if (id == "focuseffect") + stat = uint32(item->Focus.Effect); + if (id == "focustype") + stat = uint32(item->Focus.Type); + if (id == "focuslevel") + stat = uint32(item->Focus.Level); + if (id == "focuslevel2") + stat = uint32(item->Focus.Level2); + if (id == "scrolleffect") + stat = uint32(item->Scroll.Effect); + if (id == "scrolltype") + stat = uint32(item->Scroll.Type); + if (id == "scrolllevel") + stat = uint32(item->Scroll.Level); + if (id == "scrolllevel2") + stat = uint32(item->Scroll.Level2); + + safe_delete(inst); + return stat; +} + +void Mob::SetGlobal(const char *varname, const char *newvalue, int options, const char *duration, Mob *other) { + + int qgZoneid = zone->GetZoneID(); + int qgCharid = 0; + int qgNpcid = 0; + + if (this->IsNPC()) + { + qgNpcid = this->GetNPCTypeID(); + } + else if (other && other->IsNPC()) + { + qgNpcid = other->GetNPCTypeID(); + } + + if (this->IsClient()) + { + qgCharid = this->CastToClient()->CharacterID(); + } + else if (other && other->IsClient()) + { + qgCharid = other->CastToClient()->CharacterID(); + } + else + { + qgCharid = -qgNpcid; // make char id negative npc id as a fudge + } + + if (options < 0 || options > 7) + { + //cerr << "Invalid options for global var " << varname << " using defaults" << endl; + options = 0; // default = 0 (only this npcid,player and zone) + } + else + { + if (options & 1) + qgNpcid=0; + if (options & 2) + qgCharid=0; + if (options & 4) + qgZoneid=0; + } + + InsertQuestGlobal(qgCharid, qgNpcid, qgZoneid, varname, newvalue, QGVarDuration(duration)); +} + +void Mob::TarGlobal(const char *varname, const char *value, const char *duration, int qgNpcid, int qgCharid, int qgZoneid) +{ + InsertQuestGlobal(qgCharid, qgNpcid, qgZoneid, varname, value, QGVarDuration(duration)); +} + +void Mob::DelGlobal(const char *varname) { + // delglobal(varname) + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + int qgZoneid=zone->GetZoneID(); + int qgCharid=0; + int qgNpcid=0; + + if (this->IsNPC()) + { + qgNpcid = this->GetNPCTypeID(); + } + + if (this->IsClient()) + { + qgCharid = this->CastToClient()->CharacterID(); + } + else + { + qgCharid = -qgNpcid; // make char id negative npc id as a fudge + } + + if (!database.RunQuery(query, + MakeAnyLenString(&query, + "DELETE FROM quest_globals WHERE name='%s'" + " && (npcid=0 || npcid=%i) && (charid=0 || charid=%i) && (zoneid=%i || zoneid=0)", + varname,qgNpcid,qgCharid,qgZoneid),errbuf)) + { + //_log(QUESTS, "DelGlobal error deleting %s : %s", varname, errbuf); + } + safe_delete_array(query); + + if(zone) + { + ServerPacket* pack = new ServerPacket(ServerOP_QGlobalDelete, sizeof(ServerQGlobalDelete_Struct)); + ServerQGlobalDelete_Struct *qgu = (ServerQGlobalDelete_Struct*)pack->pBuffer; + + qgu->npc_id = qgNpcid; + qgu->char_id = qgCharid; + qgu->zone_id = qgZoneid; + strcpy(qgu->name, varname); + + entity_list.DeleteQGlobal(std::string((char*)qgu->name), qgu->npc_id, qgu->char_id, qgu->zone_id); + zone->DeleteQGlobal(std::string((char*)qgu->name), qgu->npc_id, qgu->char_id, qgu->zone_id); + + worldserver.SendPacket(pack); + safe_delete(pack); + } +} + +// Inserts global variable into quest_globals table +void Mob::InsertQuestGlobal(int charid, int npcid, int zoneid, const char *varname, const char *varvalue, int duration) { + + char *query = 0; + char errbuf[MYSQL_ERRMSG_SIZE]; + + // Make duration string either "unix_timestamp(now()) + xxx" or "NULL" + std::stringstream duration_ss; + + if (duration == INT_MAX) + { + duration_ss << "NULL"; + } + else + { + duration_ss << "unix_timestamp(now()) + " << duration; + } + + //NOTE: this should be escaping the contents of arglist + //npcwise a malicious script can arbitrarily alter the DB + uint32 last_id = 0; + if (!database.RunQuery(query, MakeAnyLenString(&query, + "REPLACE INTO quest_globals (charid, npcid, zoneid, name, value, expdate)" + "VALUES (%i, %i, %i, '%s', '%s', %s)", + charid, npcid, zoneid, varname, varvalue, duration_ss.str().c_str() + ), errbuf)) + { + //_log(QUESTS, "SelGlobal error inserting %s : %s", varname, errbuf); + } + safe_delete_array(query); + + if(zone) + { + //first delete our global + ServerPacket* pack = new ServerPacket(ServerOP_QGlobalDelete, sizeof(ServerQGlobalDelete_Struct)); + ServerQGlobalDelete_Struct *qgd = (ServerQGlobalDelete_Struct*)pack->pBuffer; + qgd->npc_id = npcid; + qgd->char_id = charid; + qgd->zone_id = zoneid; + qgd->from_zone_id = zone->GetZoneID(); + qgd->from_instance_id = zone->GetInstanceID(); + strcpy(qgd->name, varname); + + entity_list.DeleteQGlobal(std::string((char*)qgd->name), qgd->npc_id, qgd->char_id, qgd->zone_id); + zone->DeleteQGlobal(std::string((char*)qgd->name), qgd->npc_id, qgd->char_id, qgd->zone_id); + + worldserver.SendPacket(pack); + safe_delete(pack); + + //then create a new one with the new id + pack = new ServerPacket(ServerOP_QGlobalUpdate, sizeof(ServerQGlobalUpdate_Struct)); + ServerQGlobalUpdate_Struct *qgu = (ServerQGlobalUpdate_Struct*)pack->pBuffer; + qgu->npc_id = npcid; + qgu->char_id = charid; + qgu->zone_id = zoneid; + if(duration == INT_MAX) + { + qgu->expdate = 0xFFFFFFFF; + } + else + { + qgu->expdate = Timer::GetTimeSeconds() + duration; + } + strcpy((char*)qgu->name, varname); + strcpy((char*)qgu->value, varvalue); + qgu->id = last_id; + qgu->from_zone_id = zone->GetZoneID(); + qgu->from_instance_id = zone->GetInstanceID(); + + QGlobal temp; + temp.npc_id = npcid; + temp.char_id = charid; + temp.zone_id = zoneid; + temp.expdate = qgu->expdate; + temp.name.assign(qgu->name); + temp.value.assign(qgu->value); + entity_list.UpdateQGlobal(qgu->id, temp); + zone->UpdateQGlobal(qgu->id, temp); + + worldserver.SendPacket(pack); + safe_delete(pack); + } + +} + +// Converts duration string to duration value (in seconds) +// Return of INT_MAX indicates infinite duration +int Mob::QGVarDuration(const char *fmt) +{ + int duration = 0; + + // format: Y#### or D## or H## or M## or S## or T###### or C####### + + int len = static_cast(strlen(fmt)); + + // Default to no duration + if (len < 1) + return 0; + + // Set val to value after type character + // e.g., for "M3924", set to 3924 + int val = atoi(&fmt[0] + 1); + + switch (fmt[0]) + { + // Forever + case 'F': + case 'f': + duration = INT_MAX; + break; + // Years + case 'Y': + case 'y': + duration = val * 31556926; + break; + case 'D': + case 'd': + duration = val * 86400; + break; + // Hours + case 'H': + case 'h': + duration = val * 3600; + break; + // Minutes + case 'M': + case 'm': + duration = val * 60; + break; + // Seconds + case 'S': + case 's': + duration = val; + break; + // Invalid + default: + duration = 0; + break; + } + + return duration; +} + +void Mob::DoKnockback(Mob *caster, uint32 pushback, uint32 pushup) +{ + if(IsClient()) + { + CastToClient()->SetKnockBackExemption(true); + + EQApplicationPacket* outapp_push = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); + PlayerPositionUpdateServer_Struct* spu = (PlayerPositionUpdateServer_Struct*)outapp_push->pBuffer; + + double look_heading = caster->CalculateHeadingToTarget(GetX(), GetY()); + look_heading /= 256; + look_heading *= 360; + if(look_heading > 360) + look_heading -= 360; + + //x and y are crossed mkay + double new_x = pushback * sin(double(look_heading * 3.141592 / 180.0)); + double new_y = pushback * cos(double(look_heading * 3.141592 / 180.0)); + + spu->spawn_id = GetID(); + spu->x_pos = FloatToEQ19(GetX()); + spu->y_pos = FloatToEQ19(GetY()); + spu->z_pos = FloatToEQ19(GetZ()); + spu->delta_x = NewFloatToEQ13(static_cast(new_x)); + spu->delta_y = NewFloatToEQ13(static_cast(new_y)); + spu->delta_z = NewFloatToEQ13(static_cast(pushup)); + spu->heading = FloatToEQ19(GetHeading()); + spu->padding0002 =0; + spu->padding0006 =7; + spu->padding0014 =0x7f; + spu->padding0018 =0x5df27; + spu->animation = 0; + spu->delta_heading = NewFloatToEQ13(0); + outapp_push->priority = 6; + entity_list.QueueClients(this, outapp_push, true); + CastToClient()->FastQueuePacket(&outapp_push); + } +} + +void Mob::TrySpellOnKill(uint8 level, uint16 spell_id) +{ + if (spell_id != SPELL_UNKNOWN) + { + if(IsEffectInSpell(spell_id, SE_ProcOnSpellKillShot)) { + for (int i = 0; i < EFFECT_COUNT; i++) { + if (spells[spell_id].effectid[i] == SE_ProcOnSpellKillShot) + { + if (IsValidSpell(spells[spell_id].base2[i]) && spells[spell_id].max[i] <= level) + { + if(MakeRandomInt(0,99) < spells[spell_id].base[i]) + SpellFinished(spells[spell_id].base2[i], this, 10, 0, -1, spells[spells[spell_id].base2[i]].ResistDiff); + } + } + } + } + } + + if (!aabonuses.SpellOnKill[0] && !itembonuses.SpellOnKill[0] && !spellbonuses.SpellOnKill[0]) + return; + + // Allow to check AA, items and buffs in all cases. Base2 = Spell to fire | Base1 = % chance | Base3 = min level + for(int i = 0; i < MAX_SPELL_TRIGGER*3; i+=3) { + + if(aabonuses.SpellOnKill[i] && IsValidSpell(aabonuses.SpellOnKill[i]) && (level >= aabonuses.SpellOnKill[i + 2])) { + if(MakeRandomInt(0, 99) < static_cast(aabonuses.SpellOnKill[i + 1])) + SpellFinished(aabonuses.SpellOnKill[i], this, 10, 0, -1, spells[aabonuses.SpellOnKill[i]].ResistDiff); + } + + if(itembonuses.SpellOnKill[i] && IsValidSpell(itembonuses.SpellOnKill[i]) && (level >= itembonuses.SpellOnKill[i + 2])){ + if(MakeRandomInt(0, 99) < static_cast(itembonuses.SpellOnKill[i + 1])) + SpellFinished(itembonuses.SpellOnKill[i], this, 10, 0, -1, spells[aabonuses.SpellOnKill[i]].ResistDiff); + } + + if(spellbonuses.SpellOnKill[i] && IsValidSpell(spellbonuses.SpellOnKill[i]) && (level >= spellbonuses.SpellOnKill[i + 2])) { + if(MakeRandomInt(0, 99) < static_cast(spellbonuses.SpellOnKill[i + 1])) + SpellFinished(spellbonuses.SpellOnKill[i], this, 10, 0, -1, spells[aabonuses.SpellOnKill[i]].ResistDiff); + } + + } +} + +bool Mob::TrySpellOnDeath() +{ + if (IsNPC() && !spellbonuses.SpellOnDeath[0] && !itembonuses.SpellOnDeath[0]) + return false; + + if (IsClient() && !aabonuses.SpellOnDeath[0] && !spellbonuses.SpellOnDeath[0] && !itembonuses.SpellOnDeath[0]) + return false; + + for(int i = 0; i < MAX_SPELL_TRIGGER*2; i+=2) { + if(IsClient() && aabonuses.SpellOnDeath[i] && IsValidSpell(aabonuses.SpellOnDeath[i])) { + if(MakeRandomInt(0, 99) < static_cast(aabonuses.SpellOnDeath[i + 1])) { + SpellFinished(aabonuses.SpellOnDeath[i], this, 10, 0, -1, spells[aabonuses.SpellOnDeath[i]].ResistDiff); + } + } + + if(itembonuses.SpellOnDeath[i] && IsValidSpell(itembonuses.SpellOnDeath[i])) { + if(MakeRandomInt(0, 99) < static_cast(itembonuses.SpellOnDeath[i + 1])) { + SpellFinished(itembonuses.SpellOnDeath[i], this, 10, 0, -1, spells[itembonuses.SpellOnDeath[i]].ResistDiff); + } + } + + if(spellbonuses.SpellOnDeath[i] && IsValidSpell(spellbonuses.SpellOnDeath[i])) { + if(MakeRandomInt(0, 99) < static_cast(spellbonuses.SpellOnDeath[i + 1])) { + SpellFinished(spellbonuses.SpellOnDeath[i], this, 10, 0, -1, spells[spellbonuses.SpellOnDeath[i]].ResistDiff); + } + } + } + + BuffFadeAll(); + return false; + //You should not be able to use this effect and survive (ALWAYS return false), + //attempting to place a heal in these effects will still result + //in death because the heal will not register before the script kills you. +} + +int16 Mob::GetCritDmgMob(uint16 skill) +{ + int critDmg_mod = 0; + + // All skill dmg mod + Skill specific + critDmg_mod += itembonuses.CritDmgMob[HIGHEST_SKILL+1] + spellbonuses.CritDmgMob[HIGHEST_SKILL+1] + aabonuses.CritDmgMob[HIGHEST_SKILL+1] + + itembonuses.CritDmgMob[skill] + spellbonuses.CritDmgMob[skill] + aabonuses.CritDmgMob[skill]; + + if(critDmg_mod < -100) + critDmg_mod = -100; + + return critDmg_mod; +} + +void Mob::SetGrouped(bool v) +{ + if(v) + { + israidgrouped = false; + } + isgrouped = v; + + if(IsClient()) + { + parse->EventPlayer(EVENT_GROUP_CHANGE, CastToClient(), "", 0); + + if(!v) + CastToClient()->RemoveGroupXTargets(); + } +} + +void Mob::SetRaidGrouped(bool v) +{ + if(v) + { + isgrouped = false; + } + israidgrouped = v; + + if(IsClient()) + { + parse->EventPlayer(EVENT_GROUP_CHANGE, CastToClient(), "", 0); + } +} + +int16 Mob::GetCriticalChanceBonus(uint16 skill) +{ + int critical_chance = 0; + + // All skills + Skill specific + critical_chance += itembonuses.CriticalHitChance[HIGHEST_SKILL+1] + spellbonuses.CriticalHitChance[HIGHEST_SKILL+1] + aabonuses.CriticalHitChance[HIGHEST_SKILL+1] + + itembonuses.CriticalHitChance[skill] + spellbonuses.CriticalHitChance[skill] + aabonuses.CriticalHitChance[skill]; + + if(critical_chance < -100) + critical_chance = -100; + + return critical_chance; +} + +int16 Mob::GetMeleeDamageMod_SE(uint16 skill) +{ + int dmg_mod = 0; + + // All skill dmg mod + Skill specific + dmg_mod += itembonuses.DamageModifier[HIGHEST_SKILL+1] + spellbonuses.DamageModifier[HIGHEST_SKILL+1] + aabonuses.DamageModifier[HIGHEST_SKILL+1] + + itembonuses.DamageModifier[skill] + spellbonuses.DamageModifier[skill] + aabonuses.DamageModifier[skill]; + + dmg_mod += itembonuses.DamageModifier2[HIGHEST_SKILL+1] + spellbonuses.DamageModifier2[HIGHEST_SKILL+1] + aabonuses.DamageModifier2[HIGHEST_SKILL+1] + + itembonuses.DamageModifier2[skill] + spellbonuses.DamageModifier2[skill] + aabonuses.DamageModifier2[skill]; + + if (HasShieldEquiped() && !IsOffHandAtk()) + dmg_mod += itembonuses.ShieldEquipDmgMod[0] + spellbonuses.ShieldEquipDmgMod[0] + aabonuses.ShieldEquipDmgMod[0]; + + if(dmg_mod < -100) + dmg_mod = -100; + + return dmg_mod; +} + +int16 Mob::GetMeleeMinDamageMod_SE(uint16 skill) +{ + int dmg_mod = 0; + + dmg_mod = itembonuses.MinDamageModifier[skill] + spellbonuses.MinDamageModifier[skill] + + itembonuses.MinDamageModifier[HIGHEST_SKILL+1] + spellbonuses.MinDamageModifier[HIGHEST_SKILL+1]; + + if(dmg_mod < -100) + dmg_mod = -100; + + return dmg_mod; +} + +int16 Mob::GetCrippBlowChance() +{ + int16 crip_chance = 0; + + crip_chance += itembonuses.CrippBlowChance + spellbonuses.CrippBlowChance + aabonuses.CrippBlowChance; + + if(crip_chance < 0) + crip_chance = 0; + + return crip_chance; +} + +int16 Mob::GetSkillReuseTime(uint16 skill) +{ + int skill_reduction = this->itembonuses.SkillReuseTime[skill] + this->spellbonuses.SkillReuseTime[skill] + this->aabonuses.SkillReuseTime[skill]; + + return skill_reduction; +} + +int16 Mob::GetSkillDmgAmt(uint16 skill) +{ + int skill_dmg = 0; + + // All skill dmg(only spells do this) + Skill specific + skill_dmg += spellbonuses.SkillDamageAmount[HIGHEST_SKILL+1] + itembonuses.SkillDamageAmount[HIGHEST_SKILL+1] + aabonuses.SkillDamageAmount[HIGHEST_SKILL+1] + + itembonuses.SkillDamageAmount[skill] + spellbonuses.SkillDamageAmount[skill] + aabonuses.SkillDamageAmount[skill]; + + skill_dmg += spellbonuses.SkillDamageAmount2[HIGHEST_SKILL+1] + itembonuses.SkillDamageAmount2[HIGHEST_SKILL+1] + + itembonuses.SkillDamageAmount2[skill] + spellbonuses.SkillDamageAmount2[skill]; + + return skill_dmg; +} + +void Mob::MeleeLifeTap(int32 damage) { + + int16 lifetap_amt = 0; + lifetap_amt = spellbonuses.MeleeLifetap + itembonuses.MeleeLifetap + aabonuses.MeleeLifetap + + spellbonuses.Vampirism + itembonuses.Vampirism + aabonuses.Vampirism; + + if(lifetap_amt && damage > 0){ + + lifetap_amt = damage * lifetap_amt / 100; + mlog(COMBAT__DAMAGE, "Melee lifetap healing for %d damage.", damage); + + if (lifetap_amt > 0) + HealDamage(lifetap_amt); //Heal self for modified damage amount. + else + Damage(this, -lifetap_amt,0, SkillEvocation,false); //Dmg self for modified damage amount. + } +} + +bool Mob::TryReflectSpell(uint32 spell_id) +{ + if (!spells[spell_id].reflectable) + return false; + + int chance = itembonuses.reflect_chance + spellbonuses.reflect_chance + aabonuses.reflect_chance; + + if(chance && MakeRandomInt(0, 99) < chance) + return true; + + return false; +} + +void Mob::SpellProjectileEffect() +{ + bool time_disable = false; + + for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { + + if (projectile_increment[i] == 0){ + continue; + } + + Mob* target = entity_list.GetMobID(projectile_target_id[i]); + + float dist = 0; + + if (target) + dist = target->CalculateDistance(projectile_x[i], projectile_y[i], projectile_z[i]); + + int increment_end = 0; + increment_end = (dist / 10) - 1; //This pretty accurately determines end time for speed for 1.5 and timer of 250 ms + + if (increment_end <= projectile_increment[i]){ + + if (target && IsValidSpell(projectile_spell_id[i])) + SpellOnTarget(projectile_spell_id[i], target, false, true, spells[projectile_spell_id[i]].ResistDiff, true); + + projectile_spell_id[i] = 0; + projectile_target_id[i] = 0; + projectile_x[i] = 0, projectile_y[i] = 0, projectile_z[i] = 0; + projectile_increment[i] = 0; + time_disable = true; + } + + else { + projectile_increment[i]++; + time_disable = false; + } + } + + if (time_disable) + projectile_timer.Disable(); +} + + +void Mob::DoGravityEffect() +{ + Mob *caster = nullptr; + int away = -1; + float caster_x, caster_y, amount, value, cur_x, my_x, cur_y, my_y, x_vector, y_vector, hypot; + + // Set values so we can run through all gravity effects and then apply the culmative move at the end + // instead of many small moves if the mob/client had more than 1 gravity effect on them + cur_x = my_x = GetX(); + cur_y = my_y = GetY(); + + int buff_count = GetMaxTotalSlots(); + for (int slot = 0; slot < buff_count; slot++) + { + if (buffs[slot].spellid != SPELL_UNKNOWN && IsEffectInSpell(buffs[slot].spellid, SE_GravityEffect)) + { + for (int i = 0; i < EFFECT_COUNT; i++) + { + if(spells[buffs[slot].spellid].effectid[i] == SE_GravityEffect) { + + int casterId = buffs[slot].casterid; + if(casterId) + caster = entity_list.GetMob(casterId); + + if(!caster || casterId == this->GetID()) + continue; + + caster_x = caster->GetX(); + caster_y = caster->GetY(); + + value = static_cast(spells[buffs[slot].spellid].base[i]); + if(value == 0) + continue; + + if(value > 0) + away = 1; + + amount = fabs(value) / (100.0f); // to bring the values in line, arbitarily picked + + x_vector = cur_x - caster_x; + y_vector = cur_y - caster_y; + hypot = sqrt(x_vector*x_vector + y_vector*y_vector); + + if(hypot <= 5) // dont want to be inside the mob, even though we can, it looks bad + continue; + + x_vector /= hypot; + y_vector /= hypot; + + cur_x = cur_x + (x_vector * amount * away); + cur_y = cur_y + (y_vector * amount * away); + } + } + } + } + + if((fabs(my_x - cur_x) > 0.01) || (fabs(my_y - cur_y) > 0.01)) { + float new_ground = GetGroundZ(cur_x, cur_y); + // If we cant get LoS on our new spot then keep checking up to 5 units up. + if(!CheckLosFN(cur_x, cur_y, new_ground, GetSize())) { + for(float z_adjust = 0.1f; z_adjust < 5; z_adjust += 0.1f) { + if(CheckLosFN(cur_x, cur_y, new_ground+z_adjust, GetSize())) { + new_ground += z_adjust; + break; + } + } + // If we still fail, then lets only use the x portion(ie sliding around a wall) + if(!CheckLosFN(cur_x, my_y, new_ground, GetSize())) { + // If that doesnt work, try the y + if(!CheckLosFN(my_x, cur_y, new_ground, GetSize())) { + // If everything fails, then lets do nothing + return; + } + else { + cur_x = my_x; + } + } + else { + cur_y = my_y; + } + } + + if(IsClient()) + this->CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), cur_x, cur_y, new_ground, GetHeading()*2); // I know the heading thing is weird(chance of movepc to halve the heading value, too lazy to figure out why atm) + else + this->GMMove(cur_x, cur_y, new_ground, GetHeading()); + } +} + +void Mob::SpreadVirus(uint16 spell_id, uint16 casterID) +{ + int num_targs = spells[spell_id].viral_targets; + + Mob* caster = entity_list.GetMob(casterID); + Mob* target = nullptr; + // Only spread in zones without perm buffs + if(!zone->BuffTimersSuspended()) { + for(int i = 0; i < num_targs; i++) { + target = entity_list.GetTargetForVirus(this); + if(target) { + // Only spreads to the uninfected + if(!target->FindBuff(spell_id)) { + if(caster) + caster->SpellOnTarget(spell_id, target); + + } + } + } + } +} + +void Mob::RemoveNimbusEffect(int effectid) +{ + EQApplicationPacket* outapp = new EQApplicationPacket(OP_RemoveNimbusEffect, sizeof(RemoveNimbusEffect_Struct)); + RemoveNimbusEffect_Struct* rne = (RemoveNimbusEffect_Struct*)outapp->pBuffer; + rne->spawnid = GetID(); + rne->nimbus_effect = effectid; + entity_list.QueueClients(this, outapp); + safe_delete(outapp); +} + +bool Mob::IsBoat() const { + return (race == 72 || race == 73 || race == 114 || race == 404 || race == 550 || race == 551 || race == 552); +} + +void Mob::SetBodyType(bodyType new_body, bool overwrite_orig) { + bool needs_spawn_packet = false; + if(bodytype == 11 || bodytype >= 65 || new_body == 11 || new_body >= 65) { + needs_spawn_packet = true; + } + + if(overwrite_orig) { + orig_bodytype = new_body; + } + bodytype = new_body; + + if(needs_spawn_packet) { + EQApplicationPacket* app = new EQApplicationPacket; + CreateDespawnPacket(app, true); + entity_list.QueueClients(this, app); + CreateSpawnPacket(app, this); + entity_list.QueueClients(this, app); + safe_delete(app); + } +} + + +void Mob::ModSkillDmgTaken(SkillUseTypes skill_num, int value) +{ + if (skill_num <= HIGHEST_SKILL) + SkillDmgTaken_Mod[skill_num] = value; + + + else if (skill_num == 255 || skill_num == -1) + SkillDmgTaken_Mod[HIGHEST_SKILL+1] = value; +} + +int16 Mob::GetModSkillDmgTaken(const SkillUseTypes skill_num) +{ + if (skill_num <= HIGHEST_SKILL) + return SkillDmgTaken_Mod[skill_num]; + + else if (skill_num == 255 || skill_num == -1) + return SkillDmgTaken_Mod[HIGHEST_SKILL+1]; + + return 0; +} + +void Mob::ModVulnerability(uint8 resist, int16 value) +{ + if (resist < HIGHEST_RESIST+1) + Vulnerability_Mod[resist] = value; + + else if (resist == 255) + Vulnerability_Mod[HIGHEST_RESIST+1] = value; +} + +int16 Mob::GetModVulnerability(const uint8 resist) +{ + if (resist < HIGHEST_RESIST+1) + return Vulnerability_Mod[resist]; + + else if (resist == 255) + return Vulnerability_Mod[HIGHEST_RESIST+1]; + + return 0; +} + +void Mob::CastOnCurer(uint32 spell_id) +{ + for(int i = 0; i < EFFECT_COUNT; i++) + { + if (spells[spell_id].effectid[i] == SE_CastOnCurer) + { + if(IsValidSpell(spells[spell_id].base[i])) + { + SpellFinished(spells[spell_id].base[i], this); + } + } + } +} + +void Mob::CastOnCure(uint32 spell_id) +{ + for(int i = 0; i < EFFECT_COUNT; i++) + { + if (spells[spell_id].effectid[i] == SE_CastOnCure) + { + if(IsValidSpell(spells[spell_id].base[i])) + { + SpellFinished(spells[spell_id].base[i], this); + } + } + } +} + +void Mob::CastOnNumHitFade(uint32 spell_id) +{ + if(!IsValidSpell(spell_id)) + return; + + for(int i = 0; i < EFFECT_COUNT; i++) + { + if (spells[spell_id].effectid[i] == SE_CastonNumHitFade) + { + if(IsValidSpell(spells[spell_id].base[i])) + { + SpellFinished(spells[spell_id].base[i], this); + } + } + } +} + +void Mob::SlowMitigation(Mob* caster) +{ + if (GetSlowMitigation() && caster && caster->IsClient()) + { + if ((GetSlowMitigation() > 0) && (GetSlowMitigation() < 26)) + caster->Message_StringID(MT_SpellFailure, SLOW_MOSTLY_SUCCESSFUL); + + else if ((GetSlowMitigation() >= 26) && (GetSlowMitigation() < 74)) + caster->Message_StringID(MT_SpellFailure, SLOW_PARTIALLY_SUCCESSFUL); + + else if ((GetSlowMitigation() >= 74) && (GetSlowMitigation() < 101)) + caster->Message_StringID(MT_SpellFailure, SLOW_SLIGHTLY_SUCCESSFUL); + + else if (GetSlowMitigation() > 100) + caster->Message_StringID(MT_SpellFailure, SPELL_OPPOSITE_EFFECT); + } +} + +uint16 Mob::GetSkillByItemType(int ItemType) +{ + switch (ItemType) + { + case ItemType1HSlash: + return Skill1HSlashing; + case ItemType2HSlash: + return Skill2HSlashing; + case ItemType1HPiercing: + return Skill1HPiercing; + case ItemType1HBlunt: + return Skill1HBlunt; + case ItemType2HBlunt: + return Skill2HBlunt; + case ItemType2HPiercing: + return Skill1HPiercing; // change to 2HPiercing once activated + case ItemTypeMartial: + return SkillHandtoHand; + default: + return SkillHandtoHand; + } + return SkillHandtoHand; + } + + +bool Mob::PassLimitToSkill(uint16 spell_id, uint16 skill) { + + if (!IsValidSpell(spell_id)) + return false; + + for (int i = 0; i < EFFECT_COUNT; i++) { + if (spells[spell_id].effectid[i] == SE_LimitToSkill){ + if (spells[spell_id].base[i] == skill){ + return true; + } + } + } + return false; +} + +uint16 Mob::GetWeaponSpeedbyHand(uint16 hand) { + + uint16 weapon_speed = 0; + switch (hand) { + + case 13: + weapon_speed = attack_timer.GetDuration(); + break; + case 14: + weapon_speed = attack_dw_timer.GetDuration(); + break; + case 11: + weapon_speed = ranged_timer.GetDuration(); + break; + } + + if (weapon_speed < RuleI(Combat, MinHastedDelay)) + weapon_speed = RuleI(Combat, MinHastedDelay); + + return weapon_speed; +} + +int8 Mob::GetDecayEffectValue(uint16 spell_id, uint16 spelleffect) { + + if (!IsValidSpell(spell_id)) + return false; + + int spell_level = spells[spell_id].classes[(GetClass()%16) - 1]; + int effect_value = 0; + int lvlModifier = 100; + + int buff_count = GetMaxTotalSlots(); + for (int slot = 0; slot < buff_count; slot++){ + if (IsValidSpell(buffs[slot].spellid)){ + for (int i = 0; i < EFFECT_COUNT; i++){ + if(spells[buffs[slot].spellid].effectid[i] == spelleffect) { + + int critchance = spells[buffs[slot].spellid].base[i]; + int decay = spells[buffs[slot].spellid].base2[i]; + int lvldiff = spell_level - spells[buffs[slot].spellid].max[i]; + + if(lvldiff > 0 && decay > 0) + { + lvlModifier -= decay*lvldiff; + if (lvlModifier > 0){ + critchance = (critchance*lvlModifier)/100; + effect_value += critchance; + } + } + + else + effect_value += critchance; + } + } + } + } + + return effect_value; +} + +// Faction Mods for Alliance type spells +void Mob::AddFactionBonus(uint32 pFactionID,int32 bonus) { + std::map :: const_iterator faction_bonus; + typedef std::pair NewFactionBonus; + + faction_bonus = faction_bonuses.find(pFactionID); + if(faction_bonus == faction_bonuses.end()) + { + faction_bonuses.insert(NewFactionBonus(pFactionID,bonus)); + } + else + { + if(faction_bonus->second :: const_iterator faction_bonus; + typedef std::pair NewFactionBonus; + + faction_bonus = item_faction_bonuses.find(pFactionID); + if(faction_bonus == item_faction_bonuses.end()) + { + item_faction_bonuses.insert(NewFactionBonus(pFactionID,bonus)); + } + else + { + if((bonus > 0 && faction_bonus->second < bonus) || (bonus < 0 && faction_bonus->second > bonus)) + { + item_faction_bonuses.erase(pFactionID); + item_faction_bonuses.insert(NewFactionBonus(pFactionID,bonus)); + } + } +} + +int32 Mob::GetFactionBonus(uint32 pFactionID) { + std::map :: const_iterator faction_bonus; + faction_bonus = faction_bonuses.find(pFactionID); + if(faction_bonus != faction_bonuses.end()) + { + return (*faction_bonus).second; + } + return 0; +} + +int32 Mob::GetItemFactionBonus(uint32 pFactionID) { + std::map :: const_iterator faction_bonus; + faction_bonus = item_faction_bonuses.find(pFactionID); + if(faction_bonus != item_faction_bonuses.end()) + { + return (*faction_bonus).second; + } + return 0; +} + +void Mob::ClearItemFactionBonuses() { + item_faction_bonuses.clear(); +} + +FACTION_VALUE Mob::GetSpecialFactionCon(Mob* iOther) { + if (!iOther) + return FACTION_INDIFFERENT; + + iOther = iOther->GetOwnerOrSelf(); + Mob* self = this->GetOwnerOrSelf(); + + bool selfAIcontrolled = self->IsAIControlled(); + bool iOtherAIControlled = iOther->IsAIControlled(); + int selfPrimaryFaction = self->GetPrimaryFaction(); + int iOtherPrimaryFaction = iOther->GetPrimaryFaction(); + + if (selfPrimaryFaction >= 0 && selfAIcontrolled) + return FACTION_INDIFFERENT; + if (iOther->GetPrimaryFaction() >= 0) + return FACTION_INDIFFERENT; +/* special values: + -2 = indiff to player, ally to AI on special values, indiff to AI + -3 = dub to player, ally to AI on special values, indiff to AI + -4 = atk to player, ally to AI on special values, indiff to AI + -5 = indiff to player, indiff to AI + -6 = dub to player, indiff to AI + -7 = atk to player, indiff to AI + -8 = indiff to players, ally to AI on same value, indiff to AI + -9 = dub to players, ally to AI on same value, indiff to AI + -10 = atk to players, ally to AI on same value, indiff to AI + -11 = indiff to players, ally to AI on same value, atk to AI + -12 = dub to players, ally to AI on same value, atk to AI + -13 = atk to players, ally to AI on same value, atk to AI +*/ + switch (iOtherPrimaryFaction) { + case -2: // -2 = indiff to player, ally to AI on special values, indiff to AI + if (selfAIcontrolled && iOtherAIControlled) + return FACTION_ALLY; + else + return FACTION_INDIFFERENT; + case -3: // -3 = dub to player, ally to AI on special values, indiff to AI + if (selfAIcontrolled && iOtherAIControlled) + return FACTION_ALLY; + else + return FACTION_DUBIOUS; + case -4: // -4 = atk to player, ally to AI on special values, indiff to AI + if (selfAIcontrolled && iOtherAIControlled) + return FACTION_ALLY; + else + return FACTION_SCOWLS; + case -5: // -5 = indiff to player, indiff to AI + return FACTION_INDIFFERENT; + case -6: // -6 = dub to player, indiff to AI + if (selfAIcontrolled && iOtherAIControlled) + return FACTION_INDIFFERENT; + else + return FACTION_DUBIOUS; + case -7: // -7 = atk to player, indiff to AI + if (selfAIcontrolled && iOtherAIControlled) + return FACTION_INDIFFERENT; + else + return FACTION_SCOWLS; + case -8: // -8 = indiff to players, ally to AI on same value, indiff to AI + if (selfAIcontrolled && iOtherAIControlled) { + if (selfPrimaryFaction == iOtherPrimaryFaction) + return FACTION_ALLY; + else + return FACTION_INDIFFERENT; + } + else + return FACTION_INDIFFERENT; + case -9: // -9 = dub to players, ally to AI on same value, indiff to AI + if (selfAIcontrolled && iOtherAIControlled) { + if (selfPrimaryFaction == iOtherPrimaryFaction) + return FACTION_ALLY; + else + return FACTION_INDIFFERENT; + } + else + return FACTION_DUBIOUS; + case -10: // -10 = atk to players, ally to AI on same value, indiff to AI + if (selfAIcontrolled && iOtherAIControlled) { + if (selfPrimaryFaction == iOtherPrimaryFaction) + return FACTION_ALLY; + else + return FACTION_INDIFFERENT; + } + else + return FACTION_SCOWLS; + case -11: // -11 = indiff to players, ally to AI on same value, atk to AI + if (selfAIcontrolled && iOtherAIControlled) { + if (selfPrimaryFaction == iOtherPrimaryFaction) + return FACTION_ALLY; + else + return FACTION_SCOWLS; + } + else + return FACTION_INDIFFERENT; + case -12: // -12 = dub to players, ally to AI on same value, atk to AI + if (selfAIcontrolled && iOtherAIControlled) { + if (selfPrimaryFaction == iOtherPrimaryFaction) + return FACTION_ALLY; + else + return FACTION_SCOWLS; + + + } + else + return FACTION_DUBIOUS; + case -13: // -13 = atk to players, ally to AI on same value, atk to AI + if (selfAIcontrolled && iOtherAIControlled) { + if (selfPrimaryFaction == iOtherPrimaryFaction) + return FACTION_ALLY; + else + return FACTION_SCOWLS; + } + else + return FACTION_SCOWLS; + default: + return FACTION_INDIFFERENT; + } +} + +bool Mob::HasSpellEffect(int effectid) +{ + int i; + + uint32 buff_count = GetMaxTotalSlots(); + for(i = 0; i < buff_count; i++) + { + if(buffs[i].spellid == SPELL_UNKNOWN) { continue; } + + if(IsEffectInSpell(buffs[i].spellid, effectid)) + { + return(1); + } + } + return(0); +} + +int Mob::GetSpecialAbility(int ability) { + if(ability >= MAX_SPECIAL_ATTACK || ability < 0) { + return 0; + } + + return SpecialAbilities[ability].level; +} + +int Mob::GetSpecialAbilityParam(int ability, int param) { + if(param >= MAX_SPECIAL_ATTACK_PARAMS || param < 0 || ability >= MAX_SPECIAL_ATTACK || ability < 0) { + return 0; + } + + return SpecialAbilities[ability].params[param]; +} + +void Mob::SetSpecialAbility(int ability, int level) { + if(ability >= MAX_SPECIAL_ATTACK || ability < 0) { + return; + } + + SpecialAbilities[ability].level = level; +} + +void Mob::SetSpecialAbilityParam(int ability, int param, int value) { + if(param >= MAX_SPECIAL_ATTACK_PARAMS || param < 0 || ability >= MAX_SPECIAL_ATTACK || ability < 0) { + return; + } + + SpecialAbilities[ability].params[param] = value; +} + +void Mob::StartSpecialAbilityTimer(int ability, uint32 time) { + if (ability >= MAX_SPECIAL_ATTACK || ability < 0) { + return; + } + + if(SpecialAbilities[ability].timer) { + SpecialAbilities[ability].timer->Start(time); + } else { + SpecialAbilities[ability].timer = new Timer(time); + SpecialAbilities[ability].timer->Start(); + } +} + +void Mob::StopSpecialAbilityTimer(int ability) { + if (ability >= MAX_SPECIAL_ATTACK || ability < 0) { + return; + } + + safe_delete(SpecialAbilities[ability].timer); +} + +Timer *Mob::GetSpecialAbilityTimer(int ability) { + if (ability >= MAX_SPECIAL_ATTACK || ability < 0) { + return nullptr; + } + + return SpecialAbilities[ability].timer; +} + +void Mob::ClearSpecialAbilities() { + for(int a = 0; a < MAX_SPECIAL_ATTACK; ++a) { + SpecialAbilities[a].level = 0; + safe_delete(SpecialAbilities[a].timer); + for(int p = 0; p < MAX_SPECIAL_ATTACK_PARAMS; ++p) { + SpecialAbilities[a].params[p] = 0; + } + } +} + +void Mob::ProcessSpecialAbilities(const std::string str) { + ClearSpecialAbilities(); + + std::vector sp = SplitString(str, '^'); + for(auto iter = sp.begin(); iter != sp.end(); ++iter) { + std::vector sub_sp = SplitString((*iter), ','); + if(sub_sp.size() >= 2) { + int ability = std::stoi(sub_sp[0]); + int value = std::stoi(sub_sp[1]); + + SetSpecialAbility(ability, value); + switch(ability) { + case SPECATK_QUAD: + if(value > 0) { + SetSpecialAbility(SPECATK_TRIPLE, 1); + } + break; + case DESTRUCTIBLE_OBJECT: + if(value == 0) { + SetDestructibleObject(false); + } else { + SetDestructibleObject(true); + } + break; + default: + break; + } + + for(size_t i = 2, p = 0; i < sub_sp.size(); ++i, ++p) { + if(p >= MAX_SPECIAL_ATTACK_PARAMS) { + break; + } + + SetSpecialAbilityParam(ability, p, std::stoi(sub_sp[i])); + } + } + } +} + +// derived from client to keep these functions more consistent +// if anything seems weird, blame SoE +bool Mob::IsFacingMob(Mob *other) +{ + if (!other) + return false; + float angle = HeadingAngleToMob(other); + // what the client uses appears to be 2x our internal heading + float heading = GetHeading() * 2.0; + + if (angle > 472.0 && heading < 40.0) + angle = heading; + if (angle < 40.0 && heading > 472.0) + angle = heading; + + if (fabs(angle - heading) <= 80.0) + return true; + + return false; +} + +// All numbers derived from the client +float Mob::HeadingAngleToMob(Mob *other) +{ + float mob_x = other->GetX(); + float mob_y = other->GetY(); + float this_x = GetX(); + float this_y = GetY(); + + float y_diff = fabs(this_y - mob_y); + float x_diff = fabs(this_x - mob_x); + if (y_diff < 0.0000009999999974752427) + y_diff = 0.0000009999999974752427; + + float angle = atan2(x_diff, y_diff) * 180.0 * 0.3183099014828645; // angle, nice "pi" + + // return the right thing based on relative quadrant + // I'm sure this could be improved for readability, but whatever + if (this_y >= mob_y) { + if (mob_x >= this_x) + return (90.0 - angle + 90.0) * 511.5 * 0.0027777778; + if (mob_x <= this_x) + return (angle + 180.0) * 511.5 * 0.0027777778; + } + if (this_y > mob_y || mob_x > this_x) + return angle * 511.5 * 0.0027777778; + else + return (90.0 - angle + 270.0) * 511.5 * 0.0027777778; +} + diff --git a/zone/mobx.h b/zone/mobx.h new file mode 100644 index 000000000..4cd6d5f49 --- /dev/null +++ b/zone/mobx.h @@ -0,0 +1,1236 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2002 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 +*/ +#ifndef MOB_H +#define MOB_H + +#include "../common/features.h" +#include "common.h" +#include "entity.h" +#include "hate_list.h" +#include "pathing.h" +#include +#include +#include + +char* strn0cpy(char* dest, const char* source, uint32 size); + +#define MAX_SPECIAL_ATTACK_PARAMS 8 + +class EGNode; +class MobFearState; +class Mob : public Entity { +public: + enum CLIENT_CONN_STATUS { CLIENT_CONNECTING, CLIENT_CONNECTED, CLIENT_LINKDEAD, + CLIENT_KICKED, DISCONNECTED, CLIENT_ERROR, CLIENT_CONNECTINGALL }; + enum eStandingPetOrder { SPO_Follow, SPO_Sit, SPO_Guard }; + + struct SpecialAbility { + SpecialAbility() { + level = 0; + timer = nullptr; + for(int i = 0; i < MAX_SPECIAL_ATTACK_PARAMS; ++i) { + params[i] = 0; + } + } + + ~SpecialAbility() { + safe_delete(timer); + } + + int level; + Timer *timer; + int params[MAX_SPECIAL_ATTACK_PARAMS]; + }; + + Mob(const char* in_name, + const char* in_lastname, + int32 in_cur_hp, + int32 in_max_hp, + uint8 in_gender, + uint16 in_race, + uint8 in_class, + bodyType in_bodytype, + uint8 in_deity, + uint8 in_level, + uint32 in_npctype_id, + float in_size, + float in_runspeed, + float in_heading, + float in_x_pos, + float in_y_pos, + float in_z_pos, + uint8 in_light, + uint8 in_texture, + uint8 in_helmtexture, + uint16 in_ac, + uint16 in_atk, + uint16 in_str, + uint16 in_sta, + uint16 in_dex, + uint16 in_agi, + uint16 in_int, + uint16 in_wis, + uint16 in_cha, + uint8 in_haircolor, + uint8 in_beardcolor, + uint8 in_eyecolor1, // the eyecolors always seem to be the same, maybe left and right eye? + uint8 in_eyecolor2, + uint8 in_hairstyle, + uint8 in_luclinface, + uint8 in_beard, + uint32 in_drakkin_heritage, + uint32 in_drakkin_tattoo, + uint32 in_drakkin_details, + uint32 in_armor_tint[_MaterialCount], + uint8 in_aa_title, + uint8 in_see_invis, // see through invis + uint8 in_see_invis_undead, // see through invis vs. undead + uint8 in_see_hide, + uint8 in_see_improved_hide, + int32 in_hp_regen, + int32 in_mana_regen, + uint8 in_qglobal, + uint8 in_maxlevel, + uint32 in_scalerate + ); + virtual ~Mob(); + + inline virtual bool IsMob() const { return true; } + inline virtual bool InZone() const { return true; } + + //Somewhat sorted: needs documenting! + + //Attack + virtual void RogueBackstab(Mob* other, bool min_damage = false, int ReuseTime = 10); + virtual void RogueAssassinate(Mob* other); // solar + float MobAngle(Mob *other = 0, float ourx = 0.0f, float oury = 0.0f) const; + // greater than 90 is behind + inline bool BehindMob(Mob *other = 0, float ourx = 0.0f, float oury = 0.0f) const + { return (!other || other == this) ? true : MobAngle(other, ourx, oury) > 90.0f; } + // less than 56 is in front, greater than 56 is usually where the client generates the messages + inline bool InFrontMob(Mob *other = 0, float ourx = 0.0f, float oury = 0.0f) const + { return (!other || other == this) ? true : MobAngle(other, ourx, oury) < 56.0f; } + bool IsFacingMob(Mob *other); // kind of does the same as InFrontMob, but derived from client + float HeadingAngleToMob(Mob *other); // to keep consistent with client generated messages + virtual void RangedAttack(Mob* other) { } + virtual void ThrowingAttack(Mob* other) { } + uint16 GetThrownDamage(int16 wDmg, int32& TotalDmg, int& minDmg); + // 13 = Primary (default), 14 = secondary + virtual bool Attack(Mob* other, int Hand = 13, bool FromRiposte = false, bool IsStrikethrough = false, + bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr) = 0; + int MonkSpecialAttack(Mob* other, uint8 skill_used); + virtual void TryBackstab(Mob *other,int ReuseTime = 10); + void TriggerDefensiveProcs(const ItemInst* weapon, Mob *on, uint16 hand = 13, int damage = 0); + virtual bool AvoidDamage(Mob* attacker, int32 &damage, bool CanRiposte = true); + virtual bool CheckHitChance(Mob* attacker, SkillUseTypes skillinuse, int Hand, int16 chance_mod = 0); + virtual void TryCriticalHit(Mob *defender, uint16 skill, int32 &damage, ExtraAttackOptions *opts = nullptr); + void TryPetCriticalHit(Mob *defender, uint16 skill, int32 &damage); + virtual bool TryFinishingBlow(Mob *defender, SkillUseTypes skillinuse); + uint32 TryHeadShot(Mob* defender, SkillUseTypes skillInUse); + uint32 TryAssassinate(Mob* defender, SkillUseTypes skillInUse, uint16 ReuseTime); + virtual void DoRiposte(Mob* defender); + void ApplyMeleeDamageBonus(uint16 skill, int32 &damage); + virtual void MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit, ExtraAttackOptions *opts = nullptr); + virtual int32 GetMeleeMitDmg(Mob *attacker, int32 damage, int32 minhit, float mit_rating, float atk_rating); + bool CombatRange(Mob* other); + virtual inline bool IsBerserk() { return false; } // only clients + void RogueEvade(Mob *other); + + //Appearance + void SendLevelAppearance(); + void SendStunAppearance(); + void SendAppearanceEffect(uint32 parm1, uint32 parm2, uint32 parm3, uint32 parm4, uint32 parm5, + Client *specific_target=nullptr); + void SendTargetable(bool on, Client *specific_target = nullptr); + virtual void SendWearChange(uint8 material_slot); + virtual void SendTextureWC(uint8 slot, uint16 texture, uint32 hero_forge_model = 0, uint32 elite_material = 0, + uint32 unknown06 = 0, uint32 unknown18 = 0); + virtual void SetSlotTint(uint8 material_slot, uint8 red_tint, uint8 green_tint, uint8 blue_tint); + virtual void WearChange(uint8 material_slot, uint16 texture, uint32 color); + void DoAnim(const int animnum, int type=0, bool ackreq = true, eqFilterType filter = FilterNone); + void ProjectileAnimation(Mob* to, int item_id, bool IsArrow = false, float speed = 0, + float angle = 0, float tilt = 0, float arc = 0, const char *IDFile = nullptr); + void ChangeSize(float in_size, bool bNoRestriction = false); + inline uint8 SeeInvisible() const { return see_invis; } + inline bool SeeInvisibleUndead() const { return see_invis_undead; } + inline bool SeeHide() const { return see_hide; } + inline bool SeeImprovedHide() const { return see_improved_hide; } + bool IsInvisible(Mob* other = 0) const; + void SetInvisible(uint8 state); + bool AttackAnimation(SkillUseTypes &skillinuse, int Hand, const ItemInst* weapon); + + //Song + bool UseBardSpellLogic(uint16 spell_id = 0xffff, int slot = -1); + bool ApplyNextBardPulse(uint16 spell_id, Mob *spell_target, uint16 slot); + void BardPulse(uint16 spell_id, Mob *caster); + + //Spell + void SendSpellEffect(uint32 effectid, uint32 duration, uint32 finish_delay, bool zone_wide, + uint32 unk020, bool perm_effect = false, Client *c = nullptr); + bool IsBeneficialAllowed(Mob *target); + virtual int GetCasterLevel(uint16 spell_id); + void ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* newbon, uint16 casterID = 0, + bool item_bonus = false, uint32 ticsremaining = 0, int buffslot = -1, + bool IsAISpellEffect = false, uint16 effect_id = 0, int32 se_base = 0, int32 se_limit = 0, int32 se_max = 0); + void NegateSpellsBonuses(uint16 spell_id); + virtual float GetActSpellRange(uint16 spell_id, float range, bool IsBard = false) { return range;} + virtual int32 GetActSpellDamage(uint16 spell_id, int32 value, Mob* target = nullptr) { return value; } + virtual int32 GetActSpellHealing(uint16 spell_id, int32 value, Mob* target = nullptr) { return value; } + virtual int32 GetActSpellCost(uint16 spell_id, int32 cost){ return cost;} + virtual int32 GetActSpellDuration(uint16 spell_id, int32 duration){ return duration;} + virtual int32 GetActSpellCasttime(uint16 spell_id, int32 casttime); + float ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use_resist_override = false, + int resist_override = 0, bool CharismaCheck = false, bool CharmTick = false, bool IsRoot = false); + int ResistPhysical(int level_diff, uint8 caster_level); + uint16 GetSpecializeSkillValue(uint16 spell_id) const; + void SendSpellBarDisable(); + void SendSpellBarEnable(uint16 spellid); + void ZeroCastingVars(); + virtual void SpellProcess(); + virtual bool CastSpell(uint16 spell_id, uint16 target_id, uint16 slot = 10, int32 casttime = -1, + int32 mana_cost = -1, uint32* oSpellWillFinish = 0, uint32 item_slot = 0xFFFFFFFF, + uint32 timer = 0xFFFFFFFF, uint32 timer_duration = 0, uint32 type = 0, int16 *resist_adjust = nullptr); + virtual bool DoCastSpell(uint16 spell_id, uint16 target_id, uint16 slot = 10, int32 casttime = -1, + int32 mana_cost = -1, uint32* oSpellWillFinish = 0, uint32 item_slot = 0xFFFFFFFF, + uint32 timer = 0xFFFFFFFF, uint32 timer_duration = 0, uint32 type = 0, int16 resist_adjust = 0); + void CastedSpellFinished(uint16 spell_id, uint32 target_id, uint16 slot, uint16 mana_used, + uint32 inventory_slot = 0xFFFFFFFF, int16 resist_adjust = 0); + bool SpellFinished(uint16 spell_id, Mob *target, uint16 slot = 10, uint16 mana_used = 0, + uint32 inventory_slot = 0xFFFFFFFF, int16 resist_adjust = 0, bool isproc = false); + virtual bool SpellOnTarget(uint16 spell_id, Mob* spelltar, bool reflect = false, + bool use_resist_adjust = false, int16 resist_adjust = 0, bool isproc = false); + virtual bool SpellEffect(Mob* caster, uint16 spell_id, float partial = 100); + virtual bool DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_center, + CastAction_type &CastAction); + virtual bool CheckFizzle(uint16 spell_id); + virtual bool CheckSpellLevelRestriction(uint16 spell_id); + virtual bool IsImmuneToSpell(uint16 spell_id, Mob *caster); + virtual float GetAOERange(uint16 spell_id); + void InterruptSpell(uint16 spellid = SPELL_UNKNOWN); + void InterruptSpell(uint16, uint16, uint16 spellid = SPELL_UNKNOWN); + inline bool IsCasting() const { return((casting_spell_id != 0)); } + uint16 CastingSpellID() const { return casting_spell_id; } + bool DoCastingChecks(); + bool TryDispel(uint8 caster_level, uint8 buff_level, int level_modifier); + void SpellProjectileEffect(); + bool TrySpellProjectile(Mob* spell_target, uint16 spell_id); + void ResourceTap(int32 damage, uint16 spell_id); + void TryTriggerThreshHold(int32 damage, int effect_id, Mob* attacker); + bool CheckSpellCategory(uint16 spell_id, int category_id, int effect_id); + + + //Buff + void BuffProcess(); + virtual void DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caster_level, Mob* caster = 0); + void BuffFadeBySpellID(uint16 spell_id); + void BuffFadeByEffect(int effectid, int skipslot = -1); + void BuffFadeAll(); + void BuffFadeNonPersistDeath(); + void BuffFadeDetrimental(); + void BuffFadeBySlot(int slot, bool iRecalcBonuses = true); + void BuffFadeDetrimentalByCaster(Mob *caster); + void BuffFadeBySitModifier(); + void BuffModifyDurationBySpellID(uint16 spell_id, int32 newDuration); + int AddBuff(Mob *caster, const uint16 spell_id, int duration = 0, int32 level_override = -1); + int CanBuffStack(uint16 spellid, uint8 caster_level, bool iFailIfOverwrite = false); + int CalcBuffDuration(Mob *caster, Mob *target, uint16 spell_id, int32 caster_level_override = -1); + void SendPetBuffsToClient(); + virtual int GetCurrentBuffSlots() const { return 0; } + virtual int GetCurrentSongSlots() const { return 0; } + virtual int GetCurrentDiscSlots() const { return 0; } + virtual int GetMaxBuffSlots() const { return 0; } + virtual int GetMaxSongSlots() const { return 0; } + virtual int GetMaxDiscSlots() const { return 0; } + virtual int GetMaxTotalSlots() const { return 0; } + virtual void InitializeBuffSlots() { buffs = nullptr; current_buff_count = 0; } + virtual void UninitializeBuffSlots() { } + EQApplicationPacket *MakeBuffsPacket(bool for_target = true); + void SendBuffsToClient(Client *c); + inline Buffs_Struct* GetBuffs() { return buffs; } + void DoGravityEffect(); + void DamageShield(Mob* other, bool spell_ds = false); + int32 RuneAbsorb(int32 damage, uint16 type); + bool FindBuff(uint16 spellid); + bool FindType(uint16 type, bool bOffensive = false, uint16 threshold = 100); + int16 GetBuffSlotFromType(uint16 type); + uint16 GetSpellIDFromSlot(uint8 slot); + int CountDispellableBuffs(); + void CheckNumHitsRemaining(uint8 type, uint32 buff_slot=0, uint16 spell_id=SPELL_UNKNOWN); + bool HasNumhits() const { return has_numhits; } + inline void Numhits(bool val) { has_numhits = val; } + bool HasMGB() const { return has_MGB; } + inline void SetMGB(bool val) { has_MGB = val; } + bool HasProjectIllusion() const { return has_ProjectIllusion ; } + inline void SetProjectIllusion(bool val) { has_ProjectIllusion = val; } + void SpreadVirus(uint16 spell_id, uint16 casterID); + bool IsNimbusEffectActive(uint32 nimbus_effect); + void SetNimbusEffect(uint32 nimbus_effect); + inline virtual uint32 GetNimbusEffect1() const { return nimbus_effect1; } + inline virtual uint32 GetNimbusEffect2() const { return nimbus_effect2; } + inline virtual uint32 GetNimbusEffect3() const { return nimbus_effect3; } + void RemoveNimbusEffect(int effectid); + + //Basic Stats/Inventory + virtual void SetLevel(uint8 in_level, bool command = false) { level = in_level; } + void TempName(const char *newname = nullptr); + void SetTargetable(bool on); + bool IsTargetable() const { return m_targetable; } + bool HasShieldEquiped() const { return has_shieldequiped; } + inline void ShieldEquiped(bool val) { has_shieldequiped = val; } + virtual uint16 GetSkill(SkillUseTypes skill_num) const { return 0; } + virtual uint32 GetEquipment(uint8 material_slot) const { return(0); } + virtual int32 GetEquipmentMaterial(uint8 material_slot) const; + virtual uint32 GetEquipmentColor(uint8 material_slot) const; + virtual uint32 IsEliteMaterialItem(uint8 material_slot) const; + bool AffectedBySpellExcludingSlot(int slot, int effect); + virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, SkillUseTypes attack_skill) = 0; + virtual void Damage(Mob* from, int32 damage, uint16 spell_id, SkillUseTypes attack_skill, + bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false) = 0; + inline virtual void SetHP(int32 hp) { if (hp >= max_hp) cur_hp = max_hp; else cur_hp = hp;} + bool ChangeHP(Mob* other, int32 amount, uint16 spell_id = 0, int8 buffslot = -1, bool iBuffTic = false); + inline void SetOOCRegen(int32 newoocregen) {oocregen = newoocregen;} + virtual void Heal(); + virtual void HealDamage(uint32 ammount, Mob* caster = nullptr, uint16 spell_id = SPELL_UNKNOWN); + virtual void SetMaxHP() { cur_hp = max_hp; } + virtual inline uint16 GetBaseRace() const { return base_race; } + virtual inline uint8 GetBaseGender() const { return base_gender; } + virtual inline uint16 GetDeity() const { return deity; } + inline uint16 GetRace() const { return race; } + inline uint8 GetGender() const { return gender; } + inline uint8 GetTexture() const { return texture; } + inline uint8 GetHelmTexture() const { return helmtexture; } + inline uint8 GetHairColor() const { return haircolor; } + inline uint8 GetBeardColor() const { return beardcolor; } + inline uint8 GetEyeColor1() const { return eyecolor1; } + inline uint8 GetEyeColor2() const { return eyecolor2; } + inline uint8 GetHairStyle() const { return hairstyle; } + inline uint8 GetLuclinFace() const { return luclinface; } + inline uint8 GetBeard() const { return beard; } + inline uint8 GetDrakkinHeritage() const { return drakkin_heritage; } + inline uint8 GetDrakkinTattoo() const { return drakkin_tattoo; } + inline uint8 GetDrakkinDetails() const { return drakkin_details; } + inline uint32 GetArmorTint(uint8 i) const { return armor_tint[(i < _MaterialCount) ? i : 0]; } + inline uint8 GetClass() const { return class_; } + inline uint8 GetLevel() const { return level; } + inline uint8 GetOrigLevel() const { return orig_level; } + inline const char* GetName() const { return name; } + inline const char* GetOrigName() const { return orig_name; } + inline const char* GetLastName() const { return lastname; } + const char *GetCleanName(); + virtual void SetName(const char *new_name = nullptr) { new_name ? strn0cpy(name, new_name, 64) : + strn0cpy(name, GetName(), 64); return; }; + inline Mob* GetTarget() const { return target; } + virtual void SetTarget(Mob* mob); + virtual inline float GetHPRatio() const { return max_hp == 0 ? 0 : ((float)cur_hp/max_hp*100); } + inline virtual int16 GetAC() const { return AC + itembonuses.AC + spellbonuses.AC; } + inline virtual int16 GetATK() const { return ATK + itembonuses.ATK + spellbonuses.ATK; } + inline virtual int16 GetATKBonus() const { return itembonuses.ATK + spellbonuses.ATK; } + inline virtual int16 GetSTR() const { return STR + itembonuses.STR + spellbonuses.STR; } + inline virtual int16 GetSTA() const { return STA + itembonuses.STA + spellbonuses.STA; } + inline virtual int16 GetDEX() const { return DEX + itembonuses.DEX + spellbonuses.DEX; } + inline virtual int16 GetAGI() const { return AGI + itembonuses.AGI + spellbonuses.AGI; } + inline virtual int16 GetINT() const { return INT + itembonuses.INT + spellbonuses.INT; } + inline virtual int16 GetWIS() const { return WIS + itembonuses.WIS + spellbonuses.WIS; } + inline virtual int16 GetCHA() const { return CHA + itembonuses.CHA + spellbonuses.CHA; } + inline virtual int16 GetMR() const { return MR + itembonuses.MR + spellbonuses.MR; } + inline virtual int16 GetFR() const { return FR + itembonuses.FR + spellbonuses.FR; } + inline virtual int16 GetDR() const { return DR + itembonuses.DR + spellbonuses.DR; } + inline virtual int16 GetPR() const { return PR + itembonuses.PR + spellbonuses.PR; } + inline virtual int16 GetCR() const { return CR + itembonuses.CR + spellbonuses.CR; } + inline virtual int16 GetCorrup() const { return Corrup + itembonuses.Corrup + spellbonuses.Corrup; } + inline virtual int16 GetPhR() const { return PhR; } + inline StatBonuses GetItemBonuses() const { return itembonuses; } + inline StatBonuses GetSpellBonuses() const { return spellbonuses; } + inline StatBonuses GetAABonuses() const { return aabonuses; } + inline virtual int16 GetMaxSTR() const { return GetSTR(); } + inline virtual int16 GetMaxSTA() const { return GetSTA(); } + inline virtual int16 GetMaxDEX() const { return GetDEX(); } + inline virtual int16 GetMaxAGI() const { return GetAGI(); } + inline virtual int16 GetMaxINT() const { return GetINT(); } + inline virtual int16 GetMaxWIS() const { return GetWIS(); } + inline virtual int16 GetMaxCHA() const { return GetCHA(); } + inline virtual int16 GetMaxMR() const { return 255; } + inline virtual int16 GetMaxPR() const { return 255; } + inline virtual int16 GetMaxDR() const { return 255; } + inline virtual int16 GetMaxCR() const { return 255; } + inline virtual int16 GetMaxFR() const { return 255; } + inline virtual int16 GetDelayDeath() const { return 0; } + inline int32 GetHP() const { return cur_hp; } + inline int32 GetMaxHP() const { return max_hp; } + virtual int32 CalcMaxHP(); + inline int32 GetMaxMana() const { return max_mana; } + inline int32 GetMana() const { return cur_mana; } + int32 GetItemHPBonuses(); + int32 GetSpellHPBonuses(); + virtual const int32& SetMana(int32 amount); + inline float GetManaRatio() const { return max_mana == 0 ? 100 : + ((static_cast(cur_mana) / max_mana) * 100); } + virtual int32 CalcMaxMana(); + uint32 GetNPCTypeID() const { return npctype_id; } + inline const float GetX() const { return x_pos; } + inline const float GetY() const { return y_pos; } + inline const float GetZ() const { return z_pos; } + inline const float GetHeading() const { return heading; } + inline const float GetSize() const { return size; } + inline const float GetBaseSize() const { return base_size; } + inline const float GetTarX() const { return tarx; } + inline const float GetTarY() const { return tary; } + inline const float GetTarZ() const { return tarz; } + inline const float GetTarVX() const { return tar_vx; } + inline const float GetTarVY() const { return tar_vy; } + inline const float GetTarVZ() const { return tar_vz; } + inline const float GetTarVector() const { return tar_vector; } + inline const uint8 GetTarNDX() const { return tar_ndx; } + bool IsBoat() const; + + //Group + virtual bool HasRaid() = 0; + virtual bool HasGroup() = 0; + virtual Raid* GetRaid() = 0; + virtual Group* GetGroup() = 0; + + //Faction + virtual inline int32 GetPrimaryFaction() const { return 0; } + + //Movement + void Warp( float x, float y, float z ); + inline bool IsMoving() const { return moving; } + virtual void SetMoving(bool move) { moving = move; delta_x = 0; delta_y = 0; delta_z = 0; delta_heading = 0; } + virtual void GoToBind(uint8 bindnum = 0) { } + virtual void Gate(); + float GetWalkspeed() const { return(_GetMovementSpeed(-47)); } + float GetRunspeed() const { return(_GetMovementSpeed(0)); } + float GetBaseRunspeed() const { return runspeed; } + float GetMovespeed() const { return IsRunning() ? GetRunspeed() : GetWalkspeed(); } + bool IsRunning() const { return m_is_running; } + void SetRunning(bool val) { m_is_running = val; } + virtual void GMMove(float x, float y, float z, float heading = 0.01, bool SendUpdate = true); + void SetDeltas(float delta_x, float delta_y, float delta_z, float delta_h); + void SetTargetDestSteps(uint8 target_steps) { tar_ndx = target_steps; } + void SendPosUpdate(uint8 iSendToSelf = 0); + void MakeSpawnUpdateNoDelta(PlayerPositionUpdateServer_Struct* spu); + void MakeSpawnUpdate(PlayerPositionUpdateServer_Struct* spu); + void SendPosition(); + void SetFlyMode(uint8 flymode); + inline void Teleport(Map::Vertex NewPosition) { x_pos = NewPosition.x; y_pos = NewPosition.y; + z_pos = NewPosition.z; }; + + //AI + static uint32 GetLevelCon(uint8 mylevel, uint8 iOtherLevel); + inline uint32 GetLevelCon(uint8 iOtherLevel) const { + return this ? GetLevelCon(GetLevel(), iOtherLevel) : CON_GREEN; } + virtual void AddToHateList(Mob* other, int32 hate = 0, int32 damage = 0, bool iYellForHelp = true, + bool bFrenzy = false, bool iBuffTic = false); + bool RemoveFromHateList(Mob* mob); + void SetHate(Mob* other, int32 hate = 0, int32 damage = 0) { hate_list.Set(other,hate,damage);} + void HalveAggro(Mob *other) { uint32 in_hate = GetHateAmount(other); SetHate(other, (in_hate > 1 ? in_hate / 2 : 1)); } + void DoubleAggro(Mob *other) { uint32 in_hate = GetHateAmount(other); SetHate(other, (in_hate ? in_hate * 2 : 1)); } + uint32 GetHateAmount(Mob* tmob, bool is_dam = false) { return hate_list.GetEntHate(tmob,is_dam);} + uint32 GetDamageAmount(Mob* tmob) { return hate_list.GetEntHate(tmob, true);} + Mob* GetHateTop() { return hate_list.GetTop(this);} + Mob* GetHateDamageTop(Mob* other) { return hate_list.GetDamageTop(other);} + Mob* GetHateRandom() { return hate_list.GetRandom();} + Mob* GetHateMost() { return hate_list.GetMostHate();} + bool IsEngaged() { return(!hate_list.IsEmpty()); } + bool HateSummon(); + void FaceTarget(Mob* MobToFace = 0); + void SetHeading(float iHeading) { if(heading != iHeading) { pLastChange = Timer::GetCurrentTime(); + heading = iHeading; } } + void WipeHateList(); + void AddFeignMemory(Client* attacker); + void RemoveFromFeignMemory(Client* attacker); + void ClearFeignMemory(); + void PrintHateListToClient(Client *who) { hate_list.PrintToClient(who); } + std::list& GetHateList() { return hate_list.GetHateList(); } + bool CheckLosFN(Mob* other); + bool CheckLosFN(float posX, float posY, float posZ, float mobSize); + inline void SetChanged() { pLastChange = Timer::GetCurrentTime(); } + inline const uint32 LastChange() const { return pLastChange; } + + //Quest + void QuestReward(Client *c = nullptr, uint32 silver = 0, uint32 gold = 0, uint32 platinum = 0); + void CameraEffect(uint32 duration, uint32 intensity, Client *c = nullptr, bool global = false); + inline bool GetQglobal() const { return qglobal; } + + //Other Packet + void CreateDespawnPacket(EQApplicationPacket* app, bool Decay); + void CreateHorseSpawnPacket(EQApplicationPacket* app, const char* ownername, uint16 ownerid, Mob* ForWho = 0); + void CreateSpawnPacket(EQApplicationPacket* app, Mob* ForWho = 0); + static void CreateSpawnPacket(EQApplicationPacket* app, NewSpawn_Struct* ns); + virtual void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho); + void CreateHPPacket(EQApplicationPacket* app); + void SendHPUpdate(); + + //Util + static uint32 RandomTimer(int min, int max); + static uint8 GetDefaultGender(uint16 in_race, uint8 in_gender = 0xFF); + uint16 GetSkillByItemType(int ItemType); + virtual void MakePet(uint16 spell_id, const char* pettype, const char *petname = nullptr); + virtual void MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower, const char *petname = nullptr, float in_size = 0.0f); + bool IsWarriorClass() const; + char GetCasterClass() const; + uint8 GetArchetype() const; + void SetZone(uint32 zone_id, uint32 instance_id); + void ShowStats(Client* client); + void ShowBuffs(Client* client); + void ShowBuffList(Client* client); + float Dist(const Mob &) const; + float DistNoZ(const Mob &) const; + float DistNoRoot(const Mob &) const; + float DistNoRoot(float x, float y, float z) const; + float DistNoRootNoZ(float x, float y) const; + float DistNoRootNoZ(const Mob &) const; + static float GetReciprocalHeading(Mob* target); + bool PlotPositionAroundTarget(Mob* target, float &x_dest, float &y_dest, float &z_dest, + bool lookForAftArc = true); + + //Procs + bool AddRangedProc(uint16 spell_id, uint16 iChance = 3, uint16 base_spell_id = SPELL_UNKNOWN); + bool RemoveRangedProc(uint16 spell_id, bool bAll = false); + bool HasRangedProcs() const; + bool AddDefensiveProc(uint16 spell_id, uint16 iChance = 3, uint16 base_spell_id = SPELL_UNKNOWN); + bool RemoveDefensiveProc(uint16 spell_id, bool bAll = false); + bool HasDefensiveProcs() const; + bool HasSkillProcs() const; + bool HasSkillProcSuccess() const; + bool AddProcToWeapon(uint16 spell_id, bool bPerma = false, uint16 iChance = 3, uint16 base_spell_id = SPELL_UNKNOWN); + bool RemoveProcFromWeapon(uint16 spell_id, bool bAll = false); + bool HasProcs() const; + bool IsCombatProc(uint16 spell_id); + + //Logging + bool IsLoggingEnabled() const { return(logging_enabled); } + void EnableLogging() { logging_enabled = true; } + void DisableLogging() { logging_enabled = false; } + + + //More stuff to sort: + virtual bool IsAttackAllowed(Mob *target, bool isSpellAttack = false); + bool IsTargeted() const { return (targeted > 0); } + inline void IsTargeted(int in_tar) { targeted += in_tar; if(targeted < 0) targeted = 0;} + void SetFollowID(uint32 id) { follow = id; } + void SetFollowDistance(uint32 dist) { follow_dist = dist; } + uint32 GetFollowID() const { return follow; } + uint32 GetFollowDistance() const { return follow_dist; } + + virtual void Message(uint32 type, const char* message, ...) { } + virtual void Message_StringID(uint32 type, uint32 string_id, uint32 distance = 0) { } + virtual void Message_StringID(uint32 type, uint32 string_id, const char* message, const char* message2 = 0, + const char* message3 = 0, const char* message4 = 0, const char* message5 = 0, const char* message6 = 0, + const char* message7 = 0, const char* message8 = 0, const char* message9 = 0, uint32 distance = 0) { } + virtual void FilteredMessage_StringID(Mob *sender, uint32 type, eqFilterType filter, uint32 string_id) { } + virtual void FilteredMessage_StringID(Mob *sender, uint32 type, eqFilterType filter, + uint32 string_id, const char *message1, const char *message2 = nullptr, + const char *message3 = nullptr, const char *message4 = nullptr, + const char *message5 = nullptr, const char *message6 = nullptr, + const char *message7 = nullptr, const char *message8 = nullptr, + const char *message9 = nullptr) { } + void Say(const char *format, ...); + void Say_StringID(uint32 string_id, const char *message3 = 0, const char *message4 = 0, const char *message5 = 0, + const char *message6 = 0, const char *message7 = 0, const char *message8 = 0, const char *message9 = 0); + void Say_StringID(uint32 type, uint32 string_id, const char *message3 = 0, const char *message4 = 0, const char *message5 = 0, + const char *message6 = 0, const char *message7 = 0, const char *message8 = 0, const char *message9 = 0); + void Shout(const char *format, ...); + void Emote(const char *format, ...); + void QuestJournalledSay(Client *QuestInitiator, const char *str); + uint32 GetItemStat(uint32 itemid, const char *identifier); + + int16 CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, bool best_focus=false); + uint8 IsFocusEffect(uint16 spellid, int effect_index, bool AA=false,uint32 aa_effect=0); + void SendIllusionPacket(uint16 in_race, uint8 in_gender = 0xFF, uint8 in_texture = 0xFF, uint8 in_helmtexture = 0xFF, + uint8 in_haircolor = 0xFF, uint8 in_beardcolor = 0xFF, uint8 in_eyecolor1 = 0xFF, uint8 in_eyecolor2 = 0xFF, + uint8 in_hairstyle = 0xFF, uint8 in_luclinface = 0xFF, uint8 in_beard = 0xFF, uint8 in_aa_title = 0xFF, + uint32 in_drakkin_heritage = 0xFFFFFFFF, uint32 in_drakkin_tattoo = 0xFFFFFFFF, + uint32 in_drakkin_details = 0xFFFFFFFF, float in_size = 0xFFFFFFFF); + virtual void Stun(int duration); + virtual void UnStun(); + inline void Silence(bool newval) { silenced = newval; } + inline void Amnesia(bool newval) { amnesiad = newval; } + void TemporaryPets(uint16 spell_id, Mob *target, const char *name_override = nullptr, uint32 duration_override = 0); + void TypesTemporaryPets(uint32 typesid, Mob *target, const char *name_override = nullptr, uint32 duration_override = 0, bool followme = false); + void WakeTheDead(uint16 spell_id, Mob *target, uint32 duration); + void Spin(); + void Kill(); + bool PassCharismaCheck(Mob* caster, Mob* spellTarget, uint16 spell_id); + bool TryDeathSave(); + bool TryDivineSave(); + void DoBuffWearOffEffect(uint32 index); + void TryTriggerOnCast(uint32 spell_id, bool aa_trigger); + void TriggerOnCast(uint32 focus_spell, uint32 spell_id, bool aa_trigger); + void TrySpellTrigger(Mob *target, uint32 spell_id); + void TryApplyEffect(Mob *target, uint32 spell_id); + void TryTriggerOnValueAmount(bool IsHP = false, bool IsMana = false, bool IsEndur = false, bool IsPet = false); + void TryTwincast(Mob *caster, Mob *target, uint32 spell_id); + void TrySympatheticProc(Mob *target, uint32 spell_id); + bool TryFadeEffect(int slot); + uint16 GetSpellEffectResistChance(uint16 spell_id); + int16 GetHealRate(uint16 spell_id, Mob* caster = nullptr); + int32 GetVulnerability(Mob* caster, uint32 spell_id, uint32 ticsremaining); + int32 GetFcDamageAmtIncoming(Mob *caster, uint32 spell_id, bool use_skill = false, uint16 skill=0); + int32 GetFocusIncoming(focusType type, int effect, Mob *caster, uint32 spell_id); + int16 GetSkillDmgTaken(const SkillUseTypes skill_used); + void DoKnockback(Mob *caster, uint32 pushback, uint32 pushup); + int16 CalcResistChanceBonus(); + int16 CalcFearResistChance(); + void TrySpellOnKill(uint8 level, uint16 spell_id); + bool TrySpellOnDeath(); + void CastOnCurer(uint32 spell_id); + void CastOnCure(uint32 spell_id); + void CastOnNumHitFade(uint32 spell_id); + void SlowMitigation(Mob* caster); + int16 GetCritDmgMob(uint16 skill); + int16 GetMeleeDamageMod_SE(uint16 skill); + int16 GetMeleeMinDamageMod_SE(uint16 skill); + int16 GetCrippBlowChance(); + int16 GetSkillReuseTime(uint16 skill); + int16 GetCriticalChanceBonus(uint16 skill); + int16 GetSkillDmgAmt(uint16 skill); + bool TryReflectSpell(uint32 spell_id); + bool CanBlockSpell() const { return(spellbonuses.BlockNextSpell); } + bool DoHPToManaCovert(uint16 mana_cost = 0); + int32 ApplySpellEffectiveness(Mob* caster, int16 spell_id, int32 value, bool IsBard = false); + int8 GetDecayEffectValue(uint16 spell_id, uint16 spelleffect); + int32 GetExtraSpellAmt(uint16 spell_id, int32 extra_spell_amt, int32 base_spell_dmg); + void MeleeLifeTap(int32 damage); + bool PassCastRestriction(bool UseCastRestriction = true, int16 value = 0, bool IsDamage = true); + bool ImprovedTaunt(); + bool TryRootFadeByDamage(int buffslot, Mob* attacker); + int16 GetSlowMitigation() const {return slow_mitigation;} + + void ModSkillDmgTaken(SkillUseTypes skill_num, int value); + int16 GetModSkillDmgTaken(const SkillUseTypes skill_num); + void ModVulnerability(uint8 resist, int16 value); + int16 GetModVulnerability(const uint8 resist); + + void SetAllowBeneficial(bool value) { m_AllowBeneficial = value; } + bool GetAllowBeneficial() { if (m_AllowBeneficial || GetSpecialAbility(ALLOW_BENEFICIAL)){return true;} return false; } + void SetDisableMelee(bool value) { m_DisableMelee = value; } + bool IsMeleeDisabled() { if (m_DisableMelee || GetSpecialAbility(DISABLE_MELEE)){return true;} return false; } + + bool IsOffHandAtk() const { return offhand; } + inline void OffHandAtk(bool val) { offhand = val; } + + void SetFlurryChance(uint8 value) { SetSpecialAbilityParam(SPECATK_FLURRY, 0, value); } + uint8 GetFlurryChance() { return GetSpecialAbilityParam(SPECATK_FLURRY, 0); } + + static uint32 GetAppearanceValue(EmuAppearance iAppearance); + void SendAppearancePacket(uint32 type, uint32 value, bool WholeZone = true, bool iIgnoreSelf = false, Client *specific_target=nullptr); + void SetAppearance(EmuAppearance app, bool iIgnoreSelf = true); + inline EmuAppearance GetAppearance() const { return _appearance; } + inline const uint8 GetRunAnimSpeed() const { return pRunAnimSpeed; } + inline void SetRunAnimSpeed(int8 in) { if (pRunAnimSpeed != in) { pRunAnimSpeed = in; pLastChange = Timer::GetCurrentTime(); } } + bool IsDestructibleObject() { return destructibleobject; } + void SetDestructibleObject(bool in) { destructibleobject = in; } + + Mob* GetPet(); + void SetPet(Mob* newpet); + virtual Mob* GetOwner(); + virtual Mob* GetOwnerOrSelf(); + Mob* GetUltimateOwner(); + void SetPetID(uint16 NewPetID); + inline uint16 GetPetID() const { return petid; } + inline PetType GetPetType() const { return typeofpet; } + void SetPetType(PetType p) { typeofpet = p; } + inline int16 GetPetPower() const { return (petpower < 0) ? 0 : petpower; } + void SetPetPower(int16 p) { if (p < 0) petpower = 0; else petpower = p; } + bool IsFamiliar() const { return(typeofpet == petFamiliar); } + bool IsAnimation() const { return(typeofpet == petAnimation); } + bool IsCharmed() const { return(typeofpet == petCharmed); } + void SetOwnerID(uint16 NewOwnerID); + inline uint16 GetOwnerID() const { return ownerid; } + inline virtual bool HasOwner() { if(GetOwnerID()==0){return false;} return( entity_list.GetMob(GetOwnerID()) != 0); } + inline virtual bool IsPet() { return(HasOwner() && !IsMerc()); } + inline bool HasPet() const { if(GetPetID()==0){return false;} return (entity_list.GetMob(GetPetID()) != 0);} + bool HadTempPets() const { return(hasTempPet); } + void TempPets(bool i) { hasTempPet = i; } + bool HasPetAffinity() { if (aabonuses.GivePetGroupTarget || itembonuses.GivePetGroupTarget || spellbonuses.GivePetGroupTarget) return true; return false; } + + inline const bodyType GetBodyType() const { return bodytype; } + inline const bodyType GetOrigBodyType() const { return orig_bodytype; } + void SetBodyType(bodyType new_body, bool overwrite_orig); + + uint8 invisible, see_invis; + bool invulnerable, invisible_undead, invisible_animals, sneaking, hidden, improved_hidden; + bool see_invis_undead, see_hide, see_improved_hide; + bool qglobal; + + virtual void SetAttackTimer(); + inline void SetInvul(bool invul) { invulnerable=invul; } + inline bool GetInvul(void) { return invulnerable; } + inline void SetExtraHaste(int Haste) { ExtraHaste = Haste; } + virtual int GetHaste(); + + uint8 GetWeaponDamageBonus(const Item_Struct* Weapon); + uint16 GetDamageTable(SkillUseTypes skillinuse); + virtual int GetMonkHandToHandDamage(void); + + bool CanThisClassDoubleAttack(void) const; + bool CanThisClassDualWield(void) const; + bool CanThisClassRiposte(void) const; + bool CanThisClassDodge(void) const; + bool CanThisClassParry(void) const; + bool CanThisClassBlock(void) const; + + int GetMonkHandToHandDelay(void); + uint16 GetClassLevelFactor(); + void Mesmerize(); + inline bool IsMezzed() const { return mezzed; } + inline bool IsStunned() const { return stunned; } + inline bool IsSilenced() const { return silenced; } + inline bool IsAmnesiad() const { return amnesiad; } + + int32 ReduceDamage(int32 damage); + int32 AffectMagicalDamage(int32 damage, uint16 spell_id, const bool iBuffTic, Mob* attacker); + int32 ReduceAllDamage(int32 damage); + + virtual void DoSpecialAttackDamage(Mob *who, SkillUseTypes skill, int32 max_damage, int32 min_damage = 1, int32 hate_override = -1, int ReuseTime = 10, bool HitChance=false, bool CanAvoid=true); + virtual void DoThrowingAttackDmg(Mob* other, const ItemInst* RangeWeapon=nullptr, const Item_Struct* item=nullptr, uint16 weapon_damage=0, int16 chance_mod=0,int16 focus=0, int ReuseTime=0); + virtual void DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes skillinuse, int16 chance_mod=0, int16 focus=0, bool CanRiposte=false, int ReuseTime=0); + virtual void DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon=nullptr, const ItemInst* Ammo=nullptr, uint16 weapon_damage=0, int16 chance_mod=0, int16 focus=0, int ReuseTime=0); + bool CanDoSpecialAttack(Mob *other); + bool Flurry(ExtraAttackOptions *opts); + bool Rampage(ExtraAttackOptions *opts); + bool AddRampage(Mob*); + void ClearRampage(); + void AreaRampage(ExtraAttackOptions *opts); + + void StartEnrage(); + void ProcessEnrage(); + bool IsEnraged(); + void Taunt(NPC* who, bool always_succeed, float chance_bonus = 0); + + virtual void AI_Init(); + virtual void AI_Start(uint32 iMoveDelay = 0); + virtual void AI_Stop(); + virtual void AI_Process(); + + const char* GetEntityVariable(const char *id); + void SetEntityVariable(const char *id, const char *m_var); + bool EntityVariableExists(const char *id); + + void AI_Event_Engaged(Mob* attacker, bool iYellForHelp = true); + void AI_Event_NoLongerEngaged(); + + FACTION_VALUE GetSpecialFactionCon(Mob* iOther); + inline const bool IsAIControlled() const { return pAIControlled; } + inline const float GetAggroRange() const { return (spellbonuses.AggroRange == -1) ? pAggroRange : spellbonuses.AggroRange; } + inline const float GetAssistRange() const { return (spellbonuses.AssistRange == -1) ? pAssistRange : spellbonuses.AssistRange; } + + + inline void SetPetOrder(eStandingPetOrder i) { pStandingPetOrder = i; } + inline const eStandingPetOrder GetPetOrder() const { return pStandingPetOrder; } + inline void SetHeld(bool nState) { held = nState; } + inline const bool IsHeld() const { return held; } + inline void SetNoCast(bool nState) { nocast = nState; } + inline const bool IsNoCast() const { return nocast; } + inline void SetFocused(bool nState) { focused = nState; } + inline const bool IsFocused() const { return focused; } + inline const bool IsRoamer() const { return roamer; } + inline const bool IsRooted() const { return rooted || permarooted; } + inline const bool HasVirus() const { return has_virus; } + int GetSnaredAmount(); + + + int GetCurWp() { return cur_wp; } + + //old fear function + //void SetFeared(Mob *caster, uint32 duration, bool flee = false); + float GetFearSpeed(); + bool IsFeared() { return curfp; } // This returns true if the mob is feared or fleeing due to low HP + //old fear: inline void StartFleeing() { SetFeared(GetHateTop(), FLEE_RUN_DURATION, true); } + inline void StartFleeing() { flee_mode = true; CalculateNewFearpoint(); } + void ProcessFlee(); + void CheckFlee(); + + inline bool CheckAggro(Mob* other) {return hate_list.IsOnHateList(other);} + float CalculateHeadingToTarget(float in_x, float in_y); + bool CalculateNewPosition(float x, float y, float z, float speed, bool checkZ = false); + virtual bool CalculateNewPosition2(float x, float y, float z, float speed, bool checkZ = true); + float CalculateDistance(float x, float y, float z); + float GetGroundZ(float new_x, float new_y, float z_offset=0.0); + void SendTo(float new_x, float new_y, float new_z); + void SendToFixZ(float new_x, float new_y, float new_z); + void NPCSpecialAttacks(const char* parse, int permtag, bool reset = true, bool remove = false); + inline uint32 DontHealMeBefore() const { return pDontHealMeBefore; } + inline uint32 DontBuffMeBefore() const { return pDontBuffMeBefore; } + inline uint32 DontDotMeBefore() const { return pDontDotMeBefore; } + inline uint32 DontRootMeBefore() const { return pDontRootMeBefore; } + inline uint32 DontSnareMeBefore() const { return pDontSnareMeBefore; } + inline uint32 DontCureMeBefore() const { return pDontCureMeBefore; } + void SetDontRootMeBefore(uint32 time) { pDontRootMeBefore = time; } + void SetDontHealMeBefore(uint32 time) { pDontHealMeBefore = time; } + void SetDontBuffMeBefore(uint32 time) { pDontBuffMeBefore = time; } + void SetDontDotMeBefore(uint32 time) { pDontDotMeBefore = time; } + void SetDontSnareMeBefore(uint32 time) { pDontSnareMeBefore = time; } + void SetDontCureMeBefore(uint32 time) { pDontCureMeBefore = time; } + + // calculate interruption of spell via movement of mob + void SaveSpellLoc() {spell_x = x_pos; spell_y = y_pos; spell_z = z_pos; } + inline float GetSpellX() const {return spell_x;} + inline float GetSpellY() const {return spell_y;} + inline float GetSpellZ() const {return spell_z;} + inline bool IsGrouped() const { return isgrouped; } + void SetGrouped(bool v); + inline bool IsRaidGrouped() const { return israidgrouped; } + void SetRaidGrouped(bool v); + inline bool IsLooting() const { return islooting; } + void SetLooting(bool val) { islooting = val; } + + bool CheckWillAggro(Mob *mob); + + void InstillDoubt(Mob *who); + int16 GetResist(uint8 type) const; + Mob* GetShieldTarget() const { return shield_target; } + void SetShieldTarget(Mob* mob) { shield_target = mob; } + bool HasActiveSong() const { return(bardsong != 0); } + bool Charmed() const { return charmed; } + static uint32 GetLevelHP(uint8 tlevel); + uint32 GetZoneID() const; //for perl + virtual int32 CheckAggroAmount(uint16 spell_id, bool isproc = false); + virtual int32 CheckHealAggroAmount(uint16 spell_id, uint32 heal_possible = 0); + virtual uint32 GetAA(uint32 aa_id) const { return(0); } + + uint16 GetInstrumentMod(uint16 spell_id) const; + int CalcSpellEffectValue(uint16 spell_id, int effect_id, int caster_level = 1, Mob *caster = nullptr, int ticsremaining = 0); + int CalcSpellEffectValue_formula(int formula, int base, int max, int caster_level, uint16 spell_id, int ticsremaining = 0); + virtual int CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, int caster_level2, Mob* caster1 = nullptr, Mob* caster2 = nullptr, int buffslot = -1); + uint32 GetCastedSpellInvSlot() const { return casting_spell_inventory_slot; } + + // HP Event + inline int GetNextHPEvent() const { return nexthpevent; } + void SetNextHPEvent( int hpevent ); + void SendItemAnimation(Mob *to, const Item_Struct *item, SkillUseTypes skillInUse); + inline int& GetNextIncHPEvent() { return nextinchpevent; } + void SetNextIncHPEvent( int inchpevent ); + + inline bool DivineAura() const { return spellbonuses.DivineAura; } + inline bool Sanctuary() const { return spellbonuses.Sanctuary; } + + bool HasNPCSpecialAtk(const char* parse); + int GetSpecialAbility(int ability); + int GetSpecialAbilityParam(int ability, int param); + void SetSpecialAbility(int ability, int level); + void SetSpecialAbilityParam(int ability, int param, int value); + void StartSpecialAbilityTimer(int ability, uint32 time); + void StopSpecialAbilityTimer(int ability); + Timer *GetSpecialAbilityTimer(int ability); + void ClearSpecialAbilities(); + void ProcessSpecialAbilities(const std::string str); + + Shielders_Struct shielder[MAX_SHIELDERS]; + Trade* trade; + + inline float GetCWPX() const { return(cur_wp_x); } + inline float GetCWPY() const { return(cur_wp_y); } + inline float GetCWPZ() const { return(cur_wp_z); } + inline float GetCWPH() const { return(cur_wp_heading); } + inline float GetCWPP() const { return(static_cast(cur_wp_pause)); } + inline int GetCWP() const { return(cur_wp); } + void SetCurrentWP(uint16 waypoint) { cur_wp = waypoint; } + virtual FACTION_VALUE GetReverseFactionCon(Mob* iOther) { return FACTION_INDIFFERENT; } + + inline bool IsTrackable() const { return(trackable); } + Timer* GetAIThinkTimer() { return AIthink_timer; } + Timer* GetAIMovementTimer() { return AImovement_timer; } + Timer GetAttackTimer() { return attack_timer; } + Timer GetAttackDWTimer() { return attack_dw_timer; } + inline bool IsFindable() { return findable; } + inline uint8 GetManaPercent() { return (uint8)((float)cur_mana / (float)max_mana * 100.0f); } + virtual uint8 GetEndurancePercent() { return 0; } + + inline virtual bool IsBlockedBuff(int16 SpellID) { return false; } + inline virtual bool IsBlockedPetBuff(int16 SpellID) { return false; } + + void SetGlobal(const char *varname, const char *newvalue, int options, const char *duration, Mob *other = nullptr); + void TarGlobal(const char *varname, const char *value, const char *duration, int npcid, int charid, int zoneid); + void DelGlobal(const char *varname); + + inline void SetEmoteID(uint16 emote) { emoteid = emote; } + inline uint16 GetEmoteID() { return emoteid; } + + bool HasSpellEffect(int effectid); + int mod_effect_value(int effect_value, uint16 spell_id, int effect_type, Mob* caster); + float mod_hit_chance(float chancetohit, SkillUseTypes skillinuse, Mob* attacker); + float mod_riposte_chance(float ripostchance, Mob* attacker); + float mod_block_chance(float blockchance, Mob* attacker); + float mod_parry_chance(float parrychance, Mob* attacker); + float mod_dodge_chance(float dodgechance, Mob* attacker); + float mod_monk_weight(float monkweight, Mob* attacker); + float mod_mitigation_rating(float mitigation_rating, Mob* attacker); + float mod_attack_rating(float attack_rating, Mob* defender); + int32 mod_kick_damage(int32 dmg); + int32 mod_bash_damage(int32 dmg); + int32 mod_frenzy_damage(int32 dmg); + int32 mod_monk_special_damage(int32 ndamage, SkillUseTypes skill_type); + int32 mod_backstab_damage(int32 ndamage); + int mod_archery_bonus_chance(int bonuschance, const ItemInst* RangeWeapon); + uint32 mod_archery_bonus_damage(uint32 MaxDmg, const ItemInst* RangeWeapon); + int32 mod_archery_damage(int32 TotalDmg, bool hasbonus, const ItemInst* RangeWeapon); + uint16 mod_throwing_damage(uint16 MaxDmg); + int32 mod_cast_time(int32 cast_time); + int mod_buff_duration(int res, Mob* caster, Mob* target, uint16 spell_id); + int mod_spell_stack(uint16 spellid1, int caster_level1, Mob* caster1, uint16 spellid2, int caster_level2, Mob* caster2); + int mod_spell_resist(int resist_chance, int level_mod, int resist_modifier, int target_resist, uint8 resist_type, uint16 spell_id, Mob* caster); + void mod_spell_cast(uint16 spell_id, Mob* spelltar, bool reflect, bool use_resist_adjust, int16 resist_adjust, bool isproc); + bool mod_will_aggro(Mob *attacker, Mob *on); + +protected: + void CommonDamage(Mob* other, int32 &damage, const uint16 spell_id, const SkillUseTypes attack_skill, bool &avoidable, const int8 buffslot, const bool iBuffTic); + static uint16 GetProcID(uint16 spell_id, uint8 effect_index); + float _GetMovementSpeed(int mod) const; + virtual bool MakeNewPositionAndSendUpdate(float x, float y, float z, float speed, bool checkZ); + + virtual bool AI_EngagedCastCheck() { return(false); } + virtual bool AI_PursueCastCheck() { return(false); } + virtual bool AI_IdleCastCheck() { return(false); } + + + bool IsFullHP; + bool moved; + + std::vector RampageArray; + std::map m_EntityVariables; + + int16 SkillDmgTaken_Mod[HIGHEST_SKILL+2]; + int16 Vulnerability_Mod[HIGHEST_RESIST+2]; + bool m_AllowBeneficial; + bool m_DisableMelee; + + bool isgrouped; + bool israidgrouped; + bool pendinggroup; + bool islooting; + uint8 texture; + uint8 helmtexture; + + int AC; + int16 ATK; + int16 STR; + int16 STA; + int16 DEX; + int16 AGI; + int16 INT; + int16 WIS; + int16 CHA; + int16 MR; + int16 CR; + int16 FR; + int16 DR; + int16 PR; + int16 Corrup; + int16 PhR; + bool moving; + int targeted; + bool findable; + bool trackable; + int32 cur_hp; + int32 max_hp; + int32 base_hp; + int32 cur_mana; + int32 max_mana; + int32 hp_regen; + int32 mana_regen; + int32 oocregen; + uint8 maxlevel; + uint32 scalerate; + Buffs_Struct *buffs; + uint32 current_buff_count; + StatBonuses itembonuses; + StatBonuses spellbonuses; + StatBonuses aabonuses; + uint16 petid; + uint16 ownerid; + PetType typeofpet; + int16 petpower; + uint32 follow; + uint32 follow_dist; + bool no_target_hotkey; + + uint8 gender; + uint16 race; + uint8 base_gender; + uint16 base_race; + uint8 class_; + bodyType bodytype; + bodyType orig_bodytype; + uint16 deity; + uint8 level; + uint8 orig_level; + uint32 npctype_id; + float x_pos; + float y_pos; + float z_pos; + float heading; + uint16 animation; + float base_size; + float size; + float runspeed; + uint32 pLastChange; + bool held; + bool nocast; + bool focused; + void CalcSpellBonuses(StatBonuses* newbon); + virtual void CalcBonuses(); + void TrySkillProc(Mob *on, uint16 skill, uint16 ReuseTime, bool Success = false, uint16 hand = 0, bool IsDefensive = false); + bool PassLimitToSkill(uint16 spell_id, uint16 skill); + bool PassLimitClass(uint32 Classes_, uint16 Class_); + void TryDefensiveProc(const ItemInst* weapon, Mob *on, uint16 hand = 13, int damage=0); + void TryWeaponProc(const ItemInst* inst, const Item_Struct* weapon, Mob *on, uint16 hand = 13); + void TrySpellProc(const ItemInst* inst, const Item_Struct* weapon, Mob *on, uint16 hand = 13); + void TryWeaponProc(const ItemInst* weapon, Mob *on, uint16 hand = 13); + void ExecWeaponProc(const ItemInst* weapon, uint16 spell_id, Mob *on); + virtual float GetProcChances(float ProcBonus, uint16 weapon_speed = 30, uint16 hand = 13); + virtual float GetDefensiveProcChances(float &ProcBonus, float &ProcChance, uint16 weapon_speed = 30, uint16 hand = 13); + virtual float GetSpecialProcChances(uint16 hand); + virtual float GetAssassinateProcChances(uint16 ReuseTime); + virtual float GetSkillProcChances(uint16 ReuseTime, uint16 hand = 0); + uint16 GetWeaponSpeedbyHand(uint16 hand); + int GetWeaponDamage(Mob *against, const Item_Struct *weapon_item); + int GetWeaponDamage(Mob *against, const ItemInst *weapon_item, uint32 *hate = nullptr); + int GetKickDamage(); + int GetBashDamage(); + virtual void ApplySpecialAttackMod(SkillUseTypes skill, int32 &dmg, int32 &mindmg); + bool HasDied(); + void CalculateNewFearpoint(); + float FindGroundZ(float new_x, float new_y, float z_offset=0.0); + Map::Vertex UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &WaypointChange, bool &NodeReached); + void PrintRoute(); + + virtual float GetSympatheticProcChances(uint16 spell_id, int16 ProcRateMod, int32 ItemProcRate = 0); + + enum {MAX_PROCS = 4}; + tProc PermaProcs[MAX_PROCS]; + tProc SpellProcs[MAX_PROCS]; + tProc DefensiveProcs[MAX_PROCS]; + tProc RangedProcs[MAX_PROCS]; + tProc SkillProcs[MAX_PROCS]; + + char name[64]; + char orig_name[64]; + char clean_name[64]; + char lastname[64]; + + int32 delta_heading; + float delta_x; + float delta_y; + float delta_z; + + uint8 light; + + float fixedZ; + EmuAppearance _appearance; + uint8 pRunAnimSpeed; + bool m_is_running; + + + Timer attack_timer; + Timer attack_dw_timer; + Timer ranged_timer; + float attack_speed; //% increase/decrease in attack speed (not haste) + float slow_mitigation; // Allows for a slow mitigation (100 = 100%, 50% = 50%) + Timer tic_timer; + Timer mana_timer; + + //spell casting vars + Timer spellend_timer; + uint16 casting_spell_id; + float spell_x, spell_y, spell_z; + int attacked_count; + bool delaytimer; + uint16 casting_spell_targetid; + uint16 casting_spell_slot; + uint16 casting_spell_mana; + uint32 casting_spell_inventory_slot; + uint32 casting_spell_timer; + uint32 casting_spell_timer_duration; + uint32 casting_spell_type; + int16 casting_spell_resist_adjust; + bool casting_spell_checks; + uint16 bardsong; + uint8 bardsong_slot; + uint32 bardsong_target_id; + + Timer projectile_timer; + uint32 projectile_spell_id[MAX_SPELL_PROJECTILE]; + uint16 projectile_target_id[MAX_SPELL_PROJECTILE]; + uint8 projectile_increment[MAX_SPELL_PROJECTILE]; + float projectile_x[MAX_SPELL_PROJECTILE], projectile_y[MAX_SPELL_PROJECTILE], projectile_z[MAX_SPELL_PROJECTILE]; + + float rewind_x; + float rewind_y; + float rewind_z; + Timer rewind_timer; + + // Currently 3 max nimbus particle effects at a time + uint32 nimbus_effect1; + uint32 nimbus_effect2; + uint32 nimbus_effect3; + + uint8 haircolor; + uint8 beardcolor; + uint8 eyecolor1; // the eyecolors always seem to be the same, maybe left and right eye? + uint8 eyecolor2; + uint8 hairstyle; + uint8 luclinface; // + uint8 beard; + uint32 drakkin_heritage; + uint32 drakkin_tattoo; + uint32 drakkin_details; + uint32 armor_tint[_MaterialCount]; + + uint8 aa_title; + + Mob* shield_target; + + int ExtraHaste; // for the #haste command + bool mezzed; + bool stunned; + bool charmed; //this isnt fully implemented yet + bool rooted; + bool silenced; + bool amnesiad; + bool inWater; // Set to true or false by Water Detection code if enabled by rules + bool has_virus; // whether this mob has a viral spell on them + uint16 viral_spells[MAX_SPELL_TRIGGER*2]; // Stores the spell ids of the viruses on target and caster ids + bool offhand; + bool has_shieldequiped; + bool has_numhits; + bool has_MGB; + bool has_ProjectIllusion; + + // Bind wound + Timer bindwound_timer; + Mob* bindwound_target; + + Timer stunned_timer; + Timer spun_timer; + Timer bardsong_timer; + Timer gravity_timer; + Timer viral_timer; + uint8 viral_timer_counter; + + // MobAI stuff + eStandingPetOrder pStandingPetOrder; + uint32 minLastFightingDelayMoving; + uint32 maxLastFightingDelayMoving; + float pAggroRange; + float pAssistRange; + Timer* AIthink_timer; + Timer* AImovement_timer; + Timer* AItarget_check_timer; + bool movetimercompleted; + bool permarooted; + Timer* AIscanarea_timer; + Timer* AIwalking_timer; + Timer* AIfeignremember_timer; + uint32 pLastFightingDelayMoving; + HateList hate_list; + std::set feign_memory_list; + // This is to keep track of mobs we cast faction mod spells on + std::map faction_bonuses; // Primary FactionID, Bonus + void AddFactionBonus(uint32 pFactionID,int32 bonus); + int32 GetFactionBonus(uint32 pFactionID); + // This is to keep track of item faction modifiers + std::map item_faction_bonuses; // Primary FactionID, Bonus + void AddItemFactionBonus(uint32 pFactionID,int32 bonus); + int32 GetItemFactionBonus(uint32 pFactionID); + void ClearItemFactionBonuses(); + + void CalculateFearPosition(); + uint32 move_tic_count; + + bool flee_mode; + Timer flee_timer; + + bool pAIControlled; + bool roamer; + bool logging_enabled; + + int wandertype; + int pausetype; + + int cur_wp; + float cur_wp_x; + float cur_wp_y; + float cur_wp_z; + int cur_wp_pause; + float cur_wp_heading; + + int patrol; + float fear_walkto_x; + float fear_walkto_y; + float fear_walkto_z; + bool curfp; + + // Pathing + // + Map::Vertex PathingDestination; + Map::Vertex PathingLastPosition; + int PathingLoopCount; + int PathingLastNodeVisited; + std::list Route; + LOSType PathingLOSState; + Timer *PathingLOSCheckTimer; + Timer *PathingRouteUpdateTimerShort; + Timer *PathingRouteUpdateTimerLong; + bool DistractedFromGrid; + int PathingTraversedNodes; + + uint32 pDontHealMeBefore; + uint32 pDontBuffMeBefore; + uint32 pDontDotMeBefore; + uint32 pDontRootMeBefore; + uint32 pDontSnareMeBefore; + uint32 pDontCureMeBefore; + + // hp event + int nexthpevent; + int nextinchpevent; + + //temppet + bool hasTempPet; + + EGNode *_egnode; //the EG node we are in + float tarx; + float tary; + float tarz; + uint8 tar_ndx; + float tar_vector; + float tar_vx; + float tar_vy; + float tar_vz; + float test_vector; + + uint32 m_spellHitsLeft[38]; // Used to track which spells will have their numhits incremented when spell finishes casting, 38 Buffslots + int flymode; + bool m_targetable; + int QGVarDuration(const char *fmt); + void InsertQuestGlobal(int charid, int npcid, int zoneid, const char *name, const char *value, int expdate); + uint16 emoteid; + + SpecialAbility SpecialAbilities[MAX_SPECIAL_ATTACK]; + bool bEnraged; + bool destructibleobject; + +private: + void _StopSong(); //this is not what you think it is + Mob* target; +}; + +#endif + diff --git a/zone/net_Fix.cpp b/zone/net_Fix.cpp new file mode 100644 index 000000000..9b4080eb2 --- /dev/null +++ b/zone/net_Fix.cpp @@ -0,0 +1,644 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2002 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 +*/ + +#define DONT_SHARED_OPCODES + +#include "../common/debug.h" +#include "../common/features.h" +#include "../common/queue.h" +#include "../common/timer.h" +#include "../common/eq_stream.h" +#include "../common/eq_stream_factory.h" +#include "../common/eq_packet_structs.h" +#include "../common/mutex.h" +#include "../common/version.h" +#include "../common/eqemu_error.h" +#include "../common/packet_dump_file.h" +#include "../common/opcodemgr.h" +#include "../common/guilds.h" +#include "../common/eq_stream_ident.h" +#include "../common/patches/patches.h" +#include "../common/rulesys.h" +#include "../common/misc_functions.h" +#include "../common/string_util.h" +#include "../common/platform.h" +#include "../common/crash.h" +#include "../common/ipc_mutex.h" +#include "../common/memory_mapped_file.h" +#include "../common/eqemu_exception.h" +#include "../common/spdat.h" + +#include "zone_config.h" +#include "masterentity.h" +#include "worldserver.h" +#include "net.h" +#include "zone.h" +#include "queryserv.h" +#include "command.h" +#include "zone_config.h" +#include "titles.h" +#include "guild_mgr.h" +#include "tasks.h" + +#include "quest_parser_collection.h" +#include "embparser.h" +#include "lua_parser.h" +#include "client_logs.h" +#include "questmgr.h" + +#include +#include +#include + +#include +#include +#include +#include + +#ifdef _CRTDBG_MAP_ALLOC + #undef new + #define new new(_NORMAL_BLOCK, __FILE__, __LINE__) +#endif + +#ifdef _WINDOWS + #include + #include +#else + #include + #include "../common/unix.h" +#endif + +volatile bool RunLoops = true; +extern volatile bool ZoneLoaded; + +TimeoutManager timeout_manager; +NetConnection net; +EntityList entity_list; +WorldServer worldserver; +uint32 numclients = 0; +char errorname[32]; +uint16 adverrornum = 0; +extern Zone* zone; +EQStreamFactory eqsf(ZoneStream); +npcDecayTimes_Struct npcCorpseDecayTimes[100]; +TitleManager title_manager; +QueryServ *QServ = 0; +TaskManager *taskmanager = 0; +QuestParserCollection *parse = 0; + +const SPDat_Spell_Struct* spells; +void LoadSpells(EQEmu::MemoryMappedFile **mmf); +int32 SPDAT_RECORDS = -1; + +void Shutdown(); +extern void MapOpcodes(); + +int main(int argc, char** argv) { + RegisterExecutablePlatform(ExePlatformZone); + set_exception_handler(); + + const char *zone_name; + + QServ = new QueryServ; + + if(argc == 3) { + worldserver.SetLauncherName(argv[2]); + worldserver.SetLaunchedName(argv[1]); + if(strncmp(argv[1], "dynamic_", 8) == 0) { + //dynamic zone with a launcher name correlation + zone_name = "."; + } else { + zone_name = argv[1]; + worldserver.SetLaunchedName(zone_name); + } + } else if (argc == 2) { + worldserver.SetLauncherName("NONE"); + worldserver.SetLaunchedName(argv[1]); + if(strncmp(argv[1], "dynamic_", 8) == 0) { + //dynamic zone with a launcher name correlation + zone_name = "."; + } else { + zone_name = argv[1]; + worldserver.SetLaunchedName(zone_name); + } + } else { + zone_name = "."; + worldserver.SetLaunchedName("."); + worldserver.SetLauncherName("NONE"); + } + + _log(ZONE__INIT, "Loading server configuration.."); + if (!ZoneConfig::LoadConfig()) { + _log(ZONE__INIT_ERR, "Loading server configuration failed."); + return 1; + } + const ZoneConfig *Config=ZoneConfig::get(); + + if(!load_log_settings(Config->LogSettingsFile.c_str())) + _log(ZONE__INIT, "Warning: Unable to read %s", Config->LogSettingsFile.c_str()); + else + _log(ZONE__INIT, "Log settings loaded from %s", Config->LogSettingsFile.c_str()); + + worldserver.SetPassword(Config->SharedKey.c_str()); + + _log(ZONE__INIT, "Connecting to MySQL..."); + if (!database.Connect( + Config->DatabaseHost.c_str(), + Config->DatabaseUsername.c_str(), + Config->DatabasePassword.c_str(), + Config->DatabaseDB.c_str(), + Config->DatabasePort)) { + _log(ZONE__INIT_ERR, "Cannot continue without a database connection."); + return 1; + } + guild_mgr.SetDatabase(&database); + + GuildBanks = nullptr; + +#ifdef _EQDEBUG + _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif + + _log(ZONE__INIT, "CURRENT_VERSION: %s", CURRENT_VERSION); + + /* + * Setup nice signal handlers + */ + if (signal(SIGINT, CatchSignal) == SIG_ERR) { + _log(ZONE__INIT_ERR, "Could not set signal handler"); + return 1; + } + if (signal(SIGTERM, CatchSignal) == SIG_ERR) { + _log(ZONE__INIT_ERR, "Could not set signal handler"); + return 1; + } + #ifndef WIN32 + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) { + _log(ZONE__INIT_ERR, "Could not set signal handler"); + return 1; + } + #endif + + const char *log_ini_file = "./log.ini"; + if(!load_log_settings(log_ini_file)) + _log(ZONE__INIT, "Warning: Unable to read %s", log_ini_file); + else + _log(ZONE__INIT, "Log settings loaded from %s", log_ini_file); + + _log(ZONE__INIT, "Mapping Incoming Opcodes"); + MapOpcodes(); + _log(ZONE__INIT, "Loading Variables"); + database.LoadVariables(); + _log(ZONE__INIT, "Loading zone names"); + database.LoadZoneNames(); + _log(ZONE__INIT, "Loading items"); + if (!database.LoadItems()) { + _log(ZONE__INIT_ERR, "Loading items FAILED!"); + _log(ZONE__INIT, "Failed. But ignoring error and going on..."); + } + + _log(ZONE__INIT, "Loading npc faction lists"); + if (!database.LoadNPCFactionLists()) { + _log(ZONE__INIT_ERR, "Loading npcs faction lists FAILED!"); + CheckEQEMuErrorAndPause(); + return 1; + } + _log(ZONE__INIT, "Loading loot tables"); + if (!database.LoadLoot()) { + _log(ZONE__INIT_ERR, "Loading loot FAILED!"); + CheckEQEMuErrorAndPause(); + return 1; + } + _log(ZONE__INIT, "Loading skill caps"); + if (!database.LoadSkillCaps()) { + _log(ZONE__INIT_ERR, "Loading skill caps FAILED!"); + CheckEQEMuErrorAndPause(); + return 1; + } + + _log(ZONE__INIT, "Loading spells"); + EQEmu::MemoryMappedFile *mmf = nullptr; + LoadSpells(&mmf); + + _log(ZONE__INIT, "Loading base data"); + if (!database.LoadBaseData()) { + _log(ZONE__INIT_ERR, "Loading base data FAILED!"); + CheckEQEMuErrorAndPause(); + return 1; + } + + _log(ZONE__INIT, "Loading guilds"); + guild_mgr.LoadGuilds(); + _log(ZONE__INIT, "Loading factions"); + database.LoadFactionData(); + _log(ZONE__INIT, "Loading titles"); + title_manager.LoadTitles(); + _log(ZONE__INIT, "Loading AA effects"); + database.LoadAAEffects(); + _log(ZONE__INIT, "Loading tributes"); + database.LoadTributes(); + _log(ZONE__INIT, "Loading corpse timers"); + database.GetDecayTimes(npcCorpseDecayTimes); + _log(ZONE__INIT, "Loading commands"); + int retval=command_init(); + if(retval<0) + _log(ZONE__INIT_ERR, "Command loading FAILED"); + else + _log(ZONE__INIT, "%d commands loaded", retval); + + //rules: + { + char tmp[64]; + if (database.GetVariable("RuleSet", tmp, sizeof(tmp)-1)) { + _log(ZONE__INIT, "Loading rule set '%s'", tmp); + if(!RuleManager::Instance()->LoadRules(&database, tmp)) { + _log(ZONE__INIT_ERR, "Failed to load ruleset '%s', falling back to defaults.", tmp); + } + } else { + if(!RuleManager::Instance()->LoadRules(&database, "default")) { + _log(ZONE__INIT, "No rule set configured, using default rules"); + } else { + _log(ZONE__INIT, "Loaded default rule set 'default'", tmp); + } + } + } + + if(RuleB(TaskSystem, EnableTaskSystem)) { + _log(ZONE__INIT, "Loading Tasks"); + taskmanager = new TaskManager; + taskmanager->LoadTasks(); + } + + parse = new QuestParserCollection(); +#ifdef LUA_EQEMU + LuaParser *lua_parser = new LuaParser(); + parse->RegisterQuestInterface(lua_parser, "lua"); +#endif + +#ifdef EMBPERL + PerlembParser *perl_parser = new PerlembParser(); + parse->RegisterQuestInterface(perl_parser, "pl"); +#endif + + //now we have our parser, load the quests + _log(ZONE__INIT, "Loading quests"); + parse->ReloadQuests(); + + +#ifdef CLIENT_LOGS + LogFile->SetAllCallbacks(ClientLogs::EQEmuIO_buf); + LogFile->SetAllCallbacks(ClientLogs::EQEmuIO_fmt); + LogFile->SetAllCallbacks(ClientLogs::EQEmuIO_pva); +#endif + if (!worldserver.Connect()) { + _log(ZONE__INIT_ERR, "worldserver.Connect() FAILED!"); + } + + Timer InterserverTimer(INTERSERVER_TIMER); // does MySQL pings and auto-reconnect +#ifdef EQPROFILE +#ifdef PROFILE_DUMP_TIME + Timer profile_dump_timer(PROFILE_DUMP_TIME*1000); + profile_dump_timer.Start(); +#endif +#endif + if (!strlen(zone_name) || !strcmp(zone_name,".")) { + _log(ZONE__INIT, "Entering sleep mode"); + } else if (!Zone::Bootup(database.GetZoneID(zone_name), 0, true)) { //todo: go above and fix this to allow cmd line instance + _log(ZONE__INIT_ERR, "Zone bootup FAILED!"); + zone = 0; + } + + //register all the patches we have avaliable with the stream identifier. + EQStreamIdentifier stream_identifier; + RegisterAllPatches(stream_identifier); + +#ifndef WIN32 + _log(COMMON__THREADS, "Main thread running with thread id %d", pthread_self()); +#endif + + Timer quest_timers(100); + UpdateWindowTitle(); + bool worldwasconnected = worldserver.Connected(); + EQStream* eqss; + EQStreamInterface *eqsi; + uint8 IDLEZONEUPDATE = 200; + uint8 ZONEUPDATE = 10; + Timer zoneupdate_timer(ZONEUPDATE); + zoneupdate_timer.Start(); + while(RunLoops) { + { //profiler block to omit the sleep from times + + //Advance the timer to our current point in time + Timer::SetCurrentTime(); + + //process stuff from world + worldserver.Process(); + + if (!eqsf.IsOpen() && Config->ZonePort!=0) { + _log(ZONE__INIT, "Starting EQ Network server on port %d",Config->ZonePort); + if (!eqsf.Open(Config->ZonePort)) { + _log(ZONE__INIT_ERR, "Failed to open port %d",Config->ZonePort); + ZoneConfig::SetZonePort(0); + worldserver.Disconnect(); + worldwasconnected = false; + } + } + + //check the factory for any new incoming streams. + while ((eqss = eqsf.Pop())) { + //pull the stream out of the factory and give it to the stream identifier + //which will figure out what patch they are running, and set up the dynamic + //structures and opcodes for that patch. + struct in_addr in; + in.s_addr = eqss->GetRemoteIP(); + _log(WORLD__CLIENT, "New connection from %s:%d", inet_ntoa(in),ntohs(eqss->GetRemotePort())); + stream_identifier.AddStream(eqss); //takes the stream + } + + //give the stream identifier a chance to do its work.... + stream_identifier.Process(); + + //check the stream identifier for any now-identified streams + while((eqsi = stream_identifier.PopIdentified())) { + //now that we know what patch they are running, start up their client object + struct in_addr in; + in.s_addr = eqsi->GetRemoteIP(); + _log(WORLD__CLIENT, "New client from %s:%d", inet_ntoa(in), ntohs(eqsi->GetRemotePort())); + Client* client = new Client(eqsi); + entity_list.AddClient(client); + } + + if ( numclients < 1 && zoneupdate_timer.GetDuration() != IDLEZONEUPDATE ) + zoneupdate_timer.SetTimer(IDLEZONEUPDATE); + else if ( numclients > 0 && zoneupdate_timer.GetDuration() == IDLEZONEUPDATE ) + { + zoneupdate_timer.SetTimer(ZONEUPDATE); + zoneupdate_timer.Trigger(); + } + + //check for timeouts in other threads + timeout_manager.CheckTimeouts(); + + if (worldserver.Connected()) { + worldwasconnected = true; + } + else { + if (worldwasconnected && ZoneLoaded) + entity_list.ChannelMessageFromWorld(0, 0, 6, 0, 0, "WARNING: World server connection lost"); + worldwasconnected = false; + } + + if (ZoneLoaded && zoneupdate_timer.Check()) { + { + if(net.group_timer.Enabled() && net.group_timer.Check()) + entity_list.GroupProcess(); + + if(net.door_timer.Enabled() && net.door_timer.Check()) + entity_list.DoorProcess(); + + if(net.object_timer.Enabled() && net.object_timer.Check()) + entity_list.ObjectProcess(); + + if(net.corpse_timer.Enabled() && net.corpse_timer.Check()) + entity_list.CorpseProcess(); + + if(net.trap_timer.Enabled() && net.trap_timer.Check()) + entity_list.TrapProcess(); + + if(net.raid_timer.Enabled() && net.raid_timer.Check()) + entity_list.RaidProcess(); + + entity_list.Process(); + + entity_list.MobProcess(); + + entity_list.BeaconProcess(); + + if (zone) { + if(!zone->Process()) { + Zone::Shutdown(); + } + } + + if(quest_timers.Check()) + quest_manager.Process(); + + } + } + if (InterserverTimer.Check()) { + InterserverTimer.Start(); + database.ping(); + // AsyncLoadVariables(dbasync, &database); + entity_list.UpdateWho(); + if (worldserver.TryReconnect() && (!worldserver.Connected())) + worldserver.AsyncConnect(); + } + +#if defined(_EQDEBUG) && defined(DEBUG_PC) + QueryPerformanceCounter(&tmp3); + mainloop_time += tmp3.QuadPart - tmp2.QuadPart; + if (!--tmp0) { + tmp0 = 200; + printf("Elapsed Tics : %9.0f (%1.4f sec)\n", (double)mainloop_time, ((double)mainloop_time/tmp.QuadPart)); + printf("NPCAI Tics : %9.0f (%1.2f%%)\n", (double)npcai_time, ((double)npcai_time/mainloop_time)*100); + printf("FindSpell Tics: %9.0f (%1.2f%%)\n", (double)findspell_time, ((double)findspell_time/mainloop_time)*100); + printf("AtkAllowd Tics: %9.0f (%1.2f%%)\n", (double)IsAttackAllowed_time, ((double)IsAttackAllowed_time/mainloop_time)*100); + printf("ClientPro Tics: %9.0f (%1.2f%%)\n", (double)clientprocess_time, ((double)clientprocess_time/mainloop_time)*100); + printf("ClientAtk Tics: %9.0f (%1.2f%%)\n", (double)clientattack_time, ((double)clientattack_time/mainloop_time)*100); + mainloop_time = 0; + npcai_time = 0; + findspell_time = 0; + IsAttackAllowed_time = 0; + clientprocess_time = 0; + clientattack_time = 0; + } +#endif +#ifdef EQPROFILE +#ifdef PROFILE_DUMP_TIME + if(profile_dump_timer.Check()) { + DumpZoneProfile(); + } +#endif +#endif + } //end extra profiler block + Sleep(ZoneTimerResolution); + } + + entity_list.Clear(); + + parse->ClearInterfaces(); + +#ifdef EMBPERL + safe_delete(perl_parser); +#endif + +#ifdef LUA_EQEMU + safe_delete(lua_parser); +#endif + + safe_delete(mmf); + safe_delete(Config); + + if (zone != 0) + Zone::Shutdown(true); + //Fix for Linux world server problem. + eqsf.Close(); + worldserver.Disconnect(); + safe_delete(taskmanager); + command_deinit(); + safe_delete(parse); + CheckEQEMuErrorAndPause(); + _log(ZONE__INIT, "Proper zone shutdown complete."); + return 0; +} + +void CatchSignal(int sig_num) { +#ifdef _WINDOWS + _log(ZONE__INIT, "Recieved signal: %i", sig_num); +#endif + RunLoops = false; +} + +void Shutdown() +{ + Zone::Shutdown(true); + RunLoops = false; + worldserver.Disconnect(); + // safe_delete(worldserver); + _log(ZONE__INIT, "Shutting down..."); +} + +uint32 NetConnection::GetIP() +{ + char name[255+1]; + size_t len = 0; + hostent* host = 0; + + if (gethostname(name, len) < 0 || len <= 0) + { + return 0; + } + + host = (hostent*)gethostbyname(name); + if (host == 0) + { + return 0; + } + + return inet_addr(host->h_addr); +} + +uint32 NetConnection::GetIP(char* name) +{ + hostent* host = 0; + + host = (hostent*)gethostbyname(name); + if (host == 0) + { + return 0; + } + + return inet_addr(host->h_addr); + +} + +void NetConnection::SaveInfo(char* address, uint32 port, char* waddress, char* filename) { + + ZoneAddress = new char[strlen(address)+1]; + strcpy(ZoneAddress, address); + ZonePort = port; + WorldAddress = new char[strlen(waddress)+1]; + strcpy(WorldAddress, waddress); + strn0cpy(ZoneFileName, filename, sizeof(ZoneFileName)); +} + +NetConnection::NetConnection() +: + object_timer(5000), + door_timer(5000), + corpse_timer(2000), + group_timer(1000), + raid_timer(1000), + trap_timer(1000) +{ + ZonePort = 0; + ZoneAddress = 0; + WorldAddress = 0; + group_timer.Disable(); + raid_timer.Disable(); + corpse_timer.Disable(); + door_timer.Disable(); + object_timer.Disable(); + trap_timer.Disable(); +} + +NetConnection::~NetConnection() { + if (ZoneAddress != 0) + safe_delete_array(ZoneAddress); + if (WorldAddress != 0) + safe_delete_array(WorldAddress); +} + +void LoadSpells(EQEmu::MemoryMappedFile **mmf) { + int records = database.GetMaxSpellID() + 1; + + try { + EQEmu::IPCMutex mutex("spells"); + mutex.Lock(); + *mmf = new EQEmu::MemoryMappedFile("shared/spells"); + uint32 size = (*mmf)->Size(); + if(size != (records * sizeof(SPDat_Spell_Struct))) { + EQ_EXCEPT("Zone", "Unable to load spells: (*mmf)->Size() != records * sizeof(SPDat_Spell_Struct)"); + } + + spells = reinterpret_cast((*mmf)->Get()); + mutex.Unlock(); + } catch(std::exception &ex) { + LogFile->write(EQEMuLog::Error, "Error loading spells: %s", ex.what()); + return; + } + + SPDAT_RECORDS = records; +} + +/* Update Window Title with relevant information */ +void UpdateWindowTitle(char* iNewTitle) { +#ifdef _WINDOWS + char tmp[500]; + if (iNewTitle) { + snprintf(tmp, sizeof(tmp), "%i: %s", ZoneConfig::get()->ZonePort, iNewTitle); + } + else { + if (zone) { + #if defined(GOTFRAGS) || defined(_EQDEBUG) + snprintf(tmp, sizeof(tmp), "%i: %s, %i clients, %i", ZoneConfig::get()->ZonePort, zone->GetShortName(), numclients, getpid()); + #else + snprintf(tmp, sizeof(tmp), "%s :: clients: %i inst_id: %i inst_ver: %i :: port: %i", zone->GetShortName(), numclients, zone->GetInstanceID(), zone->GetInstanceVersion(), ZoneConfig::get()->ZonePort); + #endif + } + else { + #if defined(GOTFRAGS) || defined(_EQDEBUG) + snprintf(tmp, sizeof(tmp), "%i: sleeping, %i", ZoneConfig::get()->ZonePort, getpid()); + #else + snprintf(tmp, sizeof(tmp), "%i: sleeping", ZoneConfig::get()->ZonePort); + #endif + } + } + SetConsoleTitle(tmp); +#endif +} diff --git a/zone/npcx.cpp b/zone/npcx.cpp new file mode 100644 index 000000000..7d2766854 --- /dev/null +++ b/zone/npcx.cpp @@ -0,0 +1,2492 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2002 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/debug.h" +#include +#include +#include +#include +#include "../common/moremath.h" +#include +#include "../common/packet_dump_file.h" +#include "zone.h" +#ifdef _WINDOWS +#define snprintf _snprintf +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#else +#include +#include +#endif + +#include "npc.h" +#include "map.h" +#include "entity.h" +#include "masterentity.h" +#include "../common/spdat.h" +#include "../common/bodytypes.h" +#include "spawngroup.h" +#include "../common/MiscFunctions.h" +#include "../common/StringUtil.h" +#include "../common/rulesys.h" +#include "StringIDs.h" + +//#define SPELLQUEUE //Use only if you want to be spammed by spell testing + + +extern Zone* zone; +extern volatile bool ZoneLoaded; +extern EntityList entity_list; + +#include "QuestParserCollection.h" + +NPC::NPC(const NPCType* d, Spawn2* in_respawn, float x, float y, float z, float heading, int iflymode, bool IsCorpse) +: Mob(d->name, + d->lastname, + d->max_hp, + d->max_hp, + d->gender, + d->race, + d->class_, + (bodyType)d->bodytype, + d->deity, + d->level, + d->npc_id, + d->size, + d->runspeed, + heading, + x, + y, + z, + d->light, + d->texture, + d->helmtexture, + d->AC, + d->ATK, + d->STR, + d->STA, + d->DEX, + d->AGI, + d->INT, + d->WIS, + d->CHA, + d->haircolor, + d->beardcolor, + d->eyecolor1, + d->eyecolor2, + d->hairstyle, + d->luclinface, + d->beard, + d->drakkin_heritage, + d->drakkin_tattoo, + d->drakkin_details, + (uint32*)d->armor_tint, + 0, + d->see_invis, // pass see_invis/see_ivu flags to mob constructor + d->see_invis_undead, + d->see_hide, + d->see_improved_hide, + d->hp_regen, + d->mana_regen, + d->qglobal, + d->maxlevel, + d->scalerate ), + attacked_timer(CombatEventTimer_expire), + swarm_timer(100), + classattack_timer(1000), + knightattack_timer(1000), + assist_timer(AIassistcheck_delay), + qglobal_purge_timer(30000), + sendhpupdate_timer(1000), + enraged_timer(1000), + taunt_timer(TauntReuseTime * 1000) +{ + //What is the point of this, since the names get mangled.. + Mob* mob = entity_list.GetMob(name); + if(mob != 0) + entity_list.RemoveEntity(mob->GetID()); + + int moblevel=GetLevel(); + + NPCTypedata = d; + NPCTypedata_ours = nullptr; + respawn2 = in_respawn; + swarm_timer.Disable(); + + taunting = false; + proximity = nullptr; + copper = 0; + silver = 0; + gold = 0; + platinum = 0; + max_dmg = d->max_dmg; + min_dmg = d->min_dmg; + attack_count = d->attack_count; + grid = 0; + wp_m = 0; + max_wp=0; + save_wp = 0; + spawn_group = 0; + swarmInfoPtr = nullptr; + spellscale = d->spellscale; + healscale = d->healscale; + + logging_enabled = NPC_DEFAULT_LOGGING_ENABLED; + + pAggroRange = d->aggroradius; + pAssistRange = d->assistradius; + findable = d->findable; + trackable = d->trackable; + + MR = d->MR; + CR = d->CR; + DR = d->DR; + FR = d->FR; + PR = d->PR; + Corrup = d->Corrup; + PhR = d->PhR; + + STR = d->STR; + STA = d->STA; + AGI = d->AGI; + DEX = d->DEX; + INT = d->INT; + WIS = d->WIS; + CHA = d->CHA; + npc_mana = d->Mana; + + //quick fix of ordering if they screwed it up in the DB + if(max_dmg < min_dmg) { + int tmp = min_dmg; + min_dmg = max_dmg; + max_dmg = tmp; + } + + // Max Level and Stat Scaling if maxlevel is set + if(maxlevel > level) + { + LevelScale(); + } + + // Set Resists if they are 0 in the DB + CalcNPCResists(); + + // Set Mana and HP Regen Rates if they are 0 in the DB + CalcNPCRegen(); + + // Set Min and Max Damage if they are 0 in the DB + if(max_dmg == 0){ + CalcNPCDamage(); + } + + accuracy_rating = d->accuracy_rating; + ATK = d->ATK; + + CalcMaxMana(); + SetMana(GetMaxMana()); + + MerchantType = d->merchanttype; + merchant_open = GetClass() == MERCHANT; + adventure_template_id = d->adventure_template; + org_x = x; + org_y = y; + org_z = z; + flymode = iflymode; + guard_x = -1; //just some value we might be able to recongize as "unset" + guard_y = -1; + guard_z = -1; + guard_heading = 0; + guard_anim = eaStanding; + roambox_distance = 0; + roambox_max_x = -2; + roambox_max_y = -2; + roambox_min_x = -2; + roambox_min_y = -2; + roambox_movingto_x = -2; + roambox_movingto_y = -2; + roambox_min_delay = 1000; + roambox_delay = 1000; + org_heading = heading; + p_depop = false; + loottable_id = d->loottable_id; + + no_target_hotkey = d->no_target_hotkey; + + primary_faction = 0; + SetNPCFactionID(d->npc_faction_id); + + npc_spells_id = 0; + HasAISpell = false; + HasAISpellEffects = false; + + if(GetClass() == MERCERNARY_MASTER && RuleB(Mercs, AllowMercs)) + { + LoadMercTypes(); + LoadMercs(); + } + + SpellFocusDMG = 0; + SpellFocusHeal = 0; + + pet_spell_id = 0; + + delaytimer = false; + combat_event = false; + attack_speed = d->attack_speed; + slow_mitigation = d->slow_mitigation; + + EntityList::RemoveNumbers(name); + entity_list.MakeNameUnique(name); + + npc_aggro = d->npc_aggro; + + if(!IsMerc()) + AI_Start(); + + d_meele_texture1 = d->d_meele_texture1; + d_meele_texture2 = d->d_meele_texture2; + memset(equipment, 0, sizeof(equipment)); + prim_melee_type = d->prim_melee_type; + sec_melee_type = d->sec_melee_type; + + // If Melee Textures are not set, set attack type to Hand to Hand as default + if(!d_meele_texture1) + prim_melee_type = 28; + if(!d_meele_texture2) + sec_melee_type = 28; + + //give NPCs skill values... + int r; + for(r = 0; r <= HIGHEST_SKILL; r++) { + skills[r] = database.GetSkillCap(GetClass(),(SkillUseTypes)r,moblevel); + } + + if(d->trap_template > 0) + { + std::map >::iterator trap_ent_iter; + std::list trap_list; + + trap_ent_iter = zone->ldon_trap_entry_list.find(d->trap_template); + if(trap_ent_iter != zone->ldon_trap_entry_list.end()) + { + trap_list = trap_ent_iter->second; + if(trap_list.size() > 0) + { + std::list::iterator trap_list_iter = trap_list.begin(); + std::advance(trap_list_iter, MakeRandomInt(0, trap_list.size() - 1)); + LDoNTrapTemplate* tt = (*trap_list_iter); + if(tt) + { + if((uint8)tt->spell_id > 0) + { + ldon_trapped = true; + ldon_spell_id = tt->spell_id; + } + else + { + ldon_trapped = false; + ldon_spell_id = 0; + } + + ldon_trap_type = (uint8)tt->type; + if(tt->locked > 0) + { + ldon_locked = true; + ldon_locked_skill = tt->skill; + } + else + { + ldon_locked = false; + ldon_locked_skill = 0; + } + ldon_trap_detected = 0; + } + } + else + { + ldon_trapped = false; + ldon_trap_type = 0; + ldon_spell_id = 0; + ldon_locked = false; + ldon_locked_skill = 0; + ldon_trap_detected = 0; + } + } + else + { + ldon_trapped = false; + ldon_trap_type = 0; + ldon_spell_id = 0; + ldon_locked = false; + ldon_locked_skill = 0; + ldon_trap_detected = 0; + } + } + else + { + ldon_trapped = false; + ldon_trap_type = 0; + ldon_spell_id = 0; + ldon_locked = false; + ldon_locked_skill = 0; + ldon_trap_detected = 0; + } + reface_timer = new Timer(15000); + reface_timer->Disable(); + qGlobals = nullptr; + guard_x_saved = 0; + guard_y_saved = 0; + guard_z_saved = 0; + guard_heading_saved = 0; + SetEmoteID(d->emoteid); + InitializeBuffSlots(); + CalcBonuses(); +} + +NPC::~NPC() +{ + AI_Stop(); + + if(proximity != nullptr) { + entity_list.RemoveProximity(GetID()); + safe_delete(proximity); + } + + safe_delete(NPCTypedata_ours); + + { + ItemList::iterator cur,end; + cur = itemlist.begin(); + end = itemlist.end(); + for(; cur != end; ++cur) { + ServerLootItem_Struct* item = *cur; + safe_delete(item); + } + itemlist.clear(); + } + + { + std::list::iterator cur,end; + cur = faction_list.begin(); + end = faction_list.end(); + for(; cur != end; ++cur) { + struct NPCFaction* fac = *cur; + safe_delete(fac); + } + faction_list.clear(); + } + + safe_delete(reface_timer); + safe_delete(swarmInfoPtr); + safe_delete(qGlobals); + //Moved this from ~Mob, because it could cause a crash in some cases where a hate list was still used and that mob was the only one on the hate list. + entity_list.RemoveFromTargets(this, true); + UninitializeBuffSlots(); +} + +void NPC::SetTarget(Mob* mob) { + if(mob == GetTarget()) //dont bother if they are allready our target + return; + + //our target is already set, do not turn from the course, unless our current target is dead. + if(GetSwarmInfo() && GetTarget() && (GetTarget()->GetHP() > 0)) { + Mob *targ = entity_list.GetMob(GetSwarmInfo()->target); + if(targ != mob){ + return; + } + } + + if (mob) { + SetAttackTimer(); + } else { + ranged_timer.Disable(); + //attack_timer.Disable(); + attack_dw_timer.Disable(); + } + Mob::SetTarget(mob); +} + +ServerLootItem_Struct* NPC::GetItem(int slot_id) { + ItemList::iterator cur,end; + cur = itemlist.begin(); + end = itemlist.end(); + for(; cur != end; ++cur) { + ServerLootItem_Struct* item = *cur; + if (item->equipSlot == slot_id) { + return item; + } + } + return(nullptr); +} + +void NPC::RemoveItem(uint32 item_id, uint16 quantity, uint16 slot) { + ItemList::iterator cur,end; + cur = itemlist.begin(); + end = itemlist.end(); + for(; cur != end; ++cur) { + ServerLootItem_Struct* item = *cur; + if (item->item_id == item_id && slot <= 0 && quantity <= 0) { + itemlist.erase(cur); + return; + } + else if (item->item_id == item_id && item->equipSlot == slot && quantity >= 1) { + //std::cout<<"NPC::RemoveItem"<<" equipSlot:"<equipSlot<<" quantity:"<< quantity<charges <= quantity) + itemlist.erase(cur); + else + item->charges -= quantity; + return; + } + } +} + +void NPC::CheckMinMaxLevel(Mob *them) +{ + if(them == nullptr || !them->IsClient()) + return; + + uint16 themlevel = them->GetLevel(); + uint8 material; + + std::list::iterator cur = itemlist.begin(); + while(cur != itemlist.end()) + { + if(!(*cur)) + return; + + if(themlevel < (*cur)->minlevel || themlevel > (*cur)->maxlevel) + { + material = Inventory::CalcMaterialFromSlot((*cur)->equipSlot); + if(material != 0xFF) + SendWearChange(material); + + cur = itemlist.erase(cur); + continue; + } + ++cur; + } + +} + +void NPC::ClearItemList() { + ItemList::iterator cur,end; + cur = itemlist.begin(); + end = itemlist.end(); + for(; cur != end; ++cur) { + ServerLootItem_Struct* item = *cur; + safe_delete(item); + } + itemlist.clear(); +} + +void NPC::QueryLoot(Client* to) { + int x = 0; + to->Message(0, "Coin: %ip %ig %is %ic", platinum, gold, silver, copper); + + ItemList::iterator cur,end; + cur = itemlist.begin(); + end = itemlist.end(); + for(; cur != end; ++cur) { + const Item_Struct* item = database.GetItem((*cur)->item_id); + if (item) + if (to->GetClientVersion() >= EQClientRoF) + { + to->Message(0, "minlvl: %i maxlvl: %i %i: %c%06X0000000000000000000000000000000000000000000000000%s%c",(*cur)->minlevel, (*cur)->maxlevel, (int) item->ID,0x12, item->ID, item->Name, 0x12); + } + else if (to->GetClientVersion() >= EQClientSoF) + { + to->Message(0, "minlvl: %i maxlvl: %i %i: %c%06X00000000000000000000000000000000000000000000%s%c",(*cur)->minlevel, (*cur)->maxlevel, (int) item->ID,0x12, item->ID, item->Name, 0x12); + } + else + { + to->Message(0, "minlvl: %i maxlvl: %i %i: %c%06X000000000000000000000000000000000000000%s%c",(*cur)->minlevel, (*cur)->maxlevel, (int) item->ID,0x12, item->ID, item->Name, 0x12); + } + else + LogFile->write(EQEMuLog::Error, "Database error, invalid item"); + x++; + } + to->Message(0, "%i items on %s.", x, GetName()); +} + +void NPC::AddCash(uint16 in_copper, uint16 in_silver, uint16 in_gold, uint16 in_platinum) { + if(in_copper >= 0) + copper = in_copper; + else + copper = 0; + + if(in_silver >= 0) + silver = in_silver; + else + silver = 0; + + if(in_gold >= 0) + gold = in_gold; + else + gold = 0; + + if(in_platinum >= 0) + platinum = in_platinum; + else + platinum = 0; +} + +void NPC::AddCash() { + copper = MakeRandomInt(1, 100); + silver = MakeRandomInt(1, 50); + gold = MakeRandomInt(1, 10); + platinum = MakeRandomInt(1, 5); +} + +void NPC::RemoveCash() { + copper = 0; + silver = 0; + gold = 0; + platinum = 0; +} + +bool NPC::Process() +{ + if (IsStunned() && stunned_timer.Check()) + { + this->stunned = false; + this->stunned_timer.Disable(); + this->spun_timer.Disable(); + } + + if (p_depop) + { + Mob* owner = entity_list.GetMob(this->ownerid); + if (owner != 0) + { + //if(GetBodyType() != BT_SwarmPet) + // owner->SetPetID(0); + this->ownerid = 0; + this->petid = 0; + } + return false; + } + + SpellProcess(); + + if(tic_timer.Check()) + { + BuffProcess(); + + if(curfp) + ProcessFlee(); + + uint32 bonus = 0; + + if(GetAppearance() == eaSitting) + bonus+=3; + + int32 OOCRegen = 0; + if(oocregen > 0){ //should pull from Mob class + OOCRegen += GetMaxHP() * oocregen / 100; + } + //Lieka Edit:Fixing NPC regen.NPCs should regen to full during a set duration, not based on their HPs.Increase NPC's HPs by % of total HPs / tick. + if((GetHP() < GetMaxHP()) && !IsPet()) { + if(!IsEngaged()) {//NPC out of combat + if(hp_regen > OOCRegen) + SetHP(GetHP() + hp_regen); + else + SetHP(GetHP() + OOCRegen); + } else + SetHP(GetHP()+hp_regen); + } else if(GetHP() < GetMaxHP() && GetOwnerID() !=0) { + if(!IsEngaged()) //pet + SetHP(GetHP()+hp_regen+bonus+(GetLevel()/5)); + else + SetHP(GetHP()+hp_regen+bonus); + } else + SetHP(GetHP()+hp_regen); + + if(GetMana() < GetMaxMana()) { + SetMana(GetMana()+mana_regen+bonus); + } + + + if(zone->adv_data && !p_depop) + { + ServerZoneAdventureDataReply_Struct* ds = (ServerZoneAdventureDataReply_Struct*)zone->adv_data; + if(ds->type == Adventure_Rescue && ds->data_id == GetNPCTypeID()) + { + Mob *o = GetOwner(); + if(o && o->IsClient()) + { + float x_diff = ds->dest_x - GetX(); + float y_diff = ds->dest_y - GetY(); + float z_diff = ds->dest_z - GetZ(); + float dist = ((x_diff * x_diff) + (y_diff * y_diff) + (z_diff * z_diff)); + if(dist < RuleR(Adventure, DistanceForRescueComplete)) + { + zone->DoAdventureCountIncrease(); + Say("You don't know what this means to me. Thank you so much for finding and saving me from" + " this wretched place. I'll find my way from here."); + Depop(); + } + } + } + } + } + + if (sendhpupdate_timer.Check() && (IsTargeted() || (IsPet() && GetOwner() && GetOwner()->IsClient()))) { + if(!IsFullHP || cur_hp 999) + viral_timer_counter = 0; + } + + if(projectile_timer.Check()) + SpellProjectileEffect(); + + if(spellbonuses.GravityEffect == 1) { + if(gravity_timer.Check()) + DoGravityEffect(); + } + + if(reface_timer->Check() && !IsEngaged() && (guard_x == GetX() && guard_y == GetY() && guard_z == GetZ())) { + SetHeading(guard_heading); + SendPosition(); + reface_timer->Disable(); + } + + if (IsMezzed()) + return true; + + if(IsStunned()) { + if(spun_timer.Check()) + Spin(); + return true; + } + + if (enraged_timer.Check()){ + ProcessEnrage(); + } + + //Handle assists... + if(assist_timer.Check() && IsEngaged() && !Charmed()) { + entity_list.AIYellForHelp(this, GetTarget()); + } + + if(qGlobals) + { + if(qglobal_purge_timer.Check()) + { + qGlobals->PurgeExpiredGlobals(); + } + } + + AI_Process(); + + return true; +} + +uint32 NPC::CountLoot() { + return(itemlist.size()); +} + +void NPC::Depop(bool StartSpawnTimer) { + uint16 emoteid = this->GetEmoteID(); + if(emoteid != 0) + this->DoNPCEmote(ONDESPAWN,emoteid); + p_depop = true; + if (StartSpawnTimer) { + if (respawn2 != 0) { + respawn2->DeathReset(); + } + } +} + +bool NPC::DatabaseCastAccepted(int spell_id) { + for (int i=0; i < 12; i++) { + switch(spells[spell_id].effectid[i]) { + case SE_Stamina: { + if(IsEngaged() && GetHPRatio() < 100) + return true; + else + return false; + break; + } + case SE_CurrentHPOnce: + case SE_CurrentHP: { + if(this->GetHPRatio() < 100 && spells[spell_id].buffduration == 0) + return true; + else + return false; + break; + } + + case SE_HealOverTime: { + if(this->GetHPRatio() < 100) + return true; + else + return false; + break; + } + case SE_DamageShield: { + return true; + } + case SE_NecPet: + case SE_SummonPet: { + if(GetPet()){ +#ifdef SPELLQUEUE + printf("%s: Attempted to make a second pet, denied.\n",GetName()); +#endif + return false; + } + break; + } + case SE_LocateCorpse: + case SE_SummonCorpse: { + return false; //Pfft, npcs don't need to summon corpses/locate corpses! + break; + } + default: + if(spells[spell_id].goodEffect == 1 && !(spells[spell_id].buffduration == 0 && this->GetHPRatio() == 100) && !IsEngaged()) + return true; + return false; + } + } + return false; +} + +NPC* NPC::SpawnNPC(const char* spawncommand, float in_x, float in_y, float in_z, float in_heading, Client* client) { + if(spawncommand == 0 || spawncommand[0] == 0) { + return 0; + } + else { + Seperator sep(spawncommand); + //Lets see if someone didn't fill out the whole #spawn function properly + if (!sep.IsNumber(1)) + sprintf(sep.arg[1],"1"); + if (!sep.IsNumber(2)) + sprintf(sep.arg[2],"1"); + if (!sep.IsNumber(3)) + sprintf(sep.arg[3],"0"); + if (atoi(sep.arg[4]) > 2100000000 || atoi(sep.arg[4]) <= 0) + sprintf(sep.arg[4]," "); + if (!strcmp(sep.arg[5],"-")) + sprintf(sep.arg[5]," "); + if (!sep.IsNumber(5)) + sprintf(sep.arg[5]," "); + if (!sep.IsNumber(6)) + sprintf(sep.arg[6],"1"); + if (!sep.IsNumber(8)) + sprintf(sep.arg[8],"0"); + if (!sep.IsNumber(9)) + sprintf(sep.arg[9], "0"); + if (!sep.IsNumber(7)) + sprintf(sep.arg[7],"0"); + if (!strcmp(sep.arg[4],"-")) + sprintf(sep.arg[4]," "); + if (!sep.IsNumber(10)) // bodytype + sprintf(sep.arg[10], "0"); + //Calc MaxHP if client neglected to enter it... + if (!sep.IsNumber(4)) { + //Stolen from Client::GetMaxHP... + uint8 multiplier = 0; + int tmplevel = atoi(sep.arg[2]); + switch(atoi(sep.arg[5])) + { + case WARRIOR: + if (tmplevel < 20) + multiplier = 22; + else if (tmplevel < 30) + multiplier = 23; + else if (tmplevel < 40) + multiplier = 25; + else if (tmplevel < 53) + multiplier = 27; + else if (tmplevel < 57) + multiplier = 28; + else + multiplier = 30; + break; + + case DRUID: + case CLERIC: + case SHAMAN: + multiplier = 15; + break; + + case PALADIN: + case SHADOWKNIGHT: + if (tmplevel < 35) + multiplier = 21; + else if (tmplevel < 45) + multiplier = 22; + else if (tmplevel < 51) + multiplier = 23; + else if (tmplevel < 56) + multiplier = 24; + else if (tmplevel < 60) + multiplier = 25; + else + multiplier = 26; + break; + + case MONK: + case BARD: + case ROGUE: + //case BEASTLORD: + if (tmplevel < 51) + multiplier = 18; + else if (tmplevel < 58) + multiplier = 19; + else + multiplier = 20; + break; + + case RANGER: + if (tmplevel < 58) + multiplier = 20; + else + multiplier = 21; + break; + + case MAGICIAN: + case WIZARD: + case NECROMANCER: + case ENCHANTER: + multiplier = 12; + break; + + default: + if (tmplevel < 35) + multiplier = 21; + else if (tmplevel < 45) + multiplier = 22; + else if (tmplevel < 51) + multiplier = 23; + else if (tmplevel < 56) + multiplier = 24; + else if (tmplevel < 60) + multiplier = 25; + else + multiplier = 26; + break; + } + sprintf(sep.arg[4],"%i",5+multiplier*atoi(sep.arg[2])+multiplier*atoi(sep.arg[2])*75/300); + } + + // Autoselect NPC Gender + if (sep.arg[5][0] == 0) { + sprintf(sep.arg[5], "%i", (int) Mob::GetDefaultGender(atoi(sep.arg[1]))); + } + + //Time to create the NPC!! + NPCType* npc_type = new NPCType; + memset(npc_type, 0, sizeof(NPCType)); + + strncpy(npc_type->name, sep.arg[0], 60); + npc_type->cur_hp = atoi(sep.arg[4]); + npc_type->max_hp = atoi(sep.arg[4]); + npc_type->race = atoi(sep.arg[1]); + npc_type->gender = atoi(sep.arg[5]); + npc_type->class_ = atoi(sep.arg[6]); + npc_type->deity = 1; + npc_type->level = atoi(sep.arg[2]); + npc_type->npc_id = 0; + npc_type->loottable_id = 0; + npc_type->texture = atoi(sep.arg[3]); + npc_type->light = 0; + npc_type->runspeed = 1.25; + npc_type->d_meele_texture1 = atoi(sep.arg[7]); + npc_type->d_meele_texture2 = atoi(sep.arg[8]); + npc_type->merchanttype = atoi(sep.arg[9]); + npc_type->bodytype = atoi(sep.arg[10]); + + npc_type->STR = 150; + npc_type->STA = 150; + npc_type->DEX = 150; + npc_type->AGI = 150; + npc_type->INT = 150; + npc_type->WIS = 150; + npc_type->CHA = 150; + + npc_type->prim_melee_type = 28; + npc_type->sec_melee_type = 28; + + NPC* npc = new NPC(npc_type, 0, in_x, in_y, in_z, in_heading/8, FlyMode3); + npc->GiveNPCTypeData(npc_type); + + entity_list.AddNPC(npc); + + if (client) { + // Notify client of spawn data + client->Message(0, "New spawn:"); + client->Message(0, "Name: %s", npc->name); + client->Message(0, "Race: %u", npc->race); + client->Message(0, "Level: %u", npc->level); + client->Message(0, "Material: %u", npc->texture); + client->Message(0, "Current/Max HP: %i", npc->max_hp); + client->Message(0, "Gender: %u", npc->gender); + client->Message(0, "Class: %u", npc->class_); + client->Message(0, "Weapon Item Number: %u/%u", npc->d_meele_texture1, npc->d_meele_texture2); + client->Message(0, "MerchantID: %u", npc->MerchantType); + client->Message(0, "Bodytype: %u", npc->bodytype); + } + + return npc; + } +} + +uint32 ZoneDatabase::NPCSpawnDB(uint8 command, const char* zone, uint32 zone_version, Client *c, NPC* spawn, uint32 extra) { + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + uint32 tmp = 0; + uint32 tmp2 = 0; + uint32 last_insert_id = 0; + switch (command) { + case 0: { // Create a new NPC and add all spawn related data + uint32 npc_type_id = 0; + uint32 spawngroupid; + if (extra && c && c->GetZoneID()) + { + // Set an npc_type ID within the standard range for the current zone if possible (zone_id * 1000) + int starting_npc_id = c->GetZoneID() * 1000; + if (RunQuery(query, MakeAnyLenString(&query, "SELECT MAX(id) FROM npc_types WHERE id >= %i AND id < %i", starting_npc_id, (starting_npc_id + 1000)), errbuf, &result)) { + row = mysql_fetch_row(result); + if(row) + { + if (row[0]) + { + npc_type_id = atoi(row[0]) + 1; + // Prevent the npc_type id from exceeding the range for this zone + if (npc_type_id >= (starting_npc_id + 1000)) + { + npc_type_id = 0; + } + } + else + { + // row[0] is nullptr - No npc_type IDs set in this range yet + npc_type_id = starting_npc_id; + } + } + + safe_delete_array(query); + mysql_free_result(result); + } + } + char tmpstr[64]; + EntityList::RemoveNumbers(strn0cpy(tmpstr, spawn->GetName(), sizeof(tmpstr))); + if (npc_type_id) + { + if (!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO npc_types (id, name, level, race, class, hp, gender, texture, helmtexture, size, loottable_id, merchant_id, face, runspeed, prim_melee_type, sec_melee_type) values(%i,\"%s\",%i,%i,%i,%i,%i,%i,%i,%f,%i,%i,%i,%f,%i,%i)", npc_type_id, tmpstr, spawn->GetLevel(), spawn->GetRace(), spawn->GetClass(), spawn->GetMaxHP(), spawn->GetGender(), spawn->GetTexture(), spawn->GetHelmTexture(), spawn->GetSize(), spawn->GetLoottableID(), spawn->MerchantType, 0, spawn->GetRunspeed(), 28, 28), errbuf, 0, 0, &npc_type_id)) { + LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query, errbuf); + safe_delete(query); + return false; + } + } + else + { + if (!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO npc_types (name, level, race, class, hp, gender, texture, helmtexture, size, loottable_id, merchant_id, face, runspeed, prim_melee_type, sec_melee_type) values(\"%s\",%i,%i,%i,%i,%i,%i,%i,%f,%i,%i,%i,%f,%i,%i)", tmpstr, spawn->GetLevel(), spawn->GetRace(), spawn->GetClass(), spawn->GetMaxHP(), spawn->GetGender(), spawn->GetTexture(), spawn->GetHelmTexture(), spawn->GetSize(), spawn->GetLoottableID(), spawn->MerchantType, 0, spawn->GetRunspeed(), 28, 28), errbuf, 0, 0, &npc_type_id)) { + LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query, errbuf); + safe_delete(query); + return false; + } + } + if(c) c->LogSQL(query); + safe_delete_array(query); + snprintf(tmpstr, sizeof(tmpstr), "%s-%s", zone, spawn->GetName()); + if (!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO spawngroup (id, name) values(%i, '%s')", tmp, tmpstr), errbuf, 0, 0, &spawngroupid)) { + LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query, errbuf); + safe_delete(query); + return false; + } + if(c) c->LogSQL(query); + safe_delete_array(query); + if (!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO spawn2 (zone, version, x, y, z, respawntime, heading, spawngroupID) values('%s', %u, %f, %f, %f, %i, %f, %i)", zone, zone_version, spawn->GetX(), spawn->GetY(), spawn->GetZ(), 1200, spawn->GetHeading(), spawngroupid), errbuf, 0, 0, &tmp)) { + LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query, errbuf); + safe_delete(query); + return false; + } + if(c) c->LogSQL(query); + safe_delete_array(query); + if (!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO spawnentry (spawngroupID, npcID, chance) values(%i, %i, %i)", spawngroupid, npc_type_id, 100), errbuf, 0)) { + LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query, errbuf); + safe_delete(query); + return false; + } + if(c) c->LogSQL(query); + safe_delete_array(query); + return true; + break; + } + case 1:{ // Add new spawn group and spawn point for an existing NPC Type ID + tmp2 = spawn->GetNPCTypeID(); + char tmpstr[64]; + snprintf(tmpstr, sizeof(tmpstr), "%s%s%i", zone, spawn->GetName(),Timer::GetCurrentTime()); + if (!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO spawngroup (name) values('%s')", tmpstr), errbuf, 0, 0, &last_insert_id)) { + LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query, errbuf); + safe_delete(query); + return false; + } + if(c) c->LogSQL(query); + safe_delete_array(query); + + uint32 respawntime = 0; + uint32 spawnid = 0; + if (extra) + respawntime = extra; + else if(spawn->respawn2 && spawn->respawn2->RespawnTimer() != 0) + respawntime = spawn->respawn2->RespawnTimer(); + else + respawntime = 1200; + if (!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO spawn2 (zone, version, x, y, z, respawntime, heading, spawngroupID) values('%s', %u, %f, %f, %f, %i, %f, %i)", zone, zone_version, spawn->GetX(), spawn->GetY(), spawn->GetZ(), respawntime, spawn->GetHeading(), last_insert_id), errbuf, 0, 0, &spawnid)) { + LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query, errbuf); + safe_delete(query); + return false; + } + if(c) c->LogSQL(query); + safe_delete_array(query); + + if (!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO spawnentry (spawngroupID, npcID, chance) values(%i, %i, %i)", last_insert_id, tmp2, 100), errbuf, 0)) { + LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query, errbuf); + safe_delete(query); + return false; + } + if(c) c->LogSQL(query); + safe_delete_array(query); + return spawnid; + break; + } + case 2: { // Update npc_type appearance and other data on targeted spawn + if (!RunQuery(query, MakeAnyLenString(&query, "UPDATE npc_types SET name=\"%s\", level=%i, race=%i, class=%i, hp=%i, gender=%i, texture=%i, helmtexture=%i, size=%i, loottable_id=%i, merchant_id=%i, face=%i, WHERE id=%i", spawn->GetName(), spawn->GetLevel(), spawn->GetRace(), spawn->GetClass(), spawn->GetMaxHP(), spawn->GetGender(), spawn->GetTexture(), spawn->GetHelmTexture(), spawn->GetSize(), spawn->GetLoottableID(), spawn->MerchantType, spawn->GetNPCTypeID()), errbuf, 0)) { + if(c) c->LogSQL(query); + safe_delete_array(query); + return true; + } + else { + safe_delete_array(query); + return false; + } + break; + } + case 3: { // delete spawn from spawning, but leave in npc_types table + if (!RunQuery(query, MakeAnyLenString(&query, "SELECT id,spawngroupID from spawn2 where zone='%s' AND spawngroupID=%i", zone, spawn->GetSp2()), errbuf, &result)) { + safe_delete_array(query); + return 0; + } + safe_delete_array(query); + + row = mysql_fetch_row(result); + if (row == nullptr) return false; + if (row[0]) tmp = atoi(row[0]); + if (row[1]) tmp2 = atoi(row[1]); + + if (!RunQuery(query, MakeAnyLenString(&query, "DELETE FROM spawn2 WHERE id='%i'", tmp), errbuf,0)) { + safe_delete(query); + return false; + } + if(c) c->LogSQL(query); + safe_delete_array(query); + if (!RunQuery(query, MakeAnyLenString(&query, "DELETE FROM spawngroup WHERE id='%i'", tmp2), errbuf,0)) { + safe_delete(query); + return false; + } + if(c) c->LogSQL(query); + safe_delete_array(query); + if (!RunQuery(query, MakeAnyLenString(&query, "DELETE FROM spawnentry WHERE spawngroupID='%i'", tmp2), errbuf,0)) { + safe_delete(query); + return false; + } + if(c) c->LogSQL(query); + safe_delete_array(query); + return true; + + + break; + } + case 4: { //delete spawn from DB (including npc_type) + if (!RunQuery(query, MakeAnyLenString(&query, "SELECT id,spawngroupID from spawn2 where zone='%s' AND version=%u AND spawngroupID=%i", zone, zone_version, spawn->GetSp2()), errbuf, &result)) { + safe_delete_array(query); + return(0); + } + safe_delete_array(query); + + row = mysql_fetch_row(result); + if (row == nullptr) return false; + if (row[0]) tmp = atoi(row[0]); + if (row[1]) tmp2 = atoi(row[1]); + mysql_free_result(result); + + if (!RunQuery(query, MakeAnyLenString(&query, "DELETE FROM spawn2 WHERE id='%i'", tmp), errbuf,0)) { + safe_delete(query); + return false; + } + if(c) c->LogSQL(query); + safe_delete_array(query); + if (!RunQuery(query, MakeAnyLenString(&query, "DELETE FROM spawngroup WHERE id='%i'", tmp2), errbuf,0)) { + safe_delete(query); + return false; + } + if(c) c->LogSQL(query); + safe_delete_array(query); + if (!RunQuery(query, MakeAnyLenString(&query, "DELETE FROM spawnentry WHERE spawngroupID='%i'", tmp2), errbuf,0)) { + safe_delete(query); + return false; + } + if(c) c->LogSQL(query); + safe_delete_array(query); + if (!RunQuery(query, MakeAnyLenString(&query, "DELETE FROM npc_types WHERE id='%i'", spawn->GetNPCTypeID()), errbuf,0)) { + safe_delete(query); + return false; + } + if(c) c->LogSQL(query); + safe_delete_array(query); + return true; + break; + } + case 5: { // add a spawn from spawngroup + if (!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO spawn2 (zone, version, x, y, z, respawntime, heading, spawngroupID) values('%s', %u, %f, %f, %f, %i, %f, %i)", zone, zone_version, c->GetX(), c->GetY(), c->GetZ(), 120, c->GetHeading(), extra), errbuf, 0, 0, &tmp)) { + safe_delete(query); + return false; + } + if(c) c->LogSQL(query); + safe_delete_array(query); + + return true; + break; + } + case 6: { // add npc_type + uint32 npc_type_id; + char tmpstr[64]; + EntityList::RemoveNumbers(strn0cpy(tmpstr, spawn->GetName(), sizeof(tmpstr))); + if (!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO npc_types (name, level, race, class, hp, gender, texture, helmtexture, size, loottable_id, merchant_id, face, runspeed, prim_melee_type, sec_melee_type) values(\"%s\",%i,%i,%i,%i,%i,%i,%i,%f,%i,%i,%i,%f,%i,%i)", tmpstr, spawn->GetLevel(), spawn->GetRace(), spawn->GetClass(), spawn->GetMaxHP(), spawn->GetGender(), spawn->GetTexture(), spawn->GetHelmTexture(), spawn->GetSize(), spawn->GetLoottableID(), spawn->MerchantType, 0, spawn->GetRunspeed(), 28, 28), errbuf, 0, 0, &npc_type_id)) { + safe_delete(query); + return false; + } + if(c) c->LogSQL(query); + safe_delete_array(query); + if(c) c->Message(0, "%s npc_type ID %i created successfully!", tmpstr, npc_type_id); + return true; + break; + } + } + return false; +} + +int32 NPC::GetEquipmentMaterial(uint8 material_slot) const +{ + if (material_slot >= _MaterialCount) + return 0; + + int inv_slot = Inventory::CalcSlotFromMaterial(material_slot); + if (inv_slot == -1) + return 0; + if(equipment[inv_slot] == 0) { + switch(material_slot) { + case MaterialHead: + return helmtexture; + case MaterialChest: + return texture; + case MaterialPrimary: + return d_meele_texture1; + case MaterialSecondary: + return d_meele_texture2; + default: + //they have nothing in the slot, and its not a special slot... they get nothing. + return(0); + } + } + + //they have some loot item in this slot, pass it up to the default handler + return(Mob::GetEquipmentMaterial(material_slot)); +} + +uint32 NPC::GetMaxDamage(uint8 tlevel) +{ + uint32 dmg = 0; + if (tlevel < 40) + dmg = tlevel*2+2; + else if (tlevel < 50) + dmg = level*25/10+2; + else if (tlevel < 60) + dmg = (tlevel*3+2)+((tlevel-50)*30); + else + dmg = (tlevel*3+2)+((tlevel-50)*35); + return dmg; +} + +void NPC::PickPocket(Client* thief) { + + thief->CheckIncreaseSkill(SkillPickPockets, nullptr, 5); + + //make sure were allowed to targte them: + int olevel = GetLevel(); + if(olevel > (thief->GetLevel() + THIEF_PICKPOCKET_OVER)) { + thief->Message(13, "You are too inexperienced to pick pocket this target"); + thief->SendPickPocketResponse(this, 0, PickPocketFailed); + //should we check aggro + return; + } + + if(MakeRandomInt(0, 100) > 95){ + AddToHateList(thief, 50); + Say("Stop thief!"); + thief->Message(13, "You are noticed trying to steal!"); + thief->SendPickPocketResponse(this, 0, PickPocketFailed); + return; + } + + int steal_skill = thief->GetSkill(SkillPickPockets); + int stealchance = steal_skill*100/(5*olevel+5); + ItemInst* inst = 0; + int x = 0; + int slot[50]; + int steal_items[50]; + int charges[50]; + int money[4]; + money[0] = GetPlatinum(); + money[1] = GetGold(); + money[2] = GetSilver(); + money[3] = GetCopper(); + if (steal_skill < 125) + money[0] = 0; + if (steal_skill < 60) + money[1] = 0; + memset(slot,0,50); + memset(steal_items,0,50); + memset(charges,0,50); + //Determine wheter to steal money or an item. + bool no_coin = ((money[0] + money[1] + money[2] + money[3]) == 0); + bool steal_item = (MakeRandomInt(0, 99) < 50 || no_coin); + if (steal_item) + { + ItemList::iterator cur,end; + cur = itemlist.begin(); + end = itemlist.end(); + for(; cur != end && x < 49; ++cur) { + ServerLootItem_Struct* citem = *cur; + const Item_Struct* item = database.GetItem(citem->item_id); + if (item) + { + inst = database.CreateItem(item, citem->charges); + bool is_arrow = (item->ItemType == ItemTypeArrow) ? true : false; + int slot_id = thief->GetInv().FindFreeSlot(false, true, inst->GetItem()->Size, is_arrow); + if (/*!Equipped(item->ID) &&*/ + !item->Magic && item->NoDrop != 0 && !inst->IsType(ItemClassContainer) && slot_id != SLOT_INVALID + /*&& steal_skill > item->StealSkill*/ ) + { + slot[x] = slot_id; + steal_items[x] = item->ID; + if (inst->IsStackable()) + charges[x] = 1; + else + charges[x] = citem->charges; + x++; + } + } + } + if (x > 0) + { + int random = MakeRandomInt(0, x-1); + inst = database.CreateItem(steal_items[random], charges[random]); + if (inst) + { + const Item_Struct* item = inst->GetItem(); + if (item) + { + if (/*item->StealSkill || */steal_skill >= stealchance) + { + thief->PutItemInInventory(slot[random], *inst); + thief->SendItemPacket(slot[random], inst, ItemPacketTrade); + RemoveItem(item->ID); + thief->SendPickPocketResponse(this, 0, PickPocketItem, item); + } + else + steal_item = false; + } + else + steal_item = false; + } + else + steal_item = false; + } + else if (!no_coin) + { + steal_item = false; + } + else + { + thief->Message(0, "This target's pockets are empty"); + thief->SendPickPocketResponse(this, 0, PickPocketFailed); + } + } + if (!steal_item) //Steal money + { + uint32 amt = MakeRandomInt(1, (steal_skill/25)+1); + int steal_type = 0; + if (!money[0]) + { + steal_type = 1; + if (!money[1]) + { + steal_type = 2; + if (!money[2]) + { + steal_type = 3; + } + } + } + + if (MakeRandomInt(0, 100) <= stealchance) + { + switch (steal_type) + { + case 0:{ + if (amt > GetPlatinum()) + amt = GetPlatinum(); + SetPlatinum(GetPlatinum()-amt); + thief->AddMoneyToPP(0,0,0,amt,false); + thief->SendPickPocketResponse(this, amt, PickPocketPlatinum); + break; + } + case 1:{ + if (amt > GetGold()) + amt = GetGold(); + SetGold(GetGold()-amt); + thief->AddMoneyToPP(0,0,amt,0,false); + thief->SendPickPocketResponse(this, amt, PickPocketGold); + break; + } + case 2:{ + if (amt > GetSilver()) + amt = GetSilver(); + SetSilver(GetSilver()-amt); + thief->AddMoneyToPP(0,amt,0,0,false); + thief->SendPickPocketResponse(this, amt, PickPocketSilver); + break; + } + case 3:{ + if (amt > GetCopper()) + amt = GetCopper(); + SetCopper(GetCopper()-amt); + thief->AddMoneyToPP(amt,0,0,0,false); + thief->SendPickPocketResponse(this, amt, PickPocketCopper); + break; + } + } + } + else + { + thief->SendPickPocketResponse(this, 0, PickPocketFailed); + } + } + safe_delete(inst); +} + +void Mob::NPCSpecialAttacks(const char* parse, int permtag, bool reset, bool remove) { + if(reset) + { + ClearSpecialAbilities(); + } + + const char* orig_parse = parse; + while (*parse) + { + switch(*parse) + { + case 'E': + SetSpecialAbility(SPECATK_ENRAGE, remove ? 0 : 1); + break; + case 'F': + SetSpecialAbility(SPECATK_FLURRY, remove ? 0 : 1); + break; + case 'R': + SetSpecialAbility(SPECATK_RAMPAGE, remove ? 0 : 1); + break; + case 'r': + SetSpecialAbility(SPECATK_AREA_RAMPAGE, remove ? 0 : 1); + break; + case 'S': + if(remove) { + SetSpecialAbility(SPECATK_SUMMON, 0); + StopSpecialAbilityTimer(SPECATK_SUMMON); + } else { + SetSpecialAbility(SPECATK_SUMMON, 1); + } + break; + case 'T': + SetSpecialAbility(SPECATK_TRIPLE, remove ? 0 : 1); + break; + case 'Q': + //quad requires triple to work properly + if(remove) { + SetSpecialAbility(SPECATK_QUAD, 0); + } else { + SetSpecialAbility(SPECATK_TRIPLE, 1); + SetSpecialAbility(SPECATK_QUAD, 1); + } + break; + case 'b': + SetSpecialAbility(SPECATK_BANE, remove ? 0 : 1); + break; + case 'm': + SetSpecialAbility(SPECATK_MAGICAL, remove ? 0 : 1); + break; + case 'U': + SetSpecialAbility(UNSLOWABLE, remove ? 0 : 1); + break; + case 'M': + SetSpecialAbility(UNMEZABLE, remove ? 0 : 1); + break; + case 'C': + SetSpecialAbility(UNCHARMABLE, remove ? 0 : 1); + break; + case 'N': + SetSpecialAbility(UNSTUNABLE, remove ? 0 : 1); + break; + case 'I': + SetSpecialAbility(UNSNAREABLE, remove ? 0 : 1); + break; + case 'D': + SetSpecialAbility(UNFEARABLE, remove ? 0 : 1); + break; + case 'K': + SetSpecialAbility(UNDISPELLABLE, remove ? 0 : 1); + break; + case 'A': + SetSpecialAbility(IMMUNE_MELEE, remove ? 0 : 1); + break; + case 'B': + SetSpecialAbility(IMMUNE_MAGIC, remove ? 0 : 1); + break; + case 'f': + SetSpecialAbility(IMMUNE_FLEEING, remove ? 0 : 1); + break; + case 'O': + SetSpecialAbility(IMMUNE_MELEE_EXCEPT_BANE, remove ? 0 : 1); + break; + case 'W': + SetSpecialAbility(IMMUNE_MELEE_NONMAGICAL, remove ? 0 : 1); + break; + case 'H': + SetSpecialAbility(IMMUNE_AGGRO, remove ? 0 : 1); + break; + case 'G': + SetSpecialAbility(IMMUNE_AGGRO_ON, remove ? 0 : 1); + break; + case 'g': + SetSpecialAbility(IMMUNE_CASTING_FROM_RANGE, remove ? 0 : 1); + break; + case 'd': + SetSpecialAbility(IMMUNE_FEIGN_DEATH, remove ? 0 : 1); + break; + case 'Y': + SetSpecialAbility(SPECATK_RANGED_ATK, remove ? 0 : 1); + break; + case 'L': + SetSpecialAbility(SPECATK_INNATE_DW, remove ? 0 : 1); + break; + case 't': + SetSpecialAbility(NPC_TUNNELVISION, remove ? 0 : 1); + break; + case 'n': + SetSpecialAbility(NPC_NO_BUFFHEAL_FRIENDS, remove ? 0 : 1); + break; + case 'p': + SetSpecialAbility(IMMUNE_PACIFY, remove ? 0 : 1); + break; + case 'J': + SetSpecialAbility(LEASH, remove ? 0 : 1); + break; + case 'j': + SetSpecialAbility(TETHER, remove ? 0 : 1); + break; + case 'o': + SetSpecialAbility(DESTRUCTIBLE_OBJECT, remove ? 0 : 1); + SetDestructibleObject(remove ? true : false); + break; + case 'Z': + SetSpecialAbility(NO_HARM_FROM_CLIENT, remove ? 0 : 1); + break; + case 'i': + SetSpecialAbility(IMMUNE_TAUNT, remove ? 0 : 1); + break; + case 'e': + SetSpecialAbility(ALWAYS_FLEE, remove ? 0 : 1); + break; + case 'h': + SetSpecialAbility(FLEE_PERCENT, remove ? 0 : 1); + break; + + default: + break; + } + parse++; + } + + if(permtag == 1 && this->GetNPCTypeID() > 0) + { + if(database.SetSpecialAttkFlag(this->GetNPCTypeID(), orig_parse)) + { + LogFile->write(EQEMuLog::Normal, "NPCTypeID: %i flagged to '%s' for Special Attacks.\n",this->GetNPCTypeID(),orig_parse); + } + } +} + +bool Mob::HasNPCSpecialAtk(const char* parse) { + + bool HasAllAttacks = true; + + while (*parse && HasAllAttacks == true) + { + switch(*parse) + { + case 'E': + if (!GetSpecialAbility(SPECATK_ENRAGE)) + HasAllAttacks = false; + break; + case 'F': + if (!GetSpecialAbility(SPECATK_FLURRY)) + HasAllAttacks = false; + break; + case 'R': + if (!GetSpecialAbility(SPECATK_RAMPAGE)) + HasAllAttacks = false; + break; + case 'r': + if (!GetSpecialAbility(SPECATK_AREA_RAMPAGE)) + HasAllAttacks = false; + break; + case 'S': + if (!GetSpecialAbility(SPECATK_SUMMON)) + HasAllAttacks = false; + break; + case 'T': + if (!GetSpecialAbility(SPECATK_TRIPLE)) + HasAllAttacks = false; + break; + case 'Q': + if (!GetSpecialAbility(SPECATK_QUAD)) + HasAllAttacks = false; + break; + case 'b': + if (!GetSpecialAbility(SPECATK_BANE)) + HasAllAttacks = false; + break; + case 'm': + if (!GetSpecialAbility(SPECATK_MAGICAL)) + HasAllAttacks = false; + break; + case 'U': + if (!GetSpecialAbility(UNSLOWABLE)) + HasAllAttacks = false; + break; + case 'M': + if (!GetSpecialAbility(UNMEZABLE)) + HasAllAttacks = false; + break; + case 'C': + if (!GetSpecialAbility(UNCHARMABLE)) + HasAllAttacks = false; + break; + case 'N': + if (!GetSpecialAbility(UNSTUNABLE)) + HasAllAttacks = false; + break; + case 'I': + if (!GetSpecialAbility(UNSNAREABLE)) + HasAllAttacks = false; + break; + case 'D': + if (!GetSpecialAbility(UNFEARABLE)) + HasAllAttacks = false; + break; + case 'A': + if (!GetSpecialAbility(IMMUNE_MELEE)) + HasAllAttacks = false; + break; + case 'B': + if (!GetSpecialAbility(IMMUNE_MAGIC)) + HasAllAttacks = false; + break; + case 'f': + if (!GetSpecialAbility(IMMUNE_FLEEING)) + HasAllAttacks = false; + break; + case 'O': + if (!GetSpecialAbility(IMMUNE_MELEE_EXCEPT_BANE)) + HasAllAttacks = false; + break; + case 'W': + if (!GetSpecialAbility(IMMUNE_MELEE_NONMAGICAL)) + HasAllAttacks = false; + break; + case 'H': + if (!GetSpecialAbility(IMMUNE_AGGRO)) + HasAllAttacks = false; + break; + case 'G': + if (!GetSpecialAbility(IMMUNE_AGGRO_ON)) + HasAllAttacks = false; + break; + case 'g': + if (!GetSpecialAbility(IMMUNE_CASTING_FROM_RANGE)) + HasAllAttacks = false; + break; + case 'd': + if (!GetSpecialAbility(IMMUNE_FEIGN_DEATH)) + HasAllAttacks = false; + break; + case 'Y': + if (!GetSpecialAbility(SPECATK_RANGED_ATK)) + HasAllAttacks = false; + break; + case 'L': + if (!GetSpecialAbility(SPECATK_INNATE_DW)) + HasAllAttacks = false; + break; + case 't': + if (!GetSpecialAbility(NPC_TUNNELVISION)) + HasAllAttacks = false; + break; + case 'n': + if (!GetSpecialAbility(NPC_NO_BUFFHEAL_FRIENDS)) + HasAllAttacks = false; + break; + case 'p': + if(!GetSpecialAbility(IMMUNE_PACIFY)) + HasAllAttacks = false; + break; + case 'J': + if(!GetSpecialAbility(LEASH)) + HasAllAttacks = false; + break; + case 'j': + if(!GetSpecialAbility(TETHER)) + HasAllAttacks = false; + break; + case 'o': + if(!GetSpecialAbility(DESTRUCTIBLE_OBJECT)) + { + HasAllAttacks = false; + SetDestructibleObject(false); + } + break; + case 'Z': + if(!GetSpecialAbility(NO_HARM_FROM_CLIENT)){ + HasAllAttacks = false; + } + break; + case 'e': + if(!GetSpecialAbility(ALWAYS_FLEE)) + HasAllAttacks = false; + break; + case 'h': + if(!GetSpecialAbility(FLEE_PERCENT)) + HasAllAttacks = false; + break; + default: + HasAllAttacks = false; + break; + } + parse++; + } + + return HasAllAttacks; +} + +void NPC::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) +{ + Mob::FillSpawnStruct(ns, ForWho); + + //Basic settings to make sure swarm pets work properly. + if (GetSwarmOwner()) { + Client *c = entity_list.GetClientByID(GetSwarmOwner()); + if(c) { + SetAllowBeneficial(1); //Allow client cast swarm pets to be heal/buffed. + //This is a hack to allow CLIENT swarm pets NOT to be targeted with F8. Warning: Will turn name 'Yellow'! + if (RuleB(Pets, SwarmPetNotTargetableWithHotKey)) + ns->spawn.IsMercenary = 1; + } + //NPC cast swarm pets should still be targetable with F8. + else + ns->spawn.IsMercenary = 0; + } + + //Not recommended if using above (However, this will work better on older clients). + if (RuleB(Pets, UnTargetableSwarmPet)) { + if(GetOwnerID() || GetSwarmOwner()) { + ns->spawn.is_pet = 1; + if (!IsCharmed() && GetOwnerID()) { + Client *c = entity_list.GetClientByID(GetOwnerID()); + if(c) + sprintf(ns->spawn.lastName, "%s's Pet", c->GetName()); + } + else if (GetSwarmOwner()) { + ns->spawn.bodytype = 11; + if(!IsCharmed()) + { + Client *c = entity_list.GetClientByID(GetSwarmOwner()); + if(c) + sprintf(ns->spawn.lastName, "%s's Pet", c->GetName()); + } + } + } + } else { + if(GetOwnerID()) { + ns->spawn.is_pet = 1; + if (!IsCharmed() && GetOwnerID()) { + Client *c = entity_list.GetClientByID(GetOwnerID()); + if(c) + sprintf(ns->spawn.lastName, "%s's Pet", c->GetName()); + } + } else + ns->spawn.is_pet = 0; + } + + ns->spawn.is_npc = 1; +} + +void NPC::SetLevel(uint8 in_level, bool command) +{ + if(in_level > level) + SendLevelAppearance(); + level = in_level; + SendAppearancePacket(AT_WhoLevel, in_level); +} + +void NPC::ModifyNPCStat(const char *identifier, const char *newValue) +{ + std::string id = identifier; + std::string val = newValue; + for(int i = 0; i < id.length(); ++i) + { + id[i] = std::tolower(id[i]); + } + + if(id == "ac") + { + AC = atoi(val.c_str()); + return; + } + + if(id == "str") + { + STR = atoi(val.c_str()); + return; + } + + if(id == "sta") + { + STA = atoi(val.c_str()); + return; + } + + if(id == "agi") + { + AGI = atoi(val.c_str()); + return; + } + + if(id == "dex") + { + DEX = atoi(val.c_str()); + return; + } + + if(id == "wis") + { + WIS = atoi(val.c_str()); + CalcMaxMana(); + return; + } + + if(id == "int" || id == "_int") + { + INT = atoi(val.c_str()); + CalcMaxMana(); + return; + } + + if(id == "cha") + { + CHA = atoi(val.c_str()); + return; + } + + if(id == "max_hp") + { + base_hp = atoi(val.c_str()); + CalcMaxHP(); + if(cur_hp > max_hp) + cur_hp = max_hp; + return; + } + + if(id == "max_mana") + { + npc_mana = atoi(val.c_str()); + CalcMaxMana(); + if(cur_mana > max_mana) + cur_mana = max_mana; + return; + } + + if(id == "mr") + { + MR = atoi(val.c_str()); + return; + } + + if(id == "fr") + { + FR = atoi(val.c_str()); + return; + } + + if(id == "cr") + { + CR = atoi(val.c_str()); + return; + } + + if(id == "pr") + { + PR = atoi(val.c_str()); + return; + } + + if(id == "dr") + { + DR = atoi(val.c_str()); + return; + } + + if(id == "PhR") + { + PhR = atoi(val.c_str()); + return; + } + + if(id == "runspeed") + { + runspeed = (float)atof(val.c_str()); + CalcBonuses(); + return; + } + + if(id == "special_attacks") + { + //Added reset flag. + NPCSpecialAttacks(val.c_str(), 0, 1); + return; + } + + if(id == "attack_speed") + { + attack_speed = (float)atof(val.c_str()); + CalcBonuses(); + return; + } + + if(id == "atk") + { + ATK = atoi(val.c_str()); + return; + } + + if(id == "accuracy") + { + accuracy_rating = atoi(val.c_str()); + return; + } + + if(id == "trackable") + { + trackable = atoi(val.c_str()); + return; + } + + if(id == "min_hit") + { + min_dmg = atoi(val.c_str()); + return; + } + + if(id == "max_hit") + { + max_dmg = atoi(val.c_str()); + return; + } + + if(id == "attack_count") + { + attack_count = atoi(val.c_str()); + return; + } + + if(id == "see_invis") + { + see_invis = atoi(val.c_str()); + return; + } + + if(id == "see_invis_undead") + { + see_invis_undead = atoi(val.c_str()); + return; + } + + if(id == "see_hide") + { + see_hide = atoi(val.c_str()); + return; + } + + if(id == "see_improved_hide") + { + see_improved_hide = atoi(val.c_str()); + return; + } + + if(id == "hp_regen") + { + hp_regen = atoi(val.c_str()); + return; + } + + if(id == "mana_regen") + { + mana_regen = atoi(val.c_str()); + return; + } + + if(id == "level") + { + SetLevel(atoi(val.c_str())); + return; + } + + if(id == "aggro") + { + pAggroRange = atof(val.c_str()); + return; + } + + if(id == "assist") + { + pAssistRange = atof(val.c_str()); + return; + } + + if(id == "slow_mitigation") + { + slow_mitigation = atoi(val.c_str()); + return; + } + if(id == "loottable_id") + { + loottable_id = atof(val.c_str()); + return; + } + if(id == "healscale") + { + healscale = atof(val.c_str()); + return; + } + if(id == "spellscale") + { + spellscale = atof(val.c_str()); + return; + } +} + +void NPC::LevelScale() { + + uint8 random_level = (MakeRandomInt(level, maxlevel)); + + float scaling = (((random_level / (float)level) - 1) * (scalerate / 100.0f)); + + // Compensate for scale rates at low levels so they don't add too much + uint8 scale_adjust = 1; + if(level > 0 && level <= 5) + scale_adjust = 10; + if(level > 5 && level <= 10) + scale_adjust = 5; + if(level > 10 && level <= 15) + scale_adjust = 3; + if(level > 15 && level <= 25) + scale_adjust = 2; + + base_hp += (int)(base_hp * scaling); + max_hp += (int)(max_hp * scaling); + cur_hp = max_hp; + STR += (int)(STR * scaling / scale_adjust); + STA += (int)(STA * scaling / scale_adjust); + AGI += (int)(AGI * scaling / scale_adjust); + DEX += (int)(DEX * scaling / scale_adjust); + INT += (int)(INT * scaling / scale_adjust); + WIS += (int)(WIS * scaling / scale_adjust); + CHA += (int)(CHA * scaling / scale_adjust); + if (MR) + MR += (int)(MR * scaling / scale_adjust); + if (CR) + CR += (int)(CR * scaling / scale_adjust); + if (DR) + DR += (int)(DR * scaling / scale_adjust); + if (FR) + FR += (int)(FR * scaling / scale_adjust); + if (PR) + PR += (int)(PR * scaling / scale_adjust); + + if (max_dmg) + { + max_dmg += (int)(max_dmg * scaling / scale_adjust); + min_dmg += (int)(min_dmg * scaling / scale_adjust); + } + + level = random_level; + + return; +} + +void NPC::CalcNPCResists() { + + if (!MR) + MR = (GetLevel() * 11)/10; + if (!CR) + CR = (GetLevel() * 11)/10; + if (!DR) + DR = (GetLevel() * 11)/10; + if (!FR) + FR = (GetLevel() * 11)/10; + if (!PR) + PR = (GetLevel() * 11)/10; + if (!Corrup) + Corrup = 15; + if (!PhR) + PhR = 10; + return; +} + +void NPC::CalcNPCRegen() { + + // Fix for lazy db-updaters (regen values left at 0) + if (GetCasterClass() != 'N' && mana_regen == 0) + mana_regen = (GetLevel() / 10) + 4; + else if(mana_regen < 0) + mana_regen = 0; + else + mana_regen = mana_regen; + + // Gives low end monsters no regen if set to 0 in database. Should make low end monsters killable + // Might want to lower this to /5 rather than 10. + if(hp_regen == 0) + { + if(GetLevel() <= 6) + hp_regen = 1; + else if(GetLevel() > 6 && GetLevel() <= 10) + hp_regen = 2; + else if(GetLevel() > 10 && GetLevel() <= 15) + hp_regen = 3; + else if(GetLevel() > 15 && GetLevel() <= 20) + hp_regen = 5; + else if(GetLevel() > 20 && GetLevel() <= 30) + hp_regen = 7; + else if(GetLevel() > 30 && GetLevel() <= 35) + hp_regen = 9; + else if(GetLevel() > 35 && GetLevel() <= 40) + hp_regen = 12; + else if(GetLevel() > 40 && GetLevel() <= 45) + hp_regen = 18; + else if(GetLevel() > 45 && GetLevel() <= 50) + hp_regen = 21; + else + hp_regen = 30; + } else if(hp_regen < 0) { + hp_regen = 0; + } else + hp_regen = hp_regen; + + return; +} + +void NPC::CalcNPCDamage() { + + int AC_adjust=12; + + if (GetLevel() >= 66) { + if (min_dmg==0) + min_dmg = 220; + if (max_dmg==0) + max_dmg = ((((99000)*(GetLevel()-64))/400)*AC_adjust/10); + } + else if (GetLevel() >= 60 && GetLevel() <= 65){ + if(min_dmg==0) + min_dmg = (GetLevel()+(GetLevel()/3)); + if(max_dmg==0) + max_dmg = (GetLevel()*3)*AC_adjust/10; + } + else if (GetLevel() >= 51 && GetLevel() <= 59){ + if(min_dmg==0) + min_dmg = (GetLevel()+(GetLevel()/3)); + if(max_dmg==0) + max_dmg = (GetLevel()*3)*AC_adjust/10; + } + else if (GetLevel() >= 40 && GetLevel() <= 50) { + if (min_dmg==0) + min_dmg = GetLevel(); + if(max_dmg==0) + max_dmg = (GetLevel()*3)*AC_adjust/10; + } + else if (GetLevel() >= 28 && GetLevel() <= 39) { + if (min_dmg==0) + min_dmg = GetLevel() / 2; + if (max_dmg==0) + max_dmg = ((GetLevel()*2)+2)*AC_adjust/10; + } + else if (GetLevel() <= 27) { + if (min_dmg==0) + min_dmg=1; + if (max_dmg==0) + max_dmg = (GetLevel()*2)*AC_adjust/10; + } + + int clfact = GetClassLevelFactor(); + min_dmg = (min_dmg * clfact) / 220; + max_dmg = (max_dmg * clfact) / 220; + + return; +} + + +uint32 NPC::GetSpawnPointID() const +{ + if(respawn2) + { + return respawn2->GetID(); + } + return 0; +} + +void NPC::NPCSlotTexture(uint8 slot, uint16 texture) +{ + if (slot == 7) { + d_meele_texture1 = texture; + } + else if (slot == 8) { + d_meele_texture2 = texture; + } + else if (slot < 6) { + // Reserved for texturing individual armor slots + } + return; +} + +uint32 NPC::GetSwarmOwner() +{ + if(GetSwarmInfo() != nullptr) + { + return GetSwarmInfo()->owner_id; + } + return 0; +} + +uint32 NPC::GetSwarmTarget() +{ + if(GetSwarmInfo() != nullptr) + { + return GetSwarmInfo()->target; + } + return 0; +} + +void NPC::SetSwarmTarget(int target_id) +{ + if(GetSwarmInfo() != nullptr) + { + GetSwarmInfo()->target = target_id; + } + return; +} + +int32 NPC::CalcMaxMana() { + if(npc_mana == 0) { + switch (GetCasterClass()) { + case 'I': + max_mana = (((GetINT()/2)+1) * GetLevel()) + spellbonuses.Mana + itembonuses.Mana; + break; + case 'W': + max_mana = (((GetWIS()/2)+1) * GetLevel()) + spellbonuses.Mana + itembonuses.Mana; + break; + case 'N': + default: + max_mana = 0; + break; + } + if (max_mana < 0) { + max_mana = 0; + } + + return max_mana; + } else { + switch (GetCasterClass()) { + case 'I': + max_mana = npc_mana + spellbonuses.Mana + itembonuses.Mana; + break; + case 'W': + max_mana = npc_mana + spellbonuses.Mana + itembonuses.Mana; + break; + case 'N': + default: + max_mana = 0; + break; + } + if (max_mana < 0) { + max_mana = 0; + } + + return max_mana; + } +} + +void NPC::SignalNPC(int _signal_id) +{ + signal_q.push_back(_signal_id); +} + +NPC_Emote_Struct* NPC::GetNPCEmote(uint16 emoteid, uint8 event_) { + LinkedListIterator iterator(zone->NPCEmoteList); + iterator.Reset(); + while(iterator.MoreElements()) + { + NPC_Emote_Struct* nes = iterator.GetData(); + if (emoteid == nes->emoteid && event_ == nes->event_) { + return (nes); + } + iterator.Advance(); + } + return (nullptr); +} + +void NPC::DoNPCEmote(uint8 event_, uint16 emoteid) +{ + if(this == nullptr || emoteid == 0) + { + return; + } + + NPC_Emote_Struct* nes = GetNPCEmote(emoteid,event_); + if(nes == nullptr) + { + return; + } + + if(emoteid == nes->emoteid) + { + if(nes->type == 1) + this->Emote("%s",nes->text); + else if(nes->type == 2) + this->Shout("%s",nes->text); + else if(nes->type == 3) + entity_list.MessageClose_StringID(this, true, 200, 10, GENERIC_STRING, nes->text); + else + this->Say("%s",nes->text); + } +} + +bool NPC::CanTalk() +{ + //Races that should be able to talk. (Races up to Titanium) + + uint16 TalkRace[473] = + {1,2,3,4,5,6,7,8,9,10,11,12,0,0,15,16,0,18,19,20,0,0,23,0,25,0,0,0,0,0,0, + 32,0,0,0,0,0,0,39,40,0,0,0,44,0,0,0,0,49,0,51,0,53,54,55,56,57,58,0,0,0, + 62,0,64,65,66,67,0,0,70,71,0,0,0,0,0,77,78,79,0,81,82,0,0,0,86,0,0,0,90, + 0,92,93,94,95,0,0,98,99,0,101,0,103,0,0,0,0,0,0,110,111,112,0,0,0,0,0,0, + 0,0,0,0,123,0,0,126,0,128,0,130,131,0,0,0,0,136,137,0,139,140,0,0,0,144, + 0,0,0,0,0,150,151,152,153,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,183,184,0,0,187,188,189,0,0,0,0,0,195,196,0,198,0,0,0,202,0, + 0,205,0,0,208,0,0,0,0,0,0,0,0,217,0,219,0,0,0,0,0,0,226,0,0,229,230,0,0, + 0,0,235,236,0,238,239,240,241,242,243,244,0,246,247,0,0,0,251,0,0,254,255, + 256,257,0,0,0,0,0,0,0,0,266,267,0,0,270,271,0,0,0,0,0,277,278,0,0,0,0,283, + 284,0,286,0,288,289,290,0,0,0,0,295,296,297,298,299,300,0,0,0,304,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,320,0,322,323,324,325,0,0,0,0,330,331,332,333,334,335, + 336,337,338,339,340,341,342,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,359,360,361,362, + 0,364,365,366,0,368,369,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,385,386,0,0,0,0,0,392, + 393,394,395,396,397,398,0,400,402,0,0,0,0,406,0,408,0,0,411,0,413,0,0,0,417, + 0,0,420,0,0,0,0,425,0,0,0,0,0,0,0,433,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,458,0,0,0,0,0,0,0,0,467,0,0,470,0,0,473}; + + int talk_check = TalkRace[GetRace() - 1]; + + if (TalkRace[GetRace() - 1] > 0) + return true; + + return false; +} + +//this is called with 'this' as the mob being looked at, and +//iOther the mob who is doing the looking. It should figure out +//what iOther thinks about 'this' +FACTION_VALUE NPC::GetReverseFactionCon(Mob* iOther) { + iOther = iOther->GetOwnerOrSelf(); + int primaryFaction= iOther->GetPrimaryFaction(); + + //I am pretty sure that this special faction call is backwards + //and should be iOther->GetSpecialFactionCon(this) + if (primaryFaction < 0) + return GetSpecialFactionCon(iOther); + + if (primaryFaction == 0) + return FACTION_INDIFFERENT; + + //if we are a pet, use our owner's faction stuff + Mob *own = GetOwner(); + if (own != nullptr) + return own->GetReverseFactionCon(iOther); + + //make sure iOther is an npc + //also, if we dont have a faction, then they arnt gunna think anything of us either + if(!iOther->IsNPC() || GetPrimaryFaction() == 0) + return(FACTION_INDIFFERENT); + + //if we get here, iOther is an NPC too + + //otherwise, employ the npc faction stuff + //so we need to look at iOther's faction table to see + //what iOther thinks about our primary faction + return(iOther->CastToNPC()->CheckNPCFactionAlly(GetPrimaryFaction())); +} + +//Look through our faction list and return a faction con based +//on the npc_value for the other person's primary faction in our list. +FACTION_VALUE NPC::CheckNPCFactionAlly(int32 other_faction) { + std::list::iterator cur,end; + cur = faction_list.begin(); + end = faction_list.end(); + for(; cur != end; ++cur) { + struct NPCFaction* fac = *cur; + if ((int32)fac->factionID == other_faction) { + if (fac->npc_value > 0) + return FACTION_ALLY; + else if (fac->npc_value < 0) + return FACTION_SCOWLS; + else + return FACTION_INDIFFERENT; + } + } + return FACTION_INDIFFERENT; +} + +bool NPC::IsFactionListAlly(uint32 other_faction) { + return(CheckNPCFactionAlly(other_faction) == FACTION_ALLY); +} + +int NPC::GetScore() +{ + int lv = std::min(70, (int)GetLevel()); + int basedmg = (lv*2)*(1+(lv / 100)) - (lv / 2); + int minx = 0; + int basehp = 0; + int hpcontrib = 0; + int dmgcontrib = 0; + int spccontrib = 0; + int hp = GetMaxHP(); + int mindmg = min_dmg; + int maxdmg = max_dmg; + int final; + + if(lv < 46) + { + minx = static_cast (ceil( ((lv - (lv / 10.0)) - 1.0) )); + basehp = (lv * 10) + (lv * lv); + } + else + { + minx = static_cast (ceil( ((lv - (lv / 10.0)) - 1.0) - (( lv - 45.0 ) / 2.0) )); + basehp = (lv * 10) + ((lv * lv) * 4); + } + + if(hp > basehp) + { + hpcontrib = static_cast (((hp / static_cast (basehp)) * 1.5)); + if(hpcontrib > 5) { hpcontrib = 5; } + + if(maxdmg > basedmg) + { + dmgcontrib = static_cast (ceil( ((maxdmg / basedmg) * 1.5) )); + } + + if(HasNPCSpecialAtk("E")) { spccontrib++; } //Enrage + if(HasNPCSpecialAtk("F")) { spccontrib++; } //Flurry + if(HasNPCSpecialAtk("R")) { spccontrib++; } //Rampage + if(HasNPCSpecialAtk("r")) { spccontrib++; } //Area Rampage + if(HasNPCSpecialAtk("S")) { spccontrib++; } //Summon + if(HasNPCSpecialAtk("T")) { spccontrib += 2; } //Triple + if(HasNPCSpecialAtk("Q")) { spccontrib += 3; } //Quad + if(HasNPCSpecialAtk("U")) { spccontrib += 5; } //Unslowable + if(HasNPCSpecialAtk("L")) { spccontrib++; } //Innate Dual Wield + } + + if(npc_spells_id > 12) + { + if(lv < 16) + spccontrib++; + else + spccontrib += static_cast (floor(lv/15.0)); + } + + final = minx + hpcontrib + dmgcontrib + spccontrib; + final = std::max(1, final); + final = std::min(100, final); + return(final); +} + +uint32 NPC::GetSpawnKillCount() +{ + uint32 sid = GetSpawnPointID(); + + if(sid > 0) + { + return(zone->GetSpawnKillCount(sid)); + } + + return(0); +} + +void NPC::DoQuestPause(Mob *other) { + if(IsMoving() && !IsOnHatelist(other)) { + PauseWandering(RuleI(NPC, SayPauseTimeInSec)); + FaceTarget(other); + } else if(!IsMoving()) { + FaceTarget(other); + } + +} diff --git a/zone/perl_npcX.cpp b/zone/perl_npcX.cpp new file mode 100644 index 000000000..957baf7ab --- /dev/null +++ b/zone/perl_npcX.cpp @@ -0,0 +1,2487 @@ +/* +* This file was generated automatically by xsubpp version 1.9508 from the +* contents of tmp. Do not edit this file, edit tmp instead. +* +* ANY CHANGES MADE HERE WILL BE LOST! +* +*/ + + +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2004 EQEMu Development Team (http://eqemulator.net) + + 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/features.h" +#ifdef EMBPERL_XS_CLASSES +#include "../common/debug.h" +#include "embperl.h" + +#ifdef seed +#undef seed +#endif + +typedef const char Const_char; + +#include "npc.h" + +#ifdef THIS /* this macro seems to leak out on some systems */ +#undef THIS +#endif + + +XS(XS_NPC_SignalNPC); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_SignalNPC) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::SignalNPC(THIS, _signal_id)"); + { + NPC * THIS; + int _signal_id = (int)SvIV(ST(1)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->SignalNPC(_signal_id); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_CheckNPCFactionAlly); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_CheckNPCFactionAlly) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::CheckNPCFactionAlly(THIS, other_faction)"); + { + NPC * THIS; + FACTION_VALUE RETVAL; + dXSTARG; + int32 other_faction = (int32)SvIV(ST(1)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->CheckNPCFactionAlly(other_faction); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_AddItem); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_AddItem) +{ + dXSARGS; + if (items < 2 || items > 4) + Perl_croak(aTHX_ "Usage: NPC::AddItem(THIS, itemid, charges = 0, equipitem = true)"); + { + NPC * THIS; + uint32 itemid = (uint32)SvUV(ST(1)); + uint16 charges = 0; + bool equipitem = true; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + if (items > 2) + charges = (uint16)SvUV(ST(2)); + if (items > 3) + equipitem = (bool)SvTRUE(ST(3)); + + THIS->AddItem(itemid, charges, equipitem); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_AddLootTable); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_AddLootTable) +{ + dXSARGS; + if (items < 1) + Perl_croak(aTHX_ "Usage: NPC::AddLootTable(THIS, [loottable_id])"); + { + NPC * THIS; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + uint32 loottable_id = 0; + + if(items > 1) + { + loottable_id = (uint32)SvUV(ST(1)); + THIS->AddLootTable(loottable_id); + } + else + { + THIS->AddLootTable(); + } + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_RemoveItem); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_RemoveItem) +{ + dXSARGS; + if (items < 2 || items > 4) + Perl_croak(aTHX_ "Usage: NPC::RemoveItem(THIS, item_id, quantity= 0, slot= 0)"); + { + NPC * THIS; + uint32 item_id = (uint32)SvUV(ST(1)); + uint16 quantity; + uint16 slot; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + if (items < 3) + quantity = 0; + else { + quantity = (uint16)SvUV(ST(2)); + } + + if (items < 4) + slot = 0; + else { + slot = (uint16)SvUV(ST(3)); + } + + THIS->RemoveItem(item_id, quantity, slot); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_ClearItemList); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_ClearItemList) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::ClearItemList(THIS)"); + { + NPC * THIS; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->ClearItemList(); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_AddCash); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_AddCash) +{ + dXSARGS; + if (items != 5) + Perl_croak(aTHX_ "Usage: NPC::AddCash(THIS, in_copper, in_silver, in_gold, in_platinum)"); + { + NPC * THIS; + uint16 in_copper = (uint16)SvUV(ST(1)); + uint16 in_silver = (uint16)SvUV(ST(2)); + uint16 in_gold = (uint16)SvUV(ST(3)); + uint16 in_platinum = (uint16)SvUV(ST(4)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->AddCash(in_copper, in_silver, in_gold, in_platinum); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_RemoveCash); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_RemoveCash) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::RemoveCash(THIS)"); + { + NPC * THIS; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->RemoveCash(); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_CountLoot); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_CountLoot) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::CountLoot(THIS)"); + { + NPC * THIS; + uint32 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->CountLoot(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetLoottableID); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetLoottableID) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetLoottableID(THIS)"); + { + NPC * THIS; + uint32 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetLoottableID(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetCopper); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetCopper) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetCopper(THIS)"); + { + NPC * THIS; + uint32 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetCopper(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetSilver); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetSilver) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetSilver(THIS)"); + { + NPC * THIS; + uint32 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetSilver(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetGold); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetGold) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetGold(THIS)"); + { + NPC * THIS; + uint32 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetGold(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetPlatinum); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetPlatinum) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetPlatinum(THIS)"); + { + NPC * THIS; + uint32 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetPlatinum(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_SetCopper); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_SetCopper) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::SetCopper(THIS, amt)"); + { + NPC * THIS; + uint32 amt = (uint32)SvUV(ST(1)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->SetCopper(amt); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_SetSilver); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_SetSilver) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::SetSilver(THIS, amt)"); + { + NPC * THIS; + uint32 amt = (uint32)SvUV(ST(1)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->SetSilver(amt); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_SetGold); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_SetGold) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::SetGold(THIS, amt)"); + { + NPC * THIS; + uint32 amt = (uint32)SvUV(ST(1)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->SetGold(amt); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_SetPlatinum); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_SetPlatinum) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::SetPlatinum(THIS, amt)"); + { + NPC * THIS; + uint32 amt = (uint32)SvUV(ST(1)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->SetPlatinum(amt); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_SetGrid); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_SetGrid) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::SetGrid(THIS, grid_)"); + { + NPC * THIS; + int32 grid_ = (int32)SvIV(ST(1)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->SetGrid(grid_); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_SetSaveWaypoint); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_SetSaveWaypoint) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::SetSaveWaypoint(THIS, waypoint)"); + { + NPC * THIS; + uint16 waypoint = (uint16)SvUV(ST(1)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->SetSaveWaypoint(waypoint); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_SetSp2); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_SetSp2) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::SetSp2(THIS, sg2)"); + { + NPC * THIS; + uint32 sg2 = (uint32)SvUV(ST(1)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->SetSp2(sg2); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_GetWaypointMax); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetWaypointMax) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetWaypointMax(THIS)"); + { + NPC * THIS; + uint16 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetWaypointMax(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetGrid); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetGrid) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetGrid(THIS)"); + { + NPC * THIS; + int32 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetGrid(); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetSp2); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetSp2) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetSp2(THIS)"); + { + NPC * THIS; + uint32 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetSp2(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetNPCFactionID); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetNPCFactionID) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetNPCFactionID(THIS)"); + { + NPC * THIS; + int32 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetNPCFactionID(); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetPrimaryFaction); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetPrimaryFaction) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetPrimaryFaction(THIS)"); + { + NPC * THIS; + int32 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetPrimaryFaction(); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetNPCHate); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetNPCHate) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::GetNPCHate(THIS, in_ent)"); + { + NPC * THIS; + int32 RETVAL; + dXSTARG; + Mob* in_ent; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + if (sv_derived_from(ST(1), "Mob")) { + IV tmp = SvIV((SV*)SvRV(ST(1))); + in_ent = INT2PTR(Mob *,tmp); + } + else + Perl_croak(aTHX_ "in_ent is not of type Mob"); + if(in_ent == nullptr) + Perl_croak(aTHX_ "in_ent is nullptr, avoiding crash."); + + RETVAL = THIS->GetNPCHate(in_ent); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_IsOnHatelist); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_IsOnHatelist) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::IsOnHatelist(THIS, p)"); + { + NPC * THIS; + bool RETVAL; + Mob* p; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + if (sv_derived_from(ST(1), "Mob")) { + IV tmp = SvIV((SV*)SvRV(ST(1))); + p = INT2PTR(Mob *,tmp); + } + else + Perl_croak(aTHX_ "p is not of type Mob"); + if(p == nullptr) + Perl_croak(aTHX_ "p is nullptr, avoiding crash."); + + RETVAL = THIS->IsOnHatelist(p); + ST(0) = boolSV(RETVAL); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_NPC_SetNPCFactionID); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_SetNPCFactionID) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::SetNPCFactionID(THIS, in)"); + { + NPC * THIS; + int32 in = (int32)SvIV(ST(1)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->SetNPCFactionID(in); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_GetMaxDMG); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetMaxDMG) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetMaxDMG(THIS)"); + { + NPC * THIS; + uint32 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetMaxDMG(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetMinDMG); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetMinDMG) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetMinDMG(THIS)"); + { + NPC * THIS; + uint32 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetMinDMG(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + + +XS(XS_NPC_IsAnimal); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_IsAnimal) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::IsAnimal(THIS)"); + { + NPC * THIS; + bool RETVAL; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->IsAnimal(); + ST(0) = boolSV(RETVAL); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_NPC_GetPetSpellID); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetPetSpellID) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetPetSpellID(THIS)"); + { + NPC * THIS; + uint16 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetPetSpellID(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_SetPetSpellID); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_SetPetSpellID) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::SetPetSpellID(THIS, amt)"); + { + NPC * THIS; + uint16 amt = (uint16)SvUV(ST(1)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->SetPetSpellID(amt); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_GetMaxDamage); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetMaxDamage) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::GetMaxDamage(THIS, tlevel)"); + { + NPC * THIS; + uint32 RETVAL; + dXSTARG; + uint8 tlevel = (uint8)SvUV(ST(1)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetMaxDamage(tlevel); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_SetTaunting); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_SetTaunting) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::SetTaunting(THIS, tog)"); + { + NPC * THIS; + bool tog = (bool)SvTRUE(ST(1)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->SetTaunting(tog); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_PickPocket); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_PickPocket) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::PickPocket(THIS, thief)"); + { + NPC * THIS; + Client* thief; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + if (sv_derived_from(ST(1), "Client")) { + IV tmp = SvIV((SV*)SvRV(ST(1))); + thief = INT2PTR(Client *,tmp); + } + else + Perl_croak(aTHX_ "thief is not of type Client"); + if(thief == nullptr) + Perl_croak(aTHX_ "thief is nullptr, avoiding crash."); + + THIS->PickPocket(thief); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_StartSwarmTimer); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_StartSwarmTimer) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::StartSwarmTimer(THIS, duration)"); + { + NPC * THIS; + uint32 duration = (uint32)SvUV(ST(1)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->StartSwarmTimer(duration); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_DoClassAttacks); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_DoClassAttacks) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::DoClassAttacks(THIS, target)"); + { + NPC * THIS; + Mob * target; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + if (sv_derived_from(ST(1), "Mob")) { + IV tmp = SvIV((SV*)SvRV(ST(1))); + target = INT2PTR(Mob *,tmp); + } + else + Perl_croak(aTHX_ "target is not of type Mob"); + if(target == nullptr) + Perl_croak(aTHX_ "target is nullptr, avoiding crash."); + + THIS->DoClassAttacks(target); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_GetMaxWp); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetMaxWp) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetMaxWp(THIS)"); + { + NPC * THIS; + int RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetMaxWp(); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_DisplayWaypointInfo); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_DisplayWaypointInfo) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::DisplayWaypointInfo(THIS, to)"); + { + NPC * THIS; + Client * to; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + if (sv_derived_from(ST(1), "Client")) { + IV tmp = SvIV((SV*)SvRV(ST(1))); + to = INT2PTR(Client *,tmp); + } + else + Perl_croak(aTHX_ "to is not of type Client"); + if(to == nullptr) + Perl_croak(aTHX_ "to is nullptr, avoiding crash."); + + THIS->DisplayWaypointInfo(to); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_CalculateNewWaypoint); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_CalculateNewWaypoint) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::CalculateNewWaypoint(THIS)"); + { + NPC * THIS; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->CalculateNewWaypoint(); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_AssignWaypoints); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_AssignWaypoints) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::AssignWaypoints(THIS, grid)"); + { + NPC * THIS; + uint32 grid = (uint32)SvUV(ST(1)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->AssignWaypoints(grid); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_SetWaypointPause); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_SetWaypointPause) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::SetWaypointPause(THIS)"); + { + NPC * THIS; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->SetWaypointPause(); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_UpdateWaypoint); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_UpdateWaypoint) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::UpdateWaypoint(THIS, wp_index)"); + { + NPC * THIS; + int wp_index = (int)SvIV(ST(1)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->UpdateWaypoint(wp_index); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_StopWandering); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_StopWandering) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::StopWandering(THIS)"); + { + NPC * THIS; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->StopWandering(); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_ResumeWandering); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_ResumeWandering) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::ResumeWandering(THIS)"); + { + NPC * THIS; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->ResumeWandering(); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_PauseWandering); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_PauseWandering) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::PauseWandering(THIS, pausetime)"); + { + NPC * THIS; + int pausetime = (int)SvIV(ST(1)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->PauseWandering(pausetime); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_MoveTo); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_MoveTo) +{ + dXSARGS; + if (items != 4 && items != 5 && items != 6) + Perl_croak(aTHX_ "Usage: NPC::MoveTo(THIS, mtx, mty, mtz, [mth, saveguard?])"); + { + NPC * THIS; + float mtx = (float)SvNV(ST(1)); + float mty = (float)SvNV(ST(2)); + float mtz = (float)SvNV(ST(3)); + float mth; + bool saveguard; + + if(items > 4) + mth = (float)SvNV(ST(4)); + else + mth = 0; + + if(items > 5) + saveguard = (bool)SvTRUE(ST(5)); + else + saveguard = false; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->MoveTo(mtx, mty, mtz, mth, saveguard); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_NextGuardPosition); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_NextGuardPosition) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::NextGuardPosition(THIS)"); + { + NPC * THIS; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->NextGuardPosition(); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_SaveGuardSpot); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_SaveGuardSpot) +{ + dXSARGS; + if (items < 1 || items > 2) + Perl_croak(aTHX_ "Usage: NPC::SaveGuardSpot(THIS, iClearGuardSpot= false)"); + { + NPC * THIS; + bool iClearGuardSpot; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + if (items < 2) + iClearGuardSpot = false; + else { + iClearGuardSpot = (bool)SvTRUE(ST(1)); + } + + THIS->SaveGuardSpot(iClearGuardSpot); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_IsGuarding); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_IsGuarding) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::IsGuarding(THIS)"); + { + NPC * THIS; + bool RETVAL; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->IsGuarding(); + ST(0) = boolSV(RETVAL); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + +XS(XS_NPC_AI_SetRoambox); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_AI_SetRoambox) +{ + dXSARGS; + if (items < 6 || items > 8) + Perl_croak(aTHX_ "Usage: NPC::AI_SetRoambox(THIS, iDist, iMaxX, iMinX, iMaxY, iMinY, iDelay= 2500, iMinDelay= 2500)"); + { + NPC * THIS; + float iDist = (float)SvNV(ST(1)); + float iMaxX = (float)SvNV(ST(2)); + float iMinX = (float)SvNV(ST(3)); + float iMaxY = (float)SvNV(ST(4)); + float iMinY = (float)SvNV(ST(5)); + uint32 iDelay; + uint32 iMinDelay; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + if (items < 7){ + iMinDelay = 2500; + iDelay = 2500; + } + else if (items < 8){ + iMinDelay = 2500; + iDelay = (uint32)SvUV(ST(6)); + } + else { + iDelay = (uint32)SvUV(ST(6)); + iMinDelay = (uint32)SvUV(ST(7)); + } + + THIS->AI_SetRoambox(iDist, iMaxX, iMinX, iMaxY, iMinY, iDelay, iMinDelay); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_GetNPCSpellsID); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetNPCSpellsID) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetNPCSpellsID(THIS)"); + { + NPC * THIS; + uint32 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetNPCSpellsID(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetSpawnPointID); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetSpawnPointID) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetSpawnPointID(THIS)"); + { + NPC * THIS; + uint32 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetSpawnPointID(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetSpawnPointX); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetSpawnPointX) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetSpawnPointX(THIS)"); + { + NPC * THIS; + float RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + + RETVAL = THIS->GetSpawnPointX(); + XSprePUSH; PUSHn((double)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetSpawnPointY); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetSpawnPointY) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetSpawnPointY(THIS)"); + { + NPC * THIS; + float RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + + RETVAL = THIS->GetSpawnPointY(); + XSprePUSH; PUSHn((double)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetSpawnPointZ); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetSpawnPointZ) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetSpawnPointZ(THIS)"); + { + NPC * THIS; + float RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + + RETVAL = THIS->GetSpawnPointZ(); + XSprePUSH; PUSHn((double)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetSpawnPointH); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetSpawnPointH) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetSpawnPointH(THIS)"); + { + NPC * THIS; + float RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + + RETVAL = THIS->GetSpawnPointH(); + XSprePUSH; PUSHn((double)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetGuardPointX); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetGuardPointX) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetGuardPointX(THIS)"); + { + NPC * THIS; + float RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + + RETVAL = THIS->GetGuardPointX(); + XSprePUSH; PUSHn((double)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetGuardPointY); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetGuardPointY) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetGuardPointY(THIS)"); + { + NPC * THIS; + float RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + + RETVAL = THIS->GetGuardPointY(); + XSprePUSH; PUSHn((double)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetGuardPointZ); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetGuardPointZ) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetGuardPointZ(THIS)"); + { + NPC * THIS; + float RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + + RETVAL = THIS->GetGuardPointZ(); + XSprePUSH; PUSHn((double)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_SetPrimSkill); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_SetPrimSkill) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::SetPrimSkill(THIS, skill_id)"); + { + NPC * THIS; + int skill_id = (int)SvIV(ST(1)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->SetPrimSkill(skill_id); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_SetSecSkill); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_SetSecSkill) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::SetSecSkill(THIS, skill_id)"); + { + NPC * THIS; + int skill_id = (int)SvIV(ST(1)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->SetSecSkill(skill_id); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_GetPrimSkill); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetPrimSkill) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetPrimSkill(THIS)"); + { + NPC * THIS; + uint32 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetPrimSkill(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetSecSkill); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetSecSkill) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetSecSkill(THIS)"); + { + NPC * THIS; + uint32 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetSecSkill(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetSwarmOwner); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetSwarmOwner) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetSwarmOwner(THIS)"); + { + NPC * THIS; + uint32 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetSwarmOwner(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetSwarmTarget); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetSwarmTarget) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetSwarmTarget(THIS)"); + { + NPC * THIS; + uint32 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetSwarmTarget(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_SetSwarmTarget); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_SetSwarmTarget) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::SetSwarmTarget(THIS, target_id)"); + { + NPC * THIS; + int target_id = (int)SvIV(ST(1)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->SetSwarmTarget(target_id); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_ModifyNPCStat); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_ModifyNPCStat) +{ + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: NPC::ModifyNPCStat(THIS, identifier, newValue)"); + { + NPC * THIS; + Const_char * identifier = (Const_char *)SvPV_nolen(ST(1)); + Const_char * newValue = (Const_char *)SvPV_nolen(ST(2)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->ModifyNPCStat(identifier, newValue); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_AddSpellToNPCList); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_AddSpellToNPCList) +{ + dXSARGS; + if (items != 7) + Perl_croak(aTHX_ "Usage: NPC::AddAISpell(THIS, priority, spell_id, type, mana_cost, recast_delay, resist_adjust)"); + { + NPC * THIS; + int priority = (int)SvIV(ST(1)); + int spell_id = (int)SvIV(ST(2)); + int type = (int)SvIV(ST(3)); + int mana_cost = (int)SvIV(ST(4)); + int recast_delay = (int)SvIV(ST(5)); + int resist_adjust = (int)SvIV(ST(6)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->AddSpellToNPCList(priority, spell_id, type, mana_cost, recast_delay, resist_adjust); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_RemoveSpellFromNPCList); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_RemoveSpellFromNPCList) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::RemoveAISpell(THIS, spell_id)"); + { + NPC * THIS; + int spell_id = (int)SvIV(ST(1)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->RemoveSpellFromNPCList(spell_id); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_SetSpellFocusDMG); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_SetSpellFocusDMG) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::SetSpellFocusDMG(THIS, NewSpellFocusDMG)"); + { + NPC * THIS; + int32 NewSpellFocusDMG = (int32)SvIV(ST(1)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->SetSpellFocusDMG(NewSpellFocusDMG); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_GetSpellFocusDMG); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetSpellFocusDMG) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetSpellFocusDMG(THIS)"); + { + NPC * THIS; + int32 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetSpellFocusDMG(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_SetSpellFocusHeal); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_SetSpellFocusHeal) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::SetSpellFocusHeal(THIS, NewSpellFocusHeal)"); + { + NPC * THIS; + int32 NewSpellFocusHeal = (int32)SvIV(ST(1)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->SetSpellFocusHeal(NewSpellFocusHeal); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_GetSpellFocusHeal); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetSpellFocusHeal) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetSpellFocusHeal(THIS)"); + { + NPC * THIS; + int32 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetSpellFocusHeal(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetSlowMitigation); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetSlowMitigation) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetSlowMitigation(THIS)"); + { + NPC * THIS; + int16 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetSlowMitigation(); + XSprePUSH; PUSHn((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetAttackSpeed); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetAttackSpeed) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetAttackSpeed(THIS)"); + { + NPC * THIS; + float RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetAttackSpeed(); + XSprePUSH; PUSHn((double)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetAttackDelay); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetAttackDelay) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetAttackDelay(THIS)"); + { + NPC * THIS; + float RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetAttackDelay(); + XSprePUSH; PUSHn((double)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetAccuracyRating); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetAccuracyRating) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetAccuracyRating(THIS)"); + { + NPC * THIS; + int32 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetAccuracyRating(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetAvoidanceRating); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetAvoidanceRating) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetAvoidanceyRating(THIS)"); + { + NPC * THIS; + int32 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetAvoidanceRating(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetSpawnKillCount); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetSpawnKillCount) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetSpawnKillCount(THIS)"); + { + NPC * THIS; + uint32 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == NULL) + Perl_croak(aTHX_ "THIS is NULL, avoiding crash."); + + RETVAL = THIS->GetSpawnKillCount(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_GetScore); /* prototype to pass -Wmissing-prototypes */ +XS(XS_NPC_GetScore) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetScore(THIS)"); + { + NPC * THIS; + int RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == NULL) + Perl_croak(aTHX_ "THIS is NULL, avoiding crash."); + + RETVAL = THIS->GetScore(); + XSprePUSH; PUSHi((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_SetMerchantProbability); +XS(XS_NPC_SetMerchantProbability) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: NPC::SetMerchantProbability(THIS, Probability)"); + { + NPC *THIS; + uint8 Probability = (uint8)SvIV(ST(1)); + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->SetMerchantProbability(Probability); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_GetMerchantProbability); +XS(XS_NPC_GetMerchantProbability) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: NPC::GetMerchantProbability(THIS)"); + { + NPC *THIS; + uint8 RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == NULL) + Perl_croak(aTHX_ "THIS is NULL, avoiding crash."); + + RETVAL = THIS->GetMerchantProbability(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_NPC_AddMeleeProc); +XS(XS_NPC_AddMeleeProc) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: NPC::AddMeleeProc(THIS,spellid,chance)"); + { + NPC * THIS; + int spell_id = (int)SvIV(ST(1)); + int chance = (int)SvIV(ST(2)); + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == NULL) + Perl_croak(aTHX_ "THIS is NULL, avoiding crash."); + + THIS->AddProcToWeapon(spell_id, true, chance); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_AddRangedProc); +XS(XS_NPC_AddRangedProc) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: NPC::AddRangedProc(THIS,spellid,chance)"); + { + NPC * THIS; + int spell_id = (int)SvIV(ST(1)); + int chance = (int)SvIV(ST(2)); + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == NULL) + Perl_croak(aTHX_ "THIS is NULL, avoiding crash."); + + THIS->AddDefensiveProc(spell_id,chance); + } + XSRETURN_EMPTY; +} + +XS(XS_NPC_AddDefensiveProc); +XS(XS_NPC_AddDefensiveProc) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: NPC::AddDefensiveProc(THIS,spellid,chance)"); + { + NPC * THIS; + int spell_id = (int)SvIV(ST(1)); + int chance = (int)SvIV(ST(2)); + dXSTARG; + + if (sv_derived_from(ST(0), "NPC")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(NPC *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type NPC"); + if(THIS == NULL) + Perl_croak(aTHX_ "THIS is NULL, avoiding crash."); + + THIS->AddProcToWeapon(spell_id, true, chance); + } + XSRETURN_EMPTY; +} + + +#ifdef __cplusplus +extern "C" +#endif +XS(boot_NPC); /* prototype to pass -Wmissing-prototypes */ +XS(boot_NPC) +{ + dXSARGS; + char file[256]; + strncpy(file, __FILE__, 256); + file[255] = 0; + + if(items != 1) + fprintf(stderr, "boot_quest does not take any arguments."); + char buf[128]; + + //add the strcpy stuff to get rid of const warnings.... + + XS_VERSION_BOOTCHECK ; + + newXSproto(strcpy(buf, "SignalNPC"), XS_NPC_SignalNPC, file, "$$"); + newXSproto(strcpy(buf, "CheckNPCFactionAlly"), XS_NPC_CheckNPCFactionAlly, file, "$$"); + newXSproto(strcpy(buf, "AddItem"), XS_NPC_AddItem, file, "$$;$$"); + newXSproto(strcpy(buf, "AddLootTable"), XS_NPC_AddLootTable, file, "$"); + newXSproto(strcpy(buf, "RemoveItem"), XS_NPC_RemoveItem, file, "$$;$$"); + newXSproto(strcpy(buf, "ClearItemList"), XS_NPC_ClearItemList, file, "$"); + newXSproto(strcpy(buf, "AddCash"), XS_NPC_AddCash, file, "$$$$$"); + newXSproto(strcpy(buf, "RemoveCash"), XS_NPC_RemoveCash, file, "$"); + newXSproto(strcpy(buf, "CountLoot"), XS_NPC_CountLoot, file, "$"); + newXSproto(strcpy(buf, "GetLoottableID"), XS_NPC_GetLoottableID, file, "$"); + newXSproto(strcpy(buf, "GetCopper"), XS_NPC_GetCopper, file, "$"); + newXSproto(strcpy(buf, "GetSilver"), XS_NPC_GetSilver, file, "$"); + newXSproto(strcpy(buf, "GetGold"), XS_NPC_GetGold, file, "$"); + newXSproto(strcpy(buf, "GetPlatinum"), XS_NPC_GetPlatinum, file, "$"); + newXSproto(strcpy(buf, "SetCopper"), XS_NPC_SetCopper, file, "$$"); + newXSproto(strcpy(buf, "SetSilver"), XS_NPC_SetSilver, file, "$$"); + newXSproto(strcpy(buf, "SetGold"), XS_NPC_SetGold, file, "$$"); + newXSproto(strcpy(buf, "SetPlatinum"), XS_NPC_SetPlatinum, file, "$$"); + newXSproto(strcpy(buf, "SetGrid"), XS_NPC_SetGrid, file, "$$"); + newXSproto(strcpy(buf, "SetSaveWaypoint"), XS_NPC_SetSaveWaypoint, file, "$$"); + newXSproto(strcpy(buf, "SetSp2"), XS_NPC_SetSp2, file, "$$"); + newXSproto(strcpy(buf, "GetWaypointMax"), XS_NPC_GetWaypointMax, file, "$"); + newXSproto(strcpy(buf, "GetGrid"), XS_NPC_GetGrid, file, "$"); + newXSproto(strcpy(buf, "GetSp2"), XS_NPC_GetSp2, file, "$"); + newXSproto(strcpy(buf, "GetNPCFactionID"), XS_NPC_GetNPCFactionID, file, "$"); + newXSproto(strcpy(buf, "GetPrimaryFaction"), XS_NPC_GetPrimaryFaction, file, "$"); + newXSproto(strcpy(buf, "GetNPCHate"), XS_NPC_GetNPCHate, file, "$$"); + newXSproto(strcpy(buf, "IsOnHatelist"), XS_NPC_IsOnHatelist, file, "$$"); + newXSproto(strcpy(buf, "SetNPCFactionID"), XS_NPC_SetNPCFactionID, file, "$$"); + newXSproto(strcpy(buf, "GetMaxDMG"), XS_NPC_GetMaxDMG, file, "$"); + newXSproto(strcpy(buf, "GetMinDMG"), XS_NPC_GetMinDMG, file, "$"); + newXSproto(strcpy(buf, "IsAnimal"), XS_NPC_IsAnimal, file, "$"); + newXSproto(strcpy(buf, "GetPetSpellID"), XS_NPC_GetPetSpellID, file, "$"); + newXSproto(strcpy(buf, "SetPetSpellID"), XS_NPC_SetPetSpellID, file, "$$"); + newXSproto(strcpy(buf, "GetMaxDamage"), XS_NPC_GetMaxDamage, file, "$$"); + newXSproto(strcpy(buf, "SetTaunting"), XS_NPC_SetTaunting, file, "$$"); + newXSproto(strcpy(buf, "PickPocket"), XS_NPC_PickPocket, file, "$$"); + newXSproto(strcpy(buf, "StartSwarmTimer"), XS_NPC_StartSwarmTimer, file, "$$"); + newXSproto(strcpy(buf, "DoClassAttacks"), XS_NPC_DoClassAttacks, file, "$$"); + newXSproto(strcpy(buf, "GetMaxWp"), XS_NPC_GetMaxWp, file, "$"); + newXSproto(strcpy(buf, "DisplayWaypointInfo"), XS_NPC_DisplayWaypointInfo, file, "$$"); + newXSproto(strcpy(buf, "CalculateNewWaypoint"), XS_NPC_CalculateNewWaypoint, file, "$"); + newXSproto(strcpy(buf, "AssignWaypoints"), XS_NPC_AssignWaypoints, file, "$$"); + newXSproto(strcpy(buf, "SetWaypointPause"), XS_NPC_SetWaypointPause, file, "$"); + newXSproto(strcpy(buf, "UpdateWaypoint"), XS_NPC_UpdateWaypoint, file, "$$"); + newXSproto(strcpy(buf, "StopWandering"), XS_NPC_StopWandering, file, "$"); + newXSproto(strcpy(buf, "ResumeWandering"), XS_NPC_ResumeWandering, file, "$"); + newXSproto(strcpy(buf, "PauseWandering"), XS_NPC_PauseWandering, file, "$$"); + newXSproto(strcpy(buf, "MoveTo"), XS_NPC_MoveTo, file, "$$$$"); + newXSproto(strcpy(buf, "NextGuardPosition"), XS_NPC_NextGuardPosition, file, "$"); + newXSproto(strcpy(buf, "SaveGuardSpot"), XS_NPC_SaveGuardSpot, file, "$;$"); + newXSproto(strcpy(buf, "IsGuarding"), XS_NPC_IsGuarding, file, "$"); + newXSproto(strcpy(buf, "AI_SetRoambox"), XS_NPC_AI_SetRoambox, file, "$$$$$$;$$"); + newXSproto(strcpy(buf, "GetNPCSpellsID"), XS_NPC_GetNPCSpellsID, file, "$"); + newXSproto(strcpy(buf, "GetSpawnPointID"), XS_NPC_GetSpawnPointID, file, "$"); + newXSproto(strcpy(buf, "GetSpawnPointX"), XS_NPC_GetSpawnPointX, file, "$"); + newXSproto(strcpy(buf, "GetSpawnPointY"), XS_NPC_GetSpawnPointY, file, "$"); + newXSproto(strcpy(buf, "GetSpawnPointZ"), XS_NPC_GetSpawnPointZ, file, "$"); + newXSproto(strcpy(buf, "GetSpawnPointH"), XS_NPC_GetSpawnPointH, file, "$"); + newXSproto(strcpy(buf, "GetGuardPointX"), XS_NPC_GetGuardPointX, file, "$"); + newXSproto(strcpy(buf, "GetGuardPointY"), XS_NPC_GetGuardPointY, file, "$"); + newXSproto(strcpy(buf, "GetGuardPointZ"), XS_NPC_GetGuardPointZ, file, "$"); + newXSproto(strcpy(buf, "SetPrimSkill"), XS_NPC_SetPrimSkill, file, "$$"); + newXSproto(strcpy(buf, "SetSecSkill"), XS_NPC_SetSecSkill, file, "$$"); + newXSproto(strcpy(buf, "GetPrimSkill"), XS_NPC_GetPrimSkill, file, "$"); + newXSproto(strcpy(buf, "GetSecSkill"), XS_NPC_GetSecSkill, file, "$"); + newXSproto(strcpy(buf, "GetSwarmOwner"), XS_NPC_GetSwarmOwner, file, "$"); + newXSproto(strcpy(buf, "GetSwarmTarget"), XS_NPC_GetSwarmTarget, file, "$"); + newXSproto(strcpy(buf, "SetSwarmTarget"), XS_NPC_SetSwarmTarget, file, "$$"); + newXSproto(strcpy(buf, "ModifyNPCStat"), XS_NPC_ModifyNPCStat, file, "$$$"); + newXSproto(strcpy(buf, "AddAISpell"), XS_NPC_AddSpellToNPCList, file, "$$$$$$$"); + newXSproto(strcpy(buf, "RemoveAISpell"), XS_NPC_RemoveSpellFromNPCList, file, "$$"); + newXSproto(strcpy(buf, "SetSpellFocusDMG"), XS_NPC_SetSpellFocusDMG, file, "$$"); + newXSproto(strcpy(buf, "SetSpellFocusHeal"), XS_NPC_SetSpellFocusHeal, file, "$$"); + newXSproto(strcpy(buf, "GetSpellFocusDMG"), XS_NPC_GetSpellFocusDMG, file, "$"); + newXSproto(strcpy(buf, "GetSpellFocusHeal"), XS_NPC_GetSpellFocusHeal, file, "$"); + newXSproto(strcpy(buf, "GetSlowMitigation"), XS_NPC_GetSlowMitigation, file, "$"); + newXSproto(strcpy(buf, "GetAttackSpeed"), XS_NPC_GetAttackSpeed, file, "$"); + newXSproto(strcpy(buf, "GetAttackDelay"), XS_NPC_GetAttackDelay, file, "$"); + newXSproto(strcpy(buf, "GetAccuracyRating"), XS_NPC_GetAccuracyRating, file, "$"); + newXSproto(strcpy(buf, "GetAvoidanceRating"), XS_NPC_GetAvoidanceRating, file, "$"); + newXSproto(strcpy(buf, "GetSpawnKillCount"), XS_NPC_GetSpawnKillCount, file, "$"); + newXSproto(strcpy(buf, "GetScore"), XS_NPC_GetScore, file, "$"); + newXSproto(strcpy(buf, "SetMerchantProbability"), XS_NPC_SetMerchantProbability, file, "$$"); + newXSproto(strcpy(buf, "GetMerchantProbability"), XS_NPC_GetMerchantProbability, file, "$"); + newXSproto(strcpy(buf, "AddMeleeProc"), XS_NPC_AddMeleeProc, file, "$$$"); + newXSproto(strcpy(buf, "AddRangedProc"), XS_NPC_AddRangedProc, file, "$$$"); + newXSproto(strcpy(buf, "AddDefensiveProc"), XS_NPC_AddDefensiveProc, file, "$$$"); + XSRETURN_YES; +} + +#endif //EMBPERL_XS_CLASSES + diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index ecf2bb7ba..5490b9c6f 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -1270,7 +1270,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Melee Absorb Rune: %+i", effect_value); #endif - effect_value = ApplySpellEffectiveness(caster, spell_id, effect_value); + if (caster) + effect_value = caster->ApplySpellEffectiveness(spell_id, effect_value); + buffs[buffslot].melee_rune = effect_value; break; } @@ -3020,7 +3022,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove } int Mob::CalcSpellEffectValue(uint16 spell_id, int effect_id, int caster_level, uint32 instrument_mod, Mob *caster, - int ticsremaining) + int ticsremaining, uint16 caster_id) { int formula, base, max, effect_value; @@ -3048,7 +3050,7 @@ int Mob::CalcSpellEffectValue(uint16 spell_id, int effect_id, int caster_level, spells[spell_id].effectid[effect_id] != SE_ManaRegen_v2) { int oval = effect_value; - int mod = ApplySpellEffectiveness(caster, spell_id, instrument_mod, true); + int mod = ApplySpellEffectiveness(spell_id, instrument_mod, true, caster_id); effect_value = effect_value * mod / 10; Log.Out(Logs::Detail, Logs::Spells, "Effect value %d altered with bard modifier of %d to yeild %d", oval, mod, effect_value); @@ -6043,16 +6045,21 @@ int32 Mob::GetFocusIncoming(focusType type, int effect, Mob *caster, uint32 spel return value; } -int32 Mob::ApplySpellEffectiveness(Mob* caster, int16 spell_id, int32 value, bool IsBard) { +int32 Mob::ApplySpellEffectiveness(int16 spell_id, int32 value, bool IsBard, uint16 caster_id) { // 9-17-12: This is likely causing crashes, disabled till can resolve. if (IsBard) return value; + Mob* caster = this; + + if (caster_id && caster_id != GetID())//Make sure we are checking the casters focus + caster = entity_list.GetMob(caster_id); + if (!caster) return value; - int16 focus = GetFocusEffect(focusFcBaseEffects, spell_id); + int16 focus = caster->GetFocusEffect(focusFcBaseEffects, spell_id); if (IsBard) value += focus;