From ef2c17748e612959d75328c023b6abb9c364ef1b Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sun, 31 Jul 2016 17:16:23 -0400 Subject: [PATCH 01/78] Implement extra spell gems! New limits: Tit: 9 SoF: 9 SoD: 10 UF: 12 RoF: 12 RoF2: 12 The SoF client doesn't actually support 10 like SoF should RoF/RoF2 actually have 4 extra broken spell gems in the UI. They don't work and will likely crash your client Quest stuff assumes you are passing in valid slots. (note the old default of 10 should be 22) There are still somethings to do like clean up the memmed spells if one switches to an older client that doesn't support as many as their previous client. --- changelog.txt | 10 ++++ common/emu_constants.h | 20 +++++++ common/eq_packet_structs.h | 8 +-- common/patches/rof.cpp | 93 +++++++++++++++++++++++++++---- common/patches/rof.h | 18 ++++++ common/patches/rof2.cpp | 93 +++++++++++++++++++++++++++---- common/patches/rof2.h | 18 ++++++ common/patches/rof2_structs.h | 2 +- common/patches/rof_structs.h | 2 +- common/patches/sod.cpp | 72 ++++++++++++++++++++++++ common/patches/sod.h | 16 ++++++ common/patches/sod_structs.h | 2 +- common/patches/sof.cpp | 92 +++++++++++++++++++++++++++++- common/patches/sof.h | 17 ++++++ common/patches/sof_ops.h | 1 + common/patches/sof_structs.h | 2 +- common/patches/titanium.cpp | 92 +++++++++++++++++++++++++++++- common/patches/titanium.h | 17 ++++++ common/patches/titanium_ops.h | 1 + common/patches/titanium_structs.h | 2 +- common/patches/uf.cpp | 84 ++++++++++++++++++++++++++-- common/patches/uf.h | 18 ++++++ common/patches/uf_structs.h | 2 +- world/client.cpp | 2 +- zone/aa.cpp | 4 +- zone/attack.cpp | 4 +- zone/bot.cpp | 26 ++++----- zone/bot.h | 12 ++-- zone/bot_command.cpp | 2 +- zone/client.cpp | 6 +- zone/client.h | 1 + zone/client_packet.cpp | 38 ++++++------- zone/command.cpp | 8 +-- zone/common.h | 7 --- zone/effects.cpp | 6 +- zone/lua_mob.cpp | 22 ++++---- zone/merc.cpp | 4 +- zone/mob.cpp | 51 ++++++++--------- zone/mob.h | 17 +++--- zone/mob_ai.cpp | 8 +-- zone/perl_mob.cpp | 10 ++-- zone/questmgr.cpp | 4 +- zone/special_attacks.cpp | 4 +- zone/spell_effects.cpp | 8 +-- zone/spells.cpp | 73 ++++++++++++------------ zone/trap.cpp | 2 +- 46 files changed, 800 insertions(+), 201 deletions(-) diff --git a/changelog.txt b/changelog.txt index f1af1009a..5221f170b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,15 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 07/31/2016 == +mackal: Implement more spell gems! + - There are a few things still left to due like make dealing with losing gems nice (reset AAs, going to an older client etc) + - Sadly SoF disc release doesn't support gem 10 like one might expect :( + - So changed clients: + - SoD = 10 + - UF = 12 + - RoF/RoF2 = 12. I know the UI supports 16, but the client does not and can cause client crashes + - The quest APIs assume you pass a valid spell gem ... + == 07/28/2016 == Uleat: Implemented zone memory-mapped file usage - Zone map files are converted to pre-loaded binary files, bypassing the (sometimes) time-consuming raw data transform process diff --git a/common/emu_constants.h b/common/emu_constants.h index 799161aa8..bf8189acc 100644 --- a/common/emu_constants.h +++ b/common/emu_constants.h @@ -44,6 +44,26 @@ namespace EQEmu const size_t SayLinkBodySize = RoF2::constants::SayLinkBodySize; } /*constants*/ + enum class CastingSlot : uint32 { + Gem1 = 0, + Gem2 = 1, + Gem3 = 2, + Gem4 = 3, + Gem5 = 4, + Gem6 = 5, + Gem7 = 6, + Gem8 = 7, + Gem9 = 8, + Gem10 = 9, + Gem11 = 10, + Gem12 = 11, + MaxGems = 12, + Ability = 20, // HT/LoH for Tit + PotionBelt = 21, // Tit uses a different slot for PB + Item = 22, + Discipline = 23, + AltAbility = 0xFF + }; } /*EQEmu*/ diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 3252d07cc..ab0447cb8 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -386,7 +386,7 @@ struct MemorizeSpell_Struct { uint32 slot; // Spot in the spell book/memorized slot uint32 spell_id; // Spell id (200 or c8 is minor healing, etc) uint32 scribing; // 1 if memorizing a spell, set to 0 if scribing to book, 2 if un-memming -uint32 unknown12; +uint32 reduction; // lower reuse }; /* @@ -834,7 +834,7 @@ struct SuspendedMinion_Struct */ static const uint32 MAX_PP_LANGUAGE = 28; static const uint32 MAX_PP_SPELLBOOK = 480; // Set for all functions -static const uint32 MAX_PP_MEMSPELL = 9; // Set to latest client so functions can work right +static const uint32 MAX_PP_MEMSPELL = static_cast(EQEmu::CastingSlot::MaxGems); // Set to latest client so functions can work right -- 12 static const uint32 MAX_PP_REF_SPELLBOOK = 480; // Set for Player Profile size retain static const uint32 MAX_PP_REF_MEMSPELL = 9; // Set for Player Profile size retain @@ -915,7 +915,7 @@ struct PlayerProfile_Struct /*0245*/ uint8 guildbanker; /*0246*/ uint8 unknown0246[6]; // /*0252*/ uint32 intoxication; -/*0256*/ uint32 spellSlotRefresh[MAX_PP_REF_MEMSPELL]; //in ms +/*0256*/ uint32 spellSlotRefresh[MAX_PP_MEMSPELL]; //in ms /*0292*/ uint32 abilitySlotRefresh; /*0296*/ uint8 haircolor; // Player hair color /*0297*/ uint8 beardcolor; // Player beard color @@ -956,7 +956,7 @@ struct PlayerProfile_Struct /*2580*/ uint8 unknown2616[4]; /*2584*/ uint32 spell_book[MAX_PP_REF_SPELLBOOK]; /*4504*/ uint8 unknown4540[128]; // Was [428] all 0xff -/*4632*/ uint32 mem_spells[MAX_PP_REF_MEMSPELL]; +/*4632*/ uint32 mem_spells[MAX_PP_MEMSPELL]; /*4668*/ uint8 unknown4704[32]; // /*4700*/ float y; // Player y position /*4704*/ float x; // Player x position diff --git a/common/patches/rof.cpp b/common/patches/rof.cpp index 4bf8ed63d..014e6f8d9 100644 --- a/common/patches/rof.cpp +++ b/common/patches/rof.cpp @@ -63,6 +63,9 @@ namespace RoF // client to server text link converter static inline void RoFToServerTextLink(std::string& serverTextLink, const std::string& rofTextLink); + static inline CastingSlot ServerToRoFCastingSlot(EQEmu::CastingSlot slot); + static inline EQEmu::CastingSlot RoFToServerCastingSlot(CastingSlot slot); + void Register(EQStreamIdentifier &into) { //create our opcode manager if we havent already @@ -507,10 +510,7 @@ namespace RoF ENCODE_LENGTH_EXACT(CastSpell_Struct); SETUP_DIRECT_ENCODE(CastSpell_Struct, structs::CastSpell_Struct); - if (emu->slot == 10) - eq->slot = 13; - else - OUT(slot); + eq->slot = static_cast(ServerToRoFCastingSlot(static_cast(emu->slot))); OUT(spell_id); eq->inventory_slot = ServerToRoFSlot(emu->inventoryslot); @@ -2259,11 +2259,11 @@ namespace RoF outapp->WriteUInt32(structs::MAX_PP_MEMSPELL); // Memorised spell slots - for (uint32 r = 0; r < MAX_PP_MEMSPELL; r++) + for (uint32 r = 0; r < MAX_PP_MEMSPELL; r++) // first 12 { outapp->WriteUInt32(emu->mem_spells[r]); } - // zeroes for the rest of the slots + // zeroes for the rest of the slots -- the other 4 which don't work at all! for (uint32 r = 0; r < structs::MAX_PP_MEMSPELL - MAX_PP_MEMSPELL; r++) { outapp->WriteUInt32(0xFFFFFFFFU); @@ -4334,10 +4334,7 @@ namespace RoF DECODE_LENGTH_EXACT(structs::CastSpell_Struct); SETUP_DIRECT_DECODE(CastSpell_Struct, structs::CastSpell_Struct); - if (eq->slot == 13) - emu->slot = 10; - else - IN(slot); + emu->slot = static_cast(RoFToServerCastingSlot(static_cast(eq->slot))); IN(spell_id); emu->inventoryslot = RoFToServerSlot(eq->inventory_slot); @@ -6009,4 +6006,80 @@ namespace RoF } } + static inline CastingSlot ServerToRoFCastingSlot(EQEmu::CastingSlot slot) + { + switch (slot) { + case EQEmu::CastingSlot::Gem1: + return CastingSlot::Gem1; + case EQEmu::CastingSlot::Gem2: + return CastingSlot::Gem2; + case EQEmu::CastingSlot::Gem3: + return CastingSlot::Gem3; + case EQEmu::CastingSlot::Gem4: + return CastingSlot::Gem4; + case EQEmu::CastingSlot::Gem5: + return CastingSlot::Gem5; + case EQEmu::CastingSlot::Gem6: + return CastingSlot::Gem6; + case EQEmu::CastingSlot::Gem7: + return CastingSlot::Gem7; + case EQEmu::CastingSlot::Gem8: + return CastingSlot::Gem8; + case EQEmu::CastingSlot::Gem9: + return CastingSlot::Gem9; + case EQEmu::CastingSlot::Gem10: + return CastingSlot::Gem10; + case EQEmu::CastingSlot::Gem11: + return CastingSlot::Gem11; + case EQEmu::CastingSlot::Gem12: + return CastingSlot::Gem12; + case EQEmu::CastingSlot::Item: + case EQEmu::CastingSlot::PotionBelt: + return CastingSlot::Item; + case EQEmu::CastingSlot::Discipline: + return CastingSlot::Discipline; + case EQEmu::CastingSlot::AltAbility: + return CastingSlot::AltAbility; + default: // we shouldn't have any issues with other slots ... just return something + return CastingSlot::Discipline; + } + } + + static inline EQEmu::CastingSlot RoFToServerCastingSlot(CastingSlot slot) + { + switch (slot) { + case CastingSlot::Gem1: + return EQEmu::CastingSlot::Gem1; + case CastingSlot::Gem2: + return EQEmu::CastingSlot::Gem2; + case CastingSlot::Gem3: + return EQEmu::CastingSlot::Gem3; + case CastingSlot::Gem4: + return EQEmu::CastingSlot::Gem4; + case CastingSlot::Gem5: + return EQEmu::CastingSlot::Gem5; + case CastingSlot::Gem6: + return EQEmu::CastingSlot::Gem6; + case CastingSlot::Gem7: + return EQEmu::CastingSlot::Gem7; + case CastingSlot::Gem8: + return EQEmu::CastingSlot::Gem8; + case CastingSlot::Gem9: + return EQEmu::CastingSlot::Gem9; + case CastingSlot::Gem10: + return EQEmu::CastingSlot::Gem10; + case CastingSlot::Gem11: + return EQEmu::CastingSlot::Gem11; + case CastingSlot::Gem12: + return EQEmu::CastingSlot::Gem12; + case CastingSlot::Discipline: + return EQEmu::CastingSlot::Discipline; + case CastingSlot::Item: + return EQEmu::CastingSlot::Item; + case CastingSlot::AltAbility: + return EQEmu::CastingSlot::AltAbility; + default: // we shouldn't have any issues with other slots ... just return something + return EQEmu::CastingSlot::Discipline; + } + } } /*RoF*/ diff --git a/common/patches/rof.h b/common/patches/rof.h index 205998b32..146b34f1f 100644 --- a/common/patches/rof.h +++ b/common/patches/rof.h @@ -50,6 +50,24 @@ namespace RoF #include "rof_ops.h" }; + enum class CastingSlot : uint32 { + Gem1 = 0, + Gem2 = 1, + Gem3 = 2, + Gem4 = 3, + Gem5 = 4, + Gem6 = 5, + Gem7 = 6, + Gem8 = 7, + Gem9 = 8, + Gem10 = 9, + Gem11 = 10, + Gem12 = 11, + Item = 12, + Discipline = 13, + AltAbility = 0xFF + }; + }; /*RoF*/ #endif /*COMMON_ROF_H*/ diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 41d34a50a..79802e262 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -63,6 +63,9 @@ namespace RoF2 // client to server text link converter static inline void RoF2ToServerTextLink(std::string& serverTextLink, const std::string& rof2TextLink); + static inline CastingSlot ServerToRoF2CastingSlot(EQEmu::CastingSlot slot); + static inline EQEmu::CastingSlot RoF2ToServerCastingSlot(CastingSlot slot); + void Register(EQStreamIdentifier &into) { //create our opcode manager if we havent already @@ -584,10 +587,7 @@ namespace RoF2 ENCODE_LENGTH_EXACT(CastSpell_Struct); SETUP_DIRECT_ENCODE(CastSpell_Struct, structs::CastSpell_Struct); - if (emu->slot == 10) - eq->slot = 13; - else - OUT(slot); + eq->slot = static_cast(ServerToRoF2CastingSlot(static_cast(emu->slot))); OUT(spell_id); eq->inventory_slot = ServerToRoF2Slot(emu->inventoryslot); @@ -2346,11 +2346,11 @@ namespace RoF2 outapp->WriteUInt32(structs::MAX_PP_MEMSPELL); // Memorised spell slots - for (uint32 r = 0; r < MAX_PP_MEMSPELL; r++) + for (uint32 r = 0; r < MAX_PP_MEMSPELL; r++) // write first 12 { outapp->WriteUInt32(emu->mem_spells[r]); } - // zeroes for the rest of the slots + // zeroes for the rest of the slots the other 4, which actually don't work on the client at all :D for (uint32 r = 0; r < structs::MAX_PP_MEMSPELL - MAX_PP_MEMSPELL; r++) { outapp->WriteUInt32(0xFFFFFFFFU); @@ -4574,10 +4574,7 @@ namespace RoF2 DECODE_LENGTH_EXACT(structs::CastSpell_Struct); SETUP_DIRECT_DECODE(CastSpell_Struct, structs::CastSpell_Struct); - if (eq->slot == 13) - emu->slot = 10; - else - IN(slot); + emu->slot = static_cast(RoF2ToServerCastingSlot(static_cast(eq->slot))); IN(spell_id); emu->inventoryslot = RoF2ToServerSlot(eq->inventory_slot); @@ -6314,4 +6311,80 @@ namespace RoF2 } } + static inline CastingSlot ServerToRoF2CastingSlot(EQEmu::CastingSlot slot) + { + switch (slot) { + case EQEmu::CastingSlot::Gem1: + return CastingSlot::Gem1; + case EQEmu::CastingSlot::Gem2: + return CastingSlot::Gem2; + case EQEmu::CastingSlot::Gem3: + return CastingSlot::Gem3; + case EQEmu::CastingSlot::Gem4: + return CastingSlot::Gem4; + case EQEmu::CastingSlot::Gem5: + return CastingSlot::Gem5; + case EQEmu::CastingSlot::Gem6: + return CastingSlot::Gem6; + case EQEmu::CastingSlot::Gem7: + return CastingSlot::Gem7; + case EQEmu::CastingSlot::Gem8: + return CastingSlot::Gem8; + case EQEmu::CastingSlot::Gem9: + return CastingSlot::Gem9; + case EQEmu::CastingSlot::Gem10: + return CastingSlot::Gem10; + case EQEmu::CastingSlot::Gem11: + return CastingSlot::Gem11; + case EQEmu::CastingSlot::Gem12: + return CastingSlot::Gem12; + case EQEmu::CastingSlot::Item: + case EQEmu::CastingSlot::PotionBelt: + return CastingSlot::Item; + case EQEmu::CastingSlot::Discipline: + return CastingSlot::Discipline; + case EQEmu::CastingSlot::AltAbility: + return CastingSlot::AltAbility; + default: // we shouldn't have any issues with other slots ... just return something + return CastingSlot::Discipline; + } + } + + static inline EQEmu::CastingSlot RoF2ToServerCastingSlot(CastingSlot slot) + { + switch (slot) { + case CastingSlot::Gem1: + return EQEmu::CastingSlot::Gem1; + case CastingSlot::Gem2: + return EQEmu::CastingSlot::Gem2; + case CastingSlot::Gem3: + return EQEmu::CastingSlot::Gem3; + case CastingSlot::Gem4: + return EQEmu::CastingSlot::Gem4; + case CastingSlot::Gem5: + return EQEmu::CastingSlot::Gem5; + case CastingSlot::Gem6: + return EQEmu::CastingSlot::Gem6; + case CastingSlot::Gem7: + return EQEmu::CastingSlot::Gem7; + case CastingSlot::Gem8: + return EQEmu::CastingSlot::Gem8; + case CastingSlot::Gem9: + return EQEmu::CastingSlot::Gem9; + case CastingSlot::Gem10: + return EQEmu::CastingSlot::Gem10; + case CastingSlot::Gem11: + return EQEmu::CastingSlot::Gem11; + case CastingSlot::Gem12: + return EQEmu::CastingSlot::Gem12; + case CastingSlot::Discipline: + return EQEmu::CastingSlot::Discipline; + case CastingSlot::Item: + return EQEmu::CastingSlot::Item; + case CastingSlot::AltAbility: + return EQEmu::CastingSlot::AltAbility; + default: // we shouldn't have any issues with other slots ... just return something + return EQEmu::CastingSlot::Discipline; + } + } } /*RoF2*/ diff --git a/common/patches/rof2.h b/common/patches/rof2.h index 9eef8e596..4fb94a1d4 100644 --- a/common/patches/rof2.h +++ b/common/patches/rof2.h @@ -50,6 +50,24 @@ namespace RoF2 #include "rof2_ops.h" }; + enum class CastingSlot : uint32 { + Gem1 = 0, + Gem2 = 1, + Gem3 = 2, + Gem4 = 3, + Gem5 = 4, + Gem6 = 5, + Gem7 = 6, + Gem8 = 7, + Gem9 = 8, + Gem10 = 9, + Gem11 = 10, + Gem12 = 11, + Item = 12, + Discipline = 13, + AltAbility = 0xFF + }; + }; /*RoF2*/ #endif /*COMMON_ROF2_H*/ diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index 61cbb0d0b..971ebd5e0 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -635,7 +635,7 @@ struct MemorizeSpell_Struct { uint32 slot; // Spot in the spell book/memorized slot uint32 spell_id; // Spell id (200 or c8 is minor healing, etc) uint32 scribing; // 1 if memorizing a spell, set to 0 if scribing to book, 2 if un-memming -uint32 unknown12; +uint32 reduction; // lowers reuse }; /* diff --git a/common/patches/rof_structs.h b/common/patches/rof_structs.h index 9c5aa8ab8..24c4b15f8 100644 --- a/common/patches/rof_structs.h +++ b/common/patches/rof_structs.h @@ -624,7 +624,7 @@ struct MemorizeSpell_Struct { uint32 slot; // Spot in the spell book/memorized slot uint32 spell_id; // Spell id (200 or c8 is minor healing, etc) uint32 scribing; // 1 if memorizing a spell, set to 0 if scribing to book, 2 if un-memming -uint32 unknown12; +uint32 reduction; // lowers reuse }; /* diff --git a/common/patches/sod.cpp b/common/patches/sod.cpp index c148a02be..52bbdc309 100644 --- a/common/patches/sod.cpp +++ b/common/patches/sod.cpp @@ -59,6 +59,9 @@ namespace SoD // client to server text link converter static inline void SoDToServerTextLink(std::string& serverTextLink, const std::string& sodTextLink); + static inline CastingSlot ServerToSoDCastingSlot(EQEmu::CastingSlot slot); + static inline EQEmu::CastingSlot SoDToServerCastingSlot(CastingSlot slot); + void Register(EQStreamIdentifier &into) { //create our opcode manager if we havent already @@ -2948,6 +2951,7 @@ namespace SoD DECODE_LENGTH_EXACT(structs::CastSpell_Struct); SETUP_DIRECT_DECODE(CastSpell_Struct, structs::CastSpell_Struct); + emu->slot = static_cast(SoDToServerCastingSlot(static_cast(eq->slot))); IN(slot); IN(spell_id); emu->inventoryslot = SoDToServerSlot(eq->inventoryslot); @@ -4013,4 +4017,72 @@ namespace SoD } } + static inline CastingSlot ServerToSoDCastingSlot(EQEmu::CastingSlot slot) + { + switch (slot) { + case EQEmu::CastingSlot::Gem1: + return CastingSlot::Gem1; + case EQEmu::CastingSlot::Gem2: + return CastingSlot::Gem2; + case EQEmu::CastingSlot::Gem3: + return CastingSlot::Gem3; + case EQEmu::CastingSlot::Gem4: + return CastingSlot::Gem4; + case EQEmu::CastingSlot::Gem5: + return CastingSlot::Gem5; + case EQEmu::CastingSlot::Gem6: + return CastingSlot::Gem6; + case EQEmu::CastingSlot::Gem7: + return CastingSlot::Gem7; + case EQEmu::CastingSlot::Gem8: + return CastingSlot::Gem8; + case EQEmu::CastingSlot::Gem9: + return CastingSlot::Gem9; + case EQEmu::CastingSlot::Gem10: + return CastingSlot::Gem10; + case EQEmu::CastingSlot::Item: + case EQEmu::CastingSlot::PotionBelt: + return CastingSlot::Item; + case EQEmu::CastingSlot::Discipline: + return CastingSlot::Discipline; + case EQEmu::CastingSlot::AltAbility: + return CastingSlot::AltAbility; + default: // we shouldn't have any issues with other slots ... just return something + return CastingSlot::Discipline; + } + } + + static inline EQEmu::CastingSlot SoDToServerCastingSlot(CastingSlot slot) + { + switch (slot) { + case CastingSlot::Gem1: + return EQEmu::CastingSlot::Gem1; + case CastingSlot::Gem2: + return EQEmu::CastingSlot::Gem2; + case CastingSlot::Gem3: + return EQEmu::CastingSlot::Gem3; + case CastingSlot::Gem4: + return EQEmu::CastingSlot::Gem4; + case CastingSlot::Gem5: + return EQEmu::CastingSlot::Gem5; + case CastingSlot::Gem6: + return EQEmu::CastingSlot::Gem6; + case CastingSlot::Gem7: + return EQEmu::CastingSlot::Gem7; + case CastingSlot::Gem8: + return EQEmu::CastingSlot::Gem8; + case CastingSlot::Gem9: + return EQEmu::CastingSlot::Gem9; + case CastingSlot::Gem10: + return EQEmu::CastingSlot::Gem10; + case CastingSlot::Discipline: + return EQEmu::CastingSlot::Discipline; + case CastingSlot::Item: + return EQEmu::CastingSlot::Item; + case CastingSlot::AltAbility: + return EQEmu::CastingSlot::AltAbility; + default: // we shouldn't have any issues with other slots ... just return something + return EQEmu::CastingSlot::Discipline; + } + } } /*SoD*/ diff --git a/common/patches/sod.h b/common/patches/sod.h index 977645231..5c29e36c0 100644 --- a/common/patches/sod.h +++ b/common/patches/sod.h @@ -50,6 +50,22 @@ namespace SoD #include "sod_ops.h" }; + enum class CastingSlot : uint32 { + Gem1 = 0, + Gem2 = 1, + Gem3 = 2, + Gem4 = 3, + Gem5 = 4, + Gem6 = 5, + Gem7 = 6, + Gem8 = 7, + Gem9 = 8, + Gem10 = 9, + Item = 10, + Discipline = 11, + AltAbility = 0xFF + }; + }; /*SoD*/ #endif /*COMMON_SOD_H*/ diff --git a/common/patches/sod_structs.h b/common/patches/sod_structs.h index 191d048e9..bdf6a6fd3 100644 --- a/common/patches/sod_structs.h +++ b/common/patches/sod_structs.h @@ -479,7 +479,7 @@ struct MemorizeSpell_Struct { uint32 slot; // Spot in the spell book/memorized slot uint32 spell_id; // Spell id (200 or c8 is minor healing, etc) uint32 scribing; // 1 if memorizing a spell, set to 0 if scribing to book, 2 if un-memming -//uint32 unknown12; +uint32 reduction; // lowers reuse }; /* diff --git a/common/patches/sof.cpp b/common/patches/sof.cpp index 7984debd9..4f804bb03 100644 --- a/common/patches/sof.cpp +++ b/common/patches/sof.cpp @@ -59,6 +59,9 @@ namespace SoF // client to server text link converter static inline void SoFToServerTextLink(std::string& serverTextLink, const std::string& sofTextLink); + static inline CastingSlot ServerToSoFCastingSlot(EQEmu::CastingSlot slot); + static inline EQEmu::CastingSlot SoFToServerCastingSlot(CastingSlot slot, uint32 itemlocation); + void Register(EQStreamIdentifier &into) { //create our opcode manager if we havent already @@ -938,6 +941,22 @@ namespace SoF FINISH_ENCODE(); } + ENCODE(OP_MemorizeSpell) + { + ENCODE_LENGTH_EXACT(MemorizeSpell_Struct); + SETUP_DIRECT_ENCODE(MemorizeSpell_Struct, structs::MemorizeSpell_Struct); + + // Since HT/LoH are translated up, we need to translate down only for memSpellSpellbar case + if (emu->scribing == 3) + eq->slot = static_cast(ServerToSoFCastingSlot(static_cast(emu->slot))); + else + OUT(slot); + OUT(spell_id); + OUT(scribing); + + FINISH_ENCODE(); + } + ENCODE(OP_MoveItem) { ENCODE_LENGTH_EXACT(MoveItem_Struct); @@ -2366,7 +2385,7 @@ namespace SoF DECODE_LENGTH_EXACT(structs::CastSpell_Struct); SETUP_DIRECT_DECODE(CastSpell_Struct, structs::CastSpell_Struct); - IN(slot); + emu->slot = static_cast(SoFToServerCastingSlot(static_cast(eq->slot), eq->inventoryslot)); IN(spell_id); emu->inventoryslot = SoFToServerSlot(eq->inventoryslot); IN(target_id); @@ -3355,4 +3374,75 @@ namespace SoF } } + static inline CastingSlot ServerToSoFCastingSlot(EQEmu::CastingSlot slot) + { + switch (slot) { + case EQEmu::CastingSlot::Gem1: + return CastingSlot::Gem1; + case EQEmu::CastingSlot::Gem2: + return CastingSlot::Gem2; + case EQEmu::CastingSlot::Gem3: + return CastingSlot::Gem3; + case EQEmu::CastingSlot::Gem4: + return CastingSlot::Gem4; + case EQEmu::CastingSlot::Gem5: + return CastingSlot::Gem5; + case EQEmu::CastingSlot::Gem6: + return CastingSlot::Gem6; + case EQEmu::CastingSlot::Gem7: + return CastingSlot::Gem7; + case EQEmu::CastingSlot::Gem8: + return CastingSlot::Gem8; + case EQEmu::CastingSlot::Gem9: + return CastingSlot::Gem9; + case EQEmu::CastingSlot::Item: + return CastingSlot::Item; + case EQEmu::CastingSlot::PotionBelt: + return CastingSlot::PotionBelt; + case EQEmu::CastingSlot::Discipline: + return CastingSlot::Discipline; + case EQEmu::CastingSlot::AltAbility: + return CastingSlot::AltAbility; + default: // we shouldn't have any issues with other slots ... just return something + return CastingSlot::Discipline; + } + } + + static inline EQEmu::CastingSlot SoFToServerCastingSlot(CastingSlot slot, uint32 itemlocation) + { + switch (slot) { + case CastingSlot::Gem1: + return EQEmu::CastingSlot::Gem1; + case CastingSlot::Gem2: + return EQEmu::CastingSlot::Gem2; + case CastingSlot::Gem3: + return EQEmu::CastingSlot::Gem3; + case CastingSlot::Gem4: + return EQEmu::CastingSlot::Gem4; + case CastingSlot::Gem5: + return EQEmu::CastingSlot::Gem5; + case CastingSlot::Gem6: + return EQEmu::CastingSlot::Gem6; + case CastingSlot::Gem7: + return EQEmu::CastingSlot::Gem7; + case CastingSlot::Gem8: + return EQEmu::CastingSlot::Gem8; + case CastingSlot::Gem9: + return EQEmu::CastingSlot::Gem9; + case CastingSlot::Ability: + return EQEmu::CastingSlot::Ability; + // Tit uses 10 for item and discipline casting, but items have a valid location + case CastingSlot::Item: + if (itemlocation == INVALID_INDEX) + return EQEmu::CastingSlot::Discipline; + else + return EQEmu::CastingSlot::Item; + case CastingSlot::PotionBelt: + return EQEmu::CastingSlot::PotionBelt; + case CastingSlot::AltAbility: + return EQEmu::CastingSlot::AltAbility; + default: // we shouldn't have any issues with other slots ... just return something + return EQEmu::CastingSlot::Discipline; + } + } } /*SoF*/ diff --git a/common/patches/sof.h b/common/patches/sof.h index dde55f9a3..47c023471 100644 --- a/common/patches/sof.h +++ b/common/patches/sof.h @@ -50,6 +50,23 @@ namespace SoF #include "sof_ops.h" }; + enum class CastingSlot : uint32 { + Gem1 = 0, + Gem2 = 1, + Gem3 = 2, + Gem4 = 3, + Gem5 = 4, + Gem6 = 5, + Gem7 = 6, + Gem8 = 7, + Gem9 = 8, + Ability = 9, + Item = 10, + Discipline = 10, + PotionBelt = 11, + AltAbility = 0xFF + }; + }; /*SoF*/ #endif /*COMMON_SOF_H*/ diff --git a/common/patches/sof_ops.h b/common/patches/sof_ops.h index 01caf8674..68ff402f1 100644 --- a/common/patches/sof_ops.h +++ b/common/patches/sof_ops.h @@ -57,6 +57,7 @@ E(OP_LeadershipExpUpdate) E(OP_LogServer) E(OP_LootItem) E(OP_ManaChange) +E(OP_MemorizeSpell) E(OP_MoveItem) E(OP_NewSpawn) E(OP_NewZone) diff --git a/common/patches/sof_structs.h b/common/patches/sof_structs.h index 86dc91e14..06604e71d 100644 --- a/common/patches/sof_structs.h +++ b/common/patches/sof_structs.h @@ -458,7 +458,7 @@ struct MemorizeSpell_Struct { uint32 slot; // Spot in the spell book/memorized slot uint32 spell_id; // Spell id (200 or c8 is minor healing, etc) uint32 scribing; // 1 if memorizing a spell, set to 0 if scribing to book, 2 if un-memming -//uint32 unknown12; +uint32 reduction; // lowers reuse }; /* diff --git a/common/patches/titanium.cpp b/common/patches/titanium.cpp index 98ece403c..28984a26f 100644 --- a/common/patches/titanium.cpp +++ b/common/patches/titanium.cpp @@ -58,6 +58,9 @@ namespace Titanium // client to server text link converter static inline void TitaniumToServerTextLink(std::string& serverTextLink, const std::string& titaniumTextLink); + static inline CastingSlot ServerToTitaniumCastingSlot(EQEmu::CastingSlot slot); + static inline EQEmu::CastingSlot TitaniumToServerCastingSlot(CastingSlot slot, uint32 itemlocation); + void Register(EQStreamIdentifier &into) { auto Config = EQEmuConfig::get(); @@ -833,6 +836,22 @@ namespace Titanium FINISH_ENCODE(); } + ENCODE(OP_MemorizeSpell) + { + ENCODE_LENGTH_EXACT(MemorizeSpell_Struct); + SETUP_DIRECT_ENCODE(MemorizeSpell_Struct, structs::MemorizeSpell_Struct); + + // Since HT/LoH are translated up, we need to translate down only for memSpellSpellbar case + if (emu->scribing == 3) + eq->slot = static_cast(ServerToTitaniumCastingSlot(static_cast(emu->slot))); + else + OUT(slot); + OUT(spell_id); + OUT(scribing); + + FINISH_ENCODE(); + } + ENCODE(OP_MoveItem) { ENCODE_LENGTH_EXACT(MoveItem_Struct); @@ -1731,7 +1750,7 @@ namespace Titanium DECODE_LENGTH_EXACT(structs::CastSpell_Struct); SETUP_DIRECT_DECODE(CastSpell_Struct, structs::CastSpell_Struct); - IN(slot); + emu->slot = static_cast(TitaniumToServerCastingSlot(static_cast(eq->slot), eq->inventoryslot)); IN(spell_id); emu->inventoryslot = TitaniumToServerSlot(eq->inventoryslot); IN(target_id); @@ -2487,4 +2506,75 @@ namespace Titanium } } + static inline CastingSlot ServerToTitaniumCastingSlot(EQEmu::CastingSlot slot) + { + switch (slot) { + case EQEmu::CastingSlot::Gem1: + return CastingSlot::Gem1; + case EQEmu::CastingSlot::Gem2: + return CastingSlot::Gem2; + case EQEmu::CastingSlot::Gem3: + return CastingSlot::Gem3; + case EQEmu::CastingSlot::Gem4: + return CastingSlot::Gem4; + case EQEmu::CastingSlot::Gem5: + return CastingSlot::Gem5; + case EQEmu::CastingSlot::Gem6: + return CastingSlot::Gem6; + case EQEmu::CastingSlot::Gem7: + return CastingSlot::Gem7; + case EQEmu::CastingSlot::Gem8: + return CastingSlot::Gem8; + case EQEmu::CastingSlot::Gem9: + return CastingSlot::Gem9; + case EQEmu::CastingSlot::Item: + return CastingSlot::Item; + case EQEmu::CastingSlot::PotionBelt: + return CastingSlot::PotionBelt; + case EQEmu::CastingSlot::Discipline: + return CastingSlot::Discipline; + case EQEmu::CastingSlot::AltAbility: + return CastingSlot::AltAbility; + default: // we shouldn't have any issues with other slots ... just return something + return CastingSlot::Discipline; + } + } + + static inline EQEmu::CastingSlot TitaniumToServerCastingSlot(CastingSlot slot, uint32 itemlocation) + { + switch (slot) { + case CastingSlot::Gem1: + return EQEmu::CastingSlot::Gem1; + case CastingSlot::Gem2: + return EQEmu::CastingSlot::Gem2; + case CastingSlot::Gem3: + return EQEmu::CastingSlot::Gem3; + case CastingSlot::Gem4: + return EQEmu::CastingSlot::Gem4; + case CastingSlot::Gem5: + return EQEmu::CastingSlot::Gem5; + case CastingSlot::Gem6: + return EQEmu::CastingSlot::Gem6; + case CastingSlot::Gem7: + return EQEmu::CastingSlot::Gem7; + case CastingSlot::Gem8: + return EQEmu::CastingSlot::Gem8; + case CastingSlot::Gem9: + return EQEmu::CastingSlot::Gem9; + case CastingSlot::Ability: + return EQEmu::CastingSlot::Ability; + // Tit uses 10 for item and discipline casting, but items have a valid location + case CastingSlot::Item: + if (itemlocation == INVALID_INDEX) + return EQEmu::CastingSlot::Discipline; + else + return EQEmu::CastingSlot::Item; + case CastingSlot::PotionBelt: + return EQEmu::CastingSlot::PotionBelt; + case CastingSlot::AltAbility: + return EQEmu::CastingSlot::AltAbility; + default: // we shouldn't have any issues with other slots ... just return something + return EQEmu::CastingSlot::Discipline; + } + } } /*Titanium*/ diff --git a/common/patches/titanium.h b/common/patches/titanium.h index 2f063c9ad..ea68dd59a 100644 --- a/common/patches/titanium.h +++ b/common/patches/titanium.h @@ -50,6 +50,23 @@ namespace Titanium #include "titanium_ops.h" }; + enum class CastingSlot : uint32 { + Gem1 = 0, + Gem2 = 1, + Gem3 = 2, + Gem4 = 3, + Gem5 = 4, + Gem6 = 5, + Gem7 = 6, + Gem8 = 7, + Gem9 = 8, + Ability = 9, + Item = 10, + Discipline = 10, + PotionBelt = 11, + AltAbility = 0xFF + }; + }; /*Titanium*/ #endif /*COMMON_TITANIUM_H*/ diff --git a/common/patches/titanium_ops.h b/common/patches/titanium_ops.h index f878fbd76..9ca0ba770 100644 --- a/common/patches/titanium_ops.h +++ b/common/patches/titanium_ops.h @@ -50,6 +50,7 @@ E(OP_ItemPacket) E(OP_LeadershipExpUpdate) E(OP_LFGuild) E(OP_LootItem) +E(OP_MemorizeSpell) E(OP_MoveItem) E(OP_OnLevelMessage) E(OP_PetBuffWindow) diff --git a/common/patches/titanium_structs.h b/common/patches/titanium_structs.h index caa798da0..b7b2bac67 100644 --- a/common/patches/titanium_structs.h +++ b/common/patches/titanium_structs.h @@ -390,7 +390,7 @@ struct MemorizeSpell_Struct { uint32 slot; // Spot in the spell book/memorized slot uint32 spell_id; // Spell id (200 or c8 is minor healing, etc) uint32 scribing; // 1 if memorizing a spell, set to 0 if scribing to book, 2 if un-memming -uint32 unknown12; +uint32 reduction; // lowers reuse }; /* diff --git a/common/patches/uf.cpp b/common/patches/uf.cpp index 80a266ebe..735243f09 100644 --- a/common/patches/uf.cpp +++ b/common/patches/uf.cpp @@ -59,6 +59,9 @@ namespace UF // client to server text link converter static inline void UFToServerTextLink(std::string& serverTextLink, const std::string& ufTextLink); + static inline CastingSlot ServerToUFCastingSlot(EQEmu::CastingSlot slot); + static inline EQEmu::CastingSlot UFToServerCastingSlot(CastingSlot slot); + void Register(EQStreamIdentifier &into) { //create our opcode manager if we havent already @@ -3259,10 +3262,7 @@ namespace UF DECODE_LENGTH_EXACT(structs::CastSpell_Struct); SETUP_DIRECT_DECODE(CastSpell_Struct, structs::CastSpell_Struct); - if (eq->slot == 13) - emu->slot = 10; - else - IN(slot); + emu->slot = static_cast(UFToServerCastingSlot(static_cast(eq->slot))); IN(spell_id); emu->inventoryslot = UFToServerSlot(eq->inventoryslot); @@ -4378,4 +4378,80 @@ namespace UF } } + static inline CastingSlot ServerToUFCastingSlot(EQEmu::CastingSlot slot) + { + switch (slot) { + case EQEmu::CastingSlot::Gem1: + return CastingSlot::Gem1; + case EQEmu::CastingSlot::Gem2: + return CastingSlot::Gem2; + case EQEmu::CastingSlot::Gem3: + return CastingSlot::Gem3; + case EQEmu::CastingSlot::Gem4: + return CastingSlot::Gem4; + case EQEmu::CastingSlot::Gem5: + return CastingSlot::Gem5; + case EQEmu::CastingSlot::Gem6: + return CastingSlot::Gem6; + case EQEmu::CastingSlot::Gem7: + return CastingSlot::Gem7; + case EQEmu::CastingSlot::Gem8: + return CastingSlot::Gem8; + case EQEmu::CastingSlot::Gem9: + return CastingSlot::Gem9; + case EQEmu::CastingSlot::Gem10: + return CastingSlot::Gem10; + case EQEmu::CastingSlot::Gem11: + return CastingSlot::Gem11; + case EQEmu::CastingSlot::Gem12: + return CastingSlot::Gem12; + case EQEmu::CastingSlot::Item: + case EQEmu::CastingSlot::PotionBelt: + return CastingSlot::Item; + case EQEmu::CastingSlot::Discipline: + return CastingSlot::Discipline; + case EQEmu::CastingSlot::AltAbility: + return CastingSlot::AltAbility; + default: // we shouldn't have any issues with other slots ... just return something + return CastingSlot::Discipline; + } + } + + static inline EQEmu::CastingSlot UFToServerCastingSlot(CastingSlot slot) + { + switch (slot) { + case CastingSlot::Gem1: + return EQEmu::CastingSlot::Gem1; + case CastingSlot::Gem2: + return EQEmu::CastingSlot::Gem2; + case CastingSlot::Gem3: + return EQEmu::CastingSlot::Gem3; + case CastingSlot::Gem4: + return EQEmu::CastingSlot::Gem4; + case CastingSlot::Gem5: + return EQEmu::CastingSlot::Gem5; + case CastingSlot::Gem6: + return EQEmu::CastingSlot::Gem6; + case CastingSlot::Gem7: + return EQEmu::CastingSlot::Gem7; + case CastingSlot::Gem8: + return EQEmu::CastingSlot::Gem8; + case CastingSlot::Gem9: + return EQEmu::CastingSlot::Gem9; + case CastingSlot::Gem10: + return EQEmu::CastingSlot::Gem10; + case CastingSlot::Gem11: + return EQEmu::CastingSlot::Gem11; + case CastingSlot::Gem12: + return EQEmu::CastingSlot::Gem12; + case CastingSlot::Discipline: + return EQEmu::CastingSlot::Discipline; + case CastingSlot::Item: + return EQEmu::CastingSlot::Item; + case CastingSlot::AltAbility: + return EQEmu::CastingSlot::AltAbility; + default: // we shouldn't have any issues with other slots ... just return something + return EQEmu::CastingSlot::Discipline; + } + } } /*UF*/ diff --git a/common/patches/uf.h b/common/patches/uf.h index c36d9e851..1dc5046c3 100644 --- a/common/patches/uf.h +++ b/common/patches/uf.h @@ -50,6 +50,24 @@ namespace UF #include "uf_ops.h" }; + enum class CastingSlot : uint32 { + Gem1 = 0, + Gem2 = 1, + Gem3 = 2, + Gem4 = 3, + Gem5 = 4, + Gem6 = 5, + Gem7 = 6, + Gem8 = 7, + Gem9 = 8, + Gem10 = 9, + Gem11 = 10, + Gem12 = 11, + Item = 12, + Discipline = 13, + AltAbility = 0xFF + }; + }; /*UF*/ #endif /*COMMON_UF_H*/ diff --git a/common/patches/uf_structs.h b/common/patches/uf_structs.h index 932213827..7ef766d86 100644 --- a/common/patches/uf_structs.h +++ b/common/patches/uf_structs.h @@ -479,7 +479,7 @@ struct MemorizeSpell_Struct { uint32 slot; // Spot in the spell book/memorized slot uint32 spell_id; // Spell id (200 or c8 is minor healing, etc) uint32 scribing; // 1 if memorizing a spell, set to 0 if scribing to book, 2 if un-memming -//uint32 unknown12; +uint32 reduction; // lowers reuse }; /* diff --git a/world/client.cpp b/world/client.cpp index 8265ab03a..460fa294d 100644 --- a/world/client.cpp +++ b/world/client.cpp @@ -1483,7 +1483,7 @@ bool Client::OPCharCreate(char *name, CharCreate_Struct *cc) for (i = 0; i < MAX_PP_REF_SPELLBOOK; i++) pp.spell_book[i] = 0xFFFFFFFF; - for(i = 0; i < MAX_PP_REF_MEMSPELL; i++) + for(i = 0; i < MAX_PP_MEMSPELL; i++) pp.mem_spells[i] = 0xFFFFFFFF; for(i = 0; i < BUFF_COUNT; i++) diff --git a/zone/aa.cpp b/zone/aa.cpp index d18936956..f6facec8c 100644 --- a/zone/aa.cpp +++ b/zone/aa.cpp @@ -1183,12 +1183,12 @@ void Client::ActivateAlternateAdvancementAbility(int rank_id, int target_id) { CommonBreakInvisible(); // Bards can cast instant cast AAs while they are casting another song if(spells[rank->spell].cast_time == 0 && GetClass() == BARD && IsBardSong(casting_spell_id)) { - if(!SpellFinished(rank->spell, entity_list.GetMob(target_id), ALTERNATE_ABILITY_SPELL_SLOT, spells[rank->spell].mana, -1, spells[rank->spell].ResistDiff, false)) { + if(!SpellFinished(rank->spell, entity_list.GetMob(target_id), EQEmu::CastingSlot::AltAbility, spells[rank->spell].mana, -1, spells[rank->spell].ResistDiff, false)) { return; } ExpendAlternateAdvancementCharge(ability->id); } else { - if(!CastSpell(rank->spell, target_id, ALTERNATE_ABILITY_SPELL_SLOT, -1, -1, 0, -1, rank->spell_type + pTimerAAStart, cooldown, nullptr, rank->id)) { + if(!CastSpell(rank->spell, target_id, EQEmu::CastingSlot::AltAbility, -1, -1, 0, -1, rank->spell_type + pTimerAAStart, cooldown, nullptr, rank->id)) { return; } } diff --git a/zone/attack.cpp b/zone/attack.cpp index 1c7796f42..54f98c97a 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -1205,7 +1205,7 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b IsValidSpell(aabonuses.SkillAttackProc[2])) { float chance = aabonuses.SkillAttackProc[0] / 1000.0f; if (zone->random.Roll(chance)) - SpellFinished(aabonuses.SkillAttackProc[2], other, 10, 0, -1, + 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); @@ -1821,7 +1821,7 @@ void NPC::Damage(Mob* other, int32 damage, uint16 spell_id, EQEmu::skills::Skill if(IsLDoNTrapped()) { Message_StringID(13, LDON_ACCIDENT_SETOFF2); - SpellFinished(GetLDoNTrapSpellID(), other, 10, 0, -1, spells[GetLDoNTrapSpellID()].ResistDiff, false); + SpellFinished(GetLDoNTrapSpellID(), other, EQEmu::CastingSlot::Item, 0, -1, spells[GetLDoNTrapSpellID()].ResistDiff, false); SetLDoNTrapSpellID(0); SetLDoNTrapped(false); SetLDoNTrapDetected(false); diff --git a/zone/bot.cpp b/zone/bot.cpp index 10f8c12f4..e4c9f0038 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2085,7 +2085,7 @@ void Bot::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills: CheckNumHitsRemaining(NumHit::OutgoingHitSuccess); if ((skillinuse == EQEmu::skills::SkillDragonPunch) && GetAA(aaDragonPunch) && zone->random.Int(0, 99) < 25){ - SpellFinished(904, other, 10, 0, -1, spells[904].ResistDiff); + SpellFinished(904, other, EQEmu::CastingSlot::Item, 0, -1, spells[904].ResistDiff); other->Stun(100); } @@ -5900,7 +5900,7 @@ void Bot::DoBuffTic(const Buffs_Struct &buff, int slot, Mob* caster) { Mob::DoBuffTic(buff, slot, caster); } -bool Bot::CastSpell(uint16 spell_id, uint16 target_id, uint16 slot, int32 cast_time, int32 mana_cost, +bool Bot::CastSpell(uint16 spell_id, uint16 target_id, EQEmu::CastingSlot slot, int32 cast_time, int32 mana_cost, uint32* oSpellWillFinish, uint32 item_slot, int16 *resist_adjust, uint32 aa_id) { bool Result = false; if(zone && !zone->IsSpellBlocked(spell_id, glm::vec3(GetPosition()))) { @@ -5920,7 +5920,7 @@ bool Bot::CastSpell(uint16 spell_id, uint16 target_id, uint16 slot, int32 cast_t Message_StringID(13, MELEE_SILENCE); if(casting_spell_id) - AI_Event_SpellCastFinished(false, casting_spell_slot); + AI_Event_SpellCastFinished(false, static_cast(casting_spell_slot)); return false; } @@ -5929,7 +5929,7 @@ bool Bot::CastSpell(uint16 spell_id, uint16 target_id, uint16 slot, int32 cast_t if(IsDetrimentalSpell(spell_id) && !zone->CanDoCombat()){ Message_StringID(13, SPELL_WOULDNT_HOLD); if(casting_spell_id) - AI_Event_SpellCastFinished(false, casting_spell_slot); + AI_Event_SpellCastFinished(false, static_cast(casting_spell_slot)); return false; } @@ -5940,7 +5940,7 @@ bool Bot::CastSpell(uint16 spell_id, uint16 target_id, uint16 slot, int32 cast_t return false; } - if(slot < MAX_PP_MEMSPELL && !CheckFizzle(spell_id)) { + if(slot < EQEmu::CastingSlot::MaxGems && !CheckFizzle(spell_id)) { int fizzle_msg = IsBardSong(spell_id) ? MISS_NOTE : SPELL_FIZZLE; InterruptSpell(fizzle_msg, 0x121, spell_id); @@ -5954,7 +5954,7 @@ bool Bot::CastSpell(uint16 spell_id, uint16 target_id, uint16 slot, int32 cast_t Log.Out(Logs::Detail, Logs::Spells, "Casting a new spell/song while singing a song. Killing old song %d.", bardsong); bardsong = 0; bardsong_target_id = 0; - bardsong_slot = 0; + bardsong_slot = EQEmu::CastingSlot::Gem1; bardsong_timer.Disable(); } @@ -6084,7 +6084,7 @@ bool Bot::IsImmuneToSpell(uint16 spell_id, Mob *caster) { return Result; } -bool Bot::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_center, CastAction_type &CastAction, uint16 slot) { +bool Bot::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_center, CastAction_type &CastAction, EQEmu::CastingSlot slot) { bool Result = false; SpellTargetType targetType = spells[spell_id].targettype; if(targetType == ST_GroupClientAndPet) { @@ -6097,7 +6097,7 @@ bool Bot::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce return Result; } -bool Bot::DoCastSpell(uint16 spell_id, uint16 target_id, uint16 slot, int32 cast_time, int32 mana_cost, uint32* oSpellWillFinish, uint32 item_slot, uint32 aa_id) { +bool Bot::DoCastSpell(uint16 spell_id, uint16 target_id, EQEmu::CastingSlot slot, int32 cast_time, int32 mana_cost, uint32* oSpellWillFinish, uint32 item_slot, uint32 aa_id) { bool Result = false; if(GetClass() == BARD) cast_time = 0; @@ -6201,7 +6201,7 @@ void Bot::GenerateSpecialAttacks() { SetSpecialAbility(SPECATK_TRIPLE, 1); } -bool Bot::DoFinishedSpellAETarget(uint16 spell_id, Mob* spellTarget, uint16 slot, bool& stopLogic) { +bool Bot::DoFinishedSpellAETarget(uint16 spell_id, Mob* spellTarget, EQEmu::CastingSlot slot, bool& stopLogic) { if(GetClass() == BARD) { if(!ApplyNextBardPulse(bardsong, this, bardsong_slot)) InterruptSpell(SONG_ENDS_ABRUPTLY, 0x121, bardsong); @@ -6211,7 +6211,7 @@ bool Bot::DoFinishedSpellAETarget(uint16 spell_id, Mob* spellTarget, uint16 slot return true; } -bool Bot::DoFinishedSpellSingleTarget(uint16 spell_id, Mob* spellTarget, uint16 slot, bool& stopLogic) { +bool Bot::DoFinishedSpellSingleTarget(uint16 spell_id, Mob* spellTarget, EQEmu::CastingSlot slot, bool& stopLogic) { if(spellTarget) { if(IsGrouped() && (spellTarget->IsBot() || spellTarget->IsClient()) && RuleB(Bots, GroupBuffing)) { bool noGroupSpell = false; @@ -6223,7 +6223,7 @@ bool Bot::DoFinishedSpellSingleTarget(uint16 spell_id, Mob* spellTarget, uint16 bool spelltypeequal = ((spelltype == 2) || (spelltype == 16) || (spelltype == 32)); bool spelltypetargetequal = ((spelltype == 8) && (spells[thespell].targettype == ST_Self)); bool spelltypeclassequal = ((spelltype == 1024) && (GetClass() == SHAMAN)); - bool slotequal = (slot == USE_ITEM_SPELL_SLOT); + bool slotequal = (slot == EQEmu::CastingSlot::Item); if(spellequal || slotequal) { if((spelltypeequal || spelltypetargetequal) || spelltypeclassequal || slotequal) { if(((spells[thespell].effectid[0] == 0) && (spells[thespell].base[0] < 0)) && @@ -6262,7 +6262,7 @@ bool Bot::DoFinishedSpellSingleTarget(uint16 spell_id, Mob* spellTarget, uint16 return true; } -bool Bot::DoFinishedSpellGroupTarget(uint16 spell_id, Mob* spellTarget, uint16 slot, bool& stopLogic) { +bool Bot::DoFinishedSpellGroupTarget(uint16 spell_id, Mob* spellTarget, EQEmu::CastingSlot slot, bool& stopLogic) { bool isMainGroupMGB = false; if(isMainGroupMGB && (GetClass() != BARD)) { BotGroupSay(this, "MGB %s", spells[spell_id].name); @@ -8251,7 +8251,7 @@ bool Bot::UseDiscipline(uint32 spell_id, uint32 target) { if(IsCasting()) InterruptSpell(); - CastSpell(spell_id, target, DISCIPLINE_SPELL_SLOT); + CastSpell(spell_id, target, EQEmu::CastingSlot::Discipline); return true; } diff --git a/zone/bot.h b/zone/bot.h index b294fa108..3d45475a3 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -274,9 +274,9 @@ public: virtual void SetAttackTimer(); uint32 GetClassHPFactor(); virtual int32 CalcMaxHP(); - bool DoFinishedSpellAETarget(uint16 spell_id, Mob* spellTarget, uint16 slot, bool &stopLogic); - bool DoFinishedSpellSingleTarget(uint16 spell_id, Mob* spellTarget, uint16 slot, bool &stopLogic); - bool DoFinishedSpellGroupTarget(uint16 spell_id, Mob* spellTarget, uint16 slot, bool &stopLogic); + bool DoFinishedSpellAETarget(uint16 spell_id, Mob* spellTarget, EQEmu::CastingSlot slot, bool &stopLogic); + bool DoFinishedSpellSingleTarget(uint16 spell_id, Mob* spellTarget, EQEmu::CastingSlot slot, bool &stopLogic); + bool DoFinishedSpellGroupTarget(uint16 spell_id, Mob* spellTarget, EQEmu::CastingSlot slot, bool &stopLogic); void SendBotArcheryWearChange(uint8 material_slot, uint32 material, uint32 color); void Camp(bool databaseSave = true); virtual void AddToHateList(Mob* other, uint32 hate = 0, int32 damage = 0, bool iYellForHelp = true, bool bFrenzy = false, bool iBuffTic = false); @@ -374,12 +374,12 @@ public: virtual float GetAOERange(uint16 spell_id); virtual bool SpellEffect(Mob* caster, uint16 spell_id, float partial = 100); virtual void DoBuffTic(const Buffs_Struct &buff, int slot, Mob* caster = nullptr); - virtual bool CastSpell(uint16 spell_id, uint16 target_id, uint16 slot = USE_ITEM_SPELL_SLOT, int32 casttime = -1, int32 mana_cost = -1, uint32* oSpellWillFinish = 0, + virtual bool CastSpell(uint16 spell_id, uint16 target_id, EQEmu::CastingSlot slot = EQEmu::CastingSlot::Item, int32 casttime = -1, int32 mana_cost = -1, uint32* oSpellWillFinish = 0, uint32 item_slot = 0xFFFFFFFF, int16 *resist_adjust = nullptr, uint32 aa_id = 0); virtual bool SpellOnTarget(uint16 spell_id, Mob* spelltar); virtual bool IsImmuneToSpell(uint16 spell_id, Mob *caster); - virtual bool DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_center, CastAction_type &CastAction, uint16 slot); - virtual bool DoCastSpell(uint16 spell_id, uint16 target_id, uint16 slot = USE_ITEM_SPELL_SLOT, int32 casttime = -1, int32 mana_cost = -1, uint32* oSpellWillFinish = 0, uint32 item_slot = 0xFFFFFFFF, uint32 aa_id = 0); + virtual bool DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_center, CastAction_type &CastAction, EQEmu::CastingSlot slot); + virtual bool DoCastSpell(uint16 spell_id, uint16 target_id, EQEmu::CastingSlot slot = EQEmu::CastingSlot::Item, int32 casttime = -1, int32 mana_cost = -1, uint32* oSpellWillFinish = 0, uint32 item_slot = 0xFFFFFFFF, uint32 aa_id = 0); // Bot Equipment & Inventory Class Methods void BotTradeSwapItem(Client* client, int16 lootSlot, const ItemInst* inst, const ItemInst* inst_swap, uint32 equipableSlots, std::string* errorMessage, bool swap = true); diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index e3b16219a..6b07bbdda 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -7613,7 +7613,7 @@ bool helper_cast_standard_spell(Bot* casting_bot, Mob* target_mob, int spell_id, if (annouce_cast) Bot::BotGroupSay(casting_bot, "Attempting to cast '%s' on %s", spells[spell_id].name, target_mob->GetCleanName()); - return casting_bot->CastSpell(spell_id, target_mob->GetID(), 1, -1, -1, dont_root_before); + return casting_bot->CastSpell(spell_id, target_mob->GetID(), EQEmu::CastingSlot::Gem2, -1, -1, dont_root_before); } bool helper_command_alias_fail(Client *bot_owner, const char* command_handler, const char *alias, const char *command) diff --git a/zone/client.cpp b/zone/client.cpp index 732485a60..bdc89e3b9 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -4604,7 +4604,7 @@ void Client::HandleLDoNOpen(NPC *target) if(target->GetLDoNTrapSpellID() != 0) { Message_StringID(13, LDON_ACCIDENT_SETOFF2); - target->SpellFinished(target->GetLDoNTrapSpellID(), this, 10, 0, -1, spells[target->GetLDoNTrapSpellID()].ResistDiff); + target->SpellFinished(target->GetLDoNTrapSpellID(), this, EQEmu::CastingSlot::Item, 0, -1, spells[target->GetLDoNTrapSpellID()].ResistDiff); target->SetLDoNTrapSpellID(0); target->SetLDoNTrapped(false); target->SetLDoNTrapDetected(false); @@ -4726,7 +4726,7 @@ void Client::HandleLDoNDisarm(NPC *target, uint16 skill, uint8 type) break; case -1: Message_StringID(13, LDON_ACCIDENT_SETOFF2); - target->SpellFinished(target->GetLDoNTrapSpellID(), this, 10, 0, -1, spells[target->GetLDoNTrapSpellID()].ResistDiff); + target->SpellFinished(target->GetLDoNTrapSpellID(), this, EQEmu::CastingSlot::Item, 0, -1, spells[target->GetLDoNTrapSpellID()].ResistDiff); target->SetLDoNTrapSpellID(0); target->SetLDoNTrapped(false); target->SetLDoNTrapDetected(false); @@ -4745,7 +4745,7 @@ void Client::HandleLDoNPickLock(NPC *target, uint16 skill, uint8 type) if(target->IsLDoNTrapped()) { Message_StringID(13, LDON_ACCIDENT_SETOFF2); - target->SpellFinished(target->GetLDoNTrapSpellID(), this, 10, 0, -1, spells[target->GetLDoNTrapSpellID()].ResistDiff); + target->SpellFinished(target->GetLDoNTrapSpellID(), this, EQEmu::CastingSlot::Item, 0, -1, spells[target->GetLDoNTrapSpellID()].ResistDiff); target->SetLDoNTrapSpellID(0); target->SetLDoNTrapped(false); target->SetLDoNTrapDetected(false); diff --git a/zone/client.h b/zone/client.h index 1d30b97ce..a17392518 100644 --- a/zone/client.h +++ b/zone/client.h @@ -105,6 +105,7 @@ enum { //Type arguments to the Message* routines. #define SPELLBAR_UNLOCK 0x2bc enum { //scribing argument to MemorizeSpell + memSpellUnknown = -1, // this modifies some state data memSpellScribing = 0, memSpellMemorize = 1, memSpellForget = 2, diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index f0de676f9..a827a3008 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -3966,6 +3966,7 @@ void Client::Handle_OP_CancelTrade(const EQApplicationPacket *app) void Client::Handle_OP_CastSpell(const EQApplicationPacket *app) { + using EQEmu::CastingSlot; if (app->size != sizeof(CastSpell_Struct)) { std::cout << "Wrong size: OP_CastSpell, size=" << app->size << ", expected " << sizeof(CastSpell_Struct) << std::endl; return; @@ -3978,13 +3979,13 @@ void Client::Handle_OP_CastSpell(const EQApplicationPacket *app) CastSpell_Struct* castspell = (CastSpell_Struct*)app->pBuffer; - m_TargetRing = glm::vec3(castspell->x_pos, castspell->y_pos, castspell->z_pos); + m_TargetRing = glm::vec3(castspell->x_pos, castspell->y_pos, castspell->z_pos); Log.Out(Logs::General, Logs::Spells, "OP CastSpell: slot=%d, spell=%d, target=%d, inv=%lx", castspell->slot, castspell->spell_id, castspell->target_id, (unsigned long)castspell->inventoryslot); + CastingSlot slot = static_cast(castspell->slot); /* Memorized Spell */ - if (m_pp.mem_spells[castspell->slot] && m_pp.mem_spells[castspell->slot] == castspell->spell_id){ - + if (m_pp.mem_spells[castspell->slot] && m_pp.mem_spells[castspell->slot] == castspell->spell_id) { uint16 spell_to_cast = 0; if (castspell->slot < MAX_PP_MEMSPELL) { spell_to_cast = m_pp.mem_spells[castspell->slot]; @@ -3998,20 +3999,12 @@ void Client::Handle_OP_CastSpell(const EQApplicationPacket *app) return; } - CastSpell(spell_to_cast, castspell->target_id, castspell->slot); + CastSpell(spell_to_cast, castspell->target_id, slot); } /* Spell Slot or Potion Belt Slot */ - else if ((castspell->slot == USE_ITEM_SPELL_SLOT) || (castspell->slot == POTION_BELT_SPELL_SLOT)|| (castspell->slot == TARGET_RING_SPELL_SLOT)) // ITEM or POTION cast + else if (slot == CastingSlot::Item || slot == CastingSlot::PotionBelt) // ITEM or POTION cast { - //discipline, using the item spell slot - if (castspell->inventoryslot == INVALID_INDEX) { - if (!UseDiscipline(castspell->spell_id, castspell->target_id)) { - Log.Out(Logs::General, Logs::Spells, "Unknown ability being used by %s, spell being cast is: %i\n", GetName(), castspell->spell_id); - InterruptSpell(castspell->spell_id); - } - return; - } - else if (m_inv.SupportsClickCasting(castspell->inventoryslot) || (castspell->slot == POTION_BELT_SPELL_SLOT) || (castspell->slot == TARGET_RING_SPELL_SLOT)) // sanity check + if (m_inv.SupportsClickCasting(castspell->inventoryslot) || slot == CastingSlot::PotionBelt) // sanity check { // packet field types will be reviewed as packet transistions occur const ItemInst* inst = m_inv[castspell->inventoryslot]; //slot values are int16, need to check packet on this field @@ -4036,7 +4029,7 @@ void Client::Handle_OP_CastSpell(const EQApplicationPacket *app) int i = parse->EventItem(EVENT_ITEM_CLICK_CAST, this, p_inst, nullptr, "", castspell->inventoryslot); if (i == 0) { - CastSpell(item->Click.Effect, castspell->target_id, castspell->slot, item->CastTime, 0, 0, castspell->inventoryslot); + CastSpell(item->Click.Effect, castspell->target_id, slot, item->CastTime, 0, 0, castspell->inventoryslot); } else { InterruptSpell(castspell->spell_id); @@ -4056,7 +4049,7 @@ void Client::Handle_OP_CastSpell(const EQApplicationPacket *app) int i = parse->EventItem(EVENT_ITEM_CLICK_CAST, this, p_inst, nullptr, "", castspell->inventoryslot); if (i == 0) { - CastSpell(item->Click.Effect, castspell->target_id, castspell->slot, item->CastTime, 0, 0, castspell->inventoryslot); + CastSpell(item->Click.Effect, castspell->target_id, slot, item->CastTime, 0, 0, castspell->inventoryslot); } else { InterruptSpell(castspell->spell_id); @@ -4081,8 +4074,8 @@ void Client::Handle_OP_CastSpell(const EQApplicationPacket *app) InterruptSpell(castspell->spell_id); } } - /* Discipline */ - else if (castspell->slot == DISCIPLINE_SPELL_SLOT) { + /* Discipline -- older clients use the same slot as items, but we translate to it's own */ + else if (slot == CastingSlot::Discipline) { if (!UseDiscipline(castspell->spell_id, castspell->target_id)) { Log.Out(Logs::General, Logs::Spells, "Unknown ability being used by %s, spell being cast is: %i\n", GetName(), castspell->spell_id); InterruptSpell(castspell->spell_id); @@ -4090,7 +4083,7 @@ void Client::Handle_OP_CastSpell(const EQApplicationPacket *app) } } /* ABILITY cast (LoH and Harm Touch) */ - else if (castspell->slot == ABILITY_SPELL_SLOT) { + else if (slot == CastingSlot::Ability) { uint16 spell_to_cast = 0; if (castspell->spell_id == SPELL_LAY_ON_HANDS && GetClass() == PALADIN) { @@ -4120,7 +4113,7 @@ void Client::Handle_OP_CastSpell(const EQApplicationPacket *app) } if (spell_to_cast > 0) // if we've matched LoH or HT, cast now - CastSpell(spell_to_cast, castspell->target_id, castspell->slot); + CastSpell(spell_to_cast, castspell->target_id, slot); } return; } @@ -8410,6 +8403,7 @@ void Client::Handle_OP_ItemPreview(const EQApplicationPacket *app) void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app) { + using EQEmu::CastingSlot; if (app->size != sizeof(ItemVerifyRequest_Struct)) { Log.Out(Logs::General, Logs::Error, "OP size error: OP_ItemVerifyRequest expected:%i got:%i", sizeof(ItemVerifyRequest_Struct), app->size); @@ -8554,7 +8548,7 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app) if (i == 0) { if (!IsCastWhileInvis(item->Click.Effect)) CommonBreakInvisible(); // client can't do this for us :( - CastSpell(item->Click.Effect, target_id, USE_ITEM_SPELL_SLOT, item->CastTime, 0, 0, slot_id); + CastSpell(item->Click.Effect, target_id, CastingSlot::Item, item->CastTime, 0, 0, slot_id); } } else @@ -8583,7 +8577,7 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app) if (i == 0) { if (!IsCastWhileInvis(augitem->Click.Effect)) CommonBreakInvisible(); // client can't do this for us :( - CastSpell(augitem->Click.Effect, target_id, USE_ITEM_SPELL_SLOT, augitem->CastTime, 0, 0, slot_id); + CastSpell(augitem->Click.Effect, target_id, CastingSlot::Item, augitem->CastTime, 0, 0, slot_id); } } else diff --git a/zone/command.cpp b/zone/command.cpp index 68049cc43..95770097d 100644 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -2199,14 +2199,14 @@ void command_castspell(Client *c, const Seperator *sep) else if (c->GetTarget() == 0) if(c->Admin() >= commandInstacast) - c->SpellFinished(spellid, 0, USE_ITEM_SPELL_SLOT, 0, -1, spells[spellid].ResistDiff); + c->SpellFinished(spellid, 0, EQEmu::CastingSlot::Item, 0, -1, spells[spellid].ResistDiff); else - c->CastSpell(spellid, 0, USE_ITEM_SPELL_SLOT, 0); + c->CastSpell(spellid, 0, EQEmu::CastingSlot::Item, 0); else if(c->Admin() >= commandInstacast) - c->SpellFinished(spellid, c->GetTarget(), 10, 0, -1, spells[spellid].ResistDiff); + c->SpellFinished(spellid, c->GetTarget(), EQEmu::CastingSlot::Item, 0, -1, spells[spellid].ResistDiff); else - c->CastSpell(spellid, c->GetTarget()->GetID(), USE_ITEM_SPELL_SLOT, 0); + c->CastSpell(spellid, c->GetTarget()->GetID(), EQEmu::CastingSlot::Item, 0); } } diff --git a/zone/common.h b/zone/common.h index 97bb1a50b..9a5768c56 100644 --- a/zone/common.h +++ b/zone/common.h @@ -17,13 +17,6 @@ #define _NPCPET(x) (x && x->IsNPC() && x->CastToMob()->GetOwner() && x->CastToMob()->GetOwner()->IsNPC()) #define _BECOMENPCPET(x) (x && x->CastToMob()->GetOwner() && x->CastToMob()->GetOwner()->IsClient() && x->CastToMob()->GetOwner()->CastToClient()->IsBecomeNPC()) -#define USE_ITEM_SPELL_SLOT 10 -#define POTION_BELT_SPELL_SLOT 11 -#define TARGET_RING_SPELL_SLOT 12 -#define DISCIPLINE_SPELL_SLOT 10 -#define ABILITY_SPELL_SLOT 9 -#define ALTERNATE_ABILITY_SPELL_SLOT 0xFF - //LOS Parameters: #define HEAD_POSITION 0.9f //ratio of GetSize() where NPCs see from #define SEE_POSITION 0.5f //ratio of GetSize() where NPCs try to see for LOS diff --git a/zone/effects.cpp b/zone/effects.cpp index 7d0b9af02..5b022f97d 100644 --- a/zone/effects.cpp +++ b/zone/effects.cpp @@ -684,9 +684,9 @@ bool Client::UseDiscipline(uint32 spell_id, uint32 target) { } if (reduced_recast > 0) - CastSpell(spell_id, target, DISCIPLINE_SPELL_SLOT, -1, -1, 0, -1, (uint32)DiscTimer, reduced_recast); + CastSpell(spell_id, target, EQEmu::CastingSlot::Discipline, -1, -1, 0, -1, (uint32)DiscTimer, reduced_recast); else{ - CastSpell(spell_id, target, DISCIPLINE_SPELL_SLOT); + CastSpell(spell_id, target, EQEmu::CastingSlot::Discipline); return true; } @@ -694,7 +694,7 @@ bool Client::UseDiscipline(uint32 spell_id, uint32 target) { } else { - CastSpell(spell_id, target, DISCIPLINE_SPELL_SLOT); + CastSpell(spell_id, target, EQEmu::CastingSlot::Discipline); } return(true); } diff --git a/zone/lua_mob.cpp b/zone/lua_mob.cpp index 124726b3f..f5b8c8382 100644 --- a/zone/lua_mob.cpp +++ b/zone/lua_mob.cpp @@ -759,28 +759,28 @@ bool Lua_Mob::CastSpell(int spell_id, int target_id) { bool Lua_Mob::CastSpell(int spell_id, int target_id, int slot) { Lua_Safe_Call_Bool(); - return self->CastSpell(spell_id, target_id, slot); + return self->CastSpell(spell_id, target_id, static_cast(slot)); } bool Lua_Mob::CastSpell(int spell_id, int target_id, int slot, int cast_time) { Lua_Safe_Call_Bool(); - return self->CastSpell(spell_id, target_id, slot, cast_time); + return self->CastSpell(spell_id, target_id, static_cast(slot), cast_time); } bool Lua_Mob::CastSpell(int spell_id, int target_id, int slot, int cast_time, int mana_cost) { Lua_Safe_Call_Bool(); - return self->CastSpell(spell_id, target_id, slot, cast_time, mana_cost); + return self->CastSpell(spell_id, target_id, static_cast(slot), cast_time, mana_cost); } bool Lua_Mob::CastSpell(int spell_id, int target_id, int slot, int cast_time, int mana_cost, int item_slot) { Lua_Safe_Call_Bool(); - return self->CastSpell(spell_id, target_id, slot, cast_time, mana_cost, nullptr, static_cast(item_slot)); + return self->CastSpell(spell_id, target_id, static_cast(slot), cast_time, mana_cost, nullptr, static_cast(item_slot)); } bool Lua_Mob::CastSpell(int spell_id, int target_id, int slot, int cast_time, int mana_cost, int item_slot, int timer, int timer_duration) { Lua_Safe_Call_Bool(); - return self->CastSpell(spell_id, target_id, slot, cast_time, mana_cost, nullptr, static_cast(item_slot), + return self->CastSpell(spell_id, target_id, static_cast(slot), cast_time, mana_cost, nullptr, static_cast(item_slot), static_cast(timer), static_cast(timer_duration)); } @@ -789,7 +789,7 @@ bool Lua_Mob::CastSpell(int spell_id, int target_id, int slot, int cast_time, in Lua_Safe_Call_Bool(); int16 res = resist_adjust; - return self->CastSpell(spell_id, target_id, slot, cast_time, mana_cost, nullptr, static_cast(item_slot), + return self->CastSpell(spell_id, target_id, static_cast(slot), cast_time, mana_cost, nullptr, static_cast(item_slot), static_cast(timer), static_cast(timer_duration), &res); } @@ -800,27 +800,27 @@ bool Lua_Mob::SpellFinished(int spell_id, Lua_Mob target) { bool Lua_Mob::SpellFinished(int spell_id, Lua_Mob target, int slot) { Lua_Safe_Call_Bool(); - return self->SpellFinished(spell_id, target, slot); + return self->SpellFinished(spell_id, target, static_cast(slot)); } bool Lua_Mob::SpellFinished(int spell_id, Lua_Mob target, int slot, int mana_used) { Lua_Safe_Call_Bool(); - return self->SpellFinished(spell_id, target, slot, mana_used); + return self->SpellFinished(spell_id, target, static_cast(slot), mana_used); } bool Lua_Mob::SpellFinished(int spell_id, Lua_Mob target, int slot, int mana_used, uint32 inventory_slot) { Lua_Safe_Call_Bool(); - return self->SpellFinished(spell_id, target, slot, mana_used, inventory_slot); + return self->SpellFinished(spell_id, target, static_cast(slot), mana_used, inventory_slot); } bool Lua_Mob::SpellFinished(int spell_id, Lua_Mob target, int slot, int mana_used, uint32 inventory_slot, int resist_adjust) { Lua_Safe_Call_Bool(); - return self->SpellFinished(spell_id, target, slot, mana_used, inventory_slot, resist_adjust); + return self->SpellFinished(spell_id, target, static_cast(slot), mana_used, inventory_slot, resist_adjust); } bool Lua_Mob::SpellFinished(int spell_id, Lua_Mob target, int slot, int mana_used, uint32 inventory_slot, int resist_adjust, bool proc) { Lua_Safe_Call_Bool(); - return self->SpellFinished(spell_id, target, slot, mana_used, inventory_slot, resist_adjust, proc); + return self->SpellFinished(spell_id, target, static_cast(slot), mana_used, inventory_slot, resist_adjust, proc); } void Lua_Mob::SpellEffect(Lua_Mob caster, int spell_id, double partial) { diff --git a/zone/merc.cpp b/zone/merc.cpp index 4ff8e8d90..c787d00c8 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -1963,7 +1963,7 @@ bool Merc::AIDoSpellCast(uint16 spellid, Mob* tar, int32 mana_cost, uint32* oDon SendPosition(); SetMoving(false); - result = CastSpell(spellid, tar->GetID(), 1, -1, mana_cost, oDontDoAgainBefore, -1, -1, 0, 0); + result = CastSpell(spellid, tar->GetID(), EQEmu::CastingSlot::Gem2, -1, mana_cost, oDontDoAgainBefore, -1, -1, 0, 0); if(IsCasting() && IsSitting()) Stand(); @@ -4015,7 +4015,7 @@ bool Merc::UseDiscipline(int32 spell_id, int32 target) { if(IsCasting()) InterruptSpell(); - CastSpell(spell_id, target, DISCIPLINE_SPELL_SLOT); + CastSpell(spell_id, target, EQEmu::CastingSlot::Discipline); return(true); } diff --git a/zone/mob.cpp b/zone/mob.cpp index 580e154e7..63737617e 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -3225,12 +3225,12 @@ void Mob::ExecWeaponProc(const ItemInst *inst, uint16 spell_id, Mob *on, int lev twinproc = true; if (IsBeneficialSpell(spell_id)) { - SpellFinished(spell_id, this, 10, 0, -1, spells[spell_id].ResistDiff, true, level_override); + SpellFinished(spell_id, this, EQEmu::CastingSlot::Item, 0, -1, spells[spell_id].ResistDiff, true, level_override); if(twinproc) SpellOnTarget(spell_id, this, false, false, 0, true, level_override); } else if(!(on->IsClient() && on->CastToClient()->dead)) { //dont proc on dead clients - SpellFinished(spell_id, on, 10, 0, -1, spells[spell_id].ResistDiff, true, level_override); + SpellFinished(spell_id, on, EQEmu::CastingSlot::Item, 0, -1, spells[spell_id].ResistDiff, true, level_override); if(twinproc) SpellOnTarget(spell_id, on, false, false, 0, true, level_override); } @@ -3518,10 +3518,9 @@ void Mob::TryTriggerOnCast(uint32 spell_id, bool aa_trigger) } } - void Mob::TriggerOnCast(uint32 focus_spell, uint32 spell_id, bool aa_trigger) { - if(!IsValidSpell(focus_spell) || !IsValidSpell(spell_id)) + if (!IsValidSpell(focus_spell) || !IsValidSpell(spell_id)) return; uint32 trigger_spell_id = 0; @@ -3532,15 +3531,17 @@ void Mob::TriggerOnCast(uint32 focus_spell, uint32 spell_id, bool aa_trigger) if (rank) trigger_spell_id = CastToClient()->CalcAAFocus(focusTriggerOnCast, *rank, spell_id); - if(IsValidSpell(trigger_spell_id) && GetTarget()) - SpellFinished(trigger_spell_id, GetTarget(), 10, 0, -1, spells[trigger_spell_id].ResistDiff); + if (IsValidSpell(trigger_spell_id) && GetTarget()) + SpellFinished(trigger_spell_id, GetTarget(), EQEmu::CastingSlot::Item, 0, -1, + spells[trigger_spell_id].ResistDiff); } - else{ + else { trigger_spell_id = CalcFocusEffect(focusTriggerOnCast, focus_spell, spell_id); - if(IsValidSpell(trigger_spell_id) && GetTarget()){ - SpellFinished(trigger_spell_id, GetTarget(),10, 0, -1, spells[trigger_spell_id].ResistDiff); + if (IsValidSpell(trigger_spell_id) && GetTarget()) { + SpellFinished(trigger_spell_id, GetTarget(), EQEmu::CastingSlot::Item, 0, -1, + spells[trigger_spell_id].ResistDiff); CheckNumHitsRemaining(NumHit::MatchingSpells, -1, focus_spell); } } @@ -3570,7 +3571,7 @@ bool Mob::TrySpellTrigger(Mob *target, uint32 spell_id, int effect) { // If we trigger an effect then its over. if (IsValidSpell(spells[spell_id].base2[i])){ - SpellFinished(spells[spell_id].base2[i], target, 10, 0, -1, spells[spells[spell_id].base2[i]].ResistDiff); + SpellFinished(spells[spell_id].base2[i], target, EQEmu::CastingSlot::Item, 0, -1, spells[spells[spell_id].base2[i]].ResistDiff); return true; } } @@ -3589,7 +3590,7 @@ bool Mob::TrySpellTrigger(Mob *target, uint32 spell_id, int effect) if(zone->random.Int(0, 100) <= spells[spell_id].base[effect]) { if (IsValidSpell(spells[spell_id].base2[effect])){ - SpellFinished(spells[spell_id].base2[effect], target, 10, 0, -1, spells[spells[spell_id].base2[effect]].ResistDiff); + SpellFinished(spells[spell_id].base2[effect], target, EQEmu::CastingSlot::Item, 0, -1, spells[spells[spell_id].base2[effect]].ResistDiff); return true; //Only trigger once of these per spell effect. } } @@ -3666,7 +3667,7 @@ void Mob::TryTriggerOnValueAmount(bool IsHP, bool IsMana, bool IsEndur, bool IsP } if (use_spell){ - SpellFinished(spells[spell_id].base[i], this, 10, 0, -1, spells[spell_id].ResistDiff); + SpellFinished(spells[spell_id].base[i], this, EQEmu::CastingSlot::Item, 0, -1, spells[spell_id].ResistDiff); if(!TryFadeEffect(e)) BuffFadeBySlot(e); @@ -3694,7 +3695,7 @@ void Mob::TryTwincast(Mob *caster, Mob *target, uint32 spell_id) if(zone->random.Roll(focus)) { Message(MT_Spells,"You twincast %s!",spells[spell_id].name); - SpellFinished(spell_id, target, 10, 0, -1, spells[spell_id].ResistDiff); + SpellFinished(spell_id, target, EQEmu::CastingSlot::Item, 0, -1, spells[spell_id].ResistDiff); } } } @@ -3712,7 +3713,7 @@ void Mob::TryTwincast(Mob *caster, Mob *target, uint32 spell_id) { if(zone->random.Roll(focus)) { - SpellFinished(spell_id, target, 10, 0, -1, spells[spell_id].ResistDiff); + SpellFinished(spell_id, target, EQEmu::CastingSlot::Item, 0, -1, spells[spell_id].ResistDiff); } } } @@ -3831,10 +3832,10 @@ bool Mob::TryFadeEffect(int slot) if(IsValidSpell(spell_id)) { if (IsBeneficialSpell(spell_id)) { - SpellFinished(spell_id, this, 10, 0, -1, spells[spell_id].ResistDiff); + SpellFinished(spell_id, this, EQEmu::CastingSlot::Item, 0, -1, spells[spell_id].ResistDiff); } else if(!(IsClient() && CastToClient()->dead)) { - SpellFinished(spell_id, this, 10, 0, -1, spells[spell_id].ResistDiff); + SpellFinished(spell_id, this, EQEmu::CastingSlot::Item, 0, -1, spells[spell_id].ResistDiff); } return true; } @@ -3868,7 +3869,7 @@ void Mob::TrySympatheticProc(Mob *target, uint32 spell_id) SpellFinished(focus_trigger, target); else - SpellFinished(focus_trigger, this, 10, 0, -1, spells[focus_trigger].ResistDiff); + SpellFinished(focus_trigger, this, EQEmu::CastingSlot::Item, 0, -1, spells[focus_trigger].ResistDiff); } // For detrimental spells, if the triggered spell is beneficial, then it will land on the caster // if the triggered spell is also detrimental, then it will land on the target @@ -3878,7 +3879,7 @@ void Mob::TrySympatheticProc(Mob *target, uint32 spell_id) SpellFinished(focus_trigger, this); else - SpellFinished(focus_trigger, target, 10, 0, -1, spells[focus_trigger].ResistDiff); + SpellFinished(focus_trigger, target, EQEmu::CastingSlot::Item, 0, -1, spells[focus_trigger].ResistDiff); } CheckNumHitsRemaining(NumHit::MatchingSpells, -1, focus_spell); @@ -4529,7 +4530,7 @@ void Mob::TrySpellOnKill(uint8 level, uint16 spell_id) if (IsValidSpell(spells[spell_id].base2[i]) && spells[spell_id].max[i] <= level) { if(zone->random.Roll(spells[spell_id].base[i])) - SpellFinished(spells[spell_id].base2[i], this, 10, 0, -1, spells[spells[spell_id].base2[i]].ResistDiff); + SpellFinished(spells[spell_id].base2[i], this, EQEmu::CastingSlot::Item, 0, -1, spells[spells[spell_id].base2[i]].ResistDiff); } } } @@ -4544,17 +4545,17 @@ void Mob::TrySpellOnKill(uint8 level, uint16 spell_id) if(aabonuses.SpellOnKill[i] && IsValidSpell(aabonuses.SpellOnKill[i]) && (level >= aabonuses.SpellOnKill[i + 2])) { if(zone->random.Roll(static_cast(aabonuses.SpellOnKill[i + 1]))) - SpellFinished(aabonuses.SpellOnKill[i], this, 10, 0, -1, spells[aabonuses.SpellOnKill[i]].ResistDiff); + SpellFinished(aabonuses.SpellOnKill[i], this, EQEmu::CastingSlot::Item, 0, -1, spells[aabonuses.SpellOnKill[i]].ResistDiff); } if(itembonuses.SpellOnKill[i] && IsValidSpell(itembonuses.SpellOnKill[i]) && (level >= itembonuses.SpellOnKill[i + 2])){ if(zone->random.Roll(static_cast(itembonuses.SpellOnKill[i + 1]))) - SpellFinished(itembonuses.SpellOnKill[i], this, 10, 0, -1, spells[aabonuses.SpellOnKill[i]].ResistDiff); + SpellFinished(itembonuses.SpellOnKill[i], this, EQEmu::CastingSlot::Item, 0, -1, spells[aabonuses.SpellOnKill[i]].ResistDiff); } if(spellbonuses.SpellOnKill[i] && IsValidSpell(spellbonuses.SpellOnKill[i]) && (level >= spellbonuses.SpellOnKill[i + 2])) { if(zone->random.Roll(static_cast(spellbonuses.SpellOnKill[i + 1]))) - SpellFinished(spellbonuses.SpellOnKill[i], this, 10, 0, -1, spells[aabonuses.SpellOnKill[i]].ResistDiff); + SpellFinished(spellbonuses.SpellOnKill[i], this, EQEmu::CastingSlot::Item, 0, -1, spells[aabonuses.SpellOnKill[i]].ResistDiff); } } @@ -4571,19 +4572,19 @@ bool Mob::TrySpellOnDeath() for(int i = 0; i < MAX_SPELL_TRIGGER*2; i+=2) { if(IsClient() && aabonuses.SpellOnDeath[i] && IsValidSpell(aabonuses.SpellOnDeath[i])) { if(zone->random.Roll(static_cast(aabonuses.SpellOnDeath[i + 1]))) { - SpellFinished(aabonuses.SpellOnDeath[i], this, 10, 0, -1, spells[aabonuses.SpellOnDeath[i]].ResistDiff); + SpellFinished(aabonuses.SpellOnDeath[i], this, EQEmu::CastingSlot::Item, 0, -1, spells[aabonuses.SpellOnDeath[i]].ResistDiff); } } if(itembonuses.SpellOnDeath[i] && IsValidSpell(itembonuses.SpellOnDeath[i])) { if(zone->random.Roll(static_cast(itembonuses.SpellOnDeath[i + 1]))) { - SpellFinished(itembonuses.SpellOnDeath[i], this, 10, 0, -1, spells[itembonuses.SpellOnDeath[i]].ResistDiff); + SpellFinished(itembonuses.SpellOnDeath[i], this, EQEmu::CastingSlot::Item, 0, -1, spells[itembonuses.SpellOnDeath[i]].ResistDiff); } } if(spellbonuses.SpellOnDeath[i] && IsValidSpell(spellbonuses.SpellOnDeath[i])) { if(zone->random.Roll(static_cast(spellbonuses.SpellOnDeath[i + 1]))) { - SpellFinished(spellbonuses.SpellOnDeath[i], this, 10, 0, -1, spells[spellbonuses.SpellOnDeath[i]].ResistDiff); + SpellFinished(spellbonuses.SpellOnDeath[i], this, EQEmu::CastingSlot::Item, 0, -1, spells[spellbonuses.SpellOnDeath[i]].ResistDiff); } } } diff --git a/zone/mob.h b/zone/mob.h index 3106df4ff..365cf672f 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -26,6 +26,7 @@ #include "aa_ability.h" #include "aa.h" #include "../common/light_source.h" +#include "../common/emu_constants.h" #include #include #include @@ -218,7 +219,7 @@ public: //Song bool UseBardSpellLogic(uint16 spell_id = 0xffff, int slot = -1); - bool ApplyNextBardPulse(uint16 spell_id, Mob *spell_target, uint16 slot); + bool ApplyNextBardPulse(uint16 spell_id, Mob *spell_target, EQEmu::CastingSlot slot); void BardPulse(uint16 spell_id, Mob *caster); //Spell @@ -248,23 +249,23 @@ public: void SendSpellBarEnable(uint16 spellid); void ZeroCastingVars(); virtual void SpellProcess(); - virtual bool CastSpell(uint16 spell_id, uint16 target_id, uint16 slot = USE_ITEM_SPELL_SLOT, int32 casttime = -1, + virtual bool CastSpell(uint16 spell_id, uint16 target_id, EQEmu::CastingSlot slot = EQEmu::CastingSlot::Item, int32 casttime = -1, int32 mana_cost = -1, uint32* oSpellWillFinish = 0, uint32 item_slot = 0xFFFFFFFF, uint32 timer = 0xFFFFFFFF, uint32 timer_duration = 0, int16 *resist_adjust = nullptr, uint32 aa_id = 0); - virtual bool DoCastSpell(uint16 spell_id, uint16 target_id, uint16 slot = 10, int32 casttime = -1, + virtual bool DoCastSpell(uint16 spell_id, uint16 target_id, EQEmu::CastingSlot slot = EQEmu::CastingSlot::Item, int32 casttime = -1, int32 mana_cost = -1, uint32* oSpellWillFinish = 0, uint32 item_slot = 0xFFFFFFFF, uint32 timer = 0xFFFFFFFF, uint32 timer_duration = 0, int16 resist_adjust = 0, uint32 aa_id = 0); - void CastedSpellFinished(uint16 spell_id, uint32 target_id, uint16 slot, uint16 mana_used, + void CastedSpellFinished(uint16 spell_id, uint32 target_id, EQEmu::CastingSlot slot, uint16 mana_used, uint32 inventory_slot = 0xFFFFFFFF, int16 resist_adjust = 0); - bool SpellFinished(uint16 spell_id, Mob *target, uint16 slot = 10, uint16 mana_used = 0, + bool SpellFinished(uint16 spell_id, Mob *target, EQEmu::CastingSlot slot = EQEmu::CastingSlot::Item, uint16 mana_used = 0, uint32 inventory_slot = 0xFFFFFFFF, int16 resist_adjust = 0, bool isproc = false, int level_override = -1); virtual bool SpellOnTarget(uint16 spell_id, Mob* spelltar, bool reflect = false, bool use_resist_adjust = false, int16 resist_adjust = 0, bool isproc = false, int level_override = -1); virtual bool SpellEffect(Mob* caster, uint16 spell_id, float partial = 100, int level_override = -1); virtual bool DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_center, - CastAction_type &CastAction, uint16 slot); + CastAction_type &CastAction, EQEmu::CastingSlot slot); virtual bool CheckFizzle(uint16 spell_id); virtual bool CheckSpellLevelRestriction(uint16 spell_id); virtual bool IsImmuneToSpell(uint16 spell_id, Mob *caster); @@ -1215,7 +1216,7 @@ protected: int attacked_count; bool delaytimer; uint16 casting_spell_targetid; - uint16 casting_spell_slot; + EQEmu::CastingSlot casting_spell_slot; uint16 casting_spell_mana; uint32 casting_spell_inventory_slot; uint32 casting_spell_timer; @@ -1225,7 +1226,7 @@ protected: uint32 casting_spell_aa_id; bool casting_spell_checks; uint16 bardsong; - uint8 bardsong_slot; + EQEmu::CastingSlot bardsong_slot; uint32 bardsong_target_id; bool ActiveProjectileATK; diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index ef7d222dd..eb795f6c3 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -341,7 +341,7 @@ bool NPC::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain SetCurrentSpeed(0); } - return CastSpell(AIspells[i].spellid, tar->GetID(), 1, AIspells[i].manacost == -2 ? 0 : -1, mana_cost, oDontDoAgainBefore, -1, -1, 0, &(AIspells[i].resist_adjust)); + return CastSpell(AIspells[i].spellid, tar->GetID(), EQEmu::CastingSlot::Gem2, AIspells[i].manacost == -2 ? 0 : -1, mana_cost, oDontDoAgainBefore, -1, -1, 0, &(AIspells[i].resist_adjust)); } bool EntityList::AICheckCloseBeneficialSpells(NPC* caster, uint8 iChance, float iRange, uint16 iSpellTypes) { @@ -673,11 +673,11 @@ void Client::AI_SpellCast() } uint32 spell_to_cast = 0xFFFFFFFF; - uint32 slot_to_use = 10; + EQEmu::CastingSlot slot_to_use = EQEmu::CastingSlot::Item; if(valid_spells.size() == 1) { spell_to_cast = valid_spells[0]; - slot_to_use = slots[0]; + slot_to_use = static_cast(slots[0]); } else if(valid_spells.empty()) { @@ -687,7 +687,7 @@ void Client::AI_SpellCast() { uint32 idx = zone->random.Int(0, (valid_spells.size()-1)); spell_to_cast = valid_spells[idx]; - slot_to_use = slots[idx]; + slot_to_use = static_cast(slots[idx]); } if(IsMezSpell(spell_to_cast) || IsFearSpell(spell_to_cast)) diff --git a/zone/perl_mob.cpp b/zone/perl_mob.cpp index 4a6fab4e4..f171d8d79 100644 --- a/zone/perl_mob.cpp +++ b/zone/perl_mob.cpp @@ -3982,12 +3982,12 @@ XS(XS_Mob_CastSpell) { dXSARGS; if (items < 3 || items > 7) - Perl_croak(aTHX_ "Usage: Mob::CastSpell(THIS, spell_id, target_id, slot= 10, casttime= -1, mana_cost= -1, resist_adjust = 0)"); + Perl_croak(aTHX_ "Usage: Mob::CastSpell(THIS, spell_id, target_id, slot= 22, casttime= -1, mana_cost= -1, resist_adjust = 0)"); { Mob * THIS; uint16 spell_id = (uint16)SvUV(ST(1)); uint16 target_id = (uint16)SvUV(ST(2)); - uint16 slot; + EQEmu::CastingSlot slot; int32 casttime; int32 mana_cost; int16 resist_adjust; @@ -4002,9 +4002,9 @@ XS(XS_Mob_CastSpell) Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); if (items < 4) - slot = 10; + slot = EQEmu::CastingSlot::Item; else { - slot = (uint16)SvUV(ST(3)); + slot = static_cast(SvUV(ST(3))); } if (items < 5) @@ -4085,7 +4085,7 @@ XS(XS_Mob_SpellFinished) resist_diff = spells[spell_id].ResistDiff; } - THIS->SpellFinished(spell_id, spell_target, 10, mana_cost, -1, resist_diff); + THIS->SpellFinished(spell_id, spell_target, EQEmu::CastingSlot::Item, mana_cost, -1, resist_diff); } XSRETURN_EMPTY; } diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index fdd9c3df2..78fc2b206 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -365,14 +365,14 @@ void QuestManager::castspell(int spell_id, int target_id) { if (owner) { Mob *tgt = entity_list.GetMob(target_id); if(tgt != nullptr) - owner->SpellFinished(spell_id, tgt, 10, 0, -1, spells[spell_id].ResistDiff); + owner->SpellFinished(spell_id, tgt, EQEmu::CastingSlot::Item, 0, -1, spells[spell_id].ResistDiff); } } void QuestManager::selfcast(int spell_id) { QuestManagerCurrentQuestVars(); if (initiator) - initiator->SpellFinished(spell_id, initiator, 10, 0, -1, spells[spell_id].ResistDiff); + initiator->SpellFinished(spell_id, initiator, EQEmu::CastingSlot::Item, 0, -1, spells[spell_id].ResistDiff); } void QuestManager::addloot(int item_id, int charges, bool equipitem) { diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 8b4cf0af8..41fb18ef2 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -149,7 +149,7 @@ void Mob::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 IsValidSpell(aabonuses.SkillAttackProc[2])) { float chance = aabonuses.SkillAttackProc[0] / 1000.0f; if (zone->random.Roll(chance)) - SpellFinished(aabonuses.SkillAttackProc[2], who, 10, 0, -1, + SpellFinished(aabonuses.SkillAttackProc[2], who, EQEmu::CastingSlot::Item, 0, -1, spells[aabonuses.SkillAttackProc[2]].ResistDiff); } who->Damage(this, max_damage, SPELL_UNKNOWN, skill, false); @@ -2429,7 +2429,7 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills: IsValidSpell(aabonuses.SkillAttackProc[2])) { float chance = aabonuses.SkillAttackProc[0] / 1000.0f; if (zone->random.Roll(chance)) - SpellFinished(aabonuses.SkillAttackProc[2], other, 10, 0, -1, + SpellFinished(aabonuses.SkillAttackProc[2], other, EQEmu::CastingSlot::Item, 0, -1, spells[aabonuses.SkillAttackProc[2]].ResistDiff); } other->Damage(this, damage, SPELL_UNKNOWN, skillinuse); diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 0209ba4df..4c5b805ad 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -2745,7 +2745,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if (caster && IsValidSpell(spells[spell_id].base2[i])){ if(zone->random.Roll(spells[spell_id].base[i])) - caster->SpellFinished(spells[spell_id].base2[i], this, 10, 0, -1, spells[spells[spell_id].base2[i]].ResistDiff); + caster->SpellFinished(spells[spell_id].base2[i], this, EQEmu::CastingSlot::Item, 0, -1, spells[spells[spell_id].base2[i]].ResistDiff); } break; } @@ -5135,7 +5135,7 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo if (Caston_spell_id) { if (IsValidSpell(Caston_spell_id) && (Caston_spell_id != spell_id)) - SpellFinished(Caston_spell_id, this, 10, 0, -1, spells[Caston_spell_id].ResistDiff); + SpellFinished(Caston_spell_id, this, EQEmu::CastingSlot::Item, 0, -1, spells[Caston_spell_id].ResistDiff); } return (value * lvlModifier / 100); @@ -6683,10 +6683,10 @@ void Mob::TryTriggerThreshHold(int32 damage, int effect_id, Mob* attacker){ if (IsValidSpell(spell_id)) { if (IsBeneficialSpell(spell_id)) - SpellFinished(spell_id, this, 10, 0, -1, spells[spell_id].ResistDiff); + SpellFinished(spell_id, this, EQEmu::CastingSlot::Item, 0, -1, spells[spell_id].ResistDiff); else if(attacker) - SpellFinished(spell_id, attacker, 10, 0, -1, spells[spell_id].ResistDiff); + SpellFinished(spell_id, attacker, EQEmu::CastingSlot::Item, 0, -1, spells[spell_id].ResistDiff); } } } diff --git a/zone/spells.cpp b/zone/spells.cpp index b1565be7f..6ae4d3920 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -104,6 +104,8 @@ extern Zone* zone; extern volatile bool is_zone_loaded; extern WorldServer worldserver; +using EQEmu::CastingSlot; + // this is run constantly for every mob void Mob::SpellProcess() { @@ -145,13 +147,13 @@ void NPC::SpellProcess() // the rule is you can cast one triggered (usually timed) spell at a time // but things like SpellFinished() can run concurrent with a triggered cast // to allow procs to work -bool Mob::CastSpell(uint16 spell_id, uint16 target_id, uint16 slot, +bool Mob::CastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, int32 cast_time, int32 mana_cost, uint32* oSpellWillFinish, uint32 item_slot, uint32 timer, uint32 timer_duration, int16 *resist_adjust, uint32 aa_id) { Log.Out(Logs::Detail, Logs::Spells, "CastSpell called for spell %s (%d) on entity %d, slot %d, time %d, mana %d, from item slot %d", - (IsValidSpell(spell_id))?spells[spell_id].name:"UNKNOWN SPELL", spell_id, target_id, slot, cast_time, mana_cost, (item_slot==0xFFFFFFFF)?999:item_slot); + (IsValidSpell(spell_id))?spells[spell_id].name:"UNKNOWN SPELL", spell_id, target_id, static_cast(slot), cast_time, mana_cost, (item_slot==0xFFFFFFFF)?999:item_slot); if(casting_spell_id == spell_id) ZeroCastingVars(); @@ -178,7 +180,7 @@ bool Mob::CastSpell(uint16 spell_id, uint16 target_id, uint16 slot, if(IsClient()) CastToClient()->SendSpellBarEnable(spell_id); if(casting_spell_id && IsNPC()) - CastToNPC()->AI_Event_SpellCastFinished(false, casting_spell_slot); + CastToNPC()->AI_Event_SpellCastFinished(false, static_cast(casting_spell_slot)); return(false); } //It appears that the Sanctuary effect is removed by a check on the client side (keep this however for redundancy) @@ -201,7 +203,7 @@ bool Mob::CastSpell(uint16 spell_id, uint16 target_id, uint16 slot, if(IsClient()) CastToClient()->SendSpellBarEnable(spell_id); if(casting_spell_id && IsNPC()) - CastToNPC()->AI_Event_SpellCastFinished(false, casting_spell_slot); + CastToNPC()->AI_Event_SpellCastFinished(false, static_cast(casting_spell_slot)); return(false); } @@ -231,7 +233,7 @@ bool Mob::CastSpell(uint16 spell_id, uint16 target_id, uint16 slot, // check for fizzle // note that CheckFizzle itself doesn't let NPCs fizzle, // but this code allows for it. - if(slot < MAX_PP_MEMSPELL && !CheckFizzle(spell_id)) + if(slot < CastingSlot::MaxGems && !CheckFizzle(spell_id)) { int fizzle_msg = IsBardSong(spell_id) ? MISS_NOTE : SPELL_FIZZLE; InterruptSpell(fizzle_msg, 0x121, spell_id); @@ -252,7 +254,7 @@ bool Mob::CastSpell(uint16 spell_id, uint16 target_id, uint16 slot, } //Added to prevent MQ2 exploitation of equipping normally-unequippable/clickable items with effects and clicking them for benefits. - if(item_slot && IsClient() && ((slot == USE_ITEM_SPELL_SLOT) || (slot == POTION_BELT_SPELL_SLOT) || (slot == TARGET_RING_SPELL_SLOT))) + if(item_slot && IsClient() && (slot == CastingSlot::Item || slot == CastingSlot::PotionBelt)) { ItemInst *itm = CastToClient()->GetInv().GetItem(item_slot); int bitmask = 1; @@ -336,7 +338,7 @@ bool Mob::CastSpell(uint16 spell_id, uint16 target_id, uint16 slot, // this is the 2nd phase of CastSpell, broken up like this to make it easier // to repeat a spell for bard songs // -bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, uint16 slot, +bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, int32 cast_time, int32 mana_cost, uint32* oSpellWillFinish, uint32 item_slot, uint32 timer, uint32 timer_duration, int16 resist_adjust, uint32 aa_id) @@ -353,7 +355,7 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, uint16 slot, const SPDat_Spell_Struct &spell = spells[spell_id]; Log.Out(Logs::Detail, Logs::Spells, "DoCastSpell called for spell %s (%d) on entity %d, slot %d, time %d, mana %d, from item %d", - spell.name, spell_id, target_id, slot, cast_time, mana_cost, item_slot==0xFFFFFFFF?999:item_slot); + spell.name, spell_id, target_id, static_cast(slot), cast_time, mana_cost, item_slot==0xFFFFFFFF?999:item_slot); casting_spell_id = spell_id; casting_spell_slot = slot; @@ -418,7 +420,7 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, uint16 slot, // If you're at full mana, let it cast even if you dont have enough mana // we calculated this above, now enforce it - if(mana_cost > 0 && slot != USE_ITEM_SPELL_SLOT) + if(mana_cost > 0 && slot != CastingSlot::Item) { int my_curmana = GetMana(); int my_maxmana = GetMaxMana(); @@ -487,7 +489,7 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, uint16 slot, safe_delete(outapp); outapp = nullptr; - if (IsClient() && slot == USE_ITEM_SPELL_SLOT &&item_slot != 0xFFFFFFFF) { + if (IsClient() && slot == CastingSlot::Item && item_slot != 0xFFFFFFFF) { auto item = CastToClient()->GetInv().GetItem(item_slot); if (item && item->GetItem()) Message_StringID(MT_Spells, BEGINS_TO_GLOW, item->GetItem()->Name); @@ -767,7 +769,7 @@ void Mob::ZeroCastingVars() spellend_timer.Disable(); casting_spell_id = 0; casting_spell_targetid = 0; - casting_spell_slot = 0; + casting_spell_slot = CastingSlot::Gem1; casting_spell_mana = 0; casting_spell_inventory_slot = 0; casting_spell_timer = 0; @@ -802,7 +804,7 @@ void Mob::InterruptSpell(uint16 message, uint16 color, uint16 spellid) } if(casting_spell_id && IsNPC()) { - CastToNPC()->AI_Event_SpellCastFinished(false, casting_spell_slot); + CastToNPC()->AI_Event_SpellCastFinished(false, static_cast(casting_spell_slot)); } if(casting_spell_aa_id && IsClient()) { //Rest AA Timer on failed cast @@ -880,12 +882,12 @@ void Mob::InterruptSpell(uint16 message, uint16 color, uint16 spellid) // NOTE: do not put range checking, etc into this function. this should // just check timed spell specific things before passing off to SpellFinished // which figures out proper targets etc -void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, uint16 slot, +void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slot, uint16 mana_used, uint32 inventory_slot, int16 resist_adjust) { bool IsFromItem = false; - if(IsClient() && slot != USE_ITEM_SPELL_SLOT && slot != POTION_BELT_SPELL_SLOT && slot != TARGET_RING_SPELL_SLOT && spells[spell_id].recast_time > 1000) { // 10 is item + if(IsClient() && slot != CastingSlot::Item && slot != CastingSlot::Item && spells[spell_id].recast_time > 1000) { // 10 is item if(!CastToClient()->GetPTimers().Expired(&database, pTimerSpellStart + spell_id, false)) { //should we issue a message or send them a spell gem packet? Message_StringID(13, SPELL_RECAST); @@ -895,7 +897,7 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, uint16 slot, } } - if(IsClient() && ((slot == USE_ITEM_SPELL_SLOT) || (slot == POTION_BELT_SPELL_SLOT) || (slot == TARGET_RING_SPELL_SLOT))) + if(IsClient() && (slot == CastingSlot::Item || slot == CastingSlot::PotionBelt)) { IsFromItem = true; ItemInst *itm = CastToClient()->GetInv().GetItem(inventory_slot); @@ -1193,7 +1195,7 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, uint16 slot, int16 DeleteChargeFromSlot = -1; - if(IsClient() && ((slot == USE_ITEM_SPELL_SLOT) || (slot == POTION_BELT_SPELL_SLOT) || (slot == TARGET_RING_SPELL_SLOT)) + if(IsClient() && (slot == CastingSlot::Item || slot == CastingSlot::PotionBelt) && inventory_slot != 0xFFFFFFFF) // 10 is an item { bool fromaug = false; @@ -1307,7 +1309,7 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, uint16 slot, if(IsClient()) { this->CastToClient()->CheckSongSkillIncrease(spell_id); - this->CastToClient()->MemorizeSpell(slot, spell_id, memSpellSpellbar); + this->CastToClient()->MemorizeSpell(static_cast(slot), spell_id, memSpellSpellbar); } Log.Out(Logs::Detail, Logs::Spells, "Bard song %d should be started", spell_id); } @@ -1319,21 +1321,18 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, uint16 slot, SendSpellBarEnable(spell_id); // this causes the delayed refresh of the spell bar gems - c->MemorizeSpell(slot, spell_id, memSpellSpellbar); + c->MemorizeSpell(static_cast(slot), spell_id, memSpellSpellbar); // this tells the client that casting may happen again SetMana(GetMana()); // skills - if(slot < MAX_PP_MEMSPELL) - { - c->CheckIncreaseSkill(spells[spell_id].skill, nullptr); + c->CheckIncreaseSkill(spells[spell_id].skill, nullptr); - // increased chance of gaining channel skill if you regained concentration - c->CheckIncreaseSkill(EQEmu::skills::SkillChanneling, nullptr, regain_conc ? 5 : 0); + // increased chance of gaining channel skill if you regained concentration + c->CheckIncreaseSkill(EQEmu::skills::SkillChanneling, nullptr, regain_conc ? 5 : 0); - c->CheckSpecializeIncrease(spell_id); - } + c->CheckSpecializeIncrease(spell_id); } } @@ -1348,8 +1347,8 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, uint16 slot, } -bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_center, CastAction_type &CastAction, uint16 slot) { - +bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_center, CastAction_type &CastAction, CastingSlot slot) +{ /* The basic types of spells: @@ -1682,7 +1681,7 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce case ST_Group: case ST_GroupNoPets: { - if(IsClient() && CastToClient()->TGB() && IsTGBCompatibleSpell(spell_id) && slot != USE_ITEM_SPELL_SLOT) { + if(IsClient() && CastToClient()->TGB() && IsTGBCompatibleSpell(spell_id) && slot != CastingSlot::Item) { if( (!target) || (target->IsNPC() && !(target->GetOwner() && target->GetOwner()->IsClient())) || (target->IsCorpse()) ) @@ -1904,7 +1903,7 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce // only used from CastedSpellFinished, and procs // we can't interrupt in this, or anything called from this! // if you need to abort the casting, return false -bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16 mana_used, +bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, uint16 mana_used, uint32 inventory_slot, int16 resist_adjust, bool isproc, int level_override) { //EQApplicationPacket *outapp = nullptr; @@ -2263,7 +2262,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16 bool mgb = HasMGB() && spells[spell_id].can_mgb; // if this was a spell slot or an ability use up the mana for it - if(slot != USE_ITEM_SPELL_SLOT && slot != POTION_BELT_SPELL_SLOT && slot != TARGET_RING_SPELL_SLOT && mana_used > 0) + if(slot != CastingSlot::Item && slot != CastingSlot::PotionBelt && mana_used > 0) { mana_used = GetActSpellCost(spell_id, mana_used); if (mgb) { @@ -2326,7 +2325,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16 } } - if(IsClient() && ((slot == USE_ITEM_SPELL_SLOT) || (slot == POTION_BELT_SPELL_SLOT) || (slot == TARGET_RING_SPELL_SLOT))) + if(IsClient() && (slot == CastingSlot::Item || slot == CastingSlot::PotionBelt)) { ItemInst *itm = CastToClient()->GetInv().GetItem(inventory_slot); if(itm && itm->GetItem()->RecastDelay > 0){ @@ -2345,7 +2344,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16 } if(IsNPC()) - CastToNPC()->AI_Event_SpellCastFinished(true, slot); + CastToNPC()->AI_Event_SpellCastFinished(true, static_cast(slot)); return true; } @@ -2360,8 +2359,8 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16 * * return false to stop the song */ -bool Mob::ApplyNextBardPulse(uint16 spell_id, Mob *spell_target, uint16 slot) { - if(slot == USE_ITEM_SPELL_SLOT) { +bool Mob::ApplyNextBardPulse(uint16 spell_id, Mob *spell_target, CastingSlot slot) { + if(slot == CastingSlot::Item) { //bard songs should never come from items... Log.Out(Logs::Detail, Logs::Spells, "Bard Song Pulse %d: Supposidly cast from an item. Killing song.", spell_id); return(false); @@ -3769,7 +3768,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r } if (IsValidSpell(spells[spell_id].RecourseLink) && spells[spell_id].RecourseLink != spell_id) - SpellFinished(spells[spell_id].RecourseLink, this, 10, 0, -1, spells[spells[spell_id].RecourseLink].ResistDiff); + SpellFinished(spells[spell_id].RecourseLink, this, EQEmu::CastingSlot::Item, 0, -1, spells[spells[spell_id].RecourseLink].ResistDiff); if (IsDetrimentalSpell(spell_id)) { @@ -5245,7 +5244,7 @@ bool Mob::UseBardSpellLogic(uint16 spell_id, int slot) spell_id = casting_spell_id; if(slot == -1) - slot = casting_spell_slot; + slot = static_cast(casting_spell_slot); // should we treat this as a bard singing? return @@ -5273,7 +5272,7 @@ void Mob::_StopSong() { bardsong = 0; bardsong_target_id = 0; - bardsong_slot = 0; + bardsong_slot = CastingSlot::Gem1; bardsong_timer.Disable(); } diff --git a/zone/trap.cpp b/zone/trap.cpp index 5a644bb03..87000f940 100644 --- a/zone/trap.cpp +++ b/zone/trap.cpp @@ -115,7 +115,7 @@ void Trap::Trigger(Mob* trigger) entity_list.MessageClose(trigger,false,100,13,"%s",message.c_str()); } if(hiddenTrigger){ - hiddenTrigger->SpellFinished(effectvalue, trigger, 10, 0, -1, spells[effectvalue].ResistDiff); + hiddenTrigger->SpellFinished(effectvalue, trigger, EQEmu::CastingSlot::Item, 0, -1, spells[effectvalue].ResistDiff); } break; case trapTypeAlarm: From f612f8be429a9dfdf15965cd72a29d6186b95927 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sun, 31 Jul 2016 17:35:33 -0400 Subject: [PATCH 02/78] Fix typo --- zone/spells.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zone/spells.cpp b/zone/spells.cpp index 6ae4d3920..e605bfca8 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -887,7 +887,7 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo { bool IsFromItem = false; - if(IsClient() && slot != CastingSlot::Item && slot != CastingSlot::Item && spells[spell_id].recast_time > 1000) { // 10 is item + if(IsClient() && slot != CastingSlot::Item && slot != CastingSlot::PotionBelt && spells[spell_id].recast_time > 1000) { // 10 is item if(!CastToClient()->GetPTimers().Expired(&database, pTimerSpellStart + spell_id, false)) { //should we issue a message or send them a spell gem packet? Message_StringID(13, SPELL_RECAST); @@ -3768,7 +3768,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r } if (IsValidSpell(spells[spell_id].RecourseLink) && spells[spell_id].RecourseLink != spell_id) - SpellFinished(spells[spell_id].RecourseLink, this, EQEmu::CastingSlot::Item, 0, -1, spells[spells[spell_id].RecourseLink].ResistDiff); + SpellFinished(spells[spell_id].RecourseLink, this, CastingSlot::Item, 0, -1, spells[spells[spell_id].RecourseLink].ResistDiff); if (IsDetrimentalSpell(spell_id)) { From e862994716c14a7df14279525e109d192d9f93aa Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sun, 31 Jul 2016 18:09:04 -0400 Subject: [PATCH 03/78] RoF/RoF2 spell gem refresh in PP This fixes issues with long recast spells --- common/patches/rof.cpp | 7 ++++--- common/patches/rof2.cpp | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/common/patches/rof.cpp b/common/patches/rof.cpp index 014e6f8d9..b7cde6af8 100644 --- a/common/patches/rof.cpp +++ b/common/patches/rof.cpp @@ -2269,12 +2269,13 @@ namespace RoF outapp->WriteUInt32(0xFFFFFFFFU); } - outapp->WriteUInt32(13); // Unknown count + outapp->WriteUInt32(13); // gem refresh count - for (uint32 r = 0; r < 13; r++) + for (uint32 r = 0; r < MAX_PP_MEMSPELL; r++) { - outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(emu->spellSlotRefresh[r]); // spell gem refresh } + outapp->WriteUInt32(0); // also refresh -- historically HT/LoH :P outapp->WriteUInt8(0); // Unknown diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 79802e262..1699eefae 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -2356,12 +2356,13 @@ namespace RoF2 outapp->WriteUInt32(0xFFFFFFFFU); } - outapp->WriteUInt32(13); // Unknown count + outapp->WriteUInt32(13); // gem refresh counts - for (uint32 r = 0; r < 13; r++) + for (uint32 r = 0; r < MAX_PP_MEMSPELL; r++) { - outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(emu->spellSlotRefresh[r]); // spell gem refresh } + outapp->WriteUInt32(0); // also refresh -- historically HT/LoH :P outapp->WriteUInt8(0); // Unknown From 2bae779a9b72c9637a299a6ff7cbb780681cc5cf Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Tue, 2 Aug 2016 16:48:58 -0400 Subject: [PATCH 04/78] NPCs don't have separate buff windows --- zone/client.h | 2 ++ zone/mob.h | 2 ++ zone/spells.cpp | 42 ++++++++++++++++++++++++++++++------------ 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/zone/client.h b/zone/client.h index a17392518..32d0e176c 100644 --- a/zone/client.h +++ b/zone/client.h @@ -511,6 +511,8 @@ public: virtual int GetMaxSongSlots() const { return 12; } virtual int GetMaxDiscSlots() const { return 1; } virtual int GetMaxTotalSlots() const { return 38; } + virtual uint32 GetFirstBuffSlot(bool disc, bool song); + virtual uint32 GetLastBuffSlot(bool disc, bool song); virtual void InitializeBuffSlots(); virtual void UninitializeBuffSlots(); diff --git a/zone/mob.h b/zone/mob.h index 365cf672f..a01a4b8f5 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -307,6 +307,8 @@ public: virtual int GetMaxSongSlots() const { return 0; } virtual int GetMaxDiscSlots() const { return 0; } virtual int GetMaxTotalSlots() const { return 0; } + virtual uint32 GetFirstBuffSlot(bool disc, bool song); + virtual uint32 GetLastBuffSlot(bool disc, bool song); virtual void InitializeBuffSlots() { buffs = nullptr; current_buff_count = 0; } virtual void UninitializeBuffSlots() { } EQApplicationPacket *MakeBuffsPacket(bool for_target = true); diff --git a/zone/spells.cpp b/zone/spells.cpp index e605bfca8..38d30b53b 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -3046,6 +3046,34 @@ bool Client::CheckSpellLevelRestriction(uint16 spell_id) return true; } +uint32 Mob::GetFirstBuffSlot(bool disc, bool song) +{ + return 0; +} + +uint32 Mob::GetLastBuffSlot(bool disc, bool song) +{ + return GetCurrentBuffSlots(); +} + +uint32 Client::GetFirstBuffSlot(bool disc, bool song) +{ + if (disc) + return GetMaxBuffSlots() + GetMaxSongSlots(); + if (song) + return GetMaxBuffSlots(); + return 0; +} + +uint32 Client::GetLastBuffSlot(bool disc, bool song) +{ + if (disc) + return GetMaxBuffSlots() + GetMaxSongSlots() + GetCurrentDiscSlots(); + if (song) + return GetMaxBuffSlots() + GetCurrentSongSlots(); + return GetCurrentBuffSlots(); +} + // returns the slot the buff was added to, -1 if it wasn't added due to // stacking problems, and -2 if this is not a buff // if caster is null, the buff will be added with the caster level being @@ -3083,18 +3111,8 @@ int Mob::AddBuff(Mob *caster, uint16 spell_id, int duration, int32 level_overrid // we also check if overwriting will occur. this is so after this loop // we can determine if there will be room for this buff int buff_count = GetMaxTotalSlots(); - uint32 start_slot = 0; - uint32 end_slot = 0; - if (IsDisciplineBuff(spell_id)) { - start_slot = GetMaxBuffSlots() + GetMaxSongSlots(); - end_slot = start_slot + GetCurrentDiscSlots(); - } else if(spells[spell_id].short_buff_box) { - start_slot = GetMaxBuffSlots(); - end_slot = start_slot + GetCurrentSongSlots(); - } else { - start_slot = 0; - end_slot = GetCurrentBuffSlots(); - } + uint32 start_slot = GetFirstBuffSlot(IsDisciplineBuff(spell_id), spells[spell_id].short_buff_box); + uint32 end_slot = GetLastBuffSlot(IsDisciplineBuff(spell_id), spells[spell_id].short_buff_box); for (buffslot = 0; buffslot < buff_count; buffslot++) { const Buffs_Struct &curbuf = buffs[buffslot]; From 7c9bd80c1e5dc482ec1880dcc51f14dff1012ea5 Mon Sep 17 00:00:00 2001 From: Uleat Date: Tue, 2 Aug 2016 17:49:08 -0400 Subject: [PATCH 05/78] Changed SendZoneSpawnsBulk to a more like-like behavior --- changelog.txt | 6 ++++++ zone/entity.cpp | 29 +++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index 5221f170b..db7133b0b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,11 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 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%) + - World-to-Zone client loading appears to greatly benefit from this (tested 'devastation' - pre-change: ~22 seconds, post-change: 12~15 seconds) + - This change does not affect the final spawning of mobs in the client + == 07/31/2016 == mackal: Implement more spell gems! - There are a few things still left to due like make dealing with losing gems nice (reset AAs, going to an older client etc) diff --git a/zone/entity.cpp b/zone/entity.cpp index d398c9bf6..5da1190d0 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -1228,7 +1228,9 @@ void EntityList::SendZoneSpawnsBulk(Client *client) maxspawns = mob_list.size(); auto bzsp = new BulkZoneSpawnPacket(client, maxspawns); - int32 race=-1; + bool delaypkt = false; + const glm::vec4& cpos = client->GetPosition(); + const float dmax = 600.0 * 600.0; for (auto it = mob_list.begin(); it != mob_list.end(); ++it) { spawn = it->second; if (spawn && spawn->GetID() > 0 && spawn->Spawned()) { @@ -1236,8 +1238,30 @@ void EntityList::SendZoneSpawnsBulk(Client *client) spawn->CastToClient()->IsHoveringForRespawn())) continue; - race = spawn->GetRace(); +#if 1 + const glm::vec4& spos = spawn->GetPosition(); + + delaypkt = false; + if (DistanceSquared(cpos, spos) > dmax || (spawn->IsClient() && (spawn->GetRace() == MINOR_ILL_OBJ || spawn->GetRace() == TREE))) + delaypkt = true; + + if (delaypkt) { + app = new EQApplicationPacket; + spawn->CreateSpawnPacket(app); + client->QueuePacket(app, true, Client::CLIENT_CONNECTED); + safe_delete(app); + } + else { + memset(&ns, 0, sizeof(NewSpawn_Struct)); + spawn->FillSpawnStruct(&ns, client); + bzsp->AddSpawn(&ns); + } + spawn->SendArmorAppearance(client); +#else + /* original code kept for spawn packet research */ + int32 race = spawn->GetRace(); + // Illusion races on PCs don't work as a mass spawn // But they will work as an add_spawn AFTER CLIENT_CONNECTED. if (spawn->IsClient() && (race == MINOR_ILL_OBJ || race == TREE)) { @@ -1255,6 +1279,7 @@ void EntityList::SendZoneSpawnsBulk(Client *client) // Despite being sent in the OP_ZoneSpawns packet, the client // does not display worn armor correctly so display it. spawn->SendArmorAppearance(client); +#endif } } safe_delete(bzsp); From 64cf613189abe6ce195ae1db2ca9130b90695a06 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Tue, 2 Aug 2016 19:00:33 -0400 Subject: [PATCH 06/78] Update GetCurrentBuffSlots() for TSS free slots The client also checks if you have any bonus from spells and items so why not check that as well --- zone/spells.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/zone/spells.cpp b/zone/spells.cpp index 38d30b53b..fbda108f0 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -5466,10 +5466,14 @@ void Mob::BuffModifyDurationBySpellID(uint16 spell_id, int32 newDuration) int Client::GetCurrentBuffSlots() const { - if(15 + aabonuses.BuffSlotIncrease > 25) - return 25; - else - return 15 + aabonuses.BuffSlotIncrease; + int numbuffs = 15; + // client does check spells and items + numbuffs += aabonuses.BuffSlotIncrease + spellbonuses.BuffSlotIncrease + itembonuses.BuffSlotIncrease; + if (GetLevel() > 70) + numbuffs++; + if (GetLevel() > 74) + numbuffs++; + return EQEmu::ClampUpper(numbuffs, GetMaxBuffSlots()); } int Client::GetCurrentSongSlots() const From d68a3b191e7f8429e0db6d96207565b0e366da25 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Tue, 2 Aug 2016 21:08:05 -0400 Subject: [PATCH 07/78] Give pets 30 buff because that's what they got --- common/eq_packet_structs.h | 5 +++-- common/patches/rof.cpp | 6 +++--- common/patches/rof2.cpp | 6 +++--- common/patches/sod.cpp | 4 ++-- common/patches/sof.cpp | 4 ++-- common/patches/titanium.cpp | 4 ++-- common/patches/uf.cpp | 6 +++--- common/ruletypes.h | 2 +- zone/bot.cpp | 8 ++++---- zone/bot_database.cpp | 4 ++-- zone/spells.cpp | 4 ++-- zone/zonedb.cpp | 3 ++- zone/zonedb.h | 2 +- 13 files changed, 30 insertions(+), 28 deletions(-) diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index ab0447cb8..fc1c9f9e7 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -30,6 +30,7 @@ static const uint32 BUFF_COUNT = 25; +static const uint32 PET_BUFF_COUNT = 30; static const uint32 MAX_MERC = 100; static const uint32 MAX_MERC_GRADES = 10; static const uint32 MAX_MERC_STANCES = 10; @@ -522,8 +523,8 @@ struct BuffRemoveRequest_Struct struct PetBuff_Struct { /*000*/ uint32 petid; -/*004*/ uint32 spellid[BUFF_COUNT+5]; -/*124*/ int32 ticsremaining[BUFF_COUNT+5]; +/*004*/ uint32 spellid[PET_BUFF_COUNT]; +/*124*/ int32 ticsremaining[PET_BUFF_COUNT]; /*244*/ uint32 buffcount; }; diff --git a/common/patches/rof.cpp b/common/patches/rof.cpp index b7cde6af8..bee434ec3 100644 --- a/common/patches/rof.cpp +++ b/common/patches/rof.cpp @@ -2012,18 +2012,18 @@ namespace RoF __packet->WriteUInt8(1); // 1 indicates all buffs on the pet (0 to add or remove a single buff) __packet->WriteUInt16(emu->buffcount); - for (uint16 i = 0; i < BUFF_COUNT; ++i) + for (uint16 i = 0; i < PET_BUFF_COUNT; ++i) { if (emu->spellid[i]) { __packet->WriteUInt32(i); __packet->WriteUInt32(emu->spellid[i]); __packet->WriteUInt32(emu->ticsremaining[i]); - __packet->WriteUInt32(0); // Unknown + __packet->WriteUInt32(0); // numhits __packet->WriteString(""); } } - __packet->WriteUInt8(0); // Unknown + __packet->WriteUInt8(0); // some sort of type FINISH_ENCODE(); } diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 1699eefae..52fdeb02b 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -2099,18 +2099,18 @@ namespace RoF2 __packet->WriteUInt8(1); // 1 indicates all buffs on the pet (0 to add or remove a single buff) __packet->WriteUInt16(emu->buffcount); - for (uint16 i = 0; i < BUFF_COUNT; ++i) + for (uint16 i = 0; i < PET_BUFF_COUNT; ++i) { if (emu->spellid[i]) { __packet->WriteUInt32(i); __packet->WriteUInt32(emu->spellid[i]); __packet->WriteUInt32(emu->ticsremaining[i]); - __packet->WriteUInt32(0); // Unknown + __packet->WriteUInt32(0); // num hits __packet->WriteString(""); } } - __packet->WriteUInt8(0); // Unknown + __packet->WriteUInt8(0); // some sort of type FINISH_ENCODE(); } diff --git a/common/patches/sod.cpp b/common/patches/sod.cpp index 52bbdc309..4def89212 100644 --- a/common/patches/sod.cpp +++ b/common/patches/sod.cpp @@ -1502,7 +1502,7 @@ namespace SoD VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->petid); VARSTRUCT_ENCODE_TYPE(uint16, Buffer, emu->buffcount); - for (unsigned int i = 0; i < BUFF_COUNT; ++i) + for (unsigned int i = 0; i < PET_BUFF_COUNT; ++i) { if (emu->spellid[i]) { @@ -1513,7 +1513,7 @@ namespace SoD } } - VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->buffcount); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->buffcount); // I think this is actually some sort of type delete[] __emu_buffer; dest->FastQueuePacket(&in, ack_req); diff --git a/common/patches/sof.cpp b/common/patches/sof.cpp index 4f804bb03..a34e5029a 100644 --- a/common/patches/sof.cpp +++ b/common/patches/sof.cpp @@ -1177,9 +1177,9 @@ namespace SoF OUT(petid); OUT(buffcount); - int EQBuffSlot = 0; + int EQBuffSlot = 0; // do we really want to shuffle them around like this? - for (uint32 EmuBuffSlot = 0; EmuBuffSlot < BUFF_COUNT; ++EmuBuffSlot) + for (uint32 EmuBuffSlot = 0; EmuBuffSlot < PET_BUFF_COUNT; ++EmuBuffSlot) { if (emu->spellid[EmuBuffSlot]) { diff --git a/common/patches/titanium.cpp b/common/patches/titanium.cpp index 28984a26f..cc10f6487 100644 --- a/common/patches/titanium.cpp +++ b/common/patches/titanium.cpp @@ -891,9 +891,9 @@ namespace Titanium OUT(petid); OUT(buffcount); - int EQBuffSlot = 0; + int EQBuffSlot = 0; // do we really want to shuffle them around like this? - for (uint32 EmuBuffSlot = 0; EmuBuffSlot < BUFF_COUNT; ++EmuBuffSlot) + for (uint32 EmuBuffSlot = 0; EmuBuffSlot < PET_BUFF_COUNT; ++EmuBuffSlot) { if (emu->spellid[EmuBuffSlot]) { diff --git a/common/patches/uf.cpp b/common/patches/uf.cpp index 735243f09..3e03c8bb2 100644 --- a/common/patches/uf.cpp +++ b/common/patches/uf.cpp @@ -1764,18 +1764,18 @@ namespace UF VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 1); VARSTRUCT_ENCODE_TYPE(uint16, Buffer, emu->buffcount); - for (unsigned int i = 0; i < BUFF_COUNT; ++i) + for (unsigned int i = 0; i < PET_BUFF_COUNT; ++i) { if (emu->spellid[i]) { VARSTRUCT_ENCODE_TYPE(uint32, Buffer, i); VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->spellid[i]); VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->ticsremaining[i]); - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // numhits VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // This is a string. Name of the caster of the buff. } } - VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->buffcount); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->buffcount); /// I think this is actually some sort of type delete[] __emu_buffer; dest->FastQueuePacket(&in, ack_req); diff --git a/common/ruletypes.h b/common/ruletypes.h index 41dbeacd2..1a64b1ebd 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -329,7 +329,7 @@ RULE_INT(Spells, MaxBuffSlotsNPC, 25) RULE_INT(Spells, MaxSongSlotsNPC, 10) RULE_INT(Spells, MaxDiscSlotsNPC, 1) RULE_INT(Spells, MaxTotalSlotsNPC, 36) -RULE_INT(Spells, MaxTotalSlotsPET, 25) // do not set this higher than 25 until the player profile is removed from the blob +RULE_INT(Spells, MaxTotalSlotsPET, 30) // do not set this higher than 25 until the player profile is removed from the blob RULE_BOOL (Spells, EnableBlockedBuffs, true) RULE_INT(Spells, ReflectType, 1) //0 = disabled, 1 = single target player spells only, 2 = all player spells, 3 = all single target spells, 4 = all spells RULE_INT(Spells, VirusSpreadDistance, 30) // The distance a viral spell will jump to its next victim diff --git a/zone/bot.cpp b/zone/bot.cpp index e4c9f0038..14acc9b57 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -1709,8 +1709,8 @@ bool Bot::LoadPet() NPC *pet_inst = GetPet()->CastToNPC(); - SpellBuff_Struct pet_buffs[BUFF_COUNT]; - memset(pet_buffs, 0, (sizeof(SpellBuff_Struct) * BUFF_COUNT)); + SpellBuff_Struct pet_buffs[PET_BUFF_COUNT]; + memset(pet_buffs, 0, (sizeof(SpellBuff_Struct) * PET_BUFF_COUNT)); if (!botdb.LoadPetBuffs(GetBotID(), pet_buffs)) bot_owner->Message(13, "%s for %s's pet", BotDatabase::fail::LoadPetBuffs(), GetCleanName()); @@ -1741,11 +1741,11 @@ bool Bot::SavePet() return false; char* pet_name = new char[64]; - SpellBuff_Struct pet_buffs[BUFF_COUNT]; + SpellBuff_Struct pet_buffs[PET_BUFF_COUNT]; uint32 pet_items[EQEmu::legacy::EQUIPMENT_SIZE]; memset(pet_name, 0, 64); - memset(pet_buffs, 0, (sizeof(SpellBuff_Struct) * BUFF_COUNT)); + memset(pet_buffs, 0, (sizeof(SpellBuff_Struct) * PET_BUFF_COUNT)); memset(pet_items, 0, (sizeof(uint32) * EQEmu::legacy::EQUIPMENT_SIZE)); pet_inst->GetPetState(pet_buffs, pet_items, pet_name); diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp index 300f4c8b4..db9827a8a 100644 --- a/zone/bot_database.cpp +++ b/zone/bot_database.cpp @@ -1472,7 +1472,7 @@ bool BotDatabase::LoadPetBuffs(const uint32 bot_id, SpellBuff_Struct* pet_buffs) return true; int buff_index = 0; - for (auto row = results.begin(); row != results.end() && buff_index < BUFF_COUNT; ++row) { + for (auto row = results.begin(); row != results.end() && buff_index < PET_BUFF_COUNT; ++row) { pet_buffs[buff_index].spellid = atoi(row[0]); pet_buffs[buff_index].level = atoi(row[1]); pet_buffs[buff_index].duration = atoi(row[2]); @@ -1509,7 +1509,7 @@ bool BotDatabase::SavePetBuffs(const uint32 bot_id, const SpellBuff_Struct* pet_ if (!saved_pet_index) return true; - for (int buff_index = 0; buff_index < BUFF_COUNT; ++buff_index) { + for (int buff_index = 0; buff_index < PET_BUFF_COUNT; ++buff_index) { if (!pet_buffs[buff_index].spellid || pet_buffs[buff_index].spellid == SPELL_UNKNOWN) continue; diff --git a/zone/spells.cpp b/zone/spells.cpp index fbda108f0..60837381e 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -5368,8 +5368,8 @@ void Mob::SendPetBuffsToClient() int MaxSlots = GetMaxTotalSlots(); - if(MaxSlots > BUFF_COUNT) - MaxSlots = BUFF_COUNT; + if(MaxSlots > PET_BUFF_COUNT) + MaxSlots = PET_BUFF_COUNT; for(int buffslot = 0; buffslot < MaxSlots; buffslot++) { diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index d0c80f37f..7f86f5110 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -3178,7 +3178,8 @@ void ZoneDatabase::SavePetInfo(Client *client) query.clear(); // pet buffs! - for (int index = 0; index < RuleI(Spells, MaxTotalSlotsPET); index++) { + int max_slots = RuleI(Spells, MaxTotalSlotsPET); + for (int index = 0; index < max_slots; index++) { if (petinfo->Buffs[index].spellid == SPELL_UNKNOWN || petinfo->Buffs[index].spellid == 0) continue; if (query.length() == 0) diff --git a/zone/zonedb.h b/zone/zonedb.h index ef5a6810d..689f9a82f 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -125,7 +125,7 @@ struct PetInfo { uint32 HP; uint32 Mana; float size; - SpellBuff_Struct Buffs[BUFF_COUNT]; + SpellBuff_Struct Buffs[PET_BUFF_COUNT]; uint32 Items[EQEmu::legacy::EQUIPMENT_SIZE]; char Name[64]; }; From 37ecc690882f19da4765910a68521fe80a2cd22f Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Tue, 2 Aug 2016 21:10:32 -0400 Subject: [PATCH 08/78] Update UF packet stuff to have 30 BUFF_COUNT --- common/patches/uf.cpp | 2 +- common/patches/uf_structs.h | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/common/patches/uf.cpp b/common/patches/uf.cpp index 3e03c8bb2..1ff6c8e82 100644 --- a/common/patches/uf.cpp +++ b/common/patches/uf.cpp @@ -1870,7 +1870,7 @@ namespace UF OUT(thirst_level); OUT(hunger_level); //PS this needs to be figured out more; but it was 'good enough' - for (r = 0; r < structs::BUFF_COUNT; r++) + for (r = 0; r < BUFF_COUNT; r++) { if (emu->buffs[r].spellid != 0xFFFF && emu->buffs[r].spellid != 0) { diff --git a/common/patches/uf_structs.h b/common/patches/uf_structs.h index 7ef766d86..3c582e6e6 100644 --- a/common/patches/uf_structs.h +++ b/common/patches/uf_structs.h @@ -26,7 +26,7 @@ namespace UF namespace structs { -static const uint32 BUFF_COUNT = 25; +static const uint32 BUFF_COUNT = 30; /* ** Compiler override to ensure @@ -958,8 +958,7 @@ struct PlayerProfile_Struct /*07880*/ uint32 toxicity; // Potion Toxicity (15=too toxic, each potion adds 3) /*07884*/ uint32 thirst_level; // Drink (ticks till next drink) /*07888*/ uint32 hunger_level; // Food (ticks till next eat) -/*07892*/ SpellBuff_Struct buffs[BUFF_COUNT]; // [1900] Buffs currently on the player (30 Max) - (Each Size 76) -/*09792*/ uint8 unknown09792[380]; // BUFF_COUNT has been left at 25. These are the extra 5 buffs in Underfoot +/*07892*/ SpellBuff_Struct buffs[BUFF_COUNT]; // [2280] Buffs currently on the player (30 Max) - (Each Size 76) /*10172*/ Disciplines_Struct disciplines; // [400] Known disciplines /*10972*/ uint32 recastTimers[MAX_RECAST_TYPES]; // Timers (UNIX Time of last use) /*11052*/ uint8 unknown11052[160]; // Some type of Timers @@ -2167,9 +2166,7 @@ struct GroupFollow_Struct { // Underfoot Follow Struct struct InspectBuffs_Struct { /*000*/ uint32 spell_id[BUFF_COUNT]; -/*100*/ uint32 filler100[5]; // BUFF_COUNT is really 30... /*120*/ int32 tics_remaining[BUFF_COUNT]; -/*220*/ uint32 filler220[5]; // BUFF_COUNT is really 30... }; From 69db67efe581d690057c5895ac52bbcd524ee67d Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Tue, 2 Aug 2016 23:32:37 -0400 Subject: [PATCH 09/78] Remove extra save --- zone/mob.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/zone/mob.cpp b/zone/mob.cpp index 63737617e..e056a81c2 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -2357,7 +2357,6 @@ void Mob::SetZone(uint32 zone_id, uint32 instance_id) { CastToClient()->GetPP().zone_id = zone_id; CastToClient()->GetPP().zoneInstance = instance_id; - CastToClient()->Save(); } Save(); } From 16125c38af323b5c3df347298e12fefe5003bd2d Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Wed, 3 Aug 2016 00:13:47 -0400 Subject: [PATCH 10/78] Lets not save here if we're zoning --- zone/client_process.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 354d5b6aa..06054e620 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -624,7 +624,7 @@ bool Client::Process() { { //client logged out or errored out //ResetTrade(); - if (client_state != CLIENT_KICKED) { + if (client_state != CLIENT_KICKED && !zoning) { Save(); } From acb5bb3e3ed160bde02aa40449254ab15196750b Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Wed, 3 Aug 2016 00:33:22 -0400 Subject: [PATCH 11/78] If this flag is set, we JUST saved Also saved due to an OP_Save very recently ... --- zone/client_process.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 06054e620..e1a769743 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -624,7 +624,7 @@ bool Client::Process() { { //client logged out or errored out //ResetTrade(); - if (client_state != CLIENT_KICKED && !zoning) { + if (client_state != CLIENT_KICKED && !zoning && !instalog) { Save(); } From f26dce39c33c1eac53a453f3629fa98f1b101473 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Wed, 3 Aug 2016 01:13:51 -0400 Subject: [PATCH 12/78] Rate limit saving to at most once a second There are A LOT of unneeded saves ... This will prevent a lot of excessive database hits at least with very little room to exploit --- zone/client.cpp | 8 +++++++- zone/client.h | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/zone/client.cpp b/zone/client.cpp index bdc89e3b9..7cfdf512f 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -155,7 +155,8 @@ Client::Client(EQStreamInterface* ieqs) m_Proximity(FLT_MAX, FLT_MAX, FLT_MAX), //arbitrary large number m_ZoneSummonLocation(-2.0f,-2.0f,-2.0f), m_AutoAttackPosition(0.0f, 0.0f, 0.0f, 0.0f), - m_AutoAttackTargetLocation(0.0f, 0.0f, 0.0f) + m_AutoAttackTargetLocation(0.0f, 0.0f, 0.0f), + m_lastsave(-1) { for(int cf=0; cf < _FilterCount; cf++) ClientFilters[cf] = FilterShow; @@ -569,6 +570,10 @@ bool Client::Save(uint8 iCommitNow) { if(!ClientDataLoaded()) return false; + // saved less than 2 seconds ago, lets just skip for now + if ((time(nullptr) - m_lastsave) < 2) + return true; + /* Wrote current basics to PP for saves */ m_pp.x = m_Position.x; m_pp.y = m_Position.y; @@ -658,6 +663,7 @@ bool Client::Save(uint8 iCommitNow) { database.SaveCharacterData(this->CharacterID(), this->AccountID(), &m_pp, &m_epp); /* Save Character Data */ + m_lastsave = time(nullptr); return true; } diff --git a/zone/client.h b/zone/client.h index 32d0e176c..e7b7b50b9 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1458,6 +1458,9 @@ private: Timer helm_toggle_timer; Timer light_update_timer; + + time_t m_lastsave; + glm::vec3 m_Proximity; void BulkSendInventoryItems(); From 538d6a2a33561ad95dbc624edcce653885f7167c Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Wed, 3 Aug 2016 01:44:33 -0400 Subject: [PATCH 13/78] Hack to fix RNG Nature Veil line --- common/spdat.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/common/spdat.cpp b/common/spdat.cpp index ab9150122..eeaf2be60 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -235,8 +235,11 @@ bool IsBeneficialSpell(uint16 spell_id) // If the resisttype is magic and SpellAffectIndex is Calm/memblur/dispell sight // it's not beneficial if (spells[spell_id].resisttype == RESIST_MAGIC) { - if (sai == SAI_Calm || sai == SAI_Dispell_Sight || - sai == SAI_Memory_Blur || sai == SAI_Calm_Song) + // checking these SAI cause issues with the rng defensive proc line + // So I guess instead of fixing it for real, just a quick hack :P + if (spells[spell_id].effectid[0] != SE_DefensiveProc && + (sai == SAI_Calm || sai == SAI_Dispell_Sight || sai == SAI_Memory_Blur || + sai == SAI_Calm_Song)) return false; } else { // If the resisttype is not magic and spell is Bind Sight or Cast Sight From 60f2d14caa9bcadc72a9a47ea0324b5e109fc7b3 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Wed, 3 Aug 2016 17:37:09 -0400 Subject: [PATCH 14/78] Rework bash/kick stun based on client This is what the client is doing. It doesn't reuse all the old rules, so those can't be tweaked unless someone wants to add them back in --- common/races.h | 1 + common/spdat.h | 2 +- zone/attack.cpp | 106 ++++++++++++++++++++++------------------------ zone/string_ids.h | 3 +- 4 files changed, 55 insertions(+), 57 deletions(-) diff --git a/common/races.h b/common/races.h index 050b2fd78..d34d9b754 100644 --- a/common/races.h +++ b/common/races.h @@ -45,6 +45,7 @@ #define TIGER 63 #define ELEMENTAL 75 #define ALLIGATOR 91 +#define OGGOK_CITIZEN 93 #define EYE_OF_ZOMM 108 #define WOLF_ELEMENTAL 120 #define INVISIBLE_MAN 127 diff --git a/common/spdat.h b/common/spdat.h index cb6e49fc9..2734f6078 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -446,7 +446,7 @@ typedef enum { #define SE_IncreaseRunSpeedCap 290 // implemented[AA] - increases run speed over the hard cap #define SE_Purify 291 // implemented - Removes determental effects #define SE_StrikeThrough2 292 // implemented[AA] - increasing chance of bypassing an opponent's special defenses, such as dodge, block, parry, and riposte. -#define SE_FrontalStunResist 293 // implemented[AA] - Reduce chance to be stunned from front. +#define SE_FrontalStunResist 293 // implemented[AA] - Reduce chance to be stunned from front. -- live descriptions sounds like this isn't limited to frontal anymore #define SE_CriticalSpellChance 294 // implemented - increase chance to critical hit and critical damage modifier. //#define SE_ReduceTimerSpecial 295 // not used #define SE_FcSpellVulnerability 296 // implemented - increase in incoming spell damage diff --git a/zone/attack.cpp b/zone/attack.cpp index 54f98c97a..d4d8f141c 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -23,6 +23,7 @@ #include "../common/skills.h" #include "../common/spdat.h" #include "../common/string_util.h" +#include "../common/data_verification.h" #include "queryserv.h" #include "quest_parser_collection.h" #include "string_ids.h" @@ -3169,68 +3170,63 @@ void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, cons BuffFadeByEffect(SE_Mez); } - //check stun chances if bashing - if (damage > 0 && ((skill_used == EQEmu::skills::SkillBash || skill_used == EQEmu::skills::SkillKick) && attacker)) { - // NPCs can stun with their bash/kick as soon as they receive it. - // Clients can stun mobs under level 56 with their kick when they get level 55 or greater. - // Clients have a chance to stun if the mob is 56+ - - // Calculate the chance to stun - int stun_chance = 0; - if (!GetSpecialAbility(UNSTUNABLE)) { - if (attacker->IsNPC()) { - stun_chance = RuleI(Combat, NPCBashKickStunChance); - } else if (attacker->IsClient()) { - // Less than base immunity - // Client vs. Client always uses the chance - if (!IsClient() && GetLevel() <= RuleI(Spells, BaseImmunityLevel)) { - if (skill_used == EQEmu::skills::SkillBash) // Bash always will - stun_chance = 100; - else if (attacker->GetLevel() >= RuleI(Combat, ClientStunLevel)) - stun_chance = 100; // only if you're over level 55 and using kick - } else { // higher than base immunity or Client vs. Client - // not sure on this number, use same as NPC for now - if (skill_used == EQEmu::skills::SkillKick && attacker->GetLevel() < RuleI(Combat, ClientStunLevel)) - stun_chance = RuleI(Combat, NPCBashKickStunChance); - else if (skill_used == EQEmu::skills::SkillBash) - stun_chance = RuleI(Combat, NPCBashKickStunChance) + - attacker->spellbonuses.StunBashChance + - attacker->itembonuses.StunBashChance + - attacker->aabonuses.StunBashChance; - } - } + // broken up for readability + // This is based on what the client is doing + // We had a bunch of stuff like BaseImmunityLevel checks, which I think is suppose to just be for spells + // This is missing some merc checks, but those mostly just skipped the spell bonuses I think ... + bool can_stun = false; + int stunbash_chance = 0; // bonus + if (attacker) { + if (skill_used == EQEmu::skills::SkillBash) { + can_stun = true; + if (attacker->IsClient()) + stunbash_chance = attacker->spellbonuses.StunBashChance + + attacker->itembonuses.StunBashChance + + attacker->aabonuses.StunBashChance; + } else if (skill_used == EQEmu::skills::SkillKick && + (attacker->GetLevel() > 55 || attacker->IsNPC()) && GetClass() == WARRIOR) { + can_stun = true; } - if (stun_chance && zone->random.Roll(stun_chance)) { - // Passed stun, try to resist now - int stun_resist = itembonuses.StunResist + spellbonuses.StunResist; - int frontal_stun_resist = itembonuses.FrontalStunResist + spellbonuses.FrontalStunResist; - - Log.Out(Logs::Detail, Logs::Combat, "Stun passed, checking resists. Was %d chance.", stun_chance); - if (IsClient()) { - stun_resist += aabonuses.StunResist; - frontal_stun_resist += aabonuses.FrontalStunResist; - } - - // frontal stun check for ogres/bonuses - if (((GetBaseRace() == OGRE && IsClient()) || - (frontal_stun_resist && zone->random.Roll(frontal_stun_resist))) && - !attacker->BehindMob(this, attacker->GetX(), attacker->GetY())) { - Log.Out(Logs::Detail, Logs::Combat, "Frontal stun resisted. %d chance.", frontal_stun_resist); - - } else { - // Normal stun resist check. - if (stun_resist && zone->random.Roll(stun_resist)) { + if ((GetBaseRace() == OGRE || GetBaseRace() == OGGOK_CITIZEN) && + !attacker->BehindMob(this, attacker->GetX(), attacker->GetY())) + can_stun = false; + if (GetSpecialAbility(UNSTUNABLE)) + can_stun = false; + } + if (can_stun) { + int bashsave_roll = zone->random.Int(0, 100); + if (bashsave_roll > 98 || bashsave_roll > (55 - stunbash_chance)) { + // did stun -- roll other resists + // SE_FrontalStunResist description says any angle now a days + int stun_resist2 = spellbonuses.FrontalStunResist + itembonuses.FrontalStunResist + + aabonuses.FrontalStunResist; + if (zone->random.Int(1, 100) > stun_resist2) { + // stun resist 2 failed + // time to check SE_StunResist and mod2 stun resist + int stun_resist = + spellbonuses.StunResist + itembonuses.StunResist + aabonuses.StunResist; + if (zone->random.Int(0, 100) >= stun_resist) { + // did stun + // nothing else to check! + Stun(2000); // straight 2 seconds every time + } else { + // stun resist passed! if (IsClient()) Message_StringID(MT_Stun, SHAKE_OFF_STUN); - Log.Out(Logs::Detail, Logs::Combat, "Stun Resisted. %d chance.", stun_resist); - } else { - Log.Out(Logs::Detail, Logs::Combat, "Stunned. %d resist chance.", stun_resist); - Stun(zone->random.Int(0, 2) * 1000); // 0-2 seconds } + } else { + // stun resist 2 passed! + if (IsClient()) + Message_StringID(MT_Stun, AVOID_STUNNING_BLOW); } } else { - Log.Out(Logs::Detail, Logs::Combat, "Stun failed. %d chance.", stun_chance); + // main stun failed -- extra interrupt roll + if (IsCasting() && + !EQEmu::ValueWithin(casting_spell_id, 859, 1023)) // these spells are excluded + // 90% chance >< -- stun immune won't reach this branch though :( + if (zone->random.Int(0, 9) > 1) + InterruptSpell(); } } diff --git a/zone/string_ids.h b/zone/string_ids.h index be05b1eb2..e1fb4de7c 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -297,6 +297,7 @@ #define ADVENTURE_COMPLETE 5147 //You received %1 points for successfully completing the adventure. #define SUCCOR_FAIL 5169 //The portal collapes before you can escape! #define PET_ATTACKING 5501 //%1 tells you, 'Attacking %2 Master.' +#define AVOID_STUNNING_BLOW 5753 //You avoid the stunning blow. #define FATAL_BOW_SHOT 5745 //%1 performs a FATAL BOW SHOT!! #define MELEE_SILENCE 5806 //You *CANNOT* use this melee ability, you are suffering from amnesia! #define DISCIPLINE_REUSE_MSG 5807 //You can use the ability %1 again in %2 hour(s) %3 minute(s) %4 seconds. @@ -354,7 +355,7 @@ #define HIT_NON_MELEE 9073 //%1 hit %2 for %3 points of non-melee damage. #define GLOWS_BLUE 9074 //Your %1 glows blue. #define GLOWS_RED 9075 //Your %1 glows red. -#define SHAKE_OFF_STUN 9077 +#define SHAKE_OFF_STUN 9077 //You shake off the stun effect! #define STRIKETHROUGH_STRING 9078 //You strike through your opponent's defenses! #define SPELL_REFLECT 9082 //%1's spell has been reflected by %2. #define NEW_SPELLS_AVAIL 9149 //You have new spells available to you. Check the merchants near your guild master. From 66fec40169ea4236ce007dfd64cf8af7147ecfd4 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Wed, 3 Aug 2016 22:38:33 -0400 Subject: [PATCH 15/78] Move Enrage check to after the immune ripo check --- zone/attack.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/zone/attack.cpp b/zone/attack.cpp index d4d8f141c..2b37be197 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -370,18 +370,15 @@ bool Mob::AvoidDamage(Mob *other, int32 &damage, int hand) counter_dodge = attacker->GetSpecialAbilityParam(COUNTER_AVOID_DAMAGE, 4); } - ////////////////////////////////////////////////////////// - // make enrage same as riposte - ///////////////////////////////////////////////////////// - if (IsEnraged() && InFront) { - damage = -3; - Log.Out(Logs::Detail, Logs::Combat, "I am enraged, riposting frontal attack."); - } - // 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::legacy::SlotRange && CanThisClassRiposte() && InFront && !ImmuneRipo) { + if (IsEnraged()) { + damage = -3; + Log.Out(Logs::Detail, Logs::Combat, "I am enraged, riposting frontal attack."); + return true; + } if (IsClient()) CastToClient()->CheckIncreaseSkill(EQEmu::skills::SkillRiposte, other, -10); // check auto discs ... I guess aa/items too :P From c81a5e078370e48d8ed6626e7bc6da530ae964ac Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Wed, 3 Aug 2016 23:06:00 -0400 Subject: [PATCH 16/78] Limit casting skill ups to casting skills --- common/skills.cpp | 13 +++++++++++++ common/skills.h | 9 +++++---- zone/spells.cpp | 10 ++++++---- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/common/skills.cpp b/common/skills.cpp index 9be28781f..b1cf59c12 100644 --- a/common/skills.cpp +++ b/common/skills.cpp @@ -110,6 +110,19 @@ bool EQEmu::skills::IsBardInstrumentSkill(SkillType skill) } } +bool EQEmu::skills::IsCastingSkill(SkillType skill) +{ + switch (skill) { + case SkillAbjuration: + case SkillAlteration: + case SkillConjuration: + case SkillEvocation: + return true; + default: + return false; + } +} + const std::map& EQEmu::skills::GetSkillTypeMap() { /* VS2013 code diff --git a/common/skills.h b/common/skills.h index cd7603667..19a996729 100644 --- a/common/skills.h +++ b/common/skills.h @@ -161,10 +161,11 @@ namespace EQEmu // server profile does not reflect this yet..so, prefixed with 'PACKET_' #define PACKET_SKILL_ARRAY_SIZE 100 - extern bool IsTradeskill(SkillType skill); - extern bool IsSpecializedSkill(SkillType skill); - extern float GetSkillMeleePushForce(SkillType skill); - extern bool IsBardInstrumentSkill(SkillType skill); + bool IsTradeskill(SkillType skill); + bool IsSpecializedSkill(SkillType skill); + float GetSkillMeleePushForce(SkillType skill); + bool IsBardInstrumentSkill(SkillType skill); + bool IsCastingSkill(SkillType skill); extern const std::map& GetSkillTypeMap(); diff --git a/zone/spells.cpp b/zone/spells.cpp index 60837381e..0eebcb082 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -1327,12 +1327,14 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo SetMana(GetMana()); // skills - c->CheckIncreaseSkill(spells[spell_id].skill, nullptr); + if (EQEmu::skills::IsCastingSkill(spells[spell_id].skill)) { + c->CheckIncreaseSkill(spells[spell_id].skill, nullptr); - // increased chance of gaining channel skill if you regained concentration - c->CheckIncreaseSkill(EQEmu::skills::SkillChanneling, nullptr, regain_conc ? 5 : 0); + // increased chance of gaining channel skill if you regained concentration + c->CheckIncreaseSkill(EQEmu::skills::SkillChanneling, nullptr, regain_conc ? 5 : 0); - c->CheckSpecializeIncrease(spell_id); + c->CheckSpecializeIncrease(spell_id); + } } } From 33407ee0daeb140eedda654cc53313f2835e9bf9 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Wed, 3 Aug 2016 23:14:02 -0400 Subject: [PATCH 17/78] Forgot divination --- common/skills.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/common/skills.cpp b/common/skills.cpp index b1cf59c12..a23d77a7b 100644 --- a/common/skills.cpp +++ b/common/skills.cpp @@ -116,6 +116,7 @@ bool EQEmu::skills::IsCastingSkill(SkillType skill) case SkillAbjuration: case SkillAlteration: case SkillConjuration: + case SkillDivination: case SkillEvocation: return true; default: From d0fbbed20d42f119b21cd7849a79efaa9cb7530d Mon Sep 17 00:00:00 2001 From: Uleat Date: Thu, 4 Aug 2016 07:09:34 -0400 Subject: [PATCH 18/78] Added a trap for Bot::GetNeedsCured() random crash (bot server admins: watch your logs!) --- zone/bot.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 14acc9b57..a3dd19b2d 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -8159,7 +8159,25 @@ bool Bot::GetNeedsCured(Mob *tar) { int buffsWithCounters = 0; needCured = true; for (unsigned int j = 0; j < buff_count; j++) { - if(tar->GetBuffs()[j].spellid != SPELL_UNKNOWN) { + // 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(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 8dc8e5321889635a8b15318e0bd298918e7e4df8 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Thu, 4 Aug 2016 13:50:20 -0400 Subject: [PATCH 19/78] Added some more comments to PassCastRestrictions --- zone/spell_effects.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 4c5b805ad..5708541d4 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -6226,7 +6226,7 @@ bool Mob::PassCastRestriction(bool UseCastRestriction, int16 value, bool IsDama { /*If return TRUE spell met all restrictions and can continue (this = target). This check is used when the spell_new field CastRestriction is defined OR spell effect '0'(DD/Heal) has a defined limit - Range 1 : UNKNOWN + Range 1 : UNKNOWN -- the spells with this seem to not have a restiction, true for now Range 100 : *Animal OR Humanoid Range 101 : *Dragon Range 102 : *Animal OR Insect @@ -6253,7 +6253,10 @@ bool Mob::PassCastRestriction(bool UseCastRestriction, int16 value, bool IsDama Range 124 : *Undead HP < 10% Range 125 : *Clockwork HP < 10% Range 126 : *Wisp HP < 10% - Range 127-130 : UNKNOWN + Range 127 : UNKNOWN + Range 128 : pure melee -- guess + Range 129 : pure caster -- guess + Range 130 : hybrid -- guess Range 150 : UNKNOWN Range 190 : No Raid boss flag *not implemented Range 191 : This spell will deal less damage to 'exceptionally strong targets' - Raid boss flag *not implemented @@ -6265,12 +6268,17 @@ bool Mob::PassCastRestriction(bool UseCastRestriction, int16 value, bool IsDama Range 300 - 303 : UNKOWN *not implemented Range 304 : Chain + Plate class (buffs) Range 399 - 409 : Heal if HP within a specified range (400 = 0-25% 401 = 25 - 35% 402 = 35-45% ect) - Range 410 - 411 : UNKOWN + Range 410 - 411 : UNKOWN -- examples are auras that cast on NPCs maybe in combat/out of combat? Range 500 - 599 : Heal if HP less than a specified value Range 600 - 699 : Limit to Body Type [base2 - 600 = Body] Range 700 : NPC only -- from patch notes "Wizard - Arcane Fusion no longer deals damage to non-NPC targets. This should ensure that wizards who fail their Bucolic Gambit are slightly less likely to annihilate themselves." Range 701 : NOT PET - Range 800 : UKNOWN + Range 800 : UKNOWN -- Target's Target Test (16598) + Range 812 : UNKNOWN -- triggered by Thaumatize Owner + Range 814 : UNKNOWN -- Vegetentacles + Range 815 : UNKNOWN -- Pumpkin Pulp Splash + Range 816 : UNKNOWN -- Rotten Fruit Splash + Range 817 : UNKNOWN -- Tainted Bixie Pollen Splash Range 818 - 819 : If Undead/If Not Undead Range 820 - 822 : UKNOWN Range 835 : Unknown *not implemented @@ -6290,6 +6298,9 @@ bool Mob::PassCastRestriction(bool UseCastRestriction, int16 value, bool IsDama switch(value) { + case 1: + return true; + case 100: if ((GetBodyType() == BT_Animal) || (GetBodyType() == BT_Humanoid)) return true; From e89fa01d8977901b1e014df803a5959b259d7c95 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Thu, 4 Aug 2016 20:33:29 -0400 Subject: [PATCH 20/78] Port Aggro:UseLevelAggro from EQMacEmu This will make level 18+ mobs braver --- common/ruletypes.h | 1 + zone/aggro.cpp | 58 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index 1a64b1ebd..14288391e 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -518,6 +518,7 @@ RULE_REAL(Aggro, TunnelVisionAggroMod, 0.75) //people not currently the top hate RULE_INT(Aggro, MaxScalingProcAggro, 400) // Set to -1 for no limit. Maxmimum amount of aggro that HP scaling SPA effect in a proc will add. RULE_INT(Aggro, IntAggroThreshold, 75) // Int <= this will aggro regardless of level difference. RULE_BOOL(Aggro, AllowTickPulling, false) // tick pulling is an exploit in an NPC's call for help fixed sometime in 2006 on live +RULE_BOOL(Aggro, UseLevelAggro, true) // Level 18+ and Undead will aggro regardless of level difference. (this will disabled Rule:IntAggroThreshold if set to true) RULE_CATEGORY_END() RULE_CATEGORY(TaskSystem) diff --git a/zone/aggro.cpp b/zone/aggro.cpp index 847c51e76..4cf403545 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -156,10 +156,21 @@ void NPC::DescribeAggro(Client *towho, Mob *mob, bool verbose) { return; } - if(GetINT() > RuleI(Aggro, IntAggroThreshold) && mob->GetLevelCon(GetLevel()) == CON_GREEN ) { - towho->Message(0, "...%s is red to me (basically)", mob->GetName(), - dist2, iAggroRange2); - return; + if (RuleB(Aggro, UseLevelAggro)) + { + if (GetLevel() < 18 && mob->GetLevelCon(GetLevel()) == CON_GREEN && GetBodyType() != 3) + { + towho->Message(0, "...%s is red to me (basically)", mob->GetName(), dist2, iAggroRange2); + return; + } + } + else + { + if(GetINT() > RuleI(Aggro, IntAggroThreshold) && mob->GetLevelCon(GetLevel()) == CON_GREEN ) { + towho->Message(0, "...%s is red to me (basically)", mob->GetName(), + dist2, iAggroRange2); + return; + } } if(verbose) { @@ -321,11 +332,12 @@ bool Mob::CheckWillAggro(Mob *mob) { int heroicCHA_mod = mob->itembonuses.HeroicCHA/25; // 800 Heroic CHA cap if(heroicCHA_mod > THREATENLY_ARRGO_CHANCE) heroicCHA_mod = THREATENLY_ARRGO_CHANCE; - if + if (RuleB(Aggro, UseLevelAggro) && ( //old InZone check taken care of above by !mob->CastToClient()->Connected() ( - ( GetINT() <= RuleI(Aggro, IntAggroThreshold) ) + ( GetLevel() >= 18 ) + ||(GetBodyType() == 3) ||( mob->IsClient() && mob->CastToClient()->IsSitting() ) ||( mob->GetLevelCon(GetLevel()) != CON_GREEN ) @@ -344,6 +356,7 @@ bool Mob::CheckWillAggro(Mob *mob) { ) ) ) + ) { //FatherNiwtit: make sure we can see them. last since it is very expensive if(CheckLosFN(mob)) { @@ -351,6 +364,39 @@ bool Mob::CheckWillAggro(Mob *mob) { return( mod_will_aggro(mob, this) ); } } + else + { + if + ( + //old InZone check taken care of above by !mob->CastToClient()->Connected() + ( + ( GetINT() <= RuleI(Aggro, IntAggroThreshold) ) + ||( mob->IsClient() && mob->CastToClient()->IsSitting() ) + ||( mob->GetLevelCon(GetLevel()) != CON_GREEN ) + + ) + && + ( + ( + fv == FACTION_SCOWLS + || + (mob->GetPrimaryFaction() != GetPrimaryFaction() && mob->GetPrimaryFaction() == -4 && GetOwner() == nullptr) + || + ( + fv == FACTION_THREATENLY + && zone->random.Roll(THREATENLY_ARRGO_CHANCE - heroicCHA_mod) + ) + ) + ) + ) + { + //FatherNiwtit: make sure we can see them. last since it is very expensive + if(CheckLosFN(mob)) { + Log.Out(Logs::Detail, Logs::Aggro, "Check aggro for %s target %s.", GetName(), mob->GetName()); + return( mod_will_aggro(mob, this) ); + } + } + } Log.Out(Logs::Detail, Logs::Aggro, "Is In zone?:%d\n", mob->InZone()); Log.Out(Logs::Detail, Logs::Aggro, "Dist^2: %f\n", dist2); From 1d12f92934c0a1fb9defeed43dd4ac914e666cb9 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Thu, 4 Aug 2016 20:36:15 -0400 Subject: [PATCH 21/78] Level 50+ NPCs will now respond to yells for help regardless of con color --- zone/aggro.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/aggro.cpp b/zone/aggro.cpp index 4cf403545..d50c1d43e 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -508,7 +508,7 @@ void EntityList::AIYellForHelp(Mob* sender, Mob* attacker) { { //if they are in range, make sure we are not green... //then jump in if they are our friend - if(attacker->GetLevelCon(mob->GetLevel()) != CON_GREEN) + if(mob->GetLevel() >= 50 || attacker->GetLevelCon(mob->GetLevel()) != CON_GREEN) { bool useprimfaction = false; if(mob->GetPrimaryFaction() == sender->CastToNPC()->GetPrimaryFaction()) From d53d569020a41f5d013b893a9fdf8e90688898cb Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Thu, 4 Aug 2016 22:12:33 -0400 Subject: [PATCH 22/78] Port EQMacEmu's improved NPC stat scaling formula Old formula can be used by setting NPC::NewLevelSacling to false --- common/ruletypes.h | 1 + zone/npc.cpp | 116 +++++++++++++++++++++++++++++++-------------- 2 files changed, 82 insertions(+), 35 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index 14288391e..8ef840c96 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -503,6 +503,7 @@ RULE_BOOL(NPC, EnableMeritBasedFaction, false) // If set to true, faction will g RULE_INT(NPC, NPCToNPCAggroTimerMin, 500) RULE_INT(NPC, NPCToNPCAggroTimerMax, 6000) RULE_BOOL(NPC, UseClassAsLastName, true) // Uses class archetype as LastName for npcs with none +RULE_BOOL(NPC, NewLevelScaling, true) // Better level scaling, use old if new formulas would break your server RULE_CATEGORY_END() RULE_CATEGORY(Aggro) diff --git a/zone/npc.cpp b/zone/npc.cpp index d1d9e8a44..15e96f3bf 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -2006,44 +2006,90 @@ void NPC::LevelScale() { float scaling = (((random_level / (float)level) - 1) * (scalerate / 100.0f)); - // Compensate for scale rates at low levels so they don't add too much - uint8 scale_adjust = 1; - if(level > 0 && level <= 5) - scale_adjust = 10; - if(level > 5 && level <= 10) - scale_adjust = 5; - if(level > 10 && level <= 15) - scale_adjust = 3; - if(level > 15 && level <= 25) - scale_adjust = 2; + if (RuleB(NPC, NewLevelScaling)) { + if (scalerate == 0 || maxlevel <= 25) { + // pre-pop seems to scale by 20 HP increments while newer by 100 + // We also don't want 100 increments on newer noobie zones, check level + if (zone->GetZoneID() < 200 || level < 48) { + max_hp += (random_level - level) * 20; + base_hp += (random_level - level) * 20; + } else { + max_hp += (random_level - level) * 100; + base_hp += (random_level - level) * 100; + } - base_hp += (int)(base_hp * scaling); - max_hp += (int)(max_hp * scaling); - cur_hp = max_hp; - STR += (int)(STR * scaling / scale_adjust); - STA += (int)(STA * scaling / scale_adjust); - AGI += (int)(AGI * scaling / scale_adjust); - DEX += (int)(DEX * scaling / scale_adjust); - INT += (int)(INT * scaling / scale_adjust); - WIS += (int)(WIS * scaling / scale_adjust); - CHA += (int)(CHA * scaling / scale_adjust); - if (MR) - MR += (int)(MR * scaling / scale_adjust); - if (CR) - CR += (int)(CR * scaling / scale_adjust); - if (DR) - DR += (int)(DR * scaling / scale_adjust); - if (FR) - FR += (int)(FR * scaling / scale_adjust); - if (PR) - PR += (int)(PR * scaling / scale_adjust); + cur_hp = max_hp; + max_dmg += (random_level - level) * 2; + } else { + uint8 scale_adjust = 1; + + base_hp += (int)(base_hp * scaling); + max_hp += (int)(max_hp * scaling); + cur_hp = max_hp; + + if (max_dmg) { + max_dmg += (int)(max_dmg * scaling / scale_adjust); + min_dmg += (int)(min_dmg * scaling / scale_adjust); + } + + STR += (int)(STR * scaling / scale_adjust); + STA += (int)(STA * scaling / scale_adjust); + AGI += (int)(AGI * scaling / scale_adjust); + DEX += (int)(DEX * scaling / scale_adjust); + INT += (int)(INT * scaling / scale_adjust); + WIS += (int)(WIS * scaling / scale_adjust); + CHA += (int)(CHA * scaling / scale_adjust); + if (MR) + MR += (int)(MR * scaling / scale_adjust); + if (CR) + CR += (int)(CR * scaling / scale_adjust); + if (DR) + DR += (int)(DR * scaling / scale_adjust); + if (FR) + FR += (int)(FR * scaling / scale_adjust); + if (PR) + PR += (int)(PR * scaling / scale_adjust); + } + } else { + // Compensate for scale rates at low levels so they don't add too much + uint8 scale_adjust = 1; + if(level > 0 && level <= 5) + scale_adjust = 10; + if(level > 5 && level <= 10) + scale_adjust = 5; + if(level > 10 && level <= 15) + scale_adjust = 3; + if(level > 15 && level <= 25) + scale_adjust = 2; + + base_hp += (int)(base_hp * scaling); + max_hp += (int)(max_hp * scaling); + cur_hp = max_hp; + STR += (int)(STR * scaling / scale_adjust); + STA += (int)(STA * scaling / scale_adjust); + AGI += (int)(AGI * scaling / scale_adjust); + DEX += (int)(DEX * scaling / scale_adjust); + INT += (int)(INT * scaling / scale_adjust); + WIS += (int)(WIS * scaling / scale_adjust); + CHA += (int)(CHA * scaling / scale_adjust); + if (MR) + MR += (int)(MR * scaling / scale_adjust); + if (CR) + CR += (int)(CR * scaling / scale_adjust); + if (DR) + DR += (int)(DR * scaling / scale_adjust); + if (FR) + FR += (int)(FR * scaling / scale_adjust); + if (PR) + PR += (int)(PR * scaling / scale_adjust); + + if (max_dmg) + { + max_dmg += (int)(max_dmg * scaling / scale_adjust); + min_dmg += (int)(min_dmg * scaling / scale_adjust); + } - if (max_dmg) - { - max_dmg += (int)(max_dmg * scaling / scale_adjust); - min_dmg += (int)(min_dmg * scaling / scale_adjust); } - level = random_level; return; From 68df09a5701ac47ae5b2ec3e890441f87cc79dd7 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Thu, 4 Aug 2016 23:56:08 -0400 Subject: [PATCH 23/78] Implement PVP regions --- zone/client.cpp | 40 ++++++++++++++++++++++++++++++++++------ zone/client.h | 8 ++++++-- zone/client_packet.cpp | 7 +++++-- zone/water_map.h | 3 ++- zone/water_map_v1.cpp | 4 ++++ zone/water_map_v1.h | 1 + zone/water_map_v2.cpp | 4 ++++ zone/water_map_v2.h | 1 + 8 files changed, 57 insertions(+), 11 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index 7cfdf512f..eebe204ee 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -44,6 +44,7 @@ extern volatile bool RunLoops; #include "zonedb.h" #include "petitions.h" #include "command.h" +#include "water_map.h" #ifdef BOTS #include "bot_command.h" #endif @@ -156,7 +157,8 @@ Client::Client(EQStreamInterface* ieqs) m_ZoneSummonLocation(-2.0f,-2.0f,-2.0f), m_AutoAttackPosition(0.0f, 0.0f, 0.0f, 0.0f), m_AutoAttackTargetLocation(0.0f, 0.0f, 0.0f), - m_lastsave(-1) + m_lastsave(-1), + last_region_type(RegionTypeUnsupported) { for(int cf=0; cf < _FilterCount; cf++) ClientFilters[cf] = FilterShow; @@ -2482,13 +2484,15 @@ uint16 Client::GetMaxSkillAfterSpecializationRules(EQEmu::skills::SkillType skil return Result; } -void Client::SetPVP(bool toggle) { +void Client::SetPVP(bool toggle, bool message) { m_pp.pvp = toggle ? 1 : 0; - if(GetPVP()) - this->Message_StringID(MT_Shout,PVP_ON); - else - Message(13, "You no longer follow the ways of discord."); + if (message) { + if(GetPVP()) + this->Message_StringID(MT_Shout,PVP_ON); + else + Message(13, "You no longer follow the ways of discord."); + } SendAppearancePacket(AT_PVP, GetPVP()); Save(); @@ -8533,3 +8537,27 @@ uint32 Client::GetMoney(uint8 type, uint8 subtype) { int Client::GetAccountAge() { return (time(nullptr) - GetAccountCreation()); } + +void Client::CheckRegionTypeChanges() +{ + if (!zone->HasWaterMap()) + return; + + auto new_region = zone->watermap->ReturnRegionType(glm::vec3(m_Position)); + + // still same region, do nothing + if (last_region_type == new_region) + return; + + // region type changed + last_region_type = new_region; + + // PVP is the only state we need to keep track of, so we can just return now for PVP servers + if (RuleI(World, PVPSettings) > 0) + return; + + if (last_region_type == RegionTypePVP) + SetPVP(true, false); + else if (GetPVP()) + SetPVP(false, false); +} diff --git a/zone/client.h b/zone/client.h index e7b7b50b9..3bd2b29c5 100644 --- a/zone/client.h +++ b/zone/client.h @@ -27,6 +27,7 @@ class Object; class Raid; class Seperator; class ServerPacket; +enum WaterRegionType : int; namespace EQEmu { @@ -360,9 +361,9 @@ public: int32 LevelRegen(); void HPTick(); void SetGM(bool toggle); - void SetPVP(bool toggle); + void SetPVP(bool toggle, bool message = true); - inline bool GetPVP() const { return zone->GetZoneID() == 77 ? true : (m_pp.pvp != 0); } + inline bool GetPVP() const { return m_pp.pvp != 0; } inline bool GetGM() const { return m_pp.gm != 0; } inline void SetBaseClass(uint32 i) { m_pp.class_=i; } @@ -1233,6 +1234,8 @@ public: void SendHPUpdateMarquee(); + void CheckRegionTypeChanges(); + protected: friend class Mob; void CalcItemBonuses(StatBonuses* newbon); @@ -1417,6 +1420,7 @@ private: uint8 zonesummon_ignorerestrictions; ZoneMode zone_mode; + WaterRegionType last_region_type; Timer position_timer; uint8 position_timer_counter; diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index a827a3008..2da75b364 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -4576,8 +4576,11 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) safe_delete(outapp); } - if(zone->watermap && zone->watermap->InLiquid(glm::vec3(m_Position))) - CheckIncreaseSkill(EQEmu::skills::SkillSwimming, nullptr, -17); + if (zone->watermap) { + if (zone->watermap->InLiquid(glm::vec3(m_Position))) + CheckIncreaseSkill(EQEmu::skills::SkillSwimming, nullptr, -17); + CheckRegionTypeChanges(); + } return; } diff --git a/zone/water_map.h b/zone/water_map.h index 2b6e0ce3c..1d6f6a34f 100644 --- a/zone/water_map.h +++ b/zone/water_map.h @@ -8,7 +8,7 @@ extern const ZoneConfig *Config; -enum WaterRegionType { +enum WaterRegionType : int { RegionTypeUnsupported = -2, RegionTypeUntagged = -1, RegionTypeNormal = 0, @@ -33,6 +33,7 @@ public: virtual bool InVWater(const glm::vec3& location) const = 0; virtual bool InLava(const glm::vec3& location) const = 0; virtual bool InLiquid(const glm::vec3& location) const = 0; + virtual bool InPvP(const glm::vec3& location) const = 0; protected: virtual bool Load(FILE *fp) { return false; } diff --git a/zone/water_map_v1.cpp b/zone/water_map_v1.cpp index 431df4224..dd2e1539c 100644 --- a/zone/water_map_v1.cpp +++ b/zone/water_map_v1.cpp @@ -30,6 +30,10 @@ bool WaterMapV1::InLiquid(const glm::vec3& location) const { return InWater(location) || InLava(location); } +bool WaterMapV1::InPvP(const glm::vec3& location) const { + return ReturnRegionType(location) == RegionTypePVP; +} + bool WaterMapV1::Load(FILE *fp) { uint32 bsp_tree_size; if (fread(&bsp_tree_size, sizeof(bsp_tree_size), 1, fp) != 1) { diff --git a/zone/water_map_v1.h b/zone/water_map_v1.h index 6697aa0c8..e4cb167d7 100644 --- a/zone/water_map_v1.h +++ b/zone/water_map_v1.h @@ -24,6 +24,7 @@ public: virtual bool InVWater(const glm::vec3& location) const; virtual bool InLava(const glm::vec3& location) const; virtual bool InLiquid(const glm::vec3& location) const; + virtual bool InPvP(const glm::vec3& location) const; protected: virtual bool Load(FILE *fp); diff --git a/zone/water_map_v2.cpp b/zone/water_map_v2.cpp index a57999a48..48383fe3c 100644 --- a/zone/water_map_v2.cpp +++ b/zone/water_map_v2.cpp @@ -33,6 +33,10 @@ bool WaterMapV2::InLiquid(const glm::vec3& location) const { return InWater(location) || InLava(location); } +bool WaterMapV2::InPvP(const glm::vec3& location) const { + return ReturnRegionType(location) == RegionTypePVP; +} + bool WaterMapV2::Load(FILE *fp) { uint32 region_count; if (fread(®ion_count, sizeof(region_count), 1, fp) != 1) { diff --git a/zone/water_map_v2.h b/zone/water_map_v2.h index 6429937f0..523191fe3 100644 --- a/zone/water_map_v2.h +++ b/zone/water_map_v2.h @@ -17,6 +17,7 @@ public: virtual bool InVWater(const glm::vec3& location) const; virtual bool InLava(const glm::vec3& location) const; virtual bool InLiquid(const glm::vec3& location) const; + virtual bool InPvP(const glm::vec3& location) const; protected: virtual bool Load(FILE *fp); From 6a7ea65dd0c68c05639f52ed6880af4c272ffc20 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Fri, 5 Aug 2016 01:07:12 -0400 Subject: [PATCH 24/78] Ask water map if we're in a zoneline to prevent false positives I don't think this should open up any chance to exploit Trying to use a ZL to go somewhere else is still detected etc This should really cut down on false positives and we really can't see real cheater from all the noise this creates --- zone/water_map.h | 1 + zone/water_map_v1.cpp | 4 ++++ zone/water_map_v1.h | 1 + zone/water_map_v2.cpp | 4 ++++ zone/water_map_v2.h | 1 + zone/zone.cpp | 4 +++- 6 files changed, 14 insertions(+), 1 deletion(-) diff --git a/zone/water_map.h b/zone/water_map.h index 1d6f6a34f..975703b40 100644 --- a/zone/water_map.h +++ b/zone/water_map.h @@ -34,6 +34,7 @@ public: virtual bool InLava(const glm::vec3& location) const = 0; virtual bool InLiquid(const glm::vec3& location) const = 0; virtual bool InPvP(const glm::vec3& location) const = 0; + virtual bool InZoneLine(const glm::vec3& location) const = 0; protected: virtual bool Load(FILE *fp) { return false; } diff --git a/zone/water_map_v1.cpp b/zone/water_map_v1.cpp index dd2e1539c..7d2479a3b 100644 --- a/zone/water_map_v1.cpp +++ b/zone/water_map_v1.cpp @@ -34,6 +34,10 @@ bool WaterMapV1::InPvP(const glm::vec3& location) const { return ReturnRegionType(location) == RegionTypePVP; } +bool WaterMapV1::InZoneLine(const glm::vec3& location) const { + return ReturnRegionType(location) == RegionTypeZoneLine; +} + bool WaterMapV1::Load(FILE *fp) { uint32 bsp_tree_size; if (fread(&bsp_tree_size, sizeof(bsp_tree_size), 1, fp) != 1) { diff --git a/zone/water_map_v1.h b/zone/water_map_v1.h index e4cb167d7..af4d4966f 100644 --- a/zone/water_map_v1.h +++ b/zone/water_map_v1.h @@ -25,6 +25,7 @@ public: virtual bool InLava(const glm::vec3& location) const; virtual bool InLiquid(const glm::vec3& location) const; virtual bool InPvP(const glm::vec3& location) const; + virtual bool InZoneLine(const glm::vec3& location) const; protected: virtual bool Load(FILE *fp); diff --git a/zone/water_map_v2.cpp b/zone/water_map_v2.cpp index 48383fe3c..939b9be97 100644 --- a/zone/water_map_v2.cpp +++ b/zone/water_map_v2.cpp @@ -37,6 +37,10 @@ bool WaterMapV2::InPvP(const glm::vec3& location) const { return ReturnRegionType(location) == RegionTypePVP; } +bool WaterMapV2::InZoneLine(const glm::vec3& location) const { + return ReturnRegionType(location) == RegionTypeZoneLine; +} + bool WaterMapV2::Load(FILE *fp) { uint32 region_count; if (fread(®ion_count, sizeof(region_count), 1, fp) != 1) { diff --git a/zone/water_map_v2.h b/zone/water_map_v2.h index 523191fe3..704d1a119 100644 --- a/zone/water_map_v2.h +++ b/zone/water_map_v2.h @@ -18,6 +18,7 @@ public: virtual bool InLava(const glm::vec3& location) const; virtual bool InLiquid(const glm::vec3& location) const; virtual bool InPvP(const glm::vec3& location) const; + virtual bool InZoneLine(const glm::vec3& location) const; protected: virtual bool Load(FILE *fp); diff --git a/zone/zone.cpp b/zone/zone.cpp index df48d0cfa..1165d7e9b 100644 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -1594,7 +1594,9 @@ ZonePoint* Zone::GetClosestZonePoint(const glm::vec3& location, uint32 to, Clien iterator.Advance(); } - if(closest_dist > 400.0f && closest_dist < max_distance2) + // if we have a water map and it says we're in a zoneline, lets assume it's just a really big zone line + // this shouldn't open up any exploits since those situations are detected later on + if ((zone->HasWaterMap() && !zone->watermap->InZoneLine(glm::vec3(client->GetPosition()))) || (!zone->HasWaterMap() && closest_dist > 400.0f && closest_dist < max_distance2)) { if(client) client->CheatDetected(MQZoneUnknownDest, location.x, location.y, location.z); // Someone is trying to use /zone From e5746c3b2eecfb8d7a5f86c3d0d9ea2805916245 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Fri, 5 Aug 2016 22:14:20 -0400 Subject: [PATCH 25/78] Fix EVENT_LOOT broken by b43cfa126 --- zone/corpse.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zone/corpse.cpp b/zone/corpse.cpp index ff5e4806b..ca32fc528 100644 --- a/zone/corpse.cpp +++ b/zone/corpse.cpp @@ -1163,9 +1163,9 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { } char buf[88]; - char corpse_name[64]; - strcpy(corpse_name, corpse_name); - snprintf(buf, 87, "%d %d %s", inst->GetItem()->ID, inst->GetCharges(), EntityList::RemoveNumbers(corpse_name)); + char q_corpse_name[64]; + strcpy(q_corpse_name, corpse_name); + snprintf(buf, 87, "%d %d %s", inst->GetItem()->ID, inst->GetCharges(), EntityList::RemoveNumbers(q_corpse_name)); buf[87] = '\0'; std::vector args; args.push_back(inst); From e86d11250b49ac3eb644be003d8d4427ec035c5c Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sun, 7 Aug 2016 14:32:30 -0400 Subject: [PATCH 26/78] Actually remove expendable AAs from the DB --- zone/aa.cpp | 4 ++++ zone/client.cpp | 5 +++++ zone/client.h | 1 + 3 files changed, 10 insertions(+) diff --git a/zone/aa.cpp b/zone/aa.cpp index f6facec8c..1640a9eff 100644 --- a/zone/aa.cpp +++ b/zone/aa.cpp @@ -1240,6 +1240,10 @@ void Mob::ExpendAlternateAdvancementCharge(uint32 aa_id) { CastToClient()->GetEPP().expended_aa += r->cost; } } + if (IsClient()) { + auto c = CastToClient(); + c->RemoveExpendedAA(ability->first_rank_id); + } aa_ranks.erase(iter.first); } diff --git a/zone/client.cpp b/zone/client.cpp index eebe204ee..11799fb9e 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -568,6 +568,11 @@ bool Client::SaveAA() { return true; } +void Client::RemoveExpendedAA(int aa_id) +{ + database.QueryDatabase(StringFormat("DELETE from `character_alternate_abilities` WHERE `id` = %d and `aa_id` = %d", character_id, aa_id)); +} + bool Client::Save(uint8 iCommitNow) { if(!ClientDataLoaded()) return false; diff --git a/zone/client.h b/zone/client.h index 3bd2b29c5..819d6ed17 100644 --- a/zone/client.h +++ b/zone/client.h @@ -328,6 +328,7 @@ public: /* New PP Save Functions */ bool SaveCurrency(){ return database.SaveCharacterCurrency(this->CharacterID(), &m_pp); } bool SaveAA(); + void RemoveExpendedAA(int aa_id); inline bool ClientDataLoaded() const { return client_data_loaded; } inline bool Connected() const { return (client_state == CLIENT_CONNECTED); } From 4b93ef0a98990266e07d9a675695d4ca6f3bc5da Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sun, 7 Aug 2016 18:17:39 -0400 Subject: [PATCH 27/78] Fix SE_CastOnFadeEffect --- zone/spell_effects.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 5708541d4..2a48b9e99 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -3668,10 +3668,11 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) break; } // These effects always trigger when they fade. + // Should we have this triggered from else where? case SE_CastOnFadeEffect: case SE_CastOnFadeEffectNPC: case SE_CastOnFadeEffectAlways: { - if (buff.ticsremaining == 1) { + if (buff.ticsremaining == 0) { SpellOnTarget(spells[buff.spellid].base[i], this); } break; From bdb083eac7675ab3141f51b04be99d9020ba2a6f Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sun, 7 Aug 2016 18:55:02 -0400 Subject: [PATCH 28/78] Previous disc buff check incorrectly excluded Savage Spirit AA line This still doesn't fix Untamed Rage ... unsure on that one :( --- common/spdat.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/common/spdat.cpp b/common/spdat.cpp index eeaf2be60..f22a82000 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -672,9 +672,7 @@ bool IsDisciplineBuff(uint16 spell_id) if (!IsValidSpell(spell_id)) return false; - if (spells[spell_id].mana == 0 && spells[spell_id].short_buff_box == 0 && - (spells[spell_id].EndurCost || spells[spell_id].EndurUpkeep) && - spells[spell_id].targettype == ST_Self) + if (spells[spell_id].IsDisciplineBuff && spells[spell_id].targettype == ST_Self) return true; return false; From 051f9ffab9e8ad5728a7fd70f0a4ddfe9d25f109 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Mon, 8 Aug 2016 15:22:26 -0400 Subject: [PATCH 29/78] fix bard song mods on instant spells (nukes, procs) --- zone/spell_effects.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 2a48b9e99..b567bae83 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -201,7 +201,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove continue; effect = spell.effectid[i]; - effect_value = CalcSpellEffectValue(spell_id, i, caster_level, buffslot > -1 ? buffs[buffslot].instrument_mod : 10, caster ? caster : this); + // if buff slot, use instrument mod there, otherwise calc it + uint32 inst = buffslot > -1 ? buffs[buffslot].instrument_mod : caster ? caster->GetInstrumentMod(spell_id) : 10; + effect_value = CalcSpellEffectValue(spell_id, i, caster_level, inst, caster ? caster : this); if(spell_id == SPELL_LAY_ON_HANDS && caster && caster->GetAA(aaImprovedLayOnHands)) effect_value = GetMaxHP(); From 27f6826fd34ccb6f87a31df424ada722bbf8b2ba Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Mon, 8 Aug 2016 20:21:38 -0400 Subject: [PATCH 30/78] Add rule Spells:AllowItemTGB for custom servers --- common/ruletypes.h | 1 + zone/spells.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index 8ef840c96..562d4580f 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -379,6 +379,7 @@ RULE_BOOL(Spells, UseAdditiveFocusFromWornSlot, false) // Allows an additive foc RULE_BOOL(Spells, AlwaysSendTargetsBuffs, false) // ignore LAA level if true RULE_BOOL(Spells, FlatItemExtraSpellAmt, false) // allow SpellDmg stat to affect all spells, regardless of cast time/cooldown/etc RULE_BOOL(Spells, IgnoreSpellDmgLvlRestriction, false) // ignore the 5 level spread on applying SpellDmg +RULE_BOOL(Spells, AllowItemTGB, false) // TGB doesn't work with items on live, custom servers want it though RULE_CATEGORY_END() RULE_CATEGORY(Combat) diff --git a/zone/spells.cpp b/zone/spells.cpp index 0eebcb082..838d37162 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -1683,7 +1683,7 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce case ST_Group: case ST_GroupNoPets: { - if(IsClient() && CastToClient()->TGB() && IsTGBCompatibleSpell(spell_id) && slot != CastingSlot::Item) { + if(IsClient() && CastToClient()->TGB() && IsTGBCompatibleSpell(spell_id) && (slot != CastingSlot::Item || RuleB(Spells, AllowItemTGB))) { if( (!target) || (target->IsNPC() && !(target->GetOwner() && target->GetOwner()->IsClient())) || (target->IsCorpse()) ) From 7d62b208ca7aca46289079e7974668e86a4ebfb7 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Tue, 9 Aug 2016 21:30:10 -0400 Subject: [PATCH 31/78] Revert "Rate limit saving to at most once a second" This reverts commit f26dce39c33c1eac53a453f3629fa98f1b101473. No easy mode I guess --- zone/client.cpp | 6 ------ zone/client.h | 3 --- 2 files changed, 9 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index 11799fb9e..cc07fff2c 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -157,7 +157,6 @@ Client::Client(EQStreamInterface* ieqs) m_ZoneSummonLocation(-2.0f,-2.0f,-2.0f), m_AutoAttackPosition(0.0f, 0.0f, 0.0f, 0.0f), m_AutoAttackTargetLocation(0.0f, 0.0f, 0.0f), - m_lastsave(-1), last_region_type(RegionTypeUnsupported) { for(int cf=0; cf < _FilterCount; cf++) @@ -577,10 +576,6 @@ bool Client::Save(uint8 iCommitNow) { if(!ClientDataLoaded()) return false; - // saved less than 2 seconds ago, lets just skip for now - if ((time(nullptr) - m_lastsave) < 2) - return true; - /* Wrote current basics to PP for saves */ m_pp.x = m_Position.x; m_pp.y = m_Position.y; @@ -670,7 +665,6 @@ bool Client::Save(uint8 iCommitNow) { database.SaveCharacterData(this->CharacterID(), this->AccountID(), &m_pp, &m_epp); /* Save Character Data */ - m_lastsave = time(nullptr); return true; } diff --git a/zone/client.h b/zone/client.h index 819d6ed17..dc04fbc1e 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1463,9 +1463,6 @@ private: Timer helm_toggle_timer; Timer light_update_timer; - - time_t m_lastsave; - glm::vec3 m_Proximity; void BulkSendInventoryItems(); From 38d3f9b7c0a8764cb5252f7056734fb4779c69d9 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Wed, 10 Aug 2016 13:16:32 -0400 Subject: [PATCH 32/78] Client checks song flag first This fixes bugs with buffs marked as disc and song (they go to the song window now) Before the client got confused and gave up displaying them at all! --- zone/spells.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zone/spells.cpp b/zone/spells.cpp index 838d37162..6f7cc47b7 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -3060,19 +3060,19 @@ uint32 Mob::GetLastBuffSlot(bool disc, bool song) uint32 Client::GetFirstBuffSlot(bool disc, bool song) { - if (disc) - return GetMaxBuffSlots() + GetMaxSongSlots(); if (song) return GetMaxBuffSlots(); + if (disc) + return GetMaxBuffSlots() + GetMaxSongSlots(); return 0; } uint32 Client::GetLastBuffSlot(bool disc, bool song) { - if (disc) - return GetMaxBuffSlots() + GetMaxSongSlots() + GetCurrentDiscSlots(); if (song) return GetMaxBuffSlots() + GetCurrentSongSlots(); + if (disc) + return GetMaxBuffSlots() + GetMaxSongSlots() + GetCurrentDiscSlots(); return GetCurrentBuffSlots(); } From e90e141a796fadbc0d099179c0cc0afc20fed692 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Wed, 10 Aug 2016 23:51:06 -0400 Subject: [PATCH 33/78] std::unordered_map::count is much slower than find --- zone/entity.cpp | 45 +++++++++++++++++++++++++++++++++++---------- zone/entity.h | 42 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 71 insertions(+), 16 deletions(-) diff --git a/zone/entity.cpp b/zone/entity.cpp index 5da1190d0..2e2f79058 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -923,12 +923,18 @@ bool EntityList::MakeDoorSpawnPacket(EQApplicationPacket *app, Client *client) Entity *EntityList::GetEntityMob(uint16 id) { - return mob_list.count(id) ? mob_list.at(id) : nullptr; + auto it = mob_list.find(id); + if (it != mob_list.end()) + return it->second; + return nullptr; } Entity *EntityList::GetEntityMerc(uint16 id) { - return merc_list.count(id) ? merc_list.at(id) : nullptr; + auto it = merc_list.find(id); + if (it != merc_list.end()) + return it->second; + return nullptr; } Entity *EntityList::GetEntityMob(const char *name) @@ -948,12 +954,18 @@ Entity *EntityList::GetEntityMob(const char *name) Entity *EntityList::GetEntityDoor(uint16 id) { - return door_list.count(id) ? door_list.at(id) : nullptr; + auto it = door_list.find(id); + if (it != door_list.end()) + return it->second; + return nullptr; } Entity *EntityList::GetEntityCorpse(uint16 id) { - return corpse_list.count(id) ? corpse_list.at(id) : nullptr; + auto it = corpse_list.find(id); + if (it != corpse_list.end()) + return it->second; + return nullptr; } Entity *EntityList::GetEntityCorpse(const char *name) @@ -973,22 +985,34 @@ Entity *EntityList::GetEntityCorpse(const char *name) Entity *EntityList::GetEntityTrap(uint16 id) { - return trap_list.count(id) ? trap_list.at(id) : nullptr; + auto it = trap_list.find(id); + if (it != trap_list.end()) + return it->second; + return nullptr; } Entity *EntityList::GetEntityObject(uint16 id) { - return object_list.count(id) ? object_list.at(id) : nullptr; + auto it = object_list.find(id); + if (it != object_list.end()) + return it->second; + return nullptr; } Entity *EntityList::GetEntityBeacon(uint16 id) { - return beacon_list.count(id) ? beacon_list.at(id) : nullptr; + auto it = beacon_list.find(id); + if (it != beacon_list.end()) + return it->second; + return nullptr; } Entity *EntityList::GetEntityEncounter(uint16 id) { - return encounter_list.count(id) ? encounter_list.at(id) : nullptr; + auto it = encounter_list.find(id); + if (it != encounter_list.end()) + return it->second; + return nullptr; } Entity *EntityList::GetID(uint16 get_id) @@ -1184,6 +1208,8 @@ void EntityList::ChannelMessage(Mob *from, uint8 chan_num, uint8 language, void EntityList::ChannelMessageSend(Mob *to, uint8 chan_num, uint8 language, const char *message, ...) { + if (!to->IsClient()) + return; va_list argptr; char buffer[4096]; @@ -1191,8 +1217,7 @@ void EntityList::ChannelMessageSend(Mob *to, uint8 chan_num, uint8 language, con vsnprintf(buffer, 4096, message, argptr); va_end(argptr); - if (client_list.count(to->GetID())) - client_list.at(to->GetID())->ChannelMessageSend(0, 0, chan_num, language, buffer); + to->CastToClient()->ChannelMessageSend(0, 0, chan_num, language, buffer); } void EntityList::SendZoneSpawns(Client *client) diff --git a/zone/entity.h b/zone/entity.h index 90a372af8..8abb34ca2 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -148,14 +148,29 @@ public: bool IsMobSpawnedByNpcTypeID(uint32 get_id); Mob *GetTargetForVirus(Mob* spreader, int range); inline NPC *GetNPCByID(uint16 id) - { return npc_list.count(id) ? npc_list.at(id) : nullptr; } + { + auto it = npc_list.find(id); + if (it != npc_list.end()) + return it->second; + return nullptr; + } NPC *GetNPCByNPCTypeID(uint32 npc_id); inline Merc *GetMercByID(uint16 id) - { return merc_list.count(id) ? merc_list.at(id) : nullptr; } + { + auto it = merc_list.find(id); + if (it != merc_list.end()) + return it->second; + return nullptr; + } Client *GetClientByName(const char *name); Client *GetClientByAccID(uint32 accid); inline Client *GetClientByID(uint16 id) - { return client_list.count(id) ? client_list.at(id) : nullptr; } + { + auto it = client_list.find(id); + if (it != client_list.end()) + return it->second; + return nullptr; + } Client *GetClientByCharID(uint32 iCharID); Client *GetClientByWID(uint32 iWID); Client *GetClient(uint32 ip, uint16 port); @@ -172,7 +187,12 @@ public: Corpse *GetCorpseByOwner(Client* client); Corpse *GetCorpseByOwnerWithinRange(Client* client, Mob* center, int range); inline Corpse *GetCorpseByID(uint16 id) - { return corpse_list.count(id) ? corpse_list.at(id) : nullptr; } + { + auto it = corpse_list.find(id); + if (it != corpse_list.end()) + return it->second; + return nullptr; + } Corpse *GetCorpseByDBID(uint32 dbid); Corpse *GetCorpseByName(const char* name); @@ -181,10 +201,20 @@ public: Client* FindCorpseDragger(uint16 CorpseID); inline Object *GetObjectByID(uint16 id) - { return object_list.count(id) ? object_list.at(id) : nullptr; } + { + auto it = object_list.find(id); + if (it != object_list.end()) + return it->second; + return nullptr; + } Object *GetObjectByDBID(uint32 id); inline Doors *GetDoorsByID(uint16 id) - { return door_list.count(id) ? door_list.at(id) : nullptr; } + { + auto it = door_list.find(id); + if (it != door_list.end()) + return it->second; + return nullptr; + } Doors *GetDoorsByDoorID(uint32 id); Doors *GetDoorsByDBID(uint32 id); void RemoveAllCorpsesByCharID(uint32 charid); From c1c9ec2790586aa5f275afd919f4087d3556feef Mon Sep 17 00:00:00 2001 From: Russell Kinasz Date: Thu, 11 Aug 2016 11:04:03 -0700 Subject: [PATCH 34/78] Prevent crash in spell casting when group doesn't exist --- zone/spells.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/zone/spells.cpp b/zone/spells.cpp index 6f7cc47b7..855a2917c 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -1729,7 +1729,9 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce { if(IsGrouped()) { - group_id_caster = GetGroup()->GetID(); + if (Group* group = GetGroup()) { + group_id_caster = group->GetID(); + } } else if(IsRaidGrouped()) { @@ -1744,7 +1746,9 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce Mob *owner = GetOwner(); if(owner->IsGrouped()) { - group_id_caster = owner->GetGroup()->GetID(); + if (Group* group = owner->GetGroup()) { + group_id_caster = group->GetID(); + } } else if(owner->IsRaidGrouped()) { @@ -1773,7 +1777,9 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce { if(spell_target->IsGrouped()) { - group_id_target = spell_target->GetGroup()->GetID(); + if (Group* group = spell_target->GetGroup()) { + group_id_target = group->GetID(); + } } else if(spell_target->IsRaidGrouped()) { @@ -1788,7 +1794,9 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce Mob *owner = spell_target->GetOwner(); if(owner->IsGrouped()) { - group_id_target = owner->GetGroup()->GetID(); + if (Group* group = owner->GetGroup()) { + group_id_target = group->GetID(); + } } else if(owner->IsRaidGrouped()) { From 039e0fbb83bd704d978d78d66bae12cfbab97f8a Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Fri, 12 Aug 2016 21:52:46 -0400 Subject: [PATCH 35/78] NPC innate procs overwrite TargetType to ST_Target --- zone/mob.cpp | 2 +- zone/mob.h | 2 +- zone/mob_ai.cpp | 4 +++- zone/npc.cpp | 1 + zone/npc.h | 2 ++ zone/spells.cpp | 9 +++++++-- 6 files changed, 15 insertions(+), 5 deletions(-) diff --git a/zone/mob.cpp b/zone/mob.cpp index e056a81c2..accd36bb0 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -3223,7 +3223,7 @@ void Mob::ExecWeaponProc(const ItemInst *inst, uint16 spell_id, Mob *on, int lev if(twinproc_chance && zone->random.Roll(twinproc_chance)) twinproc = true; - if (IsBeneficialSpell(spell_id)) { + if (IsBeneficialSpell(spell_id) && (!IsNPC() || (IsNPC() && CastToNPC()->GetInnateProcSpellID() != spell_id))) { // NPC innate procs don't take this path ever SpellFinished(spell_id, this, EQEmu::CastingSlot::Item, 0, -1, spells[spell_id].ResistDiff, true, level_override); if(twinproc) SpellOnTarget(spell_id, this, false, false, 0, true, level_override); diff --git a/zone/mob.h b/zone/mob.h index a01a4b8f5..b80667b02 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -265,7 +265,7 @@ public: bool use_resist_adjust = false, int16 resist_adjust = 0, bool isproc = false, int level_override = -1); virtual bool SpellEffect(Mob* caster, uint16 spell_id, float partial = 100, int level_override = -1); virtual bool DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_center, - CastAction_type &CastAction, EQEmu::CastingSlot slot); + CastAction_type &CastAction, EQEmu::CastingSlot slot, bool isproc = false); virtual bool CheckFizzle(uint16 spell_id); virtual bool CheckSpellLevelRestriction(uint16 spell_id); virtual bool IsImmuneToSpell(uint16 spell_id, Mob *caster); diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index eb795f6c3..eb14865ce 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -2362,8 +2362,10 @@ bool NPC::AI_AddNPCSpells(uint32 iDBSpellsID) { return a.priority > b.priority; }); - if (IsValidSpell(attack_proc_spell)) + if (IsValidSpell(attack_proc_spell)) { AddProcToWeapon(attack_proc_spell, true, proc_chance); + innate_proc_spell_id = attack_proc_spell; + } if (IsValidSpell(range_proc_spell)) AddRangedProc(range_proc_spell, (rproc_chance + 100)); diff --git a/zone/npc.cpp b/zone/npc.cpp index 15e96f3bf..1fc27dc5a 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -233,6 +233,7 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, const glm::vec4& position, int if npc_spells_id = 0; HasAISpell = false; HasAISpellEffects = false; + innate_proc_spell_id = 0; if(GetClass() == MERCERNARY_MASTER && RuleB(Mercs, AllowMercs)) { diff --git a/zone/npc.h b/zone/npc.h index 0023d4273..d3e7928a2 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -407,6 +407,7 @@ public: void mod_npc_killed_merit(Mob* c); void mod_npc_killed(Mob* oos); void AISpellsList(Client *c); + uint16 GetInnateProcSpellID() const { return innate_proc_spell_id; } uint32 GetHeroForgeModel() const { return herosforgemodel; } void SetHeroForgeModel(uint32 model) { herosforgemodel = model; } @@ -454,6 +455,7 @@ protected: virtual bool AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = 0); AISpellsVar_Struct AISpellVar; int16 GetFocusEffect(focusType type, uint16 spell_id); + uint16 innate_proc_spell_id; uint32 npc_spells_effects_id; std::vector AIspellsEffects; diff --git a/zone/spells.cpp b/zone/spells.cpp index 855a2917c..38c38b212 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -1349,7 +1349,7 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo } -bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_center, CastAction_type &CastAction, CastingSlot slot) +bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_center, CastAction_type &CastAction, CastingSlot slot, bool isproc) { /* The basic types of spells: @@ -1396,6 +1396,11 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce targetType = ST_GroupClientAndPet; } + // NPC innate procs override the target type to single target. + // Yes. This code will cause issues if they have the proc as innate AND on a weapon. Oh well. + if (isproc && IsNPC() && CastToNPC()->GetInnateProcSpellID() == spell_id) + targetType = ST_Target; + if (spell_target && !spell_target->PassCastRestriction(true, spells[spell_id].CastRestriction)){ Message_StringID(13,SPELL_NEED_TAR); return false; @@ -1983,7 +1988,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, ui //determine the type of spell target we have CastAction_type CastAction; - if(!DetermineSpellTargets(spell_id, spell_target, ae_center, CastAction, slot)) + if(!DetermineSpellTargets(spell_id, spell_target, ae_center, CastAction, slot, isproc)) return(false); Log.Out(Logs::Detail, Logs::Spells, "Spell %d: target type %d, target %s, AE center %s", spell_id, CastAction, spell_target?spell_target->GetName():"NONE", ae_center?ae_center->GetName():"NONE"); From 4e4d82857c09e40e68155aa7e5ff44572aec2644 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Fri, 12 Aug 2016 22:07:03 -0400 Subject: [PATCH 36/78] Move OP_BeginCast above instant cast shortcut For casted seplls, we should always see this. Mostly this shortcut breaks spell awareness for NPC spells. (most of them are instant cast) --- zone/spells.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/zone/spells.cpp b/zone/spells.cpp index 38c38b212..2144d3276 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -345,7 +345,6 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, { Mob* pMob = nullptr; int32 orgcasttime; - EQApplicationPacket *outapp = nullptr; if(!IsValidSpell(spell_id)) { InterruptSpell(); @@ -455,6 +454,18 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, Log.Out(Logs::Detail, Logs::Spells, "Spell %d: Casting time %d (orig %d), mana cost %d", spell_id, cast_time, orgcasttime, mana_cost); + // now tell the people in the area -- we ALWAYS want to send this, even instant cast spells. + // The only time this is skipped is for NPC innate procs and weapon procs. Procs from buffs + // oddly still send this. Since those cases don't reach here, we don't need to check them + auto outapp = new EQApplicationPacket(OP_BeginCast,sizeof(BeginCast_Struct)); + BeginCast_Struct* begincast = (BeginCast_Struct*)outapp->pBuffer; + begincast->caster_id = GetID(); + begincast->spell_id = spell_id; + begincast->cast_time = orgcasttime; // client calculates reduced time by itself + outapp->priority = 3; + entity_list.QueueCloseClients(this, outapp, false, 200, 0, true); //IsClient() ? FILTER_PCSPELLS : FILTER_NPCSPELLS); + safe_delete(outapp); + // cast time is 0, just finish it right now and be done with it if(cast_time == 0) { CastedSpellFinished(spell_id, target_id, slot, mana_cost, item_slot, resist_adjust); @@ -478,17 +489,6 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, if (oSpellWillFinish) *oSpellWillFinish = Timer::GetCurrentTime() + cast_time + 100; - // now tell the people in the area - outapp = new EQApplicationPacket(OP_BeginCast,sizeof(BeginCast_Struct)); - BeginCast_Struct* begincast = (BeginCast_Struct*)outapp->pBuffer; - begincast->caster_id = GetID(); - begincast->spell_id = spell_id; - begincast->cast_time = orgcasttime; // client calculates reduced time by itself - outapp->priority = 3; - entity_list.QueueCloseClients(this, outapp, false, 200, 0, true); //IsClient() ? FILTER_PCSPELLS : FILTER_NPCSPELLS); - safe_delete(outapp); - outapp = nullptr; - if (IsClient() && slot == CastingSlot::Item && item_slot != 0xFFFFFFFF) { auto item = CastToClient()->GetInv().GetItem(item_slot); if (item && item->GetItem()) From 50de63117d25d97f9450f9628c2a8c36e9a04e1f Mon Sep 17 00:00:00 2001 From: Kinglykrab Date: Sat, 13 Aug 2016 07:19:58 -0400 Subject: [PATCH 37/78] Added optional avoidance cap rules. Check changelog.txt. --- changelog.txt | 7 +++++++ common/ruletypes.h | 2 ++ zone/client_mods.cpp | 14 ++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/changelog.txt b/changelog.txt index db7133b0b..4f71af6d5 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,12 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 08/13/2016 == +Kinglykrab: Implemented optional avoidance cap rules. + - Serves to eliminate God-like characters on custom servers with high item stats + - 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%) diff --git a/common/ruletypes.h b/common/ruletypes.h index 562d4580f..c19ebee45 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -142,6 +142,8 @@ RULE_INT(Character, InvSnapshotMinRetryM, 30) // Time (in minutes) to re-attempt RULE_INT(Character, InvSnapshotHistoryD, 30) // Time (in days) to keep snapshot entries RULE_BOOL(Character, RestrictSpellScribing, false) // Restricts spell scribing to allowable races/classes of spell scroll, if true RULE_BOOL(Character, UseStackablePickPocketing, true) // Allows stackable pickpocketed items to stack instead of only being allowed in empty inventory slots +RULE_BOOL(Character, EnableAvoidanceCap, false) +RULE_INT(Character, AvoidanceCap, 750) // 750 Is a pretty good value, seen people dodge all attacks beyond 1,000 Avoidance RULE_CATEGORY_END() RULE_CATEGORY(Mercs) diff --git a/zone/client_mods.cpp b/zone/client_mods.cpp index 804e8df62..c57fc340f 100644 --- a/zone/client_mods.cpp +++ b/zone/client_mods.cpp @@ -1036,6 +1036,13 @@ int32 Client::CalcAC() 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 @@ -1113,6 +1120,13 @@ int32 Client::GetACAvoid() if (avoidance < 0) { avoidance = 0; } + + if (RuleB(Character, EnableAvoidanceCap)) { + if ((avoidance * 1000 / 847) > RuleI(Character, AvoidanceCap)) { + return RuleI(Character, AvoidanceCap); + } + } + return (avoidance * 1000 / 847); } From f01c8909664c3521809de46a1a2e49fe2a0c2660 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sat, 13 Aug 2016 15:09:43 -0400 Subject: [PATCH 38/78] Crash fix The other thing needs to be looked at too, but I guess we never run into an issue where this actually is a nullptr because bad things would happen here ... --- common/shareddb.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/shareddb.cpp b/common/shareddb.cpp index c9213f3a2..fd89831c0 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -455,7 +455,7 @@ bool SharedDatabase::GetSharedBank(uint32 id, Inventory *inv, bool is_charid) } } - if (row[9]) { + if (inst && row[9]) { std::string data_str(row[9]); std::string idAsString; std::string value; @@ -480,6 +480,7 @@ bool SharedDatabase::GetSharedBank(uint32 id, Inventory *inv, bool is_charid) } } + // theoretically inst can be nullptr ... this would be very bad ... put_slot_id = inv->PutItem(slot_id, *inst); safe_delete(inst); From 97dc0a84dd57d96afb30906c1a75332e354b8bd6 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sat, 13 Aug 2016 15:14:31 -0400 Subject: [PATCH 39/78] Fix logic paren issue --- world/client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/world/client.cpp b/world/client.cpp index 460fa294d..d2ca3705a 100644 --- a/world/client.cpp +++ b/world/client.cpp @@ -142,7 +142,7 @@ void Client::SendLogServer() if(RuleB(World, IsGMPetitionWindowEnabled)) l->enable_petition_wnd = 1; - if(RuleI(World, FVNoDropFlag) == 1 || RuleI(World, FVNoDropFlag) == 2 && GetAdmin() > RuleI(Character, MinStatusForNoDropExemptions)) + if((RuleI(World, FVNoDropFlag) == 1 || RuleI(World, FVNoDropFlag) == 2) && GetAdmin() > RuleI(Character, MinStatusForNoDropExemptions)) l->enable_FV = 1; QueuePacket(outapp); From 488c4941d2fb988545f4fe430e75d37f496d2be5 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sat, 13 Aug 2016 15:19:10 -0400 Subject: [PATCH 40/78] Fix potential crash --- world/zonelist.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/world/zonelist.cpp b/world/zonelist.cpp index abd5c1057..55600c367 100644 --- a/world/zonelist.cpp +++ b/world/zonelist.cpp @@ -88,7 +88,7 @@ void ZSList::Process() { Process(); CatchSignal(2); } - if(reminder && reminder->Check()){ + if(reminder && reminder->Check() && shutdowntimer){ SendEmoteMessage(0,0,0,15,":SYSTEM MSG:World coming down, everyone log out now. World will shut down in %i minutes...", ((shutdowntimer->GetRemainingTime()/1000) / 60)); } LinkedListIterator iterator(list); From 7a4c9b36a8300824ee4f1f7c5caafd08a456aa72 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sat, 13 Aug 2016 15:20:48 -0400 Subject: [PATCH 41/78] Fix logic issue in SendAlternateAdvancementRank --- zone/aa.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/aa.cpp b/zone/aa.cpp index 1640a9eff..97b444311 100644 --- a/zone/aa.cpp +++ b/zone/aa.cpp @@ -867,7 +867,7 @@ void Client::SendAlternateAdvancementRank(int aa_id, int level) { aai->max_level = ability->GetMaxLevel(this); aai->prev_id = rank->prev_id; - if(rank->next && !CanUseAlternateAdvancementRank(rank->next) || ability->charges > 0) { + if((rank->next && !CanUseAlternateAdvancementRank(rank->next)) || ability->charges > 0) { aai->next_id = -1; } else { aai->next_id = rank->next_id; From 00cfe2d25fd68b448e0532c48f2e6ae8543b945e Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sat, 13 Aug 2016 15:26:07 -0400 Subject: [PATCH 42/78] Fix potential crashes in attack.cpp --- zone/attack.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zone/attack.cpp b/zone/attack.cpp index 2b37be197..af5a90540 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -3261,7 +3261,7 @@ void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, cons a->damage = damage; a->spellid = spell_id; a->special = special; - a->meleepush_xy = attacker->GetHeading() * 2.0f; + a->meleepush_xy = attacker ? attacker->GetHeading() * 2.0f : 0.0f; if (RuleB(Combat, MeleePush) && damage > 0 && !IsRooted() && (IsClient() || zone->random.Roll(RuleI(Combat, MeleePushChance)))) { a->force = EQEmu::skills::GetSkillMeleePushForce(skill_used); @@ -3798,7 +3798,7 @@ void Mob::TryPetCriticalHit(Mob *defender, uint16 skill, int32 &damage) void Mob::TryCriticalHit(Mob *defender, uint16 skill, int32 &damage, ExtraAttackOptions *opts) { - if(damage < 1) + 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 @@ -3819,8 +3819,8 @@ void Mob::TryCriticalHit(Mob *defender, uint16 skill, int32 &damage, ExtraAttack bool IsBerskerSPA = false; //1: Try Slay Undead - if (defender && (defender->GetBodyType() == BT_Undead || - defender->GetBodyType() == BT_SummonedUndead || defender->GetBodyType() == BT_Vampire)) { + 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; From ef3cf099b84a062202c307ed5b4df2c7794334e1 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sat, 13 Aug 2016 15:35:28 -0400 Subject: [PATCH 43/78] Fix potential crash in Sacrifice --- zone/client.cpp | 98 ++++++++++++++++++++++++------------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index cc07fff2c..a943598c0 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -3683,58 +3683,58 @@ void Client::SacrificeConfirm(Client *caster) //Essentially a special case death function void Client::Sacrifice(Client *caster) { - if(GetLevel() >= RuleI(Spells, SacrificeMinLevel) && GetLevel() <= RuleI(Spells, SacrificeMaxLevel)){ - int exploss = (int)(GetLevel() * (GetLevel() / 18.0) * 12000); - if(exploss < GetEXP()){ - SetEXP(GetEXP()-exploss, GetAAXP()); - SendLogoutPackets(); + if (GetLevel() >= RuleI(Spells, SacrificeMinLevel) && GetLevel() <= RuleI(Spells, SacrificeMaxLevel)) { + int exploss = (int)(GetLevel() * (GetLevel() / 18.0) * 12000); + if (exploss < GetEXP()) { + SetEXP(GetEXP() - exploss, GetAAXP()); + SendLogoutPackets(); - //make our become corpse packet, and queue to ourself before OP_Death. - EQApplicationPacket app2(OP_BecomeCorpse, sizeof(BecomeCorpse_Struct)); - BecomeCorpse_Struct* bc = (BecomeCorpse_Struct*)app2.pBuffer; - bc->spawn_id = GetID(); - bc->x = GetX(); - bc->y = GetY(); - bc->z = GetZ(); - QueuePacket(&app2); + // make our become corpse packet, and queue to ourself before OP_Death. + EQApplicationPacket app2(OP_BecomeCorpse, sizeof(BecomeCorpse_Struct)); + BecomeCorpse_Struct *bc = (BecomeCorpse_Struct *)app2.pBuffer; + bc->spawn_id = GetID(); + bc->x = GetX(); + bc->y = GetY(); + bc->z = GetZ(); + QueuePacket(&app2); - // make death packet - EQApplicationPacket app(OP_Death, sizeof(Death_Struct)); - Death_Struct* d = (Death_Struct*)app.pBuffer; - d->spawn_id = GetID(); - d->killer_id = caster ? caster->GetID() : 0; - d->bindzoneid = GetPP().binds[0].zoneId; - d->spell_id = SPELL_UNKNOWN; - d->attack_skill = 0xe7; - d->damage = 0; - app.priority = 6; - entity_list.QueueClients(this, &app); + // make death packet + EQApplicationPacket app(OP_Death, sizeof(Death_Struct)); + Death_Struct *d = (Death_Struct *)app.pBuffer; + d->spawn_id = GetID(); + d->killer_id = caster ? caster->GetID() : 0; + d->bindzoneid = GetPP().binds[0].zoneId; + d->spell_id = SPELL_UNKNOWN; + d->attack_skill = 0xe7; + d->damage = 0; + app.priority = 6; + entity_list.QueueClients(this, &app); - BuffFadeAll(); - UnmemSpellAll(); - Group *g = GetGroup(); - if(g){ - g->MemberZoned(this); - } - Raid *r = entity_list.GetRaidByClient(this); - if(r){ - r->MemberZoned(this); - } - ClearAllProximities(); - if(RuleB(Character, LeaveCorpses)){ - auto new_corpse = new Corpse(this, 0); - entity_list.AddCorpse(new_corpse, GetID()); - SetID(0); - entity_list.QueueClients(this, &app2, true); - } - Save(); - GoToDeath(); - caster->SummonItem(RuleI(Spells, SacrificeItemID)); - } - } - else{ - caster->Message_StringID(13, SAC_TOO_LOW); //This being is not a worthy sacrifice. - } + BuffFadeAll(); + UnmemSpellAll(); + Group *g = GetGroup(); + if (g) { + g->MemberZoned(this); + } + Raid *r = entity_list.GetRaidByClient(this); + if (r) { + r->MemberZoned(this); + } + ClearAllProximities(); + if (RuleB(Character, LeaveCorpses)) { + auto new_corpse = new Corpse(this, 0); + entity_list.AddCorpse(new_corpse, GetID()); + SetID(0); + entity_list.QueueClients(this, &app2, true); + } + Save(); + GoToDeath(); + if (caster) // I guess it's possible? + caster->SummonItem(RuleI(Spells, SacrificeItemID)); + } + } else { + caster->Message_StringID(13, SAC_TOO_LOW); // This being is not a worthy sacrifice. + } } void Client::SendOPTranslocateConfirm(Mob *Caster, uint16 SpellID) { From ab35f8b842753c3c7ddf1dde5057d25a4dccc1aa Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sat, 13 Aug 2016 15:39:08 -0400 Subject: [PATCH 44/78] Fix memset in QuestReward --- zone/client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/client.cpp b/zone/client.cpp index a943598c0..76e3ce8c4 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -8405,7 +8405,7 @@ void Client::SendColoredText(uint32 color, std::string message) void Client::QuestReward(Mob* target, uint32 copper, uint32 silver, uint32 gold, uint32 platinum, uint32 itemid, uint32 exp, bool faction) { auto outapp = new EQApplicationPacket(OP_Sound, sizeof(QuestReward_Struct)); - memset(outapp->pBuffer, 0, sizeof(outapp->pBuffer)); + memset(outapp->pBuffer, 0, sizeof(QuestReward_Struct)); QuestReward_Struct* qr = (QuestReward_Struct*)outapp->pBuffer; qr->mob_id = target->GetID(); // Entity ID for the from mob name From a8db4532d020e4fbd051c69145477019d0836742 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sat, 13 Aug 2016 15:46:35 -0400 Subject: [PATCH 45/78] Fix potential crash in #iteminfo --- zone/command.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/zone/command.cpp b/zone/command.cpp index 95770097d..16001d670 100644 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -4339,7 +4339,10 @@ void command_goto(Client *c, const Seperator *sep) void command_iteminfo(Client *c, const Seperator *sep) { auto inst = c->GetInv()[EQEmu::legacy::SlotCursor]; - if (!inst) { c->Message(13, "Error: You need an item on your cursor for this command"); } + if (!inst) { + c->Message(13, "Error: You need an item on your cursor for this command"); + return; + } auto item = inst->GetItem(); if (!item) { Log.Out(Logs::General, Logs::Inventory, "(%s) Command #iteminfo processed an item with no data pointer"); From 3efc925264f1ba8b940bd5432f3662f1751b7b8c Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sat, 13 Aug 2016 15:47:44 -0400 Subject: [PATCH 46/78] And another --- zone/command.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/zone/command.cpp b/zone/command.cpp index 16001d670..4c60ad65b 100644 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -4347,6 +4347,7 @@ void command_iteminfo(Client *c, const Seperator *sep) if (!item) { Log.Out(Logs::General, Logs::Inventory, "(%s) Command #iteminfo processed an item with no data pointer"); c->Message(13, "Error: This item has no data reference"); + return; } EQEmu::SayLinkEngine linker; From f06a9b3dcea8cd721675f3b3765591b6de800b77 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sat, 13 Aug 2016 15:58:02 -0400 Subject: [PATCH 47/78] use std::abs in Map::FindClosestZ --- zone/map.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/map.cpp b/zone/map.cpp index 630e277c2..29a386331 100644 --- a/zone/map.cpp +++ b/zone/map.cpp @@ -163,7 +163,7 @@ float Map::FindClosestZ(glm::vec3 &start, glm::vec3 *result) const { to.z = -BEST_Z_INVALID; hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance); if (hit) { - if (abs(from.z - result->z) < abs(ClosestZ - from.z)) + if (std::abs(from.z - result->z) < std::abs(ClosestZ - from.z)) return result->z; } From 8ce2921e3d3d9efd143ebdf27ce88b43e78e169d Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sat, 13 Aug 2016 16:45:32 -0400 Subject: [PATCH 48/78] Fix potential crashes in Mob::SpellEffect --- zone/spell_effects.cpp | 71 +++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index b567bae83..9dee868c7 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -357,8 +357,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; int32 val = 0; - val = 7500*effect_value; - val = caster->GetActSpellHealing(spell_id, val, this); + val = 7500 * effect_value; + if (caster) + val = caster->GetActSpellHealing(spell_id, val, this); if (val > 0) HealDamage(val, caster); @@ -377,12 +378,14 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove snprintf(effect_desc, _EDLEN, "Current Mana: %+i", effect_value); #endif SetMana(GetMana() + effect_value); - caster->SetMana(caster->GetMana() + std::abs(effect_value)); + if (caster) + caster->SetMana(caster->GetMana() + std::abs(effect_value)); if (effect_value < 0) TryTriggerOnValueAmount(false, true); #ifdef SPELL_EFFECT_SPAM - caster->Message(0, "You have gained %+i mana!", effect_value); + if (caster) + caster->Message(0, "You have gained %+i mana!", effect_value); #endif } } @@ -534,7 +537,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove } } - if (effect == SE_GateCastersBindpoint && caster->IsClient()) + if (effect == SE_GateCastersBindpoint && caster && caster->IsClient()) { // Teleport Bind uses caster's bind point int index = spells[spell_id].base[i] - 1; if (index < 0 || index > 4) @@ -650,7 +653,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove //Added client messages to give some indication this effect is active. // Is there a message generated? Too disgusted by raids. uint32 time = spell.base[i] * 10 * 1000; - if (caster->IsClient()) { + if (caster && caster->IsClient()) { if (caster->IsGrouped()) { auto group = caster->GetGroup(); for (int i = 0; i < 6; ++i) @@ -697,7 +700,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove ((GetLevel() > max_level) && caster && (!caster->IsNPC() || (caster->IsNPC() && !RuleB(Spells, NPCIgnoreBaseImmunity)))))) { - caster->Message_StringID(MT_SpellFailure, IMMUNE_STUN); + if (caster) + caster->Message_StringID(MT_SpellFailure, IMMUNE_STUN); } else { int stun_resist = itembonuses.StunResist+spellbonuses.StunResist; if (IsClient()) @@ -706,7 +710,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if (stun_resist <= 0 || zone->random.Int(0,99) >= stun_resist) { Log.Out(Logs::Detail, Logs::Combat, "Stunned. We had %d percent resist chance.", stun_resist); - if (caster->IsClient()) + if (caster && caster->IsClient()) effect_value += effect_value*caster->GetFocusEffect(focusFcStunTimeMod, spell_id)/100; Stun(effect_value); @@ -918,11 +922,11 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove cd->meleepush_xy = action->sequence; CastToClient()->QueuePacket(action_packet); - if(caster->IsClient() && caster != this) + if(caster && caster->IsClient() && caster != this) caster->CastToClient()->QueuePacket(action_packet); CastToClient()->QueuePacket(message_packet); - if(caster->IsClient() && caster != this) + if(caster && caster->IsClient() && caster != this) caster->CastToClient()->QueuePacket(message_packet); CastToClient()->SetBindPoint(spells[spell_id].base[i] - 1); @@ -1033,7 +1037,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if(zone->random.Roll(effect_value)) Gate(spells[spell_id].base2[i] - 1); - else + else if (caster) caster->Message_StringID(MT_SpellFailure,GATE_FAIL); } break; @@ -1045,7 +1049,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove snprintf(effect_desc, _EDLEN, "Cancel Magic: %d", effect_value); #endif if(GetSpecialAbility(UNDISPELLABLE)){ - caster->Message_StringID(MT_SpellFailure, SPELL_NO_EFFECT, spells[spell_id].name); + if (caster) + caster->Message_StringID(MT_SpellFailure, SPELL_NO_EFFECT, spells[spell_id].name); break; } @@ -1055,7 +1060,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove spells[buffs[slot].spellid].dispel_flag == 0 && !IsDiscipline(buffs[slot].spellid)) { - if (TryDispel(caster->GetLevel(),buffs[slot].casterlevel, effect_value)){ + if (caster && TryDispel(caster->GetLevel(),buffs[slot].casterlevel, effect_value)){ BuffFadeBySlot(slot); slot = buff_count; } @@ -1070,7 +1075,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove snprintf(effect_desc, _EDLEN, "Dispel Detrimental: %d", effect_value); #endif if(GetSpecialAbility(UNDISPELLABLE)){ - caster->Message_StringID(MT_SpellFailure, SPELL_NO_EFFECT, spells[spell_id].name); + if (caster) + caster->Message_StringID(MT_SpellFailure, SPELL_NO_EFFECT, spells[spell_id].name); break; } @@ -1080,7 +1086,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove IsDetrimentalSpell(buffs[slot].spellid) && spells[buffs[slot].spellid].dispel_flag == 0) { - if (TryDispel(caster->GetLevel(),buffs[slot].casterlevel, effect_value)){ + if (caster && TryDispel(caster->GetLevel(),buffs[slot].casterlevel, effect_value)){ BuffFadeBySlot(slot); slot = buff_count; } @@ -1095,7 +1101,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove snprintf(effect_desc, _EDLEN, "Dispel Beneficial: %d", effect_value); #endif if(GetSpecialAbility(UNDISPELLABLE)){ - caster->Message_StringID(MT_SpellFailure, SPELL_NO_EFFECT, spells[spell_id].name); + if (caster) + caster->Message_StringID(MT_SpellFailure, SPELL_NO_EFFECT, spells[spell_id].name); break; } @@ -1105,7 +1112,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove IsBeneficialSpell(buffs[slot].spellid) && spells[buffs[slot].spellid].dispel_flag == 0) { - if (TryDispel(caster->GetLevel(),buffs[slot].casterlevel, effect_value)){ + if (caster && TryDispel(caster->GetLevel(),buffs[slot].casterlevel, effect_value)){ BuffFadeBySlot(slot); slot = buff_count; } @@ -1122,7 +1129,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if (buffs[slot].spellid != SPELL_UNKNOWN && IsDetrimentalSpell(buffs[slot].spellid)) { - if (TryDispel(caster->GetLevel(),buffs[slot].casterlevel, effect_value)){ + if (caster && TryDispel(caster->GetLevel(),buffs[slot].casterlevel, effect_value)){ BuffFadeBySlot(slot); } } @@ -1511,7 +1518,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove ((GetLevel() > max_level) && caster && (!caster->IsNPC() || (caster->IsNPC() && !RuleB(Spells, NPCIgnoreBaseImmunity))))) { - caster->Message_StringID(MT_Shout, IMMUNE_STUN); + if (caster) + caster->Message_StringID(MT_Shout, IMMUNE_STUN); } else { @@ -1728,7 +1736,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } } - else { + else if (client) { Raid *r = entity_list.GetRaidByClient(caster->CastToClient()); if(r) { @@ -1768,7 +1776,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove Message_StringID(4, CORPSE_CANT_SENSE); } } - else + else if (caster) caster->Message_StringID(MT_SpellFailure, SPELL_LEVEL_REQ); } else { @@ -2115,7 +2123,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Sacrifice"); #endif - if(!IsClient() || !caster->IsClient()){ + if(!caster || !IsClient() || !caster->IsClient()){ break; } CastToClient()->SacrificeConfirm(caster->CastToClient()); @@ -2124,12 +2132,15 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_SummonPC: { - if(IsClient()){ - CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), caster->GetX(), caster->GetY(), caster->GetZ(), caster->GetHeading(), 2, SummonPC); + if (!caster) + break; + if (IsClient()) { + CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), caster->GetX(), + caster->GetY(), caster->GetZ(), caster->GetHeading(), 2, + SummonPC); Message(15, "You have been summoned!"); entity_list.ClearAggro(this); - } - else + } else caster->Message(13, "This spell can only be cast on players."); break; @@ -2176,6 +2187,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_TemporaryPets: //Dook- swarms and wards: { + if (!caster) + break; // this makes necro epic 1.5/2.0 proc work properly if((spell_id != 6882) && (spell_id != 6884)) // Chaotic Jester/Steadfast Servant { @@ -2271,6 +2284,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove */ int16 focus = 0; int ReuseTime = spells[spell_id].recast_time + spells[spell_id].recovery_time; + if (!caster) + break; focus = caster->GetFocusEffect(focusFcBaseEffects, spell_id); @@ -2294,7 +2309,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove snprintf(effect_desc, _EDLEN, "Wake The Dead"); #endif //meh dupe issue with npc casting this - if(caster->IsClient()){ + if(caster && caster->IsClient()){ int dur = spells[spell_id].max[i]; if (!dur) dur = 60; @@ -2659,7 +2674,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_Taunt: { - if (IsNPC()){ + if (caster && IsNPC()){ caster->Taunt(this->CastToNPC(), false, static_cast(spell.base[i]), true, spell.base2[i]); } break; From 48fb483de6899aa32dbce039b9f8a6ed04458c00 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sat, 13 Aug 2016 17:05:48 -0400 Subject: [PATCH 49/78] Fix typo --- zone/spell_effects.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 9dee868c7..0b67ea523 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -1736,7 +1736,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } } - else if (client) { + else if (caster) { Raid *r = entity_list.GetRaidByClient(caster->CastToClient()); if(r) { From 69f06f736c3fd24f24d7f46331781ed41f84828d Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sat, 13 Aug 2016 17:15:00 -0400 Subject: [PATCH 50/78] Fix potential crashes in zone/spells.cpp --- zone/spells.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/spells.cpp b/zone/spells.cpp index 2144d3276..ea8067b8c 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -3139,7 +3139,7 @@ int Mob::AddBuff(Mob *caster, uint16 spell_id, int duration, int32 level_overrid if (ret == -1) { // stop the spell Log.Out(Logs::Detail, Logs::Spells, "Adding buff %d failed: stacking prevented by spell %d in slot %d with caster level %d", spell_id, curbuf.spellid, buffslot, curbuf.casterlevel); - if (caster->IsClient() && RuleB(Client, UseLiveBlockedMessage)) { + if (caster && caster->IsClient() && RuleB(Client, UseLiveBlockedMessage)) { caster->Message(13, "Your %s did not take hold on %s. (Blocked by %s.)", spells[spell_id].name, this->GetName(), spells[curbuf.spellid].name); } return -1; From 35c1eccbe1415d2ee2acf908527308e05d0cc256 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sat, 13 Aug 2016 17:15:57 -0400 Subject: [PATCH 51/78] Fix potential crash in zone/worldserver.cpp --- zone/worldserver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index 969c4b947..0f00ed0c5 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -765,7 +765,7 @@ void WorldServer::Process() { zone->SetZoneHasCurrentTime(true); } - if (zone->is_zone_time_localized){ + if (zone && zone->is_zone_time_localized){ Log.Out(Logs::General, Logs::Zone_Server, "Received request to sync time from world, but our time is localized currently"); } break; From fd1e425abc6bd3d8e6fee94aa3eacf6b4f3e95d6 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sat, 13 Aug 2016 17:19:20 -0400 Subject: [PATCH 52/78] Fix potential crash in ucs/clientlist.cpp --- ucs/clientlist.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ucs/clientlist.cpp b/ucs/clientlist.cpp index 47e0eb726..f49e02986 100644 --- a/ucs/clientlist.cpp +++ b/ucs/clientlist.cpp @@ -1985,7 +1985,7 @@ void Client::ChannelGrantVoice(std::string CommandString) { return; } - if(RequiredChannel->IsOwner(RequiredClient->GetName()) || RequiredChannel->IsModerator(RequiredClient->GetName())) { + if(RequiredClient && (RequiredChannel->IsOwner(RequiredClient->GetName()) || RequiredChannel->IsModerator(RequiredClient->GetName()))) { GeneralChannelMessage("The channel owner and moderators automatically have voice."); return; From 7f9af238f8803667dfe66b8abb68207255d6f078 Mon Sep 17 00:00:00 2001 From: Uleat Date: Sat, 13 Aug 2016 19:51:12 -0400 Subject: [PATCH 53/78] Fix for potential crash in ItemInst::GetTotalItemCount() --- common/item.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/item.cpp b/common/item.cpp index 41e3d86c3..61e7f926a 100644 --- a/common/item.cpp +++ b/common/item.cpp @@ -1863,6 +1863,9 @@ uint8 ItemInst::FirstOpenSlot() const uint8 ItemInst::GetTotalItemCount() const { + if (!m_item) + return 0; + uint8 item_count = 1; if (m_item && !m_item->IsClassBag()) { return item_count; } From e894e96404181af7cf715ed18028688d252723de Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sun, 14 Aug 2016 23:32:27 -0400 Subject: [PATCH 54/78] Implement Linked Spell Reuse Timers They started linked spells at OoW launch (I think) At least canni was linked then. This is rather user unfriendly, but that's live like. Ex. the spells aren't actually put on cool down so you can attempt to cast them still but you will be interrupted. Titanium is particularly unfriendly with large differences in reuse times --- changelog.txt | 6 ++++++ common/emu_oplist.h | 1 + common/eq_packet_structs.h | 12 +++++++++++ common/ptimer.h | 4 +++- utils/patches/patch_RoF.conf | 1 + utils/patches/patch_RoF2.conf | 1 + utils/patches/patch_SoD.conf | 1 + utils/patches/patch_SoF.conf | 1 + utils/patches/patch_Titanium.conf | 1 + utils/patches/patch_UF.conf | 1 + zone/client.h | 3 +++ zone/spells.cpp | 35 +++++++++++++++++++++++++++++-- 12 files changed, 64 insertions(+), 3 deletions(-) diff --git a/changelog.txt b/changelog.txt index 4f71af6d5..7647ea8ec 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,11 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 08/14/2016 == +mackal: Implement Linked Spell Reuse Timers + - For whatever reason this is a bit unfriendly, but that's how it is on live. + - Titanium is especially unfriendly with large differences in reuse times (ex higher canni and the first 4) + - Unsure when this went live for spells, but canni was at least linked at OoW launch + == 08/13/2016 == Kinglykrab: Implemented optional avoidance cap rules. - Serves to eliminate God-like characters on custom servers with high item stats diff --git a/common/emu_oplist.h b/common/emu_oplist.h index 8b913095b..6b7c58841 100644 --- a/common/emu_oplist.h +++ b/common/emu_oplist.h @@ -289,6 +289,7 @@ N(OP_LFGuild), N(OP_LFPCommand), N(OP_LFPGetMatchesRequest), N(OP_LFPGetMatchesResponse), +N(OP_LinkedReuse), N(OP_LoadSpellSet), N(OP_LocInfo), N(OP_LockoutTimerInfo), diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index fc1c9f9e7..12a08fa36 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -390,6 +390,18 @@ uint32 scribing; // 1 if memorizing a spell, set to 0 if scribing to book, 2 if uint32 reduction; // lower reuse }; +/* +** Linked Spell Reuse Timer +** Length: 12 +** Comes before the OP_Memorize +** Live (maybe TDS steam) has an extra DWORD after timer_id +*/ +struct LinkedSpellReuseTimer_Struct { + uint32 timer_id; // Timer ID of the spell + uint32 end_time; // timestamp of when it will be ready + uint32 start_time; // timestamp of when it started +}; + /* ** Make Charmed Pet ** Length: 12 Bytes diff --git a/common/ptimer.h b/common/ptimer.h index 7194ab49b..d7398bea3 100644 --- a/common/ptimer.h +++ b/common/ptimer.h @@ -37,10 +37,12 @@ enum { //values for pTimerType pTimerSenseTraps = 12, pTimerDisarmTraps = 13, pTimerDisciplineReuseStart = 14, - pTimerDisciplineReuseEnd = 24, + pTimerDisciplineReuseEnd = 24, // client actually has 20 ids, but still no disc go that high even on live pTimerCombatAbility = 25, pTimerCombatAbility2 = 26, // RoF2+ Tiger Claw is unlinked from other monk skills, generic in case other classes ever need it pTimerBeggingPickPocket = 27, + pTimerLinkedSpellReuseStart = 28, + pTimerLinkedSpellReuseEnd = 48, pTimerLayHands = 87, //these IDs are used by client too pTimerHarmTouch = 89, //so dont change them diff --git a/utils/patches/patch_RoF.conf b/utils/patches/patch_RoF.conf index 00682d57f..aefcd8dbc 100644 --- a/utils/patches/patch_RoF.conf +++ b/utils/patches/patch_RoF.conf @@ -172,6 +172,7 @@ OP_BeginCast=0x17ff OP_ColoredText=0x41cb OP_ConsentResponse=0x183d OP_MemorizeSpell=0x2fac +OP_LinkedReuse=0x3ac0 OP_SwapSpell=0x4736 OP_CastSpell=0x1cb5 OP_Consider=0x4d8d diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index 9237a0238..19ef8d842 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -171,6 +171,7 @@ OP_BeginCast=0x318f OP_ColoredText=0x43af OP_ConsentResponse=0x384a OP_MemorizeSpell=0x217c +OP_LinkedReuse=0x1619 OP_SwapSpell=0x0efa OP_CastSpell=0x1287 OP_Consider=0x742b diff --git a/utils/patches/patch_SoD.conf b/utils/patches/patch_SoD.conf index 4944b0ef3..08e072d33 100644 --- a/utils/patches/patch_SoD.conf +++ b/utils/patches/patch_SoD.conf @@ -171,6 +171,7 @@ OP_BeginCast=0x0d5a # C OP_ColoredText=0x569a # C OP_ConsentResponse=0x6e47 # C OP_MemorizeSpell=0x8543 # C +OP_LinkedReuse=0x6ef9 OP_SwapSpell=0x3fd2 # C OP_CastSpell=0x3582 # C OP_Consider=0x6024 # C diff --git a/utils/patches/patch_SoF.conf b/utils/patches/patch_SoF.conf index 113038e49..d758daea9 100644 --- a/utils/patches/patch_SoF.conf +++ b/utils/patches/patch_SoF.conf @@ -169,6 +169,7 @@ OP_BeginCast=0x5A50 #SEQ 12/04/08 OP_ColoredText=0x3BC7 #SEQ 12/04/08 OP_ConsentResponse=0x4D30 #SEQ 12/04/08 OP_MemorizeSpell=0x6A93 #SEQ 12/04/08 +OP_LinkedReuse=0x2c26 OP_SwapSpell=0x1418 #SEQ 12/04/08 OP_CastSpell=0x7F5D #SEQ 12/04/08 OP_Consider=0x32E1 #SEQ 12/04/08 diff --git a/utils/patches/patch_Titanium.conf b/utils/patches/patch_Titanium.conf index a48de92b5..23f07109f 100644 --- a/utils/patches/patch_Titanium.conf +++ b/utils/patches/patch_Titanium.conf @@ -170,6 +170,7 @@ OP_Save=0x736b # ShowEQ 10/27/05 OP_Camp=0x78c1 # ShowEQ 10/27/05 OP_EndLootRequest=0x2316 # ShowEQ 10/27/05 OP_MemorizeSpell=0x308e # ShowEQ 10/27/05 +OP_LinkedReuse=0x6a00 OP_SwapSpell=0x2126 # ShowEQ 10/27/05 OP_CastSpell=0x304b # ShowEQ 10/27/05 OP_DeleteSpell=0x4f37 diff --git a/utils/patches/patch_UF.conf b/utils/patches/patch_UF.conf index 4b650176f..f41b62740 100644 --- a/utils/patches/patch_UF.conf +++ b/utils/patches/patch_UF.conf @@ -173,6 +173,7 @@ OP_BeginCast=0x0d5a # C OP_ColoredText=0x71bf # C OP_ConsentResponse=0x0e87 # C OP_MemorizeSpell=0x3887 # C +OP_LinkedReuse=0x1b26 OP_SwapSpell=0x5805 # C OP_CastSpell=0x50c2 # C OP_Consider=0x3c2d # C diff --git a/zone/client.h b/zone/client.h index dc04fbc1e..df7c1fce3 100644 --- a/zone/client.h +++ b/zone/client.h @@ -894,6 +894,9 @@ public: void SendDisciplineTimer(uint32 timer_id, uint32 duration); bool UseDiscipline(uint32 spell_id, uint32 target); + void SetLinkedSpellReuseTimer(uint32 timer_id, uint32 duration); + bool IsLinkedSpellReuseTimerReady(uint32 timer_id); + bool CheckTitle(int titleset); void EnableTitle(int titleset); void RemoveTitle(int titleset); diff --git a/zone/spells.cpp b/zone/spells.cpp index ea8067b8c..d310589b1 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -552,6 +552,10 @@ bool Mob::DoCastingChecks() } } + if (IsClient() && spells[spell_id].EndurTimerIndex > 0 && casting_spell_slot < CastingSlot::MaxGems) + if (!CastToClient()->IsLinkedSpellReuseTimerReady(spells[spell_id].EndurTimerIndex)) + return false; + casting_spell_checks = true; return true; } @@ -1308,8 +1312,11 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo { if(IsClient()) { - this->CastToClient()->CheckSongSkillIncrease(spell_id); - this->CastToClient()->MemorizeSpell(static_cast(slot), spell_id, memSpellSpellbar); + Client *c = CastToClient(); + c->CheckSongSkillIncrease(spell_id); + if (spells[spell_id].EndurTimerIndex > 0 && slot < CastingSlot::MaxGems) + c->SetLinkedSpellReuseTimer(spells[spell_id].EndurTimerIndex, spells[spell_id].recast_time / 1000); + c->MemorizeSpell(static_cast(slot), spell_id, memSpellSpellbar); } Log.Out(Logs::Detail, Logs::Spells, "Bard song %d should be started", spell_id); } @@ -1321,6 +1328,8 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo SendSpellBarEnable(spell_id); // this causes the delayed refresh of the spell bar gems + if (spells[spell_id].EndurTimerIndex > 0 && slot < CastingSlot::MaxGems) + c->SetLinkedSpellReuseTimer(spells[spell_id].EndurTimerIndex, spells[spell_id].recast_time / 1000); c->MemorizeSpell(static_cast(slot), spell_id, memSpellSpellbar); // this tells the client that casting may happen again @@ -5680,3 +5689,25 @@ void Mob::ConeDirectional(uint16 spell_id, int16 resist_adjust) ++iter; } } + +// duration in seconds +void Client::SetLinkedSpellReuseTimer(uint32 timer_id, uint32 duration) +{ + if (timer_id > 19) + return; + GetPTimers().Start(pTimerLinkedSpellReuseStart + timer_id, duration); + auto outapp = new EQApplicationPacket(OP_LinkedReuse, sizeof(LinkedSpellReuseTimer_Struct)); + auto lr = (LinkedSpellReuseTimer_Struct *)outapp->pBuffer; + lr->timer_id = timer_id; + lr->start_time = Timer::GetCurrentTime() / 1000; + lr->end_time = lr->start_time + duration; + FastQueuePacket(&outapp); +} + +bool Client::IsLinkedSpellReuseTimerReady(uint32 timer_id) +{ + if (timer_id > 19) + return true; + return GetPTimers().Expired(&database, pTimerLinkedSpellReuseStart + timer_id); +} + From ae5689ffb4f778143dec3492e2eab1c593f9c76a Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Mon, 15 Aug 2016 01:17:53 -0400 Subject: [PATCH 55/78] Clean up OP_ManaChange --- common/eq_packet_structs.h | 9 +++++---- common/patches/rof.cpp | 3 ++- common/patches/rof2.cpp | 3 ++- common/patches/rof2_structs.h | 11 ++++++----- common/patches/rof_structs.h | 11 ++++++----- common/patches/sod.cpp | 3 ++- common/patches/sod_structs.h | 11 ++++++----- common/patches/sof.cpp | 3 ++- common/patches/sof_structs.h | 11 ++++++----- common/patches/titanium_structs.h | 10 +++++----- common/patches/uf.cpp | 3 ++- common/patches/uf_structs.h | 11 ++++++----- zone/client.cpp | 1 + zone/spells.cpp | 1 + 14 files changed, 52 insertions(+), 39 deletions(-) diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 12a08fa36..a1b614b97 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -432,10 +432,11 @@ struct DeleteSpell_Struct struct ManaChange_Struct { - uint32 new_mana; // New Mana AMount - uint32 stamina; - uint32 spell_id; - uint32 unknown12; +/*00*/ uint32 new_mana; // New Mana AMount +/*04*/ uint32 stamina; +/*08*/ uint32 spell_id; +/*12*/ uint8 keepcasting; // won't stop the cast. Change mana while casting? +/*13*/ uint8 padding[3]; // client doesn't read it, garbage data seems like }; struct SwapSpell_Struct diff --git a/common/patches/rof.cpp b/common/patches/rof.cpp index bee434ec3..939ad03fa 100644 --- a/common/patches/rof.cpp +++ b/common/patches/rof.cpp @@ -1623,7 +1623,8 @@ namespace RoF OUT(new_mana); OUT(stamina); OUT(spell_id); - eq->unknown16 = -1; // Self Interrupt/Success = -1, Fizzle = 1, Other Interrupt = 2? + OUT(keepcasting); + eq->slot = -1; // this is spell gem slot. It's -1 in normal operation FINISH_ENCODE(); } diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 52fdeb02b..118419f48 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -1701,7 +1701,8 @@ namespace RoF2 OUT(new_mana); OUT(stamina); OUT(spell_id); - eq->unknown16 = -1; // Self Interrupt/Success = -1, Fizzle = 1, Other Interrupt = 2? + OUT(keepcasting); + eq->slot = -1; // this is spell gem slot. It's -1 in normal operation FINISH_ENCODE(); } diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index 971ebd5e0..ae4d2c799 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -668,11 +668,12 @@ struct DeleteSpell_Struct struct ManaChange_Struct { - uint32 new_mana; // New Mana AMount - uint32 stamina; - uint32 spell_id; - uint32 unknown12; - uint32 unknown16; +/*00*/ uint32 new_mana; // New Mana AMount +/*04*/ uint32 stamina; +/*08*/ uint32 spell_id; +/*12*/ uint8 keepcasting; // won't stop the cast. Change mana while casting? +/*13*/ uint8 padding[3]; // client doesn't read it, garbage data seems like +/*16*/ int32 slot; // -1 for normal usage slot for when we want silent interrupt? I think it does timer stuff or something. Linked Spell Reuse interrupt uses it }; struct SwapSpell_Struct diff --git a/common/patches/rof_structs.h b/common/patches/rof_structs.h index 24c4b15f8..74b47c09e 100644 --- a/common/patches/rof_structs.h +++ b/common/patches/rof_structs.h @@ -657,11 +657,12 @@ struct DeleteSpell_Struct struct ManaChange_Struct { - uint32 new_mana; // New Mana AMount - uint32 stamina; - uint32 spell_id; - uint32 unknown12; - uint32 unknown16; +/*00*/ uint32 new_mana; // New Mana AMount +/*04*/ uint32 stamina; +/*08*/ uint32 spell_id; +/*12*/ uint8 keepcasting; // won't stop the cast. Change mana while casting? +/*13*/ uint8 padding[3]; // client doesn't read it, garbage data seems like +/*16*/ int32 slot; // -1 for normal usage slot for when we want silent interrupt? I think it does timer stuff or something. Linked Spell Reuse interrupt uses it }; struct SwapSpell_Struct diff --git a/common/patches/sod.cpp b/common/patches/sod.cpp index 4def89212..e23c1ca78 100644 --- a/common/patches/sod.cpp +++ b/common/patches/sod.cpp @@ -1139,7 +1139,8 @@ namespace SoD OUT(new_mana); OUT(stamina); OUT(spell_id); - eq->unknown16 = -1; // Self Interrupt/Success = -1, Fizzle = 1, Other Interrupt = 2? + OUT(keepcasting); + eq->slot = -1; // this is spell gem slot. It's -1 in normal operation FINISH_ENCODE(); } diff --git a/common/patches/sod_structs.h b/common/patches/sod_structs.h index bdf6a6fd3..6a73b7283 100644 --- a/common/patches/sod_structs.h +++ b/common/patches/sod_structs.h @@ -512,11 +512,12 @@ struct DeleteSpell_Struct struct ManaChange_Struct { - uint32 new_mana; // New Mana AMount - uint32 stamina; - uint32 spell_id; - uint32 unknown12; - uint32 unknown16; +/*00*/ uint32 new_mana; // New Mana AMount +/*04*/ uint32 stamina; +/*08*/ uint32 spell_id; +/*12*/ uint8 keepcasting; // won't stop the cast. Change mana while casting? +/*13*/ uint8 padding[3]; // client doesn't read it, garbage data seems like +/*16*/ int32 slot; // -1 for normal usage slot for when we want silent interrupt? I think it does timer stuff or something. Linked Spell Reuse interrupt uses it }; struct SwapSpell_Struct diff --git a/common/patches/sof.cpp b/common/patches/sof.cpp index a34e5029a..3ab4a7631 100644 --- a/common/patches/sof.cpp +++ b/common/patches/sof.cpp @@ -936,7 +936,8 @@ namespace SoF OUT(new_mana); OUT(stamina); OUT(spell_id); - eq->unknown16 = -1; // Self Interrupt/Success = -1, Fizzle = 1, Other Interrupt = 2? + OUT(keepcasting); + eq->slot = -1; // this is spell gem slot. It's -1 in normal operation FINISH_ENCODE(); } diff --git a/common/patches/sof_structs.h b/common/patches/sof_structs.h index 06604e71d..0256132bd 100644 --- a/common/patches/sof_structs.h +++ b/common/patches/sof_structs.h @@ -491,11 +491,12 @@ struct DeleteSpell_Struct struct ManaChange_Struct { - uint32 new_mana; // New Mana AMount - uint32 stamina; - uint32 spell_id; - uint32 unknown12; - uint32 unknown16; +/*00*/ uint32 new_mana; // New Mana AMount +/*04*/ uint32 stamina; +/*08*/ uint32 spell_id; +/*12*/ uint8 keepcasting; // won't stop the cast. Change mana while casting? +/*13*/ uint8 padding[3]; // client doesn't read it, garbage data seems like +/*16*/ int32 slot; // -1 for normal usage slot for when we want silent interrupt? I think it does timer stuff or something. Linked Spell Reuse interrupt uses it }; struct SwapSpell_Struct diff --git a/common/patches/titanium_structs.h b/common/patches/titanium_structs.h index b7b2bac67..8d258871f 100644 --- a/common/patches/titanium_structs.h +++ b/common/patches/titanium_structs.h @@ -420,13 +420,13 @@ struct DeleteSpell_Struct /*005*/uint8 unknowndss006[3]; /*008*/ }; - struct ManaChange_Struct { - uint32 new_mana; // New Mana AMount - uint32 stamina; - uint32 spell_id; - uint32 unknown12; +/*00*/ uint32 new_mana; // New Mana AMount +/*04*/ uint32 stamina; +/*08*/ uint32 spell_id; +/*12*/ uint8 keepcasting; // won't stop the cast. Change mana while casting? +/*13*/ uint8 padding[3]; // client doesn't read it, garbage data seems like }; struct SwapSpell_Struct diff --git a/common/patches/uf.cpp b/common/patches/uf.cpp index 1ff6c8e82..6a1a03a09 100644 --- a/common/patches/uf.cpp +++ b/common/patches/uf.cpp @@ -1386,7 +1386,8 @@ namespace UF OUT(new_mana); OUT(stamina); OUT(spell_id); - eq->unknown16 = -1; // Self Interrupt/Success = -1, Fizzle = 1, Other Interrupt = 2? + OUT(keepcasting); + eq->slot = -1; // this is spell gem slot. It's -1 in normal operation FINISH_ENCODE(); } diff --git a/common/patches/uf_structs.h b/common/patches/uf_structs.h index 3c582e6e6..46e5b21cb 100644 --- a/common/patches/uf_structs.h +++ b/common/patches/uf_structs.h @@ -512,11 +512,12 @@ struct DeleteSpell_Struct struct ManaChange_Struct { - uint32 new_mana; // New Mana AMount - uint32 stamina; - uint32 spell_id; - uint32 unknown12; - uint32 unknown16; +/*00*/ uint32 new_mana; // New Mana AMount +/*04*/ uint32 stamina; +/*08*/ uint32 spell_id; +/*12*/ uint8 keepcasting; // won't stop the cast. Change mana while casting? +/*13*/ uint8 padding[3]; // client doesn't read it, garbage data seems like +/*16*/ int32 slot; // -1 for normal usage slot for when we want silent interrupt? I think it does timer stuff or something. Linked Spell Reuse interrupt uses it }; struct SwapSpell_Struct diff --git a/zone/client.cpp b/zone/client.cpp index 76e3ce8c4..b4bbfaefc 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -1809,6 +1809,7 @@ void Client::SendManaUpdatePacket() { manachange->new_mana = cur_mana; manachange->stamina = cur_end; manachange->spell_id = casting_spell_id; //always going to be 0... since we check IsCasting() + manachange->keepcasting = 1; outapp->priority = 6; QueuePacket(outapp); safe_delete(outapp); diff --git a/zone/spells.cpp b/zone/spells.cpp index d310589b1..417cdb5a5 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -4754,6 +4754,7 @@ void Mob::SendSpellBarEnable(uint16 spell_id) manachange->new_mana = GetMana(); manachange->spell_id = spell_id; manachange->stamina = CastToClient()->GetEndurance(); + manachange->keepcasting = 0; outapp->priority = 6; CastToClient()->QueuePacket(outapp); safe_delete(outapp); From 3c95545ea380feb9734eee42f537bfd7cbcabd90 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Mon, 15 Aug 2016 01:23:47 -0400 Subject: [PATCH 56/78] Remove IsCasting check from Client::SendManaUpdatePacket --- zone/client.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index b4bbfaefc..59998bfdb 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -1794,7 +1794,7 @@ const int32& Client::SetMana(int32 amount) { } void Client::SendManaUpdatePacket() { - if (!Connected() || IsCasting()) + if (!Connected()) return; if (ClientVersion() >= EQEmu::versions::ClientVersion::SoD) { @@ -1808,7 +1808,7 @@ void Client::SendManaUpdatePacket() { ManaChange_Struct* manachange = (ManaChange_Struct*)outapp->pBuffer; manachange->new_mana = cur_mana; manachange->stamina = cur_end; - manachange->spell_id = casting_spell_id; //always going to be 0... since we check IsCasting() + manachange->spell_id = casting_spell_id; manachange->keepcasting = 1; outapp->priority = 6; QueuePacket(outapp); From 26772b721cff59b283a88ee0edca0bcdc1b45ef5 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Mon, 15 Aug 2016 14:21:39 -0400 Subject: [PATCH 57/78] Fix overhaste stack check --- zone/spells.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/zone/spells.cpp b/zone/spells.cpp index 417cdb5a5..0271ea687 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -2995,8 +2995,7 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, if ( effect1 == SE_AttackSpeed || - effect1 == SE_AttackSpeed2 || - effect1 == SE_AttackSpeed3 + effect1 == SE_AttackSpeed2 ) { sp1_value -= 100; From 3d64878e60f6bafaf966e59d2d13100badf12327 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Mon, 15 Aug 2016 15:23:37 -0400 Subject: [PATCH 58/78] Skip OP_BeginCast for discs --- zone/spells.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/zone/spells.cpp b/zone/spells.cpp index 0271ea687..701f0355f 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -457,14 +457,16 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, // now tell the people in the area -- we ALWAYS want to send this, even instant cast spells. // The only time this is skipped is for NPC innate procs and weapon procs. Procs from buffs // oddly still send this. Since those cases don't reach here, we don't need to check them - auto outapp = new EQApplicationPacket(OP_BeginCast,sizeof(BeginCast_Struct)); - BeginCast_Struct* begincast = (BeginCast_Struct*)outapp->pBuffer; - begincast->caster_id = GetID(); - begincast->spell_id = spell_id; - begincast->cast_time = orgcasttime; // client calculates reduced time by itself - outapp->priority = 3; - entity_list.QueueCloseClients(this, outapp, false, 200, 0, true); //IsClient() ? FILTER_PCSPELLS : FILTER_NPCSPELLS); - safe_delete(outapp); + if (slot != CastingSlot::Discipline) { + auto outapp = new EQApplicationPacket(OP_BeginCast,sizeof(BeginCast_Struct)); + BeginCast_Struct* begincast = (BeginCast_Struct*)outapp->pBuffer; + begincast->caster_id = GetID(); + begincast->spell_id = spell_id; + begincast->cast_time = orgcasttime; // client calculates reduced time by itself + outapp->priority = 3; + entity_list.QueueCloseClients(this, outapp, false, 200, 0, true); //IsClient() ? FILTER_PCSPELLS : FILTER_NPCSPELLS); + safe_delete(outapp); + } // cast time is 0, just finish it right now and be done with it if(cast_time == 0) { From 5a0d2b527bc7e0742d9e3ad84b1de8788a931dd4 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Tue, 16 Aug 2016 17:52:14 -0400 Subject: [PATCH 59/78] Add a StopCasting function and make some use of it Unsure if all of these cases should use interrupt or stop casting --- zone/mob.h | 1 + zone/spells.cpp | 32 +++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/zone/mob.h b/zone/mob.h index b80667b02..927ecf862 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -272,6 +272,7 @@ public: virtual float GetAOERange(uint16 spell_id); void InterruptSpell(uint16 spellid = SPELL_UNKNOWN); void InterruptSpell(uint16, uint16, uint16 spellid = SPELL_UNKNOWN); + void StopCasting(); inline bool IsCasting() const { return((casting_spell_id != 0)); } uint16 CastingSpellID() const { return casting_spell_id; } bool DoCastingChecks(); diff --git a/zone/spells.cpp b/zone/spells.cpp index 701f0355f..4e79ede91 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -470,6 +470,10 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, // cast time is 0, just finish it right now and be done with it if(cast_time == 0) { + if (!DoCastingChecks()) { + StopCasting(); + return false; + } CastedSpellFinished(spell_id, target_id, slot, mana_cost, item_slot, resist_adjust); return(true); } @@ -498,7 +502,7 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, } if (!DoCastingChecks()) { - InterruptSpell(); + StopCasting(); return false; } @@ -882,6 +886,32 @@ void Mob::InterruptSpell(uint16 message, uint16 color, uint16 spellid) } +// this is like interrupt, just it doesn't spam interrupt packets to everyone +// There are a few cases where this is what live does :P +void Mob::StopCasting() +{ + if (casting_spell_id && IsNPC()) { + CastToNPC()->AI_Event_SpellCastFinished(false, static_cast(casting_spell_slot)); + } + + if (IsClient()) { + auto c = CastToClient(); + if (casting_spell_aa_id) { //Rest AA Timer on failed cast + c->Message_StringID(MT_SpellFailure, ABILITY_FAILED); + c->ResetAlternateAdvancementTimer(casting_spell_aa_id); + } + + auto outapp = new EQApplicationPacket(OP_ManaChange, sizeof(ManaChange_Struct)); + auto mc = (ManaChange_Struct *)outapp->pBuffer; + mc->new_mana = GetMana(); + mc->stamina = GetEndurance(); + mc->spell_id = casting_spell_id; + mc->keepcasting = 0; + c->FastQueuePacket(&outapp); + } + ZeroCastingVars(); +} + // this is called after the timer is up and the spell is finished // casting. everything goes through here, including items with zero cast time // only to be used from SpellProcess From d3afde1aa14fdba385dec64ec4ac92e0dc37b320 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Tue, 16 Aug 2016 19:36:26 -0500 Subject: [PATCH 60/78] Travis Discord test --- utils/scripts/eqemu_update.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/scripts/eqemu_update.pl b/utils/scripts/eqemu_update.pl index 6d1e32c3a..e54c7e73a 100644 --- a/utils/scripts/eqemu_update.pl +++ b/utils/scripts/eqemu_update.pl @@ -4,7 +4,7 @@ #::: Automatic (Database) Upgrade Script #::: Author: Akkadius #::: Purpose: To upgrade databases with ease and maintain versioning -########################################################### +########################################################### $menu_displayed = 0; From 94fabc87f00155cd9eafeb6c644574b648681d7a Mon Sep 17 00:00:00 2001 From: Akkadius Date: Tue, 16 Aug 2016 19:50:12 -0500 Subject: [PATCH 61/78] Travis Discord test 2 --- utils/scripts/eqemu_update.pl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/scripts/eqemu_update.pl b/utils/scripts/eqemu_update.pl index e54c7e73a..6be6f030b 100644 --- a/utils/scripts/eqemu_update.pl +++ b/utils/scripts/eqemu_update.pl @@ -1,10 +1,10 @@ #!/usr/bin/perl -########################################################### +########################################################### #::: Automatic (Database) Upgrade Script #::: Author: Akkadius #::: Purpose: To upgrade databases with ease and maintain versioning -########################################################### +########################################################### $menu_displayed = 0; From b85d5a6d98f2f81acd899d47c987669ae5b14015 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Tue, 16 Aug 2016 20:44:04 -0500 Subject: [PATCH 62/78] Discord final test --- utils/scripts/eqemu_update.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/scripts/eqemu_update.pl b/utils/scripts/eqemu_update.pl index 6be6f030b..c28d078c3 100644 --- a/utils/scripts/eqemu_update.pl +++ b/utils/scripts/eqemu_update.pl @@ -4,7 +4,7 @@ #::: Automatic (Database) Upgrade Script #::: Author: Akkadius #::: Purpose: To upgrade databases with ease and maintain versioning -########################################################### +########################################################### $menu_displayed = 0; From 1def512b4c8284920f3d7d84607748092c76d137 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Tue, 16 Aug 2016 21:56:43 -0500 Subject: [PATCH 63/78] One more test --- utils/scripts/eqemu_update.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/scripts/eqemu_update.pl b/utils/scripts/eqemu_update.pl index c28d078c3..6d1e32c3a 100644 --- a/utils/scripts/eqemu_update.pl +++ b/utils/scripts/eqemu_update.pl @@ -1,6 +1,6 @@ #!/usr/bin/perl -########################################################### +########################################################### #::: Automatic (Database) Upgrade Script #::: Author: Akkadius #::: Purpose: To upgrade databases with ease and maintain versioning From f9f3a8f3bd78c9eae3cd923ffb29e7d7e64f7610 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Wed, 17 Aug 2016 01:10:37 -0400 Subject: [PATCH 64/78] Expendable Arrows ignore EQ --- zone/special_attacks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 41fb18ef2..eb7f5aac1 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -786,7 +786,7 @@ void Client::RangedAttack(Mob* other, bool CanDoubleAttack) { //EndlessQuiver AA base1 = 100% Chance to avoid consumption arrow. int ChanceAvoidConsume = aabonuses.ConsumeProjectile + itembonuses.ConsumeProjectile + spellbonuses.ConsumeProjectile; - if (!ChanceAvoidConsume || (ChanceAvoidConsume < 100 && zone->random.Int(0,99) > ChanceAvoidConsume)){ + if (RangeItem->ExpendableArrow || !ChanceAvoidConsume || (ChanceAvoidConsume < 100 && zone->random.Int(0,99) > ChanceAvoidConsume)){ DeleteItemInInventory(ammo_slot, 1, true); Log.Out(Logs::Detail, Logs::Combat, "Consumed one arrow from slot %d", ammo_slot); } else { From a07149919d1d21bf5624d9404d016e5e51f91ee5 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Thu, 18 Aug 2016 17:28:32 -0500 Subject: [PATCH 65/78] Pets now don't actually spawn until the player has fully entered the zone (Live-like) --- zone/client_packet.cpp | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 2da75b364..37108697c 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -502,6 +502,27 @@ void Client::CompleteConnect() SetDuelTarget(0); SetDueling(false); + database.LoadPetInfo(this); + /* + This was moved before the spawn packets are sent + in hopes that it adds more consistency... + Remake pet + */ + if (m_petinfo.SpellID > 1 && !GetPet() && m_petinfo.SpellID <= SPDAT_RECORDS) { + MakePoweredPet(m_petinfo.SpellID, spells[m_petinfo.SpellID].teleport_zone, m_petinfo.petpower, m_petinfo.Name, m_petinfo.size); + if (GetPet() && GetPet()->IsNPC()) { + NPC *pet = GetPet()->CastToNPC(); + pet->SetPetState(m_petinfo.Buffs, m_petinfo.Items); + pet->CalcBonuses(); + pet->SetHP(m_petinfo.HP); + pet->SetMana(m_petinfo.Mana); + } + m_petinfo.SpellID = 0; + } + /* Moved here so it's after where we load the pet data. */ + if (!GetAA(aaPersistentMinion)) + memset(&m_suspendedminion, 0, sizeof(PetInfo)); + EnteringMessages(this); LoadZoneFlags(); @@ -1628,27 +1649,6 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) if (m_pp.RestTimer) rest_timer.Start(m_pp.RestTimer * 1000); - database.LoadPetInfo(this); - /* - This was moved before the spawn packets are sent - in hopes that it adds more consistency... - Remake pet - */ - if (m_petinfo.SpellID > 1 && !GetPet() && m_petinfo.SpellID <= SPDAT_RECORDS) { - MakePoweredPet(m_petinfo.SpellID, spells[m_petinfo.SpellID].teleport_zone, m_petinfo.petpower, m_petinfo.Name, m_petinfo.size); - if (GetPet() && GetPet()->IsNPC()) { - NPC *pet = GetPet()->CastToNPC(); - pet->SetPetState(m_petinfo.Buffs, m_petinfo.Items); - pet->CalcBonuses(); - pet->SetHP(m_petinfo.HP); - pet->SetMana(m_petinfo.Mana); - } - m_petinfo.SpellID = 0; - } - /* Moved here so it's after where we load the pet data. */ - if (!GetAA(aaPersistentMinion)) - memset(&m_suspendedminion, 0, sizeof(PetInfo)); - /* Server Zone Entry Packet */ outapp = new EQApplicationPacket(OP_ZoneEntry, sizeof(ServerZoneEntry_Struct)); ServerZoneEntry_Struct* sze = (ServerZoneEntry_Struct*)outapp->pBuffer; From 0789d10d3e69d185394e678a9e9c5afa8ae0327e Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Thu, 18 Aug 2016 20:51:58 -0400 Subject: [PATCH 66/78] Add logging message for setting linked reuse --- zone/spells.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/zone/spells.cpp b/zone/spells.cpp index 4e79ede91..fe2ad1946 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -5727,6 +5727,7 @@ void Client::SetLinkedSpellReuseTimer(uint32 timer_id, uint32 duration) { if (timer_id > 19) return; + Log.Out(Logs::Detail, Logs::Spells, "Setting Linked Spell Reuse %d for %d", timer_id, duration); GetPTimers().Start(pTimerLinkedSpellReuseStart + timer_id, duration); auto outapp = new EQApplicationPacket(OP_LinkedReuse, sizeof(LinkedSpellReuseTimer_Struct)); auto lr = (LinkedSpellReuseTimer_Struct *)outapp->pBuffer; From f67cd057f3ba3dbe67f4f274a13490ff41439491 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Thu, 18 Aug 2016 21:09:02 -0400 Subject: [PATCH 67/78] Fix issue with linked spell timers --- zone/spells.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/spells.cpp b/zone/spells.cpp index fe2ad1946..e12f8042f 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -5741,6 +5741,6 @@ bool Client::IsLinkedSpellReuseTimerReady(uint32 timer_id) { if (timer_id > 19) return true; - return GetPTimers().Expired(&database, pTimerLinkedSpellReuseStart + timer_id); + return GetPTimers().Expired(&database, pTimerLinkedSpellReuseStart + timer_id, false); } From 6daf207323d90b2e67e64e5b1054b1f49226555b Mon Sep 17 00:00:00 2001 From: Akkadius Date: Fri, 19 Aug 2016 16:31:46 -0500 Subject: [PATCH 68/78] Quick test --- utils/scripts/eqemu_update.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/scripts/eqemu_update.pl b/utils/scripts/eqemu_update.pl index 6d1e32c3a..c28d078c3 100644 --- a/utils/scripts/eqemu_update.pl +++ b/utils/scripts/eqemu_update.pl @@ -1,6 +1,6 @@ #!/usr/bin/perl -########################################################### +########################################################### #::: Automatic (Database) Upgrade Script #::: Author: Akkadius #::: Purpose: To upgrade databases with ease and maintain versioning From 696c02c0f022da9622cce59053406e513ac8d9dd Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sat, 20 Aug 2016 14:30:21 -0400 Subject: [PATCH 69/78] Move instrument mod outside of the EFFECT lop in SpellEffect --- zone/spell_effects.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 0b67ea523..8f66345d2 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -194,6 +194,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove bool SE_SpellTrigger_HasCast = false; + // if buff slot, use instrument mod there, otherwise calc it + uint32 instrument_mod = buffslot > -1 ? buffs[buffslot].instrument_mod : caster ? caster->GetInstrumentMod(spell_id) : 10; // iterate through the effects in the spell for (i = 0; i < EFFECT_COUNT; i++) { @@ -201,9 +203,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove continue; effect = spell.effectid[i]; - // if buff slot, use instrument mod there, otherwise calc it - uint32 inst = buffslot > -1 ? buffs[buffslot].instrument_mod : caster ? caster->GetInstrumentMod(spell_id) : 10; - effect_value = CalcSpellEffectValue(spell_id, i, caster_level, inst, caster ? caster : this); + effect_value = CalcSpellEffectValue(spell_id, i, caster_level, instrument_mod, caster ? caster : this); if(spell_id == SPELL_LAY_ON_HANDS && caster && caster->GetAA(aaImprovedLayOnHands)) effect_value = GetMaxHP(); From f5a7117bdfae8c80cb1429cc78c124b9087135c3 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Sat, 20 Aug 2016 23:13:16 -0500 Subject: [PATCH 70/78] Add character_table_list.txt for database backup script reference --- utils/sql/character_table_list.txt | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 utils/sql/character_table_list.txt diff --git a/utils/sql/character_table_list.txt b/utils/sql/character_table_list.txt new file mode 100644 index 000000000..b0fdd2c06 --- /dev/null +++ b/utils/sql/character_table_list.txt @@ -0,0 +1,34 @@ +adventure_stats +char_recipe_list +character_activities +character_alt_currency +character_alternate_abilities +character_bandolier +character_bind +character_currency +character_data +character_data +character_disciplines +character_enabledtasks +character_inspect_messages +character_languages +character_leadership_abilities +character_material +character_memmed_spells +character_potionbelt +character_skills +character_spells +character_tribute +completed_tasks +faction_values +friends +guild_members +instance_list_player +inventory +keyring +mail +player_titlesets +quest_globals +timers +titles +zone_flags \ No newline at end of file From afe42ccdaf89b7321012e72f46d5000f82358aae Mon Sep 17 00:00:00 2001 From: Akkadius Date: Sat, 20 Aug 2016 23:23:17 -0500 Subject: [PATCH 71/78] Update db_dumper.pl --- utils/scripts/db_dumper.pl | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/utils/scripts/db_dumper.pl b/utils/scripts/db_dumper.pl index 5b4bb229d..4abf2c2c9 100644 --- a/utils/scripts/db_dumper.pl +++ b/utils/scripts/db_dumper.pl @@ -32,7 +32,6 @@ if(!$ARGV[0]){ print " database=\"dbname\" - Manually specify databasename, default is database in eqemu_config.xml\n"; print " tables=\"table1,table2,table3\" - Manually specify tables, default is to dump all tables from database\n"; print " compress - Compress Database with 7-ZIP, will fallback to WinRAR depending on what is installed (Must be installed to default program dir)...\n"; - print " nolock - Does not lock tables, meant for backuping while the server is running..\n"; print ' Example: perl DB_Dumper.pl Loc="E:\Backups"' . "\n\n"; print "######################################################\n"; exit; @@ -60,9 +59,6 @@ print "Arguments\n" if $Debug; $n = 0; while($ARGV[$n]){ print $n . ': ' . $ARGV[$n] . "\n" if $Debug; - if($ARGV[$n]=~/nolock/i){ - $no_lock = 1; - } if($ARGV[$n]=~/compress/i){ print "Compression SET\n"; $Compress = 1; @@ -108,15 +104,12 @@ else { } if($t_tables ne ""){ - $tables_f_l = substr($t_tables_l, 0, 20) . '...'; + $tables_f_l = substr($t_tables_l, 0, 20) . '-'; $target_file = '' . $tables_f_l . '_' . $date . ''; print "Performing table based backup...\n"; #::: Backup Database... print "Backing up Database " . $db . "... \n\n"; - if($no_lock == 1){ - $added_parameters .= " --skip-lock-tables "; - } - $cmd = 'mysqldump -u' . $user . ' --host ' . $host . ' ' . $added_parameters . ' --max_allowed_packet=512M --password="' . $pass . '" ' . $db . ' ' . $t_tables . ' > "' . $B_LOC[1] . '' . $file_app . '' . $target_file . '.sql"'; + $cmd = 'mysqldump -u' . $user . ' --host ' . $host . ' --max_allowed_packet=512M --password="' . $pass . '" ' . $db . ' ' . $t_tables . ' > "' . $B_LOC[1] . '' . $file_app . '' . $target_file . '.sql"'; printcmd($cmd); system($cmd); } @@ -124,10 +117,7 @@ else{ #::: Entire DB Backup $target_file = '' . $db . '_' . $date . ''; #::: Backup Database... print "Backing up Database " . $db . "... \n\n"; - if($no_lock == 1){ - $added_parameters .= " --skip-lock-tables "; - } - $cmd = 'mysqldump -u' . $user . ' --host ' . $host . ' ' . $added_parameters . ' --max_allowed_packet=512M --password="' . $pass . '" ' . $db . ' > "' . $B_LOC[1] . '' . $file_app . '' . $target_file . '.sql"'; + $cmd = 'mysqldump -u' . $user . ' --host ' . $host . ' --max_allowed_packet=512M --password="' . $pass . '" ' . $db . ' > "' . $B_LOC[1] . '' . $file_app . '' . $target_file . '.sql"'; printcmd($cmd); system($cmd); } From 8dd18a43a0bb5f08682d4babba0a40adbc20ec38 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Sat, 20 Aug 2016 23:31:34 -0500 Subject: [PATCH 72/78] Update db_dumper [skip ci] --- utils/scripts/db_dumper.pl | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/utils/scripts/db_dumper.pl b/utils/scripts/db_dumper.pl index 4abf2c2c9..cf460252b 100644 --- a/utils/scripts/db_dumper.pl +++ b/utils/scripts/db_dumper.pl @@ -32,6 +32,7 @@ if(!$ARGV[0]){ print " database=\"dbname\" - Manually specify databasename, default is database in eqemu_config.xml\n"; print " tables=\"table1,table2,table3\" - Manually specify tables, default is to dump all tables from database\n"; print " compress - Compress Database with 7-ZIP, will fallback to WinRAR depending on what is installed (Must be installed to default program dir)...\n"; + print " nolock - Does not lock tables, meant for backuping while the server is running..\n"; print ' Example: perl DB_Dumper.pl Loc="E:\Backups"' . "\n\n"; print "######################################################\n"; exit; @@ -59,6 +60,9 @@ print "Arguments\n" if $Debug; $n = 0; while($ARGV[$n]){ print $n . ': ' . $ARGV[$n] . "\n" if $Debug; + if($ARGV[$n]=~/nolock/i){ + $no_lock = 1; + } if($ARGV[$n]=~/compress/i){ print "Compression SET\n"; $Compress = 1; @@ -109,7 +113,10 @@ if($t_tables ne ""){ print "Performing table based backup...\n"; #::: Backup Database... print "Backing up Database " . $db . "... \n\n"; - $cmd = 'mysqldump -u' . $user . ' --host ' . $host . ' --max_allowed_packet=512M --password="' . $pass . '" ' . $db . ' ' . $t_tables . ' > "' . $B_LOC[1] . '' . $file_app . '' . $target_file . '.sql"'; + if($no_lock == 1){ + $added_parameters .= " --skip-lock-tables "; + } + $cmd = 'mysqldump -u' . $user . ' --host ' . $host . ' ' . $added_parameters . ' --max_allowed_packet=512M --password="' . $pass . '" ' . $db . ' ' . $t_tables . ' > "' . $B_LOC[1] . '' . $file_app . '' . $target_file . '.sql"'; printcmd($cmd); system($cmd); } @@ -117,7 +124,10 @@ else{ #::: Entire DB Backup $target_file = '' . $db . '_' . $date . ''; #::: Backup Database... print "Backing up Database " . $db . "... \n\n"; - $cmd = 'mysqldump -u' . $user . ' --host ' . $host . ' --max_allowed_packet=512M --password="' . $pass . '" ' . $db . ' > "' . $B_LOC[1] . '' . $file_app . '' . $target_file . '.sql"'; + if($no_lock == 1){ + $added_parameters .= " --skip-lock-tables "; + } + $cmd = 'mysqldump -u' . $user . ' --host ' . $host . ' ' . $added_parameters . ' --max_allowed_packet=512M --password="' . $pass . '" ' . $db . ' > "' . $B_LOC[1] . '' . $file_app . '' . $target_file . '.sql"'; printcmd($cmd); system($cmd); } From 93464e396377def98f15cc6e9a6f86db53e93881 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Sat, 20 Aug 2016 23:32:01 -0500 Subject: [PATCH 73/78] Remove duplicate table [skip ci] --- utils/sql/character_table_list.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/utils/sql/character_table_list.txt b/utils/sql/character_table_list.txt index b0fdd2c06..2a902d009 100644 --- a/utils/sql/character_table_list.txt +++ b/utils/sql/character_table_list.txt @@ -7,7 +7,6 @@ character_bandolier character_bind character_currency character_data -character_data character_disciplines character_enabledtasks character_inspect_messages @@ -31,4 +30,4 @@ player_titlesets quest_globals timers titles -zone_flags \ No newline at end of file +zone_flags From 0b06044dcedd59df3bd6c2c00613e02ff0408103 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Sat, 20 Aug 2016 23:47:53 -0500 Subject: [PATCH 74/78] Update db_dumper [skip ci] Fix issues with file name output when no compression is set Add option to set backup_name="backup_name" to prefix backup outputs --- utils/scripts/db_dumper.pl | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/utils/scripts/db_dumper.pl b/utils/scripts/db_dumper.pl index cf460252b..47f14feef 100644 --- a/utils/scripts/db_dumper.pl +++ b/utils/scripts/db_dumper.pl @@ -33,6 +33,7 @@ if(!$ARGV[0]){ print " tables=\"table1,table2,table3\" - Manually specify tables, default is to dump all tables from database\n"; print " compress - Compress Database with 7-ZIP, will fallback to WinRAR depending on what is installed (Must be installed to default program dir)...\n"; print " nolock - Does not lock tables, meant for backuping while the server is running..\n"; + print " backup_name=\"name\" - Sets database backup prefix name\n"; print ' Example: perl DB_Dumper.pl Loc="E:\Backups"' . "\n\n"; print "######################################################\n"; exit; @@ -72,6 +73,11 @@ while($ARGV[$n]){ print "Database is " . $DB_NAME[1] . "\n"; $db = $DB_NAME[1]; } + if($ARGV[$n]=~/backup_name=/i){ + @data = split('=', $ARGV[$n]); + print "Backup Name is " . $data[1] . "\n"; + $backup_name = $data[1]; + } if($ARGV[$n]=~/loc=/i){ @B_LOC = split('=', $ARGV[$n]); print "Backup Directory: " . $B_LOC[1] . "\n"; @@ -109,7 +115,13 @@ else { if($t_tables ne ""){ $tables_f_l = substr($t_tables_l, 0, 20) . '-'; - $target_file = '' . $tables_f_l . '_' . $date . ''; + if($backup_name){ + $target_file = $backup_name . '_' . $date . ''; + } + else { + $target_file = '' . $tables_f_l . '_' . $date . ''; + } + print "Performing table based backup...\n"; #::: Backup Database... print "Backing up Database " . $db . "... \n\n"; @@ -121,7 +133,14 @@ if($t_tables ne ""){ system($cmd); } else{ #::: Entire DB Backup - $target_file = '' . $db . '_' . $date . ''; + + if($backup_name){ + $target_file = $backup_name . '_' . $db . '_' . $date . ''; + } + else { + $target_file = '' . $db . '_' . $date . ''; + } + #::: Backup Database... print "Backing up Database " . $db . "... \n\n"; if($no_lock == 1){ @@ -195,6 +214,9 @@ if($Compress == 1){ $final_file = $target_file . ".tar.gz"; } } +else { + $final_file = $target_file . ".sql"; +} #::: Get Final File Location for display if($B_LOC[1] ne ""){ $final_loc = $B_LOC[1] . '' . $file_app . ""; } From 4065df7930d34f14bf202165fd68f9ecd0c15ea5 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Sat, 20 Aug 2016 23:49:51 -0500 Subject: [PATCH 75/78] Update eqemu_update.pl [skip ci] Add option 21) Dump DB Player tables (Exports to backups\player_tables_export_(date).sql) Player table list is referenced/maintained in utils/sql/character_table_list.txt --- utils/scripts/eqemu_update.pl | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/utils/scripts/eqemu_update.pl b/utils/scripts/eqemu_update.pl index c28d078c3..67ab528cf 100644 --- a/utils/scripts/eqemu_update.pl +++ b/utils/scripts/eqemu_update.pl @@ -1,6 +1,6 @@ #!/usr/bin/perl -########################################################### +########################################################### #::: Automatic (Database) Upgrade Script #::: Author: Akkadius #::: Purpose: To upgrade databases with ease and maintain versioning @@ -295,9 +295,10 @@ sub show_menu_prompt { 13 => \&do_windows_login_server_setup, 14 => \&remove_duplicate_rule_values, 15 => \&fetch_utility_scripts, - 18 => \&fetch_latest_windows_binaries_bots, + 16 => \&fetch_latest_windows_binaries_bots, 19 => \&do_bots_db_schema_drop, 20 => \&do_update_self, + 21 => \&database_dump_player_tables, 0 => \&script_exit, ); @@ -378,6 +379,7 @@ return <){ + chomp; + $o = $_; + $tables .= $o . ","; + } + $tables = substr($tables, 0, -1); + + print `perl db_dumper.pl database="$db" loc="backups" tables="$tables" backup_name="player_tables_export" nolock`; + + print "\nPress any key to continue...\n"; + + <>; #Read from STDIN + +} + sub database_dump_compress { check_for_database_dump_script(); print "Performing database backup....\n"; From a8ba56363277753f83a31096587fd805d382977e Mon Sep 17 00:00:00 2001 From: Akkadius Date: Sat, 20 Aug 2016 23:50:59 -0500 Subject: [PATCH 76/78] Update eqemu_update.pl [skip ci] --- utils/scripts/eqemu_update.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/scripts/eqemu_update.pl b/utils/scripts/eqemu_update.pl index 67ab528cf..a7da3c792 100644 --- a/utils/scripts/eqemu_update.pl +++ b/utils/scripts/eqemu_update.pl @@ -295,7 +295,7 @@ sub show_menu_prompt { 13 => \&do_windows_login_server_setup, 14 => \&remove_duplicate_rule_values, 15 => \&fetch_utility_scripts, - 16 => \&fetch_latest_windows_binaries_bots, + 18 => \&fetch_latest_windows_binaries_bots, 19 => \&do_bots_db_schema_drop, 20 => \&do_update_self, 21 => \&database_dump_player_tables, From c2b31bd6e2a447567bc636a73ba7b4b8249575e5 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sun, 21 Aug 2016 20:26:54 -0400 Subject: [PATCH 77/78] Fix EVENT_ATTACK crash (thanks image) Note: I guess we need to check if it's null in the actual quest too --- zone/attack.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/attack.cpp b/zone/attack.cpp index af5a90540..ed6a26daf 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -1800,7 +1800,7 @@ void NPC::Damage(Mob* other, int32 damage, uint16 spell_id, EQEmu::skills::Skill //handle EVENT_ATTACK. Resets after we have not been attacked for 12 seconds if(attacked_timer.Check()) { - Log.Out(Logs::Detail, Logs::Combat, "Triggering EVENT_ATTACK due to attack by %s", other->GetName()); + Log.Out(Logs::Detail, Logs::Combat, "Triggering EVENT_ATTACK due to attack by %s", other ? other->GetName() : "nullptr"); parse->EventNPC(EVENT_ATTACK, this, other, "", 0); } attacked_timer.Start(CombatEventTimer_expire); From 2c3107fbe909708bc46de41b93aa5c96c7210bd6 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Mon, 22 Aug 2016 16:07:44 -0500 Subject: [PATCH 78/78] Uploading initial eqemu_server.pl (was eqemu_update.pl) to start deprecrating of eqemu_update.pl and re-working of the structure of the script, more to come. --- utils/scripts/eqemu_server.pl | 1552 +++++++++++++++++++++++++++++++++ 1 file changed, 1552 insertions(+) create mode 100644 utils/scripts/eqemu_server.pl diff --git a/utils/scripts/eqemu_server.pl b/utils/scripts/eqemu_server.pl new file mode 100644 index 000000000..b575bb1cf --- /dev/null +++ b/utils/scripts/eqemu_server.pl @@ -0,0 +1,1552 @@ +#!/usr/bin/perl + +########################################################### +#::: Automatic (Database) Upgrade Script +#::: Author: Akkadius +#::: Purpose: To upgrade databases with ease and maintain versioning +########################################################### + +$menu_displayed = 0; + +use Config; +use File::Copy qw(copy); +use POSIX qw(strftime); +use File::Path; +use File::Find; +use URI::Escape; +use Time::HiRes qw(usleep); + +$time_stamp = strftime('%m-%d-%Y', gmtime()); + +$console_output .= " Operating System is: $Config{osname}\n"; +if($Config{osname}=~/freebsd|linux/i){ $OS = "Linux"; } +if($Config{osname}=~/Win|MS/i){ $OS = "Windows"; } + +#::: If current version is less than what world is reporting, then download a new one... +$current_version = 14; + +if($ARGV[0] eq "V"){ + if($ARGV[1] > $current_version){ + print "eqemu_update.pl Automatic Database Upgrade Needs updating...\n"; + print " Current version: " . $current_version . "\n"; + print " New version: " . $ARGV[1] . "\n"; + get_remote_file("https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/eqemu_update.pl", "eqemu_update.pl"); + exit; + } + else{ + print "[Upgrade Script] No script update necessary \n"; + } + exit; +} + +#::: Sets database run stage check +$db_run_stage = 0; + +$perl_version = $^V; +$perl_version =~s/v//g; +print "Perl Version is " . $perl_version . "\n"; +if($perl_version > 5.12){ no warnings 'uninitialized'; } +no warnings; + +($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(); + +my $confile = "eqemu_config.xml"; #default +open(F, "<$confile"); +my $indb = 0; +while() { + s/\r//g; + if(//i) { $indb = 1; } + next unless($indb == 1); + if(/<\/database>/i) { $indb = 0; last; } + if(/(.*)<\/host>/i) { $host = $1; } + elsif(/(.*)<\/username>/i) { $user = $1; } + elsif(/(.*)<\/password>/i) { $pass = $1; } + elsif(/(.*)<\/db>/i) { $db = $1; } +} + +$console_output = +"============================================================ + EQEmu: Automatic Upgrade Check +============================================================ +"; + +if($OS eq "Windows"){ + $has_mysql_path = `echo %PATH%`; + if($has_mysql_path=~/MySQL|MariaDB/i){ + @mysql = split(';', $has_mysql_path); + foreach my $v (@mysql){ + if($v=~/MySQL|MariaDB/i){ + $v =~s/\n//g; + $path = trim($v) . "/mysql"; + last; + } + } + $console_output .= " (Windows) MySQL is in system path \n"; + $console_output .= " Path = " . $path . "\n"; + $console_output .= "============================================================\n"; + } +} + +#::: Linux Check +if($OS eq "Linux"){ + $path = `which mysql`; + if ($path eq "") { + $path = `which mariadb`; + } + $path =~s/\n//g; + + $console_output .= " (Linux) MySQL is in system path \n"; + $console_output .= " Path = " . $path . "\n"; + $console_output .= "============================================================\n"; +} + +#::: Path not found, error and exit +if($path eq ""){ + print "MySQL path not found, please add the path for automatic database upgrading to continue... \n\n"; + print "script_exiting...\n"; + exit; +} + +if($ARGV[0] eq "install_peq_db"){ + + $db_name = "peq"; + if($ARGV[1]){ + $db_name = $ARGV[1]; + } + + $db = $db_name; + + #::: Database Routines + print "MariaDB :: Creating Database '" . $db_name . "'\n"; + print `"$path" --host $host --user $user --password="$pass" -N -B -e "DROP DATABASE IF EXISTS $db_name;"`; + print `"$path" --host $host --user $user --password="$pass" -N -B -e "CREATE DATABASE $db_name"`; + if($OS eq "Windows"){ @db_version = split(': ', `world db_version`); } + if($OS eq "Linux"){ @db_version = split(': ', `./world db_version`); } + $bin_db_ver = trim($db_version[1]); + check_db_version_table(); + $local_db_ver = trim(get_mysql_result("SELECT version FROM db_version LIMIT 1")); + fetch_peq_db_full(); + print "\nFetching Latest Database Updates...\n"; + main_db_management(); + print "\nApplying Latest Database Updates...\n"; + main_db_management(); + + print get_mysql_result("UPDATE `launcher` SET `dynamics` = 30 WHERE `name` = 'zone'"); +} + +if($ARGV[0] eq "remove_duplicate_rules"){ + remove_duplicate_rule_values(); + exit; +} + +if($ARGV[0] eq "installer"){ + print "Running EQEmu Server installer routines...\n"; + mkdir('logs'); + mkdir('updates_staged'); + mkdir('shared'); + fetch_latest_windows_binaries(); + map_files_fetch_bulk(); + opcodes_fetch(); + plugins_fetch(); + quest_files_fetch(); + lua_modules_fetch(); + + #::: Binary dll's + get_remote_file("https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/lua51.dll", "lua51.dll", 1); + get_remote_file("https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/zlib1.dll", "zlib1.dll", 1); + get_remote_file("https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/libmysql.dll", "libmysql.dll", 1); + + #::: Server scripts + fetch_utility_scripts(); + + #::: Database Routines + print "MariaDB :: Creating Database 'peq'\n"; + print `"$path" --host $host --user $user --password="$pass" -N -B -e "DROP DATABASE IF EXISTS peq;"`; + print `"$path" --host $host --user $user --password="$pass" -N -B -e "CREATE DATABASE peq"`; + if($OS eq "Windows"){ @db_version = split(': ', `world db_version`); } + if($OS eq "Linux"){ @db_version = split(': ', `./world db_version`); } + $bin_db_ver = trim($db_version[1]); + check_db_version_table(); + $local_db_ver = trim(get_mysql_result("SELECT version FROM db_version LIMIT 1")); + fetch_peq_db_full(); + print "\nFetching Latest Database Updates...\n"; + main_db_management(); + print "\nApplying Latest Database Updates...\n"; + main_db_management(); + + print get_mysql_result("UPDATE `launcher` SET `dynamics` = 30 WHERE `name` = 'zone'"); + + if($OS eq "Windows"){ + check_windows_firewall_rules(); + do_windows_login_server_setup(); + } + exit; +} + +if($ARGV[0] eq "db_dump_compress"){ database_dump_compress(); exit; } +if($ARGV[0] eq "login_server_setup"){ + do_windows_login_server_setup(); + exit; +} + +#::: Create db_update working directory if not created +mkdir('db_update'); + +#::: Check if db_version table exists... +if(trim(get_mysql_result("SHOW COLUMNS FROM db_version LIKE 'Revision'")) ne "" && $db){ + print get_mysql_result("DROP TABLE db_version"); + print "Old db_version table present, dropping...\n\n"; +} + +sub check_db_version_table{ + if(get_mysql_result("SHOW TABLES LIKE 'db_version'") eq "" && $db){ + print get_mysql_result(" + CREATE TABLE db_version ( + version int(11) DEFAULT '0' + ) ENGINE=InnoDB DEFAULT CHARSET=latin1; + INSERT INTO db_version (version) VALUES ('1000');"); + print "Table 'db_version' does not exists.... Creating...\n\n"; + } +} + +check_db_version_table(); + +if($OS eq "Windows"){ @db_version = split(': ', `world db_version`); } +if($OS eq "Linux"){ @db_version = split(': ', `./world db_version`); } + +$bin_db_ver = trim($db_version[1]); +$local_db_ver = trim(get_mysql_result("SELECT version FROM db_version LIMIT 1")); + +#::: If ran from Linux startup script, supress output +if($bin_db_ver == $local_db_ver && $ARGV[0] eq "ran_from_start"){ + print "Database up to date...\n"; + exit; +} +else{ + print $console_output if $db; +} + +if($db){ + print " Binary Revision / Local: (" . $bin_db_ver . " / " . $local_db_ver . ")\n"; + + #::: Bots + #::: Make sure we're running a bots binary to begin with + if(trim($db_version[2]) > 0){ + $bots_local_db_version = get_bots_db_version(); + if($bots_local_db_version > 0){ + print " (Bots) Binary Revision / Local: (" . trim($db_version[2]) . " / " . $bots_local_db_version . ")\n"; + } + } + + #::: If World ran this script, and our version is up to date, continue... + if($bin_db_ver <= $local_db_ver && $ARGV[0] eq "ran_from_world"){ + print " Database up to Date: Continuing World Bootup...\n"; + print "============================================================\n"; + exit; + } + +} + +if($local_db_ver < $bin_db_ver && $ARGV[0] eq "ran_from_world"){ + print "You have missing database updates, type 1 or 2 to backup your database before running them as recommended...\n\n"; + #::: Display Menu + show_menu_prompt(); +} +else{ + #::: Most likely ran standalone + print "\n"; + show_menu_prompt(); +} + +sub do_update_self{ + get_remote_file("https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/eqemu_update.pl", "eqemu_update.pl"); + die "Rerun eqemu_update.pl"; +} + +sub fetch_utility_scripts { + if($OS eq "Windows"){ + get_remote_file("https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/t_database_backup.bat", "t_database_backup.bat"); + get_remote_file("https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/t_start_server.bat", "t_start_server.bat"); + get_remote_file("https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/t_start_server_with_login_server.bat", "t_start_server_with_login_server.bat"); + get_remote_file("https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/t_stop_server.bat", "t_stop_server.bat"); + get_remote_file("https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/t_server_crash_report.pl", "t_server_crash_report.pl"); + get_remote_file("https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/win_server_launcher.pl", "win_server_launcher.pl"); + get_remote_file("https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/t_start_server_with_login_server.bat", "t_start_server_with_login_server.bat"); + } + else { + print "No scripts found for OS: " . $OS . "...\n"; + } +} + +sub show_menu_prompt { + my %dispatch = ( + 1 => \&database_dump, + 2 => \&database_dump_compress, + 3 => \&main_db_management, + 4 => \&bots_db_management, + 5 => \&opcodes_fetch, + 6 => \&map_files_fetch, + 7 => \&plugins_fetch, + 8 => \&quest_files_fetch, + 9 => \&lua_modules_fetch, + 10 => \&aa_fetch, + 11 => \&fetch_latest_windows_binaries, + 12 => \&fetch_server_dlls, + 13 => \&do_windows_login_server_setup, + 14 => \&remove_duplicate_rule_values, + 15 => \&fetch_utility_scripts, + 18 => \&fetch_latest_windows_binaries_bots, + 19 => \&do_bots_db_schema_drop, + 20 => \&do_update_self, + 21 => \&database_dump_player_tables, + 0 => \&script_exit, + ); + + while (1) { + { + local $| = 1; + if(!$menu_show && ($ARGV[0] eq "ran_from_world" || $ARGV[0] eq "ran_from_start")){ + $menu_show++; + next; + } + print menu_options(), '> '; + $menu_displayed++; + if($menu_displayed > 50){ + print "Safety: Menu looping too many times, exiting...\n"; + exit; + } + } + + my $choice = <>; + + $choice =~ s/\A\s+//; + $choice =~ s/\s+\z//; + + if (defined(my $handler = $dispatch{$choice})) { + my $result = $handler->(); + unless (defined $result) { + exit 0; + } + } + else { + if($ARGV[0] ne "ran_from_world"){ + # warn "\n\nInvalid selection\n\n"; + } + } + } +} + +sub menu_options { + if(@total_updates){ + if($bots_db_management == 1){ + $option[3] = "Check and stage pending REQUIRED Database updates"; + $bots_management = "Run pending REQUIRED updates... (" . scalar (@total_updates) . ")"; + } + else{ + $option[3] = "Run pending REQUIRED updates... (" . scalar (@total_updates) . ")"; + if(get_mysql_result("SHOW TABLES LIKE 'bots'") eq ""){ + $bots_management = "Install bots database pre-requisites (Requires bots server binaries)"; + } + else{ + $bots_management = "Check for Bot pending REQUIRED database updates... (Must have bots enabled)"; + } + } + } + else{ + $option[3] = "Check and stage pending REQUIRED Database updates"; + $bots_management = "Check for Bot REQUIRED database updates... (Must have bots enabled)"; + } + +return <){ + chomp; + $o = $_; + $tables .= $o . ","; + } + $tables = substr($tables, 0, -1); + + print `perl db_dumper.pl database="$db" loc="backups" tables="$tables" backup_name="player_tables_export" nolock`; + + print "\nPress any key to continue...\n"; + + <>; #Read from STDIN + +} + +sub database_dump_compress { + check_for_database_dump_script(); + print "Performing database backup....\n"; + print `perl db_dumper.pl database="$db" loc="backups" compress`; +} + +sub script_exit{ + #::: Cleanup staged folder... + rmtree("updates_staged/"); + exit; +} + +#::: Returns Tab Delimited MySQL Result from Command Line +sub get_mysql_result{ + my $run_query = $_[0]; + if(!$db){ return; } + if($OS eq "Windows"){ return `"$path" --host $host --user $user --password="$pass" $db -N -B -e "$run_query"`; } + if($OS eq "Linux"){ + $run_query =~s/`//g; + return `$path --user="$user" --host $host --password="$pass" $db -N -B -e "$run_query"`; + } +} + +sub get_mysql_result_from_file{ + my $update_file = $_[0]; + if(!$db){ return; } + if($OS eq "Windows"){ return `"$path" --host $host --user $user --password="$pass" --force $db < $update_file`; } + if($OS eq "Linux"){ return `"$path" --host $host --user $user --password="$pass" --force $db < $update_file`; } +} + +#::: Gets Remote File based on URL (1st Arg), and saves to destination file (2nd Arg) +#::: Example: get_remote_file("https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/db_update_manifest.txt", "db_update/db_update_manifest.txt"); +sub get_remote_file{ + my $URL = $_[0]; + my $Dest_File = $_[1]; + my $content_type = $_[2]; + + #::: Build file path of the destination file so that we may check for the folder's existence and make it if necessary + if($Dest_File=~/\//i){ + my @dir_path = split('/', $Dest_File); + $build_path = ""; + $di = 0; + while($dir_path[$di]){ + $build_path .= $dir_path[$di] . "/"; + #::: If path does not exist, create the directory... + if (!-d $build_path) { + mkdir($build_path); + } + if(!$dir_path[$di + 2] && $dir_path[$di + 1]){ + # print $actual_path . "\n"; + $actual_path = $build_path; + last; + } + $di++; + } + } + + if($OS eq "Windows"){ + #::: For non-text type requests... + if($content_type == 1){ + $break = 0; + while($break == 0) { + use LWP::Simple qw(getstore); + if(!getstore($URL, $Dest_File)){ + # print "Error, no connection or failed request...\n\n"; + } + # sleep(1); + #::: Make sure the file exists before continuing... + if(-e $Dest_File) { + $break = 1; + print " [URL] :: " . $URL . "\n"; + print " [Saved] :: " . $Dest_File . "\n"; + } else { $break = 0; } + usleep(500); + } + } + else{ + $break = 0; + while($break == 0) { + require LWP::UserAgent; + my $ua = LWP::UserAgent->new; + $ua->timeout(10); + $ua->env_proxy; + my $response = $ua->get($URL); + if ($response->is_success){ + open (FILE, '> ' . $Dest_File . ''); + print FILE $response->decoded_content; + close (FILE); + } + else { + # print "Error, no connection or failed request...\n\n"; + } + if(-e $Dest_File) { + $break = 1; + print " [URL] :: " . $URL . "\n"; + print " [Saved] :: " . $Dest_File . "\n"; + } else { $break = 0; } + usleep(500); + } + } + } + if($OS eq "Linux"){ + #::: wget -O db_update/db_update_manifest.txt https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/db_update_manifest.txt + $wget = `wget --no-check-certificate --quiet -O $Dest_File $URL`; + print " o URL: (" . $URL . ")\n"; + print " o Saved: (" . $Dest_File . ") \n"; + if($wget=~/unable to resolve/i){ + print "Error, no connection or failed request...\n\n"; + #die; + } + } +} + +#::: Trim Whitespaces +sub trim { + my $string = $_[0]; + $string =~ s/^\s+//; + $string =~ s/\s+$//; + return $string; +} + +#::: Fetch Latest PEQ AA's +sub aa_fetch{ + if(!$db){ + print "No database present, check your eqemu_config.xml for proper MySQL/MariaDB configuration...\n"; + return; + } + + print "Pulling down PEQ AA Tables...\n"; + get_remote_file("https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/peq_aa_tables_post_rework.sql", "db_update/peq_aa_tables_post_rework.sql"); + print "\n\nInstalling AA Tables...\n"; + print get_mysql_result_from_file("db_update/peq_aa_tables_post_rework.sql"); + print "\nDone...\n\n"; +} + +#::: Fetch Latest Opcodes +sub opcodes_fetch{ + print "Pulling down latest opcodes...\n"; + %opcodes = ( + 1 => ["opcodes", "https://raw.githubusercontent.com/EQEmu/Server/master/utils/patches/opcodes.conf"], + 2 => ["mail_opcodes", "https://raw.githubusercontent.com/EQEmu/Server/master/utils/patches/mail_opcodes.conf"], + 3 => ["Titanium", "https://raw.githubusercontent.com/EQEmu/Server/master/utils/patches/patch_Titanium.conf"], + 4 => ["Secrets of Faydwer", "https://raw.githubusercontent.com/EQEmu/Server/master/utils/patches/patch_SoF.conf"], + 5 => ["Seeds of Destruction", "https://raw.githubusercontent.com/EQEmu/Server/master/utils/patches/patch_SoD.conf"], + 6 => ["Underfoot", "https://raw.githubusercontent.com/EQEmu/Server/master/utils/patches/patch_UF.conf"], + 7 => ["Rain of Fear", "https://raw.githubusercontent.com/EQEmu/Server/master/utils/patches/patch_RoF.conf"], + 8 => ["Rain of Fear 2", "https://raw.githubusercontent.com/EQEmu/Server/master/utils/patches/patch_RoF2.conf"], + ); + $loop = 1; + while($opcodes{$loop}[0]){ + #::: Split the URL by the patches folder to get the file name from URL + @real_file = split("patches/", $opcodes{$loop}[1]); + $find = 0; + while($real_file[$find]){ + $file_name = $real_file[$find]; + $find++; + } + + print "\nDownloading (" . $opcodes{$loop}[0] . ") File: '" . $file_name . "'...\n\n"; + get_remote_file($opcodes{$loop}[1], $file_name); + $loop++; + } + print "\nDone...\n\n"; +} + +sub remove_duplicate_rule_values{ + $ruleset_id = trim(get_mysql_result("SELECT `ruleset_id` FROM `rule_sets` WHERE `name` = 'default'")); + print "Default Ruleset ID: " . $ruleset_id . "\n"; + + $total_removed = 0; + #::: Store Default values... + $mysql_result = get_mysql_result("SELECT * FROM `rule_values` WHERE `ruleset_id` = " . $ruleset_id); + my @lines = split("\n", $mysql_result); + foreach my $val (@lines){ + my @values = split("\t", $val); + $rule_set_values{$values[1]}[0] = $values[2]; + } + #::: Compare default values against other rulesets to check for duplicates... + $mysql_result = get_mysql_result("SELECT * FROM `rule_values` WHERE `ruleset_id` != " . $ruleset_id); + my @lines = split("\n", $mysql_result); + foreach my $val (@lines){ + my @values = split("\t", $val); + if($values[2] == $rule_set_values{$values[1]}[0]){ + print "DUPLICATE : " . $values[1] . " (Ruleset (" . $values[0] . ")) matches default value of : " . $values[2] . ", removing...\n"; + get_mysql_result("DELETE FROM `rule_values` WHERE `ruleset_id` = " . $values[0] . " AND `rule_name` = '" . $values[1] . "'"); + $total_removed++; + } + } + + print "Total duplicate rules removed... " . $total_removed . "\n"; +} + +sub copy_file{ + $l_source_file = $_[0]; + $l_dest_file = $_[1]; + if($l_dest_file=~/\//i){ + my @dir_path = split('/', $l_dest_file); + $build_path = ""; + $di = 0; + while($dir_path[$di]){ + $build_path .= $dir_path[$di] . "/"; + #::: If path does not exist, create the directory... + if (!-d $build_path) { + mkdir($build_path); + } + if(!$dir_path[$di + 2] && $dir_path[$di + 1]){ + # print $actual_path . "\n"; + $actual_path = $build_path; + last; + } + $di++; + } + } + copy $l_source_file, $l_dest_file; +} + +sub fetch_latest_windows_binaries{ + print "\n --- Fetching Latest Windows Binaries... --- \n"; + get_remote_file("https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/master_windows_build.zip", "updates_staged/master_windows_build.zip", 1); + print "\n --- Fetched Latest Windows Binaries... --- \n"; + print "\n --- Extracting... --- \n"; + unzip('updates_staged/master_windows_build.zip', 'updates_staged/binaries/'); + my @files; + my $start_dir = "updates_staged/binaries"; + find( + sub { push @files, $File::Find::name unless -d; }, + $start_dir + ); + for my $file (@files) { + $dest_file = $file; + $dest_file =~s/updates_staged\/binaries\///g; + print "Installing :: " . $dest_file . "\n"; + copy_file($file, $dest_file); + } + print "\n --- Done... --- \n"; + + rmtree('updates_staged'); +} + +sub fetch_latest_windows_binaries_bots{ + print "\n --- Fetching Latest Windows Binaries with Bots... --- \n"; + get_remote_file("https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/master_windows_build_bots.zip", "updates_staged/master_windows_build_bots.zip", 1); + print "\n --- Fetched Latest Windows Binaries with Bots... --- \n"; + print "\n --- Extracting... --- \n"; + unzip('updates_staged/master_windows_build_bots.zip', 'updates_staged/binaries/'); + my @files; + my $start_dir = "updates_staged/binaries"; + find( + sub { push @files, $File::Find::name unless -d; }, + $start_dir + ); + for my $file (@files) { + $dest_file = $file; + $dest_file =~s/updates_staged\/binaries\///g; + print "Installing :: " . $dest_file . "\n"; + copy_file($file, $dest_file); + } + print "\n --- Done... --- \n"; + + rmtree('updates_staged'); +} + +sub do_windows_login_server_setup{ + print "\n --- Fetching Loginserver... --- \n"; + get_remote_file("https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/login_server.zip", "updates_staged/login_server.zip", 1); + print "\n --- Extracting... --- \n"; + unzip('updates_staged/login_server.zip', 'updates_staged/login_server/'); + my @files; + my $start_dir = "updates_staged/login_server"; + find( + sub { push @files, $File::Find::name unless -d; }, + $start_dir + ); + for my $file (@files) { + $dest_file = $file; + $dest_file =~s/updates_staged\/login_server\///g; + print "Installing :: " . $dest_file . "\n"; + copy_file($file, $dest_file); + } + print "\n Done... \n"; + + print "Pulling down Loginserver database tables...\n"; + get_remote_file("https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/login_server_tables.sql", "db_update/login_server_tables.sql"); + print "\n\nInstalling Loginserver tables...\n"; + print get_mysql_result_from_file("db_update/login_server_tables.sql"); + print "\nDone...\n\n"; + + add_login_server_firewall_rules(); + + rmtree('updates_staged'); + rmtree('db_update'); + + print "\nPress any key to continue...\n"; + + <>; #Read from STDIN + +} + +sub add_login_server_firewall_rules{ + #::: Check Loginserver Firewall install for Windows + if($OS eq "Windows"){ + $output = `netsh advfirewall firewall show rule name=all`; + @output_buffer = split("\n", $output); + $has_loginserver_rules_titanium = 0; + $has_loginserver_rules_sod = 0; + foreach my $val (@output_buffer){ + if($val=~/Rule Name/i){ + $val=~s/Rule Name://g; + if($val=~/EQEmu Loginserver/i && $val=~/Titanium/i){ + $has_loginserver_rules_titanium = 1; + print "Found existing rule :: " . trim($val) . "\n"; + } + if($val=~/EQEmu Loginserver/i && $val=~/SOD/i){ + $has_loginserver_rules_sod = 1; + print "Found existing rule :: " . trim($val) . "\n"; + } + } + } + + if($has_loginserver_rules_titanium == 0){ + print "Attempting to add EQEmu Loginserver Firewall Rules (Titanium) (TCP) port 5998 \n"; + print `netsh advfirewall firewall add rule name="EQEmu Loginserver (Titanium) (5998) TCP" dir=in action=allow protocol=TCP localport=5998`; + print "Attempting to add EQEmu Loginserver Firewall Rules (Titanium) (UDP) port 5998 \n"; + print `netsh advfirewall firewall add rule name="EQEmu Loginserver (Titanium) (5998) UDP" dir=in action=allow protocol=UDP localport=5998`; + } + if($has_loginserver_rules_sod == 0){ + print "Attempting to add EQEmu Loginserver Firewall Rules (SOD+) (TCP) port 5999 \n"; + print `netsh advfirewall firewall add rule name="EQEmu Loginserver (SOD+) (5999) TCP" dir=in action=allow protocol=TCP localport=5999`; + print "Attempting to add EQEmu Loginserver Firewall Rules (SOD+) (UDP) port 5999 \n"; + print `netsh advfirewall firewall add rule name="EQEmu Loginserver (SOD+) (5999) UDP" dir=in action=allow protocol=UDP localport=5999`; + } + + print "If firewall rules don't add you must run this script (eqemu_update.pl) as administrator\n"; + print "\n"; + print "#::: Instructions \n"; + print "In order to connect your server to the loginserver you must point your eqemu_config.xml to your local server similar to the following:\n"; + print " + + login.eqemulator.net + 5998 + + + + + 127.0.0.1 + 5998 + + + + "; + print "\nWhen done, make sure your EverQuest client points to your loginserver's IP (In this case it would be 127.0.0.1) in the eqhosts.txt file\n"; + } +} + +sub check_windows_firewall_rules{ + $output = `netsh advfirewall firewall show rule name=all`; + @output_buffer = split("\n", $output); + $has_world_rules = 0; + $has_zone_rules = 0; + foreach my $val (@output_buffer){ + if($val=~/Rule Name/i){ + $val=~s/Rule Name://g; + if($val=~/EQEmu World/i){ + $has_world_rules = 1; + print "Found existing rule :: " . trim($val) . "\n"; + } + if($val=~/EQEmu Zone/i){ + $has_zone_rules = 1; + print "Found existing rule :: " . trim($val) . "\n"; + } + } + } + + if($has_world_rules == 0){ + print "Attempting to add EQEmu World Firewall Rules (TCP) port 9000 \n"; + print `netsh advfirewall firewall add rule name="EQEmu World (9000) TCP" dir=in action=allow protocol=TCP localport=9000`; + print "Attempting to add EQEmu World Firewall Rules (UDP) port 9000 \n"; + print `netsh advfirewall firewall add rule name="EQEmu World (9000) UDP" dir=in action=allow protocol=UDP localport=9000`; + } + if($has_zone_rules == 0){ + print "Attempting to add EQEmu Zones (7000-7500) TCP \n"; + print `netsh advfirewall firewall add rule name="EQEmu Zones (7000-7500) TCP" dir=in action=allow protocol=TCP localport=7000-7500`; + print "Attempting to add EQEmu Zones (7000-7500) UDP \n"; + print `netsh advfirewall firewall add rule name="EQEmu Zones (7000-7500) UDP" dir=in action=allow protocol=UDP localport=7000-7500`; + } +} + +sub fetch_server_dlls{ + print "Fetching lua51.dll, zlib1.dll, libmysql.dll...\n"; + get_remote_file("https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/lua51.dll", "lua51.dll", 1); + get_remote_file("https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/zlib1.dll", "zlib1.dll", 1); + get_remote_file("https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/libmysql.dll", "libmysql.dll", 1); +} + +sub fetch_peq_db_full{ + print "Downloading latest PEQ Database... Please wait...\n"; + get_remote_file("http://edit.peqtgc.com/weekly/peq_beta.zip", "updates_staged/peq_beta.zip", 1); + print "Downloaded latest PEQ Database... Extracting...\n"; + unzip('updates_staged/peq_beta.zip', 'updates_staged/peq_db/'); + my $start_dir = "updates_staged\\peq_db"; + find( + sub { push @files, $File::Find::name unless -d; }, + $start_dir + ); + for my $file (@files) { + $dest_file = $file; + $dest_file =~s/updates_staged\\peq_db\///g; + if($file=~/peqbeta|player_tables/i){ + print "MariaDB :: Installing :: " . $dest_file . "\n"; + get_mysql_result_from_file($file); + } + if($file=~/eqtime/i){ + print "Installing eqtime.cfg\n"; + copy_file($file, "eqtime.cfg"); + } + } +} + +sub map_files_fetch_bulk{ + print "\n --- Fetching Latest Maps... (This could take a few minutes...) --- \n"; + get_remote_file("http://github.com/Akkadius/EQEmuMaps/archive/master.zip", "maps/maps.zip", 1); + unzip('maps/maps.zip', 'maps/'); + my @files; + my $start_dir = "maps\\EQEmuMaps-master\\maps"; + find( + sub { push @files, $File::Find::name unless -d; }, + $start_dir + ); + for my $file (@files) { + $dest_file = $file; + $dest_file =~s/maps\\EQEmuMaps-master\\maps\///g; + print "Installing :: " . $dest_file . "\n"; + copy_file($file, "maps/" . $new_file); + } + print "\n --- Fetched Latest Maps... --- \n"; + + rmtree('maps/EQEmuMaps-master'); + unlink('maps/maps.zip'); +} + +sub map_files_fetch{ + print "\n --- Fetching Latest Maps --- \n"; + + get_remote_file("https://raw.githubusercontent.com/Akkadius/EQEmuMaps/master/!eqemu_maps_manifest.txt", "updates_staged/eqemu_maps_manifest.txt"); + + #::: Get Data from manifest + open (FILE, "updates_staged/eqemu_maps_manifest.txt"); + $i = 0; + while (){ + chomp; + $o = $_; + @manifest_map_data = split(',', $o); + if($manifest_map_data[0] ne ""){ + $maps_manifest[$i] = [$manifest_map_data[0], $manifest_map_data[1]]; + $i++; + } + } + + #::: Download + $fc = 0; + for($m = 0; $m <= $i; $m++){ + my $file_existing = $maps_manifest[$m][0]; + my $file_existing_size = (stat $file_existing)[7]; + if($file_existing_size != $maps_manifest[$m][1]){ + print "Updating: '" . $maps_manifest[$m][0] . "'\n"; + get_remote_file("https://raw.githubusercontent.com/Akkadius/EQEmuMaps/master/" . $maps_manifest[$m][0], $maps_manifest[$m][0], 1); + $fc++; + } + } + + if($fc == 0){ + print "\nNo Map Updates found... \n\n"; + } +} + +sub quest_files_fetch{ + if (!-e "updates_staged/Quests-Plugins-master/quests/") { + print "\n --- Fetching Latest Quests --- \n"; + get_remote_file("https://github.com/EQEmu/Quests-Plugins/archive/master.zip", "updates_staged/Quests-Plugins-master.zip", 1); + print "\nFetched latest quests...\n"; + mkdir('updates_staged'); + unzip('updates_staged/Quests-Plugins-master.zip', 'updates_staged/'); + } + + $fc = 0; + use File::Find; + use File::Compare; + + my @files; + my $start_dir = "updates_staged/Quests-Plugins-master/quests/"; + find( + sub { push @files, $File::Find::name unless -d; }, + $start_dir + ); + for my $file (@files) { + if($file=~/\.pl|\.lua|\.ext/i){ + $staged_file = $file; + $dest_file = $file; + $dest_file =~s/updates_staged\/Quests-Plugins-master\///g; + + if (!-e $dest_file) { + copy_file($staged_file, $dest_file); + print "Installing :: '" . $dest_file . "'\n"; + $fc++; + } + else{ + $diff = do_file_diff($dest_file, $staged_file); + if($diff ne ""){ + $backup_dest = "updates_backups/" . $time_stamp . "/" . $dest_file; + + print $diff . "\n"; + print "\nFile Different :: '" . $dest_file . "'\n"; + print "\nDo you wish to update this Quest? '" . $dest_file . "' [Yes (Enter) - No (N)] \nA backup will be found in '" . $backup_dest . "'\n"; + my $input = ; + if($input=~/N/i){} + else{ + #::: Make a backup + copy_file($dest_file, $backup_dest); + #::: Copy staged to running + copy($staged_file, $dest_file); + print "Installing :: '" . $dest_file . "'\n\n"; + } + $fc++; + } + } + } + } + + rmtree('updates_staged'); + + if($fc == 0){ + print "\nNo Quest Updates found... \n\n"; + } +} + +sub lua_modules_fetch{ + if (!-e "updates_staged/Quests-Plugins-master/quests/lua_modules/") { + print "\n --- Fetching Latest LUA Modules --- \n"; + get_remote_file("https://github.com/EQEmu/Quests-Plugins/archive/master.zip", "updates_staged/Quests-Plugins-master.zip", 1); + print "\nFetched latest LUA Modules...\n"; + unzip('updates_staged/Quests-Plugins-master.zip', 'updates_staged/'); + } + + $fc = 0; + use File::Find; + use File::Compare; + + my @files; + my $start_dir = "updates_staged/Quests-Plugins-master/quests/lua_modules/"; + find( + sub { push @files, $File::Find::name unless -d; }, + $start_dir + ); + for my $file (@files) { + if($file=~/\.pl|\.lua|\.ext/i){ + $staged_file = $file; + $dest_file = $file; + $dest_file =~s/updates_staged\/Quests-Plugins-master\/quests\///g; + + if (!-e $dest_file) { + copy_file($staged_file, $dest_file); + print "Installing :: '" . $dest_file . "'\n"; + $fc++; + } + else{ + $diff = do_file_diff($dest_file, $staged_file); + if($diff ne ""){ + $backup_dest = "updates_backups/" . $time_stamp . "/" . $dest_file; + print $diff . "\n"; + print "\nFile Different :: '" . $dest_file . "'\n"; + print "\nDo you wish to update this LUA Module? '" . $dest_file . "' [Yes (Enter) - No (N)] \nA backup will be found in '" . $backup_dest . "'\n"; + my $input = ; + if($input=~/N/i){} + else{ + #::: Make a backup + copy_file($dest_file, $backup_dest); + #::: Copy staged to running + copy($staged_file, $dest_file); + print "Installing :: '" . $dest_file . "'\n\n"; + } + $fc++; + } + } + } + } + + if($fc == 0){ + print "\nNo LUA Modules Updates found... \n\n"; + } +} + +sub plugins_fetch{ + if (!-e "updates_staged/Quests-Plugins-master/plugins/") { + print "\n --- Fetching Latest Plugins --- \n"; + get_remote_file("https://github.com/EQEmu/Quests-Plugins/archive/master.zip", "updates_staged/Quests-Plugins-master.zip", 1); + print "\nFetched latest plugins...\n"; + unzip('updates_staged/Quests-Plugins-master.zip', 'updates_staged/'); + } + + $fc = 0; + use File::Find; + use File::Compare; + + my @files; + my $start_dir = "updates_staged/Quests-Plugins-master/plugins/"; + find( + sub { push @files, $File::Find::name unless -d; }, + $start_dir + ); + for my $file (@files) { + if($file=~/\.pl|\.lua|\.ext/i){ + $staged_file = $file; + $dest_file = $file; + $dest_file =~s/updates_staged\/Quests-Plugins-master\///g; + + if (!-e $dest_file) { + copy_file($staged_file, $dest_file); + print "Installing :: '" . $dest_file . "'\n"; + $fc++; + } + else{ + $diff = do_file_diff($dest_file, $staged_file); + if($diff ne ""){ + $backup_dest = "updates_backups/" . $time_stamp . "/" . $dest_file; + print $diff . "\n"; + print "\nFile Different :: '" . $dest_file . "'\n"; + print "\nDo you wish to update this Plugin? '" . $dest_file . "' [Yes (Enter) - No (N)] \nA backup will be found in '" . $backup_dest . "'\n"; + my $input = ; + if($input=~/N/i){} + else{ + #::: Make a backup + copy_file($dest_file, $backup_dest); + #::: Copy staged to running + copy($staged_file, $dest_file); + print "Installing :: '" . $dest_file . "'\n\n"; + } + $fc++; + } + } + } + } + + if($fc == 0){ + print "\nNo Plugin Updates found... \n\n"; + } +} + +sub do_file_diff{ + $file_1 = $_[0]; + $file_2 = $_[1]; + if($OS eq "Windows"){ + eval "use Text::Diff"; + $diff = diff($file_1, $file_2, { STYLE => "Unified" }); + return $diff; + } + if($OS eq "Linux"){ + # print 'diff -u "$file_1" "$file_2"' . "\n"; + return `diff -u "$file_1" "$file_2"`; + } +} + +sub unzip{ + $archive_to_unzip = $_[0]; + $dest_folder = $_[1]; + + if($OS eq "Windows"){ + eval "use Archive::Zip qw( :ERROR_CODES :CONSTANTS )"; + my $zip = Archive::Zip->new(); + unless ( $zip->read($archive_to_unzip) == AZ_OK ) { + die 'read error'; + } + print "Extracting...\n"; + $zip->extractTree('', $dest_folder); + } + if($OS eq "Linux"){ + print `unzip -o "$archive_to_unzip" -d "$dest_folder"`; + } +} + +sub are_file_sizes_different{ + $file_1 = $_[0]; + $file_2 = $_[1]; + my $file_1 = (stat $file_1)[7]; + my $file_2 = (stat $file_2)[7]; + # print $file_1 . " :: " . $file_2 . "\n"; + if($file_1 != $file_2){ + return 1; + } + return; +} + +sub do_bots_db_schema_drop{ + #"drop_bots.sql" is run before reverting database back to 'normal' + print "Fetching drop_bots.sql...\n"; + get_remote_file("https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/git/bots/drop_bots.sql", "db_update/drop_bots.sql"); + print get_mysql_result_from_file("db_update/drop_bots.sql"); + + print "Restoring normality...\n"; + print get_mysql_result("DELETE FROM `rule_values` WHERE `rule_name` LIKE 'Bots:%';"); + + if(get_mysql_result("SHOW TABLES LIKE 'commands'") ne "" && $db){ + print get_mysql_result("DELETE FROM `commands` WHERE `command` LIKE 'bot';"); + } + + if(get_mysql_result("SHOW TABLES LIKE 'command_settings'") ne "" && $db){ + print get_mysql_result("DELETE FROM `command_settings` WHERE `command` LIKE 'bot';"); + } + + if(get_mysql_result("SHOW KEYS FROM `group_id` WHERE `Key_name` LIKE 'PRIMARY'") ne "" && $db){ + print get_mysql_result("ALTER TABLE `group_id` DROP PRIMARY KEY;"); + } + print get_mysql_result("ALTER TABLE `group_id` ADD PRIMARY KEY (`groupid`, `charid`, `ismerc`);"); + + if(get_mysql_result("SHOW KEYS FROM `guild_members` WHERE `Key_name` LIKE 'PRIMARY'") ne "" && $db){ + print get_mysql_result("ALTER TABLE `guild_members` DROP PRIMARY KEY;"); + } + print get_mysql_result("ALTER TABLE `guild_members` ADD PRIMARY KEY (`char_id`);"); + + print get_mysql_result("UPDATE `spawn2` SET `enabled` = 0 WHERE `id` IN (59297,59298);"); + + if(get_mysql_result("SHOW COLUMNS FROM `db_version` LIKE 'bots_version'") ne "" && $db){ + print get_mysql_result("UPDATE `db_version` SET `bots_version` = 0;"); + } +} + +sub modify_db_for_bots{ + #Called after the db bots schema (2015_09_30_bots.sql) has been loaded + print "Modifying database for bots...\n"; + print get_mysql_result("UPDATE `spawn2` SET `enabled` = 1 WHERE `id` IN (59297,59298);"); + + if(get_mysql_result("SHOW KEYS FROM `guild_members` WHERE `Key_name` LIKE 'PRIMARY'") ne "" && $db){ + print get_mysql_result("ALTER TABLE `guild_members` DROP PRIMARY KEY;"); + } + + if(get_mysql_result("SHOW KEYS FROM `group_id` WHERE `Key_name` LIKE 'PRIMARY'") ne "" && $db){ + print get_mysql_result("ALTER TABLE `group_id` DROP PRIMARY KEY;"); + } + print get_mysql_result("ALTER TABLE `group_id` ADD PRIMARY KEY USING BTREE(`groupid`, `charid`, `name`, `ismerc`);"); + + if(get_mysql_result("SHOW TABLES LIKE 'command_settings'") ne "" && get_mysql_result("SELECT `command` FROM `command_settings` WHERE `command` LIKE 'bot'") eq "" && $db){ + print get_mysql_result("INSERT INTO `command_settings` VALUES ('bot', '0', '');"); + } + + if(get_mysql_result("SHOW TABLES LIKE 'commands'") ne "" && get_mysql_result("SELECT `command` FROM `commands` WHERE `command` LIKE 'bot'") eq "" && $db){ + print get_mysql_result("INSERT INTO `commands` VALUES ('bot', '0');"); + } + + if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:BotAAExpansion'") ne "" && $db){ + print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:AAExpansion' WHERE `rule_name` LIKE 'Bots:BotAAExpansion';"); + } + if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:AAExpansion'") eq "" && $db){ + print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:AAExpansion', '8', 'The expansion through which bots will obtain AAs');"); + } + + if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:CreateBotCount'") ne "" && $db){ + print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:CreationLimit' WHERE `rule_name` LIKE 'Bots:CreateBotCount';"); + } + if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:CreationLimit'") eq "" && $db){ + print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:CreationLimit', '150', 'Number of bots that each account can create');"); + } + + if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:BotFinishBuffing'") ne "" && $db){ + print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:FinishBuffing' WHERE `rule_name` LIKE 'Bots:BotFinishBuffing';"); + } + if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:FinishBuffing'") eq "" && $db){ + print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:FinishBuffing', 'false', 'Allow for buffs to complete even if the bot caster is out of mana. Only affects buffing out of combat.');"); + } + + if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:BotGroupBuffing'") ne "" && $db){ + print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:GroupBuffing' WHERE `rule_name` LIKE 'Bots:BotGroupBuffing';"); + } + if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:GroupBuffing'") eq "" && $db){ + print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:GroupBuffing', 'false', 'Bots will cast single target buffs as group buffs, default is false for single. Does not make single target buffs work for MGB.');"); + } + + if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:BotManaRegen'") ne "" && $db){ + print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:ManaRegen' WHERE `rule_name` LIKE 'Bots:BotManaRegen';"); + } + if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:ManaRegen'") eq "" && $db){ + print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:ManaRegen', '3.0', 'Adjust mana regen for bots, 1 is fast and higher numbers slow it down 3 is about the same as players.');"); + } + + if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:BotQuest'") ne "" && $db){ + print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:QuestableSpawnLimit' WHERE `rule_name` LIKE 'Bots:BotQuest';"); + } + if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:QuestableSpawnLimit'") eq "" && $db){ + print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:QuestableSpawnLimit', 'false', 'Optional quest method to manage bot spawn limits using the quest_globals name bot_spawn_limit, see: /bazaar/Aediles_Thrall.pl');"); + } + + if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:BotSpellQuest'") ne "" && $db){ + print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:QuestableSpells' WHERE `rule_name` LIKE 'Bots:BotSpellQuest';"); + } + if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:QuestableSpells'") eq "" && $db){ + print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:QuestableSpells', 'false', 'Anita Thrall\\\'s (Anita_Thrall.pl) Bot Spell Scriber quests.');"); + } + + if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:SpawnBotCount'") ne "" && $db){ + print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:SpawnLimit' WHERE `rule_name` LIKE 'Bots:SpawnBotCount';"); + } + if(get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:SpawnLimit'") eq "" && $db){ + print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:SpawnLimit', '71', 'Number of bots a character can have spawned at one time, You + 71 bots is a 12 group raid');"); + } + + convert_existing_bot_data(); +} + +sub convert_existing_bot_data{ + if(get_mysql_result("SHOW TABLES LIKE 'bots'") ne "" && $db){ + print "Converting existing bot data...\n"; + print get_mysql_result("INSERT INTO `bot_data` (`bot_id`, `owner_id`, `spells_id`, `name`, `last_name`, `zone_id`, `gender`, `race`, `class`, `level`, `creation_day`, `last_spawn`, `time_spawned`, `size`, `face`, `hair_color`, `hair_style`, `beard`, `beard_color`, `eye_color_1`, `eye_color_2`, `drakkin_heritage`, `drakkin_tattoo`, `drakkin_details`, `ac`, `atk`, `hp`, `mana`, `str`, `sta`, `cha`, `dex`, `int`, `agi`, `wis`, `fire`, `cold`, `magic`, `poison`, `disease`, `corruption`) SELECT `BotID`, `BotOwnerCharacterID`, `BotSpellsID`, `Name`, `LastName`, `LastZoneId`, `Gender`, `Race`, `Class`, `BotLevel`, UNIX_TIMESTAMP(`BotCreateDate`), UNIX_TIMESTAMP(`LastSpawnDate`), `TotalPlayTime`, `Size`, `Face`, `LuclinHairColor`, `LuclinHairStyle`, `LuclinBeard`, `LuclinBeardColor`, `LuclinEyeColor`, `LuclinEyeColor2`, `DrakkinHeritage`, `DrakkinTattoo`, `DrakkinDetails`, `AC`, `ATK`, `HP`, `Mana`, `STR`, `STA`, `CHA`, `DEX`, `_INT`, `AGI`, `WIS`, `FR`, `CR`, `MR`, `PR`, `DR`, `Corrup` FROM `bots`;"); + + print get_mysql_result("INSERT INTO `bot_inspect_messages` (`bot_id`, `inspect_message`) SELECT `BotID`, `BotInspectMessage` FROM `bots`;"); + + print get_mysql_result("RENAME TABLE `bots` TO `bots_old`;"); + } + + if(get_mysql_result("SHOW TABLES LIKE 'botstances'") ne "" && $db){ + print get_mysql_result("INSERT INTO `bot_stances` (`bot_id`, `stance_id`) SELECT bs.`BotID`, bs.`StanceID` FROM `botstances` bs INNER JOIN `bot_data` bd ON bs.`BotID` = bd.`bot_id`;"); + + print get_mysql_result("RENAME TABLE `botstances` TO `botstances_old`;"); + } + + if(get_mysql_result("SHOW TABLES LIKE 'bottimers'") ne "" && $db){ + print get_mysql_result("INSERT INTO `bot_timers` (`bot_id`, `timer_id`, `timer_value`) SELECT bt.`BotID`, bt.`TimerID`, bt.`Value` FROM `bottimers` bt INNER JOIN `bot_data` bd ON bt.`BotID` = bd.`bot_id`;"); + + print get_mysql_result("RENAME TABLE `bottimers` TO `bottimers_old`;"); + } + + if(get_mysql_result("SHOW TABLES LIKE 'botbuffs'") ne "" && $db){ + print get_mysql_result("INSERT INTO `bot_buffs` (`buffs_index`, `bot_id`, `spell_id`, `caster_level`, `duration_formula`, `tics_remaining`, `poison_counters`, `disease_counters`, `curse_counters`, `corruption_counters`, `numhits`, `melee_rune`, `magic_rune`, `persistent`) SELECT bb.`BotBuffId`, bb.`BotId`, bb.`SpellId`, bb.`CasterLevel`, bb.`DurationFormula`, bb.`TicsRemaining`, bb.`PoisonCounters`, bb.`DiseaseCounters`, bb.`CurseCounters`, bb.`CorruptionCounters`, bb.`HitCount`, bb.`MeleeRune`, bb.`MagicRune`, bb.`Persistent` FROM `botbuffs` bb INNER JOIN `bot_data` bd ON bb.`BotId` = bd.`bot_id`;"); + + if(get_mysql_result("SHOW COLUMNS FROM `botbuffs` LIKE 'dot_rune'") ne "" && $db){ + print get_mysql_result("UPDATE `bot_buffs` bb INNER JOIN `botbuffs` bbo ON bb.`buffs_index` = bbo.`BotBuffId` SET bb.`dot_rune` = bbo.`dot_rune` WHERE bb.`bot_id` = bbo.`BotID`;"); + } + + if(get_mysql_result("SHOW COLUMNS FROM `botbuffs` LIKE 'caston_x'") ne "" && $db){ + print get_mysql_result("UPDATE `bot_buffs` bb INNER JOIN `botbuffs` bbo ON bb.`buffs_index` = bbo.`BotBuffId` SET bb.`caston_x` = bbo.`caston_x` WHERE bb.`bot_id` = bbo.`BotID`;"); + } + + if(get_mysql_result("SHOW COLUMNS FROM `botbuffs` LIKE 'caston_y'") ne "" && $db){ + print get_mysql_result("UPDATE `bot_buffs` bb INNER JOIN `botbuffs` bbo ON bb.`buffs_index` = bbo.`BotBuffId` SET bb.`caston_y` = bbo.`caston_y` WHERE bb.`bot_id` = bbo.`BotID`;"); + } + + if(get_mysql_result("SHOW COLUMNS FROM `botbuffs` LIKE 'caston_z'") ne "" && $db){ + print get_mysql_result("UPDATE `bot_buffs` bb INNER JOIN `botbuffs` bbo ON bb.`buffs_index` = bbo.`BotBuffId` SET bb.`caston_z` = bbo.`caston_z` WHERE bb.`bot_id` = bbo.`BotID`;"); + } + + if(get_mysql_result("SHOW COLUMNS FROM `botbuffs` LIKE 'ExtraDIChance'") ne "" && $db){ + print get_mysql_result("UPDATE `bot_buffs` bb INNER JOIN `botbuffs` bbo ON bb.`buffs_index` = bbo.`BotBuffId` SET bb.`extra_di_chance` = bbo.`ExtraDIChance` WHERE bb.`bot_id` = bbo.`BotID`;"); + } + + print get_mysql_result("RENAME TABLE `botbuffs` TO `botbuffs_old`;"); + } + + if(get_mysql_result("SHOW TABLES LIKE 'botinventory'") ne "" && $db){ + print get_mysql_result("INSERT INTO `bot_inventories` (`inventories_index`, `bot_id`, `slot_id`, `item_id`, `inst_charges`, `inst_color`, `inst_no_drop`, `augment_1`, `augment_2`, `augment_3`, `augment_4`, `augment_5`) SELECT bi.`BotInventoryID`, bi.`BotID`, bi.`SlotID`, bi.`ItemID`, bi.`charges`, bi.`color`, bi.`instnodrop`, bi.`augslot1`, bi.`augslot2`, bi.`augslot3`, bi.`augslot4`, bi.`augslot5` FROM `botinventory` bi INNER JOIN `bot_data` bd ON bi.`BotID` = bd.`bot_id`;"); + + if(get_mysql_result("SHOW COLUMNS FROM `botinventory` LIKE 'augslot6'") ne "" && $db){ + print get_mysql_result("UPDATE `bot_inventories` bi INNER JOIN `botinventory` bio ON bi.`inventories_index` = bio.`BotInventoryID` SET bi.`augment_6` = bio.`augslot6` WHERE bi.`bot_id` = bio.`BotID`;"); + } + + print get_mysql_result("RENAME TABLE `botinventory` TO `botinventory_old`;"); + } + + if(get_mysql_result("SHOW TABLES LIKE 'botpets'") ne "" && $db){ + print get_mysql_result("INSERT INTO `bot_pets` (`pets_index`, `pet_id`, `bot_id`, `name`, `mana`, `hp`) SELECT bp.`BotPetsId`, bp.`PetId`, bp.`BotId`, bp.`Name`, bp.`Mana`, bp.`HitPoints` FROM `botpets` bp INNER JOIN `bot_data` bd ON bp.`BotId` = bd.`bot_id`;"); + + print get_mysql_result("RENAME TABLE `botpets` TO `botpets_old`;"); + } + + if(get_mysql_result("SHOW TABLES LIKE 'botpetbuffs'") ne "" && $db){ + print get_mysql_result("INSERT INTO `bot_pet_buffs` (`pet_buffs_index`, `pets_index`, `spell_id`, `caster_level`, `duration`) SELECT bpb.`BotPetBuffId`, bpb.`BotPetsId`, bpb.`SpellId`, bpb.`CasterLevel`, bpb.`Duration` FROM `botpetbuffs` bpb INNER JOIN `bot_pets` bp ON bpb.`BotPetsId` = bp.`pets_index`;"); + + print get_mysql_result("RENAME TABLE `botpetbuffs` TO `botpetbuffs_old`;"); + } + + if(get_mysql_result("SHOW TABLES LIKE 'botpetinventory'") ne "" && $db){ + print get_mysql_result("INSERT INTO `bot_pet_inventories` (`pet_inventories_index`, `pets_index`, `item_id`) SELECT bpi.`BotPetInventoryId`, bpi.`BotPetsId`, bpi.`ItemId` FROM `botpetinventory` bpi INNER JOIN `bot_pets` bp ON bpi.`BotPetsId` = bp.`pets_index`;"); + + print get_mysql_result("RENAME TABLE `botpetinventory` TO `botpetinventory_old`;"); + } + + if(get_mysql_result("SHOW TABLES LIKE 'botgroup'") ne "" && $db){ + print get_mysql_result("INSERT INTO `bot_groups` (`groups_index`, `group_leader_id`, `group_name`) SELECT bg.`BotGroupId`, bg.`BotGroupLeaderBotId`, bg.`BotGroupName` FROM `botgroup` bg INNER JOIN `bot_data` bd ON bg.`BotGroupLeaderBotId` = bd.`bot_id`;"); + + print get_mysql_result("RENAME TABLE `botgroup` TO `botgroup_old`;"); + } + + if(get_mysql_result("SHOW TABLES LIKE 'botgroupmembers'") ne "" && $db){ + print get_mysql_result("INSERT INTO `bot_group_members` (`group_members_index`, `groups_index`, `bot_id`) SELECT bgm.`BotGroupMemberId`, bgm.`BotGroupId`, bgm.`BotId` FROM `botgroupmembers` bgm INNER JOIN `bot_groups` bg ON bgm.`BotGroupId` = bg.`groups_index` INNER JOIN `bot_data` bd ON bgm.`BotId` = bd.`bot_id`;"); + + print get_mysql_result("RENAME TABLE `botgroupmembers` TO `botgroupmembers_old`;"); + } + + if(get_mysql_result("SHOW TABLES LIKE 'botguildmembers'") ne "" && $db){ + print get_mysql_result("INSERT INTO `bot_guild_members` (`bot_id`, `guild_id`, `rank`, `tribute_enable`, `total_tribute`, `last_tribute`, `banker`, `public_note`, `alt`) SELECT bgm.`char_id`, bgm.`guild_id`, bgm.`rank`, bgm.`tribute_enable`, bgm.`total_tribute`, bgm.`last_tribute`, bgm.`banker`, bgm.`public_note`, bgm.`alt` FROM `botguildmembers` bgm INNER JOIN `guilds` g ON bgm.`guild_id` = g.`id` INNER JOIN `bot_data` bd ON bgm.`char_id` = bd.`bot_id`;"); + + print get_mysql_result("RENAME TABLE `botguildmembers` TO `botguildmembers_old`;"); + } +} + +sub get_bots_db_version{ + #::: Check if bots_version column exists... + if(get_mysql_result("SHOW COLUMNS FROM db_version LIKE 'bots_version'") eq "" && $db){ + print get_mysql_result("ALTER TABLE db_version ADD bots_version int(11) DEFAULT '0' AFTER version;"); + print "\nColumn 'bots_version' does not exists.... Adding to 'db_version' table...\n\n"; + } + $bots_local_db_version = trim(get_mysql_result("SELECT bots_version FROM db_version LIMIT 1")); + return $bots_local_db_version; +} + +sub bots_db_management{ + #::: Main Binary Database version + $bin_db_ver = trim($db_version[2]); + + #::: If we have stale data from main db run + if($db_run_stage > 0 && $bots_db_management == 0){ + clear_database_runs(); + } + + if($bin_db_ver == 0){ + print "Your server binaries (world/zone) are not compiled for bots...\n"; + return; + } + + #::: Set on flag for running bot updates... + $bots_db_management = 1; + + $bots_local_db_version = get_bots_db_version(); + + run_database_check(); +} + +sub main_db_management{ + #::: If we have stale data from bots db run + if($db_run_stage > 0 && $bots_db_management == 1){ + clear_database_runs(); + } + + #::: Main Binary Database version + $bin_db_ver = trim($db_version[1]); + + $bots_db_management = 0; + run_database_check(); +} + +sub clear_database_runs{ + # print "DEBUG :: clear_database_runs\n\n"; + #::: Clear manifest data... + %m_d = (); + #::: Clear updates... + @total_updates = (); + #::: Clear stage + $db_run_stage = 0; +} + +#::: Responsible for Database Upgrade Routines +sub run_database_check{ + + if(!$db){ + print "No database present, check your eqemu_config.xml for proper MySQL/MariaDB configuration...\n"; + return; + } + + if(!@total_updates){ + #::: Pull down bots database manifest + if($bots_db_management == 1){ + print "Retrieving latest bots database manifest...\n"; + get_remote_file("https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/git/bots/bots_db_update_manifest.txt", "db_update/db_update_manifest.txt"); + } + #::: Pull down mainstream database manifest + else{ + print "Retrieving latest database manifest...\n"; + get_remote_file("https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/db_update_manifest.txt", "db_update/db_update_manifest.txt"); + } + } + + #::: Run 2 - Running pending updates... + if(@total_updates){ + @total_updates = sort @total_updates; + foreach my $val (@total_updates){ + $file_name = trim($m_d{$val}[1]); + print "Running Update: " . $val . " - " . $file_name . "\n"; + print get_mysql_result_from_file("db_update/$file_name"); + print get_mysql_result("UPDATE db_version SET version = $val WHERE version < $val"); + + if($bots_db_management == 1 && $val == 9000){ + modify_db_for_bots(); + } + } + $db_run_stage = 2; + } + #::: Run 1 - Initial checking of needed updates... + else{ + print "Reading manifest...\n\n"; + use Data::Dumper; + open (FILE, "db_update/db_update_manifest.txt"); + while () { + chomp; + $o = $_; + if($o=~/#/i){ next; } + @manifest = split('\|', $o); + $m_d{$manifest[0]} = [@manifest]; + } + #::: Setting Manifest stage... + $db_run_stage = 1; + } + + @total_updates = (); + + #::: This is where we set checkpoints for where a database might be so we don't check so far back in the manifest... + $revision_check = 1000; + if(get_mysql_result("SHOW TABLES LIKE 'character_data'") ne ""){ + $revision_check = 9000; + } + + #::: Iterate through Manifest backwards from binary version down to local version... + for($i = $bin_db_ver; $i > $revision_check; $i--){ + if(!defined($m_d{$i}[0])){ next; } + + $file_name = trim($m_d{$i}[1]); + $query_check = trim($m_d{$i}[2]); + $match_type = trim($m_d{$i}[3]); + $match_text = trim($m_d{$i}[4]); + + #::: Match type update + if($match_type eq "contains"){ + if(trim(get_mysql_result($query_check))=~/$match_text/i){ + print "Missing DB Update " . $i . " '" . $file_name . "' \n"; + fetch_missing_db_update($i, $file_name); + push(@total_updates, $i); + } + else{ + print "DB up to date with: " . $i . " - '" . $file_name . "' \n"; + } + print_match_debug(); + print_break(); + } + if($match_type eq "missing"){ + if(get_mysql_result($query_check)=~/$match_text/i){ + print "DB up to date with: " . $i . " - '" . $file_name . "' \n"; + next; + } + else{ + print "Missing DB Update " . $i . " '" . $file_name . "' \n"; + fetch_missing_db_update($i, $file_name); + push(@total_updates, $i); + } + print_match_debug(); + print_break(); + } + if($match_type eq "empty"){ + if(get_mysql_result($query_check) eq ""){ + print "Missing DB Update " . $i . " '" . $file_name . "' \n"; + fetch_missing_db_update($i, $file_name); + push(@total_updates, $i); + } + else{ + print "DB up to date with: " . $i . " - '" . $file_name . "' \n"; + } + print_match_debug(); + print_break(); + } + if($match_type eq "not_empty"){ + if(get_mysql_result($query_check) ne ""){ + print "Missing DB Update " . $i . " '" . $file_name . "' \n"; + fetch_missing_db_update($i, $file_name); + push(@total_updates, $i); + } + else{ + print "DB up to date with: " . $i . " - '" . $file_name . "' \n"; + } + print_match_debug(); + print_break(); + } + } + print "\n"; + + if(scalar (@total_updates) == 0 && $db_run_stage == 2){ + print "No updates need to be run...\n"; + if($bots_db_management == 1){ + print "Setting Database to Bots Binary Version (" . $bin_db_ver . ") if not already...\n\n"; + get_mysql_result("UPDATE db_version SET bots_version = $bin_db_ver"); + } + else{ + print "Setting Database to Binary Version (" . $bin_db_ver . ") if not already...\n\n"; + get_mysql_result("UPDATE db_version SET version = $bin_db_ver"); + } + + clear_database_runs(); + } +} + +sub fetch_missing_db_update{ + $db_update = $_[0]; + $update_file = $_[1]; + if($db_update >= 9000){ + if($bots_db_management == 1){ + get_remote_file("https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/git/bots/required/" . $update_file, "db_update/" . $update_file . ""); + } + else{ + get_remote_file("https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/git/required/" . $update_file, "db_update/" . $update_file . ""); + } + } + elsif($db_update >= 5000 && $db_update <= 9000){ + get_remote_file("https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/svn/" . $update_file, "db_update/" . $update_file . ""); + } +} + +sub print_match_debug{ + if(!$debug){ return; } + print " Match Type: '" . $match_type . "'\n"; + print " Match Text: '" . $match_text . "'\n"; + print " Query Check: '" . $query_check . "'\n"; + print " Result: '" . trim(get_mysql_result($query_check)) . "'\n"; +} +sub print_break{ + if(!$debug){ return; } + print "\n==============================================\n"; +} \ No newline at end of file