fix: Cleans up potential memory leak in monster abilities. (#2185)

This commit is contained in:
Kamron Batman 2025-05-15 19:42:54 -07:00 • committed by GitHub
parent f2526c82f3
commit 91b2912ee4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 52 additions and 33 deletions

View file

@ -1,12 +1,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using ModernUO.CodeGeneratedEvents;
namespace Server.Mobiles; namespace Server.Mobiles;
/// <summary> /// <summary>
/// Abstract class used to build singletons for managing a specific monster ability. /// Abstract class used to build singletons for managing a specific monster ability.
/// </summary> /// </summary>
public abstract partial class MonsterAbility public abstract class MonsterAbility
{ {
private Dictionary<BaseCreature, long> _nextTriggerTicks; private Dictionary<BaseCreature, long> _nextTriggerTicks;
@ -18,39 +19,34 @@ public abstract partial class MonsterAbility
public virtual TimeSpan MinTriggerCooldown => TimeSpan.Zero; public virtual TimeSpan MinTriggerCooldown => TimeSpan.Zero;
public virtual TimeSpan MaxTriggerCooldown => TimeSpan.Zero; public virtual TimeSpan MaxTriggerCooldown => TimeSpan.Zero;
public bool WillTrigger(MonsterAbilityTrigger trigger) => (AbilityTrigger & trigger) != 0;
/// <summary> /// <summary>
/// Returns true if ability is not on cooldown, and the chance to trigger succeeds. /// Returns true if ability is not on cooldown, and the chance to trigger succeeds.
/// </summary> /// </summary>
/// <returns>Boolean indicating the ability can trigger.</returns> /// <returns>Boolean indicating the ability can trigger.</returns>
public virtual bool CanTrigger(BaseCreature source, MonsterAbilityTrigger 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; 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; var c = ChanceToTrigger;
if (c >= 1) return c >= 1 || c > 0 && c > Utility.RandomDouble();
{
return true;
}
if (c <= 0)
{
return false;
}
var rnd = Utility.RandomDouble();
return c > rnd;
} }
/// <summary> /// <summary>
@ -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);
}
}
} }

View file

@ -1,15 +1,14 @@
using System; using System;
using Server.Random;
using WeightedMonsterAbility = Server.Random.WeightedValue<Server.Mobiles.MonsterAbility>; using WeightedMonsterAbility = Server.Random.WeightedValue<Server.Mobiles.MonsterAbility>;
namespace Server.Mobiles; namespace Server.Mobiles;
public class MonsterAbilityGroup : MonsterAbility public class MonsterAbilityGroup : MonsterAbility
{ {
private WeightedMonsterAbility[] _weightedAbilities; private readonly WeightedMonsterAbility[] _weightedAbilities;
private WeightedMonsterAbility[] _availableToTrigger; private readonly WeightedMonsterAbility[] _availableToTrigger;
private readonly MonsterAbilityTrigger _triggers;
private int _availableToTriggerCount; private int _availableToTriggerCount;
private MonsterAbilityTrigger _triggers;
public MonsterAbilityGroup(params WeightedMonsterAbility[] weightedAbilities) public MonsterAbilityGroup(params WeightedMonsterAbility[] weightedAbilities)
{ {
@ -18,8 +17,7 @@ public class MonsterAbilityGroup : MonsterAbility
for (var i = 0; i < _weightedAbilities.Length; i++) for (var i = 0; i < _weightedAbilities.Length; i++)
{ {
var weightedAbility = _weightedAbilities[i]; _triggers |= _weightedAbilities[i].Value.AbilityTrigger;
_triggers |= weightedAbility.Value.AbilityTrigger;
} }
} }
@ -66,7 +64,7 @@ public class MonsterAbilityGroup : MonsterAbility
for (var i = 0; i < _weightedAbilities.Length; i++) for (var i = 0; i < _weightedAbilities.Length; i++)
{ {
var weightedAbility = _weightedAbilities[i]; var weightedAbility = _weightedAbilities[i];
if (weightedAbility.Value.WillTrigger(trigger) && weightedAbility.Value.CanTrigger(source, trigger)) if (weightedAbility.Value.CanTrigger(source, trigger))
{ {
_availableToTrigger[_availableToTriggerCount++] = weightedAbility; _availableToTrigger[_availableToTriggerCount++] = weightedAbility;
} }
@ -83,7 +81,7 @@ public class MonsterAbilityGroup : MonsterAbility
return; return;
} }
var slice = new ReadOnlySpan<WeightedValue<MonsterAbility>>(_availableToTrigger, 0, _availableToTriggerCount); var slice = new ReadOnlySpan<WeightedMonsterAbility>(_availableToTrigger, 0, _availableToTriggerCount);
var chosenAbility = slice.RandomWeightedElement().Value; var chosenAbility = slice.RandomWeightedElement().Value;
// Just in case? // Just in case?

View file

@ -8,4 +8,8 @@ public class ReflectPhysicalDamage : MonsterAbility
public override MonsterAbilityType AbilityType => MonsterAbilityType.ReflectPhysicalDamage; public override MonsterAbilityType AbilityType => MonsterAbilityType.ReflectPhysicalDamage;
public virtual int PercentReflected => 10; public virtual int PercentReflected => 10;
public override void Trigger(MonsterAbilityTrigger trigger, BaseCreature source, Mobile target)
{
}
} }

