fix: Fixes single target abilities. Adds drain life ability. (#1276)

This commit is contained in:
Kamron Batman 2022-11-23 19:06:27 -08:00 • committed by GitHub
parent 46afffd7d0
commit f884193b70
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 263 additions and 264 deletions

View file

@ -14,7 +14,6 @@ public class DestroyEquipment : MonsterAbilitySingleTarget
protected override void OnTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender)
{
base.OnTarget(trigger, source, defender);
using var queue = PooledRefQueue<Item>.Create();
for (var i = 0; i < defender.Items.Count; i++)
@ -47,6 +46,8 @@ public class DestroyEquipment : MonsterAbilitySingleTarget
return;
}
source.DoHarmful(defender);
var toDestroy = queue.PeekRandom();
var name = toDestroy.Name?.Trim().DefaultIfNullOrEmpty(toDestroy.ItemData.Name);

View file

@ -0,0 +1,59 @@
using System;
namespace Server.Mobiles;
public class DrainLifeAttack : MonsterAbilitySingleTargetDoT
{
public override MonsterAbilityType AbilityType => MonsterAbilityType.DrainLife;
public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.GiveMeleeDamage;
public override TimeSpan MinDelay => TimeSpan.FromSeconds(1.0);
public override TimeSpan MaxDelay => TimeSpan.FromSeconds(1.0);
protected override int GetCount(BaseCreature source, Mobile defender) => 5;
public override double ChanceToTrigger => 0.5;
protected override bool CanEffectTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender) =>
base.CanEffectTarget(trigger, source, defender) && defender.Mana > 14 && !IsUnderEffect(defender);
protected override void OnBeforeTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender)
{
if (RemoveEffect(source, defender))
{
defender.SendLocalizedMessage(1070847); // The creature continues to steal your life force!
}
else
{
defender.SendLocalizedMessage(1070848); // You feel your life force being stolen away.
}
}
protected override void OnTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender)
{
base.OnTarget(trigger, source, defender);
source.DoHarmful(defender);
defender.Mana -= 15;
if (defender.Alive)
{
var damageGiven = AOS.Damage(defender, source, 5, 0, 0, 0, 0, 100);
source.Hits += damageGiven;
}
else
{
RemoveEffect(source, defender);
defender.SendLocalizedMessage(1070849); // The drain on your life force is gone.
}
}
protected override void EndEffect(BaseCreature source, Mobile defender)
{
}
protected override void OnEffectExpired(BaseCreature source, Mobile defender)
{
defender.SendLocalizedMessage(1070849); // The drain on your life force is gone.
}
}

View file

