From d40d21121a7471a2b68d6dca1ae4f213bd5b0d69 Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Sun, 15 Aug 2021 23:59:10 -0400 Subject: [PATCH] [Feature] Implemented /shield ability and related affects (#1494) * shield ability initial work * updates * update * updates * Update client_process.cpp * major updates optimized pet support perl support * updates * minor update * fix merge error * requested changes * variable fix * optimization * minor update * Revert "optimization" This reverts commit 27e11e758b23933ba8b6878d12d3eeb1e780aeda. * fix reset variables on shield_target if shielder dies or zones during shielding. * edge case fix Catch and fix situations where shield target doesn't have shielder variable cleared. Can occur if shielder . uses ability when target is not in combat then zones. * combined packet and mob function Shield now uses a common pathway through ShieldAbility, added parameters to perl function * Addressing formatting for Kayen * Fix function typo Co-authored-by: Akkadius --- common/ptimer.h | 2 + common/spdat.h | 8 +-- zone/attack.cpp | 56 +++++++++++++++-- zone/bonuses.cpp | 49 +++++++++++++-- zone/client.cpp | 13 ---- zone/client.h | 1 - zone/client_packet.cpp | 107 +++++++++++-------------------- zone/client_process.cpp | 30 +-------- zone/common.h | 13 ++-- zone/mob.cpp | 135 +++++++++++++++++++++++++++++++++++++--- zone/mob.h | 34 +++++++--- zone/mob_ai.cpp | 4 ++ zone/perl_mob.cpp | 86 +++++++++++++------------ zone/spell_effects.cpp | 14 ++++- zone/string_ids.h | 2 + 15 files changed, 363 insertions(+), 191 deletions(-) diff --git a/common/ptimer.h b/common/ptimer.h index 2232b55b5..349f20534 100644 --- a/common/ptimer.h +++ b/common/ptimer.h @@ -45,6 +45,8 @@ enum : int { //values for pTimerType pTimerLinkedSpellReuseStart = 28, pTimerLinkedSpellReuseEnd = 48, + pTimerShieldAbility = 86, + pTimerLayHands = 87, //these IDs are used by client too pTimerHarmTouch = 89, //so dont change them diff --git a/common/spdat.h b/common/spdat.h index 816d8a908..8c5726c53 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -573,7 +573,7 @@ typedef enum { #define SE_FleshToBone 207 // implemented //#define SE_PurgePoison 208 // not used #define SE_DispelBeneficial 209 // implemented -//#define SE_PetShield 210 // *not implemented +#define SE_PetShield 210 // implmented, @ShieldAbility, allows pet to 'shield' owner for 50 pct of damage taken for a duration, base: Time multiplier 1=12 seconds, 2=24 ect, limit: mitigation on pet owner override (not on live), max: mitigation on pet overide (not on live) #define SE_AEMelee 211 // implemented TO DO: Implement to allow NPC use (client only atm). #define SE_FrenziedDevastation 212 // implemented - increase spell criticals + all DD spells cast 2x mana. #define SE_PetMaxHP 213 // implemented[AA] - increases the maximum hit points of your pet @@ -593,7 +593,7 @@ typedef enum { #define SE_ReduceSkillTimer 227 // implemented #define SE_ReduceFallDamage 228 // implented - reduce the damage that you take from falling #define SE_PersistantCasting 229 // implemented -#define SE_ExtendedShielding 230 // not used as bonus - increase range of /shield ability +#define SE_ExtendedShielding 230 // implemented, @ShieldAbility, extends the range of your /shield ability by an amount of distance, base: distance units, limit: none, max: none #define SE_StunBashChance 231 // implemented - increase chance to stun from bash. #define SE_DivineSave 232 // implemented (base1 == % chance on death to insta-res) (base2 == spell cast on save) #define SE_Metabolism 233 // implemented - Modifies food/drink consumption rates. @@ -618,7 +618,7 @@ typedef enum { #define SE_FrontalBackstabChance 252 // implemented[AA] - chance to perform a full damage backstab from front. #define SE_FrontalBackstabMinDmg 253 // implemented[AA] - allow a frontal backstab for mininum damage. #define SE_Blank 254 // implemented -#define SE_ShieldDuration 255 // not implemented as bonus - increases duration of /shield +#define SE_ShieldDuration 255 // implemented, , @ShieldAbility, extends the duration of your /shield ability, base: seconds, limit: none, max: none #define SE_ShroudofStealth 256 // implemented #define SE_PetDiscipline 257 // not implemented as bonus - /pet hold - official name is GivePetHold #define SE_TripleBackstab 258 // implemented[AA] - chance to perform a triple backstab @@ -729,7 +729,7 @@ typedef enum { #define SE_BandolierSlots 363 // *not implemented[AA] 'Battle Ready' expands the bandolier by one additional save slot per rank. #define SE_TripleAttackChance 364 // implemented #define SE_ProcOnSpellKillShot 365 // implemented - chance to trigger a spell on kill when the kill is caused by a specific spell with this effect in it (10470 Venin) -#define SE_GroupShielding 366 // *not implemented[AA] This gives you /shieldgroup +//#define SE_GroupShielding 366 // *not implemented[AA] This gives you /shieldgroup #define SE_SetBodyType 367 // implemented - set body type of base1 so it can be affected by spells that are limited to that type (Plant, Animal, Undead, etc) //#define SE_FactionMod 368 // *not implemented - increases faction with base1 (faction id, live won't match up w/ ours) by base2 #define SE_CorruptionCounter 369 // implemented diff --git a/zone/attack.cpp b/zone/attack.cpp index 0a828fcb7..fac1301e5 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -1655,9 +1655,7 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, EQ::skills::Skill int exploss = 0; LogCombat("Fatal blow dealt by [{}] with [{}] damage, spell [{}], skill [{}]", killerMob ? killerMob->GetName() : "Unknown", damage, spell, attack_skill); - /* - #1: Send death packet to everyone - */ + // #1: Send death packet to everyone uint8 killed_level = GetLevel(); SendLogoutPackets(); @@ -1684,13 +1682,12 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, EQ::skills::Skill app.priority = 6; entity_list.QueueClients(this, &app); - /* - #2: figure out things that affect the player dying and mark them dead - */ + // #2: figure out things that affect the player dying and mark them dead InterruptSpell(); SetPet(0); SetHorseId(0); + ShieldAbilityClearVariables(); dead = true; if (GetMerc()) { @@ -2252,6 +2249,8 @@ bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, EQ::skills::SkillTy Log(Logs::Detail, Logs::Attack, "%s Mobs currently Aggro %i", __FUNCTION__, zone->MobsAggroCount()); } + ShieldAbilityClearVariables(); + SetHP(0); SetPet(0); @@ -5278,9 +5277,54 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttac 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); + hit.damage_done -= hit.damage_done * defender->GetShieldTargetMitigation() / 100; //Default shielded takes 50 pct damage + } + CheckNumHitsRemaining(NumHit::OutgoingHitSuccess); } +void Mob::DoShieldDamageOnShielder(Mob *shield_target, int hit_damage_done, EQ::skills::SkillType skillInUse) +{ + if (!shield_target) { + return; + } + + Mob *shielder = entity_list.GetMob(shield_target->GetShielderID()); + if (!shielder) { + shield_target->SetShielderID(0); + shield_target->SetShieldTargetMitigation(0); + return; + } + + if (shield_target->CalculateDistance(shielder->GetX(), shielder->GetY(), shielder->GetZ()) > static_cast(shielder->GetMaxShielderDistance())) { + shielder->SetShieldTargetID(0); + shielder->SetShielderMitigation(0); + shielder->SetShielderMaxDistance(0); + shielder->shield_timer.Disable(); + shield_target->SetShielderID(0); + shield_target->SetShieldTargetMitigation(0); + return; //Too far away, no message is given thoughh. + } + + int mitigation = shielder->GetShielderMitigation(); //Default shielder mitigates 25 pct of damage taken, this can be increased up to max 50 by equiping a shield item + if (shielder->IsClient() && shielder->HasShieldEquiped()) { + EQ::ItemInstance* inst = shielder->CastToClient()->GetInv().GetItem(EQ::invslot::slotSecondary); + if (inst) { + const EQ::ItemData* shield = inst->GetItem(); + if (shield && shield->ItemType == EQ::item::ItemTypeShield) { + mitigation += shield->AC * 50 / 100; //1% increase per 2 AC + std::min(50, mitigation);//50 pct max mitigation bonus from /shield + } + } + } + + hit_damage_done -= hit_damage_done * mitigation / 100; + shielder->Damage(this, hit_damage_done, SPELL_UNKNOWN, skillInUse, true, -1, false, m_specialattacks); + shielder->CheckNumHitsRemaining(NumHit::OutgoingHitSuccess); +} + void Mob::CommonBreakInvisibleFromCombat() { //break invis when you attack diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index 94660345f..cf1a0ccc0 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -1633,6 +1633,23 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) if (newbon->DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_CHANCE] < base1) { newbon->DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_CHANCE] = base1; newbon->DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_DMG_BONUS] = base2; + + } + break; + } + + case SE_ExtendedShielding: + { + if (newbon->ExtendedShielding < base1) { + newbon->ExtendedShielding = base1; + } + break; + } + + case SE_ShieldDuration: + { + if (newbon->ShieldDuration < base1) { + newbon->ShieldDuration = base1; } break; } @@ -1650,10 +1667,6 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; case SE_SecondaryForte: break; - case SE_ExtendedShielding: - break; - case SE_ShieldDuration: - break; case SE_ReduceApplyPoisonTime: break; case SE_NimbleEvasion: @@ -3567,6 +3580,34 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne new_bonus->Pet_Add_Atk += effect_value; break; + case SE_ExtendedShielding: + { + if (AdditiveWornBonus) { + 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){ + new_bonus->ExtendedShielding = effect_value; + } + break; + } + + case SE_ShieldDuration: + { + if (AdditiveWornBonus) { + 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){ + new_bonus->ShieldDuration = effect_value; + } + break; + } + case SE_Worn_Endurance_Regen_Cap: new_bonus->ItemEnduranceRegenCap += effect_value; break; diff --git a/zone/client.cpp b/zone/client.cpp index 98a3deec6..040601ad0 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -138,7 +138,6 @@ Client::Client(EQStreamInterface* ieqs) linkdead_timer(RuleI(Zone,ClientLinkdeadMS)), dead_timer(2000), global_channel_timer(1000), - shield_timer(500), fishing_timer(8000), endupkeep_timer(1000), forget_timer(0), @@ -200,7 +199,6 @@ Client::Client(EQStreamInterface* ieqs) account_id = 0; admin = 0; lsaccountid = 0; - shield_target = nullptr; guild_id = GUILD_NONE; guildrank = 0; GuildBanker = false; @@ -236,7 +234,6 @@ Client::Client(EQStreamInterface* ieqs) pQueuedSaveWorkID = 0; position_update_same_count = 0; fishing_timer.Disable(); - shield_timer.Disable(); dead_timer.Disable(); camp_timer.Disable(); autosave_timer.Disable(); @@ -420,16 +417,6 @@ Client::~Client() { } } - if (shield_target) { - for (int y = 0; y < 2; y++) { - if (shield_target->shielder[y].shielder_id == GetID()) { - shield_target->shielder[y].shielder_id = 0; - shield_target->shielder[y].shielder_bonus = 0; - } - } - shield_target = nullptr; - } - if(GetTarget()) GetTarget()->IsTargeted(-1); diff --git a/zone/client.h b/zone/client.h index 06f57c8dd..ff80cebc7 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1804,7 +1804,6 @@ private: Timer linkdead_timer; Timer dead_timer; Timer global_channel_timer; - Timer shield_timer; Timer fishing_timer; Timer endupkeep_timer; Timer forget_timer; // our 2 min everybody forgets you timer diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 08a442b85..50d3cd375 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -12806,87 +12806,52 @@ void Client::Handle_OP_SetTitle(const EQApplicationPacket *app) void Client::Handle_OP_Shielding(const EQApplicationPacket *app) { + /* + /shield command mechanics + Warriors get this skill at level 30 + Used by typing /shield while targeting a player + While active for the duration of 12 seconds baseline. The 'shield target' will take 50 pct less damage and + the 'shielder' will be hit with the damage taken by the 'shield target' after all applicable mitigiont is calculated, + the damage on the 'shielder' will be reduced by 25 percent, this reduction can be increased to 50 pct if equiping a shield. + You receive a 1% increase in mitigation for every 2 AC on the shield. + Shielder must stay with in a close distance (15 units) to your 'shield target'. If either move out of range, shield ends, no message given. + Both duration and shield range can be modified by AA. + Recast is 3 minutes. + + For custom use cases, Mob::ShieldAbility can be used in quests with all parameters being altered. This functional + is also used for SPA 201 SE_PetShield, which functions in a simalar manner with pet shielding owner. + + Note: If either the shielder or the shield target die all variables are reset on both. + + */ + if (app->size != sizeof(Shielding_Struct)) { LogError("OP size error: OP_Shielding expected:[{}] got:[{}]", sizeof(Shielding_Struct), app->size); return; } - if (GetClass() != WARRIOR) - { + + if (GetLevel() < 30) { //Client gives message + return; + } + + if (GetClass() != WARRIOR){ return; } - if (shield_target) - { - entity_list.MessageCloseString( - this, false, 100, 0, - END_SHIELDING, GetName(), shield_target->GetName()); - for (int y = 0; y < 2; y++) - { - if (shield_target->shielder[y].shielder_id == GetID()) - { - shield_target->shielder[y].shielder_id = 0; - shield_target->shielder[y].shielder_bonus = 0; - } - } + pTimerType timer = pTimerShieldAbility; + + if (!p_timers.Expired(&database, timer, false)) { + uint32 remain = p_timers.GetRemainingTime(timer); + Message(Chat::White, "You can use the ability /shield in %d minutes %d seconds.", ((remain) / 60), (remain % 60)); + return; } + Shielding_Struct* shield = (Shielding_Struct*)app->pBuffer; - shield_target = entity_list.GetMob(shield->target_id); - bool ack = false; - EQ::ItemInstance* inst = GetInv().GetItem(EQ::invslot::slotSecondary); - if (!shield_target) - return; - if (inst) - { - const EQ::ItemData* shield = inst->GetItem(); - if (shield && shield->ItemType == EQ::item::ItemTypeShield) - { - for (int x = 0; x < 2; x++) - { - if (shield_target->shielder[x].shielder_id == 0) - { - entity_list.MessageCloseString( - this, false, 100, 0, - START_SHIELDING, GetName(), shield_target->GetName()); - shield_target->shielder[x].shielder_id = GetID(); - int shieldbonus = shield->AC * 2; - switch (GetAA(197)) - { - case 1: - shieldbonus = shieldbonus * 115 / 100; - break; - case 2: - shieldbonus = shieldbonus * 125 / 100; - break; - case 3: - shieldbonus = shieldbonus * 150 / 100; - break; - } - shield_target->shielder[x].shielder_bonus = shieldbonus; - shield_timer.Start(); - ack = true; - break; - } - } - } - else - { - Message(0, "You must have a shield equipped to shield a target!"); - shield_target = 0; - return; - } - } - else - { - Message(0, "You must have a shield equipped to shield a target!"); - shield_target = 0; - return; - } - if (!ack) - { - MessageString(Chat::White, ALREADY_SHIELDED); - shield_target = 0; - return; + + if (ShieldAbility(shield->target_id, 15, 12000, 50, 25, true, false)) { + p_timers.Start(timer, SHIELD_ABILITY_RECAST_TIME); } + return; } diff --git a/zone/client_process.cpp b/zone/client_process.cpp index c984ee800..6106ee8ae 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -464,33 +464,9 @@ bool Client::Process() { if (gravity_timer.Check()) DoGravityEffect(); } - - if (shield_timer.Check()) - { - if (shield_target) - { - if (!CombatRange(shield_target)) - { - entity_list.MessageCloseString( - this, false, 100, 0, - END_SHIELDING, GetCleanName(), shield_target->GetCleanName()); - for (int y = 0; y < 2; y++) - { - if (shield_target->shielder[y].shielder_id == GetID()) - { - shield_target->shielder[y].shielder_id = 0; - shield_target->shielder[y].shielder_bonus = 0; - } - } - shield_target = 0; - shield_timer.Disable(); - } - } - else - { - shield_target = 0; - shield_timer.Disable(); - } + + if (shield_timer.Check()) { + ShieldAbilityFinish(); } SpellProcess(); diff --git a/zone/common.h b/zone/common.h index 5afde8019..4945a880c 100644 --- a/zone/common.h +++ b/zone/common.h @@ -22,8 +22,6 @@ #define SEE_POSITION 0.5f //ratio of GetSize() where NPCs try to see for LOS #define CHECK_LOS_STEP 1.0f -#define MAX_SHIELDERS 2 //I dont know if this is based on a client limit - #define ARCHETYPE_HYBRID 1 #define ARCHETYPE_CASTER 2 #define ARCHETYPE_MELEE 3 @@ -109,6 +107,8 @@ #define WEAPON_STANCE_TYPE_MAX 2 +#define SHIELD_ABILITY_RECAST_TIME 180 + typedef enum { //focus types focusSpellHaste = 1, //@Fc, SPA: 127, SE_IncreaseSpellHaste, On Caster, cast time mod pct, base: pct focusSpellDuration, //@Fc, SPA: 128, SE_IncreaseSpellDuration, On Caster, spell duration mod pct, base: pct @@ -553,8 +553,11 @@ struct StatBonuses { int32 ItemEnduranceRegenCap; // modify endurance regen cap 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% + int32 ShieldDuration; // extends duration of /shield ability + int32 ExtendedShielding; // extends range of /shield ability + int8 Packrat; // weight reduction for items, 1 point = 10% uint8 BuffSlotIncrease; // Increases number of available buff slots uint32 DelayDeath; // how far below 0 hp you can go int8 BaseMovementSpeed; // Adjust base run speed, does not stack with other movement bonuses. @@ -685,10 +688,6 @@ typedef struct int level_override; } tProc; -struct Shielders_Struct { - uint32 shielder_id; - uint16 shielder_bonus; -}; struct WeaponStance_Struct { bool enabled; diff --git a/zone/mob.cpp b/zone/mob.cpp index bf537e7ce..6d4dfc0e9 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -261,7 +261,6 @@ Mob::Mob( MR = CR = FR = DR = PR = Corrup = PhR = 0; ExtraHaste = 0; bEnraged = false; - shield_target = nullptr; current_mana = 0; max_mana = 0; hp_regen = in_hp_regen; @@ -376,11 +375,13 @@ Mob::Mob( silenced = false; amnesiad = false; inWater = false; - int m; - for (m = 0; m < MAX_SHIELDERS; m++) { - shielder[m].shielder_id = 0; - shielder[m].shielder_bonus = 0; - } + + shield_timer.Disable(); + m_shield_target_id = 0; + m_shielder_id = 0; + m_shield_target_mitigation = 0; + m_shielder_mitigation = 0; + m_shielder_max_distance = 0; destructibleobject = false; wandertype = 0; @@ -3144,7 +3145,7 @@ int32 Mob::GetActSpellCasttime(uint16 spell_id, int32 casttime) cast_reducer += cast_reducer_no_limit; casttime = casttime * (100 - cast_reducer) / 100; casttime -= cast_reducer_amt; - + return std::max(casttime, 0); } @@ -4944,11 +4945,11 @@ int16 Mob::GetPositionalDmgAmt(Mob* defender) if (back_arc_dmg_amt || front_arc_dmg_amt) { if (BehindMob(defender, GetX(), GetY())) - total_amt = back_arc_dmg_amt; + total_amt = back_arc_dmg_amt; else total_amt = front_arc_dmg_amt; } - + return total_amt; } @@ -6196,6 +6197,122 @@ float Mob::GetDefaultRaceSize() const { return GetRaceGenderDefaultHeight(race, gender); } +bool Mob::ShieldAbility(uint32 target_id, int shielder_max_distance, int shield_duration, int shield_target_mitigation, int shielder_mitigation, bool use_aa, bool can_shield_npc) +{ + Mob* shield_target = entity_list.GetMob(target_id); + if (!shield_target) { + return false; + } + + if (!can_shield_npc && shield_target->IsNPC()) { + if (IsClient()) { + MessageString(Chat::White, SHIELD_TARGET_NPC); + } + return false; + } + + if (shield_target->GetID() == GetID()) { //Client will give message "You can not shield yourself" + return false; + } + + //Edge case situations. If 'Shield Target' still has Shielder set but Shielder is not in zone. Catch and fix here. + if (shield_target->GetShielderID() && !entity_list.GetMob(shield_target->GetShielderID())) { + shield_target->SetShielderID(0); + } + + if (GetShielderID() && !entity_list.GetMob(GetShielderID())) { + SetShielderID(0); + } + + //You have a shielder, or your 'Shield Target' already has a 'Shielder' + if (GetShielderID() || shield_target->GetShielderID()) { + if (IsClient()) { + MessageString(Chat::White, ALREADY_SHIELDED); + } + return false; + } + + //You are being shielded or already have a 'Shield Target' + if (GetShieldTargetID() || shield_target->GetShieldTargetID()) { + if (IsClient()) { + MessageString(Chat::White, ALREADY_SHIELDING); + } + return false; + } + + //AA to increase SPA 230 extended shielding (default live is 15 distance units) + if (use_aa) { + shielder_max_distance += aabonuses.ExtendedShielding + itembonuses.ExtendedShielding + spellbonuses.ExtendedShielding; + shielder_max_distance = std::max(shielder_max_distance, 0); + } + + if (shield_target->CalculateDistance(GetX(), GetY(), GetZ()) > static_cast(shielder_max_distance)) { + return false; //Live does not give a message when out of range. + } + + entity_list.MessageCloseString(this, false, 100, 0, START_SHIELDING, GetCleanName(), shield_target->GetCleanName()); + + SetShieldTargetID(shield_target->GetID()); + SetShielderMitigation(shield_target_mitigation); + SetShielderMaxDistance(shielder_max_distance); + + shield_target->SetShielderID(GetID()); + shield_target->SetShieldTargetMitigation(shield_target_mitigation); + + //Calculate AA for adding time SPA 255 extend shield duration (Baseline ability is 12 seconds) + if (use_aa) { + shield_duration += (aabonuses.ShieldDuration + itembonuses.ShieldDuration + spellbonuses.ShieldDuration) * 1000; + shield_duration = std::max(shield_duration, 1); //Incase of negative modifiers lets just make min duration 1 ms. + } + + shield_timer.Start(static_cast(shield_duration)); + return true; +} + +void Mob::ShieldAbilityFinish() +{ + Mob* shield_target = entity_list.GetMob(GetShieldTargetID()); + + if (shield_target) { + entity_list.MessageCloseString(this, false, 100, 0, END_SHIELDING, GetCleanName(), shield_target->GetCleanName()); + shield_target->SetShielderID(0); + shield_target->SetShieldTargetMitigation(0); + } + SetShieldTargetID(0); + SetShielderMitigation(0); + SetShielderMaxDistance(0); + shield_timer.Disable(); +} + +void Mob::ShieldAbilityClearVariables() +{ + //If 'shield target' dies + if (GetShielderID()){ + Mob* shielder = entity_list.GetMob(GetShielderID()); + if (shielder) { + shielder->SetShieldTargetID(0); + shielder->SetShielderMitigation(0); + shielder->SetShielderMaxDistance(0); + shielder->shield_timer.Disable(); + } + SetShielderID(0); + SetShieldTargetMitigation(0); + } + + //If 'shielder' dies + if (GetShieldTargetID()) { + Mob* shield_target = entity_list.GetMob(GetShieldTargetID()); + if (shield_target) { + shield_target->SetShielderID(0); + shield_target->SetShieldTargetMitigation(0); + } + SetShieldTargetID(0); + SetShielderMitigation(0); + SetShielderMaxDistance(0); + shield_timer.Disable(); + } +} + #ifdef BOTS bool Mob::JoinHealRotationTargetPool(std::shared_ptr* heal_rotation) { diff --git a/zone/mob.h b/zone/mob.h index 2f0c166b0..a951d7cb3 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -843,11 +843,11 @@ public: bool HarmonySpellLevelCheck(int32 spell_id, Mob* target = nullptr); bool CanFocusUseRandomEffectivenessByType(focusType type); int GetFocusRandomEffectivenessValue(int focus_base, int focus_base2, bool best_focus = 0); - + bool TryDoubleMeleeRoundEffect(); bool GetUseDoubleMeleeRoundDmgBonus() const { return use_double_melee_round_dmg_bonus; } inline void SetUseDoubleMeleeRoundDmgBonus(bool val) { use_double_melee_round_dmg_bonus = val; } - + void CastSpellOnLand(Mob* caster, int32 spell_id); void FocusProcLimitProcess(); bool ApplyFocusProcLimiter(int32 spell_id, int buffslot = -1); @@ -1094,8 +1094,6 @@ public: void InstillDoubt(Mob *who); int16 GetResist(uint8 type) const; - Mob* GetShieldTarget() const { return shield_target; } - void SetShieldTarget(Mob* mob) { shield_target = mob; } bool HasActiveSong() const { return(bardsong != 0); } bool Charmed() const { return typeofpet == petCharmed; } static uint32 GetLevelHP(uint8 tlevel); @@ -1133,13 +1131,28 @@ public: bool IsMoved() { return moved; } void SetMoved(bool moveflag) { moved = moveflag; } - Shielders_Struct shielder[MAX_SHIELDERS]; Trade* trade; + bool ShieldAbility(uint32 target_id, int shielder_max_distance = 15, int shield_duration = 12000, int shield_target_mitigation = 50, int shielder_mitigation = 75, bool use_aa = false, bool can_shield_npc = true); + void DoShieldDamageOnShielder(Mob *shield_target, int hit_damage_done, EQ::skills::SkillType skillInUse); + void ShieldAbilityFinish(); + void ShieldAbilityClearVariables(); + inline uint32 GetShielderID() const { return m_shielder_id; } + inline void SetShielderID(uint32 val) { m_shielder_id = val; } + inline uint32 GetShieldTargetID() const { return m_shield_target_id; } + inline void SetShieldTargetID(uint32 val) { m_shield_target_id = val; } + inline int GetShieldTargetMitigation() const { return m_shield_target_mitigation; } + inline void SetShieldTargetMitigation(int val) { m_shield_target_mitigation = val; } + inline int GetShielderMitigation() const { return m_shielder_mitigation; } + inline void SetShielderMitigation(int val) { m_shielder_mitigation = val; } + inline int GetMaxShielderDistance() const { return m_shielder_max_distance; } + inline void SetShielderMaxDistance(int val) { m_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); } @@ -1439,6 +1452,13 @@ protected: Timer mana_timer; Timer focus_proc_limit_timer; + Timer shield_timer; + uint32 m_shield_target_id; + uint32 m_shielder_id; + int m_shield_target_mitigation; + int m_shielder_mitigation; + int m_shielder_max_distance; + //spell casting vars Timer spellend_timer; uint16 casting_spell_id; @@ -1485,8 +1505,6 @@ protected: uint8 aa_title; - Mob* shield_target; - int ExtraHaste; // for the #haste command bool mezzed; bool stunned; @@ -1626,7 +1644,7 @@ protected: std::unordered_map> aa_ranks; Timer aa_timers[aaTimerMax]; - + bool is_horse; AuraMgr aura_mgr; diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 09dd36df3..962d63f3d 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -1128,6 +1128,10 @@ void Mob::AI_Process() { if (focus_proc_limit_timer.Check()) FocusProcLimitProcess(); + if (shield_timer.Check()) { + ShieldAbilityFinish(); + } + auto npcSpawnPoint = CastToNPC()->GetSpawnPoint(); if (GetSpecialAbility(TETHER)) { float tether_range = static_cast(GetSpecialAbilityParam(TETHER, 0)); diff --git a/zone/perl_mob.cpp b/zone/perl_mob.cpp index 71fcb02a4..24a9ec3b9 100644 --- a/zone/perl_mob.cpp +++ b/zone/perl_mob.cpp @@ -4179,44 +4179,6 @@ XS(XS_Mob_GetResist) { XSRETURN(1); } -XS(XS_Mob_GetShieldTarget); /* prototype to pass -Wmissing-prototypes */ -XS(XS_Mob_GetShieldTarget) { - dXSARGS; - if (items != 1) - Perl_croak(aTHX_ "Usage: Mob::GetShieldTarget(THIS)"); // @categories Script Utility - { - Mob *THIS; - Mob *RETVAL; - VALIDATE_THIS_IS_MOB; - RETVAL = THIS->GetShieldTarget(); - ST(0) = sv_newmortal(); - sv_setref_pv(ST(0), "Mob", (void *) RETVAL); - } - XSRETURN(1); -} - -XS(XS_Mob_SetShieldTarget); /* prototype to pass -Wmissing-prototypes */ -XS(XS_Mob_SetShieldTarget) { - dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: Mob::SetShieldTarget(THIS, mob)"); // @categories Script Utility - { - Mob *THIS; - Mob *mob; - VALIDATE_THIS_IS_MOB; - if (sv_derived_from(ST(1), "Mob")) { - IV tmp = SvIV((SV *) SvRV(ST(1))); - mob = INT2PTR(Mob *, tmp); - } else - Perl_croak(aTHX_ "mob is not of type Mob"); - if (mob == nullptr) - Perl_croak(aTHX_ "mob is nullptr, avoiding crash."); - - THIS->SetShieldTarget(mob); - } - XSRETURN_EMPTY; -} - XS(XS_Mob_Charmed); /* prototype to pass -Wmissing-prototypes */ XS(XS_Mob_Charmed) { dXSARGS; @@ -6301,6 +6263,51 @@ XS(XS_Mob_AddNimbusEffect) { XSRETURN_EMPTY; } +XS(XS_Mob_ShieldAbility); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Mob_ShieldAbility) { + dXSARGS; + if (items < 2 || items > 6) + Perl_croak(aTHX_ "Usage: Mob::ShieldAbility(THIS, uint32 target_id, [int32 shielder__max_distance = 15], [int32 shield_duration = 12000], [int32 shield_target_mitigation= 50], [int32 shielder_mitigation = 50], [bool use_aa = false], bool [can_shield_npc = true]"); // @categories Spells and Disciplines + { + Mob *THIS; + uint32 target_id = (uint32)SvUV(ST(1)); + int32 shielder_max_distance = (int32)SvUV(ST(2)); + int32 shield_duration = (int32)SvUV(ST(3)); + int32 shield_target_mitigation = (int32)SvUV(ST(4)); + int32 shielder_mitigation = (int32)SvUV(ST(5)); + bool use_aa = (bool)SvTRUE(ST(6)); + bool can_shield_npc = (bool)SvTRUE(ST(7)); + + VALIDATE_THIS_IS_MOB; + if (items < 3) { + shielder_max_distance = 15; + } + + if (items < 4) { + shield_duration = 12000; + } + + if (items < 5) { + shield_target_mitigation = 50; + } + + if (items < 6) { + shielder_mitigation = 50; + } + + if (items < 7) { + use_aa = false; + } + + if (items < 8) { + can_shield_npc = true; + } + THIS->ShieldAbility(target_id, shielder_max_distance, shield_duration, shield_duration, shield_duration, use_aa, can_shield_npc); + + } + XSRETURN_EMPTY; +} + #ifdef BOTS XS(XS_Mob_CastToBot); /* prototype to pass -Wmissing-prototypes */ XS(XS_Mob_CastToBot) @@ -6561,8 +6568,6 @@ XS(boot_Mob) { newXSproto(strcpy(buf, "DontRootMeBefore"), XS_Mob_DontRootMeBefore, file, "$"); newXSproto(strcpy(buf, "DontSnareMeBefore"), XS_Mob_DontSnareMeBefore, file, "$"); newXSproto(strcpy(buf, "GetResist"), XS_Mob_GetResist, file, "$$"); - newXSproto(strcpy(buf, "GetShieldTarget"), XS_Mob_GetShieldTarget, file, "$"); - newXSproto(strcpy(buf, "SetShieldTarget"), XS_Mob_SetShieldTarget, file, "$$"); newXSproto(strcpy(buf, "Charmed"), XS_Mob_Charmed, file, "$"); newXSproto(strcpy(buf, "GetLevelHP"), XS_Mob_GetLevelHP, file, "$$"); newXSproto(strcpy(buf, "GetZoneID"), XS_Mob_GetZoneID, file, "$"); @@ -6672,6 +6677,7 @@ XS(boot_Mob) { newXSproto(strcpy(buf, "CanRaceEquipItem"), XS_Mob_CanRaceEquipItem, file, "$$"); newXSproto(strcpy(buf, "RemoveAllNimbusEffects"), XS_Mob_RemoveAllNimbusEffects, file, "$"); newXSproto(strcpy(buf, "AddNimbusEffect"), XS_Mob_AddNimbusEffect, file, "$$"); + newXSproto(strcpy(buf, "ShieldAbility"), XS_Mob_ShieldAbility, file, "$$$$$$$$"); #ifdef BOTS newXSproto(strcpy(buf, "CastToBot"), XS_Mob_CastToBot, file, "$"); #endif diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 5160e32ce..5db67a2b8 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -2950,6 +2950,19 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } + case SE_PetShield: { + if (IsPet()) { + Mob* petowner = GetOwner(); + if (petowner) { + int shield_duration = spells[spell_id].base[i] * 12 * 1000; + int shield_target_mitigation = spells[spell_id].base2[i] ? spells[spell_id].base2[i] : 50; + int shielder_mitigation = spells[spell_id].max[i] ? spells[spell_id].base2[i] : 50; + ShieldAbility(petowner->GetID(), 25, shield_duration, shield_target_mitigation, shielder_mitigation); + break; + } + } + } + case SE_Weapon_Stance: { if (IsClient()) { CastToClient()->ApplyWeaponsStance(); @@ -3170,7 +3183,6 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_LimitManaMax: case SE_DoubleRangedAttack: case SE_ShieldEquipDmgMod: - case SE_GroupShielding: case SE_TriggerOnReqTarget: case SE_LimitRace: case SE_FcLimitUse: diff --git a/zone/string_ids.h b/zone/string_ids.h index 25c62d6b2..eca1f23cf 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -287,7 +287,9 @@ #define SUSPEND_MINION_SUSPEND 3268 //%1 tells you, 'By your command, master.' #define ONLY_SUMMONED_PETS 3269 //3269 This effect only works with summoned pets. #define SUSPEND_MINION_FIGHTING 3270 //Your pet must be at peace, first. +#define SHIELD_TARGET_NPC 3278 //You must first target a living Player Character. #define ALREADY_SHIELDED 3279 //Either you or your target is already being shielded. +#define ALREADY_SHIELDING 3280 //Either you or your target is already shielding another. #define START_SHIELDING 3281 //%1 begins to use %2 as a living shield! #define END_SHIELDING 3282 //%1 ceases protecting %2. #define TRADESKILL_MISSING_ITEM 3455 //You are missing a %1.