feat: Adds Monster Abilities (#1179)

* Adds ability to create monster abilities
* Adds Fire/Cold/Chaos Breath
* Adds Grasping Claw
* Adds Summon Skeletons & Summon Undead w/ Polymorph
This commit is contained in:
Kamron Batman 2022-10-01 21:09:36 -07:00 committed by GitHub
parent 8a2c0ead3d
commit 43e7fe29c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
91 changed files with 1262 additions and 977 deletions

View file

@ -1062,6 +1062,9 @@ public static class Utility
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool RandomBool() => RandomSources.Source.NextBool();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TimeSpan RandomMinMax(TimeSpan min, TimeSpan max) => new(RandomMinMax(min.Ticks, max.Ticks));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double RandomMinMax(double min, double max)
{
@ -1540,4 +1543,67 @@ public static class Utility
return combined;
}
public static Point3D GetValidLocation(Map map, Point3D center, int range, int retries = 10)
{
if (map == null)
{
return center;
}
var loc = new Point3D(center.Z, center.Y, center.Z);
for (var i = 0; i < retries; i++)
{
loc.X = center.X + (Random(range * 2 + 1) - range);
loc.Y = center.Y + (Random(range * 2 + 1) - range);
loc.Z = center.Z;
if (map.CanSpawnMobile(loc))
{
return loc;
}
loc.Z = map.GetAverageZ(loc.X, loc.Y);
if (map.CanSpawnMobile(loc))
{
return loc;
}
}
return center;
}
public static Point3D GetValidLocationInLOS(Map map, Mobile from, int range, int retries = 10)
{
if (map == null)
{
return from.Location;
}
var center = from.Location;
var loc = center;
for (var i = 0; i < retries; i++)
{
loc.X = center.X + (Random(range * 2 + 1) - range);
loc.Y = center.Y + (Random(range * 2 + 1) - range);
loc.Z = center.Z;
if (map.CanSpawnMobile(loc) && map.LineOfSight(from, loc))
{
return loc;
}
loc.Z = map.GetAverageZ(loc.X, loc.Y);
if (map.CanSpawnMobile(loc) && map.LineOfSight(from, loc))
{
return loc;
}
}
return center;
}
}

View file

@ -39,32 +39,26 @@ namespace Server.Mobiles
ControlSlots = 1;
}
public UnholySteed(Serial serial)
: base(serial)
public UnholySteed(Serial serial) : base(serial)
{
}
public override string CorpseName => "an unholy corpse";
public override bool IsDispellable => false;
public override bool IsBondable => false;
public override bool HasBreath => true;
public override bool CanBreath => true;
public override FoodType FavoriteFood => FoodType.FruitsAndVegies | FoodType.GrainsAndHay;
private static MonsterAbility[] _abilities = { MonsterAbility.FireBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override string ApplyNameSuffix(string suffix)
{
if (suffix.Length == 0)
{
suffix = Ethic.Evil.Definition.Adjunct.String;
}
else
{
suffix = $"{suffix} {Ethic.Evil.Definition.Adjunct.String}";
return base.ApplyNameSuffix(Ethic.Evil.Definition.Adjunct.String);
}
return base.ApplyNameSuffix(suffix);
return base.ApplyNameSuffix($"{suffix} {Ethic.Evil.Definition.Adjunct.String}");
}
public override void OnDoubleClick(Mobile from)

View file

@ -39,32 +39,26 @@ namespace Server.Mobiles
ControlSlots = 1;
}
public HolySteed(Serial serial)
: base(serial)
public HolySteed(Serial serial) : base(serial)
{
}
public override string CorpseName => "a holy corpse";
public override bool IsDispellable => false;
public override bool IsBondable => false;
public override bool HasBreath => true;
public override bool CanBreath => true;
public override FoodType FavoriteFood => FoodType.FruitsAndVegies | FoodType.GrainsAndHay;
private static MonsterAbility[] _abilities = { MonsterAbility.FireBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override string ApplyNameSuffix(string suffix)
{
if (suffix.Length == 0)
{
suffix = Ethic.Hero.Definition.Adjunct.String;
}
else
{
suffix = $"{suffix} {Ethic.Hero.Definition.Adjunct.String}";
return base.ApplyNameSuffix(Ethic.Hero.Definition.Adjunct.String);
}
return base.ApplyNameSuffix(suffix);
return base.ApplyNameSuffix($"{suffix} {Ethic.Hero.Definition.Adjunct.String}");
}
public override void OnDoubleClick(Mobile from)

View file

@ -247,7 +247,7 @@ namespace Server.Items
public static bool IsMageryCreature(BaseCreature bc) => bc?.AI == AIType.AI_Mage && bc.Skills.Magery.Base > 5.0;
public static bool IsFireBreathingCreature(BaseCreature bc) => bc?.HasBreath == true;
public static bool IsFireBreathingCreature(BaseCreature bc) => bc?.HasAbility(MonsterAbilityType.FireBreath) == true;
public static bool IsPoisonImmune(BaseCreature bc) => bc?.PoisonImmune != null;

View file

@ -83,8 +83,7 @@ namespace Server.Items
public static readonly WeaponAbility InfusedThrow = Abilities[30];
public static readonly WeaponAbility MysticArc = Abilities[31];
private static readonly Dictionary<Mobile, WeaponAbilityContext> m_PlayersTable =
new();
private static readonly Dictionary<Mobile, WeaponAbilityContext> m_PlayersTable = new();
public virtual int BaseMana => 0;

View file

@ -2085,8 +2085,8 @@ namespace Server.Items
}
}
bcAtt?.OnGaveMeleeAttack(defender);
bcDef?.OnGotMeleeAttack(attacker);
bcAtt?.OnGaveMeleeAttack(defender, damage);
bcDef?.OnGotMeleeAttack(attacker, damage);
a?.OnHit(attacker, defender, damage);
move?.OnHit(attacker, defender, damage);

View file

@ -37,14 +37,12 @@ namespace Server
public static int Damage(
Mobile m, Mobile from, int damage, int phys, int fire, int cold, int pois, int nrgy,
int chaos
) =>
Damage(m, from, damage, false, phys, fire, cold, pois, nrgy, chaos);
) => Damage(m, from, damage, false, phys, fire, cold, pois, nrgy, chaos);
public static int Damage(
Mobile m, Mobile from, int damage, int phys, int fire, int cold, int pois, int nrgy,
bool keepAlive
) =>
Damage(m, from, damage, false, phys, fire, cold, pois, nrgy, 0, 0, keepAlive);
) => Damage(m, from, damage, false, phys, fire, cold, pois, nrgy, 0, 0, keepAlive);
public static int Damage(
Mobile m, Mobile from, int damage, bool ignoreArmor, int phys, int fire, int cold, int pois,
@ -80,20 +78,30 @@ namespace Server
switch (Utility.Random(5))
{
case 0:
phys += chaos;
break;
{
phys += chaos;
break;
}
case 1:
fire += chaos;
break;
{
fire += chaos;
break;
}
case 2:
cold += chaos;
break;
{
cold += chaos;
break;
}
case 3:
pois += chaos;
break;
{
pois += chaos;
break;
}
case 4:
nrgy += chaos;
break;
{
nrgy += chaos;
break;
}
}
}

View file

@ -0,0 +1,8 @@
namespace Server.Mobiles;
public class ChaosBreath : FireBreath
{
public override int BreathChaosDamage => 100;
public override int BreathFireDamage => 0;
}

View file

@ -0,0 +1,8 @@
namespace Server.Mobiles;
public class ColdBreath : FireBreath
{
public override int BreathColdDamage => 100;
public override int BreathFireDamage => 0;
public override int BreathEffectHue => 0x480;
}

View file

@ -0,0 +1,218 @@
using System;
using Server.Spells.Bushido;
namespace Server.Mobiles;
public class FireBreath : MonsterAbility
{
public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.Action;
public override MonsterAbilityType AbilityType => MonsterAbilityType.FireBreath;
public override double ChanceToTrigger => 0.5;
// Min/max seconds until next breath
public override TimeSpan MinTriggerCooldown => TimeSpan.FromSeconds(30.0);
public override TimeSpan MaxTriggerCooldown => TimeSpan.FromSeconds(45.0);
// Base damage given is: CurrentHitPoints * BreathDamageScalar
public virtual double BreathDamageScalar => Core.AOS ? 0.16 : 0.05;
// Creature stops moving for 1.0 seconds while breathing
public virtual double BreathStallTime => 1.0;
// Effect is sent 1.3 seconds after BreathAngerSound and BreathAngerAnimation is played
public virtual double BreathEffectDelay => 1.3;
// Damage is given 1.0 seconds after effect is sent
public virtual double BreathDamageDelay => 1.0;
public virtual int BreathRange(BaseCreature source) => source.RangePerception;
// Damage Types
public virtual int BreathChaosDamage => 0;
public virtual int BreathPhysicalDamage => 0;
public virtual int BreathFireDamage => 100;
public virtual int BreathColdDamage => 0;
public virtual int BreathPoisonDamage => 0;
public virtual int BreathEnergyDamage => 0;
// Effect details and sound
public virtual int BreathEffectItemID => 0x36D4;
public virtual int BreathEffectSpeed => 5;
public virtual int BreathEffectDuration => 0;
public virtual bool BreathEffectExplodes => false;
public virtual bool BreathEffectFixedDir => false;
public virtual int BreathEffectHue => 0;
public virtual int BreathEffectRenderMode => 0;
public virtual int BreathEffectSound => 0x227;
public override bool CanTrigger(BaseCreature source) => !source.Summoned && base.CanTrigger(source);
public override void Trigger(BaseCreature source, Mobile target)
{
if (CanFireBreathTarget(source, target))
{
BreathStallMovement(source);
BreathPlayAngerSound(source);
BreathPlayAngerAnimation(source);
source.Direction = source.GetDirectionTo(target);
Timer.StartTimer(TimeSpan.FromSeconds(BreathEffectDelay), () => BreathEffect_Callback(source, target));
base.Trigger(source, target);
}
}
public virtual bool CanFireBreathTarget(BaseCreature source, Mobile target)
{
var range = BreathRange(source);
return !source.IsDeadBondedPet && !source.BardPacified && target is { Alive: true, IsDeadBondedPet: false } &&
target.Map == source.Map && source.CanBeHarmful(target) && target.InRange(source, range) &&
source.InLOS(target);
}
// Anger sound/animations
public virtual int BreathAngerSound(BaseCreature source) => source.GetAngerSound();
public virtual int BreathAngerAnimation => 12;
public virtual void BreathStallMovement(BaseCreature source)
{
if (source.AIObject != null)
{
source.AIObject.NextMove = Core.TickCount + (int)(BreathStallTime * 1000);
}
}
public virtual void BreathPlayAngerSound(BaseCreature source)
{
var sound = BreathAngerSound(source);
source.PlaySound(sound);
}
public virtual void BreathPlayAngerAnimation(BaseCreature source)
{
source.Animate(BreathAngerAnimation, 5, 1, true, false, 0);
}
public virtual void BreathEffect_Callback(BaseCreature source, Mobile target)
{
if (!target.Alive || !source.CanBeHarmful(target))
{
return;
}
BreathPlayEffectSound(source);
BreathPlayEffect(source, target);
Timer.StartTimer(TimeSpan.FromSeconds(BreathDamageDelay), () => BreathDamage_Callback(source, target));
}
public virtual void BreathPlayEffectSound(BaseCreature source)
{
source.PlaySound(BreathEffectSound);
}
public virtual void BreathPlayEffect(BaseCreature source, Mobile target)
{
Effects.SendMovingEffect(
source,
target,
BreathEffectItemID,
BreathEffectSpeed,
BreathEffectDuration,
BreathEffectFixedDir,
BreathEffectExplodes,
BreathEffectHue,
BreathEffectRenderMode
);
}
public virtual void BreathDamage_Callback(BaseCreature source, Mobile target)
{
if (target is BaseCreature creature && creature.BreathImmune)
{
return;
}
if (source.CanBeHarmful(target))
{
source.DoHarmful(target);
BreathDealDamage(source, target);
}
}
public virtual void BreathDealDamage(BaseCreature source, Mobile target)
{
if (!Evasion.CheckSpellEvasion(target))
{
var physDamage = BreathPhysicalDamage;
var fireDamage = BreathFireDamage;
var coldDamage = BreathColdDamage;
var poisDamage = BreathPoisonDamage;
var nrgyDamage = BreathEnergyDamage;
if (BreathChaosDamage > 0)
{
switch (Utility.Random(5))
{
case 0:
{
physDamage += BreathChaosDamage;
break;
}
case 1:
{
fireDamage += BreathChaosDamage;
break;
}
case 2:
{
coldDamage += BreathChaosDamage;
break;
}
case 3:
{
poisDamage += BreathChaosDamage;
break;
}
case 4:
{
nrgyDamage += BreathChaosDamage;
break;
}
}
}
if (physDamage == 0 && fireDamage == 0 && coldDamage == 0 && poisDamage == 0 && nrgyDamage == 0)
{
target.Damage(BreathComputeDamage(source), source); // Unresistable damage even in AOS
}
else
{
AOS.Damage(
target,
source,
BreathComputeDamage(source),
physDamage,
fireDamage,
coldDamage,
poisDamage,
nrgyDamage
);
}
}
}
public virtual int BreathComputeDamage(BaseCreature source)
{
var damage = (int)(source.Hits * BreathDamageScalar);
if (source.IsParagon)
{
damage = (int)(damage / Paragon.HitsBuff);
}
return Math.Min(damage, 200);
}
}

