From 3c636b04fde6d67c9b2f0be47a9576bf2691ef19 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Sun, 2 Oct 2022 20:48:53 -0700 Subject: [PATCH] feat: Adds magical barrier and ebolt counter ability (#1182) * Adds magical barrier ability (Pre-UOML) * Adds ebolt counter (Post-UOML) --- .../Musical Instruments/BaseInstrument.cs | 2 +- Projects/UOContent/Misc/AOS.cs | 28 +- .../Abilities/AreaEffectMonsterAbility.cs | 4 +- .../Mobiles/Abilities/ColossalBlow.cs | 4 +- .../Mobiles/Abilities/DeathExplosion.cs | 8 +- .../Mobiles/Abilities/DestroyEquipment.cs | 6 +- .../Mobiles/Abilities/EnergyBoltCounter.cs | 25 ++ .../UOContent/Mobiles/Abilities/FireBreath.cs | 8 +- .../Mobiles/Abilities/GraspingClaw.cs | 4 +- .../Mobiles/Abilities/MagicalBarrier.cs | 83 ++++++ .../Abilities/MonsterAbility.Abilities.cs | 4 + .../Mobiles/Abilities/MonsterAbility.cs | 34 ++- .../Mobiles/Abilities/MonsterAbilityAttack.cs | 12 +- .../Abilities/MonsterAbilityTrigger.cs | 20 +- .../Mobiles/Abilities/MonsterAbilityType.cs | 4 +- .../Mobiles/Abilities/PoisonGasAreaAttack.cs | 10 +- .../Mobiles/Abilities/PoisonGasCounter.cs | 4 +- .../UOContent/Mobiles/Abilities/StunAttack.cs | 6 +- .../Mobiles/Abilities/SummonLesserUndead.cs | 4 +- .../Mobiles/Abilities/SummonUndead.cs | 4 +- .../Mobiles/Abilities/ThrowHatchetCounter.cs | 6 +- Projects/UOContent/Mobiles/BaseCreature.cs | 138 +++++++-- .../Monsters/LBR/Exodus/ExodusMinion.cs | 274 ++++++------------ .../Monsters/LBR/Exodus/ExodusOverseer.cs | 265 ++++++----------- .../Ore Elementals/CopperElemental.cs | 1 + 25 files changed, 509 insertions(+), 449 deletions(-) create mode 100644 Projects/UOContent/Mobiles/Abilities/EnergyBoltCounter.cs create mode 100644 Projects/UOContent/Mobiles/Abilities/MagicalBarrier.cs diff --git a/Projects/UOContent/Items/Skill Items/Musical Instruments/BaseInstrument.cs b/Projects/UOContent/Items/Skill Items/Musical Instruments/BaseInstrument.cs index 3e58605d6..5dc0d5dca 100644 --- a/Projects/UOContent/Items/Skill Items/Musical Instruments/BaseInstrument.cs +++ b/Projects/UOContent/Items/Skill Items/Musical Instruments/BaseInstrument.cs @@ -247,7 +247,7 @@ namespace Server.Items public static bool IsMageryCreature(BaseCreature bc) => bc?.AI == AIType.AI_Mage && bc.Skills.Magery.Base > 5.0; - public static bool IsFireBreathingCreature(BaseCreature bc) => bc?.HasAbility(MonsterAbilityType.FireBreath) == true; + public static bool IsFireBreathingCreature(BaseCreature bc) => bc?.GetAbility(MonsterAbilityType.FireBreath) != null; public static bool IsPoisonImmune(BaseCreature bc) => bc?.PoisonImmune != null; diff --git a/Projects/UOContent/Misc/AOS.cs b/Projects/UOContent/Misc/AOS.cs index 0085bdd9b..ad077879f 100644 --- a/Projects/UOContent/Misc/AOS.cs +++ b/Projects/UOContent/Misc/AOS.cs @@ -194,26 +194,24 @@ namespace Server totalDamage = m.Hits; } - if (from?.Deleted == false && from.Alive) + if (from is { Deleted: false, Alive: true }) { var reflectPhys = AosAttributes.GetValue(m, AosAttribute.ReflectPhysical); + if (reflectPhys != 0 && from is BaseCreature bcFrom) + { + var magicalBarrier = bcFrom.GetAbility(MonsterAbilityType.MagicalBarrier); + magicalBarrier.AlterMeleeDamageFrom(bcFrom, m, ref reflectPhys); + } + if (reflectPhys != 0) { - if ((from as ExodusMinion)?.FieldActive == true || - (from as ExodusOverseer)?.FieldActive == true) - { - from.FixedParticles(0x376A, 20, 10, 0x2530, EffectLayer.Waist); - from.PlaySound(0x2F4); - m.SendAsciiMessage("Your weapon cannot penetrate the creature's magical barrier"); - } - else - { - from.Damage( - Scale(damage * phys * (100 - (ignoreArmor ? 0 : m.PhysicalResistance)) / 10000, reflectPhys), - m - ); - } + var reflectDamage = Scale( + damage * phys * (100 - (ignoreArmor ? 0 : m.PhysicalResistance)) / 10000, + reflectPhys + ); + + from.Damage(reflectDamage, m); } } diff --git a/Projects/UOContent/Mobiles/Abilities/AreaEffectMonsterAbility.cs b/Projects/UOContent/Mobiles/Abilities/AreaEffectMonsterAbility.cs index d49c795d0..1541c8a0f 100644 --- a/Projects/UOContent/Mobiles/Abilities/AreaEffectMonsterAbility.cs +++ b/Projects/UOContent/Mobiles/Abilities/AreaEffectMonsterAbility.cs @@ -6,7 +6,7 @@ public abstract class AreaEffectMonsterAbility : MonsterAbility { public virtual int AreaRange => 3; - public override void Trigger(BaseCreature source, Mobile target) + public override void Trigger(MonsterAbilityTrigger trigger, BaseCreature source, Mobile target) { var eable = source.GetMobilesInRange(AreaRange); using var queue = PooledRefQueue.Create(); @@ -24,7 +24,7 @@ public abstract class AreaEffectMonsterAbility : MonsterAbility DoEffectTarget(source, queue.Dequeue()); } - base.Trigger(source, target); + base.Trigger(trigger, source, target); } protected virtual bool CanEffectTarget(BaseCreature source, Mobile defender) => diff --git a/Projects/UOContent/Mobiles/Abilities/ColossalBlow.cs b/Projects/UOContent/Mobiles/Abilities/ColossalBlow.cs index 23a5661eb..6a331e015 100644 --- a/Projects/UOContent/Mobiles/Abilities/ColossalBlow.cs +++ b/Projects/UOContent/Mobiles/Abilities/ColossalBlow.cs @@ -4,7 +4,7 @@ namespace Server.Mobiles; public class ColossalBlow : StunAttack { - public override void Trigger(BaseCreature source, Mobile target) + public override void Trigger(MonsterAbilityTrigger trigger, BaseCreature source, Mobile target) { target.Animate(21, 6, 1, true, false, 0); source.PlaySound(0xEE); @@ -15,7 +15,7 @@ public class ColossalBlow : StunAttack "You have been stunned by a colossal blow!" ); - base.Trigger(source, target); + base.Trigger(trigger, source, target); } protected override void Recover(Mobile defender) diff --git a/Projects/UOContent/Mobiles/Abilities/DeathExplosion.cs b/Projects/UOContent/Mobiles/Abilities/DeathExplosion.cs index 0449bc469..ddd9f8491 100644 --- a/Projects/UOContent/Mobiles/Abilities/DeathExplosion.cs +++ b/Projects/UOContent/Mobiles/Abilities/DeathExplosion.cs @@ -4,21 +4,21 @@ namespace Server.Mobiles; public class DeathExplosion : AreaEffectMonsterAbility { - public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.OnDeath; + public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.Death; public override MonsterAbilityType AbilityType => MonsterAbilityType.DeathExplosion; public override double ChanceToTrigger => 1.0; public override TimeSpan MinTriggerCooldown => TimeSpan.Zero; public override TimeSpan MaxTriggerCooldown => TimeSpan.Zero; - public override bool CanTrigger(BaseCreature source) => true; + public override bool CanTrigger(BaseCreature source, MonsterAbilityTrigger trigger) => true; - public override void Trigger(BaseCreature source, Mobile target) + public override void Trigger(MonsterAbilityTrigger trigger, BaseCreature source, Mobile target) { source.FixedParticles(0x36BD, 20, 10, 5044, EffectLayer.Head); source. PlaySound(0x307); - base.Trigger(source, target); + base.Trigger(trigger, source, target); } protected override void DoEffectTarget(BaseCreature source, Mobile defender) diff --git a/Projects/UOContent/Mobiles/Abilities/DestroyEquipment.cs b/Projects/UOContent/Mobiles/Abilities/DestroyEquipment.cs index f4a0ab030..3a6d40282 100644 --- a/Projects/UOContent/Mobiles/Abilities/DestroyEquipment.cs +++ b/Projects/UOContent/Mobiles/Abilities/DestroyEquipment.cs @@ -12,9 +12,9 @@ public class DestroyEquipment : MonsterAbilityAttack public virtual int AttackRange => 1; - protected override void OnAttack(BaseCreature source, Mobile defender) + protected override void OnAttack(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender) { - base.OnAttack(source, defender); + base.OnAttack(trigger, source, defender); using var queue = PooledRefQueue.Create(); for (var i = 0; i < defender.Items.Count; i++) @@ -50,6 +50,6 @@ public class DestroyEquipment : MonsterAbilityAttack defender.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1080035, name); } - protected override bool CanEffectTarget(BaseCreature source, Mobile defender) => + protected override bool CanEffectTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender) => defender.Player && defender.AccessLevel == AccessLevel.Player && source.InRange(defender, AttackRange); } diff --git a/Projects/UOContent/Mobiles/Abilities/EnergyBoltCounter.cs b/Projects/UOContent/Mobiles/Abilities/EnergyBoltCounter.cs new file mode 100644 index 000000000..6a2c84816 --- /dev/null +++ b/Projects/UOContent/Mobiles/Abilities/EnergyBoltCounter.cs @@ -0,0 +1,25 @@ +using Server.Items; + +namespace Server.Mobiles; + +public class EnergyBoltCounter : MonsterAbilityAttack +{ + public override MonsterAbilityType AbilityType => MonsterAbilityType.EnergyBoltCounter; + public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.TakeDamage; // Ranged attackers + public override double ChanceToTrigger => 0.4; + + protected override void OnAttack(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender) + { + base.OnAttack(trigger, source, defender); + + source.MovingParticles(defender, 0x379F, 7, 0, false, true, 0xBE3, 0xFCB, 0x211); + defender.PlaySound(0x229); + + source.DoHarmful(defender); + AOS.Damage(defender, source, 50, 0, 0, 0, 0, 100, 0); + } + + protected override bool CanEffectTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender) => + base.CanEffectTarget(trigger, source, defender) + && (trigger == MonsterAbilityTrigger.TakeSpellDamage || defender.Weapon is BaseRanged); +} diff --git a/Projects/UOContent/Mobiles/Abilities/FireBreath.cs b/Projects/UOContent/Mobiles/Abilities/FireBreath.cs index 527f586db..d5c17f755 100644 --- a/Projects/UOContent/Mobiles/Abilities/FireBreath.cs +++ b/Projects/UOContent/Mobiles/Abilities/FireBreath.cs @@ -5,7 +5,7 @@ namespace Server.Mobiles; public class FireBreath : MonsterAbility { - public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.OnAction; + public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.Think; public override MonsterAbilityType AbilityType => MonsterAbilityType.FireBreath; public override double ChanceToTrigger => 0.5; @@ -45,9 +45,9 @@ public class FireBreath : MonsterAbility public virtual int BreathEffectSound => 0x227; - public override bool CanTrigger(BaseCreature source) => !source.Summoned && base.CanTrigger(source); + public override bool CanTrigger(BaseCreature source, MonsterAbilityTrigger trigger) => !source.Summoned && base.CanTrigger(source, trigger); - public override void Trigger(BaseCreature source, Mobile target) + public override void Trigger(MonsterAbilityTrigger trigger, BaseCreature source, Mobile target) { if (CanFireBreathTarget(source, target)) { @@ -59,7 +59,7 @@ public class FireBreath : MonsterAbility Timer.StartTimer(TimeSpan.FromSeconds(BreathEffectDelay), () => BreathEffect_Callback(source, target)); - base.Trigger(source, target); + base.Trigger(trigger, source, target); } } diff --git a/Projects/UOContent/Mobiles/Abilities/GraspingClaw.cs b/Projects/UOContent/Mobiles/Abilities/GraspingClaw.cs index 42b9256fc..8186de066 100644 --- a/Projects/UOContent/Mobiles/Abilities/GraspingClaw.cs +++ b/Projects/UOContent/Mobiles/Abilities/GraspingClaw.cs @@ -11,7 +11,7 @@ public class GraspingClaw : MonsterAbility public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.GiveMeleeDamage; public override double ChanceToTrigger => 0.10; - public override void Trigger(BaseCreature source, Mobile target) + public override void Trigger(MonsterAbilityTrigger trigger, BaseCreature source, Mobile target) { if (_table.Remove(target, out var timer)) { @@ -55,7 +55,7 @@ public class GraspingClaw : MonsterAbility _table ??= new Dictionary(); _table[target] = timer; - base.Trigger(source, target); + base.Trigger(trigger, source, target); } private class ExpireTimer : Timer diff --git a/Projects/UOContent/Mobiles/Abilities/MagicalBarrier.cs b/Projects/UOContent/Mobiles/Abilities/MagicalBarrier.cs new file mode 100644 index 000000000..585a7695b --- /dev/null +++ b/Projects/UOContent/Mobiles/Abilities/MagicalBarrier.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; + +namespace Server.Mobiles; + +public class MagicalBarrier : MonsterAbility +{ + private HashSet _inactiveField; + + public bool HasField(Mobile source) => !_inactiveField.Contains(source); + + public override MonsterAbilityType AbilityType => MonsterAbilityType.MagicalBarrier; + + public override MonsterAbilityTrigger AbilityTrigger => + MonsterAbilityTrigger.TakeDamage | MonsterAbilityTrigger.GiveDamage | MonsterAbilityTrigger.Think; + + public override TimeSpan MinTriggerCooldown => TimeSpan.FromSeconds(10.0); + public override TimeSpan MaxTriggerCooldown => TimeSpan.FromSeconds(10.0); + + // Regeneration is subject to the cooldown, the rest are not. + public override bool CanTrigger(BaseCreature source, MonsterAbilityTrigger trigger) => + trigger != MonsterAbilityTrigger.Think || base.CanTrigger(source, trigger); + + public override void Trigger(MonsterAbilityTrigger trigger, BaseCreature source, Mobile target) + { + if (trigger == MonsterAbilityTrigger.Think && !source.IsHurt()) + { + var fieldUp = _inactiveField?.Remove(source) == true; + if (fieldUp) + { + source.FixedParticles(0, 10, 0, 0x2530, EffectLayer.Waist); + + if (_inactiveField?.Count == 0) + { + _inactiveField = null; + } + } + } + + base.Trigger(trigger, source, target); + } + + public override void AlterMeleeDamageFrom(BaseCreature source, Mobile target, ref int damage) + { + if (HasField(source)) + { + damage = 0; // no melee damage when the field is up + source.FixedParticles(0x376A, 20, 10, 0x2530, EffectLayer.Waist); + source.PlaySound(0x2F4); + target.SendLocalizedMessage(1114360); // Your weapon cannot penetrate the creature's magical barrier. + } + } + + public override void AlterSpellDamageFrom(BaseCreature source, Mobile target, ref int damage) + { + var canUseField = source.Hits >= source.HitsMax * 9 / 10; + // If we cannot use the field, deactivate it. + var fieldActive = canUseField ? HasField(source) : _inactiveField.Add(source); + + if (!fieldActive) + { + damage = 0; // no spell damage when the field is down + // should there be an effect when spells nullifying is on? + source.FixedParticles(0, 10, 0, 0x2522, EffectLayer.Waist); + target.SendLocalizedMessage(1114359); // Your attack has no effect on the creature's armor. + } + else if (!canUseField) + { + // TODO: message and effect when field turns down; cannot be verified on OSI due to a bug + source.FixedParticles(0x3735, 1, 30, 0x251F, EffectLayer.Waist); + } + } + + public override void Move(BaseCreature source, Direction d) + { + base.Move(source, d); + + if (HasField(source) && source.Combatant != null) + { + source.FixedParticles(0, 10, 0, 0x2530, EffectLayer.Waist); + } + } +} diff --git a/Projects/UOContent/Mobiles/Abilities/MonsterAbility.Abilities.cs b/Projects/UOContent/Mobiles/Abilities/MonsterAbility.Abilities.cs index 621cd796c..44e8a8c5f 100644 --- a/Projects/UOContent/Mobiles/Abilities/MonsterAbility.Abilities.cs +++ b/Projects/UOContent/Mobiles/Abilities/MonsterAbility.Abilities.cs @@ -23,9 +23,13 @@ public abstract partial class MonsterAbility // Explosions public static DeathExplosion DeathExplosion => new(); + // Direct attack counters public static ThrowHatchetCounter ThrowHatchetCounter => new(); + public static EnergyBoltCounter EnergyBoltCounter => new(); public static DestroyEquipment DestroyEquipment => new(); public static DrainLifeAreaAttack DrainLifeAreaAttack => new(); + + public static MagicalBarrier MagicalBarrier => new(); } diff --git a/Projects/UOContent/Mobiles/Abilities/MonsterAbility.cs b/Projects/UOContent/Mobiles/Abilities/MonsterAbility.cs index 82b6b9c36..c9ba47865 100644 --- a/Projects/UOContent/Mobiles/Abilities/MonsterAbility.cs +++ b/Projects/UOContent/Mobiles/Abilities/MonsterAbility.cs @@ -23,9 +23,8 @@ public abstract partial class MonsterAbility /// /// Returns true if ability is not on cooldown, and the change to trigger succeeds. /// - /// The mobile this ability is attached to /// Boolean indicating the ability can trigger. - public virtual bool CanTrigger(BaseCreature source) + public virtual bool CanTrigger(BaseCreature source, MonsterAbilityTrigger trigger) { if (_nextTriggerTicks?.TryGetValue(source, out var nextTrigger) == true && Core.TickCount < nextTrigger) { @@ -53,8 +52,7 @@ public abstract partial class MonsterAbility /// Triggers the monster's ability. Override this and call `base.Trigger(source);` to make sure /// the cooldown is tracked. /// - /// - public virtual void Trigger(BaseCreature source, Mobile target) + public virtual void Trigger(MonsterAbilityTrigger trigger, BaseCreature source, Mobile target) { if (MinTriggerCooldown <= TimeSpan.Zero && MaxTriggerCooldown <= TimeSpan.Zero) { @@ -70,4 +68,32 @@ public abstract partial class MonsterAbility _nextTriggerTicks ??= new Dictionary(); _nextTriggerTicks[source] = nextTrigger; } + + public virtual void AlterMeleeDamageFrom(BaseCreature source, Mobile target, ref int damage) + { + } + + public virtual void AlterMeleeDamageTo(BaseCreature source, Mobile target, ref int damage) + { + } + + public virtual void AlterSpellDamageScalarFrom(BaseCreature source, Mobile target, ref double scalar) + { + } + + public virtual void AlterSpellDamageScalarTo(BaseCreature source, Mobile target, ref double scalar) + { + } + + public virtual void AlterSpellDamageFrom(BaseCreature source, Mobile target, ref int damage) + { + } + + public virtual void AlterSpellDamageTo(BaseCreature source, Mobile target, ref int damage) + { + } + + public virtual void Move(BaseCreature creature, Direction d) + { + } } diff --git a/Projects/UOContent/Mobiles/Abilities/MonsterAbilityAttack.cs b/Projects/UOContent/Mobiles/Abilities/MonsterAbilityAttack.cs index 2fb8c4142..0d7a27ebc 100644 --- a/Projects/UOContent/Mobiles/Abilities/MonsterAbilityAttack.cs +++ b/Projects/UOContent/Mobiles/Abilities/MonsterAbilityAttack.cs @@ -2,21 +2,21 @@ public abstract class MonsterAbilityAttack : MonsterAbility { - public override void Trigger(BaseCreature source, Mobile target) + public override void Trigger(MonsterAbilityTrigger trigger, BaseCreature source, Mobile target) { - if (CanEffectTarget(source, target)) + if (CanEffectTarget(trigger, source, target)) { - OnAttack(source, target); + OnAttack(trigger, source, target); } - base.Trigger(source, target); + base.Trigger(trigger, source, target); } - protected virtual void OnAttack(BaseCreature source, Mobile defender) + protected virtual void OnAttack(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender) { source.DoHarmful(defender); } - protected virtual bool CanEffectTarget(BaseCreature source, Mobile defender) => + protected virtual bool CanEffectTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender) => defender.Alive && source.CanBeHarmful(defender); } diff --git a/Projects/UOContent/Mobiles/Abilities/MonsterAbilityTrigger.cs b/Projects/UOContent/Mobiles/Abilities/MonsterAbilityTrigger.cs index 88548463d..d30ed4413 100644 --- a/Projects/UOContent/Mobiles/Abilities/MonsterAbilityTrigger.cs +++ b/Projects/UOContent/Mobiles/Abilities/MonsterAbilityTrigger.cs @@ -3,16 +3,18 @@ namespace Server.Mobiles; [Flags] -public enum MonsterAbilityTrigger +public enum MonsterAbilityTrigger : ulong { - None = 0x00000000, // Passive, or some custom event. - OnAction = 0x00000001, // On Think - TakeMeleeDamage = 0x00000002, - GiveMeleeDamage = 0x00000004, // Includes firing a bow - TakeSpellDamage = 0x00000008, - GiveSpellDamage = 0x00000010, - OnCombatAction = 0x00000020, - OnDeath = 0x00000040, + None = 0x0000000000000000, // Passive, or some custom event. + Think = 0x0000000000000001, + TakeMeleeDamage = 0x0000000000000002, + GiveMeleeDamage = 0x0000000000000004, // Includes firing a bow + TakeSpellDamage = 0x0000000000000008, + GiveSpellDamage = 0x0000000000000010, + CombatAction = 0x0000000000000020, + Death = 0x0000000000000040, + Movement = 0x0000000000000080, + GiveDamage = GiveMeleeDamage | GiveSpellDamage, TakeDamage = TakeMeleeDamage | TakeSpellDamage } diff --git a/Projects/UOContent/Mobiles/Abilities/MonsterAbilityType.cs b/Projects/UOContent/Mobiles/Abilities/MonsterAbilityType.cs index e1dbe8250..4f5768923 100644 --- a/Projects/UOContent/Mobiles/Abilities/MonsterAbilityType.cs +++ b/Projects/UOContent/Mobiles/Abilities/MonsterAbilityType.cs @@ -12,5 +12,7 @@ public enum MonsterAbilityType ThrowWeapon, DestroyEquipment, DeathExplosion, - MassDrainLife + MassDrainLife, + MagicalBarrier, + EnergyBoltCounter } diff --git a/Projects/UOContent/Mobiles/Abilities/PoisonGasAreaAttack.cs b/Projects/UOContent/Mobiles/Abilities/PoisonGasAreaAttack.cs index a6e2acedc..b4188ab26 100644 --- a/Projects/UOContent/Mobiles/Abilities/PoisonGasAreaAttack.cs +++ b/Projects/UOContent/Mobiles/Abilities/PoisonGasAreaAttack.cs @@ -4,7 +4,7 @@ namespace Server.Mobiles; public class PoisonGasAreaAttack : AreaEffectMonsterAbility { - public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.OnCombatAction; + public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.CombatAction; public override MonsterAbilityType AbilityType => MonsterAbilityType.MassPoison; public override double ChanceToTrigger => 0.5; @@ -13,9 +13,9 @@ public class PoisonGasAreaAttack : AreaEffectMonsterAbility public virtual int CombatantRange => 3; - public override bool CanTrigger(BaseCreature source) + public override bool CanTrigger(BaseCreature source, MonsterAbilityTrigger trigger) { - if (!base.CanTrigger(source)) + if (!base.CanTrigger(source, trigger)) { return false; } @@ -27,12 +27,12 @@ public class PoisonGasAreaAttack : AreaEffectMonsterAbility source.CanBeHarmful(combatant) && source.InLOS(combatant); } - public override void Trigger(BaseCreature source, Mobile target) + public override void Trigger(MonsterAbilityTrigger trigger, BaseCreature source, Mobile target) { source.FixedParticles(0x376A, 9, 32, 0x2539, EffectLayer.LeftHand); source.PlaySound(0x1DE); - base.Trigger(source, target); + base.Trigger(trigger, source, target); } protected override void DoEffectTarget(BaseCreature source, Mobile defender) diff --git a/Projects/UOContent/Mobiles/Abilities/PoisonGasCounter.cs b/Projects/UOContent/Mobiles/Abilities/PoisonGasCounter.cs index a83bd459a..96dac201e 100644 --- a/Projects/UOContent/Mobiles/Abilities/PoisonGasCounter.cs +++ b/Projects/UOContent/Mobiles/Abilities/PoisonGasCounter.cs @@ -8,10 +8,10 @@ public class PoisonGasCounter : MonsterAbilityAttack public override double ChanceToTrigger => 0.05; public virtual int AttackRange => 1; - protected override bool CanEffectTarget(BaseCreature source, Mobile defender) => + protected override bool CanEffectTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender) => source.InRange(defender, AttackRange) && defender is not BaseCreature { BardProvoked: true }; - protected override void OnAttack(BaseCreature source, Mobile defender) + protected override void OnAttack(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender) { source.Animate(10, 4, 1, true, false, 0); AOS.Damage(defender, source, 50, 100, 0, 0, 0, 0, 0); diff --git a/Projects/UOContent/Mobiles/Abilities/StunAttack.cs b/Projects/UOContent/Mobiles/Abilities/StunAttack.cs index 3a74210bd..853535edd 100644 --- a/Projects/UOContent/Mobiles/Abilities/StunAttack.cs +++ b/Projects/UOContent/Mobiles/Abilities/StunAttack.cs @@ -15,9 +15,9 @@ public abstract class StunAttack : MonsterAbility public virtual TimeSpan StunDuration => TimeSpan.FromSeconds(5.0); - public override bool CanTrigger(BaseCreature source) => !_stunning.Contains(source) && base.CanTrigger(source); + public override bool CanTrigger(BaseCreature source, MonsterAbilityTrigger trigger) => !_stunning.Contains(source) && base.CanTrigger(source, trigger); - public override void Trigger(BaseCreature source, Mobile target) + public override void Trigger(MonsterAbilityTrigger trigger, BaseCreature source, Mobile target) { if (source.Weapon is BaseWeapon weapon) { @@ -33,7 +33,7 @@ public abstract class StunAttack : MonsterAbility Timer.DelayCall(StunDuration, Recover, target); } - base.Trigger(source, target); + base.Trigger(trigger, source, target); } protected virtual void Recover(Mobile defender) diff --git a/Projects/UOContent/Mobiles/Abilities/SummonLesserUndead.cs b/Projects/UOContent/Mobiles/Abilities/SummonLesserUndead.cs index 2114960f7..346fccb75 100644 --- a/Projects/UOContent/Mobiles/Abilities/SummonLesserUndead.cs +++ b/Projects/UOContent/Mobiles/Abilities/SummonLesserUndead.cs @@ -4,10 +4,10 @@ public class SummonLesserUndead : SummonUndead { private int _group; - public override void Trigger(BaseCreature source, Mobile target) + public override void Trigger(MonsterAbilityTrigger trigger, BaseCreature source, Mobile target) { _group = Utility.Random(2); - base.Trigger(source, target); + base.Trigger(trigger, source, target); } public override BaseCreature CreateSummon(BaseCreature source) diff --git a/Projects/UOContent/Mobiles/Abilities/SummonUndead.cs b/Projects/UOContent/Mobiles/Abilities/SummonUndead.cs index 8d94b9759..3412dafb7 100644 --- a/Projects/UOContent/Mobiles/Abilities/SummonUndead.cs +++ b/Projects/UOContent/Mobiles/Abilities/SummonUndead.cs @@ -21,7 +21,7 @@ public abstract class SummonUndead : MonsterAbility public virtual bool CanTrigger(BaseCreature source) => source.Followers < AmountToSummon && CanTrigger(source); - public override void Trigger(BaseCreature source, Mobile target) + public override void Trigger(MonsterAbilityTrigger trigger, BaseCreature source, Mobile target) { var amount = AmountToSummon - source.Followers; var distance = SummonRange; @@ -58,7 +58,7 @@ public abstract class SummonUndead : MonsterAbility summon.PlaySound(summon.GetAttackSound()); } - base.Trigger(source, target); + base.Trigger(trigger, source, target); } private static void EndPolymorph(BaseCreature source) diff --git a/Projects/UOContent/Mobiles/Abilities/ThrowHatchetCounter.cs b/Projects/UOContent/Mobiles/Abilities/ThrowHatchetCounter.cs index ecfe8b826..ef09a5e88 100644 --- a/Projects/UOContent/Mobiles/Abilities/ThrowHatchetCounter.cs +++ b/Projects/UOContent/Mobiles/Abilities/ThrowHatchetCounter.cs @@ -8,12 +8,12 @@ public class ThrowHatchetCounter : MonsterAbilityAttack public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.TakeDamage; public override double ChanceToTrigger => 0.4; - protected override void OnAttack(BaseCreature source, Mobile defender) + protected override void OnAttack(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender) { source.MovingEffect(defender, 0xF43, 10, 0, false, false); AOS.Damage(defender, source, 50, 100, 0, 0, 0, 0, 0); } - protected override bool CanEffectTarget(BaseCreature source, Mobile defender) => - base.CanEffectTarget(source, defender) && defender.Weapon is BaseRanged; + protected override bool CanEffectTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender) => + base.CanEffectTarget(trigger, source, defender) && defender.Weapon is BaseRanged; } diff --git a/Projects/UOContent/Mobiles/BaseCreature.cs b/Projects/UOContent/Mobiles/BaseCreature.cs index 89eec0a18..41a612f53 100644 --- a/Projects/UOContent/Mobiles/BaseCreature.cs +++ b/Projects/UOContent/Mobiles/BaseCreature.cs @@ -1118,24 +1118,25 @@ namespace Server.Mobiles public virtual MonsterAbility[] GetMonsterAbilities() => null; - public virtual bool HasAbility(MonsterAbilityType type) + public virtual MonsterAbility GetAbility(MonsterAbilityType type) { var abilities = GetMonsterAbilities(); if (abilities == null) { - return false; + return null; } for (var i = 0; i < abilities.Length; i++) { - if (abilities[i].AbilityType == type) + var ability = abilities[i]; + if (ability.AbilityType == type) { - return true; + return ability; } } - return false; + return null; } public virtual bool HasAbility(MonsterAbility ability) @@ -1170,9 +1171,96 @@ namespace Server.Mobiles for (var i = 0; i < abilities.Length; i++) { var ability = abilities[i]; - if (ability.WillTrigger(trigger) && ability.CanTrigger(this)) + if (ability.WillTrigger(trigger) && ability.CanTrigger(this, trigger)) { - ability.Trigger(this, defender); + ability.Trigger(trigger, this, defender); + } + } + } + + public virtual void TriggerAbilityMove(MonsterAbilityTrigger trigger, Mobile defender, Direction d) + { + var abilities = GetMonsterAbilities(); + + if (abilities == null) + { + return; + } + + for (var i = 0; i < abilities.Length; i++) + { + var ability = abilities[i]; + if (ability.WillTrigger(trigger) && ability.CanTrigger(this, trigger)) + { + ability.Move(this, d); + } + } + } + + public virtual void TriggerAbilityAlterDamage(MonsterAbilityTrigger trigger, Mobile defender, ref int damage) + { + var abilities = GetMonsterAbilities(); + + if (abilities == null) + { + return; + } + + for (var i = 0; i < abilities.Length; i++) + { + var ability = abilities[i]; + if (ability.WillTrigger(trigger) && ability.CanTrigger(this, trigger)) + { + if ((trigger & MonsterAbilityTrigger.GiveMeleeDamage) != 0) + { + ability.AlterMeleeDamageTo(this, defender, ref damage); + } + + if ((trigger & MonsterAbilityTrigger.TakeMeleeDamage) != 0) + { + ability.AlterMeleeDamageFrom(this, defender, ref damage); + } + + if ((trigger & MonsterAbilityTrigger.GiveSpellDamage) != 0) + { + ability.AlterSpellDamageTo(this, defender, ref damage); + } + + if ((trigger & MonsterAbilityTrigger.TakeSpellDamage) != 0) + { + ability.AlterSpellDamageFrom(this, defender, ref damage); + } + } + } + } + + public virtual void TriggerAbilityAlterDamageScalar( + MonsterAbilityTrigger trigger, + Mobile defender, + ref double scalar + ) + { + var abilities = GetMonsterAbilities(); + + if (abilities == null) + { + return; + } + + for (var i = 0; i < abilities.Length; i++) + { + var ability = abilities[i]; + if (ability.WillTrigger(trigger) && ability.CanTrigger(this, trigger)) + { + if ((trigger & MonsterAbilityTrigger.GiveSpellDamage) != 0) + { + ability.AlterSpellDamageScalarTo(this, defender, ref scalar); + } + + if ((trigger & MonsterAbilityTrigger.TakeSpellDamage) != 0) + { + ability.AlterSpellDamageScalarFrom(this, defender, ref scalar); + } } } } @@ -2232,6 +2320,17 @@ namespace Server.Mobiles TriggerAbility(MonsterAbilityTrigger.TakeMeleeDamage, attacker); } + public override bool Move(Direction d) + { + if (!base.Move(d)) + { + return false; + } + + TriggerAbilityMove(MonsterAbilityTrigger.Movement, this, d); + return true; + } + public virtual void Dispel(Mobile m) { Effects.SendLocationParticles( @@ -2869,7 +2968,7 @@ namespace Server.Mobiles public override bool OnBeforeDeath() { - TriggerAbility(MonsterAbilityTrigger.OnDeath, null); + TriggerAbility(MonsterAbilityTrigger.Death, null); var treasureLevel = TreasureMapLevel; @@ -3517,7 +3616,7 @@ namespace Server.Mobiles } // Fire breath, etc. - TriggerAbility(MonsterAbilityTrigger.OnAction, Combatant); + TriggerAbility(MonsterAbilityTrigger.Think, Combatant); if ((CanHeal || CanHealOwner) && Alive && !IsHealing && !BardPacified) { @@ -4021,26 +4120,32 @@ namespace Server.Mobiles public virtual void AlterDamageScalarFrom(Mobile caster, ref double scalar) { + TriggerAbilityAlterDamageScalar(MonsterAbilityTrigger.TakeSpellDamage, caster, ref scalar); } public virtual void AlterDamageScalarTo(Mobile target, ref double scalar) { + TriggerAbilityAlterDamageScalar(MonsterAbilityTrigger.GiveSpellDamage, target, ref scalar); } public virtual void AlterSpellDamageFrom(Mobile from, ref int damage) { + TriggerAbilityAlterDamage(MonsterAbilityTrigger.TakeSpellDamage, from, ref damage); } public virtual void AlterSpellDamageTo(Mobile to, ref int damage) { + TriggerAbilityAlterDamage(MonsterAbilityTrigger.GiveSpellDamage, to, ref damage); } public virtual void AlterMeleeDamageFrom(Mobile from, ref int damage) { + TriggerAbilityAlterDamage(MonsterAbilityTrigger.TakeMeleeDamage, from, ref damage); } public virtual void AlterMeleeDamageTo(Mobile to, ref int damage) { + TriggerAbilityAlterDamage(MonsterAbilityTrigger.GiveMeleeDamage, to, ref damage); } public virtual bool CheckFoodPreference(Item f) => @@ -4081,16 +4186,11 @@ namespace Server.Mobiles if (amount > 0) { - int stamGain; - - if (f is Gold) + int stamGain = f switch { - stamGain = amount - 50; - } - else - { - stamGain = amount * 15 - 50; - } + Gold => amount - 50, + _ => amount * 15 - 50 + }; if (stamGain > 0) { @@ -4172,7 +4272,7 @@ namespace Server.Mobiles public virtual void OnActionCombat() { - TriggerAbility(MonsterAbilityTrigger.OnCombatAction, null); + TriggerAbility(MonsterAbilityTrigger.CombatAction, null); } public virtual void OnActionGuard() diff --git a/Projects/UOContent/Mobiles/Monsters/LBR/Exodus/ExodusMinion.cs b/Projects/UOContent/Mobiles/Monsters/LBR/Exodus/ExodusMinion.cs index 2e34264ce..3e5d2573c 100644 --- a/Projects/UOContent/Mobiles/Monsters/LBR/Exodus/ExodusMinion.cs +++ b/Projects/UOContent/Mobiles/Monsters/LBR/Exodus/ExodusMinion.cs @@ -1,195 +1,107 @@ using Server.Items; -namespace Server.Mobiles +namespace Server.Mobiles; + +public class ExodusMinion : BaseCreature { - public class ExodusMinion : BaseCreature + [Constructible] + public ExodusMinion() : base(AIType.AI_Melee) { - [Constructible] - public ExodusMinion() : base(AIType.AI_Melee) + Body = 0x2F5; + + SetStr(851, 950); + SetDex(71, 80); + SetInt(61, 90); + + SetHits(511, 570); + + SetDamage(16, 22); + + SetResistance(ResistanceType.Physical, 60, 70); + SetResistance(ResistanceType.Fire, 40, 50); + SetResistance(ResistanceType.Cold, 15, 25); + SetResistance(ResistanceType.Poison, 15, 25); + SetResistance(ResistanceType.Energy, 15, 25); + + SetSkill(SkillName.MagicResist, 90.1, 100.0); + SetSkill(SkillName.Tactics, 90.1, 100.0); + SetSkill(SkillName.Wrestling, 90.1, 100.0); + + Fame = 18000; + Karma = -18000; + VirtualArmor = 65; + + PackItem(new PowerCrystal()); + PackItem(new ArcaneGem()); + PackItem(new ClockworkAssembly()); + + switch (Utility.Random(3)) { - Body = 0x2F5; - - SetStr(851, 950); - SetDex(71, 80); - SetInt(61, 90); - - SetHits(511, 570); - - SetDamage(16, 22); - - SetResistance(ResistanceType.Physical, 60, 70); - SetResistance(ResistanceType.Fire, 40, 50); - SetResistance(ResistanceType.Cold, 15, 25); - SetResistance(ResistanceType.Poison, 15, 25); - SetResistance(ResistanceType.Energy, 15, 25); - - SetSkill(SkillName.MagicResist, 90.1, 100.0); - SetSkill(SkillName.Tactics, 90.1, 100.0); - SetSkill(SkillName.Wrestling, 90.1, 100.0); - - Fame = 18000; - Karma = -18000; - VirtualArmor = 65; - - PackItem(new PowerCrystal()); - PackItem(new ArcaneGem()); - PackItem(new ClockworkAssembly()); - - switch (Utility.Random(3)) - { - case 0: + case 0: + { PackItem(new PowerCrystal()); break; - case 1: + } + case 1: + { PackItem(new ArcaneGem()); break; - case 2: + } + case 2: + { PackItem(new ClockworkAssembly()); break; - } - - FieldActive = CanUseField; - } - - public ExodusMinion(Serial serial) : base(serial) - { - } - - public override string CorpseName => "a minion's corpse"; - public bool FieldActive { get; private set; } - - public bool CanUseField // TODO: an OSI bug prevents to verify this - => Hits >= HitsMax * 9 / 10; - - public override bool IsScaredOfScaryThings => false; - public override bool IsScaryToPets => true; - - public override string DefaultName => "an exodus minion"; - - public override bool AutoDispel => true; - public override bool BardImmune => !Core.AOS; - public override Poison PoisonImmune => Poison.Lethal; - - public override void GenerateLoot() - { - AddLoot(LootPack.Average); - AddLoot(LootPack.Rich); - } - - public override int GetIdleSound() => 0x218; - - public override int GetAngerSound() => 0x26C; - - public override int GetDeathSound() => 0x211; - - public override int GetAttackSound() => 0x232; - - public override int GetHurtSound() => 0x140; - - public override void AlterMeleeDamageFrom(Mobile from, ref int damage) - { - if (FieldActive) - { - damage = 0; // no melee damage when the field is up - } - } - - public override void AlterSpellDamageFrom(Mobile from, ref int damage) - { - if (!FieldActive) - { - damage = 0; // no spell damage when the field is down - } - } - - public override void OnDamagedBySpell(Mobile from, int damage) - { - if (from?.Alive == true && Utility.RandomDouble() < 0.4) - { - SendEBolt(from); - } - - if (!FieldActive) - { - // should there be an effect when spells nullifying is on? - FixedParticles(0, 10, 0, 0x2522, EffectLayer.Waist); - } - else if (FieldActive && !CanUseField) - { - FieldActive = false; - - // TODO: message and effect when field turns down; cannot be verified on OSI due to a bug - FixedParticles(0x3735, 1, 30, 0x251F, EffectLayer.Waist); - } - } - - public override void OnGotMeleeAttack(Mobile attacker, int damage) - { - base.OnGotMeleeAttack(attacker, damage); - - if (FieldActive) - { - FixedParticles(0x376A, 20, 10, 0x2530, EffectLayer.Waist); - - PlaySound(0x2F4); - - attacker.SendAsciiMessage("Your weapon cannot penetrate the creature's magical barrier"); - } - - if (attacker?.Alive == true && attacker.Weapon is BaseRanged && Utility.RandomDouble() < 0.4) - { - SendEBolt(attacker); - } - } - - public override void OnThink() - { - base.OnThink(); - - // TODO: an OSI bug prevents to verify if the field can regenerate or not - if (!FieldActive && !IsHurt()) - { - FieldActive = true; - } - } - - public override bool Move(Direction d) - { - var move = base.Move(d); - - if (move && FieldActive && Combatant != null) - { - FixedParticles(0, 10, 0, 0x2530, EffectLayer.Waist); - } - - return move; - } - - public void SendEBolt(Mobile to) - { - MovingParticles(to, 0x379F, 7, 0, false, true, 0xBE3, 0xFCB, 0x211); - to.PlaySound(0x229); - DoHarmful(to); - AOS.Damage(to, this, 50, 0, 0, 0, 0, 100); - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - writer.Write(0); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - var version = reader.ReadInt(); - - FieldActive = CanUseField; - - if (Name == "Exodus Minion") - { - Name = null; - } + } } } + + public ExodusMinion(Serial serial) : base(serial) + { + } + + public override string CorpseName => "a minion's corpse"; + public override bool IsScaredOfScaryThings => false; + public override bool IsScaryToPets => true; + + public override string DefaultName => "an exodus minion"; + + public override bool AutoDispel => true; + public override bool BardImmune => !Core.AOS; + public override Poison PoisonImmune => Poison.Lethal; + + public override void GenerateLoot() + { + AddLoot(LootPack.Average); + AddLoot(LootPack.Rich); + } + + public override int GetIdleSound() => 0x218; + + public override int GetAngerSound() => 0x26C; + + public override int GetDeathSound() => 0x211; + + public override int GetAttackSound() => 0x232; + + public override int GetHurtSound() => 0x140; + + private static MonsterAbility[] _abilities = + { + // OSI changed the ability some time around UOML + Core.ML ? MonsterAbility.EnergyBoltCounter : MonsterAbility.MagicalBarrier + }; + + public override MonsterAbility[] GetMonsterAbilities() => _abilities; + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + writer.Write(0); + } + + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + var version = reader.ReadInt(); + } } diff --git a/Projects/UOContent/Mobiles/Monsters/LBR/Exodus/ExodusOverseer.cs b/Projects/UOContent/Mobiles/Monsters/LBR/Exodus/ExodusOverseer.cs index 48e34d632..c5f3ecb6c 100644 --- a/Projects/UOContent/Mobiles/Monsters/LBR/Exodus/ExodusOverseer.cs +++ b/Projects/UOContent/Mobiles/Monsters/LBR/Exodus/ExodusOverseer.cs @@ -1,189 +1,96 @@ using Server.Items; -namespace Server.Mobiles +namespace Server.Mobiles; + +public class ExodusOverseer : BaseCreature { - public class ExodusOverseer : BaseCreature + [Constructible] + public ExodusOverseer() : base(AIType.AI_Melee) { - [Constructible] - public ExodusOverseer() : base(AIType.AI_Melee) + Body = 0x2F4; + + SetStr(561, 650); + SetDex(76, 95); + SetInt(61, 90); + + SetHits(331, 390); + + SetDamage(13, 19); + + SetDamageType(ResistanceType.Physical, 50); + SetDamageType(ResistanceType.Energy, 50); + + SetResistance(ResistanceType.Physical, 45, 55); + SetResistance(ResistanceType.Fire, 40, 60); + SetResistance(ResistanceType.Cold, 25, 35); + SetResistance(ResistanceType.Poison, 25, 35); + SetResistance(ResistanceType.Energy, 25, 35); + + SetSkill(SkillName.MagicResist, 80.2, 98.0); + SetSkill(SkillName.Tactics, 80.2, 98.0); + SetSkill(SkillName.Wrestling, 80.2, 98.0); + + Fame = 10000; + Karma = -10000; + VirtualArmor = 50; + + if (Utility.Random(2) == 0) { - Body = 0x2F4; - - SetStr(561, 650); - SetDex(76, 95); - SetInt(61, 90); - - SetHits(331, 390); - - SetDamage(13, 19); - - SetDamageType(ResistanceType.Physical, 50); - SetDamageType(ResistanceType.Energy, 50); - - SetResistance(ResistanceType.Physical, 45, 55); - SetResistance(ResistanceType.Fire, 40, 60); - SetResistance(ResistanceType.Cold, 25, 35); - SetResistance(ResistanceType.Poison, 25, 35); - SetResistance(ResistanceType.Energy, 25, 35); - - SetSkill(SkillName.MagicResist, 80.2, 98.0); - SetSkill(SkillName.Tactics, 80.2, 98.0); - SetSkill(SkillName.Wrestling, 80.2, 98.0); - - Fame = 10000; - Karma = -10000; - VirtualArmor = 50; - - if (Utility.Random(2) == 0) - { - PackItem(new PowerCrystal()); - } - else - { - PackItem(new ArcaneGem()); - } - - FieldActive = CanUseField; + PackItem(new PowerCrystal()); } - - public ExodusOverseer(Serial serial) : base(serial) + else { - } - - public override string CorpseName => "an overseer's corpse"; - public bool FieldActive { get; private set; } - - public bool CanUseField // TODO: an OSI bug prevents to verify this - => Hits >= HitsMax * 9 / 10; - - public override bool IsScaredOfScaryThings => false; - public override bool IsScaryToPets => true; - - public override string DefaultName => "an exodus overseer"; - - public override bool AutoDispel => true; - public override bool BardImmune => !Core.AOS; - public override Poison PoisonImmune => Poison.Lethal; - - public override void GenerateLoot() - { - AddLoot(LootPack.Rich); - } - - public override int GetIdleSound() => 0xFD; - - public override int GetAngerSound() => 0x26C; - - public override int GetDeathSound() => 0x211; - - public override int GetAttackSound() => 0x23B; - - public override int GetHurtSound() => 0x140; - - public override void AlterMeleeDamageFrom(Mobile from, ref int damage) - { - if (FieldActive) - { - damage = 0; // no melee damage when the field is up - } - } - - public override void AlterSpellDamageFrom(Mobile caster, ref int damage) - { - if (!FieldActive) - { - damage = 0; // no spell damage when the field is down - } - } - - public override void OnDamagedBySpell(Mobile from, int damage) - { - if (from?.Alive == true && Utility.RandomDouble() < 0.4) - { - SendEBolt(from); - } - - if (!FieldActive) - { - // should there be an effect when spells nullifying is on? - FixedParticles(0, 10, 0, 0x2522, EffectLayer.Waist); - } - else if (FieldActive && !CanUseField) - { - FieldActive = false; - - // TODO: message and effect when field turns down; cannot be verified on OSI due to a bug - FixedParticles(0x3735, 1, 30, 0x251F, EffectLayer.Waist); - } - } - - public override void OnGotMeleeAttack(Mobile attacker, int damage) - { - base.OnGotMeleeAttack(attacker, damage); - - if (FieldActive) - { - FixedParticles(0x376A, 20, 10, 0x2530, EffectLayer.Waist); - - PlaySound(0x2F4); - - attacker.SendAsciiMessage("Your weapon cannot penetrate the creature's magical barrier"); - } - - if (attacker?.Alive == true && attacker.Weapon is BaseRanged && Utility.RandomDouble() < 0.4) - { - SendEBolt(attacker); - } - } - - public override void OnThink() - { - base.OnThink(); - - // TODO: an OSI bug prevents to verify if the field can regenerate or not - if (!FieldActive && !IsHurt()) - { - FieldActive = true; - } - } - - public override bool Move(Direction d) - { - var move = base.Move(d); - - if (move && FieldActive && Combatant != null) - { - FixedParticles(0, 10, 0, 0x2530, EffectLayer.Waist); - } - - return move; - } - - public void SendEBolt(Mobile to) - { - MovingParticles(to, 0x379F, 7, 0, false, true, 0xBE3, 0xFCB, 0x211); - to.PlaySound(0x229); - DoHarmful(to); - AOS.Damage(to, this, 50, 0, 0, 0, 0, 100); - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - writer.Write(0); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - var version = reader.ReadInt(); - - FieldActive = CanUseField; - - if (Name == "Exodus Overseer") - { - Name = null; - } + PackItem(new ArcaneGem()); } } + + public ExodusOverseer(Serial serial) : base(serial) + { + } + + public override string CorpseName => "an overseer's corpse"; + + public override bool IsScaredOfScaryThings => false; + public override bool IsScaryToPets => true; + + public override string DefaultName => "an exodus overseer"; + + public override bool AutoDispel => true; + public override bool BardImmune => !Core.AOS; + public override Poison PoisonImmune => Poison.Lethal; + + public override void GenerateLoot() + { + AddLoot(LootPack.Rich); + } + + public override int GetIdleSound() => 0xFD; + + public override int GetAngerSound() => 0x26C; + + public override int GetDeathSound() => 0x211; + + public override int GetAttackSound() => 0x23B; + + public override int GetHurtSound() => 0x140; + + private static MonsterAbility[] _abilities = + { + // OSI changed the ability some time around UOML + Core.ML ? MonsterAbility.EnergyBoltCounter : MonsterAbility.MagicalBarrier + }; + + public override MonsterAbility[] GetMonsterAbilities() => _abilities; + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + writer.Write(0); + } + + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + var version = reader.ReadInt(); + } } diff --git a/Projects/UOContent/Mobiles/Monsters/Ore Elementals/CopperElemental.cs b/Projects/UOContent/Mobiles/Monsters/Ore Elementals/CopperElemental.cs index 7f601fa83..c5da4fd2f 100644 --- a/Projects/UOContent/Mobiles/Monsters/Ore Elementals/CopperElemental.cs +++ b/Projects/UOContent/Mobiles/Monsters/Ore Elementals/CopperElemental.cs @@ -60,6 +60,7 @@ public class CopperElemental : BaseCreature public override void AlterMeleeDamageFrom(Mobile from, ref int damage) { base.AlterMeleeDamageFrom(from, ref damage); + damage /= 2; // 50% melee damage }