Compare commits

...
Sign in to create a new pull request.

4 commits

Author SHA1 Message Date
Kamron Batman
51a57f98fc
Merge branch 'main' into kbatman/fix_abilities 2025-05-15 21:59:05 -07:00
Kamron Batman
520a521c94
feat: Adds SpecialAttack trigger for monster abilities. Fixes fanning fire. 2025-05-15 21:59:01 -07:00
Kamron Batman
25bed78b59
Revert 2025-05-15 19:42:44 -07:00
Kamron Batman
5fe42912de
fix: Cleans up potential memory leak in monster abilities. 2025-05-15 19:39:58 -07:00
6 changed files with 133 additions and 115 deletions

View file

@ -1350,7 +1350,7 @@ public abstract partial class BaseWeapon
theirValue = Math.Max(0.1, defValue + 50.0); theirValue = Math.Max(0.1, defValue + 50.0);
} }
var chance = ourValue / (theirValue * 2.0) * 1.0 + (double)bonus / 100;; var chance = ourValue / (theirValue * 2.0) * 1.0 + (double)bonus / 100;
if (Core.AOS && chance < 0.02) if (Core.AOS && chance < 0.02)
{ {
@ -1997,6 +1997,11 @@ public abstract partial class BaseWeapon
Bladeweave.BladeWeaving(attacker, out var bladeweavingAbi) && Bladeweave.BladeWeaving(attacker, out var bladeweavingAbi) &&
bladeweavingAbi is ArmorIgnore; bladeweavingAbi is ArmorIgnore;
if (bcAtt?.TriggerAbilitySpecialAttack(defender) != true)
{
return;
}
var damageGiven = AOS.Damage( var damageGiven = AOS.Damage(
defender, defender,
attacker, attacker,

View file

@ -5,8 +5,23 @@ namespace Server.Mobiles;
public class FanningFire : MonsterAbilitySingleTargetDoT public class FanningFire : MonsterAbilitySingleTargetDoT
{ {
public override MonsterAbilityType AbilityType => MonsterAbilityType.FanningFire; public override MonsterAbilityType AbilityType => MonsterAbilityType.FanningFire;
public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.GiveDamage; public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.SpecialAttack;
public override double ChanceToTrigger => 0.05;
public FanningFire(double chanceToTrigger, int fireResistMod, int minDamage, int maxDamage)
{
ChanceToTrigger = chanceToTrigger;
FireResistMod = fireResistMod;
MinDamage = minDamage;
MaxDamage = maxDamage;
}
public sealed override double ChanceToTrigger { get; }
public int FireResistMod { get; }
public int MinDamage { get; }
public int MaxDamage { get; }
public const string Name = "FanningFire"; public const string Name = "FanningFire";
@ -52,16 +67,12 @@ public class FanningFire : MonsterAbilitySingleTargetDoT
*/ */
source.DoHarmful(defender); source.DoHarmful(defender);
var effect = -(defender.FireResistance / 10); defender.AddResistanceMod(new ResistanceMod(ResistanceType.Fire, Name, FireResistMod));
var mod = new ResistanceMod(ResistanceType.Fire, Name, effect); defender.FixedParticles(0x3709, 10, 30, 0x34, EffectLayer.RightFoot);
defender.AddResistanceMod(mod);
defender.FixedParticles(0x37B9, 10, 30, 0x34, EffectLayer.RightFoot);
defender.PlaySound(0x208); defender.PlaySound(0x208);
// TODO: Trigger replaces a normal attack. AOS.Damage(defender, source, Utility.RandomMinMax(MinDamage, MaxDamage), 0, 100, 0, 0, 0);
AOS.Damage(defender, source, Utility.RandomMinMax(35, 45), 0, 100, 0, 0, 0);
} }
protected override void EffectTick(BaseCreature source, Mobile defender, ref TimeSpan nextDelay) protected override void EffectTick(BaseCreature source, Mobile defender, ref TimeSpan nextDelay)

View file

@ -14,7 +14,7 @@ public static class MonsterAbilities
// Resistance Debuffs // Resistance Debuffs
public static GraspingClaw GraspingClaw => new(); public static GraspingClaw GraspingClaw => new();
public static RuneCorruption RuneCorruption => new(); public static RuneCorruption RuneCorruption => new();
public static FanningFire FanningFire => new(); public static FanningFire FanningFire => new(0.05, -10, 35, 45);
// Summon Undead // Summon Undead
public static SummonSkeletonsCounter SummonSkeletonsCounter => new(); public static SummonSkeletonsCounter SummonSkeletonsCounter => new();

View file

@ -14,6 +14,7 @@ public enum MonsterAbilityTrigger : ulong
CombatAction = 0x0000000000000020, CombatAction = 0x0000000000000020,
Death = 0x0000000000000040, Death = 0x0000000000000040,
Movement = 0x0000000000000080, Movement = 0x0000000000000080,
SpecialAttack = 0x0000000000000100, // Triggers instead of a regular attack
GiveDamage = GiveMeleeDamage | GiveSpellDamage, GiveDamage = GiveMeleeDamage | GiveSpellDamage,
TakeDamage = TakeMeleeDamage | TakeSpellDamage TakeDamage = TakeMeleeDamage | TakeSpellDamage

View file

@ -1313,6 +1313,28 @@ namespace Server.Mobiles
} }
} }
public virtual bool TriggerAbilitySpecialAttack(Mobile target)
{
var abilities = GetMonsterAbilities();
if (abilities == null)
{
return false;
}
for (var i = 0; i < abilities.Length; i++)
{
var ability = abilities[i];
if (ability.CanTrigger(this, MonsterAbilityTrigger.SpecialAttack))
{
ability.Trigger(MonsterAbilityTrigger.SpecialAttack, this, target);
return true;
}
}
return false;
}
public virtual WeaponAbility GetWeaponAbility() => null; public virtual WeaponAbility GetWeaponAbility() => null;
public virtual bool IsEnemy(Mobile m) public virtual bool IsEnemy(Mobile m)

