[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 <akkadius1@gmail.com>
This commit is contained in:
KayenEQ 2021-08-15 23:59:10 -04:00 committed by GitHub
parent 9c62bf3c2f
commit d40d21121a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 363 additions and 191 deletions

View File

@ -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

View File

@ -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

View File

@ -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<float>(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

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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;
}

View File

@ -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();

View File

@ -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;

View File

@ -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<float>(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<uint32>(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<HealRotation>* heal_rotation)
{

View File

@ -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<float>(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<uint32, std::pair<uint32, uint32>> aa_ranks;
Timer aa_timers[aaTimerMax];
bool is_horse;
AuraMgr aura_mgr;

View File

@ -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<float>(GetSpecialAbilityParam(TETHER, 0));

View File

@ -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

View File

@ -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:

View File

@ -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.