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 2a524ca8c..3bf1e2728 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -572,7 +572,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, @Shiedling, allows pet to 'shield' owner for 50 pct of damage taken for a duration, base: 1 (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 diff --git a/zone/attack.cpp b/zone/attack.cpp index aeb82d07b..ef0a7905f 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -1691,6 +1691,7 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, EQ::skills::Skill InterruptSpell(); SetPet(0); SetHorseId(0); + ShieldAbilityClearVariables(); dead = true; if (GetMerc()) { @@ -2252,6 +2253,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,61 +5281,53 @@ 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)); - Shout("Shielder ID [%i]", defender->GetShielderID()); - if (defender->GetShielderID()) { - hit.damage_done = hit.damage_done * 50 / 100;//Don't round. 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* shielder_target, int hit_damage_done, EQ::skills::SkillType skillInUse) +void Mob::DoShieldDamageOnShielder(Mob* shield_target, int hit_damage_done, EQ::skills::SkillType skillInUse) { - if (!shielder_target) { + if (!shield_target) { return; } - Mob *shielder = entity_list.GetMob(shielder_target->GetShielderID()); + Mob *shielder = entity_list.GetMob(shield_target->GetShielderID()); if (!shielder) { return; } - //AA to increase SPA 230 extended shielding - int max_shielder_distance = 15; - int distance_mod = aabonuses.ExtendedShielding + itembonuses.ExtendedShielding + spellbonuses.ExtendedShielding; - max_shielder_distance += max_shielder_distance * distance_mod / 100; - max_shielder_distance = std::max(max_shielder_distance, 0); - - if (shielder_target->CalculateDistance(shielder->GetX(), shielder->GetY(), shielder->GetZ()) > static_cast(max_shielder_distance)) { - //Clear variables Shield end - Shout("Clear variables shield end"); + if (shield_target->CalculateDistance(shielder->GetX(), shielder->GetY(), shielder->GetZ()) > static_cast(shielder->GetMaxShielderDistance())) { shielder->SetShieldTargetID(0); - shielder_target->SetShielderID(0); + shielder->SetShielderMitigation(0); + shielder->SetShielerMaxDistance(0); + shielder->shield_timer.Disable(); + shield_target->SetShielderID(0); + shield_target->SetShieldTargetMitigation(0); return; //Too far away, no message is given thoughh. } - int mitigation = 75; + 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->HasShieldEquiped() && shielder->IsClient()) { + if (shielder->IsClient() && shielder->HasShieldEquiped()) { - EQ::ItemInstance* inst = CastToClient()->GetInv().GetItem(EQ::invslot::slotSecondary); + 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 * 0.50; //1% increase per 2 AC + mitigation += shield->AC * 50 / 100; //1% increase per 2 AC + std::min(50, mitigation);//50 pct max mitigation bonus from /shield } } } - - mitigation = std::max(mitigation, 50); - - hit_damage_done = hit_damage_done * mitigation / 100; + 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); } diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index a7ab97c2a..be8895b5d 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -12804,14 +12804,30 @@ 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; } - //TODO: Defensive makes it not cast? - //TODO: Bankers ect don't let you shjield 6826 You can not perform shielding while you are speaking with a banker, a merchant, or a guildmaster. - if (GetLevel() < 30) { //Client gives message return; } @@ -12820,20 +12836,24 @@ void Client::Handle_OP_Shielding(const EQApplicationPacket *app) return; } + 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; Mob* shield_target = entity_list.GetMob(shield->target_id); - Shout("PACKET Shielder %i", shield_target->GetShielderID()); - Shout("PACKET Shield Target %i", GetShieldTargetID()); - if (!shield_target) { return; } - if (shield_target->IsNPC()) { - //You must first target a living player //TODO Find string + MessageString(Chat::White, SHIELD_TARGET_NPC); return; } @@ -12841,48 +12861,43 @@ void Client::Handle_OP_Shielding(const EQApplicationPacket *app) return; } - //Does 'Shield Target' already have a 'Shielder' - if (shield_target->GetShielderID() == GetID()) { + //You are a 'Shield Target' already have a 'Shielder' + if (GetShielderID() || shield_target->GetShielderID()) { MessageString(Chat::White, ALREADY_SHIELDED); return; } - //Does 'Shielder' already have a 'Shield Target' - if (GetShieldTargetID() == GetID()) { - MessageString(Chat::White, ALREADY_SHIELDED); + //You are being shielded or already have a 'Shield Target' + if (GetShieldTargetID() || shield_target->GetShieldTargetID()) { + MessageString(Chat::White, ALREADY_SHIELDING); return; } - //AA to increase SPA 230 extended shielding - - int max_shielder_distance = 15; - int distance_mod = aabonuses.ExtendedShielding + itembonuses.ExtendedShielding + spellbonuses.ExtendedShielding; - max_shielder_distance += max_shielder_distance * distance_mod / 100; - max_shielder_distance = std::max(max_shielder_distance, 0); - if (shield_target->CalculateDistance(GetX(), GetY(), GetZ()) > static_cast(max_shielder_distance)) { + //AA to increase SPA 230 extended shielding + int max_shlder_distance = 15; + max_shlder_distance += aabonuses.ExtendedShielding + itembonuses.ExtendedShielding + spellbonuses.ExtendedShielding; + max_shlder_distance = std::max(max_shlder_distance, 0); + + if (shield_target->CalculateDistance(GetX(), GetY(), GetZ()) > static_cast(max_shlder_distance)) { return; //Too far away, no message is given thoughh. } entity_list.MessageCloseString(this, false, 100, 0, START_SHIELDING, GetName(), shield_target->GetName()); - //Apply to Shielder - //shield_ability.shield_target_id = shield_target->GetID(); - - //Apply to Shield Target - //shield_target->shield_ability.shielder_id = GetID(); - SetShieldTargetID(shield_target->GetID()); + SetShielderMitigation(25); + SetShielerMaxDistance(max_shlder_distance); + shield_target->SetShielderID(GetID()); - - + shield_target->SetShieldTargetMitigation(50); + //Calculate AA for adding time SPA 255 extend shield duration int shield_duration = 12000; - Shout("1 Duration %i", shield_duration); shield_duration += (aabonuses.ShieldDuration + itembonuses.ShieldDuration + spellbonuses.ShieldDuration) * 1000; - Shout("2 Duration %i", shield_duration); shield_duration = std::max(shield_duration, 1); //Incase of negative modifiers lets just make min duration 1 ms. - Shout("3 Duration %i", shield_duration); - shield_timer.Start(shield_duration); + shield_timer.Start(static_cast(shield_duration)); + + p_timers.Start(timer, SHIELD_ABILITY_RECAST_TIME); return; } diff --git a/zone/client_process.cpp b/zone/client_process.cpp index fae7f83e1..e7b551341 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -461,14 +461,7 @@ bool Client::Process() { } if (shield_timer.Check()) { - Mob * shield_target = entity_list.GetMob(GetShieldTargetID()); - - if (shield_target) { - entity_list.MessageCloseString(this, false, 100, 0, END_SHIELDING, GetCleanName(), shield_target->GetCleanName()); - } - shield_timer.Disable(); - SetShieldTargetID(0); - shield_target->SetShielderID(0); + ShieldAbilityFinish(); } SpellProcess(); diff --git a/zone/common.h b/zone/common.h index 5b062e1f6..f0d7a6959 100644 --- a/zone/common.h +++ b/zone/common.h @@ -105,6 +105,8 @@ #define AURA_HARDCAP 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 @@ -544,7 +546,6 @@ struct StatBonuses { 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 - // AAs int32 ShieldDuration; // extends duration of /shield ability int32 ExtendedShielding; // extends range of /shield ability diff --git a/zone/mob.cpp b/zone/mob.cpp index ed750eebb..d654f6d17 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -378,6 +378,9 @@ Mob::Mob( shield_timer.Disable(); shield_target_id = 0; shielder_id = 0; + shield_target_mitigation = 0; + shielder_mitigation = 0; + shielder_max_distance = 0; destructibleobject = false; wandertype = 0; @@ -6118,6 +6121,104 @@ float Mob::GetDefaultRaceSize() const { return GetRaceGenderDefaultHeight(race, gender); } +void Mob::ShieldAbility(uint32 target_id, int max_shlder_distance, int shield_duration, int shld_target_mitigation, int shlder_mitigation) +{ + + Mob* shield_target = entity_list.GetMob(target_id); + + if (!shield_target) { + return; + } + + if (shield_target->GetID() == GetID()) { + return; + } + + //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; + } + + //You are being shielded or already have a 'Shield Target' + if (GetShieldTargetID() || shield_target->GetShieldTargetID()) { + + if (IsClient()) { + MessageString(Chat::White, ALREADY_SHIELDING); + return; + } + } + + if (shield_target->CalculateDistance(GetX(), GetY(), GetZ()) > static_cast(max_shlder_distance)) { + if (IsClient()) { + MessageString(Chat::White, TARGET_TOO_FAR); //Live doesn't give any message for failure, for the quest ability lets allow it. + } + return; + } + + entity_list.MessageCloseString(this, false, 100, 0, START_SHIELDING, GetCleanName(), shield_target->GetCleanName()); + + SetShieldTargetID(shield_target->GetID()); + SetShielderMitigation(shlder_mitigation); + SetShielerMaxDistance(max_shlder_distance); + + shield_target->SetShielderID(GetID()); + shield_target->SetShieldTargetMitigation(shld_target_mitigation); + + shield_timer.Start(shield_duration); +} + +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); + SetShielerMaxDistance(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->SetShielerMaxDistance(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); + SetShielerMaxDistance(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 40b19467c..3202fd170 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -1125,11 +1125,20 @@ public: Trade* trade; + void ShieldAbility(uint32 target_id, int max_shlder_distance = 15, int shield_duration = 12000, int shld_target_mitigation = 50, int shlder_mitigation = 75); void DoShieldDamageOnShielder(Mob* shield_target, int hit_damage_done, EQ::skills::SkillType skillInUse); - inline int GetShielderID() const { return shielder_id; } - inline void SetShielderID(int ent_id) { shielder_id = ent_id; } - inline int GetShieldTargetID() const { return shield_target_id; } - inline void SetShieldTargetID(int ent_id) { shield_target_id = ent_id; } + void ShieldAbilityFinish(); + void ShieldAbilityClearVariables(); + inline uint32 GetShielderID() const { return shielder_id; } + inline void SetShielderID(uint32 val) { shielder_id = val; } + inline uint32 GetShieldTargetID() const { return shield_target_id; } + inline void SetShieldTargetID(uint32 val) { shield_target_id = val; } + inline int GetShieldTargetMitigation() const { return shield_target_mitigation; } + inline void SetShieldTargetMitigation(int val) { shield_target_mitigation = val; } + inline int GetShielderMitigation() const { return shielder_mitigation; } + inline void SetShielderMitigation(int val) { shielder_mitigation = val; } + inline int GetMaxShielderDistance() const { return shielder_max_distance; } + inline void SetShielerMaxDistance(int val) { shielder_max_distance = val; } inline glm::vec4 GetCurrentWayPoint() const { return m_CurrentWayPoint; } inline float GetCWPP() const { return(static_cast(cur_wp_pause)); } @@ -1433,6 +1442,9 @@ protected: Timer shield_timer; uint32 shield_target_id; uint32 shielder_id; + int shield_target_mitigation; + int shielder_mitigation; + int shielder_max_distance; //spell casting vars Timer spellend_timer; 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 386ea3115..5ed7a30e1 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,50 @@ 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 max_shielder_distance, int32 shield_duration [ms]"); // @categories Spells and Disciplines + { + Mob *THIS; + uint32 target_id = (uint32)SvUV(ST(1)); + int32 max_shlder_distance; + int32 shld_duration; + int32 shld_target_mitigation; + int32 shlder_mitigation; + + VALIDATE_THIS_IS_MOB; + if (items < 3) + max_shlder_distance = 15; + else { + max_shlder_distance = max_shlder_distance = (int32)SvUV(ST(2)); + } + + if (items < 4) + shld_duration = 12000; + else { + shld_duration = (int32)SvUV(ST(3)); + } + + if (items < 5) + shld_target_mitigation = 50; + else { + shld_target_mitigation = (int32)SvUV(ST(4)); + } + + if (items < 6) + shlder_mitigation = 50; + else { + shlder_mitigation = (int32)SvUV(ST(5)); + } + + THIS->ShieldAbility(target_id, max_shlder_distance, shld_duration, shld_target_mitigation, shlder_mitigation); + + } + XSRETURN_EMPTY; +} + #ifdef BOTS XS(XS_Mob_CastToBot); /* prototype to pass -Wmissing-prototypes */ XS(XS_Mob_CastToBot) @@ -6561,8 +6567,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 +6676,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 a463e29e7..7230ad480 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -2950,6 +2950,20 @@ 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 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_PersistentEffect: MakeAura(spell_id); break; 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.