diff --git a/src/MHServerEmu.Games/Entities/WorldEntity.Procs.cs b/src/MHServerEmu.Games/Entities/WorldEntity.Procs.cs index 743fa09e4..413666719 100644 --- a/src/MHServerEmu.Games/Entities/WorldEntity.Procs.cs +++ b/src/MHServerEmu.Games/Entities/WorldEntity.Procs.cs @@ -127,6 +127,12 @@ public virtual void TryActivateOnHitProcs(ProcTriggerType triggerType, PowerResu // Copy proc properties to a temporary collection for iteration using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) + { + // No proc properties - still need to handle cancel-on-proc-trigger conditions + ConditionCollection?.RemoveCancelOnProcTriggerConditions(triggerType); + return; + } // Non-keyword procs foreach (var kvp in procProperties.IteratePropertyRange(PropertyEnum.Proc, (int)triggerType)) @@ -247,6 +253,12 @@ public void TryActivateOnCollideProcs(ProcTriggerType triggerType, WorldEntity t Vector3 targetPosition = target != null ? target.RegionLocation.Position : collisionPosition; using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) + { + ConditionCollection?.RemoveCancelOnProcTriggerConditions(triggerType); + return; + } + foreach (var kvp in procProperties.IteratePropertyRange(PropertyEnum.Proc, (int)triggerType)) { if (CheckProc(kvp, out Power procPower) == false) @@ -273,6 +285,8 @@ public void TryActivateOnConditionEndProcs(Condition condition) // 8 return; using PropertyCollection procProperties = GetProcProperties(condition.Properties); + if (procProperties == null) + return; // Run common proc logic TryActivateProcsCommon(ProcTriggerType.OnConditionEnd, procProperties); @@ -310,6 +324,9 @@ public void TryActivateOnConditionStackCountProcs(Condition condition) // 9 return; using PropertyCollection procProperties = GetProcProperties(condition.Properties); + if (procProperties == null) + return; + foreach (var kvp in procProperties.IteratePropertyRange(PropertyEnum.Proc, (int)ProcTriggerType.OnConditionStackCount)) { if (CheckProc(kvp, out Power procPower, param) == false) @@ -342,6 +359,12 @@ public void TryActivateOnDeathProcs(PowerResults powerResults) // 12 } using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) + { + ConditionCollection?.RemoveCancelOnProcTriggerConditions(ProcTriggerType.OnDeath); + return; + } + foreach (var kvp in procProperties.IteratePropertyRange(PropertyEnum.Proc, (int)ProcTriggerType.OnDeath)) { if (CheckProc(kvp, out Power procPower) == false) @@ -410,6 +433,9 @@ public void TryActivateOnEnduranceProcs(ManaType manaType) // 14-15 return; using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) + return; + foreach (var kvp in procProperties) { Property.FromParam(kvp.Key, 0, out int triggerTypeValue); @@ -455,6 +481,9 @@ public void TryActivateOnGotAttackedProcs(PowerResults powerResults) // 16 WorldEntity target = Game.EntityManager.GetEntity(powerResults.UltimateOwnerId); using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) + return; + foreach (var kvp in procProperties.IteratePropertyRange(PropertyEnum.Proc, (int)ProcTriggerType.OnGotAttacked)) { if (CheckProc(kvp, out Power procPower) == false) @@ -521,6 +550,11 @@ public void TryActivateOnGotDamagedProcs(ProcTriggerType triggerType, PowerResul }; using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) + { + ConditionCollection?.RemoveCancelOnProcTriggerConditions(triggerType); + return; + } // Non-keyworded procs foreach (var kvp in procProperties.IteratePropertyRange(PropertyEnum.Proc, (int)triggerType)) @@ -602,6 +636,9 @@ public bool TryActivateOnHealthProcs(PrototypeId procPowerProtoRef = PrototypeId int param = (int)(MathHelper.Ratio(Properties[PropertyEnum.Health], healthMax) * 100f); using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) + return true; + foreach (var kvp in procProperties.IteratePropertyRange(PropertyEnum.Proc)) { Property.FromParam(kvp.Key, 0, out int triggerTypeValue); @@ -682,6 +719,7 @@ public bool TryActivateOnHealthProcs(PrototypeId procPowerProtoRef = PrototypeId public void TryActivateOnInCombatProcs() // 32 { using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) return; TryActivateProcsCommon(ProcTriggerType.OnInCombat, procProperties); } @@ -694,6 +732,12 @@ public void TryActivateOnInteractedWithProcs(ProcTriggerType triggerType, WorldE return; using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) + { + ConditionCollection?.RemoveCancelOnProcTriggerConditions(triggerType); + return; + } + foreach (var kvp in procProperties.IteratePropertyRange(PropertyEnum.Proc, (int)triggerType)) { if (CheckProc(kvp, out Power procPower) == false) @@ -742,6 +786,11 @@ public virtual void TryActivateOnKillProcs(ProcTriggerType triggerType, PowerRes return; using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) + { + ConditionCollection?.RemoveCancelOnProcTriggerConditions(triggerType); + return; + } // Non-keyworded procs foreach (var kvp in procProperties.IteratePropertyRange(PropertyEnum.Proc, (int)triggerType)) @@ -845,31 +894,40 @@ public virtual void TryActivateOnKillProcs(ProcTriggerType triggerType, PowerRes public void TryActivateOnKnockdownEndProcs() // 40 { using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) return; TryActivateProcsCommon(ProcTriggerType.OnKnockdownEnd, procProperties); } public void TryActivateOnLifespanExpiredProcs() // 41 { using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) return; TryActivateProcsCommon(ProcTriggerType.OnLifespanExpired, procProperties); } public void TryActivateOnLootPickupProcs(WorldEntity item) // 42 { using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) return; TryActivateProcsCommon(ProcTriggerType.OnLootPickup, procProperties, item); } public void TryActivateOnMissileAbsorbedProcs() // 43 { using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) return; TryActivateProcsCommon(ProcTriggerType.OnMissileAbsorbed, procProperties); } public void TryActivateOnMovementStartedProcs() // 44 { using PropertyCollection procProperties = GetProcProperties(Properties); - + if (procProperties == null) + { + ConditionCollection?.RemoveCancelOnProcTriggerConditions(ProcTriggerType.OnMovementStarted); + return; + } + foreach (var kvp in procProperties.IteratePropertyRange(PropertyEnum.Proc, (int)ProcTriggerType.OnMovementStarted)) { if (CheckProc(kvp, out Power procPower) == false) @@ -893,6 +951,11 @@ public void TryActivateOnMovementStartedProcs() // 44 public void TryActivateOnMovementStoppedProcs() // 45 { using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) + { + ConditionCollection?.RemoveCancelOnProcTriggerConditions(ProcTriggerType.OnMovementStopped); + return; + } foreach (var kvp in procProperties.IteratePropertyRange(PropertyEnum.Proc, (int)ProcTriggerType.OnMovementStopped)) { @@ -917,12 +980,14 @@ public void TryActivateOnMovementStoppedProcs() // 45 public void TryActivateOnNegStatusAppliedProcs() // 46 { using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) return; TryActivateProcsCommon(ProcTriggerType.OnNegStatusApplied, procProperties); } public void TryActivateOnOrbPickupProcs(Agent orb) // 47 { using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) return; // Non-keyworded procs TryActivateProcsCommon(ProcTriggerType.OnOrbPickup, procProperties); @@ -953,6 +1018,7 @@ public void TryActivateOnOrbPickupProcs(Agent orb) // 47 public void TryActivateOnOutCombatProcs() // 48 { using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) return; TryActivateProcsCommon(ProcTriggerType.OnOutCombat, procProperties); } @@ -962,6 +1028,12 @@ public void TryActivateOnOverlapBeginProcs(WorldEntity target, Vector3 overlapPo return; using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) + { + ConditionCollection?.RemoveCancelOnProcTriggerConditions(ProcTriggerType.OnOverlapBegin); + return; + } + foreach (var kvp in procProperties.IteratePropertyRange(PropertyEnum.Proc, (int)ProcTriggerType.OnOverlapBegin)) TryActivateOnOverlapBeginProcHelper(kvp, target, overlapPosition); @@ -1015,6 +1087,7 @@ private void TryActivateOnOverlapBeginProcHelper(in KeyValuePair oldPips) TryActivateProcsCommon(ProcTriggerType.OnSecondaryResourcePipGain, procProperties); @@ -1218,12 +1304,14 @@ public void TryActivateOnSecondaryResourcePipsChangeProcs(int newPips, int oldPi public void TryActivateOnSkillshotReflectProcs() // 69 { using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) return; TryActivateProcsCommon(ProcTriggerType.OnSkillshotReflect, procProperties); } public void TryActivateOnSummonPetProcs(WorldEntity pet) // 70 { using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) return; // Non-keyworded procs TryActivateProcsCommon(ProcTriggerType.OnSummonPet, procProperties); @@ -1255,7 +1343,8 @@ public void TryActivateOnMissileHitProcs(Power power, WorldEntity target) // 7 { float procChanceMultiplier = power.Prototype.OnHitProcChanceMultiplier; - using PropertyCollection procProperties = ObjectPoolManager.Instance.Get(); + using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) return; // Non-keyworded procs TryActivateProcsCommon(ProcTriggerType.OnMissileHit, procProperties, null, procChanceMultiplier); @@ -1293,6 +1382,12 @@ public virtual void TryActivateOnHotspotNegatedProcs(WorldEntity other) // 73 return; using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) + { + ConditionCollection?.RemoveCancelOnProcTriggerConditions(ProcTriggerType.OnHotspotNegated); + return; + } + foreach (var kvp in procProperties.IteratePropertyRange(PropertyEnum.Proc, (int)ProcTriggerType.OnHotspotNegated)) { if (CheckProc(kvp, out Power procPower) == false) @@ -1323,6 +1418,11 @@ public void TryActivateOnControlledEntityReleasedProcs(WorldEntity controller) return; using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) + { + ConditionCollection?.RemoveCancelOnProcTriggerConditions(ProcTriggerType.OnControlledEntityReleased); + return; + } // Non-keyworded procs foreach (var kvp in procProperties.IteratePropertyRange(PropertyEnum.Proc, (int)ProcTriggerType.OnControlledEntityReleased)) @@ -1410,6 +1510,11 @@ private void TryActivateOnBlockOrDodgeProcHelper(ProcTriggerType triggerType, Po return; using PropertyCollection procProperties = GetProcProperties(Properties); + if (procProperties == null) + { + ConditionCollection?.RemoveCancelOnProcTriggerConditions(triggerType); + return; + } // Non-keyworded procs foreach (var kvp in procProperties.IteratePropertyRange(PropertyEnum.Proc, (int)triggerType)) @@ -1733,6 +1838,10 @@ private Power GetProcPower(in KeyValuePair procProper private static PropertyCollection GetProcProperties(PropertyCollection source) { + // Early exit: check if the source has any proc properties before allocating and copying. + if (source.HasPropertyInRange(Property.ProcPropertyTypesAll) == false) + return null; + PropertyCollection destination = ObjectPoolManager.Instance.Get(); foreach (PropertyEnum propertyEnum in Property.ProcPropertyTypesAll) diff --git a/src/MHServerEmu.Games/Entities/WorldEntity.cs b/src/MHServerEmu.Games/Entities/WorldEntity.cs index 301e8b503..5ffd22a54 100644 --- a/src/MHServerEmu.Games/Entities/WorldEntity.cs +++ b/src/MHServerEmu.Games/Entities/WorldEntity.cs @@ -1696,6 +1696,9 @@ public bool UpdateProcEffectPowers(PropertyCollection properties, bool assignPow EntityManager entityManager = Game.EntityManager; using PropertyCollection procProperties = GetProcProperties(properties); + if (procProperties == null) + return false; + foreach (var kvp in procProperties.IteratePropertyRange(Property.ProcPropertyTypesAll)) { Property.FromParam(kvp.Key, 1, out PrototypeId procPowerProtoRef); diff --git a/src/MHServerEmu.Games/Properties/PropertyCollection.cs b/src/MHServerEmu.Games/Properties/PropertyCollection.cs index 7cb4205ff..a405de5f9 100644 --- a/src/MHServerEmu.Games/Properties/PropertyCollection.cs +++ b/src/MHServerEmu.Games/Properties/PropertyCollection.cs @@ -1484,5 +1484,36 @@ public CurveProperty(PropertyId propertyId, PropertyId indexPropertyId, CurveId CurveId = curveId; } } + + /// + /// Returns if this contains any properties + /// with a matching any value in the provided . + /// + public bool HasPropertyInRange(ReadOnlySpan propertyEnums) + { + foreach (PropertyEnum propertyEnum in propertyEnums) + { + if (HasPropertyInRange(propertyEnum)) + return true; + } + + return false; + } + + /// + /// Returns if this contains any properties + /// with the specified . + /// + public bool HasPropertyInRange(PropertyEnum propertyEnum) + { + // If IteratePropertyRange for this enum would yield any results, return true. + // This is equivalent to checking if the iterator is non-empty, but allows + // implementations to use a more efficient check if available (e.g. checking + // a bucket or index rather than iterating). + foreach (var kvp in IteratePropertyRange(propertyEnum)) + return true; + + return false; + } } }