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:
parent
1cf5d59116
commit
3c636b04fd
25 changed files with 509 additions and 449 deletions
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) =>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
25
Projects/UOContent/Mobiles/Abilities/EnergyBoltCounter.cs
Normal file
25
Projects/UOContent/Mobiles/Abilities/EnergyBoltCounter.cs
Normal 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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
83
Projects/UOContent/Mobiles/Abilities/MagicalBarrier.cs
Normal file
83
Projects/UOContent/Mobiles/Abilities/MagicalBarrier.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,5 +12,7 @@ public enum MonsterAbilityType
|
|||
ThrowWeapon,
|
||||
DestroyEquipment,
|
||||
DeathExplosion,
|
||||
MassDrainLife
|
||||
MassDrainLife,
|
||||
MagicalBarrier,
|
||||
EnergyBoltCounter
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue