From bd2eb5baabd898c5bc86a222d4e181b755389d2e Mon Sep 17 00:00:00 2001 From: hondacrx Date: Tue, 25 Apr 2023 08:38:55 -0400 Subject: [PATCH] Core/Creature: Implemented sparring with max health percent thresholds Port From (https://github.com/TrinityCore/TrinityCore/commit/0750b7f8455df39a64462636ca296c6f2aa2b048) --- Source/Game/AI/CoreAI/UnitAI.cs | 2 - .../Game/Entities/Creature/Creature.Fields.cs | 1 + Source/Game/Entities/Creature/Creature.cs | 60 ++++++++++++++++++- Source/Game/Entities/Unit/Unit.Combat.cs | 9 +++ Source/Game/Entities/Unit/Unit.cs | 13 ++++ Source/Game/Globals/ObjectManager.cs | 47 +++++++++++++++ .../Game/Movement/Generators/HomeMovement.cs | 1 + Source/Game/Spells/Spell.cs | 5 ++ Source/Game/World/WorldManager.cs | 3 + 9 files changed, 138 insertions(+), 3 deletions(-) diff --git a/Source/Game/AI/CoreAI/UnitAI.cs b/Source/Game/AI/CoreAI/UnitAI.cs index 2eb61bb58..af909feb9 100644 --- a/Source/Game/AI/CoreAI/UnitAI.cs +++ b/Source/Game/AI/CoreAI/UnitAI.cs @@ -490,8 +490,6 @@ namespace Game.AI me.ScheduleAIChange(); } - public virtual bool ShouldSparWith(Unit target) { return false; } - public virtual void DoAction(int action) { } public virtual uint GetData(uint id = 0) { return 0; } public virtual void SetData(uint id, uint value) { } diff --git a/Source/Game/Entities/Creature/Creature.Fields.cs b/Source/Game/Entities/Creature/Creature.Fields.cs index 05b7886e5..2197ca4c3 100644 --- a/Source/Game/Entities/Creature/Creature.Fields.cs +++ b/Source/Game/Entities/Creature/Creature.Fields.cs @@ -30,6 +30,7 @@ namespace Game.Entities uint _gossipMenuId; uint? _trainerId; + float _sparringHealthPct; public ulong m_PlayerDamageReq; public float m_SightDistance; diff --git a/Source/Game/Entities/Creature/Creature.cs b/Source/Game/Entities/Creature/Creature.cs index 86c3e7916..fce3b0441 100644 --- a/Source/Game/Entities/Creature/Creature.cs +++ b/Source/Game/Entities/Creature/Creature.cs @@ -413,7 +413,7 @@ namespace Game.Entities InitializeMovementFlags(); LoadCreaturesAddon(); - + LoadCreaturesSparringHealth(); LoadTemplateImmunities(); GetThreatManager().EvaluateSuppressed(); @@ -855,7 +855,9 @@ namespace Game.Entities m_corpseDelay = WorldConfig.GetUIntValue(WorldCfg.CorpseDecayNormal); break; } + LoadCreaturesAddon(); + LoadCreaturesSparringHealth(); //! Need to be called after LoadCreaturesAddon - MOVEMENTFLAG_HOVER is set there posZ += GetHoverOffset(); @@ -1570,6 +1572,54 @@ namespace Game.Entities } } + float GetSparringHealthPct() { return _sparringHealthPct; } + + public void OverrideSparringHealthPct(List healthPct) + { + _sparringHealthPct = healthPct.SelectRandom(); + } + + public uint CalculateDamageForSparring(Unit attacker, uint damage) + { + if (GetSparringHealthPct() == 0) + return damage; + + if (!attacker.IsCreature() || attacker.IsCharmedOwnedByPlayerOrPlayer() || IsCharmedOwnedByPlayerOrPlayer()) + return damage; + + if (GetHealthPct() <= GetSparringHealthPct()) + return 0; + + uint sparringHealth = (uint)(GetMaxHealth() * GetSparringHealthPct() / 100); + if (GetHealth() - damage <= sparringHealth) + return (uint)(GetHealth() - sparringHealth); + + if (damage >= GetHealth()) + return (uint)(GetHealth() - 1); + + return damage; + } + + public bool ShouldFakeDamageFrom(Unit attacker) + { + if (GetSparringHealthPct() == 0) + return false; + + if (!attacker) + return false; + + if (!attacker.IsCreature()) + return false; + + if (attacker.IsCharmedOwnedByPlayerOrPlayer() || IsCharmedOwnedByPlayerOrPlayer()) + return false; + + if (GetHealthPct() > GetSparringHealthPct()) + return false; + + return true; + } + bool CreateFromProto(ulong guidlow, uint entry, CreatureData data = null, uint vehId = 0) { SetZoneScript(); @@ -1960,6 +2010,7 @@ namespace Game.Entities InitializeMovementAI(); base.SetDeathState(DeathState.Alive); LoadCreaturesAddon(); + LoadCreaturesSparringHealth(); } } @@ -2462,6 +2513,13 @@ namespace Game.Entities return true; } + public void LoadCreaturesSparringHealth() + { + var templateValues = Global.ObjectMgr.GetCreatureTemplateSparringValues(GetCreatureTemplate().Entry); + if (!templateValues.Empty()) + _sparringHealthPct = templateValues.SelectRandom(); + } + // Send a message to LocalDefense channel for players opposition team in the zone public void SendZoneUnderAttackMessage(Player attacker) { diff --git a/Source/Game/Entities/Unit/Unit.Combat.cs b/Source/Game/Entities/Unit/Unit.Combat.cs index c591473d5..66e40666a 100644 --- a/Source/Game/Entities/Unit/Unit.Combat.cs +++ b/Source/Game/Entities/Unit/Unit.Combat.cs @@ -564,6 +564,15 @@ namespace Game.Entities CalculateMeleeDamage(victim, out damageInfo, attType); // Send log damage message to client DealDamageMods(damageInfo.Attacker, victim, ref damageInfo.Damage, ref damageInfo.Absorb); + + // sparring + Creature victimCreature = victim.ToCreature(); + if (victimCreature != null) + { + if (victimCreature.ShouldFakeDamageFrom(damageInfo.Attacker)) + damageInfo.HitInfo |= HitInfo.FakeDamage; + } + SendAttackStateUpdate(damageInfo); _lastDamagedTargetGuid = victim.GetGUID(); diff --git a/Source/Game/Entities/Unit/Unit.cs b/Source/Game/Entities/Unit/Unit.cs index 6db787721..3df7b8634 100644 --- a/Source/Game/Entities/Unit/Unit.cs +++ b/Source/Game/Entities/Unit/Unit.cs @@ -2489,6 +2489,11 @@ namespace Game.Entities { uint tmpDamage = damageTaken; + // sparring + Creature victimCreature = victim.ToCreature(); + if (victimCreature != null) + tmpDamage = victimCreature.CalculateDamageForSparring(attacker, tmpDamage); + victim.GetAI()?.DamageTaken(attacker, ref tmpDamage, damagetype, spellProto); attacker?.GetAI()?.DamageDealt(victim, ref tmpDamage, damagetype); @@ -3652,6 +3657,14 @@ namespace Game.Entities uint split_absorb = 0; DealDamageMods(damageInfo.GetAttacker(), caster, ref splitDamage, ref split_absorb); + // sparring + Creature victimCreature = damageInfo.GetVictim().ToCreature(); + if (victimCreature != null) + { + if (victimCreature.ShouldFakeDamageFrom(damageInfo.GetAttacker())) + damageInfo.ModifyDamage((int)(damageInfo.GetDamage() * -1)); + } + SpellNonMeleeDamage log = new(damageInfo.GetAttacker(), caster, itr.GetSpellInfo(), itr.GetBase().GetSpellVisual(), damageInfo.GetSchoolMask(), itr.GetBase().GetCastId()); CleanDamage cleanDamage = new(splitDamage, 0, WeaponAttackType.BaseAttack, MeleeHitOutcome.Normal); DealDamage(damageInfo.GetAttacker(), caster, splitDamage, cleanDamage, DamageEffectType.Direct, damageInfo.GetSchoolMask(), itr.GetSpellInfo(), false); diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs index 39e97a9d8..9be54d7b8 100644 --- a/Source/Game/Globals/ObjectManager.cs +++ b/Source/Game/Globals/ObjectManager.cs @@ -2691,6 +2691,44 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creature model based info in {1} ms", count, Time.GetMSTimeDiffToNow(time)); } + + public void LoadCreatureTemplateSparring() + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 + SQLResult result = DB.World.Query("SELECT Entry, NoNPCDamageBelowHealthPct FROM creature_template_sparring"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 creature template sparring definitions. DB table `creature_template_sparring` is empty."); + return; + } + + uint count = 0; + do + { + uint entry = result.Read(0); + float noNPCDamageBelowHealthPct = result.Read(1); + + if (GetCreatureTemplate(entry) == null) + { + Log.outError(LogFilter.Sql, $"Creature template (Entry: {entry}) does not exist but has a record in `creature_template_sparring`"); + continue; + } + + if (noNPCDamageBelowHealthPct <= 0 || noNPCDamageBelowHealthPct > 100) + { + Log.outError(LogFilter.Sql, $"Creature (Entry: {entry}) has invalid NoNPCDamageBelowHealthPct ({noNPCDamageBelowHealthPct}) defined in `creature_template_sparring`. Skipping"); + continue; + } + _creatureTemplateSparringStorage.Add(entry, noNPCDamageBelowHealthPct); + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} creature template sparring rows in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); + } + public void LoadCreatureScalingData() { uint oldMSTime = Time.GetMSTime(); @@ -5345,14 +5383,22 @@ namespace Game return true; } + public VendorItemData GetNpcVendorItemList(uint entry) { return cacheVendorItemStorage.LookupByKey(entry); } + + public List GetCreatureTemplateSparringValues(uint entry) + { + return _creatureTemplateSparringStorage.LookupByKey(entry); + } + public CreatureMovementData GetCreatureMovementOverride(ulong spawnId) { return creatureMovementOverrides.LookupByKey(spawnId); } + public EquipmentInfo GetEquipmentInfo(uint entry, int id) { var equip = equipmentInfoStorage.LookupByKey(entry); @@ -11022,6 +11068,7 @@ namespace Game Dictionary creatureAddonStorage = new(); MultiMap creatureQuestItemStorage = new(); Dictionary creatureTemplateAddonStorage = new(); + MultiMap _creatureTemplateSparringStorage = new(); Dictionary creatureMovementOverrides = new(); MultiMap> equipmentInfoStorage = new(); Dictionary linkedRespawnStorage = new(); diff --git a/Source/Game/Movement/Generators/HomeMovement.cs b/Source/Game/Movement/Generators/HomeMovement.cs index 0991b1e09..355c045db 100644 --- a/Source/Game/Movement/Generators/HomeMovement.cs +++ b/Source/Game/Movement/Generators/HomeMovement.cs @@ -65,6 +65,7 @@ namespace Game.AI owner.SetSpawnHealth(); owner.LoadCreaturesAddon(); + owner.LoadCreaturesSparringHealth(); if (owner.IsVehicle()) owner.GetVehicleKit().Reset(true); diff --git a/Source/Game/Spells/Spell.cs b/Source/Game/Spells/Spell.cs index 57e719af3..78b20c26c 100644 --- a/Source/Game/Spells/Spell.cs +++ b/Source/Game/Spells/Spell.cs @@ -8475,6 +8475,11 @@ namespace Game.Spells spell.m_damage = (int)damageInfo.damage; + // sparring + Creature victimCreature = damageInfo.target.ToCreature(); + if (victimCreature != null) + damageInfo.damage = victimCreature.CalculateDamageForSparring(damageInfo.attacker, damageInfo.damage); + caster.DealSpellDamage(damageInfo, true); // Send log damage message to client diff --git a/Source/Game/World/WorldManager.cs b/Source/Game/World/WorldManager.cs index 8fb99563d..6b2172f33 100644 --- a/Source/Game/World/WorldManager.cs +++ b/Source/Game/World/WorldManager.cs @@ -606,6 +606,9 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loading Creature template addons..."); Global.ObjectMgr.LoadCreatureTemplateAddons(); + Log.outInfo(LogFilter.ServerLoading, "Loading Creature template sparring..."); + Global.ObjectMgr.LoadCreatureTemplateSparring(); + Log.outInfo(LogFilter.ServerLoading, "Loading Creature template scaling..."); Global.ObjectMgr.LoadCreatureScalingData();