diff --git a/Projects/UOContent/Mobiles/Abilities/MonsterAbilityGroup.cs b/Projects/UOContent/Mobiles/Abilities/MonsterAbilityGroup.cs new file mode 100644 index 000000000..c431d4d35 --- /dev/null +++ b/Projects/UOContent/Mobiles/Abilities/MonsterAbilityGroup.cs @@ -0,0 +1,155 @@ +using System; +using Server.Random; +using WeightedMonsterAbility = Server.Random.WeightedValue; + +namespace Server.Mobiles; + +public class MonsterAbilityGroup : MonsterAbility +{ + private WeightedMonsterAbility[] _weightedAbilities; + private WeightedMonsterAbility[] _availableToTrigger; + private int _availableToTriggerCount; + private MonsterAbilityTrigger _triggers; + + public MonsterAbilityGroup(params WeightedMonsterAbility[] weightedAbilities) + { + _weightedAbilities = weightedAbilities; + _availableToTrigger = new WeightedMonsterAbility[weightedAbilities.Length]; + + for (var i = 0; i < _weightedAbilities.Length; i++) + { + var weightedAbility = _weightedAbilities[i]; + _triggers |= weightedAbility.Value.AbilityTrigger; + } + } + + public override MonsterAbilityTrigger AbilityTrigger => _triggers; + + public bool HasAbility(MonsterAbility ability) + { + for (var i = 0; i < _weightedAbilities.Length; i++) + { + if (_weightedAbilities[i].Value == ability) + { + return true; + } + } + + return false; + } + + // Note: Does not get an ability randomly by weight + // This is used for passives or lookups + public MonsterAbility GetAbilityWithType(MonsterAbilityType type) + { + for (var i = 0; i < _weightedAbilities.Length; i++) + { + var ability = _weightedAbilities[i].Value; + if (ability.AbilityType == type) + { + return ability; + } + } + + return null; + } + + public override bool CanTrigger(BaseCreature source, MonsterAbilityTrigger trigger) + { + if (!base.CanTrigger(source, trigger)) + { + return false; + } + + _availableToTriggerCount = 0; + + for (var i = 0; i < _weightedAbilities.Length; i++) + { + var weightedAbility = _weightedAbilities[i]; + if (weightedAbility.Value.WillTrigger(trigger) && weightedAbility.Value.CanTrigger(source, trigger)) + { + _availableToTrigger[_availableToTriggerCount++] = weightedAbility; + } + } + + return _availableToTriggerCount > 0; + } + + // Note: Trigger will choose an ability at random by weight + public override void Trigger(MonsterAbilityTrigger trigger, BaseCreature source, Mobile target) + { + if (_availableToTriggerCount <= 0) + { + return; + } + + var slice = new ReadOnlySpan>(_availableToTrigger, 0, _availableToTriggerCount); + var chosenAbility = slice.RandomWeightedElement().Value; + + // Just in case? + if (chosenAbility == null) + { + return; + } + + base.Trigger(trigger, source, target); + chosenAbility.Trigger(trigger, source, target); + _availableToTriggerCount = 0; // Just in case trigger is called without CanTrigger + } + + public override void AlterMeleeDamageFrom(BaseCreature source, Mobile target, ref int damage) + { + for (var i = 0; i < _weightedAbilities.Length; i++) + { + _weightedAbilities[i].Value.AlterMeleeDamageFrom(source, target, ref damage); + } + } + + public override void AlterMeleeDamageTo(BaseCreature source, Mobile target, ref int damage) + { + for (var i = 0; i < _weightedAbilities.Length; i++) + { + _weightedAbilities[i].Value.AlterMeleeDamageTo(source, target, ref damage); + } + } + + public override void AlterSpellDamageScalarFrom(BaseCreature source, Mobile target, ref double scalar) + { + for (var i = 0; i < _weightedAbilities.Length; i++) + { + _weightedAbilities[i].Value.AlterSpellDamageScalarFrom(source, target, ref scalar); + } + } + + public override void AlterSpellDamageScalarTo(BaseCreature source, Mobile target, ref double scalar) + { + for (var i = 0; i < _weightedAbilities.Length; i++) + { + _weightedAbilities[i].Value.AlterSpellDamageScalarTo(source, target, ref scalar); + } + } + + public override void AlterSpellDamageFrom(BaseCreature source, Mobile target, ref int damage) + { + for (var i = 0; i < _weightedAbilities.Length; i++) + { + _weightedAbilities[i].Value.AlterSpellDamageFrom(source, target, ref damage); + } + } + + public override void AlterSpellDamageTo(BaseCreature source, Mobile target, ref int damage) + { + for (var i = 0; i < _weightedAbilities.Length; i++) + { + _weightedAbilities[i].Value.AlterSpellDamageTo(source, target, ref damage); + } + } + + public override void Move(BaseCreature creature, Direction d) + { + for (var i = 0; i < _weightedAbilities.Length; i++) + { + _weightedAbilities[i].Value.Move(creature, d); + } + } +} diff --git a/Projects/UOContent/Mobiles/BaseCreature.cs b/Projects/UOContent/Mobiles/BaseCreature.cs index 092383d94..103511ac3 100644 --- a/Projects/UOContent/Mobiles/BaseCreature.cs +++ b/Projects/UOContent/Mobiles/BaseCreature.cs @@ -1130,7 +1130,15 @@ namespace Server.Mobiles for (var i = 0; i < abilities.Length; i++) { var ability = abilities[i]; - if (ability.AbilityType == type) + if (ability is MonsterAbilityGroup group) + { + ability = group.GetAbilityWithType(type); + if (ability != null) + { + return ability; + } + } + else if (ability.AbilityType == type) { return ability; } @@ -1141,6 +1149,11 @@ namespace Server.Mobiles public virtual bool HasAbility(MonsterAbility ability) { + if (ability == null) + { + return false; + } + var abilities = GetMonsterAbilities(); if (abilities == null) @@ -1150,7 +1163,7 @@ namespace Server.Mobiles for (var i = 0; i < abilities.Length; i++) { - if (abilities[i] == ability) + if (abilities[i] == ability || (ability as MonsterAbilityGroup)?.HasAbility(ability) == true) { return true; } @@ -4290,7 +4303,6 @@ namespace Server.Mobiles public virtual void OnActionCombat() { - TriggerAbility(MonsterAbilityTrigger.CombatAction, null); } public virtual void OnActionGuard()