mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 21:01:29 +00:00
[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:
parent
9c62bf3c2f
commit
d40d21121a
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
|
||||
135
zone/mob.cpp
135
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<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)
|
||||
{
|
||||
|
||||
34
zone/mob.h
34
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<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;
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user