[Bug Fix] Bard update fixes 1 (#1982)

* fix for bard item charge consumables

* [Bug Fix] Bards not consuming item click charges on instant cast items.

* [Bug Fix] Bard update fixes 1

bards not respecting deity/race/class restrictions on instant cast items
This commit is contained in:
KayenEQ 2022-02-09 15:07:38 -05:00 committed by GitHub
parent f65a6d2761
commit 1f560529da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 163 additions and 132 deletions

View File

@ -8792,14 +8792,9 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app)
spell_id = item->Click.Effect; spell_id = item->Click.Effect;
bool is_casting_bard_song = false; bool is_casting_bard_song = false;
if if (spell_id > 0) {
(
spell_id > 0 && if (!IsValidSpell(spell_id) ||
(
!IsValidSpell(spell_id) ||
casting_spell_id ||
delaytimer ||
spellend_timer.Enabled() ||
IsStunned() || IsStunned() ||
IsFeared() || IsFeared() ||
IsMezzed() || IsMezzed() ||
@ -8808,10 +8803,16 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app)
(IsSilenced() && !IsDiscipline(spell_id)) || (IsSilenced() && !IsDiscipline(spell_id)) ||
(IsAmnesiad() && IsDiscipline(spell_id)) || (IsAmnesiad() && IsDiscipline(spell_id)) ||
(IsDetrimentalSpell(spell_id) && !zone->CanDoCombat()) || (IsDetrimentalSpell(spell_id) && !zone->CanDoCombat()) ||
(inst->IsScaling() && inst->GetExp() <= 0) // charms don't have spells when less than 0 (inst->IsScaling() && inst->GetExp() <= 0)) { // charms don't have spells when less than 0
)
) SendSpellBarEnable(spell_id);
{ return;
}
if (casting_spell_id ||
delaytimer ||
spellend_timer.Enabled()) {
/* /*
Bards on live can click items while casting spell gems, it stops that song cast and replaces it with item click cast. Bards on live can click items while casting spell gems, it stops that song cast and replaces it with item click cast.
Can not click while casting other items. Can not click while casting other items.
@ -8826,6 +8827,7 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app)
return; return;
} }
} }
}
// Modern clients don't require pet targeted for item clicks that are ST_Pet // Modern clients don't require pet targeted for item clicks that are ST_Pet
if (spell_id > 0 && (spells[spell_id].target_type == ST_Pet || spells[spell_id].target_type == ST_SummonedPet)) if (spell_id > 0 && (spells[spell_id].target_type == ST_Pet || spells[spell_id].target_type == ST_SummonedPet))
target_id = GetPetID(); target_id = GetPetID();

View File

@ -357,6 +357,8 @@ public:
void ApplySpellEffectIllusion(int32 spell_id, Mob* caster, int buffslot, int base, int limit, int max); void ApplySpellEffectIllusion(int32 spell_id, Mob* caster, int buffslot, int base, int limit, int max);
void ApplyIllusionToCorpse(int32 spell_id, Corpse* new_corpse); void ApplyIllusionToCorpse(int32 spell_id, Corpse* new_corpse);
void SendIllusionWearChange(Client* c); void SendIllusionWearChange(Client* c);
int16 GetItemSlotToConsumeCharge(int32 spell_id, uint32 inventory_slot);
bool CheckItemRaceClassDietyRestrictionsOnCast(uint32 inventory_slot);
//Bard //Bard
bool ApplyBardPulse(int32 spell_id, Mob *spell_target, EQ::spells::CastingSlot slot); bool ApplyBardPulse(int32 spell_id, Mob *spell_target, EQ::spells::CastingSlot slot);

View File

@ -208,53 +208,8 @@ bool Mob::CastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot,
//Added to prevent MQ2 exploitation of equipping normally-unequippable/clickable items with effects and clicking them for benefits. //Added to prevent MQ2 exploitation of equipping normally-unequippable/clickable items with effects and clicking them for benefits.
if (item_slot && IsClient() && (slot == CastingSlot::Item || slot == CastingSlot::PotionBelt)) if (item_slot && IsClient() && (slot == CastingSlot::Item || slot == CastingSlot::PotionBelt))
{ {
EQ::ItemInstance *itm = CastToClient()->GetInv().GetItem(item_slot); if (!CheckItemRaceClassDietyRestrictionsOnCast(item_slot)) {
int bitmask = 1; return false;
bitmask = bitmask << (CastToClient()->GetClass() - 1);
if( itm && itm->GetItem()->Classes != 65535 ) {
if ((itm->GetItem()->Click.Type == EQ::item::ItemEffectEquipClick) && !(itm->GetItem()->Classes & bitmask)) {
if (CastToClient()->ClientVersion() < EQ::versions::ClientVersion::SoF) {
// They are casting a spell from an item that requires equipping but shouldn't let them equip it
LogError("HACKER: [{}] (account: [{}]) attempted to click an equip-only effect on item [{}] (id: [{}]) which they shouldn't be able to equip!",
CastToClient()->GetCleanName(), CastToClient()->AccountName(), itm->GetItem()->Name, itm->GetItem()->ID);
database.SetHackerFlag(CastToClient()->AccountName(), CastToClient()->GetCleanName(), "Clicking equip-only item with an invalid class");
}
else {
MessageString(Chat::Red, MUST_EQUIP_ITEM);
}
return(false);
}
if ((itm->GetItem()->Click.Type == EQ::item::ItemEffectClick2) && !(itm->GetItem()->Classes & bitmask)) {
if (CastToClient()->ClientVersion() < EQ::versions::ClientVersion::SoF) {
// They are casting a spell from an item that they don't meet the race/class requirements to cast
LogError("HACKER: [{}] (account: [{}]) attempted to click a race/class restricted effect on item [{}] (id: [{}]) which they shouldn't be able to click!",
CastToClient()->GetCleanName(), CastToClient()->AccountName(), itm->GetItem()->Name, itm->GetItem()->ID);
database.SetHackerFlag(CastToClient()->AccountName(), CastToClient()->GetCleanName(), "Clicking race/class restricted item with an invalid class");
}
else {
if (CastToClient()->ClientVersion() >= EQ::versions::ClientVersion::RoF)
{
// Line 181 in eqstr_us.txt was changed in RoF+
Message(Chat::Yellow, "Your race, class, or deity cannot use this item.");
}
else
{
MessageString(Chat::Red, CANNOT_USE_ITEM);
}
}
return(false);
}
}
if (itm && (itm->GetItem()->Click.Type == EQ::item::ItemEffectEquipClick) && item_slot > EQ::invslot::EQUIPMENT_END){
if (CastToClient()->ClientVersion() < EQ::versions::ClientVersion::SoF) {
// They are attempting to cast a must equip clicky without having it equipped
LogError("HACKER: [{}] (account: [{}]) attempted to click an equip-only effect on item [{}] (id: [{}]) without equiping it!", CastToClient()->GetCleanName(), CastToClient()->AccountName(), itm->GetItem()->Name, itm->GetItem()->ID);
database.SetHackerFlag(CastToClient()->AccountName(), CastToClient()->GetCleanName(), "Clicking equip-only item without equiping it");
}
else {
MessageString(Chat::Red, MUST_EQUIP_ITEM);
}
return(false);
} }
} }
@ -1627,57 +1582,7 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo
if(IsClient() && (slot == CastingSlot::Item || slot == CastingSlot::PotionBelt) if(IsClient() && (slot == CastingSlot::Item || slot == CastingSlot::PotionBelt)
&& inventory_slot != 0xFFFFFFFF) // 10 is an item && inventory_slot != 0xFFFFFFFF) // 10 is an item
{ {
bool fromaug = false; DeleteChargeFromSlot = GetItemSlotToConsumeCharge(spell_id, inventory_slot);
EQ::ItemData* augitem = nullptr;
uint32 recastdelay = 0;
int recasttype = 0;
while (true) {
if (item == nullptr)
break;
for (int r = EQ::invaug::SOCKET_BEGIN; r <= EQ::invaug::SOCKET_END; r++) {
const EQ::ItemInstance* aug_i = item->GetAugment(r);
if (!aug_i)
continue;
const EQ::ItemData* aug = aug_i->GetItem();
if (!aug)
continue;
if (aug->Click.Effect == spell_id)
{
recastdelay = aug_i->GetItem()->RecastDelay;
recasttype = aug_i->GetItem()->RecastType;
fromaug = true;
break;
}
}
break;
}
if (item && item->IsClassCommon() && (item->GetItem()->Click.Effect == spell_id) && item->GetCharges() || fromaug)
{
//const ItemData* item = item->GetItem();
int16 charges = item->GetItem()->MaxCharges;
if(fromaug) { charges = -1; } //Don't destroy the parent item
if(charges > -1) { // charged item, expend a charge
LogSpells("Spell [{}]: Consuming a charge from item [{}] ([{}]) which had [{}]/[{}] charges", spell_id, item->GetItem()->Name, item->GetItem()->ID, item->GetCharges(), item->GetItem()->MaxCharges);
DeleteChargeFromSlot = inventory_slot;
} else {
LogSpells("Spell [{}]: Cast from unlimited charge item [{}] ([{}]) ([{}] charges)", spell_id, item->GetItem()->Name, item->GetItem()->ID, item->GetItem()->MaxCharges);
}
}
else
{
LogSpells("Item used to cast spell [{}] was missing from inventory slot [{}] after casting!", spell_id, inventory_slot);
Message(Chat::Red, "Casting Error: Active casting item not found in inventory slot %i", inventory_slot);
InterruptSpell();
return;
}
} }
// we're done casting, now try to apply the spell // we're done casting, now try to apply the spell
@ -1699,8 +1604,9 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo
TryTriggerOnCastFocusEffect(focusTriggerOnCast, spell_id); TryTriggerOnCastFocusEffect(focusTriggerOnCast, spell_id);
if(DeleteChargeFromSlot >= 0) if (DeleteChargeFromSlot >= 0) {
CastToClient()->DeleteItemInInventory(DeleteChargeFromSlot, 1, true); CastToClient()->DeleteItemInInventory(DeleteChargeFromSlot, 1, true);
}
// //
// at this point the spell has successfully been cast // at this point the spell has successfully been cast
@ -6638,7 +6544,128 @@ void Mob::DoBardCastingFromItemClick(bool is_casting_bard_song, uint32 cast_time
CastSpell(spell_id, target_id, CastingSlot::Item, cast_time, 0, 0, item_slot); CastSpell(spell_id, target_id, CastingSlot::Item, cast_time, 0, 0, item_slot);
} }
//Instant cast items do not stop bard songs or interrupt casting. //Instant cast items do not stop bard songs or interrupt casting.
else if (DoCastingChecksOnCaster(spell_id)) { else if (CheckItemRaceClassDietyRestrictionsOnCast(item_slot) && DoCastingChecksOnCaster(spell_id)) {
SpellFinished(spell_id, entity_list.GetMob(target_id), CastingSlot::Item, 0, item_slot); int16 DeleteChargeFromSlot = GetItemSlotToConsumeCharge(spell_id, item_slot);
if (SpellFinished(spell_id, entity_list.GetMob(target_id), CastingSlot::Item, 0, item_slot)) {
if (IsClient() && DeleteChargeFromSlot >= 0) {
CastToClient()->DeleteItemInInventory(DeleteChargeFromSlot, 1, true);
}
}
}
}
int16 Mob::GetItemSlotToConsumeCharge(int32 spell_id, uint32 inventory_slot)
{
int16 DeleteChargeFromSlot = -1;
if (!IsClient() || inventory_slot == 0xFFFFFFFF) {
return DeleteChargeFromSlot;
}
EQ::ItemInstance *item = nullptr;
item = CastToClient()->GetInv().GetItem(inventory_slot);
bool fromaug = false;
EQ::ItemData* augitem = nullptr;
while (true) {
if (item == nullptr)
break;
for (int r = EQ::invaug::SOCKET_BEGIN; r <= EQ::invaug::SOCKET_END; r++) {
const EQ::ItemInstance* aug_i = item->GetAugment(r);
if (!aug_i) {
continue;
}
const EQ::ItemData* aug = aug_i->GetItem();
if (!aug) {
continue;
}
if (aug->Click.Effect == spell_id){
fromaug = true;
break;
}
}
break;
}
if (item && item->IsClassCommon() && (item->GetItem()->Click.Effect == spell_id) && item->GetCharges() || fromaug){
int16 charges = item->GetItem()->MaxCharges;
if (fromaug) { charges = -1; } //Don't destroy the parent item
if (charges > -1) { // charged item, expend a charge
LogSpells("Spell [{}]: Consuming a charge from item [{}] ([{}]) which had [{}]/[{}] charges", spell_id, item->GetItem()->Name, item->GetItem()->ID, item->GetCharges(), item->GetItem()->MaxCharges);
DeleteChargeFromSlot = inventory_slot;
}
else {
LogSpells("Spell [{}]: Cast from unlimited charge item [{}] ([{}]) ([{}] charges)", spell_id, item->GetItem()->Name, item->GetItem()->ID, item->GetItem()->MaxCharges);
}
}
else{
LogSpells("Item used to cast spell [{}] was missing from inventory slot [{}] after casting!", spell_id, inventory_slot);
Message(Chat::Red, "Casting Error: Active casting item not found in inventory slot %i", inventory_slot);
InterruptSpell();
return DeleteChargeFromSlot;
}
return DeleteChargeFromSlot;
}
bool Mob::CheckItemRaceClassDietyRestrictionsOnCast(uint32 inventory_slot) {
if (inventory_slot == 0xFFFFFFFF) {
return false;
}
//Added to prevent MQ2 exploitation of equipping normally-unequippable/clickable items with effects and clicking them for benefits.
EQ::ItemInstance *itm = CastToClient()->GetInv().GetItem(inventory_slot);
int bitmask = 1;
bitmask = bitmask << (CastToClient()->GetClass() - 1);
if (itm && itm->GetItem()->Classes != 65535) {
if ((itm->GetItem()->Click.Type == EQ::item::ItemEffectEquipClick) && !(itm->GetItem()->Classes & bitmask)) {
if (CastToClient()->ClientVersion() < EQ::versions::ClientVersion::SoF) {
// They are casting a spell from an item that requires equipping but shouldn't let them equip it
LogError("HACKER: [{}] (account: [{}]) attempted to click an equip-only effect on item [{}] (id: [{}]) which they shouldn't be able to equip!",
CastToClient()->GetCleanName(), CastToClient()->AccountName(), itm->GetItem()->Name, itm->GetItem()->ID);
database.SetHackerFlag(CastToClient()->AccountName(), CastToClient()->GetCleanName(), "Clicking equip-only item with an invalid class");
}
else {
MessageString(Chat::Red, MUST_EQUIP_ITEM);
}
return(false);
}
if ((itm->GetItem()->Click.Type == EQ::item::ItemEffectClick2) && !(itm->GetItem()->Classes & bitmask)) {
if (CastToClient()->ClientVersion() < EQ::versions::ClientVersion::SoF) {
// They are casting a spell from an item that they don't meet the race/class requirements to cast
LogError("HACKER: [{}] (account: [{}]) attempted to click a race/class restricted effect on item [{}] (id: [{}]) which they shouldn't be able to click!",
CastToClient()->GetCleanName(), CastToClient()->AccountName(), itm->GetItem()->Name, itm->GetItem()->ID);
database.SetHackerFlag(CastToClient()->AccountName(), CastToClient()->GetCleanName(), "Clicking race/class restricted item with an invalid class");
}
else {
if (CastToClient()->ClientVersion() >= EQ::versions::ClientVersion::RoF)
{
// Line 181 in eqstr_us.txt was changed in RoF+
Message(Chat::Yellow, "Your race, class, or deity cannot use this item.");
}
else
{
MessageString(Chat::Red, CANNOT_USE_ITEM);
}
}
return(false);
}
}
if (itm && (itm->GetItem()->Click.Type == EQ::item::ItemEffectEquipClick) && inventory_slot > EQ::invslot::EQUIPMENT_END) {
if (CastToClient()->ClientVersion() < EQ::versions::ClientVersion::SoF) {
// They are attempting to cast a must equip clicky without having it equipped
LogError("HACKER: [{}] (account: [{}]) attempted to click an equip-only effect on item [{}] (id: [{}]) without equiping it!", CastToClient()->GetCleanName(), CastToClient()->AccountName(), itm->GetItem()->Name, itm->GetItem()->ID);
database.SetHackerFlag(CastToClient()->AccountName(), CastToClient()->GetCleanName(), "Clicking equip-only item without equiping it");
}
else {
MessageString(Chat::Red, MUST_EQUIP_ITEM);
}
return(false);
} }
} }