diff --git a/zone/attack.cpp b/zone/attack.cpp index 5abba347b..e4aa80225 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -4803,3 +4803,145 @@ void Mob::CommonBreakInvisible() hidden = false; improved_hidden = false; } + +void Mob::SetAttackTimer() +{ + attack_timer.SetAtTrigger(4000, true); +} + +void Client::SetAttackTimer() +{ + float PermaHaste = GetPermaHaste() * 100.0f; + + //default value for attack timer in case they have + //an invalid weapon equipped: + attack_timer.SetAtTrigger(4000, true); + + Timer *TimerToUse = nullptr; + const Item_Struct *PrimaryWeapon = nullptr; + + for (int i = MainRange; i <= MainSecondary; i++) { + //pick a timer + if (i == MainPrimary) + TimerToUse = &attack_timer; + else if (i == MainRange) + TimerToUse = &ranged_timer; + else if (i == MainSecondary) + TimerToUse = &attack_dw_timer; + else //invalid slot (hands will always hit this) + continue; + + const Item_Struct *ItemToUse = nullptr; + + //find our item + ItemInst *ci = GetInv().GetItem(i); + if (ci) + ItemToUse = ci->GetItem(); + + //special offhand stuff + if (i == MainSecondary) { + //if we have a 2H weapon in our main hand, no dual + if (PrimaryWeapon != nullptr) { + if (PrimaryWeapon->ItemClass == ItemClassCommon + && (PrimaryWeapon->ItemType == ItemType2HSlash + || PrimaryWeapon->ItemType == ItemType2HBlunt + || PrimaryWeapon->ItemType == ItemType2HPiercing)) { + attack_dw_timer.Disable(); + continue; + } + } + + //if we cant dual wield, skip it + if (!CanThisClassDualWield()) { + attack_dw_timer.Disable(); + continue; + } + } + + //see if we have a valid weapon + if (ItemToUse != nullptr) { + //check type and damage/delay + if (ItemToUse->ItemClass != ItemClassCommon + || ItemToUse->Damage == 0 + || ItemToUse->Delay == 0) { + //no weapon + ItemToUse = nullptr; + } + // Check to see if skill is valid + else if ((ItemToUse->ItemType > ItemTypeLargeThrowing) && + (ItemToUse->ItemType != ItemTypeMartial) && + (ItemToUse->ItemType != ItemType2HPiercing)) { + //no weapon + ItemToUse = nullptr; + } + } + + int16 DelayMod = std::max(itembonuses.HundredHands + spellbonuses.HundredHands, -99); + int speed = 0; + + //if we have no weapon.. + if (ItemToUse == nullptr) { + //above checks ensure ranged weapons do not fall into here + // Work out if we're a monk + if ((GetClass() == MONK) || (GetClass() == BEASTLORD)) + speed = static_cast((GetMonkHandToHandDelay() * (100 + DelayMod) / 100) * PermaHaste); + else + speed = static_cast((36 * (100 + DelayMod) / 100) * PermaHaste); + } else { + //we have a weapon, use its delay + // Convert weapon delay to timer resolution (milliseconds) + //delay * 100 + speed = static_cast((ItemToUse->Delay * (100 + DelayMod) / 100) * PermaHaste); + if (ItemToUse->ItemType == ItemTypeBow || ItemToUse->ItemType == ItemTypeLargeThrowing) { + float quiver_haste = GetQuiverHaste(); + if (quiver_haste > 0) + speed *= quiver_haste; + } + } + TimerToUse->SetAtTrigger(std::max(RuleI(Combat, MinHastedDelay), speed), true); + + if (i == MainPrimary) + PrimaryWeapon = ItemToUse; + } +} + +void NPC::SetAttackTimer() +{ + float PermaHaste = GetPermaHaste(); + + //default value for attack timer in case they have + //an invalid weapon equipped: + attack_timer.SetAtTrigger(4000, true); + + Timer *TimerToUse = nullptr; + + for (int i = MainRange; i <= MainSecondary; i++) { + //pick a timer + if (i == MainPrimary) + TimerToUse = &attack_timer; + else if (i == MainRange) + TimerToUse = &ranged_timer; + else if (i == MainSecondary) + TimerToUse = &attack_dw_timer; + else //invalid slot (hands will always hit this) + continue; + + //special offhand stuff + if (i == MainSecondary) { + //NPCs get it for free at 13 + if(GetLevel() < 13) { + attack_dw_timer.Disable(); + continue; + } + } + + int16 DelayMod = std::max(itembonuses.HundredHands + spellbonuses.HundredHands, -99); + + // Technically NPCs should do some logic for weapons, but the effect is minimal + // What they do is take the lower of their set delay and the weapon's + // ex. Mob's delay set to 20, weapon set to 19, delay 19 + // Mob's delay set to 20, weapon set to 21, delay 20 + int speed = static_cast((36 * (100 + DelayMod) / 100) * (100.0f + attack_speed) * PermaHaste); + TimerToUse->SetAtTrigger(std::max(RuleI(Combat, MinHastedDelay), speed), true); + } +} diff --git a/zone/client.cpp b/zone/client.cpp index 8340dde9f..90f26ff18 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -8346,3 +8346,19 @@ void Client::ShowNumHits() return; } +float Client::GetQuiverHaste() +{ + float quiver_haste = 0; + for (int r = EmuConstants::GENERAL_BEGIN; r <= EmuConstants::GENERAL_END; r++) { + const ItemInst *pi = GetInv().GetItem(r); + if (!pi) + continue; + if (pi->IsType(ItemClassContainer) && pi->GetItem()->BagType == BagTypeQuiver) { + float temp_wr = (pi->GetItem()->BagWR / RuleI(Combat, QuiverWRHasteDiv)); + quiver_haste = std::max(temp_wr, quiver_haste); + } + } + if (quiver_haste > 0) + quiver_haste = 1.0f / (1.0f + static_cast(quiver_haste) / 100.0f); + return quiver_haste; +} diff --git a/zone/client.h b/zone/client.h index 9d6de336a..697e4e2d3 100644 --- a/zone/client.h +++ b/zone/client.h @@ -222,6 +222,8 @@ public: virtual Group* GetGroup() { return entity_list.GetGroupByClient(this); } virtual inline bool IsBerserk() { return berserk; } virtual int32 GetMeleeMitDmg(Mob *attacker, int32 damage, int32 minhit, float mit_rating, float atk_rating); + virtual void SetAttackTimer(); + float GetQuiverHaste(); void AI_Init(); void AI_Start(uint32 iMoveDelay = 0); @@ -1194,9 +1196,9 @@ public: int32 mod_client_xp(int32 in_exp, NPC *npc); uint32 mod_client_xp_for_level(uint32 xp, uint16 check_level); int mod_client_haste_cap(int cap); - int mod_consume(Item_Struct *item, ItemUseTypes type, int change); - int mod_food_value(const Item_Struct *item, int change); - int mod_drink_value(const Item_Struct *item, int change); + int mod_consume(Item_Struct *item, ItemUseTypes type, int change); + int mod_food_value(const Item_Struct *item, int change); + int mod_drink_value(const Item_Struct *item, int change); void SetEngagedRaidTarget(bool value) { EngagedRaidTarget = value; } bool GetEngagedRaidTarget() const { return EngagedRaidTarget; } diff --git a/zone/mob.cpp b/zone/mob.cpp index 2b38e4714..73c3ec068 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -1939,157 +1939,6 @@ void Mob::Kill() { Death(this, 0, SPELL_UNKNOWN, SkillHandtoHand); } -void Mob::SetAttackTimer() { - float PermaHaste; - if (GetHaste()) - PermaHaste = 1 / (1 + (float)GetHaste() / 100); - else - PermaHaste = 1.0f; - - //default value for attack timer in case they have - //an invalid weapon equipped: - attack_timer.SetAtTrigger(4000, true); - - Timer* TimerToUse = nullptr; - const Item_Struct* PrimaryWeapon = nullptr; - - for (int i=MainRange; i<=MainSecondary; i++) { - - //pick a timer - if (i == MainPrimary) - TimerToUse = &attack_timer; - else if (i == MainRange) - TimerToUse = &ranged_timer; - else if(i == MainSecondary) - TimerToUse = &attack_dw_timer; - else //invalid slot (hands will always hit this) - continue; - - const Item_Struct* ItemToUse = nullptr; - - //find our item - if (IsClient()) { - ItemInst* ci = CastToClient()->GetInv().GetItem(i); - if (ci) - ItemToUse = ci->GetItem(); - } else if(IsNPC()) - { - //The code before here was fundementally flawed because equipment[] - //isn't the same as PC inventory and also: - //NPCs don't use weapon speed to dictate how fast they hit anyway. - ItemToUse = nullptr; - } - - //special offhand stuff - if(i == MainSecondary) { - //if we have a 2H weapon in our main hand, no dual - if(PrimaryWeapon != nullptr) { - if( PrimaryWeapon->ItemClass == ItemClassCommon - && (PrimaryWeapon->ItemType == ItemType2HSlash - || PrimaryWeapon->ItemType == ItemType2HBlunt - || PrimaryWeapon->ItemType == ItemType2HPiercing)) { - attack_dw_timer.Disable(); - continue; - } - } - - //clients must have the skill to use it... - if(IsClient()) { - //if we cant dual wield, skip it - if (!CanThisClassDualWield()) { - attack_dw_timer.Disable(); - continue; - } - } else { - //NPCs get it for free at 13 - if(GetLevel() < 13) { - attack_dw_timer.Disable(); - continue; - } - } - } - - //see if we have a valid weapon - if(ItemToUse != nullptr) { - //check type and damage/delay - if(ItemToUse->ItemClass != ItemClassCommon - || ItemToUse->Damage == 0 - || ItemToUse->Delay == 0) { - //no weapon - ItemToUse = nullptr; - } - // Check to see if skill is valid - else if((ItemToUse->ItemType > ItemTypeLargeThrowing) && (ItemToUse->ItemType != ItemTypeMartial) && (ItemToUse->ItemType != ItemType2HPiercing)) { - //no weapon - ItemToUse = nullptr; - } - } - - int16 DelayMod = itembonuses.HundredHands + spellbonuses.HundredHands; - if (DelayMod < -99) - DelayMod = -99; - - //if we have no weapon.. - if (ItemToUse == nullptr) { - //above checks ensure ranged weapons do not fall into here - // Work out if we're a monk - if ((GetClass() == MONK) || (GetClass() == BEASTLORD)) { - //we are a monk, use special delay - int speed = (int)( (GetMonkHandToHandDelay()*(100+DelayMod)/100)*(100.0f+attack_speed)*PermaHaste); - // 1200 seemed too much, with delay 10 weapons available - if(speed < RuleI(Combat, MinHastedDelay)) //lower bound - speed = RuleI(Combat, MinHastedDelay); - TimerToUse->SetAtTrigger(speed, true); // Hand to hand, delay based on level or epic - } else { - //not a monk... using fist, regular delay - int speed = (int)((36 *(100+DelayMod)/100)*(100.0f+attack_speed)*PermaHaste); - if(speed < RuleI(Combat, MinHastedDelay) && IsClient()) //lower bound - speed = RuleI(Combat, MinHastedDelay); - TimerToUse->SetAtTrigger(speed, true); // Hand to hand, non-monk 2/36 - } - } else { - //we have a weapon, use its delay - // Convert weapon delay to timer resolution (milliseconds) - //delay * 100 - int speed = (int)((ItemToUse->Delay*(100+DelayMod)/100)*(100.0f+attack_speed)*PermaHaste); - if(speed < RuleI(Combat, MinHastedDelay)) - speed = RuleI(Combat, MinHastedDelay); - - if(ItemToUse && (ItemToUse->ItemType == ItemTypeBow || ItemToUse->ItemType == ItemTypeLargeThrowing)) - { - if(IsClient()) - { - float max_quiver = 0; - for(int r = EmuConstants::GENERAL_BEGIN; r <= EmuConstants::GENERAL_END; r++) - { - const ItemInst *pi = CastToClient()->GetInv().GetItem(r); - if(!pi) - continue; - if(pi->IsType(ItemClassContainer) && pi->GetItem()->BagType == BagTypeQuiver) - { - float temp_wr = ( pi->GetItem()->BagWR / RuleI(Combat, QuiverWRHasteDiv) ); - if(temp_wr > max_quiver) - { - max_quiver = temp_wr; - } - } - } - if(max_quiver > 0) - { - float quiver_haste = 1 / (1 + max_quiver / 100); - speed *= quiver_haste; - } - } - } - TimerToUse->SetAtTrigger(speed, true); - } - - if(i == MainPrimary) - PrimaryWeapon = ItemToUse; - } - -} - bool Mob::CanThisClassDualWield(void) const { if(!IsClient()) { return(GetSkill(SkillDualWield) > 0); diff --git a/zone/mob.h b/zone/mob.h index 028f0497f..16c45ce1f 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -684,6 +684,7 @@ public: inline bool GetInvul(void) { return invulnerable; } inline void SetExtraHaste(int Haste) { ExtraHaste = Haste; } virtual int GetHaste(); + inline float GetPermaHaste() { return GetHaste() ? 1.0f / (1.0f + static_cast(GetHaste()) / 100.0f) : 1.0f; } uint8 GetWeaponDamageBonus(const Item_Struct* Weapon); uint16 GetDamageTable(SkillUseTypes skillinuse); diff --git a/zone/npc.h b/zone/npc.h index 5b28260d4..b23522bee 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -134,7 +134,6 @@ public: void CalcNPCRegen(); void CalcNPCDamage(); - int32 GetActSpellDamage(uint16 spell_id, int32 value, Mob* target = nullptr); int32 GetActSpellHealing(uint16 spell_id, int32 value, Mob* target = nullptr); inline void SetSpellFocusDMG(int32 NewSpellFocusDMG) {SpellFocusDMG = NewSpellFocusDMG;} @@ -158,6 +157,7 @@ public: virtual void InitializeBuffSlots(); virtual void UninitializeBuffSlots(); + virtual void SetAttackTimer(); virtual void RangedAttack(Mob* other); virtual void ThrowingAttack(Mob* other) { } int32 GetNumberOfAttacks() const { return attack_count; } @@ -388,16 +388,16 @@ public: inline void SetHealScale(float amt) { healscale = amt; } inline float GetHealScale() { return healscale; } - uint32 GetSpawnKillCount(); - int GetScore(); - void SetMerchantProbability(uint8 amt) { probability = amt; } + uint32 GetSpawnKillCount(); + int GetScore(); + void SetMerchantProbability(uint8 amt) { probability = amt; } uint8 GetMerchantProbability() { return probability; } - void mod_prespawn(Spawn2 *sp); - int mod_npc_damage(int damage, SkillUseTypes skillinuse, int hand, const Item_Struct* weapon, Mob* other); + void mod_prespawn(Spawn2 *sp); + int mod_npc_damage(int damage, SkillUseTypes skillinuse, int hand, const Item_Struct* weapon, Mob* other); void mod_npc_killed_merit(Mob* c); void mod_npc_killed(Mob* oos); - void AISpellsList(Client *c); - + void AISpellsList(Client *c); + bool IsRaidTarget() const { return raid_target; }; protected: