feat: Refactor Poison system, implement Darkglow & Parasitic effects (#2385)

## Summary

- Refactors the poison system to separate `Index` (globally unique ID) from `Level` (tier within a family), enabling multiple poison families (Standard, Darkglow, Parasitic) to coexist without collisions
- Implements Darkglow and Parasitic poison special effects from Mondain's Legacy: Darkglow boosts damage by 10% when attacker is ranged, Parasitic heals the attacker for damage dealt in melee range
- Fixes several bugs: `Register()` crashing on duplicate `Level` values across families, `IncreaseLevel()` crossing family boundaries, `InfectiousStrike` and `NinjaWeapons` stripping poison family via level-based lookups, and `ArchCure`/`CleansingWinds` using raw `Level + 1` instead of `IncreaseLevel()`

## Changes

**`Projects/Server/Poison.cs`** — Adds `PoisonFamily` enum and abstract `Family` property. Adds `Index` as unique identifier. Fixes `Register()` to check `Index` uniqueness (not `Level`) and validate the new poison's name (not the existing one's). Fixes `IncreaseLevel()` to use `Index + 1`, naturally respecting family boundaries via Index gaps. Replaces linear name lookup with `Dictionary`-based `PoisonsByName`.

**`Projects/UOContent/Misc/Poison.cs`** — Adds `family` parameter to `PoisonImpl`. Implements Darkglow effect (10% damage boost when `From` >1 tile, cliloc 1072850) and Parasitic effect (heals `From` for damage dealt within 1 tile, cliloc 1060203) in `PoisonTimer.OnTick()`. Renames `m_` fields to `_` convention.

**`Projects/UOContent/Misc/PoisonKinds.cs`** — New file. Moves poison registration out of `PoisonImpl` into `PoisonKinds.Configure()`. Adds `PoisonFamily` to Darkglow/Parasitic registrations. Provides extension properties (`Lesser`, `Deadly`, `LesserDarkglow`, etc.), `GetPoison(int level)` (standard-only), `GetPoisonByFamilyAndLevel()`, and `IsDarkglow`/`IsParasitic` instance helpers.

**`Projects/UOContent/Items/Weapons/Abilities/InfectiousStrike.cs`** — Family-aware poison scaling: Darkglow caps at Deadly (Poisoning/33.3), Parasitic caps at Lethal (Poisoning/25), Standard unchanged. Level bump uses `IncreaseLevel()` with family boundary check.

**`Projects/UOContent/Items/Skill Items/Ninjitsu/NinjaWeapons.cs`** — EvilOmen level bump uses `Poison.IncreaseLevel()` instead of `Poison.GetPoison(Level + 1)`.

**`Projects/UOContent/Spells/Fourth/ArchCure.cs`** and **`CleansingWindsSpell.cs`** — Replace `poison.Level + 1` with `Poison.IncreaseLevel(poison).Level` for family-safe cure chance calculation.

**`Projects/Server/Serialization/SerializationExtensions.cs`** — Serializes/deserializes `Index` instead of `Level`.

**`DarkglowPotion.cs`** / **`ParasiticPotion.cs`** — Point to actual Darkglow/Parasitic poisons instead of placeholder `Greater`.

**`PotionKeg.cs`** / **`BasePotion.cs`** — Adds Darkglow, Parasitic, Invisibility, and FlintsPungentBrew to `PotionEffect` enum and keg label support.

## Test plan

- [ ] `dotnet build` compiles cleanly (verified, 0 warnings 0 errors)
- [ ] Verify `PoisonKinds.Configure()` registers all poisons without throwing (Register bug fix)
- [ ] Standard poison behavior unchanged — PoisonField, PoisonSpell, SerpentArrow, SavageShaman, TrappableContainer all use `GetPoison(int level)` which now correctly filters to Standard family
- [ ] Darkglow: poison tick deals +10% damage when attacker is >1 tile away, sends "Darkglow poison increases your damage!" message
- [ ] Parasitic: poison tick heals attacker for damage dealt when within 1 tile, sends heal message
- [ ] InfectiousStrike preserves poison family and respects family-specific skill scaling
- [ ] EvilOmen + NinjaWeapons level bump stays within poison family
- [ ] ArchCure/CleansingWinds cure chance calculations work correctly across all poison families
- [ ] Serialization round-trips correctly using Index
This commit is contained in:
Kamron Batman 2026-03-21 21:27:22 -07:00 • committed by GitHub
parent 3bb38bcb5b
commit 992bc95164
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 316 additions and 205 deletions