View file

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
namespace Server.Mobiles;
public class GraspingClaw : MonsterAbility
{
private Dictionary<Mobile, ExpireTimer> _table;
public override MonsterAbilityType AbilityType => MonsterAbilityType.GraspingClaw;
public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.GiveMeleeDamage;
public override double ChanceToTrigger => 0.10;
public override void Trigger(BaseCreature source, Mobile target)
{
if (_table.Remove(target, out var timer))
{
timer.DoExpire();
target.SendLocalizedMessage(1070837); // The creature lands another blow in your weakened state.
}
else
{
// The blow from the creature's claws has made you more susceptible to physical attacks.
target.SendLocalizedMessage(1070836);
}
/**
* Grasping Claw
* Start cliloc: 1070836
* Effect: Physical resistance -15% for 5 seconds
* Refresh Cliloc: 1070837
* End cliloc: 1070838
* Effect:
* - Type: "3"
* - From: Player
* - To: 0x0
* - ItemId: "0x37B9"
* - ItemIdName: "glow"
* - FromLocation: "(1149 808, 32)"
* - ToLocation: "(1149 808, 32)"
* - Speed: "10" - Duration: "5"
* - FixedDirection: "True"
* - Explode: "False"
*/
var effect = -(target.PhysicalResistance * 15 / 100);
var mod = new ResistanceMod(ResistanceType.Physical, effect);
target.FixedEffect(0x37B9, 10, 5);
target.AddResistanceMod(mod);
timer = new ExpireTimer(this, target, mod, TimeSpan.FromSeconds(5.0));
timer.Start();
_table ??= new Dictionary<Mobile, ExpireTimer>();
_table[target] = timer;
base.Trigger(source, target);
}
private class ExpireTimer : Timer
{
private GraspingClaw _graspingClaw;
private Mobile _mobile;
private ResistanceMod _mod;
public ExpireTimer(GraspingClaw claw, Mobile m, ResistanceMod mod, TimeSpan delay) : base(delay)
{
_mobile = m;
_mod = mod;
_graspingClaw = claw;
}
public void DoExpire()
{
_mobile.RemoveResistanceMod(_mod);
Stop();
_graspingClaw._table?.Remove(_mobile);
}
protected override void OnTick()
{
_mobile.SendLocalizedMessage(1070838); // Your resistance to physical attacks has returned.
DoExpire();
}
}
}

View file

@ -0,0 +1,15 @@
namespace Server.Mobiles;
public abstract partial class MonsterAbility
{
// Fire Breath
public static FireBreath FireBreath => new();
public static ChaosBreath ChaosBreath => new();
public static ColdBreath ColdBreath => new();
public static GraspingClaw GraspingClaw => new();
// Summon Undead
public static SummonSkeletons SummonSkeletons => new();
public static SummonLesserUndead SummonLesserUndead => new();
}

View file

@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
namespace Server.Mobiles;
/// <summary>
/// Abstract class used to build singletons for managing a specific monster ability.
/// </summary>
public abstract partial class MonsterAbility
{
private Dictionary<BaseCreature, long> _nextTriggerTicks;
public virtual MonsterAbilityType AbilityType => MonsterAbilityType.Generic;
public virtual MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.None;
public virtual double ChanceToTrigger => 1.0;
public virtual TimeSpan MinTriggerCooldown => TimeSpan.Zero;
public virtual TimeSpan MaxTriggerCooldown => TimeSpan.Zero;
public bool WillTrigger(MonsterAbilityTrigger trigger) => (AbilityTrigger & trigger) != 0;
/// <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)
{
if (_nextTriggerTicks?.TryGetValue(source, out var nextTrigger) == true && Core.TickCount < nextTrigger)
{
return false;
}
var c = ChanceToTrigger;
if (c >= 1)
{
return true;
}
if (c <= 0)
{
return false;
}
var rnd = Utility.RandomDouble();
return c > rnd;
}
/// <summary>
/// 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)
{
if (MinTriggerCooldown <= TimeSpan.Zero && MaxTriggerCooldown <= TimeSpan.Zero)
{
return;
}
var randomAmount = MinTriggerCooldown == MaxTriggerCooldown
? MinTriggerCooldown
: Utility.RandomMinMax(MinTriggerCooldown, MaxTriggerCooldown);
var nextTrigger = Core.TickCount + (long)randomAmount.TotalMilliseconds;
_nextTriggerTicks ??= new Dictionary<BaseCreature, long>();
_nextTriggerTicks[source] = nextTrigger;
}
}

View file

@ -0,0 +1,16 @@
using System;
namespace Server.Mobiles;
[Flags]
public enum MonsterAbilityTrigger
{
None = 0x00000000, // Passive, or some custom event.
Action = 0x00000001, // On Think
TakeMeleeDamage = 0x00000002,
GiveMeleeDamage = 0x00000004, // Includes firing a bow
TakeSpellDamage = 0x00000008,
GiveSpellDamage = 0x00000010,
TakeDamage = TakeMeleeDamage | TakeSpellDamage
}

View file

@ -0,0 +1,9 @@
namespace Server.Mobiles;
public enum MonsterAbilityType
{
Generic,
FireBreath,
GraspingClaw,
SummonUndead
}

View file

@ -0,0 +1,33 @@
namespace Server.Mobiles;
public class SummonLesserUndead : SummonUndead
{
private int _group;
public override void Trigger(BaseCreature source, Mobile target)
{
_group = Utility.Random(2);
base.Trigger(source, target);
}
public override BaseCreature CreateSummon(BaseCreature source)
{
return _group switch
{
0 => Utility.Random(4) switch
{
0 => new Skeleton(),
1 => new SkeletalMage(),
2 => new Mummy(),
3 => new Zombie(),
},
_ => Utility.Random(4) switch
{
0 => new Ghoul(),
1 => new Spectre(),
2 => new Shade(),
3 => new Bogle()
}
};
}
}

View file

@ -0,0 +1,6 @@
namespace Server.Mobiles;
public class SummonSkeletons : SummonUndead
{
public override BaseCreature CreateSummon(BaseCreature source) => new Skeleton();
}

View file

@ -0,0 +1,69 @@
using System;
namespace Server.Mobiles;
public abstract class SummonUndead : MonsterAbility
{
public override MonsterAbilityType AbilityType => MonsterAbilityType.SummonUndead;
public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.TakeDamage;
public override double ChanceToTrigger => 0.05;
public override TimeSpan MinTriggerCooldown => TimeSpan.FromMinutes(5);
public override TimeSpan MaxTriggerCooldown => TimeSpan.FromMinutes(5);
public virtual double ChanceToPolymorph => 0.25;
public virtual TimeSpan PolymorphDuration => TimeSpan.FromMinutes(5);
public virtual int GetTransformBodyValue() => Utility.RandomBool() ? 50 : 56;
public virtual int AmountToSummon => 4;
public virtual int SummonRange => 4;
public abstract BaseCreature CreateSummon(BaseCreature source);
public virtual bool CanTrigger(BaseCreature source) => source.Followers < AmountToSummon && CanTrigger(source);
public override void Trigger(BaseCreature source, Mobile target)
{
var amount = AmountToSummon - source.Followers;
var distance = SummonRange;
int willTransform = !source.Paralyzed && ChanceToPolymorph > 0 && ChanceToPolymorph > Utility.RandomDouble()
? Utility.Random(amount)
: -1;
for (var i = 0; i < amount; i++)
{
var loc = Utility.GetValidLocationInLOS(source.Map, source, distance);
BaseCreature summon;
if (i == willTransform)
{
summon = source;
source.BodyMod = GetTransformBodyValue();
source.HueMod = 0;
// Name is not changed
Timer.DelayCall(PolymorphDuration, EndPolymorph, source);
}
else
{
summon = CreateSummon(source);
summon.Team = source.Team;
summon.Summoned = true;
summon.SummonMaster = source;
summon.FightMode = FightMode.Closest;
summon.Combatant = target;
}
summon.MoveToWorld(loc, source.Map);
Effects.SendLocationEffect(summon.Location, summon.Map, 0x3728, 10);
summon.PlaySound(0x48F);
summon.PlaySound(summon.GetAttackSound());
}
base.Trigger(source, target);
}
private static void EndPolymorph(BaseCreature source)
{
source.BodyMod = 0;
source.HueMod = -1;
}
}

View file