@ -10,8 +10,6 @@ public class EnergyBoltCounter : MonsterAbilitySingleTarget
protected override void OnTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender)
{
base.OnTarget(trigger, source, defender);
source.MovingParticles(defender, 0x379F, 7, 0, false, true, 0xBE3, 0xFCB, 0x211);
defender.PlaySound(0x229);

View file

@ -8,8 +8,6 @@ public class FanThrowCounter : MonsterAbilitySingleTarget
protected override void OnTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender)
{
base.OnTarget(trigger, source, defender);
/* Fan Throw
* Effect:
* - To: "0x57D4F5B"
@ -35,6 +33,7 @@ public class FanThrowCounter : MonsterAbilitySingleTarget
0
);
source.DoHarmful(defender);
AOS.Damage(defender, source, Utility.RandomMinMax(50, 65), 100, 0, 0, 0, 0);
}

View file

@ -8,8 +8,19 @@ public class FanningFire : MonsterAbilitySingleTargetDoT
public const string Name = "FanningFire";
protected override void StartEffect(BaseCreature source, Mobile defender)
protected override void OnBeforeTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender)
{
// Renew the effects
RemoveEffect(source, defender);
// The creature fans you with fire, reducing your resistance to fire attacks.
defender.SendLocalizedMessage(1070833);
}
protected override void OnTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender)
{
base.OnTarget(trigger, source, defender);
/**
* Fanning Fire
* Start cliloc: 1070833
@ -38,11 +49,7 @@ public class FanningFire : MonsterAbilitySingleTargetDoT
* - Unknown: "0x0"
*/
RemoveEffect(source, defender);
// The creature fans you with fire, reducing your resistance to fire attacks.
defender.SendLocalizedMessage(1070833);
source.DoHarmful(defender);
var effect = -(defender.FireResistance / 10);
var mod = new ResistanceMod(ResistanceType.Fire, Name, effect);

View file

@ -8,7 +8,7 @@ public class GraspingClaw : MonsterAbilitySingleTargetDoT
private const string Name = "GraspingClaw";
protected override void StartEffect(BaseCreature source, Mobile defender)
protected override void OnBeforeTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender)
{
if (RemoveEffect(source, defender))
{
@ -19,6 +19,11 @@ public class GraspingClaw : MonsterAbilitySingleTargetDoT
// The blow from the creature's claws has made you more susceptible to physical attacks.
defender.SendLocalizedMessage(1070836);
}
}
protected override void OnTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender)
{
base.OnTarget(trigger, source, defender);
/**
* Grasping Claw
@ -39,6 +44,7 @@ public class GraspingClaw : MonsterAbilitySingleTargetDoT
* - Explode: "False"
*/
source.DoHarmful(defender);
var mod = new ResistanceMod(ResistanceType.Physical, Name, -(defender.PhysicalResistance * 15 / 100));
defender.AddResistanceMod(mod);

View file

@ -40,7 +40,9 @@ public static class MonsterAbilities
public static DestroyEquipment DestroyEquipment => new();
// Life Drain
public static DrainLifeAreaAttack DrainLifeAreaAttack => new();
public static DrainLifeAttack DrainLifeAttack => new();
public static MagicalBarrier MagicalBarrier => new();

View file

@ -6,17 +6,19 @@ public abstract class MonsterAbilitySingleTarget : MonsterAbility
{
if (CanEffectTarget(trigger, source, target))
{
OnBeforeTarget(trigger, source, target);
OnTarget(trigger, source, target);
}
base.Trigger(trigger, source, target);
}
protected virtual void OnTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender)
protected virtual void OnBeforeTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender)
{
source.DoHarmful(defender);
}
protected abstract void OnTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender);
protected virtual bool CanEffectTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender) =>
defender.Alive && source.CanBeHarmful(defender);
}

View file

@ -18,14 +18,10 @@ public abstract class MonsterAbilitySingleTargetDoT : MonsterAbilitySingleTarget
var count = GetCount(source, defender);
var timer = _table[defender] = new ExpireTimer(this, source, defender, duration, count);
timer.Start();
StartEffect(source, defender);
}
protected virtual int GetCount(BaseCreature source, Mobile defender) => 1;
protected abstract void StartEffect(BaseCreature source, Mobile defender);
protected virtual void EffectTick(BaseCreature source, Mobile defender, out TimeSpan nextDelay)
{
nextDelay = Utility.RandomMinMax(MinDelay, MaxDelay);
@ -72,8 +68,11 @@ public abstract class MonsterAbilitySingleTargetDoT : MonsterAbilitySingleTarget
_ability.EffectTick(_source, _defender, out var delay);
Delay = delay;
_ability.RemoveEffect(_source, _defender);
_ability.OnEffectExpired(_source, _defender);
if (RemainingCount == 0)
{
_ability.RemoveEffect(_source, _defender);
_ability.OnEffectExpired(_source, _defender);
}
}
}
}

View file

@ -12,6 +12,7 @@ public enum MonsterAbilityType
ThrowWeapon,
DestroyEquipment,
DeathExplosion,
DrainLife,
MassDrainLife,
MagicalBarrier,
EnergyBoltCounter,

View file

