Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement Elapsed Time compensation to Attack Timer adjustements and DW adjustment #2410

Open
wants to merge 8 commits into
base: development
Choose a base branch
from
61 changes: 28 additions & 33 deletions src/game/Objects/Unit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ Unit::Unit()
m_createResistance = 0;

m_attacking = nullptr;
m_openerAttack = true;
m_modSpellHitChance = 0.0f;
m_baseSpellCritChance = 5;

Expand Down Expand Up @@ -291,14 +292,17 @@ void Unit::Update(uint32 update_diff, uint32 p_time)
ExtraAttacksLocked(false);
}

if (uint32 base_att = GetAttackTimer(BASE_ATTACK))
SetAttackTimer(BASE_ATTACK, (update_diff >= base_att ? 0 : base_att - update_diff));
if (int32 base_att = GetAttackTimer(BASE_ATTACK))
if (base_att > 0)
SetAttackTimer(BASE_ATTACK, base_att - update_diff);

if (uint32 base_att = GetAttackTimer(OFF_ATTACK))
SetAttackTimer(OFF_ATTACK, (update_diff >= base_att ? 0 : base_att - update_diff));
if (int32 off_att = GetAttackTimer(OFF_ATTACK))
if (off_att > 0)
SetAttackTimer(OFF_ATTACK, off_att - update_diff);

if (uint32 ranged_att = GetAttackTimer(RANGED_ATTACK))
SetAttackTimer(RANGED_ATTACK, (update_diff >= ranged_att ? 0 : ranged_att - update_diff));
if (int32 ranged_att = GetAttackTimer(RANGED_ATTACK))
if (ranged_att > 0)
SetAttackTimer(RANGED_ATTACK, ranged_att - update_diff);

if (IsAlive())
ModifyAuraState(AURA_STATE_HEALTHLESS_20_PERCENT, GetHealth() < GetMaxHealth() * 0.20f);
Expand Down Expand Up @@ -379,24 +383,17 @@ bool Unit::UpdateMeleeAttackingState()

if (IsAttackReady(BASE_ATTACK))
{
// prevent base and off attack in same time, delay attack at 0.2 sec
if (HaveOffhandWeapon())
{
if (GetAttackTimer(OFF_ATTACK) < ATTACK_DISPLAY_DELAY)
SetAttackTimer(OFF_ATTACK, ATTACK_DISPLAY_DELAY);
}
AttackerStateUpdate(pVictim, BASE_ATTACK);
ResetAttackTimer(BASE_ATTACK);
ResetAttackTimer(BASE_ATTACK, !m_openerAttack);
if (m_openerAttack && HaveOffhandWeapon() && GetAttackTimer(OFF_ATTACK) < ATTACK_DISPLAY_DELAY)
SetAttackTimer(OFF_ATTACK, ATTACK_DISPLAY_DELAY);
m_openerAttack = false;
}
if (HaveOffhandWeapon() && IsAttackReady(OFF_ATTACK))
else if (HaveOffhandWeapon() && IsAttackReady(OFF_ATTACK))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this change cause off hand attack to never go off when server has high update time and main hand attack speed is lower than it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it would in the case where the server update time is longer than mainhand attack speed. It probably shouldn't happen, but I have no retail wow videos of a DW unit attacking when the server is that laggy.

Note though "DW never hits if the server update time is longer than MH attack speed" is already true for how vmangos is before this PR, because of this:

                if (HaveOffhandWeapon())
                {
                    if (GetAttackTimer(OFF_ATTACK) < ATTACK_DISPLAY_DELAY)
                        SetAttackTimer(OFF_ATTACK, ATTACK_DISPLAY_DELAY);
                }

Making every world update when its very high update time, the mainhand lands and delays the offhand by 200 ms so it's no longer ready within that same update, then the next update happens and, the MH is ready so it attacks with it and delays the offhand by 200ms again, and so on making the offhand never hit.

I'm not sure what the retail wow would do in this situation. I don't have enough data to conclude if an offhand can hit in the same world update as a mainhand. People in classic discords describe a situation where if offhand and mainhand hit very close then the warriors with the talent flurry only lose one charge of the buff. Is that a spellbatching thing or were melee attacks batched as well in classic era, I don't know.

{
// prevent base and off attack in same time, delay attack at 0.2 sec
uint32 base_att = GetAttackTimer(BASE_ATTACK);
if (base_att < ATTACK_DISPLAY_DELAY)
SetAttackTimer(BASE_ATTACK, ATTACK_DISPLAY_DELAY);
// do attack
AttackerStateUpdate(pVictim, OFF_ATTACK);
ResetAttackTimer(OFF_ATTACK);
ResetAttackTimer(OFF_ATTACK, !m_openerAttack);
m_openerAttack = false;
}
break;
}
Expand Down Expand Up @@ -473,9 +470,9 @@ void Unit::SendMovementPacket(uint16 opcode, bool includingSelf)
SendMovementMessageToSet(std::move(data), includingSelf);
}