@ -46,12 +46,14 @@ namespace Server.Mobiles
public override string CorpseName => "a hell cat corpse";
public override string DefaultName => "a hell cat";
public override bool HasBreath => true; // fire breath enabled
public override int Hides => 10;
public override HideType HideType => HideType.Spined;
public override FoodType FavoriteFood => FoodType.Meat;
public override PackInstinct PackInstinct => PackInstinct.Feline;
private static MonsterAbility[] _abilities = { MonsterAbility.FireBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void GenerateLoot()
{
AddLoot(LootPack.Meager);

View file

@ -44,13 +44,14 @@ namespace Server.Mobiles
public override string CorpseName => "a hell cat corpse";
public override string DefaultName => "a hell cat";
public override bool HasBreath => true; // fire breath enabled
public override int Hides => 10;
public override HideType HideType => HideType.Spined;
public override FoodType FavoriteFood => FoodType.Meat;
public override PackInstinct PackInstinct => PackInstinct.Feline;
private static MonsterAbility[] _abilities = { MonsterAbility.FireBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void GenerateLoot()
{
AddLoot(LootPack.Average);

View file

@ -44,11 +44,12 @@ namespace Server.Mobiles
}
public override string CorpseName => "a fire steed corpse";
public override bool HasBreath => true; // fire breath enabled
public override FoodType FavoriteFood => FoodType.Meat;
public override PackInstinct PackInstinct => PackInstinct.Daemon | PackInstinct.Equine;
private static MonsterAbility[] _abilities = { MonsterAbility.FireBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void GenerateLoot()
{
PackItem(new SulfurousAsh(Utility.RandomMinMax(151, 300)));

View file

@ -13,10 +13,11 @@ namespace Server.Mobiles
}
public override string CorpseName => "a hellsteed corpse";
public override bool HasBreath => true;
public override int BreathChaosDamage => 100;
public override Poison PoisonImmune => Poison.Lethal;
private static MonsterAbility[] _abilities = { MonsterAbility.ChaosBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public static void SetStats(BaseCreature steed)
{
steed.SetStr(201, 210);

View file

@ -1,280 +1,161 @@
using System;
using System.Collections.Generic;
using Server.Engines.Plants;
using Server.Items;
namespace Server.Mobiles
namespace Server.Mobiles;
public class Hiryu : BaseMount
{
public class Hiryu : BaseMount
[Constructible]
public Hiryu() : base("a hiryu", 243, 0x3E94, AIType.AI_Melee)
{
private static readonly Dictionary<Mobile, ExpireTimer> m_Table = new();
Hue = GetHue();
[Constructible]
public Hiryu() : base("a hiryu", 243, 0x3E94, AIType.AI_Melee)
SetStr(1201, 1410);
SetDex(171, 270);
SetInt(301, 325);
SetHits(901, 1100);
SetMana(60);
SetDamage(20, 30);
SetDamageType(ResistanceType.Physical, 100);
SetResistance(ResistanceType.Physical, 55, 70);
SetResistance(ResistanceType.Fire, 70, 90);
SetResistance(ResistanceType.Cold, 15, 25);
SetResistance(ResistanceType.Poison, 40, 50);
SetResistance(ResistanceType.Energy, 40, 50);
SetSkill(SkillName.Anatomy, 75.1, 80.0);
SetSkill(SkillName.MagicResist, 85.1, 100.0);
SetSkill(SkillName.Tactics, 100.1, 110.0);
SetSkill(SkillName.Wrestling, 100.1, 120.0);
Fame = 18000;
Karma = -18000;
Tamable = true;
ControlSlots = 4;
MinTameSkill = 98.7;
if (Utility.RandomDouble() < .33)
{
Hue = GetHue();
SetStr(1201, 1410);
SetDex(171, 270);
SetInt(301, 325);
SetHits(901, 1100);
SetMana(60);
SetDamage(20, 30);
SetDamageType(ResistanceType.Physical, 100);
SetResistance(ResistanceType.Physical, 55, 70);
SetResistance(ResistanceType.Fire, 70, 90);
SetResistance(ResistanceType.Cold, 15, 25);
SetResistance(ResistanceType.Poison, 40, 50);
SetResistance(ResistanceType.Energy, 40, 50);
SetSkill(SkillName.Anatomy, 75.1, 80.0);
SetSkill(SkillName.MagicResist, 85.1, 100.0);
SetSkill(SkillName.Tactics, 100.1, 110.0);
SetSkill(SkillName.Wrestling, 100.1, 120.0);
Fame = 18000;
Karma = -18000;
Tamable = true;
ControlSlots = 4;
MinTameSkill = 98.7;
if (Utility.RandomDouble() < .33)
{
PackItem(Seed.RandomBonsaiSeed());
}
if (Core.ML && Utility.RandomDouble() < .33)
{
PackItem(Seed.RandomPeculiarSeed(3));
}
PackItem(Seed.RandomBonsaiSeed());
}
public Hiryu(Serial serial)
: base(serial)
if (Core.ML && Utility.RandomDouble() < .33)
{
PackItem(Seed.RandomPeculiarSeed(3));
}
}
public Hiryu(Serial serial) : base(serial)
{
}
public override string CorpseName => "a hiryu corpse";
public override double WeaponAbilityChance => 0.07; /* 1 in 15 chance of using per landed hit */
public override bool StatLossAfterTame => true;
public override int TreasureMapLevel => 5;
public override int Meat => 16;
public override int Hides => 60;
public override FoodType FavoriteFood => FoodType.Meat;
public override bool CanAngerOnTame => true;
private static MonsterAbility[] _abilities = { MonsterAbility.GraspingClaw };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override WeaponAbility GetWeaponAbility() => WeaponAbility.Dismount;
private static int GetHue()
{
return Utility.Random(1075) switch
{
1074 => 0x85C, // Strong Green 0.09%
1073 => 0x490, // Strong Purple 0.09%
>= 1071 => 0x030, // Green 0.19%
>= 1069 => 0x037, // Strong Yellow 0.19%
>= 1066 => 0x295, // Light Green 0.28%
>= 1063 => 0x123, // Cyan 0.28%
>= 1058 => 0x482, // Ice Blue 0.47%
>= 1050 => 0x487, // Blue/Yellow 0.74%
>= 1040 => CraftResources.GetHue(CraftResource.Gold), // Gold 0.93%
>= 1030 => CraftResources.GetHue(CraftResource.Agapite), // Agapite 0.93%
>= 1020 => 0x495, // Strong Cyan 0.93%
>= 1010 => 0x48D, // Light Blue 0.93%
>= 1000 => 0x47F, // Ice Green 0.93%
_ => 0 // No Hue 93.02%
} | 0x8000;
}
public override int GetAngerSound() => 0x4FE;
public override int GetIdleSound() => 0x4FD;
public override int GetAttackSound() => 0x4FC;
public override int GetHurtSound() => 0x4FF;
public override int GetDeathSound() => 0x4FB;
public override void GenerateLoot()
{
AddLoot(LootPack.FilthyRich, 3);
AddLoot(LootPack.Gems, 4);
}
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.Write(2);
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
var version = reader.ReadInt();
if (version <= 1)
{
Timer.StartTimer(() => Fix(version));
}
public override string CorpseName => "a hiryu corpse";
public override double WeaponAbilityChance => 0.07; /* 1 in 15 chance of using per landed hit */
public override bool StatLossAfterTame => true;
public override int TreasureMapLevel => 5;
public override int Meat => 16;
public override int Hides => 60;
public override FoodType FavoriteFood => FoodType.Meat;
public override bool CanAngerOnTame => true;
public override WeaponAbility GetWeaponAbility() => WeaponAbility.Dismount;
private static int GetHue()
if (version < 2)
{
var rand = Utility.Random(1075);
if (rand <= 0)
for (var i = 0; i < Skills.Length; ++i)
{
return 0x855C;
}
Skills[i].Cap = Math.Max(100.0, Skills[i].Cap * 0.9);
if (rand <= 1)
{
return 0x8490;
}
if (rand <= 3)
{
return 0x8030;
}
if (rand <= 5)
{
return 0x8037;
}
if (rand <= 8)
{
return 0x8295;
}
if (rand <= 11)
{
return 0x8123;
}
if (rand <= 16)
{
return 0x8482;
}
if (rand <= 24)
{
return 0x8487;
}
if (rand <= 34)
{
return 0x8032;
}
if (rand <= 44)
{
return 0x8899;
}
if (rand <= 54)
{
return 0x8495;
}
if (rand <= 64)
{
return 0x848D;
}
if (rand <= 74)
{
return 0x847F;
}
return 0;
}
public override int GetAngerSound() => 0x4FE;
public override int GetIdleSound() => 0x4FD;
public override int GetAttackSound() => 0x4FC;
public override int GetHurtSound() => 0x4FF;
public override int GetDeathSound() => 0x4FB;
public override void GenerateLoot()
{
AddLoot(LootPack.FilthyRich, 3);
AddLoot(LootPack.Gems, 4);
}
public override void OnGaveMeleeAttack(Mobile defender)
{
base.OnGaveMeleeAttack(defender);
if (Utility.RandomDouble() >= 0.1)
{
return;
}
/* Grasping Claw
* Start cliloc: 1070836
* Effect: Physical resistance -15% for 5 seconds
* End cliloc: 1070838
* Effect: Type: "3" - From: "0x57D4F5B" (player) - To: "0x0" - ItemId: "0x37B9" - ItemIdName: "glow" - FromLocation: "(1149 808, 32)" - ToLocation: "(1149 808, 32)" - Speed: "10" - Duration: "5" - FixedDirection: "True" - Explode: "False"
*/
if (m_Table.TryGetValue(defender, out var timer))
{
timer.DoExpire();
defender.SendLocalizedMessage(1070837); // The creature lands another blow in your weakened state.
}
else
{
defender.SendLocalizedMessage(
1070836
); // The blow from the creature's claws has made you more susceptible to physical attacks.
}
var effect = -(defender.PhysicalResistance * 15 / 100);
var mod = new ResistanceMod(ResistanceType.Physical, effect);
defender.FixedEffect(0x37B9, 10, 5);
defender.AddResistanceMod(mod);
timer = new ExpireTimer(defender, mod, TimeSpan.FromSeconds(5.0));
timer.Start();
m_Table[defender] = timer;
}
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.Write(2);
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
var version = reader.ReadInt();
if (version <= 1)
{
Timer.StartTimer(() => Fix(version));
}
if (version < 2)
{
for (var i = 0; i < Skills.Length; ++i)
if (Skills[i].Base > Skills[i].Cap)
{
Skills[i].Cap = Math.Max(100.0, Skills[i].Cap * 0.9);
if (Skills[i].Base > Skills[i].Cap)
{
Skills[i].Base = Skills[i].Cap;
}
Skills[i].Base = Skills[i].Cap;
}
}
}
}
private void Fix(int version)
private void Fix(int version)
{
switch (version)
{
switch (version)
{
case 1:
case 1:
{
if (InternalItem != null)
{
if (InternalItem != null)
{
InternalItem.Hue = Hue;
}
goto case 0;
InternalItem.Hue = Hue;
}
case 0:
{
Hue = GetHue();
break;
}
}
}
private class ExpireTimer : Timer
{
private readonly Mobile m_Mobile;
private readonly ResistanceMod m_Mod;
public ExpireTimer(Mobile m, ResistanceMod mod, TimeSpan delay)
: base(delay)
{
m_Mobile = m;
m_Mod = mod;
}
public void DoExpire()
{
m_Mobile.RemoveResistanceMod(m_Mod);
Stop();
m_Table.Remove(m_Mobile);
}
protected override void OnTick()
{
m_Mobile.SendLocalizedMessage(1070838); // Your resistance to physical attacks has returned.
DoExpire();
}
goto case 0;
}
case 0:
{
Hue = GetHue();
break;
}
}
}
}

View file

@ -1,282 +1,176 @@
using System;
using System.Collections.Generic;
using Server.Engines.Plants;
using Server.Items;
namespace Server.Mobiles
namespace Server.Mobiles;
public class LesserHiryu : BaseMount
{
public class LesserHiryu : BaseMount
[Constructible]
public LesserHiryu() : base("a lesser hiryu", 243, 0x3E94, AIType.AI_Melee)
{
private static readonly Dictionary<Mobile, ExpireTimer> m_Table = new();
Hue = GetHue();
[Constructible]
public LesserHiryu() : base("a lesser hiryu", 243, 0x3E94, AIType.AI_Melee)
SetStr(301, 410);
SetDex(171, 270);
SetInt(301, 325);
SetHits(401, 600);
SetMana(60);
SetDamage(18, 23);
SetDamageType(ResistanceType.Physical, 100);
SetResistance(ResistanceType.Physical, 45, 70);
SetResistance(ResistanceType.Fire, 60, 80);
SetResistance(ResistanceType.Cold, 5, 15);
SetResistance(ResistanceType.Poison, 30, 40);
SetResistance(ResistanceType.Energy, 30, 40);
SetSkill(SkillName.Anatomy, 75.1, 80.0);
SetSkill(SkillName.MagicResist, 85.1, 100.0);
SetSkill(SkillName.Tactics, 100.1, 110.0);
SetSkill(SkillName.Wrestling, 100.1, 120.0);
Fame = 10000;
Karma = -10000;
Tamable = true;
ControlSlots = 3;
MinTameSkill = 98.7;
if (Utility.RandomDouble() < .33)
{
Hue = GetHue();
PackItem(Seed.RandomBonsaiSeed());
}
}
SetStr(301, 410);
SetDex(171, 270);
SetInt(301, 325);
public LesserHiryu(Serial serial) : base(serial)
{
}
SetHits(401, 600);
SetMana(60);
public override string CorpseName => "a hiryu corpse";
public override double WeaponAbilityChance => 0.07; /* 1 in 15 chance of using; 1 in 5 chance of success */
SetDamage(18, 23);
public override bool StatLossAfterTame => true;
SetDamageType(ResistanceType.Physical, 100);
public override int TreasureMapLevel => 3;
public override int Meat => 16;
public override int Hides => 60;
public override FoodType FavoriteFood => FoodType.Meat;
public override bool CanAngerOnTame => true;
SetResistance(ResistanceType.Physical, 45, 70);
SetResistance(ResistanceType.Fire, 60, 80);
SetResistance(ResistanceType.Cold, 5, 15);
SetResistance(ResistanceType.Poison, 30, 40);
SetResistance(ResistanceType.Energy, 30, 40);
public override WeaponAbility GetWeaponAbility() => WeaponAbility.Dismount;
SetSkill(SkillName.Anatomy, 75.1, 80.0);
SetSkill(SkillName.MagicResist, 85.1, 100.0);
SetSkill(SkillName.Tactics, 100.1, 110.0);
SetSkill(SkillName.Wrestling, 100.1, 120.0);
private static MonsterAbility[] _abilities = { MonsterAbility.GraspingClaw };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
Fame = 10000;
Karma = -10000;
private static int GetHue()
{
return Utility.Random(525) switch
{
524 => 0x258, // Midnight Blue 0.19%
523 => CraftResources.GetHue(CraftResource.Valorite), // Valorite 0.19%
>= 520 => 0x7D4, // Dark Green 0.57%
>= 510 => 0x163, // Green 1.90%
>= 500 => 0x295, // Green 1.90%
_ => 0 // No Hue 95.24%
} | 0x8000;
}
Tamable = true;
ControlSlots = 3;
MinTameSkill = 98.7;
public override bool OverrideBondingReqs() => ControlMaster.Skills.Bushido.Base >= 90.0;
if (Utility.RandomDouble() < .33)
{
PackItem(Seed.RandomBonsaiSeed());
}
public override int GetAngerSound() => 0x4FE;
public override int GetIdleSound() => 0x4FD;
public override int GetAttackSound() => 0x4FC;
public override int GetHurtSound() => 0x4FF;
public override int GetDeathSound() => 0x4FB;
public override void GenerateLoot()
{
AddLoot(LootPack.FilthyRich, 2);
AddLoot(LootPack.Gems, 4);
}
public override double GetControlChance(Mobile m, bool useBaseSkill = false)
{
var tamingChance = base.GetControlChance(m, useBaseSkill);
if (tamingChance >= 0.95)
{
return tamingChance;
}
public LesserHiryu(Serial serial)
: base(serial)
var skill = useBaseSkill ? m.Skills.Bushido.Base : m.Skills.Bushido.Value;
if (skill < 90.0)
{
return tamingChance;
}
public override string CorpseName => "a hiryu corpse";
public override double WeaponAbilityChance => 0.07; /* 1 in 15 chance of using; 1 in 5 chance of success */
var bushidoChance = (skill - 30.0) / 100;
public override bool StatLossAfterTame => true;
public override int TreasureMapLevel => 3;
public override int Meat => 16;
public override int Hides => 60;
public override FoodType FavoriteFood => FoodType.Meat;
public override bool CanAngerOnTame => true;
public override WeaponAbility GetWeaponAbility() => WeaponAbility.Dismount;
private static int GetHue()
if (m.Skills.Bushido.Base >= 120)
{
var rand = Utility.Random(527);
/*
500 527 No Hue Color 94.88% 0
10 527 Green 1.90% 0x8295
10 527 Green 1.90% 0x8163 (Very Close to Above Green) //this one is an approximation
5 527 Dark Green 0.95% 0x87D4
1 527 Valorite 0.19% 0x88AB
1 527 Midnight Blue 0.19% 0x8258
* */
if (rand <= 0)
{
return 0x8258;
}
if (rand <= 1)
{
return 0x88AB;
}
if (rand <= 6)
{
return 0x87D4;
}
if (rand <= 16)
{
return 0x8163;
}
if (rand <= 26)
{
return 0x8295;
}
return 0;
bushidoChance += 0.05;
}
public override bool OverrideBondingReqs()
{
if (ControlMaster.Skills.Bushido.Base >= 90.0)
{
return true;
}
return bushidoChance > tamingChance ? bushidoChance : tamingChance;
}
return false;
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.Write(2);
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
var version = reader.ReadInt();
if (version <= 1)
{
Timer.StartTimer(() => Fix(version));
}
public override int GetAngerSound() => 0x4FE;
public override int GetIdleSound() => 0x4FD;
public override int GetAttackSound() => 0x4FC;
public override int GetHurtSound() => 0x4FF;
public override int GetDeathSound() => 0x4FB;
public override void GenerateLoot()
if (version < 2)
{
AddLoot(LootPack.FilthyRich, 2);
AddLoot(LootPack.Gems, 4);
}
public override double GetControlChance(Mobile m, bool useBaseSkill = false)
{
var tamingChance = base.GetControlChance(m, useBaseSkill);
if (tamingChance >= 0.95)
for (var i = 0; i < Skills.Length; ++i)
{
return tamingChance;
}
Skills[i].Cap = Math.Max(100.0, Skills[i].Cap * 0.9);
var skill = useBaseSkill ? m.Skills.Bushido.Base : m.Skills.Bushido.Value;
if (skill < 90.0)
{
return tamingChance;
}
var bushidoChance = (skill - 30.0) / 100;
if (m.Skills.Bushido.Base >= 120)
{
bushidoChance += 0.05;
}
return bushidoChance > tamingChance ? bushidoChance : tamingChance;
}
public override void OnGaveMeleeAttack(Mobile defender)
{
base.OnGaveMeleeAttack(defender);
if (Utility.RandomDouble() >= 0.1)
{
return;
}
/* Grasping Claw
* Start cliloc: 1070836
* Effect: Physical resistance -15% for 5 seconds
* End cliloc: 1070838
* Effect: Type: "3" - From: "0x57D4F5B" (player) - To: "0x0" - ItemId: "0x37B9" - ItemIdName: "glow" - FromLocation: "(1149 808, 32)" - ToLocation: "(1149 808, 32)" - Speed: "10" - Duration: "5" - FixedDirection: "True" - Explode: "False"
*/
if (m_Table.TryGetValue(defender, out var timer))
{
timer.DoExpire();
defender.SendLocalizedMessage(1070837); // The creature lands another blow in your weakened state.
}
else
{
defender.SendLocalizedMessage(
1070836
); // The blow from the creature's claws has made you more susceptible to physical attacks.
}
var effect = -(defender.PhysicalResistance * 15 / 100);
var mod = new ResistanceMod(ResistanceType.Physical, effect);
defender.FixedEffect(0x37B9, 10, 5);
defender.AddResistanceMod(mod);
timer = new ExpireTimer(defender, mod, TimeSpan.FromSeconds(5.0));
timer.Start();
m_Table[defender] = timer;
}
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.Write(2);
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
var version = reader.ReadInt();
if (version <= 1)
{
Timer.StartTimer(() => Fix(version));
}
if (version < 2)
{
for (var i = 0; i < Skills.Length; ++i)
if (Skills[i].Base > Skills[i].Cap)
{
Skills[i].Cap = Math.Max(100.0, Skills[i].Cap * 0.9);
if (Skills[i].Base > Skills[i].Cap)
{
Skills[i].Base = Skills[i].Cap;
}
Skills[i].Base = Skills[i].Cap;
}
}
}
}
private void Fix(int version)
private void Fix(int version)
{
switch (version)
{
switch (version)
{
case 1:
case 1:
{
if (InternalItem != null)
{
if (InternalItem != null)
{
InternalItem.Hue = Hue;
}
goto case 0;
InternalItem.Hue = Hue;
}
case 0:
{
Hue = GetHue();
break;
}
}
}
private class ExpireTimer : Timer
{
private readonly Mobile m_Mobile;
private readonly ResistanceMod m_Mod;
public ExpireTimer(Mobile m, ResistanceMod mod, TimeSpan delay)
: base(delay)
{
m_Mobile = m;
m_Mod = mod;
}
public void DoExpire()
{
m_Mobile.RemoveResistanceMod(m_Mod);
Stop();
m_Table.Remove(m_Mobile);
}
protected override void OnTick()
{
m_Mobile.SendLocalizedMessage(1070838); // Your resistance to physical attacks has returned.
DoExpire();
}
goto case 0;
}
case 0:
{
Hue = GetHue();
break;
}
}
}
}

View file

@ -95,14 +95,15 @@ namespace Server.Mobiles
}
public override string CorpseName => "a nightmare corpse";
public override bool HasBreath => true; // fire breath enabled
public override int Meat => 5;
public override int Hides => 10;
public override HideType HideType => HideType.Barbed;
public override FoodType FavoriteFood => FoodType.Meat;
public override bool CanAngerOnTame => true;
private static MonsterAbility[] _abilities = { MonsterAbility.FireBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void GenerateLoot()
{
AddLoot(LootPack.Rich);

View file

@ -50,11 +50,12 @@ namespace Server.Mobiles
public override string CorpseName => "a lava lizard corpse";
public override string DefaultName => "a lava lizard";
public override bool HasBreath => true; // fire breath enabled
public override int Hides => 12;
public override HideType HideType => HideType.Spined;
private static MonsterAbility[] _abilities = { MonsterAbility.FireBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void GenerateLoot()
{
AddLoot(LootPack.Meager);

View file

@ -50,12 +50,13 @@ namespace Server.Mobiles
public override string DefaultName => "a lava serpent";
public override bool DeathAdderCharmable => true;
public override bool HasBreath => true; // fire breath enabled
public override int Meat => 4;
public override int Hides => 15;
public override HideType HideType => HideType.Spined;
private static MonsterAbility[] _abilities = { MonsterAbility.FireBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void GenerateLoot()
{
AddLoot(LootPack.Average);

View file

@ -48,10 +48,11 @@ namespace Server.Mobiles
public override string DefaultName => "a lava snake";
public override bool DeathAdderCharmable => true;
public override bool HasBreath => true; // fire breath enabled
public override int Meat => 1;
private static MonsterAbility[] _abilities = { MonsterAbility.FireBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void GenerateLoot()
{
AddLoot(LootPack.Poor);

View file

@ -292,7 +292,6 @@ namespace Server.Mobiles
private long m_NextAura;
private long m_NextBreathTime;
private long m_NextHealOwnerTime = Core.TickCount;
private long m_NextHealTime = Core.TickCount;
@ -866,7 +865,6 @@ namespace Server.Mobiles
public static bool Summoning { get; set; }
public virtual bool CanBreath => HasBreath && !Summoned;
public virtual bool IsDispellable => Summoned && !IsAnimatedDead;
// If they are following a waypoint, they'll continue to follow it even if players aren't around
@ -1033,53 +1031,9 @@ namespace Server.Mobiles
[CommandProperty(AccessLevel.GameMaster)]
public int DirectDamage { get; set; }
// Must be overridden in subclass to enable
public virtual bool HasBreath => false;
// Base damage given is: CurrentHitPoints * BreathDamageScalar
public virtual double BreathDamageScalar => Core.AOS ? 0.16 : 0.05;
// Min/max seconds until next breath
public virtual double BreathMinDelay => 30.0;
public virtual double BreathMaxDelay => 45.0;
// Creature stops moving for 1.0 seconds while breathing
public virtual double BreathStallTime => 1.0;
// Effect is sent 1.3 seconds after BreathAngerSound and BreathAngerAnimation is played
public virtual double BreathEffectDelay => 1.3;
// Damage is given 1.0 seconds after effect is sent
public virtual double BreathDamageDelay => 1.0;
public virtual int BreathRange => RangePerception;
// Damage types
public virtual int BreathChaosDamage => 0;
public virtual int BreathPhysicalDamage => 0;
public virtual int BreathFireDamage => 100;
public virtual int BreathColdDamage => 0;
public virtual int BreathPoisonDamage => 0;
public virtual int BreathEnergyDamage => 0;
// Is immune to breath damages
public virtual bool BreathImmune => false;
// Effect details and sound
public virtual int BreathEffectItemID => 0x36D4;
public virtual int BreathEffectSpeed => 5;
public virtual int BreathEffectDuration => 0;
public virtual bool BreathEffectExplodes => false;
public virtual bool BreathEffectFixedDir => false;
public virtual int BreathEffectHue => 0;
public virtual int BreathEffectRenderMode => 0;
public virtual int BreathEffectSound => 0x227;
// Anger sound/animations
public virtual int BreathAngerSound => GetAngerSound();
public virtual int BreathAngerAnimation => 12;
public virtual bool CanFlee => !m_Paragon;
public DateTime EndFleeTime { get; set; }
@ -1162,6 +1116,67 @@ namespace Server.Mobiles
}
}
public virtual MonsterAbility[] GetMonsterAbilities() => null;
public virtual bool HasAbility(MonsterAbilityType type)
{
var abilities = GetMonsterAbilities();
if (abilities == null)
{
return false;
}
for (var i = 0; i < abilities.Length; i++)
{
if (abilities[i].AbilityType == type)
{
return true;
}
}
return false;
}
public virtual bool HasAbility(MonsterAbility ability)
{
var abilities = GetMonsterAbilities();
if (abilities == null)
{
return false;
}
for (var i = 0; i < abilities.Length; i++)
{
if (abilities[i] == ability)
{
return true;
}
}
return false;
}
public virtual void TriggerAbility(MonsterAbilityTrigger trigger, Mobile defender)
{
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))
{
ability.Trigger(this, defender);
}
}
}
public virtual WeaponAbility GetWeaponAbility() => null;
public virtual bool IsEnemy(Mobile m)
@ -1478,12 +1493,19 @@ namespace Server.Mobiles
base.OnDamage(amount, from, willKill);
}
public virtual void OnDamagedBySpell(Mobile from)
public virtual void OnDamagedBySpell(Mobile from, int damage)
{
if (CanBeDistracted && ControlOrder == OrderType.Follow)
{
CheckDistracted(from);
}
TriggerAbility(MonsterAbilityTrigger.TakeSpellDamage, from);
}
public virtual void OnDamageSpell(Mobile defender, int damage)
{
TriggerAbility(MonsterAbilityTrigger.GiveSpellDamage, defender);
}
public virtual void OnHarmfulSpell(Mobile from)
@ -2199,13 +2221,15 @@ namespace Server.Mobiles
}
}
public virtual void OnGotMeleeAttack(Mobile attacker)
public virtual void OnGotMeleeAttack(Mobile attacker, int damage)
{
if (AutoDispel && attacker is BaseCreature creature && creature.IsDispellable &&
AutoDispelChance > Utility.RandomDouble())
{
Dispel(creature);
}
TriggerAbility(MonsterAbilityTrigger.TakeMeleeDamage, attacker);
}
public virtual void Dispel(Mobile m)
@ -2222,7 +2246,7 @@ namespace Server.Mobiles
m.Delete();
}
public virtual void OnGaveMeleeAttack(Mobile defender)
public virtual void OnGaveMeleeAttack(Mobile defender, int damage)
{
var p = m_Paragon ? PoisonImpl.IncreaseLevel(HitPoison) : HitPoison;
@ -2241,6 +2265,8 @@ namespace Server.Mobiles
{
Dispel(creature);
}
TriggerAbility(MonsterAbilityTrigger.GiveMeleeDamage, defender);
}
public override void OnAfterDelete()
@ -3488,24 +3514,8 @@ namespace Server.Mobiles
m_NextRummageTime = tc + (int)TimeSpan.FromMinutes(delay).TotalMilliseconds;
}
// tested: controlled dragons do breath fire, what about summoned skeletal dragons?
if (CanBreath && tc - m_NextBreathTime >= 0)
{
var target = Combatant;
if (target?.Alive == true && !target.IsDeadBondedPet && CanBeHarmful(target) && target.Map == Map &&
!IsDeadBondedPet && target.InRange(this, BreathRange) && InLOS(target) && !BardPacified)
{
if (Core.TickCount - m_NextBreathTime < 30000 && Utility.RandomBool())
{
BreathStart(target);
}
m_NextBreathTime = tc + (int)TimeSpan
.FromSeconds(BreathMinDelay + Utility.RandomDouble() * (BreathMaxDelay - BreathMinDelay))
.TotalMilliseconds;
}
}
// Fire breath, etc.
TriggerAbility(MonsterAbilityTrigger.Action, Combatant);
if ((CanHeal || CanHealOwner) && Alive && !IsHealing && !BardPacified)
{
@ -3893,161 +3903,6 @@ namespace Server.Mobiles
}
}
public virtual void BreathStart(Mobile target)
{
BreathStallMovement();
BreathPlayAngerSound();
BreathPlayAngerAnimation();
Direction = GetDirectionTo(target);
Timer.StartTimer(TimeSpan.FromSeconds(BreathEffectDelay), () => BreathEffect_Callback(target));
}
public virtual void BreathStallMovement()
{
if (AIObject != null)
{
AIObject.NextMove = Core.TickCount + (int)(BreathStallTime * 1000);
}
}
public virtual void BreathPlayAngerSound()
{
PlaySound(BreathAngerSound);
}
public virtual void BreathPlayAngerAnimation()
{
Animate(BreathAngerAnimation, 5, 1, true, false, 0);
}
public virtual void BreathEffect_Callback(Mobile target)
{
if (!target.Alive || !CanBeHarmful(target))
{
return;
}
BreathPlayEffectSound();
BreathPlayEffect(target);
Timer.StartTimer(TimeSpan.FromSeconds(BreathDamageDelay), () => BreathDamage_Callback(target));
}
public virtual void BreathPlayEffectSound()
{
PlaySound(BreathEffectSound);
}
public virtual void BreathPlayEffect(Mobile target)
{
Effects.SendMovingEffect(
this,
target,
BreathEffectItemID,
BreathEffectSpeed,
BreathEffectDuration,
BreathEffectFixedDir,
BreathEffectExplodes,
BreathEffectHue,
BreathEffectRenderMode
);
}
public virtual void BreathDamage_Callback(Mobile target)
{
if (target is BaseCreature creature && creature.BreathImmune)
{
return;
}
if (CanBeHarmful(target))
{
DoHarmful(target);
BreathDealDamage(target);
}
}
public virtual void BreathDealDamage(Mobile target)
{
if (!Evasion.CheckSpellEvasion(target))
{
var physDamage = BreathPhysicalDamage;
var fireDamage = BreathFireDamage;
var coldDamage = BreathColdDamage;
var poisDamage = BreathPoisonDamage;
var nrgyDamage = BreathEnergyDamage;
if (BreathChaosDamage > 0)
{
switch (Utility.Random(5))
{
case 0:
{
physDamage += BreathChaosDamage;
break;
}
case 1:
{
fireDamage += BreathChaosDamage;
break;
}
case 2:
{
coldDamage += BreathChaosDamage;
break;
}
case 3:
{
poisDamage += BreathChaosDamage;
break;
}
case 4:
{
nrgyDamage += BreathChaosDamage;
break;
}
}
}
if (physDamage == 0 && fireDamage == 0 && coldDamage == 0 && poisDamage == 0 && nrgyDamage == 0)
{
target.Damage(BreathComputeDamage(), this); // Unresistable damage even in AOS
}
else
{
AOS.Damage(
target,
this,
BreathComputeDamage(),
physDamage,
fireDamage,
coldDamage,
poisDamage,
nrgyDamage
);
}
}
}
public virtual int BreathComputeDamage()
{
var damage = (int)(Hits * BreathDamageScalar);
if (IsParagon)
{
damage = (int)(damage / Paragon.HitsBuff);
}
if (damage > 200)
{
damage = 200;
}
return damage;
}
public void SpillAcid(int amount)
{
SpillAcid(null, amount);

View file

@ -73,6 +73,9 @@ namespace Server.Mobiles
AddLoot(LootPack.MedScrolls, 2);
}
private static MonsterAbility[] _abilities = { MonsterAbility.SummonLesserUndead };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);