@ -14,6 +14,7 @@ public class PoisonGasCounter : MonsterAbilitySingleTarget
protected override void OnTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender)
{
source.Animate(10, 4, 1, true, false, 0);
source.DoHarmful(defender);
AOS.Damage(defender, source, 50, 100, 0, 0, 0, 0, 0);
defender.FixedParticles(0x36BD, 1, 10, 0x1F78, 0xA6, 0, (EffectLayer)255);

View file

@ -8,7 +8,7 @@ public class RuneCorruption : MonsterAbilitySingleTargetDoT
private const string Name = "RuneCorruption";
protected override void OnTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender)
protected override void OnBeforeTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender)
{
if (RemoveEffect(source, defender))
{
@ -18,12 +18,12 @@ public class RuneCorruption : MonsterAbilitySingleTargetDoT
{
defender.SendLocalizedMessage(1070846); // The creature magically corrupts your armor!
}
base.OnTarget(trigger, source, defender);
}
protected override void StartEffect(BaseCreature source, Mobile defender)
protected override void OnTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender)
{
base.OnTarget(trigger, source, defender);
/**
* Rune Corruption
* Start cliloc: 1070846 "The creature magically corrupts your armor!"
@ -31,6 +31,8 @@ public class RuneCorruption : MonsterAbilitySingleTargetDoT
* End ASCII: "The corruption of your armor has worn off"
*/
source.DoHarmful(defender);
if (Core.ML)
{
if (defender.PhysicalResistance > 0)

View file

@ -11,6 +11,7 @@ public class ThrowHatchetCounter : MonsterAbilitySingleTarget
protected override void OnTarget(MonsterAbilityTrigger trigger, BaseCreature source, Mobile defender)
{
source.MovingEffect(defender, 0xF43, 10, 0, false, false);
source.DoHarmful(defender);
AOS.Damage(defender, source, 50, 100, 0, 0, 0, 0, 0);
}

View file

@ -1,86 +1,72 @@
namespace Server.Mobiles
namespace Server.Mobiles;
public class Guile : Changeling
{
public class Guile : Changeling
[Constructible]
public Guile()
{
[Constructible]
public Guile()
{
IsParagon = true;
IsParagon = true;
Hue = DefaultHue;
Hue = DefaultHue;
SetStr(53, 214);
SetDex(243, 367);
SetInt(369, 586);
SetStr(53, 214);
SetDex(243, 367);
SetInt(369, 586);
SetHits(1013, 1058);
SetStam(243, 367);
SetMana(369, 586);
SetHits(1013, 1058);
SetStam(243, 367);
SetMana(369, 586);
SetDamage(14, 20);
SetDamage(14, 20);
SetDamageType(ResistanceType.Physical, 100);
SetDamageType(ResistanceType.Physical, 100);
SetResistance(ResistanceType.Physical, 80, 90);
SetResistance(ResistanceType.Fire, 43, 46);
SetResistance(ResistanceType.Cold, 42, 44);
SetResistance(ResistanceType.Poison, 42, 50);
SetResistance(ResistanceType.Energy, 47, 50);
SetResistance(ResistanceType.Physical, 80, 90);
SetResistance(ResistanceType.Fire, 43, 46);
SetResistance(ResistanceType.Cold, 42, 44);
SetResistance(ResistanceType.Poison, 42, 50);
SetResistance(ResistanceType.Energy, 47, 50);
SetSkill(SkillName.Wrestling, 12.8, 16.7);
SetSkill(SkillName.Tactics, 102.6, 131.0);
SetSkill(SkillName.MagicResist, 141.2, 161.6);
SetSkill(SkillName.Magery, 108.4, 120.0);
SetSkill(SkillName.EvalInt, 108.4, 120.0);
SetSkill(SkillName.Meditation, 109.2, 120.0);
SetSkill(SkillName.Wrestling, 12.8, 16.7);
SetSkill(SkillName.Tactics, 102.6, 131.0);
SetSkill(SkillName.MagicResist, 141.2, 161.6);
SetSkill(SkillName.Magery, 108.4, 120.0);
SetSkill(SkillName.EvalInt, 108.4, 120.0);
SetSkill(SkillName.Meditation, 109.2, 120.0);
Fame = 21000;
Karma = -21000;
}
Fame = 21000;
Karma = -21000;
}
public Guile(Serial serial)
: base(serial)
{
}
public Guile(Serial serial)
: base(serial)
{
}
public override string CorpseName => "a Guile corpse";
public override string DefaultName => "Guile";
public override int DefaultHue => 0x3F;
public override string CorpseName => "a Guile corpse";
public override string DefaultName => "Guile";
public override int DefaultHue => 0x3F;
public override bool GivesMLMinorArtifact => true;
public override bool GivesMLMinorArtifact => true;
private static MonsterAbility[] _abilities = { MonsterAbilities.DrainLifeAttack };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender, damage);
public override void GenerateLoot()
{
AddLoot(LootPack.UltraRich, 2);
}
if (Utility.RandomBool())
{
if (!Kappa.IsBeingDrained(defender) && Mana > 14)
{
defender.SendLocalizedMessage(1070848); // You feel your life force being stolen away.
Kappa.BeginLifeDrain(defender, this);
Mana -= 15;
}
}
}
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
public override void GenerateLoot()
{
AddLoot(LootPack.UltraRich, 2);
}
writer.Write(0); // version
}
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
writer.Write(0); // version
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
var version = reader.ReadInt();
}
var version = reader.ReadInt();
}
}

