diff --git a/data/sql/updates/pending_db_world/rev_1726975868981293914.sql b/data/sql/updates/pending_db_world/rev_1726975868981293914.sql new file mode 100644 index 000000000..79fc33d2c --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1726975868981293914.sql @@ -0,0 +1,53 @@ +-- +UPDATE `creature_template` SET `flags_extra` = `flags_extra` | 2048 WHERE `entry` IN ( +3672, -- Boahn +4403, -- Muckshell Pincer +4540, -- Scarlet Monk +7033, -- Firegut Ogre +8218, -- Witherheart the Stalker +10478, -- Splintered Skeleton +10488, -- Risen Construct +11022, -- Alexi Barov +12100, -- Lava Reaver +12262, -- Ziggurat Protector +12263, -- Slaughterhouse Protector +14688, -- Prince Sandoval +14882, -- Atal'ai Mistress +16299, -- Skeletal Shocktrooper +16422, -- Skeletal Soldier +16593, -- Shattered Hand Brawler +16808, -- Warchief Kargath Bladefist +16936, -- Dreghood Wanderer +18120, -- Ango'rosh Mauler +18211, -- Murkblood Brute +18541, -- Urdak +18860, -- Daughter of Destiny +19191, -- Arazzius the Cruel +20456, -- Ethereum Researcher +20582, -- Shattered Hand Brawler (1) +20597, -- Warchief Kargath Bladefist (1) +20683, -- Prophetess Cavrylin +20783, -- Porfus the Gem Gorger +20784, -- Armbreaker Huffaz +20785, -- Fel Tinkerer Zortan +20786, -- Gul'bor +20790, -- Malevus the Mad +20929, -- Wrath Lord +20984, -- Protectorate Defender +21639, -- Illidari Slayer +21717, -- Dragonmaw Wrangler +21805, -- Protectorate Avenger +21877, -- Karsius the Ancient Watcher +22004, -- Leoroxx +22076, -- Torloth the Magnificent +22082, -- Shadowmoon Slayer +22199, -- Slaag +22377, -- Akuno +22825, -- Matron Li-sahar +23008, -- Ethereum Jailor +23410, -- Spirit of Udalo +23411, -- Spirit of Olum +24882, -- Brutallus +37132, -- Ymirjar Battle-Maiden +38132 -- Ymirjar Battle-Maiden (1) +); diff --git a/src/server/game/AI/CoreAI/UnitAI.cpp b/src/server/game/AI/CoreAI/UnitAI.cpp index 710f4a453..8c9b4b9bd 100644 --- a/src/server/game/AI/CoreAI/UnitAI.cpp +++ b/src/server/game/AI/CoreAI/UnitAI.cpp @@ -52,7 +52,7 @@ void UnitAI::DoMeleeAttackIfReady() if (me->isAttackReady()) { // xinef: prevent base and off attack in same time, delay attack at 0.2 sec - if (me->haveOffhandWeapon()) + if (me->HasOffhandWeaponForAttack()) if (me->getAttackTimer(OFF_ATTACK) < ATTACK_DISPLAY_DELAY) me->setAttackTimer(OFF_ATTACK, ATTACK_DISPLAY_DELAY); @@ -60,7 +60,7 @@ void UnitAI::DoMeleeAttackIfReady() me->resetAttackTimer(); } - if (me->haveOffhandWeapon() && me->isAttackReady(OFF_ATTACK)) + if (me->HasOffhandWeaponForAttack() && me->isAttackReady(OFF_ATTACK)) { // xinef: delay main hand attack if both will hit at the same time (players code) if (me->getAttackTimer(BASE_ATTACK) < ATTACK_DISPLAY_DELAY) diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp index 81211529b..fb7b01fec 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp @@ -522,17 +522,33 @@ void ScriptedAI::SetEquipmentSlots(bool loadDefault, int32 mainHand /*= EQUIP_NO if (loadDefault) { me->LoadEquipment(me->GetOriginalEquipmentId(), true); + if (me->HasWeapon(OFF_ATTACK)) + me->SetCanDualWield(true); + else + me->SetCanDualWield(false); return; } if (mainHand >= 0) - me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 0, uint32(mainHand)); + { + me->SetVirtualItem(0, uint32(mainHand)); + me->UpdateDamagePhysical(BASE_ATTACK); + } if (offHand >= 0) - me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 1, uint32(offHand)); + { + me->SetVirtualItem(1, uint32(offHand)); + if (offHand >= 1) + me->SetCanDualWield(true); + else + me->SetCanDualWield(false); + } if (ranged >= 0) - me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 2, uint32(ranged)); + { + me->SetVirtualItem(2, uint32(ranged)); + me->UpdateDamagePhysical(RANGED_ATTACK); + } } enum eNPCs diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 01fcd1f11..83373b681 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -524,6 +524,8 @@ bool Creature::InitEntry(uint32 Entry, const CreatureData* data) SetFloatValue(UNIT_FIELD_HOVERHEIGHT, cinfo->HoverHeight); + SetCanDualWield(cinfo->flags_extra & CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK); + // checked at loading m_defaultMovementType = MovementGeneratorType(cinfo->MovementType); if (!m_wanderDistance && m_defaultMovementType == RANDOM_MOTION_TYPE) @@ -568,6 +570,8 @@ bool Creature::UpdateEntry(uint32 Entry, const CreatureData* data, bool changele ReplaceAllDynamicFlags(dynamicflags); + SetCanDualWield(cInfo->flags_extra & CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK); + SetAttackTime(BASE_ATTACK, cInfo->BaseAttackTime); SetAttackTime(OFF_ATTACK, cInfo->BaseAttackTime); SetAttackTime(RANGED_ATTACK, cInfo->RangeAttackTime); @@ -3172,6 +3176,14 @@ bool Creature::IsImmuneToKnockback() const return cinfo && (cinfo->flags_extra & CREATURE_FLAG_EXTRA_IMMUNITY_KNOCKBACK); } +bool Creature::HasWeapon(WeaponAttackType type) const +{ + const uint8 slot = uint8(type); + ItemEntry const* item = sItemStore.LookupEntry(GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + slot)); + + return ((item && item->ClassID == ITEM_CLASS_WEAPON) || (type == OFF_ATTACK && CanDualWield())); +} + /** * @brief Enable or disable the creature's walk mode by removing: MOVEMENTFLAG_WALKING. Infom also the client */ diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 1c1aa3321..7a91b3243 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -190,6 +190,8 @@ public: void UpdateAttackPowerAndDamage(bool ranged = false) override; void CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, bool addTotalPct, float& minDamage, float& maxDamage, uint8 damageIndex) override; + bool HasWeapon(WeaponAttackType type) const override; + bool HasWeaponForAttack(WeaponAttackType type) const override { return (Unit::HasWeaponForAttack(type) && HasWeapon(type)); } void SetCanDualWield(bool value) override; [[nodiscard]] int8 GetOriginalEquipmentId() const { return m_originalEquipmentId; } uint8 GetCurrentEquipmentId() { return m_equipmentId; } diff --git a/src/server/game/Entities/Creature/CreatureData.h b/src/server/game/Entities/Creature/CreatureData.h index b14cb85ea..60f4c6a7f 100644 --- a/src/server/game/Entities/Creature/CreatureData.h +++ b/src/server/game/Entities/Creature/CreatureData.h @@ -54,7 +54,7 @@ enum CreatureFlagsExtra : uint32 CREATURE_FLAG_EXTRA_NO_TAUNT = 0x00000100, // creature is immune to taunt auras and 'attack me' effects CREATURE_FLAG_EXTRA_NO_MOVE_FLAGS_UPDATE = 0x00000200, // creature won't update movement flags CREATURE_FLAG_EXTRA_GHOST_VISIBILITY = 0x00000400, // creature will only be visible to dead players - CREATURE_FLAG_EXTRA_UNUSED_12 = 0x00000800, /// @todo: Implement CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK (creature will use offhand attacks) + CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK = 0x00000800, // creature will use offhand attacks CREATURE_FLAG_EXTRA_NO_SELL_VENDOR = 0x00001000, // players can't sell items to this vendor CREATURE_FLAG_EXTRA_IGNORE_COMBAT = 0x00002000, CREATURE_FLAG_EXTRA_WORLDEVENT = 0x00004000, // custom flag for world event creatures (left room for merging) @@ -77,9 +77,7 @@ enum CreatureFlagsExtra : uint32 CREATURE_FLAG_EXTRA_HARD_RESET = 0x80000000, // Masks - CREATURE_FLAG_EXTRA_UNUSED = (CREATURE_FLAG_EXTRA_UNUSED_12), // SKIP - - CREATURE_FLAG_EXTRA_DB_ALLOWED = (0xFFFFFFFF & ~(CREATURE_FLAG_EXTRA_UNUSED | CREATURE_FLAG_EXTRA_DUNGEON_BOSS)) // SKIP + CREATURE_FLAG_EXTRA_DB_ALLOWED = (0xFFFFFFFF & ~CREATURE_FLAG_EXTRA_DUNGEON_BOSS) // SKIP }; enum class CreatureGroundMovementType : uint8 diff --git a/src/server/game/Entities/Creature/enuminfo_CreatureData.cpp b/src/server/game/Entities/Creature/enuminfo_CreatureData.cpp index b1af41ce3..1fa83fe3f 100644 --- a/src/server/game/Entities/Creature/enuminfo_CreatureData.cpp +++ b/src/server/game/Entities/Creature/enuminfo_CreatureData.cpp @@ -42,7 +42,7 @@ AC_API_EXPORT EnumText EnumUtils::ToString(CreatureFlagsExtr case CREATURE_FLAG_EXTRA_NO_TAUNT: return { "CREATURE_FLAG_EXTRA_NO_TAUNT", "CREATURE_FLAG_EXTRA_NO_TAUNT", "creature is immune to taunt auras and 'attack me' effects" }; case CREATURE_FLAG_EXTRA_NO_MOVE_FLAGS_UPDATE: return { "CREATURE_FLAG_EXTRA_NO_MOVE_FLAGS_UPDATE", "CREATURE_FLAG_EXTRA_NO_MOVE_FLAGS_UPDATE", "creature won't update movement flags" }; case CREATURE_FLAG_EXTRA_GHOST_VISIBILITY: return { "CREATURE_FLAG_EXTRA_GHOST_VISIBILITY", "CREATURE_FLAG_EXTRA_GHOST_VISIBILITY", "creature will only be visible to dead players" }; - case CREATURE_FLAG_EXTRA_UNUSED_12: return { "CREATURE_FLAG_EXTRA_UNUSED_12", "CREATURE_FLAG_EXTRA_UNUSED_12", "/ @todo: Implement CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK (creature will use offhand attacks)" }; + case CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK: return { "CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK", "CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK", "/ @todo: Implement CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK (creature will use offhand attacks)" }; case CREATURE_FLAG_EXTRA_NO_SELL_VENDOR: return { "CREATURE_FLAG_EXTRA_NO_SELL_VENDOR", "CREATURE_FLAG_EXTRA_NO_SELL_VENDOR", "players can't sell items to this vendor" }; case CREATURE_FLAG_EXTRA_IGNORE_COMBAT: return { "CREATURE_FLAG_EXTRA_IGNORE_COMBAT", "CREATURE_FLAG_EXTRA_IGNORE_COMBAT", "" }; case CREATURE_FLAG_EXTRA_WORLDEVENT: return { "CREATURE_FLAG_EXTRA_WORLDEVENT", "CREATURE_FLAG_EXTRA_WORLDEVENT", "custom flag for world event creatures (left room for merging)" }; @@ -86,7 +86,7 @@ AC_API_EXPORT CreatureFlagsExtra EnumUtils::FromIndex(std::s case 8: return CREATURE_FLAG_EXTRA_NO_TAUNT; case 9: return CREATURE_FLAG_EXTRA_NO_MOVE_FLAGS_UPDATE; case 10: return CREATURE_FLAG_EXTRA_GHOST_VISIBILITY; - case 11: return CREATURE_FLAG_EXTRA_UNUSED_12; + case 11: return CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK; case 12: return CREATURE_FLAG_EXTRA_NO_SELL_VENDOR; case 13: return CREATURE_FLAG_EXTRA_IGNORE_COMBAT; case 14: return CREATURE_FLAG_EXTRA_WORLDEVENT; @@ -127,7 +127,7 @@ AC_API_EXPORT std::size_t EnumUtils::ToIndex(CreatureFlagsEx case CREATURE_FLAG_EXTRA_NO_TAUNT: return 8; case CREATURE_FLAG_EXTRA_NO_MOVE_FLAGS_UPDATE: return 9; case CREATURE_FLAG_EXTRA_GHOST_VISIBILITY: return 10; - case CREATURE_FLAG_EXTRA_UNUSED_12: return 11; + case CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK: return 11; case CREATURE_FLAG_EXTRA_NO_SELL_VENDOR: return 12; case CREATURE_FLAG_EXTRA_IGNORE_COMBAT: return 13; case CREATURE_FLAG_EXTRA_WORLDEVENT: return 14; diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 0569360f5..b00468be5 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1248,6 +1248,8 @@ public: return GetItemByPos(bag, slot); } [[nodiscard]] Item* GetWeaponForAttack(WeaponAttackType attackType, bool useable = false) const; + bool HasWeapon(WeaponAttackType type) const override { return GetWeaponForAttack(type, false); } + bool HasWeaponForAttack(WeaponAttackType type) const override { return (Unit::HasWeaponForAttack(type) && GetWeaponForAttack(type, true)); } [[nodiscard]] Item* GetShield(bool useable = false) const; static uint8 GetAttackBySlot(uint8 slot); // MAX_ATTACK if not weapon slot std::vector& GetItemUpdateQueue() { return m_itemUpdateQueue; } diff --git a/src/server/game/Entities/Player/PlayerUpdates.cpp b/src/server/game/Entities/Player/PlayerUpdates.cpp index 594100616..5dcf409fa 100644 --- a/src/server/game/Entities/Player/PlayerUpdates.cpp +++ b/src/server/game/Entities/Player/PlayerUpdates.cpp @@ -195,7 +195,7 @@ void Player::Update(uint32 p_time) // prevent base and off attack in same time, delay attack at // 0.2 sec - if (haveOffhandWeapon()) + if (HasOffhandWeaponForAttack()) if (getAttackTimer(OFF_ATTACK) < ATTACK_DISPLAY_DELAY) setAttackTimer(OFF_ATTACK, ATTACK_DISPLAY_DELAY); @@ -205,7 +205,7 @@ void Player::Update(uint32 p_time) } } - if (haveOffhandWeapon() && isAttackReady(OFF_ATTACK)) + if (HasOffhandWeaponForAttack() && isAttackReady(OFF_ATTACK)) { if (!IsWithinMeleeRange(victim)) setAttackTimer(OFF_ATTACK, 100); diff --git a/src/server/game/Entities/Unit/StatSystem.cpp b/src/server/game/Entities/Unit/StatSystem.cpp index aee22dcd2..0706808ae 100644 --- a/src/server/game/Entities/Unit/StatSystem.cpp +++ b/src/server/game/Entities/Unit/StatSystem.cpp @@ -512,7 +512,7 @@ void Player::UpdateAttackPowerAndDamage(bool ranged) else { UpdateDamagePhysical(BASE_ATTACK); - if (CanDualWield() && haveOffhandWeapon()) //allow update offhand damage only if player knows DualWield Spec and has equipped offhand weapon + if (CanDualWield() && HasOffhandWeaponForAttack()) //allow update offhand damage only if player knows DualWield Spec and has equipped offhand weapon UpdateDamagePhysical(OFF_ATTACK); if (IsClass(CLASS_SHAMAN, CLASS_CONTEXT_STATS) || IsClass(CLASS_PALADIN, CLASS_CONTEXT_STATS)) // mental quickness UpdateSpellDamageAndHealingBonus(); @@ -567,7 +567,7 @@ void Player::CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, bo float weaponMinDamage = GetWeaponDamageRange(attType, MINDAMAGE); float weaponMaxDamage = GetWeaponDamageRange(attType, MAXDAMAGE); - if (IsInFeralForm()) // check if player is druid and in cat or bear forms + if (IsAttackSpeedOverridenShapeShift()) // forms with no override on attack speed use normal weapon damage { uint8 lvl = GetLevel(); if (lvl > 60) @@ -1118,7 +1118,7 @@ void Creature::CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, break; } - if (attType == OFF_ATTACK && !haveOffhandWeapon()) + if (attType == OFF_ATTACK && !HasOffhandWeaponForAttack()) { minDamage = 0.0f; maxDamage = 0.0f; @@ -1128,10 +1128,11 @@ void Creature::CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, float weaponMinDamage = GetWeaponDamageRange(attType, MINDAMAGE); float weaponMaxDamage = GetWeaponDamageRange(attType, MAXDAMAGE); - if (!CanUseAttackType(attType)) // disarm case + // Disarm for creatures + if (HasWeapon(attType) && !HasWeaponForAttack(attType)) { - weaponMinDamage = 0.0f; - weaponMaxDamage = 0.0f; + minDamage *= 0.5f; + maxDamage *= 0.5f; } float attackPower = GetTotalAttackPowerValue(attType); diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 19c2be2f9..3aea9ddf4 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -1856,7 +1856,7 @@ void Unit::DealMeleeDamage(CalcDamageInfo* damageInfo, bool durabilityLoss) float offtime = float(victim->getAttackTimer(OFF_ATTACK)); float basetime = float(victim->getAttackTimer(BASE_ATTACK)); // Reduce attack time - if (victim->haveOffhandWeapon() && offtime < basetime) + if (victim->HasOffhandWeaponForAttack() && offtime < basetime) { float percent20 = victim->GetAttackTime(OFF_ATTACK) * 0.20f; float percent60 = 3.0f * percent20; @@ -8430,7 +8430,7 @@ bool Unit::HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggere if (dummySpell->SpellIconID == 2023) { // Must Dual Wield - if (!procSpell || !haveOffhandWeapon()) + if (!procSpell || !HasOffhandWeaponForAttack()) return false; // Chance as basepoints for dummy aura if (!roll_chance_i(triggerAmount)) @@ -10348,9 +10348,9 @@ bool Unit::Attack(Unit* victim, bool meleeAttack) SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); } - // delay offhand weapon attack to next attack time - if (haveOffhandWeapon() && isAttackReady(OFF_ATTACK)) - setAttackTimer(OFF_ATTACK, ATTACK_DISPLAY_DELAY); + // delay offhand weapon attack by 50% of the base attack time + if (HasOffhandWeaponForAttack() && isAttackReady(OFF_ATTACK)) + setAttackTimer(OFF_ATTACK, std::max(getAttackTimer(OFF_ATTACK), getAttackTimer(BASE_ATTACK) + int32(CalculatePct(GetFloatValue(UNIT_FIELD_BASEATTACKTIME), 50)))); if (meleeAttack) SendMeleeAttackStart(victim); @@ -13400,7 +13400,7 @@ float Unit::GetWeaponProcChance() const // (odd formula...) if (isAttackReady(BASE_ATTACK)) return (GetAttackTime(BASE_ATTACK) * 1.8f / 1000.0f); - else if (haveOffhandWeapon() && isAttackReady(OFF_ATTACK)) + else if (HasOffhandWeaponForAttack() && isAttackReady(OFF_ATTACK)) return (GetAttackTime(OFF_ATTACK) * 1.6f / 1000.0f); return 0; } @@ -15406,7 +15406,7 @@ float Unit::GetTotalAttackPowerValue(WeaponAttackType attType, Unit* victim) con float Unit::GetWeaponDamageRange(WeaponAttackType attType, WeaponDamageRange type, uint8 damageIndex /*= 0*/) const { - if (attType == OFF_ATTACK && !haveOffhandWeapon()) + if (attType == OFF_ATTACK && !HasOffhandWeaponForAttack()) return 0.0f; return m_weaponDamage[attType][type][damageIndex]; @@ -18953,7 +18953,7 @@ float Unit::MeleeSpellMissChance(Unit const* victim, WeaponAttackType attType, i float missChance = victim->GetUnitMissChance(attType); // Check if dual wielding, add additional miss penalty - when mainhand has on next swing spell, offhand doesnt suffer penalty - if (!spellId && (attType != RANGED_ATTACK) && haveOffhandWeapon() && (!m_currentSpells[CURRENT_MELEE_SPELL] || !m_currentSpells[CURRENT_MELEE_SPELL]->IsNextMeleeSwingSpell())) + if (!spellId && (attType != RANGED_ATTACK) && HasOffhandWeaponForAttack() && (!m_currentSpells[CURRENT_MELEE_SPELL] || !m_currentSpells[CURRENT_MELEE_SPELL]->IsNextMeleeSwingSpell())) { missChance += 19; } @@ -19467,6 +19467,16 @@ Unit* Unit::GetRedirectThreatTarget() const return _redirectThreatInfo.GetTargetGUID() ? ObjectAccessor::GetUnit(*this, _redirectThreatInfo.GetTargetGUID()) : nullptr; } +bool Unit::IsAttackSpeedOverridenShapeShift() const +{ + // Mirroring clientside gameplay logic + if (ShapeshiftForm form = GetShapeshiftForm()) + if (SpellShapeshiftFormEntry const* entry = sSpellShapeshiftFormStore.LookupEntry(form)) + return entry->attackSpeed > 0; + + return false; +} + void Unit::JumpTo(float speedXY, float speedZ, bool forward) { float angle = forward ? 0 : M_PI; @@ -21120,6 +21130,22 @@ void Unit::Whisper(std::string_view text, Language language, Player* target, boo target->SendDirectMessage(&data); } +uint32 Unit::GetVirtualItemId(uint32 slot) const +{ + if (slot >= MAX_EQUIPMENT_ITEMS) + return 0; + + return GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + slot); +} + +void Unit::SetVirtualItem(uint32 slot, uint32 itemId) +{ + if (slot >= MAX_EQUIPMENT_ITEMS) + return; + + SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + slot, itemId); +} + void Unit::Talk(uint32 textId, ChatMsg msgType, float textRange, WorldObject const* target) { if (!sObjectMgr->GetBroadcastText(textId)) diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 74dae4667..611ab635e 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -1052,8 +1052,22 @@ public: [[nodiscard]] float GetUnitMissChance(WeaponAttackType attType) const; float GetUnitCriticalChance(WeaponAttackType attackType, Unit const* victim) const; int32 GetMechanicResistChance(SpellInfo const* spell); + + virtual bool HasWeapon(WeaponAttackType type) const = 0; + inline bool HasMainhandWeapon() const { return HasWeapon(BASE_ATTACK); } + inline bool HasOffhandWeapon() const { return HasWeapon(OFF_ATTACK); } + inline bool HasRangedWeapon() const { return HasWeapon(RANGED_ATTACK); } + + inline bool hasMainhandWeaponForAttack() const { return HasWeaponForAttack(BASE_ATTACK); } + virtual bool HasWeaponForAttack(WeaponAttackType type) const { return CanUseAttackType(type); } + inline bool HasMainhandWeaponForAttack() const { return HasWeaponForAttack(BASE_ATTACK); } + inline bool HasOffhandWeaponForAttack() const { return HasWeaponForAttack(OFF_ATTACK); } + inline bool HasRangedWeaponForAttack() const { return HasWeaponForAttack(RANGED_ATTACK); } [[nodiscard]] bool CanUseAttackType(uint8 attacktype) const { + if (IsAttackSpeedOverridenShapeShift()) + return false; + switch (attacktype) { case BASE_ATTACK: @@ -1062,8 +1076,9 @@ public: return !HasUnitFlag2(UNIT_FLAG2_DISARM_OFFHAND); case RANGED_ATTACK: return !HasUnitFlag2(UNIT_FLAG2_DISARM_RANGED); + default: + return true; } - return true; } [[nodiscard]] virtual uint32 GetShieldBlockValue() const = 0; @@ -1466,6 +1481,8 @@ public: SetByteValue(UNIT_FIELD_BYTES_2, 3, form); } + bool IsAttackSpeedOverridenShapeShift() const; + [[nodiscard]] bool IsInFeralForm() const { ShapeshiftForm form = GetShapeshiftForm(); @@ -1760,6 +1777,9 @@ public: [[nodiscard]] float GetCollisionWidth() const override; [[nodiscard]] float GetCollisionRadius() const override; + uint32 GetVirtualItemId(uint32 slot) const; + void SetVirtualItem(uint32 slot, uint32 itemId); + void ProcessPositionDataChanged(PositionFullTerrainStatus const& data) override; virtual void ProcessTerrainStatusUpdate(); diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index bde6e7b35..fd52274b9 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -4068,7 +4068,7 @@ void Spell::_cast(bool skipCheck) { m_caster->resetAttackTimer(BASE_ATTACK); - if (m_caster->haveOffhandWeapon()) + if (m_caster->HasOffhandWeaponForAttack()) { m_caster->resetAttackTimer(OFF_ATTACK); }