View file

@ -4,75 +4,56 @@ using System.Runtime.CompilerServices;
namespace Server; namespace Server;
public enum PoisonFamily { Standard, Darkglow, Parasitic }
public abstract class Poison : ISpanParsable<Poison> public abstract class Poison : ISpanParsable<Poison>
{ {
/*public abstract TimeSpan Interval{ get; } public static List<Poison> Poisons { get; } = [];
public abstract TimeSpan Duration{ get; }*/ public static Dictionary<string, Poison> PoisonsByName { get; } = new(StringComparer.OrdinalIgnoreCase);
public Poison(int index) => Index = index;
public int Index { get; }
public abstract string Name { get; } public abstract string Name { get; }
public abstract int Level { get; } public abstract int Level { get; }
public abstract PoisonFamily Family { get; }
public static Poison Lesser => GetPoison("Lesser");
public static Poison Regular => GetPoison("Regular");
public static Poison Greater => GetPoison("Greater");
public static Poison Deadly => GetPoison("Deadly");
public static Poison Lethal => GetPoison("Lethal");
public static List<Poison> Poisons { get; } = new();
public abstract Timer ConstructTimer(Mobile m); public abstract Timer ConstructTimer(Mobile m);
/*public abstract void OnDamage( Mobile m, ref object state );*/
public override string ToString() => Name; public override string ToString() => Name;
public static void Register(Poison reg) public static void Register(Poison reg)
{ {
var regName = reg.Name.ToLower(); var regName = reg.Name;
for (var i = 0; i < Poisons.Count; i++) for (var i = 0; i < Poisons.Count; i++)
{ {
if (reg.Level == Poisons[i].Level) var poison = Poisons[i];
if (reg.Index == poison.Index)
{ {
throw new Exception("A poison with that level already exists."); throw new Exception("A poison with that index already exists.");
} }
if (regName == Poisons[i].Name.ToLower()) if (GetPoison(regName) != null)
{ {
throw new Exception("A poison with that name already exists."); throw new Exception("A poison with that name already exists.");
} }
} }
Poisons.Add(reg); Poisons.Add(reg);
PoisonsByName.Add(regName, reg);
} }
public static Poison GetPoison(int level) [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ public static Poison GetPoisonByIndex(int index) => index >= 0 && index < Poisons.Count ? Poisons[index] : null;
for (var i = 0; i < Poisons.Count; ++i)
{
var p = Poisons[i];
if (p.Level == level) [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ public static Poison IncreaseLevel(Poison oldPoison) =>
return p; oldPoison == null ? null : GetPoisonByIndex(oldPoison.Index + 1) ?? oldPoison;
}
}
return null; [MethodImpl(MethodImplOptions.AggressiveInlining)]
} public static Poison GetPoison(ReadOnlySpan<char> name) =>
PoisonsByName.GetAlternateLookup<ReadOnlySpan<char>>().TryGetValue(name, out var poison) ? poison : null;
public static Poison GetPoison(ReadOnlySpan<char> name)
{
for (var i = 0; i < Poisons.Count; ++i)
{
var p = Poisons[i];
if (name.InsensitiveEquals(p.Name))
{
return p;
}
}
return null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Poison Parse(string s) => Parse(s, null); public static Poison Parse(string s) => Parse(s, null);
@ -86,9 +67,9 @@ public abstract class Poison : ISpanParsable<Poison>
public static Poison Parse(ReadOnlySpan<char> s, IFormatProvider provider) public static Poison Parse(ReadOnlySpan<char> s, IFormatProvider provider)
{ {
if (int.TryParse(s, provider, out var pLevel)) if (int.TryParse(s, provider, out var index))
{ {
var result = GetPoison(pLevel); var result = GetPoisonByIndex(index);
if (result != null) if (result != null)
{ {
return result; return result;
@ -106,9 +87,9 @@ public abstract class Poison : ISpanParsable<Poison>
public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider provider, out Poison result) public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider provider, out Poison result)
{ {
if (int.TryParse(s, provider, out var pLevel)) if (int.TryParse(s, provider, out var index))
{ {
result = GetPoison(pLevel); result = GetPoisonByIndex(index);
if (result != null) if (result != null)
{ {
return true; return true;

View file

@ -182,10 +182,10 @@ public static class SerializationExtensions
else else
{ {
writer.Write(true); writer.Write(true);
writer.Write((byte)p.Level); writer.Write((byte)p.Index);
} }
} }
public static Poison ReadPoison(this IGenericReader reader) => public static Poison ReadPoison(this IGenericReader reader) =>
reader.ReadBool() ? Poison.GetPoison(reader.ReadByte()) : null; reader.ReadBool() ? Poison.GetPoisonByIndex(reader.ReadByte()) : null;
} }

View file

@ -12,7 +12,9 @@ public partial class PotionKeg : Item
TileData.ItemTable[0x1940].Height = 4; TileData.ItemTable[0x1940].Height = 4;
} }
[InvalidateProperties] [SerializableField(0)] [SerializedCommandProperty(AccessLevel.GameMaster)] [InvalidateProperties]
[SerializableField(0)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private PotionEffect _type; private PotionEffect _type;
[Constructible] [Constructible]
@ -45,12 +47,20 @@ public partial class PotionKeg : Item
{ {
get get
{ {
if (_held > 0 && (int)_type >= (int)PotionEffect.Conflagration) if (_held <= 0)
{ {
return 1072658 + (int)_type - (int)PotionEffect.Conflagration; return 1041641;
} }
return _held > 0 ? 1041620 + (int)_type : 1041641; return _type switch
{
< PotionEffect.Nightsight or > PotionEffect.FlintsPungentBrew => 1041619,
PotionEffect.FlintsPungentBrew => 1113608,
>= PotionEffect.Parasitic => 1080069 + (int)_type - (int)PotionEffect.Parasitic,
PotionEffect.Invisibility => 1080071,
>= PotionEffect.Conflagration => 1072658 + (int)_type - (int)PotionEffect.Conflagration,
_ => 1041620 + (int)_type
};
} }
} }
@ -249,6 +259,9 @@ public partial class PotionKeg : Item
PotionEffect.ConflagrationGreater => new GreaterConflagrationPotion(), PotionEffect.ConflagrationGreater => new GreaterConflagrationPotion(),
PotionEffect.ConfusionBlast => new ConfusionBlastPotion(), PotionEffect.ConfusionBlast => new ConfusionBlastPotion(),
PotionEffect.ConfusionBlastGreater => new GreaterConfusionBlastPotion(), PotionEffect.ConfusionBlastGreater => new GreaterConfusionBlastPotion(),
PotionEffect.Invisibility => new InvisibilityPotion(),
PotionEffect.Parasitic => new ParasiticPotion(),
PotionEffect.Darkglow => new DarkglowPotion(),
_ => new NightSightPotion() _ => new NightSightPotion()
}; };
@ -279,6 +292,9 @@ public partial class PotionKeg : Item
_ when type == typeof(GreaterConflagrationPotion) => PotionEffect.ConflagrationGreater, _ when type == typeof(GreaterConflagrationPotion) => PotionEffect.ConflagrationGreater,
_ when type == typeof(ConfusionBlastPotion) => PotionEffect.ConfusionBlast, _ when type == typeof(ConfusionBlastPotion) => PotionEffect.ConfusionBlast,
_ when type == typeof(GreaterConfusionBlastPotion) => PotionEffect.ConfusionBlastGreater, _ when type == typeof(GreaterConfusionBlastPotion) => PotionEffect.ConfusionBlastGreater,
_ /* when type == typeof(NightSightPotion) */ => PotionEffect.Nightsight _ when type == typeof(InvisibilityPotion) => PotionEffect.Invisibility,
_ when type == typeof(ParasiticPotion) => PotionEffect.Parasitic,
_ when type == typeof(DarkglowPotion) => PotionEffect.Darkglow,
_ => PotionEffect.Nightsight
}; };
} }

View file

@ -35,7 +35,8 @@ public enum PotionEffect
ConfusionBlastGreater, ConfusionBlastGreater,
Invisibility, Invisibility,
Parasitic, Parasitic,
Darkglow Darkglow,
FlintsPungentBrew
} }
[SerializationGenerator(2, false)] [SerializationGenerator(2, false)]