View file

@ -100,7 +100,7 @@ namespace Server.Mobiles
{
if (Utility.RandomBool() && Mana > 14 && to != null)
{
damage = damage + damage / 2;
damage += damage / 2;
to.SendLocalizedMessage(1060091); // You take extra damage from the crushing attack!
to.PlaySound(0x1E1);
to.FixedParticles(0x377A, 1, 32, 0x26da, 0, 0, 0);

View file

@ -1,208 +1,143 @@
using System;
using System.Collections.Generic;
using Server.Engines.Plants;
using Server.Items;
namespace Server.Mobiles
namespace Server.Mobiles;
public class Kappa : BaseCreature
{
public class Kappa : BaseCreature
[Constructible]
public Kappa() : base(AIType.AI_Melee)
{
private static readonly Dictionary<Mobile, InternalTimer> m_Table = new();
Body = 240;
[Constructible]
public Kappa() : base(AIType.AI_Melee)
SetStr(186, 230);
SetDex(51, 75);
SetInt(41, 55);
SetMana(30);
SetHits(151, 180);
SetDamage(6, 12);
SetDamageType(ResistanceType.Physical, 100);
SetResistance(ResistanceType.Physical, 35, 50);
SetResistance(ResistanceType.Fire, 35, 50);
SetResistance(ResistanceType.Cold, 25, 50);
SetResistance(ResistanceType.Poison, 35, 50);
SetResistance(ResistanceType.Energy, 20, 30);
SetSkill(SkillName.MagicResist, 60.1, 70.0);
SetSkill(SkillName.Tactics, 79.1, 89.0);
SetSkill(SkillName.Wrestling, 60.1, 70.0);
Fame = 1700;
Karma = -1700;
PackItem(new RawFishSteak(3));
for (var i = 0; i < 2; i++)
{
Body = 240;
SetStr(186, 230);
SetDex(51, 75);
SetInt(41, 55);
SetMana(30);
SetHits(151, 180);
SetDamage(6, 12);
SetDamageType(ResistanceType.Physical, 100);
SetResistance(ResistanceType.Physical, 35, 50);
SetResistance(ResistanceType.Fire, 35, 50);
SetResistance(ResistanceType.Cold, 25, 50);
SetResistance(ResistanceType.Poison, 35, 50);
SetResistance(ResistanceType.Energy, 20, 30);
SetSkill(SkillName.MagicResist, 60.1, 70.0);
SetSkill(SkillName.Tactics, 79.1, 89.0);
SetSkill(SkillName.Wrestling, 60.1, 70.0);
Fame = 1700;
Karma = -1700;
PackItem(new RawFishSteak(3));
for (var i = 0; i < 2; i++)
switch (Utility.Random(6))
{
switch (Utility.Random(6))
{
case 0:
case 0:
{
PackItem(new Gears());
break;
case 1:
}
case 1:
{
PackItem(new Hinge());
break;
case 2:
}
case 2:
{
PackItem(new Axle());
break;
}
}
if (Core.ML && Utility.RandomDouble() < .33)
{
PackItem(Seed.RandomPeculiarSeed(4));
}
}
}
public Kappa(Serial serial) : base(serial)
if (Core.ML && Utility.Random(3) == 0)
{
PackItem(Seed.RandomPeculiarSeed(4));
}
}
public override string CorpseName => "a kappa corpse";
public override string DefaultName => "a kappa";
public Kappa(Serial serial) : base(serial)
{
}
public override void GenerateLoot()
public override string CorpseName => "a kappa corpse";
public override string DefaultName => "a kappa";
private static MonsterAbility[] _abilities = { MonsterAbilities.DrainLifeAttack };
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
public override void GenerateLoot()
{
AddLoot(LootPack.Meager);
AddLoot(LootPack.Average);
}
public override int GetAngerSound() => 0x50B;
public override int GetIdleSound() => 0x50A;
public override int GetAttackSound() => 0x509;
public override int GetHurtSound() => 0x50C;
public override int GetDeathSound() => 0x508;
public override void OnDamage(int amount, Mobile from, bool willKill)
{
// Acid Blood ability
if (from?.Map != null)
{
AddLoot(LootPack.Meager);
AddLoot(LootPack.Average);
}
public override int GetAngerSound() => 0x50B;
public override int GetIdleSound() => 0x50A;
public override int GetAttackSound() => 0x509;
public override int GetHurtSound() => 0x50C;
public override int GetDeathSound() => 0x508;
public override void OnGaveMeleeAttack(Mobile defender, int damage)
{
base.OnGaveMeleeAttack(defender, damage);
if (Utility.RandomBool())
var amt = 0;
Mobile target = this;
var rand = Utility.Random(1, 100);
if (willKill)
{
if (!IsBeingDrained(defender) && Mana > 14)
amt = ((rand % 5) >> 2) + 3;
}
if (Hits < 100 && rand < 21)
{
target = rand % 2 < 1 ? this : from;
amt++;
}
if (amt > 0)
{
SpillAcid(target, amt);
from.SendLocalizedMessage(1070820); // The creature spills a pool of acidic slime!
if (Mana > 14)
{
defender.SendLocalizedMessage(1070848); // You feel your life force being stolen away.
BeginLifeDrain(defender, this);
Mana -= 15;
}
}
}
public static bool IsBeingDrained(Mobile m) => m_Table.ContainsKey(m);
base.OnDamage(amount, from, willKill);
}
public static void BeginLifeDrain(Mobile m, Mobile from)
{
m_Table.TryGetValue(m, out var timer);
timer?.Stop();
m_Table[m] = timer = new InternalTimer(from, m);
public override Item NewHarmfulItem() => new PoolOfAcid(TimeSpan.FromSeconds(10), 5, 10)
{
Name = "slime"
};
timer.Start();
}
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
}
public static void DrainLife(Mobile m, Mobile from)
{
if (m.Alive)
{
var damageGiven = AOS.Damage(m, from, 5, 0, 0, 0, 0, 100);
from.Hits += damageGiven;
}
else
{
EndLifeDrain(m);
}
}
public static void EndLifeDrain(Mobile m)
{
if (m_Table.Remove(m, out var timer))
{
timer?.Stop();
m.SendLocalizedMessage(1070849); // The drain on your life force is gone.
}
}
public override void OnDamage(int amount, Mobile from, bool willKill)
{
if (from?.Map != null)
{
var amt = 0;
Mobile target = this;
var rand = Utility.Random(1, 100);
if (willKill)
{
amt = ((rand % 5) >> 2) + 3;
}
if (Hits < 100 && rand < 21)
{
target = rand % 2 < 1 ? this : from;
amt++;
}
if (amt > 0)
{
SpillAcid(target, amt);
from.SendLocalizedMessage(1070820);
if (Mana > 14)
{
Mana -= 15;
}
}
}
base.OnDamage(amount, from, willKill);
}
public override Item NewHarmfulItem() => new PoolOfAcid(TimeSpan.FromSeconds(10), 5, 10)
{
Name = "slime"
};
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();
}
private class InternalTimer : Timer
{
private readonly Mobile m_From;
private readonly Mobile m_Mobile;
private int m_Count;
public InternalTimer(Mobile from, Mobile m) : base(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0))
{
m_From = from;
m_Mobile = m;
}
protected override void OnTick()
{
DrainLife(m_Mobile, m_From);
if (Running && ++m_Count == 5)
{
EndLifeDrain(m_Mobile);
}
}
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
var version = reader.ReadInt();
}
}