View file

@ -1216,7 +1216,7 @@ namespace Server.Mobiles
for (var i = 0; i < abilities.Length; i++) for (var i = 0; i < abilities.Length; i++)
{ {
var ability = abilities[i]; var ability = abilities[i];
if (ability.WillTrigger(trigger) && ability.CanTrigger(this, trigger)) if (ability.CanTrigger(this, trigger))
{ {
ability.Trigger(trigger, this, defender); ability.Trigger(trigger, this, defender);
triggered = true; triggered = true;
@ -1238,7 +1238,7 @@ namespace Server.Mobiles
for (var i = 0; i < abilities.Length; i++) for (var i = 0; i < abilities.Length; i++)
{ {
var ability = abilities[i]; var ability = abilities[i];
if (ability.WillTrigger(trigger) && ability.CanTrigger(this, trigger)) if (ability.CanTrigger(this, trigger))
{ {
ability.Move(this, d); ability.Move(this, d);
} }
@ -1257,7 +1257,7 @@ namespace Server.Mobiles
for (var i = 0; i < abilities.Length; i++) for (var i = 0; i < abilities.Length; i++)
{ {
var ability = abilities[i]; var ability = abilities[i];
if (ability.WillTrigger(trigger) && ability.CanTrigger(this, trigger)) if (ability.CanTrigger(this, trigger))
{ {
if ((trigger & MonsterAbilityTrigger.GiveMeleeDamage) != 0) if ((trigger & MonsterAbilityTrigger.GiveMeleeDamage) != 0)
{ {
@ -1298,7 +1298,7 @@ namespace Server.Mobiles
for (var i = 0; i < abilities.Length; i++) for (var i = 0; i < abilities.Length; i++)
{ {
var ability = abilities[i]; var ability = abilities[i];
if (ability.WillTrigger(trigger) && ability.CanTrigger(this, trigger)) if (ability.CanTrigger(this, trigger))
{ {
if ((trigger & MonsterAbilityTrigger.GiveSpellDamage) != 0) if ((trigger & MonsterAbilityTrigger.GiveSpellDamage) != 0)
{ {
@ -3263,7 +3263,7 @@ namespace Server.Mobiles
} }
[GeneratedEvent(nameof(CreatureDeathEvent))] [GeneratedEvent(nameof(CreatureDeathEvent))]
public static partial void CreatureDeathEvent(Mobile m); public static partial void CreatureDeathEvent(BaseCreature bc);
public override void OnDeath(Container c) public override void OnDeath(Container c)
{ {
@ -3454,8 +3454,13 @@ namespace Server.Mobiles
CreatureDeathEvent(this); CreatureDeathEvent(this);
} }
[GeneratedEvent(nameof(CreatureDeletedEvent))]
public static partial void CreatureDeletedEvent(BaseCreature bc);
public override void OnDelete() public override void OnDelete()
{ {
CreatureDeletedEvent(this);
var m = m_ControlMaster; var m = m_ControlMaster;
SetControlMaster(null); SetControlMaster(null);