Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Paul Coene 2016-08-23 13:55:52 -04:00
commit 8048239a81
88 changed files with 3316 additions and 592 deletions

View File

@ -1,5 +1,34 @@
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
- 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%)
- 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)
- 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

View File

@ -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*/

View File

@ -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),

View File

@ -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;
@ -386,7 +387,19 @@ 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
};
/*
** 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
};
/*
@ -419,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
@ -522,8 +536,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;
};
@ -834,7 +848,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<uint32>(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 +929,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 +970,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

View File

@ -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; }

View File

@ -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<uint32>(ServerToRoFCastingSlot(static_cast<EQEmu::CastingSlot>(emu->slot)));
OUT(spell_id);
eq->inventory_slot = ServerToRoFSlot(emu->inventoryslot);
@ -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();
}
@ -2012,18 +2013,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();
}
@ -2259,22 +2260,23 @@ 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);
}
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
@ -4334,10 +4336,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<uint32>(RoFToServerCastingSlot(static_cast<CastingSlot>(eq->slot)));
IN(spell_id);
emu->inventoryslot = RoFToServerSlot(eq->inventory_slot);
@ -6009,4 +6008,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*/

View File

@ -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*/

View File

@ -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<uint32>(ServerToRoF2CastingSlot(static_cast<EQEmu::CastingSlot>(emu->slot)));
OUT(spell_id);
eq->inventory_slot = ServerToRoF2Slot(emu->inventoryslot);
@ -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();
}
@ -2099,18 +2100,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();
}
@ -2346,22 +2347,23 @@ 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);
}
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
@ -4574,10 +4576,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<uint32>(RoF2ToServerCastingSlot(static_cast<CastingSlot>(eq->slot)));
IN(spell_id);
emu->inventoryslot = RoF2ToServerSlot(eq->inventory_slot);
@ -6314,4 +6313,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*/

View File

@ -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*/

View File

@ -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
};
/*
@ -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

View File

@ -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
};
/*
@ -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

View File

@ -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
@ -1136,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();
}
@ -1499,7 +1503,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])
{
@ -1510,7 +1514,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);
@ -2948,6 +2952,7 @@ namespace SoD
DECODE_LENGTH_EXACT(structs::CastSpell_Struct);
SETUP_DIRECT_DECODE(CastSpell_Struct, structs::CastSpell_Struct);
emu->slot = static_cast<uint32>(SoDToServerCastingSlot(static_cast<CastingSlot>(eq->slot)));
IN(slot);
IN(spell_id);
emu->inventoryslot = SoDToServerSlot(eq->inventoryslot);
@ -4013,4 +4018,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*/

View File

@ -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*/

View File

@ -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
};
/*
@ -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

View File

@ -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
@ -933,7 +936,24 @@ 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();
}
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<uint32>(ServerToSoFCastingSlot(static_cast<EQEmu::CastingSlot>(emu->slot)));
else
OUT(slot);
OUT(spell_id);
OUT(scribing);
FINISH_ENCODE();
}
@ -1158,9 +1178,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])
{
@ -2366,7 +2386,7 @@ namespace SoF
DECODE_LENGTH_EXACT(structs::CastSpell_Struct);
SETUP_DIRECT_DECODE(CastSpell_Struct, structs::CastSpell_Struct);
IN(slot);
emu->slot = static_cast<uint32>(SoFToServerCastingSlot(static_cast<CastingSlot>(eq->slot), eq->inventoryslot));
IN(spell_id);
emu->inventoryslot = SoFToServerSlot(eq->inventoryslot);
IN(target_id);
@ -3355,4 +3375,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*/

View File

@ -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*/

View File

@ -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)

View File

@ -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
};
/*
@ -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

View File

@ -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<uint32>(ServerToTitaniumCastingSlot(static_cast<EQEmu::CastingSlot>(emu->slot)));
else
OUT(slot);
OUT(spell_id);
OUT(scribing);
FINISH_ENCODE();
}
ENCODE(OP_MoveItem)
{
ENCODE_LENGTH_EXACT(MoveItem_Struct);
@ -872,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])
{
@ -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<uint32>(TitaniumToServerCastingSlot(static_cast<CastingSlot>(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*/

