feat(Core/Creatures): implement a sparring system (#19824)

This commit is contained in:
Grimdhex
2025-01-26 09:40:37 +01:00
committed by GitHub
parent 2b4a6cc902
commit edf2959a26
8 changed files with 114 additions and 1 deletions

View File

@@ -273,7 +273,7 @@ Creature::Creature(bool isWorldObject): Unit(isWorldObject), MovableMapObject(),
m_transportCheckTimer(1000), lootPickPocketRestoreTime(0), m_combatPulseTime(0), m_combatPulseDelay(0), m_reactState(REACT_AGGRESSIVE), m_defaultMovementType(IDLE_MOTION_TYPE),
m_spawnId(0), m_equipmentId(0), m_originalEquipmentId(0), m_AlreadyCallAssistance(false),
m_AlreadySearchedAssistance(false), m_regenHealth(true), m_regenPower(true), m_AI_locked(false), m_meleeDamageSchoolMask(SPELL_SCHOOL_MASK_NORMAL), m_originalEntry(0), m_moveInLineOfSightDisabled(false), m_moveInLineOfSightStrictlyDisabled(false),
m_homePosition(), m_transportHomePosition(), m_creatureInfo(nullptr), m_creatureData(nullptr), m_detectionDistance(20.0f), m_waypointID(0), m_path_id(0), m_formation(nullptr), m_lastLeashExtensionTime(nullptr), m_cannotReachTimer(0),
m_homePosition(), m_transportHomePosition(), m_creatureInfo(nullptr), m_creatureData(nullptr), m_detectionDistance(20.0f),_sparringPct(0.0f), m_waypointID(0), m_path_id(0), m_formation(nullptr), m_lastLeashExtensionTime(nullptr), m_cannotReachTimer(0),
_isMissingSwimmingFlagOutOfCombat(false), m_assistanceTimer(0), _playerDamageReq(0), _damagedByPlayer(false), _isCombatMovementAllowed(true)
{
m_regenTimer = CREATURE_REGEN_INTERVAL;
@@ -608,6 +608,8 @@ bool Creature::UpdateEntry(uint32 Entry, const CreatureData* data, bool changele
SetCanModifyStats(true);
UpdateAllStats();
LoadSparringPct();
// checked and error show at loading templates
if (FactionTemplateEntry const* factionTemplate = sFactionTemplateStore.LookupEntry(cInfo->faction))
{
@@ -1189,6 +1191,7 @@ bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, u
}
LoadCreaturesAddon();
LoadSparringPct();
//! Need to be called after LoadCreaturesAddon - MOVEMENTFLAG_HOVER is set there
m_positionZ += GetHoverHeight();
@@ -2024,6 +2027,8 @@ void Creature::setDeathState(DeathState state, bool despawn)
Motion_Initialize();
LoadCreaturesAddon(true);
LoadSparringPct();
if (GetCreatureData() && GetPhaseMask() != GetCreatureData()->phaseMask)
SetPhaseMask(GetCreatureData()->phaseMask, false);
}
@@ -2797,6 +2802,18 @@ bool Creature::LoadCreaturesAddon(bool reload)
return true;
}
void Creature::LoadSparringPct()
{
ObjectGuid::LowType spawnId = GetSpawnId();
auto const& sparringData = sObjectMgr->GetSparringData();
auto itr = sparringData.find(spawnId);
if (itr != sparringData.end() && !itr->second.empty())
{
_sparringPct = itr->second[0];
}
}
/// Send a message to LocalDefense channel for players opposition team in the zone
void Creature::SendZoneUnderAttackMessage(Player* attacker)
{

View File

@@ -187,6 +187,9 @@ public:
void UpdateAttackPowerAndDamage(bool ranged = false) override;
void CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, bool addTotalPct, float& minDamage, float& maxDamage, uint8 damageIndex) override;
void LoadSparringPct();
[[nodiscard]] float GetSparringPct() const { return _sparringPct; }
bool HasWeapon(WeaponAttackType type) const override;
bool HasWeaponForAttack(WeaponAttackType type) const override { return (Unit::HasWeaponForAttack(type) && HasWeapon(type)); }
void SetCanDualWield(bool value) override;
@@ -483,6 +486,8 @@ protected:
float m_detectionDistance;
uint16 m_LootMode; // bitmask, default LOOT_MODE_DEFAULT, determines what loot will be lootable
float _sparringPct;
[[nodiscard]] bool IsInvisibleDueToDespawn() const override;
bool CanAlwaysSee(WorldObject const* obj) const override;
bool IsAlwaysDetectableFor(WorldObject const* seer) const override;

View File

@@ -1032,6 +1032,17 @@ uint32 Unit::DealDamage(Unit* attacker, Unit* victim, uint32 damage, CleanDamage
}
}
// Sparring
if (victim->CanSparringWith(attacker))
{
if (damage >= victim->GetHealth())
damage = 0;
uint32 sparringHealth = victim->GetHealth() * (victim->ToCreature()->GetSparringPct() / 100);
if (victim->GetHealth() - damage <= sparringHealth)
damage = 0;
}
if (health <= damage)
{
LOG_DEBUG("entities.unit", "DealDamage: victim just died");
@@ -2635,6 +2646,10 @@ void Unit::AttackerStateUpdate(Unit* victim, WeaponAttackType attType /*= BASE_A
Unit::DealDamageMods(victim, damageInfo.damages[i].damage, &damageInfo.damages[i].absorb);
}
// Related to sparring system. Allow attack animations even if there are no damages
if (victim->CanSparringWith(damageInfo.attacker))
damageInfo.HitInfo |= HITINFO_FAKE_DAMAGE;
SendAttackStateUpdate(&damageInfo);
//TriggerAurasProcOnEvent(damageInfo);
@@ -3954,6 +3969,24 @@ void Unit::_UpdateAutoRepeatSpell()
}
}
bool Unit::CanSparringWith(Unit const* attacker) const
{
if (!IsCreature() || IsCharmedOwnedByPlayerOrPlayer())
return false;
if (!attacker)
return false;
if (!attacker->IsCreature() || attacker->IsCharmedOwnedByPlayerOrPlayer())
return false;
if (Creature const* creature = ToCreature())
if (!creature->GetSparringPct())
return false;
return true;
}
void Unit::SetCurrentCastedSpell(Spell* pSpell)
{
ASSERT(pSpell); // nullptr may be never passed here, use InterruptSpell or InterruptNonMeleeSpells

View File

@@ -2038,6 +2038,8 @@ protected:
void _UpdateAutoRepeatSpell();
bool CanSparringWith(Unit const* attacker) const; ///@brief: Check if unit is eligible for sparring damages. Work only if attacker and victim are creatures.
bool IsAlwaysVisibleFor(WorldObject const* seer) const override;
bool IsAlwaysDetectableFor(WorldObject const* seer) const override;