Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 111 additions & 2 deletions src/MHServerEmu.Games/Entities/WorldEntity.Procs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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)
Expand All @@ -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);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -455,6 +481,9 @@ public void TryActivateOnGotAttackedProcs(PowerResults powerResults) // 16
WorldEntity target = Game.EntityManager.GetEntity<WorldEntity>(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)
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}

Expand All @@ -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)
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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)
Expand All @@ -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))
{
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}

Expand All @@ -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);

Expand Down Expand Up @@ -1015,6 +1087,7 @@ private void TryActivateOnOverlapBeginProcHelper(in KeyValuePair<PropertyId, Pro
public void TryActivateOnPetDeathProcs(WorldEntity pet) // 50
{
using PropertyCollection procProperties = GetProcProperties(Properties);
if (procProperties == null) return;

// Non-keyworded procs
TryActivateProcsCommon(ProcTriggerType.OnPetDeath, procProperties);
Expand Down Expand Up @@ -1056,6 +1129,11 @@ public void TryActivateOnPetHitProcs(PowerResults powerResults, WorldEntity pet)
float procChanceMultiplier = powerProto.OnHitProcChanceMultiplier;

using PropertyCollection procProperties = GetProcProperties(Properties);
if (procProperties == null)
{
ConditionCollection?.RemoveCancelOnProcTriggerConditions(ProcTriggerType.OnPetHit);
return;
}

// Non-keyworded procs
foreach (var kvp in procProperties.IteratePropertyRange(Property.ProcPropertyTypesAll))
Expand Down Expand Up @@ -1119,6 +1197,11 @@ public void TryActivateOnPowerUseProcs(ProcTriggerType triggerType, Power onPowe
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))
Expand Down Expand Up @@ -1182,6 +1265,7 @@ public void TryActivateOnPowerUseProcs(ProcTriggerType triggerType, Power onPowe
public void TryActivateOnRunestonePickupProcs()
{
using PropertyCollection procProperties = GetProcProperties(Properties);
if (procProperties == null) return;
TryActivateProcsCommon(ProcTriggerType.OnRunestonePickup, procProperties);
}

Expand All @@ -1193,6 +1277,7 @@ public void TryActivateOnSecondaryResourceValueChangeProcs(float newValue) // 6
if (newValue == 0f)
{
using PropertyCollection procProperties = GetProcProperties(Properties);
if (procProperties == null) return;
TryActivateProcsCommon(ProcTriggerType.OnSecondaryResourceEmpty, procProperties);
}
}
Expand All @@ -1203,6 +1288,7 @@ public void TryActivateOnSecondaryResourcePipsChangeProcs(int newPips, int oldPi
return;

using PropertyCollection procProperties = GetProcProperties(Properties);
if (procProperties == null) return;

if (newPips > oldPips)
TryActivateProcsCommon(ProcTriggerType.OnSecondaryResourcePipGain, procProperties);
Expand All @@ -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);
Expand Down Expand Up @@ -1255,7 +1343,8 @@ public void TryActivateOnMissileHitProcs(Power power, WorldEntity target) // 7
{
float procChanceMultiplier = power.Prototype.OnHitProcChanceMultiplier;

using PropertyCollection procProperties = ObjectPoolManager.Instance.Get<PropertyCollection>();
using PropertyCollection procProperties = GetProcProperties(Properties);
if (procProperties == null) return;

// Non-keyworded procs
TryActivateProcsCommon(ProcTriggerType.OnMissileHit, procProperties, null, procChanceMultiplier);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -1733,6 +1838,10 @@ private Power GetProcPower(in KeyValuePair<PropertyId, PropertyValue> 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<PropertyCollection>();

foreach (PropertyEnum propertyEnum in Property.ProcPropertyTypesAll)
Expand Down
3 changes: 3 additions & 0 deletions src/MHServerEmu.Games/Entities/WorldEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
31 changes: 31 additions & 0 deletions src/MHServerEmu.Games/Properties/PropertyCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1484,5 +1484,36 @@ public CurveProperty(PropertyId propertyId, PropertyId indexPropertyId, CurveId
CurveId = curveId;
}
}

/// <summary>
/// Returns <see langword="true"/> if this <see cref="PropertyCollection"/> contains any properties
/// with a <see cref="PropertyEnum"/> matching any value in the provided <see cref="ReadOnlySpan{T}"/>.
/// </summary>
public bool HasPropertyInRange(ReadOnlySpan<PropertyEnum> propertyEnums)
{
foreach (PropertyEnum propertyEnum in propertyEnums)
{
if (HasPropertyInRange(propertyEnum))
return true;
}

return false;
}

/// <summary>
/// Returns <see langword="true"/> if this <see cref="PropertyCollection"/> contains any properties
/// with the specified <see cref="PropertyEnum"/>.
/// </summary>
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;
}
}
}