diff --git a/common/spdat.cpp b/common/spdat.cpp index e5d85ba9e..2b45a8300 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -1236,6 +1236,7 @@ bool IsEffectIgnoredInStacking(int spa) case SE_Ff_CasterClass: case SE_Ff_Same_Caster: case SE_Proc_Timer_Modifier: + case SE_Weapon_Stance: case SE_TwinCastBlocker: case SE_Fc_CastTimeAmt: case SE_Fc_CastTimeMod2: diff --git a/common/spdat.h b/common/spdat.h index d54110e98..75ce36d19 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -64,6 +64,7 @@ #define SPELL_SHAPECHANGE70 6503 #define SPELL_MANA_BURN 2751 #define SPELL_LIFE_BURN 2755 +#define SPELL_TOUCH_OF_THE_DIVINE 4789 // these have known hardcoded behavior but we don't do anything yet, move them above this comment when fixed #define SPELL_THE_DAINS_JUSTICE 1476 #define SPELL_MODULATION 1502 @@ -834,11 +835,11 @@ typedef enum { #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_Buy_AA_Rank 472 // implemented, @Special, Used in AA abilities that have Enable/Disable toggle. Spell on Disabled Rank has this effect in it, base: 1, limit: none, max: none, Note: This will not just buy an AA #define SE_Double_Backstab_Front 473 // implemented - Chance to double backstab from front #define SE_Pet_Crit_Melee_Damage_Pct_Owner 474 // implemenetd - Critical damage mod applied to pets from owner #define SE_Trigger_Spell_Non_Item 475 // implemented - Trigger spell on cast only if not from item click. -//#define SE_Weapon_Stance 476 // +#define SE_Weapon_Stance 476 // implemented, @Misc, Apply a specific spell buffs automatically depending 2Hander, Shield or Duel Wield is equiped, base: spellid, base: 0=2H 1=Shield 2=DW, max: none #define SE_Hatelist_To_Top_Index 477 // Implemented - Chance to be set to top of rampage list #define SE_Hatelist_To_Tail_Index 478 // Implemented - Chance to be set to bottom of rampage list #define SE_Ff_Value_Min 479 // implemented, @Ff, Minimum base value of a spell that can be focused, base: spells to be focused base1 value @@ -865,10 +866,10 @@ typedef enum { #define SE_Fc_CastTimeMod2 500 // implemented, @Fc, On Caster, cast time mod pct, base: pct, Note: Can reduce to instant cast #define SE_Fc_CastTimeAmt 501 // implemented, @Fc, On Caster, cast time mod flat amt, base: milliseconds, Note: Can reduce to instant cast #define SE_Fearstun 502 // implemented - Stun with a max level limit. Normal stun restrictions don't apply. -#define SE_Melee_Damage_Position_Mod 503 // implemented - modify melee damage by pct if done from Front or Behind -//#define SE_Melee_Damage_Position_Amt 504 // -#define SE_Damage_Taken_Position_Mod 505 // implemented - mitigate melee damage by pct if dmg taken from Front or Behind -//#define SE_Damage_Taken_Position_Amt 506 // +#define SE_Melee_Damage_Position_Mod 503 // implemented, @OffBonus, modify melee damage by percent if done from Front or Behind opponent, base: pct, limit: 0=back 1=front, max: none +#define SE_Melee_Damage_Position_Amt 504 // implemented, @OffBonus, modify melee damage by flat amount if done from Front or Behind opponent, base: amt, limit: 0=back 1=front, max: none +#define SE_Damage_Taken_Position_Mod 505 // implemented, @DefBonus, modify melee damage by percent if dmg taken from Front or Behind, base: pct, limit: 0=back 1=front, max: none +#define SE_Damage_Taken_Position_Amt 506 // implemented -@DefBonus, modify melee damage by flat amount if dmg taken from your Front or Behind, base: amt, limit: 0=back 1=front, max: none #define SE_Fc_Amplify_Mod 507 // implemented, @Fc, On Caster, damage-heal-dot mod pct, base: pct #define SE_Fc_Amplify_Amt 508 // implemented, @Fc, On Caster, damage-heal-dot mod flat amt, base: amt #define SE_Health_Transfer 509 // implemented - exchange health for damage or healing on a target. ie Lifeburn/Act of Valor diff --git a/zone/aa.cpp b/zone/aa.cpp index 0b19065bf..fff2ae727 100644 --- a/zone/aa.cpp +++ b/zone/aa.cpp @@ -1162,6 +1162,7 @@ void Client::IncrementAlternateAdvancementRank(int rank_id) { void Client::ActivateAlternateAdvancementAbility(int rank_id, int target_id) { AA::Rank *rank = zone->GetAlternateAdvancementRank(rank_id); + if(!rank) { return; } @@ -1178,9 +1179,11 @@ void Client::ActivateAlternateAdvancementAbility(int rank_id, int target_id) { if(!CanUseAlternateAdvancementRank(rank)) { return; } + + bool use_toggle_passive_hotkey = UseTogglePassiveHotkey(*rank); //make sure it is not a passive - if(!rank->effects.empty()) { + if(!rank->effects.empty() && !use_toggle_passive_hotkey) { return; } @@ -1188,7 +1191,6 @@ void Client::ActivateAlternateAdvancementAbility(int rank_id, int target_id) { // We don't have the AA if (!GetAA(rank_id, &charges)) return; - //if expendable make sure we have charges if(ability->charges > 0 && charges < 1) return; @@ -1241,15 +1243,21 @@ void Client::ActivateAlternateAdvancementAbility(int rank_id, int target_id) { } } - // 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), EQ::spells::CastingSlot::AltAbility, spells[rank->spell].mana, -1, spells[rank->spell].ResistDiff, false)) { - return; + if (use_toggle_passive_hotkey) { + TogglePassiveAlternativeAdvancement(*rank, ability->id); + } + else { + // 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), EQ::spells::CastingSlot::AltAbility, spells[rank->spell].mana, -1, spells[rank->spell].ResistDiff, false)) { + return; + } + ExpendAlternateAdvancementCharge(ability->id); } - ExpendAlternateAdvancementCharge(ability->id); - } else { - if(!CastSpell(rank->spell, target_id, EQ::spells::CastingSlot::AltAbility, -1, -1, 0, -1, rank->spell_type + pTimerAAStart, cooldown, nullptr, rank->id)) { - return; + else { + if (!CastSpell(rank->spell, target_id, EQ::spells::CastingSlot::AltAbility, -1, -1, 0, -1, rank->spell_type + pTimerAAStart, cooldown, nullptr, rank->id)) { + return; + } } } @@ -1287,16 +1295,16 @@ int Mob::GetAlternateAdvancementCooldownReduction(AA::Rank *rank_in) { } void Mob::ExpendAlternateAdvancementCharge(uint32 aa_id) { - for(auto &iter : aa_ranks) { + for (auto &iter : aa_ranks) { AA::Ability *ability = zone->GetAlternateAdvancementAbility(iter.first); - if(ability && aa_id == ability->id) { - if(iter.second.second > 0) { + if (ability && aa_id == ability->id) { + if (iter.second.second > 0) { iter.second.second -= 1; - if(iter.second.second == 0) { - if(IsClient()) { + if (iter.second.second == 0) { + if (IsClient()) { AA::Rank *r = ability->GetRankByPointsSpent(iter.second.first); - if(r) { + if (r) { CastToClient()->GetEPP().expended_aa += r->cost; } } @@ -1307,7 +1315,7 @@ void Mob::ExpendAlternateAdvancementCharge(uint32 aa_id) { aa_ranks.erase(iter.first); } - if(IsClient()) { + if (IsClient()) { Client *c = CastToClient(); c->SaveAA(); c->SendAlternateAdvancementPoints(); @@ -1796,3 +1804,169 @@ bool Mob::CheckAATimer(int timer) } return false; } + +void Client::TogglePassiveAlternativeAdvancement(const AA::Rank &rank, uint32 ability_id) +{ + /* + Certain AA, like Weapon Stance line use a special toggle Hotkey to enable or disable the AA's passive abilities. + This is occurs by doing the following. Each 'rank' of Weapon Stance is actually 2 actual ranks. + First rank is always the Disabled version which cost X amount of AA. Second rank is the Enabled version which cost 0 AA. + When you buy the first rank, you make a hotkey that on live say 'Weapon Stance Disabled', if you clik that it then BUYS the + next rank of AA (cost 0) which switches the hotkey to 'Enabled Weapon Stance' and you are given the passive buff effects. + If you click the Enabled hotkey, it causes you to lose an AA rank and once again be disabled. Thus, you are switching between + two AA ranks. Thefore when creating an AA using this ability, you need generate both ranks. Follow the same pattern for additional ranks. + + IMPORTANT! The toggle system can be used to Enable or Disable ANY passive AA. You just need to follow the instructions on how to create it. + Example: Enable or Disable a buff that gives a large hate modifier. Play may Enable when tanking and Disable when DPS ect. + + Note: On live the Enabled rank is shown having a Charge of 1, while Disabled rank has no charges. Our current code doesn't support that. Do not use charges. + Note: Live uses a spell 'Disable Ability' ID 46164 to trigger a script to do the AA rank changes. At present time it is not coded to require that, any spell id works. + Note: Discovered a bug on ROF2, where when you buy first rank of an AA with a hotkey, it will always display the title of the second rank in the database. Be aware. No easy fix. + + Dev Note(Kayen 8/1/21): The system as set up is very similar to live, with exception that live gives the Enabled rank 1 Charge. The code here emulates what happens when a + charge would be expended. + + Instructions for how to make the AA - assuming a basic level of knowledge of how AA's work. + - aa_abilities table : Create new ability with a hotkey, type 3, zero charges + - aa_ranks table : [Disabled rank] First rank, should have a cost > 0 (this is what you buy), Set hotkeys, MUST SET A SPELL CONTAINING EFFECT SE_Buy_AA_Rank(SPA 472), set a short recast timer. + [Enabled rank] Second rank, should have a cost = 0, Set hotkeys, Set any valid spell ID you want (it has to exist but does nothing), set a short recast timer. + *Recommend if doing custom, just make the hotkey titled 'Toggle ' and use for both. + + - aa_rank_effects table : [Disabled rank] No data needed in the aa_ranks_effect table + [Enabled rank] Second rank set effect_id = 457 (weapon stance), slot 1,2,3, base1= spell triggers, base= weapon type (0=2H,1=SH,2=DW), for slot 1,2,3 + + Example SQL -Disabled + DO NOT ADD any data to the aa_rank_effects for this rank_id + + -Enabled + INSERT INTO aa_rank_effects (rank_id, slot, effect_id, base1, base2) VALUES (20003, 1, 476, 145,0); + INSERT INTO aa_rank_effects (rank_id, slot, effect_id, base1, base2) VALUES (20003, 2, 476, 174,1); + INSERT INTO aa_rank_effects (rank_id, slot, effect_id, base1, base2) VALUES (20003, 3, 476, 172,2); + + Warning: If you want to design an AA that only uses one weapon type to trigger, like will only apply buff if Shield. Do not include data for other types. Never have a base value=0 + in the Enabled rank. + + */ + + bool enable_next_rank = IsEffectInSpell(rank.spell, SE_Buy_AA_Rank); + + if (enable_next_rank) { + + //Enable + TogglePurchaseAlternativeAdvancementRank(rank.next_id); + Message(Chat::Spells, "You enable an ability."); //Message live gives you. Should come from spell. + + AA::Rank *rank_next = zone->GetAlternateAdvancementRank(rank.next_id); + + //Add checks for any special cases for toggle. + if (IsEffectinAlternateAdvancementRankEffects(*rank_next, SE_Weapon_Stance)) { + weaponstance.aabonus_enabled = true; + ApplyWeaponsStance(); + } + return; + } + else { + + //Disable + ResetAlternateAdvancementRank(ability_id); + TogglePurchaseAlternativeAdvancementRank(rank.prev_id); + Message(Chat::Spells, "You disable an ability."); //Message live gives you. Should come from spell. + + //Add checks for any special cases for toggle. + if (IsEffectinAlternateAdvancementRankEffects(rank, SE_Weapon_Stance)) { + weaponstance.aabonus_enabled = false; + BuffFadeBySpellID(weaponstance.aabonus_buff_spell_id); + } + return; + } +} + +bool Client::UseTogglePassiveHotkey(const AA::Rank &rank) { + + /* + Disabled rank needs a rank spell containing the SE_Buy_AA_Rank effect to return true. + Enabled rank checks to see if the prior rank contains a rank spell with SE_Buy_AA_Rank, if so true. + + Note: On live the enabled rank is Expendable with Charge 1. + + We have already confirmed the rank spell is valid before this function is called. + */ + + + if (IsEffectInSpell(rank.spell, SE_Buy_AA_Rank)) {//Checked when is Disabled. + return true; + } + else if (rank.prev_id != -1) {//Check when effect is Enabled. + AA::Rank *rank_prev = zone->GetAlternateAdvancementRank(rank.prev_id); + + if (IsEffectInSpell(rank_prev->spell, SE_Buy_AA_Rank)) { + return true; + } + } + return false; +} + +bool Client::IsEffectinAlternateAdvancementRankEffects(const AA::Rank &rank, int effect_id) { + + for (const auto &e : rank.effects) { + + if (e.effect_id == effect_id) { + return true; + } + } + return false; +} + +void Client::ResetAlternateAdvancementRank(uint32 aa_id) { + + /* + Resets your AA to baseline + */ + + for(auto &iter : aa_ranks) { + + AA::Ability *ability = zone->GetAlternateAdvancementAbility(iter.first); + + if(ability && aa_id == ability->id) { + RemoveExpendedAA(ability->first_rank_id); + aa_ranks.erase(iter.first); + SaveAA(); + SendAlternateAdvancementPoints(); + return; + } + } +} + +void Client::TogglePurchaseAlternativeAdvancementRank(int rank_id){ + + /* + Stripped down version of purchasing AA. Will give no messages. + Used with toggle hotkey functions. + */ + + AA::Rank *rank = zone->GetAlternateAdvancementRank(rank_id); + if (!rank) { + return; + } + + if (!rank->base_ability) { + return; + } + + if (!CanPurchaseAlternateAdvancementRank(rank, false, false)) { + return; + } + + rank_id = rank->base_ability->first_rank_id; + SetAA(rank_id, rank->current_value, 0); + + if (rank->next) { + SendAlternateAdvancementRank(rank->base_ability->id, rank->next->current_value); + } + + SaveAA(); + SendAlternateAdvancementPoints(); + SendAlternateAdvancementStats(); + CalcBonuses(); +} + diff --git a/zone/attack.cpp b/zone/attack.cpp index ef0a7905f..392a06f4a 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -5222,7 +5222,7 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttac // Seems the crit message is generated before some of them :P // worn item +skill dmg, SPA 220, 418. Live has a normalized version that should be here too - hit.min_damage += GetSkillDmgAmt(hit.skill); + hit.min_damage += GetSkillDmgAmt(hit.skill) + GetPositionalDmgAmt(defender); // shielding mod2 if (defender->itembonuses.MeleeMitigation) @@ -5279,7 +5279,7 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttac int pct_damage_reduction = defender->GetSkillDmgTaken(hit.skill, opts) + defender->GetPositionalDmgTaken(this); - hit.damage_done += (hit.damage_done * pct_damage_reduction / 100) + (defender->GetFcDamageAmtIncoming(this, 0, true, hit.skill)); + hit.damage_done += (hit.damage_done * pct_damage_reduction / 100) + (defender->GetFcDamageAmtIncoming(this, 0, true, hit.skill)) + defender->GetPositionalDmgTakenAmt(this); if (defender->GetShielderID()) { DoShieldDamageOnShielder(defender, hit.damage_done, hit.skill); diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index bfdb4553e..81ee35ab4 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -154,6 +154,7 @@ void Client::CalcItemBonuses(StatBonuses* newbon) { SetShieldEquiped(false); SetTwoHandBluntEquiped(false); SetTwoHanderEquipped(false); + SetDuelWeaponsEquiped(false); unsigned int i; // Update: MainAmmo should only calc skill mods (TODO: Check for other cases) @@ -171,8 +172,13 @@ void Client::CalcItemBonuses(StatBonuses* newbon) { SetTwoHandBluntEquiped(true); SetTwoHanderEquipped(true); } - else if (i == EQ::invslot::slotPrimary && (item && (item->ItemType == EQ::item::ItemType2HSlash || item->ItemType == EQ::item::ItemType2HPiercing))) + else if (i == EQ::invslot::slotPrimary && (item && (item->ItemType == EQ::item::ItemType2HSlash || item->ItemType == EQ::item::ItemType2HPiercing))) { SetTwoHanderEquipped(true); + } + } + + if (CanThisClassDualWield()) { + SetDuelWeaponsEquiped(true); } //tribute items @@ -1539,6 +1545,26 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } + case SE_Damage_Taken_Position_Amt: + { + //Mitigate if damage taken from behind base2 = 0, from front base2 = 1 + if (base2 < 0 || base2 > 2) + break; + + newbon->Damage_Taken_Position_Amt[base2] += base1; + break; + } + + case SE_Melee_Damage_Position_Amt: + { + //Mitigate if damage taken from behind base2 = 0, from front base2 = 1 + if (base2 < 0 || base2 > 2) + break; + + newbon->Melee_Damage_Position_Amt[base2] += base1; + break; + } + case SE_DS_Mitigation_Amount: newbon->DS_Mitigation_Amount += base1; break; @@ -1555,6 +1581,25 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) newbon->Pet_Add_Atk += base1; break; + case SE_Weapon_Stance: + { + if (IsValidSpell(base1)) { //base1 is the spell_id of buff + if (base2 <= WEAPON_STANCE_TYPE_MAX) { //0=2H, 1=Shield, 2=DW + if (IsValidSpell(newbon->WeaponStance[base2])) { //Check if we already a spell_id saved for this effect + if (spells[newbon->WeaponStance[base2]].rank < spells[base1].rank) { //If so, check if any new spellids with higher rank exist (live spells for this are ranked). + newbon->WeaponStance[base2] = base1; //Overwrite with new effect + SetWeaponStanceEnabled(true); + } + } + else { + newbon->WeaponStance[base2] = base1; //If no prior effect exists, then apply + SetWeaponStanceEnabled(true); + } + } + } + break; + } + case SE_ExtraAttackChance: { if (newbon->ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] < base1) { @@ -1598,7 +1643,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) } break; } - + // to do case SE_PetDiscipline: break; @@ -1615,6 +1660,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) case SE_TrapCircumvention: break; + // not handled here case SE_HastenedAASkill: // not handled here but don't want to clutter debug log -- these may need to be verified to ignore @@ -3470,6 +3516,26 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } + case SE_Damage_Taken_Position_Amt: + { + //Mitigate if damage taken from behind base2 = 0, from front base2 = 1 + if (base2 < 0 || base2 > 2) + break; + + new_bonus->Damage_Taken_Position_Amt[base2] += effect_value; + break; + } + + case SE_Melee_Damage_Position_Amt: + { + //Mitigate if damage taken from behind base2 = 0, from front base2 = 1 + if (base2 < 0 || base2 > 2) + break; + + new_bonus->Melee_Damage_Position_Amt[base2] += effect_value; + break; + } + case SE_DS_Mitigation_Amount: new_bonus->DS_Mitigation_Amount += effect_value; break; @@ -3491,10 +3557,12 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne if (AdditiveWornBonus) { new_bonus->ExtendedShielding += effect_value; } - else if (effect_value < 0 && new_bonus->ExtendedShielding > effect_value) + else if (effect_value < 0 && new_bonus->ExtendedShielding > effect_value){ new_bonus->ExtendedShielding = effect_value; - else if (effect_value > 0 && new_bonus->ExtendedShielding < effect_value) + } + else if (effect_value > 0 && new_bonus->ExtendedShielding < effect_value){ new_bonus->ExtendedShielding = effect_value; + } break; } @@ -3503,10 +3571,44 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne if (AdditiveWornBonus) { new_bonus->ShieldDuration += effect_value; } - else if (effect_value < 0 && new_bonus->ShieldDuration > effect_value) + else if (effect_value < 0 && new_bonus->ShieldDuration > effect_value){ new_bonus->ShieldDuration = effect_value; - else if (effect_value > 0 && new_bonus->ShieldDuration < effect_value) + } + else if (effect_value > 0 && new_bonus->ShieldDuration < effect_value){ new_bonus->ShieldDuration = effect_value; + } + break; + } + + case SE_Weapon_Stance: { + if (IsValidSpell(effect_value)) { //base1 is the spell_id of buff + if (base2 <= WEAPON_STANCE_TYPE_MAX) { //0=2H, 1=Shield, 2=DW + if (IsValidSpell(new_bonus->WeaponStance[base2])) { //Check if we already a spell_id saved for this effect + if (spells[new_bonus->WeaponStance[base2]].rank < spells[effect_value].rank) { //If so, check if any new spellids with higher rank exist (live spells for this are ranked). + new_bonus->WeaponStance[base2] = effect_value; //Overwrite with new effect + SetWeaponStanceEnabled(true); + + if (WornType) { + weaponstance.itembonus_enabled = true; + } + else { + weaponstance.spellbonus_enabled = true; + } + } + } + else { + new_bonus->WeaponStance[base2] = effect_value; //If no prior effect exists, then apply + SetWeaponStanceEnabled(true); + + if (WornType) { + weaponstance.itembonus_enabled = true; + } + else { + weaponstance.spellbonus_enabled = true; + } + } + } + } break; } @@ -5098,21 +5200,39 @@ void Mob::NegateSpellsBonuses(uint16 spell_id) break; case SE_Melee_Damage_Position_Mod: - spellbonuses.Melee_Damage_Position_Mod[SBIndex::POSITIONAL_DAMAGE_MOD] = effect_value; - aabonuses.Melee_Damage_Position_Mod[SBIndex::POSITIONAL_DAMAGE_MOD] = effect_value; - itembonuses.Melee_Damage_Position_Mod[SBIndex::POSITIONAL_DAMAGE_MOD] = effect_value; - spellbonuses.Melee_Damage_Position_Mod[SBIndex::POSITIONAL_LOCATION] = effect_value; - aabonuses.Melee_Damage_Position_Mod[SBIndex::POSITIONAL_LOCATION] = effect_value; - itembonuses.Melee_Damage_Position_Mod[SBIndex::POSITIONAL_LOCATION] = effect_value; + spellbonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_BACK] = effect_value; + aabonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_BACK] = effect_value; + itembonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_BACK] = effect_value; + spellbonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_FRONT] = effect_value; + aabonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_FRONT] = effect_value; + itembonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_FRONT] = effect_value; break; case SE_Damage_Taken_Position_Mod: - spellbonuses.Damage_Taken_Position_Mod[SBIndex::POSITIONAL_DAMAGE_MOD] = effect_value; - aabonuses.Damage_Taken_Position_Mod[SBIndex::POSITIONAL_DAMAGE_MOD] = effect_value; - itembonuses.Damage_Taken_Position_Mod[SBIndex::POSITIONAL_DAMAGE_MOD] = effect_value; - spellbonuses.Damage_Taken_Position_Mod[SBIndex::POSITIONAL_LOCATION] = effect_value; - aabonuses.Damage_Taken_Position_Mod[SBIndex::POSITIONAL_LOCATION] = effect_value; - itembonuses.Damage_Taken_Position_Mod[SBIndex::POSITIONAL_LOCATION] = effect_value; + spellbonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_BACK] = effect_value; + aabonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_BACK] = effect_value; + itembonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_BACK] = effect_value; + spellbonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_FRONT] = effect_value; + aabonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_FRONT] = effect_value; + itembonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_FRONT] = effect_value; + break; + + case SE_Melee_Damage_Position_Amt: + spellbonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_BACK] = effect_value; + aabonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_BACK] = effect_value; + itembonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_BACK] = effect_value; + spellbonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_FRONT] = effect_value; + aabonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_FRONT] = effect_value; + itembonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_FRONT] = effect_value; + break; + + case SE_Damage_Taken_Position_Amt: + spellbonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_BACK] = effect_value; + aabonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_BACK] = effect_value; + itembonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_BACK] = effect_value; + spellbonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_FRONT] = effect_value; + aabonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_FRONT] = effect_value; + itembonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_FRONT] = effect_value; break; diff --git a/zone/client.cpp b/zone/client.cpp index 9522c3489..040601ad0 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -1399,7 +1399,7 @@ bool Client::UpdateLDoNPoints(uint32 theme_id, int points) { mmc_points += (mir_points + m_pp.ldon_points_mir); mir_points = (0 - m_pp.ldon_points_mir); } - + if(m_pp.ldon_points_mmc < (0 - mmc_points)) { ruj_points += (mmc_points + m_pp.ldon_points_mmc); mmc_points = (0 - m_pp.ldon_points_mmc); @@ -2406,9 +2406,9 @@ bool Client::CheckIncreaseSkill(EQ::skills::SkillType skillid, Mob *against_who, parse->EventPlayer(EVENT_USE_SKILL, this, buffer, 0); if (against_who) { if ( - against_who->GetSpecialAbility(IMMUNE_AGGRO) || - against_who->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) || - against_who->IsClient() || + against_who->GetSpecialAbility(IMMUNE_AGGRO) || + against_who->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) || + against_who->IsClient() || GetLevelCon(against_who->GetLevel()) == CON_GRAY ) { //false by default @@ -9953,7 +9953,7 @@ void Client::MovePCDynamicZone(const std::string& zone_name, int zone_version, b MovePCDynamicZone(zone_id, zone_version, msg_if_invalid); } -void Client::Fling(float value, float target_x, float target_y, float target_z, bool ignore_los, bool clipping) { +void Client::Fling(float value, float target_x, float target_y, float target_z, bool ignore_los, bool clipping) { BuffFadeByEffect(SE_Levitate); if (CheckLosFN(target_x, target_y, target_z, 6.0f) || ignore_los) { auto outapp_fling = new EQApplicationPacket(OP_Fling, sizeof(fling_struct)); @@ -9962,7 +9962,7 @@ void Client::Fling(float value, float target_x, float target_y, float target_z, flingTo->collision = 0; else flingTo->collision = -1; - + flingTo->travel_time = -1; flingTo->unk3 = 1; flingTo->disable_fall_damage = 1; @@ -10017,7 +10017,7 @@ std::vector Client::GetLearnableDisciplines(uint8 min_level, uint8 max_leve if (learnable) { learnable_disciplines.push_back(spell_id); } - } + } return learnable_disciplines; } @@ -10027,7 +10027,7 @@ std::vector Client::GetLearnedDisciplines() { if (IsValidSpell(m_pp.disciplines.values[index])) { learned_disciplines.push_back(m_pp.disciplines.values[index]); } - } + } return learned_disciplines; } @@ -10037,7 +10037,7 @@ std::vector Client::GetMemmedSpells() { if (IsValidSpell(m_pp.mem_spells[index])) { memmed_spells.push_back(m_pp.mem_spells[index]); } - } + } return memmed_spells; } @@ -10083,7 +10083,7 @@ std::vector Client::GetScribeableSpells(uint8 min_level, uint8 max_level) { if (scribeable) { scribeable_spells.push_back(spell_id); } - } + } return scribeable_spells; } @@ -10161,7 +10161,7 @@ void Client::SendToInstance(std::string instance_type, std::string zone_short_na return; } - DataBucket::SetData(full_bucket_name, itoa(instance_id), itoa(duration)); + DataBucket::SetData(full_bucket_name, itoa(instance_id), itoa(duration)); } AssignToInstance(instance_id); @@ -10208,7 +10208,7 @@ void Client::RemoveItem(uint32 item_id, uint32 quantity) }; int removed_count = 0; const size_t size = sizeof(slots) / sizeof(slots[0]); - for (int slot_index = 0; slot_index < size; ++slot_index) { + for (int slot_index = 0; slot_index < size; ++slot_index) { for (int slot_id = slots[slot_index][0]; slot_id <= slots[slot_index][1]; ++slot_id) { if (removed_count == quantity) { break; @@ -10236,3 +10236,220 @@ void Client::SetGMStatus(int newStatus) { if (this->Admin() != newStatus) database.UpdateGMStatus(this->AccountID(), newStatus); } + +void Client::ApplyWeaponsStance() +{ + /* + + If you have a weapons stance bonus from at least one bonus type, each time you change weapons this function will ensure the correct + associated buffs are applied, and previous buff is removed. If your weapon stance bonus is completely removed it will, ensure buff is + also removed (ie, removing an item that has worn effect with weapon stance, or clicking off a buff). If client no longer has/never had + any spells/item/aa bonuses with weapon stance effect this function will only do a simple bool check. + + Note: Live like behavior is once you have the triggered buff you can manually click it off to remove it. Swaping any items in inventory will + reapply it automatically. + + Only buff spells should be used as triggered spell effect. IsBuffSpell function also checks spell id validity. + WeaponStance bonus arrary: 0=2H Weapon 1=Shield 2=Dualweild + + Toggling ON or OFF + - From spells, just remove the Primary buff that contains the WeaponStance effect in it. + - For items with worn effect, unequip the item. + - For AA abilities, a hotkey is used to Enable and Disable the effect. See. Client::TogglePassiveAlternativeAdvancement in aa.cpp for extensive details. + + Rank + - Most important for AA, but if you have more than one of WeaponStance effect for a given type, the spell trigger buff will apply whatever has the highest + 'rank' value from the spells table. AA's on live for this effect naturally do this. Be awere of this if making custom spells/worn effects/AA. + + When creating weapon stance effects, you do not need to use all three types. For example, can make an effect where you only get a buff from equiping shield. + + */ + + if (!IsWeaponStanceEnabled()) { + return; + } + + bool enabled = false; + bool item_bonus_exists = false; + bool aa_bonus_exists = false; + + if (weaponstance.spellbonus_enabled) { + + if (spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_2H] || spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD] || + spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]) { + + enabled = true; + + // Check if no longer has correct combination of weapon type and buff, if so remove buff. + if (!HasTwoHanderEquipped() && IsBuffSpell(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_2H]) && + FindBuff(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_2H])) { + BuffFadeBySpellID(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_2H]); + } + else if (!HasShieldEquiped() && IsBuffSpell(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD]) && + FindBuff(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD])) { + BuffFadeBySpellID(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD]); + } + else if (!HasDualWeaponsEquiped() && + IsBuffSpell(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]) && + FindBuff(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD])) { + BuffFadeBySpellID(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]); + } + // If you have correct combination of weapon type and bonus, and do not already have buff, then apply buff. + if (HasTwoHanderEquipped() && IsBuffSpell(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_2H])) { + if (!FindBuff(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_2H])) { + SpellOnTarget(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_2H], this); + } + weaponstance.spellbonus_buff_spell_id = spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_2H]; + } + else if (HasShieldEquiped() && IsBuffSpell(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD])) { + + if (!FindBuff(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD])) { + SpellOnTarget(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD], this); + } + weaponstance.spellbonus_buff_spell_id = spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD]; + } + else if (HasDualWeaponsEquiped() && IsBuffSpell(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD])) { + + if (!FindBuff(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD])) { + SpellOnTarget(spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD], this); + } + weaponstance.spellbonus_buff_spell_id = spellbonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]; + } + } + } + + // Spellbonus effect removal is checked in BuffFadeBySlot(int slot, bool iRecalcBonuses) in spell_effects.cpp when the buff is clicked off or fades. + + if (weaponstance.itembonus_enabled) { + + if (itembonuses.WeaponStance[WEAPON_STANCE_TYPE_2H] || itembonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD] || + itembonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]) { + + enabled = true; + item_bonus_exists = true; + + + // Edge case check if have multiple items with WeaponStance worn effect. Make sure correct buffs are applied if items are removed but others left on. + if (weaponstance.itembonus_buff_spell_id) { + + bool buff_desync = true; + if (weaponstance.itembonus_buff_spell_id == itembonuses.WeaponStance[WEAPON_STANCE_TYPE_2H] || + weaponstance.itembonus_buff_spell_id == itembonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD] || + (weaponstance.itembonus_buff_spell_id == itembonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD])) { + buff_desync = false; + } + + if (buff_desync) { + int fade_spell = weaponstance.itembonus_buff_spell_id; + weaponstance.itembonus_buff_spell_id = 0; //Need to zero this before we fade to prevent any recursive loops. + BuffFadeBySpellID(fade_spell); + } + } + + // Check if no longer has correct combination of weapon type and buff, if so remove buff. + if (!HasTwoHanderEquipped() && IsBuffSpell(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_2H]) && + FindBuff(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_2H])) { + BuffFadeBySpellID(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_2H]); + } + else if (!HasShieldEquiped() && IsBuffSpell(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD]) && + FindBuff(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD])) { + BuffFadeBySpellID(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD]); + } + else if (!HasDualWeaponsEquiped() && IsBuffSpell(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]) && + FindBuff(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD])) { + BuffFadeBySpellID(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]); + } + + // If you have correct combination of weapon type and bonus, and do not already have buff, then apply buff. + if (HasTwoHanderEquipped() && IsBuffSpell(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_2H])) { + + if (!FindBuff(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_2H])) { + SpellOnTarget(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_2H], this); + } + weaponstance.itembonus_buff_spell_id = itembonuses.WeaponStance[WEAPON_STANCE_TYPE_2H]; + } + else if (HasShieldEquiped() && IsBuffSpell(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD])) { + + if (!FindBuff(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD])) { + SpellOnTarget(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD], this); + } + weaponstance.itembonus_buff_spell_id = itembonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD]; + } + else if (HasDualWeaponsEquiped() && IsBuffSpell(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD])) { + if (!FindBuff(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD])) { + SpellOnTarget(itembonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD], this); + } + weaponstance.itembonus_buff_spell_id = itembonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]; + } + } + } + + // Itembonus effect removal when item is removed + if (!item_bonus_exists && weaponstance.itembonus_enabled) { + weaponstance.itembonus_enabled = false; + + if (weaponstance.itembonus_buff_spell_id) { + BuffFadeBySpellID(weaponstance.itembonus_buff_spell_id); + weaponstance.itembonus_buff_spell_id = 0; + } + } + + if (weaponstance.aabonus_enabled) { + + if (aabonuses.WeaponStance[WEAPON_STANCE_TYPE_2H] || aabonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD] || + aabonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]) { + + enabled = true; + aa_bonus_exists = true; + + //Check if no longer has correct combination of weapon type and buff, if so remove buff. + if (!HasTwoHanderEquipped() && IsBuffSpell(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_2H]) && + FindBuff(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_2H])) { + BuffFadeBySpellID(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_2H]); + } + + else if (!HasShieldEquiped() && IsBuffSpell(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD]) && + FindBuff(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD])) { + BuffFadeBySpellID(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD]); + } + + else if (!HasDualWeaponsEquiped() && IsBuffSpell(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]) && + FindBuff(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD])) { + BuffFadeBySpellID(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]); + } + + //If you have correct combination of weapon type and bonus, and do not already have buff, then apply buff. + if (HasTwoHanderEquipped() && IsBuffSpell(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_2H])) { + if (!FindBuff(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_2H])) { + SpellOnTarget(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_2H], this); + } + weaponstance.aabonus_buff_spell_id = aabonuses.WeaponStance[WEAPON_STANCE_TYPE_2H]; + } + + else if (HasShieldEquiped() && IsBuffSpell(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD])) { + if (!FindBuff(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD])) { + SpellOnTarget(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD], this); + } + weaponstance.aabonus_buff_spell_id = aabonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD]; + } + + else if (HasDualWeaponsEquiped() && IsBuffSpell(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD])) { + + if (!FindBuff(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD])) { + SpellOnTarget(aabonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD], this); + } + weaponstance.aabonus_buff_spell_id = aabonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]; + } + } + } + + // AA bonus removal is checked in TogglePassiveAA in aa.cpp. when the hot key is toggled. + + // If no bonuses remain present, prevent additional future checks until new bonus is applied. + if (!enabled) { + SetWeaponStanceEnabled(false); + weaponstance.aabonus_enabled = false; + weaponstance.itembonus_enabled = false; + weaponstance.spellbonus_enabled = false; + } +} diff --git a/zone/client.h b/zone/client.h index 3236ab901..ff80cebc7 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1554,6 +1554,13 @@ public: void ShowNumHits(); // work around function for numhits not showing on buffs + void ApplyWeaponsStance(); + void TogglePassiveAlternativeAdvancement(const AA::Rank &rank, uint32 ability_id); + bool UseTogglePassiveHotkey(const AA::Rank &rank); + void TogglePurchaseAlternativeAdvancementRank(int rank_id); + void ResetAlternateAdvancementRank(uint32 aa_id); + bool IsEffectinAlternateAdvancementRankEffects(const AA::Rank &rank, int effect_id); + void TripInterrogateInvState() { interrogateinv_flag = true; } bool GetInterrogateInvState() { return interrogateinv_flag; } diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index be8895b5d..df124009f 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -1717,6 +1717,8 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) /* Task Packets */ LoadClientTaskState(); + ApplyWeaponsStance(); + m_expedition_id = ExpeditionsRepository::GetIDByMemberID(database, CharacterID()); /** diff --git a/zone/common.h b/zone/common.h index f0d7a6959..cf7f827d3 100644 --- a/zone/common.h +++ b/zone/common.h @@ -104,6 +104,8 @@ #define PET_BUTTON_SPELLHOLD 9 #define AURA_HARDCAP 2 +#define WEAPON_STANCE_TYPE_MAX 2 + #define SHIELD_ABILITY_RECAST_TIME 180 @@ -325,7 +327,7 @@ struct Buffs_Struct { int32 ExtraDIChance; int16 RootBreakChance; //Not saved to dbase uint32 instrument_mod; - int16 focusproclimit_time; //timer to limit number of procs from focus effects + int16 focusproclimit_time; //timer to limit number of procs from focus effects int16 focusproclimit_procamt; //amount of procs that can be cast before timer limiter is set bool persistant_buff; bool client; //True if the caster is a client @@ -540,11 +542,15 @@ struct StatBonuses { int32 AC_Avoidance_Max_Percent; // Increase AC avoidance by percent int32 Damage_Taken_Position_Mod[2]; // base = percent melee damage reduction base2 0=back 1=front. [0]Back[1]Front int32 Melee_Damage_Position_Mod[2]; // base = percent melee damage increase base2 0=back 1=front. [0]Back[1]Front + int32 Damage_Taken_Position_Amt[2]; // base = flat amt melee damage reduction base2 0=back 1=front. [0]Back[1]Front + int32 Melee_Damage_Position_Amt[2]; // base = flat amt melee damage increase base2 0=back 1=front. [0]Back[1]Front int32 Double_Backstab_Front; // base = percent chance to double back stab front int32 DS_Mitigation_Amount; // base = flat amt DS mitigation. Negative value to reduce int32 DS_Mitigation_Percentage; // base = percent amt of DS mitigation. Negative value to reduce int32 Pet_Crit_Melee_Damage_Pct_Owner; // base = percent mod for pet critcal damage from owner int32 Pet_Add_Atk; // base = Pet ATK bonus from owner + int32 WeaponStance[WEAPON_STANCE_TYPE_MAX +1];// base = trigger spell id, base2 = 0 is 2h, 1 is shield, 2 is dual wield, [0]spid 2h, [1]spid shield, [2]spid DW + // AAs int32 ShieldDuration; // extends duration of /shield ability @@ -651,8 +657,8 @@ namespace SBIndex { constexpr uint16 ROOT_BUFFSLOT = 1; // SPA 99 constexpr uint16 RUNE_AMOUNT = 0; // SPA 55 constexpr uint16 RUNE_BUFFSLOT = 1; // SPA 78 - constexpr uint16 POSITIONAL_DAMAGE_MOD = 0; // SPA 503-506 - constexpr uint16 POSITIONAL_LOCATION = 1; // SPA 503-506 + constexpr uint16 POSITION_BACK = 0; // SPA 503-506 + constexpr uint16 POSITION_FRONT = 1; // SPA 503-506 constexpr uint16 PET_RAMPAGE_CHANCE = 0; // SPA 464,465 constexpr uint16 PET_RAMPAGE_DMG_MOD = 1; // SPA 465,465 constexpr uint16 SKILLPROC_CHANCE = 0; // SPA 427 @@ -678,6 +684,21 @@ typedef struct int level_override; } tProc; + +struct WeaponStance_Struct { + bool enabled; + bool spellbonus_enabled; + bool itembonus_enabled; + bool aabonus_enabled; + int spellbonus_buff_spell_id; + int itembonus_buff_spell_id; + int aabonus_buff_spell_id; +}; + +constexpr uint16 WEAPON_STANCE_TYPE_2H = 0; +constexpr uint16 WEAPON_STANCE_TYPE_SHIELD = 1; +constexpr uint16 WEAPON_STANCE_TYPE_DUAL_WIELD = 2; + typedef struct { uint16 increment; diff --git a/zone/inventory.cpp b/zone/inventory.cpp index 8e1b92448..20f1989fb 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -2026,6 +2026,7 @@ bool Client::SwapItem(MoveItem_Struct* move_in) { // Step 8: Re-calc stats CalcBonuses(); + ApplyWeaponsStance(); return true; } diff --git a/zone/mob.cpp b/zone/mob.cpp index d654f6d17..5ab8ad863 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -234,6 +234,7 @@ Mob::Mob( has_shieldequiped = false; has_twohandbluntequiped = false; has_twohanderequipped = false; + has_duelweaponsequiped = false; can_facestab = false; has_numhits = false; has_MGB = false; @@ -409,6 +410,14 @@ Mob::Mob( viral_spells[i] = 0; } + weaponstance.enabled = false; + weaponstance.spellbonus_enabled = false; //Set when bonus is applied + weaponstance.itembonus_enabled = false; //Set when bonus is applied + weaponstance.aabonus_enabled = false; //Controlled by function TogglePassiveAA + weaponstance.spellbonus_buff_spell_id = 0; + weaponstance.itembonus_buff_spell_id = 0; + weaponstance.aabonus_buff_spell_id = 0; + pStandingPetOrder = SPO_Follow; pseudo_rooted = false; @@ -3857,8 +3866,8 @@ int32 Mob::GetPositionalDmgTaken(Mob *attacker) int back_arc = 0; int total_mod = 0; - back_arc += itembonuses.Damage_Taken_Position_Mod[SBIndex::POSITIONAL_DAMAGE_MOD] + aabonuses.Damage_Taken_Position_Mod[SBIndex::POSITIONAL_DAMAGE_MOD] + spellbonuses.Damage_Taken_Position_Mod[SBIndex::POSITIONAL_DAMAGE_MOD]; - front_arc += itembonuses.Damage_Taken_Position_Mod[SBIndex::POSITIONAL_LOCATION] + aabonuses.Damage_Taken_Position_Mod[SBIndex::POSITIONAL_LOCATION] + spellbonuses.Damage_Taken_Position_Mod[SBIndex::POSITIONAL_LOCATION]; + back_arc += itembonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_BACK] + aabonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_BACK] + spellbonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_BACK]; + front_arc += itembonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_FRONT] + aabonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_FRONT] + spellbonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_FRONT]; if (back_arc || front_arc) { //Do they have this bonus? if (attacker->BehindMob(this, attacker->GetX(), attacker->GetY()))//Check if attacker is striking from behind @@ -3875,6 +3884,29 @@ int32 Mob::GetPositionalDmgTaken(Mob *attacker) return total_mod; } +int32 Mob::GetPositionalDmgTakenAmt(Mob *attacker) +{ + if (!attacker) + return 0; + + int front_arc = 0; + int back_arc = 0; + int total_amt = 0; + + back_arc += itembonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_BACK] + aabonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_BACK] + spellbonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_BACK]; + front_arc += itembonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_FRONT] + aabonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_FRONT] + spellbonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_FRONT]; + + if (back_arc || front_arc) { + if (attacker->BehindMob(this, attacker->GetX(), attacker->GetY())) + total_amt = back_arc; + else + total_amt = front_arc; + } + + return total_amt; +} + + int16 Mob::GetHealRate(uint16 spell_id, Mob* caster) { int16 heal_rate = 0; @@ -4851,8 +4883,8 @@ int16 Mob::GetMeleeDmgPositionMod(Mob* defender) int back_arc = 0; int total_mod = 0; - back_arc += itembonuses.Melee_Damage_Position_Mod[SBIndex::POSITIONAL_DAMAGE_MOD] + aabonuses.Melee_Damage_Position_Mod[SBIndex::POSITIONAL_DAMAGE_MOD] + spellbonuses.Melee_Damage_Position_Mod[SBIndex::POSITIONAL_DAMAGE_MOD]; - front_arc += itembonuses.Melee_Damage_Position_Mod[SBIndex::POSITIONAL_LOCATION] + aabonuses.Melee_Damage_Position_Mod[SBIndex::POSITIONAL_LOCATION] + spellbonuses.Melee_Damage_Position_Mod[SBIndex::POSITIONAL_LOCATION]; + back_arc += itembonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_BACK] + aabonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_BACK] + spellbonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_BACK]; + front_arc += itembonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_FRONT] + aabonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_FRONT] + spellbonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_FRONT]; if (back_arc || front_arc) { //Do they have this bonus? if (BehindMob(defender, GetX(), GetY()))//Check if attacker is striking from behind @@ -4891,6 +4923,30 @@ int16 Mob::GetSkillDmgAmt(uint16 skill) return skill_dmg; } +int16 Mob::GetPositionalDmgAmt(Mob* defender) +{ + if (!defender) + return 0; + + //SPA 504 + int front_arc_dmg_amt = 0; + int back_arc_dmg_amt = 0; + + int total_amt = 0; + + back_arc_dmg_amt += itembonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_BACK] + aabonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_BACK] + spellbonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_BACK]; + front_arc_dmg_amt += itembonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_FRONT] + aabonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_FRONT] + spellbonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_FRONT]; + + if (back_arc_dmg_amt || front_arc_dmg_amt) { + if (BehindMob(defender, GetX(), GetY())) + total_amt = back_arc_dmg_amt; + else + total_amt = front_arc_dmg_amt; + } + + return total_amt; +} + void Mob::MeleeLifeTap(int32 damage) { int32 lifetap_amt = 0; diff --git a/zone/mob.h b/zone/mob.h index 3202fd170..46be51d2c 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -432,6 +432,8 @@ public: inline void SetTwoHandBluntEquiped(bool val) { has_twohandbluntequiped = val; } bool HasTwoHanderEquipped() { return has_twohanderequipped; } void SetTwoHanderEquipped(bool val) { has_twohanderequipped = val; } + bool HasDualWeaponsEquiped() const { return has_duelweaponsequiped; } + inline void SetDuelWeaponsEquiped(bool val) { has_duelweaponsequiped = val; } bool CanFacestab() { return can_facestab; } void SetFacestab(bool val) { can_facestab = val; } virtual uint16 GetSkill(EQ::skills::SkillType skill_num) const { return 0; } @@ -804,6 +806,7 @@ public: int32 GetFocusIncoming(focusType type, int effect, Mob *caster, uint32 spell_id); int32 GetSkillDmgTaken(const EQ::skills::SkillType skill_used, ExtraAttackOptions *opts = nullptr); int32 GetPositionalDmgTaken(Mob *attacker); + int32 GetPositionalDmgTakenAmt(Mob *attacker); void DoKnockback(Mob *caster, uint32 pushback, uint32 pushup); int16 CalcResistChanceBonus(); int16 CalcFearResistChance(); @@ -821,6 +824,7 @@ public: int16 GetSkillReuseTime(uint16 skill); int GetCriticalChanceBonus(uint16 skill); int16 GetSkillDmgAmt(uint16 skill); + int16 GetPositionalDmgAmt(Mob* defender); bool TryReflectSpell(uint32 spell_id); inline bool CanBlockSpell() const { return(spellbonuses.FocusEffects[focusBlockNextSpell]); } bool DoHPToManaCovert(uint16 mana_cost = 0); @@ -1140,6 +1144,11 @@ public: inline int GetMaxShielderDistance() const { return shielder_max_distance; } inline void SetShielerMaxDistance(int val) { shielder_max_distance = val; } + WeaponStance_Struct weaponstance; + bool IsWeaponStanceEnabled() const { return weaponstance.enabled; } + inline void SetWeaponStanceEnabled(bool val) { weaponstance.enabled = val; } + + inline glm::vec4 GetCurrentWayPoint() const { return m_CurrentWayPoint; } inline float GetCWPP() const { return(static_cast(cur_wp_pause)); } inline int GetCWP() const { return(cur_wp); } @@ -1506,6 +1515,7 @@ protected: bool has_shieldequiped; bool has_twohandbluntequiped; bool has_twohanderequipped; + bool has_duelweaponsequiped; bool can_facestab; bool has_numhits; bool has_MGB; diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 5d633c5f8..c2dfa996a 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -2959,7 +2959,12 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove int shld_target_mitigation = spells[spell_id].base2[i] ? spells[spell_id].base2[i] : 50; int shlder_mitigation = spells[spell_id].max[i] ? spells[spell_id].base2[i] : 50; ShieldAbility(petowner->GetID(), 25, shield_duration, shld_target_mitigation, shlder_mitigation); + break; } + + case SE_Weapon_Stance: { + if (IsClient()) { + CastToClient()->ApplyWeaponsStance(); } break; } @@ -3218,6 +3223,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_Critical_Melee_Damage_Mod_Max: case SE_Melee_Damage_Position_Mod: case SE_Damage_Taken_Position_Mod: + case SE_Melee_Damage_Position_Amt: + case SE_Damage_Taken_Position_Amt: case SE_DS_Mitigation_Amount: case SE_DS_Mitigation_Percentage: case SE_Double_Backstab_Front: @@ -3242,6 +3249,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_AddExtraAttackPct_1h_Primary: case SE_AddExtraAttackPct_1h_Secondary: case SE_Skill_Base_Damage_Mod: + case SE_Buy_AA_Rank: { break; @@ -4372,6 +4380,17 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) } } + case SE_Weapon_Stance: + { + /* + If we click off the spell buff (or fades naturally) giving us + Weapon Stance effects it should remove all associated buff. + */ + if (weaponstance.spellbonus_buff_spell_id) { + BuffFadeBySpellID(weaponstance.spellbonus_buff_spell_id); + } + weaponstance.spellbonus_enabled = false; + } } } @@ -6598,7 +6617,7 @@ bool Mob::TryDivineSave() } } - SpellOnTarget(4789, this); //Touch of the Divine=4789, an Invulnerability/HoT/Purify effect + SpellOnTarget(SPELL_TOUCH_OF_THE_DIVINE, this); //Touch of the Divine=4789, an Invulnerability/HoT/Purify effect SendHPUpdate(); return true; }