View file

@ -2,121 +2,100 @@ using ModernUO.Serialization;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Server.Mobiles namespace Server.Mobiles;
[SerializationGenerator(0, false)]
public partial class LadyJennifyr : SkeletalKnight
{ {
[SerializationGenerator(0, false)] private static readonly Dictionary<Mobile, ExpireTimer> _table = new();
public partial class LadyJennifyr : SkeletalKnight
[Constructible]
public LadyJennifyr()
{ {
private static readonly Dictionary<Mobile, ExpireTimer> m_Table = new(); IsParagon = true;
[Constructible] Hue = 0x76D;
public LadyJennifyr()
SetStr(208, 309);
SetDex(91, 118);
SetInt(44, 101);
SetHits(1113, 1285);
SetDamage(15, 25);
SetDamageType(ResistanceType.Physical, 40);
SetDamageType(ResistanceType.Cold, 60);
SetResistance(ResistanceType.Physical, 56, 65);
SetResistance(ResistanceType.Fire, 41, 49);
SetResistance(ResistanceType.Cold, 71, 80);
SetResistance(ResistanceType.Poison, 41, 50);
SetResistance(ResistanceType.Energy, 50, 58);
SetSkill(SkillName.Wrestling, 127.9, 137.1);
SetSkill(SkillName.Tactics, 128.4, 141.9);
SetSkill(SkillName.MagicResist, 102.1, 119.5);
SetSkill(SkillName.Anatomy, 129.0, 137.5);
Fame = 18000;
Karma = -18000;
}
public override string CorpseName => "a Lady Jennifyr corpse";
public override string DefaultName => "Lady Jennifyr";
/*
// TODO: Uncomment once added
public override void OnDeath( Container c )
{
base.OnDeath( c );
if (Utility.RandomDouble() < 0.15)
c.DropItem( new DisintegratingThesisNotes() );
if (Utility.RandomDouble() < 0.1)
c.DropItem( new ParrotItem() );
}
*/
public override bool GivesMLMinorArtifact => true;
public override void GenerateLoot()
{
AddLoot(LootPack.UltraRich, 3);
}
private static readonly MonsterAbility[] _abilities =
[
new FanningFire(0.10, -10, 35, 45)
];
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
private class ExpireTimer : Timer
{
private readonly Mobile _mobile;
private readonly ResistanceMod _mod;
public ExpireTimer(Mobile m, ResistanceMod mod) : base(TimeSpan.FromSeconds(10))
{ {
IsParagon = true; _mobile = m;
_mod = mod;
Hue = 0x76D;
SetStr(208, 309);
SetDex(91, 118);
SetInt(44, 101);
SetHits(1113, 1285);
SetDamage(15, 25);
SetDamageType(ResistanceType.Physical, 40);
SetDamageType(ResistanceType.Cold, 60);
SetResistance(ResistanceType.Physical, 56, 65);
SetResistance(ResistanceType.Fire, 41, 49);
SetResistance(ResistanceType.Cold, 71, 80);
SetResistance(ResistanceType.Poison, 41, 50);
SetResistance(ResistanceType.Energy, 50, 58);
SetSkill(SkillName.Wrestling, 127.9, 137.1);
SetSkill(SkillName.Tactics, 128.4, 141.9);
SetSkill(SkillName.MagicResist, 102.1, 119.5);
SetSkill(SkillName.Anatomy, 129.0, 137.5);
Fame = 18000;
Karma = -18000;
} }
public override string CorpseName => "a Lady Jennifyr corpse"; public void DoExpire()
public override string DefaultName => "Lady Jennifyr";
/*
// TODO: Uncomment once added
public override void OnDeath( Container c )
{ {
base.OnDeath( c ); _mobile.RemoveResistanceMod(_mod);
if (Utility.RandomDouble() < 0.15) Stop();
c.DropItem( new DisintegratingThesisNotes() );
if (Utility.RandomDouble() < 0.1)
c.DropItem( new ParrotItem() );
}
*/
public override bool GivesMLMinorArtifact => true;
public override void GenerateLoot()
{
AddLoot(LootPack.UltraRich, 3);
} }
public override void OnGaveMeleeAttack(Mobile defender, int damage) protected override void OnTick()
{ {
base.OnGaveMeleeAttack(defender, damage); _mobile.SendLocalizedMessage(1070834); // Your resistance to fire attacks has returned.
DoExpire();
if (Utility.RandomDouble() < 0.9) _table.Remove(_mobile);
{
return;
}
if (m_Table.Remove(defender, out var timer))
{
timer.DoExpire();
}
defender.FixedParticles(0x3709, 10, 30, 5052, EffectLayer.LeftFoot);
defender.PlaySound(0x208);
// The creature fans you with fire, reducing your resistance to fire attacks.
defender.SendLocalizedMessage(1070833);
var mod = new ResistanceMod(ResistanceType.Fire, "FireResistFanningFire", -10);
defender.AddResistanceMod(mod);
m_Table[defender] = timer = new ExpireTimer(defender, mod);
timer.Start();
}
private class ExpireTimer : Timer
{
private readonly Mobile m_Mobile;
private readonly ResistanceMod m_Mod;
public ExpireTimer(Mobile m, ResistanceMod mod)
: base(TimeSpan.FromSeconds(10))
{
m_Mobile = m;
m_Mod = mod;
}
public void DoExpire()
{
m_Mobile.RemoveResistanceMod(m_Mod);
Stop();
}
protected override void OnTick()
{
m_Mobile.SendLocalizedMessage(1070834); // Your resistance to fire attacks has returned.
DoExpire();
m_Table.Remove(m_Mobile);
}
} }
} }
} }