View file

@ -8,8 +8,7 @@ public partial class DarkglowPotion : BasePoisonPotion
[Constructible] [Constructible]
public DarkglowPotion() : base(PotionEffect.Darkglow) => Hue = 0x96; public DarkglowPotion() : base(PotionEffect.Darkglow) => Hue = 0x96;
/* public override Poison Poison => Poison.DarkGlow; // MUST be restored when prerequisites are done */ public override Poison Poison => Poison.GreaterDarkglow;
public override Poison Poison => Poison.Greater;
public override double MinPoisoningSkill => 95.0; public override double MinPoisoningSkill => 95.0;
public override double MaxPoisoningSkill => 100.0; public override double MaxPoisoningSkill => 100.0;

View file

@ -8,9 +8,10 @@ public partial class ParasiticPotion : BasePoisonPotion
[Constructible] [Constructible]
public ParasiticPotion() : base(PotionEffect.Parasitic) => Hue = 0x17C; public ParasiticPotion() : base(PotionEffect.Parasitic) => Hue = 0x17C;
/* public override Poison Poison => Poison.Parasitic; // MUST be restored when prerequisites are done */ public override Poison Poison => Poison.DeadlyParasitic;
public override Poison Poison => Poison.Greater;
public override double MinPoisoningSkill => 95.0; public override double MinPoisoningSkill => 95.0;
public override double MaxPoisoningSkill => 100.0; public override double MaxPoisoningSkill => 100.0;
public override int LabelNumber => 1072848; // Parasitic Poison
} }

