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:
parent
3bb38bcb5b
commit
992bc95164
12 changed files with 316 additions and 205 deletions
|
|
@ -4,75 +4,56 @@ using System.Runtime.CompilerServices;
|
|||
|
||||
namespace Server;
|
||||
|
||||
public enum PoisonFamily { Standard, Darkglow, Parasitic }
|
||||
|
||||
public abstract class Poison : ISpanParsable<Poison>
|
||||
{
|
||||
/*public abstract TimeSpan Interval{ get; }
|
||||
public abstract TimeSpan Duration{ get; }*/
|
||||
public static List<Poison> Poisons { 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 int Level { 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 PoisonFamily Family { get; }
|
||||
|
||||
public abstract Timer ConstructTimer(Mobile m);
|
||||
/*public abstract void OnDamage( Mobile m, ref object state );*/
|
||||
|
||||
public override string ToString() => Name;
|
||||
|
||||
public static void Register(Poison reg)
|
||||
{
|
||||
var regName = reg.Name.ToLower();
|
||||
var regName = reg.Name;
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
Poisons.Add(reg);
|
||||
PoisonsByName.Add(regName, reg);
|
||||
}
|
||||
|
||||
public static Poison GetPoison(int level)
|
||||
{
|
||||
for (var i = 0; i < Poisons.Count; ++i)
|
||||
{
|
||||
var p = Poisons[i];
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Poison GetPoisonByIndex(int index) => index >= 0 && index < Poisons.Count ? Poisons[index] : null;
|
||||
|
||||
if (p.Level == level)
|
||||
{
|
||||
return p;
|
||||
}
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Poison IncreaseLevel(Poison oldPoison) =>
|
||||
oldPoison == null ? null : GetPoisonByIndex(oldPoison.Index + 1) ?? oldPoison;
|
||||
|
||||
return 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)]
|
||||
public static Poison GetPoison(ReadOnlySpan<char> name) =>
|
||||
PoisonsByName.GetAlternateLookup<ReadOnlySpan<char>>().TryGetValue(name, out var poison) ? poison : null;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return result;
|
||||
|
|
@ -106,9 +87,9 @@ public abstract class Poison : ISpanParsable<Poison>
|
|||
|
||||
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)
|
||||
{
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -182,10 +182,10 @@ public static class SerializationExtensions
|
|||
else
|
||||
{
|
||||
writer.Write(true);
|
||||
writer.Write((byte)p.Level);
|
||||
writer.Write((byte)p.Index);
|
||||
}
|
||||
}
|
||||
|
||||
public static Poison ReadPoison(this IGenericReader reader) =>
|
||||
reader.ReadBool() ? Poison.GetPoison(reader.ReadByte()) : null;
|
||||
reader.ReadBool() ? Poison.GetPoisonByIndex(reader.ReadByte()) : null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ public partial class PotionKeg : Item
|
|||
TileData.ItemTable[0x1940].Height = 4;
|
||||
}
|
||||
|
||||
[InvalidateProperties] [SerializableField(0)] [SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
[InvalidateProperties]
|
||||
[SerializableField(0)]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private PotionEffect _type;
|
||||
|
||||
[Constructible]
|
||||
|
|
@ -45,12 +47,20 @@ public partial class PotionKeg : Item
|
|||
{
|
||||
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.ConfusionBlast => new ConfusionBlastPotion(),
|
||||
PotionEffect.ConfusionBlastGreater => new GreaterConfusionBlastPotion(),
|
||||
PotionEffect.Invisibility => new InvisibilityPotion(),
|
||||
PotionEffect.Parasitic => new ParasiticPotion(),
|
||||
PotionEffect.Darkglow => new DarkglowPotion(),
|
||||
_ => new NightSightPotion()
|
||||
};
|
||||
|
||||
|
|
@ -279,6 +292,9 @@ public partial class PotionKeg : Item
|
|||
_ when type == typeof(GreaterConflagrationPotion) => PotionEffect.ConflagrationGreater,
|
||||
_ when type == typeof(ConfusionBlastPotion) => PotionEffect.ConfusionBlast,
|
||||
_ 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
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ public enum PotionEffect
|
|||
ConfusionBlastGreater,
|
||||
Invisibility,
|
||||
Parasitic,
|
||||
Darkglow
|
||||
Darkglow,
|
||||
FlintsPungentBrew
|
||||
}
|
||||
|
||||
[SerializationGenerator(2, false)]
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ public partial class DarkglowPotion : BasePoisonPotion
|
|||
[Constructible]
|
||||
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.Greater;
|
||||
public override Poison Poison => Poison.GreaterDarkglow;
|
||||
|
||||
public override double MinPoisoningSkill => 95.0;
|
||||
public override double MaxPoisoningSkill => 100.0;
|
||||
|
|
|
|||
|
|
@ -8,9 +8,10 @@ public partial class ParasiticPotion : BasePoisonPotion
|
|||
[Constructible]
|
||||
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.Greater;
|
||||
public override Poison Poison => Poison.DeadlyParasitic;
|
||||
|
||||
public override double MinPoisoningSkill => 95.0;
|
||||
public override double MaxPoisoningSkill => 100.0;
|
||||
|
||||
public override int LabelNumber => 1072848; // Parasitic Poison
|
||||
}
|
||||
|
|
|
|||
|
|
@ -289,7 +289,7 @@ public static class NinjaWeapon
|
|||
{
|
||||
if (EvilOmenSpell.EndEffect(target))
|
||||
{
|
||||
target.ApplyPoison(from, Poison.GetPoison(weapon.Poison.Level + 1));
|
||||
target.ApplyPoison(from, Poison.IncreaseLevel(weapon.Poison));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -46,26 +46,31 @@ namespace Server.Items
|
|||
|
||||
--weapon.PoisonCharges;
|
||||
|
||||
var poisoningSkill = attacker.Skills.Poisoning.Value;
|
||||
|
||||
// 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)
|
||||
{
|
||||
p = Poison.GetPoison(maxLevel);
|
||||
p = Poison.GetPoisonByFamilyAndLevel(family, maxLevel);
|
||||
}
|
||||
|
||||
if (attacker.Skills.Poisoning.Value / 100.0 > Utility.RandomDouble())
|
||||
{
|
||||
var level = p.Level + 1;
|
||||
var newPoison = Poison.GetPoison(level);
|
||||
|
||||
if (newPoison != null)
|
||||
// Check if the poison can be increased.
|
||||
var newPoison = Poison.IncreaseLevel(p);
|
||||
if (newPoison != p && poisoningSkill / 100.0 > Utility.RandomDouble())
|
||||
{
|
||||
p = newPoison;
|
||||
|
||||
attacker.SendLocalizedMessage(1060080); // Your precise strike has increased the level of the poison by 1
|
||||
defender.SendLocalizedMessage(1060081); // The poison seems extra effective!
|
||||
}
|
||||
}
|
||||
|
||||
defender.PlaySound(0xDD);
|
||||
defender.FixedParticles(0x3728, 244, 25, 9941, 1266, 0, EffectLayer.Waist);
|
||||
|
|
|
|||
|
|
@ -6,114 +6,104 @@ using Server.Spells;
|
|||
using Server.Spells.Necromancy;
|
||||
using Server.Spells.Ninjitsu;
|
||||
|
||||
namespace Server
|
||||
namespace Server;
|
||||
|
||||
public class PoisonImpl : Poison
|
||||
{
|
||||
public class PoisonImpl : Poison
|
||||
{
|
||||
private readonly int m_Count;
|
||||
|
||||
// Timers
|
||||
private readonly TimeSpan m_Delay;
|
||||
private readonly TimeSpan m_Interval;
|
||||
private readonly int m_Maximum;
|
||||
private readonly int m_MessageInterval;
|
||||
|
||||
// Info
|
||||
|
||||
// Damage
|
||||
private readonly int m_Minimum;
|
||||
private readonly double m_Scalar;
|
||||
private readonly int _count;
|
||||
private readonly TimeSpan _delay;
|
||||
private readonly TimeSpan _interval;
|
||||
private readonly int _maximum;
|
||||
private readonly int _messageInterval;
|
||||
private readonly int _minimum;
|
||||
private readonly double _scalar;
|
||||
|
||||
public PoisonImpl(
|
||||
string name, int level, int min, int max, double percent, double delay, double interval, int count,
|
||||
int messageInterval
|
||||
)
|
||||
string name, int index, int level, int min, int max, double percent, double delay, double interval, int count,
|
||||
int messageInterval, PoisonFamily family = PoisonFamily.Standard
|
||||
) : base(index)
|
||||
{
|
||||
Name = name;
|
||||
Level = level;
|
||||
m_Minimum = min;
|
||||
m_Maximum = max;
|
||||
m_Scalar = percent * 0.01;
|
||||
m_Delay = TimeSpan.FromSeconds(delay);
|
||||
m_Interval = TimeSpan.FromSeconds(interval);
|
||||
m_Count = count;
|
||||
m_MessageInterval = messageInterval;
|
||||
Family = family;
|
||||
_minimum = min;
|
||||
_maximum = max;
|
||||
_scalar = percent * 0.01;
|
||||
_delay = TimeSpan.FromSeconds(delay);
|
||||
_interval = TimeSpan.FromSeconds(interval);
|
||||
_count = count;
|
||||
_messageInterval = messageInterval;
|
||||
}
|
||||
|
||||
public override string Name { get; }
|
||||
|
||||
public override int Level { get; }
|
||||
|
||||
[CallPriority(10)]
|
||||
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 PoisonFamily Family { get; }
|
||||
|
||||
public override Timer ConstructTimer(Mobile m) => new PoisonTimer(m, this);
|
||||
|
||||
public class PoisonTimer : Timer
|
||||
{
|
||||
private readonly Mobile m_Mobile;
|
||||
private readonly PoisonImpl m_Poison;
|
||||
private int m_Index;
|
||||
private int m_LastDamage;
|
||||
private readonly Mobile _mobile;
|
||||
private readonly PoisonImpl _poison;
|
||||
private int _index;
|
||||
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;
|
||||
m_Mobile = m;
|
||||
m_Poison = p;
|
||||
_mobile = m;
|
||||
_poison = p;
|
||||
}
|
||||
|
||||
public Mobile From { get; set; }
|
||||
public Mobile From{ get; set; }
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
if (Core.AOS && m_Poison.Level < 4 &&
|
||||
TransformationSpellHelper.UnderTransformation(m_Mobile, typeof(VampiricEmbraceSpell)) ||
|
||||
m_Poison.Level < 3 && OrangePetals.UnderEffect(m_Mobile) ||
|
||||
AnimalForm.UnderTransformation(m_Mobile, typeof(Unicorn)))
|
||||
if ((Core.AOS && _poison.Level < 4 &&
|
||||
TransformationSpellHelper.UnderTransformation(_mobile, typeof(VampiricEmbraceSpell)) ||
|
||||
_poison.Level < 3 && OrangePetals.UnderEffect(_mobile) ||
|
||||
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 *
|
||||
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 *
|
||||
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();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_Index++ == m_Poison.m_Count)
|
||||
if (_index++ == _poison._count)
|
||||
{
|
||||
m_Mobile.SendLocalizedMessage(502136); // The poison seems to have worn off.
|
||||
m_Mobile.Poison = null;
|
||||
_mobile.SendLocalizedMessage(502136); // The poison seems to have worn off.
|
||||
_mobile.Poison = null;
|
||||
|
||||
Stop();
|
||||
return;
|
||||
|
|
@ -121,42 +111,51 @@ namespace Server
|
|||
|
||||
int damage;
|
||||
|
||||
if (!Core.AOS && m_LastDamage != 0 && Utility.RandomBool())
|
||||
if (!Core.AOS && _lastDamage != 0 && Utility.RandomBool())
|
||||
{
|
||||
damage = m_LastDamage;
|
||||
damage = _lastDamage;
|
||||
}
|
||||
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
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
109
Projects/UOContent/Misc/PoisonKinds.cs
Normal file
109
Projects/UOContent/Misc/PoisonKinds.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -70,8 +70,8 @@ namespace Server.Spells.Fourth
|
|||
|
||||
if (poison != null)
|
||||
{
|
||||
var chanceToCure = 10000 + (int)(Caster.Skills.Magery.Value * 75) -
|
||||
(poison.Level + 1) * 1750;
|
||||
var poisonLevel = Poison.IncreaseLevel(poison).Level;
|
||||
var chanceToCure = 10000 + (int)(Caster.Skills.Magery.Value * 75) - poisonLevel * 1750;
|
||||
chanceToCure /= 100;
|
||||
chanceToCure -= 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ public class CleansingWindsSpell : MysticSpell, ITargetingSpell<Mobile>
|
|||
|
||||
if (target.Poisoned)
|
||||
{
|
||||
var poisonLevel = target.Poison.Level + 1;
|
||||
var poisonLevel = Poison.IncreaseLevel(target.Poison).Level;
|
||||
var chanceToCure = cureChance - poisonLevel * 1750;
|
||||
|
||||
if (chanceToCure > 10000 || chanceToCure > Utility.Random(10000) && target.CurePoison(Caster))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue