From 5d9ec0c4bfa82422c662aaa1b8879b3837e9840f Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Sun, 27 Mar 2016 10:27:23 -0400 Subject: [PATCH 1/4] Removed unneccessary entitylist check from ApplySpellBonuses Fixed an issue with FCBaseEffects not applying bonus when cast on targets from runes. --- Server | 1 + common/shareddbx.cpp | 2162 +++++++ common/spdat.h | 2 +- .../required/2014_02_12_spells_new_update.sql | 13 + ...014_04_10_No_Target_With_Hotkey - Copy.sql | 3 + .../git/required/2014_06_22_MetabolismAAs.sql | 6 + zone/AA_base.cpp | 1990 +++++++ zone/AA_v1.cpp | 2032 +++++++ zone/MobAI_M.cpp | 2760 +++++++++ zone/attackx.cpp | 4663 +++++++++++++++ zone/bonuses.cpp | 6 +- zone/bonusesxx.cpp | 4337 ++++++++++++++ zone/groupsx.cpp | 2194 +++++++ zone/mob.h | 4 +- zone/mobx.cpp | 5148 +++++++++++++++++ zone/mobx.h | 1236 ++++ zone/net_Fix.cpp | 644 +++ zone/npcx.cpp | 2492 ++++++++ zone/perl_npcX.cpp | 2487 ++++++++ zone/spell_effects.cpp | 17 +- 20 files changed, 32184 insertions(+), 13 deletions(-) create mode 160000 Server create mode 100644 common/shareddbx.cpp create mode 100644 utils/sql/git/required/2014_02_12_spells_new_update.sql create mode 100644 utils/sql/git/required/2014_04_10_No_Target_With_Hotkey - Copy.sql create mode 100644 utils/sql/git/required/2014_06_22_MetabolismAAs.sql create mode 100644 zone/AA_base.cpp create mode 100644 zone/AA_v1.cpp create mode 100644 zone/MobAI_M.cpp create mode 100644 zone/attackx.cpp create mode 100644 zone/bonusesxx.cpp create mode 100644 zone/groupsx.cpp create mode 100644 zone/mobx.cpp create mode 100644 zone/mobx.h create mode 100644 zone/net_Fix.cpp create mode 100644 zone/npcx.cpp create mode 100644 zone/perl_npcX.cpp 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; From cc554be1df3dd10332f673d2ffa2180927b48429 Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Sun, 27 Mar 2016 10:36:49 -0400 Subject: [PATCH 2/4] Revert "Removed unneccessary entitylist check from ApplySpellBonuses" --- Server | 1 - common/shareddbx.cpp | 2162 ------- common/spdat.h | 2 +- .../required/2014_02_12_spells_new_update.sql | 13 - ...014_04_10_No_Target_With_Hotkey - Copy.sql | 3 - .../git/required/2014_06_22_MetabolismAAs.sql | 6 - zone/AA_base.cpp | 1990 ------- zone/AA_v1.cpp | 2032 ------- zone/MobAI_M.cpp | 2760 --------- zone/attackx.cpp | 4663 --------------- zone/bonuses.cpp | 6 +- zone/bonusesxx.cpp | 4337 -------------- zone/groupsx.cpp | 2194 ------- zone/mob.h | 4 +- zone/mobx.cpp | 5148 ----------------- zone/mobx.h | 1236 ---- zone/net_Fix.cpp | 644 --- zone/npcx.cpp | 2492 -------- zone/perl_npcX.cpp | 2487 -------- zone/spell_effects.cpp | 17 +- 20 files changed, 13 insertions(+), 32184 deletions(-) delete mode 160000 Server delete mode 100644 common/shareddbx.cpp delete mode 100644 utils/sql/git/required/2014_02_12_spells_new_update.sql delete mode 100644 utils/sql/git/required/2014_04_10_No_Target_With_Hotkey - Copy.sql delete mode 100644 utils/sql/git/required/2014_06_22_MetabolismAAs.sql delete mode 100644 zone/AA_base.cpp delete mode 100644 zone/AA_v1.cpp delete mode 100644 zone/MobAI_M.cpp delete mode 100644 zone/attackx.cpp delete mode 100644 zone/bonusesxx.cpp delete mode 100644 zone/groupsx.cpp delete mode 100644 zone/mobx.cpp delete mode 100644 zone/mobx.h delete mode 100644 zone/net_Fix.cpp delete mode 100644 zone/npcx.cpp delete mode 100644 zone/perl_npcX.cpp diff --git a/Server b/Server deleted file mode 160000 index 9d78eec48..000000000 --- a/Server +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9d78eec485fb73c8d61ce035590474556390783e diff --git a/common/shareddbx.cpp b/common/shareddbx.cpp deleted file mode 100644 index 60e29cccb..000000000 --- a/common/shareddbx.cpp +++ /dev/null @@ -1,2162 +0,0 @@ -#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 5bb29b8c4..0e9096f1a 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 deleted file mode 100644 index ba552e1ed..000000000 --- a/utils/sql/git/required/2014_02_12_spells_new_update.sql +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index 12e9963a5..000000000 --- a/utils/sql/git/required/2014_04_10_No_Target_With_Hotkey - Copy.sql +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index 3a957edb2..000000000 --- a/utils/sql/git/required/2014_06_22_MetabolismAAs.sql +++ /dev/null @@ -1,6 +0,0 @@ --- 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 deleted file mode 100644 index 8566c09b5..000000000 --- a/zone/AA_base.cpp +++ /dev/null @@ -1,1990 +0,0 @@ -/* 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 deleted file mode 100644 index fb1413927..000000000 --- a/zone/AA_v1.cpp +++ /dev/null @@ -1,2032 +0,0 @@ -/* 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 deleted file mode 100644 index 7574f19ad..000000000 --- a/zone/MobAI_M.cpp +++ /dev/null @@ -1,2760 +0,0 @@ -/* 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 deleted file mode 100644 index 3f96399be..000000000 --- a/zone/attackx.cpp +++ /dev/null @@ -1,4663 +0,0 @@ -/* 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 f57e92247..413b8d6c6 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -1543,10 +1543,14 @@ 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 @@ -1573,7 +1577,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, nullptr, ticsremaining, casterId); + effect_value = CalcSpellEffectValue(spell_id, i, casterlevel, instrument_mod, caster, ticsremaining); base2 = spells[spell_id].base2[i]; max = spells[spell_id].max[i]; } diff --git a/zone/bonusesxx.cpp b/zone/bonusesxx.cpp deleted file mode 100644 index 8e23eb5fb..000000000 --- a/zone/bonusesxx.cpp +++ /dev/null @@ -1,4337 +0,0 @@ -/* 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 deleted file mode 100644 index 6aee5f38b..000000000 --- a/zone/groupsx.cpp +++ /dev/null @@ -1,2194 +0,0 @@ -/* 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 97b75a4fb..7083e45ca 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(int16 spell_id, int32 value, bool IsBard = false, uint16 caster_id=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); @@ -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,uint16 casterid=0); + int CalcSpellEffectValue(uint16 spell_id, int effect_id, int caster_level = 1, uint32 instrument_mod = 10, 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; } diff --git a/zone/mobx.cpp b/zone/mobx.cpp deleted file mode 100644 index 340dfdfea..000000000 --- a/zone/mobx.cpp +++ /dev/null @@ -1,5148 +0,0 @@ -/* 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 deleted file mode 100644 index 4cd6d5f49..000000000 --- a/zone/mobx.h +++ /dev/null @@ -1,1236 +0,0 @@ -/* 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 deleted file mode 100644 index 9b4080eb2..000000000 --- a/zone/net_Fix.cpp +++ /dev/null @@ -1,644 +0,0 @@ -/* 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 deleted file mode 100644 index 7d2766854..000000000 --- a/zone/npcx.cpp +++ /dev/null @@ -1,2492 +0,0 @@ -/* 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 deleted file mode 100644 index 957baf7ab..000000000 --- a/zone/perl_npcX.cpp +++ /dev/null @@ -1,2487 +0,0 @@ -/* -* 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 5490b9c6f..ecf2bb7ba 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -1270,9 +1270,7 @@ 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 - if (caster) - effect_value = caster->ApplySpellEffectiveness(spell_id, effect_value); - + effect_value = ApplySpellEffectiveness(caster, spell_id, effect_value); buffs[buffslot].melee_rune = effect_value; break; } @@ -3022,7 +3020,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, uint16 caster_id) + int ticsremaining) { int formula, base, max, effect_value; @@ -3050,7 +3048,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(spell_id, instrument_mod, true, caster_id); + int mod = ApplySpellEffectiveness(caster, spell_id, instrument_mod, true); 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); @@ -6045,21 +6043,16 @@ int32 Mob::GetFocusIncoming(focusType type, int effect, Mob *caster, uint32 spel return value; } -int32 Mob::ApplySpellEffectiveness(int16 spell_id, int32 value, bool IsBard, uint16 caster_id) { +int32 Mob::ApplySpellEffectiveness(Mob* caster, int16 spell_id, int32 value, bool IsBard) { // 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 = caster->GetFocusEffect(focusFcBaseEffects, spell_id); + int16 focus = GetFocusEffect(focusFcBaseEffects, spell_id); if (IsBard) value += focus; From d1facd9368ebeb24051376dabfefdbbbd8449c55 Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Sun, 27 Mar 2016 10:57:29 -0400 Subject: [PATCH 3/4] Kayen: delete bad test files accidently merged --- common/shareddbx.cpp | 2162 -------- .../required/2014_02_12_spells_new_update.sql | 13 - ...014_04_10_No_Target_With_Hotkey - Copy.sql | 3 - .../git/required/2014_06_22_MetabolismAAs.sql | 6 - zone/AA_base.cpp | 1990 ------- zone/AA_v1.cpp | 2032 ------- zone/MobAI_M.cpp | 2760 ---------- zone/attackx.cpp | 4663 ----------------- zone/bonusesxx.cpp | 4337 --------------- zone/groupsx.cpp | 2194 -------- zone/npcx.cpp | 2492 --------- zone/perl_npcX.cpp | 2487 --------- 12 files changed, 25139 deletions(-) delete mode 100644 common/shareddbx.cpp delete mode 100644 utils/sql/git/required/2014_02_12_spells_new_update.sql delete mode 100644 utils/sql/git/required/2014_04_10_No_Target_With_Hotkey - Copy.sql delete mode 100644 utils/sql/git/required/2014_06_22_MetabolismAAs.sql delete mode 100644 zone/AA_base.cpp delete mode 100644 zone/AA_v1.cpp delete mode 100644 zone/MobAI_M.cpp delete mode 100644 zone/attackx.cpp delete mode 100644 zone/bonusesxx.cpp delete mode 100644 zone/groupsx.cpp delete mode 100644 zone/npcx.cpp delete mode 100644 zone/perl_npcX.cpp diff --git a/common/shareddbx.cpp b/common/shareddbx.cpp deleted file mode 100644 index 60e29cccb..000000000 --- a/common/shareddbx.cpp +++ /dev/null @@ -1,2162 +0,0 @@ -#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/utils/sql/git/required/2014_02_12_spells_new_update.sql b/utils/sql/git/required/2014_02_12_spells_new_update.sql deleted file mode 100644 index ba552e1ed..000000000 --- a/utils/sql/git/required/2014_02_12_spells_new_update.sql +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index 12e9963a5..000000000 --- a/utils/sql/git/required/2014_04_10_No_Target_With_Hotkey - Copy.sql +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index 3a957edb2..000000000 --- a/utils/sql/git/required/2014_06_22_MetabolismAAs.sql +++ /dev/null @@ -1,6 +0,0 @@ --- 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 deleted file mode 100644 index 8566c09b5..000000000 --- a/zone/AA_base.cpp +++ /dev/null @@ -1,1990 +0,0 @@ -/* 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 deleted file mode 100644 index fb1413927..000000000 --- a/zone/AA_v1.cpp +++ /dev/null @@ -1,2032 +0,0 @@ -/* 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 deleted file mode 100644 index 7574f19ad..000000000 --- a/zone/MobAI_M.cpp +++ /dev/null @@ -1,2760 +0,0 @@ -/* 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 deleted file mode 100644 index 3f96399be..000000000 --- a/zone/attackx.cpp +++ /dev/null @@ -1,4663 +0,0 @@ -/* 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/bonusesxx.cpp b/zone/bonusesxx.cpp deleted file mode 100644 index 8e23eb5fb..000000000 --- a/zone/bonusesxx.cpp +++ /dev/null @@ -1,4337 +0,0 @@ -/* 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 deleted file mode 100644 index 6aee5f38b..000000000 --- a/zone/groupsx.cpp +++ /dev/null @@ -1,2194 +0,0 @@ -/* 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/npcx.cpp b/zone/npcx.cpp deleted file mode 100644 index 7d2766854..000000000 --- a/zone/npcx.cpp +++ /dev/null @@ -1,2492 +0,0 @@ -/* 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 deleted file mode 100644 index 957baf7ab..000000000 --- a/zone/perl_npcX.cpp +++ /dev/null @@ -1,2487 +0,0 @@ -/* -* 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 - From 365a08ee869f6048f1a7c3a084701a677fe16ca1 Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Sun, 27 Mar 2016 11:08:08 -0400 Subject: [PATCH 4/4] Removed unneccessary entitylist check from ApplySpellBonuses Fixed an issue with FCBaseEffects not applying bonus when cast on targets from runes. --- zone/bonuses.cpp | 6 +----- zone/mob.h | 6 +++--- zone/mod_functions.cpp | 2 +- zone/spell_effects.cpp | 19 +++++++++++++------ 4 files changed, 18 insertions(+), 15 deletions(-) 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/mob.h b/zone/mob.h index 7083e45ca..8cb899d0f 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; } @@ -956,7 +956,7 @@ public: inline uint16 GetEmoteID() { return emoteid; } bool HasSpellEffect(int effectid); - int mod_effect_value(int effect_value, uint16 spell_id, int effect_type, Mob* caster); + int mod_effect_value(int effect_value, uint16 spell_id, int effect_type, Mob* caster, uint16 caster_id); 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); diff --git a/zone/mod_functions.cpp b/zone/mod_functions.cpp index 6ae0eddad..5c98541c0 100644 --- a/zone/mod_functions.cpp +++ b/zone/mod_functions.cpp @@ -108,7 +108,7 @@ int Client::mod_food_value(const Item_Struct *item, int change) { return(change) int Client::mod_drink_value(const Item_Struct *item, int change) { return(change); } //effect_vallue - Spell effect value as calculated by default formulas. You will want to ignore effects that don't lend themselves to scaling - pet ID's, gate coords, etc. -int Mob::mod_effect_value(int effect_value, uint16 spell_id, int effect_type, Mob* caster) { return(effect_value); } +int Mob::mod_effect_value(int effect_value, uint16 spell_id, int effect_type, Mob* caster, uint16 caster_id) { return(effect_value); } //chancetohit - 0 to 100 percent - set over 1000 for a guaranteed hit float Mob::mod_hit_chance(float chancetohit, SkillUseTypes skillinuse, Mob* attacker) { return(chancetohit); } diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index ecf2bb7ba..f0b93ca9c 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,13 +3050,13 @@ 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); } - effect_value = mod_effect_value(effect_value, spell_id, spells[spell_id].effectid[effect_id], caster); + effect_value = mod_effect_value(effect_value, spell_id, spells[spell_id].effectid[effect_id], caster, caster_id); return 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;