View File

@ -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*/

View File

@ -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)

View File

@ -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
};
/*
@ -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

View File

@ -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
@ -1383,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();
}
@ -1761,18 +1765,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);
@ -1867,7 +1871,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)
{
@ -3259,10 +3263,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<uint32>(UFToServerCastingSlot(static_cast<CastingSlot>(eq->slot)));
IN(spell_id);
emu->inventoryslot = UFToServerSlot(eq->inventoryslot);
@ -4378,4 +4379,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*/

View File

@ -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*/

View File

@ -26,7 +26,7 @@ namespace UF
namespace structs {
static const uint32 BUFF_COUNT = 25;
static const uint32 BUFF_COUNT = 30;
/*
** Compiler override to ensure
@ -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
};
/*
@ -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
@ -958,8 +959,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 +2167,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...
};

View File

@ -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

View File

@ -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

View File

@ -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)
@ -329,7 +331,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
@ -379,6 +381,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)
@ -503,6 +506,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)
@ -518,6 +522,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)

View File

@ -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);

View File

@ -110,6 +110,20 @@ bool EQEmu::skills::IsBardInstrumentSkill(SkillType skill)
}
}
bool EQEmu::skills::IsCastingSkill(SkillType skill)
{
switch (skill) {
case SkillAbjuration:
case SkillAlteration:
case SkillConjuration:
case SkillDivination:
case SkillEvocation:
return true;
default:
return false;
}
}
const std::map<EQEmu::skills::SkillType, std::string>& EQEmu::skills::GetSkillTypeMap()
{
/* VS2013 code

View File

@ -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<SkillType, std::string>& GetSkillTypeMap();

View File

@ -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
@ -669,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;

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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";
@ -108,8 +114,14 @@ else {
}
if($t_tables ne ""){
$tables_f_l = substr($t_tables_l, 0, 20) . '...';
$target_file = '' . $tables_f_l . '_' . $date . '';
$tables_f_l = substr($t_tables_l, 0, 20) . '-';
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 . ""; }

File diff suppressed because it is too large Load Diff

View File

@ -298,6 +298,7 @@ sub show_menu_prompt {
18 => \&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 <<EO_MENU;
18) [Windows Server Build Bots] :: Download Latest and Stable Server Build with Bots
19) [EQEmu DB Drop Bots Schema] :: Remove Bots schema and return database to normal state
20) [Update the updater] Force update this script (Redownload)
21) [DB :: Backup Player Tables] :: Backs up player tables
0) Exit
Enter numbered option and press enter...
@ -404,6 +406,30 @@ sub database_dump {
print "Performing database backup....\n";
print `perl db_dumper.pl database="$db" loc="backups"`;
}
sub database_dump_player_tables {
check_for_database_dump_script();
print "Performing database backup of player tables....\n";
get_remote_file("https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/character_table_list.txt", "backups/character_table_list.txt");
$tables = "";
open (FILE, "backups/character_table_list.txt");
$i = 0;
while (<FILE>){
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";

View File

@ -0,0 +1,33 @@
adventure_stats
char_recipe_list
character_activities
character_alt_currency
character_alternate_abilities
character_bandolier
character_bind
character_currency
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

View File

@ -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);
@ -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++)

View File

@ -88,7 +88,7 @@ void ZSList::Process() {
Process();
CatchSignal(2);
}
if(reminder && reminder->Check()){
if(reminder && reminder->Check() && shutdowntimer){
SendEmoteMessage(0,0,0,15,"<SYSTEMWIDE MESSAGE>:SYSTEM MSG:World coming down, everyone log out now. World will shut down in %i minutes...", ((shutdowntimer->GetRemainingTime()/1000) / 60));
}
LinkedListIterator<ZoneServer*> iterator(list);

View File

@ -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;
@ -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;
}
}
@ -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);
}

View File

@ -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);
@ -462,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())

View File

@ -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"
@ -369,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
@ -1205,7 +1203,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);
@ -1802,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);
@ -1821,7 +1819,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);
@ -3169,68 +3167,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();
}
}
@ -3268,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);
@ -3805,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
@ -3826,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<float>(SlayRateBonus) / 10000.0f;

View File

@ -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);
@ -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<uint16>(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<uint16>(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);
@ -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)) {
@ -8251,7 +8269,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;
}

View File

@ -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);

View File

@ -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)

View File

@ -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;

View File

@ -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
@ -155,7 +156,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),
last_region_type(RegionTypeUnsupported)
{
for(int cf=0; cf < _FilterCount; cf++)
ClientFilters[cf] = FilterShow;
@ -565,6 +567,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;
@ -1787,7 +1794,7 @@ const int32& Client::SetMana(int32 amount) {
}
void Client::SendManaUpdatePacket() {
if (!Connected() || IsCasting())
if (!Connected())
return;
if (ClientVersion() >= EQEmu::versions::ClientVersion::SoD) {
@ -1801,7 +1808,8 @@ 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);
safe_delete(outapp);
@ -2476,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();
@ -3674,58 +3684,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) {
@ -4604,7 +4614,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 +4736,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 +4755,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);
@ -8396,7 +8406,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
@ -8527,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);
}

View File

@ -27,6 +27,7 @@ class Object;
class Raid;
class Seperator;
class ServerPacket;
enum WaterRegionType : int;
namespace EQEmu
{
@ -105,6 +106,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,
@ -326,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); }
@ -359,9 +362,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; }
@ -510,6 +513,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();
@ -889,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);
@ -1230,6 +1238,8 @@ public:
void SendHPUpdateMarquee();
void CheckRegionTypeChanges();
protected:
friend class Mob;
void CalcItemBonuses(StatBonuses* newbon);
@ -1414,6 +1424,7 @@ private:
uint8 zonesummon_ignorerestrictions;
ZoneMode zone_mode;
WaterRegionType last_region_type;
Timer position_timer;
uint8 position_timer_counter;

View File

@ -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);
}

View File

@ -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;
@ -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<CastingSlot>(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;
}
@ -4583,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;
}
@ -8410,6 +8406,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 +8551,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 +8580,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

View File

@ -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 && !instalog) {
Save();
}

View File

@ -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);
}
}
@ -4339,11 +4339,15 @@ 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");
c->Message(13, "Error: This item has no data reference");
return;
}
EQEmu::SayLinkEngine linker;

View File

@ -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

View File

@ -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<EQEmu::Any> args;
args.push_back(inst);

View File

@ -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);
}

View File

@ -927,12 +927,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)
@ -952,12 +958,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)
@ -977,22 +989,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)
@ -1188,6 +1212,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];
@ -1195,8 +1221,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)
@ -1232,7 +1257,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()) {
@ -1240,8 +1267,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)) {
@ -1259,6 +1308,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);

View File

@ -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);

View File

@ -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<EQEmu::CastingSlot>(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<EQEmu::CastingSlot>(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<EQEmu::CastingSlot>(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<uint32>(item_slot));
return self->CastSpell(spell_id, target_id, static_cast<EQEmu::CastingSlot>(slot), cast_time, mana_cost, nullptr, static_cast<uint32>(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<uint32>(item_slot),
return self->CastSpell(spell_id, target_id, static_cast<EQEmu::CastingSlot>(slot), cast_time, mana_cost, nullptr, static_cast<uint32>(item_slot),
static_cast<uint32>(timer), static_cast<uint32>(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<uint32>(item_slot),
return self->CastSpell(spell_id, target_id, static_cast<EQEmu::CastingSlot>(slot), cast_time, mana_cost, nullptr, static_cast<uint32>(item_slot),
static_cast<uint32>(timer), static_cast<uint32>(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<EQEmu::CastingSlot>(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<EQEmu::CastingSlot>(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<EQEmu::CastingSlot>(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<EQEmu::CastingSlot>(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<EQEmu::CastingSlot>(slot), mana_used, inventory_slot, resist_adjust, proc);
}
void Lua_Mob::SpellEffect(Lua_Mob caster, int spell_id, double partial) {

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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();
}
@ -3224,13 +3223,13 @@ 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)) {
SpellFinished(spell_id, this, 10, 0, -1, spells[spell_id].ResistDiff, true, level_override);
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);
}
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 +3517,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 +3530,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 +3570,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 +3589,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 +3666,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 +3694,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 +3712,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 +3831,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 +3868,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 +3878,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 +4529,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 +4544,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<int>(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<int>(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<int>(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 +4571,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<int>(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<int>(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<int>(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);
}
}
}

View File

@ -26,6 +26,7 @@
#include "aa_ability.h"
#include "aa.h"
#include "../common/light_source.h"
#include "../common/emu_constants.h"
#include <set>
#include <vector>
#include <memory>
@ -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,29 +249,30 @@ 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, bool isproc = false);
virtual bool CheckFizzle(uint16 spell_id);
virtual bool CheckSpellLevelRestriction(uint16 spell_id);
virtual bool IsImmuneToSpell(uint16 spell_id, Mob *caster);
virtual float GetAOERange(uint16 spell_id);
void InterruptSpell(uint16 spellid = SPELL_UNKNOWN);
void InterruptSpell(uint16, uint16, uint16 spellid = SPELL_UNKNOWN);
void StopCasting();
inline bool IsCasting() const { return((casting_spell_id != 0)); }
uint16 CastingSpellID() const { return casting_spell_id; }
bool DoCastingChecks();
@ -306,6 +308,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);
@ -1216,7 +1220,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;
@ -1226,7 +1230,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;

View File

@ -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<EQEmu::CastingSlot>(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<EQEmu::CastingSlot>(slots[idx]);
}
if(IsMezSpell(spell_to_cast) || IsFearSpell(spell_to_cast))
@ -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));

View File

@ -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))
{
@ -2006,44 +2007,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;

View File

@ -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_Struct> AIspellsEffects;

View File

@ -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<EQEmu::CastingSlot>(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;
}

View File

@ -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) {

View File

@ -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);
@ -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 {
@ -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);

View File

@ -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,7 +203,7 @@ 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);
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();
@ -355,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);
@ -375,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
}
}
@ -532,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)
@ -648,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)
@ -695,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())
@ -704,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);
@ -916,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);
@ -1031,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;
@ -1043,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;
}
@ -1053,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;
}
@ -1068,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;
}
@ -1078,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;
}
@ -1093,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;
}
@ -1103,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;
}
@ -1120,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);
}
}
@ -1509,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
{
@ -1726,7 +1736,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
break;
}
}
else {
else if (caster) {
Raid *r = entity_list.GetRaidByClient(caster->CastToClient());
if(r)
{
@ -1766,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 {
@ -2113,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());
@ -2122,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;
@ -2174,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
{
@ -2269,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);
@ -2292,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;
@ -2657,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<float>(spell.base[i]), true, spell.base2[i]);
}
break;
@ -2745,7 +2762,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;
}
@ -3668,10 +3685,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;
@ -5135,7 +5153,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);
@ -6226,7 +6244,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 +6271,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 +6286,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 +6316,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;
@ -6683,10 +6712,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);
}
}
}

View File

@ -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<int>(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<uint16>(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<uint16>(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,14 +338,13 @@ 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)
{
Mob* pMob = nullptr;
int32 orgcasttime;
EQApplicationPacket *outapp = nullptr;
if(!IsValidSpell(spell_id)) {
InterruptSpell();
@ -353,7 +354,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<int>(slot), cast_time, mana_cost, item_slot==0xFFFFFFFF?999:item_slot);
casting_spell_id = spell_id;
casting_spell_slot = slot;
@ -418,7 +419,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();
@ -453,8 +454,26 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, uint16 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
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) {
if (!DoCastingChecks()) {
StopCasting();
return false;
}
CastedSpellFinished(spell_id, target_id, slot, mana_cost, item_slot, resist_adjust);
return(true);
}
@ -476,25 +495,14 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, uint16 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 == 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);
}
if (!DoCastingChecks()) {
InterruptSpell();
StopCasting();
return false;
}
@ -550,6 +558,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;
}
@ -767,7 +779,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 +814,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<uint16>(casting_spell_slot));
}
if(casting_spell_aa_id && IsClient()) { //Rest AA Timer on failed cast
@ -874,18 +886,44 @@ 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<uint16>(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
// 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::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);
@ -895,7 +933,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 +1231,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;
@ -1306,8 +1344,11 @@ 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);
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<uint32>(slot), spell_id, memSpellSpellbar);
}
Log.Out(Logs::Detail, Logs::Spells, "Bard song %d should be started", spell_id);
}
@ -1319,14 +1360,15 @@ 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);
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<uint32>(slot), spell_id, memSpellSpellbar);
// this tells the client that casting may happen again
SetMana(GetMana());
// skills
if(slot < MAX_PP_MEMSPELL)
{
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
@ -1348,8 +1390,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, bool isproc)
{
/*
The basic types of spells:
@ -1395,6 +1437,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;
@ -1682,7 +1729,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 || RuleB(Spells, AllowItemTGB))) {
if( (!target) ||
(target->IsNPC() && !(target->GetOwner() && target->GetOwner()->IsClient())) ||
(target->IsCorpse()) )
@ -1728,7 +1775,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())
{
@ -1743,7 +1792,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())
{
@ -1772,7 +1823,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())
{
@ -1787,7 +1840,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())
{
@ -1904,7 +1959,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;
@ -1974,7 +2029,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16
//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");
@ -2263,7 +2318,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 +2381,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 +2400,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<uint16>(slot));
return true;
}
@ -2360,8 +2415,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);
@ -2972,8 +3027,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;
@ -3047,6 +3101,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 (song)
return GetMaxBuffSlots();
if (disc)
return GetMaxBuffSlots() + GetMaxSongSlots();
return 0;
}
uint32 Client::GetLastBuffSlot(bool disc, bool song)
{
if (song)
return GetMaxBuffSlots() + GetCurrentSongSlots();
if (disc)
return GetMaxBuffSlots() + GetMaxSongSlots() + GetCurrentDiscSlots();
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
@ -3084,18 +3166,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];
@ -3107,7 +3179,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;
@ -3769,7 +3841,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, CastingSlot::Item, 0, -1, spells[spells[spell_id].RecourseLink].ResistDiff);
if (IsDetrimentalSpell(spell_id)) {
@ -4713,6 +4785,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);
@ -5245,7 +5318,7 @@ bool Mob::UseBardSpellLogic(uint16 spell_id, int slot)
spell_id = casting_spell_id;
if(slot == -1)
slot = casting_spell_slot;
slot = static_cast<int>(casting_spell_slot);
// should we treat this as a bard singing?
return
@ -5273,7 +5346,7 @@ void Mob::_StopSong()
{
bardsong = 0;
bardsong_target_id = 0;
bardsong_slot = 0;
bardsong_slot = CastingSlot::Gem1;
bardsong_timer.Disable();
}
@ -5351,8 +5424,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++)
{
@ -5449,10 +5522,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
@ -5644,3 +5721,26 @@ 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;
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;
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, false);
}

View File

@ -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.

View File

@ -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:

View File

@ -8,7 +8,7 @@
extern const ZoneConfig *Config;
enum WaterRegionType {
enum WaterRegionType : int {
RegionTypeUnsupported = -2,
RegionTypeUntagged = -1,
RegionTypeNormal = 0,
@ -33,6 +33,8 @@ 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;
virtual bool InZoneLine(const glm::vec3& location) const = 0;
protected:
virtual bool Load(FILE *fp) { return false; }

View File

@ -30,6 +30,14 @@ 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::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) {

View File

@ -24,6 +24,8 @@ 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;
virtual bool InZoneLine(const glm::vec3& location) const;
protected:
virtual bool Load(FILE *fp);

View File

@ -33,6 +33,14 @@ 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::InZoneLine(const glm::vec3& location) const {
return ReturnRegionType(location) == RegionTypeZoneLine;
}
bool WaterMapV2::Load(FILE *fp) {
uint32 region_count;
if (fread(&region_count, sizeof(region_count), 1, fp) != 1) {

View File

@ -17,6 +17,8 @@ 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;
virtual bool InZoneLine(const glm::vec3& location) const;
protected:
virtual bool Load(FILE *fp);

View File

@ -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;

View File

@ -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

View File

@ -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)

View File

@ -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];
};