View file

@ -106,9 +106,9 @@ namespace Server.Mobiles
AddLoot(LootPack.Gems, 1);
}
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (!m_Stunning && Utility.RandomDouble() < 0.3)
{

View file

@ -45,11 +45,13 @@ namespace Server.Mobiles
public override string CorpseName => "a charred corpse";
public override bool HasBreath => true; // fire breath enabled
public override int TreasureMapLevel => 1;
public override int Meat => 1;
public override bool CanFly => true;
private static MonsterAbility[] _abilities = { MonsterAbility.FireBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void GenerateLoot()
{
AddLoot(LootPack.Rich);

View file

@ -65,7 +65,7 @@ namespace Server.Mobiles
AddLoot(LootPack.Gems, 2);
}
public override void OnDamagedBySpell(Mobile from)
public override void OnDamagedBySpell(Mobile from, int damage)
{
if (from?.Alive == true && Utility.RandomDouble() < 0.4)
{
@ -73,9 +73,9 @@ namespace Server.Mobiles
}
}
public override void OnGotMeleeAttack(Mobile attacker)
public override void OnGotMeleeAttack(Mobile attacker, int damage)
{
base.OnGotMeleeAttack(attacker);
base.OnGotMeleeAttack(attacker, damage);
if (attacker?.Alive == true && attacker.Weapon is BaseRanged && Utility.RandomDouble() < 0.4)
{

View file

@ -59,6 +59,9 @@ namespace Server.Mobiles
public override Poison PoisonImmune => Poison.Lethal;
public override int TreasureMapLevel => 4;
private static MonsterAbility[] _abilities = { MonsterAbility.SummonSkeletons };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void GenerateLoot()
{
AddLoot(LootPack.FilthyRich);

View file

@ -116,9 +116,9 @@ namespace Server.Mobiles
}
}
public override void OnGotMeleeAttack(Mobile attacker)
public override void OnGotMeleeAttack(Mobile attacker, int damage)
{
base.OnGotMeleeAttack(attacker);
base.OnGotMeleeAttack(attacker, damage);
if (Utility.RandomDouble() < 0.1)
{

View file

@ -83,9 +83,9 @@ namespace Server.Mobiles
eable.Free();
}
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (Utility.RandomDouble() <= 0.1)
{
@ -93,9 +93,9 @@ namespace Server.Mobiles
}
}
public override void OnGotMeleeAttack(Mobile attacker)
public override void OnGotMeleeAttack(Mobile attacker, int damage)
{
base.OnGotMeleeAttack(attacker);
base.OnGotMeleeAttack(attacker, damage);
if (Utility.RandomDouble() <= 0.1)
{

View file

@ -103,9 +103,9 @@ namespace Server.Mobiles
public override int GetHurtSound() => 0x140;
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (!m_Stunning && Utility.RandomDouble() < 0.3)
{

View file

@ -99,7 +99,7 @@ namespace Server.Mobiles
}
}
public override void OnDamagedBySpell(Mobile caster)
public override void OnDamagedBySpell(Mobile caster, int damage)
{
if (caster == this)
{

View file

@ -38,20 +38,30 @@ namespace Server.Mobiles
switch (Utility.Random(5))
{
case 0:
PackItem(new BoneArms());
break;
{
PackItem(new BoneArms());
break;
}
case 1:
PackItem(new BoneChest());
break;
{
PackItem(new BoneChest());
break;
}
case 2:
PackItem(new BoneGloves());
break;
{
PackItem(new BoneGloves());
break;
}
case 3:
PackItem(new BoneLegs());
break;
{
PackItem(new BoneLegs());
break;
}
case 4:
PackItem(new BoneHelm());
break;
{
PackItem(new BoneHelm());
break;
}
}
}

View file

@ -102,7 +102,7 @@ namespace Server.Mobiles
}
}
public override void OnDamagedBySpell(Mobile from)
public override void OnDamagedBySpell(Mobile from, int damage)
{
if (from?.Alive == true && Utility.RandomDouble() < 0.4)
{
@ -123,9 +123,9 @@ namespace Server.Mobiles
}
}
public override void OnGotMeleeAttack(Mobile attacker)
public override void OnGotMeleeAttack(Mobile attacker, int damage)
{
base.OnGotMeleeAttack(attacker);
base.OnGotMeleeAttack(attacker, damage);
if (FieldActive)
{

View file

@ -96,7 +96,7 @@ namespace Server.Mobiles
}
}
public override void OnDamagedBySpell(Mobile from)
public override void OnDamagedBySpell(Mobile from, int damage)
{
if (from?.Alive == true && Utility.RandomDouble() < 0.4)
{
@ -117,9 +117,9 @@ namespace Server.Mobiles
}
}
public override void OnGotMeleeAttack(Mobile attacker)
public override void OnGotMeleeAttack(Mobile attacker, int damage)
{
base.OnGotMeleeAttack(attacker);
base.OnGotMeleeAttack(attacker, damage);
if (FieldActive)
{

View file

@ -96,13 +96,15 @@ namespace Server.Mobiles
public override string CorpseName => "a chaos dragoon corpse";
public override string DefaultName => "a chaos dragoon";
public override bool HasBreath => true;
public override bool AutoDispel => true;
public override bool BardImmune => !Core.AOS;
public override bool CanRummageCorpses => true;
public override bool AlwaysMurderer => true;
public override bool ShowFameTitle => false;
private static MonsterAbility[] _abilities = { MonsterAbility.FireBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override int GetIdleSound() => 0x2CE;
public override int GetDeathSound() => 0x2CC;

View file

@ -40,9 +40,7 @@ namespace Server.Mobiles
Fame = 8000;
Karma = -8000;
CraftResource res;
res = Utility.Random(6) switch
var res = Utility.Random(6) switch
{
0 => CraftResource.BlackScales,
1 => CraftResource.RedScales,
@ -111,14 +109,15 @@ namespace Server.Mobiles
public override string CorpseName => "a chaos dragoon elite corpse";
public override string DefaultName => "a chaos dragoon elite";
public override bool HasBreath => true;
public override bool AutoDispel => true;
public override bool BardImmune => !Core.AOS;
public override bool CanRummageCorpses => true;
public override bool AlwaysMurderer => true;
public override bool ShowFameTitle => false;
private static MonsterAbility[] _abilities = { MonsterAbility.FireBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override int GetIdleSound() => 0x2CE;
public override int GetDeathSound() => 0x2CC;

View file

@ -71,9 +71,9 @@ namespace Server.Mobiles
public override int GetDeathSound() => 0x28D;
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (Utility.RandomDouble() > 0.2)
{

View file

@ -69,9 +69,9 @@ namespace Server.Mobiles
AddLoot(LootPack.UltraRich, 3);
}
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (Utility.RandomDouble() >= 0.1)
{

View file

@ -5,8 +5,7 @@ namespace Server.Mobiles
public class RedDeath : SkeletalMount
{
[Constructible]
public RedDeath()
: base("Red Death")
public RedDeath() : base("Red Death")
{
IsParagon = true;
@ -52,8 +51,7 @@ namespace Server.Mobiles
}
}
public RedDeath(Serial serial)
: base(serial)
public RedDeath(Serial serial) : base(serial)
{
}
@ -61,9 +59,9 @@ namespace Server.Mobiles
public override bool GivesMLMinorArtifact => true;
public override bool AlwaysMurderer => true;
public override bool HasBreath => true;
public override int BreathChaosDamage => 100;
public override int BreathFireDamage => 0;
private static MonsterAbility[] _abilities = { MonsterAbility.ChaosBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void GenerateLoot()
{

View file

@ -50,10 +50,10 @@ namespace Server.Mobiles
public override void OnDeath( Container c )
{
base.OnDeath( c );
if (Utility.RandomDouble() < 0.15)
c.DropItem( new DisintegratingThesisNotes() );
if (Utility.RandomDouble() < 0.05)
c.DropItem( new AssassinChest() );
}
@ -66,9 +66,9 @@ namespace Server.Mobiles
AddLoot(LootPack.UltraRich, 2);
}
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (Utility.RandomDouble() < 0.1)
{
@ -76,9 +76,9 @@ namespace Server.Mobiles
}
}
public override void OnGotMeleeAttack(Mobile attacker)
public override void OnGotMeleeAttack(Mobile attacker, int damage)
{
base.OnGotMeleeAttack(attacker);
base.OnGotMeleeAttack(attacker, damage);
if (Utility.RandomDouble() < 0.1)
{

View file

@ -38,19 +38,19 @@ namespace Server.Mobiles
// TODO: Fame/Karma
}
public Hydra(Serial serial)
: base(serial)
public Hydra(Serial serial) : base(serial)
{
}
public override string CorpseName => "a hydra corpse";
public override string DefaultName => "a hydra";
public override bool HasBreath => true;
public override int Hides => 40;
public override int Meat => 19;
public override int TreasureMapLevel => 5;
private static MonsterAbility[] _abilities = { MonsterAbility.FireBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void GenerateLoot()
{
AddLoot(LootPack.UltraRich, 3);

View file

@ -54,9 +54,7 @@ namespace Server.Mobiles
public override string CorpseName => "a dragon corpse";
public override bool StatLossAfterTame => true;
public override string DefaultName => "a greater dragon";
public override bool ReacquireOnMovement => !Controlled;
public override bool HasBreath => true; // fire breath enabled
public override bool AutoDispel => !Controlled;
public override int TreasureMapLevel => 5;
public override int Meat => 19;
@ -68,6 +66,9 @@ namespace Server.Mobiles
public override bool CanAngerOnTame => true;
public override bool CanFly => true;
private static MonsterAbility[] _abilities = { MonsterAbility.FireBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void GenerateLoot()
{
AddLoot(LootPack.FilthyRich, 4);

View file

@ -46,12 +46,14 @@ namespace Server.Mobiles
public override int TreasureMapLevel => 5;
public override int Meat => 5;
public override int Hides => 10;
public override bool CanBreath => true;
public override bool CanAngerOnTame => true;
public override bool StatLossAfterTame => true;
public override FoodType FavoriteFood => FoodType.Meat;
public override bool CanFly => true;
private static MonsterAbility[] _abilities = { MonsterAbility.FireBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void GenerateLoot()
{
AddLoot(LootPack.AosUltraRich, 3);

View file

@ -72,9 +72,9 @@ namespace Server.Mobiles
AddLoot(LootPack.Gems);
}
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (Utility.RandomDouble() < 0.1)
{
@ -82,9 +82,9 @@ namespace Server.Mobiles
}
}
public override void OnGotMeleeAttack(Mobile attacker)
public override void OnGotMeleeAttack(Mobile attacker, int damage)
{
base.OnGotMeleeAttack(attacker);
base.OnGotMeleeAttack(attacker, damage);
if (Utility.RandomDouble() < 0.1)
{

View file

@ -172,9 +172,9 @@ namespace Server.Mobiles
}
}
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (Utility.RandomDouble() < 0.25)
{

View file

@ -163,9 +163,9 @@ namespace Server.Mobiles
public override int GetDeathSound() => 0x59c;
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (Utility.RandomDouble() <= 0.2)
{

View file

@ -109,18 +109,18 @@ namespace Server.Mobiles
}
}
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
defender.Damage(Utility.Random(20, 10), this);
defender.Stam -= Utility.Random(20, 10);
defender.Mana -= Utility.Random(20, 10);
}
public override void OnGotMeleeAttack(Mobile attacker)
public override void OnGotMeleeAttack(Mobile attacker, int damage)
{
base.OnGotMeleeAttack(attacker);
base.OnGotMeleeAttack(attacker, damage);
if (Utility.RandomDouble() <= 0.1)
{

View file

@ -49,9 +49,9 @@ namespace Server.Mobiles
public override bool GivesMLMinorArtifact => true;
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (Utility.RandomBool())
{

View file

@ -96,9 +96,9 @@ namespace Server.Mobiles
}
// TODO: Put this attack shared with Hiryu and Lesser Hiryu in one place
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (Utility.RandomDouble() >= 0.1)
{

View file

@ -44,12 +44,13 @@ namespace Server.Mobiles
public override string CorpseName => "a hell hound corpse";
public override string DefaultName => "a hell hound";
public override bool HasBreath => true; // fire breath enabled
public override int Meat => 1;
public override FoodType FavoriteFood => FoodType.Meat;
public override PackInstinct PackInstinct => PackInstinct.Canine;
private static MonsterAbility[] _abilities = { MonsterAbility.FireBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void GenerateLoot()
{
AddLoot(LootPack.Average);

View file

@ -97,18 +97,18 @@ namespace Server.Mobiles
public override int GetDeathSound() => 0x2F7;
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
defender.Damage(Utility.Random(10, 10), this);
defender.Stam -= Utility.Random(10, 10);
defender.Mana -= Utility.Random(10, 10);
}
public override void OnGotMeleeAttack(Mobile attacker)
public override void OnGotMeleeAttack(Mobile attacker, int damage)
{
base.OnGotMeleeAttack(attacker);
base.OnGotMeleeAttack(attacker, damage);
attacker.Damage(Utility.Random(10, 10), this);
attacker.Stam -= Utility.Random(10, 10);

View file

@ -169,9 +169,9 @@ namespace Server.Mobiles
return base.GetHurtSound();
}
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (!m_Stunning && Utility.RandomDouble() < 0.3)
{

View file

@ -115,16 +115,16 @@ namespace Server.Mobiles
// TODO: dungeon chest, healthy gland
}
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
defender.ApplyPoison(this, IsParagon ? Poison.Lethal : Poison.Deadly);
defender.FixedParticles(0x374A, 10, 15, 5021, EffectLayer.Waist);
defender.PlaySound(0x1CB);
}
public override void OnDamagedBySpell(Mobile caster)
public override void OnDamagedBySpell(Mobile caster, int damage)
{
if (Map != null && caster != this && Utility.RandomDouble() < 0.25)
{
@ -137,10 +137,10 @@ namespace Server.Mobiles
Say(1053034); // * The plague beast creates another beast from its flesh! *
}
base.OnDamagedBySpell(caster);
base.OnDamagedBySpell(caster, damage);
}
public override void OnGotMeleeAttack(Mobile attacker)
public override void OnGotMeleeAttack(Mobile attacker, int damage)
{
if (Map != null && attacker != this && Utility.RandomDouble() < 0.25)
{
@ -153,7 +153,7 @@ namespace Server.Mobiles
Say(1053034); // * The plague beast creates another beast from its flesh! *
}
base.OnGotMeleeAttack(attacker);
base.OnGotMeleeAttack(attacker, damage);
}
public override int GetIdleSound() => 0x1BF;

View file

@ -118,9 +118,9 @@ namespace Server.Mobiles
eable.Free();
}
public override void OnGotMeleeAttack(Mobile attacker)
public override void OnGotMeleeAttack(Mobile attacker, int damage)
{
base.OnGotMeleeAttack(attacker);
base.OnGotMeleeAttack(attacker, damage);
if (Hits > HitsMax / 4)
{

View file

@ -46,7 +46,6 @@ namespace Server.Mobiles
public override string DefaultName => "an ancient wyrm";
public override bool ReacquireOnMovement => true;
public override bool HasBreath => true; // fire breath enabled
public override bool AutoDispel => true;
public override HideType HideType => HideType.Barbed;
public override int Hides => 40;
@ -58,6 +57,9 @@ namespace Server.Mobiles
public override int TreasureMapLevel => 5;
public override bool CanFly => true;
private static MonsterAbility[] _abilities = { MonsterAbility.FireBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void GenerateLoot()
{
AddLoot(LootPack.FilthyRich, 3);

View file

@ -57,12 +57,13 @@ namespace Server.Mobiles
public override string CorpseName => "a deep sea serpents corpse";
public override string DefaultName => "a deep sea serpent";
public override bool HasBreath => true;
public override int Meat => 1;
public override int Scales => 8;
public override ScaleType ScaleType => ScaleType.Blue;
private static MonsterAbility[] _abilities = { MonsterAbility.FireBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void GenerateLoot()
{
AddLoot(LootPack.Meager);

View file

@ -48,7 +48,6 @@ namespace Server.Mobiles
public override string DefaultName => "a dragon";
public override bool ReacquireOnMovement => !Controlled;
public override bool HasBreath => true; // fire breath enabled
public override bool AutoDispel => !Controlled;
public override int TreasureMapLevel => 4;
public override int Meat => 19;
@ -60,6 +59,9 @@ namespace Server.Mobiles
public override bool CanAngerOnTame => true;
public override bool CanFly => true;
private static MonsterAbility[] _abilities = { MonsterAbility.FireBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void GenerateLoot()
{
AddLoot(LootPack.FilthyRich, 2);

View file

@ -49,14 +49,8 @@ namespace Server.Mobiles
CantWalk = true;
PackItem(new MessageInABottle());
var rope = new Rope();
rope.ItemID = 0x14F8;
PackItem(rope);
rope = new Rope();
rope.ItemID = 0x14FA;
PackItem(rope);
PackItem(new Rope { ItemID = 0x14F8 });
PackItem(new Rope { ItemID = 0x14FA });
}
public Leviathan(Serial serial) : base(serial)
@ -69,15 +63,6 @@ namespace Server.Mobiles
public override string DefaultName => "a leviathan";
public override bool HasBreath => true;
public override int BreathPhysicalDamage => 70; // TODO: Verify damage type
public override int BreathColdDamage => 30;
public override int BreathFireDamage => 0;
public override int BreathEffectHue => 0x1ED;
public override double BreathDamageScalar => 0.05;
public override double BreathMinDelay => 5.0;
public override double BreathMaxDelay => 7.5;
public override int TreasureMapLevel => 5;
public static Type[] Artifacts { get; } =
@ -109,6 +94,9 @@ namespace Server.Mobiles
typeof(VioletCourage)
};
private static MonsterAbility[] _abilities = { MonsterAbility.FireBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void GenerateLoot()
{
AddLoot(LootPack.FilthyRich, 5);
@ -176,5 +164,18 @@ namespace Server.Mobiles
Fisher = null;
}
private static MonsterAbility _ability = new LevianthanBreath();
public class LevianthanBreath : FireBreath
{
public override int BreathPhysicalDamage => 70;
public override int BreathColdDamage => 30;
public override int BreathFireDamage => 0;
public override int BreathEffectHue => 0x1ED;
public override double BreathDamageScalar => 0.05;
public override TimeSpan MinTriggerCooldown => TimeSpan.FromSeconds(5.0);
public override TimeSpan MaxTriggerCooldown => TimeSpan.FromSeconds(7.5);
}
}
}

View file

@ -60,8 +60,6 @@ namespace Server.Mobiles
public override string CorpseName => "a sea serpents corpse";
public override string DefaultName => "a sea serpent";
public override bool HasBreath => true;
public override int TreasureMapLevel => 2;
public override int Hides => 10;
@ -69,6 +67,9 @@ namespace Server.Mobiles
public override int Scales => 8;
public override ScaleType ScaleType => ScaleType.Blue;
private static MonsterAbility[] _abilities = { MonsterAbility.FireBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void GenerateLoot()
{
AddLoot(LootPack.Meager);

View file

@ -51,9 +51,7 @@ namespace Server.Mobiles
public override string CorpseName => "a dragon corpse";
public override string DefaultName => "a serpentine dragon";
public override bool ReacquireOnMovement => true;
public override bool HasBreath => true; // fire breath enabled
public override double BonusPetDamageScalar => Core.SE ? 3.0 : 1.0;
public override bool AutoDispel => true;
@ -64,6 +62,9 @@ namespace Server.Mobiles
public override ScaleType ScaleType => Utility.RandomBool() ? ScaleType.Black : ScaleType.White;
public override int TreasureMapLevel => 4;
private static MonsterAbility[] _abilities = { MonsterAbility.FireBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void GenerateLoot()
{
AddLoot(LootPack.FilthyRich, 2);
@ -80,9 +81,9 @@ namespace Server.Mobiles
public override int GetHurtSound() => 0x2C3;
public override void OnGotMeleeAttack(Mobile attacker)
public override void OnGotMeleeAttack(Mobile attacker, int damage)
{
base.OnGotMeleeAttack(attacker);
base.OnGotMeleeAttack(attacker, damage);
if (!Core.SE && Utility.RandomDouble() < 0.2 && attacker is BaseCreature c && c.Controlled &&
c.ControlMaster != null)

View file

@ -44,9 +44,7 @@ namespace Server.Mobiles
public override string CorpseName => "a shadow wyrm corpse";
public override string DefaultName => "a shadow wyrm";
public override bool ReacquireOnMovement => true;
public override bool HasBreath => true; // fire breath enabled
public override bool AutoDispel => true;
public override Poison PoisonImmune => Poison.Deadly;
public override Poison HitPoison => Poison.Deadly;
@ -59,6 +57,9 @@ namespace Server.Mobiles
public override HideType HideType => HideType.Barbed;
public override bool CanFly => true;
private static MonsterAbility[] _abilities = { MonsterAbility.FireBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void GenerateLoot()
{
AddLoot(LootPack.FilthyRich, 3);

View file

@ -1,3 +1,5 @@
using System.Collections.Generic;
namespace Server.Mobiles
{
public class SkeletalDragon : BaseCreature
@ -47,10 +49,6 @@ namespace Server.Mobiles
public override string DefaultName => "a skeletal dragon";
public override bool ReacquireOnMovement => true;
public override bool HasBreath => true; // fire breath enabled
public override int BreathFireDamage => 0;
public override int BreathColdDamage => 100;
public override int BreathEffectHue => 0x480;
public override double BonusPetDamageScalar => Core.SE ? 3.0 : 1.0;
// TODO: Undead summoning?
@ -62,6 +60,9 @@ namespace Server.Mobiles
public override int Hides => 20;
public override HideType HideType => HideType.Barbed;
private static MonsterAbility[] _abilities = { MonsterAbility.ColdBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void GenerateLoot()
{
AddLoot(LootPack.FilthyRich, 4);

View file

@ -49,7 +49,6 @@ namespace Server.Mobiles
public override string DefaultName => "a drake";
public override bool ReacquireOnMovement => true;
public override bool HasBreath => true; // fire breath enabled
public override int TreasureMapLevel => 2;
public override int Meat => 10;
public override int Hides => 20;
@ -59,6 +58,9 @@ namespace Server.Mobiles
public override FoodType FavoriteFood => FoodType.Meat | FoodType.Fish;
public override bool CanFly => true;
private static MonsterAbility[] _abilities = { MonsterAbility.FireBreath };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void GenerateLoot()
{
AddLoot(LootPack.Rich);

View file

@ -89,9 +89,9 @@ namespace Server.Mobiles
return base.OnBeforeDeath();
}
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (Utility.RandomDouble() >= 0.1)
{

View file

@ -101,21 +101,21 @@ namespace Server.Mobiles
AOS.Damage(to, this, Utility.RandomMinMax(50, 65), 100, 0, 0, 0, 0);
}
public override void OnDamagedBySpell(Mobile attacker)
public override void OnDamagedBySpell(Mobile attacker, int damage)
{
base.OnDamagedBySpell(attacker);
base.OnDamagedBySpell(attacker, damage);
ThrowFan(attacker);
}
public override void OnGotMeleeAttack(Mobile attacker)
public override void OnGotMeleeAttack(Mobile attacker, int damage)
{
base.OnGotMeleeAttack(attacker);
base.OnGotMeleeAttack(attacker, damage);
ThrowFan(attacker);
}
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (!IsFanned(defender) && Utility.RandomDouble() < 0.05)
{

View file

@ -85,9 +85,9 @@ namespace Server.Mobiles
public override int GetDeathSound() => 0x508;
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (Utility.RandomBool())
{

View file

@ -59,9 +59,9 @@ namespace Server.Mobiles
AddLoot(LootPack.Rich, 3);
}
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (Utility.RandomDouble() < 0.1)
{

View file

@ -73,9 +73,9 @@ namespace Server.Mobiles
// TODO: Snowball
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (Utility.RandomDouble() >= 0.1)
{

View file

@ -56,9 +56,9 @@ namespace Server.Mobiles
AddLoot(LootPack.Gems, 2);
}
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (Utility.RandomDouble() >= 0.1 || m_Table.Contains(defender))
{

View file

@ -95,9 +95,9 @@ namespace Server.Mobiles
AddLoot(LootPack.MedScrolls, 1);
}
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (Utility.RandomDouble() >= 0.05)
{

View file

@ -78,9 +78,9 @@ namespace Server.Mobiles
AddLoot(LootPack.Rich);
}
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (Utility.RandomDouble() >= 0.1)
{

View file

@ -68,16 +68,16 @@ namespace Server.Mobiles
AddLoot(LootPack.Gems, 6);
}
public override void OnDamagedBySpell(Mobile attacker)
public override void OnDamagedBySpell(Mobile attacker, int damage)
{
base.OnDamagedBySpell(attacker);
base.OnDamagedBySpell(attacker, damage);
DoCounter(attacker);
}
public override void OnGotMeleeAttack(Mobile attacker)
public override void OnGotMeleeAttack(Mobile attacker, int damage)
{
base.OnGotMeleeAttack(attacker);
base.OnGotMeleeAttack(attacker, damage);
DoCounter(attacker);
}

View file

@ -89,9 +89,9 @@ namespace Server.Mobiles
// TODO: Axe Throw
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (Utility.RandomDouble() < 0.1)
{

View file

@ -97,9 +97,9 @@ namespace Server.Mobiles
// TODO: Body Transformation
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (Utility.RandomDouble() < 0.1)
{

View file

@ -95,9 +95,9 @@ namespace Server.Mobiles
// TODO: Throwing Dagger
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (Utility.RandomDouble() < 0.1)
{

View file

@ -201,16 +201,16 @@ namespace Server.Mobiles
}
}
public override void OnGotMeleeAttack(Mobile attacker)
public override void OnGotMeleeAttack(Mobile attacker, int damage)
{
base.OnGotMeleeAttack(attacker);
base.OnGotMeleeAttack(attacker, damage);
DoSpecialAbility(attacker);
}
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
DoSpecialAbility(defender);
}

View file

@ -158,18 +158,18 @@ namespace Server.Mobiles
}
}
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
defender.Damage(Utility.Random(20, 10), this);
defender.Stam -= Utility.Random(20, 10);
defender.Mana -= Utility.Random(20, 10);
}
public override void OnGotMeleeAttack(Mobile attacker)
public override void OnGotMeleeAttack(Mobile attacker, int damage)
{
base.OnGotMeleeAttack(attacker);
base.OnGotMeleeAttack(attacker, damage);
CheckQueen();

View file

@ -68,9 +68,9 @@ namespace Server.Mobiles
AddLoot(LootPack.UltraRich, 4);
}
public override void OnGotMeleeAttack(Mobile attacker)
public override void OnGotMeleeAttack(Mobile attacker, int damage)
{
base.OnGotMeleeAttack(attacker);
base.OnGotMeleeAttack(attacker, damage);
// TODO: Web ability
}

View file

@ -140,9 +140,9 @@ namespace Server.Mobiles
}
}
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (Utility.RandomDouble() <= 0.1) // 10% chance to drop or throw an unholy bone
{
@ -152,9 +152,9 @@ namespace Server.Mobiles
CheckSpeedBoost();
}
public override void OnGotMeleeAttack(Mobile attacker)
public override void OnGotMeleeAttack(Mobile attacker, int damage)
{
base.OnGotMeleeAttack(attacker);
base.OnGotMeleeAttack(attacker, damage);
if (Utility.RandomDouble() <= 0.1) // 10% chance to drop or throw an unholy bone
{

View file

@ -79,9 +79,9 @@ namespace Server.Mobiles
AddLoot(LootPack.UltraRich, 4);
}
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (Utility.RandomDouble() <= 0.2)
{

View file

@ -122,9 +122,9 @@ namespace Server.Mobiles
eable.Free();
}
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
if (Utility.RandomDouble() <= 0.25)
{
@ -132,9 +132,9 @@ namespace Server.Mobiles
}
}
public override void OnGotMeleeAttack(Mobile attacker)
public override void OnGotMeleeAttack(Mobile attacker, int damage)
{
base.OnGotMeleeAttack(attacker);
base.OnGotMeleeAttack(attacker, damage);
if (Utility.RandomDouble() <= 0.25)
{

View file

@ -89,17 +89,17 @@ namespace Server.Mobiles
// TODO: Hit Lightning Area
public override void OnDamagedBySpell(Mobile attacker)
public override void OnDamagedBySpell(Mobile attacker, int damage)
{
base.OnDamagedBySpell(attacker);
base.OnDamagedBySpell(attacker, damage);
ScaleResistances();
DoCounter(attacker);
}
public override void OnGotMeleeAttack(Mobile attacker)
public override void OnGotMeleeAttack(Mobile attacker, int damage)
{
base.OnGotMeleeAttack(attacker);
base.OnGotMeleeAttack(attacker, damage);
ScaleResistances();
DoCounter(attacker);

View file

@ -84,18 +84,18 @@ namespace Server.Mobiles
}
}
public override void OnGaveMeleeAttack(Mobile defender)
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender);
base.OnGaveMeleeAttack(defender, damage);
defender.Damage(Utility.Random(20, 10), this);
defender.Stam -= Utility.Random(20, 10);
defender.Mana -= Utility.Random(20, 10);
}
public override void OnGotMeleeAttack(Mobile attacker)
public override void OnGotMeleeAttack(Mobile attacker, int damage)
{
base.OnGotMeleeAttack(attacker);
base.OnGotMeleeAttack(attacker, damage);
if (Utility.RandomDouble() <= 0.1)
{

View file

@ -935,26 +935,29 @@ namespace Server.Spells
public static void Damage(Spell spell, TimeSpan delay, Mobile target, Mobile from, double damage)
{
var iDamage = (int)damage;
var damageGiven = (int)damage;
if (delay == TimeSpan.Zero)
{
(from as BaseCreature)?.AlterSpellDamageTo(target, ref iDamage);
var bcFrom = from as BaseCreature;
var bcTarget = target as BaseCreature;
bcTarget?.AlterSpellDamageFrom(from, ref iDamage);
target.Damage(iDamage, from);
bcFrom?.AlterSpellDamageTo(target, ref damageGiven);
bcTarget?.AlterSpellDamageFrom(from, ref damageGiven);
target.Damage(damageGiven, from);
bcFrom?.OnDamageSpell(target, damageGiven);
if (from != null)
{
bcTarget?.OnHarmfulSpell(from);
bcTarget?.OnDamagedBySpell(from);
bcTarget?.OnDamagedBySpell(from, damageGiven);
}
}
else
{
new SpellDamageTimer(spell, target, from, iDamage, delay).Start();
new SpellDamageTimer(spell, target, from, damageGiven, delay).Start();
}
}
@ -1004,26 +1007,30 @@ namespace Server.Spells
if (delay == TimeSpan.Zero)
{
(from as BaseCreature)?.AlterSpellDamageTo(target, ref dmg);
var bcFrom = from as BaseCreature;
var bcTarget = target as BaseCreature;
bcFrom?.AlterSpellDamageTo(target, ref dmg);
(target as BaseCreature)?.AlterSpellDamageFrom(from, ref dmg);
bcTarget?.AlterSpellDamageFrom(from, ref dmg);
WeightOverloading.DFA = dfa;
var damageGiven = AOS.Damage(target, from, dmg, phys, fire, cold, pois, nrgy, chaos);
WeightOverloading.DFA = DFAlgorithm.Standard;
bcFrom?.OnDamageSpell(target, damageGiven);
if (bcTarget != null && from != null && delay == TimeSpan.Zero)
{
bcTarget.OnHarmfulSpell(from);
bcTarget.OnDamagedBySpell(from, damageGiven);
}
}
else
{
new SpellDamageTimerAOS(spell, delay, target, from, dmg, phys, fire, cold, pois, nrgy, chaos, dfa).Start();
}
if (target is BaseCreature c && from != null && delay == TimeSpan.Zero)
{
c.OnHarmfulSpell(from);
c.OnDamagedBySpell(from);
}
}
public static void DoLeech(int damageGiven, Mobile from, Mobile target)
@ -1127,16 +1134,17 @@ namespace Server.Spells
protected override void OnTick()
{
var bcFrom = m_From as BaseCreature;
var bcTarg = m_Target as BaseCreature;
if (m_From is BaseCreature bcFrom && m_Target != null)
if (m_Target != null)
{
bcFrom.AlterSpellDamageTo(m_Target, ref m_Damage);
bcFrom?.AlterSpellDamageTo(m_Target, ref m_Damage);
}
if (bcTarg != null && m_From != null)
if (m_From != null)
{
bcTarg.AlterSpellDamageFrom(m_From, ref m_Damage);
bcTarg?.AlterSpellDamageFrom(m_From, ref m_Damage);
}
WeightOverloading.DFA = m_DFA;
@ -1145,10 +1153,12 @@ namespace Server.Spells
WeightOverloading.DFA = DFAlgorithm.Standard;
if (bcTarg != null && m_From != null)
bcFrom?.OnDamageSpell(m_Target, damageGiven);
if (m_From != null)
{
bcTarg.OnHarmfulSpell(m_From);
bcTarg.OnDamagedBySpell(m_From);
bcTarg?.OnHarmfulSpell(m_From);
bcTarg?.OnDamagedBySpell(m_From, damageGiven);
}
m_Spell?.RemoveDelayedDamageContext(m_Target);