void Unit::ResetAttackTimer(WeaponAttackType type)
void Unit::ResetAttackTimer(WeaponAttackType type, bool compensateDiff/*= false*/)
{
m_attackTimer[type] = uint32(GetAttackTime(type) * m_modAttackSpeedPct[type]);
m_attackTimer[type] = uint32(GetAttackTime(type) * m_modAttackSpeedPct[type]) + ((compensateDiff) ? std::min(m_attackTimer[type], 0) : 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to add a separate if statement below instead of doing it like that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I try to do it like this,. m_attackTimer was already set to the attackspeed in the line above it, so this will always add 0

m_attackTimer[type] = uint32(GetAttackTime(type) * m_modAttackSpeedPct[type])
if  (compensateDiff) 
    m_attackTimer[type] += std::min(m_attackTimer[type], 0)
// m_attackTimer was already set to the attack speed above, so this will always add min(m_attackTimer, 0) = 0

So I can first save it then add the saved value after setting to 0 like this:

int32 savedAttackTimer = std::min(m_attackTimer[type], 0)
m_attackTimer[type] = uint32(GetAttackTime(type) * m_modAttackSpeedPct[type])
if  (compensateDiff) 
    m_attackTimer[type] += savedAttackTimer 

Is this method better?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about

if (compensateDiff && m_attackTimer[type] < 0)
    m_attackTimer[type] += int32(GetAttackTime(type) * m_modAttackSpeedPct[type])
else
    m_attackTimer[type] = int32(GetAttackTime(type) * m_modAttackSpeedPct[type]);

}

void Unit::RemoveSpellsCausingAura(AuraType auraType, AuraRemoveMode mode)
Expand Down Expand Up @@ -1599,23 +1596,23 @@ void Unit::DealMeleeDamage(CalcDamageInfo* damageInfo, bool durabilityLoss)
float percent20 = pVictim->GetAttackTime(OFF_ATTACK) * 0.20f;
float percent60 = 3.0f * percent20;
if (offtime > percent20 && offtime <= percent60)
pVictim->SetAttackTimer(OFF_ATTACK, uint32(percent20));
pVictim->SetAttackTimer(OFF_ATTACK, int32(percent20));
else if (offtime > percent60)
{
offtime -= 2.0f * percent20;
pVictim->SetAttackTimer(OFF_ATTACK, uint32(offtime));
pVictim->SetAttackTimer(OFF_ATTACK, int32(offtime));
}
}
else
{
float percent20 = pVictim->GetAttackTime(BASE_ATTACK) * 0.20f;
float percent60 = 3.0f * percent20;
if (basetime > percent20 && basetime <= percent60)
pVictim->SetAttackTimer(BASE_ATTACK, uint32(percent20));
pVictim->SetAttackTimer(BASE_ATTACK, int32(percent20));
else if (basetime > percent60)
{
basetime -= 2.0f * percent20;
pVictim->SetAttackTimer(BASE_ATTACK, uint32(basetime));
pVictim->SetAttackTimer(BASE_ATTACK, int32(basetime));
}
}
}
Expand Down Expand Up @@ -2866,7 +2863,7 @@ void Unit::_UpdateAutoRepeatSpell()
spell->prepare(m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_targets);

// all went good, reset attack
ResetAttackTimer(RANGED_ATTACK);
ResetAttackTimer(RANGED_ATTACK, true); //Compensate Diff always because opener Attack delay is handled by m_AutoRepeatFirstCast
SetStandState(UNIT_STAND_STATE_STAND);
}
}
Expand Down Expand Up @@ -4639,13 +4636,11 @@ bool Unit::Attack(Unit* victim, bool meleeAttack)
pGuardian->AI()->OwnerAttacked(victim);
}

// delay offhand weapon attack to next attack time
if (HaveOffhandWeapon())
ResetAttackTimer(OFF_ATTACK);

if (meleeAttack)
{
m_openerAttack = true;
SendMeleeAttackStart(victim);

}
return true;
}

Expand Down Expand Up @@ -9274,7 +9269,7 @@ bool Unit::IsPolymorphed() const

bool Unit::IsAttackReady(WeaponAttackType type) const
{
return m_attackTimer[type] == 0;
return m_attackTimer[type] <= 0;
}

void Unit::SetDisplayId(uint32 displayId)
Expand Down
10 changes: 6 additions & 4 deletions src/game/Objects/Unit.h
Original file line number Diff line number Diff line change
Expand Up @@ -908,9 +908,10 @@ class Unit : public SpellCaster
HostileRefManager m_HostileRefManager; // Manage all Units that are threatened by us
std::vector<ObjectGuid> m_tauntGuids;
protected:
uint32 m_attackTimer[MAX_ATTACK];
int32 m_attackTimer[MAX_ATTACK];
AttackerSet m_attackers;
Unit* m_attacking;
bool m_openerAttack; // The unit's first attack against an enemy.
uint32 m_reactiveTimer[MAX_REACTIVE];
ObjectGuid m_reactiveTarget[MAX_REACTIVE];
typedef std::map<ObjectGuid /*attackerGuid*/, uint32 /*damage*/ > DamageTakenHistoryMap;
Expand All @@ -922,19 +923,20 @@ class Unit : public SpellCaster
* @param type The type of weapon that we want to update the time for
* @param time the remaining time until we can attack with the WeaponAttackType again
*/
void SetAttackTimer(WeaponAttackType type, uint32 time) { m_attackTimer[type] = time; }
void SetAttackTimer(WeaponAttackType type, int32 time) { m_attackTimer[type] = time; }
/**
* Resets the attack timer to the base value decided by Unit::m_modAttackSpeedPct and
* Unit::GetAttackTime
* @param type The weapon attack type to reset the attack timer for.
* @param compensateDiff Deduct the diff in update tick from the reseted timer.
*/
void ResetAttackTimer(WeaponAttackType type = BASE_ATTACK);
void ResetAttackTimer(WeaponAttackType type = BASE_ATTACK, bool compensateDiff = false);
/**
* Get's the remaining time until we can do an attack
* @param type The weapon type to check the remaining time for
* @return The remaining time until we can attack with this weapon type.
*/
uint32 GetAttackTimer(WeaponAttackType type) const { return m_attackTimer[type]; }
int32 GetAttackTimer(WeaponAttackType type) const { return m_attackTimer[type]; }
/**
* Checks whether the unit can do an attack. Does this by checking the attacktimer for the
* WeaponAttackType, can probably be thought of as a cooldown for each swing/shot
Expand Down
Loading