From 4556adcd10bdd3b0e749d7853bd3b858f855d087 Mon Sep 17 00:00:00 2001 From: Crome696 Date: Sun, 12 Jul 2026 10:50:30 +0200 Subject: [PATCH] feat(items): implement searing weapon property --- .../Weapons/ExtendedWeaponAttributesTests.cs | 8 + .../UOContent/Items/Weapons/Axes/BaseAxe.cs | 4 +- .../Items/Weapons/BaseWeapon.Migrations.cs | 40 +++ .../UOContent/Items/Weapons/BaseWeapon.cs | 41 ++- .../Items/Weapons/Knives/BaseKnife.cs | 4 +- .../Items/Weapons/Maces/BaseBashing.cs | 4 +- .../Items/Weapons/PoleArms/BasePoleArm.cs | 4 +- .../Items/Weapons/Ranged/BaseRanged.cs | 8 +- Projects/UOContent/Items/Weapons/Searing.cs | 134 ++++++++ .../Items/Weapons/SpearsAndForks/BaseSpear.cs | 4 +- .../Items/Weapons/Staves/BaseStaff.cs | 4 +- .../Items/Weapons/Swords/BaseSword.cs | 4 +- .../Items/Weapons/Throwing/BaseThrown.cs | 4 +- .../Server.Items.BaseWeapon.v15.json | 295 ++++++++++++++++++ Projects/UOContent/Misc/AOS.cs | 23 +- Projects/UOContent/Misc/RegenRates.cs | 2 +- 16 files changed, 559 insertions(+), 24 deletions(-) create mode 100644 Projects/UOContent/Items/Weapons/Searing.cs create mode 100644 Projects/UOContent/Migrations/Server.Items.BaseWeapon.v15.json diff --git a/Projects/UOContent.Tests/Tests/Items/Weapons/ExtendedWeaponAttributesTests.cs b/Projects/UOContent.Tests/Tests/Items/Weapons/ExtendedWeaponAttributesTests.cs index 86318d472..7d9da6c65 100644 --- a/Projects/UOContent.Tests/Tests/Items/Weapons/ExtendedWeaponAttributesTests.cs +++ b/Projects/UOContent.Tests/Tests/Items/Weapons/ExtendedWeaponAttributesTests.cs @@ -31,6 +31,7 @@ public class ExtendedWeaponAttributesTests Assert.Equal(0x00000040, (int)ExtendedWeaponAttribute.Focus); Assert.Equal(0x00000080, (int)ExtendedWeaponAttribute.BoneBreaker); Assert.Equal(0x00000100, (int)ExtendedWeaponAttribute.AssassinHoned); + Assert.Equal(0x00000200, (int)ExtendedWeaponAttribute.Searing); } [Fact] @@ -51,6 +52,8 @@ public class ExtendedWeaponAttributesTests Assert.Equal(0, weapon.ExtendedWeaponAttributes.Focus); Assert.Equal(0, weapon.ExtendedWeaponAttributes.BoneBreaker); Assert.Equal(0, weapon.ExtendedWeaponAttributes.AssassinHoned); + Assert.Equal(0, weapon.ExtendedWeaponAttributes.Searing); + Assert.False(weapon.SearingIgnited); } finally { @@ -91,6 +94,7 @@ public class ExtendedWeaponAttributesTests AssertStaffCommandProperty(nameof(ExtendedWeaponAttributes.Focus)); AssertStaffCommandProperty(nameof(ExtendedWeaponAttributes.BoneBreaker)); AssertStaffCommandProperty(nameof(ExtendedWeaponAttributes.AssassinHoned)); + AssertStaffCommandProperty(nameof(ExtendedWeaponAttributes.Searing)); } [Fact] @@ -112,6 +116,8 @@ public class ExtendedWeaponAttributesTests weapon.ExtendedWeaponAttributes.Focus = 1; weapon.ExtendedWeaponAttributes.BoneBreaker = 1; weapon.ExtendedWeaponAttributes.AssassinHoned = 1; + weapon.ExtendedWeaponAttributes.Searing = 1; + weapon.SearingIgnited = true; var writer = new BufferWriter(true); weapon.Serialize(writer); @@ -131,6 +137,8 @@ public class ExtendedWeaponAttributesTests Assert.Equal(1, deserialized.ExtendedWeaponAttributes.Focus); Assert.Equal(1, deserialized.ExtendedWeaponAttributes.BoneBreaker); Assert.Equal(1, deserialized.ExtendedWeaponAttributes.AssassinHoned); + Assert.Equal(1, deserialized.ExtendedWeaponAttributes.Searing); + Assert.True(deserialized.SearingIgnited); } finally { diff --git a/Projects/UOContent/Items/Weapons/Axes/BaseAxe.cs b/Projects/UOContent/Items/Weapons/Axes/BaseAxe.cs index 13c41d0c4..7af332aba 100644 --- a/Projects/UOContent/Items/Weapons/Axes/BaseAxe.cs +++ b/Projects/UOContent/Items/Weapons/Axes/BaseAxe.cs @@ -108,9 +108,9 @@ namespace Server.Items } } - public override void OnHit(Mobile attacker, Mobile defender, double damageBonus = 1) + public override void OnHit(Mobile attacker, Mobile defender, double damageBonus = 1, bool searingProcEligible = false) { - base.OnHit(attacker, defender, damageBonus); + base.OnHit(attacker, defender, damageBonus, searingProcEligible); if (!Core.AOS && Core.UOR && (attacker.Player || attacker.Body.IsHuman) && Layer == Layer.TwoHanded && attacker.Skills.Anatomy.Value >= 80 && diff --git a/Projects/UOContent/Items/Weapons/BaseWeapon.Migrations.cs b/Projects/UOContent/Items/Weapons/BaseWeapon.Migrations.cs index d07f1ea74..7589c0f81 100644 --- a/Projects/UOContent/Items/Weapons/BaseWeapon.Migrations.cs +++ b/Projects/UOContent/Items/Weapons/BaseWeapon.Migrations.cs @@ -152,4 +152,44 @@ public abstract partial class BaseWeapon _unwieldyOriginalWeight = content.UnwieldyOriginalWeight; _absorptionAttributes = AbsorptionAttributesDefaultValue(); } + + private void MigrateFrom(V14Content content) + { + _damageLevel = content.DamageLevel ?? WeaponDamageLevel.Regular; + _accuracyLevel = content.AccuracyLevel ?? WeaponAccuracyLevel.Regular; + _durabilityLevel = content.DurabilityLevel ?? WeaponDurabilityLevel.Regular; + _quality = content.Quality ?? WeaponQuality.Regular; + _hitPoints = content.HitPoints ?? 0; + _maxHitPoints = content.MaxHitPoints ?? 0; + _slayer = content.Slayer ?? SlayerName.None; + _poison = content.Poison; + _poisonCharges = content.PoisonCharges ?? 0; + _crafter = content.Crafter; + _identified = content.Identified; + _strRequirement = content.StrRequirement ?? -1; + _dexRequirement = content.DexRequirement ?? -1; + _intRequirement = content.IntRequirement ?? -1; + _minDamage = content.MinDamage ?? -1; + _maxDamage = content.MaxDamage ?? -1; + _hitSound = content.HitSound ?? -1; + _missSound = content.MissSound ?? -1; + _speed = content.Speed ?? -1; + _maxRange = content.MaxRange ?? -1; + _skill = content.Skill ?? (SkillName)(-1); + _type = content.Type ?? (WeaponType)(-1); + _animation = content.Animation ?? (WeaponAnimation)(-1); + _resource = content.Resource ?? CraftResource.Iron; + _attributes = content.Attributes ?? AttributesDefaultValue(); + _weaponAttributes = content.WeaponAttributes ?? WeaponAttributesDefaultValue(); + _playerConstructed = content.PlayerConstructed; + _skillBonuses = content.SkillBonuses ?? SkillBonusesDefaultValue(); + _slayer2 = content.Slayer2 ?? SlayerName.None; + _aosElementDamages = content.AosElementDamages ?? AosElementAttributesDefaultValue(); + _engravedText = content.EngravedText; + _extendedWeaponAttributes = content.ExtendedWeaponAttributes ?? ExtendedWeaponAttributesDefaultValue(); + _negativeAttributes = content.NegativeAttributes ?? NegativeAttributesDefaultValue(); + _unwieldyOriginalWeight = content.UnwieldyOriginalWeight; + _absorptionAttributes = content.AbsorptionAttributes ?? AbsorptionAttributesDefaultValue(); + _searingIgnited = false; + } } diff --git a/Projects/UOContent/Items/Weapons/BaseWeapon.cs b/Projects/UOContent/Items/Weapons/BaseWeapon.cs index ec455fd3c..dc0f740dd 100644 --- a/Projects/UOContent/Items/Weapons/BaseWeapon.cs +++ b/Projects/UOContent/Items/Weapons/BaseWeapon.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using ModernUO.Serialization; using Server.Collections; +using Server.ContextMenus; using Server.Engines.Craft; using Server.Engines.Virtues; using Server.Ethics; @@ -28,7 +29,7 @@ public interface ISlayer SlayerName Slayer2 { get; set; } } -[SerializationGenerator(14, false)] +[SerializationGenerator(15, false)] public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftable, ISlayer, IDurability, IAosItem, IIdentifiable { @@ -223,6 +224,12 @@ public abstract partial class BaseWeapon [SerializableFieldDefault(34)] private AbsorptionAttributes AbsorptionAttributesDefaultValue() => new(this); + // Field 35 intentionally has no save flag; BaseWeapon's legacy save-flag mask is already at the high bit. + [InvalidateProperties] + [SerializableField(35)] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private bool _searingIgnited; + private FactionItem m_FactionState; private SkillMod m_SkillMod, m_MageMod; private int _lastParryChance; @@ -973,9 +980,11 @@ public abstract partial class BaseWeapon } } + var searingProcEligible = Searing.BeginAttack(this, attacker); + if (CheckHit(attacker, defender)) { - OnHit(attacker, defender, damageBonus); + OnHit(attacker, defender, damageBonus, searingProcEligible); } else { @@ -1203,10 +1212,21 @@ public abstract partial class BaseWeapon } } + public override void GetContextMenuEntries(Mobile from, ref PooledRefList list) + { + base.GetContextMenuEntries(from, ref list); + + if (Core.HS && ExtendedWeaponAttributes.Searing != 0 && Parent == from && from.Weapon == this) + { + list.Add(new ToggleSearingEntry(SearingIgnited)); + } + } + public override void OnRemoved(IEntity parent) { ClearLastParryChance(); FocusContext.Clear(this); + SearingIgnited = false; EnchantSpell.StopEffect(this); if (parent is not Mobile m) @@ -1258,10 +1278,23 @@ public abstract partial class BaseWeapon public override void OnAfterDelete() { FocusContext.Clear(this); + SearingIgnited = false; EnchantSpell.StopEffect(this); base.OnAfterDelete(); } + private sealed class ToggleSearingEntry(bool ignited) : ContextMenuEntry(ignited ? 1151174 : 1151173) + { + public override void OnClick(Mobile from, IEntity target) + { + if (Core.HS && target is BaseWeapon { Deleted: false, ExtendedWeaponAttributes.Searing: not 0 } weapon && + weapon.Parent == from && from.Weapon == weapon) + { + weapon.SearingIgnited = !weapon.SearingIgnited; + } + } + } + public override void OnMapChange() { base.OnMapChange(); @@ -1270,6 +1303,7 @@ public abstract partial class BaseWeapon if (Map == null || Map == Map.Internal) { FocusContext.Clear(this); + SearingIgnited = false; } if ((Map == null || Map == Map.Internal) && Parent is Mobile m && ExtendedWeaponAttributes.BattleLust != 0) @@ -1920,7 +1954,7 @@ public abstract partial class BaseWeapon }; } - public virtual void OnHit(Mobile attacker, Mobile defender, double damageBonus = 1.0) + public virtual void OnHit(Mobile attacker, Mobile defender, double damageBonus = 1.0, bool searingProcEligible = false) { if (MirrorImage.HasClone(defender) && defender.Skills.Ninjitsu.Value / 150.0 > Utility.RandomDouble()) { @@ -2432,6 +2466,7 @@ public abstract partial class BaseWeapon Sparks.TryProcOnNormalHit(attacker, defender, ExtendedWeaponAttributes.HitSparks); Swarm.TryProcOnNormalHit(attacker, defender, ExtendedWeaponAttributes.HitSwarm); BoneBreaker.TryProcOnNormalHit(attacker, defender, ExtendedWeaponAttributes.BoneBreaker); + Searing.TryProcOnNormalHit(this, attacker, defender, searingProcEligible); } } diff --git a/Projects/UOContent/Items/Weapons/Knives/BaseKnife.cs b/Projects/UOContent/Items/Weapons/Knives/BaseKnife.cs index b676ed0a3..49d781761 100644 --- a/Projects/UOContent/Items/Weapons/Knives/BaseKnife.cs +++ b/Projects/UOContent/Items/Weapons/Knives/BaseKnife.cs @@ -24,9 +24,9 @@ namespace Server.Items from.Target = new BladedItemTarget(this); } - public override void OnHit(Mobile attacker, Mobile defender, double damageBonus = 1) + public override void OnHit(Mobile attacker, Mobile defender, double damageBonus = 1, bool searingProcEligible = false) { - base.OnHit(attacker, defender, damageBonus); + base.OnHit(attacker, defender, damageBonus, searingProcEligible); if (!Core.AOS && Poison != null && PoisonCharges > 0) { diff --git a/Projects/UOContent/Items/Weapons/Maces/BaseBashing.cs b/Projects/UOContent/Items/Weapons/Maces/BaseBashing.cs index 40ab2c919..77cb4d7ac 100644 --- a/Projects/UOContent/Items/Weapons/Maces/BaseBashing.cs +++ b/Projects/UOContent/Items/Weapons/Maces/BaseBashing.cs @@ -17,9 +17,9 @@ namespace Server.Items public override WeaponType DefType => WeaponType.Bashing; public override WeaponAnimation DefAnimation => WeaponAnimation.Bash1H; - public override void OnHit(Mobile attacker, Mobile defender, double damageBonus = 1) + public override void OnHit(Mobile attacker, Mobile defender, double damageBonus = 1, bool searingProcEligible = false) { - base.OnHit(attacker, defender, damageBonus); + base.OnHit(attacker, defender, damageBonus, searingProcEligible); defender.Stam -= Utility.Random(3, 3); // 3-5 points of stamina loss } diff --git a/Projects/UOContent/Items/Weapons/PoleArms/BasePoleArm.cs b/Projects/UOContent/Items/Weapons/PoleArms/BasePoleArm.cs index 6ee681593..469561c51 100644 --- a/Projects/UOContent/Items/Weapons/PoleArms/BasePoleArm.cs +++ b/Projects/UOContent/Items/Weapons/PoleArms/BasePoleArm.cs @@ -58,9 +58,9 @@ namespace Server.Items } } - public override void OnHit(Mobile attacker, Mobile defender, double damageBonus = 1) + public override void OnHit(Mobile attacker, Mobile defender, double damageBonus = 1, bool searingProcEligible = false) { - base.OnHit(attacker, defender, damageBonus); + base.OnHit(attacker, defender, damageBonus, searingProcEligible); if (!Core.AOS && (attacker.Player || attacker.Body.IsHuman) && Layer == Layer.TwoHanded && attacker.Skills.Anatomy.Value >= 80 && diff --git a/Projects/UOContent/Items/Weapons/Ranged/BaseRanged.cs b/Projects/UOContent/Items/Weapons/Ranged/BaseRanged.cs index 4fd786809..1ba351cb5 100644 --- a/Projects/UOContent/Items/Weapons/Ranged/BaseRanged.cs +++ b/Projects/UOContent/Items/Weapons/Ranged/BaseRanged.cs @@ -75,9 +75,11 @@ namespace Server.Items if (OnFired(attacker, defender)) { + var searingProcEligible = Searing.BeginAttack(this, attacker); + if (CheckHit(attacker, defender)) { - OnHit(attacker, defender); + OnHit(attacker, defender, 1.0, searingProcEligible); } else { @@ -102,7 +104,7 @@ namespace Server.Items return TimeSpan.FromSeconds(0.25); } - public override void OnHit(Mobile attacker, Mobile defender, double damageBonus = 1) + public override void OnHit(Mobile attacker, Mobile defender, double damageBonus = 1, bool searingProcEligible = false) { if (attacker.Player && !defender.Player && (defender.Body.IsAnimal || defender.Body.IsMonster) && Utility.RandomDouble() < 0.4 && Ammo is { } ammo && !defender.AddToBackpack(ammo)) @@ -130,7 +132,7 @@ namespace Server.Items } } - base.OnHit(attacker, defender, damageBonus); + base.OnHit(attacker, defender, damageBonus, searingProcEligible); } public override void OnMiss(Mobile attacker, Mobile defender) diff --git a/Projects/UOContent/Items/Weapons/Searing.cs b/Projects/UOContent/Items/Weapons/Searing.cs new file mode 100644 index 000000000..0ae256998 --- /dev/null +++ b/Projects/UOContent/Items/Weapons/Searing.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using ModernUO.CodeGeneratedEvents; +using Server.Mobiles; + +namespace Server.Items; + +public static class Searing +{ + // 10-15 fire damage is the issue's explicit ServUO-compatibility policy, not an EA-verified value. + internal const int MinimumFireDamage = 10; + internal const int MaximumFireDamage = 15; + internal const int SelfDamage = 4; + internal const int MeleeProcChance = 20; + internal const int RangedProcChance = 10; + internal const int PlayerRegenPenalty = 20; + internal const int NpcRegenPenalty = 60; + internal static readonly TimeSpan RegenPenaltyDuration = TimeSpan.FromSeconds(4); + + private static readonly Dictionary _contexts = []; + + public static void Configure() + { + EventSink.Logout += ClearTarget; + } + + internal static int ActiveContextCount => _contexts.Count; + + internal static bool BeginAttack(BaseWeapon weapon, Mobile attacker) + { + if (!CanUse(weapon, attacker)) + { + return false; + } + + if (attacker.Mana <= 0) + { + return false; + } + + attacker.Mana--; + return true; + } + + internal static void TryProcOnNormalHit(BaseWeapon weapon, Mobile attacker, Mobile defender, bool procEligible) + { + if (!procEligible || !CanUse(weapon, attacker) || !IsValidTarget(defender)) + { + return; + } + + var chance = weapon is BaseRanged ? RangedProcChance : MeleeProcChance; + + if (chance <= Utility.Random(100)) + { + return; + } + + attacker.Damage(SelfDamage, attacker); + AOS.Damage(defender, attacker, Utility.RandomMinMax(MinimumFireDamage, MaximumFireDamage), false, 0, 100, 0, 0, 0); + ApplyRegenPenalty(defender); + } + + internal static int GetHitRegenPenalty(Mobile mobile) => + mobile != null && _contexts.ContainsKey(mobile) ? mobile.Player ? -PlayerRegenPenalty : -NpcRegenPenalty : 0; + + internal static bool HasContext(Mobile mobile) => mobile != null && _contexts.ContainsKey(mobile); + + internal static void ExpireForTests(Mobile mobile) => Clear(mobile); + + internal static void ClearAll() + { + foreach (var context in _contexts.Values) + { + context.TimerToken.Cancel(); + } + + _contexts.Clear(); + } + + private static void ClearTarget(Mobile mobile) + { + if (mobile != null && _contexts.Remove(mobile, out var context)) + { + context.TimerToken.Cancel(); + } + } + + [OnEvent(nameof(PlayerMobile.PlayerDeathEvent))] + [OnEvent(nameof(PlayerMobile.PlayerDeletedEvent))] + [OnEvent(nameof(BaseCreature.CreatureDeathEvent))] + [OnEvent(nameof(BaseCreature.CreatureDeletedEvent))] + public static void Clear(Mobile mobile) + { + if (mobile == null) + { + return; + } + + if (_contexts.Remove(mobile, out var context)) + { + context.TimerToken.Cancel(); + } + + if (mobile.Weapon is BaseWeapon weapon) + { + weapon.SearingIgnited = false; + } + } + + private static void ApplyRegenPenalty(Mobile defender) + { + Clear(defender); + Timer.StartTimer(RegenPenaltyDuration, () => Clear(defender), out var token); + _contexts[defender] = new Context(token); + defender.CheckStatTimers(); + } + + private static bool CanUse(BaseWeapon weapon, Mobile attacker) => + Core.HS && + weapon is { Deleted: false, SearingIgnited: true } && + weapon.ExtendedWeaponAttributes.Searing != 0 && + attacker?.Deleted == false && + attacker.Alive && + attacker.Map != null && + attacker.Map != Map.Internal && + attacker.Weapon == weapon && + weapon.Parent == attacker; + + private static bool IsValidTarget(Mobile mobile) => + mobile is { Deleted: false, Alive: true } && mobile.Map != null && mobile.Map != Map.Internal; + + private readonly record struct Context(TimerExecutionToken TimerToken); +} diff --git a/Projects/UOContent/Items/Weapons/SpearsAndForks/BaseSpear.cs b/Projects/UOContent/Items/Weapons/SpearsAndForks/BaseSpear.cs index 0cedb68db..cfa93b3c0 100644 --- a/Projects/UOContent/Items/Weapons/SpearsAndForks/BaseSpear.cs +++ b/Projects/UOContent/Items/Weapons/SpearsAndForks/BaseSpear.cs @@ -18,9 +18,9 @@ namespace Server.Items public override WeaponType DefType => WeaponType.Piercing; public override WeaponAnimation DefAnimation => WeaponAnimation.Pierce2H; - public override void OnHit(Mobile attacker, Mobile defender, double damageBonus = 1) + public override void OnHit(Mobile attacker, Mobile defender, double damageBonus = 1, bool searingProcEligible = false) { - base.OnHit(attacker, defender, damageBonus); + base.OnHit(attacker, defender, damageBonus, searingProcEligible); if (!Core.AOS && Core.UOR && Layer == Layer.TwoHanded && attacker.Skills.Anatomy.Value / 400.0 >= Utility.RandomDouble() && diff --git a/Projects/UOContent/Items/Weapons/Staves/BaseStaff.cs b/Projects/UOContent/Items/Weapons/Staves/BaseStaff.cs index 57ee0a7a1..a5285ad07 100644 --- a/Projects/UOContent/Items/Weapons/Staves/BaseStaff.cs +++ b/Projects/UOContent/Items/Weapons/Staves/BaseStaff.cs @@ -16,9 +16,9 @@ namespace Server.Items public override WeaponType DefType => WeaponType.Staff; public override WeaponAnimation DefAnimation => WeaponAnimation.Bash2H; - public override void OnHit(Mobile attacker, Mobile defender, double damageBonus = 1) + public override void OnHit(Mobile attacker, Mobile defender, double damageBonus = 1, bool searingProcEligible = false) { - base.OnHit(attacker, defender, damageBonus); + base.OnHit(attacker, defender, damageBonus, searingProcEligible); defender.Stam -= Utility.Random(3, 3); // 3-5 points of stamina loss } diff --git a/Projects/UOContent/Items/Weapons/Swords/BaseSword.cs b/Projects/UOContent/Items/Weapons/Swords/BaseSword.cs index 270c39a22..5a0fb4b48 100644 --- a/Projects/UOContent/Items/Weapons/Swords/BaseSword.cs +++ b/Projects/UOContent/Items/Weapons/Swords/BaseSword.cs @@ -21,9 +21,9 @@ namespace Server.Items from.Target = new BladedItemTarget(this); } - public override void OnHit(Mobile attacker, Mobile defender, double damageBonus = 1) + public override void OnHit(Mobile attacker, Mobile defender, double damageBonus = 1, bool searingProcEligible = false) { - base.OnHit(attacker, defender, damageBonus); + base.OnHit(attacker, defender, damageBonus, searingProcEligible); if (!Core.AOS && Poison != null && PoisonCharges > 0) { diff --git a/Projects/UOContent/Items/Weapons/Throwing/BaseThrown.cs b/Projects/UOContent/Items/Weapons/Throwing/BaseThrown.cs index 3905aea14..33bbdcbf1 100644 --- a/Projects/UOContent/Items/Weapons/Throwing/BaseThrown.cs +++ b/Projects/UOContent/Items/Weapons/Throwing/BaseThrown.cs @@ -110,7 +110,7 @@ public abstract partial class BaseThrown : BaseRanged return true; } - public override void OnHit(Mobile attacker, Mobile defender, double damageBonus = 1) + public override void OnHit(Mobile attacker, Mobile defender, double damageBonus = 1, bool searingProcEligible = false) { if (WeaponAbility.GetCurrentAbility(attacker) is not MysticArc) { @@ -118,7 +118,7 @@ public abstract partial class BaseThrown : BaseRanged Timer.StartTimer(TimeSpan.FromSeconds(0.3), () => Return(attacker, defender, location)); } - base.OnHit(attacker, defender, damageBonus); + base.OnHit(attacker, defender, damageBonus, searingProcEligible); } public override void OnMiss(Mobile attacker, Mobile defender) diff --git a/Projects/UOContent/Migrations/Server.Items.BaseWeapon.v15.json b/Projects/UOContent/Migrations/Server.Items.BaseWeapon.v15.json new file mode 100644 index 000000000..7d10671dd --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Items.BaseWeapon.v15.json @@ -0,0 +1,295 @@ +{ + "version": 15, + "type": "Server.Items.BaseWeapon", + "properties": [ + { + "name": "DamageLevel", + "type": "Server.Items.WeaponDamageLevel", + "usesSaveFlag": true, + "rule": "EnumMigrationRule" + }, + { + "name": "AccuracyLevel", + "type": "Server.Items.WeaponAccuracyLevel", + "usesSaveFlag": true, + "rule": "EnumMigrationRule" + }, + { + "name": "DurabilityLevel", + "type": "Server.Items.WeaponDurabilityLevel", + "usesSaveFlag": true, + "rule": "EnumMigrationRule" + }, + { + "name": "Quality", + "type": "Server.Items.WeaponQuality", + "usesSaveFlag": true, + "rule": "EnumMigrationRule" + }, + { + "name": "HitPoints", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "MaxHitPoints", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Slayer", + "type": "Server.Items.SlayerName", + "usesSaveFlag": true, + "rule": "EnumMigrationRule" + }, + { + "name": "Poison", + "type": "Server.Poison", + "usesSaveFlag": true, + "rule": "PrimitiveUOTypeMigrationRule", + "ruleArguments": [ + "Poison" + ] + }, + { + "name": "PoisonCharges", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Crafter", + "type": "string", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Identified", + "type": "bool", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "StrRequirement", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "DexRequirement", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "IntRequirement", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "MinDamage", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "MaxDamage", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "HitSound", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "MissSound", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Speed", + "type": "float", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "MaxRange", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Skill", + "type": "Server.SkillName", + "usesSaveFlag": true, + "rule": "EnumMigrationRule" + }, + { + "name": "Type", + "type": "Server.Items.WeaponType", + "usesSaveFlag": true, + "rule": "EnumMigrationRule" + }, + { + "name": "Animation", + "type": "Server.Items.WeaponAnimation", + "usesSaveFlag": true, + "rule": "EnumMigrationRule" + }, + { + "name": "Resource", + "type": "Server.Items.CraftResource", + "usesSaveFlag": true, + "rule": "EnumMigrationRule" + }, + { + "name": "Attributes", + "type": "Server.AosAttributes", + "usesSaveFlag": true, + "rule": "RawSerializableMigrationRule", + "ruleArguments": [ + "DeserializationRequiresParent" + ] + }, + { + "name": "WeaponAttributes", + "type": "Server.AosWeaponAttributes", + "usesSaveFlag": true, + "rule": "RawSerializableMigrationRule", + "ruleArguments": [ + "DeserializationRequiresParent" + ] + }, + { + "name": "PlayerConstructed", + "type": "bool", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "SkillBonuses", + "type": "Server.AosSkillBonuses", + "usesSaveFlag": true, + "rule": "RawSerializableMigrationRule", + "ruleArguments": [ + "DeserializationRequiresParent" + ] + }, + { + "name": "Slayer2", + "type": "Server.Items.SlayerName", + "usesSaveFlag": true, + "rule": "EnumMigrationRule" + }, + { + "name": "AosElementDamages", + "type": "Server.AosElementAttributes", + "usesSaveFlag": true, + "rule": "RawSerializableMigrationRule", + "ruleArguments": [ + "DeserializationRequiresParent" + ] + }, + { + "name": "EngravedText", + "type": "string", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "ExtendedWeaponAttributes", + "type": "Server.ExtendedWeaponAttributes", + "rule": "RawSerializableMigrationRule", + "ruleArguments": [ + "DeserializationRequiresParent" + ] + }, + { + "name": "NegativeAttributes", + "type": "Server.NegativeAttributes", + "rule": "RawSerializableMigrationRule", + "ruleArguments": [ + "DeserializationRequiresParent" + ] + }, + { + "name": "UnwieldyOriginalWeight", + "type": "double", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "AbsorptionAttributes", + "type": "Server.AbsorptionAttributes", + "rule": "RawSerializableMigrationRule", + "ruleArguments": [ + "DeserializationRequiresParent" + ] + }, + { + "name": "SearingIgnited", + "type": "bool", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Misc/AOS.cs b/Projects/UOContent/Misc/AOS.cs index 81dec8c77..2ba96672f 100644 --- a/Projects/UOContent/Misc/AOS.cs +++ b/Projects/UOContent/Misc/AOS.cs @@ -1275,7 +1275,8 @@ namespace Server SplinteringWeapon = 0x00000020, Focus = 0x00000040, BoneBreaker = 0x00000080, - AssassinHoned = 0x00000100 + AssassinHoned = 0x00000100, + Searing = 0x00000200 } public sealed class ExtendedWeaponAttributes : BaseAttributes @@ -1377,6 +1378,21 @@ namespace Server set => this[ExtendedWeaponAttribute.AssassinHoned] = value; } + [CommandProperty(AccessLevel.GameMaster)] + public int Searing + { + get => this[ExtendedWeaponAttribute.Searing]; + set + { + this[ExtendedWeaponAttribute.Searing] = value; + + if (value == 0 && Owner is BaseWeapon weapon) + { + weapon.SearingIgnited = false; + } + } + } + public void GetProperties(IPropertyList list) { if (Core.HS && Bane != 0) @@ -1389,6 +1405,11 @@ namespace Server list.Add(1150018); // Focus } + if (Core.HS && Searing != 0) + { + list.Add(1151172); // Searing + } + if (Core.SA && BattleLust != 0) { list.Add(1113710); // Battle Lust diff --git a/Projects/UOContent/Misc/RegenRates.cs b/Projects/UOContent/Misc/RegenRates.cs index 02ffc1e46..570cc3d30 100644 --- a/Projects/UOContent/Misc/RegenRates.cs +++ b/Projects/UOContent/Misc/RegenRates.cs @@ -43,7 +43,7 @@ namespace Server.Misc private static TimeSpan Mobile_HitsRegenRate(Mobile from) { - var points = AosAttributes.GetValue(from, AosAttribute.RegenHits); + var points = AosAttributes.GetValue(from, AosAttribute.RegenHits) + Searing.GetHitRegenPenalty(from); var bc = from as BaseCreature;