feat(items): implement searing weapon property

This commit is contained in:
Crome696 2026-07-12 10:50:30 +02:00
parent ed19b8f4fd
commit 4556adcd10
16 changed files with 559 additions and 24 deletions

View file

@ -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
{

View file

@ -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 &&

View file

@ -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;
}
}

View file

@ -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<ContextMenuEntry> 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);
}
}

View file

@ -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)
{

View file

@ -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
}

View file

@ -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 &&

View file

@ -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)

View file

@ -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<Mobile, Context> _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);
}

View file

@ -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() &&

View file

@ -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
}

View file

@ -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)
{

View file

@ -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)

View file

@ -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": [
""
]
}
]
}

View file

@ -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

View file

@ -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;