From 80ff535215fb92ccf542e48d5e24f52691717816 Mon Sep 17 00:00:00 2001 From: Paul Coene Date: Tue, 10 Jan 2017 20:15:03 -0500 Subject: [PATCH 01/50] Added code so that books/scrolls read that are in a language other than common get language skill applied. Added code to support ReadBook slots above and beyond main inventory slots by decoding additional bag slot field. --- .../git/required/2017_01_10_book_languages.sql | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 utils/sql/git/required/2017_01_10_book_languages.sql diff --git a/utils/sql/git/required/2017_01_10_book_languages.sql b/utils/sql/git/required/2017_01_10_book_languages.sql new file mode 100644 index 000000000..508cdc156 --- /dev/null +++ b/utils/sql/git/required/2017_01_10_book_languages.sql @@ -0,0 +1,15 @@ +alter table books add language int default 0; + +drop table reading_is_fundamental; + +create table reading_is_fundamental +( +filename varchar(32), +language int +); + +insert into reading_is_fundamental (select items.filename, items.booktype from items where items.filename != "" group by filename); + +update books set books.language = (select language from reading_is_fundamental r where r.filename = books.name); + +drop table reading_is_fundamental; From df86e644f445ef579bfdfa152ae98c4bc18e69d3 Mon Sep 17 00:00:00 2001 From: Paul Coene Date: Tue, 10 Jan 2017 20:18:16 -0500 Subject: [PATCH 02/50] Now the actual code changes - lol --- common/eq_packet_structs.h | 1 + common/patches/rof2.cpp | 1 + common/patches/rof2_structs.h | 3 ++- common/shareddb.cpp | 5 ++-- common/shareddb.h | 2 +- utils/sql/db_update_manifest.txt | 1 + zone/client.cpp | 40 +++++++++++++++++++++++++++----- 7 files changed, 43 insertions(+), 10 deletions(-) diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 1d25e4d66..a3e26a9a4 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -2495,6 +2495,7 @@ struct BookRequest_Struct { uint8 window; // where to display the text (0xFF means new window) uint8 type; //type: 0=scroll, 1=book, 2=item info.. prolly others. uint32 invslot; // Only used in Sof and later clients; + int16 subslot; // The subslot inside of a bag if it is inside one. char txtfile[20]; }; diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index d56791bef..8f8d41281 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -5154,6 +5154,7 @@ namespace RoF2 IN(type); IN(invslot); + IN(subslot); emu->window = (uint8)eq->window; strn0cpy(emu->txtfile, eq->txtfile, sizeof(emu->txtfile)); diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index 850df8ddf..1ed1b9916 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -2826,7 +2826,8 @@ struct BookText_Struct { struct BookRequest_Struct { /*0000*/ uint32 window; // where to display the text (0xFFFFFFFF means new window). /*0004*/ uint16 invslot; // Is the slot, but the RoF2 conversion causes it to fail. Turned to 0 since it isnt required anyway. -/*0008*/ uint32 unknown006; // Seen FFFFFFFF +/*0006*/ int16 subslot; // Inventory sub-slot (0-x) +/*0008*/ uint16 unknown006; // Seen FFFF /*0010*/ uint16 unknown008; // seen 0000 /*0012*/ uint32 type; // 0 = Scroll, 1 = Book, 2 = Item Info. Possibly others /*0016*/ uint32 unknown0012; diff --git a/common/shareddb.cpp b/common/shareddb.cpp index fc628ea6f..a4d0e6235 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -1136,13 +1136,13 @@ const EQEmu::ItemData* SharedDatabase::IterateItems(uint32* id) { return nullptr; } -std::string SharedDatabase::GetBook(const char *txtfile) +std::string SharedDatabase::GetBook(const char *txtfile, int16 *language) { char txtfile2[20]; std::string txtout; strcpy(txtfile2, txtfile); - std::string query = StringFormat("SELECT txtfile FROM books WHERE name = '%s'", txtfile2); + std::string query = StringFormat("SELECT txtfile, language FROM books WHERE name = '%s'", txtfile2); auto results = QueryDatabase(query); if (!results.Success()) { txtout.assign(" ",1); @@ -1157,6 +1157,7 @@ std::string SharedDatabase::GetBook(const char *txtfile) auto row = results.begin(); txtout.assign(row[0],strlen(row[0])); + *language = static_cast(atoi(row[1])); return txtout; } diff --git a/common/shareddb.h b/common/shareddb.h index b58d1f138..d3d8020a1 100644 --- a/common/shareddb.h +++ b/common/shareddb.h @@ -94,7 +94,7 @@ class SharedDatabase : public Database bool SetStartingItems(PlayerProfile_Struct* pp, EQEmu::InventoryProfile* inv, uint32 si_race, uint32 si_class, uint32 si_deity, uint32 si_current_zone, char* si_name, int admin); - std::string GetBook(const char *txtfile); + std::string GetBook(const char *txtfile, int16 *language); /* Item Methods diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index aa72310ce..816a35a7c 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -355,6 +355,7 @@ 9099|2016_08_27_ip_exemptions.sql|SHOW TABLES LIKE 'ip_exemptions'|empty| 9100|2016_08_27_object_display_name.sql|SHOW COLUMNS FROM `object` LIKE 'display_name'|empty| 9101|2016_12_01_pcnpc_only.sql|SHOW COLUMNS FROM `spells_new` LIKE 'pcnpc_only_flag'|empty| +9102|2017_01_10_book_languages.sql|SHOW COLUMNS FROM `books` LIKE 'language'|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/zone/client.cpp b/zone/client.cpp index ddf10d9d5..6b6de32c1 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -33,6 +33,7 @@ extern volatile bool RunLoops; #include "../common/eqemu_logsys.h" #include "../common/features.h" +#include "../common/emu_legacy.h" #include "../common/spdat.h" #include "../common/guilds.h" #include "../common/rulesys.h" @@ -1998,6 +1999,7 @@ void Client::SetGM(bool toggle) { } void Client::ReadBook(BookRequest_Struct *book) { + int16 book_language=0; char *txtfile = book->txtfile; if(txtfile[0] == '0' && txtfile[1] == '\0') { @@ -2005,7 +2007,7 @@ void Client::ReadBook(BookRequest_Struct *book) { return; } - std::string booktxt2 = database.GetBook(txtfile); + std::string booktxt2 = database.GetBook(txtfile, &book_language); int length = booktxt2.length(); if (booktxt2[0] != '\0') { @@ -2016,21 +2018,47 @@ void Client::ReadBook(BookRequest_Struct *book) { BookText_Struct *out = (BookText_Struct *) outapp->pBuffer; out->window = book->window; - if (ClientVersion() >= EQEmu::versions::ClientVersion::SoF) - { - const EQEmu::ItemInstance *inst = m_inv[book->invslot]; + + + if (ClientVersion() >= EQEmu::versions::ClientVersion::SoF) { + // Find out what slot the book was read from. + // SoF+ need to look up book type for the output message. + int16 read_from_slot; + + if (book->subslot >= 0) { + uint16 offset; + offset = (book->invslot-23) * 10; // How many packs to skip. + read_from_slot = 251 + offset + book->subslot; + } + else { + read_from_slot = book->invslot -1; + } + + const EQEmu::ItemInstance *inst = 0; + + if (read_from_slot <= EQEmu::legacy::SLOT_PERSONAL_BAGS_END) + { + inst = m_inv[read_from_slot]; + } + if(inst) out->type = inst->GetItem()->Book; else out->type = book->type; } - else - { + else { out->type = book->type; } out->invslot = book->invslot; + memcpy(out->booktext, booktxt2.c_str(), length); + if (book_language > 0 && book_language < MAX_PP_LANGUAGE) { + if (m_pp.languages[book_language] < 100) { + GarbleMessage(out->booktext, (100 - m_pp.languages[book_language])); + } + } + QueuePacket(outapp); safe_delete(outapp); } From af4a432745bdbd893349d30a1e3c6517cf82d492 Mon Sep 17 00:00:00 2001 From: Paul Coene Date: Tue, 10 Jan 2017 20:33:01 -0500 Subject: [PATCH 03/50] Missed version.h in original commit --- common/version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/version.h b/common/version.h index 93dfba189..2d5f1d75f 100644 --- a/common/version.h +++ b/common/version.h @@ -30,7 +30,7 @@ Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9101 +#define CURRENT_BINARY_DATABASE_VERSION 9102 #ifdef BOTS #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9008 #else From 9e824876ba5dac262b121c0e60d022bb2ecc45bd Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sun, 15 Jan 2017 00:03:02 -0500 Subject: [PATCH 04/50] Combat Revamp - MAJOR BREAKING CHANGE This commit makes combat much more live like. This is based on a lot of parses done by TAKP and myself. There are numerous things based on dev quotes and hints. Pretty much all combat has changed, spell effects correct, stacking correct, etc. This is the fist stage of the revamp, I will be trying to remove some code duplication and make things generally cleaner. Server ops will have to rebalance their NPCs. AC actually means something now. Rough recommendations? Level 50 "classic" trash should be no more than 115. Classic raid mobs should be more 200+ etc Other "classic" NPCs should be a lot lower as well. PoP trash probably shouldn't exceed 120 AC PoP raids should be higher Devs have said the vast majority of NPCs didn't exceed 600 AC until very recently. The exceptions were mostly raid encounters. There really isn't a good "default" for every server, so this will be up to the devs to find where they want their server stats to be. --- common/skills.cpp | 24 + common/skills.h | 1 + zone/attack.cpp | 1437 ++++++++++++++++++++++++-------------- zone/beacon.h | 4 +- zone/bonuses.cpp | 8 +- zone/bot.cpp | 451 ++++-------- zone/bot.h | 10 +- zone/client.cpp | 6 +- zone/client.h | 11 +- zone/client_mods.cpp | 105 --- zone/common.h | 6 + zone/corpse.h | 4 +- zone/encounter.h | 4 +- zone/hate_list.cpp | 2 +- zone/merc.cpp | 21 +- zone/merc.h | 5 +- zone/mob.cpp | 6 +- zone/mob.h | 76 +- zone/mob_ai.cpp | 31 +- zone/npc.cpp | 23 +- zone/npc.h | 10 +- zone/special_attacks.cpp | 777 ++++++++------------- zone/tune.cpp | 8 +- zone/zonedb.cpp | 2 +- zone/zonedump.h | 2 +- 25 files changed, 1520 insertions(+), 1514 deletions(-) diff --git a/common/skills.cpp b/common/skills.cpp index a23d77a7b..6cb063e2e 100644 --- a/common/skills.cpp +++ b/common/skills.cpp @@ -124,6 +124,30 @@ bool EQEmu::skills::IsCastingSkill(SkillType skill) } } +int32 EQEmu::skills::GetBaseDamage(SkillType skill) +{ + switch (skill) { + case SkillBash: + return 2; + case SkillDragonPunch: + return 12; + case SkillEagleStrike: + return 7; + case SkillFlyingKick: + return 25; + case SkillKick: + return 3; + case SkillRoundKick: + return 5; + case SkillTigerClaw: + return 4; + case SkillFrenzy: + return 10; + default: + return 0; + } +} + const std::map& EQEmu::skills::GetSkillTypeMap() { /* VS2013 code diff --git a/common/skills.h b/common/skills.h index 19a996729..572b8c59f 100644 --- a/common/skills.h +++ b/common/skills.h @@ -166,6 +166,7 @@ namespace EQEmu float GetSkillMeleePushForce(SkillType skill); bool IsBardInstrumentSkill(SkillType skill); bool IsCastingSkill(SkillType skill); + int32 GetBaseDamage(SkillType skill); extern const std::map& GetSkillTypeMap(); diff --git a/zone/attack.cpp b/zone/attack.cpp index 47b7bcb66..219ee231e 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -189,6 +189,9 @@ bool Mob::CheckHitChance(Mob* other, EQEmu::skills::SkillType skillinuse, int ch Mob *defender = this; Log.Out(Logs::Detail, Logs::Attack, "CheckHitChance(%s) attacked by %s", defender->GetName(), attacker->GetName()); + if (defender->IsClient() && defender->CastToClient()->IsSitting()) + return true; + // calculate defender's avoidance auto avoidance = defender->compute_defense() + 10; // add 10 in case the NPC's stats are fucked auto evasion_bonus = defender->spellbonuses.AvoidMeleeChanceEffect; // we check this first since it has a special case @@ -276,7 +279,7 @@ bool Mob::CheckHitChance(Mob* other, EQEmu::skills::SkillType skillinuse, int ch return tohit_roll > avoid_roll; } -bool Mob::AvoidDamage(Mob *other, int32 &damage, int hand) +bool Mob::AvoidDamage(Mob *other, int &damage, int hand) { /* 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. @@ -479,274 +482,403 @@ bool Mob::AvoidDamage(Mob *other, int32 &damage, int hand) return false; } -void Mob::MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit, ExtraAttackOptions *opts) +int Mob::GetACSoftcap() { - if (damage <= 0) + // from test server Resources/ACMitigation.txt + static int war_softcaps[] = { + 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 348, 350, 352, + 354, 356, 358, 360, 362, 364, 366, 368, 370, 372, 374, 376, 378, 380, 382, 384, 386, 388, 390, 392, 394, + 396, 398, 400, 402, 404, 406, 408, 410, 412, 414, 416, 418, 420, 422, 424, 426, 428, 430, 432, 434, 436, + 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 468, 470, 472, 474, 476, 478, + 480, 482, 484, 486, 488, 490, 492, 494, 496, 498, 500, 502, 504, 506, 508, 510, 512, 514, 516, 518, 520 + }; + + static int clrbrdmnk_softcaps[] = { + 274, 276, 278, 278, 280, 282, 284, 286, 288, 290, 292, 292, 294, 296, 298, 300, 302, 304, 306, 308, 308, + 310, 312, 314, 316, 318, 320, 322, 322, 324, 326, 328, 330, 332, 334, 336, 336, 338, 340, 342, 344, 346, + 348, 350, 352, 352, 354, 356, 358, 360, 362, 364, 366, 366, 368, 370, 372, 374, 376, 378, 380, 380, 382, + 384, 386, 388, 390, 392, 394, 396, 396, 398, 400, 402, 404, 406, 408, 410, 410, 412, 414, 416, 418, 420, + 422, 424, 424, 426, 428, 430, 432, 434, 436, 438, 440, 440, 442, 444, 446, 448, 450, 452, 454, 454, 456 + }; + + static int palshd_softcaps[] = { + 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 336, + 338, 340, 342, 344, 346, 348, 350, 352, 354, 356, 358, 360, 362, 364, 366, 368, 370, 372, 374, 376, 378, + 380, 382, 384, 384, 386, 388, 390, 392, 394, 396, 398, 400, 402, 404, 406, 408, 410, 412, 414, 416, 418, + 420, 422, 424, 426, 428, 430, 432, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, + 460, 462, 464, 466, 468, 470, 472, 474, 476, 478, 480, 480, 482, 484, 486, 488, 490, 492, 494, 496, 498 + }; + + static int rng_softcaps[] = { + 286, 288, 290, 292, 294, 296, 298, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 322, + 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 344, 346, 348, 350, 352, 354, 356, 358, 360, 362, + 364, 366, 368, 368, 370, 372, 374, 376, 378, 380, 382, 384, 386, 388, 390, 390, 392, 394, 396, 398, 400, + 402, 404, 406, 408, 410, 412, 414, 414, 416, 418, 420, 422, 424, 426, 428, 430, 432, 434, 436, 436, 438, + 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 460, 462, 464, 466, 468, 470, 472, 474, 476, 478 + }; + + static int dru_softcaps[] = { + 254, 256, 258, 260, 262, 264, 264, 266, 268, 270, 272, 272, 274, 276, 278, 280, 282, 282, 284, 286, 288, + 290, 290, 292, 294, 296, 298, 300, 300, 302, 304, 306, 308, 308, 310, 312, 314, 316, 318, 318, 320, 322, + 324, 326, 328, 328, 330, 332, 334, 336, 336, 338, 340, 342, 344, 346, 346, 348, 350, 352, 354, 354, 356, + 358, 360, 362, 364, 364, 366, 368, 370, 372, 372, 374, 376, 378, 380, 382, 382, 384, 386, 388, 390, 390, + 392, 394, 396, 398, 400, 400, 402, 404, 406, 408, 410, 410, 412, 414, 416, 418, 418, 420, 422, 424, 426 + }; + + static int rogshmbstber_softcaps[] = { + 264, 266, 268, 270, 272, 272, 274, 276, 278, 280, 282, 282, 284, 286, 288, 290, 292, 294, 294, 296, 298, + 300, 302, 304, 306, 306, 308, 310, 312, 314, 316, 316, 318, 320, 322, 324, 326, 328, 328, 330, 332, 334, + 336, 338, 340, 340, 342, 344, 346, 348, 350, 350, 352, 354, 356, 358, 360, 362, 362, 364, 366, 368, 370, + 372, 374, 374, 376, 378, 380, 382, 384, 384, 386, 388, 390, 392, 394, 396, 396, 398, 400, 402, 404, 406, + 408, 408, 410, 412, 414, 416, 418, 418, 420, 422, 424, 426, 428, 430, 430, 432, 434, 436, 438, 440, 442 + }; + + static int necwizmagenc_softcaps[] = { + 248, 250, 252, 254, 256, 256, 258, 260, 262, 264, 264, 266, 268, 270, 272, 272, 274, 276, 278, 280, 280, + 282, 284, 286, 288, 288, 290, 292, 294, 296, 296, 298, 300, 302, 304, 304, 306, 308, 310, 312, 312, 314, + 316, 318, 320, 320, 322, 324, 326, 328, 328, 330, 332, 334, 336, 336, 338, 340, 342, 344, 344, 346, 348, + 350, 352, 352, 354, 356, 358, 360, 360, 362, 364, 366, 368, 368, 370, 372, 374, 376, 376, 378, 380, 382, + 384, 384, 386, 388, 390, 392, 392, 394, 396, 398, 400, 400, 402, 404, 406, 408, 408, 410, 412, 414, 416 + }; + + int level = std::min(105, static_cast(GetLevel())) - 1; + + switch (GetClass()) { + case WARRIOR: + return war_softcaps[level]; + case CLERIC: + case BARD: + case MONK: + return clrbrdmnk_softcaps[level]; + case PALADIN: + case SHADOWKNIGHT: + return palshd_softcaps[level]; + case RANGER: + return rng_softcaps[level]; + case DRUID: + return dru_softcaps[level]; + case ROGUE: + case SHAMAN: + case BEASTLORD: + case BERSERKER: + return rogshmbstber_softcaps[level]; + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + return necwizmagenc_softcaps[level]; + default: + return 350; + } +} + +double Mob::GetSoftcapReturns() +{ + // These are based on the dev post, they seem to be correct for every level + // AKA no more hard caps + switch (GetClass()) { + case WARRIOR: + return 0.35; + case CLERIC: + case BARD: + case MONK: + return 0.3; + case PALADIN: + case SHADOWKNIGHT: + return 0.33; + case RANGER: + return 0.315; + case DRUID: + return 0.265; + case ROGUE: + case SHAMAN: + case BEASTLORD: + case BERSERKER: + return 0.28; + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + return 0.25; + default: + return 0.3; + } +} + +int Mob::GetClassRaceACBonus() +{ + int ac_bonus = 0; + auto level = GetLevel(); + if (GetClass() == MONK) { + int hardcap = 30; + int softcap = 14; + if (level > 99) { + hardcap = 58; + softcap = 35; + } else if (level > 94) { + hardcap = 57; + softcap = 34; + } else if (level > 89) { + hardcap = 56; + softcap = 33; + } else if (level > 84) { + hardcap = 55; + softcap = 32; + } else if (level > 79) { + hardcap = 54; + softcap = 31; + } else if (level > 74) { + hardcap = 53; + softcap = 30; + } else if (level > 69) { + hardcap = 53; + softcap = 28; + } else if (level > 64) { + hardcap = 53; + softcap = 26; + } else if (level > 63) { + hardcap = 50; + softcap = 24; + } else if (level > 61) { + hardcap = 47; + softcap = 24; + } else if (level > 59) { + hardcap = 45; + softcap = 24; + } else if (level > 54) { + hardcap = 40; + softcap = 20; + } else if (level > 50) { + hardcap = 38; + softcap = 18; + } else if (level > 44) { + hardcap = 36; + softcap = 17; + } else if (level > 29) { + hardcap = 34; + softcap = 16; + } else if (level > 14) { + hardcap = 32; + softcap = 15; + } + int weight = IsClient() ? CastToClient()->CalcCurrentWeight() : 0; + if (weight < hardcap - 1) { + int temp = level + 5; + if (weight > softcap) { + double redux = (weight - softcap) * 6.66667; + redux = (100.0 - std::min(100.0, redux)) * 0.01; + temp = std::max(0, static_cast(temp * redux)); + } + ac_bonus = (4 * temp) / 3; + } else if (weight > hardcap + 1) { + int temp = level + 5; + double multiplier = std::min(1.0, (weight - (hardcap - 10.0)) / 100.0); + temp = (4 * temp) / 3; + ac_bonus -= static_cast(temp * multiplier); + } + } + + if (GetClass() == ROGUE) { + int level_scaler = level - 26; + if (GetAGI() < 80) + ac_bonus = level_scaler / 4; + else if (GetAGI() < 85) + ac_bonus = (level_scaler * 2) / 4; + else if (GetAGI() < 90) + ac_bonus = (level_scaler * 3) / 4; + else if (GetAGI() < 100) + ac_bonus = (level_scaler * 4) / 4; + else if (GetAGI() >= 100) + ac_bonus = (level_scaler * 5) / 4; + if (ac_bonus > 12) + ac_bonus = 12; + } + + if (GetClass() == BEASTLORD) { + int level_scaler = level - 6; + if (GetAGI() < 80) + ac_bonus = level_scaler / 5; + else if (GetAGI() < 85) + ac_bonus = (level_scaler * 2) / 5; + else if (GetAGI() < 90) + ac_bonus = (level_scaler * 3) / 5; + else if (GetAGI() < 100) + ac_bonus = (level_scaler * 4) / 5; + else if (GetAGI() >= 100) + ac_bonus = (level_scaler * 5) / 5; + if (ac_bonus > 16) + ac_bonus = 16; + } + + if (GetRace() == IKSAR) + ac_bonus += EQEmu::Clamp(static_cast(level), 10, 35); + + return ac_bonus; +} + +int Mob::ACSum() +{ + int ac = 0; // this should be base AC whenever shrouds come around + ac += itembonuses.AC; // items + food + tribute + int shield_ac = 0; + if (HasShieldEquiped() && IsClient()) { + auto client = CastToClient(); + auto inst = client->GetInv().GetItem(EQEmu::inventory::slotSecondary); + if (inst) { + if (inst->GetItemRecommendedLevel(true) <= GetLevel()) + shield_ac = inst->GetItemArmorClass(true); + else + shield_ac = client->CalcRecommendedLevelBonus(GetLevel(), inst->GetItemRecommendedLevel(true), inst->GetItemArmorClass(true)); + } + shield_ac += client->GetHeroicSTR() / 10; + } + // EQ math + ac = (ac * 4) / 3; + // anti-twink + if (GetLevel() < 50) + ac = std::min(ac, 25 + 6 * GetLevel()); + ac = std::max(0, ac + GetClassRaceACBonus()); + if (IsNPC()) { + // This is the developer tweaked number + // for the VAST amount of NPCs in EQ this number didn't exceed 600 until recently (PoWar) + // According to the guild hall Combat Dummies, a level 50 classic EQ mob it should be ~115 + // For a 60 PoP mob ~120, 70 OoW ~120 + ac += GetAC(); + Mob *owner = nullptr; + if (IsPet()) + owner = GetOwner(); + else if (CastToNPC()->GetSwarmOwner()) + owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner()); + if (owner) + ac += owner->aabonuses.PetAvoidance + owner->spellbonuses.PetAvoidance + owner->itembonuses.PetAvoidance; + } + auto spell_aa_ac = aabonuses.AC + spellbonuses.AC; + if (EQEmu::ValueWithin(static_cast(GetClass()), NECROMANCER, ENCHANTER)) + ac += GetSkill(EQEmu::skills::SkillDefense) / 2 + spell_aa_ac / 3; + else + ac += GetSkill(EQEmu::skills::SkillDefense) / 3 + spell_aa_ac / 4; + + if (GetAGI() > 70) + ac += GetAGI() / 20; + if (ac < 0) + ac = 0; + + if (IsClient() +#ifdef BOTS + || IsBot() +#endif + ) { + auto softcap = GetACSoftcap(); + auto returns = GetSoftcapReturns(); + int total_aclimitmod = aabonuses.CombatStability + itembonuses.CombatStability + spellbonuses.CombatStability; + if (total_aclimitmod) + softcap = (softcap * (100 + total_aclimitmod)) / 100; + softcap += shield_ac; + if (ac > softcap) { + auto over_cap = ac - softcap; + ac = softcap + (over_cap * returns); + } + Log.Out(Logs::Detail, Logs::Combat, "ACSum ac %d softcap %d returns %f", ac, softcap, returns); + } else { + Log.Out(Logs::Detail, Logs::Combat, "ACSum ac %d", ac); + } + return ac; +} + +int Mob::offense(EQEmu::skills::SkillType skill) +{ + int offense = GetSkill(skill); + int stat_bonus = 0; + if (skill == EQEmu::skills::SkillArchery || skill == EQEmu::skills::SkillThrowing) + stat_bonus = GetDEX(); + else + stat_bonus = GetSTR(); + if (stat_bonus >= 75) + offense += (2 * stat_bonus - 150) / 3; + offense += GetATK(); + return offense; +} + +// this assumes "this" is the defender +// this returns between 0.1 to 2.0 +double Mob::RollD20(double offense, double mitigation) +{ + if (IsClient() && CastToClient()->IsSitting()) + return 2.0; + + // this works pretty good. From Torven's implementation for TAKP + mitigation -= (mitigation - offense) / 2.0; + double diff = offense - mitigation; + double mean = 0; + double mult1, mult2; + + if (offense > 30.0) { + mult1 = offense / 200.0 + 25.75; + if (mitigation / offense < 0.35) + mult1 = mult1 + 1.0; + else if (mitigation / offense > 0.65) + mult1 = mult1 - 1.0; + mult2 = offense / 140 + 18.5; + } else { + mult1 = 11.5 + offense / 2.0; + mult2 = 14.0 + offense / 6.0; + } + + // changing the mean shifts the bell curve + // this was mostly determined by trial and error to find what fit best + if (offense > mitigation) + mean = diff / offense * mult1; + else if (mitigation > offense) + mean = diff / mitigation * mult2; + + double stddev = 8.8; // standard deviation adjusts the height of the bell + // again, trial and error to find what fit best + double theta = 2 * M_PI * zone->random.Real(0.0, 1.0); + double rho = std::sqrt(-2 * std::log(1 - zone->random.Real(0.0, 1.0))); + double d = mean + stddev * rho * std::cos(theta); + + // this combined with the stddev will produce ~15% DI1 and ~15% DI20 when mitigation == offens + d = EQEmu::Clamp(d, -9.5, 9.5); + d += 11.0; + int roll = static_cast(d); + + // the client has an array called damage_factor that is set to this value, so probably what they do + return roll * 0.1; +} + +void Mob::MeleeMitigation(Mob *attacker, int &damage, int base_damage, int offense, EQEmu::skills::SkillType skill, ExtraAttackOptions *opts) +{ + if (damage < 0 || base_damage == 0) return; Mob* defender = this; - float aa_mit = (aabonuses.CombatStability + itembonuses.CombatStability + - spellbonuses.CombatStability) / 100.0f; + auto mitigation = defender->GetMitigationAC(); + if (IsClient() && attacker->IsClient()) + mitigation = mitigation * 80 / 100; // 2004 PvP changes - if (RuleB(Combat, UseIntervalAC)) { - float softcap = (GetSkill(EQEmu::skills::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(); - int PetACBonus = 0; - - if (!IsPet()) - armor = (armor / RuleR(Combat, NPCACFactor)); - - Mob *owner = nullptr; - if (IsPet()) - owner = GetOwner(); - else if ((CastToNPC()->GetSwarmOwner())) - owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner()); - - if (owner) - PetACBonus = owner->aabonuses.PetMeleeMitigation + owner->itembonuses.PetMeleeMitigation + owner->spellbonuses.PetMeleeMitigation; - - armor += spellbonuses.AC + itembonuses.AC + PetACBonus + 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(EQEmu::skills::SkillDefense) + itembonuses.HeroicAGI / 10) / 4.0) + armor + 1; - else - mitigation_rating = ((GetSkill(EQEmu::skills::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(EQEmu::skills::SkillOffense)*1.345)); - else - attack_rating = (attacker->GetATK() + (attacker->GetSkill(EQEmu::skills::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 - int32 myac = GetAC(); - if(opts) { - myac *= (1.0f - opts->armor_pen_percent); - myac -= opts->armor_pen_flat; - } - - if (damage > 0 && myac > 0) { - int acfail=1000; - std::string tmp; - - if (database.GetVariable("ACfail", tmp)) { - acfail = (int) (atof(tmp.c_str()) * 100); - if (acfail>100) acfail=100; - } - - if (acfail<=0 || zone->random.Int(0, 100)>acfail) { - float acreduction=1; - int acrandom=300; - if (database.GetVariable("ACreduction", tmp)) - { - acreduction=atof(tmp.c_str()); - if (acreduction>100) acreduction=100; - } - - if (database.GetVariable("ACrandom", tmp)) - { - acrandom = (int) ((atof(tmp.c_str())+1) * 100); - if (acrandom>10100) acrandom=10100; - } - - if (acreduction>0) { - damage -= (int32) (GetAC() * acreduction/100.0f); - } - if (acrandom>0) { - damage -= (myac * zone->random.Int(0, acrandom) / 10000); - } - if (damage<1) damage=1; - Log.Out(Logs::Detail, Logs::Combat, "AC Damage Reduction: fail chance %d%%. Failed. Reduction %.3f%%, random %d. Resulting damage %d.", acfail, acreduction, acrandom, damage); - } else { - Log.Out(Logs::Detail, Logs::Combat, "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.MeleeMitigationEffect + defender->itembonuses.MeleeMitigationEffect + defender->aabonuses.MeleeMitigationEffect) / 100); + if (opts) { + mitigation *= (1.0f - opts->armor_pen_percent); + mitigation -= opts->armor_pen_flat; } + auto roll = RollD20(offense, mitigation); + + // 168 Defensive -- TODO: I think this is suppose to happen after damage table + // It happening after damage table also means it acts more like negative 185, which might explain + // why the spell has a negative value :P + auto meleemitspell = spellbonuses.MeleeMitigationEffect + itembonuses.MeleeMitigationEffect + aabonuses.MeleeMitigationEffect; + if (GetClass() == WARRIOR && IsClient()) + meleemitspell += 5; + + // +0.5 for rounding + damage = static_cast(roll * static_cast(base_damage) + 0.5); + + if (meleemitspell) + damage = (damage * (100 - meleemitspell)) / 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 = zone->random.Real(0, mit_rating); - float atk_roll = zone->random.Real(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.MeleeMitigationEffect + itembonuses.MeleeMitigationEffect + aabonuses.MeleeMitigationEffect) / 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.MeleeMitigationEffect + itembonuses.MeleeMitigationEffect + aabonuses.MeleeMitigationEffect) / 100.0; - if (GetClass() == WARRIOR) - spellMeleeMit += 0.05; - dmg_bonus -= dmg_bonus * (itembonuses.MeleeMitigation / 100.0); - dmg_interval -= dmg_interval * spellMeleeMit; - - float mit_roll = zone->random.Real(0, mit_rating); - float atk_roll = zone->random.Real(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)); + Log.Out(Logs::Detail, Logs::Attack, "mitigation %d vs offense %d. base %d defensive SPA %d rolled %f damage %d", mitigation, offense, base_damage, meleemitspell, roll, damage); } //Returns the weapon damage against the input mob @@ -964,10 +1096,113 @@ int Mob::GetWeaponDamage(Mob *against, const EQEmu::ItemInstance *weapon_item, u return std::max(0, dmg); } +int Client::DoDamageCaps(int base_damage) +{ + // this is based on a client function that caps melee base_damage + auto level = GetLevel(); + int cap = 0; + if (level >= 125) { + cap = 7 * level; + } else if (level >= 110) { + cap = 6 * level; + } else if (level >= 90) { + cap = 5 * level; + } else if (level >= 70) { + cap = 4 * level; + } else if (level >= 40) { + switch (GetClass()) { + case CLERIC: + case DRUID: + case SHAMAN: + cap = 80; + break; + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + cap = 40; + break; + default: + cap = 200; + break; + } + } else if (level >= 30) { + switch (GetClass()) { + case CLERIC: + case DRUID: + case SHAMAN: + cap = 26; + break; + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + cap = 18; + break; + default: + cap = 60; + break; + } + } else if (level >= 20) { + switch (GetClass()) { + case CLERIC: + case DRUID: + case SHAMAN: + cap = 20; + break; + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + cap = 12; + break; + default: + cap = 30; + break; + } + } else if (level >= 10) { + switch (GetClass()) { + case CLERIC: + case DRUID: + case SHAMAN: + cap = 12; + break; + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + cap = 10; + break; + default: + cap = 14; + break; + } + } else { + switch (GetClass()) { + case CLERIC: + case DRUID: + case SHAMAN: + cap = 9; + break; + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + cap = 6; + break; + default: + cap = 10; // this is where the 20 damage cap comes from + break; + } + } + + return std::min(cap, base_damage); +} + //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, int special) +bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) { if (!other) { SetTarget(nullptr); @@ -1027,7 +1262,7 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b AttackAnimation(skillinuse, Hand, weapon); Log.Out(Logs::Detail, Logs::Combat, "Attacking with %s in slot %d using skill %d", weapon?weapon->GetItem()->Name:"Fist", Hand, skillinuse); - /// Now figure out damage + // Now figure out damage int damage = 0; uint8 mylevel = GetLevel() ? GetLevel() : 1; uint32 hate = 0; @@ -1038,30 +1273,18 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b //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){ + // if we revamp this function be more general, we will have to make sure this isn't + // executed for anything BUT normal melee damage weapons from auto attack + if (Hand == EQEmu::inventory::slotPrimary || Hand == EQEmu::inventory::slotSecondary) + weapon_damage = DoDamageCaps(weapon_damage); auto shield_inc = spellbonuses.ShieldEquipDmgMod + itembonuses.ShieldEquipDmgMod + aabonuses.ShieldEquipDmgMod; if (shield_inc > 0 && HasShieldEquiped() && Hand == EQEmu::inventory::slotPrimary) { weapon_damage = weapon_damage * (100 + shield_inc) / 100; hate = hate * (100 + shield_inc) / 100; } - //Berserker Berserk damage bonus - if(IsBerserk() && GetClass() == BERSERKER){ - int bonus = 3 + GetLevel()/10; //unverified - weapon_damage = weapon_damage * (100+bonus) / 100; - Log.Out(Logs::Detail, Logs::Combat, "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)); + int base_damage = weapon_damage; + int min_damage = 0; // damage bonus CheckIncreaseSkill(skillinuse, other, -15); CheckIncreaseSkill(EQEmu::skills::SkillOffense, other, -15); @@ -1087,8 +1310,7 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b ucDamageBonus = GetWeaponDamageBonus(weapon ? weapon->GetItem() : (const EQEmu::ItemData*) nullptr); - min_hit += (int) ucDamageBonus; - max_hit += (int) ucDamageBonus; + min_damage = ucDamageBonus; hate += ucDamageBonus; } #endif @@ -1098,33 +1320,25 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b ucDamageBonus = GetWeaponDamageBonus(weapon ? weapon->GetItem() : (const EQEmu::ItemData*) nullptr, true); - min_hit += (int) ucDamageBonus; - max_hit += (int) ucDamageBonus; + min_damage = ucDamageBonus; hate += ucDamageBonus; } } // this effect is actually a min cap that happens after the final damage is calculated - min_hit += min_hit * GetMeleeMinDamageMod_SE(skillinuse) / 100; + int min_cap = base_damage * GetMeleeMinDamageMod_SE(skillinuse) / 100; - if(max_hit < min_hit) - max_hit = min_hit; + // damage = mod_client_damage(damage, skillinuse, Hand, weapon, other); - if(RuleB(Combat, UseIntervalAC)) - damage = max_hit; - else - damage = zone->random.Int(min_hit, max_hit); - - damage = mod_client_damage(damage, skillinuse, Hand, weapon, other); - - Log.Out(Logs::Detail, Logs::Combat, "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); + Log.Out(Logs::Detail, Logs::Combat, "Damage calculated to %d (bonus %d, base %d, str %d, skill %d, DMG %d, lv %d)", + damage, min_damage, base_damage, GetSTR(), GetSkill(skillinuse), weapon_damage, mylevel); int hit_chance_bonus = 0; + auto offense = this->offense(skillinuse); // we need this a few times if(opts) { - damage *= opts->damage_percent; - damage += opts->damage_flat; + base_damage *= opts->damage_percent; + base_damage += opts->damage_flat; hate *= opts->hate_percent; hate += opts->hate_flat; hit_chance_bonus += opts->hit_chance; @@ -1149,9 +1363,11 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b Log.Out(Logs::Detail, Logs::Combat, "Avoided damage with code %d", damage); } else { if (other->CheckHitChance(this, skillinuse, hit_chance_bonus)) { - other->MeleeMitigation(this, damage, min_hit, opts); - if (damage > 0) - CommonOutgoingHitSuccess(other, damage, skillinuse,opts); + other->MeleeMitigation(this, damage, base_damage, offense, skillinuse, opts); + if (damage > 0) { + ApplyDamageTable(damage, offense); + CommonOutgoingHitSuccess(other, damage, min_damage, min_cap, skillinuse, opts); + } Log.Out(Logs::Detail, Logs::Combat, "Final damage after all reductions: %d", damage); } else { Log.Out(Logs::Detail, Logs::Combat, "Attack missed. Damage set to 0."); @@ -1177,7 +1393,7 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b SpellFinished(aabonuses.SkillAttackProc[2], other, EQEmu::CastingSlot::Item, 0, -1, spells[aabonuses.SkillAttackProc[2]].ResistDiff); } - other->Damage(this, damage, SPELL_UNKNOWN, skillinuse, true, -1, false, special); + other->Damage(this, damage, SPELL_UNKNOWN, skillinuse, true, -1, false, m_specialattacks); if (IsDead()) return false; @@ -1205,7 +1421,7 @@ void Mob::Heal() SendHPUpdate(); } -void Client::Damage(Mob* other, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic, int special) +void Client::Damage(Mob* other, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic, eSpecialAttacks special) { if(dead || IsCorpse()) return; @@ -1522,7 +1738,7 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, EQEmu::skills::Sk return true; } -bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts, int special) +bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) { int damage = 0; @@ -1620,12 +1836,12 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool AttackAnimation(skillinuse, Hand, &weapon_inst); //basically "if not immune" then do the attack - if((weapon_damage) > 0) { + 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 - uint32 eleBane = 0; + int eleBane = 0; if(weapon){ if(weapon->BaneDmgBody == other->GetBodyType()){ eleBane += weapon->BaneDmgAmt; @@ -1652,71 +1868,36 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool 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 = zone->random.Int((min_dmg+eleBane),(max_dmg+eleBane)); + //damage = mod_npc_damage(damage, skillinuse, Hand, weapon, other); - //check if we're hitting above our max or below it. - if((min_dmg+eleBane) != 0 && damage < (min_dmg+eleBane)) { - Log.Out(Logs::Detail, Logs::Combat, "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)) { - Log.Out(Logs::Detail, Logs::Combat, "Damage (%d) is above max (%d). Setting to max.", damage, (max_dmg+eleBane)); - damage = (max_dmg+eleBane); + int lbase_damage = this->base_damage + eleBane; + int lmin_damage = this->min_damage; + int32 hate = lbase_damage + lmin_damage; + + int min_cap = lbase_damage * GetMeleeMinDamageMod_SE(skillinuse) / 100; + + int hit_chance_bonus = 0; + + if(opts) { + lbase_damage *= opts->damage_percent; + lbase_damage += opts->damage_flat; + hate *= opts->hate_percent; + hate += opts->hate_flat; + hit_chance_bonus += opts->hit_chance; } - 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()) { - Log.Out(Logs::Detail, Logs::Combat, "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, opts) / 100) + GetSkillDmgAmt(skillinuse); - - if(opts) { - damage *= opts->damage_percent; - damage += opts->damage_flat; - hate *= opts->hate_percent; - hate += opts->hate_flat; - } - - Log.Out(Logs::Detail, Logs::Combat, "Generating hate %d towards %s", hate, GetName()); - // now add done damage to the hate list - other->AddToHateList(this, hate); - + if (other->AvoidDamage(this, damage, Hand)) { + if (!bRiposte && damage == -3) + DoRiposte(other); } else { - - int hit_chance_bonus = 0; - - if(opts) { - damage *= opts->damage_percent; - damage += opts->damage_flat; - hate *= opts->hate_percent; - hate += opts->hate_flat; - hit_chance_bonus += opts->hit_chance; - } - - if (other->AvoidDamage(this, damage, Hand)) { - if (!bRiposte && damage == -3) - DoRiposte(other); + if (other->CheckHitChance(this, skillinuse, hit_chance_bonus)) { + other->MeleeMitigation(this, damage, lbase_damage, this->offense(skillinuse), skillinuse, opts); + CommonOutgoingHitSuccess(other, damage, lmin_damage, min_cap, skillinuse, opts); } else { - if (other->CheckHitChance(this, skillinuse, hit_chance_bonus)) { - other->MeleeMitigation(this, damage, min_dmg+eleBane, opts); - CommonOutgoingHitSuccess(other, damage, skillinuse, opts); - } else { - damage = 0; - } + damage = 0; } - other->AddToHateList(this, hate); } + other->AddToHateList(this, hate); Log.Out(Logs::Detail, Logs::Combat, "Final damage against %s: %d", other->GetName(), damage); @@ -1729,7 +1910,7 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool damage = -5; if(GetHP() > 0 && !other->HasDied()) { - other->Damage(this, damage, SPELL_UNKNOWN, skillinuse, true, -1, false, special); // Not avoidable client already had thier chance to Avoid + other->Damage(this, damage, SPELL_UNKNOWN, skillinuse, true, -1, false, m_specialattacks); // Not avoidable client already had thier chance to Avoid } else return false; @@ -1764,7 +1945,7 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool return false; } -void NPC::Damage(Mob* other, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic, int special) { +void NPC::Damage(Mob* other, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic, eSpecialAttacks special) { if(spell_id==0) spell_id = SPELL_UNKNOWN; @@ -2988,7 +3169,7 @@ bool Mob::CheckDoubleAttack() return zone->random.Int(1, 500) <= chance; } -void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, const EQEmu::skills::SkillType skill_used, bool &avoidable, const int8 buffslot, const bool iBuffTic, int special) { +void Mob::CommonDamage(Mob* attacker, int &damage, const uint16 spell_id, const EQEmu::skills::SkillType skill_used, bool &avoidable, const int8 buffslot, const bool iBuffTic, eSpecialAttacks special) { // This method is called with skill_used=ABJURE for Damage Shield damage. bool FromDamageShield = (skill_used == EQEmu::skills::SkillAbjuration); bool ignore_invul = false; @@ -3226,7 +3407,12 @@ void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, cons a->type = SkillDamageTypes[skill_used]; // was 0x1c a->damage = damage; a->spellid = spell_id; - a->special = special; + if (special == eSpecialAttacks::AERampage) + a->special = 1; + else if (special == eSpecialAttacks::Rampage) + a->special = 2; + else + a->special = 0; a->meleepush_xy = attacker ? attacker->GetHeading() * 2.0f : 0.0f; if (RuleB(Combat, MeleePush) && damage > 0 && !IsRooted() && (IsClient() || zone->random.Roll(RuleI(Combat, MeleePushChance)))) { @@ -3709,21 +3895,22 @@ void Mob::TrySpellProc(const EQEmu::ItemInstance *inst, const EQEmu::ItemData *w return; } -void Mob::TryPetCriticalHit(Mob *defender, uint16 skill, int32 &damage) +void Mob::TryPetCriticalHit(Mob *defender, uint16 skill, int &damage) { - if(damage < 1) + 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 + // 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 + // appears to be 70% damage, unsure if changed or just bad info before Mob *owner = nullptr; - float critChance = 0.0f; + int critChance = 0; critChance += RuleI(Combat, MeleeBaseCritChance); - uint32 critMod = 163; + int critMod = 170; - if (damage < 1) //We can't critical hit if we don't hit. + if (damage < 1) // We can't critical hit if we don't hit. return; if (IsPet()) @@ -3736,91 +3923,87 @@ void Mob::TryPetCriticalHit(Mob *defender, uint16 skill, int32 &damage) if (!owner) return; - int32 CritPetChance = owner->aabonuses.PetCriticalHit + owner->itembonuses.PetCriticalHit + owner->spellbonuses.PetCriticalHit; - int32 CritChanceBonus = GetCriticalChanceBonus(skill); + int CritPetChance = + owner->aabonuses.PetCriticalHit + owner->itembonuses.PetCriticalHit + owner->spellbonuses.PetCriticalHit; 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. + // For pets use PetCriticalHit for base chance, pets do not innately critical with without it critChance += CritPetChance; - critChance += critChance*CritChanceBonus/100.0f; } - if(critChance > 0){ - - critChance /= 100; - - if(zone->random.Roll(critChance)) - { - critMod += GetCritDmgMob(skill) * 2; // To account for base crit mod being 200 not 100 + if (critChance > 0) { + if (zone->random.Roll(critChance)) { + critMod += GetCritDmgMob(skill); + damage += 5; damage = (damage * critMod) / 100; - entity_list.FilteredMessageClose_StringID(this, false, 200, - MT_CritMelee, FilterMeleeCrits, CRITICAL_HIT, - GetCleanName(), itoa(damage)); + 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) +void Mob::TryCriticalHit(Mob *defender, uint16 skill, int &damage, int min_damage, ExtraAttackOptions *opts) { - if(damage < 1 || !defender) + if (damage < 1 || !defender) 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()) || (IsNPC() && CastToNPC()->GetSwarmOwner())) { - TryPetCriticalHit(defender,skill,damage); + TryPetCriticalHit(defender, skill, damage); return; } #ifdef BOTS if (this->IsPet() && this->GetOwner() && this->GetOwner()->IsBot()) { - this->TryPetCriticalHit(defender,skill,damage); + this->TryPetCriticalHit(defender, skill, damage); return; } -#endif //BOTS +#endif // BOTS float critChance = 0.0f; bool IsBerskerSPA = false; - //1: Try Slay Undead - if (defender->GetBodyType() == BT_Undead || - defender->GetBodyType() == BT_SummonedUndead || defender->GetBodyType() == BT_Vampire) { + // 1: Try Slay Undead + if (defender->GetBodyType() == BT_Undead || defender->GetBodyType() == BT_SummonedUndead || + defender->GetBodyType() == BT_Vampire) { int32 SlayRateBonus = aabonuses.SlayUndead[0] + itembonuses.SlayUndead[0] + spellbonuses.SlayUndead[0]; if (SlayRateBonus) { float slayChance = static_cast(SlayRateBonus) / 10000.0f; if (zone->random.Roll(slayChance)) { - int32 SlayDmgBonus = aabonuses.SlayUndead[1] + itembonuses.SlayUndead[1] + spellbonuses.SlayUndead[1]; - damage = (damage * SlayDmgBonus * 2.25) / 100; + int32 SlayDmgBonus = + aabonuses.SlayUndead[1] + itembonuses.SlayUndead[1] + spellbonuses.SlayUndead[1]; + damage += 5; + damage = (damage * SlayDmgBonus) / 100; if (GetGender() == 1) // female - entity_list.FilteredMessageClose_StringID(this, false, 200, - MT_CritMelee, FilterMeleeCrits, FEMALE_SLAYUNDEAD, - GetCleanName(), itoa(damage)); + entity_list.FilteredMessageClose_StringID( + this, false, 200, MT_CritMelee, FilterMeleeCrits, FEMALE_SLAYUNDEAD, + GetCleanName(), itoa(damage + min_damage)); else // males and neuter I guess - entity_list.FilteredMessageClose_StringID(this, false, 200, - MT_CritMelee, FilterMeleeCrits, MALE_SLAYUNDEAD, - GetCleanName(), itoa(damage)); + entity_list.FilteredMessageClose_StringID( + this, false, 200, MT_CritMelee, FilterMeleeCrits, MALE_SLAYUNDEAD, + GetCleanName(), itoa(damage + min_damage)); return; } } } - //2: Try Melee Critical + // 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. + // 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); + critChance += RuleI(Combat, ClientBaseCritChance); if (spellbonuses.BerserkSPA || itembonuses.BerserkSPA || aabonuses.BerserkSPA) - IsBerskerSPA = true; + IsBerskerSPA = true; - if (((GetClass() == WARRIOR || GetClass() == BERSERKER) && GetLevel() >= 12) || IsBerskerSPA) { + if (((GetClass() == WARRIOR || GetClass() == BERSERKER) && GetLevel() >= 12) || IsBerskerSPA) { if (IsBerserk() || IsBerskerSPA) critChance += RuleI(Combat, BerserkBaseCritChance); else @@ -3833,7 +4016,8 @@ void Mob::TryCriticalHit(Mob *defender, uint16 skill, int32 &damage, ExtraAttack if (skill == EQEmu::skills::SkillArchery && GetClass() == RANGER && GetSkill(EQEmu::skills::SkillArchery) >= 65) critChance += 6; - if (skill == EQEmu::skills::SkillThrowing && GetClass() == ROGUE && GetSkill(EQEmu::skills::SkillThrowing) >= 65) { + if (skill == EQEmu::skills::SkillThrowing && GetClass() == ROGUE && + GetSkill(EQEmu::skills::SkillThrowing) >= 65) { critChance += RuleI(Combat, RogueCritThrowingChance); deadlyChance = RuleI(Combat, RogueDeadlyStrikeChance); deadlyMod = RuleI(Combat, RogueDeadlyStrikeMod); @@ -3843,44 +4027,45 @@ void Mob::TryCriticalHit(Mob *defender, uint16 skill, int32 &damage, ExtraAttack 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 + // 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; + critChance += (float(GetDEX() - 255) / 500.0f) + 2.0f; + critChance += critChance * (float)CritChanceBonus / 100.0f; } - if(opts) { + if (opts) { critChance *= opts->crit_percent; critChance += opts->crit_flat; } - if(critChance > 0) { + if (critChance > 0) { critChance /= 100; - if(zone->random.Roll(critChance)) - { - uint32 critMod = 200; + if (zone->random.Roll(critChance)) { + if (TryFinishingBlow(defender, static_cast(skill), damage)) + return; + int critMod = 170; bool crip_success = false; int32 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. + // 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() || IsBerskerSPA)) { if (!IsBerserk() && !IsBerskerSPA) - critChance *= float(CripplingBlowChance)/100.0f; + critChance *= float(CripplingBlowChance) / 100.0f; - if ((IsBerserk() || IsBerskerSPA) || zone->random.Roll(critChance)) { - critMod = 400; + if ((IsBerserk() || IsBerskerSPA) || zone->random.Roll(critChance)) crip_success = true; - } } - critMod += GetCritDmgMob(skill) * 2; // To account for base crit mod being 200 not 100 + critMod += GetCritDmgMob(skill); + damage += 5; + int ogdmg = damage; damage = damage * critMod / 100; bool deadlySuccess = false; @@ -3892,33 +4077,37 @@ void Mob::TryCriticalHit(Mob *defender, uint16 skill, int32 &damage, ExtraAttack } if (crip_success) { - entity_list.FilteredMessageClose_StringID(this, false, 200, - MT_CritMelee, FilterMeleeCrits, CRIPPLING_BLOW, - GetCleanName(), itoa(damage)); + damage += ogdmg * 119 / 100; // the damage_e page says it's a ~1.192 increase of dmg before mod + entity_list.FilteredMessageClose_StringID(this, false, 200, MT_CritMelee, + FilterMeleeCrits, CRIPPLING_BLOW, + GetCleanName(), itoa(damage + min_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)){ + // 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)); + entity_list.FilteredMessageClose_StringID(this, false, 200, MT_CritMelee, + FilterMeleeCrits, DEADLY_STRIKE, + GetCleanName(), itoa(damage + min_damage)); } else { - entity_list.FilteredMessageClose_StringID(this, false, 200, - MT_CritMelee, FilterMeleeCrits, CRITICAL_HIT, - GetCleanName(), itoa(damage)); + entity_list.FilteredMessageClose_StringID(this, false, 200, MT_CritMelee, + FilterMeleeCrits, CRITICAL_HIT, + GetCleanName(), itoa(damage + min_damage)); } } } } -bool Mob::TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse) +bool Mob::TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse, int &damage) { - if (defender && !defender->IsClient() && defender->GetHPRatio() < 10){ + // base2 of FinishingBlowLvl is the HP limit (cur / max) * 1000, 10% is listed as 100 + if (defender && !defender->IsClient() && defender->GetHPRatio() < 10) { - uint32 FB_Dmg = aabonuses.FinishingBlow[1] + spellbonuses.FinishingBlow[1] + itembonuses.FinishingBlow[1]; + uint32 FB_Dmg = + aabonuses.FinishingBlow[1] + spellbonuses.FinishingBlow[1] + itembonuses.FinishingBlow[1]; uint32 FB_Level = 0; FB_Level = aabonuses.FinishingBlowLvl[0]; @@ -3927,12 +4116,14 @@ bool Mob::TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse) else if (FB_Level < itembonuses.FinishingBlowLvl[0]) FB_Level = itembonuses.FinishingBlowLvl[0]; - //Proc Chance value of 500 = 5% - uint32 ProcChance = (aabonuses.FinishingBlow[0] + spellbonuses.FinishingBlow[0] + spellbonuses.FinishingBlow[0])/10; + // Proc Chance value of 500 = 5% + uint32 ProcChance = + (aabonuses.FinishingBlow[0] + spellbonuses.FinishingBlow[0] + spellbonuses.FinishingBlow[0]) / 10; - if(FB_Level && FB_Dmg && (defender->GetLevel() <= FB_Level) && (ProcChance >= zone->random.Int(0, 1000))){ + if (FB_Level && FB_Dmg && (defender->GetLevel() <= FB_Level) && + (ProcChance >= zone->random.Int(0, 1000))) { entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FINISHING_BLOW, GetName()); - DoSpecialAttackDamage(defender, skillinuse, FB_Dmg, 1, -1, 10, false, false); + damage = FB_Dmg; return true; } } @@ -3991,21 +4182,10 @@ void Mob::DoRiposte(Mob *defender) } } -void Mob::ApplyMeleeDamageBonus(uint16 skill, int32 &damage,ExtraAttackOptions *opts){ - - 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; - Log.Out(Logs::Detail, Logs::Combat, "Damage bonus: %d percent from ATK and STR bonuses.", (dmgbonusmod/100)); - damage += (damage*dmgbonusmod/10000); - } - } - +void Mob::ApplyMeleeDamageBonus(uint16 skill, int &damage, ExtraAttackOptions *opts) +{ int dmgbonusmod = 0; - + dmgbonusmod += GetMeleeDamageMod_SE(skill); if (opts) dmgbonusmod += opts->melee_damage_bonus_flat; @@ -4025,48 +4205,168 @@ bool Mob::HasDied() { return Result; } -uint16 Mob::GetDamageTable(EQEmu::skills::SkillType skillinuse) +const DamageTable &Mob::GetDamageTable() const { - if(GetLevel() <= 51) - { - uint32 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; + static const DamageTable dmg_table[] = { + {210, 49, 105}, // 1-50 + {245, 35, 80}, // 51 + {245, 35, 80}, // 52 + {245, 35, 80}, // 53 + {245, 35, 80}, // 54 + {245, 35, 80}, // 55 + {265, 28, 70}, // 56 + {265, 28, 70}, // 57 + {265, 28, 70}, // 58 + {265, 28, 70}, // 59 + {285, 23, 65}, // 60 + {285, 23, 65}, // 61 + {285, 23, 65}, // 62 + {290, 21, 60}, // 63 + {290, 21, 60}, // 64 + {295, 19, 55}, // 65 + {295, 19, 55}, // 66 + {300, 19, 55}, // 67 + {300, 19, 55}, // 68 + {300, 19, 55}, // 69 + {305, 19, 55}, // 70 + {305, 19, 55}, // 71 + {310, 17, 50}, // 72 + {310, 17, 50}, // 73 + {310, 17, 50}, // 74 + {315, 17, 50}, // 75 + {315, 17, 50}, // 76 + {325, 17, 45}, // 77 + {325, 17, 45}, // 78 + {325, 17, 45}, // 79 + {335, 17, 45}, // 80 + {335, 17, 45}, // 81 + {345, 17, 45}, // 82 + {345, 17, 45}, // 83 + {345, 17, 45}, // 84 + {355, 17, 45}, // 85 + {355, 17, 45}, // 86 + {365, 17, 45}, // 87 + {365, 17, 45}, // 88 + {365, 17, 45}, // 89 + {375, 17, 45}, // 90 + {375, 17, 45}, // 91 + {380, 17, 45}, // 92 + {380, 17, 45}, // 93 + {380, 17, 45}, // 94 + {385, 17, 45}, // 95 + {385, 17, 45}, // 96 + {390, 17, 45}, // 97 + {390, 17, 45}, // 98 + {390, 17, 45}, // 99 + {395, 17, 45}, // 100 + {395, 17, 45}, // 101 + {400, 17, 45}, // 102 + {400, 17, 45}, // 103 + {400, 17, 45}, // 104 + {405, 17, 45} // 105 + }; - if(ret_table < 100) - return 100; + static const DamageTable mnk_table[] = { + {220, 45, 100}, // 1-50 + {245, 35, 80}, // 51 + {245, 35, 80}, // 52 + {245, 35, 80}, // 53 + {245, 35, 80}, // 54 + {245, 35, 80}, // 55 + {285, 23, 65}, // 56 + {285, 23, 65}, // 57 + {285, 23, 65}, // 58 + {285, 23, 65}, // 59 + {290, 21, 60}, // 60 + {290, 21, 60}, // 61 + {290, 21, 60}, // 62 + {295, 19, 55}, // 63 + {295, 19, 55}, // 64 + {300, 17, 50}, // 65 + {300, 17, 50}, // 66 + {310, 17, 50}, // 67 + {310, 17, 50}, // 68 + {310, 17, 50}, // 69 + {320, 17, 50}, // 70 + {320, 17, 50}, // 71 + {325, 15, 45}, // 72 + {325, 15, 45}, // 73 + {325, 15, 45}, // 74 + {330, 15, 45}, // 75 + {330, 15, 45}, // 76 + {335, 15, 40}, // 77 + {335, 15, 40}, // 78 + {335, 15, 40}, // 79 + {345, 15, 40}, // 80 + {345, 15, 40}, // 81 + {355, 15, 40}, // 82 + {355, 15, 40}, // 83 + {355, 15, 40}, // 84 + {365, 15, 40}, // 85 + {365, 15, 40}, // 86 + {375, 15, 40}, // 87 + {375, 15, 40}, // 88 + {375, 15, 40}, // 89 + {385, 15, 40}, // 90 + {385, 15, 40}, // 91 + {390, 15, 40}, // 92 + {390, 15, 40}, // 93 + {390, 15, 40}, // 94 + {395, 15, 40}, // 95 + {395, 15, 40}, // 96 + {400, 15, 40}, // 97 + {400, 15, 40}, // 98 + {400, 15, 40}, // 99 + {405, 15, 40}, // 100 + {405, 15, 40}, // 101 + {410, 15, 40}, // 102 + {410, 15, 40}, // 103 + {410, 15, 40}, // 104 + {415, 15, 40}, // 105 + }; - return ret_table; - } - else if(GetLevel() >= 90) - { - if(GetClass() == MONK) - return 379; - else - return 345; - } - else - { - uint32 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]; - } + bool monk = GetClass() == MONK; + bool melee = IsWarriorClass(); + // tables caped at 105 for now -- future proofed for a while at least :P + int level = std::min(static_cast(GetLevel()), 105); + + if (!melee || (!monk && level < 51)) + return dmg_table[0]; + + if (monk && level < 51) + return mnk_table[0]; + + auto &which = monk ? mnk_table : dmg_table; + return which[level - 50]; +} + +void Mob::ApplyDamageTable(int &damage, int offense) +{ + // someone may want to add this to custom servers, can remove this if that's the case + if (!IsClient() +#ifdef BOTS + && !IsBot() +#endif + ) + return; + // this was parsed, but we do see the min of 10 and the normal minus factor is 105, so makes sense + if (offense < 115) + return; + + auto &damage_table = GetDamageTable(); + + if (zone->random.Roll(damage_table.chance)) + return; + + int basebonus = offense - damage_table.minusfactor; + basebonus = std::max(10, basebonus / 2); + int extrapercent = zone->random.Roll0(basebonus); + int percent = std::min(100 + extrapercent, damage_table.max_extra); + damage = (damage * percent) / 100; + + if (IsWarriorClass() && GetLevel() > 54) + damage++; + Log.Out(Logs::Detail, Logs::Attack, "Damage table applied %d (max %d)", percent, damage_table.max_extra); } void Mob::TrySkillProc(Mob *on, uint16 skill, uint16 ReuseTime, bool Success, uint16 hand, bool IsDefensive) @@ -4368,14 +4668,108 @@ int32 Mob::RuneAbsorb(int32 damage, uint16 type) return damage; } -void Mob::CommonOutgoingHitSuccess(Mob* defender, int32 &damage, EQEmu::skills::SkillType skillInUse, ExtraAttackOptions *opts) +// min_damage is the damage bonus, we need to pass it along for a bit +// min_mod is the min hit cap, which needs to be calculated before we call this, but applied here +void Mob::CommonOutgoingHitSuccess(Mob* defender, int &damage, int min_damage, int min_mod, EQEmu::skills::SkillType skillInUse, ExtraAttackOptions *opts) { if (!defender) return; + // BER weren't parsing the halving + if (skillInUse == EQEmu::skills::SkillArchery || + (skillInUse == EQEmu::skills::SkillThrowing && GetClass() != BERSERKER)) + damage /= 2; + + if (damage < 1) + damage = 1; + + if (skillInUse == EQEmu::skills::SkillArchery) { + int bonus = aabonuses.ArcheryDamageModifier + itembonuses.ArcheryDamageModifier + spellbonuses.ArcheryDamageModifier; + damage += damage * bonus / 100; + int headshot = TryHeadShot(defender, skillInUse); + if (headshot > 0) + damage = headshot; + } + + // this parses after damage table + if (skillInUse == EQEmu::skills::SkillArchery && GetClass() == RANGER && GetLevel() > 50) { + if (defender->IsNPC() && !defender->IsMoving() && !defender->IsRooted()) { + damage *= 2; + Message_StringID(MT_CritMelee, BOW_DOUBLE_DAMAGE); + } + } + + int extra_mincap = 0; + if (skillInUse == EQEmu::skills::SkillBackstab) { + extra_mincap = GetLevel() < 7 ? 7 : GetLevel(); + if (GetLevel() >= 60) + extra_mincap = GetLevel() * 2; + else if (GetLevel() > 50) + extra_mincap = GetLevel() * 3 / 2; + if (IsSpecialAttack(eSpecialAttacks::ChaoticStab)) { + damage = extra_mincap; + } else { + int ass = TryAssassinate(defender, skillInUse, 10000); // reusetime shouldn't have an effect .... + if (ass > 0) + damage = ass; + } + } else if (skillInUse == EQEmu::skills::SkillFrenzy && GetClass() == BERSERKER && GetLevel() > 50) { + extra_mincap = 4 * GetLevel() / 5; + } + + // this has some weird ordering + // Seems the crit message is generated before some of them :P + + // worn item +skill dmg, SPA 220, 418. Live has a normalized version that should be here too + min_damage += GetSkillDmgAmt(skillInUse); + + // shielding mod2 + if (defender->itembonuses.MeleeMitigation) + min_damage -= min_damage * defender->itembonuses.MeleeMitigation / 100; + ApplyMeleeDamageBonus(skillInUse, damage, opts); - damage += (damage * defender->GetSkillDmgTaken(skillInUse, opts) / 100) + (GetSkillDmgAmt(skillInUse) + defender->GetFcDamageAmtIncoming(this, 0, true, skillInUse)); - TryCriticalHit(defender, skillInUse, damage,opts); + min_mod = std::max(min_mod, extra_mincap); + if (min_mod && damage < min_mod) // SPA 186 + damage = min_mod; + + TryCriticalHit(defender, skillInUse, damage, min_damage, opts); + + damage += min_damage; + if (IsClient()) { + int extra = 0; + switch (skillInUse) { + case EQEmu::skills::SkillThrowing: + case EQEmu::skills::SkillArchery: + extra = CastToClient()->GetHeroicDEX() / 10; + break; + default: + extra = CastToClient()->GetHeroicSTR() / 10; + break; + } + damage += extra; + } + + // this appears where they do special attack dmg mods + int spec_mod = 0; + if (IsSpecialAttack(eSpecialAttacks::Rampage)) { + int mod = GetSpecialAbilityParam(SPECATK_RAMPAGE, 2); + if (mod > 0) + spec_mod = mod; + if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) { + int spell = spellbonuses.PC_Pet_Rampage[1] + itembonuses.PC_Pet_Rampage[1] + aabonuses.PC_Pet_Rampage[1]; + if (spell > spec_mod) + spec_mod = spell; + } + } else if (IsSpecialAttack(eSpecialAttacks::AERampage)) { + int mod = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 2); + if (mod > 0) + spec_mod = mod; + } + if (spec_mod > 0) + damage = (damage * spec_mod) / 100; + + damage += (damage * defender->GetSkillDmgTaken(skillInUse, opts) / 100) + (defender->GetFcDamageAmtIncoming(this, 0, true, skillInUse)); + CheckNumHitsRemaining(NumHit::OutgoingHitSuccess); } @@ -4533,9 +4927,9 @@ void NPC::SetAttackTimer() // Mob's delay set to 20, weapon set to 21, delay 20 int speed = 0; if (RuleB(Spells, Jun182014HundredHandsRevamp)) - speed = static_cast(((attack_delay / haste_mod) + ((hhe / 1000.0f) * (attack_delay / haste_mod))) * 100); + speed = static_cast((attack_delay / haste_mod) + ((hhe / 1000.0f) * (attack_delay / haste_mod))); else - speed = static_cast(((attack_delay / haste_mod) + ((hhe / 100.0f) * attack_delay)) * 100); + speed = static_cast((attack_delay / haste_mod) + ((hhe / 100.0f) * attack_delay)); for (int i = EQEmu::inventory::slotRange; i <= EQEmu::inventory::slotSecondary; i++) { //pick a timer @@ -4632,7 +5026,7 @@ bool Client::CheckDualWield() return zone->random.Int(1, 375) <= chance; } -void Mob::DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts, int special) +void Mob::DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts) { if (!target) return; @@ -4640,10 +5034,9 @@ void Mob::DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts, int spec if (RuleB(Combat, UseLiveCombatRounds)) { // A "quad" on live really is just a successful dual wield where both double attack // The mobs that could triple lost the ability to when the triple attack skill was added in - Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts, special); + Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts); if (CanThisClassDoubleAttack() && CheckDoubleAttack()){ - Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts, special); - + Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts); if ((IsPet() || IsTempPet()) && IsPetOwnerClient()){ int chance = spellbonuses.PC_Pet_Flurry + itembonuses.PC_Pet_Flurry + aabonuses.PC_Pet_Flurry; if (chance && zone->random.Roll(chance)) @@ -4656,14 +5049,14 @@ void Mob::DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts, int spec if (IsNPC()) { int16 n_atk = CastToNPC()->GetNumberOfAttacks(); if (n_atk <= 1) { - Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts, special); + Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts); } else { for (int i = 0; i < n_atk; ++i) { - Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts, special); + Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts); } } } else { - Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts, special); + Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts); } // we use this random value in three comparisons with different @@ -4674,21 +5067,21 @@ void Mob::DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts, int spec // check double attack, this is NOT the same rules that clients use... && RandRoll < (GetLevel() + NPCDualAttackModifier)) { - Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts, special); + Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts); // 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, EQEmu::inventory::slotPrimary, false, false, false, opts, special); + Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts); // now lets check the quad attack if (GetSpecialAbility(SPECATK_QUAD) && RandRoll < (GetLevel() + NPCQuadAttackModifier)) { - Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts, special); + Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts); } } } } -void Mob::DoOffHandAttackRounds(Mob *target, ExtraAttackOptions *opts, int special) +void Mob::DoOffHandAttackRounds(Mob *target, ExtraAttackOptions *opts) { if (!target) return; @@ -4698,9 +5091,9 @@ void Mob::DoOffHandAttackRounds(Mob *target, ExtraAttackOptions *opts, int speci (RuleB(Combat, UseLiveCombatRounds) && GetSpecialAbility(SPECATK_QUAD))) || GetEquipment(EQEmu::textures::weaponSecondary) != 0) { if (CheckDualWield()) { - Attack(target, EQEmu::inventory::slotSecondary, false, false, false, opts, special); + Attack(target, EQEmu::inventory::slotSecondary, false, false, false, opts); if (CanThisClassDoubleAttack() && GetLevel() > 35 && CheckDoubleAttack()){ - Attack(target, EQEmu::inventory::slotSecondary, false, false, false, opts, special); + Attack(target, EQEmu::inventory::slotSecondary, false, false, false, opts); if ((IsPet() || IsTempPet()) && IsPetOwnerClient()){ int chance = spellbonuses.PC_Pet_Flurry + itembonuses.PC_Pet_Flurry + aabonuses.PC_Pet_Flurry; diff --git a/zone/beacon.h b/zone/beacon.h index cd66269bb..b79ed318c 100644 --- a/zone/beacon.h +++ b/zone/beacon.h @@ -35,9 +35,9 @@ public: //abstract virtual function implementations requird by base abstract class virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill) { return true; } - virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, int special = 0) { return; } + virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None) { return; } virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false, - ExtraAttackOptions *opts = nullptr, int special = 0) { return false; } + ExtraAttackOptions *opts = nullptr) { return false; } virtual bool HasRaid() { return false; } virtual bool HasGroup() { return false; } virtual Raid* GetRaid() { return 0; } diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index beae3995d..3acf6cb1d 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -46,6 +46,7 @@ void Mob::CalcBonuses() CalcMaxHP(); CalcMaxMana(); SetAttackTimer(); + CalcAC(); rooted = FindType(SE_Root); } @@ -669,6 +670,10 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) } switch (effect) { + case SE_ACv2: + case SE_ArmorClass: + newbon->AC += base1; + break; // Note: AA effects that use accuracy are skill limited, while spell effect is not. case SE_Accuracy: // Bad data or unsupported new skill @@ -1527,9 +1532,6 @@ void Mob::CalcSpellBonuses(StatBonuses* newbon) } } - // THIS IS WRONG, leaving for now - //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. } diff --git a/zone/bot.cpp b/zone/bot.cpp index 7b0739b47..ac6a6ddd7 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -1992,110 +1992,6 @@ bool Bot::CheckBotDoubleAttack(bool tripleAttack) { return false; } -void Bot::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills::SkillType skillinuse, int16 chance_mod, int16 focus, bool CanRiposte, int ReuseTime) { - if (!CanDoSpecialAttack(other)) - return; - - //For spells using skill value 98 (feral swipe ect) server sets this to 67 automatically. - if (skillinuse == EQEmu::skills::SkillBegging) - skillinuse = EQEmu::skills::SkillOffense; - - int damage = 0; - uint32 hate = 0; - int Hand = EQEmu::inventory::slotPrimary; - if (hate == 0 && weapon_damage > 1) - hate = weapon_damage; - - if(weapon_damage > 0) { - if(GetClass() == BERSERKER) { - int bonus = (3 + GetLevel( )/ 10); - weapon_damage = (weapon_damage * (100 + bonus) / 100); - } - - int32 min_hit = 1; - int32 max_hit = ((2 * weapon_damage * GetDamageTable(skillinuse)) / 100); - if(GetLevel() >= 28 && IsWarriorClass()) { - int ucDamageBonus = GetWeaponDamageBonus((const EQEmu::ItemData*) nullptr); - min_hit += (int) ucDamageBonus; - max_hit += (int) ucDamageBonus; - hate += ucDamageBonus; - } - - ApplySpecialAttackMod(skillinuse, max_hit, min_hit); - 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 = zone->random.Int(min_hit, max_hit); - - if (other->AvoidDamage(this, damage, CanRiposte ? EQEmu::inventory::slotRange : EQEmu::inventory::slotPrimary)) { // MainRange excludes ripo, primary doesn't have any extra behavior - if (damage == -3) { - DoRiposte(other); - if (HasDied()) - return; - } - } else { - if (other->CheckHitChance(this, skillinuse, chance_mod)) { - other->MeleeMitigation(this, damage, min_hit); - if (damage > 0) { - damage += damage*focus/100; - ApplyMeleeDamageBonus(skillinuse, damage); - damage += other->GetFcDamageAmtIncoming(this, 0, true, skillinuse); - damage += ((itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse)); - TryCriticalHit(other, skillinuse, damage, nullptr); - } - } else { - damage = 0; - } - } - } - else - damage = -5; - - if (skillinuse == EQEmu::skills::SkillBash){ - const EQEmu::ItemInstance* inst = GetBotItem(EQEmu::inventory::slotSecondary); - const EQEmu::ItemData* botweapon = 0; - if(inst) - botweapon = inst->GetItem(); - - if(botweapon) { - if (botweapon->ItemType == EQEmu::item::ItemTypeShield) - hate += botweapon->AC; - - hate = (hate * (100 + GetFuriousBash(botweapon->Focus.Effect)) / 100); - } - } - - other->AddToHateList(this, hate); - - bool CanSkillProc = true; - if (skillinuse == EQEmu::skills::SkillOffense){ //Hack to allow damage to display. - skillinuse = EQEmu::skills::SkillTigerClaw; //'strike' your opponent - Arbitrary choice for message. - CanSkillProc = false; //Disable skill procs - } - - other->Damage(this, damage, SPELL_UNKNOWN, skillinuse); - if (HasDied()) - return; - - if (damage > 0) - CheckNumHitsRemaining(NumHit::OutgoingHitSuccess); - - if ((skillinuse == EQEmu::skills::SkillDragonPunch) && GetAA(aaDragonPunch) && zone->random.Int(0, 99) < 25){ - SpellFinished(904, other, EQEmu::CastingSlot::Item, 0, -1, spells[904].ResistDiff); - other->Stun(100); - } - - if (CanSkillProc && HasSkillProcs()) - TrySkillProc(other, skillinuse, ReuseTime); - - if (CanSkillProc && (damage > 0) && HasSkillProcSuccess()) - TrySkillProc(other, skillinuse, ReuseTime, true); -} - void Bot::ApplySpecialAttackMod(EQEmu::skills::SkillType skill, int32 &dmg, int32 &mindmg) { int item_slot = -1; //1: Apply bonus from AC (BOOT/SHIELD/HANDS) est. 40AC=6dmg @@ -3664,7 +3560,7 @@ bool Bot::Death(Mob *killerMob, int32 damage, uint16 spell_id, EQEmu::skills::Sk return true; } -void Bot::Damage(Mob *from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic, int special) { +void Bot::Damage(Mob *from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic, eSpecialAttacks special) { if(spell_id == 0) spell_id = SPELL_UNKNOWN; @@ -3710,7 +3606,7 @@ void Bot::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b Mob::AddToHateList(other, hate, damage, iYellForHelp, bFrenzy, iBuffTic); } -bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts, int special) { +bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) { if (!other) { SetTarget(nullptr); Log.Out(Logs::General, Logs::Error, "A null Mob object was passed to Bot::Attack for evaluation!"); @@ -3778,25 +3674,7 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b //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(berserk && (GetClass() == BERSERKER)){ - int bonus = (3 + GetLevel() / 10); //unverified - weapon_damage = (weapon_damage * (100 + bonus) / 100); - Log.Out(Logs::Detail, Logs::Combat, "Berserker damage bonus increases DMG to %d", weapon_damage); - } - - //try a finishing blow.. if successful end the attack - if(TryFinishingBlow(other, skillinuse)) - return true; - - //damage formula needs some work - 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)); + int min_damage = 0; // *************************************************************** // *** Calculate the damage bonus, if applicable, for this hit *** @@ -3814,8 +3692,7 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b // Damage bonuses apply only to hits from the main hand (Hand == MainPrimary) 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 EQEmu::ItemData*) nullptr); - min_hit += (int) ucDamageBonus; - max_hit += (int) ucDamageBonus; + min_damage = ucDamageBonus; hate += ucDamageBonus; } #endif @@ -3823,28 +3700,21 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b if (Hand == EQEmu::inventory::slotSecondary) { if (aabonuses.SecondaryDmgInc || itembonuses.SecondaryDmgInc || spellbonuses.SecondaryDmgInc){ ucDamageBonus = GetWeaponDamageBonus(weapon ? weapon->GetItem() : (const EQEmu::ItemData*) nullptr); - min_hit += (int) ucDamageBonus; - max_hit += (int) ucDamageBonus; + min_damage = ucDamageBonus; hate += ucDamageBonus; } } - min_hit = (min_hit * GetMeleeMinDamageMod_SE(skillinuse) / 100); + int min_cap = (base_damage * GetMeleeMinDamageMod_SE(skillinuse) / 100); - if(max_hit < min_hit) - max_hit = min_hit; + Log.Out(Logs::Detail, Logs::Combat, "Damage calculated to %d (bonus %d, base %d, str %d, skill %d, DMG %d, lv %d)", + damage, min_damage, base_damage, GetSTR(), GetSkill(skillinuse), weapon_damage, mylevel); - if(RuleB(Combat, UseIntervalAC)) - damage = max_hit; - else - damage = zone->random.Int(min_hit, max_hit); - - Log.Out(Logs::Detail, Logs::Combat, "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, GetLevel()); + auto offense = this->offense(skillinuse); if(opts) { - damage *= opts->damage_percent; - damage += opts->damage_flat; + base_damage *= opts->damage_percent; + base_damage += opts->damage_flat; hate *= opts->hate_percent; hate += opts->hate_flat; } @@ -3866,10 +3736,11 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b } } else { if (other->CheckHitChance(this, skillinuse)) { - other->MeleeMitigation(this, damage, min_hit, opts); - ApplyMeleeDamageBonus(skillinuse, damage); - damage += ((itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse)); - TryCriticalHit(other, skillinuse, damage, opts); + other->MeleeMitigation(this, damage, base_damage, offense, skillinuse, opts); + if (damage > 0) { + ApplyDamageTable(damage, offense); + CommonOutgoingHitSuccess(other, damage, min_damage, min_cap, skillinuse, opts); + } } else { damage = 0; } @@ -3896,39 +3767,7 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b if (damage > 0) CheckNumHitsRemaining(NumHit::OutgoingHitSuccess); - //break invis when you attack - if(invisible) { - Log.Out(Logs::Detail, Logs::Combat, "Removing invisibility due to melee attack."); - BuffFadeByEffect(SE_Invisibility); - BuffFadeByEffect(SE_Invisibility2); - invisible = false; - } - - if(invisible_undead) { - Log.Out(Logs::Detail, Logs::Combat, "Removing invisibility vs. undead due to melee attack."); - BuffFadeByEffect(SE_InvisVsUndead); - BuffFadeByEffect(SE_InvisVsUndead2); - invisible_undead = false; - } - - if(invisible_animals){ - Log.Out(Logs::Detail, Logs::Combat, "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); - } - + CommonBreakInvisibleFromCombat(); if (spellbonuses.NegateIfCombat) BuffFadeByEffect(SE_NegateIfCombat); @@ -4816,21 +4655,24 @@ int Bot::GetHandToHandDamage(void) { return 2; } -bool Bot::TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse) { +bool Bot::TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse, int &damage) +{ if (!defender) return false; if (aabonuses.FinishingBlow[1] && !defender->IsClient() && defender->GetHPRatio() < 10) { - uint32 chance = (aabonuses.FinishingBlow[0] / 10); - uint32 damage = aabonuses.FinishingBlow[1]; - uint16 levelreq = aabonuses.FinishingBlowLvl[0]; - if(defender->GetLevel() <= levelreq && (chance >= zone->random.Int(0, 1000))){ - Log.Out(Logs::Detail, Logs::Combat, "Landed a finishing blow: levelreq at %d, other level %d", levelreq , defender->GetLevel()); + int chance = (aabonuses.FinishingBlow[0] / 10); + int fb_damage = aabonuses.FinishingBlow[1]; + int levelreq = aabonuses.FinishingBlowLvl[0]; + if (defender->GetLevel() <= levelreq && (chance >= zone->random.Int(0, 1000))) { + Log.Out(Logs::Detail, Logs::Combat, "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); + damage = fb_damage; return true; } else { - Log.Out(Logs::Detail, Logs::Combat, "FAILED a finishing blow: levelreq at %d, other level %d", levelreq , defender->GetLevel()); + Log.Out(Logs::Detail, Logs::Combat, "FAILED a finishing blow: levelreq at %d, other level %d", + levelreq, defender->GetLevel()); return false; } } @@ -4858,6 +4700,92 @@ void Bot::DoRiposte(Mob* defender) { } } +int Bot::GetBaseSkillDamage(EQEmu::skills::SkillType skill, Mob *target) +{ + int base = EQEmu::skills::GetBaseDamage(skill); + auto skill_level = GetSkill(skill); + switch (skill) { + case EQEmu::skills::SkillDragonPunch: + case EQEmu::skills::SkillEagleStrike: + case EQEmu::skills::SkillTigerClaw: + if (skill_level >= 25) + base++; + if (skill_level >= 75) + base++; + if (skill_level >= 125) + base++; + if (skill_level >= 175) + base++; + return base; + case EQEmu::skills::SkillFrenzy: + if (GetBotItem(EQEmu::inventory::slotSecondary)) { + if (GetLevel() > 15) + base += GetLevel() - 15; + if (base > 23) + base = 23; + if (GetLevel() > 50) + base += 2; + if (GetLevel() > 54) + base++; + if (GetLevel() > 59) + base++; + } + return base; + case EQEmu::skills::SkillFlyingKick: { + float skill_bonus = skill_level / 9.0f; + float ac_bonus = 0.0f; + auto inst = GetBotItem(EQEmu::inventory::slotFeet); + if (inst) + ac_bonus = inst->GetItemArmorClass(true) / 25.0f; + if (ac_bonus > skill_bonus) + ac_bonus = skill_bonus; + return static_cast(ac_bonus + skill_bonus); + } + case EQEmu::skills::SkillKick: { + float skill_bonus = skill_level / 10.0f; + float ac_bonus = 0.0f; + auto inst = GetBotItem(EQEmu::inventory::slotFeet); + if (inst) + ac_bonus = inst->GetItemArmorClass(true) / 25.0f; + if (ac_bonus > skill_bonus) + ac_bonus = skill_bonus; + return static_cast(ac_bonus + skill_bonus); + } + case EQEmu::skills::SkillBash: { + float skill_bonus = skill_level / 10.0f; + float ac_bonus = 0.0f; + const EQEmu::ItemInstance *inst = nullptr; + if (HasShieldEquiped()) + inst = GetBotItem(EQEmu::inventory::slotSecondary); + else if (HasTwoHanderEquipped()) + inst = GetBotItem(EQEmu::inventory::slotPrimary); + if (inst) + ac_bonus = inst->GetItemArmorClass(true) / 25.0f; + if (ac_bonus > skill_bonus) + ac_bonus = skill_bonus; + return static_cast(ac_bonus + skill_bonus); + } + case EQEmu::skills::SkillBackstab: { + float skill_bonus = static_cast(skill_level) * 0.02f; + auto inst = GetBotItem(EQEmu::inventory::slotPrimary); + if (inst && inst->GetItem() && inst->GetItem()->ItemType == EQEmu::item::ItemType1HPiercing) { + base = inst->GetItemBackstabDamage(true); + if (!inst->GetItemBackstabDamage()) + base += inst->GetItemWeaponDamage(true); + if (target) { + if (inst->GetItemElementalFlag(true) && inst->GetItemElementalDamage(true)) + base += target->ResistElementalWeaponDmg(inst); + if (inst->GetItemBaneDamageBody(true) || inst->GetItemBaneDamageRace(true)) + base += target->CheckBaneDamage(inst); + } + } + return static_cast(static_cast(base) * (skill_bonus + 2.0f)); + } + default: + return 0; + } +} + void Bot::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 max_damage, int32 min_damage, int32 hate_override, int ReuseTime, bool HitChance) { int32 hate = max_damage; if(hate_override > -1) @@ -4877,33 +4805,36 @@ void Bot::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 } } - min_damage += (min_damage * GetMeleeMinDamageMod_SE(skill) / 100); + int min_cap = max_damage * GetMeleeMinDamageMod_SE(skill) / 100; int hand = EQEmu::inventory::slotPrimary; + int damage = 0; + auto offense = this->offense(skill); if (skill == EQEmu::skills::SkillThrowing || skill == EQEmu::skills::SkillArchery) hand = EQEmu::inventory::slotRange; - if (who->AvoidDamage(this, max_damage, hand)) { - if (max_damage == -3) + if (who->AvoidDamage(this, damage, hand)) { + if (damage == -3) DoRiposte(who); } else { if (HitChance || who->CheckHitChance(this, skill)) { - who->MeleeMitigation(this, max_damage, min_damage); - ApplyMeleeDamageBonus(skill, max_damage); - max_damage += who->GetFcDamageAmtIncoming(this, 0, true, skill); - max_damage += ((itembonuses.HeroicSTR / 10) + (max_damage * who->GetSkillDmgTaken(skill) / 100) + GetSkillDmgAmt(skill)); - TryCriticalHit(who, skill, max_damage); + if (max_damage > 0) + who->MeleeMitigation(this, damage, max_damage, offense, skill); + if (damage > 0) { + ApplyDamageTable(damage, offense); + CommonOutgoingHitSuccess(who, damage, min_damage, min_cap, skill); + } } else { - max_damage = 0; + damage = 0; } } who->AddToHateList(this, hate); - who->Damage(this, max_damage, SPELL_UNKNOWN, skill, false); + who->Damage(this, damage, SPELL_UNKNOWN, skill, false); if(!GetTarget() || HasDied()) return; - if (max_damage > 0) + if (damage > 0) CheckNumHitsRemaining(NumHit::OutgoingHitSuccess); //[AA Dragon Punch] value[0] = 100 for 25%, chance value[1] = skill @@ -4919,7 +4850,7 @@ void Bot::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 if (HasSkillProcs()) TrySkillProc(who, skill, (ReuseTime * 1000)); - if (max_damage > 0 && HasSkillProcSuccess()) + if (damage > 0 && HasSkillProcSuccess()) TrySkillProc(who, skill, (ReuseTime * 1000), true); } @@ -4967,72 +4898,33 @@ void Bot::TryBackstab(Mob *other, int ReuseTime) { } } } else if(aabonuses.FrontalBackstabMinDmg || itembonuses.FrontalBackstabMinDmg || spellbonuses.FrontalBackstabMinDmg) { + m_specialattacks = eSpecialAttacks::ChaoticStab; RogueBackstab(other, true); - if (level > 54) { - float DoubleAttackProbability = ((GetSkill(EQEmu::skills::SkillDoubleAttack) + GetLevel()) / 500.0f); - if(zone->random.Real(0, 1) < DoubleAttackProbability) - if(other->GetHP() > 0) - RogueBackstab(other,true, ReuseTime); - - if (tripleChance && other->GetHP() > 0 && tripleChance > zone->random.Int(0, 100)) - RogueBackstab(other,false,ReuseTime); - } + m_specialattacks = eSpecialAttacks::None; } else Attack(other, EQEmu::inventory::slotPrimary); } -void Bot::RogueBackstab(Mob* other, bool min_damage, int ReuseTime) { - int32 ndamage = 0; - int32 max_hit = 0; - int32 min_hit = 0; - int32 hate = 0; - int32 primaryweapondamage = 0; - int32 backstab_dmg = 0; - EQEmu::ItemInstance* botweaponInst = GetBotItem(EQEmu::inventory::slotPrimary); - if(botweaponInst) { - primaryweapondamage = GetWeaponDamage(other, botweaponInst); - backstab_dmg = botweaponInst->GetItem()->BackstabDmg; - for (int i = EQEmu::inventory::socketBegin; i < EQEmu::inventory::SocketCount; ++i) { - EQEmu::ItemInstance *aug = botweaponInst->GetAugment(i); - if(aug) - backstab_dmg += aug->GetItem()->BackstabDmg; - } - } else { - primaryweapondamage = ((GetLevel() / 7) + 1); - backstab_dmg = primaryweapondamage; +void Bot::RogueBackstab(Mob *other, bool min_damage, int ReuseTime) +{ + if (!other) + return; + + EQEmu::ItemInstance *botweaponInst = GetBotItem(EQEmu::inventory::slotPrimary); + if (botweaponInst) { + if (!GetWeaponDamage(other, botweaponInst)) + return; + } else if (!GetWeaponDamage(other, (const EQEmu::ItemData *)nullptr)) { + return; } - if(primaryweapondamage > 0) { - if(level > 25) { - max_hit = (((((2 * backstab_dmg) * GetDamageTable(EQEmu::skills::SkillBackstab) / 100) * 10 * GetSkill(EQEmu::skills::SkillBackstab) / 355) + ((level - 25) / 3) + 1) * ((100 + RuleI(Combat, BackstabBonus)) / 100)); - hate = (20 * backstab_dmg * GetSkill(EQEmu::skills::SkillBackstab) / 355); - } else { - max_hit = (((((2 * backstab_dmg) * GetDamageTable(EQEmu::skills::SkillBackstab) / 100) * 10 * GetSkill(EQEmu::skills::SkillBackstab) / 355) + 1) * ((100 + RuleI(Combat, BackstabBonus)) / 100)); - hate = (20 * backstab_dmg * GetSkill(EQEmu::skills::SkillBackstab) / 355); - } + uint32 hate = 0; - if (level < 51) - min_hit = (level * 15 / 10); - else - min_hit = ((level * ( level * 5 - 105)) / 100); + int base_damage = GetBaseSkillDamage(EQEmu::skills::SkillBackstab, other); + hate = base_damage; - if (!other->CheckHitChance(this, EQEmu::skills::SkillBackstab, 0)) - ndamage = 0; - else { - if (min_damage) { - ndamage = min_hit; - } else { - if (max_hit < min_hit) - max_hit = min_hit; - - ndamage = (RuleB(Combat, UseIntervalAC) ? max_hit : zone->random.Int(min_hit, max_hit)); - } - } - } else - ndamage = -5; - - DoSpecialAttackDamage(other, EQEmu::skills::SkillBackstab, ndamage, min_hit, hate, ReuseTime); + DoSpecialAttackDamage(other, EQEmu::skills::SkillBackstab, base_damage, 0, hate, ReuseTime); DoAnim(anim1HPiercing); } @@ -5157,41 +5049,25 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { if (!target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) dmg = 0; else { - if(RuleB(Combat, UseIntervalAC)) - dmg = GetBashDamage(); - else - dmg = zone->random.Int(1, GetBashDamage()); + dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash); } } reuse = (BashReuseTime * 1000); - DoSpecialAttackDamage(target, EQEmu::skills::SkillBash, dmg, 1, -1, reuse); + DoSpecialAttackDamage(target, EQEmu::skills::SkillBash, dmg, 0, -1, reuse); did_attack = true; } } if (skill_to_use == EQEmu::skills::SkillFrenzy) { int AtkRounds = 3; - int skillmod = 0; - if (MaxSkill(EQEmu::skills::SkillFrenzy) > 0) - skillmod = (100 * GetSkill(EQEmu::skills::SkillFrenzy) / MaxSkill(EQEmu::skills::SkillFrenzy)); - - int32 max_dmg = (26 + ((((GetLevel() - 6) * 2) * skillmod) / 100)) * ((100 + RuleI(Combat, FrenzyBonus)) / 100); - int32 min_dmg = 0; + int32 max_dmg = GetBaseSkillDamage(EQEmu::skills::SkillFrenzy); DoAnim(anim2HSlashing); - if (GetLevel() < 51) - min_dmg = 1; - else - min_dmg = (GetLevel() * 8 / 10); - - if (min_dmg > max_dmg) - max_dmg = min_dmg; - reuse = (FrenzyReuseTime * 1000); did_attack = true; while(AtkRounds > 0) { if (GetTarget() && (AtkRounds == 1 || zone->random.Int(0, 100) < 75)) { - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, min_dmg, max_dmg, reuse, true); + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, 0, max_dmg, reuse, true); } AtkRounds--; @@ -5207,14 +5083,11 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { if (!target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) dmg = 0; else { - if(RuleB(Combat, UseIntervalAC)) - dmg = GetKickDamage(); - else - dmg = zone->random.Int(1, GetKickDamage()); + dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick); } } reuse = (KickReuseTime * 1000); - DoSpecialAttackDamage(target, EQEmu::skills::SkillKick, dmg, 1, -1, reuse); + DoSpecialAttackDamage(target, EQEmu::skills::SkillKick, dmg, 0, -1, reuse); did_attack = true; } } @@ -5249,26 +5122,6 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { classattack_timer.Start(reuse / HasteModifier); } -bool Bot::TryHeadShot(Mob* defender, EQEmu::skills::SkillType skillInUse) { - bool Result = false; - if (defender && (defender->GetBodyType() == BT_Humanoid) && (skillInUse == EQEmu::skills::SkillArchery) && (GetClass() == RANGER) && (GetLevel() >= 62)) { - int defenderLevel = defender->GetLevel(); - int rangerLevel = GetLevel(); - if(GetAA(aaHeadshot) && ((defenderLevel - 46) <= GetAA(aaHeadshot) * 2)) { - float AttackerChance = 0.20f + ((float)(rangerLevel - 51) * 0.005f); - float DefenderChance = (float)zone->random.Real(0.00f, 1.00f); - if(AttackerChance > DefenderChance) { - Log.Out(Logs::Detail, Logs::Combat, "Landed a headshot: Attacker chance was %f and Defender chance was %f.", AttackerChance, DefenderChance); - entity_list.MessageClose(this, false, 200, MT_CritMelee, "%s has scored a leathal HEADSHOT!", GetName()); - defender->Damage(this, (defender->GetMaxHP()+50), SPELL_UNKNOWN, skillInUse); - Result = true; - } else - Log.Out(Logs::Detail, Logs::Combat, "FAILED a headshot: Attacker chance was %f and Defender chance was %f.", AttackerChance, DefenderChance); - } - } - return Result; -} - int32 Bot::CheckAggroAmount(uint16 spellid) { int32 AggroAmount = Mob::CheckAggroAmount(spellid, nullptr); int32 focusAggro = GetBotFocusEffect(BotfocusSpellHateMod, spellid); diff --git a/zone/bot.h b/zone/bot.h index 736a464ab..15a1b0938 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -206,9 +206,9 @@ public: //abstract virtual function implementations requird by base abstract class virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill); - virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, int special = 0); + virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None); virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false, - ExtraAttackOptions *opts = nullptr, int special = 0); + ExtraAttackOptions *opts = nullptr); virtual bool HasRaid() { return (GetRaid() ? true : false); } virtual bool HasGroup() { return (GetGroup() ? true : false); } virtual Raid* GetRaid() { return entity_list.GetRaidByMob(this); } @@ -239,7 +239,7 @@ public: uint16 BotGetSpellPriority(int spellslot) { return AIspells[spellslot].priority; } virtual float GetProcChances(float ProcBonus, uint16 hand); virtual int GetHandToHandDamage(void); - virtual bool TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse); + virtual bool TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse, int &damage); virtual void DoRiposte(Mob* defender); inline virtual int32 GetATK() const { return ATK + itembonuses.ATK + spellbonuses.ATK + ((GetSTR() + GetSkill(EQEmu::skills::SkillOffense)) * 9 / 10); } inline virtual int32 GetATKBonus() const { return itembonuses.ATK + spellbonuses.ATK; } @@ -248,13 +248,12 @@ public: uint16 GetPrimarySkillValue(); uint16 MaxSkill(EQEmu::skills::SkillType skillid, uint16 class_, uint16 level) const; inline uint16 MaxSkill(EQEmu::skills::SkillType skillid) const { return MaxSkill(skillid, GetClass(), GetLevel()); } + virtual int GetBaseSkillDamage(EQEmu::skills::SkillType skill, Mob *target = nullptr); virtual void DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 max_damage, int32 min_damage = 1, int32 hate_override = -1, int ReuseTime = 10, bool HitChance = false); virtual void TryBackstab(Mob *other,int ReuseTime = 10); virtual void RogueBackstab(Mob* other, bool min_damage = false, int ReuseTime = 10); virtual void RogueAssassinate(Mob* other); virtual void DoClassAttacks(Mob *target, bool IsRiposte=false); - virtual bool TryHeadShot(Mob* defender, EQEmu::skills::SkillType skillInUse); - virtual void DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills::SkillType skillinuse, int16 chance_mod = 0, int16 focus = 0, bool CanRiposte = false, int ReuseTime = 0); virtual void ApplySpecialAttackMod(EQEmu::skills::SkillType skill, int32 &dmg, int32 &mindmg); bool CanDoSpecialAttack(Mob *other); virtual int32 CheckAggroAmount(uint16 spellid); @@ -503,7 +502,6 @@ public: bool GetAltOutOfCombatBehavior() { return _altoutofcombatbehavior;} bool GetShowHelm() { return _showhelm; } - inline virtual int32 GetAC() const { return AC; } inline virtual int32 GetSTR() const { return STR; } inline virtual int32 GetSTA() const { return STA; } inline virtual int32 GetDEX() const { return DEX; } diff --git a/zone/client.cpp b/zone/client.cpp index ddf10d9d5..a066b9b12 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -6871,8 +6871,8 @@ void Client::SendStatsWindow(Client* client, bool use_window) /* DS */ indP << "DS: " << (itembonuses.DamageShield + spellbonuses.DamageShield*-1) << " (Spell: " << (spellbonuses.DamageShield*-1) << " + Item: " << itembonuses.DamageShield << " / " << RuleI(Character, ItemDamageShieldCap) << ")
" << /* Atk */ indP << "ATK: " << GetTotalATK() << "
" << /* Atk2 */ indP << "- Base: " << GetATKRating() << " | Item: " << itembonuses.ATK << " (" << RuleI(Character, ItemATKCap) << ")~Used: " << (itembonuses.ATK * 1.342) << " | Spell: " << spellbonuses.ATK << "
" << - /* AC */ indP << "AC: " << CalcAC() << "
" << - /* AC2 */ indP << "- Mit: " << GetACMit() << " | Avoid: " << GetACAvoid() << " | Spell: " << spellbonuses.AC << " | Shield: " << shield_ac << "
" << + /* AC */ indP << "AC: " << -1 << "
" << + /* AC2 */ indP << "- Mit: " << -1 << " | Avoid: " << -1 << " | Spell: " << spellbonuses.AC << " | Shield: " << shield_ac << "
" << /* Haste */ indP << "Haste: " << GetHaste() << "
" << /* Haste2 */ indP << " - Item: " << itembonuses.haste << " + Spell: " << (spellbonuses.haste + spellbonuses.hastetype2) << " (Cap: " << RuleI(Character, HasteCap) << ") | Over: " << (spellbonuses.hastetype3 + ExtraHaste) << "
" << /* RunSpeed*/ indP << "Runspeed: " << GetRunspeed() << "
" << @@ -6910,7 +6910,7 @@ void Client::SendStatsWindow(Client* client, bool use_window) client->Message(15, "~~~~~ %s %s ~~~~~", GetCleanName(), GetLastName()); client->Message(0, " Level: %i Class: %i Race: %i DS: %i/%i Size: %1.1f Weight: %.1f/%d ", GetLevel(), GetClass(), GetRace(), GetDS(), RuleI(Character, ItemDamageShieldCap), GetSize(), (float)CalcCurrentWeight() / 10.0f, GetSTR()); client->Message(0, " HP: %i/%i HP Regen: %i/%i",GetHP(), GetMaxHP(), CalcHPRegen(), CalcHPRegenCap()); - client->Message(0, " AC: %i ( Mit.: %i + Avoid.: %i + Spell: %i ) | Shield AC: %i", CalcAC(), GetACMit(), GetACAvoid(), spellbonuses.AC, shield_ac); + client->Message(0, " AC: %i ( Mit.: %i + Avoid.: %i + Spell: %i ) | Shield AC: %i", -1, -1, -1, spellbonuses.AC, shield_ac); if(CalcMaxMana() > 0) client->Message(0, " Mana: %i/%i Mana Regen: %i/%i", GetMana(), GetMaxMana(), CalcManaRegen(), CalcManaRegenCap()); client->Message(0, " End.: %i/%i End. Regen: %i/%i",GetEndurance(), GetMaxEndurance(), CalcEnduranceRegen(), CalcEnduranceRegenCap()); diff --git a/zone/client.h b/zone/client.h index 803540ffb..687ff04d6 100644 --- a/zone/client.h +++ b/zone/client.h @@ -221,18 +221,18 @@ public: //abstract virtual function implementations required by base abstract class virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill); - virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, int special = 0); + virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None); virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false, - ExtraAttackOptions *opts = nullptr, int special = 0); + ExtraAttackOptions *opts = nullptr); virtual bool HasRaid() { return (GetRaid() ? true : false); } virtual bool HasGroup() { return (GetGroup() ? true : false); } virtual Raid* GetRaid() { return entity_list.GetRaidByClient(this); } virtual Group* GetGroup() { return entity_list.GetGroupByClient(this); } virtual inline bool IsBerserk() { return berserk; } - virtual int32 GetMeleeMitDmg(Mob *attacker, int32 damage, int32 minhit, float mit_rating, float atk_rating); virtual void SetAttackTimer(); int GetQuiverHaste(int delay); void DoAttackRounds(Mob *target, int hand, bool IsFromSpell = false); + int DoDamageCaps(int base_damage); void AI_Init(); void AI_Start(uint32 iMoveDelay = 0); @@ -418,8 +418,6 @@ public: virtual void CalcBonuses(); //these are all precalculated now - inline virtual int32 GetAC() const { return AC; } - inline virtual int32 GetATK() const { return ATK + itembonuses.ATK + spellbonuses.ATK + ((GetSTR() + GetSkill(EQEmu::skills::SkillOffense)) * 9 / 10); } inline virtual int32 GetATKBonus() const { return itembonuses.ATK + spellbonuses.ATK; } inline virtual int GetHaste() const { return Haste; } int GetRawACNoShield(int &shield_ac) const; @@ -1301,9 +1299,6 @@ private: void HandleTraderPriceUpdate(const EQApplicationPacket *app); - int32 CalcAC(); - int32 GetACMit(); - int32 GetACAvoid(); int32 CalcATK(); int32 CalcItemATKCap(); int32 CalcHaste(); diff --git a/zone/client_mods.cpp b/zone/client_mods.cpp index d9914fb26..3d3a1d529 100644 --- a/zone/client_mods.cpp +++ b/zone/client_mods.cpp @@ -1025,111 +1025,6 @@ int32 Client::acmod() return 0; }; -// This is a testing formula for AC, the value this returns should be the same value as the one the client shows... -// ac1 and ac2 are probably the damage migitation and damage avoidance numbers, not sure which is which. -// I forgot to include the iksar defense bonus and i cant find my notes now... -// AC from spells are not included (cant even cast spells yet..) -int32 Client::CalcAC() -{ - // new formula - int avoidance = (acmod() + ((GetSkill(EQEmu::skills::SkillDefense) + itembonuses.HeroicAGI / 10) * 16) / 9); - if (avoidance < 0) { - avoidance = 0; - } - - if (RuleB(Character, EnableAvoidanceCap)) { - if (avoidance > RuleI(Character, AvoidanceCap)) { - avoidance = RuleI(Character, AvoidanceCap); - } - } - - int mitigation = 0; - if (m_pp.class_ == WIZARD || m_pp.class_ == MAGICIAN || m_pp.class_ == NECROMANCER || m_pp.class_ == ENCHANTER) { - //something is wrong with this, naked casters have the wrong natural AC -// mitigation = (spellbonuses.AC/3) + (GetSkill(DEFENSE)/2) + (itembonuses.AC+1); - mitigation = (GetSkill(EQEmu::skills::SkillDefense) + itembonuses.HeroicAGI / 10) / 4 + (itembonuses.AC + 1); - //this might be off by 4.. - mitigation -= 4; - } - else { -// mitigation = (spellbonuses.AC/4) + (GetSkill(DEFENSE)/3) + ((itembonuses.AC*4)/3); - mitigation = (GetSkill(EQEmu::skills::SkillDefense) + itembonuses.HeroicAGI / 10) / 3 + ((itembonuses.AC * 4) / 3); - if (m_pp.class_ == MONK) { - mitigation += GetLevel() * 13 / 10; //the 13/10 might be wrong, but it is close... - } - } - int displayed = 0; - displayed += ((avoidance + mitigation) * 1000) / 847; //natural AC - //Iksar AC, untested - if (GetRace() == IKSAR) { - displayed += 12; - int iksarlevel = GetLevel(); - iksarlevel -= 10; - if (iksarlevel > 25) { - iksarlevel = 25; - } - if (iksarlevel > 0) { - displayed += iksarlevel * 12 / 10; - } - } - // Shield AC bonus for HeroicSTR - if (itembonuses.HeroicSTR) { - bool equiped = CastToClient()->m_inv.GetItem(EQEmu::inventory::slotSecondary); - if (equiped) { - uint8 shield = CastToClient()->m_inv.GetItem(EQEmu::inventory::slotSecondary)->GetItem()->ItemType; - if (shield == EQEmu::item::ItemTypeShield) { - displayed += itembonuses.HeroicSTR / 2; - } - } - } - //spell AC bonuses are added directly to natural total - displayed += spellbonuses.AC; - AC = displayed; - return (AC); -} - -int32 Client::GetACMit() -{ - int mitigation = 0; - if (m_pp.class_ == WIZARD || m_pp.class_ == MAGICIAN || m_pp.class_ == NECROMANCER || m_pp.class_ == ENCHANTER) { - mitigation = (GetSkill(EQEmu::skills::SkillDefense) + itembonuses.HeroicAGI / 10) / 4 + (itembonuses.AC + 1); - mitigation -= 4; - } - else { - mitigation = (GetSkill(EQEmu::skills::SkillDefense) + itembonuses.HeroicAGI / 10) / 3 + ((itembonuses.AC * 4) / 3); - if (m_pp.class_ == MONK) { - mitigation += GetLevel() * 13 / 10; //the 13/10 might be wrong, but it is close... - } - } - // Shield AC bonus for HeroicSTR - if (itembonuses.HeroicSTR) { - bool equiped = CastToClient()->m_inv.GetItem(EQEmu::inventory::slotSecondary); - if (equiped) { - uint8 shield = CastToClient()->m_inv.GetItem(EQEmu::inventory::slotSecondary)->GetItem()->ItemType; - if (shield == EQEmu::item::ItemTypeShield) { - mitigation += itembonuses.HeroicSTR / 2; - } - } - } - return (mitigation * 1000 / 847); -} - -int32 Client::GetACAvoid() -{ - int32 avoidance = (acmod() + ((GetSkill(EQEmu::skills::SkillDefense) + itembonuses.HeroicAGI / 10) * 16) / 9); - if (avoidance < 0) { - avoidance = 0; - } - - if (RuleB(Character, EnableAvoidanceCap)) { - if ((avoidance * 1000 / 847) > RuleI(Character, AvoidanceCap)) { - return RuleI(Character, AvoidanceCap); - } - } - - return (avoidance * 1000 / 847); -} - int32 Client::CalcMaxMana() { switch (GetCasterClass()) { diff --git a/zone/common.h b/zone/common.h index e621c336b..5a6782148 100644 --- a/zone/common.h +++ b/zone/common.h @@ -638,5 +638,11 @@ struct ExtraAttackOptions { }; +struct DamageTable { + int32 max_extra; // max extra damage + int32 chance; // chance not to apply? + int32 minusfactor; // difficulty of rolling +}; + #endif diff --git a/zone/corpse.h b/zone/corpse.h index 49b1cba39..e601f2aa3 100644 --- a/zone/corpse.h +++ b/zone/corpse.h @@ -52,8 +52,8 @@ class Corpse : public Mob { /* Corpse: General */ virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill) { return true; } - virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, int special = 0) { return; } - virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = true, bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr, int special = 0) { return false; } + virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None) { return; } + virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = true, bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr) { return false; } virtual bool HasRaid() { return false; } virtual bool HasGroup() { return false; } virtual Raid* GetRaid() { return 0; } diff --git a/zone/encounter.h b/zone/encounter.h index 8b672fdd4..e0d8dbcb0 100644 --- a/zone/encounter.h +++ b/zone/encounter.h @@ -35,9 +35,9 @@ public: //abstract virtual function implementations required by base abstract class virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill) { return true; } - virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, int special = 0) { return; } + virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None) { return; } virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false, - ExtraAttackOptions *opts = nullptr, int special = 0) { + ExtraAttackOptions *opts = nullptr) { return false; } virtual bool HasRaid() { return false; } diff --git a/zone/hate_list.cpp b/zone/hate_list.cpp index b98ca6923..34519b69f 100644 --- a/zone/hate_list.cpp +++ b/zone/hate_list.cpp @@ -553,7 +553,7 @@ int HateList::AreaRampage(Mob *caster, Mob *target, int count, ExtraAttackOption auto mob = entity_list.GetMobID(id); if (mob) { ++hit_count; - caster->ProcessAttackRounds(mob, opts, 1); + caster->ProcessAttackRounds(mob, opts); } } diff --git a/zone/merc.cpp b/zone/merc.cpp index 6dc00836b..6021f05d5 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -4432,13 +4432,8 @@ void Merc::DoClassAttacks(Mob *target) { dmg = -5; } else{ - if (target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) { - if(RuleB(Combat, UseIntervalAC)) - dmg = GetKickDamage(); - else - dmg = zone->random.Int(1, GetKickDamage()); - - } + if (target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) + dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick, GetTarget()); } reuse = KickReuseTime * 1000; @@ -4454,12 +4449,8 @@ void Merc::DoClassAttacks(Mob *target) { dmg = -5; } else{ - if (target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) { - if(RuleB(Combat, UseIntervalAC)) - dmg = GetBashDamage(); - else - dmg = zone->random.Int(1, GetBashDamage()); - } + if (target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) + dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash, GetTarget()); } reuse = BashReuseTime * 1000; @@ -4474,7 +4465,7 @@ void Merc::DoClassAttacks(Mob *target) { classattack_timer.Start(reuse / HasteModifier); } -bool Merc::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts, int special) +bool Merc::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) { if (!other) { SetTarget(nullptr); @@ -4485,7 +4476,7 @@ bool Merc::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, boo return NPC::Attack(other, Hand, bRiposte, IsStrikethrough, IsFromSpell, opts); } -void Merc::Damage(Mob* other, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic, int special) +void Merc::Damage(Mob* other, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic, eSpecialAttacks special) { if(IsDead() || IsCorpse()) return; diff --git a/zone/merc.h b/zone/merc.h index b563775b0..7bd4c2b9b 100644 --- a/zone/merc.h +++ b/zone/merc.h @@ -65,9 +65,9 @@ public: //abstract virtual function implementations requird by base abstract class virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill); - virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, int special = 0); + virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None); virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false, - bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr, int special = 0); + bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr); virtual bool HasRaid() { return false; } virtual bool HasGroup() { return (GetGroup() ? true : false); } virtual Raid* GetRaid() { return 0; } @@ -197,7 +197,6 @@ public: virtual void CalcBonuses(); int32 GetEndurance() const {return cur_end;} //This gets our current endurance inline uint8 GetEndurancePercent() { return (uint8)((float)cur_end / (float)max_end * 100.0f); } - inline virtual int32 GetAC() const { return AC; } inline virtual int32 GetATK() const { return ATK; } inline virtual int32 GetATKBonus() const { return itembonuses.ATK + spellbonuses.ATK; } int32 GetRawACNoShield(int &shield_ac) const; diff --git a/zone/mob.cpp b/zone/mob.cpp index c47788ee5..c5b61f546 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -109,7 +109,9 @@ Mob::Mob(const char* in_name, m_TargetV(glm::vec3()), flee_timer(FLEE_CHECK_TIMER), m_Position(position), - tmHidden(-1) + tmHidden(-1), + mitigation_ac(0), + m_specialattacks(eSpecialAttacks::None) { targeted = 0; tar_ndx=0; @@ -4643,7 +4645,7 @@ void Mob::SetRaidGrouped(bool v) } } -int16 Mob::GetCriticalChanceBonus(uint16 skill) +int Mob::GetCriticalChanceBonus(uint16 skill) { int critical_chance = 0; diff --git a/zone/mob.h b/zone/mob.h index a581ea819..802eede5c 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -54,6 +54,13 @@ namespace EQEmu class ItemInstance; } +enum class eSpecialAttacks : int { + None, + Rampage, + AERampage, + ChaoticStab +}; + class Mob : public Entity { public: enum CLIENT_CONN_STATUS { CLIENT_CONNECTING, CLIENT_CONNECTED, CLIENT_LINKDEAD, @@ -152,45 +159,51 @@ public: 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 = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false, - bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr, int special = 0) = 0; + bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr) = 0; int MonkSpecialAttack(Mob* other, uint8 skill_used); virtual void TryBackstab(Mob *other,int ReuseTime = 10); - bool AvoidDamage(Mob* attacker, int32 &damage, int hand); + bool AvoidDamage(Mob* attacker, int &damage, int hand); int compute_tohit(EQEmu::skills::SkillType skillinuse); int compute_defense(); - virtual bool CheckHitChance(Mob* attacker, EQEmu::skills::SkillType skillinuse, int 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, EQEmu::skills::SkillType skillinuse); - uint32 TryHeadShot(Mob* defender, EQEmu::skills::SkillType skillInUse); - uint32 TryAssassinate(Mob* defender, EQEmu::skills::SkillType skillInUse, uint16 ReuseTime); + bool CheckHitChance(Mob* attacker, EQEmu::skills::SkillType skillinuse, int chance_mod = 0); + virtual void TryCriticalHit(Mob *defender, uint16 skill, int &damage, int min_damage, ExtraAttackOptions *opts = nullptr); + void TryPetCriticalHit(Mob *defender, uint16 skill, int &damage); + virtual bool TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse, int &damage); + int TryHeadShot(Mob* defender, EQEmu::skills::SkillType skillInUse); + int TryAssassinate(Mob* defender, EQEmu::skills::SkillType skillInUse, uint16 ReuseTime); virtual void DoRiposte(Mob* defender); - void ApplyMeleeDamageBonus(uint16 skill, int32 &damage,ExtraAttackOptions *opts = nullptr); - 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); + void ApplyMeleeDamageBonus(uint16 skill, int &damage,ExtraAttackOptions *opts = nullptr); + int ACSum(); + int offense(EQEmu::skills::SkillType skill); + void CalcAC() { mitigation_ac = ACSum(); } + int GetACSoftcap(); + double GetSoftcapReturns(); + int GetClassRaceACBonus(); + inline int GetMitigationAC() { return mitigation_ac; } + void MeleeMitigation(Mob *attacker, int &damage, int base_damage, int offense, EQEmu::skills::SkillType, ExtraAttackOptions *opts = nullptr); + double RollD20(double offense, double mitigation); // CALL THIS FROM THE DEFENDER bool CombatRange(Mob* other); virtual inline bool IsBerserk() { return false; } // only clients void RogueEvade(Mob *other); - void CommonOutgoingHitSuccess(Mob* defender, int32 &damage, EQEmu::skills::SkillType skillInUse, ExtraAttackOptions *opts = nullptr); + void CommonOutgoingHitSuccess(Mob* defender, int &damage, int min_damage, int min_mod, EQEmu::skills::SkillType skillInUse, ExtraAttackOptions *opts = nullptr); void BreakInvisibleSpells(); virtual void CancelSneakHide(); void CommonBreakInvisible(); void CommonBreakInvisibleFromCombat(); bool HasDied(); virtual bool CheckDualWield(); - void DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr, int special = 0); - void DoOffHandAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr, int special = 0); + void DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr); + void DoOffHandAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr); virtual bool CheckDoubleAttack(); // inline process for places where we need to do them outside of the AI_Process - void ProcessAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr, int special = 0) + void ProcessAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr) { if (target) { - DoMainHandAttackRounds(target, opts, special); + DoMainHandAttackRounds(target, opts); if (CanThisClassDualWield()) - DoOffHandAttackRounds(target, opts, special); + DoOffHandAttackRounds(target, opts); } return; } @@ -367,7 +380,7 @@ public: bool AffectedBySpellExcludingSlot(int slot, int effect); virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill) = 0; virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, - bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, int special = 0) = 0; + bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None) = 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;} @@ -406,7 +419,7 @@ public: virtual void SetTarget(Mob* mob); virtual inline float GetHPRatio() const { return max_hp == 0 ? 0 : ((float)cur_hp/max_hp*100); } virtual inline int GetIntHPRatio() const { return max_hp == 0 ? 0 : static_cast(cur_hp * 100 / max_hp); } - inline virtual int32 GetAC() const { return AC + itembonuses.AC + spellbonuses.AC; } + inline int32 GetAC() const { return AC; } inline virtual int32 GetATK() const { return ATK + itembonuses.ATK + spellbonuses.ATK; } inline virtual int32 GetATKBonus() const { return itembonuses.ATK + spellbonuses.ATK; } inline virtual int32 GetSTR() const { return STR + itembonuses.STR + spellbonuses.STR; } @@ -673,7 +686,7 @@ public: int16 GetMeleeMinDamageMod_SE(uint16 skill); int16 GetCrippBlowChance(); int16 GetSkillReuseTime(uint16 skill); - int16 GetCriticalChanceBonus(uint16 skill); + int GetCriticalChanceBonus(uint16 skill); int16 GetSkillDmgAmt(uint16 skill); bool TryReflectSpell(uint32 spell_id); bool CanBlockSpell() const { return(spellbonuses.BlockNextSpell); } @@ -778,7 +791,8 @@ public: int32 GetMeleeMitigation(); uint8 GetWeaponDamageBonus(const EQEmu::ItemData* weapon, bool offhand = false); - uint16 GetDamageTable(EQEmu::skills::SkillType skillinuse); + const DamageTable &GetDamageTable() const; + void ApplyDamageTable(int &damage, int offense); virtual int GetHandToHandDamage(void); bool CanThisClassDoubleAttack(void) const; @@ -801,9 +815,9 @@ public: int32 AffectMagicalDamage(int32 damage, uint16 spell_id, const bool iBuffTic, Mob* attacker); int32 ReduceAllDamage(int32 damage); - virtual void DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 max_damage, int32 min_damage = 1, int32 hate_override = -1, int ReuseTime = 10, bool CheckHitChance = false, bool CanAvoid = true); + void DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int base_damage, int min_damage = 0, int32 hate_override = -1, int ReuseTime = 10, bool CheckHitChance = false, bool CanAvoid = true); virtual void DoThrowingAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon = nullptr, const EQEmu::ItemData* AmmoItem = nullptr, uint16 weapon_damage = 0, int16 chance_mod = 0, int16 focus = 0, int ReuseTime = 0, uint32 range_id = 0, int AmmoSlot = 0, float speed = 4.0f); - virtual void DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills::SkillType skillinuse, int16 chance_mod = 0, int16 focus = 0, bool CanRiposte = false, int ReuseTime = 0); + void DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills::SkillType skillinuse, int16 chance_mod = 0, int16 focus = 0, bool CanRiposte = false, int ReuseTime = 0); virtual void DoArcheryAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon = nullptr, const EQEmu::ItemInstance* Ammo = nullptr, uint16 weapon_damage = 0, int16 chance_mod = 0, int16 focus = 0, int ReuseTime = 0, uint32 range_id = 0, uint32 ammo_id = 0, const EQEmu::ItemData *AmmoItem = nullptr, int AmmoSlot = 0, float speed = 4.0f); bool TryProjectileAttack(Mob* other, const EQEmu::ItemData *item, EQEmu::skills::SkillType skillInUse, uint16 weapon_dmg, const EQEmu::ItemInstance* RangeWeapon, const EQEmu::ItemInstance* Ammo, int AmmoSlot, float speed); void ProjectileAttack(); @@ -816,6 +830,7 @@ public: bool AddRampage(Mob*); void ClearRampage(); void AreaRampage(ExtraAttackOptions *opts); + inline bool IsSpecialAttack(eSpecialAttacks in) { return m_specialattacks == in; } void StartEnrage(); void ProcessEnrage(); @@ -1042,7 +1057,7 @@ public: #endif protected: - void CommonDamage(Mob* other, int32 &damage, const uint16 spell_id, const EQEmu::skills::SkillType attack_skill, bool &avoidable, const int8 buffslot, const bool iBuffTic, int special = 0); + void CommonDamage(Mob* other, int &damage, const uint16 spell_id, const EQEmu::skills::SkillType attack_skill, bool &avoidable, const int8 buffslot, const bool iBuffTic, eSpecialAttacks specal = eSpecialAttacks::None); static uint16 GetProcID(uint16 spell_id, uint8 effect_index); float _GetMovementSpeed(int mod) const; int _GetWalkSpeed() const; @@ -1079,6 +1094,7 @@ protected: bool multitexture; int AC; + int mitigation_ac; // cached Mob::ACSum int32 ATK; int32 STR; int32 STA; @@ -1150,6 +1166,7 @@ protected: int base_walkspeed; int base_fearspeed; int current_speed; + eSpecialAttacks m_specialattacks; uint32 pLastChange; bool held; @@ -1174,9 +1191,10 @@ protected: uint16 GetWeaponSpeedbyHand(uint16 hand); int GetWeaponDamage(Mob *against, const EQEmu::ItemData *weapon_item); int GetWeaponDamage(Mob *against, const EQEmu::ItemInstance *weapon_item, uint32 *hate = nullptr); - int GetKickDamage(); - int GetBashDamage(); - virtual void ApplySpecialAttackMod(EQEmu::skills::SkillType skill, int32 &dmg, int32 &mindmg); +#ifdef BOTS + virtual +#endif + int GetBaseSkillDamage(EQEmu::skills::SkillType skill, Mob *target = nullptr); virtual int16 GetFocusEffect(focusType type, uint16 spell_id) { return 0; } void CalculateNewFearpoint(); float FindGroundZ(float new_x, float new_y, float z_offset=0.0); @@ -1211,7 +1229,7 @@ protected: Timer attack_dw_timer; Timer ranged_timer; float attack_speed; //% increase/decrease in attack speed (not haste) - int8 attack_delay; //delay between attacks in 10ths of seconds + int attack_delay; //delay between attacks in 10ths of seconds int16 slow_mitigation; // Allows for a slow mitigation (100 = 100%, 50% = 50%) Timer tic_timer; Timer mana_timer; diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 836cd086f..d2340581d 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -1160,11 +1160,8 @@ void Mob::AI_Process() { if ((IsPet() || IsTempPet()) && IsPetOwnerClient()){ if (spellbonuses.PC_Pet_Rampage[0] || itembonuses.PC_Pet_Rampage[0] || aabonuses.PC_Pet_Rampage[0]){ int chance = spellbonuses.PC_Pet_Rampage[0] + itembonuses.PC_Pet_Rampage[0] + aabonuses.PC_Pet_Rampage[0]; - int dmg_mod = spellbonuses.PC_Pet_Rampage[1] + itembonuses.PC_Pet_Rampage[1] + aabonuses.PC_Pet_Rampage[1]; if(zone->random.Roll(chance)) { - ExtraAttackOptions opts; - opts.damage_percent = dmg_mod / 100.0f; - Rampage(&opts); + Rampage(nullptr); } } } @@ -1175,12 +1172,7 @@ void Mob::AI_Process() { rampage_chance = rampage_chance > 0 ? rampage_chance : 20; if(zone->random.Roll(rampage_chance)) { ExtraAttackOptions opts; - int cur = GetSpecialAbilityParam(SPECATK_RAMPAGE, 2); - if(cur > 0) { - opts.damage_percent = cur / 100.0f; - } - - cur = GetSpecialAbilityParam(SPECATK_RAMPAGE, 3); + int cur = GetSpecialAbilityParam(SPECATK_RAMPAGE, 3); if(cur > 0) { opts.damage_flat = cur; } @@ -1215,12 +1207,7 @@ void Mob::AI_Process() { rampage_chance = rampage_chance > 0 ? rampage_chance : 20; if(zone->random.Roll(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); + int cur = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 3); if(cur > 0) { opts.damage_flat = cur; } @@ -1992,6 +1979,8 @@ bool Mob::Rampage(ExtraAttackOptions *opts) rampage_targets = RuleI(Combat, DefaultRampageTargets); if (rampage_targets > RuleI(Combat, MaxRampageTargets)) rampage_targets = RuleI(Combat, MaxRampageTargets); + + m_specialattacks = eSpecialAttacks::Rampage; for (int i = 0; i < RampageArray.size(); i++) { if (index_hit >= rampage_targets) break; @@ -2001,14 +1990,16 @@ bool Mob::Rampage(ExtraAttackOptions *opts) if (m_target == GetTarget()) continue; if (CombatRange(m_target)) { - ProcessAttackRounds(m_target, opts, 2); + ProcessAttackRounds(m_target, opts); index_hit++; } } } if (RuleB(Combat, RampageHitsTarget) && index_hit < rampage_targets) - ProcessAttackRounds(GetTarget(), opts, 2); + ProcessAttackRounds(GetTarget(), opts); + + m_specialattacks = eSpecialAttacks::None; return true; } @@ -2024,10 +2015,12 @@ void Mob::AreaRampage(ExtraAttackOptions *opts) int rampage_targets = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 1); rampage_targets = rampage_targets > 0 ? rampage_targets : -1; + m_specialattacks = eSpecialAttacks::AERampage; index_hit = hate_list.AreaRampage(this, GetTarget(), rampage_targets, opts); if(index_hit == 0) - ProcessAttackRounds(GetTarget(), opts, 1); + ProcessAttackRounds(GetTarget(), opts); + m_specialattacks = eSpecialAttacks::None; } uint32 Mob::GetLevelCon(uint8 mylevel, uint8 iOtherLevel) { diff --git a/zone/npc.cpp b/zone/npc.cpp index d1255f8aa..ebff4786a 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -201,6 +201,9 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, const glm::vec4& position, int if CalcNPCDamage(); } + base_damage = round((max_dmg - min_dmg) / 1.9); + min_damage = min_dmg - round(base_damage / 10.0); + accuracy_rating = d->accuracy_rating; avoidance_rating = d->avoidance_rating; ATK = d->ATK; @@ -1054,7 +1057,7 @@ NPC* NPC::SpawnNPC(const char* spawncommand, const glm::vec4& position, Client* npc_type->WIS = 150; npc_type->CHA = 150; - npc_type->attack_delay = 30; + npc_type->attack_delay = 3000; npc_type->prim_melee_type = 28; npc_type->sec_melee_type = 28; @@ -1984,13 +1987,25 @@ void NPC::ModifyNPCStat(const char *identifier, const char *newValue) else if(id == "special_attacks") { NPCSpecialAttacks(val.c_str(), 0, 1); return; } else if(id == "special_abilities") { ProcessSpecialAbilities(val.c_str()); return; } else if(id == "attack_speed") { attack_speed = (float)atof(val.c_str()); CalcBonuses(); return; } - else if(id == "attack_delay") { attack_delay = atoi(val.c_str()); CalcBonuses(); return; } + else if(id == "attack_delay") { /* TODO: fix DB */attack_delay = atoi(val.c_str()) * 100; CalcBonuses(); return; } else if(id == "atk") { ATK = atoi(val.c_str()); return; } else if(id == "accuracy") { accuracy_rating = atoi(val.c_str()); return; } else if(id == "avoidance") { avoidance_rating = atoi(val.c_str()); return; } else if(id == "trackable") { trackable = atoi(val.c_str()); return; } - else if(id == "min_hit") { min_dmg = atoi(val.c_str()); return; } - else if(id == "max_hit") { max_dmg = atoi(val.c_str()); return; } + else if(id == "min_hit") { + min_dmg = atoi(val.c_str()); + // TODO: fix DB + base_damage = round((max_dmg - min_dmg) / 1.9); + min_damage = min_dmg - round(base_damage / 10.0); + return; + } + else if(id == "max_hit") { + max_dmg = atoi(val.c_str()); + // TODO: fix DB + base_damage = round((max_dmg - min_dmg) / 1.9); + min_damage = min_dmg - round(base_damage / 10.0); + return; + } else if(id == "attack_count") { attack_count = atoi(val.c_str()); return; } else if(id == "see_invis") { see_invis = atoi(val.c_str()); return; } else if(id == "see_invis_undead") { see_invis_undead = atoi(val.c_str()); return; } diff --git a/zone/npc.h b/zone/npc.h index 4b81d45a6..543725c28 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -109,9 +109,9 @@ public: //abstract virtual function implementations requird by base abstract class virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill); - virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, int special = 0); + virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None); virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false, - bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr, int special = 0); + bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr); virtual bool HasRaid() { return false; } virtual bool HasGroup() { return false; } virtual Raid* GetRaid() { return 0; } @@ -259,9 +259,11 @@ public: uint32 GetMaxDMG() const {return max_dmg;} uint32 GetMinDMG() const {return min_dmg;} + int GetBaseDamage() const { return base_damage; } + int GetMinDamage() const { return min_damage; } float GetSlowMitigation() const { return slow_mitigation; } float GetAttackSpeed() const {return attack_speed;} - uint8 GetAttackDelay() const {return attack_delay;} + int GetAttackDelay() const {return attack_delay;} bool IsAnimal() const { return(bodytype == BT_Animal); } uint16 GetPetSpellID() const {return pet_spell_id;} void SetPetSpellID(uint16 amt) {pet_spell_id = amt;} @@ -463,6 +465,8 @@ protected: uint32 max_dmg; uint32 min_dmg; + int base_damage; + int min_damage; int32 accuracy_rating; int32 avoidance_rating; int16 attack_count; diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 86eb01921..03e4a7d90 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -26,96 +26,138 @@ #include - -int Mob::GetKickDamage() { - int multiple=(GetLevel()*100/5); - multiple += 100; - int32 dmg = (((GetSkill(EQEmu::skills::SkillKick) + GetSTR() + GetLevel()) * 100 / 9000) * multiple) + 600; //Set a base of 6 damage, 1 seemed too low at the sub level 30 level. - if(GetClass() == WARRIOR || GetClass() == WARRIORGM ||GetClass() == BERSERKER || GetClass() == BERSERKERGM) { - dmg*=12/10;//small increase for warriors +int Mob::GetBaseSkillDamage(EQEmu::skills::SkillType skill, Mob *target) +{ + int base = EQEmu::skills::GetBaseDamage(skill); + auto skill_level = GetSkill(skill); + switch (skill) { + case EQEmu::skills::SkillDragonPunch: + case EQEmu::skills::SkillEagleStrike: + case EQEmu::skills::SkillTigerClaw: + if (skill_level >= 25) + base++; + if (skill_level >= 75) + base++; + if (skill_level >= 125) + base++; + if (skill_level >= 175) + base++; + return base; + case EQEmu::skills::SkillFrenzy: + if (IsClient() && CastToClient()->GetInv().GetItem(EQEmu::inventory::slotPrimary)) { + if (GetLevel() > 15) + base += GetLevel() - 15; + if (base > 23) + base = 23; + if (GetLevel() > 50) + base += 2; + if (GetLevel() > 54) + base++; + if (GetLevel() > 59) + base++; + } + return base; + case EQEmu::skills::SkillFlyingKick: { + float skill_bonus = skill_level / 9.0f; + float ac_bonus = 0.0f; + if (IsClient()) { + auto inst = CastToClient()->GetInv().GetItem(EQEmu::inventory::slotFeet); + if (inst) + ac_bonus = inst->GetItemArmorClass(true) / 25.0f; + } + if (ac_bonus > skill_bonus) + ac_bonus = skill_bonus; + return static_cast(ac_bonus + skill_bonus); } - dmg /= 100; - - int32 mindmg = 1; - ApplySpecialAttackMod(EQEmu::skills::SkillKick, dmg, mindmg); - - dmg = mod_kick_damage(dmg); - - return(dmg); -} - -int Mob::GetBashDamage() { - int multiple=(GetLevel()*100/5); - multiple += 100; - - //this is complete shite - int32 dmg = ((((GetSkill(EQEmu::skills::SkillBash) + GetSTR()) * 100 + GetLevel() * 100 / 2) / 10000) * multiple) + 600; //Set a base of 6 damage, 1 seemed too low at the sub level 30 level. - dmg /= 100; - - int32 mindmg = 1; - ApplySpecialAttackMod(EQEmu::skills::SkillBash, dmg, mindmg); - - dmg = mod_bash_damage(dmg); - - return(dmg); -} - -void Mob::ApplySpecialAttackMod(EQEmu::skills::SkillType skill, int32 &dmg, int32 &mindmg) { - int item_slot = -1; - //1: Apply bonus from AC (BOOT/SHIELD/HANDS) est. 40AC=6dmg - if (IsClient()){ - switch (skill) { - case EQEmu::skills::SkillFlyingKick: - case EQEmu::skills::SkillRoundKick: - case EQEmu::skills::SkillKick: - item_slot = EQEmu::inventory::slotFeet; - break; - case EQEmu::skills::SkillBash: - item_slot = EQEmu::inventory::slotSecondary; - break; - case EQEmu::skills::SkillDragonPunch: - case EQEmu::skills::SkillEagleStrike: - case EQEmu::skills::SkillTigerClaw: - item_slot = EQEmu::inventory::slotHands; - break; - default: - break; + case EQEmu::skills::SkillKick: { + // there is some base *= 4 case in here? + float skill_bonus = skill_level / 10.0f; + float ac_bonus = 0.0f; + if (IsClient()) { + auto inst = CastToClient()->GetInv().GetItem(EQEmu::inventory::slotFeet); + if (inst) + ac_bonus = inst->GetItemArmorClass(true) / 25.0f; } - - if (item_slot >= 0){ - const EQEmu::ItemInstance* itm = nullptr; - itm = CastToClient()->GetInv().GetItem(item_slot); - if(itm) - dmg += itm->GetItem()->AC * (RuleI(Combat, SpecialAttackACBonus))/100; + if (ac_bonus > skill_bonus) + ac_bonus = skill_bonus; + return static_cast(ac_bonus + skill_bonus); + } + case EQEmu::skills::SkillBash: { + float skill_bonus = skill_level / 10.0f; + float ac_bonus = 0.0f; + const EQEmu::ItemInstance *inst = nullptr; + if (IsClient()) { + if (HasShieldEquiped()) + inst = CastToClient()->GetInv().GetItem(EQEmu::inventory::slotSecondary); + else if (HasTwoHanderEquipped()) + inst = CastToClient()->GetInv().GetItem(EQEmu::inventory::slotPrimary); } + if (inst) + ac_bonus = inst->GetItemArmorClass(true) / 25.0f; + if (ac_bonus > skill_bonus) + ac_bonus = skill_bonus; + return static_cast(ac_bonus + skill_bonus); + } + case EQEmu::skills::SkillBackstab: { + float skill_bonus = static_cast(skill_level) * 0.02f; + base = 3; // There seems to be a base 3 for NPCs or some how BS w/o weapon? + // until we get a better inv system for NPCs they get nerfed! + if (IsClient()) { + auto *inst = CastToClient()->GetInv().GetItem(EQEmu::inventory::slotPrimary); + if (inst && inst->GetItem() && inst->GetItem()->ItemType == EQEmu::item::ItemType1HPiercing) { + base = inst->GetItemBackstabDamage(true); + if (!inst->GetItemBackstabDamage()) + base += inst->GetItemWeaponDamage(true); + if (target) { + if (inst->GetItemElementalFlag(true) && inst->GetItemElementalDamage(true)) + base += target->ResistElementalWeaponDmg(inst); + if (inst->GetItemBaneDamageBody(true) || inst->GetItemBaneDamageRace(true)) + base += target->CheckBaneDamage(inst); + } + } + } else if (IsNPC()) { + auto *npc = CastToNPC(); + base = std::max(base, npc->GetBaseDamage()); + // parses show relatively low BS mods from lots of NPCs, so either their BS skill is super low + // or their mod is divided again, this is probably not the right mod, but it's better + skill_bonus /= 3.0f; + } + // ahh lets make sure everything is casted right :P ugly but w/e + return static_cast(static_cast(base) * (skill_bonus + 2.0f)); + } + default: + return 0; } } -void Mob::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 max_damage, int32 min_damage, int32 hate_override, int ReuseTime, - bool CheckHitChance, bool CanAvoid) { - //this really should go through the same code as normal melee damage to - //pick up all the special behavior there +void Mob::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 base_damage, int32 min_damage, + int32 hate_override, int ReuseTime, bool CheckHitChance, bool CanAvoid) +{ + // this really should go through the same code as normal melee damage to + // pick up all the special behavior there - if ((who == nullptr || ((IsClient() && CastToClient()->dead) || (who->IsClient() && who->CastToClient()->dead)) || HasDied() || (!IsAttackAllowed(who)))) + if ((who == nullptr || + ((IsClient() && CastToClient()->dead) || (who->IsClient() && who->CastToClient()->dead)) || HasDied() || + (!IsAttackAllowed(who)))) return; - - if(who->GetInvul() || who->GetSpecialAbility(IMMUNE_MELEE)) - max_damage = -5; + + int damage = 0; + + if (who->GetInvul() || who->GetSpecialAbility(IMMUNE_MELEE)) + damage = -5; if (who->GetSpecialAbility(IMMUNE_MELEE_EXCEPT_BANE) && skill != EQEmu::skills::SkillBackstab) - max_damage = -5; + damage = -5; - uint32 hate = max_damage; - if(hate_override > -1) + uint32 hate = base_damage; + if (hate_override > -1) hate = hate_override; - if (skill == EQEmu::skills::SkillBash){ - if(IsClient()){ + if (skill == EQEmu::skills::SkillBash) { + if (IsClient()) { EQEmu::ItemInstance *item = CastToClient()->GetInv().GetItem(EQEmu::inventory::slotSecondary); - if(item) - { - if (item->GetItem()->ItemType == EQEmu::item::ItemTypeShield) - { + if (item) { + if (item->GetItem()->ItemType == EQEmu::item::ItemTypeShield) { hate += item->GetItem()->AC; } const EQEmu::ItemData *itm = item->GetItem(); @@ -127,46 +169,54 @@ void Mob::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 } } - min_damage += min_damage * GetMeleeMinDamageMod_SE(skill) / 100; + int min_cap = base_damage * GetMeleeMinDamageMod_SE(skill) / 100; - int hand = EQEmu::inventory::slotPrimary; // Avoid checks hand for throwing/archery exclusion, primary should work for most + auto offense = this->offense(skill); + + int hand = EQEmu::inventory::slotPrimary; // Avoid checks hand for throwing/archery exclusion, primary should + // work for most if (skill == EQEmu::skills::SkillThrowing || skill == EQEmu::skills::SkillArchery) hand = EQEmu::inventory::slotRange; - if (who->AvoidDamage(this, max_damage, hand)) { - if (max_damage == -3) + if (who->AvoidDamage(this, damage, hand)) { + if (damage == -3) DoRiposte(who); } else { - if (!CheckHitChance || (CheckHitChance && who->CheckHitChance(this, skill))) { - who->MeleeMitigation(this, max_damage, min_damage); - CommonOutgoingHitSuccess(who, max_damage, skill); + if (!CheckHitChance || who->CheckHitChance(this, skill)) { + if (base_damage > 0) // we do this weird, so we have to check it first :( + who->MeleeMitigation(this, damage, base_damage, offense, skill); + if (damage > 0) { + ApplyDamageTable(damage, offense); + CommonOutgoingHitSuccess(who, damage, min_damage, min_cap, skill); + } } else { - max_damage = 0; + damage = 0; } } who->AddToHateList(this, hate, 0, false); - if (max_damage > 0 && aabonuses.SkillAttackProc[0] && aabonuses.SkillAttackProc[1] == skill && + if (damage > 0 && aabonuses.SkillAttackProc[0] && aabonuses.SkillAttackProc[1] == skill && IsValidSpell(aabonuses.SkillAttackProc[2])) { float chance = aabonuses.SkillAttackProc[0] / 1000.0f; if (zone->random.Roll(chance)) SpellFinished(aabonuses.SkillAttackProc[2], who, EQEmu::CastingSlot::Item, 0, -1, spells[aabonuses.SkillAttackProc[2]].ResistDiff); } - who->Damage(this, max_damage, SPELL_UNKNOWN, skill, false); + who->Damage(this, damage, SPELL_UNKNOWN, skill, false); - //Make sure 'this' has not killed the target and 'this' is not dead (Damage shield ect). - if(!GetTarget())return; - if (HasDied()) return; + // Make sure 'this' has not killed the target and 'this' is not dead (Damage shield ect). + if (!GetTarget()) + return; + if (HasDied()) + return; if (HasSkillProcs()) - TrySkillProc(who, skill, ReuseTime*1000); - - if (max_damage > 0 && HasSkillProcSuccess()) - TrySkillProc(who, skill, ReuseTime*1000, true); + TrySkillProc(who, skill, ReuseTime * 1000); + if (damage > 0 && HasSkillProcSuccess()) + TrySkillProc(who, skill, ReuseTime * 1000, true); } - +// We should probably refactor this to take the struct not the packet void Client::OPCombatAbility(const EQApplicationPacket *app) { if(!GetTarget()) return; @@ -251,20 +301,16 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { else{ if (!GetTarget()->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) { dmg = 0; - ht = GetBashDamage(); + ht = GetBaseSkillDamage(EQEmu::skills::SkillBash, GetTarget()); } else{ - if(RuleB(Combat, UseIntervalAC)) - ht = dmg = GetBashDamage(); - else - ht = dmg = zone->random.Int(1, GetBashDamage()); - + ht = dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash, GetTarget()); } } ReuseTime = BashReuseTime-1-skill_reduction; ReuseTime = (ReuseTime*HasteMod)/100; - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillBash, dmg, 1, ht, ReuseTime); + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillBash, dmg, 0, ht, ReuseTime); if(ReuseTime > 0) { p_timers.Start(timer, ReuseTime); @@ -276,28 +322,18 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { if ((ca_atk->m_atk == 100) && (ca_atk->m_skill == EQEmu::skills::SkillFrenzy)){ CheckIncreaseSkill(EQEmu::skills::SkillFrenzy, GetTarget(), 10); int AtkRounds = 3; - int skillmod = 100 * GetSkill(EQEmu::skills::SkillFrenzy) / MaxSkill(EQEmu::skills::SkillFrenzy); - int32 max_dmg = (26 + ((((GetLevel()-6) * 2)*skillmod)/100)) * ((100+RuleI(Combat, FrenzyBonus))/100); - int32 min_dmg = 0; + int32 max_dmg = GetBaseSkillDamage(EQEmu::skills::SkillFrenzy, GetTarget()); DoAnim(anim2HSlashing); max_dmg = mod_frenzy_damage(max_dmg); - if (GetLevel() < 51) - min_dmg = 1; - else - min_dmg = GetLevel()*8/10; - - if (min_dmg > max_dmg) - max_dmg = min_dmg; - ReuseTime = FrenzyReuseTime-1-skill_reduction; ReuseTime = (ReuseTime*HasteMod)/100; //Live parses show around 55% Triple 35% Double 10% Single, you will always get first hit. while(AtkRounds > 0) { if (GetTarget() && (AtkRounds == 1 || zone->random.Roll(75))) { - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, min_dmg, max_dmg, ReuseTime, true); + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, 0, max_dmg, ReuseTime, true); } AtkRounds--; } @@ -327,18 +363,15 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { else{ if (!GetTarget()->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) { dmg = 0; - ht = GetKickDamage(); + ht = GetBaseSkillDamage(EQEmu::skills::SkillKick, GetTarget()); } else{ - if(RuleB(Combat, UseIntervalAC)) - ht = dmg = GetKickDamage(); - else - ht = dmg = zone->random.Int(1, GetKickDamage()); + ht = dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick, GetTarget()); } } ReuseTime = KickReuseTime-1-skill_reduction; - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillKick, dmg, 1, ht, ReuseTime); + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillKick, dmg, 0, ht, ReuseTime); } break; @@ -395,99 +428,91 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { } //returns the reuse time in sec for the special attack used. -int Mob::MonkSpecialAttack(Mob* other, uint8 unchecked_type) +int Mob::MonkSpecialAttack(Mob *other, uint8 unchecked_type) { - if(!other) + if (!other) return 0; int32 ndamage = 0; int32 max_dmg = 0; - int32 min_dmg = 1; + int32 min_dmg = 0; int reuse = 0; - EQEmu::skills::SkillType skill_type; //to avoid casting... even though it "would work" + EQEmu::skills::SkillType skill_type; // to avoid casting... even though it "would work" uint8 itemslot = EQEmu::inventory::slotFeet; + if (IsNPC()) { + auto *npc = CastToNPC(); + min_dmg = npc->GetMinDamage(); + } - switch(unchecked_type) { + switch (unchecked_type) { case EQEmu::skills::SkillFlyingKick: skill_type = EQEmu::skills::SkillFlyingKick; - max_dmg = ((GetSTR()+GetSkill(skill_type)) * RuleI(Combat, FlyingKickBonus) / 100) + 35; - min_dmg = ((level*8)/10); - ApplySpecialAttackMod(skill_type, max_dmg, min_dmg); + max_dmg = GetBaseSkillDamage(skill_type); + min_dmg = 0; // revamped FK formula is missing the min mod? DoAnim(animFlyingKick); reuse = FlyingKickReuseTime; break; case EQEmu::skills::SkillDragonPunch: skill_type = EQEmu::skills::SkillDragonPunch; - max_dmg = ((GetSTR()+GetSkill(skill_type)) * RuleI(Combat, DragonPunchBonus) / 100) + 26; + max_dmg = GetBaseSkillDamage(skill_type); itemslot = EQEmu::inventory::slotHands; - ApplySpecialAttackMod(skill_type, max_dmg, min_dmg); DoAnim(animTailRake); reuse = TailRakeReuseTime; break; case EQEmu::skills::SkillEagleStrike: skill_type = EQEmu::skills::SkillEagleStrike; - max_dmg = ((GetSTR()+GetSkill(skill_type)) * RuleI(Combat, EagleStrikeBonus) / 100) + 19; + max_dmg = GetBaseSkillDamage(skill_type); itemslot = EQEmu::inventory::slotHands; - ApplySpecialAttackMod(skill_type, max_dmg, min_dmg); DoAnim(animEagleStrike); reuse = EagleStrikeReuseTime; break; case EQEmu::skills::SkillTigerClaw: skill_type = EQEmu::skills::SkillTigerClaw; - max_dmg = ((GetSTR()+GetSkill(skill_type)) * RuleI(Combat, TigerClawBonus) / 100) + 12; + max_dmg = GetBaseSkillDamage(skill_type); itemslot = EQEmu::inventory::slotHands; - ApplySpecialAttackMod(skill_type, max_dmg, min_dmg); DoAnim(animTigerClaw); reuse = TigerClawReuseTime; break; case EQEmu::skills::SkillRoundKick: skill_type = EQEmu::skills::SkillRoundKick; - max_dmg = ((GetSTR()+GetSkill(skill_type)) * RuleI(Combat, RoundKickBonus) / 100) + 10; - ApplySpecialAttackMod(skill_type, max_dmg, min_dmg); + max_dmg = GetBaseSkillDamage(skill_type); DoAnim(animRoundKick); reuse = RoundKickReuseTime; break; case EQEmu::skills::SkillKick: skill_type = EQEmu::skills::SkillKick; - max_dmg = GetKickDamage(); + max_dmg = GetBaseSkillDamage(skill_type); DoAnim(animKick); reuse = KickReuseTime; break; default: Log.Out(Logs::Detail, Logs::Attack, "Invalid special attack type %d attempted", unchecked_type); - return(1000); /* nice long delay for them, the caller depends on this! */ + return (1000); /* nice long delay for them, the caller depends on this! */ } - if(IsClient()){ - if(GetWeaponDamage(other, CastToClient()->GetInv().GetItem(itemslot)) <= 0){ + if (IsClient()) { + if (GetWeaponDamage(other, CastToClient()->GetInv().GetItem(itemslot)) <= 0) { ndamage = -5; } - } - else{ - if (GetWeaponDamage(other, (const EQEmu::ItemData*)nullptr) <= 0){ + } else { + if (GetWeaponDamage(other, (const EQEmu::ItemData *)nullptr) <= 0) { ndamage = -5; } } int32 ht = 0; - if(ndamage == 0){ - if(other->CheckHitChance(this, skill_type, 0)){ - if(RuleB(Combat, UseIntervalAC)) - ht = ndamage = max_dmg; - else - ht = ndamage = zone->random.Int(min_dmg, max_dmg); - } - else{ - ht = max_dmg; - } + if (ndamage == 0) { + ht = max_dmg; + if (other->CheckHitChance(this, skill_type, 0)) + ndamage = max_dmg; } - //This can potentially stack with changes to kick damage + // This can potentially stack with changes to kick damage ht = ndamage = mod_monk_special_damage(ndamage, skill_type); DoSpecialAttackDamage(other, skill_type, ndamage, min_dmg, ht, reuse); - return(reuse); + return (reuse); } void Mob::TryBackstab(Mob *other, int ReuseTime) { @@ -542,22 +567,15 @@ void Mob::TryBackstab(Mob *other, int ReuseTime) { } //Live AA - Chaotic Backstab else if(aabonuses.FrontalBackstabMinDmg || itembonuses.FrontalBackstabMinDmg || spellbonuses.FrontalBackstabMinDmg) { + m_specialattacks = eSpecialAttacks::ChaoticStab; //we can stab from any angle, we do min damage though. + // chaotic backstab can't double etc Seized can, but that's because it's a chance to do normal BS + // Live actually added SPA 473 which grants chance to double here when they revamped chaotic/seized RogueBackstab(other, true, ReuseTime); - if (level > 54) { - - // Check for double attack with main hand assuming maxed DA Skill (MS) - if(IsClient() && CastToClient()->CheckDoubleAttack()) - if(other->GetHP() > 0) - RogueBackstab(other,true, ReuseTime); - - if (tripleChance && other->GetHP() > 0 && zone->random.Roll(tripleChance)) - RogueBackstab(other,false,ReuseTime); - } - if(IsClient()) CastToClient()->CheckIncreaseSkill(EQEmu::skills::SkillBackstab, other, 10); + m_specialattacks = eSpecialAttacks::None; } else { //We do a single regular attack if we attack from the front without chaotic stab Attack(other, EQEmu::inventory::slotPrimary); @@ -570,79 +588,21 @@ void Mob::RogueBackstab(Mob* other, bool min_damage, int ReuseTime) if (!other) return; - int32 ndamage = 0; - int32 max_hit = 0; - int32 min_hit = 0; uint32 hate = 0; - int32 primaryweapondamage = 0; - int32 backstab_dmg = 0; + // make sure we can hit (bane, magical, etc) if (IsClient()) { const EQEmu::ItemInstance *wpn = CastToClient()->GetInv().GetItem(EQEmu::inventory::slotPrimary); - if (wpn) { - primaryweapondamage = GetWeaponDamage(other, wpn); - if (primaryweapondamage) { - backstab_dmg = wpn->GetItemBackstabDamage(true); - backstab_dmg += other->ResistElementalWeaponDmg(wpn); - if (wpn->GetItemBaneDamageBody(true) || wpn->GetItemBaneDamageRace(true)) - backstab_dmg += other->CheckBaneDamage(wpn); - } - } - } else { - primaryweapondamage = - (GetLevel() / 7) + 1; // fallback incase it's a npc without a weapon, 2 dmg at 10, 10 dmg at 65 - backstab_dmg = primaryweapondamage; + if (!GetWeaponDamage(other, wpn)) + return; + } else if (!GetWeaponDamage(other, (const EQEmu::ItemData*)nullptr)){ + return; } - // ex. bane can make this false - if (primaryweapondamage > 0) { - // this is very wrong but not worth it until we fix the full dmg - if (level > 25) { - max_hit = (((((2 * backstab_dmg) * GetDamageTable(EQEmu::skills::SkillBackstab) / 100) * 10 * GetSkill(EQEmu::skills::SkillBackstab) / 355) + ((level - 25) / 3) + 1) * ((100 + RuleI(Combat, BackstabBonus)) / 100)); - hate = 20 * backstab_dmg * GetSkill(EQEmu::skills::SkillBackstab) / 355; - } else { - max_hit = (((((2 * backstab_dmg) * GetDamageTable(EQEmu::skills::SkillBackstab) / 100) * 10 * GetSkill(EQEmu::skills::SkillBackstab) / 355) + 1) * ((100 + RuleI(Combat, BackstabBonus)) / 100)); - hate = 20 * backstab_dmg * GetSkill(EQEmu::skills::SkillBackstab) / 355; - } + int base_damage = GetBaseSkillDamage(EQEmu::skills::SkillBackstab, other); + hate = base_damage; - // determine minimum hits - if (level < 51) { - min_hit = (level*15/10); - } else { - // Trumpcard: Replaced switch statement with formula calc. This will give minhit increases all the way to 65. - min_hit = (level * ( level*5 - 105)) / 100; - } - - if (!other->CheckHitChance(this, EQEmu::skills::SkillBackstab, 0)) { - ndamage = 0; - } else { - if (min_damage) { - ndamage = min_hit; - } else { - if (max_hit < min_hit) - max_hit = min_hit; - - if (RuleB(Combat, UseIntervalAC)) - ndamage = max_hit; - else - ndamage = zone->random.Int(min_hit, max_hit); - } - } - } else { - ndamage = -5; - } - - ndamage = mod_backstab_damage(ndamage); - - uint32 Assassinate_Dmg = 0; - Assassinate_Dmg = TryAssassinate(other, EQEmu::skills::SkillBackstab, ReuseTime); - - if (Assassinate_Dmg) { - ndamage = Assassinate_Dmg; - entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, ASSASSINATES, GetName()); - } - - DoSpecialAttackDamage(other, EQEmu::skills::SkillBackstab, ndamage, min_hit, hate, ReuseTime, false, false); + DoSpecialAttackDamage(other, EQEmu::skills::SkillBackstab, base_damage, 0, hate, ReuseTime, true, false); DoAnim(anim1HPiercing); } @@ -867,11 +827,6 @@ void Mob::DoArcheryAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon } else { Log.Out(Logs::Detail, Logs::Combat, "Ranged attack hit %s.", other->GetName()); - bool HeadShot = false; - uint32 HeadShot_Dmg = TryHeadShot(other, EQEmu::skills::SkillArchery); - if (HeadShot_Dmg) - HeadShot = true; - uint32 hate = 0; int32 TotalDmg = 0; int16 WDmg = 0; @@ -888,85 +843,42 @@ void Mob::DoArcheryAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon return; } + // unsure when this should happen if (focus) //From FcBaseEffects - WDmg += WDmg*focus/100; + WDmg += WDmg * focus / 100; if((WDmg > 0) || (ADmg > 0)) { if(WDmg < 0) WDmg = 0; if(ADmg < 0) ADmg = 0; - uint32 MaxDmg = (RuleR(Combat, ArcheryBaseDamageBonus)*(WDmg + ADmg)*GetDamageTable(EQEmu::skills::SkillArchery)) / 100; + uint32 MaxDmg = WDmg + ADmg; hate = ((WDmg+ADmg)); - if (HeadShot) - MaxDmg = HeadShot_Dmg; - - uint16 bonusArcheryDamageModifier = aabonuses.ArcheryDamageModifier + itembonuses.ArcheryDamageModifier + spellbonuses.ArcheryDamageModifier; - - MaxDmg += MaxDmg*bonusArcheryDamageModifier / 100; - if (RuleB(Combat, ProjectileDmgOnImpact)) Log.Out(Logs::Detail, Logs::Combat, "Bow and Arrow DMG %d, Max Damage %d.", WDmg, MaxDmg); else Log.Out(Logs::Detail, Logs::Combat, "Bow DMG %d, Arrow DMG %d, Max Damage %d.", WDmg, ADmg, MaxDmg); - bool dobonus = false; - if(GetClass() == RANGER && GetLevel() > 50){ - int bonuschance = RuleI(Combat, ArcheryBonusChance); - bonuschance = mod_archery_bonus_chance(bonuschance, RangeWeapon); - - if( !RuleB(Combat, UseArcheryBonusRoll) || zone->random.Roll(bonuschance)){ - if(RuleB(Combat, ArcheryBonusRequiresStationary)){ - if(other->IsNPC() && !other->IsMoving() && !other->IsRooted()) - dobonus = true; - } - else - dobonus = true; - } - - if(dobonus){ - MaxDmg *= 2; - hate *= 2; - MaxDmg = mod_archery_bonus_damage(MaxDmg, RangeWeapon); - - Log.Out(Logs::Detail, Logs::Combat, "Ranger. Double damage success roll, doubling damage to %d", MaxDmg); - Message_StringID(MT_CritMelee, BOW_DOUBLE_DAMAGE); - } - } - if (MaxDmg == 0) MaxDmg = 1; - if(RuleB(Combat, UseIntervalAC)) - TotalDmg = MaxDmg; - else - TotalDmg = zone->random.Int(1, MaxDmg); + int min_cap = MaxDmg * GetMeleeMinDamageMod_SE(EQEmu::skills::SkillArchery) / 100; + auto offense = this->offense(EQEmu::skills::SkillArchery); - int minDmg = 1; - if(GetLevel() > 25){ - //twice, for ammo and weapon - TotalDmg += (2*((GetLevel()-25)/3)); - minDmg += (2*((GetLevel()-25)/3)); - minDmg += minDmg * GetMeleeMinDamageMod_SE(EQEmu::skills::SkillArchery) / 100; - hate += (2*((GetLevel()-25)/3)); - } + other->AvoidDamage(this, TotalDmg, EQEmu::inventory::slotRange); - if (!HeadShot) - other->AvoidDamage(this, TotalDmg, EQEmu::inventory::slotRange); - - other->MeleeMitigation(this, TotalDmg, minDmg); + other->MeleeMitigation(this, TotalDmg, MaxDmg, offense, EQEmu::skills::SkillArchery); if(TotalDmg > 0){ - CommonOutgoingHitSuccess(other, TotalDmg, EQEmu::skills::SkillArchery); - TotalDmg = mod_archery_damage(TotalDmg, dobonus, RangeWeapon); + if (IsClient()) + ApplyDamageTable(TotalDmg, offense); + CommonOutgoingHitSuccess(other, TotalDmg, 0, min_cap, EQEmu::skills::SkillArchery); + //TotalDmg = mod_archery_damage(TotalDmg, dobonus, RangeWeapon); } } else TotalDmg = -5; - if (HeadShot) - entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FATAL_BOW_SHOT, GetName()); - if (IsClient() && !CastToClient()->GetFeigned()) other->AddToHateList(this, hate, 0, false); @@ -1227,8 +1139,8 @@ void NPC::RangedAttack(Mob* other) } } -void NPC::DoRangedAttackDmg(Mob* other, bool Launch, int16 damage_mod, int16 chance_mod, EQEmu::skills::SkillType skill, float speed, const char *IDFile) { - +void NPC::DoRangedAttackDmg(Mob* other, bool Launch, int16 damage_mod, int16 chance_mod, EQEmu::skills::SkillType skill, float speed, const char *IDFile) +{ if ((other == nullptr || (other->HasDied())) || HasDied() || @@ -1271,15 +1183,9 @@ void NPC::DoRangedAttackDmg(Mob* other, bool Launch, int16 damage_mod, int16 cha } else { - int32 TotalDmg = 0; - int32 MaxDmg = max_dmg * RuleR(Combat, ArcheryNPCMultiplier); // should add a field to npc_types - int32 MinDmg = min_dmg * RuleR(Combat, ArcheryNPCMultiplier); - - if(RuleB(Combat, UseIntervalAC)) - TotalDmg = MaxDmg; - else - TotalDmg = zone->random.Int(MinDmg, MaxDmg); - + int TotalDmg = 0; + int MaxDmg = GetBaseDamage() * RuleR(Combat, ArcheryNPCMultiplier); // should add a field to npc_types + int MinDmg = GetMinDamage() * RuleR(Combat, ArcheryNPCMultiplier); if (!damage_mod) damage_mod = GetSpecialAbilityParam(SPECATK_RANGED_ATK, 3);//Damage modifier @@ -1287,10 +1193,10 @@ void NPC::DoRangedAttackDmg(Mob* other, bool Launch, int16 damage_mod, int16 cha TotalDmg += TotalDmg * damage_mod / 100; other->AvoidDamage(this, TotalDmg, EQEmu::inventory::slotRange); - other->MeleeMitigation(this, TotalDmg, MinDmg); + other->MeleeMitigation(this, TotalDmg, MaxDmg, offense(skillInUse), skillInUse); if (TotalDmg > 0) - CommonOutgoingHitSuccess(other, TotalDmg, skillInUse); + CommonOutgoingHitSuccess(other, TotalDmg, MinDmg, 0, skillInUse); else if (TotalDmg < -4) TotalDmg = -5; @@ -1313,32 +1219,6 @@ void NPC::DoRangedAttackDmg(Mob* other, bool Launch, int16 damage_mod, int16 cha TrySkillProc(other, skillInUse, 0, false, EQEmu::inventory::slotRange); } -uint16 Mob::GetThrownDamage(int16 wDmg, int32& TotalDmg, int& minDmg) { - uint16 MaxDmg = (((2 * wDmg) * GetDamageTable(EQEmu::skills::SkillThrowing)) / 100); - - if (MaxDmg == 0) - MaxDmg = 1; - - if(RuleB(Combat, UseIntervalAC)) - TotalDmg = MaxDmg; - else - TotalDmg = zone->random.Int(1, MaxDmg); - - minDmg = 1; - if(GetLevel() > 25){ - TotalDmg += ((GetLevel()-25)/3); - minDmg += ((GetLevel()-25)/3); - minDmg += minDmg * GetMeleeMinDamageMod_SE(EQEmu::skills::SkillThrowing) / 100; - } - - if(MaxDmg < minDmg) - MaxDmg = minDmg; - - MaxDmg = mod_throwing_damage(MaxDmg); - - return MaxDmg; -} - void Client::ThrowingAttack(Mob* other, bool CanDoubleAttack) { //old was 51 //conditions to use an attack checked before we are called if (!other) @@ -1422,21 +1302,18 @@ void Client::ThrowingAttack(Mob* other, bool CanDoubleAttack) { //old was 51 CommonBreakInvisibleFromCombat(); } -void Mob::DoThrowingAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon, const EQEmu::ItemData* AmmoItem, uint16 weapon_damage, int16 chance_mod, int16 focus, int ReuseTime, uint32 range_id, int AmmoSlot, float speed) +void Mob::DoThrowingAttackDmg(Mob *other, const EQEmu::ItemInstance *RangeWeapon, const EQEmu::ItemData *AmmoItem, + uint16 weapon_damage, int16 chance_mod, int16 focus, int ReuseTime, uint32 range_id, + int AmmoSlot, float speed) { if ((other == nullptr || - ((IsClient() && CastToClient()->dead) || - (other->IsClient() && other->CastToClient()->dead)) || - HasDied() || - (!IsAttackAllowed(other)) || - (other->GetInvul() || - other->GetSpecialAbility(IMMUNE_MELEE)))) - { + ((IsClient() && CastToClient()->dead) || (other->IsClient() && other->CastToClient()->dead)) || + HasDied() || (!IsAttackAllowed(other)) || (other->GetInvul() || other->GetSpecialAbility(IMMUNE_MELEE)))) { return; } - const EQEmu::ItemInstance* _RangeWeapon = nullptr; - const EQEmu::ItemData* ammo_lost = nullptr; + const EQEmu::ItemInstance *_RangeWeapon = nullptr; + const EQEmu::ItemData *ammo_lost = nullptr; /* If LaunchProjectile is false this function will do archery damage on target, @@ -1447,95 +1324,97 @@ void Mob::DoThrowingAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon bool ProjectileImpact = false; bool ProjectileMiss = false; - if (RuleB(Combat, ProjectileDmgOnImpact)){ + if (RuleB(Combat, ProjectileDmgOnImpact)) { if (AmmoItem) LaunchProjectile = true; - else{ - if (!RangeWeapon && range_id){ + else { + if (!RangeWeapon && range_id) { ProjectileImpact = true; if (weapon_damage == 0) - ProjectileMiss = true; //This indicates that MISS was originally calculated. + ProjectileMiss = true; // This indicates that MISS was originally calculated. - if (IsClient()){ + if (IsClient()) { _RangeWeapon = CastToClient()->m_inv[AmmoSlot]; - if (_RangeWeapon && _RangeWeapon->GetItem() && _RangeWeapon->GetItem()->ID != range_id) + if (_RangeWeapon && _RangeWeapon->GetItem() && + _RangeWeapon->GetItem()->ID != range_id) RangeWeapon = _RangeWeapon; else ammo_lost = database.GetItem(range_id); } } } - } - else if (AmmoItem) + } else if (AmmoItem) SendItemAnimation(other, AmmoItem, EQEmu::skills::SkillThrowing); - if (ProjectileMiss || (!ProjectileImpact && !other->CheckHitChance(this, EQEmu::skills::SkillThrowing, chance_mod))){ + if (ProjectileMiss || + (!ProjectileImpact && !other->CheckHitChance(this, EQEmu::skills::SkillThrowing, chance_mod))) { Log.Out(Logs::Detail, Logs::Combat, "Ranged attack missed %s.", other->GetName()); - if (LaunchProjectile){ - TryProjectileAttack(other, AmmoItem, EQEmu::skills::SkillThrowing, 0, RangeWeapon, nullptr, AmmoSlot, speed); + if (LaunchProjectile) { + TryProjectileAttack(other, AmmoItem, EQEmu::skills::SkillThrowing, 0, RangeWeapon, nullptr, + AmmoSlot, speed); return; - } - else + } else other->Damage(this, 0, SPELL_UNKNOWN, EQEmu::skills::SkillThrowing); } else { Log.Out(Logs::Detail, Logs::Combat, "Throwing attack hit %s.", other->GetName()); int16 WDmg = 0; - if (!weapon_damage){ + if (!weapon_damage) { if (IsClient() && RangeWeapon) WDmg = GetWeaponDamage(other, RangeWeapon); else if (AmmoItem) WDmg = GetWeaponDamage(other, AmmoItem); - if (LaunchProjectile){ - TryProjectileAttack(other, AmmoItem, EQEmu::skills::SkillThrowing, WDmg, RangeWeapon, nullptr, AmmoSlot, speed); + if (LaunchProjectile) { + TryProjectileAttack(other, AmmoItem, EQEmu::skills::SkillThrowing, WDmg, RangeWeapon, + nullptr, AmmoSlot, speed); return; } - } - else + } else WDmg = weapon_damage; - if (focus) //From FcBaseEffects - WDmg += WDmg*focus/100; + if (focus) // From FcBaseEffects + WDmg += WDmg * focus / 100; int32 TotalDmg = 0; uint32 Assassinate_Dmg = 0; if (GetClass() == ROGUE && (BehindMob(other, GetX(), GetY()))) - Assassinate_Dmg = TryAssassinate(other, EQEmu::skills::SkillThrowing, ranged_timer.GetDuration()); - - if(WDmg > 0){ - int minDmg = 1; - uint16 MaxDmg = GetThrownDamage(WDmg, TotalDmg, minDmg); + Assassinate_Dmg = + TryAssassinate(other, EQEmu::skills::SkillThrowing, ranged_timer.GetDuration()); + if (WDmg > 0) { + int min_cap = WDmg * GetMeleeMinDamageMod_SE(EQEmu::skills::SkillThrowing) / 100; + auto offense = this->offense(EQEmu::skills::SkillThrowing); if (Assassinate_Dmg) { TotalDmg = Assassinate_Dmg; - entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, ASSASSINATES, GetName()); } - Log.Out(Logs::Detail, Logs::Combat, "Item DMG %d. Max Damage %d. Hit for damage %d", WDmg, MaxDmg, TotalDmg); + Log.Out(Logs::Detail, Logs::Combat, "Item DMG %d. Hit for damage %d", WDmg, TotalDmg); if (!Assassinate_Dmg) other->AvoidDamage(this, TotalDmg, EQEmu::inventory::slotRange); - other->MeleeMitigation(this, TotalDmg, minDmg); - if(TotalDmg > 0) - CommonOutgoingHitSuccess(other, TotalDmg, EQEmu::skills::SkillThrowing); + other->MeleeMitigation(this, TotalDmg, WDmg, offense, EQEmu::skills::SkillThrowing); + if (TotalDmg > 0) + if (IsClient()) + ApplyDamageTable(TotalDmg, offense); + CommonOutgoingHitSuccess(other, TotalDmg, 0, min_cap, EQEmu::skills::SkillThrowing); } else TotalDmg = -5; if (IsClient() && !CastToClient()->GetFeigned()) - other->AddToHateList(this, 2*WDmg, 0, false); + other->AddToHateList(this, 2 * WDmg, 0, false); other->Damage(this, TotalDmg, SPELL_UNKNOWN, EQEmu::skills::SkillThrowing); - if (TotalDmg > 0 && HasSkillProcSuccess() && other && !other->HasDied()){ + if (TotalDmg > 0 && HasSkillProcSuccess() && other && !other->HasDied()) { if (ReuseTime) TrySkillProc(other, EQEmu::skills::SkillThrowing, ReuseTime); else @@ -1546,13 +1425,13 @@ void Mob::DoThrowingAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon if (LaunchProjectile) return; - //Throwing item Proc + // Throwing item Proc if (ammo_lost) TryWeaponProc(nullptr, ammo_lost, other, EQEmu::inventory::slotRange); - else if(RangeWeapon && other && !other->HasDied()) + else if (RangeWeapon && other && !other->HasDied()) TryWeaponProc(RangeWeapon, other, EQEmu::inventory::slotRange); - if (HasSkillProcs() && other && !other->HasDied()){ + if (HasSkillProcs() && other && !other->HasDied()) { if (ReuseTime) TrySkillProc(other, EQEmu::skills::SkillThrowing, ReuseTime); else @@ -1755,17 +1634,12 @@ void NPC::DoClassAttacks(Mob *target) { dmg = -5; } else{ - if (target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) { - if(RuleB(Combat, UseIntervalAC)) - dmg = GetKickDamage(); - else - dmg = zone->random.Int(1, GetKickDamage()); - - } + if (target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) + dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick); } reuse = (KickReuseTime + 3) * 1000; - DoSpecialAttackDamage(target, EQEmu::skills::SkillKick, dmg, 1, -1, reuse); + DoSpecialAttackDamage(target, EQEmu::skills::SkillKick, dmg, GetMinDamage(), -1, reuse); did_attack = true; } else { @@ -1776,16 +1650,12 @@ void NPC::DoClassAttacks(Mob *target) { dmg = -5; } else{ - if (target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) { - if(RuleB(Combat, UseIntervalAC)) - dmg = GetBashDamage(); - else - dmg = zone->random.Int(1, GetBashDamage()); - } + if (target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) + dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash); } reuse = (BashReuseTime + 3) * 1000; - DoSpecialAttackDamage(target, EQEmu::skills::SkillBash, dmg, 1, -1, reuse); + DoSpecialAttackDamage(target, EQEmu::skills::SkillBash, dmg, GetMinDamage(), -1, reuse); did_attack = true; } } @@ -1793,28 +1663,16 @@ void NPC::DoClassAttacks(Mob *target) { } case BERSERKER: case BERSERKERGM:{ int AtkRounds = 3; - int32 max_dmg = 26 + ((GetLevel()-6) * 2); - int32 min_dmg = 0; + int32 max_dmg = GetBaseSkillDamage(EQEmu::skills::SkillFrenzy); DoAnim(anim2HSlashing); - if (GetLevel() < 51) - min_dmg = 1; - else - min_dmg = GetLevel()*8/10; - - if (min_dmg > max_dmg) - max_dmg = min_dmg; - - reuse = FrenzyReuseTime * 1000; - while(AtkRounds > 0) { if (GetTarget() && (AtkRounds == 1 || zone->random.Roll(75))) { - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, min_dmg, -1, reuse, true); + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, GetMinDamage(), -1, reuse, true); } AtkRounds--; } - did_attack = true; break; } @@ -1829,16 +1687,12 @@ void NPC::DoClassAttacks(Mob *target) { dmg = -5; } else{ - if (target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) { - if(RuleB(Combat, UseIntervalAC)) - dmg = GetKickDamage(); - else - dmg = zone->random.Int(1, GetKickDamage()); - } + if (target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) + dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick); } reuse = (KickReuseTime + 3) * 1000; - DoSpecialAttackDamage(target, EQEmu::skills::SkillKick, dmg, 1, -1, reuse); + DoSpecialAttackDamage(target, EQEmu::skills::SkillKick, dmg, GetMinDamage(), -1, reuse); did_attack = true; } break; @@ -1854,16 +1708,12 @@ void NPC::DoClassAttacks(Mob *target) { dmg = -5; } else{ - if (target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) { - if(RuleB(Combat, UseIntervalAC)) - dmg = GetBashDamage(); - else - dmg = zone->random.Int(1, GetBashDamage()); - } + if (target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) + dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash); } reuse = (BashReuseTime + 3) * 1000; - DoSpecialAttackDamage(target, EQEmu::skills::SkillBash, dmg, 1, -1, reuse); + DoSpecialAttackDamage(target, EQEmu::skills::SkillBash, dmg, GetMinDamage(), -1, reuse); did_attack = true; } break; @@ -1873,6 +1723,7 @@ void NPC::DoClassAttacks(Mob *target) { classattack_timer.Start(reuse / HasteModifier); } +// this should be refactored to generate an OP_CombatAbility struct and call OPCombatAbility void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) { if(!ca_target) @@ -1964,17 +1815,13 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) dmg = 0; } else{ - if(RuleB(Combat, UseIntervalAC)) - dmg = GetBashDamage(); - else - dmg = zone->random.Int(1, GetBashDamage()); - + dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash, ca_target); } } ReuseTime = (BashReuseTime - 1) / HasteMod; - DoSpecialAttackDamage(ca_target, EQEmu::skills::SkillBash, dmg, 1, -1, ReuseTime); + DoSpecialAttackDamage(ca_target, EQEmu::skills::SkillBash, dmg, 0, -1, ReuseTime); if(ReuseTime > 0 && !IsRiposte) { p_timers.Start(pTimerCombatAbility, ReuseTime); @@ -1986,26 +1833,16 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) if (skill_to_use == EQEmu::skills::SkillFrenzy){ CheckIncreaseSkill(EQEmu::skills::SkillFrenzy, GetTarget(), 10); int AtkRounds = 3; - int skillmod = 100 * GetSkill(EQEmu::skills::SkillFrenzy) / MaxSkill(EQEmu::skills::SkillFrenzy); - int32 max_dmg = (26 + ((((GetLevel()-6) * 2)*skillmod)/100)) * ((100+RuleI(Combat, FrenzyBonus))/100); - int32 min_dmg = 0; + int32 max_dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash, GetTarget()); DoAnim(anim2HSlashing); - if (GetLevel() < 51) - min_dmg = 1; - else - min_dmg = GetLevel()*8/10; - - if (min_dmg > max_dmg) - max_dmg = min_dmg; - ReuseTime = (FrenzyReuseTime - 1) / HasteMod; //Live parses show around 55% Triple 35% Double 10% Single, you will always get first hit. while(AtkRounds > 0) { if (GetTarget() && (AtkRounds == 1 || zone->random.Roll(75))) { - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, min_dmg, max_dmg, ReuseTime, true); + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, 0, max_dmg, ReuseTime, true); } AtkRounds--; } @@ -2028,16 +1865,13 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) dmg = 0; } else{ - if(RuleB(Combat, UseIntervalAC)) - dmg = GetKickDamage(); - else - dmg = zone->random.Int(1, GetKickDamage()); + dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick, ca_target); } } ReuseTime = KickReuseTime-1; - DoSpecialAttackDamage(ca_target, EQEmu::skills::SkillKick, dmg, 1, -1, ReuseTime); + DoSpecialAttackDamage(ca_target, EQEmu::skills::SkillKick, dmg, 0, -1, ReuseTime); } } @@ -2218,7 +2052,7 @@ void Mob::InstillDoubt(Mob *who) { } } -uint32 Mob::TryHeadShot(Mob* defender, EQEmu::skills::SkillType skillInUse) { +int Mob::TryHeadShot(Mob* defender, EQEmu::skills::SkillType skillInUse) { //Only works on YOUR target. if(defender && (defender->GetBodyType() == BT_Humanoid) && !defender->IsClient() && (skillInUse == EQEmu::skills::SkillArchery) && (GetTarget() == defender)) { @@ -2233,8 +2067,10 @@ uint32 Mob::TryHeadShot(Mob* defender, EQEmu::skills::SkillType skillInUse) { if(HeadShot_Dmg && HeadShot_Level && (defender->GetLevel() <= HeadShot_Level)){ float ProcChance = GetSpecialProcChances(EQEmu::inventory::slotRange); - if(zone->random.Roll(ProcChance)) + if(zone->random.Roll(ProcChance)) { + entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FATAL_BOW_SHOT, GetName()); return HeadShot_Dmg; + } } } @@ -2269,9 +2105,9 @@ float Mob::GetSpecialProcChances(uint16 hand) return ProcChance; } -uint32 Mob::TryAssassinate(Mob* defender, EQEmu::skills::SkillType skillInUse, uint16 ReuseTime) { +int Mob::TryAssassinate(Mob* defender, EQEmu::skills::SkillType skillInUse, uint16 ReuseTime) { - if(defender && (defender->GetBodyType() == BT_Humanoid) && !defender->IsClient() && + if(defender && (defender->GetBodyType() == BT_Humanoid) && !defender->IsClient() && GetLevel() >= 60 && (skillInUse == EQEmu::skills::SkillBackstab || skillInUse == EQEmu::skills::SkillThrowing)) { uint32 Assassinate_Dmg = aabonuses.Assassinate[1] + spellbonuses.Assassinate[1] + itembonuses.Assassinate[1]; @@ -2283,13 +2119,9 @@ uint32 Mob::TryAssassinate(Mob* defender, EQEmu::skills::SkillType skillInUse, u else if (Assassinate_Level < itembonuses.AssassinateLevel) Assassinate_Level = itembonuses.AssassinateLevel; - if (GetLevel() >= 60){ //Innate Assassinate Ability if client as no bonuses. - if (!Assassinate_Level) - Assassinate_Level = 45; - - if (!Assassinate_Dmg) - Assassinate_Dmg = 32000; - } + // revamped AAs require AA line I believe? + if (!Assassinate_Level) + return 0; if(Assassinate_Dmg && Assassinate_Level && (defender->GetLevel() <= Assassinate_Level)){ float ProcChance = 0.0f; @@ -2299,8 +2131,11 @@ uint32 Mob::TryAssassinate(Mob* defender, EQEmu::skills::SkillType skillInUse, u else ProcChance = GetAssassinateProcChances(ReuseTime); - if(zone->random.Roll(ProcChance)) + if(zone->random.Roll(ProcChance)) { + entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, ASSASSINATES, + GetName()); return Assassinate_Dmg; + } } } @@ -2340,34 +2175,20 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills: /* For spells using skill value 98 (feral swipe ect) server sets this to 67 automatically. Kayen: This is unlikely to be completely accurate but use OFFENSE skill value for these effects. + TODO: We need to stop moving skill 98, it's suppose to just be a dummy skill AFAIK + Spells using offense should use the skill of your primary, if you can use it, otherwise h2h */ if (skillinuse == EQEmu::skills::SkillBegging) skillinuse = EQEmu::skills::SkillOffense; int damage = 0; uint32 hate = 0; - int Hand = EQEmu::inventory::slotPrimary; if (hate == 0 && weapon_damage > 1) hate = weapon_damage; if(weapon_damage > 0){ if (focus) //From FcBaseEffects weapon_damage += weapon_damage*focus/100; - if(GetClass() == BERSERKER){ - int bonus = 3 + GetLevel()/10; - weapon_damage = weapon_damage * (100+bonus) / 100; - } - - int32 min_hit = 1; - int32 max_hit = (2 * weapon_damage*GetDamageTable(skillinuse)) / 100; - - if(GetLevel() >= 28 && IsWarriorClass() ) { - int ucDamageBonus = GetWeaponDamageBonus((const EQEmu::ItemData*) nullptr); - min_hit += (int) ucDamageBonus; - max_hit += (int) ucDamageBonus; - hate += ucDamageBonus; - } - if (skillinuse == EQEmu::skills::SkillBash){ if(IsClient()){ EQEmu::ItemInstance *item = CastToClient()->GetInv().GetItem(EQEmu::inventory::slotSecondary); @@ -2381,16 +2202,11 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills: } } - ApplySpecialAttackMod(skillinuse, max_hit, min_hit); - 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 = zone->random.Int(min_hit, max_hit); + int min_cap = weapon_damage * GetMeleeMinDamageMod_SE(skillinuse) / 100; + auto offense = this->offense(skillinuse); + int min_damage = 0; + if (IsNPC()) + min_damage = CastToNPC()->GetMinDamage(); if (other->AvoidDamage(this, damage, CanRiposte ? EQEmu::inventory::slotRange : EQEmu::inventory::slotPrimary)) { // SlotRange excludes ripo, primary doesn't have any extra behavior if (damage == -3) { @@ -2400,8 +2216,9 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills: } } else { if (other->CheckHitChance(this, skillinuse, chance_mod)) { - other->MeleeMitigation(this, damage, min_hit); - CommonOutgoingHitSuccess(other, damage, skillinuse); + other->MeleeMitigation(this, damage, weapon_damage, offense, skillinuse); + ApplyDamageTable(damage, offense); + CommonOutgoingHitSuccess(other, damage, min_damage, min_cap, skillinuse); } else { damage = 0; } diff --git a/zone/tune.cpp b/zone/tune.cpp index 6595ad4d0..bbb56217e 100644 --- a/zone/tune.cpp +++ b/zone/tune.cpp @@ -486,7 +486,7 @@ int32 Mob::Tune_MeleeMitigation(Mob* GM, Mob *attacker, int32 damage, int32 minh } - damage = GetMeleeMitDmg(attacker, damage, minhit, mitigation_rating, attack_rating); + //damage = GetMeleeMitDmg(attacker, damage, minhit, mitigation_rating, attack_rating); } if (damage < 0) @@ -539,7 +539,7 @@ int32 Client::Tune_GetMeleeMitDmg(Mob* GM, Mob *attacker, int32 damage, int32 mi float mit_rating, float atk_rating) { if (!attacker->IsNPC() || RuleB(Combat, UseOldDamageIntervalRules)) - return Mob::GetMeleeMitDmg(attacker, damage, minhit, mit_rating, atk_rating); + return 0; //Mob::GetMeleeMitDmg(attacker, damage, minhit, mit_rating, atk_rating); int d = 10; // floats for the rounding issues float dmg_interval = (damage - minhit) / 19.0; @@ -613,7 +613,7 @@ int32 Client::GetMeleeDamage(Mob* other, bool GetMinDamage) } int min_hit = 1; - int max_hit = (2*weapon_damage*GetDamageTable(skillinuse)) / 100; + int max_hit = 2;//(2*weapon_damage*GetDamageTable(skillinuse)) / 100; if(GetLevel() < 10 && max_hit > RuleI(Combat, HitCapPre10)) max_hit = (RuleI(Combat, HitCapPre10)); @@ -1086,4 +1086,4 @@ float Mob::Tune_CheckHitChance(Mob* defender, Mob* attacker, EQEmu::skills::Skil } return(chancetohit); -} \ No newline at end of file +} diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index c4fcfc55f..ec56b8df8 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -2133,7 +2133,7 @@ const NPCType* ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load temp_npctype_data->healscale = atoi(row[87]); temp_npctype_data->no_target_hotkey = atoi(row[88]) == 1 ? true: false; temp_npctype_data->raid_target = atoi(row[89]) == 0 ? false: true; - temp_npctype_data->attack_delay = atoi(row[90]); + temp_npctype_data->attack_delay = atoi(row[90]) * 100; // TODO: fix DB temp_npctype_data->light = (atoi(row[91]) & 0x0F); temp_npctype_data->armtexture = atoi(row[92]); diff --git a/zone/zonedump.h b/zone/zonedump.h index c6121fd48..ef8da2243 100644 --- a/zone/zonedump.h +++ b/zone/zonedump.h @@ -110,7 +110,7 @@ struct NPCType uint8 spawn_limit; //only this many may be in zone at a time (0=no limit) uint8 mount_color; //only used by horse class float attack_speed; //%+- on attack delay of the mob. - uint8 attack_delay; //delay between attacks in 10ths of a second + int attack_delay; //delay between attacks in ms int accuracy_rating; // flat bonus before mods int avoidance_rating; // flat bonus before mods bool findable; //can be found with find command From 7e49a21b3bc6100bc6d0df1e2d6d274cdc97e1bb Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sun, 15 Jan 2017 13:54:10 -0500 Subject: [PATCH 05/50] Change NPC skill AC bonus Basically, live doesn't have an NPC's skill at the max for their class like we do. So for now, we'll just set their SkillDefense bonus to value / 5 --- zone/attack.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/zone/attack.cpp b/zone/attack.cpp index 219ee231e..122bc7da5 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -735,7 +735,7 @@ int Mob::ACSum() // EQ math ac = (ac * 4) / 3; // anti-twink - if (GetLevel() < 50) + if (IsClient() && GetLevel() < 50) ac = std::min(ac, 25 + 6 * GetLevel()); ac = std::max(0, ac + GetClassRaceACBonus()); if (IsNPC()) { @@ -751,12 +751,19 @@ int Mob::ACSum() owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner()); if (owner) ac += owner->aabonuses.PetAvoidance + owner->spellbonuses.PetAvoidance + owner->itembonuses.PetAvoidance; + auto spell_aa_ac = aabonuses.AC + spellbonuses.AC; + ac += GetSkill(EQEmu::skills::SkillDefense) / 5; + if (EQEmu::ValueWithin(static_cast(GetClass()), NECROMANCER, ENCHANTER)) + ac += spell_aa_ac / 3; + else + ac += spell_aa_ac / 4; + } else { // TODO: so we can't set NPC skills ... so the skill bonus ends up being HUGE so lets nerf them a bit + auto spell_aa_ac = aabonuses.AC + spellbonuses.AC; + if (EQEmu::ValueWithin(static_cast(GetClass()), NECROMANCER, ENCHANTER)) + ac += GetSkill(EQEmu::skills::SkillDefense) / 2 + spell_aa_ac / 3; + else + ac += GetSkill(EQEmu::skills::SkillDefense) / 3 + spell_aa_ac / 4; } - auto spell_aa_ac = aabonuses.AC + spellbonuses.AC; - if (EQEmu::ValueWithin(static_cast(GetClass()), NECROMANCER, ENCHANTER)) - ac += GetSkill(EQEmu::skills::SkillDefense) / 2 + spell_aa_ac / 3; - else - ac += GetSkill(EQEmu::skills::SkillDefense) / 3 + spell_aa_ac / 4; if (GetAGI() > 70) ac += GetAGI() / 20; From c030e1ce8d54ddce83508af94a38a93165cede30 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sun, 15 Jan 2017 15:39:12 -0500 Subject: [PATCH 06/50] Add rule Combat:LevelToStopDamageCaps Setting this to 1 will effectively disable damage caps Setting this to 20 will give similar results to old incorrect default rules --- common/ruletypes.h | 1 + zone/attack.cpp | 3 +++ 2 files changed, 4 insertions(+) diff --git a/common/ruletypes.h b/common/ruletypes.h index dc1a31c1f..2b0b71899 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -492,6 +492,7 @@ RULE_INT(Combat, NPCAssistCap, 5) // Maxiumium number of NPCs that will assist a RULE_INT(Combat, NPCAssistCapTimer, 6000) // Time in milliseconds a NPC will take to clear assist aggro cap space RULE_BOOL(Combat, UseRevampHandToHand, false) // use h2h revamped dmg/delays I believe this was implemented during SoF RULE_BOOL(Combat, ClassicMasterWu, false) // classic master wu uses a random special, modern doesn't +RULE_INT(Combat, LevelToStopDamageCaps, 0) // 1 will effectively disable them, 20 should give basically same results as old incorrect system RULE_CATEGORY_END() RULE_CATEGORY(NPC) diff --git a/zone/attack.cpp b/zone/attack.cpp index 122bc7da5..a1dfbc176 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -1107,6 +1107,9 @@ int Client::DoDamageCaps(int base_damage) { // this is based on a client function that caps melee base_damage auto level = GetLevel(); + auto stop_level = RuleI(Combat, LevelToStopDamageCaps); + if (stop_level && stop_level <= level) + return base_damage; int cap = 0; if (level >= 125) { cap = 7 * level; From e03a90b05dfa5eaa6c66f7de4f20fa41959e5cee Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sun, 15 Jan 2017 16:08:22 -0500 Subject: [PATCH 07/50] Refactor CheckHitChance Pulled the accuracy/avoidance spell bonuses into their own functions so we can show the total values in #showstats --- zone/attack.cpp | 157 +++++++++++++++++++++++++++--------------------- zone/mob.h | 2 + 2 files changed, 89 insertions(+), 70 deletions(-) diff --git a/zone/attack.cpp b/zone/attack.cpp index a1dfbc176..cdc3bab06 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -157,6 +157,62 @@ int Mob::compute_tohit(EQEmu::skills::SkillType skillinuse) return std::max(tohit, 1); } +// return -1 in cases that always hit +int Mob::GetTotalToHit(EQEmu::skills::SkillType skill, int chance_mod) +{ + if (chance_mod >= 10000) // override for stuff like SE_SkillAttack + return -1; + + // calculate attacker's accuracy + auto accuracy = compute_tohit(skill) + 10; // add 10 in case the NPC's stats are fucked + if (chance_mod > 0) // multiplier + accuracy *= chance_mod; + + // Torven parsed an apparent constant of 1.2 somewhere in here * 6 / 5 looks eqmathy to me! + accuracy = accuracy * 6 / 5; + + // unsure on the stacking order of these effects, rather hard to parse + // item mod2 accuracy isn't applied to range? Theory crafting and parses back it up I guess + // mod2 accuracy -- flat bonus + if (skill != EQEmu::skills::SkillArchery && skill != EQEmu::skills::SkillThrowing) + accuracy += itembonuses.HitChance; + + // 216 Melee Accuracy Amt aka SE_Accuracy -- flat bonus + accuracy += itembonuses.Accuracy[EQEmu::skills::HIGHEST_SKILL + 1] + + aabonuses.Accuracy[EQEmu::skills::HIGHEST_SKILL + 1] + + spellbonuses.Accuracy[EQEmu::skills::HIGHEST_SKILL + 1] + + itembonuses.Accuracy[skill] + + aabonuses.Accuracy[skill] + + spellbonuses.Accuracy[skill]; + + // auto hit discs (and looks like there are some autohit AAs) + if (spellbonuses.HitChanceEffect[skill] >= 10000 || aabonuses.HitChanceEffect[skill] >= 10000) + return -1; + + if (spellbonuses.HitChanceEffect[EQEmu::skills::HIGHEST_SKILL + 1] >= 10000) + return -1; + + // 184 Accuracy % aka SE_HitChance -- percentage increase + auto hit_bonus = itembonuses.HitChanceEffect[EQEmu::skills::HIGHEST_SKILL + 1] + + aabonuses.HitChanceEffect[EQEmu::skills::HIGHEST_SKILL + 1] + + spellbonuses.HitChanceEffect[EQEmu::skills::HIGHEST_SKILL + 1] + + itembonuses.HitChanceEffect[skill] + + aabonuses.HitChanceEffect[skill] + + spellbonuses.HitChanceEffect[skill]; + + accuracy = (accuracy * (100 + hit_bonus)) / 100; + + // TODO: April 2003 added an archery/throwing PVP accuracy penalty while moving, should be in here some where, + // but PVP is less important so I haven't tried parsing it at all + + // There is also 110 Ranger Archery Accuracy % which should probably be in here some where, but it's not in any spells/aas + // Name implies it's a percentage increase, if one wishes to implement, do it like the hit_bonus above but limited to ranger archery + + // There is also 183 UNUSED - Skill Increase Chance which devs say isn't used at all in code, but some spells reference it + // I do not recommend implementing this once since there are spells that use it which would make this not live-like with default spell files + return accuracy; +} + // based on dev quotes // the AGI bonus has actually drastically changed from classic int Mob::compute_defense() @@ -181,6 +237,33 @@ int Mob::compute_defense() return std::max(1, defense); } +// return -1 in cases that always miss +int Mob::GetTotalDefense() +{ + auto avoidance = compute_defense() + 10; // add 10 in case the NPC's stats are fucked + auto evasion_bonus = spellbonuses.AvoidMeleeChanceEffect; // we check this first since it has a special case + if (evasion_bonus >= 10000) + return -1; + // + // 172 Evasion aka SE_AvoidMeleeChance + evasion_bonus += itembonuses.AvoidMeleeChanceEffect + aabonuses.AvoidMeleeChanceEffect; // item bonus here isn't mod2 avoidance + + Mob *owner = nullptr; + if (IsPet()) + owner = GetOwner(); + else if (IsNPC() && CastToNPC()->GetSwarmOwner()) + owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner()); + + if (owner) // 215 Pet Avoidance % aka SE_PetAvoidance + evasion_bonus += owner->aabonuses.PetAvoidance + owner->spellbonuses.PetAvoidance + owner->itembonuses.PetAvoidance; + + // Evasion is a percentage bonus according to AA descriptions + if (evasion_bonus) + avoidance = (avoidance * (100 + evasion_bonus)) / 100; + + return avoidance; +} + // 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, EQEmu::skills::SkillType skillinuse, int chance_mod) @@ -192,80 +275,14 @@ bool Mob::CheckHitChance(Mob* other, EQEmu::skills::SkillType skillinuse, int ch if (defender->IsClient() && defender->CastToClient()->IsSitting()) return true; - // calculate defender's avoidance - auto avoidance = defender->compute_defense() + 10; // add 10 in case the NPC's stats are fucked - auto evasion_bonus = defender->spellbonuses.AvoidMeleeChanceEffect; // we check this first since it has a special case - if (evasion_bonus <= -100) - return true; - if (evasion_bonus >= 10000) // some sort of auto avoid disc + auto avoidance = defender->GetTotalDefense(); + if (avoidance == -1) // some sort of auto avoid disc return false; - // 172 Evasion aka SE_AvoidMeleeChance - evasion_bonus += defender->itembonuses.AvoidMeleeChanceEffect + defender->aabonuses.AvoidMeleeChanceEffect; // item bonus here isn't mod2 avoidance - Mob *owner = nullptr; - if (defender->IsPet()) - owner = defender->GetOwner(); - else if (defender->IsNPC() && defender->CastToNPC()->GetSwarmOwner()) - owner = entity_list.GetMobID(defender->CastToNPC()->GetSwarmOwner()); - - if (owner) // 215 Pet Avoidance % aka SE_PetAvoidance - evasion_bonus += owner->aabonuses.PetAvoidance + owner->spellbonuses.PetAvoidance + owner->itembonuses.PetAvoidance; - - // Evasion is a percentage bonus according to AA descriptions - if (evasion_bonus) - avoidance = (avoidance * (100 + evasion_bonus)) / 100; - - if (chance_mod >= 10000) // override for stuff like SE_SkillAttack + auto accuracy = attacker->GetTotalToHit(skillinuse, chance_mod); + if (accuracy == -1) return true; - // calculate attacker's accuracy - auto accuracy = attacker->compute_tohit(skillinuse) + 10; // add 10 in case the NPC's stats are fucked - if (chance_mod > 0) // multiplier - accuracy *= chance_mod; - - // Torven parsed an apparent constant of 1.2 somewhere in here * 6 / 5 looks eqmathy to me! - accuracy = accuracy * 6 / 5; - - // unsure on the stacking order of these effects, rather hard to parse - // item mod2 accuracy isn't applied to range? Theory crafting and parses back it up I guess - // mod2 accuracy -- flat bonus - if (skillinuse != EQEmu::skills::SkillArchery && skillinuse != EQEmu::skills::SkillThrowing) - accuracy += attacker->itembonuses.HitChance; - - // 216 Melee Accuracy Amt aka SE_Accuracy -- flat bonus - accuracy += attacker->itembonuses.Accuracy[EQEmu::skills::HIGHEST_SKILL + 1] + - attacker->aabonuses.Accuracy[EQEmu::skills::HIGHEST_SKILL + 1] + - attacker->spellbonuses.Accuracy[EQEmu::skills::HIGHEST_SKILL + 1] + - attacker->itembonuses.Accuracy[skillinuse] + - attacker->aabonuses.Accuracy[skillinuse] + - attacker->spellbonuses.Accuracy[skillinuse]; - - // auto hit discs (and looks like there are some autohit AAs) - if (attacker->spellbonuses.HitChanceEffect[skillinuse] >= 10000 || attacker->aabonuses.HitChanceEffect[skillinuse] >= 10000) - return true; - - if (attacker->spellbonuses.HitChanceEffect[EQEmu::skills::HIGHEST_SKILL + 1] >= 10000) - return true; - - // 184 Accuracy % aka SE_HitChance -- percentage increase - auto hit_bonus = attacker->itembonuses.HitChanceEffect[EQEmu::skills::HIGHEST_SKILL + 1] + - attacker->aabonuses.HitChanceEffect[EQEmu::skills::HIGHEST_SKILL + 1] + - attacker->spellbonuses.HitChanceEffect[EQEmu::skills::HIGHEST_SKILL + 1] + - attacker->itembonuses.HitChanceEffect[skillinuse] + - attacker->aabonuses.HitChanceEffect[skillinuse] + - attacker->spellbonuses.HitChanceEffect[skillinuse]; - - accuracy = (accuracy * (100 + hit_bonus)) / 100; - - // TODO: April 2003 added an archery/throwing PVP accuracy penalty while moving, should be in here some where, - // but PVP is less important so I haven't tried parsing it at all - - // There is also 110 Ranger Archery Accuracy % which should probably be in here some where, but it's not in any spells/aas - // Name implies it's a percentage increase, if one wishes to implement, do it like the hit_bonus above but limited to ranger archery - - // There is also 183 UNUSED - Skill Increase Chance which devs say isn't used at all in code, but some spells reference it - // I do not recommend implementing this once since there are spells that use it which would make this not live-like with default spell files - // so now we roll! // relevant dev quote: // Then your chance to simply avoid the attack is checked (defender's avoidance roll beat the attacker's accuracy roll.) diff --git a/zone/mob.h b/zone/mob.h index 802eede5c..6e390d7d6 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -166,7 +166,9 @@ public: virtual void TryBackstab(Mob *other,int ReuseTime = 10); bool AvoidDamage(Mob* attacker, int &damage, int hand); int compute_tohit(EQEmu::skills::SkillType skillinuse); + int GetTotalToHit(EQEmu::skills::SkillType skill, int chance_mod); // compute_tohit + spell bonuses int compute_defense(); + int GetTotalDefense(); // compute_defense + spell bonuses bool CheckHitChance(Mob* attacker, EQEmu::skills::SkillType skillinuse, int chance_mod = 0); virtual void TryCriticalHit(Mob *defender, uint16 skill, int &damage, int min_damage, ExtraAttackOptions *opts = nullptr); void TryPetCriticalHit(Mob *defender, uint16 skill, int &damage); From 8c6fefa33e784ae5495246d997aaeb0afa0294a4 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sun, 15 Jan 2017 16:32:15 -0500 Subject: [PATCH 08/50] Update #showstats --- zone/client.cpp | 42 +++++++++++++++++++++++++++++++++++++----- zone/mob.cpp | 3 +++ 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index a066b9b12..5b849ba58 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -6863,16 +6863,46 @@ void Client::SendStatsWindow(Client* client, bool use_window) indP + "Wind: " + itoa(GetWindMod()) + "
"; } + EQEmu::skills::SkillType skill = EQEmu::skills::SkillHandtoHand; + auto *inst = GetInv().GetItem(EQEmu::inventory::slotPrimary); + if (inst && inst->IsClassCommon()) { + switch (inst->GetItem()->ItemType) { + case EQEmu::item::ItemType1HSlash: + skill = EQEmu::skills::Skill1HSlashing; + break; + case EQEmu::item::ItemType2HSlash: + skill = EQEmu::skills::Skill2HSlashing; + break; + case EQEmu::item::ItemType1HPiercing: + skill = EQEmu::skills::Skill1HPiercing; + break; + case EQEmu::item::ItemType1HBlunt: + skill = EQEmu::skills::Skill1HBlunt; + break; + case EQEmu::item::ItemType2HBlunt: + skill = EQEmu::skills::Skill2HBlunt; + break; + case EQEmu::item::ItemType2HPiercing: + if (ClientVersion() < EQEmu::versions::ClientVersion::RoF2) + skill = EQEmu::skills::Skill1HPiercing; + else + skill = EQEmu::skills::Skill2HPiercing; + break; + default: + break; + } + } + std::ostringstream final_string; final_string << /* C/L/R */ indP << "Class: " << class_Name << indS << "Level: " << static_cast(GetLevel()) << indS << "Race: " << race_Name << "
" << /* Runes */ indP << "Rune: " << rune_number << indL << indS << "Spell Rune: " << magic_rune_number << "
" << /* HP/M/E */ HME_row << /* DS */ indP << "DS: " << (itembonuses.DamageShield + spellbonuses.DamageShield*-1) << " (Spell: " << (spellbonuses.DamageShield*-1) << " + Item: " << itembonuses.DamageShield << " / " << RuleI(Character, ItemDamageShieldCap) << ")
" << - /* Atk */ indP << "ATK: " << GetTotalATK() << "
" << - /* Atk2 */ indP << "- Base: " << GetATKRating() << " | Item: " << itembonuses.ATK << " (" << RuleI(Character, ItemATKCap) << ")~Used: " << (itembonuses.ATK * 1.342) << " | Spell: " << spellbonuses.ATK << "
" << - /* AC */ indP << "AC: " << -1 << "
" << - /* AC2 */ indP << "- Mit: " << -1 << " | Avoid: " << -1 << " | Spell: " << spellbonuses.AC << " | Shield: " << shield_ac << "
" << + /* Atk */ indP << "tohit: " << compute_tohit(skill) << " / " << GetTotalToHit(skill, 0) << "
" << + /* Atk2 */ indP << "- Offense: " << offense(skill) << " | Item: " << itembonuses.ATK << " (" << RuleI(Character, ItemATKCap) << ")~Used: " << (itembonuses.ATK * 1.342) << " | Spell: " << spellbonuses.ATK << "
" << + /* AC */ indP << "mitigation AC: " << GetMitigationAC() << "
" << + /* AC2 */ indP << "- defense: " << compute_defense() << " / " << GetTotalDefense() << " | Spell: " << spellbonuses.AC << " | Shield: " << shield_ac << "
" << /* Haste */ indP << "Haste: " << GetHaste() << "
" << /* Haste2 */ indP << " - Item: " << itembonuses.haste << " + Spell: " << (spellbonuses.haste + spellbonuses.hastetype2) << " (Cap: " << RuleI(Character, HasteCap) << ") | Over: " << (spellbonuses.hastetype3 + ExtraHaste) << "
" << /* RunSpeed*/ indP << "Runspeed: " << GetRunspeed() << "
" << @@ -6910,7 +6940,9 @@ void Client::SendStatsWindow(Client* client, bool use_window) client->Message(15, "~~~~~ %s %s ~~~~~", GetCleanName(), GetLastName()); client->Message(0, " Level: %i Class: %i Race: %i DS: %i/%i Size: %1.1f Weight: %.1f/%d ", GetLevel(), GetClass(), GetRace(), GetDS(), RuleI(Character, ItemDamageShieldCap), GetSize(), (float)CalcCurrentWeight() / 10.0f, GetSTR()); client->Message(0, " HP: %i/%i HP Regen: %i/%i",GetHP(), GetMaxHP(), CalcHPRegen(), CalcHPRegenCap()); - client->Message(0, " AC: %i ( Mit.: %i + Avoid.: %i + Spell: %i ) | Shield AC: %i", -1, -1, -1, spellbonuses.AC, shield_ac); + client->Message(0, " compute_tohit: %i TotalToHit: %i", compute_tohit(skill), GetTotalToHit(skill, 0)); + client->Message(0, " compute_defense: %i TotalDefense: %i", compute_defense(), GetTotalDefense()); + client->Message(0, " offense: %i mitigation ac: %i", offense(skill), GetMitigationAC()); if(CalcMaxMana() > 0) client->Message(0, " Mana: %i/%i Mana Regen: %i/%i", GetMana(), GetMaxMana(), CalcManaRegen(), CalcManaRegenCap()); client->Message(0, " End.: %i/%i End. Regen: %i/%i",GetEndurance(), GetMaxEndurance(), CalcEnduranceRegen(), CalcEnduranceRegenCap()); diff --git a/zone/mob.cpp b/zone/mob.cpp index c5b61f546..d8b10874e 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -1493,6 +1493,9 @@ void Mob::ShowStats(Client* client) 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: %.3f Walkspeed: %.3f", n->GetAccuracyRating(), n->MerchantType, n->GetEmoteID(), static_cast(0.025f * n->GetRunspeed()), static_cast(0.025f * n->GetWalkspeed())); + client->Message(0, " compute_tohit: %i TotalToHit: %i", n->compute_tohit(EQEmu::skills::SkillHandtoHand), n->GetTotalToHit(EQEmu::skills::SkillHandtoHand, 0)); + client->Message(0, " compute_defense: %i TotalDefense: %i", n->compute_defense(), n->GetTotalDefense()); + client->Message(0, " offense: %i mitigation ac: %i", n->offense(EQEmu::skills::SkillHandtoHand), n->GetMitigationAC()); n->QueryLoot(client); } if (IsAIControlled()) { From b92d6c57a1403b5e61e1f7a04fa256835bde8c51 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sun, 15 Jan 2017 22:19:32 -0500 Subject: [PATCH 09/50] Rework OPCombatAbiltiy a bit This is done to help remove some code duplication in the future --- zone/client.h | 2 +- zone/client_packet.cpp | 3 +- zone/special_attacks.cpp | 222 ++++++++++++++++++--------------------- 3 files changed, 106 insertions(+), 121 deletions(-) diff --git a/zone/client.h b/zone/client.h index 687ff04d6..69fdddffa 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1290,7 +1290,7 @@ private: void OPGMEndTraining(const EQApplicationPacket *app); void OPGMTrainSkill(const EQApplicationPacket *app); void OPGMSummon(const EQApplicationPacket *app); - void OPCombatAbility(const EQApplicationPacket *app); + void OPCombatAbility(const CombatAbility_Struct *ca_atk); // Bandolier Methods void CreateBandolier(const EQApplicationPacket *app); diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 56936147d..f2e78adf7 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -4622,7 +4622,8 @@ void Client::Handle_OP_CombatAbility(const EQApplicationPacket *app) std::cout << "Wrong size on OP_CombatAbility. Got: " << app->size << ", Expected: " << sizeof(CombatAbility_Struct) << std::endl; return; } - OPCombatAbility(app); + auto ca_atk = (CombatAbility_Struct *)app->pBuffer; + OPCombatAbility(ca_atk); return; } diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 03e4a7d90..a9fcf82b1 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -217,14 +217,14 @@ void Mob::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 } // We should probably refactor this to take the struct not the packet -void Client::OPCombatAbility(const EQApplicationPacket *app) { - if(!GetTarget()) +void Client::OPCombatAbility(const CombatAbility_Struct *ca_atk) +{ + if (!GetTarget()) return; - //make sure were actually able to use such an attack. - if(spellend_timer.Enabled() || IsFeared() || IsStunned() || IsMezzed() || DivineAura() || dead) + // make sure were actually able to use such an attack. + if (spellend_timer.Enabled() || IsFeared() || IsStunned() || IsMezzed() || DivineAura() || dead) return; - CombatAbility_Struct* ca_atk = (CombatAbility_Struct*) app->pBuffer; pTimerType timer = pTimerCombatAbility; // RoF2+ Tiger Claw is unlinked from other monk skills, if they ever do that for other classes there will need // to be more checks here @@ -235,15 +235,15 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { if (!MaxSkill(static_cast(ca_atk->m_skill))) return; - if(GetTarget()->GetID() != ca_atk->m_target) - return; //invalid packet. + if (GetTarget()->GetID() != ca_atk->m_target) + return; // invalid packet. - if(!IsAttackAllowed(GetTarget())) + if (!IsAttackAllowed(GetTarget())) return; - //These two are not subject to the combat ability timer, as they - //allready do their checking in conjunction with the attack timer - //throwing weapons + // These two are not subject to the combat ability timer, as they + // allready do their checking in conjunction with the attack timer + // throwing weapons if (ca_atk->m_atk == EQEmu::inventory::slotRange) { if (ca_atk->m_skill == EQEmu::skills::SkillThrowing) { SetAttackTimer(); @@ -252,7 +252,7 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { ThrowingAttack(GetTarget(), true); return; } - //ranged attack (archery) + // ranged attack (archery) if (ca_atk->m_skill == EQEmu::skills::SkillArchery) { SetAttackTimer(); RangedAttack(GetTarget()); @@ -260,15 +260,15 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { RangedAttack(GetTarget(), true); return; } - //could we return here? Im not sure is m_atk 11 is used for real specials + // could we return here? Im not sure is m_atk 11 is used for real specials } - //check range for all these abilities, they are all close combat stuff - if(!CombatRange(GetTarget())) + // check range for all these abilities, they are all close combat stuff + if (!CombatRange(GetTarget())) return; - if(!p_timers.Expired(&database, timer, false)) { - Message(13,"Ability recovery time not yet met."); + if (!p_timers.Expired(&database, timer, false)) { + Message(13, "Ability recovery time not yet met."); return; } @@ -276,18 +276,18 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { int ClientHaste = GetHaste(); int HasteMod = 0; - if(ClientHaste >= 0){ - HasteMod = (10000/(100+ClientHaste)); //+100% haste = 2x as many attacks - } - else{ - HasteMod = (100-ClientHaste); //-100% haste = 1/2 as many attacks - } + if (ClientHaste >= 0) + HasteMod = (10000 / (100 + ClientHaste)); //+100% haste = 2x as many attacks + else + HasteMod = (100 - ClientHaste); //-100% haste = 1/2 as many attacks + int32 dmg = 0; int32 skill_reduction = this->GetSkillReuseTime(ca_atk->m_skill); // not sure what the '100' indicates..if ->m_atk is not used as 'slot' reference, then change SlotRange above back to '11' - if ((ca_atk->m_atk == 100) && (ca_atk->m_skill == EQEmu::skills::SkillBash)) { // SLAM - Bash without a shield equipped + if (ca_atk->m_atk == 100 && + ca_atk->m_skill == EQEmu::skills::SkillBash) { // SLAM - Bash without a shield equipped if (GetTarget() != this) { CheckIncreaseSkill(EQEmu::skills::SkillBash, GetTarget(), 10); @@ -295,31 +295,21 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { int32 ht = 0; if (GetWeaponDamage(GetTarget(), GetInv().GetItem(EQEmu::inventory::slotSecondary)) <= 0 && - GetWeaponDamage(GetTarget(), GetInv().GetItem(EQEmu::inventory::slotShoulders)) <= 0){ + GetWeaponDamage(GetTarget(), GetInv().GetItem(EQEmu::inventory::slotShoulders)) <= 0) dmg = -5; - } - else{ - if (!GetTarget()->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) { - dmg = 0; - ht = GetBaseSkillDamage(EQEmu::skills::SkillBash, GetTarget()); - } - else{ - ht = dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash, GetTarget()); - } - } + else + ht = dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash, GetTarget()); - ReuseTime = BashReuseTime-1-skill_reduction; - ReuseTime = (ReuseTime*HasteMod)/100; - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillBash, dmg, 0, ht, ReuseTime); - if(ReuseTime > 0) - { + ReuseTime = BashReuseTime - 1 - skill_reduction; + ReuseTime = (ReuseTime * HasteMod) / 100; + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillBash, dmg, 0, ht, ReuseTime, true); + if (ReuseTime > 0) p_timers.Start(timer, ReuseTime); - } } return; } - if ((ca_atk->m_atk == 100) && (ca_atk->m_skill == EQEmu::skills::SkillFrenzy)){ + if (ca_atk->m_atk == 100 && ca_atk->m_skill == EQEmu::skills::SkillFrenzy) { CheckIncreaseSkill(EQEmu::skills::SkillFrenzy, GetTarget(), 10); int AtkRounds = 3; int32 max_dmg = GetBaseSkillDamage(EQEmu::skills::SkillFrenzy, GetTarget()); @@ -327,102 +317,96 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { max_dmg = mod_frenzy_damage(max_dmg); - ReuseTime = FrenzyReuseTime-1-skill_reduction; - ReuseTime = (ReuseTime*HasteMod)/100; + ReuseTime = FrenzyReuseTime - 1 - skill_reduction; + ReuseTime = (ReuseTime * HasteMod) / 100; - //Live parses show around 55% Triple 35% Double 10% Single, you will always get first hit. - while(AtkRounds > 0) { + // Live parses show around 55% Triple 35% Double 10% Single, you will always get first hit. + while (AtkRounds > 0) { if (GetTarget() && (AtkRounds == 1 || zone->random.Roll(75))) { - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, 0, max_dmg, ReuseTime, true); + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, 0, max_dmg, + ReuseTime, true); } AtkRounds--; } - if(ReuseTime > 0) { + if (ReuseTime > 0) p_timers.Start(timer, ReuseTime); - } return; } - switch(GetClass()){ - case BERSERKER: - case WARRIOR: - case RANGER: - case BEASTLORD: - if (ca_atk->m_atk != 100 || ca_atk->m_skill != EQEmu::skills::SkillKick) { - break; - } - if (GetTarget() != this) { - CheckIncreaseSkill(EQEmu::skills::SkillKick, GetTarget(), 10); - DoAnim(animKick); - - int32 ht = 0; - if (GetWeaponDamage(GetTarget(), GetInv().GetItem(EQEmu::inventory::slotFeet)) <= 0){ - dmg = -5; - } - else{ - if (!GetTarget()->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) { - dmg = 0; - ht = GetBaseSkillDamage(EQEmu::skills::SkillKick, GetTarget()); - } - else{ - ht = dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick, GetTarget()); - } - } - - ReuseTime = KickReuseTime-1-skill_reduction; - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillKick, dmg, 0, ht, ReuseTime); - - } + switch (GetClass()) { + case BERSERKER: + case WARRIOR: + case RANGER: + case BEASTLORD: + if (ca_atk->m_atk != 100 || ca_atk->m_skill != EQEmu::skills::SkillKick) break; - case MONK: { - ReuseTime = MonkSpecialAttack(GetTarget(), ca_atk->m_skill) - 1 - skill_reduction; + if (GetTarget() != this) { + CheckIncreaseSkill(EQEmu::skills::SkillKick, GetTarget(), 10); + DoAnim(animKick); - //Live AA - Technique of Master Wu - int wuchance = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack; - if (wuchance) { - if (wuchance >= 100 || zone->random.Roll(wuchance)) { - const int MonkSPA[5] = { EQEmu::skills::SkillFlyingKick, EQEmu::skills::SkillDragonPunch, EQEmu::skills::SkillEagleStrike, EQEmu::skills::SkillTigerClaw, EQEmu::skills::SkillRoundKick }; - int extra = 1; - // always 1/4 of the double attack chance, 25% at rank 5 (100/4) - if (zone->random.Roll(wuchance / 4)) - extra++; - // They didn't add a string ID for this. - std::string msg = StringFormat("The spirit of Master Wu fills you! You gain %d additional attack(s).", extra); - // live uses 400 here -- not sure if it's the best for all clients though - SendColoredText(400, msg); - auto classic = RuleB(Combat, ClassicMasterWu); - while (extra) { - MonkSpecialAttack(GetTarget(), classic ? MonkSPA[zone->random.Int(0, 4)] : ca_atk->m_skill); - extra--; - } - } - } + int32 ht = 0; + if (GetWeaponDamage(GetTarget(), GetInv().GetItem(EQEmu::inventory::slotFeet)) <= 0) + dmg = -5; + else + ht = dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick, GetTarget()); - if(ReuseTime < 100) { - //hackish... but we return a huge reuse time if this is an - // invalid skill, otherwise, we can safely assume it is a - // valid monk skill and just cast it to a SkillType - CheckIncreaseSkill((EQEmu::skills::SkillType) ca_atk->m_skill, GetTarget(), 10); - } - break; + ReuseTime = KickReuseTime - 1 - skill_reduction; + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillKick, dmg, 0, ht, ReuseTime, true); } - case ROGUE: { - if (ca_atk->m_atk != 100 || ca_atk->m_skill != EQEmu::skills::SkillBackstab) { - break; + break; + case MONK: { + ReuseTime = MonkSpecialAttack(GetTarget(), ca_atk->m_skill) - 1 - skill_reduction; + + // Live AA - Technique of Master Wu + int wuchance = + itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack; + if (wuchance) { + if (wuchance >= 100 || zone->random.Roll(wuchance)) { + const int MonkSPA[5] = {EQEmu::skills::SkillFlyingKick, EQEmu::skills::SkillDragonPunch, + EQEmu::skills::SkillEagleStrike, EQEmu::skills::SkillTigerClaw, + EQEmu::skills::SkillRoundKick}; + int extra = 1; + // always 1/4 of the double attack chance, 25% at rank 5 (100/4) + if (zone->random.Roll(wuchance / 4)) + extra++; + // They didn't add a string ID for this. + std::string msg = StringFormat( + "The spirit of Master Wu fills you! You gain %d additional attack(s).", extra); + // live uses 400 here -- not sure if it's the best for all clients though + SendColoredText(400, msg); + auto classic = RuleB(Combat, ClassicMasterWu); + while (extra) { + MonkSpecialAttack(GetTarget(), + classic ? MonkSPA[zone->random.Int(0, 4)] : ca_atk->m_skill); + extra--; + } } - ReuseTime = BackstabReuseTime-1 - skill_reduction; - TryBackstab(GetTarget(), ReuseTime); - break; } - default: - //they have no abilities... wtf? make em wait a bit - ReuseTime = 9 - skill_reduction; + + if (ReuseTime < 100) { + // hackish... but we return a huge reuse time if this is an + // invalid skill, otherwise, we can safely assume it is a + // valid monk skill and just cast it to a SkillType + CheckIncreaseSkill((EQEmu::skills::SkillType)ca_atk->m_skill, GetTarget(), 10); + } + break; + } + case ROGUE: { + if (ca_atk->m_atk != 100 || ca_atk->m_skill != EQEmu::skills::SkillBackstab) break; + ReuseTime = BackstabReuseTime-1 - skill_reduction; + TryBackstab(GetTarget(), ReuseTime); + break; + } + default: + //they have no abilities... wtf? make em wait a bit + ReuseTime = 9 - skill_reduction; + break; } - ReuseTime = (ReuseTime*HasteMod)/100; - if(ReuseTime > 0){ + ReuseTime = (ReuseTime * HasteMod) / 100; + if (ReuseTime > 0) { p_timers.Start(timer, ReuseTime); } } From 0d84a73e9f8acfa81eaaee7c08cabc89197ef051 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sun, 15 Jan 2017 22:54:50 -0500 Subject: [PATCH 10/50] Clean up MonkSpecialAttack too --- zone/special_attacks.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index a9fcf82b1..929c272d9 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -476,27 +476,24 @@ int Mob::MonkSpecialAttack(Mob *other, uint8 unchecked_type) if (IsClient()) { if (GetWeaponDamage(other, CastToClient()->GetInv().GetItem(itemslot)) <= 0) { - ndamage = -5; + max_dmg = -5; } } else { if (GetWeaponDamage(other, (const EQEmu::ItemData *)nullptr) <= 0) { - ndamage = -5; + max_dmg = -5; } } int32 ht = 0; - if (ndamage == 0) { + if (max_dmg > 0) ht = max_dmg; - if (other->CheckHitChance(this, skill_type, 0)) - ndamage = max_dmg; - } // This can potentially stack with changes to kick damage ht = ndamage = mod_monk_special_damage(ndamage, skill_type); - DoSpecialAttackDamage(other, skill_type, ndamage, min_dmg, ht, reuse); + DoSpecialAttackDamage(other, skill_type, max_dmg, min_dmg, ht, reuse, true); - return (reuse); + return reuse; } void Mob::TryBackstab(Mob *other, int ReuseTime) { From fb820f4fecfee050a1ba3986e61f3a2044e55c4e Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Mon, 16 Jan 2017 16:58:32 -0500 Subject: [PATCH 11/50] Add Creamo's SQL to rebalance NPCs --- .../2017_01_16_NPCCombatRebalance.sql | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 utils/sql/git/optional/2017_01_16_NPCCombatRebalance.sql diff --git a/utils/sql/git/optional/2017_01_16_NPCCombatRebalance.sql b/utils/sql/git/optional/2017_01_16_NPCCombatRebalance.sql new file mode 100644 index 000000000..43bd3e9bb --- /dev/null +++ b/utils/sql/git/optional/2017_01_16_NPCCombatRebalance.sql @@ -0,0 +1,25 @@ +update npc_types set attack_speed=0, atk=ceil(1.7*level), accuracy=ceil(1.7*level) where (name like 'Swarm%' or name like '%skel%' or name like 'BLpet%' or name like 'Sum%') +and id >510 and id <860; +update npc_types set attack_speed=0, atk=ceil(0.5*level), accuracy=ceil(0.5*level) where name like 'SumFire%'; +update npc_types set attack_speed=0, atk=ceil(1.8*level), accuracy=ceil(1.8*level) where name like 'SumAir%'; +update npc_types set attack_speed=0, atk=ceil(1.8*level), accuracy=ceil(1.8*level) where name like 'SumEarth%'; +update npc_types set attack_speed=0, atk=ceil(2.4*level), accuracy=ceil(2.4*level),gender=2,size=3 where name like 'BestialAid%'; +update npc_types set attack_speed=0, atk=ceil(2.4*level), accuracy=ceil(2.4*level),gender=2,size=3 where name like 'RagingServant%'; + + +update npc_types n +set ac= +ceil(case +when level < 3 then level*2+2 +when level < 15 and level >=3 then level*3 +when id >= 200000 and id < 224000 then 200*.5+level +else level * 4.1 end +* (case when raid_target=1 then 1.4 else 1 end) + (case when raid_target=1 then level*1.4 else 0 end)) ++ 4*ifnull((select min(expansion)from zone where zoneidnumber=floor(n.id/1000)),5) +,str=ceil((level * 4.1)*.75) + case when raid_target=1 then level else 0 end + 4*ifnull((select min(expansion)from zone where zoneidnumber=floor(n.id/1000)),5) +,sta=ceil((level * 4.1)*.75) + case when raid_target=1 then level else 0 end + 4*ifnull((select min(expansion)from zone where zoneidnumber=floor(n.id/1000)),5) +,agi=ceil((level * 4.1)*.75) + case when raid_target=1 then level else 0 end + 4*ifnull((select min(expansion)from zone where zoneidnumber=floor(n.id/1000)),5) +,_int=ceil((level * 4.1)*.75) + case when raid_target=1 then level else 0 end + 4*ifnull((select min(expansion)from zone where zoneidnumber=floor(n.id/1000)),5) +,dex=ceil((level * 4.1)*.75) + case when raid_target=1 then level else 0 end + 4*ifnull((select min(expansion)from zone where zoneidnumber=floor(n.id/1000)),5) +,wis=ceil((level * 4.1)*.75) + case when raid_target=1 then level else 0 end + 4*ifnull((select min(expansion)from zone where zoneidnumber=floor(n.id/1000)),5) +,cha=ceil((level * 4.1)*.75) + case when raid_target=1 then level else 0 end + 4*ifnull((select min(expansion)from zone where zoneidnumber=floor(n.id/1000)),5); \ No newline at end of file From 698a814fc3953ef0cb11f1578ce26741a8f48d6c Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Mon, 16 Jan 2017 17:02:34 -0500 Subject: [PATCH 12/50] Add KLS' combat sim This will show you the distribution of hits for a given offense and mitigation DI 1 is the min hit and DI 20 is the max hit --- utils/combat-sim/app.js | 138 ++++++++++++++++++++++++++++++++++++ utils/combat-sim/index.html | 30 ++++++++ utils/combat-sim/style.css | 3 + 3 files changed, 171 insertions(+) create mode 100644 utils/combat-sim/app.js create mode 100644 utils/combat-sim/index.html create mode 100644 utils/combat-sim/style.css diff --git a/utils/combat-sim/app.js b/utils/combat-sim/app.js new file mode 100644 index 000000000..691a41c96 --- /dev/null +++ b/utils/combat-sim/app.js @@ -0,0 +1,138 @@ +var app = angular.module('app', ['nvd3']); + +app.controller('MainCtrl', function($scope, $interval) { + $scope.options = { + chart: { + type: 'discreteBarChart', + height: 450, + margin: { + top: 20, + right: 20, + bottom: 50, + left: 55 + }, + x: function(d) { + return d.label; + }, + y: function(d) { + return d.value + (1e-10); + }, + showValues: true, + valueFormat: function(d) { + return d3.format(',.2r')(d); + }, + duration: 500, + xAxis: { + axisLabel: 'D1-D20' + }, + yAxis: { + axisLabel: 'Count' + } + } + }; + + $scope.offense = 100; + $scope.defense = 100; + + $scope.data = [{ + key: "Cumulative Return", + values: [] + }]; + + for (var i = 0; i < 20; ++i) { + var value = { + "label": i + 1, + "value": 0 + }; + $scope.data[0].values.push(value); + } + + function addRoll(interval) { + $scope.data[0].values[interval - 1].value += 1; + } + + var stop; + $scope.clearData = function() { + console.log('Clearing data'); + for (var i = 0; i < 20; ++i) { + $scope.data[0].values[i].value = 0; + } + }; + + $scope.start = function() { + if (angular.isDefined(stop)) + return; + + stop = $interval(doCombatRound, 100); + }; + + $scope.stop = function() { + if (angular.isDefined(stop)) { + $interval.cancel(stop); + stop = undefined; + } + }; + + $scope.$on('$destroy', function() { + $scope.stop(); + }); + + function getRandomInt(min, max) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min)) + min; + } + + function getRandom(min, max) { + return Math.random() * (max - min) + min; + } + + function addChance(bucket, chance, value) { + for (var i = 0; i < chance; ++i) { + bucket.push(value); + } + } + + function doCombatRound() { + var offense = $scope.offense; + var defense = $scope.defense; + defense = defense - ((defense - offense) / 2.0); + var diff = offense - defense; + var mean = 0.0; + var mult1 = 0.0; + var mult2 = 0.0; + + if (offense > 30.0) { + mult1 = offense / 200.0 + 25.75; + if ((defense / offense) < 0.35) { + mult1 = mult1 + 1.0; + } else if ((defense / offense) > 0.65) { + mult1 = mult1 - 1.0; + } + mult2 = offense / 140 + 18.5; + } else { + mult1 = 11.5 + offense / 2.0; + mult2 = 14.0 + offense / 6.0; + } + + if (offense > defense) { + mean = diff / offense * mult1; + } else if (defense > offense) { + mean = diff / defense * mult2; + } + + var stddev = 8.8; + var theta = 2 * Math.PI * getRandom(0.0, 1.0); + var rho = Math.sqrt(-2 * Math.log(1 - getRandom(0.0, 1.0))); + var d = mean + stddev * rho * Math.cos(theta); + + if (d < -9.5) { + d = -9.5; + } else if (d > 9.5) { + d = 9.5; + } + d = d + 11; + addRoll(parseInt(d)); + }; +}); + diff --git a/utils/combat-sim/index.html b/utils/combat-sim/index.html new file mode 100644 index 000000000..783177051 --- /dev/null +++ b/utils/combat-sim/index.html @@ -0,0 +1,30 @@ + + + + + Combat Visualization + + + + + + + + + + + + + + + +
+ + +
+
+ + +
+ + diff --git a/utils/combat-sim/style.css b/utils/combat-sim/style.css new file mode 100644 index 000000000..c427aa7f4 --- /dev/null +++ b/utils/combat-sim/style.css @@ -0,0 +1,3 @@ +.input-row { + padding: 8px; +} \ No newline at end of file From 18e6e5e5e28f0f482b4db1e7916bb16406409197 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Mon, 16 Jan 2017 17:05:57 -0500 Subject: [PATCH 13/50] Rename Defense to Mitigation on sim (since that's what it is) --- utils/combat-sim/app.js | 18 +++++++++--------- utils/combat-sim/index.html | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/utils/combat-sim/app.js b/utils/combat-sim/app.js index 691a41c96..ce5a7ac69 100644 --- a/utils/combat-sim/app.js +++ b/utils/combat-sim/app.js @@ -32,7 +32,7 @@ app.controller('MainCtrl', function($scope, $interval) { }; $scope.offense = 100; - $scope.defense = 100; + $scope.mitigation = 100; $scope.data = [{ key: "Cumulative Return", @@ -95,18 +95,18 @@ app.controller('MainCtrl', function($scope, $interval) { function doCombatRound() { var offense = $scope.offense; - var defense = $scope.defense; - defense = defense - ((defense - offense) / 2.0); - var diff = offense - defense; + var mitigation = $scope.mitigation; + mitigation = mitigation - ((mitigation - offense) / 2.0); + var diff = offense - mitigation; var mean = 0.0; var mult1 = 0.0; var mult2 = 0.0; if (offense > 30.0) { mult1 = offense / 200.0 + 25.75; - if ((defense / offense) < 0.35) { + if ((mitigation / offense) < 0.35) { mult1 = mult1 + 1.0; - } else if ((defense / offense) > 0.65) { + } else if ((mitigation / offense) > 0.65) { mult1 = mult1 - 1.0; } mult2 = offense / 140 + 18.5; @@ -115,10 +115,10 @@ app.controller('MainCtrl', function($scope, $interval) { mult2 = 14.0 + offense / 6.0; } - if (offense > defense) { + if (offense > mitigation) { mean = diff / offense * mult1; - } else if (defense > offense) { - mean = diff / defense * mult2; + } else if (mitigation > offense) { + mean = diff / mitigation * mult2; } var stddev = 8.8; diff --git a/utils/combat-sim/index.html b/utils/combat-sim/index.html index 783177051..00d21e722 100644 --- a/utils/combat-sim/index.html +++ b/utils/combat-sim/index.html @@ -23,8 +23,8 @@
- - + +
From f3e09abf22ab3f1ffe1034af7dfaafef6704f683 Mon Sep 17 00:00:00 2001 From: huffin Date: Mon, 16 Jan 2017 19:33:33 -0600 Subject: [PATCH 14/50] Update 2017_01_16_NPCCombatRebalance.sql --- .../sql/git/optional/2017_01_16_NPCCombatRebalance.sql | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/utils/sql/git/optional/2017_01_16_NPCCombatRebalance.sql b/utils/sql/git/optional/2017_01_16_NPCCombatRebalance.sql index 43bd3e9bb..d49b9def9 100644 --- a/utils/sql/git/optional/2017_01_16_NPCCombatRebalance.sql +++ b/utils/sql/git/optional/2017_01_16_NPCCombatRebalance.sql @@ -1,10 +1,10 @@ update npc_types set attack_speed=0, atk=ceil(1.7*level), accuracy=ceil(1.7*level) where (name like 'Swarm%' or name like '%skel%' or name like 'BLpet%' or name like 'Sum%') and id >510 and id <860; update npc_types set attack_speed=0, atk=ceil(0.5*level), accuracy=ceil(0.5*level) where name like 'SumFire%'; -update npc_types set attack_speed=0, atk=ceil(1.8*level), accuracy=ceil(1.8*level) where name like 'SumAir%'; -update npc_types set attack_speed=0, atk=ceil(1.8*level), accuracy=ceil(1.8*level) where name like 'SumEarth%'; -update npc_types set attack_speed=0, atk=ceil(2.4*level), accuracy=ceil(2.4*level),gender=2,size=3 where name like 'BestialAid%'; -update npc_types set attack_speed=0, atk=ceil(2.4*level), accuracy=ceil(2.4*level),gender=2,size=3 where name like 'RagingServant%'; +update npc_types set attack_speed=0, atk=ceil(1.775*level), accuracy=ceil(1.775*level) where name like 'SumAir%'; +update npc_types set attack_speed=0, atk=ceil(1.775*level), accuracy=ceil(1.775*level) where name like 'SumEarth%'; +update npc_types set attack_speed=0, atk=ceil(2.26*level), accuracy=ceil(2.26*level),gender=2,size=3 where name like 'BestialAid%'; +update npc_types set attack_speed=0, atk=ceil(2.26*level), accuracy=ceil(2.26*level),gender=2,size=3 where name like 'RagingServant%'; update npc_types n @@ -22,4 +22,4 @@ else level * 4.1 end ,_int=ceil((level * 4.1)*.75) + case when raid_target=1 then level else 0 end + 4*ifnull((select min(expansion)from zone where zoneidnumber=floor(n.id/1000)),5) ,dex=ceil((level * 4.1)*.75) + case when raid_target=1 then level else 0 end + 4*ifnull((select min(expansion)from zone where zoneidnumber=floor(n.id/1000)),5) ,wis=ceil((level * 4.1)*.75) + case when raid_target=1 then level else 0 end + 4*ifnull((select min(expansion)from zone where zoneidnumber=floor(n.id/1000)),5) -,cha=ceil((level * 4.1)*.75) + case when raid_target=1 then level else 0 end + 4*ifnull((select min(expansion)from zone where zoneidnumber=floor(n.id/1000)),5); \ No newline at end of file +,cha=ceil((level * 4.1)*.75) + case when raid_target=1 then level else 0 end + 4*ifnull((select min(expansion)from zone where zoneidnumber=floor(n.id/1000)),5); From 8f21b01b7eaf7006adf417e3fcc7c8d20bc7d76b Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Tue, 17 Jan 2017 02:52:16 -0500 Subject: [PATCH 15/50] Tweak to accuracy based on newer clients --- zone/attack.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zone/attack.cpp b/zone/attack.cpp index cdc3bab06..48b58964c 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -169,7 +169,8 @@ int Mob::GetTotalToHit(EQEmu::skills::SkillType skill, int chance_mod) accuracy *= chance_mod; // Torven parsed an apparent constant of 1.2 somewhere in here * 6 / 5 looks eqmathy to me! - accuracy = accuracy * 6 / 5; + // new test clients have 121 / 100 + accuracy = (accuracy * 121) / 100; // unsure on the stacking order of these effects, rather hard to parse // item mod2 accuracy isn't applied to range? Theory crafting and parses back it up I guess From f614c35f6e45d079c31a23b6b584ea3b684147d1 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Tue, 17 Jan 2017 23:30:50 -0500 Subject: [PATCH 16/50] Combat Revamp -- BREAKING CHANGES -- see changelog Changelog updated for combat revamp --- changelog.txt | 53 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/changelog.txt b/changelog.txt index 33bf2de1a..6a76e3b55 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,18 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 01/17/2017 == +Mackal: Combat Revamp + - This change brings melee combat into line with how combat is done on live. + - This correctly applies the PC damage tables and corrects stacking order of many spells + - Given the scope of what had to be rewritten, it was not feasible to preserve the old combat system. + This means you will likely have to rebalance your server, which sucks, but this is very + accurate so shouldn't require any more changes, at least none that would cause you to have + to rebalance your server again. + - For rebalancing, I would recommend running the optional SQL and tweaking from there. + - To help with rebalancing there is a simulator included at utils/combat-sim. + - You can enter the mitigation or offense values you would like to balance around (#showstats will show you them) + a 1 on the sim is min damage 20 is max. + - Quick recommendations for best ways to improve PC DPS, give them some worn (or AA) SE_DamageModifier and/or SE_MinDamageModifier == 12/03/2016 == Uleat: Added hack detection to trade code - If illegal items are found in trade slots when the 'trade' button is clicked, the trade is cancelled and a message is sent to the offending player @@ -28,7 +41,7 @@ take into account that rogue was sneaking. Now sneaking rogues can see full inventory on merchants (well, unless an item requires a + faction value). == 09/12/2016 == -Akkadius: Massive overhaul of the update system and EQEmu Server management utility framework +Akkadius: Massive overhaul of the update system and EQEmu Server management utility framework (known as eqemu_update.pl) now known as eqemu_server.pl - eqemu_server.pl is now a general EQEmu Server management utiltiy framework that can be used to extend to many purposes. It's main purpose is to simplify server management @@ -49,7 +62,7 @@ Akkadius: Massive overhaul of the update system and EQEmu Server management util - Regular bots database schema changes now happen automatically similarily to the above - Database checks can also be ran manually via the script menu - CLI Arguments - - Arguments passed to eqemu_server.pl can execute the same name-based operations that + - Arguments passed to eqemu_server.pl can execute the same name-based operations that are present in the interactive menu - Example: "perl eqemu_server.pl opcodes" will download opcodes - Example: "perl eqemu_server.pl backup_player_tables" will backup and export player tables @@ -64,7 +77,7 @@ Akkadius: Massive overhaul of the update system and EQEmu Server management util - server_start_dev.sh - server_start.sh - server_status.sh - - server_stop.sh + - server_stop.sh - Usage analytics - eqemu_server.pl now collects usage analytics, this is very helpful for our developers - Example: We can see how many installs have been performed: @@ -76,10 +89,10 @@ Akkadius: Massive overhaul of the update system and EQEmu Server management util - 'New Server' Utility - Running 'new_server' from the main menu or 'perl eqemu_server.pl new_server' while in a completely new folder with just the script present, will allow a server operator - to initiate a full clean PEQ install in that folder. Pulling down all assets and - installing a PEQ database with the name the server operator gives the prompts in the + to initiate a full clean PEQ install in that folder. Pulling down all assets and + installing a PEQ database with the name the server operator gives the prompts in the script - + == 09/10/2016 == noudess: Task system experience based on a % of a level did not take into account the hell levels rule. Now it does. @@ -133,7 +146,7 @@ Kinglykrab: Implemented optional avoidance cap rules. - Rule Names: - Character:EnableAvoidanceCap (default is false) - Character:AvoidanceCap (default is 750, beyond 1,000 seems to make characters dodge all attacks) - + == 08/02/2016 == Uleat: Changed 'SendZoneSpawnsBulk' behavior to use near/far criteria (live-like) when sending packets. - Zone-to-Zone client loading will see a small decrease in time (less than 10~15%) @@ -171,7 +184,7 @@ mackal: Fix up the SpellBuff struct Uleat: Important fix for mob pathing - This should fix failed pathing issues (and high cpu usage for zone.exe) for mobs in affect zones - Changed variable 'gridno' type from int16 to int32 to reflect actual return value of fetch (values do exceed 32767 aka int16.max) - - Precision loss from int32 to int16 conversion was causing grid id to be changed to quest controlled movement in cases where (gridno & 0x8000 == true) + - Precision loss from int32 to int16 conversion was causing grid id to be changed to quest controlled movement in cases where (gridno & 0x8000 == true) == 06/28/2016 == Noudess: Resurrection effects now block certain buffs like on live. @@ -240,7 +253,7 @@ Uleat: Moved database query code out of bot.cpp and into the new BotDatabase cla == 03/25/2016 == Uleat: Fix for heal rotation 'Stack Overflow' error -Kayen: Defensive procs will now only proc once per attack round (instead of every attack chance). +Kayen: Defensive procs will now only proc once per attack round (instead of every attack chance). Live like modifier added that decreases defensive proc chance if you are higher level then your attacker. == 03/24/2016 == @@ -283,7 +296,7 @@ Kinglykrab: Modified #flag so you can refresh your target's account status (GM s - Just target the person whose flag you want to refresh and type #flag. Uleat: Added itemlink functionality to the #summonitem command. Current use is limited to extracting the item id from the link. - Invoking by item link '#summonitem Arrow' produces the same result as by item id '#summonitem 8005' - + == 01/12/2016 == Athrogate: Adding ClearCompassMark() to Lua. - Lua didn't have ClearCompassMark(). Perl already had this. @@ -313,7 +326,7 @@ Kinglykrab: Added GetInstanceTimer() to Perl and Lua. - Note: If you do not provide an instance id in the method it defaults to instance id 0 and returns 0 for time remaining. - Added UpdateZoneHeader(type, value) to Perl and Lua. - Note: UpdateZoneHeader allows you to manipulate fog color, fog density, and many other zone header settings on the fly in Perl and Lua. - + == 12/21/2015 == Natedog: Updated item table fields and added a few missing fields for evolving items -DO NOT implement Heirloom items till the inventory code is fixed to allow placing NO DROP @@ -322,7 +335,7 @@ Natedog: Updated item table fields and added a few missing fields for evolving i 100% 2HSlashing (Max 50) - can only increase 2hslash by 50 MAX! (item field located though) Kinglykrab: Added GetMeleeMitigation() for NPCs and Clients in Perl and Lua. - This allows you to check total item, spell, and AA melee mitigation contribution. - + == 12/19/2015 == Kinglykrab: Added many methods to Perl and Lua, list below: - SeeInvisible() @@ -344,7 +357,7 @@ Kinglykrab: Added many methods to Perl and Lua, list below: - HasPet() - IsSilenced() - IsAmnesiad() - + == 12/16/2015 == Noudess: Repaired issue with Bind Wounds on someone else. Message was not coming out on client (hold still) and a bind wounds on someone already binding their wounds would interrupt their bind and make them stand. Also removed some duplicate messaging. @@ -353,7 +366,7 @@ Kinglykrab: Added IsBlind() and IsFeared() functionality to Perl and Lua. - Note: Both methods are Mob methods and may be used on NPCs or PCs. Natedog: Added Discipline functions, UpdateInstanceTimer function, and UnmemSpellBySpellID to lua and perl -Examples: http://wiki.eqemulator.org/i?M=Pastebin&Paste=BJ0ygmNM - + == 12/07/2015 == Uleat: Command aliases are no longer handled through the command_add() function. - To add a command alias, edit the database table `command_settings` - here, you will find three columns: `command`, `access` and `aliases` @@ -366,7 +379,7 @@ Uleat: Command aliases are no longer handled through the command_add() function. - If you need need more name space for aliases, simply edit the `command_settings` table and increase the size of the `aliases` column - The old `commands` table has been renamed to `commands_old` for reference - All of the current 'standard' commands have been added to the new `command_settings` table - + - YOU WILL NEED TO VERIFY/IMPORT OLD ACCESS VALUES AS THIS CHANGE REVERTS ALL COMMAND ACCESS VALUES TO THEIR PEQDB DEFAULTS == 11/30/2015 == @@ -388,7 +401,7 @@ Akkadius: Performance boost (exponential) - Adjusted default idle cast check tim - Database version 9089 will take care of this update automatically only if you used the default values - The CPU cost of NPC's checking the entire entity list to cast beneficial spells (Heals/Buffs) becomes extremely high when higher NPC count zones exist (Based off of process profiling) - Distance checks for every single NPC to every single other NPC who are casting beneficial spells occur every .5 - 2 seconds unless npc_spells dictates other values, which most of the time it does not - - Zones that once fluctuated from 1-8% CPU with no activity (Idle but players present) now idle at .5% based on my testings due + - Zones that once fluctuated from 1-8% CPU with no activity (Idle but players present) now idle at .5% based on my testings due to this change in conjunction with the past few performance commits, these are zones that have 600-800 NPC's in them - These values normally are overidden by the spells table (npc_spells), fields (idle_no_sp_recast_min, idle_no_sp_recast_max) @@ -396,7 +409,7 @@ Akkadius: Performance boost (exponential) - Adjusted default idle cast check tim Akkadius: Made many performance optimizing oriented code changes in the source - Added Rate limit the rate in which signals are processed for NPC's (.5 seconds instead of .01 seconds) Akkadius: Added Perl Export Settings which should heavily reduce the Perl footprint - - Normally when any sub EVENT_ gets triggered, all kinds of variables have to get exported every single time an event is triggered and + - Normally when any sub EVENT_ gets triggered, all kinds of variables have to get exported every single time an event is triggered and this can make Perl very slow when events are triggered constantly - The two most taxing variable exports are the item variables ($itemcount{} $hasitem{} $oncursor{}) and qglobals ($qglobals{}) - qglobals can pose to be an issue quickly when global qglobals build up, it is highly recommend to use the GetGlobal() and SetGlobal() @@ -424,7 +437,7 @@ Akkadius: Added Perl Export Settings which should heavily reduce the Perl footpr | 4 | EVENT_ATTACK | 0 | 1 | 1 | 0 | 1 | | 5 | EVENT_COMBAT | 1 | 1 | 1 | 0 | 1 | +----------+-----------------------------------------+-----------------+------------+-------------+-------------+--------------+ - + - If a change is made to this table while the server is live and running, you can hot reload all zone process settings via: #reloadperlexportsettings - For those who wonder what "exports" are, they are reference to variables that are made available at runtime of the sub event, such as: @@ -433,7 +446,7 @@ Akkadius: Added Perl Export Settings which should heavily reduce the Perl footpr (export_zone) : $zoneid, $instanceid, $zoneln etc. https://github.com/EQEmu/Server/blob/master/zone/embparser.cpp#L1083 (export_mob) : $x, $y, $z, $h, $hpratio etc. https://github.com/EQEmu/Server/blob/master/zone/embparser.cpp#L1032 (export_event) : (event specific) IE: EVENT_SAY ($text) https://github.com/EQEmu/Server/blob/master/zone/embparser.cpp#L1141 - + == 10/16/2015 == Uleat: Added command '#bot clearfollowdistance [ | spawned | all ]' to coincide with the activation of the load/save feature for follow_distance @@ -465,7 +478,7 @@ rules: commands: '#invsnapshot' - Takes a snapshot of target client's inventory (feature active or inactive) '#clearinvsnapshots [use rule]' - Clears snapshot entries based on bool argument ([true] - honors the 'InvSnapshotHistoryD' rule, [false] - erases all) - + == 08/02/2015 == Shendare: VS2013 query StringFormat glitches when "%f" is passed for the int GetRunSpeed(). Shendare: In CreateNewNPCCommand(), the npc_type_id and spawngroupid are created in the database, but never set in the spawn class, so later it can't delete them with #npcspawn remove or #npcspawn delete. From 9aba99388837049bd6a34ffea019003a6de3913c Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Tue, 17 Jan 2017 23:34:52 -0500 Subject: [PATCH 17/50] Add optional rule SQL for combat update [skip ci] --- utils/sql/git/optional/2017_01_17_LevelStopDamageCaps.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 utils/sql/git/optional/2017_01_17_LevelStopDamageCaps.sql diff --git a/utils/sql/git/optional/2017_01_17_LevelStopDamageCaps.sql b/utils/sql/git/optional/2017_01_17_LevelStopDamageCaps.sql new file mode 100644 index 000000000..f6e1cf35a --- /dev/null +++ b/utils/sql/git/optional/2017_01_17_LevelStopDamageCaps.sql @@ -0,0 +1,2 @@ +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Combat:LevelToStopDamageCaps', '0', '1 will effectively disable them, 20 should give basically same results as old incorrect system.'); + From 2c6e11b4642c147aaa54a9ce6444344ddb2ab8a0 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Thu, 19 Jan 2017 23:17:14 -0500 Subject: [PATCH 18/50] Have modify NPC stat AC/AGI recache AC --- zone/npc.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zone/npc.cpp b/zone/npc.cpp index ebff4786a..c327db11f 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -1959,10 +1959,10 @@ void NPC::ModifyNPCStat(const char *identifier, const char *newValue) id[i] = std::tolower(id[i]); } - if(id == "ac") { AC = atoi(val.c_str()); return; } + if(id == "ac") { AC = atoi(val.c_str()); CalcAC(); return; } else if(id == "str") { STR = atoi(val.c_str()); return; } else if(id == "sta") { STA = atoi(val.c_str()); return; } - else if(id == "agi") { AGI = atoi(val.c_str()); return; } + else if(id == "agi") { AGI = atoi(val.c_str()); CalcAC(); return; } else if(id == "dex") { DEX = atoi(val.c_str()); return; } else if(id == "wis") { WIS = atoi(val.c_str()); CalcMaxMana(); return; } else if(id == "int" || id == "_int") { INT = atoi(val.c_str()); CalcMaxMana(); return; } From 905e3acab3ea3e97b2641315d376ee4e175aaa58 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Fri, 20 Jan 2017 23:27:19 -0500 Subject: [PATCH 19/50] Fix bots? --- zone/bot.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index ac6a6ddd7..a592ab94f 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -3705,16 +3705,16 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b } } - int min_cap = (base_damage * GetMeleeMinDamageMod_SE(skillinuse) / 100); + int min_cap = (weapon_damage * GetMeleeMinDamageMod_SE(skillinuse) / 100); Log.Out(Logs::Detail, Logs::Combat, "Damage calculated to %d (bonus %d, base %d, str %d, skill %d, DMG %d, lv %d)", - damage, min_damage, base_damage, GetSTR(), GetSkill(skillinuse), weapon_damage, mylevel); + damage, min_damage, weapon_damage, GetSTR(), GetSkill(skillinuse), weapon_damage, mylevel); auto offense = this->offense(skillinuse); if(opts) { - base_damage *= opts->damage_percent; - base_damage += opts->damage_flat; + weapon_damage *= opts->damage_percent; + weapon_damage += opts->damage_flat; hate *= opts->hate_percent; hate += opts->hate_flat; } @@ -3736,7 +3736,7 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b } } else { if (other->CheckHitChance(this, skillinuse)) { - other->MeleeMitigation(this, damage, base_damage, offense, skillinuse, opts); + other->MeleeMitigation(this, damage, weapon_damage, offense, skillinuse, opts); if (damage > 0) { ApplyDamageTable(damage, offense); CommonOutgoingHitSuccess(other, damage, min_damage, min_cap, skillinuse, opts); From 7033d9d91988b2e3e0a6dd15388c05b49e23d1eb Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sat, 21 Jan 2017 01:47:06 -0500 Subject: [PATCH 20/50] memleak fix --- zone/object.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/zone/object.cpp b/zone/object.cpp index 15c14a77c..3097ceba1 100644 --- a/zone/object.cpp +++ b/zone/object.cpp @@ -225,8 +225,7 @@ Object::Object(const char *model, float x, float y, float z, float heading, uint { user = nullptr; last_user = nullptr; - EQEmu::ItemInstance* inst = nullptr; - inst = new EQEmu::ItemInstance(ItemInstWorldContainer); + EQEmu::ItemInstance* inst = new EQEmu::ItemInstance(ItemInstWorldContainer); // Initialize members m_id = 0; @@ -257,6 +256,8 @@ Object::Object(const char *model, float x, float y, float z, float heading, uint strcpy(m_data.object_name, model); else strcpy(m_data.object_name, "IT64_ACTORDEF"); //default object name if model isn't specified for some unknown reason + + safe_delete(inst); } Object::~Object() From ae81ab8a124baae1faa1138ae3bc7bba0d20dffa Mon Sep 17 00:00:00 2001 From: Uleat Date: Sat, 21 Jan 2017 21:38:32 -0500 Subject: [PATCH 21/50] Fix for obscure crash related to bots and GetNeedsCured() --- zone/bot.cpp | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index a592ab94f..86e56fc9a 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -8008,29 +8008,11 @@ bool Bot::GetNeedsCured(Mob *tar) { bool needCured = false; if(tar) { if(tar->FindType(SE_PoisonCounter) || tar->FindType(SE_DiseaseCounter) || tar->FindType(SE_CurseCounter) || tar->FindType(SE_CorruptionCounter)) { - uint32 buff_count = GetMaxTotalSlots(); + uint32 buff_count = tar->GetMaxTotalSlots(); int buffsWithCounters = 0; needCured = true; for (unsigned int j = 0; j < buff_count; j++) { - // this should prevent crashes until the cause can be found - if (!tar->GetBuffs()) { - std::string mob_type = "Unknown"; - if (tar->IsClient()) - mob_type = "Client"; - else if (tar->IsBot()) - mob_type = "Bot"; - else if (tar->IsMerc()) - mob_type = "Merc"; - else if (tar->IsPet()) - mob_type = "Pet"; - else if (tar->IsNPC()) - mob_type = "NPC"; - - Log.Out(Logs::General, Logs::Error, "Bot::GetNeedsCured() processed mob type '%s' with a null buffs pointer (mob: '%s')", mob_type.c_str(), tar->GetName()); - - continue; - } - else if(tar->GetBuffs()[j].spellid != SPELL_UNKNOWN) { + if(tar->GetBuffs()[j].spellid != SPELL_UNKNOWN) { if(CalculateCounters(tar->GetBuffs()[j].spellid) > 0) { buffsWithCounters++; if(buffsWithCounters == 1 && (tar->GetBuffs()[j].ticsremaining < 2 || (int32)((tar->GetBuffs()[j].ticsremaining * 6) / tar->GetBuffs()[j].counters) < 2)) { From 31de6a63ccf2309d37cd14e70fcbb6dc2b69fbd5 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Wed, 25 Jan 2017 21:08:59 -0500 Subject: [PATCH 22/50] Fix bot frenzy --- zone/bot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 86e56fc9a..bc9166b63 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -4718,7 +4718,7 @@ int Bot::GetBaseSkillDamage(EQEmu::skills::SkillType skill, Mob *target) base++; return base; case EQEmu::skills::SkillFrenzy: - if (GetBotItem(EQEmu::inventory::slotSecondary)) { + if (GetBotItem(EQEmu::inventory::slotPrimary)) { if (GetLevel() > 15) base += GetLevel() - 15; if (base > 23) From 3d54a4edcb8c23e1b5b393a77881ec2062bba16e Mon Sep 17 00:00:00 2001 From: Uleat Date: Thu, 26 Jan 2017 14:37:51 -0500 Subject: [PATCH 23/50] Eliminated Rogue Bot twirling combat behavior. --- changelog.txt | 3 +++ zone/bot.cpp | 36 +++++++++++++++++++----------------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/changelog.txt b/changelog.txt index 6a76e3b55..7a9a0fa65 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,8 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 01/26/2017 == +Uleat: Change rogue bot behavior to eliminate twirling combat. They will only get behind the mob if they are not the mob's target or if the mob is feared or fleeing. This may lower rogue bot dps a small fraction..but, is more in-line with realistic game mechanics. + == 01/17/2017 == Mackal: Combat Revamp - This change brings melee combat into line with how combat is done on live. diff --git a/zone/bot.cpp b/zone/bot.cpp index bc9166b63..e751f4865 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2305,24 +2305,26 @@ void Bot::AI_Process() { } if(AI_movement_timer->Check()) { - if(!IsMoving() && GetClass() == ROGUE && !BehindMob(GetTarget(), GetX(), GetY())) { - // Move the rogue to behind the mob - float newX = 0; - float newY = 0; - float newZ = 0; - if(PlotPositionAroundTarget(GetTarget(), newX, newY, newZ)) { - CalculateNewPosition2(newX, newY, newZ, GetRunspeed()); - return; + if (!IsMoving()) { + if (GetClass() == ROGUE && (GetTarget() != this || GetTarget()->IsFeared()) && !BehindMob(GetTarget(), GetX(), GetY())) { + // Move the rogue to behind the mob + float newX = 0; + float newY = 0; + float newZ = 0; + if (PlotPositionAroundTarget(GetTarget(), newX, newY, newZ)) { + CalculateNewPosition2(newX, newY, newZ, GetRunspeed()); + return; + } } - } - else if(!IsMoving() && GetClass() != ROGUE && (DistanceSquaredNoZ(m_Position, GetTarget()->GetPosition()) < GetTarget()->GetSize())) { - // If we are not a rogue trying to backstab, let's try to adjust our melee range so we don't appear to be bunched up - float newX = 0; - float newY = 0; - float newZ = 0; - if(PlotPositionAroundTarget(GetTarget(), newX, newY, newZ, false) && GetArchetype() != ARCHETYPE_CASTER) { - CalculateNewPosition2(newX, newY, newZ, GetRunspeed()); - return; + else if (GetClass() != ROGUE && (DistanceSquaredNoZ(m_Position, GetTarget()->GetPosition()) < GetTarget()->GetSize())) { + // If we are not a rogue trying to backstab, let's try to adjust our melee range so we don't appear to be bunched up + float newX = 0; + float newY = 0; + float newZ = 0; + if (PlotPositionAroundTarget(GetTarget(), newX, newY, newZ, false) && GetArchetype() != ARCHETYPE_CASTER) { + CalculateNewPosition2(newX, newY, newZ, GetRunspeed()); + return; + } } } From 36300d6df1d0f94acac029a19c00ddc16c7695d0 Mon Sep 17 00:00:00 2001 From: Uleat Date: Thu, 26 Jan 2017 17:29:39 -0500 Subject: [PATCH 24/50] Removed SE_NegateIfCombat movement spells from bot commands (i.e., Scale of Wolf) --- zone/bot_command.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index 17182451f..fa15dc7d2 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -436,6 +436,8 @@ public: case 10: if (spells[spell_id].effectdescnum != 65) break; + if (IsEffectInSpell(spell_id, SE_NegateIfCombat)) + break; entry_prototype = new STMovementSpeedEntry(); entry_prototype->SafeCastToMovementSpeed()->group = BCSpells::IsGroupType(target_type); break; From cdf0d5deb0ca6b690557037c16b0cf0aa27e209a Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Thu, 26 Jan 2017 18:04:59 -0500 Subject: [PATCH 25/50] Fix crash --- zone/mob.cpp | 2 ++ zone/mob.h | 3 ++- zone/spells.cpp | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/zone/mob.cpp b/zone/mob.cpp index d8b10874e..bcaa43ccc 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -286,6 +286,8 @@ Mob::Mob(const char* in_name, armor_tint.Slot[i].Color = in_armor_tint.Slot[i].Color; } + std::fill(std::begin(m_spellHitsLeft), std::end(m_spellHitsLeft), 0); + m_Delta = glm::vec4(); animation = 0; diff --git a/zone/mob.h b/zone/mob.h index 6e390d7d6..0dfa162c1 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -1414,7 +1414,8 @@ protected: glm::vec3 m_TargetRing; - uint32 m_spellHitsLeft[38]; // Used to track which spells will have their numhits incremented when spell finishes casting, 38 Buffslots + // we might want to do this differently, we gotta do max NPC buffs ... which is 97 + uint32 m_spellHitsLeft[EQEmu::constants::TotalBuffs]; // Used to track which spells will have their numhits incremented when spell finishes casting int flymode; bool m_targetable; int QGVarDuration(const char *fmt); diff --git a/zone/spells.cpp b/zone/spells.cpp index 9fcfc56d2..f37f2fc53 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -5565,6 +5565,7 @@ void Client::InitializeBuffSlots() for(int x = 0; x < max_slots; ++x) { buffs[x].spellid = SPELL_UNKNOWN; + buffs[x].UpdateClient = false; } current_buff_count = 0; } @@ -5581,6 +5582,7 @@ void NPC::InitializeBuffSlots() for(int x = 0; x < max_slots; ++x) { buffs[x].spellid = SPELL_UNKNOWN; + buffs[x].UpdateClient = false; } current_buff_count = 0; } From 104a0998ceef2cf18a4afafb6075e3cc82ce925e Mon Sep 17 00:00:00 2001 From: Uleat Date: Fri, 27 Jan 2017 21:28:25 -0500 Subject: [PATCH 26/50] Added rogue evade to bot combat (nothing is every really fixed until you do it a second time and add a timer...) --- zone/bot.cpp | 41 +++++++++++++++++++++++++++++++++-------- zone/bot.h | 2 ++ 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index e751f4865..7edbbbebb 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -176,6 +176,9 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to for (int i = 0; i < MaxTimer; i++) timers[i] = 0; + if (GetClass() == ROGUE) + evade_timer.Start(); + GenerateBaseStats(); if (!botdb.LoadTimers(this) && bot_owner) @@ -2306,14 +2309,36 @@ void Bot::AI_Process() { if(AI_movement_timer->Check()) { if (!IsMoving()) { - if (GetClass() == ROGUE && (GetTarget() != this || GetTarget()->IsFeared()) && !BehindMob(GetTarget(), GetX(), GetY())) { - // Move the rogue to behind the mob - float newX = 0; - float newY = 0; - float newZ = 0; - if (PlotPositionAroundTarget(GetTarget(), newX, newY, newZ)) { - CalculateNewPosition2(newX, newY, newZ, GetRunspeed()); - return; + if (GetClass() == ROGUE) { + if ((GetTarget()->GetTarget() == this) && !GetTarget()->IsFeared() && !GetTarget()->IsStunned()) { + if (evade_timer.Check(false)) { + // Hate redux actions + uint32 timer_duration = (HideReuseTime * 1000); + evade_timer.Start(timer_duration); + + Bot::BotGroupSay(this, "Attempting to evade %s", GetTarget()->GetCleanName()); + if (zone->random.Int(0, 260) < (int)GetSkill(EQEmu::skills::SkillHide)) + RogueEvade(GetTarget()); + + return; + } + //else if (GetTarget()->IsRooted()) { + // Should move rogue backwards, out of combat range + //} + + // Could add a bot accessor like.. + // bool NeedsHateRedux() { return (GetClass() == Rogue && evade_timer.check(false)); } - or something like this + // ..then add hate redux spells to caster combat repertoires + } + else if (!BehindMob(GetTarget(), GetX(), GetY())) { + // Move the rogue to behind the mob + float newX = 0; + float newY = 0; + float newZ = 0; + if (PlotPositionAroundTarget(GetTarget(), newX, newY, newZ)) { + CalculateNewPosition2(newX, newY, newZ, GetRunspeed()); + return; + } } } else if (GetClass() != ROGUE && (DistanceSquaredNoZ(m_Position, GetTarget()->GetPosition()) < GetTarget()->GetSize())) { diff --git a/zone/bot.h b/zone/bot.h index 15a1b0938..177efeffb 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -670,6 +670,8 @@ private: glm::vec3 m_PreSummonLocation; uint8 _spellCastingChances[MaxStances][MaxSpellTypes]; + Timer evade_timer; + std::shared_ptr m_member_of_heal_rotation; std::map botAAs; From 05cb9d56c283f829bc17fcdb9b33f0e5bac5db6d Mon Sep 17 00:00:00 2001 From: Uleat Date: Fri, 27 Jan 2017 23:17:25 -0500 Subject: [PATCH 27/50] Modded 2017_01_10_book_languages.sql to eliminate error message --- utils/sql/git/required/2017_01_10_book_languages.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/sql/git/required/2017_01_10_book_languages.sql b/utils/sql/git/required/2017_01_10_book_languages.sql index 508cdc156..aa767cd03 100644 --- a/utils/sql/git/required/2017_01_10_book_languages.sql +++ b/utils/sql/git/required/2017_01_10_book_languages.sql @@ -1,6 +1,6 @@ alter table books add language int default 0; -drop table reading_is_fundamental; +drop table if exists reading_is_fundamental; create table reading_is_fundamental ( From e300f82c28ca748660572e2dac172c4f4832d44d Mon Sep 17 00:00:00 2001 From: Uleat Date: Sat, 28 Jan 2017 16:07:58 -0500 Subject: [PATCH 28/50] Added "rooted mob" conditional to rogue bot hate redux methods --- zone/bot.cpp | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 7edbbbebb..42ca396af 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2311,9 +2311,12 @@ void Bot::AI_Process() { if (!IsMoving()) { if (GetClass() == ROGUE) { if ((GetTarget()->GetTarget() == this) && !GetTarget()->IsFeared() && !GetTarget()->IsStunned()) { + // Hate redux actions if (evade_timer.Check(false)) { - // Hate redux actions - uint32 timer_duration = (HideReuseTime * 1000); + // Attempt to evade + int timer_duration = (HideReuseTime - GetSkillReuseTime(EQEmu::skills::SkillHide)) * 1000; + if (timer_duration < 0) + timer_duration = 0; evade_timer.Start(timer_duration); Bot::BotGroupSay(this, "Attempting to evade %s", GetTarget()->GetCleanName()); @@ -2322,9 +2325,23 @@ void Bot::AI_Process() { return; } - //else if (GetTarget()->IsRooted()) { - // Should move rogue backwards, out of combat range - //} + else if (GetTarget()->IsRooted()) { + // Move rogue back from rooted mob - out of combat range, if necessary + float melee_distance = GetMaxMeleeRangeToTarget(GetTarget()); + float current_distance = DistanceSquared(static_cast(m_Position), static_cast(GetTarget()->GetPosition())); + + if (current_distance <= melee_distance) { + float newX = 0; + float newY = 0; + float newZ = 0; + FaceTarget(GetTarget()); + if (PlotPositionAroundTarget(this, newX, newY, newZ)) { + Bot::BotGroupSay(this, "Backing off of %s", GetTarget()->GetCleanName()); + CalculateNewPosition2(newX, newY, newZ, GetRunspeed()); + return; + } + } + } // Could add a bot accessor like.. // bool NeedsHateRedux() { return (GetClass() == Rogue && evade_timer.check(false)); } - or something like this From 37e87e8cef23994a7b2f9d444b68f8cfcb672d38 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sat, 28 Jan 2017 19:38:44 -0500 Subject: [PATCH 29/50] Rework combat to make use of a struct to fix some bugs --- common/ruletypes.h | 11 +- utils/combat-sim/app.js | 53 +-- zone/attack.cpp | 664 +++++++++++++-------------- zone/bonuses.cpp | 47 +- zone/bot.cpp | 151 +++--- zone/bot.h | 2 +- zone/common.h | 23 +- zone/lua_mob.cpp | 7 - zone/lua_mob.h | 1 - zone/merc.cpp | 22 +- zone/mob.h | 27 +- zone/special_attacks.cpp | 970 ++++++++++++++++++--------------------- 12 files changed, 893 insertions(+), 1085 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index 2b0b71899..21c4949ea 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -392,15 +392,12 @@ RULE_BOOL(Spells, NPCInnateProcOverride, true) // NPC innate procs override the RULE_CATEGORY_END() RULE_CATEGORY(Combat) -RULE_INT(Combat, MeleeBaseCritChance, 0) //The base crit chance for non warriors, NOTE: This will apply to NPCs as well -RULE_INT(Combat, WarBerBaseCritChance, 3) //The base crit chance for warriors and berserkers, only applies to clients -RULE_INT(Combat, BerserkBaseCritChance, 6) //The bonus base crit chance you get when you're berserk +RULE_INT(Combat, PetBaseCritChance, 0) // Pet Base crit chance RULE_INT(Combat, NPCBashKickLevel, 6) //The level that npcs can KICK/BASH RULE_INT(Combat, NPCBashKickStunChance, 15) //Percent chance that a bash/kick will stun -RULE_INT(Combat, RogueCritThrowingChance, 25) //Rogue throwing crit bonus -RULE_INT(Combat, RogueDeadlyStrikeChance, 80) //Rogue chance throwing from behind crit becomes a deadly strike -RULE_INT(Combat, RogueDeadlyStrikeMod, 2) //Deadly strike modifier to crit damage -RULE_INT(Combat, ClientBaseCritChance, 0) //The base crit chance for all clients, this will stack with warrior's/zerker's crit chance. +RULE_INT(Combat, MeleeCritDifficulty, 8900) // lower is easier +RULE_INT(Combat, ArcheryCritDifficulty, 3400) // lower is easier +RULE_INT(Combat, ThrowingCritDifficulty, 1100) // lower is easier RULE_BOOL(Combat, UseIntervalAC, true) RULE_INT(Combat, PetAttackMagicLevel, 30) RULE_BOOL(Combat, EnableFearPathing, true) diff --git a/utils/combat-sim/app.js b/utils/combat-sim/app.js index ce5a7ac69..1bd00364e 100644 --- a/utils/combat-sim/app.js +++ b/utils/combat-sim/app.js @@ -77,6 +77,8 @@ app.controller('MainCtrl', function($scope, $interval) { $scope.stop(); }); + var damage_mods = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]; + function getRandomInt(min, max) { min = Math.ceil(min); max = Math.floor(max); @@ -94,45 +96,20 @@ app.controller('MainCtrl', function($scope, $interval) { } function doCombatRound() { - var offense = $scope.offense; - var mitigation = $scope.mitigation; - mitigation = mitigation - ((mitigation - offense) / 2.0); - var diff = offense - mitigation; - var mean = 0.0; - var mult1 = 0.0; - var mult2 = 0.0; - - if (offense > 30.0) { - mult1 = offense / 200.0 + 25.75; - if ((mitigation / offense) < 0.35) { - mult1 = mult1 + 1.0; - } else if ((mitigation / offense) > 0.65) { - mult1 = mult1 - 1.0; - } - mult2 = offense / 140 + 18.5; - } else { - mult1 = 11.5 + offense / 2.0; - mult2 = 14.0 + offense / 6.0; + var offense = getRandomInt(0, $scope.offense + 5); + var mitigation = getRandomInt(0, $scope.mitigation + 5); + var avg = parseInt(($scope.offense + $scope.mitigation + 10) / 2); + var index = parseInt((offense - mitigation) + (avg / 2)); + if (index < 0) { + index = 0; } - - if (offense > mitigation) { - mean = diff / offense * mult1; - } else if (mitigation > offense) { - mean = diff / mitigation * mult2; - } - - var stddev = 8.8; - var theta = 2 * Math.PI * getRandom(0.0, 1.0); - var rho = Math.sqrt(-2 * Math.log(1 - getRandom(0.0, 1.0))); - var d = mean + stddev * rho * Math.cos(theta); - - if (d < -9.5) { - d = -9.5; - } else if (d > 9.5) { - d = 9.5; - } - d = d + 11; - addRoll(parseInt(d)); + index = parseInt((index * 20) / avg); + if (index >= 20) + index = 19; + if (index < 0) + index = 0; + var roll = damage_mods[index]; + addRoll(roll); }; }); diff --git a/zone/attack.cpp b/zone/attack.cpp index 48b58964c..c05be46cb 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -267,7 +267,7 @@ int Mob::GetTotalDefense() // 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, EQEmu::skills::SkillType skillinuse, int chance_mod) +bool Mob::CheckHitChance(Mob* other, DamageHitInfo &hit) { Mob *attacker = other; Mob *defender = this; @@ -280,7 +280,7 @@ bool Mob::CheckHitChance(Mob* other, EQEmu::skills::SkillType skillinuse, int ch if (avoidance == -1) // some sort of auto avoid disc return false; - auto accuracy = attacker->GetTotalToHit(skillinuse, chance_mod); + auto accuracy = hit.tohit; if (accuracy == -1) return true; @@ -297,7 +297,7 @@ bool Mob::CheckHitChance(Mob* other, EQEmu::skills::SkillType skillinuse, int ch return tohit_roll > avoid_roll; } -bool Mob::AvoidDamage(Mob *other, int &damage, int hand) +bool Mob::AvoidDamage(Mob *other, DamageHitInfo &hit) { /* 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. @@ -360,9 +360,9 @@ bool Mob::AvoidDamage(Mob *other, int &damage, int hand) // riposte -- it may seem crazy, but if the attacker has SPA 173 on them, they are immune to Ripo bool ImmuneRipo = attacker->aabonuses.RiposteChance || attacker->spellbonuses.RiposteChance || attacker->itembonuses.RiposteChance; // Need to check if we have something in MainHand to actually attack with (or fists) - if (hand != EQEmu::inventory::slotRange && (CanThisClassRiposte() || IsEnraged()) && InFront && !ImmuneRipo) { + if (hit.hand != EQEmu::inventory::slotRange && (CanThisClassRiposte() || IsEnraged()) && InFront && !ImmuneRipo) { if (IsEnraged()) { - damage = -3; + hit.damage_done = DMG_RIPOSTED; Log.Out(Logs::Detail, Logs::Combat, "I am enraged, riposting frontal attack."); return true; } @@ -370,7 +370,7 @@ bool Mob::AvoidDamage(Mob *other, int &damage, int hand) CastToClient()->CheckIncreaseSkill(EQEmu::skills::SkillRiposte, other, -10); // check auto discs ... I guess aa/items too :P if (spellbonuses.RiposteChance == 10000 || aabonuses.RiposteChance == 10000 || itembonuses.RiposteChance == 10000) { - damage = -3; + hit.damage_done = DMG_RIPOSTED; return true; } int chance = GetSkill(EQEmu::skills::SkillRiposte) + 100; @@ -382,12 +382,12 @@ bool Mob::AvoidDamage(Mob *other, int &damage, int hand) chance -= chance * counter; } // AA Slippery Attacks - if (hand == EQEmu::inventory::slotSecondary) { + if (hit.hand == EQEmu::inventory::slotSecondary) { int slip = aabonuses.OffhandRiposteFail + itembonuses.OffhandRiposteFail + spellbonuses.OffhandRiposteFail; chance += chance * slip / 100; } if (chance > 0 && zone->random.Roll(chance)) { // could be <0 from offhand stuff - damage = -3; + hit.damage_done = DMG_RIPOSTED; return true; } } @@ -409,7 +409,7 @@ bool Mob::AvoidDamage(Mob *other, int &damage, int hand) // check auto discs ... I guess aa/items too :P if (spellbonuses.IncreaseBlockChance == 10000 || aabonuses.IncreaseBlockChance == 10000 || itembonuses.IncreaseBlockChance == 10000) { - damage = -1; + hit.damage_done = DMG_BLOCKED; return true; } int chance = GetSkill(EQEmu::skills::SkillBlock) + 100; @@ -421,18 +421,18 @@ bool Mob::AvoidDamage(Mob *other, int &damage, int hand) chance -= chance * counter; } if (zone->random.Roll(chance)) { - damage = -1; + hit.damage_done = DMG_BLOCKED; return true; } } // parry - if (CanThisClassParry() && InFront && hand != EQEmu::inventory::slotRange) { + if (CanThisClassParry() && InFront && hit.hand != EQEmu::inventory::slotRange) { if (IsClient()) CastToClient()->CheckIncreaseSkill(EQEmu::skills::SkillParry, other, -10); // check auto discs ... I guess aa/items too :P if (spellbonuses.ParryChance == 10000 || aabonuses.ParryChance == 10000 || itembonuses.ParryChance == 10000) { - damage = -2; + hit.damage_done = DMG_PARRIED; return true; } int chance = GetSkill(EQEmu::skills::SkillParry) + 100; @@ -444,7 +444,7 @@ bool Mob::AvoidDamage(Mob *other, int &damage, int hand) chance -= chance * counter; } if (zone->random.Roll(chance)) { - damage = -2; + hit.damage_done = DMG_PARRIED; return true; } } @@ -455,7 +455,7 @@ bool Mob::AvoidDamage(Mob *other, int &damage, int hand) CastToClient()->CheckIncreaseSkill(EQEmu::skills::SkillDodge, other, -10); // check auto discs ... I guess aa/items too :P if (spellbonuses.DodgeChance == 10000 || aabonuses.DodgeChance == 10000 || itembonuses.DodgeChance == 10000) { - damage = -4; + hit.damage_done = DMG_DODGED; return true; } int chance = GetSkill(EQEmu::skills::SkillDodge) + 100; @@ -467,7 +467,7 @@ bool Mob::AvoidDamage(Mob *other, int &damage, int hand) chance -= chance * counter; } if (zone->random.Roll(chance)) { - damage = -4; + hit.damage_done = DMG_DODGED; return true; } } @@ -480,7 +480,7 @@ bool Mob::AvoidDamage(Mob *other, int &damage, int hand) chance -= chance * counter; } if (zone->random.Roll(chance)) { - damage = -1; + hit.damage_done = DMG_BLOCKED; return true; } } @@ -492,7 +492,7 @@ bool Mob::AvoidDamage(Mob *other, int &damage, int hand) chance -= chance * counter; } if (zone->random.Roll(chance)) { - damage = -1; + hit.damage_done = DMG_BLOCKED; return true; } } @@ -826,54 +826,32 @@ int Mob::offense(EQEmu::skills::SkillType skill) // this assumes "this" is the defender // this returns between 0.1 to 2.0 -double Mob::RollD20(double offense, double mitigation) +double Mob::RollD20(int offense, int mitigation) { + static double mods[] = { + 0.1, 0.2, 0.3, 0.4, 0.5, + 0.6, 0.7, 0.8, 0.9, 1.0, + 1.1, 1.2, 1.3, 1.4, 1.5, + 1.6, 1.7, 1.8, 1.9, 2.0 + }; + if (IsClient() && CastToClient()->IsSitting()) - return 2.0; + return mods[19]; - // this works pretty good. From Torven's implementation for TAKP - mitigation -= (mitigation - offense) / 2.0; - double diff = offense - mitigation; - double mean = 0; - double mult1, mult2; + auto atk_roll = zone->random.Roll0(offense + 5); + auto def_roll = zone->random.Roll0(mitigation + 5); - if (offense > 30.0) { - mult1 = offense / 200.0 + 25.75; - if (mitigation / offense < 0.35) - mult1 = mult1 + 1.0; - else if (mitigation / offense > 0.65) - mult1 = mult1 - 1.0; - mult2 = offense / 140 + 18.5; - } else { - mult1 = 11.5 + offense / 2.0; - mult2 = 14.0 + offense / 6.0; - } + int avg = (offense + mitigation + 10) / 2; + int index = std::max(0, (atk_roll - def_roll) + (avg / 2)); - // changing the mean shifts the bell curve - // this was mostly determined by trial and error to find what fit best - if (offense > mitigation) - mean = diff / offense * mult1; - else if (mitigation > offense) - mean = diff / mitigation * mult2; + index = EQEmu::Clamp((index * 20) / avg, 0, 19); - double stddev = 8.8; // standard deviation adjusts the height of the bell - // again, trial and error to find what fit best - double theta = 2 * M_PI * zone->random.Real(0.0, 1.0); - double rho = std::sqrt(-2 * std::log(1 - zone->random.Real(0.0, 1.0))); - double d = mean + stddev * rho * std::cos(theta); - - // this combined with the stddev will produce ~15% DI1 and ~15% DI20 when mitigation == offens - d = EQEmu::Clamp(d, -9.5, 9.5); - d += 11.0; - int roll = static_cast(d); - - // the client has an array called damage_factor that is set to this value, so probably what they do - return roll * 0.1; + return mods[index]; } -void Mob::MeleeMitigation(Mob *attacker, int &damage, int base_damage, int offense, EQEmu::skills::SkillType skill, ExtraAttackOptions *opts) +void Mob::MeleeMitigation(Mob *attacker, DamageHitInfo &hit, ExtraAttackOptions *opts) { - if (damage < 0 || base_damage == 0) + if (hit.damage_done < 0 || hit.base_damage == 0) return; Mob* defender = this; @@ -886,24 +864,14 @@ void Mob::MeleeMitigation(Mob *attacker, int &damage, int base_damage, int offen mitigation -= opts->armor_pen_flat; } - auto roll = RollD20(offense, mitigation); - - // 168 Defensive -- TODO: I think this is suppose to happen after damage table - // It happening after damage table also means it acts more like negative 185, which might explain - // why the spell has a negative value :P - auto meleemitspell = spellbonuses.MeleeMitigationEffect + itembonuses.MeleeMitigationEffect + aabonuses.MeleeMitigationEffect; - if (GetClass() == WARRIOR && IsClient()) - meleemitspell += 5; + auto roll = RollD20(hit.offense, mitigation); // +0.5 for rounding - damage = static_cast(roll * static_cast(base_damage) + 0.5); + hit.damage_done = static_cast(roll * static_cast(hit.base_damage) + 0.5); - if (meleemitspell) - damage = (damage * (100 - meleemitspell)) / 100; - - if (damage < 0) - damage = 0; - Log.Out(Logs::Detail, Logs::Attack, "mitigation %d vs offense %d. base %d defensive SPA %d rolled %f damage %d", mitigation, offense, base_damage, meleemitspell, roll, damage); + if (hit.damage_done < 0) + hit.damage_done = 0; + Log.Out(Logs::Detail, Logs::Attack, "mitigation %d vs offense %d. base %d rolled %f damage %d", mitigation, hit.offense, hit.base_damage, roll, hit.damage_done); } //Returns the weapon damage against the input mob @@ -1227,6 +1195,44 @@ int Client::DoDamageCaps(int base_damage) return std::min(cap, base_damage); } +void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts) +{ + if (!other) + return; + Log.Out(Logs::Detail, Logs::Combat, "%s::DoAttack vs %s base %d min %d offense %d tohit %d skill %d", GetName(), + other->GetName(), hit.base_damage, hit.min_damage, hit.offense, hit.tohit, hit.skill); + // check to see if we hit.. + if (other->AvoidDamage(this, hit)) { + int strike_through = itembonuses.StrikeThrough + spellbonuses.StrikeThrough + aabonuses.StrikeThrough; + if (strike_through && zone->random.Roll(strike_through)) { + Message_StringID(MT_StrikeThrough, + STRIKETHROUGH_STRING); // You strike through your opponents defenses! + hit.damage_done = 0; // set to zero, we will check this to continue + } + // I'm pretty sure you can riposte a riposte + if (hit.damage_done == DMG_RIPOSTED) { + DoRiposte(other); + //if (IsDead()) + return; + } + Log.Out(Logs::Detail, Logs::Combat, "Avoided/strikethrough damage with code %d", hit.damage_done); + } + + if (hit.damage_done == 0) { + if (other->CheckHitChance(this, hit)) { + other->MeleeMitigation(this, hit, opts); + if (hit.damage_done > 0) { + ApplyDamageTable(hit); + CommonOutgoingHitSuccess(other, hit, opts); + } + Log.Out(Logs::Detail, Logs::Combat, "Final damage after all reductions: %d", hit.damage_done); + } else { + Log.Out(Logs::Detail, Logs::Combat, "Attack missed. Damage set to 0."); + hit.damage_done = 0; + } + } +} + //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.) @@ -1284,37 +1290,34 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b Log.Out(Logs::Detail, Logs::Combat, "Attacking without a weapon."); } + DamageHitInfo my_hit; // calculate attack_skill and skillinuse depending on hand and weapon // also send Packet to near clients - EQEmu::skills::SkillType skillinuse; - AttackAnimation(skillinuse, Hand, weapon); - Log.Out(Logs::Detail, Logs::Combat, "Attacking with %s in slot %d using skill %d", weapon?weapon->GetItem()->Name:"Fist", Hand, skillinuse); + AttackAnimation(my_hit.skill, Hand, weapon); + Log.Out(Logs::Detail, Logs::Combat, "Attacking with %s in slot %d using skill %d", weapon?weapon->GetItem()->Name:"Fist", Hand, my_hit.skill); // Now figure out damage - int damage = 0; + my_hit.damage_done = 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; + my_hit.base_damage = GetWeaponDamage(other, weapon, &hate); + if (hate == 0 && my_hit.base_damage > 1) hate = my_hit.base_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){ + if (my_hit.base_damage > 0) { // if we revamp this function be more general, we will have to make sure this isn't // executed for anything BUT normal melee damage weapons from auto attack if (Hand == EQEmu::inventory::slotPrimary || Hand == EQEmu::inventory::slotSecondary) - weapon_damage = DoDamageCaps(weapon_damage); + my_hit.base_damage = DoDamageCaps(my_hit.base_damage); auto shield_inc = spellbonuses.ShieldEquipDmgMod + itembonuses.ShieldEquipDmgMod + aabonuses.ShieldEquipDmgMod; if (shield_inc > 0 && HasShieldEquiped() && Hand == EQEmu::inventory::slotPrimary) { - weapon_damage = weapon_damage * (100 + shield_inc) / 100; + my_hit.base_damage = my_hit.base_damage * (100 + shield_inc) / 100; hate = hate * (100 + shield_inc) / 100; } - int base_damage = weapon_damage; - int min_damage = 0; // damage bonus - - CheckIncreaseSkill(skillinuse, other, -15); + CheckIncreaseSkill(my_hit.skill, other, -15); CheckIncreaseSkill(EQEmu::skills::SkillOffense, other, -15); // *************************************************************** @@ -1338,7 +1341,7 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b ucDamageBonus = GetWeaponDamageBonus(weapon ? weapon->GetItem() : (const EQEmu::ItemData*) nullptr); - min_damage = ucDamageBonus; + my_hit.min_damage = ucDamageBonus; hate += ucDamageBonus; } #endif @@ -1348,62 +1351,32 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b ucDamageBonus = GetWeaponDamageBonus(weapon ? weapon->GetItem() : (const EQEmu::ItemData*) nullptr, true); - min_damage = ucDamageBonus; + my_hit.min_damage = ucDamageBonus; hate += ucDamageBonus; } } - // this effect is actually a min cap that happens after the final damage is calculated - int min_cap = base_damage * GetMeleeMinDamageMod_SE(skillinuse) / 100; - // damage = mod_client_damage(damage, skillinuse, Hand, weapon, other); - Log.Out(Logs::Detail, Logs::Combat, "Damage calculated to %d (bonus %d, base %d, str %d, skill %d, DMG %d, lv %d)", - damage, min_damage, base_damage, GetSTR(), GetSkill(skillinuse), weapon_damage, mylevel); + Log.Out(Logs::Detail, Logs::Combat, "Damage calculated: base %d min damage %d skill %d", my_hit.base_damage, my_hit.min_damage, my_hit.skill); int hit_chance_bonus = 0; - auto offense = this->offense(skillinuse); // we need this a few times + my_hit.offense = offense(my_hit.skill); // we need this a few times + my_hit.hand = Hand; if(opts) { - base_damage *= opts->damage_percent; - base_damage += opts->damage_flat; + my_hit.base_damage *= opts->damage_percent; + my_hit.base_damage += opts->damage_flat; hate *= opts->hate_percent; hate += opts->hate_flat; hit_chance_bonus += opts->hit_chance; } - //check to see if we hit.. - if (other->AvoidDamage(this, damage, Hand)) { - if (!bRiposte && !IsStrikethrough) { - int strike_through = itembonuses.StrikeThrough + spellbonuses.StrikeThrough + aabonuses.StrikeThrough; - if(strike_through && zone->random.Roll(strike_through)) { - 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; - } - // I'm pretty sure you can riposte a riposte - if (damage == -3 && !bRiposte) { - DoRiposte(other); - if (IsDead()) - return false; - } - } - Log.Out(Logs::Detail, Logs::Combat, "Avoided damage with code %d", damage); - } else { - if (other->CheckHitChance(this, skillinuse, hit_chance_bonus)) { - other->MeleeMitigation(this, damage, base_damage, offense, skillinuse, opts); - if (damage > 0) { - ApplyDamageTable(damage, offense); - CommonOutgoingHitSuccess(other, damage, min_damage, min_cap, skillinuse, opts); - } - Log.Out(Logs::Detail, Logs::Combat, "Final damage after all reductions: %d", damage); - } else { - Log.Out(Logs::Detail, Logs::Combat, "Attack missed. Damage set to 0."); - damage = 0; - } - } + my_hit.tohit = GetTotalToHit(my_hit.skill, hit_chance_bonus); + + DoAttack(other, my_hit, opts); } else { - damage = -5; + my_hit.damage_done = DMG_INVULNERABLE; } // Hate Generation is on a per swing basis, regardless of a hit, miss, or block, its always the same. @@ -1414,28 +1387,28 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b /////////////////////////////////////////////////////////// ////// Send Attack Damage /////////////////////////////////////////////////////////// - if (damage > 0 && aabonuses.SkillAttackProc[0] && aabonuses.SkillAttackProc[1] == skillinuse && + if (my_hit.damage_done > 0 && aabonuses.SkillAttackProc[0] && aabonuses.SkillAttackProc[1] == my_hit.skill && IsValidSpell(aabonuses.SkillAttackProc[2])) { float chance = aabonuses.SkillAttackProc[0] / 1000.0f; if (zone->random.Roll(chance)) SpellFinished(aabonuses.SkillAttackProc[2], other, EQEmu::CastingSlot::Item, 0, -1, spells[aabonuses.SkillAttackProc[2]].ResistDiff); } - other->Damage(this, damage, SPELL_UNKNOWN, skillinuse, true, -1, false, m_specialattacks); + other->Damage(this, my_hit.damage_done, SPELL_UNKNOWN, my_hit.skill, true, -1, false, m_specialattacks); if (IsDead()) return false; - MeleeLifeTap(damage); + MeleeLifeTap(my_hit.damage_done); - if (damage > 0 && HasSkillProcSuccess() && other && other->GetHP() > 0) - TrySkillProc(other, skillinuse, 0, true, Hand); + if (my_hit.damage_done > 0 && HasSkillProcSuccess() && other && other->GetHP() > 0) + TrySkillProc(other, my_hit.skill, 0, true, Hand); CommonBreakInvisibleFromCombat(); if(GetTarget()) - TriggerDefensiveProcs(other, Hand, true, damage); + TriggerDefensiveProcs(other, Hand, true, my_hit.damage_done); - if (damage > 0) + if (my_hit.damage_done > 0) return true; else @@ -1768,8 +1741,6 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, EQEmu::skills::Sk bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) { - int damage = 0; - if (!other) { SetTarget(nullptr); Log.Out(Logs::General, Logs::Error, "A null Mob object was passed to NPC::Attack() for evaluation!"); @@ -1797,13 +1768,16 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool FaceTarget(GetTarget()); - EQEmu::skills::SkillType skillinuse = EQEmu::skills::SkillHandtoHand; + DamageHitInfo my_hit; + my_hit.skill = EQEmu::skills::SkillHandtoHand; + my_hit.hand = Hand; + my_hit.damage_done = 0; if (Hand == EQEmu::inventory::slotPrimary) { - skillinuse = static_cast(GetPrimSkill()); + my_hit.skill = static_cast(GetPrimSkill()); OffHandAtk(false); } if (Hand == EQEmu::inventory::slotSecondary) { - skillinuse = static_cast(GetSecSkill()); + my_hit.skill = static_cast(GetSecSkill()); OffHandAtk(true); } @@ -1826,32 +1800,32 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool switch(weapon->ItemType) { case EQEmu::item::ItemType1HSlash: - skillinuse = EQEmu::skills::Skill1HSlashing; + my_hit.skill = EQEmu::skills::Skill1HSlashing; break; case EQEmu::item::ItemType2HSlash: - skillinuse = EQEmu::skills::Skill2HSlashing; + my_hit.skill = EQEmu::skills::Skill2HSlashing; break; case EQEmu::item::ItemType1HPiercing: - skillinuse = EQEmu::skills::Skill1HPiercing; + my_hit.skill = EQEmu::skills::Skill1HPiercing; break; case EQEmu::item::ItemType2HPiercing: - skillinuse = EQEmu::skills::Skill2HPiercing; + my_hit.skill = EQEmu::skills::Skill2HPiercing; break; case EQEmu::item::ItemType1HBlunt: - skillinuse = EQEmu::skills::Skill1HBlunt; + my_hit.skill = EQEmu::skills::Skill1HBlunt; break; case EQEmu::item::ItemType2HBlunt: - skillinuse = EQEmu::skills::Skill2HBlunt; + my_hit.skill = EQEmu::skills::Skill2HBlunt; break; case EQEmu::item::ItemTypeBow: - skillinuse = EQEmu::skills::SkillArchery; + my_hit.skill = EQEmu::skills::SkillArchery; break; case EQEmu::item::ItemTypeLargeThrowing: case EQEmu::item::ItemTypeSmallThrowing: - skillinuse = EQEmu::skills::SkillThrowing; + my_hit.skill = EQEmu::skills::SkillThrowing; break; default: - skillinuse = EQEmu::skills::SkillHandtoHand; + my_hit.skill = EQEmu::skills::SkillHandtoHand; break; } } @@ -1861,7 +1835,7 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool //do attack animation regardless of whether or not we can hit below int16 charges = 0; EQEmu::ItemInstance weapon_inst(weapon, charges); - AttackAnimation(skillinuse, Hand, &weapon_inst); + AttackAnimation(my_hit.skill, Hand, &weapon_inst); //basically "if not immune" then do the attack if(weapon_damage > 0) { @@ -1898,54 +1872,48 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool //damage = mod_npc_damage(damage, skillinuse, Hand, weapon, other); - int lbase_damage = this->base_damage + eleBane; - int lmin_damage = this->min_damage; - int32 hate = lbase_damage + lmin_damage; - - int min_cap = lbase_damage * GetMeleeMinDamageMod_SE(skillinuse) / 100; + my_hit.base_damage = GetBaseDamage() + eleBane; + my_hit.min_damage = GetMinDamage(); + int32 hate = my_hit.base_damage + my_hit.min_damage; int hit_chance_bonus = 0; - if(opts) { - lbase_damage *= opts->damage_percent; - lbase_damage += opts->damage_flat; + if (opts) { + my_hit.base_damage *= opts->damage_percent; + my_hit.base_damage += opts->damage_flat; hate *= opts->hate_percent; hate += opts->hate_flat; hit_chance_bonus += opts->hit_chance; } - if (other->AvoidDamage(this, damage, Hand)) { - if (!bRiposte && damage == -3) - DoRiposte(other); - } else { - if (other->CheckHitChance(this, skillinuse, hit_chance_bonus)) { - other->MeleeMitigation(this, damage, lbase_damage, this->offense(skillinuse), skillinuse, opts); - CommonOutgoingHitSuccess(other, damage, lmin_damage, min_cap, skillinuse, opts); - } else { - damage = 0; - } - } + my_hit.offense = offense(my_hit.skill); + my_hit.tohit = GetTotalToHit(my_hit.skill, hit_chance_bonus); + + DoAttack(other, my_hit, opts); + other->AddToHateList(this, hate); - Log.Out(Logs::Detail, Logs::Combat, "Final damage against %s: %d", other->GetName(), damage); + Log.Out(Logs::Detail, Logs::Combat, "Final damage against %s: %d", other->GetName(), my_hit.damage_done); if(other->IsClient() && IsPet() && GetOwner()->IsClient()) { //pets do half damage to clients in pvp - damage=damage/2; + my_hit.damage_done /= 2; + if (my_hit.damage_done < 1) + my_hit.damage_done = 1; } + } else { + my_hit.damage_done = DMG_INVULNERABLE; } - else - damage = -5; if(GetHP() > 0 && !other->HasDied()) { - other->Damage(this, damage, SPELL_UNKNOWN, skillinuse, true, -1, false, m_specialattacks); // Not avoidable client already had thier chance to Avoid + other->Damage(this, my_hit.damage_done, SPELL_UNKNOWN, my_hit.skill, true, -1, false, m_specialattacks); // Not avoidable client already had thier chance to Avoid } else return false; if (HasDied()) //killed by damage shield ect return false; - MeleeLifeTap(damage); + MeleeLifeTap(my_hit.damage_done); CommonBreakInvisibleFromCombat(); @@ -1959,14 +1927,14 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool if (!other->HasDied()) TrySpellProc(nullptr, weapon, other, Hand); - if (damage > 0 && HasSkillProcSuccess() && !other->HasDied()) - TrySkillProc(other, skillinuse, 0, true, Hand); + if (my_hit.damage_done > 0 && HasSkillProcSuccess() && !other->HasDied()) + TrySkillProc(other, my_hit.skill, 0, true, Hand); } if(GetHP() > 0 && !other->HasDied()) - TriggerDefensiveProcs(other, Hand, true, damage); + TriggerDefensiveProcs(other, Hand, true, my_hit.damage_done); - if (damage > 0) + if (my_hit.damage_done > 0) return true; else @@ -2874,7 +2842,7 @@ int32 Mob::ReduceDamage(int32 damage) if (spellbonuses.NegateAttacks[2] && (damage > spellbonuses.NegateAttacks[2])) damage -= spellbonuses.NegateAttacks[2]; else - return -6; + return DMG_RUNE; } } @@ -2935,13 +2903,13 @@ int32 Mob::ReduceDamage(int32 damage) } if(damage < 1) - return -6; + return DMG_RUNE; if (spellbonuses.MeleeRune[0] && spellbonuses.MeleeRune[1] >= 0) damage = RuneAbsorb(damage, SE_Rune); if(damage < 1) - return -6; + return DMG_RUNE; return(damage); } @@ -3166,11 +3134,11 @@ bool Client::CheckTripleAttack() if (chance < 1) return false; - int per_inc = aabonuses.TripleAttackChance + spellbonuses.TripleAttackChance + itembonuses.TripleAttackChance; - if (per_inc) - chance += chance * per_inc / 100; + int inc = aabonuses.TripleAttackChance + spellbonuses.TripleAttackChance + itembonuses.TripleAttackChance; + chance = static_cast(chance * (1 + inc / 100.0f)); + chance = (chance * 100) / (chance + 800); - return zone->random.Int(1, 1000) <= chance; + return zone->random.Int(1, 100) <= chance; } bool Client::CheckDoubleRangedAttack() { @@ -3923,9 +3891,9 @@ void Mob::TrySpellProc(const EQEmu::ItemInstance *inst, const EQEmu::ItemData *w return; } -void Mob::TryPetCriticalHit(Mob *defender, uint16 skill, int &damage) +void Mob::TryPetCriticalHit(Mob *defender, DamageHitInfo &hit) { - if (damage < 1) + if (hit.damage_done < 1) return; // Allows pets to perform critical hits. @@ -3935,12 +3903,9 @@ void Mob::TryPetCriticalHit(Mob *defender, uint16 skill, int &damage) Mob *owner = nullptr; int critChance = 0; - critChance += RuleI(Combat, MeleeBaseCritChance); + critChance += RuleI(Combat, PetBaseCritChance); // 0 by default int critMod = 170; - if (damage < 1) // We can't critical hit if we don't hit. - return; - if (IsPet()) owner = GetOwner(); else if ((IsNPC() && CastToNPC()->GetSwarmOwner())) @@ -3954,182 +3919,170 @@ void Mob::TryPetCriticalHit(Mob *defender, uint16 skill, int &damage) int CritPetChance = owner->aabonuses.PetCriticalHit + owner->itembonuses.PetCriticalHit + owner->spellbonuses.PetCriticalHit; - if (CritPetChance || critChance) { - + if (CritPetChance || critChance) // For pets use PetCriticalHit for base chance, pets do not innately critical with without it critChance += CritPetChance; - } if (critChance > 0) { if (zone->random.Roll(critChance)) { - critMod += GetCritDmgMob(skill); - damage += 5; - damage = (damage * critMod) / 100; + critMod += GetCritDmgMob(hit.skill); + hit.damage_done += 5; + hit.damage_done = (hit.damage_done * critMod) / 100; entity_list.FilteredMessageClose_StringID(this, false, 200, MT_CritMelee, FilterMeleeCrits, - CRITICAL_HIT, GetCleanName(), itoa(damage)); + CRITICAL_HIT, GetCleanName(), itoa(hit.damage_done + hit.min_damage)); } } } -void Mob::TryCriticalHit(Mob *defender, uint16 skill, int &damage, int min_damage, ExtraAttackOptions *opts) +void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *opts) { - if (damage < 1 || !defender) + if (hit.damage_done < 1 || !defender) 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()) || (IsNPC() && CastToNPC()->GetSwarmOwner())) { - TryPetCriticalHit(defender, skill, damage); + TryPetCriticalHit(defender, hit); return; } #ifdef BOTS if (this->IsPet() && this->GetOwner() && this->GetOwner()->IsBot()) { - this->TryPetCriticalHit(defender, skill, damage); + this->TryPetCriticalHit(defender, hit); return; } #endif // BOTS - float critChance = 0.0f; - bool IsBerskerSPA = false; - // 1: Try Slay Undead if (defender->GetBodyType() == BT_Undead || defender->GetBodyType() == BT_SummonedUndead || defender->GetBodyType() == BT_Vampire) { - int32 SlayRateBonus = aabonuses.SlayUndead[0] + itembonuses.SlayUndead[0] + spellbonuses.SlayUndead[0]; + int SlayRateBonus = aabonuses.SlayUndead[0] + itembonuses.SlayUndead[0] + spellbonuses.SlayUndead[0]; if (SlayRateBonus) { float slayChance = static_cast(SlayRateBonus) / 10000.0f; if (zone->random.Roll(slayChance)) { - int32 SlayDmgBonus = - aabonuses.SlayUndead[1] + itembonuses.SlayUndead[1] + spellbonuses.SlayUndead[1]; - damage += 5; - damage = (damage * SlayDmgBonus) / 100; + int SlayDmgBonus = std::max( + {aabonuses.SlayUndead[1], itembonuses.SlayUndead[1], spellbonuses.SlayUndead[1]}); + hit.damage_done = std::max(hit.damage_done, hit.base_damage) + 5; + hit.damage_done = (hit.damage_done * SlayDmgBonus) / 100; if (GetGender() == 1) // female entity_list.FilteredMessageClose_StringID( this, false, 200, MT_CritMelee, FilterMeleeCrits, FEMALE_SLAYUNDEAD, - GetCleanName(), itoa(damage + min_damage)); + GetCleanName(), itoa(hit.damage_done + hit.min_damage)); else // males and neuter I guess entity_list.FilteredMessageClose_StringID( this, false, 200, MT_CritMelee, FilterMeleeCrits, MALE_SLAYUNDEAD, - GetCleanName(), itoa(damage + min_damage)); + GetCleanName(), itoa(hit.damage_done + hit.min_damage)); return; } } } // 2: Try Melee Critical + // a lot of good info: http://giline.versus.jp/shiden/damage_e.htm, http://giline.versus.jp/shiden/su.htm - // 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); - + // We either require an innate crit chance or some SPA 169 to crit + bool innate_crit = false; + int crit_chance = GetCriticalChanceBonus(hit.skill); if (IsClient()) { - critChance += RuleI(Combat, ClientBaseCritChance); - - if (spellbonuses.BerserkSPA || itembonuses.BerserkSPA || aabonuses.BerserkSPA) - IsBerskerSPA = true; - - if (((GetClass() == WARRIOR || GetClass() == BERSERKER) && GetLevel() >= 12) || IsBerskerSPA) { - if (IsBerserk() || IsBerskerSPA) - critChance += RuleI(Combat, BerserkBaseCritChance); - else - critChance += RuleI(Combat, WarBerBaseCritChance); - } + if ((GetClass() == WARRIOR || GetClass() == BERSERKER) && GetLevel() >= 12) + innate_crit = true; + else if (GetClass() == RANGER && GetLevel() >= 12 && hit.skill == EQEmu::skills::SkillArchery) + innate_crit = true; + else if (GetClass() == ROGUE && GetLevel() >= 12 && hit.skill == EQEmu::skills::SkillThrowing) + innate_crit = true; } - int deadlyChance = 0; - int deadlyMod = 0; - if (skill == EQEmu::skills::SkillArchery && GetClass() == RANGER && GetSkill(EQEmu::skills::SkillArchery) >= 65) - critChance += 6; + // we have a chance to crit! + if (innate_crit || crit_chance) { + int difficulty = 0; + if (hit.skill == EQEmu::skills::SkillArchery) + difficulty = RuleI(Combat, ArcheryCritDifficulty); + else if (hit.skill == EQEmu::skills::SkillThrowing) + difficulty = RuleI(Combat, ThrowingCritDifficulty); + else + difficulty = RuleI(Combat, MeleeCritDifficulty); + int roll = zone->random.Int(1, difficulty); - if (skill == EQEmu::skills::SkillThrowing && GetClass() == ROGUE && - GetSkill(EQEmu::skills::SkillThrowing) >= 65) { - critChance += RuleI(Combat, RogueCritThrowingChance); - deadlyChance = RuleI(Combat, RogueDeadlyStrikeChance); - deadlyMod = RuleI(Combat, RogueDeadlyStrikeMod); - } + int dex_bonus = GetDEX(); + if (dex_bonus > 255) + dex_bonus = 255 + ((dex_bonus - 255) / 5); + dex_bonus += 45; // chances did not match live without a small boost - int CritChanceBonus = GetCriticalChanceBonus(skill); + // so if we have an innate crit we have a better chance, except for ber throwing + if (!innate_crit || (GetClass() == BERSERKER && hit.skill == EQEmu::skills::SkillThrowing)) + dex_bonus = dex_bonus * 3 / 5; - if (CritChanceBonus || critChance) { + if (crit_chance) + dex_bonus += dex_bonus * crit_chance / 100; - // 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 (zone->random.Roll(critChance)) { - if (TryFinishingBlow(defender, static_cast(skill), damage)) + // check if we crited + if (roll < dex_bonus) { + // step 1: check for finishing blow + if (TryFinishingBlow(defender, hit.damage_done)) return; - int critMod = 170; - bool crip_success = false; - int32 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() || IsBerskerSPA)) { - if (!IsBerserk() && !IsBerskerSPA) - critChance *= float(CripplingBlowChance) / 100.0f; + // step 2: calculate damage + hit.damage_done = std::max(hit.damage_done, hit.base_damage) + 5; + int og_damage = hit.damage_done; + int crit_mod = 170 + GetCritDmgMob(hit.skill); + hit.damage_done = hit.damage_done * crit_mod / 100; + Log.Out(Logs::Detail, Logs::Combat, + "Crit success roll %d dex chance %d og dmg %d crit_mod %d new dmg %d", roll, dex_bonus, + og_damage, crit_mod, hit.damage_done); - if ((IsBerserk() || IsBerskerSPA) || zone->random.Roll(critChance)) - crip_success = true; - } - - critMod += GetCritDmgMob(skill); - damage += 5; - int ogdmg = damage; - damage = damage * critMod / 100; - - bool deadlySuccess = false; - if (deadlyChance && zone->random.Roll(static_cast(deadlyChance) / 100.0f)) { + // step 3: check deadly strike + if (GetClass() == ROGUE && hit.skill == EQEmu::skills::SkillThrowing) { if (BehindMob(defender, GetX(), GetY())) { - damage *= deadlyMod; - deadlySuccess = true; + int chance = GetLevel() * 12; + if (zone->random.Int(1, 1000) < chance) { + // step 3a: check assassinate + int assdmg = TryAssassinate(defender, hit.skill); // I don't think this is right + if (assdmg) { + hit.damage_done = assdmg; + return; + } + hit.damage_done = hit.damage_done * 200 / 100; + entity_list.FilteredMessageClose_StringID( + this, false, 200, MT_CritMelee, FilterMeleeCrits, DEADLY_STRIKE, + GetCleanName(), itoa(hit.damage_done + hit.min_damage)); + return; + } } } - if (crip_success) { - damage += ogdmg * 119 / 100; // the damage_e page says it's a ~1.192 increase of dmg before mod - entity_list.FilteredMessageClose_StringID(this, false, 200, MT_CritMelee, - FilterMeleeCrits, CRIPPLING_BLOW, - GetCleanName(), itoa(damage + min_damage)); + // step 4: check crips + // this SPA was reused on live ... + bool berserk = spellbonuses.BerserkSPA || itembonuses.BerserkSPA || aabonuses.BerserkSPA; + if (!berserk) { + if (zone->random.Roll(GetCrippBlowChance())) { + berserk = true; + } // TODO: Holyforge is suppose to have an innate extra undead chance? 1/5 which matches the SPA crip though ... + } + + if (IsBerserk() || berserk) { + hit.damage_done += og_damage * 119 / 100; + Log.Out(Logs::Detail, Logs::Combat, "Crip damage %d", hit.damage_done); + entity_list.FilteredMessageClose_StringID( + this, false, 200, MT_CritMelee, FilterMeleeCrits, CRIPPLING_BLOW, GetCleanName(), + itoa(hit.damage_done + hit.min_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); + defender->Stun(2000); } - } else if (deadlySuccess) { - entity_list.FilteredMessageClose_StringID(this, false, 200, MT_CritMelee, - FilterMeleeCrits, DEADLY_STRIKE, - GetCleanName(), itoa(damage + min_damage)); - } else { - entity_list.FilteredMessageClose_StringID(this, false, 200, MT_CritMelee, - FilterMeleeCrits, CRITICAL_HIT, - GetCleanName(), itoa(damage + min_damage)); + return; } + // okay, critted but didn't do anything else, just normal message now + entity_list.FilteredMessageClose_StringID(this, false, 200, MT_CritMelee, FilterMeleeCrits, + CRITICAL_HIT, GetCleanName(), + itoa(hit.damage_done + hit.min_damage)); } } } -bool Mob::TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse, int &damage) +bool Mob::TryFinishingBlow(Mob *defender, int &damage) { // base2 of FinishingBlowLvl is the HP limit (cur / max) * 1000, 10% is listed as 100 if (defender && !defender->IsClient() && defender->GetHPRatio() < 10) { @@ -4144,12 +4097,12 @@ bool Mob::TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse, i else if (FB_Level < itembonuses.FinishingBlowLvl[0]) FB_Level = itembonuses.FinishingBlowLvl[0]; - // Proc Chance value of 500 = 5% - uint32 ProcChance = - (aabonuses.FinishingBlow[0] + spellbonuses.FinishingBlow[0] + spellbonuses.FinishingBlow[0]) / 10; + // modern AA description says rank 1 (500) is 50% chance + int ProcChance = + aabonuses.FinishingBlow[0] + spellbonuses.FinishingBlow[0] + spellbonuses.FinishingBlow[0]; if (FB_Level && FB_Dmg && (defender->GetLevel() <= FB_Level) && - (ProcChance >= zone->random.Int(0, 1000))) { + (ProcChance >= zone->random.Int(1, 1000))) { entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FINISHING_BLOW, GetName()); damage = FB_Dmg; return true; @@ -4205,12 +4158,12 @@ void Mob::DoRiposte(Mob *defender) if (defender->GetClass() == MONK) defender->MonkSpecialAttack(this, defender->aabonuses.GiveDoubleRiposte[2]); - else if (defender->IsClient() && defender->CastToClient()->HasSkill((EQEmu::skills::SkillType)defender->aabonuses.GiveDoubleRiposte[2])) + else if (defender->IsClient()) // so yeah, even if you don't have the skill you can still do the attack :P (and we don't crash anymore) defender->CastToClient()->DoClassAttacks(this, defender->aabonuses.GiveDoubleRiposte[2], true); } } -void Mob::ApplyMeleeDamageBonus(uint16 skill, int &damage, ExtraAttackOptions *opts) +void Mob::ApplyMeleeDamageMods(uint16 skill, int &damage, Mob *defender, ExtraAttackOptions *opts) { int dmgbonusmod = 0; @@ -4218,6 +4171,13 @@ void Mob::ApplyMeleeDamageBonus(uint16 skill, int &damage, ExtraAttackOptions *o if (opts) dmgbonusmod += opts->melee_damage_bonus_flat; + if (defender) { + if (defender->IsClient() && defender->GetClass() == WARRIOR) + dmgbonusmod -= 5; + // 168 defensive + dmgbonusmod += (defender->spellbonuses.MeleeMitigationEffect + itembonuses.MeleeMitigationEffect + aabonuses.MeleeMitigationEffect); + } + damage += damage * dmgbonusmod / 100; } @@ -4368,7 +4328,7 @@ const DamageTable &Mob::GetDamageTable() const return which[level - 50]; } -void Mob::ApplyDamageTable(int &damage, int offense) +void Mob::ApplyDamageTable(DamageHitInfo &hit) { // someone may want to add this to custom servers, can remove this if that's the case if (!IsClient() @@ -4378,7 +4338,7 @@ void Mob::ApplyDamageTable(int &damage, int offense) ) return; // this was parsed, but we do see the min of 10 and the normal minus factor is 105, so makes sense - if (offense < 115) + if (hit.offense < 115) return; auto &damage_table = GetDamageTable(); @@ -4386,14 +4346,14 @@ void Mob::ApplyDamageTable(int &damage, int offense) if (zone->random.Roll(damage_table.chance)) return; - int basebonus = offense - damage_table.minusfactor; + int basebonus = hit.offense - damage_table.minusfactor; basebonus = std::max(10, basebonus / 2); int extrapercent = zone->random.Roll0(basebonus); int percent = std::min(100 + extrapercent, damage_table.max_extra); - damage = (damage * percent) / 100; + hit.damage_done = (hit.damage_done * percent) / 100; if (IsWarriorClass() && GetLevel() > 54) - damage++; + hit.damage_done++; Log.Out(Logs::Detail, Logs::Attack, "Damage table applied %d (max %d)", percent, damage_table.max_extra); } @@ -4696,52 +4656,49 @@ int32 Mob::RuneAbsorb(int32 damage, uint16 type) return damage; } -// min_damage is the damage bonus, we need to pass it along for a bit -// min_mod is the min hit cap, which needs to be calculated before we call this, but applied here -void Mob::CommonOutgoingHitSuccess(Mob* defender, int &damage, int min_damage, int min_mod, EQEmu::skills::SkillType skillInUse, ExtraAttackOptions *opts) +void Mob::CommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttackOptions *opts) { if (!defender) return; // BER weren't parsing the halving - if (skillInUse == EQEmu::skills::SkillArchery || - (skillInUse == EQEmu::skills::SkillThrowing && GetClass() != BERSERKER)) - damage /= 2; + if (hit.skill == EQEmu::skills::SkillArchery || + (hit.skill == EQEmu::skills::SkillThrowing && GetClass() != BERSERKER)) + hit.damage_done /= 2; - if (damage < 1) - damage = 1; + if (hit.damage_done < 1) + hit.damage_done = 1; - if (skillInUse == EQEmu::skills::SkillArchery) { + if (hit.skill == EQEmu::skills::SkillArchery) { int bonus = aabonuses.ArcheryDamageModifier + itembonuses.ArcheryDamageModifier + spellbonuses.ArcheryDamageModifier; - damage += damage * bonus / 100; - int headshot = TryHeadShot(defender, skillInUse); - if (headshot > 0) - damage = headshot; - } - - // this parses after damage table - if (skillInUse == EQEmu::skills::SkillArchery && GetClass() == RANGER && GetLevel() > 50) { - if (defender->IsNPC() && !defender->IsMoving() && !defender->IsRooted()) { - damage *= 2; - Message_StringID(MT_CritMelee, BOW_DOUBLE_DAMAGE); + hit.damage_done += hit.damage_done * bonus / 100; + int headshot = TryHeadShot(defender, hit.skill); + if (headshot > 0) { + hit.damage_done = headshot; + } else if (GetClass() == RANGER && GetLevel() > 50) { // no double dmg on headshot + if (defender->IsNPC() && !defender->IsMoving() && !defender->IsRooted()) { + hit.damage_done *= 2; + Message_StringID(MT_CritMelee, BOW_DOUBLE_DAMAGE); + } } } int extra_mincap = 0; - if (skillInUse == EQEmu::skills::SkillBackstab) { + int min_mod = hit.base_damage * GetMeleeMinDamageMod_SE(hit.skill) / 100; + if (hit.skill == EQEmu::skills::SkillBackstab) { extra_mincap = GetLevel() < 7 ? 7 : GetLevel(); if (GetLevel() >= 60) extra_mincap = GetLevel() * 2; else if (GetLevel() > 50) extra_mincap = GetLevel() * 3 / 2; if (IsSpecialAttack(eSpecialAttacks::ChaoticStab)) { - damage = extra_mincap; + hit.damage_done = extra_mincap; } else { - int ass = TryAssassinate(defender, skillInUse, 10000); // reusetime shouldn't have an effect .... + int ass = TryAssassinate(defender, hit.skill); if (ass > 0) - damage = ass; + hit.damage_done = ass; } - } else if (skillInUse == EQEmu::skills::SkillFrenzy && GetClass() == BERSERKER && GetLevel() > 50) { + } else if (hit.skill == EQEmu::skills::SkillFrenzy && GetClass() == BERSERKER && GetLevel() > 50) { extra_mincap = 4 * GetLevel() / 5; } @@ -4749,23 +4706,23 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, int &damage, int min_damage, i // Seems the crit message is generated before some of them :P // worn item +skill dmg, SPA 220, 418. Live has a normalized version that should be here too - min_damage += GetSkillDmgAmt(skillInUse); + hit.min_damage += GetSkillDmgAmt(hit.skill); // shielding mod2 if (defender->itembonuses.MeleeMitigation) - min_damage -= min_damage * defender->itembonuses.MeleeMitigation / 100; + hit.min_damage -= hit.min_damage * defender->itembonuses.MeleeMitigation / 100; - ApplyMeleeDamageBonus(skillInUse, damage, opts); + ApplyMeleeDamageMods(hit.skill, hit.damage_done, defender, opts); min_mod = std::max(min_mod, extra_mincap); - if (min_mod && damage < min_mod) // SPA 186 - damage = min_mod; + if (min_mod && hit.damage_done < min_mod) // SPA 186 + hit.damage_done = min_mod; - TryCriticalHit(defender, skillInUse, damage, min_damage, opts); + TryCriticalHit(defender, hit, opts); - damage += min_damage; + hit.damage_done += hit.min_damage; if (IsClient()) { int extra = 0; - switch (skillInUse) { + switch (hit.skill) { case EQEmu::skills::SkillThrowing: case EQEmu::skills::SkillArchery: extra = CastToClient()->GetHeroicDEX() / 10; @@ -4774,7 +4731,7 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, int &damage, int min_damage, i extra = CastToClient()->GetHeroicSTR() / 10; break; } - damage += extra; + hit.damage_done += extra; } // this appears where they do special attack dmg mods @@ -4794,9 +4751,9 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, int &damage, int min_damage, i spec_mod = mod; } if (spec_mod > 0) - damage = (damage * spec_mod) / 100; + hit.damage_done = (hit.damage_done * spec_mod) / 100; - damage += (damage * defender->GetSkillDmgTaken(skillInUse, opts) / 100) + (defender->GetFcDamageAmtIncoming(this, 0, true, skillInUse)); + hit.damage_done += (hit.damage_done * defender->GetSkillDmgTaken(hit.skill, opts) / 100) + (defender->GetFcDamageAmtIncoming(this, 0, true, hit.skill)); CheckNumHitsRemaining(NumHit::OutgoingHitSuccess); } @@ -5003,23 +4960,22 @@ void Client::DoAttackRounds(Mob *target, int hand, bool IsFromSpell) // you can only triple from the main hand if (hand == EQEmu::inventory::slotPrimary && CanThisClassTripleAttack()) { CheckIncreaseSkill(EQEmu::skills::SkillTripleAttack, target, -10); - if (CheckTripleAttack()) + if (CheckTripleAttack()) { Attack(target, hand, false, false, IsFromSpell); + auto flurrychance = aabonuses.FlurryChance + spellbonuses.FlurryChance + + itembonuses.FlurryChance; + if (flurrychance && zone->random.Roll(flurrychance)) { + Attack(target, hand, false, false, IsFromSpell); + if (zone->random.Roll(flurrychance)) + Attack(target, hand, false, false, IsFromSpell); + Message_StringID(MT_NPCFlurry, YOU_FLURRY); + } + } } } } if (hand == EQEmu::inventory::slotPrimary) { - // According to http://www.monkly-business.net/forums/showpost.php?p=312095&postcount=168 a dev told them flurry isn't dependant on triple attack - // the parses kind of back that up and all of my parses seemed to be 4 or 5 attacks in the round which would work out to be - // doubles or triples with 2 from flurries or triple with 1 or 2 flurries ... Going with the "dev quote" I guess like we've always had it - auto flurrychance = aabonuses.FlurryChance + spellbonuses.FlurryChance + itembonuses.FlurryChance; - if (flurrychance && zone->random.Roll(flurrychance)) { - Attack(target, hand, false, false, IsFromSpell); - Attack(target, hand, false, false, IsFromSpell); - Message_StringID(MT_NPCFlurry, YOU_FLURRY); - } - // I haven't parsed where this guy happens, but it's not part of the normal chain above so this is fine auto extraattackchance = aabonuses.ExtraAttackChance + spellbonuses.ExtraAttackChance + itembonuses.ExtraAttackChance; if (extraattackchance && HasTwoHanderEquipped() && zone->random.Roll(extraattackchance)) Attack(target, hand, false, false, IsFromSpell); diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index 3acf6cb1d..1f94fb63b 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -1313,8 +1313,9 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) } case SE_HeadShotLevel: { - if (newbon->HSLevel < base1) - newbon->HSLevel = base1; + if (newbon->HSLevel[0] < base1) + newbon->HSLevel[0] = base1; + newbon->HSLevel[1] = base2; break; } @@ -1327,8 +1328,10 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) } case SE_AssassinateLevel: { - if (newbon->AssassinateLevel < base1) - newbon->AssassinateLevel = base1; + if (newbon->AssassinateLevel[0] < base1) { + newbon->AssassinateLevel[0] = base1; + newbon->AssassinateLevel[1] = base2; + } break; } @@ -1386,7 +1389,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) } case SE_MeleeMitigation: - newbon->MeleeMitigationEffect -= base1; + newbon->MeleeMitigationEffect += base1; break; case SE_ATK: @@ -1929,8 +1932,8 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; case SE_MeleeMitigation: - //for some reason... this value is negative for increased mitigation - new_bonus->MeleeMitigationEffect -= effect_value; + // This value is negative because it counteracts another SPA :P + new_bonus->MeleeMitigationEffect += effect_value; break; case SE_CriticalHitChance: @@ -3026,8 +3029,10 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_HeadShotLevel: { - if(new_bonus->HSLevel < effect_value) - new_bonus->HSLevel = effect_value; + if(new_bonus->HSLevel[0] < effect_value) { + new_bonus->HSLevel[0] = effect_value; + new_bonus->HSLevel[1] = base2; + } break; } @@ -3042,8 +3047,10 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_AssassinateLevel: { - if(new_bonus->AssassinateLevel < effect_value) - new_bonus->AssassinateLevel = effect_value; + if(new_bonus->AssassinateLevel[0] < effect_value) { + new_bonus->AssassinateLevel[0] = effect_value; + new_bonus->AssassinateLevel[1] = base2; + } break; } @@ -4649,9 +4656,12 @@ void Mob::NegateSpellsBonuses(uint16 spell_id) break; case SE_HeadShotLevel: - spellbonuses.HSLevel = effect_value; - aabonuses.HSLevel = effect_value; - itembonuses.HSLevel = effect_value; + spellbonuses.HSLevel[0] = effect_value; + aabonuses.HSLevel[0] = effect_value; + itembonuses.HSLevel[0] = effect_value; + spellbonuses.HSLevel[1] = effect_value; + aabonuses.HSLevel[1] = effect_value; + itembonuses.HSLevel[1] = effect_value; break; case SE_Assassinate: @@ -4664,9 +4674,12 @@ void Mob::NegateSpellsBonuses(uint16 spell_id) break; case SE_AssassinateLevel: - spellbonuses.AssassinateLevel = effect_value; - aabonuses.AssassinateLevel = effect_value; - itembonuses.AssassinateLevel = effect_value; + spellbonuses.AssassinateLevel[0] = effect_value; + aabonuses.AssassinateLevel[0] = effect_value; + itembonuses.AssassinateLevel[0] = effect_value; + spellbonuses.AssassinateLevel[1] = effect_value; + aabonuses.AssassinateLevel[1] = effect_value; + itembonuses.AssassinateLevel[1] = effect_value; break; case SE_FinishingBlow: diff --git a/zone/bot.cpp b/zone/bot.cpp index 42ca396af..237c1144b 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -3701,24 +3701,24 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b // calculate attack_skill and skillinuse depending on hand and weapon // also send Packet to near clients - EQEmu::skills::SkillType skillinuse; - AttackAnimation(skillinuse, Hand, weapon); - Log.Out(Logs::Detail, Logs::Combat, "Attacking with %s in slot %d using skill %d", weapon?weapon->GetItem()->Name:"Fist", Hand, skillinuse); + DamageHitInfo my_hit; + AttackAnimation(my_hit.skill, Hand, weapon); + Log.Out(Logs::Detail, Logs::Combat, "Attacking with %s in slot %d using skill %d", weapon?weapon->GetItem()->Name:"Fist", Hand, my_hit.skill); /// Now figure out damage - int damage = 0; + my_hit.damage_done =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; + my_hit.base_damage = GetWeaponDamage(other, weapon, &hate); + if (hate == 0 && my_hit.base_damage > 1) + hate = my_hit.base_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) { - int min_damage = 0; + if (my_hit.base_damage > 0) { + my_hit.min_damage = 0; // *************************************************************** // *** Calculate the damage bonus, if applicable, for this hit *** @@ -3736,7 +3736,7 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b // Damage bonuses apply only to hits from the main hand (Hand == MainPrimary) 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 EQEmu::ItemData*) nullptr); - min_damage = ucDamageBonus; + my_hit.min_damage = ucDamageBonus; hate += ucDamageBonus; } #endif @@ -3744,55 +3744,29 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b if (Hand == EQEmu::inventory::slotSecondary) { if (aabonuses.SecondaryDmgInc || itembonuses.SecondaryDmgInc || spellbonuses.SecondaryDmgInc){ ucDamageBonus = GetWeaponDamageBonus(weapon ? weapon->GetItem() : (const EQEmu::ItemData*) nullptr); - min_damage = ucDamageBonus; + my_hit.min_damage = ucDamageBonus; hate += ucDamageBonus; } } - int min_cap = (weapon_damage * GetMeleeMinDamageMod_SE(skillinuse) / 100); + Log.Out(Logs::Detail, Logs::Combat, "Damage calculated: base %d min damage %d skill %d", my_hit.base_damage, my_hit.min_damage, my_hit.skill); - Log.Out(Logs::Detail, Logs::Combat, "Damage calculated to %d (bonus %d, base %d, str %d, skill %d, DMG %d, lv %d)", - damage, min_damage, weapon_damage, GetSTR(), GetSkill(skillinuse), weapon_damage, mylevel); + my_hit.offense = offense(my_hit.skill); + my_hit.hand = Hand; - auto offense = this->offense(skillinuse); - - if(opts) { - weapon_damage *= opts->damage_percent; - weapon_damage += opts->damage_flat; + if (opts) { + my_hit.base_damage *= opts->damage_percent; + my_hit.base_damage += opts->damage_flat; hate *= opts->hate_percent; hate += opts->hate_flat; } - //check to see if we hit.. - if (other->AvoidDamage(this, damage, Hand)) { - if (!FromRiposte && !IsStrikethrough) { - int strike_through = itembonuses.StrikeThrough + spellbonuses.StrikeThrough + aabonuses.StrikeThrough; - if(strike_through && zone->random.Roll(strike_through)) { - 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; - } - if (damage == -3 && !FromRiposte) { - DoRiposte(other); - if (HasDied()) - return false; - } - } - } else { - if (other->CheckHitChance(this, skillinuse)) { - other->MeleeMitigation(this, damage, weapon_damage, offense, skillinuse, opts); - if (damage > 0) { - ApplyDamageTable(damage, offense); - CommonOutgoingHitSuccess(other, damage, min_damage, min_cap, skillinuse, opts); - } - } else { - damage = 0; - } - } - Log.Out(Logs::Detail, Logs::Combat, "Final damage after all reductions: %d", damage); + DoAttack(other, my_hit, opts); + + Log.Out(Logs::Detail, Logs::Combat, "Final damage after all reductions: %d", my_hit.damage_done); + } else { + my_hit.damage_done = DMG_INVULNERABLE; } - 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. @@ -3801,14 +3775,14 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b /////////////////////////////////////////////////////////// ////// Send Attack Damage /////////////////////////////////////////////////////////// - other->Damage(this, damage, SPELL_UNKNOWN, skillinuse); + other->Damage(this, my_hit.damage_done, SPELL_UNKNOWN, my_hit.skill); if (GetHP() < 0) return false; - MeleeLifeTap(damage); + MeleeLifeTap(my_hit.damage_done); - if (damage > 0) + if (my_hit.damage_done > 0) CheckNumHitsRemaining(NumHit::OutgoingHitSuccess); CommonBreakInvisibleFromCombat(); @@ -3816,9 +3790,9 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b BuffFadeByEffect(SE_NegateIfCombat); if(GetTarget()) - TriggerDefensiveProcs(other, Hand, true, damage); + TriggerDefensiveProcs(other, Hand, true, my_hit.damage_done); - if (damage > 0) + if (my_hit.damage_done > 0) return true; else return false; @@ -4699,16 +4673,16 @@ int Bot::GetHandToHandDamage(void) { return 2; } -bool Bot::TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse, int &damage) +bool Bot::TryFinishingBlow(Mob *defender, int &damage) { if (!defender) return false; if (aabonuses.FinishingBlow[1] && !defender->IsClient() && defender->GetHPRatio() < 10) { - int chance = (aabonuses.FinishingBlow[0] / 10); + int chance = aabonuses.FinishingBlow[0]; int fb_damage = aabonuses.FinishingBlow[1]; int levelreq = aabonuses.FinishingBlowLvl[0]; - if (defender->GetLevel() <= levelreq && (chance >= zone->random.Int(0, 1000))) { + if (defender->GetLevel() <= levelreq && (chance >= zone->random.Int(1, 1000))) { Log.Out(Logs::Detail, Logs::Combat, "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()); @@ -4849,36 +4823,29 @@ void Bot::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 } } - int min_cap = max_damage * GetMeleeMinDamageMod_SE(skill) / 100; - int hand = EQEmu::inventory::slotPrimary; - int damage = 0; - auto offense = this->offense(skill); + DamageHitInfo my_hit; + my_hit.base_damage = max_damage; + my_hit.min_damage = min_damage; + my_hit.damage_done = 0; + + my_hit.skill = skill; + my_hit.offense = offense(my_hit.skill); + my_hit.tohit = GetTotalToHit(my_hit.skill, 0); + my_hit.hand = EQEmu::inventory::slotPrimary; + if (skill == EQEmu::skills::SkillThrowing || skill == EQEmu::skills::SkillArchery) - hand = EQEmu::inventory::slotRange; - if (who->AvoidDamage(this, damage, hand)) { - if (damage == -3) - DoRiposte(who); - } else { - if (HitChance || who->CheckHitChance(this, skill)) { - if (max_damage > 0) - who->MeleeMitigation(this, damage, max_damage, offense, skill); - if (damage > 0) { - ApplyDamageTable(damage, offense); - CommonOutgoingHitSuccess(who, damage, min_damage, min_cap, skill); - } - } else { - damage = 0; - } - } + my_hit.hand = EQEmu::inventory::slotRange; + + DoAttack(who, my_hit); who->AddToHateList(this, hate); - who->Damage(this, damage, SPELL_UNKNOWN, skill, false); + who->Damage(this, my_hit.damage_done, SPELL_UNKNOWN, skill, false); if(!GetTarget() || HasDied()) return; - if (damage > 0) + if (my_hit.damage_done > 0) CheckNumHitsRemaining(NumHit::OutgoingHitSuccess); //[AA Dragon Punch] value[0] = 100 for 25%, chance value[1] = skill @@ -4894,7 +4861,7 @@ void Bot::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 if (HasSkillProcs()) TrySkillProc(who, skill, (ReuseTime * 1000)); - if (damage > 0 && HasSkillProcSuccess()) + if (my_hit.damage_done > 0 && HasSkillProcSuccess()) TrySkillProc(who, skill, (ReuseTime * 1000), true); } @@ -5030,7 +4997,6 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { return; float HasteModifier = (GetHaste() * 0.01f); - int32 dmg = 0; uint16 skill_to_use = -1; int level = GetLevel(); int reuse = (TauntReuseTime * 1000); @@ -5084,18 +5050,14 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { if(skill_to_use == -1) return; + int dmg = GetBaseSkillDamage(static_cast(skill_to_use), GetTarget()); + if (skill_to_use == EQEmu::skills::SkillBash) { if (target != this) { DoAnim(animTailRake); if (GetWeaponDamage(target, GetBotItem(EQEmu::inventory::slotSecondary)) <= 0 && GetWeaponDamage(target, GetBotItem(EQEmu::inventory::slotShoulders)) <= 0) - dmg = -5; - else { - if (!target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) - dmg = 0; - else { - dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash); - } - } + dmg = DMG_INVULNERABLE; + reuse = (BashReuseTime * 1000); DoSpecialAttackDamage(target, EQEmu::skills::SkillBash, dmg, 0, -1, reuse); did_attack = true; @@ -5104,14 +5066,13 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { if (skill_to_use == EQEmu::skills::SkillFrenzy) { int AtkRounds = 3; - int32 max_dmg = GetBaseSkillDamage(EQEmu::skills::SkillFrenzy); DoAnim(anim2HSlashing); reuse = (FrenzyReuseTime * 1000); did_attack = true; while(AtkRounds > 0) { if (GetTarget() && (AtkRounds == 1 || zone->random.Int(0, 100) < 75)) { - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, 0, max_dmg, reuse, true); + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, dmg, 0, dmg, reuse, true); } AtkRounds--; @@ -5122,14 +5083,8 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { if(target != this) { DoAnim(animKick); if (GetWeaponDamage(target, GetBotItem(EQEmu::inventory::slotFeet)) <= 0) - dmg = -5; - else { - if (!target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) - dmg = 0; - else { - dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick); - } - } + dmg = DMG_INVULNERABLE; + reuse = (KickReuseTime * 1000); DoSpecialAttackDamage(target, EQEmu::skills::SkillKick, dmg, 0, -1, reuse); did_attack = true; diff --git a/zone/bot.h b/zone/bot.h index 177efeffb..5431df341 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -239,7 +239,7 @@ public: uint16 BotGetSpellPriority(int spellslot) { return AIspells[spellslot].priority; } virtual float GetProcChances(float ProcBonus, uint16 hand); virtual int GetHandToHandDamage(void); - virtual bool TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse, int &damage); + virtual bool TryFinishingBlow(Mob *defender, int &damage); virtual void DoRiposte(Mob* defender); inline virtual int32 GetATK() const { return ATK + itembonuses.ATK + spellbonuses.ATK + ((GetSTR() + GetSkill(EQEmu::skills::SkillOffense)) * 9 / 10); } inline virtual int32 GetATKBonus() const { return itembonuses.ATK + spellbonuses.ATK; } diff --git a/zone/common.h b/zone/common.h index 5a6782148..2ed076b6f 100644 --- a/zone/common.h +++ b/zone/common.h @@ -35,6 +35,13 @@ #define CON_YELLOW 15 #define CON_RED 13 +#define DMG_BLOCKED -1 +#define DMG_PARRIED -2 +#define DMG_RIPOSTED -3 +#define DMG_DODGED -4 +#define DMG_INVULNERABLE -5 +#define DMG_RUNE -6 + //Spell specialization parameters, not sure of a better place for them #define SPECIALIZE_FIZZLE 11 //% fizzle chance reduce at 200 specialized #define SPECIALIZE_MANA_REDUCE 12 //% mana cost reduction at 200 specialized @@ -464,9 +471,9 @@ struct StatBonuses { int8 CriticalMend; // chance critical monk mend int32 ImprovedReclaimEnergy; // Modifies amount of mana returned from reclaim energy uint32 HeadShot[2]; // Headshot AA (Massive dmg vs humaniod w/ archery) 0= ? 1= Dmg - uint8 HSLevel; // Max Level Headshot will be effective at. + uint8 HSLevel[2]; // Max Level Headshot will be effective at. and chance mod uint32 Assassinate[2]; // Assassinate AA (Massive dmg vs humaniod w/ assassinate) 0= ? 1= Dmg - uint8 AssassinateLevel; // Max Level Assassinate will be effective at. + uint8 AssassinateLevel[2]; // Max Level Assassinate will be effective at. int32 PetMeleeMitigation; // Add AC to owner's pet. bool IllusionPersistence; // Causes illusions not to fade. uint16 extra_xtargets; // extra xtarget entries @@ -644,5 +651,17 @@ struct DamageTable { int32 minusfactor; // difficulty of rolling }; +struct DamageHitInfo { + //uint16 attacker; // id + //uint16 defender; // id + int base_damage; + int min_damage; + int damage_done; + int offense; + int tohit; + int hand; + EQEmu::skills::SkillType skill; +}; + #endif diff --git a/zone/lua_mob.cpp b/zone/lua_mob.cpp index d13ff4a52..add24d861 100644 --- a/zone/lua_mob.cpp +++ b/zone/lua_mob.cpp @@ -1270,12 +1270,6 @@ void Lua_Mob::DoSpecialAttackDamage(Lua_Mob other, int skill, int max_damage, in self->DoSpecialAttackDamage(other, static_cast(skill), max_damage, min_damage, hate_override, reuse_time); } -void Lua_Mob::DoSpecialAttackDamage(Lua_Mob other, int skill, int max_damage, int min_damage, int hate_override, int reuse_time, - bool hit_chance) { - Lua_Safe_Call_Void(); - self->DoSpecialAttackDamage(other, static_cast(skill), max_damage, min_damage, hate_override, reuse_time, hit_chance); -} - void Lua_Mob::DoThrowingAttackDmg(Lua_Mob other) { Lua_Safe_Call_Void(); self->DoThrowingAttackDmg(other); @@ -2218,7 +2212,6 @@ luabind::scope lua_register_mob() { .def("DoSpecialAttackDamage", (void(Lua_Mob::*)(Lua_Mob,int,int,int))&Lua_Mob::DoSpecialAttackDamage) .def("DoSpecialAttackDamage", (void(Lua_Mob::*)(Lua_Mob,int,int,int,int))&Lua_Mob::DoSpecialAttackDamage) .def("DoSpecialAttackDamage", (void(Lua_Mob::*)(Lua_Mob,int,int,int,int,int))&Lua_Mob::DoSpecialAttackDamage) - .def("DoSpecialAttackDamage", (void(Lua_Mob::*)(Lua_Mob,int,int,int,int,int,bool))&Lua_Mob::DoSpecialAttackDamage) .def("DoThrowingAttackDmg", (void(Lua_Mob::*)(Lua_Mob))&Lua_Mob::DoThrowingAttackDmg) .def("DoThrowingAttackDmg", (void(Lua_Mob::*)(Lua_Mob,Lua_ItemInst))&Lua_Mob::DoThrowingAttackDmg) .def("DoThrowingAttackDmg", (void(Lua_Mob::*)(Lua_Mob,Lua_ItemInst,Lua_Item))&Lua_Mob::DoThrowingAttackDmg) diff --git a/zone/lua_mob.h b/zone/lua_mob.h index 4fd1f0c69..37bde994a 100644 --- a/zone/lua_mob.h +++ b/zone/lua_mob.h @@ -267,7 +267,6 @@ public: void DoSpecialAttackDamage(Lua_Mob other, int skill, int max_damage, int min_damage); void DoSpecialAttackDamage(Lua_Mob other, int skill, int max_damage, int min_damage, int hate_override); void DoSpecialAttackDamage(Lua_Mob other, int skill, int max_damage, int min_damage, int hate_override, int reuse_time); - void DoSpecialAttackDamage(Lua_Mob other, int skill, int max_damage, int min_damage, int hate_override, int reuse_time, bool hit_chance); void DoThrowingAttackDmg(Lua_Mob other); void DoThrowingAttackDmg(Lua_Mob other, Lua_ItemInst range_weapon); void DoThrowingAttackDmg(Lua_Mob other, Lua_ItemInst range_weapon, Lua_Item item); diff --git a/zone/merc.cpp b/zone/merc.cpp index 6021f05d5..be8f75e60 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -4426,15 +4426,10 @@ void Merc::DoClassAttacks(Mob *target) { if(zone->random.Int(0, 100) > 25) //tested on live, warrior mobs both kick and bash, kick about 75% of the time, casting doesn't seem to make a difference. { DoAnim(animKick); - int32 dmg = 0; + int32 dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick); - if (GetWeaponDamage(target, (const EQEmu::ItemData*)nullptr) <= 0){ - dmg = -5; - } - else{ - if (target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) - dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick, GetTarget()); - } + if (GetWeaponDamage(target, (const EQEmu::ItemData*)nullptr) <= 0) + dmg = DMG_INVULNERABLE; reuse = KickReuseTime * 1000; DoSpecialAttackDamage(target, EQEmu::skills::SkillKick, dmg, 1, -1, reuse); @@ -4443,15 +4438,10 @@ void Merc::DoClassAttacks(Mob *target) { else { DoAnim(animTailRake); - int32 dmg = 0; + int32 dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash); - if (GetWeaponDamage(target, (const EQEmu::ItemData*)nullptr) <= 0){ - dmg = -5; - } - else{ - if (target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) - dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash, GetTarget()); - } + if (GetWeaponDamage(target, (const EQEmu::ItemData*)nullptr) <= 0) + dmg = DMG_INVULNERABLE; reuse = BashReuseTime * 1000; DoSpecialAttackDamage(target, EQEmu::skills::SkillBash, dmg, 1, -1, reuse); diff --git a/zone/mob.h b/zone/mob.h index 0dfa162c1..d6fd610cd 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -162,21 +162,22 @@ public: // 13 = Primary (default), 14 = secondary virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr) = 0; + void DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts = nullptr); int MonkSpecialAttack(Mob* other, uint8 skill_used); virtual void TryBackstab(Mob *other,int ReuseTime = 10); - bool AvoidDamage(Mob* attacker, int &damage, int hand); + bool AvoidDamage(Mob *attacker, DamageHitInfo &hit); int compute_tohit(EQEmu::skills::SkillType skillinuse); int GetTotalToHit(EQEmu::skills::SkillType skill, int chance_mod); // compute_tohit + spell bonuses int compute_defense(); int GetTotalDefense(); // compute_defense + spell bonuses - bool CheckHitChance(Mob* attacker, EQEmu::skills::SkillType skillinuse, int chance_mod = 0); - virtual void TryCriticalHit(Mob *defender, uint16 skill, int &damage, int min_damage, ExtraAttackOptions *opts = nullptr); - void TryPetCriticalHit(Mob *defender, uint16 skill, int &damage); - virtual bool TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse, int &damage); + bool CheckHitChance(Mob* attacker, DamageHitInfo &hit); + void TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *opts = nullptr); + void TryPetCriticalHit(Mob *defender, DamageHitInfo &hit); + virtual bool TryFinishingBlow(Mob *defender, int &damage); int TryHeadShot(Mob* defender, EQEmu::skills::SkillType skillInUse); - int TryAssassinate(Mob* defender, EQEmu::skills::SkillType skillInUse, uint16 ReuseTime); + int TryAssassinate(Mob* defender, EQEmu::skills::SkillType skillInUse); virtual void DoRiposte(Mob* defender); - void ApplyMeleeDamageBonus(uint16 skill, int &damage,ExtraAttackOptions *opts = nullptr); + void ApplyMeleeDamageMods(uint16 skill, int &damage, Mob * defender = nullptr, ExtraAttackOptions *opts = nullptr); int ACSum(); int offense(EQEmu::skills::SkillType skill); void CalcAC() { mitigation_ac = ACSum(); } @@ -184,12 +185,12 @@ public: double GetSoftcapReturns(); int GetClassRaceACBonus(); inline int GetMitigationAC() { return mitigation_ac; } - void MeleeMitigation(Mob *attacker, int &damage, int base_damage, int offense, EQEmu::skills::SkillType, ExtraAttackOptions *opts = nullptr); - double RollD20(double offense, double mitigation); // CALL THIS FROM THE DEFENDER + void MeleeMitigation(Mob *attacker, DamageHitInfo &hit, ExtraAttackOptions *opts = nullptr); + double RollD20(int offense, int mitigation); // CALL THIS FROM THE DEFENDER bool CombatRange(Mob* other); virtual inline bool IsBerserk() { return false; } // only clients void RogueEvade(Mob *other); - void CommonOutgoingHitSuccess(Mob* defender, int &damage, int min_damage, int min_mod, EQEmu::skills::SkillType skillInUse, ExtraAttackOptions *opts = nullptr); + void CommonOutgoingHitSuccess(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *opts = nullptr); void BreakInvisibleSpells(); virtual void CancelSneakHide(); void CommonBreakInvisible(); @@ -794,7 +795,7 @@ public: uint8 GetWeaponDamageBonus(const EQEmu::ItemData* weapon, bool offhand = false); const DamageTable &GetDamageTable() const; - void ApplyDamageTable(int &damage, int offense); + void ApplyDamageTable(DamageHitInfo &hit); virtual int GetHandToHandDamage(void); bool CanThisClassDoubleAttack(void) const; @@ -817,7 +818,7 @@ public: int32 AffectMagicalDamage(int32 damage, uint16 spell_id, const bool iBuffTic, Mob* attacker); int32 ReduceAllDamage(int32 damage); - void DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int base_damage, int min_damage = 0, int32 hate_override = -1, int ReuseTime = 10, bool CheckHitChance = false, bool CanAvoid = true); + void DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int base_damage, int min_damage = 0, int32 hate_override = -1, int ReuseTime = 10); virtual void DoThrowingAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon = nullptr, const EQEmu::ItemData* AmmoItem = nullptr, uint16 weapon_damage = 0, int16 chance_mod = 0, int16 focus = 0, int ReuseTime = 0, uint32 range_id = 0, int AmmoSlot = 0, float speed = 4.0f); void DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills::SkillType skillinuse, int16 chance_mod = 0, int16 focus = 0, bool CanRiposte = false, int ReuseTime = 0); virtual void DoArcheryAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon = nullptr, const EQEmu::ItemInstance* Ammo = nullptr, uint16 weapon_damage = 0, int16 chance_mod = 0, int16 focus = 0, int ReuseTime = 0, uint32 range_id = 0, uint32 ammo_id = 0, const EQEmu::ItemData *AmmoItem = nullptr, int AmmoSlot = 0, float speed = 4.0f); @@ -1187,8 +1188,6 @@ protected: void ExecWeaponProc(const EQEmu::ItemInstance* weapon, uint16 spell_id, Mob *on, int level_override = -1); virtual float GetProcChances(float ProcBonus, uint16 hand = EQEmu::inventory::slotPrimary); virtual float GetDefensiveProcChances(float &ProcBonus, float &ProcChance, uint16 hand = EQEmu::inventory::slotPrimary, Mob *on = nullptr); - virtual float GetSpecialProcChances(uint16 hand); - virtual float GetAssassinateProcChances(uint16 ReuseTime); virtual float GetSkillProcChances(uint16 ReuseTime, uint16 hand = 0); // hand = MainCharm? uint16 GetWeaponSpeedbyHand(uint16 hand); int GetWeaponDamage(Mob *against, const EQEmu::ItemData *weapon_item); diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 929c272d9..7f6ec112d 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -131,7 +131,7 @@ int Mob::GetBaseSkillDamage(EQEmu::skills::SkillType skill, Mob *target) } void Mob::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 base_damage, int32 min_damage, - int32 hate_override, int ReuseTime, bool CheckHitChance, bool CanAvoid) + int32 hate_override, int ReuseTime) { // this really should go through the same code as normal melee damage to // pick up all the special behavior there @@ -141,15 +141,22 @@ void Mob::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 (!IsAttackAllowed(who)))) return; - int damage = 0; + DamageHitInfo my_hit; + my_hit.damage_done = 0; + my_hit.base_damage = base_damage; + my_hit.min_damage = min_damage; + my_hit.skill = skill; + + if (my_hit.base_damage == 0) + my_hit.base_damage = GetBaseSkillDamage(my_hit.skill); if (who->GetInvul() || who->GetSpecialAbility(IMMUNE_MELEE)) - damage = -5; + my_hit.damage_done = DMG_INVULNERABLE; if (who->GetSpecialAbility(IMMUNE_MELEE_EXCEPT_BANE) && skill != EQEmu::skills::SkillBackstab) - damage = -5; + my_hit.damage_done = DMG_INVULNERABLE; - uint32 hate = base_damage; + uint32 hate = my_hit.base_damage; if (hate_override > -1) hate = hate_override; @@ -169,39 +176,26 @@ void Mob::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 } } - int min_cap = base_damage * GetMeleeMinDamageMod_SE(skill) / 100; + my_hit.offense = offense(my_hit.skill); + my_hit.tohit = GetTotalToHit(my_hit.skill, 0); - auto offense = this->offense(skill); - - int hand = EQEmu::inventory::slotPrimary; // Avoid checks hand for throwing/archery exclusion, primary should + my_hit.hand = EQEmu::inventory::slotPrimary; // Avoid checks hand for throwing/archery exclusion, primary should // work for most if (skill == EQEmu::skills::SkillThrowing || skill == EQEmu::skills::SkillArchery) - hand = EQEmu::inventory::slotRange; - if (who->AvoidDamage(this, damage, hand)) { - if (damage == -3) - DoRiposte(who); - } else { - if (!CheckHitChance || who->CheckHitChance(this, skill)) { - if (base_damage > 0) // we do this weird, so we have to check it first :( - who->MeleeMitigation(this, damage, base_damage, offense, skill); - if (damage > 0) { - ApplyDamageTable(damage, offense); - CommonOutgoingHitSuccess(who, damage, min_damage, min_cap, skill); - } - } else { - damage = 0; - } - } + my_hit.hand = EQEmu::inventory::slotRange; + + DoAttack(who, my_hit); who->AddToHateList(this, hate, 0, false); - if (damage > 0 && aabonuses.SkillAttackProc[0] && aabonuses.SkillAttackProc[1] == skill && + if (my_hit.damage_done > 0 && aabonuses.SkillAttackProc[0] && aabonuses.SkillAttackProc[1] == skill && IsValidSpell(aabonuses.SkillAttackProc[2])) { float chance = aabonuses.SkillAttackProc[0] / 1000.0f; if (zone->random.Roll(chance)) SpellFinished(aabonuses.SkillAttackProc[2], who, EQEmu::CastingSlot::Item, 0, -1, spells[aabonuses.SkillAttackProc[2]].ResistDiff); } - who->Damage(this, damage, SPELL_UNKNOWN, skill, false); + + who->Damage(this, my_hit.damage_done, SPELL_UNKNOWN, skill, false); // Make sure 'this' has not killed the target and 'this' is not dead (Damage shield ect). if (!GetTarget()) @@ -212,7 +206,7 @@ void Mob::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 if (HasSkillProcs()) TrySkillProc(who, skill, ReuseTime * 1000); - if (damage > 0 && HasSkillProcSuccess()) + if (my_hit.damage_done > 0 && HasSkillProcSuccess()) TrySkillProc(who, skill, ReuseTime * 1000, true); } @@ -302,7 +296,7 @@ void Client::OPCombatAbility(const CombatAbility_Struct *ca_atk) ReuseTime = BashReuseTime - 1 - skill_reduction; ReuseTime = (ReuseTime * HasteMod) / 100; - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillBash, dmg, 0, ht, ReuseTime, true); + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillBash, dmg, 0, ht, ReuseTime); if (ReuseTime > 0) p_timers.Start(timer, ReuseTime); } @@ -311,21 +305,26 @@ void Client::OPCombatAbility(const CombatAbility_Struct *ca_atk) if (ca_atk->m_atk == 100 && ca_atk->m_skill == EQEmu::skills::SkillFrenzy) { CheckIncreaseSkill(EQEmu::skills::SkillFrenzy, GetTarget(), 10); - int AtkRounds = 3; + int AtkRounds = 1; int32 max_dmg = GetBaseSkillDamage(EQEmu::skills::SkillFrenzy, GetTarget()); DoAnim(anim2HSlashing); max_dmg = mod_frenzy_damage(max_dmg); + if (GetClass() == BERSERKER) { + int chance = GetLevel() * 2 + GetSkill(EQEmu::skills::SkillFrenzy); + if (zone->random.Roll0(450) < chance) + AtkRounds++; + if (zone->random.Roll0(450) < chance) + AtkRounds++; + } + ReuseTime = FrenzyReuseTime - 1 - skill_reduction; ReuseTime = (ReuseTime * HasteMod) / 100; - // Live parses show around 55% Triple 35% Double 10% Single, you will always get first hit. while (AtkRounds > 0) { - if (GetTarget() && (AtkRounds == 1 || zone->random.Roll(75))) { - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, 0, max_dmg, - ReuseTime, true); - } + if (GetTarget()) + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, 0, max_dmg, ReuseTime); AtkRounds--; } @@ -352,7 +351,7 @@ void Client::OPCombatAbility(const CombatAbility_Struct *ca_atk) ht = dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick, GetTarget()); ReuseTime = KickReuseTime - 1 - skill_reduction; - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillKick, dmg, 0, ht, ReuseTime, true); + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillKick, dmg, 0, ht, ReuseTime); } break; case MONK: { @@ -362,25 +361,28 @@ void Client::OPCombatAbility(const CombatAbility_Struct *ca_atk) int wuchance = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack; if (wuchance) { - if (wuchance >= 100 || zone->random.Roll(wuchance)) { - const int MonkSPA[5] = {EQEmu::skills::SkillFlyingKick, EQEmu::skills::SkillDragonPunch, - EQEmu::skills::SkillEagleStrike, EQEmu::skills::SkillTigerClaw, - EQEmu::skills::SkillRoundKick}; - int extra = 1; - // always 1/4 of the double attack chance, 25% at rank 5 (100/4) - if (zone->random.Roll(wuchance / 4)) + const int MonkSPA[5] = {EQEmu::skills::SkillFlyingKick, EQEmu::skills::SkillDragonPunch, + EQEmu::skills::SkillEagleStrike, EQEmu::skills::SkillTigerClaw, + EQEmu::skills::SkillRoundKick}; + int extra = 0; + // always 1/4 of the double attack chance, 25% at rank 5 (100/4) + while (wuchance > 0) { + if (zone->random.Roll(wuchance)) extra++; - // They didn't add a string ID for this. - std::string msg = StringFormat( - "The spirit of Master Wu fills you! You gain %d additional attack(s).", extra); - // live uses 400 here -- not sure if it's the best for all clients though - SendColoredText(400, msg); - auto classic = RuleB(Combat, ClassicMasterWu); - while (extra) { - MonkSpecialAttack(GetTarget(), - classic ? MonkSPA[zone->random.Int(0, 4)] : ca_atk->m_skill); - extra--; - } + else + break; + wuchance /= 4; + } + // They didn't add a string ID for this. + std::string msg = StringFormat( + "The spirit of Master Wu fills you! You gain %d additional attack(s).", extra); + // live uses 400 here -- not sure if it's the best for all clients though + SendColoredText(400, msg); + auto classic = RuleB(Combat, ClassicMasterWu); + while (extra) { + MonkSpecialAttack(GetTarget(), + classic ? MonkSPA[zone->random.Int(0, 4)] : ca_atk->m_skill); + extra--; } } @@ -476,11 +478,11 @@ int Mob::MonkSpecialAttack(Mob *other, uint8 unchecked_type) if (IsClient()) { if (GetWeaponDamage(other, CastToClient()->GetInv().GetItem(itemslot)) <= 0) { - max_dmg = -5; + max_dmg = DMG_INVULNERABLE; } } else { if (GetWeaponDamage(other, (const EQEmu::ItemData *)nullptr) <= 0) { - max_dmg = -5; + max_dmg = DMG_INVULNERABLE; } } @@ -491,7 +493,7 @@ int Mob::MonkSpecialAttack(Mob *other, uint8 unchecked_type) // This can potentially stack with changes to kick damage ht = ndamage = mod_monk_special_damage(ndamage, skill_type); - DoSpecialAttackDamage(other, skill_type, max_dmg, min_dmg, ht, reuse, true); + DoSpecialAttackDamage(other, skill_type, max_dmg, min_dmg, ht, reuse); return reuse; } @@ -532,6 +534,7 @@ void Mob::TryBackstab(Mob *other, int ReuseTime) { RogueBackstab(other,false,ReuseTime); if (level > 54) { + // TODO: 55-59 doesn't appear to match just checking double attack, 60+ does though if(IsClient() && CastToClient()->CheckDoubleAttack()) { if(other->GetHP() > 0) @@ -583,7 +586,7 @@ void Mob::RogueBackstab(Mob* other, bool min_damage, int ReuseTime) int base_damage = GetBaseSkillDamage(EQEmu::skills::SkillBackstab, other); hate = base_damage; - DoSpecialAttackDamage(other, EQEmu::skills::SkillBackstab, base_damage, 0, hate, ReuseTime, true, false); + DoSpecialAttackDamage(other, EQEmu::skills::SkillBackstab, base_damage, 0, hate, ReuseTime); DoAnim(anim1HPiercing); } @@ -732,23 +735,20 @@ void Client::RangedAttack(Mob* other, bool CanDoubleAttack) { CommonBreakInvisibleFromCombat(); } -void Mob::DoArcheryAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon, const EQEmu::ItemInstance* Ammo, uint16 weapon_damage, int16 chance_mod, int16 focus, int ReuseTime, - uint32 range_id, uint32 ammo_id, const EQEmu::ItemData *AmmoItem, int AmmoSlot, float speed) { +void Mob::DoArcheryAttackDmg(Mob *other, const EQEmu::ItemInstance *RangeWeapon, const EQEmu::ItemInstance *Ammo, + uint16 weapon_damage, int16 chance_mod, int16 focus, int ReuseTime, uint32 range_id, + uint32 ammo_id, const EQEmu::ItemData *AmmoItem, int AmmoSlot, float speed) +{ if ((other == nullptr || - ((IsClient() && CastToClient()->dead) || - (other->IsClient() && other->CastToClient()->dead)) || - HasDied() || - (!IsAttackAllowed(other)) || - (other->GetInvul() || - other->GetSpecialAbility(IMMUNE_MELEE)))) - { + ((IsClient() && CastToClient()->dead) || (other->IsClient() && other->CastToClient()->dead)) || + HasDied() || (!IsAttackAllowed(other)) || (other->GetInvul() || other->GetSpecialAbility(IMMUNE_MELEE)))) { return; } - const EQEmu::ItemInstance* _RangeWeapon = nullptr; - const EQEmu::ItemInstance* _Ammo = nullptr; - const EQEmu::ItemData* ammo_lost = nullptr; + const EQEmu::ItemInstance *_RangeWeapon = nullptr; + const EQEmu::ItemInstance *_Ammo = nullptr; + const EQEmu::ItemData *ammo_lost = nullptr; /* If LaunchProjectile is false this function will do archery damage on target, @@ -756,32 +756,24 @@ void Mob::DoArcheryAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon this function is then run again to do the damage portion */ bool LaunchProjectile = false; - bool ProjectileImpact = false; - bool ProjectileMiss = false; - if (RuleB(Combat, ProjectileDmgOnImpact)){ - - if (AmmoItem) + if (RuleB(Combat, ProjectileDmgOnImpact)) { + if (AmmoItem) { // won't be null when we are firing the arrow LaunchProjectile = true; - else{ + } else { /* Item sync check on projectile landing. Weapon damage is already calculated so this only affects procs! Ammo proc check will use database to find proc if you used up your last ammo. - If you change range item mid projectile flight, you loose your chance to proc from bow (Deal with it!). + If you change range item mid projectile flight, you loose your chance to proc from bow (Deal + with it!). */ - if (!RangeWeapon && !Ammo && range_id && ammo_id){ - - ProjectileImpact = true; - - if (weapon_damage == 0) - ProjectileMiss = true; //This indicates that MISS was originally calculated. - - if (IsClient()){ - + if (!RangeWeapon && !Ammo && range_id && ammo_id) { + if (IsClient()) { _RangeWeapon = CastToClient()->m_inv[EQEmu::inventory::slotRange]; - if (_RangeWeapon && _RangeWeapon->GetItem() && _RangeWeapon->GetItem()->ID == range_id) + if (_RangeWeapon && _RangeWeapon->GetItem() && + _RangeWeapon->GetItem()->ID == range_id) RangeWeapon = _RangeWeapon; _Ammo = CastToClient()->m_inv[AmmoSlot]; @@ -792,103 +784,96 @@ void Mob::DoArcheryAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon } } } - } - else if (AmmoItem) + } else if (AmmoItem) { SendItemAnimation(other, AmmoItem, EQEmu::skills::SkillArchery); - - if (ProjectileMiss || (!ProjectileImpact && !other->CheckHitChance(this, EQEmu::skills::SkillArchery, chance_mod))) { - Log.Out(Logs::Detail, Logs::Combat, "Ranged attack missed %s.", other->GetName()); - - if (LaunchProjectile){ - TryProjectileAttack(other, AmmoItem, EQEmu::skills::SkillArchery, 0, RangeWeapon, Ammo, AmmoSlot, speed); - return; - } - else - other->Damage(this, 0, SPELL_UNKNOWN, EQEmu::skills::SkillArchery); - } else { - Log.Out(Logs::Detail, Logs::Combat, "Ranged attack hit %s.", other->GetName()); - - uint32 hate = 0; - int32 TotalDmg = 0; - int16 WDmg = 0; - int16 ADmg = 0; - if (!weapon_damage){ - WDmg = GetWeaponDamage(other, RangeWeapon); - ADmg = GetWeaponDamage(other, Ammo); - } - else - WDmg = weapon_damage; - - if (LaunchProjectile){//1: Shoot the Projectile once we calculate weapon damage. - TryProjectileAttack(other, AmmoItem, EQEmu::skills::SkillArchery, (WDmg + ADmg), RangeWeapon, Ammo, AmmoSlot, speed); - return; - } - - // unsure when this should happen - if (focus) //From FcBaseEffects - WDmg += WDmg * focus / 100; - - if((WDmg > 0) || (ADmg > 0)) { - if(WDmg < 0) - WDmg = 0; - if(ADmg < 0) - ADmg = 0; - uint32 MaxDmg = WDmg + ADmg; - hate = ((WDmg+ADmg)); - - if (RuleB(Combat, ProjectileDmgOnImpact)) - Log.Out(Logs::Detail, Logs::Combat, "Bow and Arrow DMG %d, Max Damage %d.", WDmg, MaxDmg); - else - Log.Out(Logs::Detail, Logs::Combat, "Bow DMG %d, Arrow DMG %d, Max Damage %d.", WDmg, ADmg, MaxDmg); - - if (MaxDmg == 0) - MaxDmg = 1; - - int min_cap = MaxDmg * GetMeleeMinDamageMod_SE(EQEmu::skills::SkillArchery) / 100; - auto offense = this->offense(EQEmu::skills::SkillArchery); - - other->AvoidDamage(this, TotalDmg, EQEmu::inventory::slotRange); - - other->MeleeMitigation(this, TotalDmg, MaxDmg, offense, EQEmu::skills::SkillArchery); - if(TotalDmg > 0){ - if (IsClient()) - ApplyDamageTable(TotalDmg, offense); - CommonOutgoingHitSuccess(other, TotalDmg, 0, min_cap, EQEmu::skills::SkillArchery); - //TotalDmg = mod_archery_damage(TotalDmg, dobonus, RangeWeapon); - } - } - else - TotalDmg = -5; - - if (IsClient() && !CastToClient()->GetFeigned()) - other->AddToHateList(this, hate, 0, false); - - other->Damage(this, TotalDmg, SPELL_UNKNOWN, EQEmu::skills::SkillArchery); - - //Skill Proc Success - if (TotalDmg > 0 && HasSkillProcSuccess() && other && !other->HasDied()){ - if (ReuseTime) - TrySkillProc(other, EQEmu::skills::SkillArchery, ReuseTime); - else - TrySkillProc(other, EQEmu::skills::SkillArchery, 0, true, EQEmu::inventory::slotRange); - } } + Log.Out(Logs::Detail, Logs::Combat, "Ranged attack hit %s.", other->GetName()); + + uint32 hate = 0; + int TotalDmg = 0; + int WDmg = 0; + int ADmg = 0; + if (!weapon_damage) { + WDmg = GetWeaponDamage(other, RangeWeapon); + ADmg = GetWeaponDamage(other, Ammo); + } else { + WDmg = weapon_damage; + } + + if (LaunchProjectile) { // 1: Shoot the Projectile once we calculate weapon damage. + TryProjectileAttack(other, AmmoItem, EQEmu::skills::SkillArchery, (WDmg + ADmg), RangeWeapon, + Ammo, AmmoSlot, speed); + return; + } + + // unsure when this should happen + if (focus) // From FcBaseEffects + WDmg += WDmg * focus / 100; + + if (WDmg > 0 || ADmg > 0) { + if (WDmg < 0) + WDmg = 0; + if (ADmg < 0) + ADmg = 0; + int MaxDmg = WDmg + ADmg; + hate = ((WDmg + ADmg)); + + if (RuleB(Combat, ProjectileDmgOnImpact)) + Log.Out(Logs::Detail, Logs::Combat, "Bow and Arrow DMG %d, Max Damage %d.", WDmg, + MaxDmg); + else + Log.Out(Logs::Detail, Logs::Combat, "Bow DMG %d, Arrow DMG %d, Max Damage %d.", WDmg, + ADmg, MaxDmg); + + if (MaxDmg == 0) + MaxDmg = 1; + + DamageHitInfo my_hit; + my_hit.base_damage = MaxDmg; + my_hit.min_damage = 0; + my_hit.damage_done = 0; + + my_hit.skill = EQEmu::skills::SkillArchery; + my_hit.offense = offense(my_hit.skill); + my_hit.tohit = GetTotalToHit(my_hit.skill, chance_mod); + my_hit.hand = EQEmu::inventory::slotRange; + + DoAttack(other, my_hit); + TotalDmg = my_hit.damage_done; + } else { + TotalDmg = DMG_INVULNERABLE; + } + + if (IsClient() && !CastToClient()->GetFeigned()) + other->AddToHateList(this, hate, 0, false); + + other->Damage(this, TotalDmg, SPELL_UNKNOWN, EQEmu::skills::SkillArchery); + + // Skill Proc Success + if (TotalDmg > 0 && HasSkillProcSuccess() && other && !other->HasDied()) { + if (ReuseTime) + TrySkillProc(other, EQEmu::skills::SkillArchery, ReuseTime); + else + TrySkillProc(other, EQEmu::skills::SkillArchery, 0, true, EQEmu::inventory::slotRange); + } + // end of old fuck + if (LaunchProjectile) - return;//Shouldn't reach this point durring initial launch phase, but just in case. + return; // Shouldn't reach this point durring initial launch phase, but just in case. - //Weapon Proc - if(RangeWeapon && other && !other->HasDied()) + // Weapon Proc + if (RangeWeapon && other && !other->HasDied()) TryWeaponProc(RangeWeapon, other, EQEmu::inventory::slotRange); - //Ammo Proc + // Ammo Proc if (ammo_lost) TryWeaponProc(nullptr, ammo_lost, other, EQEmu::inventory::slotRange); - else if(Ammo && other && !other->HasDied()) + else if (Ammo && other && !other->HasDied()) TryWeaponProc(Ammo, other, EQEmu::inventory::slotRange); - //Skill Proc - if (HasSkillProcs() && other && !other->HasDied()){ + // Skill Proc + if (HasSkillProcs() && other && !other->HasDied()) { if (ReuseTime) TrySkillProc(other, EQEmu::skills::SkillArchery, ReuseTime); else @@ -896,16 +881,18 @@ void Mob::DoArcheryAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon } } -bool Mob::TryProjectileAttack(Mob* other, const EQEmu::ItemData *item, EQEmu::skills::SkillType skillInUse, uint16 weapon_dmg, const EQEmu::ItemInstance* RangeWeapon, const EQEmu::ItemInstance* Ammo, int AmmoSlot, float speed){ - +bool Mob::TryProjectileAttack(Mob *other, const EQEmu::ItemData *item, EQEmu::skills::SkillType skillInUse, + uint16 weapon_dmg, const EQEmu::ItemInstance *RangeWeapon, + const EQEmu::ItemInstance *Ammo, int AmmoSlot, float speed) +{ if (!other) return false; int slot = -1; - //Make sure there is an avialable slot. + // Make sure there is an avialable slot. for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { - if (ProjectileAtk[i].target_id == 0){ + if (ProjectileAtk[i].target_id == 0) { slot = i; break; } @@ -917,10 +904,11 @@ bool Mob::TryProjectileAttack(Mob* other, const EQEmu::ItemData *item, EQEmu::sk float speed_mod = speed * 0.45f; float distance = other->CalculateDistance(GetX(), GetY(), GetZ()); - float hit = 60.0f + (distance / speed_mod); //Calcuation: 60 = Animation Lag, 1.8 = Speed modifier for speed of (4) + float hit = + 60.0f + (distance / speed_mod); // Calcuation: 60 = Animation Lag, 1.8 = Speed modifier for speed of (4) ProjectileAtk[slot].increment = 1; - ProjectileAtk[slot].hit_increment = static_cast(hit); //This projected hit time if target does NOT MOVE + ProjectileAtk[slot].hit_increment = static_cast(hit); // This projected hit time if target does NOT MOVE ProjectileAtk[slot].target_id = other->GetID(); ProjectileAtk[slot].wpn_dmg = weapon_dmg; ProjectileAtk[slot].origin_x = GetX(); @@ -939,64 +927,74 @@ bool Mob::TryProjectileAttack(Mob* other, const EQEmu::ItemData *item, EQEmu::sk SetProjectileAttack(true); - if(item) + if (item) SendItemAnimation(other, item, skillInUse, speed); - //else if (IsNPC()) - //ProjectileAnimation(other, 0,false,speed,0,0,0,CastToNPC()->GetAmmoIDfile(),skillInUse); + // else if (IsNPC()) + // ProjectileAnimation(other, 0,false,speed,0,0,0,CastToNPC()->GetAmmoIDfile(),skillInUse); return true; } - void Mob::ProjectileAttack() { if (!HasProjectileAttack()) - return;; + return; + ; - Mob* target = nullptr; + Mob *target = nullptr; bool disable = true; for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { - - if (ProjectileAtk[i].increment == 0){ + if (ProjectileAtk[i].increment == 0) continue; - } disable = false; - Mob* target = entity_list.GetMobID(ProjectileAtk[i].target_id); + Mob *target = entity_list.GetMobID(ProjectileAtk[i].target_id); - if (target && target->IsMoving()){ //Only recalculate hit increment if target moving - //Due to frequency that we need to check increment the targets position variables may not be updated even if moving. Do a simple check before calculating distance. - if (ProjectileAtk[i].tlast_x != target->GetX() || ProjectileAtk[i].tlast_y != target->GetY()){ + if (target && target->IsMoving()) { // Only recalculate hit increment if target moving + // Due to frequency that we need to check increment the targets position variables may not be + // updated even if moving. Do a simple check before calculating distance. + if (ProjectileAtk[i].tlast_x != target->GetX() || ProjectileAtk[i].tlast_y != target->GetY()) { ProjectileAtk[i].tlast_x = target->GetX(); ProjectileAtk[i].tlast_y = target->GetY(); - float distance = target->CalculateDistance(ProjectileAtk[i].origin_x, ProjectileAtk[i].origin_y, ProjectileAtk[i].origin_z); - float hit = 60.0f + (distance / ProjectileAtk[i].speed_mod); //Calcuation: 60 = Animation Lag, 1.8 = Speed modifier for speed of (4) + float distance = target->CalculateDistance( + ProjectileAtk[i].origin_x, ProjectileAtk[i].origin_y, ProjectileAtk[i].origin_z); + float hit = 60.0f + (distance / ProjectileAtk[i].speed_mod); // Calcuation: 60 = + // Animation Lag, 1.8 = + // Speed modifier for speed + // of (4) ProjectileAtk[i].hit_increment = static_cast(hit); } } - if (ProjectileAtk[i].hit_increment <= ProjectileAtk[i].increment){ - - if (target){ - - if (IsNPC()){ - if (ProjectileAtk[i].skill == EQEmu::skills::SkillConjuration){ + // We hit I guess? + if (ProjectileAtk[i].hit_increment <= ProjectileAtk[i].increment) { + if (target) { + if (IsNPC()) { + if (ProjectileAtk[i].skill == EQEmu::skills::SkillConjuration) { if (IsValidSpell(ProjectileAtk[i].wpn_dmg)) - SpellOnTarget(ProjectileAtk[i].wpn_dmg, target, false, true, spells[ProjectileAtk[i].wpn_dmg].ResistDiff, true); + SpellOnTarget(ProjectileAtk[i].wpn_dmg, target, false, true, + spells[ProjectileAtk[i].wpn_dmg].ResistDiff, + true); + } else { + CastToNPC()->DoRangedAttackDmg( + target, false, ProjectileAtk[i].wpn_dmg, 0, + static_cast(ProjectileAtk[i].skill)); } - else - CastToNPC()->DoRangedAttackDmg(target, false, ProjectileAtk[i].wpn_dmg, 0, static_cast(ProjectileAtk[i].skill)); - } - - else - { + } else { if (ProjectileAtk[i].skill == EQEmu::skills::SkillArchery) - DoArcheryAttackDmg(target, nullptr, nullptr,ProjectileAtk[i].wpn_dmg,0,0,0,ProjectileAtk[i].ranged_id, ProjectileAtk[i].ammo_id, nullptr, ProjectileAtk[i].ammo_slot); + DoArcheryAttackDmg(target, nullptr, nullptr, ProjectileAtk[i].wpn_dmg, + 0, 0, 0, ProjectileAtk[i].ranged_id, + ProjectileAtk[i].ammo_id, nullptr, + ProjectileAtk[i].ammo_slot); else if (ProjectileAtk[i].skill == EQEmu::skills::SkillThrowing) - DoThrowingAttackDmg(target, nullptr, nullptr,ProjectileAtk[i].wpn_dmg,0,0,0, ProjectileAtk[i].ranged_id, ProjectileAtk[i].ammo_slot); - else if (ProjectileAtk[i].skill == EQEmu::skills::SkillConjuration && IsValidSpell(ProjectileAtk[i].wpn_dmg)) - SpellOnTarget(ProjectileAtk[i].wpn_dmg, target, false, true, spells[ProjectileAtk[i].wpn_dmg].ResistDiff, true); + DoThrowingAttackDmg(target, nullptr, nullptr, ProjectileAtk[i].wpn_dmg, + 0, 0, 0, ProjectileAtk[i].ranged_id, + ProjectileAtk[i].ammo_slot); + else if (ProjectileAtk[i].skill == EQEmu::skills::SkillConjuration && + IsValidSpell(ProjectileAtk[i].wpn_dmg)) + SpellOnTarget(ProjectileAtk[i].wpn_dmg, target, false, true, + spells[ProjectileAtk[i].wpn_dmg].ResistDiff, true); } } @@ -1013,9 +1011,7 @@ void Mob::ProjectileAttack() ProjectileAtk[i].ammo_slot = 0; ProjectileAtk[i].skill = 0; ProjectileAtk[i].speed_mod = 0.0f; - } - - else { + } else { ProjectileAtk[i].increment++; } } @@ -1158,39 +1154,38 @@ void NPC::DoRangedAttackDmg(Mob* other, bool Launch, int16 damage_mod, int16 cha if (!chance_mod) chance_mod = GetSpecialAbilityParam(SPECATK_RANGED_ATK, 2); - if (!other->CheckHitChance(this, skillInUse, chance_mod)) - { - other->Damage(this, 0, SPELL_UNKNOWN, skillInUse); + int TotalDmg = 0; + int MaxDmg = GetBaseDamage() * RuleR(Combat, ArcheryNPCMultiplier); // should add a field to npc_types + int MinDmg = GetMinDamage() * RuleR(Combat, ArcheryNPCMultiplier); + + if (!damage_mod) + damage_mod = GetSpecialAbilityParam(SPECATK_RANGED_ATK, 3);//Damage modifier + + DamageHitInfo my_hit; + my_hit.base_damage = MaxDmg; + my_hit.min_damage = MinDmg; + my_hit.damage_done = 0; + + my_hit.skill = skill; + my_hit.offense = offense(my_hit.skill); + my_hit.tohit = GetTotalToHit(my_hit.skill, chance_mod); + my_hit.hand = EQEmu::inventory::slotRange; + + DoAttack(other, my_hit); + + TotalDmg = my_hit.damage_done; + + if (TotalDmg > 0) { + TotalDmg += TotalDmg * damage_mod / 100; + other->AddToHateList(this, TotalDmg, 0, false); + } else { + other->AddToHateList(this, 0, 0, false); } - else - { - int TotalDmg = 0; - int MaxDmg = GetBaseDamage() * RuleR(Combat, ArcheryNPCMultiplier); // should add a field to npc_types - int MinDmg = GetMinDamage() * RuleR(Combat, ArcheryNPCMultiplier); - if (!damage_mod) - damage_mod = GetSpecialAbilityParam(SPECATK_RANGED_ATK, 3);//Damage modifier + other->Damage(this, TotalDmg, SPELL_UNKNOWN, skillInUse); - TotalDmg += TotalDmg * damage_mod / 100; - - other->AvoidDamage(this, TotalDmg, EQEmu::inventory::slotRange); - other->MeleeMitigation(this, TotalDmg, MaxDmg, offense(skillInUse), skillInUse); - - if (TotalDmg > 0) - CommonOutgoingHitSuccess(other, TotalDmg, MinDmg, 0, skillInUse); - else if (TotalDmg < -4) - TotalDmg = -5; - - if (TotalDmg > 0) - other->AddToHateList(this, TotalDmg, 0, false); - else - other->AddToHateList(this, 0, 0, false); - - other->Damage(this, TotalDmg, SPELL_UNKNOWN, skillInUse); - - if (TotalDmg > 0 && HasSkillProcSuccess() && !other->HasDied()) - TrySkillProc(other, skillInUse, 0, true, EQEmu::inventory::slotRange); - } + if (TotalDmg > 0 && HasSkillProcSuccess() && !other->HasDied()) + TrySkillProc(other, skillInUse, 0, true, EQEmu::inventory::slotRange); //try proc on hits and misses if(other && !other->HasDied()) @@ -1302,23 +1297,13 @@ void Mob::DoThrowingAttackDmg(Mob *other, const EQEmu::ItemInstance *RangeWeapon this function is then run again to do the damage portion */ bool LaunchProjectile = false; - bool ProjectileImpact = false; - bool ProjectileMiss = false; if (RuleB(Combat, ProjectileDmgOnImpact)) { - - if (AmmoItem) + if (AmmoItem) { LaunchProjectile = true; - else { + } else { if (!RangeWeapon && range_id) { - - ProjectileImpact = true; - - if (weapon_damage == 0) - ProjectileMiss = true; // This indicates that MISS was originally calculated. - if (IsClient()) { - _RangeWeapon = CastToClient()->m_inv[AmmoSlot]; if (_RangeWeapon && _RangeWeapon->GetItem() && _RangeWeapon->GetItem()->ID != range_id) @@ -1328,81 +1313,66 @@ void Mob::DoThrowingAttackDmg(Mob *other, const EQEmu::ItemInstance *RangeWeapon } } } - } else if (AmmoItem) + } else if (AmmoItem) { SendItemAnimation(other, AmmoItem, EQEmu::skills::SkillThrowing); - - if (ProjectileMiss || - (!ProjectileImpact && !other->CheckHitChance(this, EQEmu::skills::SkillThrowing, chance_mod))) { - Log.Out(Logs::Detail, Logs::Combat, "Ranged attack missed %s.", other->GetName()); - if (LaunchProjectile) { - TryProjectileAttack(other, AmmoItem, EQEmu::skills::SkillThrowing, 0, RangeWeapon, nullptr, - AmmoSlot, speed); - return; - } else - other->Damage(this, 0, SPELL_UNKNOWN, EQEmu::skills::SkillThrowing); - } else { - Log.Out(Logs::Detail, Logs::Combat, "Throwing attack hit %s.", other->GetName()); - - int16 WDmg = 0; - - if (!weapon_damage) { - if (IsClient() && RangeWeapon) - WDmg = GetWeaponDamage(other, RangeWeapon); - else if (AmmoItem) - WDmg = GetWeaponDamage(other, AmmoItem); - - if (LaunchProjectile) { - TryProjectileAttack(other, AmmoItem, EQEmu::skills::SkillThrowing, WDmg, RangeWeapon, - nullptr, AmmoSlot, speed); - return; - } - } else - WDmg = weapon_damage; - - if (focus) // From FcBaseEffects - WDmg += WDmg * focus / 100; - - int32 TotalDmg = 0; - - uint32 Assassinate_Dmg = 0; - if (GetClass() == ROGUE && (BehindMob(other, GetX(), GetY()))) - Assassinate_Dmg = - TryAssassinate(other, EQEmu::skills::SkillThrowing, ranged_timer.GetDuration()); - - if (WDmg > 0) { - int min_cap = WDmg * GetMeleeMinDamageMod_SE(EQEmu::skills::SkillThrowing) / 100; - auto offense = this->offense(EQEmu::skills::SkillThrowing); - if (Assassinate_Dmg) { - TotalDmg = Assassinate_Dmg; - } - - Log.Out(Logs::Detail, Logs::Combat, "Item DMG %d. Hit for damage %d", WDmg, TotalDmg); - if (!Assassinate_Dmg) - other->AvoidDamage(this, TotalDmg, EQEmu::inventory::slotRange); - - other->MeleeMitigation(this, TotalDmg, WDmg, offense, EQEmu::skills::SkillThrowing); - if (TotalDmg > 0) - if (IsClient()) - ApplyDamageTable(TotalDmg, offense); - CommonOutgoingHitSuccess(other, TotalDmg, 0, min_cap, EQEmu::skills::SkillThrowing); - } - - else - TotalDmg = -5; - - if (IsClient() && !CastToClient()->GetFeigned()) - other->AddToHateList(this, 2 * WDmg, 0, false); - - other->Damage(this, TotalDmg, SPELL_UNKNOWN, EQEmu::skills::SkillThrowing); - - if (TotalDmg > 0 && HasSkillProcSuccess() && other && !other->HasDied()) { - if (ReuseTime) - TrySkillProc(other, EQEmu::skills::SkillThrowing, ReuseTime); - else - TrySkillProc(other, EQEmu::skills::SkillThrowing, 0, true, EQEmu::inventory::slotRange); - } } + Log.Out(Logs::Detail, Logs::Combat, "Throwing attack hit %s.", other->GetName()); + + int WDmg = 0; + + if (!weapon_damage) { + if (IsClient() && RangeWeapon) + WDmg = GetWeaponDamage(other, RangeWeapon); + else if (AmmoItem) + WDmg = GetWeaponDamage(other, AmmoItem); + + if (LaunchProjectile) { + TryProjectileAttack(other, AmmoItem, EQEmu::skills::SkillThrowing, WDmg, RangeWeapon, + nullptr, AmmoSlot, speed); + return; + } + } else { + WDmg = weapon_damage; + } + + if (focus) // From FcBaseEffects + WDmg += WDmg * focus / 100; + + int TotalDmg = 0; + + if (WDmg > 0) { + DamageHitInfo my_hit; + my_hit.base_damage = WDmg; + my_hit.min_damage = 0; + my_hit.damage_done = 0; + + my_hit.skill = EQEmu::skills::SkillThrowing; + my_hit.offense = offense(my_hit.skill); + my_hit.tohit = GetTotalToHit(my_hit.skill, chance_mod); + my_hit.hand = EQEmu::inventory::slotRange; + + DoAttack(other, my_hit); + TotalDmg = my_hit.damage_done; + + Log.Out(Logs::Detail, Logs::Combat, "Item DMG %d. Hit for damage %d", WDmg, TotalDmg); + } else { + TotalDmg = DMG_INVULNERABLE; + } + + if (IsClient() && !CastToClient()->GetFeigned()) + other->AddToHateList(this, WDmg, 0, false); + + other->Damage(this, TotalDmg, SPELL_UNKNOWN, EQEmu::skills::SkillThrowing); + + if (TotalDmg > 0 && HasSkillProcSuccess() && other && !other->HasDied()) { + if (ReuseTime) + TrySkillProc(other, EQEmu::skills::SkillThrowing, ReuseTime); + else + TrySkillProc(other, EQEmu::skills::SkillThrowing, 0, true, EQEmu::inventory::slotRange); + } + // end old shit + if (LaunchProjectile) return; @@ -1609,15 +1579,10 @@ void NPC::DoClassAttacks(Mob *target) { if(level >= RuleI(Combat, NPCBashKickLevel)){ if(zone->random.Roll(75)) { //tested on live, warrior mobs both kick and bash, kick about 75% of the time, casting doesn't seem to make a difference. DoAnim(animKick); - int32 dmg = 0; + int32 dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick); - if (GetWeaponDamage(target, (const EQEmu::ItemData*)nullptr) <= 0){ - dmg = -5; - } - else{ - if (target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) - dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick); - } + if (GetWeaponDamage(target, (const EQEmu::ItemData*)nullptr) <= 0) + dmg = DMG_INVULNERABLE; reuse = (KickReuseTime + 3) * 1000; DoSpecialAttackDamage(target, EQEmu::skills::SkillKick, dmg, GetMinDamage(), -1, reuse); @@ -1625,15 +1590,10 @@ void NPC::DoClassAttacks(Mob *target) { } else { DoAnim(animTailRake); - int32 dmg = 0; + int32 dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash); - if (GetWeaponDamage(target, (const EQEmu::ItemData*)nullptr) <= 0){ - dmg = -5; - } - else{ - if (target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) - dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash); - } + if (GetWeaponDamage(target, (const EQEmu::ItemData*)nullptr) <= 0) + dmg = DMG_INVULNERABLE; reuse = (BashReuseTime + 3) * 1000; DoSpecialAttackDamage(target, EQEmu::skills::SkillBash, dmg, GetMinDamage(), -1, reuse); @@ -1643,14 +1603,21 @@ void NPC::DoClassAttacks(Mob *target) { break; } case BERSERKER: case BERSERKERGM:{ - int AtkRounds = 3; + int AtkRounds = 1; int32 max_dmg = GetBaseSkillDamage(EQEmu::skills::SkillFrenzy); DoAnim(anim2HSlashing); - while(AtkRounds > 0) { - if (GetTarget() && (AtkRounds == 1 || zone->random.Roll(75))) { - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, GetMinDamage(), -1, reuse, true); - } + if (GetClass() == BERSERKER) { + int chance = GetLevel() * 2 + GetSkill(EQEmu::skills::SkillFrenzy); + if (zone->random.Roll0(450) < chance) + AtkRounds++; + if (zone->random.Roll0(450) < chance) + AtkRounds++; + } + + while (AtkRounds > 0) { + if (GetTarget()) + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, GetMinDamage(), -1, reuse); AtkRounds--; } @@ -1662,15 +1629,10 @@ void NPC::DoClassAttacks(Mob *target) { //kick if(level >= RuleI(Combat, NPCBashKickLevel)){ DoAnim(animKick); - int32 dmg = 0; + int32 dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick); - if (GetWeaponDamage(target, (const EQEmu::ItemData*)nullptr) <= 0){ - dmg = -5; - } - else{ - if (target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) - dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick); - } + if (GetWeaponDamage(target, (const EQEmu::ItemData*)nullptr) <= 0) + dmg = DMG_INVULNERABLE; reuse = (KickReuseTime + 3) * 1000; DoSpecialAttackDamage(target, EQEmu::skills::SkillKick, dmg, GetMinDamage(), -1, reuse); @@ -1683,15 +1645,10 @@ void NPC::DoClassAttacks(Mob *target) { case PALADIN: case PALADINGM:{ if(level >= RuleI(Combat, NPCBashKickLevel)){ DoAnim(animTailRake); - int32 dmg = 0; + int32 dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash); - if (GetWeaponDamage(target, (const EQEmu::ItemData*)nullptr) <= 0){ - dmg = -5; - } - else{ - if (target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) - dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash); - } + if (GetWeaponDamage(target, (const EQEmu::ItemData*)nullptr) <= 0) + dmg = DMG_INVULNERABLE; reuse = (BashReuseTime + 3) * 1000; DoSpecialAttackDamage(target, EQEmu::skills::SkillBash, dmg, GetMinDamage(), -1, reuse); @@ -1728,8 +1685,6 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) int ReuseTime = 0; float HasteMod = GetHaste() * 0.01f; - int32 dmg = 0; - uint16 skill_to_use = -1; if (skill == -1){ @@ -1784,21 +1739,14 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) if(skill_to_use == -1) return; + int dmg = GetBaseSkillDamage(static_cast(skill_to_use), GetTarget()); + if (skill_to_use == EQEmu::skills::SkillBash) { if (ca_target!=this) { DoAnim(animTailRake); - if (GetWeaponDamage(ca_target, GetInv().GetItem(EQEmu::inventory::slotSecondary)) <= 0 && GetWeaponDamage(ca_target, GetInv().GetItem(EQEmu::inventory::slotShoulders)) <= 0){ - dmg = -5; - } - else{ - if (!ca_target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) { - dmg = 0; - } - else{ - dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash, ca_target); - } - } + if (GetWeaponDamage(ca_target, GetInv().GetItem(EQEmu::inventory::slotSecondary)) <= 0 && GetWeaponDamage(ca_target, GetInv().GetItem(EQEmu::inventory::slotShoulders)) <= 0) + dmg = DMG_INVULNERABLE; ReuseTime = (BashReuseTime - 1) / HasteMod; @@ -1811,20 +1759,25 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) return; } - if (skill_to_use == EQEmu::skills::SkillFrenzy){ + if (skill_to_use == EQEmu::skills::SkillFrenzy) { CheckIncreaseSkill(EQEmu::skills::SkillFrenzy, GetTarget(), 10); - int AtkRounds = 3; - int32 max_dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash, GetTarget()); + int AtkRounds = 1; DoAnim(anim2HSlashing); ReuseTime = (FrenzyReuseTime - 1) / HasteMod; - //Live parses show around 55% Triple 35% Double 10% Single, you will always get first hit. - while(AtkRounds > 0) { + // bards can do riposte frenzy for some reason + if (!IsRiposte && GetClass() == BERSERKER) { + int chance = GetLevel() * 2 + GetSkill(EQEmu::skills::SkillFrenzy); + if (zone->random.Roll0(450) < chance) + AtkRounds++; + if (zone->random.Roll0(450) < chance) + AtkRounds++; + } - if (GetTarget() && (AtkRounds == 1 || zone->random.Roll(75))) { - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, 0, max_dmg, ReuseTime, true); - } + while(AtkRounds > 0) { + if (GetTarget()) + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, dmg, 0, dmg, ReuseTime); AtkRounds--; } @@ -1838,17 +1791,8 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) if(ca_target!=this){ DoAnim(animKick); - if (GetWeaponDamage(ca_target, GetInv().GetItem(EQEmu::inventory::slotFeet)) <= 0){ - dmg = -5; - } - else{ - if (!ca_target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) { - dmg = 0; - } - else{ - dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick, ca_target); - } - } + if (GetWeaponDamage(ca_target, GetInv().GetItem(EQEmu::inventory::slotFeet)) <= 0) + dmg = DMG_INVULNERABLE; ReuseTime = KickReuseTime-1; @@ -1866,19 +1810,28 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) //Live AA - Technique of Master Wu int wuchance = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack; if (wuchance) { - if (wuchance >= 100 || zone->random.Roll(wuchance)) { - int MonkSPA[5] = { EQEmu::skills::SkillFlyingKick, EQEmu::skills::SkillDragonPunch, EQEmu::skills::SkillEagleStrike, EQEmu::skills::SkillTigerClaw, EQEmu::skills::SkillRoundKick }; - int extra = 1; - if (zone->random.Roll(wuchance / 4)) + const int MonkSPA[5] = {EQEmu::skills::SkillFlyingKick, EQEmu::skills::SkillDragonPunch, + EQEmu::skills::SkillEagleStrike, EQEmu::skills::SkillTigerClaw, + EQEmu::skills::SkillRoundKick}; + int extra = 0; + // always 1/4 of the double attack chance, 25% at rank 5 (100/4) + while (wuchance > 0) { + if (zone->random.Roll(wuchance)) extra++; - // They didn't add a string ID for this. - std::string msg = StringFormat("The spirit of Master Wu fills you! You gain %d additional attack(s).", extra); - // live uses 400 here -- not sure if it's the best for all clients though - SendColoredText(400, msg); - while (extra) { - MonkSpecialAttack(ca_target, MonkSPA[zone->random.Int(0, 4)]); - extra--; - } + else + break; + wuchance /= 4; + } + // They didn't add a string ID for this. + std::string msg = StringFormat( + "The spirit of Master Wu fills you! You gain %d additional attack(s).", extra); + // live uses 400 here -- not sure if it's the best for all clients though + SendColoredText(400, msg); + auto classic = RuleB(Combat, ClassicMasterWu); + while (extra) { + MonkSpecialAttack(GetTarget(), + classic ? MonkSPA[zone->random.Int(0, 4)] : skill_to_use); + extra--; } } } @@ -2033,23 +1986,28 @@ void Mob::InstillDoubt(Mob *who) { } } -int Mob::TryHeadShot(Mob* defender, EQEmu::skills::SkillType skillInUse) { - //Only works on YOUR target. - if(defender && (defender->GetBodyType() == BT_Humanoid) && !defender->IsClient() - && (skillInUse == EQEmu::skills::SkillArchery) && (GetTarget() == defender)) { - +int Mob::TryHeadShot(Mob *defender, EQEmu::skills::SkillType skillInUse) +{ + // Only works on YOUR target. + if (defender && defender->GetBodyType() == BT_Humanoid && !defender->IsClient() && + skillInUse == EQEmu::skills::SkillArchery && GetTarget() == defender) { uint32 HeadShot_Dmg = aabonuses.HeadShot[1] + spellbonuses.HeadShot[1] + itembonuses.HeadShot[1]; - uint8 HeadShot_Level = 0; //Get Highest Headshot Level - HeadShot_Level = aabonuses.HSLevel; - if (HeadShot_Level < spellbonuses.HSLevel) - HeadShot_Level = spellbonuses.HSLevel; - else if (HeadShot_Level < itembonuses.HSLevel) - HeadShot_Level = itembonuses.HSLevel; + uint8 HeadShot_Level = 0; // Get Highest Headshot Level + HeadShot_Level = std::max({aabonuses.HSLevel[0], spellbonuses.HSLevel[0], itembonuses.HSLevel[0]}); - if(HeadShot_Dmg && HeadShot_Level && (defender->GetLevel() <= HeadShot_Level)){ - float ProcChance = GetSpecialProcChances(EQEmu::inventory::slotRange); - if(zone->random.Roll(ProcChance)) { - entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FATAL_BOW_SHOT, GetName()); + if (HeadShot_Dmg && HeadShot_Level && (defender->GetLevel() <= HeadShot_Level)) { + int chance = GetDEX(); + chance = 100 * chance / (chance + 3500); + if (IsClient()) + chance += CastToClient()->GetHeroicDEX() / 25; + chance *= 10; + int norm = aabonuses.HSLevel[1]; + if (norm > 0) + chance = chance * norm / 100; + chance += aabonuses.HeadShot[0] + spellbonuses.HeadShot[0] + itembonuses.HeadShot[0]; + if (zone->random.Int(1, 1000) <= chance) { + entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FATAL_BOW_SHOT, + GetName()); return HeadShot_Dmg; } } @@ -2058,61 +2016,41 @@ int Mob::TryHeadShot(Mob* defender, EQEmu::skills::SkillType skillInUse) { return 0; } -float Mob::GetSpecialProcChances(uint16 hand) +int Mob::TryAssassinate(Mob *defender, EQEmu::skills::SkillType skillInUse) { - int mydex = GetDEX(); + if (defender && (defender->GetBodyType() == BT_Humanoid) && !defender->IsClient() && GetLevel() >= 60 && + (skillInUse == EQEmu::skills::SkillBackstab || skillInUse == EQEmu::skills::SkillThrowing)) { + int chance = GetDEX(); + if (skillInUse == EQEmu::skills::SkillBackstab) { + chance = 100 * chance / (chance + 3500); + if (IsClient()) + chance += CastToClient()->GetHeroicDEX(); + chance *= 10; + int norm = aabonuses.AssassinateLevel[1]; + if (norm > 0) + chance = chance * norm / 100; + } else if (skillInUse == EQEmu::skills::SkillThrowing) { + if (chance > 255) + chance = 260; + else + chance += 5; + } - if (mydex > 255) - mydex = 255; + chance += aabonuses.Assassinate[0] + spellbonuses.Assassinate[0] + itembonuses.Assassinate[0]; - uint16 weapon_speed; - float ProcChance = 0.0f; - float ProcBonus = 0.0f; + uint32 Assassinate_Dmg = + aabonuses.Assassinate[1] + spellbonuses.Assassinate[1] + itembonuses.Assassinate[1]; - weapon_speed = GetWeaponSpeedbyHand(hand); - - if (RuleB(Combat, AdjustSpecialProcPerMinute)) { - ProcChance = (static_cast(weapon_speed) * - RuleR(Combat, AvgSpecialProcsPerMinute) / 60000.0f); - ProcBonus += static_cast(mydex/35) + static_cast(itembonuses.HeroicDEX / 25); - ProcChance += ProcChance * ProcBonus / 100.0f; - } else { - /*PRE 2014 CHANGE Dev Quote - "Elidroth SOE:Proc chance is a function of your base hardcapped Dexterity / 35 + Heroic Dexterity / 25.” - Kayen: Most reports suggest a ~ 6% chance to Headshot which consistent with above.*/ - - ProcChance = (static_cast(mydex/35) + static_cast(itembonuses.HeroicDEX / 25))/100.0f; - } - - return ProcChance; -} - -int Mob::TryAssassinate(Mob* defender, EQEmu::skills::SkillType skillInUse, uint16 ReuseTime) { - - if(defender && (defender->GetBodyType() == BT_Humanoid) && !defender->IsClient() && GetLevel() >= 60 && - (skillInUse == EQEmu::skills::SkillBackstab || skillInUse == EQEmu::skills::SkillThrowing)) { - - uint32 Assassinate_Dmg = aabonuses.Assassinate[1] + spellbonuses.Assassinate[1] + itembonuses.Assassinate[1]; - - uint8 Assassinate_Level = 0; //Get Highest Headshot Level - Assassinate_Level = aabonuses.AssassinateLevel; - if (Assassinate_Level < spellbonuses.AssassinateLevel) - Assassinate_Level = spellbonuses.AssassinateLevel; - else if (Assassinate_Level < itembonuses.AssassinateLevel) - Assassinate_Level = itembonuses.AssassinateLevel; + uint8 Assassinate_Level = 0; // Get Highest Headshot Level + Assassinate_Level = std::max( + {aabonuses.AssassinateLevel[0], spellbonuses.AssassinateLevel[0], itembonuses.AssassinateLevel[0]}); // revamped AAs require AA line I believe? if (!Assassinate_Level) return 0; - if(Assassinate_Dmg && Assassinate_Level && (defender->GetLevel() <= Assassinate_Level)){ - float ProcChance = 0.0f; - - if (skillInUse == EQEmu::skills::SkillThrowing) - ProcChance = GetSpecialProcChances(EQEmu::inventory::slotRange); - else - ProcChance = GetAssassinateProcChances(ReuseTime); - - if(zone->random.Roll(ProcChance)) { + if (Assassinate_Dmg && Assassinate_Level && (defender->GetLevel() <= Assassinate_Level)) { + if (zone->random.Int(1, 1000) <= chance) { entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, ASSASSINATES, GetName()); return Assassinate_Dmg; @@ -2123,32 +2061,8 @@ int Mob::TryAssassinate(Mob* defender, EQEmu::skills::SkillType skillInUse, uint return 0; } -float Mob::GetAssassinateProcChances(uint16 ReuseTime) -{ - int mydex = GetDEX(); - - if (mydex > 255) - mydex = 255; - - float ProcChance = 0.0f; - float ProcBonus = 0.0f; - - if (RuleB(Combat, AdjustSpecialProcPerMinute)) { - ProcChance = (static_cast(ReuseTime*1000) * - RuleR(Combat, AvgSpecialProcsPerMinute) / 60000.0f); - ProcBonus += (10 + (static_cast(mydex/10) + static_cast(itembonuses.HeroicDEX /10)))/100.0f; - ProcChance += ProcChance * ProcBonus / 100.0f; - - } else { - /* Kayen: Unable to find data on old proc rate of assassinate, no idea if our formula is real or made up. */ - ProcChance = (10 + (static_cast(mydex/10) + static_cast(itembonuses.HeroicDEX /10)))/100.0f; - - } - - return ProcChance; -} - -void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills::SkillType skillinuse, int16 chance_mod, int16 focus, bool CanRiposte, int ReuseTime) +void Mob::DoMeleeSkillAttackDmg(Mob *other, uint16 weapon_damage, EQEmu::skills::SkillType skillinuse, int16 chance_mod, + int16 focus, bool CanRiposte, int ReuseTime) { if (!CanDoSpecialAttack(other)) return; @@ -2164,17 +2078,19 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills: int damage = 0; uint32 hate = 0; - if (hate == 0 && weapon_damage > 1) hate = weapon_damage; + if (hate == 0 && weapon_damage > 1) + hate = weapon_damage; - if(weapon_damage > 0){ - if (focus) //From FcBaseEffects - weapon_damage += weapon_damage*focus/100; + if (weapon_damage > 0) { + if (focus) // From FcBaseEffects + weapon_damage += weapon_damage * focus / 100; - if (skillinuse == EQEmu::skills::SkillBash){ - if(IsClient()){ - EQEmu::ItemInstance *item = CastToClient()->GetInv().GetItem(EQEmu::inventory::slotSecondary); - if(item){ - if (item->GetItem()->ItemType == EQEmu::item::ItemTypeShield) { + if (skillinuse == EQEmu::skills::SkillBash) { + if (IsClient()) { + EQEmu::ItemInstance *item = + CastToClient()->GetInv().GetItem(EQEmu::inventory::slotSecondary); + if (item) { + if (item->GetItem()->ItemType == EQEmu::item::ItemTypeShield) { hate += item->GetItem()->AC; } const EQEmu::ItemData *itm = item->GetItem(); @@ -2183,37 +2099,30 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills: } } - int min_cap = weapon_damage * GetMeleeMinDamageMod_SE(skillinuse) / 100; - auto offense = this->offense(skillinuse); - int min_damage = 0; + DamageHitInfo my_hit; + my_hit.base_damage = weapon_damage; + my_hit.min_damage = 0; + my_hit.damage_done = 0; + + my_hit.skill = skillinuse; + my_hit.offense = offense(my_hit.skill); + my_hit.tohit = GetTotalToHit(my_hit.skill, chance_mod); + // slot range exclude ripe etc ... + my_hit.hand = CanRiposte ? EQEmu::inventory::slotRange : EQEmu::inventory::slotPrimary; + if (IsNPC()) - min_damage = CastToNPC()->GetMinDamage(); - - if (other->AvoidDamage(this, damage, CanRiposte ? EQEmu::inventory::slotRange : EQEmu::inventory::slotPrimary)) { // SlotRange excludes ripo, primary doesn't have any extra behavior - if (damage == -3) { - DoRiposte(other); - if (HasDied()) - return; - } - } else { - if (other->CheckHitChance(this, skillinuse, chance_mod)) { - other->MeleeMitigation(this, damage, weapon_damage, offense, skillinuse); - ApplyDamageTable(damage, offense); - CommonOutgoingHitSuccess(other, damage, min_damage, min_cap, skillinuse); - } else { - damage = 0; - } - } + my_hit.min_damage = CastToNPC()->GetMinDamage(); + DoAttack(other, my_hit); + damage = my_hit.damage_done; + } else { + damage = DMG_INVULNERABLE; } - else - damage = -5; - bool CanSkillProc = true; - if (skillinuse == EQEmu::skills::SkillOffense){ //Hack to allow damage to display. + if (skillinuse == EQEmu::skills::SkillOffense) { // Hack to allow damage to display. skillinuse = EQEmu::skills::SkillTigerClaw; //'strike' your opponent - Arbitrary choice for message. - CanSkillProc = false; //Disable skill procs + CanSkillProc = false; // Disable skill procs } other->AddToHateList(this, hate, 0, false); @@ -2224,6 +2133,7 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills: SpellFinished(aabonuses.SkillAttackProc[2], other, EQEmu::CastingSlot::Item, 0, -1, spells[aabonuses.SkillAttackProc[2]].ResistDiff); } + other->Damage(this, damage, SPELL_UNKNOWN, skillinuse); if (HasDied()) From c17ac67296e1744363d5660126e2658a03bfeab0 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sat, 28 Jan 2017 22:43:07 -0500 Subject: [PATCH 30/50] Tweak #showstats so bots don't skip stuff --- zone/mob.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/zone/mob.cpp b/zone/mob.cpp index bcaa43ccc..63f1d82ab 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -1495,14 +1495,15 @@ void Mob::ShowStats(Client* client) 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: %.3f Walkspeed: %.3f", n->GetAccuracyRating(), n->MerchantType, n->GetEmoteID(), static_cast(0.025f * n->GetRunspeed()), static_cast(0.025f * n->GetWalkspeed())); - client->Message(0, " compute_tohit: %i TotalToHit: %i", n->compute_tohit(EQEmu::skills::SkillHandtoHand), n->GetTotalToHit(EQEmu::skills::SkillHandtoHand, 0)); - client->Message(0, " compute_defense: %i TotalDefense: %i", n->compute_defense(), n->GetTotalDefense()); - client->Message(0, " offense: %i mitigation ac: %i", n->offense(EQEmu::skills::SkillHandtoHand), n->GetMitigationAC()); n->QueryLoot(client); } if (IsAIControlled()) { client->Message(0, " AggroRange: %1.0f AssistRange: %1.0f", GetAggroRange(), GetAssistRange()); } + + client->Message(0, " compute_tohit: %i TotalToHit: %i", n->compute_tohit(EQEmu::skills::SkillHandtoHand), n->GetTotalToHit(EQEmu::skills::SkillHandtoHand, 0)); + client->Message(0, " compute_defense: %i TotalDefense: %i", n->compute_defense(), n->GetTotalDefense()); + client->Message(0, " offense: %i mitigation ac: %i", n->offense(EQEmu::skills::SkillHandtoHand), n->GetMitigationAC()); } } From 2db6464d1423fc396dd27fa4f096d772e9ff3325 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sat, 28 Jan 2017 22:46:02 -0500 Subject: [PATCH 31/50] Fix last commit --- zone/mob.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zone/mob.cpp b/zone/mob.cpp index 63f1d82ab..41add6490 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -1501,9 +1501,9 @@ void Mob::ShowStats(Client* client) client->Message(0, " AggroRange: %1.0f AssistRange: %1.0f", GetAggroRange(), GetAssistRange()); } - client->Message(0, " compute_tohit: %i TotalToHit: %i", n->compute_tohit(EQEmu::skills::SkillHandtoHand), n->GetTotalToHit(EQEmu::skills::SkillHandtoHand, 0)); - client->Message(0, " compute_defense: %i TotalDefense: %i", n->compute_defense(), n->GetTotalDefense()); - client->Message(0, " offense: %i mitigation ac: %i", n->offense(EQEmu::skills::SkillHandtoHand), n->GetMitigationAC()); + client->Message(0, " compute_tohit: %i TotalToHit: %i", compute_tohit(EQEmu::skills::SkillHandtoHand), GetTotalToHit(EQEmu::skills::SkillHandtoHand, 0)); + client->Message(0, " compute_defense: %i TotalDefense: %i", compute_defense(), GetTotalDefense()); + client->Message(0, " offense: %i mitigation ac: %i", offense(EQEmu::skills::SkillHandtoHand), GetMitigationAC()); } } From 5d61cf5bcf15eaf3d05ad8f23bab416a70feeb25 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sat, 28 Jan 2017 23:21:12 -0500 Subject: [PATCH 32/50] Bots are dumb --- zone/bot.cpp | 39 +-------------------------------------- 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 237c1144b..0ce204d06 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -82,7 +82,6 @@ Bot::Bot(NPCType npcTypeData, Client* botOwner) : NPC(&npcTypeData, nullptr, glm // Do this once and only in this constructor GenerateAppearance(); GenerateBaseStats(); - GenerateArmorClass(); // Calculate HitPoints Last As It Uses Base Stats cur_hp = GenerateBaseHitPoints(); cur_mana = GenerateBaseManaPoints(); @@ -1140,42 +1139,6 @@ int32 Bot::acmod() { return 0; } -void Bot::GenerateArmorClass() { - /// new formula - int avoidance = 0; - avoidance = (acmod() + ((GetSkill(EQEmu::skills::SkillDefense) * 16) / 9)); - if(avoidance < 0) - avoidance = 0; - - int mitigation = 0; - if(GetClass() == WIZARD || GetClass() == MAGICIAN || GetClass() == NECROMANCER || GetClass() == ENCHANTER) { - mitigation = (GetSkill(EQEmu::skills::SkillDefense) / 4 + (itembonuses.AC + 1)); - mitigation -= 4; - } else { - mitigation = (GetSkill(EQEmu::skills::SkillDefense) / 3 + ((itembonuses.AC * 4) / 3)); - if(GetClass() == MONK) - mitigation += (GetLevel() * 13 / 10); //the 13/10 might be wrong, but it is close... - } - int displayed = 0; - displayed += (((avoidance + mitigation) * 1000) / 847); //natural AC - - //Iksar AC, untested - if(GetRace() == IKSAR) { - displayed += 12; - int iksarlevel = GetLevel(); - iksarlevel -= 10; - if(iksarlevel > 25) - iksarlevel = 25; - - if(iksarlevel > 0) - displayed += (iksarlevel * 12 / 10); - } - - //spell AC bonuses are added directly to natural total - displayed += spellbonuses.AC; - this->AC = displayed; -} - uint16 Bot::GetPrimarySkillValue() { EQEmu::skills::SkillType skill = EQEmu::skills::HIGHEST_SKILL; //because nullptr == 0, which is 1H Slashing, & we want it to return 0 from GetSkill bool equiped = m_inv.GetItem(EQEmu::inventory::slotPrimary); @@ -6157,7 +6120,7 @@ void Bot::CalcBonuses() { CalcPR(); CalcCR(); CalcCorrup(); - GenerateArmorClass(); + CalcAC(); CalcMaxHP(); CalcMaxMana(); CalcMaxEndurance(); From 984a009fbb1af0c64d4c28294e65389db9d85e8b Mon Sep 17 00:00:00 2001 From: Uleat Date: Sun, 29 Jan 2017 04:35:03 -0500 Subject: [PATCH 33/50] Temp fix for bot armor color issue - may break armor dying (not tested) --- zone/mob.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/zone/mob.cpp b/zone/mob.cpp index 41add6490..4f8b51ee9 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -2780,7 +2780,22 @@ void Mob::SendWearChange(uint8 material_slot, Client *one_client) wc->material = GetEquipmentMaterial(material_slot); wc->elite_material = IsEliteMaterialItem(material_slot); wc->hero_forge_model = GetHerosForgeModel(material_slot); + +#ifdef BOTS + if (IsBot()) { + auto item_inst = CastToBot()->GetBotItem(EQEmu::InventoryProfile::CalcSlotFromMaterial(material_slot)); + if (item_inst) + wc->color.Color = item_inst->GetColor(); + else + wc->color.Color = 0; + } + else { + wc->color.Color = GetEquipmentColor(material_slot); + } +#else wc->color.Color = GetEquipmentColor(material_slot); +#endif + wc->wear_slot_id = material_slot; if (!one_client) From dc308e2ecb2e5fa92368ca34bf9fc2cdbc77d7f6 Mon Sep 17 00:00:00 2001 From: Uleat Date: Mon, 30 Jan 2017 03:08:00 -0500 Subject: [PATCH 34/50] Fix for null columns in `books`.`language` crash --- common/version.h | 2 +- utils/sql/db_update_manifest.txt | 1 + utils/sql/git/required/2017_01_10_book_languages.sql | 2 +- utils/sql/git/required/2017_01_30_book_languages_fix.sql | 4 ++++ 4 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 utils/sql/git/required/2017_01_30_book_languages_fix.sql diff --git a/common/version.h b/common/version.h index 2d5f1d75f..f7f2ad05d 100644 --- a/common/version.h +++ b/common/version.h @@ -30,7 +30,7 @@ Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9102 +#define CURRENT_BINARY_DATABASE_VERSION 9103 #ifdef BOTS #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9008 #else diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index 816a35a7c..2c27d858d 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -356,6 +356,7 @@ 9100|2016_08_27_object_display_name.sql|SHOW COLUMNS FROM `object` LIKE 'display_name'|empty| 9101|2016_12_01_pcnpc_only.sql|SHOW COLUMNS FROM `spells_new` LIKE 'pcnpc_only_flag'|empty| 9102|2017_01_10_book_languages.sql|SHOW COLUMNS FROM `books` LIKE 'language'|empty| +9103|2017_01_30_book_languages_fix.sql|SELECT `language` from `books` WHERE `language` IS NULL|not_empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/required/2017_01_10_book_languages.sql b/utils/sql/git/required/2017_01_10_book_languages.sql index aa767cd03..1f51325a9 100644 --- a/utils/sql/git/required/2017_01_10_book_languages.sql +++ b/utils/sql/git/required/2017_01_10_book_languages.sql @@ -1,4 +1,4 @@ -alter table books add language int default 0; +alter table books add language int not null default 0; drop table if exists reading_is_fundamental; diff --git a/utils/sql/git/required/2017_01_30_book_languages_fix.sql b/utils/sql/git/required/2017_01_30_book_languages_fix.sql new file mode 100644 index 000000000..3d913b19c --- /dev/null +++ b/utils/sql/git/required/2017_01_30_book_languages_fix.sql @@ -0,0 +1,4 @@ +UPDATE `books` SET `language` = '0' WHERE `language` IS NULL; + +ALTER TABLE `books` MODIFY COLUMN `language` INT NOT NULL DEFAULT '0'; + From da9792160dc07e84d6dedca65b8db4f5f6a1c865 Mon Sep 17 00:00:00 2001 From: Uleat Date: Mon, 30 Jan 2017 17:38:17 -0500 Subject: [PATCH 35/50] Added Mob::HasTargetReflection() --- zone/bot.cpp | 4 ++-- zone/mob.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 0ce204d06..174c4568e 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2273,7 +2273,7 @@ void Bot::AI_Process() { if(AI_movement_timer->Check()) { if (!IsMoving()) { if (GetClass() == ROGUE) { - if ((GetTarget()->GetTarget() == this) && !GetTarget()->IsFeared() && !GetTarget()->IsStunned()) { + if (HasTargetReflection() && !GetTarget()->IsFeared() && !GetTarget()->IsStunned()) { // Hate redux actions if (evade_timer.Check(false)) { // Attempt to evade @@ -2299,7 +2299,7 @@ void Bot::AI_Process() { float newZ = 0; FaceTarget(GetTarget()); if (PlotPositionAroundTarget(this, newX, newY, newZ)) { - Bot::BotGroupSay(this, "Backing off of %s", GetTarget()->GetCleanName()); + Emote("steps back from %s", GetTarget()->GetCleanName()); CalculateNewPosition2(newX, newY, newZ, GetRunspeed()); return; } diff --git a/zone/mob.h b/zone/mob.h index d6fd610cd..dfa0cb16f 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -420,6 +420,7 @@ public: strn0cpy(name, GetName(), 64); return; }; inline Mob* GetTarget() const { return target; } virtual void SetTarget(Mob* mob); + inline bool HasTargetReflection() const { return (target && target->target == this); } virtual inline float GetHPRatio() const { return max_hp == 0 ? 0 : ((float)cur_hp/max_hp*100); } virtual inline int GetIntHPRatio() const { return max_hp == 0 ? 0 : static_cast(cur_hp * 100 / max_hp); } inline int32 GetAC() const { return AC; } From d8519bc270ad932047679fa1c2c86870ff29a176 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Tue, 31 Jan 2017 17:25:05 -0500 Subject: [PATCH 36/50] Weapons allow NPCs to facestab --- zone/loottables.cpp | 4 +++- zone/mob.cpp | 1 + zone/mob.h | 3 +++ zone/special_attacks.cpp | 4 ++-- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/zone/loottables.cpp b/zone/loottables.cpp index 03f1590b1..7f8e371bd 100644 --- a/zone/loottables.cpp +++ b/zone/loottables.cpp @@ -332,8 +332,10 @@ void NPC::AddLootDrop(const EQEmu::ItemData *item2, ItemList* itemlist, int16 ch CastToMob()->AddProcToWeapon(item2->Proc.Effect, true); eslot = EQEmu::textures::weaponPrimary; - if (item2->Damage > 0) + if (item2->Damage > 0) { SendAddPlayerState(PlayerState::PrimaryWeaponEquipped); + SetFacestab(true); + } if (item2->IsType2HWeapon()) SetTwoHanderEquipped(true); } diff --git a/zone/mob.cpp b/zone/mob.cpp index 4f8b51ee9..506c8b44e 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -213,6 +213,7 @@ Mob::Mob(const char* in_name, has_shieldequiped = false; has_twohandbluntequiped = false; has_twohanderequipped = false; + can_facestab = false; has_numhits = false; has_MGB = false; has_ProjectIllusion = false; diff --git a/zone/mob.h b/zone/mob.h index dfa0cb16f..52ad3937b 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -373,6 +373,8 @@ public: inline void SetTwoHandBluntEquiped(bool val) { has_twohandbluntequiped = val; } bool HasTwoHanderEquipped() { return has_twohanderequipped; } void SetTwoHanderEquipped(bool val) { has_twohanderequipped = val; } + bool CanFacestab() { return can_facestab; } + void SetFacestab(bool val) { can_facestab = val; } virtual uint16 GetSkill(EQEmu::skills::SkillType skill_num) const { return 0; } virtual uint32 GetEquipment(uint8 material_slot) const { return(0); } virtual int32 GetEquipmentMaterial(uint8 material_slot) const; @@ -1298,6 +1300,7 @@ protected: bool has_shieldequiped; bool has_twohandbluntequiped; bool has_twohanderequipped; + bool can_facestab; bool has_numhits; bool has_MGB; bool has_ProjectIllusion; diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 7f6ec112d..81c888915 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -528,8 +528,8 @@ void Mob::TryBackstab(Mob *other, int ReuseTime) { bCanFrontalBS = true; } - if (bIsBehind || bCanFrontalBS){ // Player is behind other OR can do Frontal Backstab - if (bCanFrontalBS) + if (bIsBehind || bCanFrontalBS || (IsNPC() && CanFacestab())) { // Player is behind other OR can do Frontal Backstab + if (bCanFrontalBS && IsClient()) // I don't think there is any message ... CastToClient()->Message(0,"Your fierce attack is executed with such grace, your target did not see it coming!"); RogueBackstab(other,false,ReuseTime); From b0ad9524bc80c4de68828bca82d17cc43eb40f4b Mon Sep 17 00:00:00 2001 From: Uleat Date: Tue, 31 Jan 2017 20:17:54 -0500 Subject: [PATCH 37/50] Bot movement behavior change..still in-work --- changelog.txt | 3 ++ zone/bot.cpp | 119 +++++++++++++++++++++++++++++++++++++------------- zone/bot.h | 1 + zone/mob.h | 2 +- 4 files changed, 93 insertions(+), 32 deletions(-) diff --git a/changelog.txt b/changelog.txt index 7a9a0fa65..390c45cd9 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,8 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 01/31/2017 == +Uleat: Modifed bot movement behavior in an attempt to 'normalize' it. This is a hack fix and will be revisited at some point. (Probably just need a follow function rather than use movement, when the leader of the follow chain is moving.) + == 01/26/2017 == Uleat: Change rogue bot behavior to eliminate twirling combat. They will only get behind the mob if they are not the mob's target or if the mob is feared or fleeing. This may lower rogue bot dps a small fraction..but, is more in-line with realistic game mechanics. diff --git a/zone/bot.cpp b/zone/bot.cpp index 174c4568e..3759335a4 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2305,10 +2305,6 @@ void Bot::AI_Process() { } } } - - // Could add a bot accessor like.. - // bool NeedsHateRedux() { return (GetClass() == Rogue && evade_timer.check(false)); } - or something like this - // ..then add hate redux spells to caster combat repertoires } else if (!BehindMob(GetTarget(), GetX(), GetY())) { // Move the rogue to behind the mob @@ -2472,42 +2468,67 @@ void Bot::AI_Process() { BotMeditate(true); } } else { - SetTarget(0); + SetTarget(nullptr); if (m_PlayerState & static_cast(PlayerState::Aggressive)) SendRemovePlayerState(PlayerState::Aggressive); - if(!IsMoving() && AI_think_timer->Check() && !spellend_timer.Enabled()) { - if(GetBotStance() != BotStancePassive) { - if(!AI_IdleCastCheck() && !IsCasting()) - BotMeditate(true); - } - else - BotMeditate(true); - } + Mob* follow = entity_list.GetMob(GetFollowID()); + if (!follow) + return; - if(AI_movement_timer->Check()) { - if(GetFollowID()) { - Mob* follow = entity_list.GetMob(GetFollowID()); - if(follow) { - float dist = DistanceSquared(m_Position, follow->GetPosition()); - int speed = follow->GetRunspeed(); - if(dist < GetFollowDistance() + 1000) - speed = follow->GetWalkspeed(); + float cur_dist = DistanceSquared(m_Position, follow->GetPosition()); - if(dist > GetFollowDistance()) { - CalculateNewPosition2(follow->GetX(), follow->GetY(), follow->GetZ(), speed); - if(rest_timer.Enabled()) - rest_timer.Disable(); - return; - } else { - if(moved) { - moved = false; - SetCurrentSpeed(0); - } + if (!IsMoving() && cur_dist <= GetFollowDistance()) { + if (AI_think_timer->Check()) { + if (!spellend_timer.Enabled()) { + if (GetBotStance() != BotStancePassive) { + if (!AI_IdleCastCheck() && !IsCasting() && GetClass() != BARD) + BotMeditate(true); + } + else { + if (GetClass() != BARD) + BotMeditate(true); } } } } + else if(AI_movement_timer->Check()) { + int speed = GetRunspeed(); + if (cur_dist < GetFollowDistance() + 1000) { + speed = GetWalkspeed(); + } + else if (cur_dist >= GetFollowDistance() + 8000) { + auto leader = follow; + while (leader->GetFollowID()) { + leader = entity_list.GetMob(leader->GetFollowID()); + if (!leader || leader == this) + break; + } + if (leader && leader != this && leader->GetRunspeed() > speed) + speed = leader->GetRunspeed(); + speed = (float)speed * 1.8f; // special bot sprint mod + } + + // this needs work.. + // could probably eliminate the sprint mod with the correct logic + if (cur_dist > GetFollowDistance()) { + CalculateNewPosition2(follow->GetX(), follow->GetY(), follow->GetZ(), speed); + if (rest_timer.Enabled()) + rest_timer.Disable(); + return; + } + else { + if (moved) { + moved = false; + SetCurrentSpeed(0); + } + } + } + else if (IsMoving()) { + if (GetBotStance() != BotStancePassive && GetClass() == BARD && !spellend_timer.Enabled() && AI_think_timer->Check()) { + AI_IdleCastCheck(); + } + } } } @@ -7989,6 +8010,42 @@ bool Bot::GetNeedsCured(Mob *tar) { return needCured; } +bool Bot::GetNeedsHateRedux(Mob *tar) { + // This really should be a scalar function based in class Mob that returns 'this' state..but, is inline with current Bot coding... + // TODO: Good starting point..but, can be refined.. + // TODO: Still awaiting bot spell rework.. + if (!tar || !tar->HasTargetReflection()) + return false; + + if (tar->IsClient()) { + switch (tar->GetClass()) { + // TODO: figure out affectable classes.. + // Might need flag to allow player to determine redux req... + default: + return false; + } + } + else if (tar->IsBot()) { + switch (tar->GetClass()) { + case ROGUE: + if (tar->CastToBot()->evade_timer.Check(false)) + return false; + case CLERIC: + case DRUID: + case SHAMAN: + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + return true; + default: + return false; + } + } + + return false; +} + bool Bot::HasOrMayGetAggro() { bool mayGetAggro = false; if(GetTarget() && GetTarget()->GetHateTop()) { diff --git a/zone/bot.h b/zone/bot.h index 5431df341..bda693094 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -293,6 +293,7 @@ public: bool UseDiscipline(uint32 spell_id, uint32 target); uint8 GetNumberNeedingHealedInGroup(uint8 hpr, bool includePets); bool GetNeedsCured(Mob *tar); + bool GetNeedsHateRedux(Mob *tar); bool HasOrMayGetAggro(); void SetDefaultBotStance(); void CalcChanceToCast(); diff --git a/zone/mob.h b/zone/mob.h index 52ad3937b..fe76f949d 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -422,7 +422,7 @@ public: strn0cpy(name, GetName(), 64); return; }; inline Mob* GetTarget() const { return target; } virtual void SetTarget(Mob* mob); - inline bool HasTargetReflection() const { return (target && target->target == this); } + inline bool HasTargetReflection() const { return (target && target != this && target->target == this); } virtual inline float GetHPRatio() const { return max_hp == 0 ? 0 : ((float)cur_hp/max_hp*100); } virtual inline int GetIntHPRatio() const { return max_hp == 0 ? 0 : static_cast(cur_hp * 100 / max_hp); } inline int32 GetAC() const { return AC; } From 1999982e6a43a526c6cff15873debe6ad1ab6e1e Mon Sep 17 00:00:00 2001 From: Uleat Date: Thu, 2 Feb 2017 19:10:10 -0500 Subject: [PATCH 38/50] Fix for bot load buffs instrument mod. More tweaks to bot movement hack --- zone/bot.cpp | 29 ++++++++++++++++------------- zone/bot_database.cpp | 4 +++- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 3759335a4..d5f6ff814 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2299,7 +2299,6 @@ void Bot::AI_Process() { float newZ = 0; FaceTarget(GetTarget()); if (PlotPositionAroundTarget(this, newX, newY, newZ)) { - Emote("steps back from %s", GetTarget()->GetCleanName()); CalculateNewPosition2(newX, newY, newZ, GetRunspeed()); return; } @@ -2493,24 +2492,28 @@ void Bot::AI_Process() { } } else if(AI_movement_timer->Check()) { + // something is wrong with bot movement spell bonuses - based on logging.. + // ..this code won't need to be so complex once fixed... int speed = GetRunspeed(); - if (cur_dist < GetFollowDistance() + 1000) { + if (cur_dist < GetFollowDistance() + 2000) { speed = GetWalkspeed(); } - else if (cur_dist >= GetFollowDistance() + 8000) { - auto leader = follow; - while (leader->GetFollowID()) { - leader = entity_list.GetMob(leader->GetFollowID()); - if (!leader || leader == this) - break; + else if (cur_dist >= GetFollowDistance() + 10000) { // 100 + if (cur_dist >= 22500) { // 150 + auto leader = follow; + while (leader->GetFollowID()) { + leader = entity_list.GetMob(leader->GetFollowID()); + if (!leader || leader == this) + break; + if (leader->GetRunspeed() > speed) + speed = leader->GetRunspeed(); + if (leader->IsClient()) + break; + } } - if (leader && leader != this && leader->GetRunspeed() > speed) - speed = leader->GetRunspeed(); - speed = (float)speed * 1.8f; // special bot sprint mod + speed = (float)speed * (1.0f + ((float)speed * 0.03125f)); // 1/32 - special bot sprint mod } - // this needs work.. - // could probably eliminate the sprint mod with the correct logic if (cur_dist > GetFollowDistance()) { CalculateNewPosition2(follow->GetX(), follow->GetY(), follow->GetZ(), speed); if (rest_timer.Enabled()) diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp index 04ce5b281..0f7553ada 100644 --- a/zone/bot_database.cpp +++ b/zone/bot_database.cpp @@ -617,7 +617,8 @@ bool BotDatabase::LoadBuffs(Bot* bot_inst) " `caston_x`," " `caston_y`," " `caston_z`," - " `extra_di_chance`" + " `extra_di_chance`," + " `instrument_mod`" " FROM `bot_buffs`" " WHERE `bot_id` = '%u'", bot_inst->GetBotID() @@ -657,6 +658,7 @@ bool BotDatabase::LoadBuffs(Bot* bot_inst) bot_buffs[buff_count].caston_y = atoi(row[14]); bot_buffs[buff_count].caston_z = atoi(row[15]); bot_buffs[buff_count].ExtraDIChance = atoi(row[16]); + bot_buffs[buff_count].instrument_mod = atoi(row[17]); bot_buffs[buff_count].casterid = 0; ++buff_count; } From b1f14e1e2954b61d3c3b79ac93c6753cf74179ba Mon Sep 17 00:00:00 2001 From: Uleat Date: Sat, 4 Feb 2017 05:55:10 -0500 Subject: [PATCH 39/50] Added dev script for function to retrieve effect id labels for spells from queries [skip ci] --- utils/scripts/spell_effect_token_function.sql | 494 ++++++++++++++++++ 1 file changed, 494 insertions(+) create mode 100644 utils/scripts/spell_effect_token_function.sql diff --git a/utils/scripts/spell_effect_token_function.sql b/utils/scripts/spell_effect_token_function.sql new file mode 100644 index 000000000..ee5f9afc8 --- /dev/null +++ b/utils/scripts/spell_effect_token_function.sql @@ -0,0 +1,494 @@ +DELIMITER $$ + +DROP FUNCTION IF EXISTS `GetSpellEffectToken`; + +-- This function converts a numeric spell effect id to a string label based on server code designations +-- +-- example: +-- SELECT `id`, `name`, GetSpellEffectToken(`effectid1`), GetSpellEffectToken(`effectid2`) FROM `spells_new` WHERE `id` IN ('1011', '1602'); +CREATE FUNCTION `GetSpellEffectToken` (`effect_id` INT(11)) RETURNS VARCHAR(64) +BEGIN + DECLARE `token` VARCHAR(64) DEFAULT ''; + + CASE `effect_id` + WHEN '0' THEN SET `token` = 'SE_CurrentHP'; + WHEN '1' THEN SET `token` = 'SE_ArmorClass'; + WHEN '2' THEN SET `token` = 'SE_ATK'; + WHEN '3' THEN SET `token` = 'SE_MovementSpeed'; + WHEN '4' THEN SET `token` = 'SE_STR'; + WHEN '5' THEN SET `token` = 'SE_DEX'; + WHEN '6' THEN SET `token` = 'SE_AGI'; + WHEN '7' THEN SET `token` = 'SE_STA'; + WHEN '8' THEN SET `token` = 'SE_INT'; + WHEN '9' THEN SET `token` = 'SE_WIS'; + WHEN '10' THEN SET `token` = 'SE_CHA'; + WHEN '11' THEN SET `token` = 'SE_AttackSpeed'; + WHEN '12' THEN SET `token` = 'SE_Invisibility'; + WHEN '13' THEN SET `token` = 'SE_SeeInvis'; + WHEN '14' THEN SET `token` = 'SE_WaterBreathing'; + WHEN '15' THEN SET `token` = 'SE_CurrentMana'; + WHEN '16' THEN SET `token` = 'SE_NPCFrenzy'; + WHEN '17' THEN SET `token` = 'SE_NPCAwareness'; + WHEN '18' THEN SET `token` = 'SE_Lull'; + WHEN '19' THEN SET `token` = 'SE_AddFaction'; + WHEN '20' THEN SET `token` = 'SE_Blind'; + WHEN '21' THEN SET `token` = 'SE_Stun'; + WHEN '22' THEN SET `token` = 'SE_Charm'; + WHEN '23' THEN SET `token` = 'SE_Fear'; + WHEN '24' THEN SET `token` = 'SE_Stamina'; + WHEN '25' THEN SET `token` = 'SE_BindAffinity'; + WHEN '26' THEN SET `token` = 'SE_Gate'; + WHEN '27' THEN SET `token` = 'SE_CancelMagic'; + WHEN '28' THEN SET `token` = 'SE_InvisVsUndead'; + WHEN '29' THEN SET `token` = 'SE_InvisVsAnimals'; + WHEN '30' THEN SET `token` = 'SE_ChangeFrenzyRad'; + WHEN '31' THEN SET `token` = 'SE_Mez'; + WHEN '32' THEN SET `token` = 'SE_SummonItem'; + WHEN '33' THEN SET `token` = 'SE_SummonPet'; + WHEN '34' THEN SET `token` = 'SE_Confuse'; + WHEN '35' THEN SET `token` = 'SE_DiseaseCounter'; + WHEN '36' THEN SET `token` = 'SE_PoisonCounter'; + WHEN '37' THEN SET `token` = 'SE_DetectHostile'; + WHEN '38' THEN SET `token` = 'SE_DetectMagic'; + WHEN '39' THEN SET `token` = 'SE_DetectPoison'; + WHEN '40' THEN SET `token` = 'SE_DivineAura'; + WHEN '41' THEN SET `token` = 'SE_Destroy'; + WHEN '42' THEN SET `token` = 'SE_ShadowStep'; + WHEN '43' THEN SET `token` = 'SE_Berserk'; + WHEN '44' THEN SET `token` = 'SE_Lycanthropy'; + WHEN '45' THEN SET `token` = 'SE_Vampirism'; + WHEN '46' THEN SET `token` = 'SE_ResistFire'; + WHEN '47' THEN SET `token` = 'SE_ResistCold'; + WHEN '48' THEN SET `token` = 'SE_ResistPoison'; + WHEN '49' THEN SET `token` = 'SE_ResistDisease'; + WHEN '50' THEN SET `token` = 'SE_ResistMagic'; + WHEN '51' THEN SET `token` = 'SE_DetectTraps'; + WHEN '52' THEN SET `token` = 'SE_SenseDead'; + WHEN '53' THEN SET `token` = 'SE_SenseSummoned'; + WHEN '54' THEN SET `token` = 'SE_SenseAnimals'; + WHEN '55' THEN SET `token` = 'SE_Rune'; + WHEN '56' THEN SET `token` = 'SE_TrueNorth'; + WHEN '57' THEN SET `token` = 'SE_Levitate'; + WHEN '58' THEN SET `token` = 'SE_Illusion'; + WHEN '59' THEN SET `token` = 'SE_DamageShield'; + WHEN '60' THEN SET `token` = 'SE_TransferItem'; + WHEN '61' THEN SET `token` = 'SE_Identify'; + WHEN '62' THEN SET `token` = 'SE_ItemID'; + WHEN '63' THEN SET `token` = 'SE_WipeHateList'; + WHEN '64' THEN SET `token` = 'SE_SpinTarget'; + WHEN '65' THEN SET `token` = 'SE_InfraVision'; + WHEN '66' THEN SET `token` = 'SE_UltraVision'; + WHEN '67' THEN SET `token` = 'SE_EyeOfZomm'; + WHEN '68' THEN SET `token` = 'SE_ReclaimPet'; + WHEN '69' THEN SET `token` = 'SE_TotalHP'; + WHEN '70' THEN SET `token` = 'SE_CorpseBomb'; + WHEN '71' THEN SET `token` = 'SE_NecPet'; + WHEN '72' THEN SET `token` = 'SE_PreserveCorpse'; + WHEN '73' THEN SET `token` = 'SE_BindSight'; + WHEN '74' THEN SET `token` = 'SE_FeignDeath'; + WHEN '75' THEN SET `token` = 'SE_VoiceGraft'; + WHEN '76' THEN SET `token` = 'SE_Sentinel'; + WHEN '77' THEN SET `token` = 'SE_LocateCorpse'; + WHEN '78' THEN SET `token` = 'SE_AbsorbMagicAtt'; + WHEN '79' THEN SET `token` = 'SE_CurrentHPOnce'; + WHEN '80' THEN SET `token` = 'SE_EnchantLight'; + WHEN '81' THEN SET `token` = 'SE_Revive'; + WHEN '82' THEN SET `token` = 'SE_SummonPC'; + WHEN '83' THEN SET `token` = 'SE_Teleport'; + WHEN '84' THEN SET `token` = 'SE_TossUp'; + WHEN '85' THEN SET `token` = 'SE_WeaponProc'; + WHEN '86' THEN SET `token` = 'SE_Harmony'; + WHEN '87' THEN SET `token` = 'SE_MagnifyVision'; + WHEN '88' THEN SET `token` = 'SE_Succor'; + WHEN '89' THEN SET `token` = 'SE_ModelSize'; + WHEN '90' THEN SET `token` = 'SE_Cloak'; + WHEN '91' THEN SET `token` = 'SE_SummonCorpse'; + WHEN '92' THEN SET `token` = 'SE_InstantHate'; + WHEN '93' THEN SET `token` = 'SE_StopRain'; + WHEN '94' THEN SET `token` = 'SE_NegateIfCombat'; + WHEN '95' THEN SET `token` = 'SE_Sacrifice'; + WHEN '96' THEN SET `token` = 'SE_Silence'; + WHEN '97' THEN SET `token` = 'SE_ManaPool'; + WHEN '98' THEN SET `token` = 'SE_AttackSpeed2'; + WHEN '99' THEN SET `token` = 'SE_Root'; + WHEN '100' THEN SET `token` = 'SE_HealOverTime'; + WHEN '101' THEN SET `token` = 'SE_CompleteHeal'; + WHEN '102' THEN SET `token` = 'SE_Fearless'; + WHEN '103' THEN SET `token` = 'SE_CallPet'; + WHEN '104' THEN SET `token` = 'SE_Translocate'; + WHEN '105' THEN SET `token` = 'SE_AntiGate'; + WHEN '106' THEN SET `token` = 'SE_SummonBSTPet'; + WHEN '107' THEN SET `token` = 'SE_AlterNPCLevel'; + WHEN '108' THEN SET `token` = 'SE_Familiar'; + WHEN '109' THEN SET `token` = 'SE_SummonItemIntoBag'; + WHEN '110' THEN SET `token` = 'SE_IncreaseArchery'; + WHEN '111' THEN SET `token` = 'SE_ResistAll'; + WHEN '112' THEN SET `token` = 'SE_CastingLevel'; + WHEN '113' THEN SET `token` = 'SE_SummonHorse'; + WHEN '114' THEN SET `token` = 'SE_ChangeAggro'; + WHEN '115' THEN SET `token` = 'SE_Hunger'; + WHEN '116' THEN SET `token` = 'SE_CurseCounter'; + WHEN '117' THEN SET `token` = 'SE_MagicWeapon'; + WHEN '118' THEN SET `token` = 'SE_Amplification'; + WHEN '119' THEN SET `token` = 'SE_AttackSpeed3'; + WHEN '120' THEN SET `token` = 'SE_HealRate'; + WHEN '121' THEN SET `token` = 'SE_ReverseDS'; + WHEN '122' THEN SET `token` = 'SE_ReduceSkill'; + WHEN '123' THEN SET `token` = 'SE_Screech'; + WHEN '124' THEN SET `token` = 'SE_ImprovedDamage'; + WHEN '125' THEN SET `token` = 'SE_ImprovedHeal'; + WHEN '126' THEN SET `token` = 'SE_SpellResistReduction'; + WHEN '127' THEN SET `token` = 'SE_IncreaseSpellHaste'; + WHEN '128' THEN SET `token` = 'SE_IncreaseSpellDuration'; + WHEN '129' THEN SET `token` = 'SE_IncreaseRange'; + WHEN '130' THEN SET `token` = 'SE_SpellHateMod'; + WHEN '131' THEN SET `token` = 'SE_ReduceReagentCost'; + WHEN '132' THEN SET `token` = 'SE_ReduceManaCost'; + WHEN '133' THEN SET `token` = 'SE_FcStunTimeMod'; + WHEN '134' THEN SET `token` = 'SE_LimitMaxLevel'; + WHEN '135' THEN SET `token` = 'SE_LimitResist'; + WHEN '136' THEN SET `token` = 'SE_LimitTarget'; + WHEN '137' THEN SET `token` = 'SE_LimitEffect'; + WHEN '138' THEN SET `token` = 'SE_LimitSpellType'; + WHEN '139' THEN SET `token` = 'SE_LimitSpell'; + WHEN '140' THEN SET `token` = 'SE_LimitMinDur'; + WHEN '141' THEN SET `token` = 'SE_LimitInstant'; + WHEN '142' THEN SET `token` = 'SE_LimitMinLevel'; + WHEN '143' THEN SET `token` = 'SE_LimitCastTimeMin'; + WHEN '144' THEN SET `token` = 'SE_LimitCastTimeMax'; + WHEN '145' THEN SET `token` = 'SE_Teleport2'; + WHEN '146' THEN SET `token` = 'SE_ElectricityResist'; + WHEN '147' THEN SET `token` = 'SE_PercentalHeal'; + WHEN '148' THEN SET `token` = 'SE_StackingCommand_Block'; + WHEN '149' THEN SET `token` = 'SE_StackingCommand_Overwrite'; + WHEN '150' THEN SET `token` = 'SE_DeathSave'; + WHEN '151' THEN SET `token` = 'SE_SuspendPet'; + WHEN '152' THEN SET `token` = 'SE_TemporaryPets'; + WHEN '153' THEN SET `token` = 'SE_BalanceHP'; + WHEN '154' THEN SET `token` = 'SE_DispelDetrimental'; + WHEN '155' THEN SET `token` = 'SE_SpellCritDmgIncrease'; + WHEN '156' THEN SET `token` = 'SE_IllusionCopy'; + WHEN '157' THEN SET `token` = 'SE_SpellDamageShield'; + WHEN '158' THEN SET `token` = 'SE_Reflect'; + WHEN '159' THEN SET `token` = 'SE_AllStats'; + WHEN '160' THEN SET `token` = 'SE_MakeDrunk'; + WHEN '161' THEN SET `token` = 'SE_MitigateSpellDamage'; + WHEN '162' THEN SET `token` = 'SE_MitigateMeleeDamage'; + WHEN '163' THEN SET `token` = 'SE_NegateAttacks'; + WHEN '164' THEN SET `token` = 'SE_AppraiseLDonChest'; + WHEN '165' THEN SET `token` = 'SE_DisarmLDoNTrap'; + WHEN '166' THEN SET `token` = 'SE_UnlockLDoNChest'; + WHEN '167' THEN SET `token` = 'SE_PetPowerIncrease'; + WHEN '168' THEN SET `token` = 'SE_MeleeMitigation'; + WHEN '169' THEN SET `token` = 'SE_CriticalHitChance'; + WHEN '170' THEN SET `token` = 'SE_SpellCritChance'; + WHEN '171' THEN SET `token` = 'SE_CrippBlowChance'; + WHEN '172' THEN SET `token` = 'SE_AvoidMeleeChance'; + WHEN '173' THEN SET `token` = 'SE_RiposteChance'; + WHEN '174' THEN SET `token` = 'SE_DodgeChance'; + WHEN '175' THEN SET `token` = 'SE_ParryChance'; + WHEN '176' THEN SET `token` = 'SE_DualWieldChance'; + WHEN '177' THEN SET `token` = 'SE_DoubleAttackChance'; + WHEN '178' THEN SET `token` = 'SE_MeleeLifetap'; + WHEN '179' THEN SET `token` = 'SE_AllInstrumentMod'; + WHEN '180' THEN SET `token` = 'SE_ResistSpellChance'; + WHEN '181' THEN SET `token` = 'SE_ResistFearChance'; + WHEN '182' THEN SET `token` = 'SE_HundredHands'; + WHEN '183' THEN SET `token` = 'SE_MeleeSkillCheck'; + WHEN '184' THEN SET `token` = 'SE_HitChance'; + WHEN '185' THEN SET `token` = 'SE_DamageModifier'; + WHEN '186' THEN SET `token` = 'SE_MinDamageModifier'; + WHEN '187' THEN SET `token` = 'SE_BalanceMana'; + WHEN '188' THEN SET `token` = 'SE_IncreaseBlockChance'; + WHEN '189' THEN SET `token` = 'SE_CurrentEndurance'; + WHEN '190' THEN SET `token` = 'SE_EndurancePool'; + WHEN '191' THEN SET `token` = 'SE_Amnesia'; + WHEN '192' THEN SET `token` = 'SE_Hate'; + WHEN '193' THEN SET `token` = 'SE_SkillAttack'; + WHEN '194' THEN SET `token` = 'SE_FadingMemories'; + WHEN '195' THEN SET `token` = 'SE_StunResist'; + WHEN '196' THEN SET `token` = 'SE_StrikeThrough'; + WHEN '197' THEN SET `token` = 'SE_SkillDamageTaken'; + WHEN '198' THEN SET `token` = 'SE_CurrentEnduranceOnce'; + WHEN '199' THEN SET `token` = 'SE_Taunt'; + WHEN '200' THEN SET `token` = 'SE_ProcChance'; + WHEN '201' THEN SET `token` = 'SE_RangedProc'; + WHEN '202' THEN SET `token` = 'SE_IllusionOther'; + WHEN '203' THEN SET `token` = 'SE_MassGroupBuff'; + WHEN '204' THEN SET `token` = 'SE_GroupFearImmunity'; + WHEN '205' THEN SET `token` = 'SE_Rampage'; + WHEN '206' THEN SET `token` = 'SE_AETaunt'; + WHEN '207' THEN SET `token` = 'SE_FleshToBone'; + WHEN '208' THEN SET `token` = 'SE_PurgePoison'; + WHEN '209' THEN SET `token` = 'SE_DispelBeneficial'; + WHEN '210' THEN SET `token` = 'SE_PetShield'; + WHEN '211' THEN SET `token` = 'SE_AEMelee'; + WHEN '212' THEN SET `token` = 'SE_FrenziedDevastation'; + WHEN '213' THEN SET `token` = 'SE_PetMaxHP'; + WHEN '214' THEN SET `token` = 'SE_MaxHPChange'; + WHEN '215' THEN SET `token` = 'SE_PetAvoidance'; + WHEN '216' THEN SET `token` = 'SE_Accuracy'; + WHEN '217' THEN SET `token` = 'SE_HeadShot'; + WHEN '218' THEN SET `token` = 'SE_PetCriticalHit'; + WHEN '219' THEN SET `token` = 'SE_SlayUndead'; + WHEN '220' THEN SET `token` = 'SE_SkillDamageAmount'; + WHEN '221' THEN SET `token` = 'SE_Packrat'; + WHEN '222' THEN SET `token` = 'SE_BlockBehind'; + WHEN '223' THEN SET `token` = 'SE_DoubleRiposte'; + WHEN '224' THEN SET `token` = 'SE_GiveDoubleRiposte'; + WHEN '225' THEN SET `token` = 'SE_GiveDoubleAttack'; + WHEN '226' THEN SET `token` = 'SE_TwoHandBash'; + WHEN '227' THEN SET `token` = 'SE_ReduceSkillTimer'; + WHEN '228' THEN SET `token` = 'SE_ReduceFallDamage'; + WHEN '229' THEN SET `token` = 'SE_PersistantCasting'; + WHEN '230' THEN SET `token` = 'SE_ExtendedShielding'; + WHEN '231' THEN SET `token` = 'SE_StunBashChance'; + WHEN '232' THEN SET `token` = 'SE_DivineSave'; + WHEN '233' THEN SET `token` = 'SE_Metabolism'; + WHEN '234' THEN SET `token` = 'SE_ReduceApplyPoisonTime'; + WHEN '235' THEN SET `token` = 'SE_ChannelChanceSpells'; + WHEN '236' THEN SET `token` = 'SE_FreePet'; + WHEN '237' THEN SET `token` = 'SE_GivePetGroupTarget'; + WHEN '238' THEN SET `token` = 'SE_IllusionPersistence'; + WHEN '239' THEN SET `token` = 'SE_FeignedCastOnChance'; + WHEN '240' THEN SET `token` = 'SE_StringUnbreakable'; + WHEN '241' THEN SET `token` = 'SE_ImprovedReclaimEnergy'; + WHEN '242' THEN SET `token` = 'SE_IncreaseChanceMemwipe'; + WHEN '243' THEN SET `token` = 'SE_CharmBreakChance'; + WHEN '244' THEN SET `token` = 'SE_RootBreakChance'; + WHEN '245' THEN SET `token` = 'SE_TrapCircumvention'; + WHEN '246' THEN SET `token` = 'SE_SetBreathLevel'; + WHEN '247' THEN SET `token` = 'SE_RaiseSkillCap'; + WHEN '248' THEN SET `token` = 'SE_SecondaryForte'; + WHEN '249' THEN SET `token` = 'SE_SecondaryDmgInc'; + WHEN '250' THEN SET `token` = 'SE_SpellProcChance'; + WHEN '251' THEN SET `token` = 'SE_ConsumeProjectile'; + WHEN '252' THEN SET `token` = 'SE_FrontalBackstabChance'; + WHEN '253' THEN SET `token` = 'SE_FrontalBackstabMinDmg'; + WHEN '254' THEN SET `token` = 'SE_Blank'; + WHEN '255' THEN SET `token` = 'SE_ShieldDuration'; + WHEN '256' THEN SET `token` = 'SE_ShroudofStealth'; + WHEN '257' THEN SET `token` = 'SE_PetDiscipline'; + WHEN '258' THEN SET `token` = 'SE_TripleBackstab'; + WHEN '259' THEN SET `token` = 'SE_CombatStability'; + WHEN '260' THEN SET `token` = 'SE_AddSingingMod'; + WHEN '261' THEN SET `token` = 'SE_SongModCap'; + WHEN '262' THEN SET `token` = 'SE_RaiseStatCap'; + WHEN '263' THEN SET `token` = 'SE_TradeSkillMastery'; + WHEN '264' THEN SET `token` = 'SE_HastenedAASkill'; + WHEN '265' THEN SET `token` = 'SE_MasteryofPast'; + WHEN '266' THEN SET `token` = 'SE_ExtraAttackChance'; + WHEN '267' THEN SET `token` = 'SE_PetDiscipline2'; + WHEN '268' THEN SET `token` = 'SE_ReduceTradeskillFail'; + WHEN '269' THEN SET `token` = 'SE_MaxBindWound'; + WHEN '270' THEN SET `token` = 'SE_BardSongRange'; + WHEN '271' THEN SET `token` = 'SE_BaseMovementSpeed'; + WHEN '272' THEN SET `token` = 'SE_CastingLevel2'; + WHEN '273' THEN SET `token` = 'SE_CriticalDoTChance'; + WHEN '274' THEN SET `token` = 'SE_CriticalHealChance'; + WHEN '275' THEN SET `token` = 'SE_CriticalMend'; + WHEN '276' THEN SET `token` = 'SE_Ambidexterity'; + WHEN '277' THEN SET `token` = 'SE_UnfailingDivinity'; + WHEN '278' THEN SET `token` = 'SE_FinishingBlow'; + WHEN '279' THEN SET `token` = 'SE_Flurry'; + WHEN '280' THEN SET `token` = 'SE_PetFlurry'; + WHEN '281' THEN SET `token` = 'SE_FeignedMinion'; + WHEN '282' THEN SET `token` = 'SE_ImprovedBindWound'; + WHEN '283' THEN SET `token` = 'SE_DoubleSpecialAttack'; + WHEN '284' THEN SET `token` = 'SE_LoHSetHeal'; + WHEN '285' THEN SET `token` = 'SE_NimbleEvasion'; + WHEN '286' THEN SET `token` = 'SE_FcDamageAmt'; + WHEN '287' THEN SET `token` = 'SE_SpellDurationIncByTic'; + WHEN '288' THEN SET `token` = 'SE_SkillAttackProc'; + WHEN '289' THEN SET `token` = 'SE_CastOnFadeEffect'; + WHEN '290' THEN SET `token` = 'SE_IncreaseRunSpeedCap'; + WHEN '291' THEN SET `token` = 'SE_Purify'; + WHEN '292' THEN SET `token` = 'SE_StrikeThrough2'; + WHEN '293' THEN SET `token` = 'SE_FrontalStunResist'; + WHEN '294' THEN SET `token` = 'SE_CriticalSpellChance'; + WHEN '295' THEN SET `token` = 'SE_ReduceTimerSpecial'; + WHEN '296' THEN SET `token` = 'SE_FcSpellVulnerability'; + WHEN '297' THEN SET `token` = 'SE_FcDamageAmtIncoming'; + WHEN '298' THEN SET `token` = 'SE_ChangeHeight'; + WHEN '299' THEN SET `token` = 'SE_WakeTheDead'; + WHEN '300' THEN SET `token` = 'SE_Doppelganger'; + WHEN '301' THEN SET `token` = 'SE_ArcheryDamageModifier'; + WHEN '302' THEN SET `token` = 'SE_FcDamagePctCrit'; + WHEN '303' THEN SET `token` = 'SE_FcDamageAmtCrit'; + WHEN '304' THEN SET `token` = 'SE_OffhandRiposteFail'; + WHEN '305' THEN SET `token` = 'SE_MitigateDamageShield'; + WHEN '306' THEN SET `token` = 'SE_ArmyOfTheDead'; + WHEN '307' THEN SET `token` = 'SE_Appraisal'; + WHEN '308' THEN SET `token` = 'SE_SuspendMinion'; + WHEN '309' THEN SET `token` = 'SE_GateCastersBindpoint'; + WHEN '310' THEN SET `token` = 'SE_ReduceReuseTimer'; + WHEN '311' THEN SET `token` = 'SE_LimitCombatSkills'; + WHEN '312' THEN SET `token` = 'SE_Sanctuary'; + WHEN '313' THEN SET `token` = 'SE_ForageAdditionalItems'; + WHEN '314' THEN SET `token` = 'SE_Invisibility2'; + WHEN '315' THEN SET `token` = 'SE_InvisVsUndead2'; + WHEN '316' THEN SET `token` = 'SE_ImprovedInvisAnimals'; + WHEN '317' THEN SET `token` = 'SE_ItemHPRegenCapIncrease'; + WHEN '318' THEN SET `token` = 'SE_ItemManaRegenCapIncrease'; + WHEN '319' THEN SET `token` = 'SE_CriticalHealOverTime'; + WHEN '320' THEN SET `token` = 'SE_ShieldBlock'; + WHEN '321' THEN SET `token` = 'SE_ReduceHate'; + WHEN '322' THEN SET `token` = 'SE_GateToHomeCity'; + WHEN '323' THEN SET `token` = 'SE_DefensiveProc'; + WHEN '324' THEN SET `token` = 'SE_HPToMana'; + WHEN '325' THEN SET `token` = 'SE_NoBreakAESneak'; + WHEN '326' THEN SET `token` = 'SE_SpellSlotIncrease'; + WHEN '327' THEN SET `token` = 'SE_MysticalAttune'; + WHEN '328' THEN SET `token` = 'SE_DelayDeath'; + WHEN '329' THEN SET `token` = 'SE_ManaAbsorbPercentDamage'; + WHEN '330' THEN SET `token` = 'SE_CriticalDamageMob'; + WHEN '331' THEN SET `token` = 'SE_Salvage'; + WHEN '332' THEN SET `token` = 'SE_SummonToCorpse'; + WHEN '333' THEN SET `token` = 'SE_CastOnRuneFadeEffect'; + WHEN '334' THEN SET `token` = 'SE_BardAEDot'; + WHEN '335' THEN SET `token` = 'SE_BlockNextSpellFocus'; + WHEN '336' THEN SET `token` = 'SE_IllusionaryTarget'; + WHEN '337' THEN SET `token` = 'SE_PercentXPIncrease'; + WHEN '338' THEN SET `token` = 'SE_SummonAndResAllCorpses'; + WHEN '339' THEN SET `token` = 'SE_TriggerOnCast'; + WHEN '340' THEN SET `token` = 'SE_SpellTrigger'; + WHEN '341' THEN SET `token` = 'SE_ItemAttackCapIncrease'; + WHEN '342' THEN SET `token` = 'SE_ImmuneFleeing'; + WHEN '343' THEN SET `token` = 'SE_InterruptCasting'; + WHEN '344' THEN SET `token` = 'SE_ChannelChanceItems'; + WHEN '345' THEN SET `token` = 'SE_AssassinateLevel'; + WHEN '346' THEN SET `token` = 'SE_HeadShotLevel'; + WHEN '347' THEN SET `token` = 'SE_DoubleRangedAttack'; + WHEN '348' THEN SET `token` = 'SE_LimitManaMin'; + WHEN '349' THEN SET `token` = 'SE_ShieldEquipDmgMod'; + WHEN '350' THEN SET `token` = 'SE_ManaBurn'; + WHEN '351' THEN SET `token` = 'SE_PersistentEffect'; + WHEN '352' THEN SET `token` = 'SE_IncreaseTrapCount'; + WHEN '353' THEN SET `token` = 'SE_AdditionalAura'; + WHEN '354' THEN SET `token` = 'SE_DeactivateAllTraps'; + WHEN '355' THEN SET `token` = 'SE_LearnTrap'; + WHEN '356' THEN SET `token` = 'SE_ChangeTriggerType'; + WHEN '357' THEN SET `token` = 'SE_FcMute'; + WHEN '358' THEN SET `token` = 'SE_CurrentManaOnce'; + WHEN '359' THEN SET `token` = 'SE_PassiveSenseTrap'; + WHEN '360' THEN SET `token` = 'SE_ProcOnKillShot'; + WHEN '361' THEN SET `token` = 'SE_SpellOnDeath'; + WHEN '362' THEN SET `token` = 'SE_PotionBeltSlots'; + WHEN '363' THEN SET `token` = 'SE_BandolierSlots'; + WHEN '364' THEN SET `token` = 'SE_TripleAttackChance'; + WHEN '365' THEN SET `token` = 'SE_ProcOnSpellKillShot'; + WHEN '366' THEN SET `token` = 'SE_GroupShielding'; + WHEN '367' THEN SET `token` = 'SE_SetBodyType'; + WHEN '368' THEN SET `token` = 'SE_FactionMod'; + WHEN '369' THEN SET `token` = 'SE_CorruptionCounter'; + WHEN '370' THEN SET `token` = 'SE_ResistCorruption'; + WHEN '371' THEN SET `token` = 'SE_AttackSpeed4'; + WHEN '372' THEN SET `token` = 'SE_ForageSkill'; + WHEN '373' THEN SET `token` = 'SE_CastOnFadeEffectAlways'; + WHEN '374' THEN SET `token` = 'SE_ApplyEffect'; + WHEN '375' THEN SET `token` = 'SE_DotCritDmgIncrease'; + WHEN '376' THEN SET `token` = 'SE_Fling'; + WHEN '377' THEN SET `token` = 'SE_CastOnFadeEffectNPC'; + WHEN '378' THEN SET `token` = 'SE_SpellEffectResistChance'; + WHEN '379' THEN SET `token` = 'SE_ShadowStepDirectional'; + WHEN '380' THEN SET `token` = 'SE_Knockdown'; + WHEN '381' THEN SET `token` = 'SE_KnockTowardCaster'; + WHEN '382' THEN SET `token` = 'SE_NegateSpellEffect'; + WHEN '383' THEN SET `token` = 'SE_SympatheticProc'; + WHEN '384' THEN SET `token` = 'SE_Leap'; + WHEN '385' THEN SET `token` = 'SE_LimitSpellGroup'; + WHEN '386' THEN SET `token` = 'SE_CastOnCurer'; + WHEN '387' THEN SET `token` = 'SE_CastOnCure'; + WHEN '388' THEN SET `token` = 'SE_SummonCorpseZone'; + WHEN '389' THEN SET `token` = 'SE_FcTimerRefresh'; + WHEN '390' THEN SET `token` = 'SE_FcTimerLockout'; + WHEN '391' THEN SET `token` = 'SE_LimitManaMax'; + WHEN '392' THEN SET `token` = 'SE_FcHealAmt'; + WHEN '393' THEN SET `token` = 'SE_FcHealPctIncoming'; + WHEN '394' THEN SET `token` = 'SE_FcHealAmtIncoming'; + WHEN '395' THEN SET `token` = 'SE_FcHealPctCritIncoming'; + WHEN '396' THEN SET `token` = 'SE_FcHealAmtCrit'; + WHEN '397' THEN SET `token` = 'SE_PetMeleeMitigation'; + WHEN '398' THEN SET `token` = 'SE_SwarmPetDuration'; + WHEN '399' THEN SET `token` = 'SE_FcTwincast'; + WHEN '400' THEN SET `token` = 'SE_HealGroupFromMana'; + WHEN '401' THEN SET `token` = 'SE_ManaDrainWithDmg'; + WHEN '402' THEN SET `token` = 'SE_EndDrainWithDmg'; + WHEN '403' THEN SET `token` = 'SE_LimitSpellClass'; + WHEN '404' THEN SET `token` = 'SE_LimitSpellSubclass'; + WHEN '405' THEN SET `token` = 'SE_TwoHandBluntBlock'; + WHEN '406' THEN SET `token` = 'SE_CastonNumHitFade'; + WHEN '407' THEN SET `token` = 'SE_CastonFocusEffect'; + WHEN '408' THEN SET `token` = 'SE_LimitHPPercent'; + WHEN '409' THEN SET `token` = 'SE_LimitManaPercent'; + WHEN '410' THEN SET `token` = 'SE_LimitEndPercent'; + WHEN '411' THEN SET `token` = 'SE_LimitClass'; + WHEN '412' THEN SET `token` = 'SE_LimitRace'; + WHEN '413' THEN SET `token` = 'SE_FcBaseEffects'; + WHEN '414' THEN SET `token` = 'SE_LimitCastingSkill'; + WHEN '415' THEN SET `token` = 'SE_FFItemClass'; + WHEN '416' THEN SET `token` = 'SE_ACv2'; + WHEN '417' THEN SET `token` = 'SE_ManaRegen_v2'; + WHEN '418' THEN SET `token` = 'SE_SkillDamageAmount2'; + WHEN '419' THEN SET `token` = 'SE_AddMeleeProc'; + WHEN '420' THEN SET `token` = 'SE_FcLimitUse'; + WHEN '421' THEN SET `token` = 'SE_FcIncreaseNumHits'; + WHEN '422' THEN SET `token` = 'SE_LimitUseMin'; + WHEN '423' THEN SET `token` = 'SE_LimitUseType'; + WHEN '424' THEN SET `token` = 'SE_GravityEffect'; + WHEN '425' THEN SET `token` = 'SE_Display'; + WHEN '426' THEN SET `token` = 'SE_IncreaseExtTargetWindow'; + WHEN '427' THEN SET `token` = 'SE_SkillProc'; + WHEN '428' THEN SET `token` = 'SE_LimitToSkill'; + WHEN '429' THEN SET `token` = 'SE_SkillProcSuccess'; + WHEN '430' THEN SET `token` = 'SE_PostEffect'; + WHEN '431' THEN SET `token` = 'SE_PostEffectData'; + WHEN '432' THEN SET `token` = 'SE_ExpandMaxActiveTrophyBen'; + WHEN '433' THEN SET `token` = 'SE_CriticalDotDecay'; + WHEN '434' THEN SET `token` = 'SE_CriticalHealDecay'; + WHEN '435' THEN SET `token` = 'SE_CriticalRegenDecay'; + WHEN '436' THEN SET `token` = 'SE_BeneficialCountDownHold'; + WHEN '437' THEN SET `token` = 'SE_TeleporttoAnchor'; + WHEN '438' THEN SET `token` = 'SE_TranslocatetoAnchor'; + WHEN '439' THEN SET `token` = 'SE_Assassinate'; + WHEN '440' THEN SET `token` = 'SE_FinishingBlowLvl'; + WHEN '441' THEN SET `token` = 'SE_DistanceRemoval'; + WHEN '442' THEN SET `token` = 'SE_TriggerOnReqTarget'; + WHEN '443' THEN SET `token` = 'SE_TriggerOnReqCaster'; + WHEN '444' THEN SET `token` = 'SE_ImprovedTaunt'; + WHEN '445' THEN SET `token` = 'SE_AddMercSlot'; + WHEN '446' THEN SET `token` = 'SE_AStacker'; + WHEN '447' THEN SET `token` = 'SE_BStacker'; + WHEN '448' THEN SET `token` = 'SE_CStacker'; + WHEN '449' THEN SET `token` = 'SE_DStacker'; + WHEN '450' THEN SET `token` = 'SE_MitigateDotDamage'; + WHEN '451' THEN SET `token` = 'SE_MeleeThresholdGuard'; + WHEN '452' THEN SET `token` = 'SE_SpellThresholdGuard'; + WHEN '453' THEN SET `token` = 'SE_TriggerMeleeThreshold'; + WHEN '454' THEN SET `token` = 'SE_TriggerSpellThreshold'; + WHEN '455' THEN SET `token` = 'SE_AddHatePct'; + WHEN '456' THEN SET `token` = 'SE_AddHateOverTimePct'; + WHEN '457' THEN SET `token` = 'SE_ResourceTap'; + WHEN '458' THEN SET `token` = 'SE_FactionModPct'; + WHEN '459' THEN SET `token` = 'SE_DamageModifier2'; + WHEN '460' THEN SET `token` = 'SE_Ff_Override_NotFocusable'; + WHEN '461' THEN SET `token` = 'SE_ImprovedDamage2'; + WHEN '462' THEN SET `token` = 'SE_FcDamageAmt2'; + WHEN '463' THEN SET `token` = 'SE_Shield_Target'; + WHEN '464' THEN SET `token` = 'SE_PC_Pet_Rampage'; + WHEN '465' THEN SET `token` = 'SE_PC_Pet_AE_Rampage'; + WHEN '466' THEN SET `token` = 'SE_PC_Pet_Flurry_Chance'; + WHEN '467' THEN SET `token` = 'SE_DS_Mitigation_Amount'; + WHEN '468' THEN SET `token` = 'SE_DS_Mitigation_Percentage'; + WHEN '469' THEN SET `token` = 'SE_Chance_Best_in_Spell_Grp'; + WHEN '470' THEN SET `token` = 'SE_Trigger_Best_in_Spell_Grp'; + WHEN '471' THEN SET `token` = 'SE_Double_Melee_Round'; + ELSE SET `token` = 'unk'; + END CASE; + + SET `token` = CONCAT(`token`, '(', `effect_id`, ')'); + + RETURN `token`; +END$$ + +DELIMITER ; \ No newline at end of file From a6a056ad0d3ddaa17fde0788babad7f908ff34b9 Mon Sep 17 00:00:00 2001 From: Uleat Date: Sat, 4 Feb 2017 19:52:21 -0500 Subject: [PATCH 40/50] Added rest of public spell effect list. Changed unimplemented effects to "NI_"##token format [skip ci] --- utils/scripts/spell_effect_token_function.sql | 137 +++++++++++------- 1 file changed, 81 insertions(+), 56 deletions(-) diff --git a/utils/scripts/spell_effect_token_function.sql b/utils/scripts/spell_effect_token_function.sql index ee5f9afc8..38cba8ed0 100644 --- a/utils/scripts/spell_effect_token_function.sql +++ b/utils/scripts/spell_effect_token_function.sql @@ -27,8 +27,8 @@ BEGIN WHEN '13' THEN SET `token` = 'SE_SeeInvis'; WHEN '14' THEN SET `token` = 'SE_WaterBreathing'; WHEN '15' THEN SET `token` = 'SE_CurrentMana'; - WHEN '16' THEN SET `token` = 'SE_NPCFrenzy'; - WHEN '17' THEN SET `token` = 'SE_NPCAwareness'; + WHEN '16' THEN SET `token` = 'NI_NPCFrenzy'; + WHEN '17' THEN SET `token` = 'NI_NPCAwareness'; WHEN '18' THEN SET `token` = 'SE_Lull'; WHEN '19' THEN SET `token` = 'SE_AddFaction'; WHEN '20' THEN SET `token` = 'SE_Blind'; @@ -45,12 +45,12 @@ BEGIN WHEN '31' THEN SET `token` = 'SE_Mez'; WHEN '32' THEN SET `token` = 'SE_SummonItem'; WHEN '33' THEN SET `token` = 'SE_SummonPet'; - WHEN '34' THEN SET `token` = 'SE_Confuse'; + WHEN '34' THEN SET `token` = 'NI_Confuse'; WHEN '35' THEN SET `token` = 'SE_DiseaseCounter'; WHEN '36' THEN SET `token` = 'SE_PoisonCounter'; - WHEN '37' THEN SET `token` = 'SE_DetectHostile'; - WHEN '38' THEN SET `token` = 'SE_DetectMagic'; - WHEN '39' THEN SET `token` = 'SE_DetectPoison'; + WHEN '37' THEN SET `token` = 'NI_DetectHostile'; + WHEN '38' THEN SET `token` = 'NI_DetectMagic'; + WHEN '39' THEN SET `token` = 'NI_DetectPoison'; WHEN '40' THEN SET `token` = 'SE_DivineAura'; WHEN '41' THEN SET `token` = 'SE_Destroy'; WHEN '42' THEN SET `token` = 'SE_ShadowStep'; @@ -62,7 +62,7 @@ BEGIN WHEN '48' THEN SET `token` = 'SE_ResistPoison'; WHEN '49' THEN SET `token` = 'SE_ResistDisease'; WHEN '50' THEN SET `token` = 'SE_ResistMagic'; - WHEN '51' THEN SET `token` = 'SE_DetectTraps'; + WHEN '51' THEN SET `token` = 'NI_DetectTraps'; WHEN '52' THEN SET `token` = 'SE_SenseDead'; WHEN '53' THEN SET `token` = 'SE_SenseSummoned'; WHEN '54' THEN SET `token` = 'SE_SenseAnimals'; @@ -71,9 +71,9 @@ BEGIN WHEN '57' THEN SET `token` = 'SE_Levitate'; WHEN '58' THEN SET `token` = 'SE_Illusion'; WHEN '59' THEN SET `token` = 'SE_DamageShield'; - WHEN '60' THEN SET `token` = 'SE_TransferItem'; + WHEN '60' THEN SET `token` = 'NI_TransferItem'; WHEN '61' THEN SET `token` = 'SE_Identify'; - WHEN '62' THEN SET `token` = 'SE_ItemID'; + WHEN '62' THEN SET `token` = 'NI_ItemID'; WHEN '63' THEN SET `token` = 'SE_WipeHateList'; WHEN '64' THEN SET `token` = 'SE_SpinTarget'; WHEN '65' THEN SET `token` = 'SE_InfraVision'; @@ -81,9 +81,9 @@ BEGIN WHEN '67' THEN SET `token` = 'SE_EyeOfZomm'; WHEN '68' THEN SET `token` = 'SE_ReclaimPet'; WHEN '69' THEN SET `token` = 'SE_TotalHP'; - WHEN '70' THEN SET `token` = 'SE_CorpseBomb'; + WHEN '70' THEN SET `token` = 'NI_CorpseBomb'; WHEN '71' THEN SET `token` = 'SE_NecPet'; - WHEN '72' THEN SET `token` = 'SE_PreserveCorpse'; + WHEN '72' THEN SET `token` = 'NI_PreserveCorpse'; WHEN '73' THEN SET `token` = 'SE_BindSight'; WHEN '74' THEN SET `token` = 'SE_FeignDeath'; WHEN '75' THEN SET `token` = 'SE_VoiceGraft'; @@ -91,7 +91,7 @@ BEGIN WHEN '77' THEN SET `token` = 'SE_LocateCorpse'; WHEN '78' THEN SET `token` = 'SE_AbsorbMagicAtt'; WHEN '79' THEN SET `token` = 'SE_CurrentHPOnce'; - WHEN '80' THEN SET `token` = 'SE_EnchantLight'; + WHEN '80' THEN SET `token` = 'NI_EnchantLight'; WHEN '81' THEN SET `token` = 'SE_Revive'; WHEN '82' THEN SET `token` = 'SE_SummonPC'; WHEN '83' THEN SET `token` = 'SE_Teleport'; @@ -101,7 +101,7 @@ BEGIN WHEN '87' THEN SET `token` = 'SE_MagnifyVision'; WHEN '88' THEN SET `token` = 'SE_Succor'; WHEN '89' THEN SET `token` = 'SE_ModelSize'; - WHEN '90' THEN SET `token` = 'SE_Cloak'; + WHEN '90' THEN SET `token` = 'NI_Cloak'; WHEN '91' THEN SET `token` = 'SE_SummonCorpse'; WHEN '92' THEN SET `token` = 'SE_InstantHate'; WHEN '93' THEN SET `token` = 'SE_StopRain'; @@ -121,7 +121,7 @@ BEGIN WHEN '107' THEN SET `token` = 'SE_AlterNPCLevel'; WHEN '108' THEN SET `token` = 'SE_Familiar'; WHEN '109' THEN SET `token` = 'SE_SummonItemIntoBag'; - WHEN '110' THEN SET `token` = 'SE_IncreaseArchery'; + WHEN '110' THEN SET `token` = 'NI_IncreaseArchery'; WHEN '111' THEN SET `token` = 'SE_ResistAll'; WHEN '112' THEN SET `token` = 'SE_CastingLevel'; WHEN '113' THEN SET `token` = 'SE_SummonHorse'; @@ -133,7 +133,7 @@ BEGIN WHEN '119' THEN SET `token` = 'SE_AttackSpeed3'; WHEN '120' THEN SET `token` = 'SE_HealRate'; WHEN '121' THEN SET `token` = 'SE_ReverseDS'; - WHEN '122' THEN SET `token` = 'SE_ReduceSkill'; + WHEN '122' THEN SET `token` = 'NI_ReduceSkill'; WHEN '123' THEN SET `token` = 'SE_Screech'; WHEN '124' THEN SET `token` = 'SE_ImprovedDamage'; WHEN '125' THEN SET `token` = 'SE_ImprovedHeal'; @@ -157,7 +157,7 @@ BEGIN WHEN '143' THEN SET `token` = 'SE_LimitCastTimeMin'; WHEN '144' THEN SET `token` = 'SE_LimitCastTimeMax'; WHEN '145' THEN SET `token` = 'SE_Teleport2'; - WHEN '146' THEN SET `token` = 'SE_ElectricityResist'; + WHEN '146' THEN SET `token` = 'NI_ElectricityResist'; WHEN '147' THEN SET `token` = 'SE_PercentalHeal'; WHEN '148' THEN SET `token` = 'SE_StackingCommand_Block'; WHEN '149' THEN SET `token` = 'SE_StackingCommand_Overwrite'; @@ -171,7 +171,7 @@ BEGIN WHEN '157' THEN SET `token` = 'SE_SpellDamageShield'; WHEN '158' THEN SET `token` = 'SE_Reflect'; WHEN '159' THEN SET `token` = 'SE_AllStats'; - WHEN '160' THEN SET `token` = 'SE_MakeDrunk'; + WHEN '160' THEN SET `token` = 'NI_MakeDrunk'; WHEN '161' THEN SET `token` = 'SE_MitigateSpellDamage'; WHEN '162' THEN SET `token` = 'SE_MitigateMeleeDamage'; WHEN '163' THEN SET `token` = 'SE_NegateAttacks'; @@ -219,9 +219,9 @@ BEGIN WHEN '205' THEN SET `token` = 'SE_Rampage'; WHEN '206' THEN SET `token` = 'SE_AETaunt'; WHEN '207' THEN SET `token` = 'SE_FleshToBone'; - WHEN '208' THEN SET `token` = 'SE_PurgePoison'; + WHEN '208' THEN SET `token` = 'NI_PurgePoison'; WHEN '209' THEN SET `token` = 'SE_DispelBeneficial'; - WHEN '210' THEN SET `token` = 'SE_PetShield'; + WHEN '210' THEN SET `token` = 'NI_PetShield'; WHEN '211' THEN SET `token` = 'SE_AEMelee'; WHEN '212' THEN SET `token` = 'SE_FrenziedDevastation'; WHEN '213' THEN SET `token` = 'SE_PetMaxHP'; @@ -247,11 +247,11 @@ BEGIN WHEN '233' THEN SET `token` = 'SE_Metabolism'; WHEN '234' THEN SET `token` = 'SE_ReduceApplyPoisonTime'; WHEN '235' THEN SET `token` = 'SE_ChannelChanceSpells'; - WHEN '236' THEN SET `token` = 'SE_FreePet'; + WHEN '236' THEN SET `token` = 'NI_FreePet'; WHEN '237' THEN SET `token` = 'SE_GivePetGroupTarget'; WHEN '238' THEN SET `token` = 'SE_IllusionPersistence'; WHEN '239' THEN SET `token` = 'SE_FeignedCastOnChance'; - WHEN '240' THEN SET `token` = 'SE_StringUnbreakable'; + WHEN '240' THEN SET `token` = 'NI_StringUnbreakable'; WHEN '241' THEN SET `token` = 'SE_ImprovedReclaimEnergy'; WHEN '242' THEN SET `token` = 'SE_IncreaseChanceMemwipe'; WHEN '243' THEN SET `token` = 'SE_CharmBreakChance'; @@ -295,7 +295,7 @@ BEGIN WHEN '281' THEN SET `token` = 'SE_FeignedMinion'; WHEN '282' THEN SET `token` = 'SE_ImprovedBindWound'; WHEN '283' THEN SET `token` = 'SE_DoubleSpecialAttack'; - WHEN '284' THEN SET `token` = 'SE_LoHSetHeal'; + WHEN '284' THEN SET `token` = 'NI_LoHSetHeal'; WHEN '285' THEN SET `token` = 'SE_NimbleEvasion'; WHEN '286' THEN SET `token` = 'SE_FcDamageAmt'; WHEN '287' THEN SET `token` = 'SE_SpellDurationIncByTic'; @@ -306,7 +306,7 @@ BEGIN WHEN '292' THEN SET `token` = 'SE_StrikeThrough2'; WHEN '293' THEN SET `token` = 'SE_FrontalStunResist'; WHEN '294' THEN SET `token` = 'SE_CriticalSpellChance'; - WHEN '295' THEN SET `token` = 'SE_ReduceTimerSpecial'; + WHEN '295' THEN SET `token` = 'NI_ReduceTimerSpecial'; WHEN '296' THEN SET `token` = 'SE_FcSpellVulnerability'; WHEN '297' THEN SET `token` = 'SE_FcDamageAmtIncoming'; WHEN '298' THEN SET `token` = 'SE_ChangeHeight'; @@ -317,8 +317,8 @@ BEGIN WHEN '303' THEN SET `token` = 'SE_FcDamageAmtCrit'; WHEN '304' THEN SET `token` = 'SE_OffhandRiposteFail'; WHEN '305' THEN SET `token` = 'SE_MitigateDamageShield'; - WHEN '306' THEN SET `token` = 'SE_ArmyOfTheDead'; - WHEN '307' THEN SET `token` = 'SE_Appraisal'; + WHEN '306' THEN SET `token` = 'NI_ArmyOfTheDead'; + WHEN '307' THEN SET `token` = 'NI_Appraisal'; WHEN '308' THEN SET `token` = 'SE_SuspendMinion'; WHEN '309' THEN SET `token` = 'SE_GateCastersBindpoint'; WHEN '310' THEN SET `token` = 'SE_ReduceReuseTimer'; @@ -327,7 +327,7 @@ BEGIN WHEN '313' THEN SET `token` = 'SE_ForageAdditionalItems'; WHEN '314' THEN SET `token` = 'SE_Invisibility2'; WHEN '315' THEN SET `token` = 'SE_InvisVsUndead2'; - WHEN '316' THEN SET `token` = 'SE_ImprovedInvisAnimals'; + WHEN '316' THEN SET `token` = 'NI_ImprovedInvisAnimals'; WHEN '317' THEN SET `token` = 'SE_ItemHPRegenCapIncrease'; WHEN '318' THEN SET `token` = 'SE_ItemManaRegenCapIncrease'; WHEN '319' THEN SET `token` = 'SE_CriticalHealOverTime'; @@ -347,7 +347,7 @@ BEGIN WHEN '333' THEN SET `token` = 'SE_CastOnRuneFadeEffect'; WHEN '334' THEN SET `token` = 'SE_BardAEDot'; WHEN '335' THEN SET `token` = 'SE_BlockNextSpellFocus'; - WHEN '336' THEN SET `token` = 'SE_IllusionaryTarget'; + WHEN '336' THEN SET `token` = 'NI_IllusionaryTarget'; WHEN '337' THEN SET `token` = 'SE_PercentXPIncrease'; WHEN '338' THEN SET `token` = 'SE_SummonAndResAllCorpses'; WHEN '339' THEN SET `token` = 'SE_TriggerOnCast'; @@ -362,15 +362,15 @@ BEGIN WHEN '348' THEN SET `token` = 'SE_LimitManaMin'; WHEN '349' THEN SET `token` = 'SE_ShieldEquipDmgMod'; WHEN '350' THEN SET `token` = 'SE_ManaBurn'; - WHEN '351' THEN SET `token` = 'SE_PersistentEffect'; - WHEN '352' THEN SET `token` = 'SE_IncreaseTrapCount'; - WHEN '353' THEN SET `token` = 'SE_AdditionalAura'; - WHEN '354' THEN SET `token` = 'SE_DeactivateAllTraps'; - WHEN '355' THEN SET `token` = 'SE_LearnTrap'; - WHEN '356' THEN SET `token` = 'SE_ChangeTriggerType'; + WHEN '351' THEN SET `token` = 'NI_PersistentEffect'; + WHEN '352' THEN SET `token` = 'NI_IncreaseTrapCount'; + WHEN '353' THEN SET `token` = 'NI_AdditionalAura'; + WHEN '354' THEN SET `token` = 'NI_DeactivateAllTraps'; + WHEN '355' THEN SET `token` = 'NI_LearnTrap'; + WHEN '356' THEN SET `token` = 'NI_ChangeTriggerType'; WHEN '357' THEN SET `token` = 'SE_FcMute'; WHEN '358' THEN SET `token` = 'SE_CurrentManaOnce'; - WHEN '359' THEN SET `token` = 'SE_PassiveSenseTrap'; + WHEN '359' THEN SET `token` = 'NI_PassiveSenseTrap'; WHEN '360' THEN SET `token` = 'SE_ProcOnKillShot'; WHEN '361' THEN SET `token` = 'SE_SpellOnDeath'; WHEN '362' THEN SET `token` = 'SE_PotionBeltSlots'; @@ -379,7 +379,7 @@ BEGIN WHEN '365' THEN SET `token` = 'SE_ProcOnSpellKillShot'; WHEN '366' THEN SET `token` = 'SE_GroupShielding'; WHEN '367' THEN SET `token` = 'SE_SetBodyType'; - WHEN '368' THEN SET `token` = 'SE_FactionMod'; + WHEN '368' THEN SET `token` = 'NI_FactionMod'; WHEN '369' THEN SET `token` = 'SE_CorruptionCounter'; WHEN '370' THEN SET `token` = 'SE_ResistCorruption'; WHEN '371' THEN SET `token` = 'SE_AttackSpeed4'; @@ -387,21 +387,21 @@ BEGIN WHEN '373' THEN SET `token` = 'SE_CastOnFadeEffectAlways'; WHEN '374' THEN SET `token` = 'SE_ApplyEffect'; WHEN '375' THEN SET `token` = 'SE_DotCritDmgIncrease'; - WHEN '376' THEN SET `token` = 'SE_Fling'; + WHEN '376' THEN SET `token` = 'NI_Fling'; WHEN '377' THEN SET `token` = 'SE_CastOnFadeEffectNPC'; WHEN '378' THEN SET `token` = 'SE_SpellEffectResistChance'; WHEN '379' THEN SET `token` = 'SE_ShadowStepDirectional'; WHEN '380' THEN SET `token` = 'SE_Knockdown'; - WHEN '381' THEN SET `token` = 'SE_KnockTowardCaster'; + WHEN '381' THEN SET `token` = 'NI_KnockTowardCaster'; WHEN '382' THEN SET `token` = 'SE_NegateSpellEffect'; WHEN '383' THEN SET `token` = 'SE_SympatheticProc'; WHEN '384' THEN SET `token` = 'SE_Leap'; WHEN '385' THEN SET `token` = 'SE_LimitSpellGroup'; WHEN '386' THEN SET `token` = 'SE_CastOnCurer'; WHEN '387' THEN SET `token` = 'SE_CastOnCure'; - WHEN '388' THEN SET `token` = 'SE_SummonCorpseZone'; + WHEN '388' THEN SET `token` = 'NI_SummonCorpseZone'; WHEN '389' THEN SET `token` = 'SE_FcTimerRefresh'; - WHEN '390' THEN SET `token` = 'SE_FcTimerLockout'; + WHEN '390' THEN SET `token` = 'NI_FcTimerLockout'; WHEN '391' THEN SET `token` = 'SE_LimitManaMax'; WHEN '392' THEN SET `token` = 'SE_FcHealAmt'; WHEN '393' THEN SET `token` = 'SE_FcHealPctIncoming'; @@ -426,7 +426,7 @@ BEGIN WHEN '412' THEN SET `token` = 'SE_LimitRace'; WHEN '413' THEN SET `token` = 'SE_FcBaseEffects'; WHEN '414' THEN SET `token` = 'SE_LimitCastingSkill'; - WHEN '415' THEN SET `token` = 'SE_FFItemClass'; + WHEN '415' THEN SET `token` = 'NI_FFItemClass'; WHEN '416' THEN SET `token` = 'SE_ACv2'; WHEN '417' THEN SET `token` = 'SE_ManaRegen_v2'; WHEN '418' THEN SET `token` = 'SE_SkillDamageAmount2'; @@ -436,27 +436,27 @@ BEGIN WHEN '422' THEN SET `token` = 'SE_LimitUseMin'; WHEN '423' THEN SET `token` = 'SE_LimitUseType'; WHEN '424' THEN SET `token` = 'SE_GravityEffect'; - WHEN '425' THEN SET `token` = 'SE_Display'; + WHEN '425' THEN SET `token` = 'NI_Display'; WHEN '426' THEN SET `token` = 'SE_IncreaseExtTargetWindow'; WHEN '427' THEN SET `token` = 'SE_SkillProc'; WHEN '428' THEN SET `token` = 'SE_LimitToSkill'; WHEN '429' THEN SET `token` = 'SE_SkillProcSuccess'; - WHEN '430' THEN SET `token` = 'SE_PostEffect'; - WHEN '431' THEN SET `token` = 'SE_PostEffectData'; - WHEN '432' THEN SET `token` = 'SE_ExpandMaxActiveTrophyBen'; + WHEN '430' THEN SET `token` = 'NI_PostEffect'; + WHEN '431' THEN SET `token` = 'NI_PostEffectData'; + WHEN '432' THEN SET `token` = 'NI_ExpandMaxActiveTrophyBen'; WHEN '433' THEN SET `token` = 'SE_CriticalDotDecay'; WHEN '434' THEN SET `token` = 'SE_CriticalHealDecay'; WHEN '435' THEN SET `token` = 'SE_CriticalRegenDecay'; - WHEN '436' THEN SET `token` = 'SE_BeneficialCountDownHold'; - WHEN '437' THEN SET `token` = 'SE_TeleporttoAnchor'; - WHEN '438' THEN SET `token` = 'SE_TranslocatetoAnchor'; + WHEN '436' THEN SET `token` = 'NI_BeneficialCountDownHold'; + WHEN '437' THEN SET `token` = 'NI_TeleporttoAnchor'; + WHEN '438' THEN SET `token` = 'NI_TranslocatetoAnchor'; WHEN '439' THEN SET `token` = 'SE_Assassinate'; WHEN '440' THEN SET `token` = 'SE_FinishingBlowLvl'; WHEN '441' THEN SET `token` = 'SE_DistanceRemoval'; WHEN '442' THEN SET `token` = 'SE_TriggerOnReqTarget'; WHEN '443' THEN SET `token` = 'SE_TriggerOnReqCaster'; WHEN '444' THEN SET `token` = 'SE_ImprovedTaunt'; - WHEN '445' THEN SET `token` = 'SE_AddMercSlot'; + WHEN '445' THEN SET `token` = 'NI_AddMercSlot'; WHEN '446' THEN SET `token` = 'SE_AStacker'; WHEN '447' THEN SET `token` = 'SE_BStacker'; WHEN '448' THEN SET `token` = 'SE_CStacker'; @@ -471,18 +471,43 @@ BEGIN WHEN '457' THEN SET `token` = 'SE_ResourceTap'; WHEN '458' THEN SET `token` = 'SE_FactionModPct'; WHEN '459' THEN SET `token` = 'SE_DamageModifier2'; - WHEN '460' THEN SET `token` = 'SE_Ff_Override_NotFocusable'; + WHEN '460' THEN SET `token` = 'NI_Ff_Override_NotFocusable'; WHEN '461' THEN SET `token` = 'SE_ImprovedDamage2'; WHEN '462' THEN SET `token` = 'SE_FcDamageAmt2'; - WHEN '463' THEN SET `token` = 'SE_Shield_Target'; + WHEN '463' THEN SET `token` = 'NI_Shield_Target'; WHEN '464' THEN SET `token` = 'SE_PC_Pet_Rampage'; - WHEN '465' THEN SET `token` = 'SE_PC_Pet_AE_Rampage'; + WHEN '465' THEN SET `token` = 'NI_PC_Pet_AE_Rampage'; WHEN '466' THEN SET `token` = 'SE_PC_Pet_Flurry_Chance'; - WHEN '467' THEN SET `token` = 'SE_DS_Mitigation_Amount'; - WHEN '468' THEN SET `token` = 'SE_DS_Mitigation_Percentage'; - WHEN '469' THEN SET `token` = 'SE_Chance_Best_in_Spell_Grp'; - WHEN '470' THEN SET `token` = 'SE_Trigger_Best_in_Spell_Grp'; - WHEN '471' THEN SET `token` = 'SE_Double_Melee_Round'; + WHEN '467' THEN SET `token` = 'NI_DS_Mitigation_Amount'; + WHEN '468' THEN SET `token` = 'NI_DS_Mitigation_Percentage'; + WHEN '469' THEN SET `token` = 'NI_Chance_Best_in_Spell_Grp'; + WHEN '470' THEN SET `token` = 'NI_Trigger_Best_in_Spell_Grp'; + WHEN '471' THEN SET `token` = 'NI_Double_Melee_Round'; +-- these are not defined + WHEN '472' THEN SET `token` = 'NI_Buy_AA_Rank'; + WHEN '473' THEN SET `token` = 'NI_Double_Backstab_From_Front'; + WHEN '474' THEN SET `token` = 'NI_Pet_Crit_Melee_Damage_Pct'; + WHEN '475' THEN SET `token` = 'NI_Trigger_Spell_Non_Item'; + WHEN '476' THEN SET `token` = 'NI_Weapon_Stance'; + WHEN '477' THEN SET `token` = 'NI_Hatelist_To_Top_Index'; + WHEN '478' THEN SET `token` = 'NI_Hatelist_To_Tail_Index'; + WHEN '479' THEN SET `token` = 'NI_Ff_Value_Min'; + WHEN '480' THEN SET `token` = 'NI_Ff_Value_Max'; + WHEN '481' THEN SET `token` = 'NI_Fc_Cast_Spell_On_Land'; + WHEN '482' THEN SET `token` = 'NI_Skill_Base_Damage_Mod'; + WHEN '483' THEN SET `token` = 'NI_Fc_Spell_Damage_Pct_IncomingPC'; + WHEN '484' THEN SET `token` = 'NI_Fc_Spell_Damage_Amt_IncomingPC'; + WHEN '485' THEN SET `token` = 'NI_Ff_CasterClass'; + WHEN '486' THEN SET `token` = 'NI_Ff_Same_Caster'; + WHEN '487' THEN SET `token` = 'NI_Extend_Tradeskill_Cap'; + WHEN '488' THEN SET `token` = 'NI_Defender_Melee_Force_Pct'; + WHEN '489' THEN SET `token` = 'NI_Worn_Endurance_Regen_Cap'; + WHEN '490' THEN SET `token` = 'NI_Ff_ReuseTimeMin'; + WHEN '491' THEN SET `token` = 'NI_Ff_ReuseTimeMax'; + WHEN '492' THEN SET `token` = 'NI_Ff_Endurance_Min'; + WHEN '493' THEN SET `token` = 'NI_Ff_Endurance_Max'; + WHEN '494' THEN SET `token` = 'NI_Pet_Add_Atk'; + WHEN '495' THEN SET `token` = 'NI_Ff_DurationMax'; ELSE SET `token` = 'unk'; END CASE; From df1d499da6673bd49d00eb6c120708168bb6d1e4 Mon Sep 17 00:00:00 2001 From: Uleat Date: Sun, 5 Feb 2017 02:20:40 -0500 Subject: [PATCH 41/50] Imported RoF2 race names --- common/races.cpp | 1359 ++++++++++++++++++++++++++++++++++++++++++++-- common/races.h | 735 +++++++++++++++++++++++++ 2 files changed, 2051 insertions(+), 43 deletions(-) diff --git a/common/races.cpp b/common/races.cpp index 7fd88911b..9d5fbdda0 100644 --- a/common/races.cpp +++ b/common/races.cpp @@ -21,53 +21,1326 @@ const char* GetRaceIDName(uint16 race_id) { switch (race_id) { - case HUMAN: - return "Human"; - case BARBARIAN: + // Old Race Labels + //case HUMAN: + // return "Human"; + //case BARBARIAN: + // return "Barbarian"; + //case ERUDITE: + // return "Erudite"; + //case WOOD_ELF: + // return "Wood Elf"; + //case HIGH_ELF: + // return "High Elf"; + //case DARK_ELF: + // return "Dark Elf"; + //case HALF_ELF: + // return "Half Elf"; + //case DWARF: + // return "Dwarf"; + //case TROLL: + // return "Troll"; + //case OGRE: + // return "Ogre"; + //case HALFLING: + // return "Halfling"; + //case GNOME: + // return "Gnome"; + //case IKSAR: + // return "Iksar"; + //case WEREWOLF: + // return "Werewolf"; + //case SKELETON: + // return "Skeleton"; + //case ELEMENTAL: + // return "Elemental"; + //case EYE_OF_ZOMM: + // return "Eye of Zomm"; + //case WOLF_ELEMENTAL: + // return "Wolf Elemental"; + //case IKSAR_SKELETON: + // return "Iksar Skeleton"; + //case VAHSHIR: + // return "Vah Shir"; + //case FROGLOK: + //case FROGLOK2: // "Piranha"? (low-byte issue..) + // return "Froglok"; + //case DRAKKIN: + // return "Drakkin"; + + // RoF2 Race Labels + case RT_ABHORRENT: + return "Abhorrent"; + case RT_AIR_ELEMENTAL: + case RT_AIR_ELEMENTAL_2: + return "Air Elemental"; + case RT_AIR_MEPHIT: + return "Air Mephit"; + case RT_AKHEVA: + case RT_AKHEVA_2: + return "Akheva"; + case RT_ALARAN: + return "Alaran"; + case RT_ALARAN_GHOST: + return "Alaran Ghost"; + case RT_ALARAN_SENTRY_STONE: + return "Alaran Sentry Stone"; + case RT_ALLIGATOR: + case RT_ALLIGATOR_2: + return "Alligator"; + case RT_AMYGDALAN: + case RT_AMYGDALAN_2: + return "Amygdalan"; + case RT_ANEUK: + return "Aneuk"; + case RT_ANIMATED_ARMOR: + return "Animated Armor"; + case RT_ANIMATED_HAND: + return "Animated Hand"; + case RT_ANIMATED_STATUE: + case RT_ANIMATED_STATUE_2: + return "Animated Statue"; + case RT_APEXUS: + return "Apexus"; + case RT_ARACHNID: + return "Arachnid"; + case RT_ARCANIST_OF_HATE: + return "Arcanist of Hate"; + case RT_ARMADILLO: + return "Armadillo"; + case RT_ARMOR_RACK: + return "Armor Rack"; + case RT_AVIAK: + case RT_AVIAK_2: + return "Aviak"; + case RT_AVIAK_PULL_ALONG: + return "Aviak Pull Along"; + case RT_AYONAE_RO: + return "Ayonae Ro"; + case RT_BANNER: + case RT_BANNER_2: + case RT_BANNER_3: + case RT_BANNER_4: + case RT_BANNER_5: + case RT_BANNER_6: + case RT_BANNER_7: + return "Banner"; + case RT_BANNER_10TH_ANNIVERSARY: + return "10th Anniversary Banner"; + case RT_BANSHEE: + case RT_BANSHEE_2: + case RT_BANSHEE_3: + return "Banshee"; + case RT_BARBARIAN: + case RT_BARBARIAN_2: return "Barbarian"; - case ERUDITE: - return "Erudite"; - case WOOD_ELF: - return "Wood Elf"; - case HIGH_ELF: - return "High Elf"; - case DARK_ELF: + case RT_BARREL: + return "Barrel"; + case RT_BARREL_BARGE_SHIP: + return "Barrel Barge Ship"; + case RT_BASILISK: + return "Basilisk"; + case RT_BAT: + case RT_BAT_2: + case RT_BAT_3: + return "Bat"; + case RT_BAZU: + return "Bazu"; + case RT_BEAR: + case RT_BEAR_2: + case RT_BEAR_3: + return "Bear"; + case RT_BEAR_MOUNT: + return "Bear Mount"; + case RT_BEAR_TRAP: + return "Bear Trap"; + case RT_BEETLE: + case RT_BEETLE_2: + case RT_BEETLE_3: + return "Beetle"; + case RT_BEGGAR: + return "Beggar"; + case RT_BELLIKOS: + return "Bellikos"; + case RT_BERTOXXULOUS: + case RT_BERTOXXULOUS_2: + return "Bertoxxulous"; + case RT_BIXIE: + case RT_BIXIE_2: + return "Bixie"; + case RT_BLIMP_SHIP: + return "Blimp Ship"; + case RT_BLIND_DREAMER: + return "Blind Dreamer"; + case RT_BLOOD_RAVEN: + return "Blood Raven"; + case RT_BOAT: + case RT_BOAT_2: + return "Boat"; + case RT_BOLVIRK: + return "Bolvirk"; + case RT_BONE_GOLEM: + case RT_BONE_GOLEM_2: + return "Bone Golem"; + case RT_BONES: + return "Bones"; + case RT_BOOK_DERVISH: + return "Book Dervish"; + case RT_BOT_PORTAL: + return "BoT Portal"; + case RT_BOULDER: + return "Boulder"; + case RT_BOX: + return "Box"; + case RT_BRAXI: + return "Braxi"; + case RT_BRAXI_MOUNT: + return "Braxi Mount"; + case RT_BRELL: + return "Brell"; + case RT_BRELLS_FIRST_CREATION: + return "Brell's First Creation"; + case RT_BRISTLEBANE: + return "Bristlebane"; + case RT_BROKEN_CLOCKWORK: + return "Broken Clockwork"; + case RT_BRONTOTHERIUM: + return "Brontotherium"; + case RT_BROWNIE: + case RT_BROWNIE_2: + return "Brownie"; + case RT_BUBONIAN: + return "Bubonian"; + case RT_BUBONIAN_UNDERLING: + return "Bubonian Underling"; + case RT_BURYNAI: + case RT_BURYNAI_2: + return "Burynai"; + case RT_CAKE_10TH_ANNIVERSARY: + return "10th Anniversary Cake"; + case RT_CAMPFIRE: + return "Campfire"; + case RT_CARRIER_HAND: + return "Carrier Hand"; + case RT_CAT: + return "Cat"; + case RT_CAZIC_THULE: + case RT_CAZIC_THULE_2: + return "Cazic Thule"; + case RT_CENTAUR: + case RT_CENTAUR_2: + return "Centaur"; + case RT_CHEST: + case RT_CHEST_2: + case RT_CHEST_3: + return "Chest"; + case RT_CHIMERA: + case RT_CHIMERA_2: + return "Chimera"; + case RT_CHOKIDAI: + return "Chokidai"; + case RT_CLAM: + return "Clam"; + case RT_CLIKNAR_MOUNT: + return "Cliknar Mount"; + case RT_CLIKNAR_QUEEN: + return "Cliknar Queen"; + case RT_CLIKNAR_SOLDIER: + return "Cliknar Soldier"; + case RT_CLIKNAR_WORKER: + return "Cliknar Worker"; + case RT_CLOCKWORK_BEETLE: + return "Clockwork Beetle"; + case RT_CLOCKWORK_BOAR: + return "Clockwork Boar"; + case RT_CLOCKWORK_BOMB: + return "Clockwork Bomb"; + case RT_CLOCKWORK_BRAIN: + return "Clockwork Brain"; + case RT_CLOCKWORK_GNOME: + return "Clockwork Gnome"; + case RT_CLOCKWORK_GOLEM: + return "Clockwork Golem"; + case RT_CLOCKWORK_GUARDIAN: + return "Clockwork Guardian"; + case RT_COCKATRICE: + return "Cockatrice"; + case RT_COFFIN: + case RT_COFFIN_2: + return "Coffin"; + case RT_COIN_PURSE: + return "Coin Purse"; + case RT_COLDAIN: + case RT_COLDAIN_2: + case RT_COLDAIN_3: + return "Coldain"; + case RT_CORAL: + return "Coral"; + case RT_CORATHUS: + return "Corathus"; + case RT_CRAB: + return "Crab"; + case RT_CRAGBEAST: + return "Cragbeast"; + case RT_CRAGSLITHER: + return "Cragslither"; + case RT_CROCODILE: + return "Crocodile"; + case RT_CRYSTAL: + return "Crystal"; + case RT_CRYSTAL_SHARD: + return "Crystal Shard"; + case RT_CRYSTAL_SPHERE: + return "Crystal Sphere"; + case RT_CRYSTAL_SPIDER: + return "Crystal Spider"; + case RT_CRYSTALSKIN_AMBULOID: + return "Crystalskin Ambuloid"; + case RT_CRYSTALSKIN_SESSILOID: + return "Crystalskin Sessiloid"; + case RT_DAISY_MAN: + return "Daisy Man"; + case RT_DARK_ELF: + case RT_DARK_ELF_2: return "Dark Elf"; - case HALF_ELF: - return "Half Elf"; - case DWARF: - return "Dwarf"; - case TROLL: - return "Troll"; - case OGRE: - return "Ogre"; - case HALFLING: - return "Halfling"; - case GNOME: - return "Gnome"; - case IKSAR: - return "Iksar"; - case WEREWOLF: - return "Werewolf"; - case SKELETON: - return "Skeleton"; - case ELEMENTAL: - return "Elemental"; - case EYE_OF_ZOMM: - return "Eye of Zomm"; - case WOLF_ELEMENTAL: - return "Wolf Elemental"; - case IKSAR_SKELETON: - return "Iksar Skeleton"; - case VAHSHIR: - return "Vah Shir"; - case FROGLOK: - case FROGLOK2: - return "Froglok"; - case DRAKKIN: + case RT_DARK_LORD: + return "Dark Lord"; + case RT_DEMI_LICH: + return "Demi Lich"; + case RT_DEMON_VULTURE: + return "Demon Vulture"; + case RT_DERVISH: + case RT_DERVISH_2: + case RT_DERVISH_3: + case RT_DERVISH_4: + return "Dervish"; + case RT_DERVISH_VER_5: + return "Dervish(Ver. 5)"; + case RT_DERVISH_VER_6: + return "Dervish(Ver. 6)"; + case RT_DEVOURER: + return "Devourer"; + case RT_DIRE_WOLF: + return "Dire Wolf"; + case RT_DISCORD_SHIP: + return "Discord Ship"; + case RT_DISCORDLING: + return "Discordling"; + case RT_DISEASED_FIEND: + return "Diseased Fiend"; + case RT_DJINN: + return "Djinn"; + case RT_DRACHNID: + case RT_DRACHNID_2: + return "Drachnid"; + case RT_DRACHNID_COCOON: + return "Drachnid Cocoon"; + case RT_DRACOLICH: + return "Dracolich"; + case RT_DRAGLOCK: + return "Draglock"; + case RT_DRAGON: + case RT_DRAGON_2: + case RT_DRAGON_3: + case RT_DRAGON_4: + case RT_DRAGON_5: + case RT_DRAGON_6: + case RT_DRAGON_7: + case RT_DRAGON_8: + case RT_DRAGON_9: + case RT_DRAGON_10: + case RT_DRAGON_11: + case RT_DRAGON_12: + case RT_DRAGON_13: + case RT_DRAGON_14: + case RT_DRAGON_15: + case RT_DRAGON_16: + return "Dragon"; + case RT_DRAGON_BONES: + return "Dragon Bones"; + case RT_DRAGON_EGG: + return "Dragon Egg"; + case RT_DRAGON_STATUE: + return "Dragon Statue"; + case RT_DRAGORN: + return "Dragorn"; + case RT_DRAGORN_BOX: + return "Dragorn Box"; + case RT_DRAKE: + case RT_DRAKE_2: + case RT_DRAKE_3: + return "Drake"; + case RT_DRAKKIN: return "Drakkin"; + case RT_DRIXIE: + return "Drixie"; + case RT_DROGMORE: + return "Drogmore"; + case RT_DROLVARG: + return "Drolvarg"; + case RT_DRYAD: + return "Dryad"; + case RT_DWARF: + case RT_DWARF_2: + return "Dwarf"; + case RT_DYNAMITE_KEG: + return "Dynamite Keg"; + case RT_DYNLETH: + return "Dyn'Leth"; + case RT_EARTH_ELEMENTAL: + case RT_EARTH_ELEMENTAL_2: + return "Earth Elemental"; + case RT_EARTH_MEPHIT: + return "Earth Mephit"; + case RT_EEL: + return "Eel"; + case RT_EFREETI: + case RT_EFREETI_2: + return "Efreeti"; + case RT_ELDDAR: + return "Elddar"; + case RT_ELEMENTAL: + return "Elemental"; + case RT_ELK_HEAD: + return "Elk Head"; + case RT_ELVEN_BOAT: + return "Elven Boat"; + case RT_ELVEN_GHOST: + return "Elven Ghost"; + case RT_ENCHANTED_ARMOR: + return "Enchanted Armor"; + case RT_EROLLISI: + return "Erollisi"; + case RT_ERUDITE: + case RT_ERUDITE_2: + case RT_ERUDITE_3: + return "Erudite"; + case RT_EVAN_TEST: + return "Evan Test"; + case RT_EVIL_EYE: + case RT_EVIL_EYE_2: + case RT_EVIL_EYE_3: + return "Evil Eye"; + case RT_EXOSKELETON: + return "Exoskeleton"; + case RT_EXPLOSIVE_CART: + return "Explosive Cart"; + case RT_EYE: + return "Eye"; + case RT_FAIRY: + case RT_FAIRY_2: + return "Fairy"; + case RT_FALLEN_KNIGHT: + return "Fallen Knight"; + case RT_FAUN: + return "Faun"; + case RT_FAY_DRAKE: + return "Fay Drake"; + case RT_FENNIN_RO: + return "Fennin Ro"; + case RT_FERAN: + return "Feran"; + case RT_FERAN_MOUNT: + return "Feran Mount"; + case RT_FIEND: + return "Fiend"; + case RT_FIRE_ELEMENTAL: + case RT_FIRE_ELEMENTAL_2: + return "Fire Elemental"; + case RT_FIRE_MEPHIT: + return "Fire Mephit"; + case RT_FISH: + case RT_FISH_2: + return "Fish"; + case RT_FLAG: + return "Flag"; + case RT_FLOATING_ISLAND: + return "Floating Island"; + case RT_FLOATING_SKULL: + return "Floating Skull"; + case RT_FLOATING_TOWER: + return "Floating Tower"; + case RT_FLY: + return "Fly"; + case RT_FLYING_CARPET: + return "Flying Carpet"; + case RT_FOREST_GIANT: + return "Forest Giant"; + case RT_FROG: + case RT_FROG_2: + return "Frog"; + case RT_FROGLOK: + case RT_FROGLOK_2: + case RT_FROGLOK_3: + return "Froglok"; + case RT_FROGLOK_GHOST: + return "Froglok Ghost"; + case RT_FROGLOK_SKELETON: + return "Froglok Skeleton"; + case RT_FUNGAL_FIEND: + return "Fungal Fiend"; + case RT_FUNGUS_PATCH: + return "Fungus Patch"; + case RT_FUNGUSMAN: + return "Fungusman"; + case RT_GALORIAN: + return "Galorian"; + case RT_GARGOYLE: + case RT_GARGOYLE_2: + return "Gargoyle"; + case RT_GASBAG: + return "Gasbag"; + case RT_GELATINOUS_CUBE: + case RT_GELATINOUS_CUBE_2: + return "Gelatinous Cube"; + case RT_GELIDRAN: + return "Gelidran"; + case RT_GENARI: + return "Genari"; + case RT_GEONID: + return "Geonid"; + case RT_GHOST: + case RT_GHOST_2: + case RT_GHOST_3: + case RT_GHOST_4: + return "Ghost"; + case RT_GHOST_SHIP: + case RT_GHOST_SHIP_2: + return "Ghost Ship"; + case RT_GHOUL: + case RT_GHOUL_2: + return "Ghoul"; + case RT_GIANT: + case RT_GIANT_2: + case RT_GIANT_3: + case RT_GIANT_4: + case RT_GIANT_5: + case RT_GIANT_6: + case RT_GIANT_7: + case RT_GIANT_8: + case RT_GIANT_9: + case RT_GIANT_10: + case RT_GIANT_11: + case RT_GIANT_12: + case RT_GIANT_13: + return "Giant"; + case RT_GIANT_CLOCKWORK: + return "Giant Clockwork"; + case RT_GIANT_RALLOSIAN_MATS: + return "Giant(Rallosian mats)"; + case RT_GIANT_SHADE: + return "Giant Shade"; + case RT_GIGYN: + return "Gigyn"; + case RT_GINGERBREAD_MAN: + return "Gingerbread Man"; + case RT_GIRPLAN: + return "Girplan"; + case RT_GNOLL: + case RT_GNOLL_2: + case RT_GNOLL_3: + return "Gnoll"; + case RT_GNOME: + return "Gnome"; + case RT_GNOMEWORK: + return "Gnomework"; + case RT_GNOMISH_BALLOON: + return "Gnomish Balloon"; + case RT_GNOMISH_BOAT: + return "Gnomish Boat"; + case RT_GNOMISH_HOVERING_TRANSPORT: + return "Gnomish Hovering Transport"; + case RT_GNOMISH_ROCKET_PACK: + return "Gnomish Rocket Pack"; + case RT_GOBLIN: + case RT_GOBLIN_2: + case RT_GOBLIN_3: + case RT_GOBLIN_4: + case RT_GOBLIN_5: + return "Goblin"; + case RT_GOD_LUCLIN_VER_2: + return "God - Luclin(Ver. 2)"; + case RT_GOD_LUCLIN_VER_3: + return "God - Luclin(Ver. 3)"; + case RT_GOD_LUCLIN_VER_4: + return "God - Luclin(Ver. 4)"; + case RT_GOD_OF_DISCORD: + return "God of Discord"; + case RT_GOLEM: + case RT_GOLEM_2: + return "Golem"; + case RT_GOO: + case RT_GOO_2: + case RT_GOO_3: + case RT_GOO_4: + return "Goo"; + case RT_GORAL: + return "Goral"; + case RT_GORAL_MOUNT: + return "Goral Mount"; + case RT_GORGON: + return "Gorgon"; + case RT_GORILLA: + case RT_GORILLA_2: + return "Gorilla"; + case RT_GRANDFATHER_CLOCK: + return "Grandfather Clock"; + case RT_GREKEN_YOUNG: + return "Greken - Young"; + case RT_GREKEN_YOUNG_ADULT: + return "Greken - Young Adult"; + case RT_GRENDLAEN: + return "Grendlaen"; + case RT_GRIEG_VENEFICUS: + return "Grieg Veneficus"; + case RT_GRIFFIN: + case RT_GRIFFIN_2: + return "Griffin"; + case RT_GRIMLING: + return "Grimling"; + case RT_GROUND_SHAKER: + return "Ground Shaker"; + case RT_GUARD: + case RT_GUARD_2: + case RT_GUARD_3: + case RT_GUARD_4: + return "Guard"; + case RT_GUARD_OF_JUSTICE: + return "Guard of Justice"; + case RT_GUARDIAN_CPU: + return "Guardian CPU"; + case RT_HADAL: + return "Hadal"; + case RT_HAG: + return "Hag"; + case RT_HALF_ELF: + return "Half Elf"; + case RT_HALFLING: + case RT_HALFLING_2: + return "Halfling"; + case RT_HARPY: + case RT_HARPY_2: + return "Harpy"; + case RT_HIGH_ELF: + return "High Elf"; + case RT_HIPPOGRIFF: + return "Hippogriff"; + case RT_HOLGRESH: + case RT_HOLGRESH_2: + return "Holgresh"; + case RT_HONEY_POT: + return "Honey Pot"; + case RT_HORSE: + case RT_HORSE_2: + case RT_HORSE_3: + return "Horse"; + case RT_HOVERING_PLATFORM: + return "Hovering Platform"; + case RT_HRAQUIS: + return "Hraquis"; + case RT_HUMAN: + case RT_HUMAN_2: + case RT_HUMAN_3: + case RT_HUMAN_4: + return "Human"; + case RT_HUMAN_GHOST: + return "Human Ghost"; + case RT_HUVUL: + return "Huvul"; + case RT_HYDRA_CRYSTAL: + return "Hydra Crystal"; + case RT_HYDRA_MOUNT: + return "Hydra Mount"; + case RT_HYDRA_NPC: + return "Hydra NPC"; + case RT_HYNID: + return "Hynid"; + case RT_ICE_SPECTRE: + return "Ice Spectre"; + case RT_IKAAV: + return "Ikaav"; + case RT_IKSAR: + case RT_IKSAR_2: + return "Iksar"; + case RT_IKSAR_GHOST: + return "Iksar Ghost"; + case RT_IKSAR_GOLEM: + return "Iksar Golem"; + case RT_IKSAR_SKELETON: + return "Iksar Skeleton"; + case RT_IKSAR_SPIRIT: + return "Iksar Spirit"; + case RT_IMP: + return "Imp"; + case RT_INNORUUK: + return "Innoruuk"; + case RT_INSECT: + return "Insect"; + case RT_INTERACTIVE_OBJECT: + return "Interactive Object"; + case RT_INVISIBLE_MAN: + case RT_INVISIBLE_MAN_2: + case RT_INVISIBLE_MAN_3: + return "Invisible Man"; + case RT_INVISIBLE_MAN_OF_ZOMM: + return "Invisible Man of Zomm"; + case RT_IXT: + return "Ixt"; + case RT_JOKESTER: + return "Jokester"; + case RT_JUM_JUM_BUCKET: + return "Jum Jum Bucket"; + case RT_JUNK_BEAST: + return "Junk Beast"; + case RT_KANGON: + return "Kangon"; + case RT_KANGON_MOUNT: + return "Kangon Mount"; + case RT_KARANA: + return "Karana"; + case RT_KEDGE: + case RT_KEDGE_2: + return "Kedge"; + case RT_KERRAN: + case RT_KERRAN_2: + return "Kerran"; + case RT_KIRIN: + case RT_KIRIN_2: + return "Kirin"; + case RT_KNIGHT_OF_HATE: + return "Knight of Hate"; + case RT_KNIGHT_OF_PESTILENCE: + return "Knight of Pestilence"; + case RT_KOBOLD: + case RT_KOBOLD_2: + return "Kobold"; + case RT_KRAKEN: + return "Kraken"; + case RT_KYV: + return "Kyv"; + case RT_LAUNCH: + return "Launch"; + case RT_LAVA_ROCK: + return "Lava Rock"; + case RT_LAVA_SPIDER: + return "Lava Spider"; + case RT_LAVA_SPIDER_QUEEN: + return "Lava Spider Queen"; + case RT_LEECH: + return "Leech"; + case RT_LEPERTOLOTH: + return "Lepertoloth"; + case RT_LIGHTCRAWLER: + return "Lightcrawler"; + case RT_LIGHTNING_WARRIOR: + return "Lightning Warrior"; + case RT_LION: + return "Lion"; + case RT_LIZARD_MAN: + return "Lizard Man"; + case RT_LUCLIN: + return "Luclin"; + case RT_LUGGALD: + case RT_LUGGALD_2: + return "Luggald"; + case RT_LUGGALDS: + return "Luggalds"; + case RT_MALARIAN: + return "Malarian"; + case RT_MAMMOTH: + case RT_MAMMOTH_2: + return "Mammoth"; + case RT_MAN_EATING_PLANT: + return "Man - Eating Plant"; + case RT_MANSION: + return "Mansion"; + case RT_MANTICORE: + return "Manticore"; + case RT_MANTRAP: + return "Mantrap"; + case RT_MARIONETTE: + return "Marionette"; + case RT_MASTRUQ: + return "Mastruq"; + case RT_MATA_MURAM: + return "Mata Muram"; + case RT_MEDIUM_PLANT: + return "Medium Plant"; + case RT_MEPHIT: + return "Mephit"; + case RT_MERCHANT_SHIP: + return "Merchant Ship"; + case RT_MERMAID: + return "Mermaid"; + case RT_MIMIC: + return "Mimic"; + case RT_MINI_POM: + return "Mini POM"; + case RT_MINOTAUR: + case RT_MINOTAUR_2: + case RT_MINOTAUR_3: + case RT_MINOTAUR_4: + return "Minotaur"; + case RT_MITHANIEL_MARR: + return "Mithaniel Marr"; + case RT_MORELL_THULE: + return "Morell Thule"; + case RT_MOSQUITO: + return "Mosquito"; + case RT_MOUTH_OF_INSANITY: + return "Mouth of Insanity"; + case RT_MUDDITE: + return "Muddite"; + case RT_MUMMY: + return "Mummy"; + case RT_MURAMITE_ARMOR_PILE: + return "Muramite Armor Pile"; + case RT_MURKGLIDER: + return "Murkglider"; + case RT_MURKGLIDER_EGG_SAC: + return "Murkglider Egg Sac"; + case RT_MUTNA: + return "Mutna"; + case RT_NEKHON: + return "Nekhon"; + case RT_NETHERBIAN: + return "Netherbian"; + case RT_NIGHTMARE: + return "Nightmare"; + case RT_NIGHTMARE_GARGOYLE: + return "Nightmare Gargoyle"; + case RT_NIGHTMARE_GOBLIN: + return "Nightmare Goblin"; + case RT_NIGHTMARE_MEPHIT: + return "Nightmare Mephit"; + case RT_NIGHTMARE_UNICORN: + case RT_NIGHTMARE_UNICORN_2: + return "Nightmare / Unicorn"; + case RT_NIGHTMARE_WRAITH: + return "Nightmare Wraith"; + case RT_NIHIL: + return "Nihil"; + case RT_NILBORIEN: + return "Nilborien"; + case RT_NOC: + return "Noc"; + case RT_NYMPH: + return "Nymph"; + case RT_OGRE: + case RT_OGRE_2: + return "Ogre"; + case RT_OGRE_NPC_MALE: + return "Ogre NPC - Male"; + case RT_ORB: + return "Orb"; + case RT_ORC: + case RT_ORC_2: + return "Orc"; + case RT_OTHMIR: + return "Othmir"; + case RT_OWLBEAR: + return "Owlbear"; + case RT_PARASITIC_SCAVENGER: + return "Parasitic Scavenger"; + case RT_PEGASUS: + case RT_PEGASUS_2: + case RT_PEGASUS_3: + return "Pegasus"; + case RT_PHOENIX: + return "Phoenix"; + case RT_PIRANHA: + return "Piranha"; + case RT_PIRATE: + case RT_PIRATE_2: + case RT_PIRATE_3: + case RT_PIRATE_4: + case RT_PIRATE_5: + case RT_PIRATE_6: + case RT_PIRATE_7: + case RT_PIRATE_8: + return "Pirate"; + case RT_PIRATE_SHIP: + return "Pirate Ship"; + case RT_PIXIE: + return "Pixie"; + case RT_POISON_FROG: + return "Poison Frog"; + case RT_PORTAL: + return "Portal"; + case RT_POWDER_KEG: + return "Powder Keg"; + case RT_PRESSURE_PLATE: + return "Pressure Plate"; + case RT_PUFFER_SPORE: + return "Puffer Spore"; + case RT_PUMA: + case RT_PUMA_2: + case RT_PUMA_3: + return "Puma"; + case RT_PUSLING: + return "Pusling"; + case RT_PYRILEN: + return "Pyrilen"; + case RT_RA_TUK: + return "Ra`tuk"; + case RT_RABBIT: + return "Rabbit"; + case RT_RALLOS_ZEK: + case RT_RALLOS_ZEK_2: + return "Rallos Zek"; + case RT_RALLOS_ZEK_MINION: + return "Rallos Zek Minion"; + case RT_RAPTOR: + case RT_RAPTOR_2: + return "Raptor"; + case RT_RAPTOR_MOUNT: + return "Raptor Mount"; + case RT_RAT: + case RT_RAT_2: + return "Rat"; + case RT_RAT_MOUNT: + return "Rat Mount"; + case RT_RATMAN: + case RT_RATMAN_2: + return "Ratman"; + case RT_REANIMATED_HAND: + return "Reanimated Hand"; + case RT_RECUSO: + return "Recuso"; + case RT_REGENERATION_POOL: + return "Regeneration Pool"; + case RT_RELIC_CASE: + return "Relic case "; + case RT_RHINO_BEETLE: + return "Rhino Beetle"; + case RT_RHINOCEROS: + return "Rhinoceros"; + case RT_ROBOCOPTER_OF_ZOMM: + return "Robocopter of Zomm"; + case RT_ROCK_PILE: + return "Rock Pile"; + case RT_ROCKHOPPER: + return "Rockhopper"; + case RT_RONNIE_TEST: + return "Ronnie Test"; + case RT_ROOT_TENTACLE: + return "Root Tentacle"; + case RT_ROT_DOG_MOUNT: + return "Rot Dog Mount"; + case RT_ROTDOG: + return "Rotdog"; + case RT_ROTOCOPTER: + return "Rotocopter"; + case RT_ROWBOAT: + return "Rowboat"; + case RT_ROYAL_GUARD: + return "Royal Guard"; + case RT_RUJARKIAN_ORC: + case RT_RUJARKIAN_ORC_2: + return "Rujarkian Orc"; + case RT_RUNED_ORB: + return "Runed Orb"; + case RT_RUNIC_SYMBOL: + return "Runic Symbol"; + case RT_SABER_TOOTHED_CAT: + return "Saber - toothed Cat"; + case RT_SALTPETTER_BOMB: + return "Saltpetter Bomb"; + case RT_SAND_ELF: + return "Sand Elf"; + case RT_SANDMAN: + return "Sandman"; + case RT_SARNAK: + case RT_SARNAK_2: + return "Sarnak"; + case RT_SARNAK_GOLEM: + return "Sarnak Golem"; + case RT_SARNAK_SPIRIT: + return "Sarnak Spirit"; + case RT_SARYRN: + return "Saryrn"; + case RT_SATYR: + return "Satyr"; + case RT_SCALED_WOLF: + return "Scaled Wolf"; + case RT_SCARECROW: + case RT_SCARECROW_2: + return "Scarecrow"; + case RT_SCARLET_CHEETAH: + return "Scarlet Cheetah"; + case RT_SCLERA_MOUNT: + return "Sclera Mount"; + case RT_SCORPION: + case RT_SCORPION_2: + case RT_SCORPION_3: + return "Scorpion"; + case RT_SCRYKIN: + return "Scrykin"; + case RT_SEA_TURTLE: + return "Sea Turtle"; + case RT_SEAHORSE: + return "Seahorse"; + case RT_SELYRAH: + return "Selyrah"; + case RT_SELYRAH_MOUNT: + return "Selyrah Mount"; + case RT_SERU: + return "Seru"; + case RT_SERVANT_OF_SHADOW: + return "Servant of Shadow"; + case RT_SESSILOID_MOUNT: + return "Sessiloid Mount"; + case RT_SHADE: + case RT_SHADE_2: + case RT_SHADE_3: + return "Shade"; + case RT_SHADEL: + return "Shadel"; + case RT_SHAMBLING_MOUND: + return "Shambling Mound"; + case RT_SHARK: + return "Shark"; + case RT_SHIKNAR: + return "Shik'Nar"; + case RT_SHILISKIN: + return "Shiliskin"; + case RT_SHIP: + return "Ship"; + case RT_SHIP_IN_A_BOTTLE: + return "Ship in a Bottle"; + case RT_SHISSAR: + case RT_SHISSAR_2: + return "Shissar"; + case RT_SHRIEKER: + return "Shrieker"; + case RT_SIREN: + case RT_SIREN_2: + return "Siren"; + case RT_SKELETAL_HORSE: + return "Skeletal Horse"; + case RT_SKELETON: + case RT_SKELETON_2: + case RT_SKELETON_3: + return "Skeleton"; + case RT_SKUNK: + return "Skunk"; + case RT_SKYSTRIDER: + return "Skystrider"; + case RT_SMALL_PLANT: + return "Small Plant"; + case RT_SNAKE: + case RT_SNAKE_2: + return "Snake"; + case RT_SNAKE_ELEMENTAL: + return "Snake Elemental"; + case RT_SNOW_DERVISH: + return "Snow Dervish"; + case RT_SNOW_RABBIT: + return "Snow Rabbit"; + case RT_SOKOKAR: + return "Sokokar"; + case RT_SOKOKAR_MOUNT: + return "Sokokar Mount"; + case RT_SOKOKAR_W_SADDLE: + return "Sokokar(w saddle)"; + case RT_SOLUSEK_RO: + case RT_SOLUSEK_RO_2: + return "Solusek Ro"; + case RT_SOLUSEK_RO_GUARD: + return "Solusek Ro Guard"; + case RT_SONIC_WOLF: + return "Sonic Wolf"; + case RT_SOUL_DEVOURER: + return "Soul Devourer"; + case RT_SPECTRE: + case RT_SPECTRE_2: + return "Spectre"; + case RT_SPELL_PARTICLE_1: + return "Spell Particle 1"; + case RT_SPHINX: + case RT_SPHINX_2: + return "Sphinx"; + case RT_SPIDER: + case RT_SPIDER_2: + return "Spider"; + case RT_SPIDER_EGG_SACK: + return "Spider Egg Sack"; + case RT_SPIDER_MOUNT: + return "Spider Mount"; + case RT_SPIDER_QUEEN: + return "Spider Queen"; + case RT_SPIKE_TRAP: + return "Spike Trap"; + case RT_SPIRIT_WOLF: + return "Spirit Wolf"; + case RT_SPORALI: + return "Sporali"; + case RT_STONE_JUG: + return "Stone Jug"; + case RT_STONE_PYLON: + return "Stone Pylon"; + case RT_STONE_RING: + return "Stone Ring"; + case RT_STONE_WORKER: + case RT_STONE_WORKER_2: + return "Stone Worker"; + case RT_STONEGRABBER: + return "Stonegrabber"; + case RT_STONEMITE: + return "Stonemite"; + case RT_STORMRIDER: + return "Stormrider"; + case RT_SUCCUBUS: + return "Succubus"; + case RT_SUCCULENT: + return "Succulent"; + case RT_SULLON_ZEK: + return "Sullon Zek"; + case RT_SUN_REVENANT: + return "Sun Revenant"; + case RT_SUNFLOWER: + return "Sunflower"; + case RT_SWINETOR: + return "Swinetor"; + case RT_SWORDFISH: + return "Swordfish"; + case RT_SYNARCANA: + return "Synarcana"; + case RT_TABLE: + return "Table"; + case RT_TADPOLE: + return "Tadpole"; + case RT_TAELOSIAN: + return "Taelosian"; + case RT_TALL_PLANT: + return "Tall Plant"; + case RT_TALLON_ZEK: + return "Tallon Zek"; + case RT_TANETH: + return "Taneth"; + case RT_TAREW_MARR: + return "Tarew Marr"; + case RT_TEGI: + return "Tegi"; + case RT_TELEPORT_MAN: + return "Teleport Man"; + case RT_TELEPORTATION_STAND: + return "Teleportation Stand"; + case RT_TELMIRA: + return "Telmira"; + case RT_TENTACLE_TERROR: + case RT_TENTACLE_TERROR_2: + return "Tentacle Terror"; + case RT_TERRIS_THULE: + return "Terris Thule"; + case RT_TEST_OBJECT: + return "Test Object"; + case RT_THE_RATHE: + return "The Rathe"; + case RT_THE_TRIBUNAL: + return "The Tribunal"; + case RT_THOUGHT_HORROR: + return "Thought Horror"; + case RT_TIGER: + return "Tiger"; + case RT_TIN_SOLDIER: + return "Tin Soldier"; + case RT_TOOLBOX: + return "Toolbox"; + case RT_TOPIARY_LION: + return "Topiary Lion"; + case RT_TOPIARY_LION_MOUNT: + return "Topiary Lion Mount"; + case RT_TORMENTOR: + return "Tormentor"; + case RT_TOTEM: + case RT_TOTEM_2: + return "Totem"; + case RT_TRAKANON: + return "Trakanon"; + case RT_TRANQUILION: + return "Tranquilion"; + case RT_TREANT: + case RT_TREANT_2: + case RT_TREANT_3: + return "Treant"; + case RT_TRIBUNAL: + return "Tribunal"; + case RT_TRIUMVIRATE: + return "Triumvirate"; + case RT_TROLL: + case RT_TROLL_2: + case RT_TROLL_3: + case RT_TROLL_4: + case RT_TROLL_5: + return "Troll"; + case RT_TROLL_ZOMBIE: + return "Troll Zombie"; + case RT_TRUSIK: + return "Trusik"; + case RT_TSETSIAN: + return "Tsetsian"; + case RT_TUMBLEWEED: + return "Tumbleweed"; + case RT_TUNARE: + return "Tunare"; + case RT_TUREPTA: + return "Turepta"; + case RT_UKUN: + return "Ukun"; + case RT_ULTHORK: + return "Ulthork"; + case RT_UNDEAD_CHOKIDAI: + return "Undead Chokidai"; + case RT_UNDEAD_FOOTMAN: + return "Undead Footman"; + case RT_UNDEAD_FROGLOK: + return "Undead Froglok"; + case RT_UNDEAD_IKSAR: + return "Undead Iksar"; + case RT_UNDEAD_KNIGHT: + return "Undead Knight"; + case RT_UNDEAD_SARNAK: + return "Undead Sarnak"; + case RT_UNDEAD_VEKSAR: + return "Undead Veksar"; + case RT_UNDERBULK: + return "Underbulk"; + case RT_UNICORN: + return "Unicorn"; + case RT_UNKNOWN_RACE: + case RT_UNKNOWN_RACE_2: + case RT_UNKNOWN_RACE_3: + case RT_UNKNOWN_RACE_4: + case RT_UNKNOWN_RACE_5: + case RT_UNKNOWN_RACE_6: + case RT_UNKNOWN_RACE_7: + return "UNKNOWN RACE"; + case RT_VAH_SHIR: + case RT_VAH_SHIR_2: + return "Vah Shir"; + case RT_VAH_SHIR_SKELETON: + return "Vah Shir Skeleton"; + case RT_VALLON_ZEK: + return "Vallon Zek"; + case RT_VALORIAN: + case RT_VALORIAN_2: + return "Valorian"; + case RT_VAMPIRE: + case RT_VAMPIRE_2: + case RT_VAMPIRE_3: + case RT_VAMPIRE_4: + case RT_VAMPIRE_5: + case RT_VAMPIRE_6: + case RT_VAMPIRE_7: + case RT_VAMPIRE_8: + return "Vampire"; + case RT_VASE: + return "Vase"; + case RT_VEGEROG: + return "Vegerog"; + case RT_VEKSAR: + case RT_VEKSAR_2: + case RT_VEKSAR_3: + return "Veksar"; + case RT_VENRIL_SATHIR: + return "Venril Sathir"; + case RT_VINE_MAW: + return "Vine Maw"; + case RT_WAGON: + return "Wagon"; + case RT_WALRUS: + return "Walrus"; + case RT_WAR_BOAR: + case RT_WAR_BOAR_2: + return "War Boar"; + case RT_WAR_WRAITH: + return "War Wraith"; + case RT_WASP: + return "Wasp"; + case RT_WATER_ELEMENTAL: + case RT_WATER_ELEMENTAL_2: + return "Water Elemental"; + case RT_WATER_MEPHIT: + return "Water Mephit"; + case RT_WATER_SPOUT: + return "Water Spout"; + case RT_WEAPON_RACK: + case RT_WEAPON_RACK_2: + return "Weapon Rack"; + case RT_WEB: + return "Web"; + case RT_WEDDING_ALTAR: + return "Wedding Altar"; + case RT_WEDDING_ARBOR: + return "Wedding Arbor"; + case RT_WEDDING_FLOWERS: + return "Wedding Flowers"; + case RT_WEREORC: + return "Wereorc"; + case RT_WEREWOLF: + case RT_WEREWOLF_2: + case RT_WEREWOLF_3: + return "Werewolf"; + case RT_WETFANG_MINNOW: + return "Wetfang Minnow"; + case RT_WHIRLIGIG: + return "Whirligig"; + case RT_WICKER_BASKET: + return "Wicker Basket"; + case RT_WILL_O_WISP: + return "Will - O - Wisp"; + case RT_WINE_CASK: + case RT_WINE_CASK_2: + return "Wine Cask"; + case RT_WITHERAN: + case RT_WITHERAN_2: + return "Witheran"; + case RT_WOLF: + case RT_WOLF_2: + case RT_WOLF_3: + return "Wolf"; + case RT_WOOD_ELF: + return "Wood Elf"; + case RT_WORG: + case RT_WORG_2: + return "Worg"; + case RT_WORM: + return "Worm"; + case RT_WRETCH: + return "Wretch"; + case RT_WRULON: + case RT_WRULON_2: + return "Wrulon"; + case RT_WURM: + case RT_WURM_2: + return "Wurm"; + case RT_WURM_MOUNT: + return "Wurm Mount"; + case RT_WYVERN: + case RT_WYVERN_2: + return "Wyvern"; + case RT_XALGOZ: + return "Xalgoz"; + case RT_XARIC_THE_UNSPOKEN: + return "Xaric the Unspoken"; + case RT_XEGONY: + return "Xegony"; + case RT_YAKKAR: + return "Yakkar"; + case RT_YETI: + return "Yeti"; + case RT_ZEBUXORUK: + return "Zebuxoruk"; + case RT_ZEBUXORUKS_CAGE: + return "Zebuxoruk's Cage"; + case RT_ZELNIAK: + return "Zelniak"; + case RT_ZOMBIE: + case RT_ZOMBIE_2: + return "Zombie"; default: - return "Unknown"; + return "UNKNOWN RACE"; } } diff --git a/common/races.h b/common/races.h index d34d9b754..04fdbbd32 100644 --- a/common/races.h +++ b/common/races.h @@ -113,6 +113,741 @@ #define PLAYER_RACE_ALL_MASK 65535 +#define RT_ABHORRENT 193 +#define RT_AIR_ELEMENTAL 210 +#define RT_AIR_ELEMENTAL_2 475 +#define RT_AIR_MEPHIT 291 +#define RT_AKHEVA 230 +#define RT_AKHEVA_2 722 +#define RT_ALARAN 695 +#define RT_ALARAN_GHOST 708 +#define RT_ALARAN_SENTRY_STONE 703 +#define RT_ALLIGATOR 91 +#define RT_ALLIGATOR_2 479 +#define RT_AMYGDALAN 99 +#define RT_AMYGDALAN_2 663 +#define RT_ANEUK 395 +#define RT_ANIMATED_ARMOR 323 +#define RT_ANIMATED_HAND 166 +#define RT_ANIMATED_STATUE 442 +#define RT_ANIMATED_STATUE_2 448 +#define RT_APEXUS 637 +#define RT_ARACHNID 326 +#define RT_ARCANIST_OF_HATE 352 +#define RT_ARMADILLO 87 +#define RT_ARMOR_RACK 535 +#define RT_AVIAK 13 +#define RT_AVIAK_2 558 +#define RT_AVIAK_PULL_ALONG 711 +#define RT_AYONAE_RO 498 +#define RT_BANNER 500 +#define RT_BANNER_2 553 +#define RT_BANNER_3 554 +#define RT_BANNER_4 555 +#define RT_BANNER_5 556 +#define RT_BANNER_6 557 +#define RT_BANNER_7 586 +#define RT_BANNER_10TH_ANNIVERSARY 628 +#define RT_BANSHEE 250 +#define RT_BANSHEE_2 487 +#define RT_BANSHEE_3 488 +#define RT_BARBARIAN 2 +#define RT_BARBARIAN_2 90 +#define RT_BARREL 377 +#define RT_BARREL_BARGE_SHIP 546 +#define RT_BASILISK 436 +#define RT_BAT 34 +#define RT_BAT_2 260 +#define RT_BAT_3 416 +#define RT_BAZU 409 +#define RT_BEAR 43 +#define RT_BEAR_2 305 +#define RT_BEAR_3 480 +#define RT_BEAR_MOUNT 655 +#define RT_BEAR_TRAP 503 +#define RT_BEETLE 22 +#define RT_BEETLE_2 559 +#define RT_BEETLE_3 716 +#define RT_BEGGAR 55 +#define RT_BELLIKOS 638 +#define RT_BERTOXXULOUS 152 +#define RT_BERTOXXULOUS_2 255 +#define RT_BIXIE 79 +#define RT_BIXIE_2 520 +#define RT_BLIMP_SHIP 693 +#define RT_BLIND_DREAMER 669 +#define RT_BLOOD_RAVEN 279 +#define RT_BOAT 141 +#define RT_BOAT_2 533 +#define RT_BOLVIRK 486 +#define RT_BONE_GOLEM 362 +#define RT_BONE_GOLEM_2 491 +#define RT_BONES 383 +#define RT_BOOK_DERVISH 660 +#define RT_BOT_PORTAL 329 +#define RT_BOULDER 585 +#define RT_BOX 376 +#define RT_BRAXI 688 +#define RT_BRAXI_MOUNT 676 +#define RT_BRELL 640 +#define RT_BRELLS_FIRST_CREATION 639 +#define RT_BRISTLEBANE 153 +#define RT_BROKEN_CLOCKWORK 274 +#define RT_BRONTOTHERIUM 169 +#define RT_BROWNIE 15 +#define RT_BROWNIE_2 568 +#define RT_BUBONIAN 268 +#define RT_BUBONIAN_UNDERLING 269 +#define RT_BURYNAI 144 +#define RT_BURYNAI_2 602 +#define RT_CAKE_10TH_ANNIVERSARY 629 +#define RT_CAMPFIRE 567 +#define RT_CARRIER_HAND 721 +#define RT_CAT 713 +#define RT_CAZIC_THULE 95 +#define RT_CAZIC_THULE_2 670 +#define RT_CENTAUR 16 +#define RT_CENTAUR_2 521 +#define RT_CHEST 378 +#define RT_CHEST_2 589 +#define RT_CHEST_3 590 +#define RT_CHIMERA 412 +#define RT_CHIMERA_2 582 +#define RT_CHOKIDAI 356 +#define RT_CLAM 115 +#define RT_CLIKNAR_MOUNT 652 +#define RT_CLIKNAR_QUEEN 642 +#define RT_CLIKNAR_SOLDIER 643 +#define RT_CLIKNAR_WORKER 644 +#define RT_CLOCKWORK_BEETLE 276 +#define RT_CLOCKWORK_BOAR 472 +#define RT_CLOCKWORK_BOMB 504 +#define RT_CLOCKWORK_BRAIN 249 +#define RT_CLOCKWORK_GNOME 88 +#define RT_CLOCKWORK_GOLEM 248 +#define RT_CLOCKWORK_GUARDIAN 572 +#define RT_COCKATRICE 96 +#define RT_COFFIN 382 +#define RT_COFFIN_2 592 +#define RT_COIN_PURSE 427 +#define RT_COLDAIN 183 +#define RT_COLDAIN_2 645 +#define RT_COLDAIN_3 646 +#define RT_CORAL 460 +#define RT_CORATHUS 459 +#define RT_CRAB 302 +#define RT_CRAGBEAST 390 +#define RT_CRAGSLITHER 597 +#define RT_CROCODILE 259 +#define RT_CRYSTAL 591 +#define RT_CRYSTAL_SHARD 425 +#define RT_CRYSTAL_SPHERE 616 +#define RT_CRYSTAL_SPIDER 327 +#define RT_CRYSTALSKIN_AMBULOID 641 +#define RT_CRYSTALSKIN_SESSILOID 647 +#define RT_DAISY_MAN 97 +#define RT_DARK_ELF 6 +#define RT_DARK_ELF_2 77 +#define RT_DARK_LORD 466 +#define RT_DEMI_LICH 45 +#define RT_DEMON_VULTURE 620 +#define RT_DERVISH 100 +#define RT_DERVISH_2 372 +#define RT_DERVISH_3 431 +#define RT_DERVISH_4 704 +#define RT_DERVISH_VER_5 726 +#define RT_DERVISH_VER_6 727 +#define RT_DEVOURER 159 +#define RT_DIRE_WOLF 171 +#define RT_DISCORD_SHIP 404 +#define RT_DISCORDLING 418 +#define RT_DISEASED_FIEND 253 +#define RT_DJINN 126 +#define RT_DRACHNID 57 +#define RT_DRACHNID_2 461 +#define RT_DRACHNID_COCOON 462 +#define RT_DRACOLICH 604 +#define RT_DRAGLOCK 132 +#define RT_DRAGON 49 +#define RT_DRAGON_2 122 +#define RT_DRAGON_3 165 +#define RT_DRAGON_4 184 +#define RT_DRAGON_5 192 +#define RT_DRAGON_6 195 +#define RT_DRAGON_7 196 +#define RT_DRAGON_8 198 +#define RT_DRAGON_9 304 +#define RT_DRAGON_10 435 +#define RT_DRAGON_11 437 +#define RT_DRAGON_12 438 +#define RT_DRAGON_13 452 +#define RT_DRAGON_14 530 +#define RT_DRAGON_15 531 +#define RT_DRAGON_16 569 +#define RT_DRAGON_BONES 423 +#define RT_DRAGON_EGG 445 +#define RT_DRAGON_STATUE 446 +#define RT_DRAGORN 413 +#define RT_DRAGORN_BOX 421 +#define RT_DRAKE 89 +#define RT_DRAKE_2 430 +#define RT_DRAKE_3 432 +#define RT_DRAKKIN 522 +#define RT_DRIXIE 113 +#define RT_DROGMORE 348 +#define RT_DROLVARG 133 +#define RT_DRYAD 243 +#define RT_DWARF 8 +#define RT_DWARF_2 94 +#define RT_DYNAMITE_KEG 505 +#define RT_DYNLETH 532 +#define RT_EARTH_ELEMENTAL 209 +#define RT_EARTH_ELEMENTAL_2 476 +#define RT_EARTH_MEPHIT 292 +#define RT_EEL 35 +#define RT_EFREETI 101 +#define RT_EFREETI_2 320 +#define RT_ELDDAR 489 +#define RT_ELEMENTAL 75 +#define RT_ELK_HEAD 714 +#define RT_ELVEN_BOAT 544 +#define RT_ELVEN_GHOST 587 +#define RT_ENCHANTED_ARMOR 175 +#define RT_EROLLISI 150 +#define RT_ERUDITE 3 +#define RT_ERUDITE_2 78 +#define RT_ERUDITE_3 678 +#define RT_EVAN_TEST 204 +#define RT_EVIL_EYE 21 +#define RT_EVIL_EYE_2 375 +#define RT_EVIL_EYE_3 469 +#define RT_EXOSKELETON 570 +#define RT_EXPLOSIVE_CART 692 +#define RT_EYE 108 +#define RT_FAIRY 25 +#define RT_FAIRY_2 473 +#define RT_FALLEN_KNIGHT 719 +#define RT_FAUN 182 +#define RT_FAY_DRAKE 154 +#define RT_FENNIN_RO 284 +#define RT_FERAN 410 +#define RT_FERAN_MOUNT 623 +#define RT_FIEND 300 +#define RT_FIRE_ELEMENTAL 212 +#define RT_FIRE_ELEMENTAL_2 477 +#define RT_FIRE_MEPHIT 293 +#define RT_FISH 24 +#define RT_FISH_2 148 +#define RT_FLAG 501 +#define RT_FLOATING_ISLAND 596 +#define RT_FLOATING_SKULL 512 +#define RT_FLOATING_TOWER 691 +#define RT_FLY 245 +#define RT_FLYING_CARPET 720 +#define RT_FOREST_GIANT 490 +#define RT_FROG 343 +#define RT_FROG_2 603 +#define RT_FROGLOK 26 +#define RT_FROGLOK_2 27 +#define RT_FROGLOK_3 330 +#define RT_FROGLOK_GHOST 371 +#define RT_FROGLOK_SKELETON 349 +#define RT_FUNGAL_FIEND 218 +#define RT_FUNGUS_PATCH 463 +#define RT_FUNGUSMAN 28 +#define RT_GALORIAN 228 +#define RT_GARGOYLE 29 +#define RT_GARGOYLE_2 464 +#define RT_GASBAG 30 +#define RT_GELATINOUS_CUBE 31 +#define RT_GELATINOUS_CUBE_2 712 +#define RT_GELIDRAN 417 +#define RT_GENARI 648 +#define RT_GEONID 178 +#define RT_GHOST 32 +#define RT_GHOST_2 117 +#define RT_GHOST_3 118 +#define RT_GHOST_4 334 +#define RT_GHOST_SHIP 114 +#define RT_GHOST_SHIP_2 552 +#define RT_GHOUL 33 +#define RT_GHOUL_2 571 +#define RT_GIANT 18 +#define RT_GIANT_2 140 +#define RT_GIANT_3 188 +#define RT_GIANT_4 189 +#define RT_GIANT_5 306 +#define RT_GIANT_6 307 +#define RT_GIANT_7 308 +#define RT_GIANT_8 309 +#define RT_GIANT_9 310 +#define RT_GIANT_10 311 +#define RT_GIANT_11 312 +#define RT_GIANT_12 453 +#define RT_GIANT_13 523 +#define RT_GIANT_CLOCKWORK 275 +#define RT_GIANT_RALLOSIAN_MATS 626 +#define RT_GIANT_SHADE 526 +#define RT_GIGYN 649 +#define RT_GINGERBREAD_MAN 666 +#define RT_GIRPLAN 419 +#define RT_GNOLL 39 +#define RT_GNOLL_2 524 +#define RT_GNOLL_3 617 +#define RT_GNOME 12 +#define RT_GNOMEWORK 457 +#define RT_GNOMISH_BALLOON 683 +#define RT_GNOMISH_BOAT 545 +#define RT_GNOMISH_HOVERING_TRANSPORT 685 +#define RT_GNOMISH_ROCKET_PACK 684 +#define RT_GOBLIN 40 +#define RT_GOBLIN_2 59 +#define RT_GOBLIN_3 137 +#define RT_GOBLIN_4 369 +#define RT_GOBLIN_5 433 +#define RT_GOD_LUCLIN_VER_2 728 +#define RT_GOD_LUCLIN_VER_3 729 +#define RT_GOD_LUCLIN_VER_4 731 +#define RT_GOD_OF_DISCORD 622 +#define RT_GOLEM 17 +#define RT_GOLEM_2 374 +#define RT_GOO 145 +#define RT_GOO_2 547 +#define RT_GOO_3 548 +#define RT_GOO_4 549 +#define RT_GORAL 687 +#define RT_GORAL_MOUNT 673 +#define RT_GORGON 121 +#define RT_GORILLA 41 +#define RT_GORILLA_2 560 +#define RT_GRANDFATHER_CLOCK 665 +#define RT_GREKEN_YOUNG 651 +#define RT_GREKEN_YOUNG_ADULT 650 +#define RT_GRENDLAEN 701 +#define RT_GRIEG_VENEFICUS 231 +#define RT_GRIFFIN 47 +#define RT_GRIFFIN_2 525 +#define RT_GRIMLING 202 +#define RT_GROUND_SHAKER 233 +#define RT_GUARD 44 +#define RT_GUARD_2 106 +#define RT_GUARD_3 112 +#define RT_GUARD_4 239 +#define RT_GUARD_OF_JUSTICE 251 +#define RT_GUARDIAN_CPU 593 +#define RT_HADAL 698 +#define RT_HAG 185 +#define RT_HALF_ELF 7 +#define RT_HALFLING 11 +#define RT_HALFLING_2 81 +#define RT_HARPY 111 +#define RT_HARPY_2 527 +#define RT_HIGH_ELF 5 +#define RT_HIPPOGRIFF 186 +#define RT_HOLGRESH 168 +#define RT_HOLGRESH_2 715 +#define RT_HONEY_POT 536 +#define RT_HORSE 216 +#define RT_HORSE_2 492 +#define RT_HORSE_3 518 +#define RT_HOVERING_PLATFORM 699 +#define RT_HRAQUIS 261 +#define RT_HUMAN 1 +#define RT_HUMAN_2 67 +#define RT_HUMAN_3 71 +#define RT_HUMAN_4 566 +#define RT_HUMAN_GHOST 588 +#define RT_HUVUL 400 +#define RT_HYDRA_CRYSTAL 615 +#define RT_HYDRA_MOUNT 631 +#define RT_HYDRA_NPC 632 +#define RT_HYNID 388 +#define RT_ICE_SPECTRE 174 +#define RT_IKAAV 394 +#define RT_IKSAR 128 +#define RT_IKSAR_2 139 +#define RT_IKSAR_GHOST 605 +#define RT_IKSAR_GOLEM 160 +#define RT_IKSAR_SKELETON 606 +#define RT_IKSAR_SPIRIT 147 +#define RT_IMP 46 +#define RT_INNORUUK 123 +#define RT_INSECT 370 +#define RT_INTERACTIVE_OBJECT 2250 +#define RT_INVISIBLE_MAN 127 +#define RT_INVISIBLE_MAN_2 681 +#define RT_INVISIBLE_MAN_3 690 +#define RT_INVISIBLE_MAN_OF_ZOMM 600 +#define RT_IXT 393 +#define RT_JOKESTER 384 +#define RT_JUM_JUM_BUCKET 537 +#define RT_JUNK_BEAST 273 +#define RT_KANGON 689 +#define RT_KANGON_MOUNT 677 +#define RT_KARANA 278 +#define RT_KEDGE 103 +#define RT_KEDGE_2 561 +#define RT_KERRAN 23 +#define RT_KERRAN_2 562 +#define RT_KIRIN 434 +#define RT_KIRIN_2 583 +#define RT_KNIGHT_OF_HATE 351 +#define RT_KNIGHT_OF_PESTILENCE 266 +#define RT_KOBOLD 48 +#define RT_KOBOLD_2 455 +#define RT_KRAKEN 315 +#define RT_KYV 396 +#define RT_LAUNCH 73 +#define RT_LAVA_ROCK 447 +#define RT_LAVA_SPIDER 450 +#define RT_LAVA_SPIDER_QUEEN 451 +#define RT_LEECH 104 +#define RT_LEPERTOLOTH 267 +#define RT_LIGHTCRAWLER 223 +#define RT_LIGHTNING_WARRIOR 407 +#define RT_LION 50 +#define RT_LIZARD_MAN 51 +#define RT_LUCLIN 724 +#define RT_LUGGALD 345 +#define RT_LUGGALD_2 346 +#define RT_LUGGALDS 347 +#define RT_MALARIAN 265 +#define RT_MAMMOTH 107 +#define RT_MAMMOTH_2 528 +#define RT_MAN_EATING_PLANT 162 +#define RT_MANSION 595 +#define RT_MANTICORE 172 +#define RT_MANTRAP 573 +#define RT_MARIONETTE 659 +#define RT_MASTRUQ 402 +#define RT_MATA_MURAM 406 +#define RT_MEDIUM_PLANT 541 +#define RT_MEPHIT 607 +#define RT_MERCHANT_SHIP 550 +#define RT_MERMAID 110 +#define RT_MIMIC 52 +#define RT_MINI_POM 252 +#define RT_MINOTAUR 53 +#define RT_MINOTAUR_2 420 +#define RT_MINOTAUR_3 470 +#define RT_MINOTAUR_4 574 +#define RT_MITHANIEL_MARR 296 +#define RT_MORELL_THULE 658 +#define RT_MOSQUITO 134 +#define RT_MOUTH_OF_INSANITY 281 +#define RT_MUDDITE 608 +#define RT_MUMMY 368 +#define RT_MURAMITE_ARMOR_PILE 424 +#define RT_MURKGLIDER 414 +#define RT_MURKGLIDER_EGG_SAC 429 +#define RT_MUTNA 401 +#define RT_NEKHON 614 +#define RT_NETHERBIAN 229 +#define RT_NIGHTMARE 287 +#define RT_NIGHTMARE_GARGOYLE 280 +#define RT_NIGHTMARE_GOBLIN 277 +#define RT_NIGHTMARE_MEPHIT 294 +#define RT_NIGHTMARE_UNICORN 517 +#define RT_NIGHTMARE_UNICORN_2 519 +#define RT_NIGHTMARE_WRAITH 264 +#define RT_NIHIL 385 +#define RT_NILBORIEN 317 +#define RT_NOC 397 +#define RT_NYMPH 242 +#define RT_OGRE 10 +#define RT_OGRE_2 93 +#define RT_OGRE_NPC_MALE 624 +#define RT_ORB 730 +#define RT_ORC 54 +#define RT_ORC_2 458 +#define RT_OTHMIR 190 +#define RT_OWLBEAR 206 +#define RT_PARASITIC_SCAVENGER 700 +#define RT_PEGASUS 125 +#define RT_PEGASUS_2 493 +#define RT_PEGASUS_3 732 +#define RT_PHOENIX 303 +#define RT_PIRANHA 74 +#define RT_PIRATE 335 +#define RT_PIRATE_2 336 +#define RT_PIRATE_3 337 +#define RT_PIRATE_4 338 +#define RT_PIRATE_5 339 +#define RT_PIRATE_6 340 +#define RT_PIRATE_7 341 +#define RT_PIRATE_8 342 +#define RT_PIRATE_SHIP 551 +#define RT_PIXIE 56 +#define RT_POISON_FROG 316 +#define RT_PORTAL 426 +#define RT_POWDER_KEG 636 +#define RT_PRESSURE_PLATE 506 +#define RT_PUFFER_SPORE 507 +#define RT_PUMA 76 +#define RT_PUMA_2 439 +#define RT_PUMA_3 584 +#define RT_PUSLING 270 +#define RT_PYRILEN 411 +#define RT_RA_TUK 398 +#define RT_RABBIT 668 +#define RT_RALLOS_ZEK 66 +#define RT_RALLOS_ZEK_2 288 +#define RT_RALLOS_ZEK_MINION 325 +#define RT_RAPTOR 163 +#define RT_RAPTOR_2 609 +#define RT_RAPTOR_MOUNT 680 +#define RT_RAT 36 +#define RT_RAT_2 415 +#define RT_RAT_MOUNT 656 +#define RT_RATMAN 156 +#define RT_RATMAN_2 718 +#define RT_REANIMATED_HAND 80 +#define RT_RECUSO 237 +#define RT_REGENERATION_POOL 705 +#define RT_RELIC_CASE 707 +#define RT_RHINO_BEETLE 207 +#define RT_RHINOCEROS 135 +#define RT_ROBOCOPTER_OF_ZOMM 601 +#define RT_ROCK_PILE 428 +#define RT_ROCKHOPPER 200 +#define RT_RONNIE_TEST 197 +#define RT_ROOT_TENTACLE 509 +#define RT_ROT_DOG_MOUNT 672 +#define RT_ROTDOG 662 +#define RT_ROTOCOPTER 577 +#define RT_ROWBOAT 502 +#define RT_ROYAL_GUARD 667 +#define RT_RUJARKIAN_ORC 361 +#define RT_RUJARKIAN_ORC_2 366 +#define RT_RUNED_ORB 422 +#define RT_RUNIC_SYMBOL 510 +#define RT_SABER_TOOTHED_CAT 119 +#define RT_SALTPETTER_BOMB 511 +#define RT_SAND_ELF 364 +#define RT_SANDMAN 664 +#define RT_SARNAK 131 +#define RT_SARNAK_2 610 +#define RT_SARNAK_GOLEM 164 +#define RT_SARNAK_SPIRIT 146 +#define RT_SARYRN 283 +#define RT_SATYR 529 +#define RT_SCALED_WOLF 481 +#define RT_SCARECROW 82 +#define RT_SCARECROW_2 575 +#define RT_SCARLET_CHEETAH 221 +#define RT_SCLERA_MOUNT 675 +#define RT_SCORPION 129 +#define RT_SCORPION_2 149 +#define RT_SCORPION_3 611 +#define RT_SCRYKIN 495 +#define RT_SEA_TURTLE 194 +#define RT_SEAHORSE 116 +#define RT_SELYRAH 686 +#define RT_SELYRAH_MOUNT 674 +#define RT_SERU 236 +#define RT_SERVANT_OF_SHADOW 723 +#define RT_SESSILOID_MOUNT 657 +#define RT_SHADE 224 +#define RT_SHADE_2 373 +#define RT_SHADE_3 576 +#define RT_SHADEL 205 +#define RT_SHAMBLING_MOUND 494 +#define RT_SHARK 61 +#define RT_SHIKNAR 199 +#define RT_SHILISKIN 467 +#define RT_SHIP 72 +#define RT_SHIP_IN_A_BOTTLE 702 +#define RT_SHISSAR 217 +#define RT_SHISSAR_2 563 +#define RT_SHRIEKER 227 +#define RT_SIREN 187 +#define RT_SIREN_2 564 +#define RT_SKELETAL_HORSE 282 +#define RT_SKELETON 60 +#define RT_SKELETON_2 367 +#define RT_SKELETON_3 484 +#define RT_SKUNK 83 +#define RT_SKYSTRIDER 709 +#define RT_SMALL_PLANT 540 +#define RT_SNAKE 37 +#define RT_SNAKE_2 468 +#define RT_SNAKE_ELEMENTAL 84 +#define RT_SNOW_DERVISH 170 +#define RT_SNOW_RABBIT 176 +#define RT_SOKOKAR 618 +#define RT_SOKOKAR_MOUNT 625 +#define RT_SOKOKAR_W_SADDLE 627 +#define RT_SOLUSEK_RO 58 +#define RT_SOLUSEK_RO_2 247 +#define RT_SOLUSEK_RO_GUARD 254 +#define RT_SONIC_WOLF 232 +#define RT_SOUL_DEVOURER 286 +#define RT_SPECTRE 85 +#define RT_SPECTRE_2 485 +#define RT_SPELL_PARTICLE_1 599 +#define RT_SPHINX 86 +#define RT_SPHINX_2 565 +#define RT_SPIDER 38 +#define RT_SPIDER_2 440 +#define RT_SPIDER_EGG_SACK 449 +#define RT_SPIDER_MOUNT 654 +#define RT_SPIDER_QUEEN 441 +#define RT_SPIKE_TRAP 513 +#define RT_SPIRIT_WOLF 483 +#define RT_SPORALI 456 +#define RT_STONE_JUG 539 +#define RT_STONE_PYLON 619 +#define RT_STONE_RING 508 +#define RT_STONE_WORKER 387 +#define RT_STONE_WORKER_2 405 +#define RT_STONEGRABBER 220 +#define RT_STONEMITE 391 +#define RT_STORMRIDER 272 +#define RT_SUCCUBUS 408 +#define RT_SUCCULENT 167 +#define RT_SULLON_ZEK 499 +#define RT_SUN_REVENANT 226 +#define RT_SUNFLOWER 225 +#define RT_SWINETOR 696 +#define RT_SWORDFISH 105 +#define RT_SYNARCANA 363 +#define RT_TABLE 380 +#define RT_TADPOLE 102 +#define RT_TAELOSIAN 403 +#define RT_TALL_PLANT 542 +#define RT_TALLON_ZEK 290 +#define RT_TANETH 399 +#define RT_TAREW_MARR 246 +#define RT_TEGI 215 +#define RT_TELEPORT_MAN 240 +#define RT_TELEPORTATION_STAND 706 +#define RT_TELMIRA 653 +#define RT_TENTACLE_TERROR 68 +#define RT_TENTACLE_TERROR_2 578 +#define RT_TERRIS_THULE 257 +#define RT_TEST_OBJECT 301 +#define RT_THE_RATHE 298 +#define RT_THE_TRIBUNAL 256 +#define RT_THOUGHT_HORROR 214 +#define RT_TIGER 63 +#define RT_TIN_SOLDIER 263 +#define RT_TOOLBOX 538 +#define RT_TOPIARY_LION 661 +#define RT_TOPIARY_LION_MOUNT 671 +#define RT_TORMENTOR 285 +#define RT_TOTEM 173 +#define RT_TOTEM_2 514 +#define RT_TRAKANON 19 +#define RT_TRANQUILION 262 +#define RT_TREANT 64 +#define RT_TREANT_2 244 +#define RT_TREANT_3 496 +#define RT_TRIBUNAL 151 +#define RT_TRIUMVIRATE 697 +#define RT_TROLL 9 +#define RT_TROLL_2 92 +#define RT_TROLL_3 331 +#define RT_TROLL_4 332 +#define RT_TROLL_5 333 +#define RT_TROLL_ZOMBIE 344 +#define RT_TRUSIK 386 +#define RT_TSETSIAN 612 +#define RT_TUMBLEWEED 694 +#define RT_TUNARE 62 +#define RT_TUREPTA 389 +#define RT_UKUN 392 +#define RT_ULTHORK 191 +#define RT_UNDEAD_CHOKIDAI 357 +#define RT_UNDEAD_FOOTMAN 324 +#define RT_UNDEAD_FROGLOK 350 +#define RT_UNDEAD_IKSAR 161 +#define RT_UNDEAD_KNIGHT 297 +#define RT_UNDEAD_SARNAK 155 +#define RT_UNDEAD_VEKSAR 358 +#define RT_UNDERBULK 201 +#define RT_UNICORN 124 +#define RT_UNKNOWN_RACE 0 +#define RT_UNKNOWN_RACE_2 142 +#define RT_UNKNOWN_RACE_3 143 +#define RT_UNKNOWN_RACE_4 179 +#define RT_UNKNOWN_RACE_5 180 +#define RT_UNKNOWN_RACE_6 443 +#define RT_UNKNOWN_RACE_7 444 +#define RT_VAH_SHIR 130 +#define RT_VAH_SHIR_2 238 +#define RT_VAH_SHIR_SKELETON 234 +#define RT_VALLON_ZEK 289 +#define RT_VALORIAN 318 +#define RT_VALORIAN_2 322 +#define RT_VAMPIRE 65 +#define RT_VAMPIRE_2 98 +#define RT_VAMPIRE_3 208 +#define RT_VAMPIRE_4 219 +#define RT_VAMPIRE_5 359 +#define RT_VAMPIRE_6 360 +#define RT_VAMPIRE_7 365 +#define RT_VAMPIRE_8 497 +#define RT_VASE 379 +#define RT_VEGEROG 258 +#define RT_VEKSAR 353 +#define RT_VEKSAR_2 354 +#define RT_VEKSAR_3 355 +#define RT_VENRIL_SATHIR 20 +#define RT_VINE_MAW 717 +#define RT_WAGON 621 +#define RT_WALRUS 177 +#define RT_WAR_BOAR 319 +#define RT_WAR_BOAR_2 321 +#define RT_WAR_WRAITH 313 +#define RT_WASP 109 +#define RT_WATER_ELEMENTAL 211 +#define RT_WATER_ELEMENTAL_2 478 +#define RT_WATER_MEPHIT 271 +#define RT_WATER_SPOUT 710 +#define RT_WEAPON_RACK 381 +#define RT_WEAPON_RACK_2 534 +#define RT_WEB 515 +#define RT_WEDDING_ALTAR 635 +#define RT_WEDDING_ARBOR 634 +#define RT_WEDDING_FLOWERS 633 +#define RT_WEREORC 579 +#define RT_WEREWOLF 14 +#define RT_WEREWOLF_2 241 +#define RT_WEREWOLF_3 454 +#define RT_WETFANG_MINNOW 213 +#define RT_WHIRLIGIG 682 +#define RT_WICKER_BASKET 516 +#define RT_WILL_O_WISP 69 +#define RT_WINE_CASK 543 +#define RT_WINE_CASK_2 630 +#define RT_WITHERAN 465 +#define RT_WITHERAN_2 474 +#define RT_WOLF 42 +#define RT_WOLF_2 120 +#define RT_WOLF_3 482 +#define RT_WOOD_ELF 4 +#define RT_WORG 580 +#define RT_WORG_2 594 +#define RT_WORM 203 +#define RT_WRETCH 235 +#define RT_WRULON 314 +#define RT_WRULON_2 598 +#define RT_WURM 158 +#define RT_WURM_2 613 +#define RT_WURM_MOUNT 679 +#define RT_WYVERN 157 +#define RT_WYVERN_2 581 +#define RT_XALGOZ 136 +#define RT_XARIC_THE_UNSPOKEN 725 +#define RT_XEGONY 299 +#define RT_YAKKAR 181 +#define RT_YETI 138 +#define RT_ZEBUXORUK 295 +#define RT_ZEBUXORUKS_CAGE 328 +#define RT_ZELNIAK 222 +#define RT_ZOMBIE 70 +#define RT_ZOMBIE_2 471 + const char* GetRaceIDName(uint16 race_id); const char* GetPlayerRaceName(uint32 player_race_value); From 68680ac9d819ebe3647fcf4eb5d3fc53ecfc1816 Mon Sep 17 00:00:00 2001 From: Uleat Date: Sun, 5 Feb 2017 02:37:01 -0500 Subject: [PATCH 42/50] Created sub-d for database tools --- .../scripts/{ => database_tools}/spell_effect_token_function.sql | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename utils/scripts/{ => database_tools}/spell_effect_token_function.sql (100%) diff --git a/utils/scripts/spell_effect_token_function.sql b/utils/scripts/database_tools/spell_effect_token_function.sql similarity index 100% rename from utils/scripts/spell_effect_token_function.sql rename to utils/scripts/database_tools/spell_effect_token_function.sql From 1d1382cb12b21533e5da663ee4843b812d051a7b Mon Sep 17 00:00:00 2001 From: Uleat Date: Sun, 5 Feb 2017 04:32:32 -0500 Subject: [PATCH 43/50] * Added dev script for function to retrieve race id labels from queries [skip ci] --- .../database_tools/race_token_function.sql | 756 ++++++++++++++++++ .../spell_effect_token_function.sql | 2 +- 2 files changed, 757 insertions(+), 1 deletion(-) create mode 100644 utils/scripts/database_tools/race_token_function.sql diff --git a/utils/scripts/database_tools/race_token_function.sql b/utils/scripts/database_tools/race_token_function.sql new file mode 100644 index 000000000..f31fd1177 --- /dev/null +++ b/utils/scripts/database_tools/race_token_function.sql @@ -0,0 +1,756 @@ +DELIMITER $$ + +DROP FUNCTION IF EXISTS `GetRaceToken`; + +-- This function converts a numeric race id to a string label based on server code designations +-- +-- example: +-- SELECT `id`, `name`, GetRaceToken(`race`) FROM `npc_types` WHERE `id` IN ('644', '105153', '261131'); +CREATE FUNCTION `GetRaceToken` (`race_id` INT(11)) RETURNS VARCHAR(64) +BEGIN + DECLARE `token` VARCHAR(64) DEFAULT ''; + + CASE `race_id` + WHEN '0' THEN SET `token` = 'UNKNOWN RACE'; + WHEN '1' THEN SET `token` = 'Human'; + WHEN '2' THEN SET `token` = 'Barbarian'; + WHEN '3' THEN SET `token` = 'Erudite'; + WHEN '4' THEN SET `token` = 'Wood Elf'; + WHEN '5' THEN SET `token` = 'High Elf'; + WHEN '6' THEN SET `token` = 'Dark Elf'; + WHEN '7' THEN SET `token` = 'Half Elf'; + WHEN '8' THEN SET `token` = 'Dwarf'; + WHEN '9' THEN SET `token` = 'Troll'; + WHEN '10' THEN SET `token` = 'Ogre'; + WHEN '11' THEN SET `token` = 'Halfling'; + WHEN '12' THEN SET `token` = 'Gnome'; + WHEN '13' THEN SET `token` = 'Aviak'; + WHEN '14' THEN SET `token` = 'Werewolf'; + WHEN '15' THEN SET `token` = 'Brownie'; + WHEN '16' THEN SET `token` = 'Centaur'; + WHEN '17' THEN SET `token` = 'Golem'; + WHEN '18' THEN SET `token` = 'Giant'; + WHEN '19' THEN SET `token` = 'Trakanon'; + WHEN '20' THEN SET `token` = 'Venril Sathir'; + WHEN '21' THEN SET `token` = 'Evil Eye'; + WHEN '22' THEN SET `token` = 'Beetle'; + WHEN '23' THEN SET `token` = 'Kerran'; + WHEN '24' THEN SET `token` = 'Fish'; + WHEN '25' THEN SET `token` = 'Fairy'; + WHEN '26' THEN SET `token` = 'Froglok'; + WHEN '27' THEN SET `token` = 'Froglok'; + WHEN '28' THEN SET `token` = 'Fungusman'; + WHEN '29' THEN SET `token` = 'Gargoyle'; + WHEN '30' THEN SET `token` = 'Gasbag'; + WHEN '31' THEN SET `token` = 'Gelatinous Cube'; + WHEN '32' THEN SET `token` = 'Ghost'; + WHEN '33' THEN SET `token` = 'Ghoul'; + WHEN '34' THEN SET `token` = 'Bat'; + WHEN '35' THEN SET `token` = 'Eel'; + WHEN '36' THEN SET `token` = 'Rat'; + WHEN '37' THEN SET `token` = 'Snake'; + WHEN '38' THEN SET `token` = 'Spider'; + WHEN '39' THEN SET `token` = 'Gnoll'; + WHEN '40' THEN SET `token` = 'Goblin'; + WHEN '41' THEN SET `token` = 'Gorilla'; + WHEN '42' THEN SET `token` = 'Wolf'; + WHEN '43' THEN SET `token` = 'Bear'; + WHEN '44' THEN SET `token` = 'Guard'; + WHEN '45' THEN SET `token` = 'Demi Lich'; + WHEN '46' THEN SET `token` = 'Imp'; + WHEN '47' THEN SET `token` = 'Griffin'; + WHEN '48' THEN SET `token` = 'Kobold'; + WHEN '49' THEN SET `token` = 'Dragon'; + WHEN '50' THEN SET `token` = 'Lion'; + WHEN '51' THEN SET `token` = 'Lizard Man'; + WHEN '52' THEN SET `token` = 'Mimic'; + WHEN '53' THEN SET `token` = 'Minotaur'; + WHEN '54' THEN SET `token` = 'Orc'; + WHEN '55' THEN SET `token` = 'Beggar'; + WHEN '56' THEN SET `token` = 'Pixie'; + WHEN '57' THEN SET `token` = 'Drachnid'; + WHEN '58' THEN SET `token` = 'Solusek Ro'; + WHEN '59' THEN SET `token` = 'Goblin'; + WHEN '60' THEN SET `token` = 'Skeleton'; + WHEN '61' THEN SET `token` = 'Shark'; + WHEN '62' THEN SET `token` = 'Tunare'; + WHEN '63' THEN SET `token` = 'Tiger'; + WHEN '64' THEN SET `token` = 'Treant'; + WHEN '65' THEN SET `token` = 'Vampire'; + WHEN '66' THEN SET `token` = 'Rallos Zek'; + WHEN '67' THEN SET `token` = 'Human'; + WHEN '68' THEN SET `token` = 'Tentacle Terror'; + WHEN '69' THEN SET `token` = 'Will-O-Wisp'; + WHEN '70' THEN SET `token` = 'Zombie'; + WHEN '71' THEN SET `token` = 'Human'; + WHEN '72' THEN SET `token` = 'Ship'; + WHEN '73' THEN SET `token` = 'Launch'; + WHEN '74' THEN SET `token` = 'Piranha'; + WHEN '75' THEN SET `token` = 'Elemental'; + WHEN '76' THEN SET `token` = 'Puma'; + WHEN '77' THEN SET `token` = 'Dark Elf'; + WHEN '78' THEN SET `token` = 'Erudite'; + WHEN '79' THEN SET `token` = 'Bixie'; + WHEN '80' THEN SET `token` = 'Reanimated Hand'; + WHEN '81' THEN SET `token` = 'Halfling'; + WHEN '82' THEN SET `token` = 'Scarecrow'; + WHEN '83' THEN SET `token` = 'Skunk'; + WHEN '84' THEN SET `token` = 'Snake Elemental'; + WHEN '85' THEN SET `token` = 'Spectre'; + WHEN '86' THEN SET `token` = 'Sphinx'; + WHEN '87' THEN SET `token` = 'Armadillo'; + WHEN '88' THEN SET `token` = 'Clockwork Gnome'; + WHEN '89' THEN SET `token` = 'Drake'; + WHEN '90' THEN SET `token` = 'Barbarian'; + WHEN '91' THEN SET `token` = 'Alligator'; + WHEN '92' THEN SET `token` = 'Troll'; + WHEN '93' THEN SET `token` = 'Ogre'; + WHEN '94' THEN SET `token` = 'Dwarf'; + WHEN '95' THEN SET `token` = 'Cazic Thule'; + WHEN '96' THEN SET `token` = 'Cockatrice'; + WHEN '97' THEN SET `token` = 'Daisy Man'; + WHEN '98' THEN SET `token` = 'Vampire'; + WHEN '99' THEN SET `token` = 'Amygdalan'; + WHEN '100' THEN SET `token` = 'Dervish'; + WHEN '101' THEN SET `token` = 'Efreeti'; + WHEN '102' THEN SET `token` = 'Tadpole'; + WHEN '103' THEN SET `token` = 'Kedge'; + WHEN '104' THEN SET `token` = 'Leech'; + WHEN '105' THEN SET `token` = 'Swordfish'; + WHEN '106' THEN SET `token` = 'Guard'; + WHEN '107' THEN SET `token` = 'Mammoth'; + WHEN '108' THEN SET `token` = 'Eye'; + WHEN '109' THEN SET `token` = 'Wasp'; + WHEN '110' THEN SET `token` = 'Mermaid'; + WHEN '111' THEN SET `token` = 'Harpy'; + WHEN '112' THEN SET `token` = 'Guard'; + WHEN '113' THEN SET `token` = 'Drixie'; + WHEN '114' THEN SET `token` = 'Ghost Ship'; + WHEN '115' THEN SET `token` = 'Clam'; + WHEN '116' THEN SET `token` = 'Seahorse'; + WHEN '117' THEN SET `token` = 'Ghost'; + WHEN '118' THEN SET `token` = 'Ghost'; + WHEN '119' THEN SET `token` = 'Saber-toothed Cat'; + WHEN '120' THEN SET `token` = 'Wolf'; + WHEN '121' THEN SET `token` = 'Gorgon'; + WHEN '122' THEN SET `token` = 'Dragon'; + WHEN '123' THEN SET `token` = 'Innoruuk'; + WHEN '124' THEN SET `token` = 'Unicorn'; + WHEN '125' THEN SET `token` = 'Pegasus'; + WHEN '126' THEN SET `token` = 'Djinn'; + WHEN '127' THEN SET `token` = 'Invisible Man'; + WHEN '128' THEN SET `token` = 'Iksar'; + WHEN '129' THEN SET `token` = 'Scorpion'; + WHEN '130' THEN SET `token` = 'Vah Shir'; + WHEN '131' THEN SET `token` = 'Sarnak'; + WHEN '132' THEN SET `token` = 'Draglock'; + WHEN '133' THEN SET `token` = 'Drolvarg'; + WHEN '134' THEN SET `token` = 'Mosquito'; + WHEN '135' THEN SET `token` = 'Rhinoceros'; + WHEN '136' THEN SET `token` = 'Xalgoz'; + WHEN '137' THEN SET `token` = 'Goblin'; + WHEN '138' THEN SET `token` = 'Yeti'; + WHEN '139' THEN SET `token` = 'Iksar'; + WHEN '140' THEN SET `token` = 'Giant'; + WHEN '141' THEN SET `token` = 'Boat'; + WHEN '142' THEN SET `token` = 'UNKNOWN RACE'; + WHEN '143' THEN SET `token` = 'UNKNOWN RACE'; + WHEN '144' THEN SET `token` = 'Burynai'; + WHEN '145' THEN SET `token` = 'Goo'; + WHEN '146' THEN SET `token` = 'Sarnak Spirit'; + WHEN '147' THEN SET `token` = 'Iksar Spirit'; + WHEN '148' THEN SET `token` = 'Fish'; + WHEN '149' THEN SET `token` = 'Scorpion'; + WHEN '150' THEN SET `token` = 'Erollisi'; + WHEN '151' THEN SET `token` = 'Tribunal'; + WHEN '152' THEN SET `token` = 'Bertoxxulous'; + WHEN '153' THEN SET `token` = 'Bristlebane'; + WHEN '154' THEN SET `token` = 'Fay Drake'; + WHEN '155' THEN SET `token` = 'Undead Sarnak'; + WHEN '156' THEN SET `token` = 'Ratman'; + WHEN '157' THEN SET `token` = 'Wyvern'; + WHEN '158' THEN SET `token` = 'Wurm'; + WHEN '159' THEN SET `token` = 'Devourer'; + WHEN '160' THEN SET `token` = 'Iksar Golem'; + WHEN '161' THEN SET `token` = 'Undead Iksar'; + WHEN '162' THEN SET `token` = 'Man-Eating Plant'; + WHEN '163' THEN SET `token` = 'Raptor'; + WHEN '164' THEN SET `token` = 'Sarnak Golem'; + WHEN '165' THEN SET `token` = 'Dragon'; + WHEN '166' THEN SET `token` = 'Animated Hand'; + WHEN '167' THEN SET `token` = 'Succulent'; + WHEN '168' THEN SET `token` = 'Holgresh'; + WHEN '169' THEN SET `token` = 'Brontotherium'; + WHEN '170' THEN SET `token` = 'Snow Dervish'; + WHEN '171' THEN SET `token` = 'Dire Wolf'; + WHEN '172' THEN SET `token` = 'Manticore'; + WHEN '173' THEN SET `token` = 'Totem'; + WHEN '174' THEN SET `token` = 'Ice Spectre'; + WHEN '175' THEN SET `token` = 'Enchanted Armor'; + WHEN '176' THEN SET `token` = 'Snow Rabbit'; + WHEN '177' THEN SET `token` = 'Walrus'; + WHEN '178' THEN SET `token` = 'Geonid'; + WHEN '179' THEN SET `token` = 'UNKNOWN RACE'; + WHEN '180' THEN SET `token` = 'UNKNOWN RACE'; + WHEN '181' THEN SET `token` = 'Yakkar'; + WHEN '182' THEN SET `token` = 'Faun'; + WHEN '183' THEN SET `token` = 'Coldain'; + WHEN '184' THEN SET `token` = 'Dragon'; + WHEN '185' THEN SET `token` = 'Hag'; + WHEN '186' THEN SET `token` = 'Hippogriff'; + WHEN '187' THEN SET `token` = 'Siren'; + WHEN '188' THEN SET `token` = 'Giant'; + WHEN '189' THEN SET `token` = 'Giant'; + WHEN '190' THEN SET `token` = 'Othmir'; + WHEN '191' THEN SET `token` = 'Ulthork'; + WHEN '192' THEN SET `token` = 'Dragon'; + WHEN '193' THEN SET `token` = 'Abhorrent'; + WHEN '194' THEN SET `token` = 'Sea Turtle'; + WHEN '195' THEN SET `token` = 'Dragon'; + WHEN '196' THEN SET `token` = 'Dragon'; + WHEN '197' THEN SET `token` = 'Ronnie Test'; + WHEN '198' THEN SET `token` = 'Dragon'; + WHEN '199' THEN SET `token` = 'Shik\'Nar'; + WHEN '200' THEN SET `token` = 'Rockhopper'; + WHEN '201' THEN SET `token` = 'Underbulk'; + WHEN '202' THEN SET `token` = 'Grimling'; + WHEN '203' THEN SET `token` = 'Worm'; + WHEN '204' THEN SET `token` = 'Evan Test'; + WHEN '205' THEN SET `token` = 'Shadel'; + WHEN '206' THEN SET `token` = 'Owlbear'; + WHEN '207' THEN SET `token` = 'Rhino Beetle'; + WHEN '208' THEN SET `token` = 'Vampire'; + WHEN '209' THEN SET `token` = 'Earth Elemental'; + WHEN '210' THEN SET `token` = 'Air Elemental'; + WHEN '211' THEN SET `token` = 'Water Elemental'; + WHEN '212' THEN SET `token` = 'Fire Elemental'; + WHEN '213' THEN SET `token` = 'Wetfang Minnow'; + WHEN '214' THEN SET `token` = 'Thought Horror'; + WHEN '215' THEN SET `token` = 'Tegi'; + WHEN '216' THEN SET `token` = 'Horse'; + WHEN '217' THEN SET `token` = 'Shissar'; + WHEN '218' THEN SET `token` = 'Fungal Fiend'; + WHEN '219' THEN SET `token` = 'Vampire'; + WHEN '220' THEN SET `token` = 'Stonegrabber'; + WHEN '221' THEN SET `token` = 'Scarlet Cheetah'; + WHEN '222' THEN SET `token` = 'Zelniak'; + WHEN '223' THEN SET `token` = 'Lightcrawler'; + WHEN '224' THEN SET `token` = 'Shade'; + WHEN '225' THEN SET `token` = 'Sunflower'; + WHEN '226' THEN SET `token` = 'Sun Revenant'; + WHEN '227' THEN SET `token` = 'Shrieker'; + WHEN '228' THEN SET `token` = 'Galorian'; + WHEN '229' THEN SET `token` = 'Netherbian'; + WHEN '230' THEN SET `token` = 'Akheva'; + WHEN '231' THEN SET `token` = 'Grieg Veneficus'; + WHEN '232' THEN SET `token` = 'Sonic Wolf'; + WHEN '233' THEN SET `token` = 'Ground Shaker'; + WHEN '234' THEN SET `token` = 'Vah Shir Skeleton'; + WHEN '235' THEN SET `token` = 'Wretch'; + WHEN '236' THEN SET `token` = 'Seru'; + WHEN '237' THEN SET `token` = 'Recuso'; + WHEN '238' THEN SET `token` = 'Vah Shir'; + WHEN '239' THEN SET `token` = 'Guard'; + WHEN '240' THEN SET `token` = 'Teleport Man'; + WHEN '241' THEN SET `token` = 'Werewolf'; + WHEN '242' THEN SET `token` = 'Nymph'; + WHEN '243' THEN SET `token` = 'Dryad'; + WHEN '244' THEN SET `token` = 'Treant'; + WHEN '245' THEN SET `token` = 'Fly'; + WHEN '246' THEN SET `token` = 'Tarew Marr'; + WHEN '247' THEN SET `token` = 'Solusek Ro'; + WHEN '248' THEN SET `token` = 'Clockwork Golem'; + WHEN '249' THEN SET `token` = 'Clockwork Brain'; + WHEN '250' THEN SET `token` = 'Banshee'; + WHEN '251' THEN SET `token` = 'Guard of Justice'; + WHEN '252' THEN SET `token` = 'Mini POM'; + WHEN '253' THEN SET `token` = 'Diseased Fiend'; + WHEN '254' THEN SET `token` = 'Solusek Ro Guard'; + WHEN '255' THEN SET `token` = 'Bertoxxulous'; + WHEN '256' THEN SET `token` = 'The Tribunal'; + WHEN '257' THEN SET `token` = 'Terris Thule'; + WHEN '258' THEN SET `token` = 'Vegerog'; + WHEN '259' THEN SET `token` = 'Crocodile'; + WHEN '260' THEN SET `token` = 'Bat'; + WHEN '261' THEN SET `token` = 'Hraquis'; + WHEN '262' THEN SET `token` = 'Tranquilion'; + WHEN '263' THEN SET `token` = 'Tin Soldier'; + WHEN '264' THEN SET `token` = 'Nightmare Wraith'; + WHEN '265' THEN SET `token` = 'Malarian'; + WHEN '266' THEN SET `token` = 'Knight of Pestilence'; + WHEN '267' THEN SET `token` = 'Lepertoloth'; + WHEN '268' THEN SET `token` = 'Bubonian'; + WHEN '269' THEN SET `token` = 'Bubonian Underling'; + WHEN '270' THEN SET `token` = 'Pusling'; + WHEN '271' THEN SET `token` = 'Water Mephit'; + WHEN '272' THEN SET `token` = 'Stormrider'; + WHEN '273' THEN SET `token` = 'Junk Beast'; + WHEN '274' THEN SET `token` = 'Broken Clockwork'; + WHEN '275' THEN SET `token` = 'Giant Clockwork'; + WHEN '276' THEN SET `token` = 'Clockwork Beetle'; + WHEN '277' THEN SET `token` = 'Nightmare Goblin'; + WHEN '278' THEN SET `token` = 'Karana'; + WHEN '279' THEN SET `token` = 'Blood Raven'; + WHEN '280' THEN SET `token` = 'Nightmare Gargoyle'; + WHEN '281' THEN SET `token` = 'Mouth of Insanity'; + WHEN '282' THEN SET `token` = 'Skeletal Horse'; + WHEN '283' THEN SET `token` = 'Saryrn'; + WHEN '284' THEN SET `token` = 'Fennin Ro'; + WHEN '285' THEN SET `token` = 'Tormentor'; + WHEN '286' THEN SET `token` = 'Soul Devourer'; + WHEN '287' THEN SET `token` = 'Nightmare'; + WHEN '288' THEN SET `token` = 'Rallos Zek'; + WHEN '289' THEN SET `token` = 'Vallon Zek'; + WHEN '290' THEN SET `token` = 'Tallon Zek'; + WHEN '291' THEN SET `token` = 'Air Mephit'; + WHEN '292' THEN SET `token` = 'Earth Mephit'; + WHEN '293' THEN SET `token` = 'Fire Mephit'; + WHEN '294' THEN SET `token` = 'Nightmare Mephit'; + WHEN '295' THEN SET `token` = 'Zebuxoruk'; + WHEN '296' THEN SET `token` = 'Mithaniel Marr'; + WHEN '297' THEN SET `token` = 'Undead Knight'; + WHEN '298' THEN SET `token` = 'The Rathe'; + WHEN '299' THEN SET `token` = 'Xegony'; + WHEN '300' THEN SET `token` = 'Fiend'; + WHEN '301' THEN SET `token` = 'Test Object'; + WHEN '302' THEN SET `token` = 'Crab'; + WHEN '303' THEN SET `token` = 'Phoenix'; + WHEN '304' THEN SET `token` = 'Dragon'; + WHEN '305' THEN SET `token` = 'Bear'; + WHEN '306' THEN SET `token` = 'Giant'; + WHEN '307' THEN SET `token` = 'Giant'; + WHEN '308' THEN SET `token` = 'Giant'; + WHEN '309' THEN SET `token` = 'Giant'; + WHEN '310' THEN SET `token` = 'Giant'; + WHEN '311' THEN SET `token` = 'Giant'; + WHEN '312' THEN SET `token` = 'Giant'; + WHEN '313' THEN SET `token` = 'War Wraith'; + WHEN '314' THEN SET `token` = 'Wrulon'; + WHEN '315' THEN SET `token` = 'Kraken'; + WHEN '316' THEN SET `token` = 'Poison Frog'; + WHEN '317' THEN SET `token` = 'Nilborien'; + WHEN '318' THEN SET `token` = 'Valorian'; + WHEN '319' THEN SET `token` = 'War Boar'; + WHEN '320' THEN SET `token` = 'Efreeti'; + WHEN '321' THEN SET `token` = 'War Boar'; + WHEN '322' THEN SET `token` = 'Valorian'; + WHEN '323' THEN SET `token` = 'Animated Armor'; + WHEN '324' THEN SET `token` = 'Undead Footman'; + WHEN '325' THEN SET `token` = 'Rallos Zek Minion'; + WHEN '326' THEN SET `token` = 'Arachnid'; + WHEN '327' THEN SET `token` = 'Crystal Spider'; + WHEN '328' THEN SET `token` = 'Zebuxoruk\'s Cage'; + WHEN '329' THEN SET `token` = 'BoT Portal'; + WHEN '330' THEN SET `token` = 'Froglok'; + WHEN '331' THEN SET `token` = 'Troll'; + WHEN '332' THEN SET `token` = 'Troll'; + WHEN '333' THEN SET `token` = 'Troll'; + WHEN '334' THEN SET `token` = 'Ghost'; + WHEN '335' THEN SET `token` = 'Pirate'; + WHEN '336' THEN SET `token` = 'Pirate'; + WHEN '337' THEN SET `token` = 'Pirate'; + WHEN '338' THEN SET `token` = 'Pirate'; + WHEN '339' THEN SET `token` = 'Pirate'; + WHEN '340' THEN SET `token` = 'Pirate'; + WHEN '341' THEN SET `token` = 'Pirate'; + WHEN '342' THEN SET `token` = 'Pirate'; + WHEN '343' THEN SET `token` = 'Frog'; + WHEN '344' THEN SET `token` = 'Troll Zombie'; + WHEN '345' THEN SET `token` = 'Luggald'; + WHEN '346' THEN SET `token` = 'Luggald'; + WHEN '347' THEN SET `token` = 'Luggalds'; + WHEN '348' THEN SET `token` = 'Drogmore'; + WHEN '349' THEN SET `token` = 'Froglok Skeleton'; + WHEN '350' THEN SET `token` = 'Undead Froglok'; + WHEN '351' THEN SET `token` = 'Knight of Hate'; + WHEN '352' THEN SET `token` = 'Arcanist of Hate'; + WHEN '353' THEN SET `token` = 'Veksar'; + WHEN '354' THEN SET `token` = 'Veksar'; + WHEN '355' THEN SET `token` = 'Veksar'; + WHEN '356' THEN SET `token` = 'Chokidai'; + WHEN '357' THEN SET `token` = 'Undead Chokidai'; + WHEN '358' THEN SET `token` = 'Undead Veksar'; + WHEN '359' THEN SET `token` = 'Vampire'; + WHEN '360' THEN SET `token` = 'Vampire'; + WHEN '361' THEN SET `token` = 'Rujarkian Orc'; + WHEN '362' THEN SET `token` = 'Bone Golem'; + WHEN '363' THEN SET `token` = 'Synarcana'; + WHEN '364' THEN SET `token` = 'Sand Elf'; + WHEN '365' THEN SET `token` = 'Vampire'; + WHEN '366' THEN SET `token` = 'Rujarkian Orc'; + WHEN '367' THEN SET `token` = 'Skeleton'; + WHEN '368' THEN SET `token` = 'Mummy'; + WHEN '369' THEN SET `token` = 'Goblin'; + WHEN '370' THEN SET `token` = 'Insect'; + WHEN '371' THEN SET `token` = 'Froglok Ghost'; + WHEN '372' THEN SET `token` = 'Dervish'; + WHEN '373' THEN SET `token` = 'Shade'; + WHEN '374' THEN SET `token` = 'Golem'; + WHEN '375' THEN SET `token` = 'Evil Eye'; + WHEN '376' THEN SET `token` = 'Box'; + WHEN '377' THEN SET `token` = 'Barrel'; + WHEN '378' THEN SET `token` = 'Chest'; + WHEN '379' THEN SET `token` = 'Vase'; + WHEN '380' THEN SET `token` = 'Table'; + WHEN '381' THEN SET `token` = 'Weapon Rack'; + WHEN '382' THEN SET `token` = 'Coffin'; + WHEN '383' THEN SET `token` = 'Bones'; + WHEN '384' THEN SET `token` = 'Jokester'; + WHEN '385' THEN SET `token` = 'Nihil'; + WHEN '386' THEN SET `token` = 'Trusik'; + WHEN '387' THEN SET `token` = 'Stone Worker'; + WHEN '388' THEN SET `token` = 'Hynid'; + WHEN '389' THEN SET `token` = 'Turepta'; + WHEN '390' THEN SET `token` = 'Cragbeast'; + WHEN '391' THEN SET `token` = 'Stonemite'; + WHEN '392' THEN SET `token` = 'Ukun'; + WHEN '393' THEN SET `token` = 'Ixt'; + WHEN '394' THEN SET `token` = 'Ikaav'; + WHEN '395' THEN SET `token` = 'Aneuk'; + WHEN '396' THEN SET `token` = 'Kyv'; + WHEN '397' THEN SET `token` = 'Noc'; + WHEN '398' THEN SET `token` = 'Ra`tuk'; + WHEN '399' THEN SET `token` = 'Taneth'; + WHEN '400' THEN SET `token` = 'Huvul'; + WHEN '401' THEN SET `token` = 'Mutna'; + WHEN '402' THEN SET `token` = 'Mastruq'; + WHEN '403' THEN SET `token` = 'Taelosian'; + WHEN '404' THEN SET `token` = 'Discord Ship'; + WHEN '405' THEN SET `token` = 'Stone Worker'; + WHEN '406' THEN SET `token` = 'Mata Muram'; + WHEN '407' THEN SET `token` = 'Lightning Warrior'; + WHEN '408' THEN SET `token` = 'Succubus'; + WHEN '409' THEN SET `token` = 'Bazu'; + WHEN '410' THEN SET `token` = 'Feran'; + WHEN '411' THEN SET `token` = 'Pyrilen'; + WHEN '412' THEN SET `token` = 'Chimera'; + WHEN '413' THEN SET `token` = 'Dragorn'; + WHEN '414' THEN SET `token` = 'Murkglider'; + WHEN '415' THEN SET `token` = 'Rat'; + WHEN '416' THEN SET `token` = 'Bat'; + WHEN '417' THEN SET `token` = 'Gelidran'; + WHEN '418' THEN SET `token` = 'Discordling'; + WHEN '419' THEN SET `token` = 'Girplan'; + WHEN '420' THEN SET `token` = 'Minotaur'; + WHEN '421' THEN SET `token` = 'Dragorn Box'; + WHEN '422' THEN SET `token` = 'Runed Orb'; + WHEN '423' THEN SET `token` = 'Dragon Bones'; + WHEN '424' THEN SET `token` = 'Muramite Armor Pile'; + WHEN '425' THEN SET `token` = 'Crystal Shard'; + WHEN '426' THEN SET `token` = 'Portal'; + WHEN '427' THEN SET `token` = 'Coin Purse'; + WHEN '428' THEN SET `token` = 'Rock Pile'; + WHEN '429' THEN SET `token` = 'Murkglider Egg Sac'; + WHEN '430' THEN SET `token` = 'Drake'; + WHEN '431' THEN SET `token` = 'Dervish'; + WHEN '432' THEN SET `token` = 'Drake'; + WHEN '433' THEN SET `token` = 'Goblin'; + WHEN '434' THEN SET `token` = 'Kirin'; + WHEN '435' THEN SET `token` = 'Dragon'; + WHEN '436' THEN SET `token` = 'Basilisk'; + WHEN '437' THEN SET `token` = 'Dragon'; + WHEN '438' THEN SET `token` = 'Dragon'; + WHEN '439' THEN SET `token` = 'Puma'; + WHEN '440' THEN SET `token` = 'Spider'; + WHEN '441' THEN SET `token` = 'Spider Queen'; + WHEN '442' THEN SET `token` = 'Animated Statue'; + WHEN '443' THEN SET `token` = 'UNKNOWN RACE'; + WHEN '444' THEN SET `token` = 'UNKNOWN RACE'; + WHEN '445' THEN SET `token` = 'Dragon Egg'; + WHEN '446' THEN SET `token` = 'Dragon Statue'; + WHEN '447' THEN SET `token` = 'Lava Rock'; + WHEN '448' THEN SET `token` = 'Animated Statue'; + WHEN '449' THEN SET `token` = 'Spider Egg Sack'; + WHEN '450' THEN SET `token` = 'Lava Spider'; + WHEN '451' THEN SET `token` = 'Lava Spider Queen'; + WHEN '452' THEN SET `token` = 'Dragon'; + WHEN '453' THEN SET `token` = 'Giant'; + WHEN '454' THEN SET `token` = 'Werewolf'; + WHEN '455' THEN SET `token` = 'Kobold'; + WHEN '456' THEN SET `token` = 'Sporali'; + WHEN '457' THEN SET `token` = 'Gnomework'; + WHEN '458' THEN SET `token` = 'Orc'; + WHEN '459' THEN SET `token` = 'Corathus'; + WHEN '460' THEN SET `token` = 'Coral'; + WHEN '461' THEN SET `token` = 'Drachnid'; + WHEN '462' THEN SET `token` = 'Drachnid Cocoon'; + WHEN '463' THEN SET `token` = 'Fungus Patch'; + WHEN '464' THEN SET `token` = 'Gargoyle'; + WHEN '465' THEN SET `token` = 'Witheran'; + WHEN '466' THEN SET `token` = 'Dark Lord'; + WHEN '467' THEN SET `token` = 'Shiliskin'; + WHEN '468' THEN SET `token` = 'Snake'; + WHEN '469' THEN SET `token` = 'Evil Eye'; + WHEN '470' THEN SET `token` = 'Minotaur'; + WHEN '471' THEN SET `token` = 'Zombie'; + WHEN '472' THEN SET `token` = 'Clockwork Boar'; + WHEN '473' THEN SET `token` = 'Fairy'; + WHEN '474' THEN SET `token` = 'Witheran'; + WHEN '475' THEN SET `token` = 'Air Elemental'; + WHEN '476' THEN SET `token` = 'Earth Elemental'; + WHEN '477' THEN SET `token` = 'Fire Elemental'; + WHEN '478' THEN SET `token` = 'Water Elemental'; + WHEN '479' THEN SET `token` = 'Alligator'; + WHEN '480' THEN SET `token` = 'Bear'; + WHEN '481' THEN SET `token` = 'Scaled Wolf'; + WHEN '482' THEN SET `token` = 'Wolf'; + WHEN '483' THEN SET `token` = 'Spirit Wolf'; + WHEN '484' THEN SET `token` = 'Skeleton'; + WHEN '485' THEN SET `token` = 'Spectre'; + WHEN '486' THEN SET `token` = 'Bolvirk'; + WHEN '487' THEN SET `token` = 'Banshee'; + WHEN '488' THEN SET `token` = 'Banshee'; + WHEN '489' THEN SET `token` = 'Elddar'; + WHEN '490' THEN SET `token` = 'Forest Giant'; + WHEN '491' THEN SET `token` = 'Bone Golem'; + WHEN '492' THEN SET `token` = 'Horse'; + WHEN '493' THEN SET `token` = 'Pegasus'; + WHEN '494' THEN SET `token` = 'Shambling Mound'; + WHEN '495' THEN SET `token` = 'Scrykin'; + WHEN '496' THEN SET `token` = 'Treant'; + WHEN '497' THEN SET `token` = 'Vampire'; + WHEN '498' THEN SET `token` = 'Ayonae Ro'; + WHEN '499' THEN SET `token` = 'Sullon Zek'; + WHEN '500' THEN SET `token` = 'Banner'; + WHEN '501' THEN SET `token` = 'Flag'; + WHEN '502' THEN SET `token` = 'Rowboat'; + WHEN '503' THEN SET `token` = 'Bear Trap'; + WHEN '504' THEN SET `token` = 'Clockwork Bomb'; + WHEN '505' THEN SET `token` = 'Dynamite Keg'; + WHEN '506' THEN SET `token` = 'Pressure Plate'; + WHEN '507' THEN SET `token` = 'Puffer Spore'; + WHEN '508' THEN SET `token` = 'Stone Ring'; + WHEN '509' THEN SET `token` = 'Root Tentacle'; + WHEN '510' THEN SET `token` = 'Runic Symbol'; + WHEN '511' THEN SET `token` = 'Saltpetter Bomb'; + WHEN '512' THEN SET `token` = 'Floating Skull'; + WHEN '513' THEN SET `token` = 'Spike Trap'; + WHEN '514' THEN SET `token` = 'Totem'; + WHEN '515' THEN SET `token` = 'Web'; + WHEN '516' THEN SET `token` = 'Wicker Basket'; + WHEN '517' THEN SET `token` = 'Nightmare/Unicorn'; + WHEN '518' THEN SET `token` = 'Horse'; + WHEN '519' THEN SET `token` = 'Nightmare/Unicorn'; + WHEN '520' THEN SET `token` = 'Bixie'; + WHEN '521' THEN SET `token` = 'Centaur'; + WHEN '522' THEN SET `token` = 'Drakkin'; + WHEN '523' THEN SET `token` = 'Giant'; + WHEN '524' THEN SET `token` = 'Gnoll'; + WHEN '525' THEN SET `token` = 'Griffin'; + WHEN '526' THEN SET `token` = 'Giant Shade'; + WHEN '527' THEN SET `token` = 'Harpy'; + WHEN '528' THEN SET `token` = 'Mammoth'; + WHEN '529' THEN SET `token` = 'Satyr'; + WHEN '530' THEN SET `token` = 'Dragon'; + WHEN '531' THEN SET `token` = 'Dragon'; + WHEN '532' THEN SET `token` = 'Dyn\'Leth'; + WHEN '533' THEN SET `token` = 'Boat'; + WHEN '534' THEN SET `token` = 'Weapon Rack'; + WHEN '535' THEN SET `token` = 'Armor Rack'; + WHEN '536' THEN SET `token` = 'Honey Pot'; + WHEN '537' THEN SET `token` = 'Jum Jum Bucket'; + WHEN '538' THEN SET `token` = 'Toolbox'; + WHEN '539' THEN SET `token` = 'Stone Jug'; + WHEN '540' THEN SET `token` = 'Small Plant'; + WHEN '541' THEN SET `token` = 'Medium Plant'; + WHEN '542' THEN SET `token` = 'Tall Plant'; + WHEN '543' THEN SET `token` = 'Wine Cask'; + WHEN '544' THEN SET `token` = 'Elven Boat'; + WHEN '545' THEN SET `token` = 'Gnomish Boat'; + WHEN '546' THEN SET `token` = 'Barrel Barge Ship'; + WHEN '547' THEN SET `token` = 'Goo'; + WHEN '548' THEN SET `token` = 'Goo'; + WHEN '549' THEN SET `token` = 'Goo'; + WHEN '550' THEN SET `token` = 'Merchant Ship'; + WHEN '551' THEN SET `token` = 'Pirate Ship'; + WHEN '552' THEN SET `token` = 'Ghost Ship'; + WHEN '553' THEN SET `token` = 'Banner'; + WHEN '554' THEN SET `token` = 'Banner'; + WHEN '555' THEN SET `token` = 'Banner'; + WHEN '556' THEN SET `token` = 'Banner'; + WHEN '557' THEN SET `token` = 'Banner'; + WHEN '558' THEN SET `token` = 'Aviak'; + WHEN '559' THEN SET `token` = 'Beetle'; + WHEN '560' THEN SET `token` = 'Gorilla'; + WHEN '561' THEN SET `token` = 'Kedge'; + WHEN '562' THEN SET `token` = 'Kerran'; + WHEN '563' THEN SET `token` = 'Shissar'; + WHEN '564' THEN SET `token` = 'Siren'; + WHEN '565' THEN SET `token` = 'Sphinx'; + WHEN '566' THEN SET `token` = 'Human'; + WHEN '567' THEN SET `token` = 'Campfire'; + WHEN '568' THEN SET `token` = 'Brownie'; + WHEN '569' THEN SET `token` = 'Dragon'; + WHEN '570' THEN SET `token` = 'Exoskeleton'; + WHEN '571' THEN SET `token` = 'Ghoul'; + WHEN '572' THEN SET `token` = 'Clockwork Guardian'; + WHEN '573' THEN SET `token` = 'Mantrap'; + WHEN '574' THEN SET `token` = 'Minotaur'; + WHEN '575' THEN SET `token` = 'Scarecrow'; + WHEN '576' THEN SET `token` = 'Shade'; + WHEN '577' THEN SET `token` = 'Rotocopter'; + WHEN '578' THEN SET `token` = 'Tentacle Terror'; + WHEN '579' THEN SET `token` = 'Wereorc'; + WHEN '580' THEN SET `token` = 'Worg'; + WHEN '581' THEN SET `token` = 'Wyvern'; + WHEN '582' THEN SET `token` = 'Chimera'; + WHEN '583' THEN SET `token` = 'Kirin'; + WHEN '584' THEN SET `token` = 'Puma'; + WHEN '585' THEN SET `token` = 'Boulder'; + WHEN '586' THEN SET `token` = 'Banner'; + WHEN '587' THEN SET `token` = 'Elven Ghost'; + WHEN '588' THEN SET `token` = 'Human Ghost'; + WHEN '589' THEN SET `token` = 'Chest'; + WHEN '590' THEN SET `token` = 'Chest'; + WHEN '591' THEN SET `token` = 'Crystal'; + WHEN '592' THEN SET `token` = 'Coffin'; + WHEN '593' THEN SET `token` = 'Guardian CPU'; + WHEN '594' THEN SET `token` = 'Worg'; + WHEN '595' THEN SET `token` = 'Mansion'; + WHEN '596' THEN SET `token` = 'Floating Island'; + WHEN '597' THEN SET `token` = 'Cragslither'; + WHEN '598' THEN SET `token` = 'Wrulon'; + WHEN '599' THEN SET `token` = 'Spell Particle 1'; + WHEN '600' THEN SET `token` = 'Invisible Man of Zomm'; + WHEN '601' THEN SET `token` = 'Robocopter of Zomm'; + WHEN '602' THEN SET `token` = 'Burynai'; + WHEN '603' THEN SET `token` = 'Frog'; + WHEN '604' THEN SET `token` = 'Dracolich'; + WHEN '605' THEN SET `token` = 'Iksar Ghost'; + WHEN '606' THEN SET `token` = 'Iksar Skeleton'; + WHEN '607' THEN SET `token` = 'Mephit'; + WHEN '608' THEN SET `token` = 'Muddite'; + WHEN '609' THEN SET `token` = 'Raptor'; + WHEN '610' THEN SET `token` = 'Sarnak'; + WHEN '611' THEN SET `token` = 'Scorpion'; + WHEN '612' THEN SET `token` = 'T THEN SET sian'; + WHEN '613' THEN SET `token` = 'Wurm'; + WHEN '614' THEN SET `token` = 'Nekhon'; + WHEN '615' THEN SET `token` = 'Hydra Crystal'; + WHEN '616' THEN SET `token` = 'Crystal Sphere'; + WHEN '617' THEN SET `token` = 'Gnoll'; + WHEN '618' THEN SET `token` = 'Sokokar'; + WHEN '619' THEN SET `token` = 'Stone Pylon'; + WHEN '620' THEN SET `token` = 'Demon Vulture'; + WHEN '621' THEN SET `token` = 'Wagon'; + WHEN '622' THEN SET `token` = 'God of Discord'; + WHEN '623' THEN SET `token` = 'Feran Mount'; + WHEN '624' THEN SET `token` = 'Ogre NPC - Male'; + WHEN '625' THEN SET `token` = 'Sokokar Mount'; + WHEN '626' THEN SET `token` = 'Giant (Rallosian mats)'; + WHEN '627' THEN SET `token` = 'Sokokar (w saddle)'; + WHEN '628' THEN SET `token` = '10th Anniversary Banner'; + WHEN '629' THEN SET `token` = '10th Anniversary Cake'; + WHEN '630' THEN SET `token` = 'Wine Cask'; + WHEN '631' THEN SET `token` = 'Hydra Mount'; + WHEN '632' THEN SET `token` = 'Hydra NPC'; + WHEN '633' THEN SET `token` = 'Wedding Flowers'; + WHEN '634' THEN SET `token` = 'Wedding Arbor'; + WHEN '635' THEN SET `token` = 'Wedding Altar'; + WHEN '636' THEN SET `token` = 'Powder Keg'; + WHEN '637' THEN SET `token` = 'Apexus'; + WHEN '638' THEN SET `token` = 'Bellikos'; + WHEN '639' THEN SET `token` = 'Brell\'s First Creation'; + WHEN '640' THEN SET `token` = 'Brell'; + WHEN '641' THEN SET `token` = 'Crystalskin Ambuloid'; + WHEN '642' THEN SET `token` = 'Cliknar Queen'; + WHEN '643' THEN SET `token` = 'Cliknar Soldier'; + WHEN '644' THEN SET `token` = 'Cliknar Worker'; + WHEN '645' THEN SET `token` = 'Coldain'; + WHEN '646' THEN SET `token` = 'Coldain'; + WHEN '647' THEN SET `token` = 'Crystalskin Sessiloid'; + WHEN '648' THEN SET `token` = 'Genari'; + WHEN '649' THEN SET `token` = 'Gigyn'; + WHEN '650' THEN SET `token` = 'Greken - Young Adult'; + WHEN '651' THEN SET `token` = 'Greken - Young'; + WHEN '652' THEN SET `token` = 'Cliknar Mount'; + WHEN '653' THEN SET `token` = 'Telmira'; + WHEN '654' THEN SET `token` = 'Spider Mount'; + WHEN '655' THEN SET `token` = 'Bear Mount'; + WHEN '656' THEN SET `token` = 'Rat Mount'; + WHEN '657' THEN SET `token` = 'Sessiloid Mount'; + WHEN '658' THEN SET `token` = 'Morell Thule'; + WHEN '659' THEN SET `token` = 'Marionette'; + WHEN '660' THEN SET `token` = 'Book Dervish'; + WHEN '661' THEN SET `token` = 'Topiary Lion'; + WHEN '662' THEN SET `token` = 'Rotdog'; + WHEN '663' THEN SET `token` = 'Amygdalan'; + WHEN '664' THEN SET `token` = 'Sandman'; + WHEN '665' THEN SET `token` = 'Grandfather Clock'; + WHEN '666' THEN SET `token` = 'Gingerbread Man'; + WHEN '667' THEN SET `token` = 'Royal Guard'; + WHEN '668' THEN SET `token` = 'Rabbit'; + WHEN '669' THEN SET `token` = 'Blind Dreamer'; + WHEN '670' THEN SET `token` = 'Cazic Thule'; + WHEN '671' THEN SET `token` = 'Topiary Lion Mount'; + WHEN '672' THEN SET `token` = 'Rot Dog Mount'; + WHEN '673' THEN SET `token` = 'Goral Mount'; + WHEN '674' THEN SET `token` = 'Selyrah Mount'; + WHEN '675' THEN SET `token` = 'Sclera Mount'; + WHEN '676' THEN SET `token` = 'Braxi Mount'; + WHEN '677' THEN SET `token` = 'Kangon Mount'; + WHEN '678' THEN SET `token` = 'Erudite'; + WHEN '679' THEN SET `token` = 'Wurm Mount'; + WHEN '680' THEN SET `token` = 'Raptor Mount'; + WHEN '681' THEN SET `token` = 'Invisible Man'; + WHEN '682' THEN SET `token` = 'Whirligig'; + WHEN '683' THEN SET `token` = 'Gnomish Balloon'; + WHEN '684' THEN SET `token` = 'Gnomish Rocket Pack'; + WHEN '685' THEN SET `token` = 'Gnomish Hovering Transport'; + WHEN '686' THEN SET `token` = 'Selyrah'; + WHEN '687' THEN SET `token` = 'Goral'; + WHEN '688' THEN SET `token` = 'Braxi'; + WHEN '689' THEN SET `token` = 'Kangon'; + WHEN '690' THEN SET `token` = 'Invisible Man'; + WHEN '691' THEN SET `token` = 'Floating Tower'; + WHEN '692' THEN SET `token` = 'Explosive Cart'; + WHEN '693' THEN SET `token` = 'Blimp Ship'; + WHEN '694' THEN SET `token` = 'Tumbleweed'; + WHEN '695' THEN SET `token` = 'Alaran'; + WHEN '696' THEN SET `token` = 'Swinetor'; + WHEN '697' THEN SET `token` = 'Triumvirate'; + WHEN '698' THEN SET `token` = 'Hadal'; + WHEN '699' THEN SET `token` = 'Hovering Platform'; + WHEN '700' THEN SET `token` = 'Parasitic Scavenger'; + WHEN '701' THEN SET `token` = 'Grendlaen'; + WHEN '702' THEN SET `token` = 'Ship in a Bottle'; + WHEN '703' THEN SET `token` = 'Alaran Sentry Stone'; + WHEN '704' THEN SET `token` = 'Dervish'; + WHEN '705' THEN SET `token` = 'Regeneration Pool'; + WHEN '706' THEN SET `token` = 'Teleportation Stand'; + WHEN '707' THEN SET `token` = 'Relic Case'; + WHEN '708' THEN SET `token` = 'Alaran Ghost'; + WHEN '709' THEN SET `token` = 'Skystrider'; + WHEN '710' THEN SET `token` = 'Water Spout'; + WHEN '711' THEN SET `token` = 'Aviak Pull Along'; + WHEN '712' THEN SET `token` = 'Gelatinous Cube'; + WHEN '713' THEN SET `token` = 'Cat'; + WHEN '714' THEN SET `token` = 'Elk Head'; + WHEN '715' THEN SET `token` = 'Holgresh'; + WHEN '716' THEN SET `token` = 'Beetle'; + WHEN '717' THEN SET `token` = 'Vine Maw'; + WHEN '718' THEN SET `token` = 'Ratman'; + WHEN '719' THEN SET `token` = 'Fallen Knight'; + WHEN '720' THEN SET `token` = 'Flying Carpet'; + WHEN '721' THEN SET `token` = 'Carrier Hand'; + WHEN '722' THEN SET `token` = 'Akheva'; + WHEN '723' THEN SET `token` = 'Servant of Shadow'; + WHEN '724' THEN SET `token` = 'Luclin'; + WHEN '725' THEN SET `token` = 'Xaric the Unspoken'; + WHEN '726' THEN SET `token` = 'Dervish (Ver. 5)'; + WHEN '727' THEN SET `token` = 'Dervish (Ver. 6)'; + WHEN '728' THEN SET `token` = 'God - Luclin (Ver. 2)'; + WHEN '729' THEN SET `token` = 'God - Luclin (Ver. 3)'; + WHEN '730' THEN SET `token` = 'Orb'; + WHEN '731' THEN SET `token` = 'God - Luclin (Ver. 4)'; + WHEN '732' THEN SET `token` = 'Pegasus'; + WHEN '2250' THEN SET `token` = 'Interactive Object'; + ELSE SET `token` = 'unk'; + END CASE; + + SET `token` = CONCAT(`token`, '(', `race_id`, ')'); + + RETURN `token`; +END$$ + +DELIMITER ; diff --git a/utils/scripts/database_tools/spell_effect_token_function.sql b/utils/scripts/database_tools/spell_effect_token_function.sql index 38cba8ed0..24efc9cc8 100644 --- a/utils/scripts/database_tools/spell_effect_token_function.sql +++ b/utils/scripts/database_tools/spell_effect_token_function.sql @@ -516,4 +516,4 @@ BEGIN RETURN `token`; END$$ -DELIMITER ; \ No newline at end of file +DELIMITER ; From f8f783fa462ae2d1c2350bcd076252b9e5d81ea1 Mon Sep 17 00:00:00 2001 From: Uleat Date: Sun, 5 Feb 2017 05:01:51 -0500 Subject: [PATCH 44/50] Added dev script for function to retrieve spell target type labels for spells from queries [skip ci] --- .../spell_target_type_token_function.sql | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 utils/scripts/database_tools/spell_target_type_token_function.sql diff --git a/utils/scripts/database_tools/spell_target_type_token_function.sql b/utils/scripts/database_tools/spell_target_type_token_function.sql new file mode 100644 index 000000000..6d891ba5f --- /dev/null +++ b/utils/scripts/database_tools/spell_target_type_token_function.sql @@ -0,0 +1,59 @@ +DELIMITER $$ + +DROP FUNCTION IF EXISTS `GetSpellTargetTypeToken`; + +-- This function converts a numeric spell target type to a string label based on server code designations +-- Note: `cast_restriction` look-up is not implemented at this time +-- +-- example: +-- SELECT `id`, `name`, GetSpellTargetTypeToken(`targettype`, `CastRestriction`) FROM `spells_new` WHERE `id` IN ('6836', '10763', '30057'); +CREATE FUNCTION `GetSpellTargetTypeToken` (`target_type` INT(11), `cast_restriction` INT(11)) RETURNS VARCHAR(64) +BEGIN + DECLARE `token` VARCHAR(64) DEFAULT ''; + + CASE `target_type` + WHEN '1' THEN SET `token` = 'ST_TargetOptional'; + WHEN '2' THEN SET `token` = 'ST_AEClientV1'; + WHEN '3' THEN SET `token` = 'ST_GroupTeleport'; + WHEN '4' THEN SET `token` = 'ST_AECaster'; + WHEN '5' THEN SET `token` = 'ST_Target'; + WHEN '6' THEN SET `token` = 'ST_Self'; + WHEN '8' THEN SET `token` = 'ST_AETarget'; + WHEN '9' THEN SET `token` = 'ST_Animal'; + WHEN '10' THEN SET `token` = 'ST_Undead'; + WHEN '11' THEN SET `token` = 'ST_Summoned'; + WHEN '13' THEN SET `token` = 'ST_Tap'; + WHEN '14' THEN SET `token` = 'ST_Pet'; + WHEN '15' THEN SET `token` = 'ST_Corpse'; + WHEN '16' THEN SET `token` = 'ST_Plant'; + WHEN '17' THEN SET `token` = 'ST_Giant'; + WHEN '18' THEN SET `token` = 'ST_Dragon'; + WHEN '20' THEN SET `token` = 'ST_TargetAETap'; + WHEN '24' THEN SET `token` = 'ST_UndeadAE'; + WHEN '25' THEN SET `token` = 'ST_SummonedAE'; + WHEN '32' THEN SET `token` = 'ST_AETargetHateList'; + WHEN '33' THEN SET `token` = 'ST_HateList'; + WHEN '34' THEN SET `token` = 'ST_LDoNChest_Cursed'; + WHEN '35' THEN SET `token` = 'ST_Muramite'; + WHEN '36' THEN SET `token` = 'ST_AreaClientOnly'; + WHEN '37' THEN SET `token` = 'ST_AreaNPCOnly'; + WHEN '38' THEN SET `token` = 'ST_SummonedPet'; + WHEN '39' THEN SET `token` = 'ST_GroupNoPets'; + WHEN '40' THEN SET `token` = 'ST_AEBard'; + WHEN '41' THEN SET `token` = 'ST_Group'; + WHEN '42' THEN SET `token` = 'ST_Directional'; + WHEN '43' THEN SET `token` = 'ST_GroupClientAndPet'; + WHEN '44' THEN SET `token` = 'ST_Beam'; + WHEN '45' THEN SET `token` = 'ST_Ring'; + WHEN '46' THEN SET `token` = 'ST_TargetsTarget'; + WHEN '47' THEN SET `token` = 'ST_PetMaster'; + WHEN '50' THEN SET `token` = 'ST_TargetAENoPlayersPets'; + ELSE SET `token` = 'unk'; + END CASE; + + SET `token` = CONCAT(`token`, '(', `target_type`, ')', '->(', `cast_restriction`, ')'); + + RETURN `token`; +END$$ + +DELIMITER ; From 409dc3ad35fa712556d813b2f63dfa320626dfd3 Mon Sep 17 00:00:00 2001 From: Uleat Date: Sun, 5 Feb 2017 07:56:37 -0500 Subject: [PATCH 45/50] Added dev script for function to retrieve body type labels from queries [skip ci] --- .../body_type_token_function.sql | 57 +++++++++++++++++++ .../spell_effect_token_function.sql | 2 +- 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 utils/scripts/database_tools/body_type_token_function.sql diff --git a/utils/scripts/database_tools/body_type_token_function.sql b/utils/scripts/database_tools/body_type_token_function.sql new file mode 100644 index 000000000..2482e0b4a --- /dev/null +++ b/utils/scripts/database_tools/body_type_token_function.sql @@ -0,0 +1,57 @@ +DELIMITER $$ + +DROP FUNCTION IF EXISTS `GetBodyTypeToken`; + +-- This function converts a numeric body type to a string label based on server code designations +-- Note: A preceeding '*' indicates a non-client verified token +-- +-- example: +-- SELECT `id`, `name`, GetBodyTypeToken(`bodytype`) FROM `npc_types` WHERE `id` IN ('116539', '154086', '164042'); +CREATE FUNCTION `GetBodyTypeToken` (`body_type` INT(11)) RETURNS VARCHAR(64) +BEGIN + DECLARE `token` VARCHAR(64) DEFAULT ''; + + CASE `body_type` + WHEN '1' THEN SET `token` = 'BT_Humanoid'; + WHEN '2' THEN SET `token` = 'BT_Lycanthrope'; + WHEN '3' THEN SET `token` = 'BT_Undead'; + WHEN '4' THEN SET `token` = 'BT_Giant'; + WHEN '5' THEN SET `token` = 'BT_Construct'; + WHEN '6' THEN SET `token` = 'BT_Extraplanar'; + WHEN '7' THEN SET `token` = 'BT_Magical'; + WHEN '8' THEN SET `token` = '*BT_SummonedUndead'; + WHEN '9' THEN SET `token` = '*BT_RaidGiant'; + WHEN '11' THEN SET `token` = '*BT_NoTarget'; + WHEN '12' THEN SET `token` = 'BT_Vampyre'; + WHEN '13' THEN SET `token` = 'BT_Atenha_Ra'; + WHEN '14' THEN SET `token` = 'BT_Greater_Akheva'; + WHEN '15' THEN SET `token` = 'BT_Khati_Sha'; + WHEN '16' THEN SET `token` = '*BT_Seru'; + WHEN '18' THEN SET `token` = 'BT_Draz_Nurakk'; + WHEN '19' THEN SET `token` = 'BT_Zek'; + WHEN '20' THEN SET `token` = 'BT_Luggald'; + WHEN '21' THEN SET `token` = 'BT_Animal'; + WHEN '22' THEN SET `token` = 'BT_Insect'; + WHEN '23' THEN SET `token` = 'BT_Monster'; + WHEN '24' THEN SET `token` = 'BT_Elemental/*BT_Summoned'; + WHEN '25' THEN SET `token` = 'BT_Plant'; + WHEN '26' THEN SET `token` = 'BT_Dragon'; + WHEN '27' THEN SET `token` = '*BT_Summoned2'; + WHEN '28' THEN SET `token` = 'BT_Summoned_Creature/*BT_Summoned3'; + WHEN '30' THEN SET `token` = '*BT_VeliousDragon'; + WHEN '32' THEN SET `token` = '*BT_Dragon3'; + WHEN '33' THEN SET `token` = '*BT_Boxes'; + WHEN '34' THEN SET `token` = 'BT_Muramite'; + WHEN '60' THEN SET `token` = '*BT_NoTarget2'; + WHEN '63' THEN SET `token` = '*BT_SwarmPet'; + WHEN '66' THEN SET `token` = '*BT_InvisMan'; + WHEN '67' THEN SET `token` = '*BT_Special'; + ELSE SET `token` = 'BT_UNKNOWN_BODYTYPE'; + END CASE; + + SET `token` = CONCAT(`token`, '(', `body_type`, ')'); + + RETURN `token`; +END$$ + +DELIMITER ; diff --git a/utils/scripts/database_tools/spell_effect_token_function.sql b/utils/scripts/database_tools/spell_effect_token_function.sql index 24efc9cc8..5208914dd 100644 --- a/utils/scripts/database_tools/spell_effect_token_function.sql +++ b/utils/scripts/database_tools/spell_effect_token_function.sql @@ -5,7 +5,7 @@ DROP FUNCTION IF EXISTS `GetSpellEffectToken`; -- This function converts a numeric spell effect id to a string label based on server code designations -- -- example: --- SELECT `id`, `name`, GetSpellEffectToken(`effectid1`), GetSpellEffectToken(`effectid2`) FROM `spells_new` WHERE `id` IN ('1011', '1602'); +-- SELECT `id`, `name`, GetSpellEffectToken(`effectid1`), GetSpellEffectToken(`effectid2`) FROM `spells_new` WHERE `id` IN ('1011', '1602', '11091'); CREATE FUNCTION `GetSpellEffectToken` (`effect_id` INT(11)) RETURNS VARCHAR(64) BEGIN DECLARE `token` VARCHAR(64) DEFAULT ''; From 592f9a9cb915e11307befdbeab8d744b829f7822 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sun, 5 Feb 2017 13:44:04 -0500 Subject: [PATCH 46/50] Add rule to allow non-PC pet NPCs to crit NPCs can't crit at all ever on live --- common/ruletypes.h | 1 + zone/attack.cpp | 17 +++++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index 21c4949ea..5323fda49 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -398,6 +398,7 @@ RULE_INT(Combat, NPCBashKickStunChance, 15) //Percent chance that a bash/kick wi RULE_INT(Combat, MeleeCritDifficulty, 8900) // lower is easier RULE_INT(Combat, ArcheryCritDifficulty, 3400) // lower is easier RULE_INT(Combat, ThrowingCritDifficulty, 1100) // lower is easier +RULE_BOOL(Combat, NPCCanCrit, false) // true allows non PC pet NPCs to crit RULE_BOOL(Combat, UseIntervalAC, true) RULE_INT(Combat, PetAttackMagicLevel, 30) RULE_BOOL(Combat, EnableFearPathing, true) diff --git a/zone/attack.cpp b/zone/attack.cpp index c05be46cb..1ebf2a352 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -3953,6 +3953,9 @@ void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions * } #endif // BOTS + if (IsNPC() && !RuleB(Combat, NPCCanCrit)) + return; + // 1: Try Slay Undead if (defender->GetBodyType() == BT_Undead || defender->GetBodyType() == BT_SummonedUndead || defender->GetBodyType() == BT_Vampire) { @@ -3983,14 +3986,12 @@ void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions * // We either require an innate crit chance or some SPA 169 to crit bool innate_crit = false; int crit_chance = GetCriticalChanceBonus(hit.skill); - if (IsClient()) { - if ((GetClass() == WARRIOR || GetClass() == BERSERKER) && GetLevel() >= 12) - innate_crit = true; - else if (GetClass() == RANGER && GetLevel() >= 12 && hit.skill == EQEmu::skills::SkillArchery) - innate_crit = true; - else if (GetClass() == ROGUE && GetLevel() >= 12 && hit.skill == EQEmu::skills::SkillThrowing) - innate_crit = true; - } + if ((GetClass() == WARRIOR || GetClass() == BERSERKER) && GetLevel() >= 12) + innate_crit = true; + else if (GetClass() == RANGER && GetLevel() >= 12 && hit.skill == EQEmu::skills::SkillArchery) + innate_crit = true; + else if (GetClass() == ROGUE && GetLevel() >= 12 && hit.skill == EQEmu::skills::SkillThrowing) + innate_crit = true; // we have a chance to crit! if (innate_crit || crit_chance) { From a13694c859874a696ed885f9b390463b6b6389c8 Mon Sep 17 00:00:00 2001 From: Uleat Date: Sun, 5 Feb 2017 22:11:02 -0500 Subject: [PATCH 47/50] Implemented cast restrictions in GetSpellTargetTypeToken() [skip ci] --- .../spell_target_type_token_function.sql | 147 +++++++++++++++++- 1 file changed, 144 insertions(+), 3 deletions(-) diff --git a/utils/scripts/database_tools/spell_target_type_token_function.sql b/utils/scripts/database_tools/spell_target_type_token_function.sql index 6d891ba5f..1b76714fa 100644 --- a/utils/scripts/database_tools/spell_target_type_token_function.sql +++ b/utils/scripts/database_tools/spell_target_type_token_function.sql @@ -3,13 +3,14 @@ DELIMITER $$ DROP FUNCTION IF EXISTS `GetSpellTargetTypeToken`; -- This function converts a numeric spell target type to a string label based on server code designations --- Note: `cast_restriction` look-up is not implemented at this time +-- Note: `cast_restriction` data taken from MacroQuest2 definitions -- -- example: --- SELECT `id`, `name`, GetSpellTargetTypeToken(`targettype`, `CastRestriction`) FROM `spells_new` WHERE `id` IN ('6836', '10763', '30057'); +-- SELECT `id`, `name`, GetSpellTargetTypeToken(`targettype`, `CastRestriction`) FROM `spells_new` WHERE `id` IN ('6836', '10763', '25039'); CREATE FUNCTION `GetSpellTargetTypeToken` (`target_type` INT(11), `cast_restriction` INT(11)) RETURNS VARCHAR(64) BEGIN DECLARE `token` VARCHAR(64) DEFAULT ''; + DECLARE `token2` VARCHAR(64) DEFAULT ''; CASE `target_type` WHEN '1' THEN SET `token` = 'ST_TargetOptional'; @@ -51,7 +52,147 @@ BEGIN ELSE SET `token` = 'unk'; END CASE; - SET `token` = CONCAT(`token`, '(', `target_type`, ')', '->(', `cast_restriction`, ')'); + SET `token` = CONCAT(`token`, '(', `target_type`, ')'); + + CASE `cast_restriction` + WHEN '0' THEN SET `token2` = 'None'; + -- WHEN '1' THEN SET `token2` = '---'; + WHEN '100' THEN SET `token2` = 'Only works on Animal or Humanoid'; + WHEN '101' THEN SET `token2` = 'Only works on Dragon'; + WHEN '102' THEN SET `token2` = 'Only works on Animal or Insect'; + WHEN '104' THEN SET `token2` = 'Only works on Animal'; + WHEN '105' THEN SET `token2` = 'Only works on Plant'; + WHEN '106' THEN SET `token2` = 'Only works on Giant'; + WHEN '108' THEN SET `token2` = 'Doesn\'t work on Animals or Humanoids'; + WHEN '109' THEN SET `token2` = 'Only works on Bixie'; + WHEN '110' THEN SET `token2` = 'Only works on Harpy'; + WHEN '111' THEN SET `token2` = 'Only works on Gnoll'; + WHEN '112' THEN SET `token2` = 'Only works on Sporali'; + WHEN '113' THEN SET `token2` = 'Only works on Kobold'; + WHEN '114' THEN SET `token2` = 'Only works on Shade'; + WHEN '115' THEN SET `token2` = 'Only works on Drakkin'; + WHEN '117' THEN SET `token2` = 'Only works on Animals or Plants'; + WHEN '118' THEN SET `token2` = 'Only works on Summoned'; + WHEN '119' THEN SET `token2` = 'Only works on Fire_Pet'; + WHEN '120' THEN SET `token2` = 'Only works on Undead'; + WHEN '121' THEN SET `token2` = 'Only works on Living'; + WHEN '122' THEN SET `token2` = 'Only works on Fairy'; + WHEN '123' THEN SET `token2` = 'Only works on Humanoid'; + WHEN '124' THEN SET `token2` = 'Undead HP Less Than 10%'; + WHEN '125' THEN SET `token2` = 'Clockwork HP Less Than 45%'; + WHEN '126' THEN SET `token2` = 'Wisp HP Less Than 10%'; + -- WHEN '127' THEN SET `token2` = '---'; + -- WHEN '128' THEN SET `token2` = '---'; + -- WHEN '129' THEN SET `token2` = '---'; + -- WHEN '130' THEN SET `token2` = '---'; + -- WHEN '150' THEN SET `token2` = '---'; + WHEN '190' THEN SET `token2` = 'Doesn\'t work on Raid Bosses'; + WHEN '191' THEN SET `token2` = 'Only works on Raid Bosses'; + WHEN '201' THEN SET `token2` = 'HP Above 75%'; + WHEN '203' THEN SET `token2` = 'HP Less Than 20%'; + WHEN '204' THEN SET `token2` = 'HP Less Than 50%'; + WHEN '216' THEN SET `token2` = 'Not In Combat'; + WHEN '221' THEN SET `token2` = 'At Least 1 Pet On Hatelist'; + WHEN '222' THEN SET `token2` = 'At Least 2 Pets On Hatelist'; + WHEN '223' THEN SET `token2` = 'At Least 3 Pets On Hatelist'; + WHEN '224' THEN SET `token2` = 'At Least 4 Pets On Hatelist'; + WHEN '225' THEN SET `token2` = 'At Least 5 Pets On Hatelist'; + WHEN '226' THEN SET `token2` = 'At Least 6 Pets On Hatelist'; + WHEN '227' THEN SET `token2` = 'At Least 7 Pets On Hatelist'; + WHEN '228' THEN SET `token2` = 'At Least 8 Pets On Hatelist'; + WHEN '229' THEN SET `token2` = 'At Least 9 Pets On Hatelist'; + WHEN '230' THEN SET `token2` = 'At Least 10 Pets On Hatelist'; + WHEN '231' THEN SET `token2` = 'At Least 11 Pets On Hatelist'; + WHEN '232' THEN SET `token2` = 'At Least 12 Pets On Hatelist'; + WHEN '233' THEN SET `token2` = 'At Least 13 Pets On Hatelist'; + WHEN '234' THEN SET `token2` = 'At Least 14 Pets On Hatelist'; + WHEN '235' THEN SET `token2` = 'At Least 15 Pets On Hatelist'; + WHEN '236' THEN SET `token2` = 'At Least 16 Pets On Hatelist'; + WHEN '237' THEN SET `token2` = 'At Least 17 Pets On Hatelist'; + WHEN '238' THEN SET `token2` = 'At Least 18 Pets On Hatelist'; + WHEN '239' THEN SET `token2` = 'At Least 19 Pets On Hatelist'; + WHEN '240' THEN SET `token2` = 'At Least 20 Pets On Hatelist'; + WHEN '250' THEN SET `token2` = 'HP Less Than 35%'; + WHEN '304' THEN SET `token2` = 'Chain Plate Classes'; + WHEN '399' THEN SET `token2` = 'HP Between 15 and 25%'; + WHEN '400' THEN SET `token2` = 'HP Between 1 and 25%'; + WHEN '401' THEN SET `token2` = 'HP Between 25 and 35%'; + WHEN '402' THEN SET `token2` = 'HP Between 35 and 45%'; + WHEN '403' THEN SET `token2` = 'HP Between 45 and 55%'; + WHEN '404' THEN SET `token2` = 'HP Between 55 and 65%'; + -- WHEN '410' THEN SET `token2` = '---'; + -- WHEN '411' THEN SET `token2` = '---'; + WHEN '412' THEN SET `token2` = 'HP Above 99%'; + WHEN '501' THEN SET `token2` = 'HP Below 5%'; + WHEN '502' THEN SET `token2` = 'HP Below 10%'; + WHEN '503' THEN SET `token2` = 'HP Below 15%'; + WHEN '504' THEN SET `token2` = 'HP Below 20%'; + WHEN '505' THEN SET `token2` = 'HP Below 25%'; + WHEN '506' THEN SET `token2` = 'HP Below 30%'; + WHEN '507' THEN SET `token2` = 'HP Below 35%'; + WHEN '508' THEN SET `token2` = 'HP Below 40%'; + WHEN '509' THEN SET `token2` = 'HP Below 45%'; + WHEN '510' THEN SET `token2` = 'HP Below 50%'; + WHEN '511' THEN SET `token2` = 'HP Below 55%'; + WHEN '512' THEN SET `token2` = 'HP Below 60%'; + WHEN '513' THEN SET `token2` = 'HP Below 65%'; + WHEN '514' THEN SET `token2` = 'HP Below 70%'; + WHEN '515' THEN SET `token2` = 'HP Below 75%'; + WHEN '516' THEN SET `token2` = 'HP Below 80%'; + WHEN '517' THEN SET `token2` = 'HP Below 85%'; + WHEN '518' THEN SET `token2` = 'HP Below 90%'; + WHEN '519' THEN SET `token2` = 'HP Below 95%'; + WHEN '521' THEN SET `token2` = 'Mana Below X%'; + WHEN '522' THEN SET `token2` = 'End Below 40%'; + WHEN '523' THEN SET `token2` = 'Mana Below 40%'; + -- WHEN '601' THEN SET `token2` = '---'; + WHEN '603' THEN SET `token2` = 'Only works on Undead2'; + WHEN '608' THEN SET `token2` = 'Only works on Undead3'; + WHEN '624' THEN SET `token2` = 'Only works on Summoned2'; + -- WHEN '626' THEN SET `token2` = '---'; + -- WHEN '700' THEN SET `token2` = '---'; + WHEN '701' THEN SET `token2` = 'Doesn\'t work on Pets'; + -- WHEN '800' THEN SET `token2` = '---'; + -- WHEN '812' THEN SET `token2` = '---'; + -- WHEN '814' THEN SET `token2` = '---'; + -- WHEN '815' THEN SET `token2` = '---'; + -- WHEN '816' THEN SET `token2` = '---'; + -- WHEN '817' THEN SET `token2` = '---'; + WHEN '818' THEN SET `token2` = 'Only works on Undead4'; + WHEN '819' THEN SET `token2` = 'Doesn\'t work on Undead4'; + -- WHEN '820' THEN SET `token2` = '---'; + -- WHEN '821' THEN SET `token2` = '---'; + -- WHEN '822' THEN SET `token2` = '---'; + WHEN '825' THEN SET `token2` = 'End Below 21%'; + WHEN '826' THEN SET `token2` = 'End Below 25%'; + WHEN '827' THEN SET `token2` = 'End Below 29%'; + WHEN '836' THEN SET `token2` = 'Only works on Regular Servers'; + WHEN '837' THEN SET `token2` = 'Doesn\'t work on Progression Servers'; + WHEN '842' THEN SET `token2` = 'Only works on Humanoid Level 84 Max'; + WHEN '843' THEN SET `token2` = 'Only works on Humanoid Level 86 Max'; + WHEN '844' THEN SET `token2` = 'Only works on Humanoid Level 88 Max'; + -- WHEN '845' THEN SET `token2` = '---'; + -- WHEN '846' THEN SET `token2` = '---'; + -- WHEN '847' THEN SET `token2` = '---'; + -- WHEN '860' THEN SET `token2` = '---'; + -- WHEN '861' THEN SET `token2` = '---'; + -- WHEN '862' THEN SET `token2` = '---'; + -- WHEN '863' THEN SET `token2` = '---'; + -- WHEN '864' THEN SET `token2` = '---'; + -- WHEN '865' THEN SET `token2` = '---'; + WHEN '1000' THEN SET `token2` = 'Between Level 1 and 75'; + WHEN '1001' THEN SET `token2` = 'Between Level 76 and 85'; + WHEN '1002' THEN SET `token2` = 'Between Level 86 and 95'; + WHEN '1003' THEN SET `token2` = 'Between Level 96 and 100'; + WHEN '1004' THEN SET `token2` = 'HP Less Than 80%'; + WHEN '38311' THEN SET `token2` = 'Mana Below 20%'; + WHEN '38312' THEN SET `token2` = 'Mana Below 10%'; + ELSE SET `token2` = 'unk'; + END CASE; + + SET `token2` = CONCAT(`token2`, '(', `cast_restriction`, ')'); + + SET `token` = CONCAT(`token`, ':', `token2`); RETURN `token`; END$$ From ef8b4754ea2fc65a37e46b5360a35419180b3588 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Mon, 6 Feb 2017 00:11:08 -0500 Subject: [PATCH 48/50] Fix min damage issue --- zone/attack.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/zone/attack.cpp b/zone/attack.cpp index 1ebf2a352..801242d45 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -1298,6 +1298,7 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b // Now figure out damage my_hit.damage_done = 0; + my_hit.min_damage = 0; uint8 mylevel = GetLevel() ? GetLevel() : 1; uint32 hate = 0; if (weapon) hate = weapon->GetItem()->Damage + weapon->GetItem()->ElemDmgAmt; From 5dbbc5f21cefc0ec8400f7d887151db520498e41 Mon Sep 17 00:00:00 2001 From: Uleat Date: Mon, 6 Feb 2017 07:14:50 -0500 Subject: [PATCH 49/50] Added some additional SpellAffectIndex declarations to the enumeration (no reference.) Added dev script for function to retrieve spell affect index id labels for spells from queries --- common/spdat.h | 55 ++++++++++++-- .../spell_affect_index_token_function.sql | 71 +++++++++++++++++++ .../spell_target_type_token_function.sql | 33 +-------- 3 files changed, 124 insertions(+), 35 deletions(-) create mode 100644 utils/scripts/database_tools/spell_affect_index_token_function.sql diff --git a/common/spdat.h b/common/spdat.h index 6ca588f04..e8ec2574e 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -67,11 +67,58 @@ const int SpellTypes_Beneficial = SpellType_Heal|SpellType_Buff|SpellType_Escape #define SpellType_Any 0xFFFF +// These don't appear to be consistent either through us or soe.. +// Use for generalization rather than validation enum SpellAffectIndex { - SAI_Calm = 12, // Lull and Alliance Spells - SAI_Dispell_Sight = 14, // Dispells and Spells like Bind Sight - SAI_Memory_Blur = 27, - SAI_Calm_Song = 43 // Lull and Alliance Songs + SAI_Summon_Mount_Unclass = -1, + SAI_Direct_Damage = 0, + SAI_Heal_Cure = 1, + SAI_AC_Buff = 2, + SAI_AE_Damage = 3, + SAI_Summon = 4, // Summoned Pets and Items + SAI_Sight = 5, + SAI_Mana_Regen_Resist_Song = 6, + SAI_Stat_Buff = 7, + SAI_Vanish = 9, // Invisibility and Gate/Port + SAI_Appearance = 10, // Illusion and Size + SAI_Enchanter_Pet = 11, + SAI_Calm = 12, // Lull and Alliance Spells + SAI_Fear = 13, + SAI_Dispell_Sight = 14, // Dispells and Spells like Bind Sight + SAI_Stun = 15, + SAI_Haste_Runspeed = 16, // Haste and SoW + SAI_Combat_Slow = 17, + SAI_Damage_Shield = 18, + SAI_Cannibalize_Weapon_Proc = 19, + SAI_Weaken = 20, + SAI_Banish = 21, + SAI_Blind_Poison = 22, + SAI_Cold_DD = 23, + SAI_Poison_Disease_DD = 24, + SAI_Fire_DD = 25, + SAI_Memory_Blur = 27, + SAI_Gravity_Fling = 28, + SAI_Suffocate = 29, + SAI_Lifetap_Over_Time = 30, + SAI_Fire_AE = 31, + SAI_Cold_AE = 33, + SAI_Poison_Disease_AE = 34, + SAI_Teleport = 40, + SAI_Direct_Damage_Song = 41, + SAI_Combat_Buff_Song = 42, + SAI_Calm_Song = 43, // Lull and Alliance Songs + SAI_Firework = 45, + SAI_Firework_AE = 46, + SAI_Weather_Rocket = 47, + SAI_Convert_Vitals = 50, + SAI_NPC_Special_60 = 60, + SAI_NPC_Special_61 = 61, + SAI_NPC_Special_62 = 62, + SAI_NPC_Special_63 = 63, + SAI_NPC_Special_70 = 70, + SAI_NPC_Special_71 = 71, + SAI_NPC_Special_80 = 80, + SAI_Trap_Lock = 88 }; enum RESISTTYPE { diff --git a/utils/scripts/database_tools/spell_affect_index_token_function.sql b/utils/scripts/database_tools/spell_affect_index_token_function.sql new file mode 100644 index 000000000..0aa6926c8 --- /dev/null +++ b/utils/scripts/database_tools/spell_affect_index_token_function.sql @@ -0,0 +1,71 @@ +DELIMITER $$ + +DROP FUNCTION IF EXISTS `GetSpellAffectIndexToken`; + +-- This function converts a numeric spell affect index id to a string label based on server code designations +-- +-- example: +-- SELECT `id`, `name`, GetSpellAffectIndexToken(`SpellAffectIndex`) FROM `spells_new` WHERE `id` IN ('73', '2253', '2319'); +CREATE FUNCTION `GetSpellAffectIndexToken` (`affect_index_id` INT(11)) RETURNS VARCHAR(64) +BEGIN + DECLARE `token` VARCHAR(64) DEFAULT ''; + + CASE `affect_index_id` + WHEN '-1' THEN SET `token` = 'SAI_Summon_Mount_Unclass'; + WHEN '0' THEN SET `token` = 'SAI_Direct_Damage'; + WHEN '1' THEN SET `token` = 'SAI_Heal_Cure'; + WHEN '2' THEN SET `token` = 'SAI_AC_Buff'; + WHEN '3' THEN SET `token` = 'SAI_AE_Damage'; + WHEN '4' THEN SET `token` = 'SAI_Summon'; + WHEN '5' THEN SET `token` = 'SAI_Sight'; + WHEN '6' THEN SET `token` = 'SAI_Mana_Regen_Resist_Song'; + WHEN '7' THEN SET `token` = 'SAI_Stat_Buff'; + WHEN '9' THEN SET `token` = 'SAI_Vanish'; + WHEN '10' THEN SET `token` = 'SAI_Appearance'; + WHEN '11' THEN SET `token` = 'SAI_Enchanter_Pet'; + WHEN '12' THEN SET `token` = 'SAI_Calm'; + WHEN '13' THEN SET `token` = 'SAI_Fear'; + WHEN '14' THEN SET `token` = 'SAI_Dispell_Sight'; + WHEN '15' THEN SET `token` = 'SAI_Stun'; + WHEN '16' THEN SET `token` = 'SAI_Haste_Runspeed'; + WHEN '17' THEN SET `token` = 'SAI_Combat_Slow'; + WHEN '18' THEN SET `token` = 'SAI_Damage_Shield'; + WHEN '19' THEN SET `token` = 'SAI_Cannibalize_Weapon_Proc'; + WHEN '20' THEN SET `token` = 'SAI_Weaken'; + WHEN '21' THEN SET `token` = 'SAI_Banish'; + WHEN '22' THEN SET `token` = 'SAI_Blind_Poison'; + WHEN '23' THEN SET `token` = 'SAI_Cold_DD'; + WHEN '24' THEN SET `token` = 'SAI_Poison_Disease_DD'; + WHEN '25' THEN SET `token` = 'SAI_Fire_DD'; + WHEN '27' THEN SET `token` = 'SAI_Memory_Blur'; + WHEN '28' THEN SET `token` = 'SAI_Gravity_Fling'; + WHEN '29' THEN SET `token` = 'SAI_Suffocate'; + WHEN '30' THEN SET `token` = 'SAI_Lifetap_Over_Time'; + WHEN '31' THEN SET `token` = 'SAI_Fire_AE'; + WHEN '33' THEN SET `token` = 'SAI_Cold_AE'; + WHEN '34' THEN SET `token` = 'SAI_Poison_Disease_AE'; + WHEN '40' THEN SET `token` = 'SAI_Teleport'; + WHEN '41' THEN SET `token` = 'SAI_Direct_Damage_Song'; + WHEN '42' THEN SET `token` = 'SAI_Combat_Buff_Song'; + WHEN '43' THEN SET `token` = 'SAI_Calm_Song'; + WHEN '45' THEN SET `token` = 'SAI_Firework'; + WHEN '46' THEN SET `token` = 'SAI_Firework_AE'; + WHEN '47' THEN SET `token` = 'SAI_Weather_Rocket'; + WHEN '50' THEN SET `token` = 'SAI_Convert_Vitals'; + WHEN '60' THEN SET `token` = 'SAI_NPC_Special_60'; + WHEN '61' THEN SET `token` = 'SAI_NPC_Special_61'; + WHEN '62' THEN SET `token` = 'SAI_NPC_Special_62'; + WHEN '63' THEN SET `token` = 'SAI_NPC_Special_63'; + WHEN '70' THEN SET `token` = 'SAI_NPC_Special_70'; + WHEN '71' THEN SET `token` = 'SAI_NPC_Special_71'; + WHEN '80' THEN SET `token` = 'SAI_NPC_Special_80'; + WHEN '88' THEN SET `token` = 'SAI_Lock_Trap'; + ELSE SET `token` = 'unk'; + END CASE; + + SET `token` = CONCAT(`token`, '(', `affect_index_id`, ')'); + + RETURN `token`; +END$$ + +DELIMITER ; diff --git a/utils/scripts/database_tools/spell_target_type_token_function.sql b/utils/scripts/database_tools/spell_target_type_token_function.sql index 1b76714fa..76d726bce 100644 --- a/utils/scripts/database_tools/spell_target_type_token_function.sql +++ b/utils/scripts/database_tools/spell_target_type_token_function.sql @@ -7,9 +7,9 @@ DROP FUNCTION IF EXISTS `GetSpellTargetTypeToken`; -- -- example: -- SELECT `id`, `name`, GetSpellTargetTypeToken(`targettype`, `CastRestriction`) FROM `spells_new` WHERE `id` IN ('6836', '10763', '25039'); -CREATE FUNCTION `GetSpellTargetTypeToken` (`target_type` INT(11), `cast_restriction` INT(11)) RETURNS VARCHAR(64) +CREATE FUNCTION `GetSpellTargetTypeToken` (`target_type` INT(11), `cast_restriction` INT(11)) RETURNS VARCHAR(128) BEGIN - DECLARE `token` VARCHAR(64) DEFAULT ''; + DECLARE `token` VARCHAR(128) DEFAULT ''; DECLARE `token2` VARCHAR(64) DEFAULT ''; CASE `target_type` @@ -56,7 +56,6 @@ BEGIN CASE `cast_restriction` WHEN '0' THEN SET `token2` = 'None'; - -- WHEN '1' THEN SET `token2` = '---'; WHEN '100' THEN SET `token2` = 'Only works on Animal or Humanoid'; WHEN '101' THEN SET `token2` = 'Only works on Dragon'; WHEN '102' THEN SET `token2` = 'Only works on Animal or Insect'; @@ -81,11 +80,6 @@ BEGIN WHEN '124' THEN SET `token2` = 'Undead HP Less Than 10%'; WHEN '125' THEN SET `token2` = 'Clockwork HP Less Than 45%'; WHEN '126' THEN SET `token2` = 'Wisp HP Less Than 10%'; - -- WHEN '127' THEN SET `token2` = '---'; - -- WHEN '128' THEN SET `token2` = '---'; - -- WHEN '129' THEN SET `token2` = '---'; - -- WHEN '130' THEN SET `token2` = '---'; - -- WHEN '150' THEN SET `token2` = '---'; WHEN '190' THEN SET `token2` = 'Doesn\'t work on Raid Bosses'; WHEN '191' THEN SET `token2` = 'Only works on Raid Bosses'; WHEN '201' THEN SET `token2` = 'HP Above 75%'; @@ -120,8 +114,6 @@ BEGIN WHEN '402' THEN SET `token2` = 'HP Between 35 and 45%'; WHEN '403' THEN SET `token2` = 'HP Between 45 and 55%'; WHEN '404' THEN SET `token2` = 'HP Between 55 and 65%'; - -- WHEN '410' THEN SET `token2` = '---'; - -- WHEN '411' THEN SET `token2` = '---'; WHEN '412' THEN SET `token2` = 'HP Above 99%'; WHEN '501' THEN SET `token2` = 'HP Below 5%'; WHEN '502' THEN SET `token2` = 'HP Below 10%'; @@ -145,24 +137,12 @@ BEGIN WHEN '521' THEN SET `token2` = 'Mana Below X%'; WHEN '522' THEN SET `token2` = 'End Below 40%'; WHEN '523' THEN SET `token2` = 'Mana Below 40%'; - -- WHEN '601' THEN SET `token2` = '---'; WHEN '603' THEN SET `token2` = 'Only works on Undead2'; WHEN '608' THEN SET `token2` = 'Only works on Undead3'; WHEN '624' THEN SET `token2` = 'Only works on Summoned2'; - -- WHEN '626' THEN SET `token2` = '---'; - -- WHEN '700' THEN SET `token2` = '---'; WHEN '701' THEN SET `token2` = 'Doesn\'t work on Pets'; - -- WHEN '800' THEN SET `token2` = '---'; - -- WHEN '812' THEN SET `token2` = '---'; - -- WHEN '814' THEN SET `token2` = '---'; - -- WHEN '815' THEN SET `token2` = '---'; - -- WHEN '816' THEN SET `token2` = '---'; - -- WHEN '817' THEN SET `token2` = '---'; WHEN '818' THEN SET `token2` = 'Only works on Undead4'; WHEN '819' THEN SET `token2` = 'Doesn\'t work on Undead4'; - -- WHEN '820' THEN SET `token2` = '---'; - -- WHEN '821' THEN SET `token2` = '---'; - -- WHEN '822' THEN SET `token2` = '---'; WHEN '825' THEN SET `token2` = 'End Below 21%'; WHEN '826' THEN SET `token2` = 'End Below 25%'; WHEN '827' THEN SET `token2` = 'End Below 29%'; @@ -171,15 +151,6 @@ BEGIN WHEN '842' THEN SET `token2` = 'Only works on Humanoid Level 84 Max'; WHEN '843' THEN SET `token2` = 'Only works on Humanoid Level 86 Max'; WHEN '844' THEN SET `token2` = 'Only works on Humanoid Level 88 Max'; - -- WHEN '845' THEN SET `token2` = '---'; - -- WHEN '846' THEN SET `token2` = '---'; - -- WHEN '847' THEN SET `token2` = '---'; - -- WHEN '860' THEN SET `token2` = '---'; - -- WHEN '861' THEN SET `token2` = '---'; - -- WHEN '862' THEN SET `token2` = '---'; - -- WHEN '863' THEN SET `token2` = '---'; - -- WHEN '864' THEN SET `token2` = '---'; - -- WHEN '865' THEN SET `token2` = '---'; WHEN '1000' THEN SET `token2` = 'Between Level 1 and 75'; WHEN '1001' THEN SET `token2` = 'Between Level 76 and 85'; WHEN '1002' THEN SET `token2` = 'Between Level 86 and 95'; From 3d1bb6bd088d57943d3b38778d3ab591145d4c32 Mon Sep 17 00:00:00 2001 From: Uleat Date: Mon, 6 Feb 2017 17:48:44 -0500 Subject: [PATCH 50/50] Updated SpellAffectIndex enumeration remarks [skip ci] --- common/spdat.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common/spdat.h b/common/spdat.h index e8ec2574e..3344489df 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -67,8 +67,9 @@ const int SpellTypes_Beneficial = SpellType_Heal|SpellType_Buff|SpellType_Escape #define SpellType_Any 0xFFFF -// These don't appear to be consistent either through us or soe.. -// Use for generalization rather than validation +// These should not be used to determine spell category.. +// They are a graphical affects (effects?) index only +// TODO: import sai list enum SpellAffectIndex { SAI_Summon_Mount_Unclass = -1, SAI_Direct_Damage = 0,