feat: Adds magical barrier and ebolt counter ability (#1182)

* Adds magical barrier ability (Pre-UOML)
* Adds ebolt counter (Post-UOML)
This commit is contained in:
Kamron Batman 2022-10-02 20:48:53 -07:00 committed by GitHub
parent 1cf5d59116
commit 3c636b04fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 509 additions and 449 deletions

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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<Mobile>.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) =>

View file

@ -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)

View file

@ -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)

View file

@ -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<Item>.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);
}

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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<Mobile, ExpireTimer>();
_table[target] = timer;
base.Trigger(source, target);
base.Trigger(trigger, source, target);
}
private class ExpireTimer : Timer

View file

@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
namespace Server.Mobiles;
public class MagicalBarrier : MonsterAbility
{
private HashSet<Mobile> _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);
}
}
}

View file

@ -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();
}

View file

@ -23,9 +23,8 @@ public abstract partial class MonsterAbility
/// <summary>
/// Returns true if ability is not on cooldown, and the change to trigger succeeds.
/// </summary>
/// <param name="source">The mobile this ability is attached to</param>
/// <returns>Boolean indicating the ability can trigger.</returns>
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.
/// </summary>
/// <param name="source"></param>
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<BaseCreature, long>();
_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)
{
}
}

View file

@ -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);
}

View file

@ -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
}

View file

@ -12,5 +12,7 @@ public enum MonsterAbilityType
ThrowWeapon,
DestroyEquipment,
DeathExplosion,
MassDrainLife
MassDrainLife,
MagicalBarrier,
EnergyBoltCounter
}

View file

@ -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)

View file

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

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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;
}

View file

@ -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()

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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
}