From 5fe42912de73c39dbeec1abc0db8f0e904630234 Mon Sep 17 00:00:00 2001
From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com>
Date: Thu, 15 May 2025 19:39:58 -0700
Subject: [PATCH] fix: Cleans up potential memory leak in monster abilities.
---
.../UOContent/Engines/Virtues/HonorContext.cs | 1 +
.../Mobiles/Abilities/MonsterAbility.cs | 52 ++++++++++++-------
.../Mobiles/Abilities/MonsterAbilityGroup.cs | 14 +++--
.../Abilities/ReflectPhysicalDamage.cs | 4 ++
Projects/UOContent/Mobiles/BaseCreature.cs | 15 ++++--
5 files changed, 53 insertions(+), 33 deletions(-)
diff --git a/Projects/UOContent/Engines/Virtues/HonorContext.cs b/Projects/UOContent/Engines/Virtues/HonorContext.cs
index 8e2ba1a13..85ebfa8a2 100644
--- a/Projects/UOContent/Engines/Virtues/HonorContext.cs
+++ b/Projects/UOContent/Engines/Virtues/HonorContext.cs
@@ -1,4 +1,5 @@
using System;
+using ModernUO.CodeGeneratedEvents;
using Server.Mobiles;
namespace Server.Engines.Virtues;
diff --git a/Projects/UOContent/Mobiles/Abilities/MonsterAbility.cs b/Projects/UOContent/Mobiles/Abilities/MonsterAbility.cs
index 72fb61ec3..fc227507e 100644
--- a/Projects/UOContent/Mobiles/Abilities/MonsterAbility.cs
+++ b/Projects/UOContent/Mobiles/Abilities/MonsterAbility.cs
@@ -1,12 +1,13 @@
using System;
using System.Collections.Generic;
+using ModernUO.CodeGeneratedEvents;
namespace Server.Mobiles;
///
/// Abstract class used to build singletons for managing a specific monster ability.
///
-public abstract partial class MonsterAbility
+public abstract class MonsterAbility
{
private Dictionary _nextTriggerTicks;
@@ -18,39 +19,34 @@ public abstract partial class MonsterAbility
public virtual TimeSpan MinTriggerCooldown => TimeSpan.Zero;
public virtual TimeSpan MaxTriggerCooldown => TimeSpan.Zero;
- public bool WillTrigger(MonsterAbilityTrigger trigger) => (AbilityTrigger & trigger) != 0;
-
///
/// Returns true if ability is not on cooldown, and the chance to trigger succeeds.
///
/// Boolean indicating the ability can trigger.
public virtual bool CanTrigger(BaseCreature source, MonsterAbilityTrigger trigger)
{
- if (source is not { Alive: true, Deleted: false })
+ if ((AbilityTrigger & trigger) == 0 || source is not { Alive: true, Deleted: false })
{
return false;
}
- if (_nextTriggerTicks?.TryGetValue(source, out var nextTrigger) == true && nextTrigger - Core.TickCount > 0)
+ if (_nextTriggerTicks?.TryGetValue(source, out var nextTrigger) == true)
{
- return false;
+ if (nextTrigger - Core.TickCount > 0)
+ {
+ return false;
+ }
+
+ _nextTriggerTicks.Remove(source);
+ if (_nextTriggerTicks.Count == 0)
+ {
+ _nextTriggerTicks = null;
+ }
}
var c = ChanceToTrigger;
- if (c >= 1)
- {
- return true;
- }
-
- if (c <= 0)
- {
- return false;
- }
-
- var rnd = Utility.RandomDouble();
-
- return c > rnd;
+ return c >= 1 || c > 0 && c > Utility.RandomDouble();
}
///
@@ -99,7 +95,23 @@ public abstract partial class MonsterAbility
{
}
- public virtual void Move(BaseCreature creature, Direction d)
+ public virtual void Move(BaseCreature source, Direction d)
{
}
+
+ [OnEvent(nameof(BaseCreature.CreatureDeathEvent))]
+ [OnEvent(nameof(BaseCreature.CreatureDeletedEvent))]
+ public static void InvalidateNextAbilityTriggers(BaseCreature source)
+ {
+ var abilities = source.GetMonsterAbilities();
+ if (abilities == null || abilities.Length == 0)
+ {
+ return;
+ }
+
+ for (var i = 0; i < abilities.Length; i++)
+ {
+ abilities[i]._nextTriggerTicks?.Remove(source);
+ }
+ }
}
diff --git a/Projects/UOContent/Mobiles/Abilities/MonsterAbilityGroup.cs b/Projects/UOContent/Mobiles/Abilities/MonsterAbilityGroup.cs
index c431d4d35..6555a3682 100644
--- a/Projects/UOContent/Mobiles/Abilities/MonsterAbilityGroup.cs
+++ b/Projects/UOContent/Mobiles/Abilities/MonsterAbilityGroup.cs
@@ -1,15 +1,14 @@
using System;
-using Server.Random;
using WeightedMonsterAbility = Server.Random.WeightedValue;
namespace Server.Mobiles;
public class MonsterAbilityGroup : MonsterAbility
{
- private WeightedMonsterAbility[] _weightedAbilities;
- private WeightedMonsterAbility[] _availableToTrigger;
+ private readonly WeightedMonsterAbility[] _weightedAbilities;
+ private readonly WeightedMonsterAbility[] _availableToTrigger;
+ private readonly MonsterAbilityTrigger _triggers;
private int _availableToTriggerCount;
- private MonsterAbilityTrigger _triggers;
public MonsterAbilityGroup(params WeightedMonsterAbility[] weightedAbilities)
{
@@ -18,8 +17,7 @@ public class MonsterAbilityGroup : MonsterAbility
for (var i = 0; i < _weightedAbilities.Length; i++)
{
- var weightedAbility = _weightedAbilities[i];
- _triggers |= weightedAbility.Value.AbilityTrigger;
+ _triggers |= _weightedAbilities[i].Value.AbilityTrigger;
}
}
@@ -66,7 +64,7 @@ public class MonsterAbilityGroup : MonsterAbility
for (var i = 0; i < _weightedAbilities.Length; i++)
{
var weightedAbility = _weightedAbilities[i];
- if (weightedAbility.Value.WillTrigger(trigger) && weightedAbility.Value.CanTrigger(source, trigger))
+ if (weightedAbility.Value.CanTrigger(source, trigger))
{
_availableToTrigger[_availableToTriggerCount++] = weightedAbility;
}
@@ -83,7 +81,7 @@ public class MonsterAbilityGroup : MonsterAbility
return;
}
- var slice = new ReadOnlySpan>(_availableToTrigger, 0, _availableToTriggerCount);
+ var slice = new ReadOnlySpan(_availableToTrigger, 0, _availableToTriggerCount);
var chosenAbility = slice.RandomWeightedElement().Value;
// Just in case?
diff --git a/Projects/UOContent/Mobiles/Abilities/ReflectPhysicalDamage.cs b/Projects/UOContent/Mobiles/Abilities/ReflectPhysicalDamage.cs
index ae66734ba..3aa48960f 100644
--- a/Projects/UOContent/Mobiles/Abilities/ReflectPhysicalDamage.cs
+++ b/Projects/UOContent/Mobiles/Abilities/ReflectPhysicalDamage.cs
@@ -8,4 +8,8 @@ public class ReflectPhysicalDamage : MonsterAbility
public override MonsterAbilityType AbilityType => MonsterAbilityType.ReflectPhysicalDamage;
public virtual int PercentReflected => 10;
+
+ public override void Trigger(MonsterAbilityTrigger trigger, BaseCreature source, Mobile target)
+ {
+ }
}
diff --git a/Projects/UOContent/Mobiles/BaseCreature.cs b/Projects/UOContent/Mobiles/BaseCreature.cs
index ed37b7be1..22c43ebc4 100644
--- a/Projects/UOContent/Mobiles/BaseCreature.cs
+++ b/Projects/UOContent/Mobiles/BaseCreature.cs
@@ -1216,7 +1216,7 @@ namespace Server.Mobiles
for (var i = 0; i < abilities.Length; i++)
{
var ability = abilities[i];
- if (ability.WillTrigger(trigger) && ability.CanTrigger(this, trigger))
+ if (ability.CanTrigger(this, trigger))
{
ability.Trigger(trigger, this, defender);
triggered = true;
@@ -1238,7 +1238,7 @@ namespace Server.Mobiles
for (var i = 0; i < abilities.Length; i++)
{
var ability = abilities[i];
- if (ability.WillTrigger(trigger) && ability.CanTrigger(this, trigger))
+ if (ability.CanTrigger(this, trigger))
{
ability.Move(this, d);
}
@@ -1257,7 +1257,7 @@ namespace Server.Mobiles
for (var i = 0; i < abilities.Length; i++)
{
var ability = abilities[i];
- if (ability.WillTrigger(trigger) && ability.CanTrigger(this, trigger))
+ if (ability.CanTrigger(this, trigger))
{
if ((trigger & MonsterAbilityTrigger.GiveMeleeDamage) != 0)
{
@@ -1298,7 +1298,7 @@ namespace Server.Mobiles
for (var i = 0; i < abilities.Length; i++)
{
var ability = abilities[i];
- if (ability.WillTrigger(trigger) && ability.CanTrigger(this, trigger))
+ if (ability.CanTrigger(this, trigger))
{
if ((trigger & MonsterAbilityTrigger.GiveSpellDamage) != 0)
{
@@ -3263,7 +3263,7 @@ namespace Server.Mobiles
}
[GeneratedEvent(nameof(CreatureDeathEvent))]
- public static partial void CreatureDeathEvent(Mobile m);
+ public static partial void CreatureDeathEvent(BaseCreature bc);
public override void OnDeath(Container c)
{
@@ -3454,8 +3454,13 @@ namespace Server.Mobiles
CreatureDeathEvent(this);
}
+ [GeneratedEvent(nameof(CreatureDeletedEvent))]
+ public static partial void CreatureDeletedEvent(BaseCreature bc);
+
public override void OnDelete()
{
+ CreatureDeletedEvent(this);
+
var m = m_ControlMaster;
SetControlMaster(null);