diff --git a/common/spdat.h b/common/spdat.h index 379cb8cd8..27f7e1870 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -682,7 +682,7 @@ typedef enum { #define SE_PercentXPIncrease 337 // implemented #define SE_SummonAndResAllCorpses 338 // implemented #define SE_TriggerOnCast 339 // implemented -#define SE_SpellTrigger 340 // implemented - chance to trigger spell +#define SE_SpellTrigger 340 // implemented - chance to trigger spell [Share rolls with 469] All base2 spells share roll chance, only 1 cast. #define SE_ItemAttackCapIncrease 341 // implemented[AA] - increases the maximum amount of attack you can gain from items. #define SE_ImmuneFleeing 342 // implemented - stop mob from fleeing #define SE_InterruptCasting 343 // implemented - % chance to interrupt spells being cast every tic. Cacophony (8272) @@ -811,8 +811,8 @@ typedef enum { #define SE_PC_Pet_Flurry_Chance 466 // implemented - Base1 % chance to do flurry from double attack hit. #define SE_DS_Mitigation_Amount 467 // implemented - Modify incoming damage shield damage by percentage #define SE_DS_Mitigation_Percentage 468 // implemented - Modify incoming damage shield damage by a flat amount -//#define SE_Chance_Best_in_Spell_Grp 469 // -//#define SE_Trigger_Best_in_Spell Grp 470 // +#define SE_Chance_Best_in_Spell_Grp 469 // implemented - Chance to cast highest scribed spell within a spell group. All base2 spells share roll chance, only 1 cast. +#define SE_Trigger_Best_in_Spell_Grp 470 // implemented - Chance to cast highest scribed spell within a spell group. Each spell has own chance. //#define SE_Double_Melee_Round 471 // //#define SE_Buy_AA_Rank 472 // #define SE_Double_Backstab_Front 473 // implemented - Chance to double backstab from front diff --git a/zone/client.h b/zone/client.h index 734bc66ff..74e2eed54 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1021,6 +1021,7 @@ public: int GetNextAvailableSpellBookSlot(int starting_slot = 0); inline uint32 GetSpellByBookSlot(int book_slot) { return m_pp.spell_book[book_slot]; } inline bool HasSpellScribed(int spellid) { return FindSpellBookSlotBySpellID(spellid) != -1; } + uint32 GetHighestScribedSpellinSpellGroup(uint32 spell_group); uint16 GetMaxSkillAfterSpecializationRules(EQ::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); diff --git a/zone/mob.cpp b/zone/mob.cpp index 680dc8f3d..94d85de13 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -3537,57 +3537,82 @@ void Mob::TriggerOnCast(uint32 focus_spell, uint32 spell_id, bool aa_trigger) } } + + + bool Mob::TrySpellTrigger(Mob *target, uint32 spell_id, int effect) { - if(!target || !IsValidSpell(spell_id)) + if (!target || !IsValidSpell(spell_id)) return false; - int spell_trig = 0; - // Count all the percentage chances to trigger for all effects - for(int i = 0; i < EFFECT_COUNT; i++) - { - if (spells[spell_id].effectid[i] == SE_SpellTrigger) - spell_trig += spells[spell_id].base[i]; - } - // If all the % add to 100, then only one of the effects can fire but one has to fire. - if (spell_trig == 100) - { - int trig_chance = 100; - for(int i = 0; i < EFFECT_COUNT; i++) - { - if (spells[spell_id].effectid[i] == SE_SpellTrigger) - { - if(zone->random.Int(0, trig_chance) <= spells[spell_id].base[i]) - { - // If we trigger an effect then its over. - if (IsValidSpell(spells[spell_id].base2[i])){ - SpellFinished(spells[spell_id].base2[i], target, EQ::spells::CastingSlot::Item, 0, -1, spells[spells[spell_id].base2[i]].ResistDiff); - return true; - } - } - else - { - // Increase the chance to fire for the next effect, if all effects fail, the final effect will fire. - trig_chance -= spells[spell_id].base[i]; - } - } + /*The effects SE_SpellTrigger (SPA 340) and SE_Chance_Best_in_Spell_Grp (SPA 469) work as follows, you typically will have 2-3 different spells each with their own + chance to be triggered with all chances equaling up to 100 pct, with only 1 spell out of the group being ultimately cast. + (ie Effect1 trigger spellA with 30% chance, Effect2 triggers spellB with 20% chance, Effect3 triggers spellC with 50% chance). + The following function ensures a stastically accurate chance for each spell to be cast based on their chance values. These effects are also used in spells where there + is only 1 effect using the trigger effect. In those situations we simply roll a chance for that spell to be cast once. + Note: Both SPA 340 and 469 can be in same spell and both cummulative add up to 100 pct chances. SPA469 only difference being the spell cast will + be "best in spell group", instead of a defined spell_id.*/ - } - } - // if the chances don't add to 100, then each effect gets a chance to fire, chance for no trigger as well. - else + int chance_array[EFFECT_COUNT] = {}; + int total_chance = 0; + int effect_slot = effect; + bool CastSpell = false; + + for (int i = 0; i < EFFECT_COUNT; i++) { - 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, EQ::spells::CastingSlot::Item, 0, -1, spells[spells[spell_id].base2[effect]].ResistDiff); - return true; //Only trigger once of these per spell effect. + if (spells[spell_id].effectid[i] == SE_SpellTrigger || spells[spell_id].effectid[i] == SE_Chance_Best_in_Spell_Grp) + total_chance += spells[spell_id].base[i]; + } + + if (total_chance == 100) + { + int current_chance = 0; + int cummulative_chance = 0; + + for (int i = 0; i < EFFECT_COUNT; i++){ + //Find spells with SPA 340 and add the cummulative percent chances to the roll array + if ((spells[spell_id].effectid[i] == SE_SpellTrigger) || (spells[spell_id].effectid[i] == SE_Chance_Best_in_Spell_Grp)){ + + cummulative_chance = current_chance + spells[spell_id].base[i]; + chance_array[i] = cummulative_chance; + current_chance = cummulative_chance; + } + } + int random_roll = zone->random.Int(1, 100); + //Determine which spell out of the group of the spells (each with own percent chance out of 100) will be cast based on a single roll. + for (int i = 0; i < EFFECT_COUNT; i++){ + if (chance_array[i] != 0 && random_roll <= chance_array[i]) { + effect_slot = i; + CastSpell = true; + break; } } } + + //If the chances don't add to 100, then each effect gets a chance to fire, chance for no trigger as well. + else if (zone->random.Roll(spells[spell_id].base[effect])) { + CastSpell = true; //In this case effect_slot is what was passed into function. + } + + if (CastSpell) { + if (spells[spell_id].effectid[effect_slot] == SE_SpellTrigger && IsValidSpell(spells[spell_id].base2[effect_slot])) { + SpellFinished(spells[spell_id].base2[effect_slot], target, EQ::spells::CastingSlot::Item, 0, -1, spells[spells[spell_id].base2[effect_slot]].ResistDiff); + return true; + } + else if (IsClient() & spells[spell_id].effectid[effect_slot] == SE_Chance_Best_in_Spell_Grp) { + uint32 best_spell_id = CastToClient()->GetHighestScribedSpellinSpellGroup(spells[spell_id].base2[effect_slot]); + if (IsValidSpell(best_spell_id)) { + SpellFinished(best_spell_id, target, EQ::spells::CastingSlot::Item, 0, -1, spells[best_spell_id].ResistDiff); + } + return true;//Do nothing if you don't have the any spell in spell group scribed. + } + } + return false; } + + void Mob::TryTriggerOnValueAmount(bool IsHP, bool IsMana, bool IsEndur, bool IsPet) { /* diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 801700bd5..aec467742 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -197,7 +197,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if (!IsPowerDistModSpell(spell_id)) SetSpellPowerDistanceMod(0); - bool SE_SpellTrigger_HasCast = false; + bool spell_trigger_cast_complete = false; //Used with SE_Spell_Trigger and SE_Chance_Best_in_Spell_Grp, true when spell has been triggered. // 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; @@ -2822,9 +2822,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_SpellTrigger: { - if (!SE_SpellTrigger_HasCast) { + if (!spell_trigger_cast_complete) { if (caster && caster->TrySpellTrigger(this, spell_id, i)) - SE_SpellTrigger_HasCast = true; + spell_trigger_cast_complete = true; } break; } @@ -2873,6 +2873,28 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove HealDamage(amt, caster); break; } + + case SE_Chance_Best_in_Spell_Grp: { + if (!spell_trigger_cast_complete) { + if (caster && caster->TrySpellTrigger(this, spell_id, i)) + spell_trigger_cast_complete = true; + } + break; + } + + case SE_Trigger_Best_in_Spell_Grp: { + + if (caster && !caster->IsClient()) + break; + + if (zone->random.Roll(spells[spell_id].base[i])) { + uint32 best_spell_id = caster->CastToClient()->GetHighestScribedSpellinSpellGroup(spells[spell_id].base2[i]); + + if (caster && IsValidSpell(best_spell_id)) + caster->SpellFinished(best_spell_id, this, EQ::spells::CastingSlot::Item, 0, -1, spells[best_spell_id].ResistDiff); + } + break; + } case SE_PersistentEffect: diff --git a/zone/spells.cpp b/zone/spells.cpp index 5e0db7c8a..1a1cd9a9c 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -5251,6 +5251,27 @@ int Client::FindSpellBookSlotBySpellID(uint16 spellid) { return -1; //default } +uint32 Client::GetHighestScribedSpellinSpellGroup(uint32 spell_group) +{ + //Typical live spells follow 1/5/10 rank value for actual ranks 1/2/3, but this can technically be set as anything. + + int highest_rank = 0; //highest ranked found in spellgroup + uint32 highest_spell_id = 0; //spell_id of the highest ranked spell you have scribed in that spell rank. + + for (int i = 0; i < EQ::spells::SPELLBOOK_SIZE; i++) { + + if (IsValidSpell(m_pp.spell_book[i])) { + if (spells[m_pp.spell_book[i]].spellgroup == spell_group) { + if (highest_rank < spells[m_pp.spell_book[i]].rank) { + highest_rank = spells[m_pp.spell_book[i]].rank; + highest_spell_id = m_pp.spell_book[i]; + } + } + } + } + return highest_spell_id; +} + bool Client::SpellGlobalCheck(uint16 spell_id, uint32 char_id) { std::string spell_global_name; int spell_global_value;