diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index c84c4073a..c234e303a 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -1695,6 +1695,7 @@ struct OnLevelMessage_Struct uint32 Duration; uint32 PopupID; uint32 NegativeID; + uint32 SoundControls; char ButtonName0[25]; char ButtonName1[25]; }; diff --git a/common/patches/rof.cpp b/common/patches/rof.cpp index e342c5410..23e521a57 100644 --- a/common/patches/rof.cpp +++ b/common/patches/rof.cpp @@ -1849,6 +1849,7 @@ namespace RoF eq->Text_Count = 4096; memcpy(eq->Text, emu->Text, sizeof(eq->Text)); OUT(Buttons); + OUT(SoundControls); OUT(Duration); OUT(PopupID); OUT(NegativeID); diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index e38fde6f1..5befaf08c 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -1927,6 +1927,7 @@ namespace RoF2 eq->Text_Count = 4096; memcpy(eq->Text, emu->Text, sizeof(eq->Text)); OUT(Buttons); + OUT(SoundControls); OUT(Duration); OUT(PopupID); OUT(NegativeID); diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index 13befdad9..379941d51 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -2092,7 +2092,7 @@ struct OnLevelMessage_Struct { /*0000*/ uint32 ButtonName1_Count; /*0000*/ char ButtonName1[25]; /*0000*/ uint8 Buttons; -/*0000*/ uint8 Unknown4275; // Something to do with audio controls +/*0000*/ uint8 SoundControls; // Something to do with audio controls /*0000*/ uint32 Duration; /*0000*/ uint32 PopupID; // If none zero, a response packet with 00 00 00 00 is returned on clicking the left button /*0000*/ uint32 NegativeID; // If none zero, a response packet with 01 00 00 00 is returned on clicking the right button diff --git a/common/patches/rof_structs.h b/common/patches/rof_structs.h index 32c7c0d28..4c73c28d4 100644 --- a/common/patches/rof_structs.h +++ b/common/patches/rof_structs.h @@ -2122,7 +2122,7 @@ struct OnLevelMessage_Struct { /*0000*/ uint32 ButtonName1_Count; /*0000*/ char ButtonName1[25]; /*0000*/ uint8 Buttons; -/*0000*/ uint8 Unknown4275; // Something to do with audio controls +/*0000*/ uint8 SoundControls; // Something to do with audio controls /*0000*/ uint32 Duration; /*0000*/ uint32 PopupID; // If none zero, a response packet with 00 00 00 00 is returned on clicking the left button /*0000*/ uint32 NegativeID; // If none zero, a response packet with 01 00 00 00 is returned on clicking the right button diff --git a/common/patches/sod.cpp b/common/patches/sod.cpp index c68bfacef..f6d85f2bb 100644 --- a/common/patches/sod.cpp +++ b/common/patches/sod.cpp @@ -1373,6 +1373,7 @@ namespace SoD memcpy(eq->Title, emu->Title, sizeof(eq->Title)); memcpy(eq->Text, emu->Text, sizeof(eq->Text)); OUT(Buttons); + OUT(SoundControls); OUT(Duration); OUT(PopupID); OUT(NegativeID); diff --git a/common/patches/sod_structs.h b/common/patches/sod_structs.h index fdcb03658..ed71d837b 100644 --- a/common/patches/sod_structs.h +++ b/common/patches/sod_structs.h @@ -1760,7 +1760,7 @@ struct OnLevelMessage_Struct { /*4224*/ char ButtonName0[25]; // If Buttons = 1, these two are the text for the left and right buttons respectively /*4249*/ char ButtonName1[25]; /*4274*/ uint8 Buttons; -/*4275*/ uint8 Unknown4275; // Something to do with audio controls +/*4275*/ uint8 SoundControls; // Something to do with audio controls /*4276*/ uint32 Duration; /*4280*/ uint32 PopupID; // If none zero, a response packet with 00 00 00 00 is returned on clicking the left button /*4284*/ uint32 NegativeID; // If none zero, a response packet with 01 00 00 00 is returned on clicking the right button diff --git a/common/patches/uf.cpp b/common/patches/uf.cpp index e2093aef0..b6d251953 100644 --- a/common/patches/uf.cpp +++ b/common/patches/uf.cpp @@ -1607,6 +1607,7 @@ namespace UF memcpy(eq->Title, emu->Title, sizeof(eq->Title)); memcpy(eq->Text, emu->Text, sizeof(eq->Text)); OUT(Buttons); + OUT(SoundControls); OUT(Duration); OUT(PopupID); OUT(NegativeID); diff --git a/common/patches/uf_structs.h b/common/patches/uf_structs.h index 560a9378d..5a9562117 100644 --- a/common/patches/uf_structs.h +++ b/common/patches/uf_structs.h @@ -1801,7 +1801,7 @@ struct OnLevelMessage_Struct { /*4224*/ char ButtonName0[25]; // If Buttons = 1, these two are the text for the left and right buttons respectively /*4249*/ char ButtonName1[25]; /*4274*/ uint8 Buttons; -/*4275*/ uint8 Unknown4275; // Something to do with audio controls +/*4275*/ uint8 SoundControls; // Something to do with audio controls /*4276*/ uint32 Duration; /*4280*/ uint32 PopupID; // If none zero, a response packet with 00 00 00 00 is returned on clicking the left button /*4284*/ uint32 NegativeID; // If none zero, a response packet with 01 00 00 00 is returned on clicking the right button diff --git a/zone/aa.cpp b/zone/aa.cpp index e002dd4f2..51cb376da 100644 --- a/zone/aa.cpp +++ b/zone/aa.cpp @@ -1202,6 +1202,11 @@ void Client::ActivateAlternateAdvancementAbility(int rank_id, int target_id) { Message_StringID(MT_SpellFailure, SNEAK_RESTRICT); return; } + // + // Modern clients don't require pet targeted for AA casts that are ST_Pet + if (spells[rank->spell].targettype == ST_Pet || spells[rank->spell].targettype == ST_SummonedPet) + target_id = GetPetID(); + // 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), EQEmu::CastingSlot::AltAbility, spells[rank->spell].mana, -1, spells[rank->spell].ResistDiff, false)) { diff --git a/zone/client.cpp b/zone/client.cpp index b25e4e18a..8f1f9631c 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -3960,6 +3960,46 @@ void Client::SendPopupToClient(const char *Title, const char *Text, uint32 Popup safe_delete(outapp); } +void Client::SendFullPopup(const char *Title, const char *Text, uint32 PopupID, uint32 NegativeID, uint32 Buttons, uint32 Duration, const char *ButtonName0, const char *ButtonName1, uint32 SoundControls) { + auto outapp = new EQApplicationPacket(OP_OnLevelMessage, sizeof(OnLevelMessage_Struct)); + OnLevelMessage_Struct *olms = (OnLevelMessage_Struct *)outapp->pBuffer; + + if((strlen(Text) > (sizeof(olms->Text)-1)) || (strlen(Title) > (sizeof(olms->Title) - 1)) ) { + safe_delete(outapp); + return; + } + + if (ButtonName0 && ButtonName1 && ( (strlen(ButtonName0) > (sizeof(olms->ButtonName0) - 1)) || (strlen(ButtonName1) > (sizeof(olms->ButtonName1) - 1)) ) ) { + safe_delete(outapp); + return; + } + + strcpy(olms->Title, Title); + strcpy(olms->Text, Text); + + olms->Buttons = Buttons; + + if (ButtonName0 == NULL || ButtonName1 == NULL) { + sprintf(olms->ButtonName0, "%s", "Yes"); + sprintf(olms->ButtonName1, "%s", "No"); + } else { + strcpy(olms->ButtonName0, ButtonName0); + strcpy(olms->ButtonName1, ButtonName1); + } + + if(Duration > 0) + olms->Duration = Duration * 1000; + else + olms->Duration = 0xffffffff; + + olms->PopupID = PopupID; + olms->NegativeID = NegativeID; + olms->SoundControls = SoundControls; + + QueuePacket(outapp); + safe_delete(outapp); +} + void Client::SendWindow(uint32 PopupID, uint32 NegativeID, uint32 Buttons, const char *ButtonName0, const char *ButtonName1, uint32 Duration, int title_type, Client* target, const char *Title, const char *Text, ...) { va_list argptr; char buffer[4096]; @@ -8532,7 +8572,7 @@ void Client::Consume(const EQEmu::ItemData *item, uint8 type, int16 slot, bool a if (type == EQEmu::item::ItemTypeFood) { - int hchange = item->CastTime * cons_mod; + int hchange = item->CastTime_ * cons_mod; hchange = mod_food_value(item, hchange); if(hchange < 0) { return; } @@ -8549,7 +8589,7 @@ void Client::Consume(const EQEmu::ItemData *item, uint8 type, int16 slot, bool a } else { - int tchange = item->CastTime * cons_mod; + int tchange = item->CastTime_ * cons_mod; tchange = mod_drink_value(item, tchange); if(tchange < 0) { return; } diff --git a/zone/client.h b/zone/client.h index 89e0d9391..0f0f90873 100644 --- a/zone/client.h +++ b/zone/client.h @@ -946,6 +946,7 @@ public: inline bool HasSpellScribed(int spellid) { return (FindSpellBookSlotBySpellID(spellid) != -1 ? true : false); } uint16 GetMaxSkillAfterSpecializationRules(EQEmu::skills::SkillType skillid, uint16 maxSkill); void SendPopupToClient(const char *Title, const char *Text, uint32 PopupID = 0, uint32 Buttons = 0, uint32 Duration = 0); + void SendFullPopup(const char *Title, const char *Text, uint32 PopupID = 0, uint32 NegativeID = 0, uint32 Buttons = 0, uint32 Duration = 0, const char *ButtonName0 = 0, const char *ButtonName1 = 0, uint32 SoundControls = 0); void SendWindow(uint32 PopupID, uint32 NegativeID, uint32 Buttons, const char *ButtonName0, const char *ButtonName1, uint32 Duration, int title_type, Client* target, const char *Title, const char *Text, ...); bool PendingTranslocate; time_t TranslocateTime; diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index bbd76183d..361c67d40 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -8599,13 +8599,17 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app) (IsAmnesiad() && IsDiscipline(spell_id)) || (IsDetrimentalSpell(spell_id) && !zone->CanDoCombat()) || (inst->IsScaling() && inst->GetExp() <= 0) // charms don't have spells when less than 0 - ) ) + ) { SendSpellBarEnable(spell_id); return; } + // Modern clients don't require pet targeted for item clicks that are ST_Pet + if (spell_id > 0 && (spells[spell_id].targettype == ST_Pet || spells[spell_id].targettype == ST_SummonedPet)) + target_id = GetPetID(); + Log(Logs::General, Logs::None, "OP ItemVerifyRequest: spell=%i, target=%i, inv=%i", spell_id, target_id, slot_id); if (m_inv.SupportsClickCasting(slot_id) || ((item->ItemType == EQEmu::item::ItemTypePotion || item->PotionBelt) && m_inv.SupportsPotionBeltCasting(slot_id))) // sanity check diff --git a/zone/effects.cpp b/zone/effects.cpp index 2537751aa..8d8265e32 100644 --- a/zone/effects.cpp +++ b/zone/effects.cpp @@ -356,67 +356,15 @@ int32 Client::GetActSpellCost(uint16 spell_id, int32 cost) cost -= mana_back; } - // This formula was derived from the following resource: - // http://www.eqsummoners.com/eq1/specialization-library.html - // WildcardX - float PercentManaReduction = 0; - float SpecializeSkill = GetSpecializeSkillValue(spell_id); - int SuccessChance = zone->random.Int(0, 100); - - float bonus = 1.0; - switch(GetAA(aaSpellCastingMastery)) - { - case 1: - bonus += 0.05; - break; - case 2: - bonus += 0.15; - break; - case 3: - bonus += 0.30; - break; - } - - bonus += 0.05f * GetAA(aaAdvancedSpellCastingMastery); - - if(SuccessChance <= (SpecializeSkill * 0.3 * bonus)) - { - PercentManaReduction = 1 + 0.05f * SpecializeSkill; - switch(GetAA(aaSpellCastingMastery)) - { - case 1: - PercentManaReduction += 2.5; - break; - case 2: - PercentManaReduction += 5.0; - break; - case 3: - PercentManaReduction += 10.0; - break; - } - - switch(GetAA(aaAdvancedSpellCastingMastery)) - { - case 1: - PercentManaReduction += 2.5; - break; - case 2: - PercentManaReduction += 5.0; - break; - case 3: - PercentManaReduction += 10.0; - break; - } - } + int spec = GetSpecializeSkillValue(spell_id); + int PercentManaReduction = 0; + if (spec) + PercentManaReduction = 1 + spec / 20; // there seems to be some non-obvious rounding here, let's truncate for now. int16 focus_redux = GetFocusEffect(focusManaCost, spell_id); + PercentManaReduction += focus_redux; - if(focus_redux > 0) - { - PercentManaReduction += zone->random.Real(1, (double)focus_redux); - } - - cost -= (cost * (PercentManaReduction / 100)); + cost -= cost * PercentManaReduction / 100; // Gift of Mana - reduces spell cost to 1 mana if(focus_redux >= 100) { diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index 46746970c..622551ce0 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -6444,6 +6444,47 @@ XS(XS_Client_GetAccountAge) { XSRETURN(1); } +XS(XS_Client_Popup2); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Client_Popup2) +{ + dXSARGS; + if (items < 3 || items > 10) + Perl_croak(aTHX_ "Usage: Client::SendFullPopup(THIS, Title, Text, PopupID, NegativeID, Buttons, Duration, ButtonName0, ButtonName1, SoundControls)"); + { + Client * THIS; + char* Title = (char *)SvPV_nolen(ST(1)); + char* Text = (char *)SvPV_nolen(ST(2)); + uint32 PopupID = 0; + uint32 NegativeID = 0; + uint32 Buttons = 0; + uint32 Duration = 0; + char* ButtonName0 = 0; + char* ButtonName1 = 0; + uint32 SoundControls = 0; + + if (sv_derived_from(ST(0), "Client")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(Client *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type Client"); + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + if (items > 3) { PopupID = (uint32)SvUV(ST(3)); } + if (items > 4) { NegativeID = (uint32)SvUV(ST(4)); } + if (items > 5) { Buttons = (uint32)SvUV(ST(5)); } + if (items > 6) { Duration = (uint32)SvUV(ST(6)); } + if (items > 7) { ButtonName0 = (char *)SvPV_nolen(ST(7)); } + if (items > 8) { ButtonName1 = (char *)SvPV_nolen(ST(8)); } + if (items > 9) { SoundControls = (uint32)SvUV(ST(9)); } + + + THIS->SendFullPopup(Title, Text, PopupID, NegativeID, Buttons, Duration, ButtonName0, ButtonName1, SoundControls); + } + XSRETURN_EMPTY; +} + #ifdef __cplusplus extern "C" @@ -6698,6 +6739,7 @@ XS(boot_Client) newXSproto(strcpy(buf, "CalcEXP"), XS_Client_CalcEXP, file, "$"); newXSproto(strcpy(buf, "GetMoney"), XS_Client_GetMoney, file, "$$$"); newXSproto(strcpy(buf, "GetAccountAge"), XS_Client_GetAccountAge, file, "$"); + newXSproto(strcpy(buf, "Popup2"), XS_Client_Popup2, file, "$$$;$$$$$$$"); XSRETURN_YES; } diff --git a/zone/spells.cpp b/zone/spells.cpp index 2be0a0bce..ef0553526 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -3059,6 +3059,12 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, if (IsEffectIgnoredInStacking(effect1)) continue; + // negative AC affects are skipped. Ex. Sun's Corona and Glacier Breath should stack + // There may be more SPAs we need to add here .... + // The client does just check base rather than calculating the affect change value. + if ((effect1 == SE_ArmorClass || effect1 == SE_ACv2) && sp2.base[i] < 0) + continue; + /* If target is a npc and caster1 and caster2 exist If Caster1 isn't the same as Caster2 and the effect is a DoT then ignore it.