View file

@ -289,7 +289,7 @@ public static class NinjaWeapon
{ {
if (EvilOmenSpell.EndEffect(target)) if (EvilOmenSpell.EndEffect(target))
{ {
target.ApplyPoison(from, Poison.GetPoison(weapon.Poison.Level + 1)); target.ApplyPoison(from, Poison.IncreaseLevel(weapon.Poison));
} }
else else
{ {

View file

@ -46,26 +46,31 @@ namespace Server.Items
--weapon.PoisonCharges; --weapon.PoisonCharges;
var poisoningSkill = attacker.Skills.Poisoning.Value;
// Infectious strike special move now uses poisoning skill to help determine potency // Infectious strike special move now uses poisoning skill to help determine potency
var maxLevel = Math.Max((int)(attacker.Skills.Poisoning.Value / 20), 0); var family = p.Family;
int maxLevel = family switch
{
PoisonFamily.Darkglow => Math.Min((int)(poisoningSkill / 33.3), 3),
PoisonFamily.Parasitic => Math.Min((int)(poisoningSkill / 25), 4),
_ => Math.Max((int)(poisoningSkill / 20), 0)
};
if (p.Level > maxLevel) if (p.Level > maxLevel)
{ {
p = Poison.GetPoison(maxLevel); p = Poison.GetPoisonByFamilyAndLevel(family, maxLevel);
} }
if (attacker.Skills.Poisoning.Value / 100.0 > Utility.RandomDouble()) // Check if the poison can be increased.
{ var newPoison = Poison.IncreaseLevel(p);
var level = p.Level + 1; if (newPoison != p && poisoningSkill / 100.0 > Utility.RandomDouble())
var newPoison = Poison.GetPoison(level);
if (newPoison != null)
{ {
p = newPoison; p = newPoison;
attacker.SendLocalizedMessage(1060080); // Your precise strike has increased the level of the poison by 1 attacker.SendLocalizedMessage(1060080); // Your precise strike has increased the level of the poison by 1
defender.SendLocalizedMessage(1060081); // The poison seems extra effective! defender.SendLocalizedMessage(1060081); // The poison seems extra effective!
} }
}
defender.PlaySound(0xDD); defender.PlaySound(0xDD);
defender.FixedParticles(0x3728, 244, 25, 9941, 1266, 0, EffectLayer.Waist); defender.FixedParticles(0x3728, 244, 25, 9941, 1266, 0, EffectLayer.Waist);

View file

@ -6,114 +6,104 @@ using Server.Spells;
using Server.Spells.Necromancy; using Server.Spells.Necromancy;
using Server.Spells.Ninjitsu; using Server.Spells.Ninjitsu;
namespace Server namespace Server;
{
public class PoisonImpl : Poison public class PoisonImpl : Poison
{ {
private readonly int m_Count; private readonly int _count;
private readonly TimeSpan _delay;
// Timers private readonly TimeSpan _interval;
private readonly TimeSpan m_Delay; private readonly int _maximum;
private readonly TimeSpan m_Interval; private readonly int _messageInterval;
private readonly int m_Maximum; private readonly int _minimum;
private readonly int m_MessageInterval; private readonly double _scalar;
// Info
// Damage
private readonly int m_Minimum;
private readonly double m_Scalar;
public PoisonImpl( public PoisonImpl(
string name, int level, int min, int max, double percent, double delay, double interval, int count, string name, int index, int level, int min, int max, double percent, double delay, double interval, int count,
int messageInterval int messageInterval, PoisonFamily family = PoisonFamily.Standard
) ) : base(index)
{ {
Name = name; Name = name;
Level = level; Level = level;
m_Minimum = min; Family = family;
m_Maximum = max; _minimum = min;
m_Scalar = percent * 0.01; _maximum = max;
m_Delay = TimeSpan.FromSeconds(delay); _scalar = percent * 0.01;
m_Interval = TimeSpan.FromSeconds(interval); _delay = TimeSpan.FromSeconds(delay);
m_Count = count; _interval = TimeSpan.FromSeconds(interval);
m_MessageInterval = messageInterval; _count = count;
_messageInterval = messageInterval;
} }
public override string Name { get; } public override string Name { get; }
public override int Level { get; } public override int Level { get; }
[CallPriority(10)] public override PoisonFamily Family { get; }
public static void Configure()
{
if (Core.AOS)
{
Register(new PoisonImpl("Lesser", 0, 4, 16, 7.5, 3.0, 2.25, 10, 4));
Register(new PoisonImpl("Regular", 1, 8, 18, 10.0, 3.0, 3.25, 10, 3));
Register(new PoisonImpl("Greater", 2, 12, 20, 15.0, 3.0, 4.25, 10, 2));
Register(new PoisonImpl("Deadly", 3, 16, 30, 30.0, 3.0, 5.25, 15, 2));
Register(new PoisonImpl("Lethal", 4, 20, 50, 35.0, 3.0, 5.25, 20, 2));
}
else
{
Register(new PoisonImpl("Lesser", 0, 4, 26, 2.500, 3.5, 3.0, 10, 2));
Register(new PoisonImpl("Regular", 1, 5, 26, 3.125, 3.5, 3.0, 10, 2));
Register(new PoisonImpl("Greater", 2, 6, 26, 6.250, 3.5, 3.0, 10, 2));
Register(new PoisonImpl("Deadly", 3, 7, 26, 12.500, 3.5, 4.0, 10, 2));
Register(new PoisonImpl("Lethal", 4, 9, 26, 25.000, 3.5, 5.0, 10, 2));
}
}
public static Poison IncreaseLevel(Poison oldPoison)
{
var newPoison = oldPoison == null ? null : GetPoison(oldPoison.Level + 1);
return newPoison ?? oldPoison;
}
public override Timer ConstructTimer(Mobile m) => new PoisonTimer(m, this); public override Timer ConstructTimer(Mobile m) => new PoisonTimer(m, this);
public class PoisonTimer : Timer public class PoisonTimer : Timer
{ {
private readonly Mobile m_Mobile; private readonly Mobile _mobile;
private readonly PoisonImpl m_Poison; private readonly PoisonImpl _poison;
private int m_Index; private int _index;
private int m_LastDamage; private int _lastDamage;
public PoisonTimer(Mobile m, PoisonImpl p) : base(p.m_Delay, p.m_Interval) public PoisonTimer(Mobile m, PoisonImpl p) : base(p._delay, p._interval)
{ {
From = m; From = m;
m_Mobile = m; _mobile = m;
m_Poison = p; _poison = p;
} }
public Mobile From{ get; set; } public Mobile From{ get; set; }
protected override void OnTick() protected override void OnTick()
{ {
if (Core.AOS && m_Poison.Level < 4 && if ((Core.AOS && _poison.Level < 4 &&
TransformationSpellHelper.UnderTransformation(m_Mobile, typeof(VampiricEmbraceSpell)) || TransformationSpellHelper.UnderTransformation(_mobile, typeof(VampiricEmbraceSpell)) ||
m_Poison.Level < 3 && OrangePetals.UnderEffect(m_Mobile) || _poison.Level < 3 && OrangePetals.UnderEffect(_mobile) ||
AnimalForm.UnderTransformation(m_Mobile, typeof(Unicorn))) AnimalForm.UnderTransformation(_mobile, typeof(Unicorn))) && _mobile.CurePoison(_mobile))
{ {
if (m_Mobile.CurePoison(m_Mobile)) if (Core.SA)
{ {
// * You feel yourself resisting the effects of the poison * // * You feel yourself resisting the effects of the poison *
m_Mobile.LocalOverheadMessage(MessageType.Emote, 0x3F, 1114441); _mobile.LocalOverheadMessage(MessageType.Emote, 0x3F, 1114441);
}
else
{
_mobile.LocalOverheadMessage(
MessageType.Emote,
0x3F,
true,
"* You feel yourself resisting the effects of the poison *"
);
}
if (Core.SA)
{
// * ~1_NAME~ seems resistant to the poison * // * ~1_NAME~ seems resistant to the poison *
m_Mobile.NonlocalOverheadMessage(MessageType.Emote, 0x3F, 1114442, m_Mobile.Name); _mobile.NonlocalOverheadMessage(MessageType.Emote, 0x3F, 1114442, _mobile.Name);
}
else
{
_mobile.LocalOverheadMessage(
MessageType.Emote,
0x3F,
true,
$"* {_mobile.Name} seems resistant to the poison *"
);
}
Stop(); Stop();
return; return;
} }
}
if (m_Index++ == m_Poison.m_Count) if (_index++ == _poison._count)
{ {
m_Mobile.SendLocalizedMessage(502136); // The poison seems to have worn off. _mobile.SendLocalizedMessage(502136); // The poison seems to have worn off.
m_Mobile.Poison = null; _mobile.Poison = null;
Stop(); Stop();
return; return;
@ -121,42 +111,51 @@ namespace Server
int damage; int damage;
if (!Core.AOS && m_LastDamage != 0 && Utility.RandomBool()) if (!Core.AOS && _lastDamage != 0 && Utility.RandomBool())
{ {
damage = m_LastDamage; damage = _lastDamage;
} }
else else
{ {
damage = 1 + (int)(m_Mobile.Hits * m_Poison.m_Scalar); damage = 1 + (int)(_mobile.Hits * _poison._scalar);
damage = Math.Clamp(damage, _poison._minimum, _poison._maximum);
if (damage < m_Poison.m_Minimum) _lastDamage = damage;
}
// Darkglow: 10% damage boost when attacker is more than 1 tile away
if (_poison.Family == PoisonFamily.Darkglow && From != null && From.Map == _mobile.Map &&
!From.InRange(_mobile, 1))
{ {
damage = m_Poison.m_Minimum; damage = (int)(damage * 1.1);
// Darkglow poison increases your damage!
From.SendLocalizedMessage(1072850);
} }
else if (damage > m_Poison.m_Maximum)
From?.DoHarmful(_mobile, true);
(_mobile as IHonorTarget)?.ReceivedHonorContext?.OnTargetPoisoned();
AOS.Damage(_mobile, From, damage, 0, 0, 0, 100, 0);
// Parasitic: heals attacker for damage dealt when within 1 tile
if (_poison.Family == PoisonFamily.Parasitic && From != null && From.Map == _mobile.Map &&
From.InRange(_mobile, 1))
{ {
damage = m_Poison.m_Maximum; From.Heal(damage);
// You have had ~1_HEALED_AMOUNT~ hit points healed.
From.SendLocalizedMessage(1060203, damage.ToString());
} }
m_LastDamage = damage;
}
From?.DoHarmful(m_Mobile, true);
(m_Mobile as IHonorTarget)?.ReceivedHonorContext?.OnTargetPoisoned();
AOS.Damage(m_Mobile, From, damage, 0, 0, 0, 100, 0);
// OSI: randomly revealed between first and third damage tick, guessing 60% chance // OSI: randomly revealed between first and third damage tick, guessing 60% chance
if (Utility.RandomDouble() < 0.40) if (Utility.RandomDouble() < 0.40)
{ {
m_Mobile.RevealingAction(); _mobile.RevealingAction();
} }
if (m_Index % m_Poison.m_MessageInterval == 0) if (_index % _poison._messageInterval == 0)
{ {
m_Mobile.OnPoisoned(From, m_Poison, m_Poison); _mobile.OnPoisoned(From, _poison, _poison);
}
} }
} }
} }

View file

@ -0,0 +1,109 @@
namespace Server;
public static class PoisonKinds
{
private static Poison _lesser;
private static Poison _regular;
private static Poison _greater;
private static Poison _deadly;
private static Poison _lethal;
private static Poison _lesserDarkglow;
private static Poison _regularDarkglow;
private static Poison _greaterDarkglow;
private static Poison _deadlyDarkglow;
private static Poison _lesserParasitic;
private static Poison _regularParasitic;
private static Poison _greaterParasitic;
private static Poison _deadlyParasitic;
private static Poison _lethalParasitic;
extension(Poison poison)
{
public static Poison Lesser => _lesser ??= Poison.GetPoison("Lesser");
public static Poison Regular => _regular ??= Poison.GetPoison("Regular");
public static Poison Greater => _greater ??= Poison.GetPoison("Greater");
public static Poison Deadly => _deadly ??= Poison.GetPoison("Deadly");
public static Poison Lethal => _lethal ??= Poison.GetPoison("Lethal");
public static Poison LesserDarkglow => _lesserDarkglow ??= Poison.GetPoison("LesserDarkglow");
public static Poison RegularDarkglow => _regularDarkglow ??= Poison.GetPoison("RegularDarkglow");
public static Poison GreaterDarkglow => _greaterDarkglow ??= Poison.GetPoison("GreaterDarkglow");
public static Poison DeadlyDarkglow => _deadlyDarkglow ??= Poison.GetPoison("DeadlyDarkglow");
public static Poison LesserParasitic => _lesserParasitic ??= Poison.GetPoison("LesserParasitic");
public static Poison RegularParasitic => _regularParasitic ??= Poison.GetPoison("RegularParasitic");
public static Poison GreaterParasitic => _greaterParasitic ??= Poison.GetPoison("GreaterParasitic");
public static Poison DeadlyParasitic => _deadlyParasitic ??= Poison.GetPoison("DeadlyParasitic");
public static Poison LethalParasitic => _lethalParasitic ??= Poison.GetPoison("LethalParasitic");
public bool IsDarkglow => poison.Family == PoisonFamily.Darkglow;
public bool IsParasitic => poison.Family == PoisonFamily.Parasitic;
public static Poison GetPoison(int level)
{
for (var i = 0; i < Poison.Poisons.Count; ++i)
{
var p = Poison.Poisons[i];
if (p.Family == PoisonFamily.Standard && p.Level == level)
{
return p;
}
}
return null;
}
public static Poison GetPoisonByFamilyAndLevel(PoisonFamily family, int level)
{
for (var i = 0; i < Poison.Poisons.Count; ++i)
{
var p = Poison.Poisons[i];
if (p.Family == family && p.Level == level)
{
return p;
}
}
return null;
}
}
[CallPriority(10)]
public static void Configure()
{
if (Core.AOS)
{
Poison.Register(new PoisonImpl("Lesser", 0, 0, 4, 16, 7.5, 3.0, 2.25, 10, 4));
Poison.Register(new PoisonImpl("Regular", 1, 1, 8, 18, 10.0, 3.0, 3.25, 10, 3));
Poison.Register(new PoisonImpl("Greater", 2, 2, 12, 20, 15.0, 3.0, 4.25, 10, 2));
Poison.Register(new PoisonImpl("Deadly", 3, 3, 16, 30, 30.0, 3.0, 5.25, 15, 2));
Poison.Register(new PoisonImpl("Lethal", 4, 4, 20, 50, 35.0, 3.0, 5.25, 20, 2));
}
else
{
Poison.Register(new PoisonImpl("Lesser", 0, 0, 4, 26, 2.5, 3.5, 3.0, 10, 2));
Poison.Register(new PoisonImpl("Regular", 1, 1, 5, 26, 3.125, 3.5, 3.0, 10, 2));
Poison.Register(new PoisonImpl("Greater", 2, 2, 6, 26, 6.25, 3.5, 3.0, 10, 2));
Poison.Register(new PoisonImpl("Deadly", 3, 3, 7, 26, 12.5, 3.5, 4.0, 10, 2));
Poison.Register(new PoisonImpl("Lethal", 4, 4, 9 , 26, 25.0, 3.5, 5.0, 10, 2));
}
if (Core.ML)
{
Poison.Register(new PoisonImpl("LesserDarkglow", 10, 0, 4, 16, 7.5, 3.0, 2.25, 10, 4, PoisonFamily.Darkglow));
Poison.Register(new PoisonImpl("RegularDarkglow", 11, 1, 8, 18, 10.0, 3.0, 3.25, 10, 3, PoisonFamily.Darkglow));
Poison.Register(new PoisonImpl("GreaterDarkglow", 12, 2, 12, 20, 15.0, 3.0, 4.25, 10, 2, PoisonFamily.Darkglow));
Poison.Register(new PoisonImpl("DeadlyDarkglow", 13, 3, 16, 30, 30.0, 3.0, 5.25, 15, 2, PoisonFamily.Darkglow));
Poison.Register(new PoisonImpl("LesserParasitic", 20, 0, 4, 16, 7.5, 3.0, 2.25, 10, 4, PoisonFamily.Parasitic));
Poison.Register(new PoisonImpl("RegularParasitic", 21, 1, 8, 18, 10.0, 3.0, 3.25, 10, 3, PoisonFamily.Parasitic));
Poison.Register(new PoisonImpl("GreaterParasitic", 22, 2, 12, 20, 15.0, 3.0, 4.25, 10, 2, PoisonFamily.Parasitic));
Poison.Register(new PoisonImpl("DeadlyParasitic", 23, 3, 16, 30, 30.0, 3.0, 5.25, 15, 2, PoisonFamily.Parasitic));
Poison.Register(new PoisonImpl("LethalParasitic", 24, 4, 20, 50, 35.0, 3.0, 5.25, 20, 2, PoisonFamily.Parasitic));
}
}
}

View file

@ -70,8 +70,8 @@ namespace Server.Spells.Fourth
if (poison != null) if (poison != null)
{ {
var chanceToCure = 10000 + (int)(Caster.Skills.Magery.Value * 75) - var poisonLevel = Poison.IncreaseLevel(poison).Level;
(poison.Level + 1) * 1750; var chanceToCure = 10000 + (int)(Caster.Skills.Magery.Value * 75) - poisonLevel * 1750;
chanceToCure /= 100; chanceToCure /= 100;
chanceToCure -= 1; chanceToCure -= 1;

View file

@ -99,7 +99,7 @@ public class CleansingWindsSpell : MysticSpell, ITargetingSpell<Mobile>
if (target.Poisoned) if (target.Poisoned)
{ {
var poisonLevel = target.Poison.Level + 1; var poisonLevel = Poison.IncreaseLevel(target.Poison).Level;
var chanceToCure = cureChance - poisonLevel * 1750; var chanceToCure = cureChance - poisonLevel * 1750;
if (chanceToCure > 10000 || chanceToCure > Utility.Random(10000) && target.CurePoison(Caster)) if (chanceToCure > 10000 || chanceToCure > Utility.Random(10000) && target.CurePoison(Caster))