From 51ad6d65dc0749d5a91844f254fd7d3d053fa772 Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Tue, 10 Aug 2021 15:46:37 -0400 Subject: [PATCH] [Spells] Implemented SPA 476 SE_Weapons_Stance and Live-like AA Enable/Disable Toggle (#1477) * Work started on SPA 476 defines * bonus structure add bonus structure set up * updates spa476 updates spa476 * spell bonus now functional spell bonus working well. * major update with debug messages aa, item and spell now working * Pre clean up, effect implemented working for AA, spells, items, all checked for stacking issues. * removed debug messages removed debug messages * spdat description added spdat description added * minor fix removed debug shout removed unneeded code check. * syntax updates, minor fixes syntax updates, minor fixes * syntax fixes syntax fixes * improvements to code moved function to check at swap item. Easier to manage and more live like behavior. Required minor adjustment Still working on AA toggle. * updates to aa buy, functionalish * Syntax / Formatting * Add break / default to switch * updates * completed v2 * Major revisions Main function check moved to when items are swapped and out of when ever bonus are recalculated. AA Toggle and data structure now more accurate to live. * Update aa.cpp * debug removed * implemented SE_Buy_AA_Rank Closer to live. * Update aa.cpp broadening AA toggle to be more general use. * improved various checks aa toggle is now broadly implemented to be usable with any passive effect. Co-authored-by: Akkadius --- common/spdat.cpp | 1 + common/spdat.h | 5 +- zone/aa.cpp | 208 ++++++++++++++++++++++++++++++++--- zone/bonuses.cpp | 61 ++++++++++- zone/client.cpp | 241 +++++++++++++++++++++++++++++++++++++++-- zone/client.h | 7 ++ zone/client_packet.cpp | 2 + zone/common.h | 20 +++- zone/inventory.cpp | 1 + zone/mob.cpp | 9 ++ zone/mob.h | 7 ++ zone/spell_effects.cpp | 21 +++- 12 files changed, 548 insertions(+), 35 deletions(-) diff --git a/common/spdat.cpp b/common/spdat.cpp index e5d85ba9e3..2b45a8300f 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 df059f358a..affa7bd8c8 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 diff --git a/zone/aa.cpp b/zone/aa.cpp index 0b19065bfe..fff2ae727a 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/bonuses.cpp b/zone/bonuses.cpp index 57ca16d6a9..33c229ba2a 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 @@ -1555,6 +1561,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) { @@ -1583,6 +1608,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } + // to do case SE_PetDiscipline: break; @@ -1603,6 +1629,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 @@ -3474,6 +3501,38 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne new_bonus->Pet_Add_Atk += 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; + } + //Special custom cases for loading effects on to NPC from 'npc_spels_effects' table if (IsAISpellEffect) { diff --git a/zone/client.cpp b/zone/client.cpp index 29ab42c9e2..98a3deec65 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -1412,7 +1412,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); @@ -2419,9 +2419,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 @@ -9966,7 +9966,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)); @@ -9975,7 +9975,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; @@ -10030,7 +10030,7 @@ std::vector Client::GetLearnableDisciplines(uint8 min_level, uint8 max_leve if (learnable) { learnable_disciplines.push_back(spell_id); } - } + } return learnable_disciplines; } @@ -10040,7 +10040,7 @@ std::vector Client::GetLearnedDisciplines() { if (IsValidSpell(m_pp.disciplines.values[index])) { learned_disciplines.push_back(m_pp.disciplines.values[index]); } - } + } return learned_disciplines; } @@ -10050,7 +10050,7 @@ std::vector Client::GetMemmedSpells() { if (IsValidSpell(m_pp.mem_spells[index])) { memmed_spells.push_back(m_pp.mem_spells[index]); } - } + } return memmed_spells; } @@ -10096,7 +10096,7 @@ std::vector Client::GetScribeableSpells(uint8 min_level, uint8 max_level) { if (scribeable) { scribeable_spells.push_back(spell_id); } - } + } return scribeable_spells; } @@ -10174,7 +10174,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); @@ -10221,7 +10221,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; @@ -10249,3 +10249,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 1da699d9b5..06f57c8ddf 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1554,6 +1554,13 @@ class Client : public Mob 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 41d66705ce..08a442b85a 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 51fd362258..e92ef309e9 100644 --- a/zone/common.h +++ b/zone/common.h @@ -106,6 +106,8 @@ #define PET_BUTTON_SPELLHOLD 9 #define AURA_HARDCAP 2 +#define WEAPON_STANCE_TYPE_MAX 2 + typedef enum { //focus types focusSpellHaste = 1, //@Fc, SPA: 127, SE_IncreaseSpellHaste, On Caster, cast time mod pct, base: pct @@ -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 @@ -545,7 +547,7 @@ struct StatBonuses { 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 int8 Packrat; //weight reduction for items, 1 point = 10% @@ -682,6 +684,20 @@ struct Shielders_Struct { uint16 shielder_bonus; }; +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 8e1b924487..20f1989fb4 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 aac0078a33..64333363bd 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; @@ -408,6 +409,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; diff --git a/zone/mob.h b/zone/mob.h index 970c117ae1..acabdce050 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -432,6 +432,8 @@ class Mob : public Entity { 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; } @@ -1128,6 +1130,10 @@ class Mob : public Entity { Shielders_Struct shielder[MAX_SHIELDERS]; Trade* trade; + 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); } @@ -1489,6 +1495,7 @@ class Mob : public Entity { 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 a463e29e77..fa2d1eca4b 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -2950,6 +2950,13 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } + case SE_Weapon_Stance: { + if (IsClient()) { + CastToClient()->ApplyWeaponsStance(); + } + break; + } + case SE_PersistentEffect: MakeAura(spell_id); break; @@ -3229,6 +3236,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; @@ -4359,6 +4367,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; + } } } @@ -6585,7 +6604,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; }