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 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;
|
public static bool IsPoisonImmune(BaseCreature bc) => bc?.PoisonImmune != null;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -194,26 +194,24 @@ namespace Server
|
||||||
totalDamage = m.Hits;
|
totalDamage = m.Hits;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (from?.Deleted == false && from.Alive)
|
if (from is { Deleted: false, Alive: true })
|
||||||
{
|
{
|
||||||
var reflectPhys = AosAttributes.GetValue(m, AosAttribute.ReflectPhysical);
|
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 (reflectPhys != 0)
|
||||||
{
|
{
|
||||||
if ((from as ExodusMinion)?.FieldActive == true ||
|
var reflectDamage = Scale(
|
||||||
(from as ExodusOverseer)?.FieldActive == true)
|
damage * phys * (100 - (ignoreArmor ? 0 : m.PhysicalResistance)) / 10000,
|
||||||
{
|
reflectPhys
|
||||||
from.FixedParticles(0x376A, 20, 10, 0x2530, EffectLayer.Waist);
|
);
|
||||||
from.PlaySound(0x2F4);
|
|
||||||
m.SendAsciiMessage("Your weapon cannot penetrate the creature's magical barrier");
|
from.Damage(reflectDamage, m);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
from.Damage(
|
|
||||||
Scale(damage * phys * (100 - (ignoreArmor ? 0 : m.PhysicalResistance)) / 10000, reflectPhys),
|
|
||||||
m
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ public abstract class AreaEffectMonsterAbility : MonsterAbility
|
||||||
{
|
{
|
||||||
public virtual int AreaRange => 3;
|
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);
|
var eable = source.GetMobilesInRange(AreaRange);
|
||||||
using var queue = PooledRefQueue<Mobile>.Create();
|
using var queue = PooledRefQueue<Mobile>.Create();
|
||||||
|
|
@ -24,7 +24,7 @@ public abstract class AreaEffectMonsterAbility : MonsterAbility
|
||||||
DoEffectTarget(source, queue.Dequeue());
|
DoEffectTarget(source, queue.Dequeue());
|
||||||
}
|
}
|
||||||
|
|
||||||
base.Trigger(source, target);
|
base.Trigger(trigger, source, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual bool CanEffectTarget(BaseCreature source, Mobile defender) =>
|
protected virtual bool CanEffectTarget(BaseCreature source, Mobile defender) =>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ namespace Server.Mobiles;
|
||||||
|
|
||||||
public class ColossalBlow : StunAttack
|
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);
|
target.Animate(21, 6, 1, true, false, 0);
|
||||||
source.PlaySound(0xEE);
|
source.PlaySound(0xEE);
|
||||||
|
|
@ -15,7 +15,7 @@ public class ColossalBlow : StunAttack
|
||||||
"You have been stunned by a colossal blow!"
|
"You have been stunned by a colossal blow!"
|
||||||
);
|
);
|
||||||
|
|
||||||
base.Trigger(source, target);
|
base.Trigger(trigger, source, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Recover(Mobile defender)
|
protected override void Recover(Mobile defender)
|
||||||
|
|
|
||||||
|
|
@ -4,21 +4,21 @@ namespace Server.Mobiles;
|
||||||
|
|
||||||
public class DeathExplosion : AreaEffectMonsterAbility
|
public class DeathExplosion : AreaEffectMonsterAbility
|
||||||
{
|
{
|
||||||
public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.OnDeath;
|
public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.Death;
|
||||||
public override MonsterAbilityType AbilityType => MonsterAbilityType.DeathExplosion;
|
public override MonsterAbilityType AbilityType => MonsterAbilityType.DeathExplosion;
|
||||||
|
|
||||||
public override double ChanceToTrigger => 1.0;
|
public override double ChanceToTrigger => 1.0;
|
||||||
public override TimeSpan MinTriggerCooldown => TimeSpan.Zero;
|
public override TimeSpan MinTriggerCooldown => TimeSpan.Zero;
|
||||||
public override TimeSpan MaxTriggerCooldown => 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.FixedParticles(0x36BD, 20, 10, 5044, EffectLayer.Head);
|
||||||
source. PlaySound(0x307);
|
source. PlaySound(0x307);
|
||||||
|
|
||||||
base.Trigger(source, target);
|
base.Trigger(trigger, source, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void DoEffectTarget(BaseCreature source, Mobile defender)
|
protected override void DoEffectTarget(BaseCreature source, Mobile defender)
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ public class DestroyEquipment : MonsterAbilityAttack
|
||||||
|
|
||||||
public virtual int AttackRange => 1;
|
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();
|
using var queue = PooledRefQueue<Item>.Create();
|
||||||
|
|
||||||
for (var i = 0; i < defender.Items.Count; i++)
|
for (var i = 0; i < defender.Items.Count; i++)
|
||||||
|
|
@ -50,6 +50,6 @@ public class DestroyEquipment : MonsterAbilityAttack
|
||||||
defender.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1080035, name);
|
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);
|
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 class FireBreath : MonsterAbility
|
||||||
{
|
{
|
||||||
public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.OnAction;
|
public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.Think;
|
||||||
public override MonsterAbilityType AbilityType => MonsterAbilityType.FireBreath;
|
public override MonsterAbilityType AbilityType => MonsterAbilityType.FireBreath;
|
||||||
public override double ChanceToTrigger => 0.5;
|
public override double ChanceToTrigger => 0.5;
|
||||||
|
|
||||||
|
|
@ -45,9 +45,9 @@ public class FireBreath : MonsterAbility
|
||||||
|
|
||||||
public virtual int BreathEffectSound => 0x227;
|
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))
|
if (CanFireBreathTarget(source, target))
|
||||||
{
|
{
|
||||||
|
|
@ -59,7 +59,7 @@ public class FireBreath : MonsterAbility
|
||||||
|
|
||||||
Timer.StartTimer(TimeSpan.FromSeconds(BreathEffectDelay), () => BreathEffect_Callback(source, target));
|
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 MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.GiveMeleeDamage;
|
||||||
public override double ChanceToTrigger => 0.10;
|
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))
|
if (_table.Remove(target, out var timer))
|
||||||
{
|
{
|
||||||
|
|
@ -55,7 +55,7 @@ public class GraspingClaw : MonsterAbility
|
||||||
_table ??= new Dictionary<Mobile, ExpireTimer>();
|
_table ??= new Dictionary<Mobile, ExpireTimer>();
|
||||||
_table[target] = timer;
|
_table[target] = timer;
|
||||||
|
|
||||||
base.Trigger(source, target);
|
base.Trigger(trigger, source, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ExpireTimer : Timer
|
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
|
// Explosions
|
||||||
public static DeathExplosion DeathExplosion => new();
|
public static DeathExplosion DeathExplosion => new();
|
||||||
|
|
||||||
|
// Direct attack counters
|
||||||
public static ThrowHatchetCounter ThrowHatchetCounter => new();
|
public static ThrowHatchetCounter ThrowHatchetCounter => new();
|
||||||
|
public static EnergyBoltCounter EnergyBoltCounter => new();
|
||||||
|
|
||||||
public static DestroyEquipment DestroyEquipment => new();
|
public static DestroyEquipment DestroyEquipment => new();
|
||||||
|
|
||||||
public static DrainLifeAreaAttack DrainLifeAreaAttack => new();
|
public static DrainLifeAreaAttack DrainLifeAreaAttack => new();
|
||||||
|
|
||||||
|
public static MagicalBarrier MagicalBarrier => new();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,8 @@ public abstract partial class MonsterAbility
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if ability is not on cooldown, and the change to trigger succeeds.
|
/// Returns true if ability is not on cooldown, and the change to trigger succeeds.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="source">The mobile this ability is attached to</param>
|
|
||||||
/// <returns>Boolean indicating the ability can trigger.</returns>
|
/// <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)
|
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
|
/// Triggers the monster's ability. Override this and call `base.Trigger(source);` to make sure
|
||||||
/// the cooldown is tracked.
|
/// the cooldown is tracked.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="source"></param>
|
public virtual void Trigger(MonsterAbilityTrigger trigger, BaseCreature source, Mobile target)
|
||||||
public virtual void Trigger(BaseCreature source, Mobile target)
|
|
||||||
{
|
{
|
||||||
if (MinTriggerCooldown <= TimeSpan.Zero && MaxTriggerCooldown <= TimeSpan.Zero)
|
if (MinTriggerCooldown <= TimeSpan.Zero && MaxTriggerCooldown <= TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
|
|
@ -70,4 +68,32 @@ public abstract partial class MonsterAbility
|
||||||
_nextTriggerTicks ??= new Dictionary<BaseCreature, long>();
|
_nextTriggerTicks ??= new Dictionary<BaseCreature, long>();
|
||||||
_nextTriggerTicks[source] = nextTrigger;
|
_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 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);
|
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);
|
defender.Alive && source.CanBeHarmful(defender);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,18 @@
|
||||||
namespace Server.Mobiles;
|
namespace Server.Mobiles;
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
public enum MonsterAbilityTrigger
|
public enum MonsterAbilityTrigger : ulong
|
||||||
{
|
{
|
||||||
None = 0x00000000, // Passive, or some custom event.
|
None = 0x0000000000000000, // Passive, or some custom event.
|
||||||
OnAction = 0x00000001, // On Think
|
Think = 0x0000000000000001,
|
||||||
TakeMeleeDamage = 0x00000002,
|
TakeMeleeDamage = 0x0000000000000002,
|
||||||
GiveMeleeDamage = 0x00000004, // Includes firing a bow
|
GiveMeleeDamage = 0x0000000000000004, // Includes firing a bow
|
||||||
TakeSpellDamage = 0x00000008,
|
TakeSpellDamage = 0x0000000000000008,
|
||||||
GiveSpellDamage = 0x00000010,
|
GiveSpellDamage = 0x0000000000000010,
|
||||||
OnCombatAction = 0x00000020,
|
CombatAction = 0x0000000000000020,
|
||||||
OnDeath = 0x00000040,
|
Death = 0x0000000000000040,
|
||||||
|
Movement = 0x0000000000000080,
|
||||||
|
|
||||||
|
GiveDamage = GiveMeleeDamage | GiveSpellDamage,
|
||||||
TakeDamage = TakeMeleeDamage | TakeSpellDamage
|
TakeDamage = TakeMeleeDamage | TakeSpellDamage
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,7 @@ public enum MonsterAbilityType
|
||||||
ThrowWeapon,
|
ThrowWeapon,
|
||||||
DestroyEquipment,
|
DestroyEquipment,
|
||||||
DeathExplosion,
|
DeathExplosion,
|
||||||
MassDrainLife
|
MassDrainLife,
|
||||||
|
MagicalBarrier,
|
||||||
|
EnergyBoltCounter
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ namespace Server.Mobiles;
|
||||||
|
|
||||||
public class PoisonGasAreaAttack : AreaEffectMonsterAbility
|
public class PoisonGasAreaAttack : AreaEffectMonsterAbility
|
||||||
{
|
{
|
||||||
public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.OnCombatAction;
|
public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.CombatAction;
|
||||||
public override MonsterAbilityType AbilityType => MonsterAbilityType.MassPoison;
|
public override MonsterAbilityType AbilityType => MonsterAbilityType.MassPoison;
|
||||||
|
|
||||||
public override double ChanceToTrigger => 0.5;
|
public override double ChanceToTrigger => 0.5;
|
||||||
|
|
@ -13,9 +13,9 @@ public class PoisonGasAreaAttack : AreaEffectMonsterAbility
|
||||||
|
|
||||||
public virtual int CombatantRange => 3;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -27,12 +27,12 @@ public class PoisonGasAreaAttack : AreaEffectMonsterAbility
|
||||||
source.CanBeHarmful(combatant) && source.InLOS(combatant);
|
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.FixedParticles(0x376A, 9, 32, 0x2539, EffectLayer.LeftHand);
|
||||||
source.PlaySound(0x1DE);
|
source.PlaySound(0x1DE);
|
||||||
|
|
||||||
base.Trigger(source, target);
|
base.Trigger(trigger, source, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void DoEffectTarget(BaseCreature source, Mobile defender)
|
protected override void DoEffectTarget(BaseCreature source, Mobile defender)
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ public class PoisonGasCounter : MonsterAbilityAttack
|
||||||
public override double ChanceToTrigger => 0.05;
|
public override double ChanceToTrigger => 0.05;
|
||||||
public virtual int AttackRange => 1;
|
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 };
|
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);
|
source.Animate(10, 4, 1, true, false, 0);
|
||||||
AOS.Damage(defender, source, 50, 100, 0, 0, 0, 0, 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 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)
|
if (source.Weapon is BaseWeapon weapon)
|
||||||
{
|
{
|
||||||
|
|
@ -33,7 +33,7 @@ public abstract class StunAttack : MonsterAbility
|
||||||
Timer.DelayCall(StunDuration, Recover, target);
|
Timer.DelayCall(StunDuration, Recover, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
base.Trigger(source, target);
|
base.Trigger(trigger, source, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Recover(Mobile defender)
|
protected virtual void Recover(Mobile defender)
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ public class SummonLesserUndead : SummonUndead
|
||||||
{
|
{
|
||||||
private int _group;
|
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);
|
_group = Utility.Random(2);
|
||||||
base.Trigger(source, target);
|
base.Trigger(trigger, source, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override BaseCreature CreateSummon(BaseCreature source)
|
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 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 amount = AmountToSummon - source.Followers;
|
||||||
var distance = SummonRange;
|
var distance = SummonRange;
|
||||||
|
|
@ -58,7 +58,7 @@ public abstract class SummonUndead : MonsterAbility
|
||||||
summon.PlaySound(summon.GetAttackSound());
|
summon.PlaySound(summon.GetAttackSound());
|
||||||
}
|
}
|
||||||
|
|
||||||
base.Trigger(source, target);
|
base.Trigger(trigger, source, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EndPolymorph(BaseCreature source)
|
private static void EndPolymorph(BaseCreature source)
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,12 @@ public class ThrowHatchetCounter : MonsterAbilityAttack
|
||||||
public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.TakeDamage;
|
public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.TakeDamage;
|
||||||
public override double ChanceToTrigger => 0.4;
|
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);
|
source.MovingEffect(defender, 0xF43, 10, 0, false, false);
|
||||||
AOS.Damage(defender, source, 50, 100, 0, 0, 0, 0, 0);
|
AOS.Damage(defender, source, 50, 100, 0, 0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool CanEffectTarget(BaseCreature source, Mobile defender) =>
|
protected override bool CanEffectTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender) =>
|
||||||
base.CanEffectTarget(source, defender) && defender.Weapon is BaseRanged;
|
base.CanEffectTarget(trigger, source, defender) && defender.Weapon is BaseRanged;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1118,24 +1118,25 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
public virtual MonsterAbility[] GetMonsterAbilities() => null;
|
public virtual MonsterAbility[] GetMonsterAbilities() => null;
|
||||||
|
|
||||||
public virtual bool HasAbility(MonsterAbilityType type)
|
public virtual MonsterAbility GetAbility(MonsterAbilityType type)
|
||||||
{
|
{
|
||||||
var abilities = GetMonsterAbilities();
|
var abilities = GetMonsterAbilities();
|
||||||
|
|
||||||
if (abilities == null)
|
if (abilities == null)
|
||||||
{
|
{
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < abilities.Length; i++)
|
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)
|
public virtual bool HasAbility(MonsterAbility ability)
|
||||||
|
|
@ -1170,9 +1171,96 @@ namespace Server.Mobiles
|
||||||
for (var i = 0; i < abilities.Length; i++)
|
for (var i = 0; i < abilities.Length; i++)
|
||||||
{
|
{
|
||||||
var ability = abilities[i];
|
var ability = abilities[i];
|
||||||
if (ability.WillTrigger(trigger) && ability.CanTrigger(this))
|
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);
|
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)
|
public virtual void Dispel(Mobile m)
|
||||||
{
|
{
|
||||||
Effects.SendLocationParticles(
|
Effects.SendLocationParticles(
|
||||||
|
|
@ -2869,7 +2968,7 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
public override bool OnBeforeDeath()
|
public override bool OnBeforeDeath()
|
||||||
{
|
{
|
||||||
TriggerAbility(MonsterAbilityTrigger.OnDeath, null);
|
TriggerAbility(MonsterAbilityTrigger.Death, null);
|
||||||
|
|
||||||
var treasureLevel = TreasureMapLevel;
|
var treasureLevel = TreasureMapLevel;
|
||||||
|
|
||||||
|
|
@ -3517,7 +3616,7 @@ namespace Server.Mobiles
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fire breath, etc.
|
// Fire breath, etc.
|
||||||
TriggerAbility(MonsterAbilityTrigger.OnAction, Combatant);
|
TriggerAbility(MonsterAbilityTrigger.Think, Combatant);
|
||||||
|
|
||||||
if ((CanHeal || CanHealOwner) && Alive && !IsHealing && !BardPacified)
|
if ((CanHeal || CanHealOwner) && Alive && !IsHealing && !BardPacified)
|
||||||
{
|
{
|
||||||
|
|
@ -4021,26 +4120,32 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
public virtual void AlterDamageScalarFrom(Mobile caster, ref double scalar)
|
public virtual void AlterDamageScalarFrom(Mobile caster, ref double scalar)
|
||||||
{
|
{
|
||||||
|
TriggerAbilityAlterDamageScalar(MonsterAbilityTrigger.TakeSpellDamage, caster, ref scalar);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void AlterDamageScalarTo(Mobile target, ref double 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)
|
public virtual void AlterSpellDamageFrom(Mobile from, ref int damage)
|
||||||
{
|
{
|
||||||
|
TriggerAbilityAlterDamage(MonsterAbilityTrigger.TakeSpellDamage, from, ref damage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void AlterSpellDamageTo(Mobile to, ref int 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)
|
public virtual void AlterMeleeDamageFrom(Mobile from, ref int damage)
|
||||||
{
|
{
|
||||||
|
TriggerAbilityAlterDamage(MonsterAbilityTrigger.TakeMeleeDamage, from, ref damage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void AlterMeleeDamageTo(Mobile to, ref int damage)
|
public virtual void AlterMeleeDamageTo(Mobile to, ref int damage)
|
||||||
{
|
{
|
||||||
|
TriggerAbilityAlterDamage(MonsterAbilityTrigger.GiveMeleeDamage, to, ref damage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool CheckFoodPreference(Item f) =>
|
public virtual bool CheckFoodPreference(Item f) =>
|
||||||
|
|
@ -4081,16 +4186,11 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
if (amount > 0)
|
if (amount > 0)
|
||||||
{
|
{
|
||||||
int stamGain;
|
int stamGain = f switch
|
||||||
|
|
||||||
if (f is Gold)
|
|
||||||
{
|
{
|
||||||
stamGain = amount - 50;
|
Gold => amount - 50,
|
||||||
}
|
_ => amount * 15 - 50
|
||||||
else
|
};
|
||||||
{
|
|
||||||
stamGain = amount * 15 - 50;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stamGain > 0)
|
if (stamGain > 0)
|
||||||
{
|
{
|
||||||
|
|
@ -4172,7 +4272,7 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
public virtual void OnActionCombat()
|
public virtual void OnActionCombat()
|
||||||
{
|
{
|
||||||
TriggerAbility(MonsterAbilityTrigger.OnCombatAction, null);
|
TriggerAbility(MonsterAbilityTrigger.CombatAction, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnActionGuard()
|
public virtual void OnActionGuard()
|
||||||
|
|
|
||||||
|
|
@ -1,195 +1,107 @@
|
||||||
using Server.Items;
|
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]
|
Body = 0x2F5;
|
||||||
public ExodusMinion() : base(AIType.AI_Melee)
|
|
||||||
|
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;
|
case 0:
|
||||||
|
{
|
||||||
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:
|
|
||||||
PackItem(new PowerCrystal());
|
PackItem(new PowerCrystal());
|
||||||
break;
|
break;
|
||||||
case 1:
|
}
|
||||||
|
case 1:
|
||||||
|
{
|
||||||
PackItem(new ArcaneGem());
|
PackItem(new ArcaneGem());
|
||||||
break;
|
break;
|
||||||
case 2:
|
}
|
||||||
|
case 2:
|
||||||
|
{
|
||||||
PackItem(new ClockworkAssembly());
|
PackItem(new ClockworkAssembly());
|
||||||
break;
|
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;
|
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]
|
Body = 0x2F4;
|
||||||
public ExodusOverseer() : base(AIType.AI_Melee)
|
|
||||||
|
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;
|
PackItem(new PowerCrystal());
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
public ExodusOverseer(Serial serial) : base(serial)
|
|
||||||
{
|
{
|
||||||
}
|
PackItem(new ArcaneGem());
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
public override void AlterMeleeDamageFrom(Mobile from, ref int damage)
|
||||||
{
|
{
|
||||||
base.AlterMeleeDamageFrom(from, ref damage);
|
base.AlterMeleeDamageFrom(from, ref damage);
|
||||||
|
|
||||||
damage /= 2; // 50% melee damage
|
damage /= 2; // 50% melee damage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue