feat(Core/Creatures): implement a sparring system (#19824)
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
--
|
||||||
|
DROP TABLE IF EXISTS `creature_sparring`;
|
||||||
|
CREATE TABLE `creature_sparring` (
|
||||||
|
`GUID` int unsigned NOT NULL,
|
||||||
|
`SparringPCT` float NOT NULL,
|
||||||
|
PRIMARY KEY (`GUID`),
|
||||||
|
FOREIGN KEY (`GUID`) REFERENCES creature(`guid`),
|
||||||
|
CONSTRAINT `creature_sparring_chk_1` CHECK (`SparringPCT` BETWEEN 0 AND 100)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
@@ -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_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_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_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)
|
_isMissingSwimmingFlagOutOfCombat(false), m_assistanceTimer(0), _playerDamageReq(0), _damagedByPlayer(false), _isCombatMovementAllowed(true)
|
||||||
{
|
{
|
||||||
m_regenTimer = CREATURE_REGEN_INTERVAL;
|
m_regenTimer = CREATURE_REGEN_INTERVAL;
|
||||||
@@ -608,6 +608,8 @@ bool Creature::UpdateEntry(uint32 Entry, const CreatureData* data, bool changele
|
|||||||
SetCanModifyStats(true);
|
SetCanModifyStats(true);
|
||||||
UpdateAllStats();
|
UpdateAllStats();
|
||||||
|
|
||||||
|
LoadSparringPct();
|
||||||
|
|
||||||
// checked and error show at loading templates
|
// checked and error show at loading templates
|
||||||
if (FactionTemplateEntry const* factionTemplate = sFactionTemplateStore.LookupEntry(cInfo->faction))
|
if (FactionTemplateEntry const* factionTemplate = sFactionTemplateStore.LookupEntry(cInfo->faction))
|
||||||
{
|
{
|
||||||
@@ -1189,6 +1191,7 @@ bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, u
|
|||||||
}
|
}
|
||||||
|
|
||||||
LoadCreaturesAddon();
|
LoadCreaturesAddon();
|
||||||
|
LoadSparringPct();
|
||||||
|
|
||||||
//! Need to be called after LoadCreaturesAddon - MOVEMENTFLAG_HOVER is set there
|
//! Need to be called after LoadCreaturesAddon - MOVEMENTFLAG_HOVER is set there
|
||||||
m_positionZ += GetHoverHeight();
|
m_positionZ += GetHoverHeight();
|
||||||
@@ -2024,6 +2027,8 @@ void Creature::setDeathState(DeathState state, bool despawn)
|
|||||||
|
|
||||||
Motion_Initialize();
|
Motion_Initialize();
|
||||||
LoadCreaturesAddon(true);
|
LoadCreaturesAddon(true);
|
||||||
|
LoadSparringPct();
|
||||||
|
|
||||||
if (GetCreatureData() && GetPhaseMask() != GetCreatureData()->phaseMask)
|
if (GetCreatureData() && GetPhaseMask() != GetCreatureData()->phaseMask)
|
||||||
SetPhaseMask(GetCreatureData()->phaseMask, false);
|
SetPhaseMask(GetCreatureData()->phaseMask, false);
|
||||||
}
|
}
|
||||||
@@ -2797,6 +2802,18 @@ bool Creature::LoadCreaturesAddon(bool reload)
|
|||||||
return true;
|
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
|
/// Send a message to LocalDefense channel for players opposition team in the zone
|
||||||
void Creature::SendZoneUnderAttackMessage(Player* attacker)
|
void Creature::SendZoneUnderAttackMessage(Player* attacker)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -187,6 +187,9 @@ public:
|
|||||||
void UpdateAttackPowerAndDamage(bool ranged = false) override;
|
void UpdateAttackPowerAndDamage(bool ranged = false) override;
|
||||||
void CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, bool addTotalPct, float& minDamage, float& maxDamage, uint8 damageIndex) 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 HasWeapon(WeaponAttackType type) const override;
|
||||||
bool HasWeaponForAttack(WeaponAttackType type) const override { return (Unit::HasWeaponForAttack(type) && HasWeapon(type)); }
|
bool HasWeaponForAttack(WeaponAttackType type) const override { return (Unit::HasWeaponForAttack(type) && HasWeapon(type)); }
|
||||||
void SetCanDualWield(bool value) override;
|
void SetCanDualWield(bool value) override;
|
||||||
@@ -483,6 +486,8 @@ protected:
|
|||||||
float m_detectionDistance;
|
float m_detectionDistance;
|
||||||
uint16 m_LootMode; // bitmask, default LOOT_MODE_DEFAULT, determines what loot will be lootable
|
uint16 m_LootMode; // bitmask, default LOOT_MODE_DEFAULT, determines what loot will be lootable
|
||||||
|
|
||||||
|
float _sparringPct;
|
||||||
|
|
||||||
[[nodiscard]] bool IsInvisibleDueToDespawn() const override;
|
[[nodiscard]] bool IsInvisibleDueToDespawn() const override;
|
||||||
bool CanAlwaysSee(WorldObject const* obj) const override;
|
bool CanAlwaysSee(WorldObject const* obj) const override;
|
||||||
bool IsAlwaysDetectableFor(WorldObject const* seer) const override;
|
bool IsAlwaysDetectableFor(WorldObject const* seer) const override;
|
||||||
|
|||||||
@@ -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)
|
if (health <= damage)
|
||||||
{
|
{
|
||||||
LOG_DEBUG("entities.unit", "DealDamage: victim just died");
|
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);
|
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);
|
SendAttackStateUpdate(&damageInfo);
|
||||||
|
|
||||||
//TriggerAurasProcOnEvent(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)
|
void Unit::SetCurrentCastedSpell(Spell* pSpell)
|
||||||
{
|
{
|
||||||
ASSERT(pSpell); // nullptr may be never passed here, use InterruptSpell or InterruptNonMeleeSpells
|
ASSERT(pSpell); // nullptr may be never passed here, use InterruptSpell or InterruptNonMeleeSpells
|
||||||
|
|||||||
@@ -2038,6 +2038,8 @@ protected:
|
|||||||
|
|
||||||
void _UpdateAutoRepeatSpell();
|
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 IsAlwaysVisibleFor(WorldObject const* seer) const override;
|
||||||
bool IsAlwaysDetectableFor(WorldObject const* seer) const override;
|
bool IsAlwaysDetectableFor(WorldObject const* seer) const override;
|
||||||
|
|
||||||
|
|||||||
@@ -2298,6 +2298,42 @@ void ObjectMgr::LoadCreatures()
|
|||||||
LOG_INFO("server.loading", " ");
|
LOG_INFO("server.loading", " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ObjectMgr::LoadCreatureSparring()
|
||||||
|
{
|
||||||
|
uint32 oldMSTime = getMSTime();
|
||||||
|
|
||||||
|
QueryResult result = WorldDatabase.Query("SELECT GUID, SparringPCT FROM creature_sparring");
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
LOG_WARN("server.loading", ">> Loaded 0 sparring data. DB table `creature_sparring` is empty.");
|
||||||
|
LOG_INFO("server.loading", " ");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 count = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
Field* fields = result->Fetch();
|
||||||
|
|
||||||
|
ObjectGuid::LowType spawnId = fields[0].Get<uint32>();
|
||||||
|
float sparringHealthPct = fields[1].Get<float>();
|
||||||
|
|
||||||
|
if (!sObjectMgr->GetCreatureData(spawnId))
|
||||||
|
{
|
||||||
|
LOG_ERROR("sql.sql", "Entry {} has a record in `creature_sparring` but doesn't exist in `creatures` table");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_creatureSparringStore[spawnId].push_back(sparringHealthPct);
|
||||||
|
|
||||||
|
++count;
|
||||||
|
} while (result->NextRow());
|
||||||
|
|
||||||
|
LOG_INFO("server.loading", ">> Loaded {} sparring data in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||||||
|
LOG_INFO("server.loading", " ");
|
||||||
|
}
|
||||||
|
|
||||||
void ObjectMgr::AddCreatureToGrid(ObjectGuid::LowType guid, CreatureData const* data)
|
void ObjectMgr::AddCreatureToGrid(ObjectGuid::LowType guid, CreatureData const* data)
|
||||||
{
|
{
|
||||||
uint8 mask = data->spawnMask;
|
uint8 mask = data->spawnMask;
|
||||||
|
|||||||
@@ -751,6 +751,8 @@ public:
|
|||||||
|
|
||||||
typedef std::map<uint32, uint32> CharacterConversionMap;
|
typedef std::map<uint32, uint32> CharacterConversionMap;
|
||||||
|
|
||||||
|
typedef std::unordered_map<ObjectGuid::LowType, std::vector<float>> CreatureSparringContainer;
|
||||||
|
|
||||||
GameObjectTemplate const* GetGameObjectTemplate(uint32 entry);
|
GameObjectTemplate const* GetGameObjectTemplate(uint32 entry);
|
||||||
bool IsGameObjectStaticTransport(uint32 entry);
|
bool IsGameObjectStaticTransport(uint32 entry);
|
||||||
[[nodiscard]] GameObjectTemplateContainer const* GetGameObjectTemplates() const { return &_gameObjectTemplateStore; }
|
[[nodiscard]] GameObjectTemplateContainer const* GetGameObjectTemplates() const { return &_gameObjectTemplateStore; }
|
||||||
@@ -1028,6 +1030,7 @@ public:
|
|||||||
void LoadCreatureQuestItems();
|
void LoadCreatureQuestItems();
|
||||||
void LoadTempSummons();
|
void LoadTempSummons();
|
||||||
void LoadCreatures();
|
void LoadCreatures();
|
||||||
|
void LoadCreatureSparring();
|
||||||
void LoadLinkedRespawn();
|
void LoadLinkedRespawn();
|
||||||
bool SetCreatureLinkedRespawn(ObjectGuid::LowType guid, ObjectGuid::LowType linkedGuid);
|
bool SetCreatureLinkedRespawn(ObjectGuid::LowType guid, ObjectGuid::LowType linkedGuid);
|
||||||
void LoadCreatureAddons();
|
void LoadCreatureAddons();
|
||||||
@@ -1201,6 +1204,9 @@ public:
|
|||||||
if (itr == _creatureDataStore.end()) return nullptr;
|
if (itr == _creatureDataStore.end()) return nullptr;
|
||||||
return &itr->second;
|
return &itr->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] CreatureSparringContainer const& GetSparringData() const { return _creatureSparringStore; }
|
||||||
|
|
||||||
CreatureData& NewOrExistCreatureData(ObjectGuid::LowType spawnId) { return _creatureDataStore[spawnId]; }
|
CreatureData& NewOrExistCreatureData(ObjectGuid::LowType spawnId) { return _creatureDataStore[spawnId]; }
|
||||||
void DeleteCreatureData(ObjectGuid::LowType spawnId);
|
void DeleteCreatureData(ObjectGuid::LowType spawnId);
|
||||||
[[nodiscard]] ObjectGuid GetLinkedRespawnGuid(ObjectGuid guid) const
|
[[nodiscard]] ObjectGuid GetLinkedRespawnGuid(ObjectGuid guid) const
|
||||||
@@ -1526,6 +1532,8 @@ private:
|
|||||||
PageTextContainer _pageTextStore;
|
PageTextContainer _pageTextStore;
|
||||||
InstanceTemplateContainer _instanceTemplateStore;
|
InstanceTemplateContainer _instanceTemplateStore;
|
||||||
|
|
||||||
|
CreatureSparringContainer _creatureSparringStore;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void LoadScripts(ScriptsType type);
|
void LoadScripts(ScriptsType type);
|
||||||
void LoadQuestRelationsHelper(QuestRelations& map, std::string const& table, bool starter, bool go);
|
void LoadQuestRelationsHelper(QuestRelations& map, std::string const& table, bool starter, bool go);
|
||||||
|
|||||||
@@ -1760,6 +1760,9 @@ void World::SetInitialWorldSettings()
|
|||||||
LOG_INFO("server.loading", "Loading Creature Data...");
|
LOG_INFO("server.loading", "Loading Creature Data...");
|
||||||
sObjectMgr->LoadCreatures();
|
sObjectMgr->LoadCreatures();
|
||||||
|
|
||||||
|
LOG_INFO("server.loading", "Loading Creature sparring...");
|
||||||
|
sObjectMgr->LoadCreatureSparring();
|
||||||
|
|
||||||
LOG_INFO("server.loading", "Loading Temporary Summon Data...");
|
LOG_INFO("server.loading", "Loading Temporary Summon Data...");
|
||||||
sObjectMgr->LoadTempSummons(); // must be after LoadCreatureTemplates() and LoadGameObjectTemplates()
|
sObjectMgr->LoadTempSummons(); // must be after LoadCreatureTemplates() and LoadGameObjectTemplates()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user